前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >聊聊内核中IO子系统-上篇

聊聊内核中IO子系统-上篇

作者头像
用户4700054
发布2022-08-17 13:17:28
4650
发布2022-08-17 13:17:28
举报

I/O子系统概貌

  • VFS:内核提供不同实现文件系统的抽象,应用端一般请求到vfs,vfs在调用实际文件系统的posix语义函数,可以理解为vfs作为用户态和实际文件系统的之间的转换桥梁,为用户态提供对于底层磁盘文件系统无感知的文件系统服务层。
  • Page Cache: 缓存文件系统的数据,这里包括文件系统元数据和文件系统数据,在块缓存之上构建页缓存(常说的Buffer/Cache).
  • Mapping Layer:如果内核需要从块设备上读取数据,就必须知道数据在物理设备上的位置,这个是由映射层Mapping Layer来完成。
  • Generic Block Layer:通用块设备层目标是为不同的块设备建立统一的模型,它负责接受上层的IO请求,最后在发出IO请求(在块设备上执行操作,启动I/O操作,传输请求数据)
  • I/O Scheduler:IO调度层根据调度策略对IO请求进行排序,缓存请求而且合并相邻的请求,根据设置号的调度策略,回调驱动提供的回调函数,用来处理具体的IO请求。
  • Block Device Driver:负责向磁盘控制器发送IO指令执行实际的数据传输。

I/O子系统中对象

  • struct gendisk: 内核通过gendisk把磁盘类设备中通用的部分抽象出来。
  • struct hd_struct:对于分区的请求可以线性的映射到物理磁盘,这种分区的体现在内核中使用hd_struct表示。
  • struct block_device:在内核中磁盘和分区对应一个块设备,每个块设备有自己唯一的块设备编号和名称,根据块设备可以找到磁盘,这个通用的块设备抽象层是通过block_device呈现。

I/O处理流程

  • 上层使用通用块设备层提供的IO接口向块设备提交IO请求,IO请求放I/O调度层的调度队列中,在根据调度策略进行合并和排序,最后把转换后的I/O请求派发给具体的块设备的队列,最后由设备驱动层来进行处理。
  • 这里涉及到了两种I/O请求,一种是通用块设备层的I/O请求,这个在内核中是以struct bio呈现;另外一种设备驱动层的I/O请求,在内核中是以struct request呈现。
代码语言:javascript
复制
	// 驱动层的IO请求
struct request {
	// 指向包含请求的请求队列的指针
	struct request_queue *q;
	struct blk_mq_ctx *mq_ctx;

	int cpu;
	// 请求标志
	unsigned int cmd_flags;		/* op and common flags */
	req_flags_t rq_flags;

	int internal_tag;

	// 请求的数据长度
	unsigned int __data_len;	/* total data len */
	int tag;
	// 请求的起始扇区的编号
	sector_t __sector;		
	// 没完成传输的第一个bio
	struct bio *bio;
	// 租后一个bio
	struct bio *biotail;
	// request请求放入的I/O调度队列
	struct list_head queuelist;
	union {
		// 链入电梯算法哈希表,按照最后一个扇区的编号进行哈希,方便查找和合并对象
		struct hlist_node hash;	/* merge hash */
		struct list_head ipi_list;
	};

	union {
		struct rb_node rb_node;	/* sort/lookup */
		struct bio_vec special_vec;
		void *completion_data;
		int error_count; /* for legacy drivers, don't use */
	};
	union {
		struct {
			struct io_cq		*icq;
			void			*priv[2];
		} elv;

		struct {
			unsigned int		seq;
			struct list_head	list;
			rq_end_io_fn		*saved_end_io;
		} flush;
	};
	// IO请求所对应的磁盘结构指针
	struct gendisk *rq_disk;
	// IO请求所在的磁盘对应的分区
	struct hd_struct *part;

	// IO提交到内核的时间
	u64 start_time_ns;

	// IO提交到驱动层的时间
	u64 io_start_time_ns;

#ifdef CONFIG_BLK_WBT
	unsigned short wbt_flags;
#endif
#ifdef CONFIG_BLK_DEV_THROTTLING_LOW
	unsigned short throtl_size;
#endif

	/*
	 * Number of scatter-gather DMA addr+len pairs after
	 * physical address coalescing is performed.
	 */
	unsigned short nr_phys_segments;

#if defined(CONFIG_BLK_DEV_INTEGRITY)
	unsigned short nr_integrity_segments;
#endif

	unsigned short write_hint;
	unsigned short ioprio;

	void *special;		
	// 额外的树长度
	unsigned int extra_len;	/* length of alignment and padding */

	enum mq_rq_state state;
	refcount_t ref;

	// 请求的超时时间
	unsigned int timeout;

	/* access through blk_rq_set_deadline, blk_rq_deadline */
	unsigned long __deadline;

	struct list_head timeout_list;

	union {
		struct __call_single_data csd;
		u64 fifo_time;
	};

	// 请求完成的回调函数
	rq_end_io_fn *end_io;
	// 完成回调函数的参数
	void *end_io_data;

	// next_rq关联两个request实现SCSI双向指令
	struct request *next_rq;

#ifdef CONFIG_BLK_CGROUP
	struct request_list *rl;		/* rl this rq is alloced from */
#endif
};


// 来自用户态的IO请求在内核的表现形式bio
struct bio {
	// 指向属于同一个request的后一个bio
	struct bio		*bi_next;	
	// bio请求的磁盘
	struct gendisk		*bi_disk;
	unsigned int		bi_opf;		
	// bio的状态
	unsigned short		bi_flags;	/* status, etc and bvec pool number */
	unsigned short		bi_ioprio;
	unsigned short		bi_write_hint;
	blk_status_t		bi_status;
	// 磁盘对应的那个分区标识
	u8			bi_partno;

	// bio中的Segment的个数
	unsigned int		bi_phys_segments;

	// 缓存bio的第一个segment的长度,根据这个最大段长度快速判断是否能合并
	unsigned int		bi_seg_front_size;
	// 缓存bio的最后一个segment的长度
	unsigned int		bi_seg_back_size;

	struct bvec_iter	bi_iter;

	atomic_t		__bi_remaining;
	// bio的IO操作完成后回调函数
	bio_end_io_t		*bi_end_io;
	// 私有数据指针,在通用块设备层和驱动IO结束使用
	void			*bi_private;
#ifdef CONFIG_BLK_CGROUP
	/*
	 * Optional ioc and css associated with this bio.  Put on bio
	 * release.  Read comment on top of bio_associate_current().
	 */
	struct io_context	*bi_ioc;
	struct cgroup_subsys_state *bi_css;
	struct blkcg_gq		*bi_blkg;
	struct bio_issue	bi_issue;
#endif
	union {
#if defined(CONFIG_BLK_DEV_INTEGRITY)
		struct bio_integrity_payload *bi_integrity; /* data integrity */
#endif
	};
	// bio的bio_vec数组中包含segmnet的个数
	unsigned short		bi_vcnt;	/* how many bio_vec's */


	unsigned short		bi_max_vecs;	/* max bvl_vecs we can hold */

	atomic_t		__bi_cnt;	/* pin count */
	// 存储bio的数组
	struct bio_vec		*bi_io_vec;	

	struct bio_set		*bi_pool;
	// 内嵌的bio数组
	struct bio_vec		bi_inline_vecs[0];
};

内核的存储

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-05-26,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • I/O子系统概貌
  • I/O子系统中对象
  • I/O处理流程
  • 内核的存储栈
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档