Linux内核设计笔记14——块I/O层
块I/O层
基本概念
系统中可以随机访问固定大小数据片的硬件设备称做块设备,这些固定大小的数据片称之为块。还有一种基本的设备称之为字符设备,其需要按照顺序访问,比如键盘。
- 扇区:块设备中最小的寻址单元称为扇区,扇区是块设备的物理属性。
- 块: 文件系统最小的逻辑可寻址单元。是文件系统的一种抽象。
- 缓冲区: 当一个快被调入内存时候,存在一个缓冲区中。每个缓冲区与一个块对应,相当于磁盘块在内存中的表示。
- 缓冲区头: 每个缓冲区都有一个描述符 buffer_head ,用来描述内核处理数据时的相关控制信息。
struct buffer_head {
	unsigned long b_state;		/* buffer state bitmap (see below) */
	struct buffer_head *b_this_page;/* circular list of page's buffers */
	struct page *b_page;		/* the page this bh is mapped to */
	sector_t b_blocknr;		/* start block number */
	size_t b_size;			/* size of mapping */
	char *b_data;			/* pointer to data within the page */
	struct block_device *b_bdev;
	bh_end_io_t *b_end_io;		/* I/O completion */
 	void *b_private;		/* reserved for b_end_io */
	struct list_head b_assoc_buffers; /* associated with another mapping */
	struct address_space *b_assoc_map;	/* mapping this buffer is
						   associated with */
	atomic_t b_count;		/* users using this buffer_head */
};
// b_state成员的标志如下所示:
enum bh_state_bits {
	BH_Uptodate,	/* Contains valid data */
	BH_Dirty,	/* Is dirty */
	BH_Lock,	/* Is locked */
	BH_Req,		/* Has been submitted for I/O */
	BH_Uptodate_Lock,/* Used by the first bh in a page, to serialise
			  * IO completion of other buffers in the page
			  */
	BH_Mapped,	/* Has a disk mapping */
	BH_New,		/* Disk mapping was newly created by get_block */
	BH_Async_Read,	/* Is under end_buffer_async_read I/O */
	BH_Async_Write,	/* Is under end_buffer_async_write I/O */
	BH_Delay,	/* Buffer is not yet allocated on disk */
	BH_Boundary,	/* Block is followed by a discontiguity */
	BH_Write_EIO,	/* I/O error on write */
	BH_Ordered,	/* ordered write */
	BH_Eopnotsupp,	/* operation not supported (barrier) */
	BH_Unwritten,	/* Buffer is allocated on disk but not written */
	BH_Quiet,	/* Buffer Error Prinks to be quiet */
	BH_PrivateStart,/* not a state bit, but the first bit available
			 * for private allocation by other entities
			 */
};
缓冲区头结构的第一个成员是b_state表示缓冲区状态,其可以是一种或几种标志的组合。
b_count是缓冲区使用计数,可以使用get_bh()和put_bh()对该成员进行增减。在使用缓冲区之前应该首先使用get_bh()增加缓存区计数,使用完之后使用put_bh()减少其使用计数。
与缓冲区对应的物理磁盘块由b_blocknr_th成员索引,该值是b_bdev指明的块设备中的逻辑块号。
与缓冲区对应的而物理内存页是由b_page表示,另外b_data直接指向相应的块(位于b_page所指向的页的某个位置上),块的大小由b_size表示,起始位置在b_data处,结束位置在b_data+b_size处。
缓冲区头的目的在于描述磁盘快和物理缓冲区之间的映射关系。
bio结构体
内核中块IO的基本操作由bio结构体表示,该结构体代表了正在活动的以片断链表形式组织的块IO操作,一个片段是一小块连续的内存缓冲区。这样的话就不需要保证单个缓冲区一定要连续起来。
struct bio {
	sector_t				bi_sector;	/* device address in 512 byte 磁盘上的扇区
										sectors */
	struct bio				*bi_next;	/* request queue link */
	struct block_device		*bi_bdev;
	unsigned long			bi_flags;	/* status, command, etc */
	unsigned long			bi_rw;		/* bottom bits READ/WRITE,
										 * top bits priority
										 */
	unsigned short			bi_vcnt;	/* how many bio_vec's */
	unsigned short			bi_idx;		/* current index into bvl_vec */
	/* Number of segments in this BIO after
	 * physical address coalescing is performed.
	 */
	unsigned int			bi_phys_segments;
	unsigned int			bi_size;	/* residual I/O count */
	/*
	 * To keep track of the max segment size, we account for the
	 * sizes of the first and last mergeable segments in this bio.
	 */
	unsigned int			bi_seg_front_size;
	unsigned int			bi_seg_back_size;
	unsigned int			bi_max_vecs;	/* max bvl_vecs we can hold */
	unsigned int			bi_comp_cpu;	/* completion CPU */
	atomic_t				bi_cnt;		/* pin count */
	struct bio_vec			*bi_io_vec;	/* the actual vec list */
	bio_end_io_t			*bi_end_io;
	void					*bi_private;
#if defined(CONFIG_BLK_DEV_INTEGRITY)
	struct bio_integrity_payload *bi_integrity;  /* data integrity */
#endif
	bio_destructor_t		*bi_destructor;	/* destructor */
	/*
	 * We can inline a number of vecs at the end of the bio, to avoid
	 * double allocations for a small number of bio_vecs. This member
	 * MUST obviously be kept at the very end of the bio.
	 */
	struct bio_vec			bi_inline_vecs[0];
};
bi_io_vec指向一个bio_vec结构体数组,每个bio_vec结构包含<page, offset, len>三个元素,描述一个特定片断:片断所在的物理页、块在物理页中的偏移,从给定偏移量开始的块长度。
bi_vcnt表示bi_io_vec所指向的数组中bio_vec的数量。当块IO操作执行完后,bi_idx指向数组的当前索引。
每个IO请求都通过一个bio结构体表示,每个请求包含了一个或多个块,这些块存储在bio_vec中。bio_vec结构体描述了每个片断在物理页中的实际位置。bi_idx指向数组中当前的bio_vec片断,块I/O层可以通过它跟踪块IO完成的进度。
缓冲区头和bio结构体之间有着明显的差别,bio结构体代表的是IO操作,它可以包括内存中的一个或多个页;而另一方面,buffer_head结构体代表的是一个缓冲区,它描述的仅仅是磁盘中的一个块。因为缓冲区头是关联单独页中的单独块,所以它可能引起不必要的分割,将请求按块进行分割,只能靠以后重新组合。bio结构体是轻量级的,他表述的块不需要连续存储区,并且不需要分割I/O操作。
请求队列
块设备将它们挂起的块IO请求保存在请求队列中,该队列有request_queue结构体表示,定义在文件<linux/blkdev.h>中,包含一个双向请求链表以及相关的控制信息。通过内核中文件系统这样的高层代码将请求加入到队列中。请求队列只要不为空,队列对应的块设备驱动程序就从队列中获取请求,然后将其送到对应的块设备上去。
I/O调度程序
内核不会简单的按照请求产生的次序将IO请求提供给相应的块设备,而是进行了一定的优化。
I/O调度程序将磁盘I/O资源分配给系统中挂起的块I/O请求。I/O调度程序觉得队列中请求的排序,以及在什么时候将请求发送到块设备。具体来说,这种资源分配是通过将请求队列中挂起的请求进行请求合并和排序来完成的。
进程调度程序是将处理器资源分配给系统中运行的进程。
合并是指将两个多多个请求结合成一个新请求,比如有两个请求是从磁盘的相邻位置读取数据,那么就可以将两个请求合并为一个请求。合并请求可以减少对磁盘寻址的次数,从而减少系统开销。
排列是指将请求队列按照磁盘扇区增长的方向排列,这样可以使磁盘头始终沿着同一个方向访问磁盘,减小了访问磁盘的时间。
linus电梯——I/O调度程序
当有新的调度程序加入请求队列时,首先检查在已有队列中是否有请求可以与之合并;若没有就插入请求队列,插入时要保证整个请求队列依然时沿着扇区方向的,若没有合适的位置,就插入到队列尾部;除此之外,当发现队列中有队列驻留时间过长的请求,那么新的请求也会加入到尾队列尾部。这是为了避免相邻磁盘的请求太多而造成的访问其他位置的请求难以得到执行。
最终期限I/O调度程序
为了解决I/O调度程序饥饿而提出的方法,出于减少磁盘访问时间的考虑,对某个磁盘区域的繁重操作显然会使其他区域的访问得不到执行。
I/O请求中,写操作通常是在内核有空的时候才将请求提交给磁盘,也就是是说写操作和提交给他的应用程序异步执行。而读操作恰恰相反,通常当应用程序提交一个读请求时,应用程序会发生阻塞,直到读请求被满足,即读操作与提交他的应用程序同步执行。可见,读请求对系统性能性能影响很大。
在最后期限IO调度程序中,每个请求都有一个超时时间(比如 读500ms, 写5s)。最后期限调度类似于Linus电梯,也是以物理磁盘扇区增长的顺序排序的。当新的请求到来也是类似于linus电梯方式进行合并和插入。但是,最后期限调度程序也会同时将请求插入到额外的队列中。读请求按照次序被插入到特定的FIFO队列中,写请求被插入到特定的FIFO队列中。虽然普通队列是以磁盘扇区顺序排列的,但是着这两个额外的队列是FIFO队列(先进先出)。普通情况下,是将排序队列头部的请求不断发送给磁盘。
如果在写FIFO队列头,或者在读FIFO队列头的请求超时,那么最后期限调度程序便会在FIFO队列中提取请求进行操作。该方法可以一定程度上减小饥饿情况的发生。
预测I/O调度程序
预测I/O调度程序在最终期限调度程序的基础上,增加了一个空闲时间,也就说当请求提交后,不会立即返回处理其他请求,而是可以空闲几个ms,等待磁盘相邻位置的请求。对于应用程序来说,这是一个提交请求的机会,任何相邻磁盘位置操作请求都会得到立即处理。在等待时间结束后,预测IO处理程序返回原来位置基础执行以前剩下的请求。如果一个相邻磁盘位置的请求在空闲时刻到来,那么它就会立刻得到执行,从而减小了二次寻址。
完全公正的排队I/O调度程序
该方法是为了专有工作负荷设计的,在实际中也为多挣工作负荷提供了较好的性能。该方法是将I/O请求根据引起该请求的进程来组织的,比如进程foo发起的请求放进foo队列,bar进程发起的请求放入bar队列。然后,以时间轮片一次从每个队列中选取请求。
空操作I/O调度程序
所谓空操作是指,该调度方法仅仅进行请求的合并,而不进行其他操作。这主要针对闪存这一类的块设备,这类设备是正在的随机存储,没有必要按序访问。
Linux内核设计笔记14——块I/O层的更多相关文章
- Linux内核设计笔记10——内核同步
		Linux内核同步笔记 几个基本概念 - 临界区(critical region):访问和操作共享数据的代码段: - 原子操作:操作在执行中不被打断,要么不执行,要么执行完: - 竞争条件: 两个线程 ... 
