FreeBSD kernel SOCKET I/F 探検 (3)

socket() システムコール (1)

必要とする知識など:
  • UNIX (含む Linux) 上での C 言語と SOCKET によるプログラミング経験および TCP/IP に関する知識。

以前から気になっていることがあるのですが、この連載の主題の「探検」。パソコンで漢字変換した際に、木へんの「検」とこざとへんの「険」が出てきます。で、ググってみたところ

  • 探検: 調査
  • 探険: 危険を伴う探検

といった解説があって、なんとなく納得しました。駅前探検倶楽部は、「探検」で良かったんですね。で、インターネットではなく製本された辞書を引きたかったのですが、手元に無い... ;_;

それでは本題に入ります。

前回は、kernel 内に踏み込む足掛かりとなるプログラムの提示と解説を行ないました。今回はいよいよ kernel のソースコードを覗いていきます。

どこから覗いていくか?ということですが、前回提示した足掛かりとなるプログラムから追い掛けていきます。server.c と client.c がありますが、server が動作していないと client は正しく動作できないので、まずは、server.c の処理から見てきます。

#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>

/**
 * Main
 */
int
main(int argc, char *argv[])
{
       int port;
       int sd;
       int result;
       struct sockaddr_in sa;
       struct sockaddr_in from;
       socklen_t addrlen;
       ssize_t rbytes;
       ssize_t sbytes;
       char buf[1024];

       /* Is not enough argument? */
       if (argc != 2) {
               fprintf(stderr, "Usage: server PortNo\n");
               exit(1);
       }
       port = atoi(argv[1]);

       /* Create a socket */
       sd = socket(PF_INET, SOCK_DGRAM, 0);
       if (sd < 0) {
               perror("socket");
               exit(1);
       }

       /* Bind the port number */
       bzero(&sa, sizeof (sa));
#ifdef __FreeBSD__
       sa.sin_len = sizeof (sa);
#endif /* __FreeBSD__ */
       sa.sin_family = AF_INET;
       sa.sin_port = htons(port);
       result = bind(sd, (struct sockaddr *)&sa, sizeof (sa));
       if (result < 0) {
               perror("bind");
               exit(1);
       }

       while (1) {
               /* Receive data */
               addrlen = sizeof (from);
               rbytes = recvfrom(sd, buf, sizeof (buf), 0, (struct sockaddr *)&from, &addrlen);
               if (rbytes < 0) {
                       perror("recvfrom");
                       continue;
               }
               printf("Received %dbytes\n", rbytes);

               /* Send data */
               sbytes = sendto(sd, buf, rbytes, 0, (struct sockaddr *)&from, addrlen);
               if (sbytes < 0) {
                       perror("sendto");
                       continue;
               }
               if (sbytes != rbytes) {
                       fprintf(stderr, "Length of the transmitted data does not matched: Tried to send %dbytes, Transmitted %dbytes.\n", rbytes, sbytes);
               }
       }

       /* Success */
       return 0;
}

main() 関数の先頭から見ていきますが、本連載の主旨は kernel のソースコードの探検なので、変数の宣言やif (argc != 2) などの様に、主旨とは直接関連しない項目に関しては省いていきます。主に注目していくのは、socket() などのシステムコールです。

ちょっと脱線1
今回「システムコール」 [1] という言葉を使用しましたが、一般に(UNIX の世界では?) カーネルの機能を呼び出す関数を、「システムコール」と呼んでいます。例えば、ファイルを開く際に使用する open() と fopen()という関数が用意されています。open() はカーネルの機能を呼び出すので、システムコールですが、fopen() 自身は直接カーネル機能を呼び出してはいないので、「ライブラリ関数」と呼ばれています。

server.c の main() 関数には、まず始めに socket() システムコールの呼び出しがあります。

socket(PF_INET, SOCK_DGRAM, 0);

socet() システムコールは、3つの引数を取ります。オンラインマニュアル SOCKET(2) [2] の SYNOPSIS には、以下のように記載されています。

int
socket(int domain, int type, int protocol);

第1引数の domain には、プロトコルファミリーを指定します。server.c では IPv4 を使用するので、PF_INET を指定します。

第2引数の type には、には通信種別を指定します。server.c では、UDP を使用するので、SOCK_DGRAM を指定します。

第3引数の protocol には、使用するプロトコルを指定しますが、通常はデフォルトのプロトコルを示す 0 を指定します。

