Chinaunix首页 | 论坛 | 博客
  • 博客访问: 149911
  • 博文数量: 10
  • 博客积分: 207
  • 博客等级: 入伍新兵
  • 技术积分: 380
  • 用 户 组: 普通用户
  • 注册时间: 2012-11-10 12:44
文章存档

2015年(2)

2013年(1)

2012年(7)

我的朋友

分类: LINUX

2012-11-29 19:21:26

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_readvsys_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_readvsys_writev的区别在于分别调用的是vfs_readvvfs_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函数来一次处理vleniovec的数组,,如果这个函数没有实现,则调用循环调用vlenfile->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有必要来一个对比和总结了,详见下一篇

 

阅读(7486) | 评论(0) | 转发(2) |
给主人留下些什么吧!~~