linux 用readv和writev实现服务器和客户端,Linux VFS中readv,writev系统调用实现原理

readv函数对应系统调用在内核里面的入口函数为sys_readv

用户空间writev函数对应系统调用在内核里面的入口函数为sys_writev

[root@syslab ~]# grep readv /usr/include/asm/unistd_64.h

#define __NR_readv19

__SYSCALL(__NR_readv, sys_readv)

#define __NR_preadv295

__SYSCALL(__NR_preadv, sys_preadv)

#define __NR_process_vm_readv310

__SYSCALL(__NR_process_vm_readv, sys_process_vm_readv)

[root@syslab ~]# grep writev /usr/include/asm/unistd_64.h

#define __NR_writev20

__SYSCALL(__NR_writev, sys_writev)

#define __NR_pwritev296

__SYSCALL(__NR_pwritev, sys_pwritev)

#define __NR_process_vm_writev311

__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);

}

这和一文中讲到的就是一样了(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有必要来一个对比和总结了,详见下一篇

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值