- Linux内核设计笔记12——内存管理
		内存管理学习笔记 页 页是内核管理内存的基本单位,内存管理单元(MMU,管理内存并把虚拟地址转化为物理地址的硬件)通常以页为单位进行处理,从虚拟内存的角度看,页就是最小单位. struct page{ ... 
- Linux内核设计笔记7——中断
		中断与中断处理 何为中断? 一种由设备发向处理器的电信号 中断不与处理器时钟同步,随时可以发生,内核随时可能因为中断到来而被打断. 每一个中断都有唯一一个数字标志,称之为中断线(IRQ) 异常是由软件 ... 
- Linux内核设计笔记13——虚拟文件系统
		虚拟文件系统 内核在它的底层文件系统系统接口上建立一个抽象层,该抽象层使Linux可以支持各种文件系统,即便他们在功能和行为上存在很大差异. VFS抽象层定义了各个文件系统都支持的基本的.概念上的接口 ... 
- Linux内核设计笔记8——下半部
		# 下半部笔记 1. 软中断 软中断实现 软中断是在编译期间静态分配,其结构如下所示,结构中包含一个接受该结构体指针作为参数的action函数. struct softirq_action{ void ... 
- Linux内核设计笔记11——定时器
		定时器与时间管理笔记 内核中的时间 时钟中断:内核中的系统定时器以某种频率触发中断,该频率可以通过编程预定. 节拍率HZ:时钟中断的频率称为节拍率. 节拍:相邻两次中断的时间间隔称为节拍,1/节拍率. ... 
- 初探内核之《Linux内核设计与实现》笔记上
		内核简介 本篇简单介绍内核相关的基本概念. 主要内容: 单内核和微内核 内核版本号 1. 单内核和微内核 原理 优势 劣势 单内核 整个内核都在一个大内核地址空间上运行. 1. 简单.2. 高效 ... 
- 《Linux内核设计与实现》读书笔记 - 目录 (完结)
		读完这本书回过头才发现, 第一篇笔记居然是 2012年8月发的, 将近一年半的时间才看完这本书(汗!!!). 为了方便以后查看, 做个<Linux内核设计与实现>读书笔记 的目录: < ... 
- 《Linux内核设计与实现》读书笔记(十七)- 设备与模块
		本章主要讨论与linux的设备驱动和设备管理的相关的4个内核成分,设备类型,模块,内核对象,sysfs. 主要内容: 设备类型 内核模块 内核对象 sysfs 总结 1. 设备类型 linux中主要由 ... 
随机推荐
- oracle使用DBMS_RANDOM包生成随机数据
			(一)DBMS_RANDOM包信息 DBMS_RANDOM包包含3个存储过程,4个函数,1个类型,一共8个模块,如下. SQL> desc dbms_random Element Type -- ... 
- Nodejs中获取参数以及处理参数
			先看题干效果 在这里我们建了一个表单 填入表单需要提交的信息 对两个参数进行获取和一个加法计算 表单html代码 <form action='http://localhost:8080' met ... 
- meven 配置
			配置meven 自定义文件[User Settings] D:\java\apache-maven-3.5.3\conf\settings.xml settings.xml 新增两端配置信息 < ... 
- 消息队列MSMQ的使用
			1.MSMQ安装 控制面板-程序和功能-打开或关闭Windows功能-Microsoft Message Queue(MSMQ)服务器,选中所有,点击确定. 2.消息队列的应用场景(转载自http:/ ... 
- Python-爬虫小计
			# -*-coding:utf8-*-import requestsfrom bs4 import BeautifulSoupimport timeimport osimport urllibimpo ... 
- php ecshop 二级域名切换跳转时session不同步,解决session无法共享同步导致无法登陆或者无法退出的问题
			echshop基础上做了单点登录的 一级域名与二级域名 退出时 清空session 都是一级域名的session 因为二级域名的session是设置在二级域名上的 echshop基础上没有做单点登录的 ... 
- sublime text 3安装 package control 插件的方法
			自动安装的方法 - 快捷键ctrl+` 或者View->Show Console,输入如下代码 import urllib.request,os;pf='Package Control.sub ... 
- rtsp over tcp并设置多个options
			版权声明:本文为博主原创文章,未经博主允许不得转载. var vlc=document.getElementById("vlc"); var options = new Array ... 
- transient是干嘛的
			Java的serialization提供了一种持久化对象实例的机制.当持久化对象时,可能有一个特殊的对象数据成员,我们不想用 serialization机制来保存它.为了在一个特定对象的一个域上关闭s ... 
- Vue.js核心概念
			# 1. Vue.js是什么? 1). 一位华裔前Google工程师(尤雨溪)开发的前端js库 2). 作用: 动态构建用户界面 3). 特点: * 遵循MVVM模式 * 编码简洁, 体积小, 运行效 ... 
