linux
内核在读写数据时候,可能会发生短暂的停顿等待磁盘数据 ,本质原因是进程
发起read/write
系统调用时候,由用户态切换到内核态,进程
会经历磁盘数据达到后而引起的中断和从中断点继续执行的两个步骤。进程进入中断后,内核把磁盘数据的读写请求是交给了磁盘,磁盘硬件完成读写操作后发起硬中断唤醒被IO 中断
的进程,继续执行后续的操作。read/write
系统调用需要三个参数,第一个是文件描述符fd
,第二个是内存缓冲区buf,第三个是读写的字节数。read/write
系统调用由用户态进程发起,然后进入内核态的vfs层,调用文件描述符对应的struct file
来完成对磁盘文件系统的读写操作。块缓存/页缓存(block cache/page cache)
进行交互,读写的文件page没有在page cache中,这时候才会向块设备发起请求或者读写操作设置标记跳过page cache直接将读写 请求提交到块设备层。page cache
缓存的文件数据的page
,内核中的page cache
的管理是通过struct address_space
的对象进行管理。内核的vfs
层如果发现请求的文件数据在page cache
中,直接从page cache
中获取,而不用和块设备交互 。如果不在page cache
中则需要将读写请求提交到块设备层来获取数据.这里需要注意的linux
内存管理是以page(页)
为基本单位,但是块设备读写是以块
为单位,因此在页缓存的基础上,把页分割为多个块并建立块缓存。
// read 系统调用的定义
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
return ksys_read(fd, buf, count);
}
// write系统调用的定义
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
size_t, count)
{
return ksys_write(fd, buf, count);
}
read
系统调用最终请求落到了ksys_read
内核函数 ,而对于整个read
操作的流程会经历read->ksys_read->vfs_read->file->f_op->read或者file->f_op->read_iter
write
系统调用最终请求落到了ksys_write
内核函数 ,而对于整个write
操作的流程会经历write->ksys_write->vfs_write->file->f_op->write或者file->f_op->write_iter
ssize_t ksys_read(unsigned int fd, char __user *buf, size_t count)
{
// 根据fd得到本进程内fd对应的struct file
struct fd f = fdget_pos(fd);
ssize_t ret = -EBADF;
if (f.file) {
// 获取struct file当前读取的pos
loff_t pos = file_pos_read(f.file);
// 经过vfs_read->具体磁盘文件系统的read调用
ret = vfs_read(f.file, buf, count, &pos);
if (ret >= 0)
// 更改struct file中的读写的位置pos
file_pos_write(f.file, pos);
fdput_pos(f);
}
return ret;
}
ssize_t ksys_write(unsigned int fd, const char __user *buf, size_t count)
{
// 根据fd得到本进程内fd对应的struct file
struct fd f = fdget_pos(fd);
ssize_t ret = -EBADF;
if (f.file) {
// 获取struct file当前读取的pos
loff_t pos = file_pos_read(f.file);
// 经过vfs_write->具体磁盘文件系统的write调用
ret = vfs_write(f.file, buf, count, &pos);
if (ret >= 0)
// 更改struct file中的读写的位置pos
file_pos_write(f.file, pos);
fdput_pos(f);
}
return ret;
}
磁盘文件系统ext4
的读写操作定义在const struct file_operations ext4_file_operations
.任何非缓存情况下文件读写都会到具体文件系统的读写函数。vfs
层所有的读写操作函数赋值都是通过ext4_file_operations
来赋值的。从ksys_write
开始到ext4_file_write_iter
函数,如果请求IO的方式的是DIRECT_IO
方式直接走generic_file_direct_write
逻辑;否则是走generic_perform_write
函数,准备好页缓存和块缓存,然后将数据写入到对应的缓冲区,最后标记这些缓冲区为dirty
完成。// ext4磁盘文件系统的struct file操作函数表,这里仅仅列举了read/write
const struct file_operations ext4_file_operations = {
.read_iter = ext4_file_read_iter,
.write_iter = ext4_file_write_iter,
};
// ext4文件系统的page cache的管理
static const struct address_space_operations ext4_aops = {
.write_begin = ext4_write_begin,
.write_end = ext4_write_end,
.direct_IO = ext4_direct_IO,
};
// ext4文件系统写操作函数
ssize_t __generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
// 判断是否是O_DIRECT
if (iocb->ki_flags & IOCB_DIRECT) {
written = generic_file_direct_write(iocb, from) {
struct address_space *mapping = file->f_mapping;
// 走ext4的dio函数
written = mapping->a_ops->direct_IO(iocb, from)
{
// 执行Ext4的di函数
ext4_direct_IO(iocb, from) {
ext4_direct_IO_write(iocb, iter) {
// 最终走块设备 层的__blockdev_direct_IO函数进行dio的写入
ret = __blockdev_direct_IO(iocb, inode, inode->i_sb->s_bdev, iter,get_block_func, ext4_end_io_dio, NULL,dio_flags);
}
}
}
}
} else {
// 走ext4 page cache逻辑,逐个iovec和page进行写处理操作
written = generic_perform_write(file, from, iocb->ki_pos)
{
// a_ops->write_begin 函数是准备缓冲区,读入对应的数据结构
status = a_ops->write_begin(file, mapping, pos, bytes, flags,&page, &fsdata)
{
ext4_write_begin(file, mapping, pos, bytes, flags,&page, &fsdata) {
ret = __block_write_begin(page, pos, len, ext4_get_block);
}
}
// 将用户的缓冲区内容复制到page cache/block cache.
copied = iov_iter_copy_from_user_atomic(page, i, offset, bytes);
// a_ops->write_end 函数是将缓冲区标记为脏准备会写。
status = a_ops->write_end(file, mapping, pos, bytes, copied,page, fsdata)
{
ext4_write_end(file, mapping, pos, bytes, copied,page, fsdata) {
copied = block_write_end(file, mapping, pos,len, copied, page, fsdata);
ext4_mark_inode_dirty(handle, inode);
}
}
}
}
out:
current->backing_dev_info = NULL;
return written ? written : err;
}
ext4
文件系统读操作又开始的ksys_read
到ext4_file_operations->ext4_file_read_iter
函数,用户态发起read
到内核态vfs
层,最后在到具体的ext4文件系统的读。读操作
首先是查找page cache
,如果找不到则通过通用块设备IO层向磁盘发起读操作请求。在读操作
过程中如果是直接IO
则不经过page cache
;否则调用ext4的响应的page cache
方法从cache中读取数据。// ext4 文件读取操作的执行流程
static ssize_t ext4_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
#ifdef CONFIG_FS_DAX
// 支持dax设备,IO不仅过IO协议栈,直接和磁盘驱动交互
if (IS_DAX(file_inode(iocb->ki_filp)))
return ext4_dax_read_iter(iocb, to);
#endif
return generic_file_read_iter(iocb, to)
{
// 如果是DIO,则进入page cache的dio函数
if (iocb->ki_flags & IOCB_DIRECT) {
retval = mapping->a_ops->direct_IO(iocb, iter)
{
// 进入ext4的dio读函数,直接和块设备交互
ext4_direct_IO(iocb, iter)
{
ext4_direct_IO_read(iocb, iter) {
__blockdev_direct_IO(iocb, inode, inode->i_sb->s_bdev,iter, ext4_dio_get_block, NULL, NULL, 0);
}
}
}
}else {
// 数据的从缓冲区
retval = generic_file_buffered_read(iocb, iter, retval)
{
// 调用对应ext4函数从page cache中获取数据
error = mapping->a_ops->readpage(filp, page)
{
ext4_readpage(filp, page) {
ext4_mpage_readpages(page->mapping, NULL, page, 1,false);
}
}
}
}
}
}