我是靠谱客的博主 殷勤烧鹅,最近开发中收集的这篇文章主要介绍linux 用readv和writev实现服务器和客户端,Linux中readv,writev系统调用实现原理,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

用户空间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了)www.it165.net

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有必要来一个对比和总结了,详见下一篇

最后

以上就是殷勤烧鹅为你收集整理的linux 用readv和writev实现服务器和客户端,Linux中readv,writev系统调用实现原理的全部内容,希望文章能够帮你解决linux 用readv和writev实现服务器和客户端,Linux中readv,writev系统调用实现原理所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(44)

评论列表共有 0 条评论

立即
投稿
返回
顶部