Linuxカーネルもくもく会#21に参加してきました

Linuxカーネルもくもく会 #21に参加してきました。iij/ipgenというパケットジェネレータで使用されている、高速パケットI/Oフレームワークの"netmap"に興味が湧きつつあり、少しずつソースコードを読み進めています。 今回と前回のもくもく会でnetmapのLinux実装部分のコードを読んでみたので、把握できた内容を簡単にまとめてみました。

netmapのコードはGitHubから取得できます。現時点ではLinux,FreeBSD,Windows環境向けにnetmapが実装されています。ちなみにnetmapは当初FreeBSDで開発されていたこともあり、netmapのコードはFreeBSDカーネルにマージされています。

Linuxにおけるopen("/dev/netmap",...)処理

さっそくLinuxにおけるnetmapの実装を見てみます。netmapのソースコードには内部構造の詳しい解説コメントが記載されており、実装の理解の助けになります。例えばnetmap.cには以下のコメントがあり、Linuxではlinux_netmap_open()から見て行くと良さそうです。

sys/dev/netmap/netmap.c
/* --- internals ----
 *
 * Roadmap to the code that implements the above.
 *
 * > 1. a process/thread issues one or more open() on /dev/netmap, to create
 * >    select()able file descriptor on which events are reported.
 *
 *      Internally, we allocate a netmap_priv_d structure, that will be
 *      initialized on ioctl(NIOCREGIF). There is one netmap_priv_d
 *      structure for each open().
 *
 *      os-specific:
 *          FreeBSD: see netmap_open() (netmap_freebsd.c)
 *          linux:   see linux_netmap_open() (netmap_linux.c) 

処理の流れを把握するため、linux_netmap_open()の関数呼び出し階層を示します。

LINUX/netmap_linux.c:
1057 static struct file_operations netmap_fops = {
1058     .owner = THIS_MODULE,
1059     .open = linux_netmap_open,
1060     .mmap = linux_netmap_mmap,
1061     LIN_IOCTL_NAME = linux_netmap_ioctl,
1062     .poll = linux_netmap_poll,
1063     .release = linux_netmap_release,
1064 };

関数の呼び出し階層は以下のようになっています。

-> LINUX/netmap_linux.c:linux_netmap_open()
  -> sys/dev/netmap/netmap.c:netmap_priv_new()
    -> LINUX/netmap_linux.c:nm_os_get_module()

linux_netmap_open()は帰り値として常に0を返すようです。変数errorは握りつぶされている気がする...。

1036 static int
1037 linux_netmap_open(struct inode *inode, struct file *file)
1038 {
1039         struct netmap_priv_d *priv;
1040         int error;
1041         (void)inode;    /* UNUSED */
1042
1043         NMG_LOCK();
1044         priv = netmap_priv_new();
1045         if (priv == NULL) {
1046                 error = -ENOMEM;
1047                 goto out;
1048         }
1049         file->private_data = priv;
1050 out:
1051         NMG_UNLOCK();
1052
1053         return (0);
1054 }

netmap_priv_new()はnetmap用の構造体(struct netmap_priv_d)用のカーネルメモリを確保し、linux_netmap_open()に返します。 先ほどの変数errorの値はlinux_netmap_open()から参照できなさそうですが、file->private_data == NULLの判定をエラーが発生しているかどうかは判定できそうです。

 959 struct netmap_priv_d*
 960 netmap_priv_new(void)
 961 {
 962         struct netmap_priv_d *priv;
 963
 964         priv = malloc(sizeof(struct netmap_priv_d), M_DEVBUF,
 965                               M_NOWAIT | M_ZERO);
 966         if (priv == NULL)
 967                 return NULL;
 968         priv->np_refs = 1;
 969         nm_os_get_module();
 970         return priv;
 971 }

nm_os_get_module()は自身のモジュールを取得するだけの処理です。

