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のソースコードには処理の流れやデータ構造の詳しい説明がコメントの形で記載されており理解の助けになります。