socket()システムコールがソケットを確保するまで

Linuxのネットワークインタフェースに採用されている、BSDソケットですが、Where is the socket()?を参考に、socket()システムコールの深追いをしました。細かいところでも間違っていたら突っ込んでください。カーネルのバージョンは、2.6.20-16-genericです。
socktest.c

#include <sys/socket.h>

int main(int argc, char** argv){
	int res = socket(AF_INET, SOCK_STREAM, 0);
}

単純にソケットを作成するだけのプログラムです。

 $ gcc socktest.c
 $ nm a.out
・・・
08049588 d p.5756
         U socket@@GLIBC_2.0
 $

socket()は、glibc2.0のライブラリ関数であることがわかります。Linuxカーネル2.6解読室によれば、socket()システムコールの実体は、socketcall()だそうです。

int socketcall(int call, unsigned long *args);

第1引数callにソケット関数の種類、argsには大元の引数が渡されます。
しかし、これだけでは何をしているかわかりません。そこで、gdbを使って逆アセンブルを行うと、もうひと潜りしてシステムコール呼び出しの中身が見えてきます。

 $ gcc -static -g socktest.c
 $ gdb a.out
(gdb) disassemble socket
Dump of assembler code for function socket:
0x0804ea50 <socket+0>:  mov    %ebx,%edx
0x0804ea52 <socket+2>:  mov    $0x66,%eax
0x0804ea57 <socket+7>:  mov    $0x1,%ebx
0x0804ea5c <socket+12>: lea    0x4(%esp),%ecx
0x0804ea60 <socket+16>: int    $0x80
0x0804ea62 <socket+18>: mov    %edx,%ebx
0x0804ea64 <socket+20>: cmp    $0xffffff83,%eax
0x0804ea67 <socket+23>: jae    0x804f9a0 <__syscall_error>
0x0804ea6d <socket+29>: ret    
End of assembler dump.
(gdb) 

システムコールの呼び出しは、「int 0x80」で行われます。その際、汎用レジスタ%eax、%ebx、%ecx、%edxの値が引数として渡されます。

ひとまず進めます。

ソフトウェア割り込みが CPU に発行されると CPU はあらかじめ設定された割り込みハンドラを実行します。x86 Linux では 0x80 に設定された割り込みベクタがシステムコール起動処理に対応しています。割り込みを契機に CPU は特権モードに移ってスタックがカーネルスタックに切り替わり、eax レジスタからシステムコール番号を取得して対応するシステムコールを起動します。
Linux カーネルのコンテキストスイッチ処理を読み解く - naoyaのはてなダイアリー

というわけで、見てみます。
arch/i386/kernel/entry.S

ENTRY(system_call)
・・・
syscall_call:
	call *sys_call_table(,%eax,4)
	movl %eax,PT_EAX(%esp)		# store the return value
・・・

で、sys_call_tableが%eaxを引数にして呼び出されて、
arch/i386/kernel/syscall_table.S

ENTRY(sys_call_table)
・・・
・・・
	.long sys_fstatfs	/* 100 */
	.long sys_ioperm
	.long sys_socketcall	/* 102 */
	.long sys_syslog
・・・

とここで、102に対応した関数はsys_socketcallだとわかます。この対応は、http://www.nk.rim.or.jp/~jun/lxasm/syscalltb.htmlに詳しいです。
Linuxのソケット処理は、sys_socketcall関数で一括処理されています。
先ほど%ebxに代入した0x1ですが、include/linux/net.hで、SYS_SOCKETとマクロ定義されていることがわかります。

・・・
#define SYS_SOCKET	1		/* sys_socket(2)		*/
#define SYS_BIND	2		/* sys_bind(2)			*/
・・・

ここまでで、システムコールが呼び出され、システムコールの種類、がわかりました。以後、カーネル内部のソケット処理に突入します。ドキドキ
カーネル内部では、ソケットシステムコールは、sys_socketcall()で実装されています。SYS_SOCKETが第1引数として渡されます。
net/socket.c

・・・
asmlinkage long sys_socketcall(int call, unsigned long __user *args)
{
	unsigned long a[6];
	unsigned long a0, a1;
	int err;

	if (call < 1 || call > SYS_RECVMSG)
		return -EINVAL;

	/* copy_from_user should be SMP safe. */
	if (copy_from_user(a, args, nargs[call]))
		return -EFAULT;

	err = audit_socketcall(nargs[call] / sizeof(unsigned long), a);
	if (err)
		return err;

	a0 = a[0];
	a1 = a[1];

	switch (call) {
	case SYS_SOCKET:
		err = sys_socket(a0, a1, a[2]);
		break;
	case SYS_BIND:
		err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]);
		break;
	case SYS_CONNECT:
		err = sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
		break;
・・・

引数callが、switch文で評価されます。ここでは新規ソケットを作成するために、sys_socket()が呼び出されます。argsには、socket()の引数が入っているはずなので、AF_INET、SOCK_STREAMと0です。ローカル変数に代入されて(なぜか先頭のふたつだけ)、sys_socket()に渡されます。sys_socket()は同じくsocket.cに、

