Edited at

Linuxでユーザー空間で動作するプログラムとハードウェアがメモリを共有するためのデバイスドライバ(NumPy対応)

More than 1 year has passed since last update.


はじめに

前回投稿した「Linuxでユーザー空間で動作するプログラムとハードウェアがメモリを共有するためのデバイスドライバで紹介したudmabufを NumPy(Pythonの数値計算ライブラリ)で使えるようにしました。具体的には、udmabuf でカーネル内に確保したバッファ領域を NumPy の memmap でマッピングして、ndarray と同じような操作が出来るようにします。この記事では、その方法について説明します。


udmabuf の更新

残念ながら udmabuf のバージョンが version 0.5.0(2016/4/24) 以前では、次のようなエラーが出ます。

shell# python

Python 2.7.9 (default, Aug 13 2016, 17:56:53)
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy as np
>>> m = np.memmap('/dev/udmabuf0', dtype=np.uint8, mode='r+', shape=(100))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.7/dist-packages/numpy/core/memmap.py", line 217, in __new__
fid.seek(0, 2)
IOError: [Errno 29] Illegal seek

これは udmabuf が lseek に対応していなかったのが原因です。どうやら NumPy の memmap は seek(0,2) を使ってファイルのサイズを得ているようですが、私が udmabuf に lseek() を実装するのを忘れていました。

udmabuf version 0.6.0(2017/1/29) で lseek に対応しましたので、NumPy で udmabuf を使う場合は、umdabuf version 0.6.0 以降を使ってください。


使用例


udmabuf のインストール

まずは udmabuf をインストールします。具体的な方法は、「Linuxでユーザー空間で動作するプログラムとハードウェアがメモリを共有するためのデバイスドライバ を参照してください。

次の例では udmabuf.ko を直接 insmod でインストールしています。その際、udmabuf0 として 8MByte のバッファを確保しています。

shell# insmod udmabuf.ko udmabuf0=8388608

[34654.590294] alloc_contig_range: [1f100, 1f900) PFNs busy
[34654.596154] alloc_contig_range: [1f200, 1fa00) PFNs busy
[34654.622746] udmabuf udmabuf0: driver installed
[34654.627153] udmabuf udmabuf0: major number = 237
[34654.631889] udmabuf udmabuf0: minor number = 0
[34654.636685] udmabuf udmabuf0: phys address = 0x1f300000
[34654.642002] udmabuf udmabuf0: buffer size = 8388608


udmabuf_test.py

次に python+NumPy による簡単なテストをするスクリプトを示します。


udmabuf_test.py

import numpy as np

import time

class Udmabuf:
"""A simple udmabuf class"""

def __init__(self, name):
self.name = name
self.device_name = '/dev/%s' % self.name
self.class_path = '/sys/class/udmabuf/%s' % self.name
for line in open(self.class_path + '/size'):
self.buf_size = int(line)
break

def memmap(self, dtype, shape):
self.item_size = np.dtype(dtype).itemsize
self.mem_map = np.memmap(self.device_name, dtype=dtype, mode='r+', shape=shape)

def test_1(a):
for i in range (0,9):
a *= 0
a += 0x31

if __name__ == '__main__':
udmabuf = Udmabuf('udmabuf0')
test_dtype = np.uint8
test_size = int(udmabuf.buf_size/(np.dtype(test_dtype).itemsize))
udmabuf.memmap(dtype=test_dtype, shape=(test_size))
comparison = np.zeros(test_size, dtype=test_dtype)
print ("test_size : %d" % test_size)
start = time.time()
test_1(udmabuf.mem_map)
elapsed_time = time.time() - start
print ("udmabuf0 : elapsed_time:{0}".format(elapsed_time) + "[sec]")
start = time.time()
test_1(comparison)
elapsed_time = time.time() - start
print ("comparison : elapsed_time:{0}".format(elapsed_time) + "[sec]")
if np.array_equal(udmabuf.mem_map, comparison):
print ("udmabuf0 == comparison : OK")
else:
print ("udmabuf0 != comparison : NG")


上記スクリプトでは、udmabuf でカーネル内に確保したバッファ領域を numpy.memmap で python から使えるようにしています。

numpy.memmap で生成されたオブジェクトは numpy.ndarray と同様の操作ができるようになります。

上記スクリプトでは a *=0 と a += 0x31 を10回繰り返して、実行時間を測定しています。


実行結果

前節のスクリプトを実行すると次のような結果になりました。

shell# python udmabuf_test.py

test_size : 8388608
udmabuf0 : elapsed_time:1.53304982185[sec]
comparison : elapsed_time:1.536673069[sec]
udmabuf0 == comparison : OK

udmabuf0(カーネル内に確保したバッファ領域)に対する操作と、ndarray で同じ操作を行った場合(comparison)の実行時間はほぼ同じでした。すなわち udmabuf0 も CPU キャッシュが有効に働いていると思われます。

このスクリプトを実行した後で、udmabuf0 の内容を確認しました。

shell# dd if=/dev/udmabuf0 of=udmabuf0.bin bs=8388608

1+0 records in
1+0 records out
8388608 bytes (8.4 MB) copied, 0.151531 s, 55.4 MB/s
shell#
shell# od -t x1 udmabuf0.bin
0000000 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31
*
40000000

スクリプト実行後も、実行した結果がバッファに残っていることが確認できました。

念のため、NumPy でも読めることを確認しましょう。

shell# python

Python 2.7.9 (default, Aug 13 2016, 17:56:53)
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy as np
>>> a = np.memmap('/dev/udmabuf0', dtype=np.uint8, mode='r+', shape=(8388608))
>>> a
memmap([49, 49, 49, ..., 49, 49, 49], dtype=uint8)
>>> a.itemsize
1
>>> a.size
8388608
>>>


まとめ

udmabuf を使ってカーネル内に確保したバッファ領域を python の NumPy で操作できるようになりました。これにより C で書かれたコードやライブラリを使うこと無く、python だけで 直接 FPGA の PL(Programmable Logic)部を扱えるようになります。

FPGA の PL(Programmable Logic)部 が NumPy 形式に対応することが出来れば、今よりももう少し簡単に CPU(Python+NumPy) と FPGA アクセラレータとが連携できるようになるでしょう。