それが僕には楽しかったんです。

僕と MySQL と時々 MariaDB

最速のEchoサーバーを目指して、LinuxKernelモジュールを作っていく part3

はじめに

前回まで
rabbitfoot141.hatenablog.com
rabbitfoot141.hatenablog.com

この二回でカーネルモジュールで標準出力や、カーネルスレッドを動かすところまでやった。
今回はこれをTCPサーバー化するのを目標にやっていく。

ネットワーク周り

さくっと実装したいが、カーネルモジュールでのネットワーク周りがわからないので
そこあたりから色々調べていく。

ソケット周り

これが使えないとネットワーク通信ができないのでこれがどういう仕様で実装されているか調べてみる。


見つかった。

http://elixir.free-electrons.com/linux/v3.4/source/include/linux/net.h#L138

これは

#include <linux/net.h>

というヘッダーにあることがわかった。

これの138行目にsocketという名前の構造体があり。
こいつがBSDのソケットとして使えるようなので使うことにする。

/**
 *  struct socket - general BSD socket
 *  @state: socket state (%SS_CONNECTED, etc)
 *  @type: socket type (%SOCK_STREAM, etc)
 *  @flags: socket flags (%SOCK_ASYNC_NOSPACE, etc)
 *  @ops: protocol specific socket operations
 *  @file: File back pointer for gc
 *  @sk: internal networking protocol agnostic socket representation
 *  @wq: wait queue for several uses
 */
struct socket {
	socket_state		state;

	kmemcheck_bitfield_begin(type);
	short			type;
	kmemcheck_bitfield_end(type);

	unsigned long		flags;

	struct socket_wq __rcu	*wq;

	struct file		*file;
	struct sock		*sk;
	const struct proto_ops	*ops;
};

色々なコードや、文献を漁っているとこの中でも次に重要になるのが

const struct proto_ops	*ops;

という構造体。
この構造体の実装は次のようになっている。

struct proto_ops {
	int		family;
	struct module	*owner;
	int		(*release)   (struct socket *sock);
	int		(*bind)	     (struct socket *sock,
				      struct sockaddr *myaddr,
				      int sockaddr_len);
	int		(*connect)   (struct socket *sock,
				      struct sockaddr *vaddr,
				      int sockaddr_len, int flags);
	int		(*socketpair)(struct socket *sock1,
				      struct socket *sock2);
	int		(*accept)    (struct socket *sock,
				      struct socket *newsock, int flags);
	int		(*getname)   (struct socket *sock,
				      struct sockaddr *addr,
				      int *sockaddr_len, int peer);
	unsigned int	(*poll)	     (struct file *file, struct socket *sock,
				      struct poll_table_struct *wait);
	int		(*ioctl)     (struct socket *sock, unsigned int cmd,
				      unsigned long arg);
#ifdef CONFIG_COMPAT
	int	 	(*compat_ioctl) (struct socket *sock, unsigned int cmd,
				      unsigned long arg);
#endif
	int		(*listen)    (struct socket *sock, int len);
	int		(*shutdown)  (struct socket *sock, int flags);
	int		(*setsockopt)(struct socket *sock, int level,
				      int optname, char __user *optval, unsigned int optlen);
	int		(*getsockopt)(struct socket *sock, int level,
				      int optname, char __user *optval, int __user *optlen);
#ifdef CONFIG_COMPAT
	int		(*compat_setsockopt)(struct socket *sock, int level,
				      int optname, char __user *optval, unsigned int optlen);
	int		(*compat_getsockopt)(struct socket *sock, int level,
				      int optname, char __user *optval, int __user *optlen);
#endif
	int		(*sendmsg)   (struct kiocb *iocb, struct socket *sock,
				      struct msghdr *m, size_t total_len);
	int		(*recvmsg)   (struct kiocb *iocb, struct socket *sock,
				      struct msghdr *m, size_t total_len,
				      int flags);
	int		(*mmap)	     (struct file *file, struct socket *sock,
				      struct vm_area_struct * vma);
	ssize_t		(*sendpage)  (struct socket *sock, struct page *page,
				      int offset, size_t size, int flags);
	ssize_t 	(*splice_read)(struct socket *sock,  loff_t *ppos,
				       struct pipe_inode_info *pipe, size_t len, unsigned int flags);
	void		(*set_peek_off)(struct sock *sk, int val);
};

bind,listen,acceptなどなど、どこかで見たことのあるような単語が並んでいる。
実際にtcpサーバを構築するときは、これらの関数に必要な情報を渡して呼び出す必要がある。


なんやかんや、C言語でネットワークプログラミングをする時と同じ様なあれこれはあるので
それと似たような感じでコードを書けそう。


この後で実装するコードは、カーネルスレッドを多用するので
kernel_lock関数を使った方がいいとかはまた別途考える必要がある。

文字列周り

文字列周りの扱いはどうしようかと調べていたら、linux/socket.hにちょうどいい構造体があった。

/*
 *	As we do 4.4BSD message passing we use a 4.4BSD message passing
 *	system, not 4.3. Thus msg_accrights(len) are now missing. They
 *	belong in an obscure libc emulation or the bin.
 */
 
struct msghdr {
	void	*	msg_name;	/* Socket name			*/
	int		msg_namelen;	/* Length of name		*/
	struct iovec *	msg_iov;	/* Data blocks			*/
	__kernel_size_t	msg_iovlen;	/* Number of blocks		*/
	void 	*	msg_control;	/* Per protocol magic (eg BSD file descriptor passing) */
	__kernel_size_t	msg_controllen;	/* Length of cmsg list */
	unsigned	msg_flags;
};

kernelでネットワークのレスポンスを扱うにはなにやらこれがいいらしい。

実装の前に

全体の流れを整理する。

  • 必要なsocket類を構造体などで宣言する。
  • listenをするスレッドを作る
  • 上記のスレッド内でsocketを作成、bind、listenなどを行う
  • それが完了したら更にacceptを行うスレッドを作る
  • 文字列が送信されてきたら、それを送り返す

実装する

実装してみた、結果はこちら。

github.com


ヘッダーファイルに直書きしているのは後で直す。

ただし、実行してみるとaccept errorと表示されている。

[  689.884212] accept_thread the csock is :474605440,474606080
[  689.884216] accept start
[  689.884220] accept error

なるはやで解決しないといけないものは
accept errorと表示されるので、上記opsを見なおしてなぜそこでコケたのか調べる。
あとrmmodを実行した際に

rmmod: ERROR: ../libkmod/libkmod-module.c:793 kmod_module_remove_module() could not remove 'fastecho': Device or resource busy
rmmod: ERROR: could not remove module fastecho.ko: Device or resource busy

といったエラーが出てinsmodするために再起動しないといけない。
それはだるいのでどうにかしたい。

以上、学生アドベントカレンダー25日目の記事でした。