Scatter/Gather I/O(readv/writev)の実装

readv、writevは複数バッファをまとめて読み書きするシステムコールです。 writevはiovec 構造体の配列に書き込みたいものを突っ込んでまとめてその複数バッファーを書き込み、readvは指定した個数分の複数バッファーをiovec 構造体の配列に格納します。例えばwritevによる複数バッファの書き込みはこんな感じです。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/uio.h>
int
 main( int argc, char** argv ) {
    int fd = 0;
    ssize_t written;
    struct iovec iov[3];
    char *buffer0, *buffer1, *buffer2;
    char *file = argv[1];
    fd = open( file,  O_RDWR|O_CREAT|O_EXCL, 0666 );
    if ( fd < 0 ) {
        printf("cannot open file:%s\n", file);
        return -1;
    }
    buffer0 = "buffer0 string";
    buffer1 = "buffer1 string";
    buffer2 = "buffer2 string";
    iov[0].iov_base = buffer0;
    iov[0].iov_len = strlen(buffer0);
    iov[1].iov_base = buffer1;
    iov[1].iov_len = strlen(buffer1);
    iov[2].iov_base = buffer2;
    iov[2].iov_len = strlen(buffer2);
    written = writev(fd, iov, 3);
    close(fd);
    return 0;
}

※iovec 構造体の定義

/* Structure for scatter/gather I/O.  */
struct iovec
{
   void *iov_base; /* Pointer to data.  */
   size_t iov_len; /* Length of data.  */
};

manによると基本的にはこのシステムコールの特徴は次の2つです。

  1. 1. readv、writevともに複数のバッファにデータを読み込む点を除いてそれぞれread(2)、write(2)と全く同様の動作を行う。
  2. 2. readvと writevともにatomic。writevによるデータ書き込み中に他のスレッド、プロセスのwrite による割り込みが入らない。readvも同様にファイルから連続するデータブロックが読み出すことを保証。

本当にこのシステムコールが上記のような実装になっているのか確かめるために実際にソースコードを覗いてみます。readv、writevでは内部で次のdo_readv_writev関数がコールされます。

do_readv_writev @ linux-2.6.12.1/fs/read_write.c

static ssize_t do_readv_writev(int type, struct file *file,
                   const struct iovec __user * uvector,
                   unsigned long nr_segs, loff_t *pos)
{
    ...
    ret = rw_verify_area(type, file, pos, tot_len);
    if (ret)
        goto out;
    fnv = NULL;
    if (type == READ) {
        fn = file->f_op->read;
        fnv = file->f_op->readv;
    } else {
        fn = (io_fn_t)file->f_op->write;
        fnv = file->f_op->writev;
    }
    if (fnv) {
        ret = fnv(file, iov, nr_segs, pos);
        goto out;
    }
    /* Do it by hand, with file-ops */
    ret = 0;
    vector = iov;
    while (nr_segs > 0) {
        void __user * base;
        size_t len;
        ssize_t nr;

        base = vector->iov_base;
        len = vector->iov_len;
        vector++;
        nr_segs--;

        nr = fn(file, base, len, pos);

        if (nr < 0) {
            if (!ret) ret = nr;
            break;
        }
        ret += nr;
        if (nr != len)
            break;
    }
    ...
}

後半のwhileブロックを見るとたしかにreadv、writevのときそれぞれ内部でread、writeがループで繰り返されています。 想像を超えたシンプルさでした。次にatomicを保障している箇所ですがREADかWRITEのtypeチェックを行う前のrw_verify_areaがそれっぽい。ということでrw_verify_areaを覗いてみます。

rw_verify_area @ linux-2.6.12.1/fs/read_write.c

int rw_verify_area(int read_write, struct file *file, loff_t *ppos, size_t count)
{
    struct inode *inode;
    loff_t pos;

    if (unlikely(count > file->f_maxcount))
        goto Einval;
    pos = *ppos;
    if (unlikely((pos < 0) || (loff_t) (pos + count) < 0))
        goto Einval;

    inode = file->f_dentry->d_inode;
    if (inode->i_flock && MANDATORY_LOCK(inode))
        return locks_mandatory_area(read_write == READ ? FLOCK_VERIFY_READ : FLOCK_VERIFY_WRITE, inode, file, pos, count);
    return 0;

Einval:
    return -EINVAL;
}

ここではファイルの読み込み・書き込みエリアのバリデーションチェックとlocks_mandatory_areaで排他チェックを行っています。念のためlocks_mandatory_areaを覗いてみます。

locks_mandatory_area @ linux-2.6.12.1/fs/locks.c

/**
 * locks_mandatory_area - Check for a conflicting lock
 * @read_write: %FLOCK_VERIFY_WRITE for exclusive access, %FLOCK_VERIFY_READ
 *      for shared
 * @inode:      the file to check
 * @filp:       how the file was opened (if it was)
 * @offset:     start of area to check
 * @count:      length of area to check
 *
 * Searches the inode's list of locks to find any POSIX locks which conflict.
 * This function is called from rw_verify_area() and
 * locks_verify_truncate().
 */

int locks_mandatory_area(int read_write, struct inode *inode,
             struct file *filp, loff_t offset,
             size_t count)
{
    struct file_lock fl;
    int error;

    locks_init_lock(&fl);
    fl.fl_owner = current->files;
    fl.fl_pid = current->tgid;
    fl.fl_file = filp;
    fl.fl_flags = FL_POSIX | FL_ACCESS;
    if (filp && !(filp->f_flags & O_NONBLOCK))
        fl.fl_flags |= FL_SLEEP;
    fl.fl_type = (read_write == FLOCK_VERIFY_WRITE) ? F_WRLCK : F_RDLCK;
    fl.fl_start = offset;
    fl.fl_end = offset + count - 1;

    for (;;) {
        error = __posix_lock_file(inode, &fl);
        if (error != -EAGAIN)
            break;
        if (!(fl.fl_flags & FL_SLEEP))
            break;
        error = wait_event_interruptible(fl.fl_wait, !fl.fl_next);
        if (!error) {
            /*
             * If we've been sleeping someone might have
             * changed the permissions behind our back.
             */

            if ((inode->i_mode & (S_ISGID | S_IXGRP)) == S_ISGID)
                continue;
        }

        locks_delete_block(&fl);
        break;
    }

    return error;
}

※ kernelソースコードlinux-2.6.12.1

以上、簡単ではありますがreadv、writevの実装でした。 このシステムコールはこれまで何度か業務で利用していておなじみな物のではありましたが特に内部の実装を気にすることなく使ってました。実際に見てみると単純な複数バッファ分のread, writeの繰り返しではありますがその処理間に排他制御がしっかりされているのでパフォーマンスはさておき安心して使えると思います。その辺の自分で実装するのは面倒ですから。

No related posts.

Posted in: Programming / プログラミング

Tags: , ,



DeliciousFacebookRedditTwitterGoogle

2 Comments

rssComments RSS transmitTrackBack Identifier URI


Keep posting stuff like this i really like it

Comment by cna training on June 8, 2010 1:48 pm


Terrific work! This is the type of information that should be shared around the web. Shame on the search engines for not positioning this post higher!

Comment by physical therapist on June 17, 2010 5:28 pm

addLeave a comment