vmsplice
Rev.1を表示中。最新版はこちら。
vmspliceはユーザ空間メモリをpipeバッファにspliceすることで、ユーザ空間メモリをpipeバッファと共有します。ユーザ空間の仮想アドレスはカーネル空間で使用できません。仮想アドレスからpageを取得し、そのpageに改めてカーネル空間の仮想メモリを割り当てる事で実装しています。pipeファイルidのfdが書き込みならvmsplice_to_pipe()、読み込みならvmsplice_to_user()をコールします。
SYSCALL_DEFINE4(vmsplice, int, fd, const struct iovec __user *, iov, unsigned long, nr_segs, unsigned int, flags) { struct file *file; long error; int fput; if (unlikely(nr_segs > UIO_MAXIOV)) return -EINVAL; else if (unlikely(!nr_segs)) return 0; error = -EBADF; file = fget_light(fd, &fput); if (file) { if (file->f_mode & FMODE_WRITE) error = vmsplice_to_pipe(file, iov, nr_segs, flags); else if (file->f_mode & FMODE_READ) error = vmsplice_to_user(file, iov, nr_segs, flags); fput_light(file, fput); } return error; }pipeからユーザ空間メモリで書き出す処理です。splice_grow_spd()はpipeバッファサイズに応じて、sdp->page/partialを取得設定します。get_iovec_page_array()で、iovで設定しているのユーザ空間メモリのpageを、spd.pagesに取り出して、splice_to_pipe()でpipeのデータをsdpのそのpageに書き出します。
static long vmsplice_to_pipe(struct file *file, const struct iovec __user *iov, unsigned long nr_segs, unsigned int flags) { struct pipe_inode_info *pipe; struct page *pages[PIPE_DEF_BUFFERS]; struct partial_page partial[PIPE_DEF_BUFFERS]; struct splice_pipe_desc spd = { .pages = pages, .partial = partial, .flags = flags, .ops = &user_page_pipe_buf_ops, .spd_release = spd_release_page, }; long ret; pipe = get_pipe_info(file); if (!pipe) return -EBADF; if (splice_grow_spd(pipe, &spd)) return -ENOMEM; spd.nr_pages = get_iovec_page_array(iov, nr_segs, spd.pages, spd.partial, flags & SPLICE_F_GIFT, pipe->buffers); if (spd.nr_pages <= 0) ret = spd.nr_pages; else ret = splice_to_pipe(pipe, &spd); splice_shrink_spd(pipe, &spd); return ret; }まず、カーネルパスではユーザ空間を参照するため、iovのentryをカーネル空間にコピーして処理します。base/lenでそのアドレス/サイズを引数とし、get_user_pages_fast()をコールします。この関数でそのpageをpages[buffers]に取得し、offset/lenを設定します。
alignedはオプションSPLICE_F_GIFTが設定されていれば、1となり、この場合entryはpageサイズ単位でないといけません。splice_to_pipe()でpipeバッファーとspdでリンクする時、複写するか移動するかの処理があって、SPLICE_F_GIFTは移動となり、従ってpageサイズに満たない領域は、他で使われていることがあります。そうなるといけません。そのためpageサイズ全てが他に使われてないことが保障されなければなりません。
static int get_iovec_page_array(const struct iovec __user *iov, unsigned int nr_vecs, struct page **pages, struct partial_page *partial, int aligned, unsigned int pipe_buffers) { int buffers = 0, error = 0; while (nr_vecs) { unsigned long off, npages; struct iovec entry; void __user *base; size_t len; int i; error = -EFAULT; if (copy_from_user(&entry, iov, sizeof(entry))) break; base = entry.iov_base; len = entry.iov_len; error = 0; if (unlikely(!len)) break; error = -EFAULT; if (!access_ok(VERIFY_READ, base, len)) break; off = (unsigned long) base & ~PAGE_MASK; error = -EINVAL; if (aligned && (off || len & ~PAGE_MASK)) break; npages = (off + len + PAGE_SIZE - 1) >> PAGE_SHIFT; if (npages > pipe_buffers - buffers) npages = pipe_buffers - buffers; error = get_user_pages_fast((unsigned long)base, npages, 0, &pages[buffers]); if (unlikely(error <= 0)) break; for (i = 0; i < error; i++) { const int plen = min_t(size_t, len, PAGE_SIZE - off); partial[buffers].offset = off; partial[buffers].len = plen; off = 0; len -= plen; buffers++; } if (len) break; if (error < npages || buffers == pipe_buffers) break; nr_vecs--; iov++; } if (buffers) return buffers; return error; }