ちょっと脱線2
たまに socket() システムコールを解説した WEB ページや、socket() システムコールを使用したプログラムで socket(AF_INET, ...) と書かれていることがあります。AF_INET はアドレスファミリー用の定義なので、socket() システムコールの第一引数であるプロトコルファミリーには、PF_* を指定するのが正解です。AF_* を指定するのは誤りといって良いでしょう。が、/usr/include/sys/socket.h(http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/socket.h?view=markup) では #define PF_INET AF_INET と定義されているので AF_INET を指定しても問題無く動作します。

ここで呼び出している socket() システムコール内部では、(i386 の場合) INT [3] 命令を実行しその結果 kernel 内の例外処理が呼び出され、その先でここで呼び出した socket() システムコールに該当する処理が呼び出されます。この例外処理などに関しては別の機会に解説を行ないます。今回は、システムコールを呼び出すと kernel 内の該当する処理が呼び出されるということを理解しておいてください。

socket() システムコールを呼び出すと、kernel 内の sys_socket() 関数が呼ばれます。

sys_socket() 関数は、/usr/src/sys/kern/uipc_syscalls.c (http://svnweb.freebsd.org/base/release/9.1.0/sys/kern/uipc_syscalls.c?view=markup) で以下の様に定義されています。

int
sys_socket(td, uap)
        struct thread *td;
        struct socket_args /* {
                int     domain;
                int     type;
                int     protocol;
        } */ *uap;

第一引数の struct thread *td は、スレッドに関する情報です。第2引数の struct socket_args *uap のメンバ変数は、socket() システムコールの引数と一致します。

struct socket_args は、/usr/src/sys/sys/sysproto.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/sysproto.h?view=markup) で定義されています。

この後にローカル変数の定義 (fdp, so など)、AUDIT_ARG_SOCKET、#ifdef MAC とありますが、

  • ローカル変数の定義に関する説明は、必要の無い限り割愛します。
  • AUDIT_ARG_SOCKET は引数の検査を行なっていると思われますが、実際の処理に直接影響はないので、これに関する説明は割愛します。
  • #ifdef MAC は Mac 用の処理と思われるので割愛します。

この他、次回以降に出てくる uma_zalloc() などの様に、本連載の主旨から必要とは思われないものに関しても、詳細な説明は行ないません。

割愛した部分を除くと、sys_socket() 関数の最初の処理は、変数 fdp の初期化です。

fdp = td->td_proc->p_fd;

fdp はローカル変数で、struct filedesc *fdp; と定義されています。struct filedesc は、ファイルディスクリプタを管理しています。ファイルディスクリプタとは、プロセスが開いているファイルに関する情報です。プロセスが開いているファイルとは、例えば open(2) システムコールを使用して開いたファイルです

struct filedesc は、/usr/src/sys/sys/filedesc.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/filedesc.h?view=markup) で定義されています。

td は本関数の第一引数で、struct thread *td; と定義されています。struct thread は、スレッドに関する情報を管理しています。このスレッドとは、server.c から作成した a.out を起動して生成されたしたプロセス内のスレッドです。server.c はシングルスレッドのプロセスですが、kernel 内部ではこのように管理しています。

struct thread は、/usr/src/sys/sys/proc.h (http://svnweb.freebsd.org/base/release/9.1.0/sys/sys/proc.h?view=markup) で定義されています。

td->td_proc は struct thread のメンバ変数で、struct proc *td_proc; と定義されています。struct proc は、プロセスに関する情報を管理する構造体で、struct thread と同じ proc.h 内で定義されています。td->td_proc->p_fd は、struct proc のメンバ変数で、struct filedesc *p_fd; と定義されています。

要するにここでは、server プロセスが開いているファイルの管理用の情報を、ローカル変数 fdp に代入しています。

fdp が示しているデータ構造を以下に示します。

今回の処理で変更した変数を青で、変更した値は赤で示しています。

[1]sokect システムコールのオンラインマニュアル (http://www.freebsd.org/cgi/man.cgi?query=socket&apropos=0&sektion=0&manpath=FreeBSD+9.1-RELEASE&arch=default&format=html) の HISTORY 欄にも、「The socket() system call appeared in 4.2BSD.」この様に「system call」と記述されています。
[2]この連載では、この表記「大文字英字(数字)」で該当するオンラインマニュアルを示すものとします。この場合、socket システムコールのオンラインマニュアルを指します。カッコ内の数字は、オンラインマニュアルのセクション番号です。大文字にしているのは、man socket コマンドを実行すると先頭にSOCKET(2) と表示されるので、それに習っています。
[3]/usr/src/lib/libc/i386/SYS.h(http://svnweb.freebsd.org/base/release/9.1.0/lib/libc/i386/SYS.h?view=markup) で「int $0x80」と定義されています。
記事執筆者: asou
記事公開日:2013年09月18日