歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> Linux VFS中readv,writev系統調用實現原理

Linux VFS中readv,writev系統調用實現原理

日期:2017/2/27 16:03:36   编辑:Linux教程
用戶空間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--;
}
Copyright © Linux教程網 All Rights Reserved