全部博文(2759)
分类: LINUX
2014-12-21 10:24:41
原文地址:Linux VFS中readv,writev系统调用实现原理 作者:up哥小号
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--;
}
参考:linux kernel 3.6.7
讲到这里,sys_read,sys_readv,sys_write,sys_writev有必要来一个对比和总结了,详见下一篇