readvシステムコール
readvはファイルを複数のバッファに読み込みます。これはglibで実装しているかと思いきや、そうでなく、readvシステムコールとしカーネルでの実装となっています。このような処理をカーネルで実装すべき物なのかどうか?
結論から言うと、ファイル参照の実装そのものが複数バッファを介してのやり取りで、むしろreadシステムコールは、この複数バッファの1つを利用してファイルを読み込む稀なケースと言うことです。
ファイルread/writeでこのような実装としているのは、上記のような機能をユーザ空間に提供するというのでなく(実務上そんな恩恵があるやに思えません。)、カーネル内部でファイルIDを介してファイルを読み込むsplice等、必要とするバッファをvmalloc()で1つのリニアアドレス空間として取得することなく、page単位でそのバッファとする。と言うことにあるように思います。
readvシステムコールは、vfs_readv()/do_readv_writev()からgeneric_file_aio_read()がコールされます。O_DIRECTならキャッシュから取得することなく、mapping->a_ops->direct_IO()をコールします。apping->a_ops->direct_IO()についてもiovec単位での処理となります。
count = retvalは、O_DIRECTでないなら、retvalは0で、iov[]をnr_segs分1つづつdo_generic_file_read()で読み込みます。O_DIRECTなら通常この処理となりませんが、次世代のファイルシステムと呼ばれるBtrfs(CoWによるジャーナル機能)では、direct_IO()で完全に読み込めない場合があるようです。
結論から言うと、ファイル参照の実装そのものが複数バッファを介してのやり取りで、むしろreadシステムコールは、この複数バッファの1つを利用してファイルを読み込む稀なケースと言うことです。
ファイルread/writeでこのような実装としているのは、上記のような機能をユーザ空間に提供するというのでなく(実務上そんな恩恵があるやに思えません。)、カーネル内部でファイルIDを介してファイルを読み込むsplice等、必要とするバッファをvmalloc()で1つのリニアアドレス空間として取得することなく、page単位でそのバッファとする。と言うことにあるように思います。
#include <stdio.h> #include <fcntl.h> #include <string.h> #include <stdlib.h> #include <sys/uio.h> void do_fread(char* file); void do_fwrite(char* file); void main(int argc, char *argv[]) { do_fwrite(argv[1]); do_fread(argv[1]); } void do_fwrite(char* file) { int fd; char *buff; int len; fd = open(file, O_RDWR | O_CREAT); buff = "111222333"; write(fd, buff, strlen(buff)); close(fd); } void do_fread(char* file) { int i, fd; char buff[3][4]; struct iovec vector[3]; memset(buff, 0, sizeof(buff)); for (i = 0; i < 3; i++) { vector[i].iov_base = buff[i]; vector[i].iov_len = sizeof(buff[i]) - 1; } fd = open(file, O_RDWR | O_CREAT); printf("total len:%d\n", readv(fd, vector, 3)); for (i = 0; i < 3; i++) { printf("vector%d:%s\n", i, buff[i]); } close( fd ); }
[root@localhost test]# ./a.out hoge total len:9 vector0:111 vector1:222 vector2:333
readvシステムコールは、vfs_readv()/do_readv_writev()からgeneric_file_aio_read()がコールされます。O_DIRECTならキャッシュから取得することなく、mapping->a_ops->direct_IO()をコールします。apping->a_ops->direct_IO()についてもiovec単位での処理となります。
count = retvalは、O_DIRECTでないなら、retvalは0で、iov[]をnr_segs分1つづつdo_generic_file_read()で読み込みます。O_DIRECTなら通常この処理となりませんが、次世代のファイルシステムと呼ばれるBtrfs(CoWによるジャーナル機能)では、direct_IO()で完全に読み込めない場合があるようです。
ssize_t generic_file_aio_read(struct kiocb *iocb, const struct iovec *iov, unsigned long nr_segs, loff_t pos) { struct file *filp = iocb->ki_filp; ssize_t retval; unsigned long seg = 0; size_t count; loff_t *ppos = &iocb->ki_pos; count = 0; retval = generic_segment_checks(iov, &nr_segs, &count, VERIFY_WRITE); if (retval) return retval; if (filp->f_flags & O_DIRECT) { loff_t size; struct address_space *mapping; struct inode *inode; mapping = filp->f_mapping; inode = mapping->host; if (!count) goto out; /* skip atime */ size = i_size_read(inode); if (pos < size) { retval = filemap_write_and_wait_range(mapping, pos, pos + iov_length(iov, nr_segs) - 1); if (!retval) { struct blk_plug plug; blk_start_plug(&plug); retval = mapping->a_ops->direct_IO(READ, iocb, iov, pos, nr_segs); blk_finish_plug(&plug); } if (retval > 0) { *ppos = pos + retval; count -= retval; } if (retval < 0 || !count || *ppos >= size) { file_accessed(filp); goto out; } } } count = retval; for (seg = 0; seg < nr_segs; seg++) { read_descriptor_t desc; loff_t offset = 0; if (count) { if (count > iov[seg].iov_len) { count -= iov[seg].iov_len; continue; } offset = count; count = 0; } desc.written = 0; desc.arg.buf = iov[seg].iov_base + offset; desc.count = iov[seg].iov_len - offset; if (desc.count == 0) continue; desc.error = 0; do_generic_file_read(filp, ppos, &desc, file_read_actor); retval += desc.written; if (desc.error) { retval = retval ?: desc.error; break; } if (desc.count > 0) break; } out: return retval; } EXPORT_SYMBOL(generic_file_aio_read);readシステムコールは、iov.iov_base = buf/iov.iov_len=lenとし、nr_segsを1として、filp->f_op->aio_read()をコールし、まさにreadvシステムコールで、1つのiovでreadvしたのと同じです。
ssize_t do_sync_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos) { struct iovec iov = { .iov_base = buf, .iov_len = len }; struct kiocb kiocb; ssize_t ret; init_sync_kiocb(&kiocb, filp); kiocb.ki_pos = *ppos; kiocb.ki_left = len; kiocb.ki_nbytes = len; for (;;) { ret = filp->f_op->aio_read(&kiocb, &iov, 1, kiocb.ki_pos); if (ret != -EIOCBRETRY) break; wait_on_retry_sync_kiocb(&kiocb); } if (-EIOCBQUEUED == ret) ret = wait_on_sync_kiocb(&kiocb); *ppos = kiocb.ki_pos; return ret; }spliceシステムコールはdefault_file_splice_read()がコールされます。読み込むファイルバッファとし、vmalloc()で非連続となるpageでリニアアドレス空間を作ることなく、alloc_page()でpage単位でstruct iovecのbufferでファイル読み込みが可能となります。
ssize_t default_file_splice_read(struct file *in, loff_t *ppos, struct pipe_inode_info *pipe, size_t len, unsigned int flags) { unsigned int nr_pages; unsigned int nr_freed; size_t offset; struct page *pages[PIPE_DEF_BUFFERS]; struct partial_page partial[PIPE_DEF_BUFFERS]; struct iovec *vec, __vec[PIPE_DEF_BUFFERS]; ssize_t res; size_t this_len; int error; int i; struct splice_pipe_desc spd = { .pages = pages, .partial = partial, .flags = flags, .ops = &default_pipe_buf_ops, .spd_release = spd_release_page, }; if (splice_grow_spd(pipe, &spd)) return -ENOMEM; res = -ENOMEM; vec = __vec; if (pipe->buffers > PIPE_DEF_BUFFERS) { vec = kmalloc(pipe->buffers * sizeof(struct iovec), GFP_KERNEL); if (!vec) goto shrink_ret; } offset = *ppos & ~PAGE_CACHE_MASK; nr_pages = (len + offset + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT; for (i = 0; i < nr_pages && i < pipe->buffers && len; i++) { struct page *page; page = alloc_page(GFP_USER); error = -ENOMEM; if (!page) goto err; this_len = min_t(size_t, len, PAGE_CACHE_SIZE - offset); vec[i].iov_base = (void __user *) page_address(page); vec[i].iov_len = this_len; spd.pages[i] = page; spd.nr_pages++; len -= this_len; offset = 0; } res = kernel_readv(in, vec, spd.nr_pages, *ppos); if (res < 0) { error = res; goto err; } error = 0; if (!res) goto err; nr_freed = 0; for (i = 0; i < spd.nr_pages; i++) { this_len = min_t(size_t, vec[i].iov_len, res); spd.partial[i].offset = 0; spd.partial[i].len = this_len; if (!this_len) { __free_page(spd.pages[i]); spd.pages[i] = NULL; nr_freed++; } res -= this_len; } spd.nr_pages -= nr_freed; res = splice_to_pipe(pipe, &spd); if (res > 0) *ppos += res; shrink_ret: if (vec != __vec) kfree(vec); splice_shrink_spd(pipe, &spd); return res; err: for (i = 0; i < spd.nr_pages; i++) __free_page(spd.pages[i]); res = error; goto shrink_ret; } EXPORT_SYMBOL(default_file_splice_read);