前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >聊聊Posix语义之读写系统调用

聊聊Posix语义之读写系统调用

作者头像
用户4700054
发布2022-08-17 13:00:36
9170
发布2022-08-17 13:00:36
举报
文章被收录于专栏:存储内核技术交流

基本介绍

  • 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(页)为基本单位,但是块设备读写是以为单位,因此在页缓存的基础上,把页分割为多个块并建立块缓存。
代码语言:javascript
复制
// 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);
}

读写系统系统调用实现分析

write/read系统调用执行链
  • 之前分析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
代码语言:javascript
复制
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完成。
代码语言:javascript
复制
// 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_readext4_file_operations->ext4_file_read_iter函数,用户态发起read到内核态vfs层,最后在到具体的ext4文件系统的读。读操作首先是查找page cache,如果找不到则通过通用块设备IO层向磁盘发起读操作请求。在读操作过程中如果是直接IO则不经过page cache;否则调用ext4的响应的page cache方法从cache中读取数据。
代码语言:javascript
复制
// 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);
					}
				}
			}
		}
	}
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 存储内核技术交流 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基本介绍
  • 读写系统系统调用实现分析
    • write/read系统调用执行链
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档