asmlinkage long sys_socket(int family, int type, int protocol)
{
	int retval;
	struct socket *sock;

	retval = sock_create(family, type, protocol, &sock);
	if (retval < 0)
		goto out;

	retval = sock_map_fd(sock);
	if (retval < 0)
		goto out_release;

out:
	/* It may be already another descriptor 8) Not kernel problem. */
	return retval;

out_release:
	sock_release(sock);
	return retval;
}

とあります。引数がわかりやすくなりました。a0(AF_INET)、a1(SOCK_STREAM)、a[2](0)はそれぞれ、アドレスファミリ、ソケットタイプ、プロトコルに対応ですね。ここらへんはソケットプログラミングの話です。
ここで重要なのはsock_create()。深追いしていきます。なお、各関数の順序は入れ替えてあります。
net/socket.c

・・・
int sock_create(int family, int type, int protocol, struct socket **res)
{
	return __sock_create(family, type, protocol, res, 0);
}
・・・
static int __sock_create(int family, int type, int protocol,
			 struct socket **res, int kern)
{
・・・
	/*
	 *	Allocate the socket and allow the family to set things up. if
	 *	the protocol is 0, the family is instructed to select an appropriate
	 *	default.
	 */
	sock = sock_alloc();
	if (!sock) {
		if (net_ratelimit())
			printk(KERN_WARNING "socket: no more sockets\n");
		return -ENFILE;	/* Not exactly a match, but its the
				   closest posix thing */
	}

	sock->type = type;
・・・
}
・・・
static struct socket *sock_alloc(void)
{
	struct inode *inode;
	struct socket *sock;

	inode = new_inode(sock_mnt->mnt_sb);
	if (!inode)
		return NULL;

	sock = SOCKET_I(inode);

	inode->i_mode = S_IFSOCK | S_IRWXUGO;
	inode->i_uid = current->fsuid;
	inode->i_gid = current->fsgid;

	get_cpu_var(sockets_in_use)++;
	put_cpu_var(sockets_in_use);
	return sock;
}
・・・

new_inode()関数でinodeを確保し、そのinodeポインタを元に、ソケットを確保します。
include/net/sock.h

static inline struct socket *SOCKET_I(struct inode *inode)
{
	return &container_of(inode, struct socket_alloc, vfs_inode)->socket;
}

include/linux/kernel.h

/**
 * container_of - cast a member of a structure out to the containing structure
 * @ptr:	the pointer to the member.
 * @type:	the type of the container struct this is embedded in.
 * @member:	the name of the member within the struct.
 *
 */
#define container_of(ptr, type, member) ({			\
        const typeof( ((type *)0)->member ) *__mptr = (ptr);	\
        (type *)( (char *)__mptr - offsetof(type,member) );})

という具合に、底に辿り着きました。この、container_of()マクロで、カーネルはソケットを確保します。
ソケットの実体は、socket構造体(及びsock構造体)で、include/linux/net.hで次のように定義されています。

/**
 *  struct socket - general BSD socket
 *  @state: socket state (%SS_CONNECTED, etc)
 *  @flags: socket flags (%SOCK_ASYNC_NOSPACE, etc)
 *  @ops: protocol specific socket operations
 *  @fasync_list: Asynchronous wake up list
 *  @file: File back pointer for gc
 *  @sk: internal networking protocol agnostic socket representation
 *  @wait: wait queue for several uses
 *  @type: socket type (%SOCK_STREAM, etc)
 */
struct socket {
	socket_state		state;
	unsigned long		flags;
	const struct proto_ops	*ops;
	struct fasync_struct	*fasync_list;
	struct file		*file;
	struct sock		*sk;
	wait_queue_head_t	wait;
	short			type;
};

そして、socket構造体は、socket_alloc構造体のメンバとして定義されています。

include/net/sock.h
struct socket_alloc {
	struct socket socket;
	struct inode vfs_inode;
};

socket構造体と共に、inode構造体もメンバとして定義されています。inode構造体は、先ほどnew_inode()関数で確保しました。このinodeを含んだsocket_alloc構造体の先頭アドレスがわかれば、socket構造体メンバへアクセスできます。ここで、container_ofマクロ関数の出番です。
このマクロについては、http://hira.main.jp/wiki/pukiwiki.php?container_of()%2Flinux2.6と、list_headの謎: #include <fujita.h>の説明がわかりやすいです。ここで実体があるのはinodeのみですが、構造体の型(struct socket_alloc)と、メンバ名(vfs_inode)を渡すことで、構造体自体の先頭アドレスが計算される仕組みです。つまり、inode構造体にsocket_alloc構造体を割り当てることで、socket構造体へのアクセスを実現する、ということでしょうか。
この理解でいくと、ここでsocket構造体の確保は完了です。つまり、システムコールレベルでいう、ソケットが作成されたということになります。
container_ofマクロは、こういった計算を行う汎用的なインタフェースとなっているため、カーネルの様々な箇所で使用されているようです。
以上、ユーザ空間におけるシステムコールから、カーネル空間でのソケット確保までを追ってみました。ソケットは、疑似ファイルシステムとして実装されているのですが、ファイルシステムの理解が乏しいため追えていません。inode自体の理解もまだまだ。続きが書けるように頑張ります。