funini.com 自由研究 準備編

ソケットを read したらどうなるの?

ファイルは read/write するもの、ソケットは recv/send するものというのが 普通ですよね。 でも select() にはファイルもソケットもつっこめます…ということは、両者にそんなに違いはない!?
実は socket() が返すファイル番号は、ファイルディスクリプタとしても使えます。 だから、ソケットを read したり write したりもできます。 その仕組みをちょっと紹介してみます。

sys_socket()

asmlinkage long sys_socket(int family, int type, int protocol)
{
        int retval;
        struct socket *sock;
        int flags;
        // 各種フラグの設定
...

        retval = sock_create(family, type, protocol, &sock);
        if (retval < 0)
                goto out;
        // ここで、ファイルディスクリプタとしての準備をする
        retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
        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;
}

sock_map_fd : net/socket.c

int sock_map_fd(struct socket *sock, int flags)
{
        struct file *newfile;
        int fd = sock_alloc_fd(&newfile, flags);

        if (likely(fd >= 0)) {
                // ここで、fd の機能を追加する
                int err = sock_attach_fd(sock, newfile, flags);

                if (unlikely(err < 0)) {
                        put_filp(newfile);
                        put_unused_fd(fd);
                        return err;
                }
                fd_install(fd, newfile);
        }
        return fd;
}
static int sock_attach_fd(struct socket *sock, struct file *file, int flags)
{
        struct dentry *dentry;
        struct qstr name = { .name = "" };

        dentry = d_alloc(sock_mnt->mnt_sb->s_root, &name);
        if (unlikely(!dentry))
                return -ENOMEM;

        dentry->d_op = &sockfs_dentry_operations;
        /*
         * We dont want to push this dentry into global dentry hash table.
         * We pretend dentry is already hashed, by unsetting DCACHE_UNHASHED
         * This permits a working /proc/$pid/fd/XXX on sockets
         */
        dentry->d_flags &= ~DCACHE_UNHASHED;
        d_instantiate(dentry, SOCK_INODE(sock));

        sock->file = file;
        init_file(file, sock_mnt, dentry, FMODE_READ | FMODE_WRITE,
                  &socket_file_ops);
        // ここがポイント。
        // read とか write とか出来るように、file 構造体としての体裁を整える。
        SOCK_INODE(sock)->i_fop = &socket_file_ops;
        file->f_flags = O_RDWR | (flags & O_NONBLOCK);
        file->f_pos = 0;
        file->private_data = sock;

        return 0;
}
ここで、socjet_file_ops の定義は以下のように成っている。
static const struct file_operations socket_file_ops = {
        .owner =        THIS_MODULE,      // ソケットのオーナー
        .llseek =       no_llseek,        // seek はできない
        .aio_read =     sock_aio_read,    // 非同期 read の定義
        .aio_write =    sock_aio_write,   // 非同期 write の定義
        .poll =         sock_poll,        // 
        .unlocked_ioctl = sock_ioctl,     // ioctl
#ifdef CONFIG_COMPAT
        .compat_ioctl = compat_sock_ioctl,
#endif
        .mmap =         sock_mmap,
        .open =         sock_no_open,   /* special open code to disallow open via /proc */
                                          // open はできない
        .release =      sock_close,
        .fasync =       sock_fasync,
        .sendpage =     sock_sendpage,
        .splice_write = generic_splice_sendpage,
        .splice_read =  sock_splice_read,
};
一方、file 構造体オリジナルの定義はこちら。
(from include/linux/fs.h)


システムコール表: syscall_table_32.S

システムコールのはじまりはいつもここ。
ENTRY(sys_call_table)
        .long sys_restart_syscall       /* 0 - old "setup()" system call, used for restarting */
        .long sys_exit
        .long sys_fork
        .long sys_read
read はシステムコール番号 3 番で、実体は sys_read。

sys_read(): fs/read_write.c

ファイルディスクリプタ番号 (fd) から、file 構造体を探し、 これを指定して vfs_read を呼びます。
asmlinkage ssize_t sys_read(unsigned int fd, char __user * buf, size_t count)
{
        struct file *file;
        ssize_t ret = -EBADF;
        int fput_needed;

        // ファイル構造体の取得
        file = fget_light(fd, &fput_needed);
        if (file) {
                // ファイルポインタの位置を取得
                loff_t pos = file_pos_read(file);
                // ファイルの読み込み
                ret = vfs_read(file, buf, count, &pos);
                // 現在のファイルポインタの位置を書き込み
                // (次回 read したときにちゃんと次から読めるように)
                file_pos_write(file, pos);
                fput_light(file, fput_needed);
        }

        return ret;
}