LinuxEye - Linux系统教程

LinuxEye - Linux系统教程

当前位置: 主页 > Linux教程 >

Linux VFS中readv,writev系统调用实现原理

时间:2012-12-02 12:04来源:CU 编辑:up哥小号 点击:
用户空间readv函数对应系统调用在内核里面的入口函数为sys_readv 用户空间writev函数对应系统调用在内核里面的入口函数为sys_writev [root@syslab ~]# grep readv /usr/include/asm/unistd_64.h #define __NR_r
用户空间readv函数对应系统调用在内核里面的入口函数为sys_readv
用户空间writev函数对应系统调用在内核里面的入口函数为sys_writev

[root@syslab ~]# grep readv /usr/include/asm/unistd_64.h
#define __NR_readv                              19
__SYSCALL(__NR_readv, sys_readv)
#define __NR_preadv                             295
__SYSCALL(__NR_preadv, sys_preadv)
#define __NR_process_vm_readv                   310
__SYSCALL(__NR_process_vm_readv, sys_process_vm_readv)

[root@syslab ~]# grep writev /usr/include/asm/unistd_64.h
#define __NR_writev                             20
__SYSCALL(__NR_writev, sys_writev)
#define __NR_pwritev                            296
__SYSCALL(__NR_pwritev, sys_pwritev)
#define __NR_process_vm_writev                  311
__SYSCALL(__NR_process_vm_writev, sys_process_vm_writev)

内核中sys_readv和sys_writev实现如下
SYSCALL_DEFINE3(readv, unsigned long, fd, const struct iovec __user *, vec,
                   unsigned long, vlen)
{
         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_readv(file, vec, vlen, &pos);
                   file_pos_write(file, pos);
                   fput_light(file, fput_needed);
         }

         if (ret > 0)
                   add_rchar(current, ret);
         inc_syscr(current);
         return ret;
}

SYSCALL_DEFINE3(writev, unsigned long, fd, const struct iovec __user *, vec,
                   unsigned long, vlen)
{
         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_writev(file, vec, vlen, &pos);
                   file_pos_write(file, pos);
                   fput_light(file, fput_needed);
         }

         if (ret > 0)
                   add_wchar(current, ret);
         inc_syscw(current);
         return ret;
}

可以看到,sys_readv和sys_writev的区别在于分别调用的是vfs_readv和vfs_writev,
而这两个函数最终调用的do_readv_writev(READ, file, vec, vlen, pos);和return do_readv_writev(WRITE, file, vec, vlen, pos);,所以,仅type参数不同,所以,这里我们仅讨论vfs_readv函数实现,vfs_writev函数实现基本同vfs_readv

Vfs_readv实现
ssize_t vfs_readv(struct file *file, const struct  iovec __user *vec, unsigned long vlen, loff_t *pos)
  1. 如果文件的模式字段(file->f_mode)中不可读(不含有FMODE_READ),则返回错误(vfs_writev这里判断的就是WRITE了)
  2. 如果文件系统既没有实现file->file_operation->read也没有实现file->file_operation->aio_read函数,则返回错误(vfs_writev这里判断的就是write函数了)
  3. 调用do_readv_writev(READ, file, vec, vlen, pos);(vfs_writev这里就把READ换成WRITE了)

3.1    在内核里面新建一个struct iovec 数组,数组大小内核默认为8个,然后用一个struct iovec *iov指针指向这个数组的首地址。

3.2    如果用户空间struct iovec __user *vec数组(长度为unsigned long vlen)长度vlen大于8,则调用kmalloc(vlen*sizeof(struct iovec), GFP_KERNEL)从内存中重新分配一个数组,大小为vlen个 struct iovec,并把3.1中的iov指针指向这个新分配的内存区

3.3       把用户空间传人的参数iovec数组拷贝到内核中刚分配的这个内存区中去

注:3.1-3.3我们得知,这和sys_read,sys_write系统调用不同,sys_read,sys_write只创建了一个struct iov。但是相同点在于,struct iov结构体
struct iovec
{
void __user *iov_base;    /* BSD uses caddr_t (1003.1g requires void *) */
__kernel_size_t iov_len; /* Must be size_t (1003.1g) */
};

里面的用户空间的实际内容,还是指向用户空间的实际内容,并没有拷贝到内核地址空间中来,内核只是用了一个指针指向了用户空间的这片内容。

3.4 优先调用文件系统的file->file_operation->aio_read函数来一次处理vlen个iovec的数组,,如果这个函数没有实现,则调用循环调用vlen次file->file_operation->read来处理(每次处理一个iovec结构体)。具体实现如下
fnv = NULL;
if (type == READ) {//对应vfs_readv
           fn = file->f_op->read;
           fnv = file->f_op->aio_read;
} else {//对应vfs_writev
           fn = (io_fn_t)file->f_op->write;
           fnv = file->f_op->aio_write;
}

if (fnv)
           ret = do_sync_readv_writev(file, iov, vlen, tot_len,pos, fnv);
else
                    ret = do_loop_readv_writev(file, iov, vlen, pos, fn);

3.5 do_sync_readv_writev实现
而其中ssize_t do_sync_readv_writev(struct file *filp, const struct iovec *iov,
           unsigned long nr_segs, size_t len, loff_t *ppos, iov_fn_t fn)中关键部分为
for (;;) {
           ret = fn(&kiocb, iov, nr_segs, kiocb.ki_pos);
           if (ret != -EIOCBRETRY)
                    break;
           wait_on_retry_sync_kiocb(&kiocb);
         }

这和Linux 中read系统调用实现原理一文中讲到的就是一样了(fn替换成read或者write相应的函数即可)。

所以,如果是读操作,则调用文件系统的file->file_operation->aio_read(&kiocb, iov, nr_segs, kiocb.ki_pos);来完成读操作。文件系统(如ext3)会发起具体的请求去把数据从磁盘上面读出来,用读取到的值填充用户空间的iovec数组(把内容填入iovec数组指向的用户空间地址中)

如果是写操作,则调用文件系统的file->file_operation->aio_write(&kiocb, iov, nr_segs, kiocb.ki_pos);来完成读操作。文件系统(如ext3)会发起具体的请求去把用户空间的数据写到磁盘里面去)

3.6 do_loop_readv_writev实现
         看函数的名字我们基本就猜到了,循环做这件事情,此函数在vfs_readv中如果file->file_operation->aio_read没有实现时调用,或者在vfs_writev中如果没有实现file->file_operation->aio_writev时才调用

         实现如我们猜测,如下
while (nr_segs > 0) {//以省略部分方便我们理解
                   nr = fn(filp, base, len, ppos); //如果是vfs_readv,这里就是nr= file->f_op->read(filp, base, len, ppos)了,因为fn是一个函数指针。
                   nr_segs--;
}

转载请保留固定链接: https://linuxeye.com/Linux/983.html

------分隔线----------------------------
标签:linuxreadvwritev
栏目列表
推荐内容