LINUX/netmap_linux.c:
  63 void
  64 nm_os_get_module(void)
  65 {
  66         __module_get(THIS_MODULE);
  67 }

処理がlinux_netmap_open()に戻ってきたのち、確保したstruct netmap_priv_dへの参照をfile->private_dataに設定します。

1036 static int
1037 linux_netmap_open(struct inode *inode, struct file *file)
1038 {
1039         struct netmap_priv_d *priv;
...
1049         file->private_data = priv;
1050 out:
1051         NMG_UNLOCK();
1052
1053         return (0);
1054 }

linux_netmap_open()の変数errorについて

linux_netmap_open()内の変数errorは握りつぶされている(呼び出し元に変数errorの値が渡らない)ように見える件、例えばLinuxのwrite()に対応する関数ではエラー値を負値にしてreturnしています。これに倣うとlinux_netmap_open()でもreturn (-ENOMEM);とするのが良いのかもしれません。

linux-4.5/fs/read_write.c:
 617 SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
 618                 size_t, count)
 619 {
 620         struct fd f = fdget_pos(fd);
 621         ssize_t ret = -EBADF;
 622
 623         if (f.file) {
 624                 loff_t pos = file_pos_read(f.file);
 625                 ret = vfs_write(f.file, buf, count, &pos);
 626                 if (ret >= 0)
 627                         file_pos_write(f.file, pos);
 628                 fdput_pos(f);
 629         }
 630
 631         return ret;
 632 }

Linuxカーネルmalloc()に対するグルーコード

netmap_priv_new()でmalloc()を呼んでいます。このmalloc()はLinuxでもBSD系と同じ引数で呼び出せるよう、ラッパーコードを挟んでいます。netmapはFreeBSDで実装されたこともあり、グルーコードの形で既存の処理を変更することなく別プラットフォームへの移植を行っている、ということですね。

sys/dev/netmap/netmap.c:
 959 struct netmap_priv_d*
 960 netmap_priv_new(void)
 961 {
 ...
 964         priv = malloc(sizeof(struct netmap_priv_d), M_DEVBUF,
 965                               M_NOWAIT | M_ZERO);
 966         if (priv == NULL)
 967                 return NULL;
 ...
 971 }

*_glue.hで各プラットフォーム向けのmalloc()ラッパーコードが実装されています。

$ global malloc
LINUX/bsd_glue.h
WINDOWS/win_glue.h

Linux/bsd_glue.hではkmalloc()へのラッパーになっています。

LINUX/bsd_glue.h:
304 /*
305  * in the malloc/free code we ignore the type
306  */
307 /* use volatile to fix a probable compiler error on 2.6.25 */
308 #define malloc(_size, type, flags)                      \
309         ({ volatile int _v = _size; kmalloc(_v, GFP_ATOMIC | __GFP_ZERO); })

WINDOWS/win_glue.hではwin_kernel_malloc()を挟んでExAllocatePoolWithTag APIを読んでいます。

WINDOWS/win_glue.h:
315 /*-------------------------------------------
316  *      KERNEL MEMORY ALLOCATION and management
317  */
...
321 #define malloc(size, _ty, flags)                win_kernel_malloc(size, _ty, flags)
...
325 /*
326  * we default to always allocating and zeroing
327  */
328 static void *
329 win_kernel_malloc(size_t size, int32_t ty, int flags)
330 {
331         void* mem = ExAllocatePoolWithTag(NonPagedPool, size, ty);
332
333         (void)flags;
334
335         if (mem != NULL) { // && flags & M_ZERO XXX todo
336                 RtlZeroMemory(mem, size);
337         }
338         return mem;
339 }

まとめ

Linuxにおけるnetmapのopen()に関する処理を追いかけてみました。linux_netmap_open()は比較的シンプルな実装になっています。netmapのソースコードには処理の流れやデータ構造の詳しい説明がコメントの形で記載されており理解の助けになります。