Linux内核写文件流程
接上篇Linux内核读文件流程,写这篇Linux内核写文件流程。文中涉及的内核代码版本是linux内核版本号:3.0.13-0.27 sles11sp2版本。
用户态write函数到内核态的调用是:
|
1
2
|
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
size_t, count)
|
SYSCALL_DEFINE3调用的vfs_write,vfs_write调用rw_verify_area检查当前是否有写权限,然后调用虚拟文件系统(vfs)的f_op指针write函数。f_op指针write函数在ext3_file_operations中定义的,定义为do_sync_write。
|
1
2
3
4
5
6
7
8
|
ret = rw_verify_area(WRITE, file, pos, count);
if (ret >= 0) {
count = ret;
if (file->f_op->write)
ret = file->f_op->write(file, buf, count, pos);
else
ret = do_sync_write(file, buf, count, pos);
}
|
在write中函数定义了iov和kiocb,调用aio_write完成数据写入,aio_write是在ext3_file_operations中定义为generic_file_aio_write;然后等待wait_on_retry_sync_kiocb将数据刷新到磁盘。
|
1
2
3
4
5
6
|
for (;;) {
ret = filp->f_op->aio_write(&kiocb, &iov, 1, kiocb.ki_pos);
if (ret != -EIOCBRETRY)
break;
wait_on_retry_sync_kiocb(&kiocb);
}
|
generic_file_aio_write同样在开始检查各种锁等控制开关,直接通过__generic_file_aio_write将功能实现,但大部分检查是在__generic_file_aio_write里面测试的,如vfs_check_frozen检查文件系统是否有冻僵(不能写入),generic_write_checks权限块设备等检查。文件的修改时间也是在此处刷新的:file_update_time。然后检查是否有直接写入(O_DIRECT)的参数,O_DIRECT部分后面再说,如果没有则是generic_file_buffered_write,最后进入到generic_perform_write.
generic_perform_write也是文件写入的主要完成部分,整个部分是由一个do{}while循环完成的。首先是通过write_begin函数进行一些预处理工作,具体指向ext3_write_begin函数。ext3_write_begin首先用grab_cache_page_write_begin获取页高速缓存,调用__block_write_begin,在其中用get_block获取物理块号,同时启用journal机制。
当write_begin已经完成的时候,将用户态数据拷贝到内核态的page上。
|
1
2
3
|
pagefault_disable();
copied = iov_iter_copy_from_user_atomic(page, i, offset, bytes);
pagefault_enable();
|
通过write_end完成具体写入,由于ext3的journal有3中不同的写入方式,此处介绍默认的order方式,ext3_ordered_write_end函数首先调用block_write_end函数,其中调用__block_commit_write提交写入的数据,但事实上其提交只是对buff的状态做了处理,并没有其他大的操作。
|
1
2
|
set_buffer_uptodate(bh);
mark_buffer_dirty(bh);
|
而后的walk_page_buffers只是通过journal_dirty_data_fn利用journal机制将数据标记为脏,然后用update_file_sizes更新文件所属inode的属性,并置为脏。
|
1
2
3
4
5
6
7
8
9
10
|
static void update_file_sizes(struct inode *inode, loff_t pos, unsigned copied)
{
/* What matters to us is i_disksize. We don't write i_size anywhere */
if (pos + copied > inode->i_size)
i_size_write(inode, pos + copied);
if (pos + copied > EXT3_I(inode)->i_disksize) {
EXT3_I(inode)->i_disksize = pos + copied;
mark_inode_dirty(inode);
}
}
|
此时将inode添加到孤儿链表中,具体介绍见链接。
|
1
2
|
if (pos + len > inode->i_size && ext3_can_truncate(inode))
ext3_orphan_add(handle, inode);
|
事实上前面都没有做刷新磁盘的操作,最后还是回到generic_perform_write函数,有balance_dirty_pages_ratelimited函数,顾名思义,平衡脏页比率,在此处进行脏页的刷新。另外一篇文章有详细介绍,点击Linux缓存写回机制
如此才完成了文件的写入过程。
—结束—
浙公网安备 33010602011771号