一:前言
文件的读写是文件系统中最核心也是最复杂的一部份,它牵涉到了很多的概念.之前分析文件系统其它操作的时候,遇到与文件系统相关的读写部份都忽略过去了.在这一节里,来讨论一下文件的读写是怎样实现的.
二:I/O请求的概述
如之前所提到的,为了提高文件的操作效率,文件系统中的内容都是缓存在内存里的.每当发起一个Rear/Write请求的时候,都会到页面高速缓存中寻找具体的页面.如果页面不存在,则在页面高速缓存中建立相关页面的缓存.如果当前的页面不是最新的.那就必须要到具体的文件系统中读取数据了.一般来说,内核提供了这样的界面:它产生一个I/O请求.这个界面为上层隐藏了下层的不同实现.在这个界面中,将产生的I/O请求提交给I/O调度.再与I/O调度调用具体的块设备驱动程序.
整个过程如下图所示:
上图中的Generic Block Layer就是上面描述中所说的I/O的界面.
接下来我们以上图从下到上的层次进行讨论.
三:块设备驱动
块设备与字符设备的区别在于:块设备可以随机的访问,例如磁盘.正是因为它可以随机访问,内核才需要一个高效的手段去管理每一个块设备.例如对磁盘的操作,每次移动磁针都需要花不少的时候,所以尽量让其处理完相同磁道内的请求再将磁针移动到另外的磁道.而对于字符设备来说,不存在这样的顾虑,只需按顺序从里面读/写就可以了.
先来看一下块设备驱动所涉及到的数据结构.
3.1: block_device结构:
struct block_device {
//主次驱备号
dev_t bd_dev; /* not a kdev_t - it's a search key */
//指向bdev文件系统中块设备对应的文件索引号
struct inode * bd_inode; /* will die */
//计数器,统计块驱备被打开了多少次
int bd_openers;
// 块设备打开和关闭的信号量
struct semaphore bd_sem; /* open/close mutex */
//禁止在块设备上建行新安装的信号量
struct semaphore bd_mount_sem; /* mount mutex */
//已打开的块设备文件inode链表
struct list_head bd_inodes;
//块设备描述符的当前拥有者
void * bd_holder;
//统计字段,统计对bd_holder进行更改的次数
int bd_holders;
//如果当前块设备是一个分区,此成员指向它所属的磁盘的设备
//否则指向该描述符的本身
struct block_device * bd_contains;
//块大小
unsigned bd_block_size;
//指向分区描述符的指针
struct hd_struct * bd_part;
/* number of times partitions within this device have been opened. */
//统计字段,统计块设备分区被打开的次数
unsigned bd_part_count;
//读取块设备分区表时设置的标志
int bd_invalidated;
//指向块设备所属磁盘的gendisk
struct gendisk * bd_disk;
//指向块设备描述符链表的指针
struct list_head bd_list;
//指向块设备的专门描述符backing_dev_info
struct backing_dev_info *bd_inode_backing_dev_info;
/*
* Private data. You must have bd_claim'ed the block_device
* to use this. NOTE: bd_claim allows an owner to claim
* the same device multiple times, the owner must take special
* care to not mess up bd_private for that case.
*/
//块设备的私有区
unsigned long bd_private;
}
通常,对于块设备来说还涉及到一个分区问题.分区在内核中是用hd_struct来表示的.
3.2: hd_struct结构:
struct hd_struct {
//磁盘分区的起始扇区
sector_t start_sect;
//分区的长度,即扇区的数目
sector_t nr_sects;
//内嵌的kobject
struct kobject kobj;
//分区的读操作次数,读取扇区数,写操作次数,写扇区数
unsigned reads, read_sectors, writes, write_sectors;
//policy:如果分区是只读的,置为1.否则为0
//partno:磁盘中分区的相对索引
int policy, partno;
}
每个具体的块设备都会都应一个磁盘,在内核中磁盘用gendisk表示.
3.3: gendisk结构:
struct gendisk {
//磁盘的主驱备号
int major; /* major number of driver */
//与磁盘关联的第一个设备号
int first_minor;
//与磁盘关联的设备号范围
int minors; /* maximum number of minors, =1 for
* disks that can't be partitioned. */
//磁盘的名字
char disk_name[32]; /* name of major driver */
//磁盘的分区描述符数组
struct hd_struct **part; /* [indexed by minor] */
//块设备的操作指针
struct block_device_operations *fops;
//指向磁盘请求队列指针
struct request_queue *queue;
//块设备的私有区
void *private_data;
//磁盘内存区大小(扇区数目)
sector_t capacity;
//描述磁盘类型的标志
int flags;
//devfs 文件系统中的名字
char devfs_name[64]; /* devfs crap */
//不再使用
int number; /* more of the same */
//指向磁盘中硬件设备的device指针
struct device *driverfs_dev;
//内嵌kobject指针
struct kobject kobj;
//记录磁盘中断定时器
struct timer_rand_state *random;
//如果只读,此值为1.否则为0
int policy;
//写入磁盘的扇区数计数器
atomic_t sync_io; /* RAID */
//统计磁盘队列使用情况的时间戳
unsigned long stamp, stamp_idle;
//正在进行的I/O操作数
int in_flight;
//统计每个CPU使用磁盘的情况
#ifdef CONFIG_SMP
struct disk_stats *dkstats;
#else
struct disk_stats dkstats;
#endif
}
以上三个数据结构的关系,如下图所示:
如上图所示:
每个块设备分区的bd_contains会指它的总块设备节点,它的bd_part会指向它的分区表.bd_disk会指向它所属的磁盘.
从上图中也可以看出:每个磁盘都会对应一个request_queue.对于上层的I/O请求就是通过它来完成的了.它的结构如下:
3.4:request_queue结构:
struct request_queue
{
/*
* Together with queue_head for cacheline sharing
*/
//待处理请求的链表
struct list_head queue_head;
//指向队列中首先可能合并的请求描述符
struct request *last_merge;
//指向I/O调度算法指针
elevator_t elevator;
/*
* the queue request freelist, one for reads and one for writes
*/
//为分配请请求描述符所使用的数据结构
struct request_list rq;
//驱动程序策略例程入口点的方法
request_fn_proc *request_fn;
//检查是否可能将bio合并到请求队列的最后一个请求的方法
merge_request_fn *back_merge_fn;
//检查是否可能将bio合并到请求队列的第一个请求中的方法
merge_request_fn *front_merge_fn;
//试图合并两个相邻请求的方法
merge_requests_fn *merge_requests_fn;
//将一个新请求插入请求队列时所调用的方法
make_request_fn *make_request_fn;
//该方法反这个处理请求的命令发送给硬件设备
prep_rq_fn *prep_rq_fn;
//去掉块设备方法
unplug_fn *unplug_fn;
//当增加一个新段时,该方法驼回可插入到某个已存在的bio 结构中的字节数
merge_bvec_fn *merge_bvec_fn;
//将某个请求加入到请求队列时,会调用此方法
activity_fn *activity_fn;
//刷新请求队列时所调用的方法
issue_flush_fn *issue_flush_fn;
/*
* Auto-unplugging state
*/
//插入设备时所用到的定时器
struct timer_list unplug_timer;
//如果请求队列中待处理请求数大于该值,将立即去掉请求设备
int unplug_thresh; /* After this many requests */
//去掉设备之间的延迟
unsigned long unplug_delay; /* After this many jiffies */
//去掉设备时使用的操作队列
struct work_struct unplug_work;
//
struct backing_dev_info backing_dev_info;
/*
* The queue owner gets to use this for whatever they like.
* ll_rw_blk doesn't touch it.
*/
//指向块设备驱动程序中的私有数据
void *queuedata;
//activity_fn()所用的参数
void *activity_data;
/*
* queue needs bounce pages for pages above this limit
*/
//如果页框号大于该值,将使用回弹缓存冲
unsigned long bounce_pfn;
//回弹缓存区页面的分配标志
int bounce_gfp;
/*
* various queue flags, see QUEUE_* below
*/
//描述请求队列的标志
unsigned long queue_flags;
/*
* protects queue structures from reentrancy
*/
//指向请求队列锁的指针
spinlock_t *queue_lock;
/*
* queue kobject
*/
//内嵌的kobject
struct kobject kobj;
/*
* queue settings
*/
//请求队列中允许的最大请求数
unsigned long nr_requests; /* Max # of requests */
//如果待请求的数目超过了该值,则认为该队列是拥挤的
unsigned int nr_congestion_on;
//如果待请求数目在这个阀值下,则认为该队列是不拥挤的
unsigned int nr_congestion_off;
//单个请求所能处理的最大扇区(可调的)
unsigned short max_sectors;
//单个请求所能处理的最大扇区(硬约束)
unsigned short max_hw_sectors;
//单个请求所能处理的最大物理段数
unsigned short max_phys_segments;
//单个请求所能处理的最大物理段数(DMA的约束)
unsigned short max_hw_segments;
//扇区中以字节 为单位的大小
unsigned short hardsect_size;
//物理段的最大长度(以字节为单位)
unsigned int max_segment_size;
//段合并的内存边界屏弊字
unsigned long seg_boundary_mask;
//DMA缓冲区的起始地址和长度的对齐
unsigned int dma_alignment;
//空闲/忙标记的位图.用于带标记的请求
struct blk_queue_tag *queue_tags;
//请求队列的引用计数
atomic_t refcnt;
//请求队列中待处理的请求数
unsigned int in_flight;
/*
* sg stuff
*/
//用户定义的命令超时
unsigned int sg_timeout;
//Not Use
unsigned int sg_reserved_size;
}
request_queue表示的是一个请求队列,每一个请求都是用request来表示的.
3.5: request结构:
struct request {
//用来形成链表
struct list_head queuelist; /* looking for ->queue? you must _not_
* access it directly, use
* blkdev_dequeue_request! */
//请求描述符的标志
unsigned long flags; /* see REQ_ bits below */
/* Maintain bio traversal state for part by part I/O submission.
* hard_* are block layer internals, no driver should touch them!
*/
//要传送的下一个扇区
sector_t sector; /* next sector to submit */
//要传送的扇区数目
unsigned long nr_sectors; /* no. of sectors left to submit */
/* no. of sectors left to submit in the current segment */
//当前bio段传送扇区的数目
unsigned int current_nr_sectors;
//要传送的下一个扇区号
sector_t hard_sector; /* next sector to complete */
//整个过程中要传送的扇区号
unsigned long hard_nr_sectors; /* no. of sectors left to complete */
/* no. of sectors left to complete in the current segment */
//当前bio段要传送的扇区数目
unsigned int hard_cur_sectors;
/* no. of segments left to submit in the current bio */
//
unsigned short nr_cbio_segments;
/* no. of sectors left to submit in the current bio */
unsigned long nr_cbio_sectors;
struct bio *cbio; /* next bio to submit */
//请求中第一个没有完成的bio
struct bio *bio; /* next unfinished bio to complete */
//最后的bio
struct bio *biotail;
//指向I/O调度的私有区
void *elevator_private;
//请求的状态
int rq_status; /* should split this into a few status bits */
//请求所引用的磁盘描述符
struct gendisk *rq_disk;
//统计传送失败的计数
int errors;
//请求开始的时间
unsigned long start_time;
/* Number of scatter-gather DMA addr+len pairs after
* physical address coalescing is performed.
*/
//请求的物理段数
unsigned short nr_phys_segments;
/* Number of scatter-gather addr+len pairs after
* physical and DMA remapping hardware coalescing is performed.
* This is the number of scatter-gather entries the driver
* will actually have to deal with after DMA mapping is done.
*/
//请求的硬段数
unsigned short nr_hw_segments;
//与请求相关的标识
int tag;
//数据传送的缓冲区,如果是高端内存,此成员值为NULL
char *buffer;
//请求的引用计数
int ref_count;
//指向包含请求的请求队列描述符
request_queue_t *q;
struct request_list *rl;
//指向数据传送终止的completion
struct completion *waiting;
//对设备发达“特殊请求所用到的指针”
void *special;
/*
* when request is used as a packet command carrier
*/
//cmd中的数据长度
unsigned int cmd_len;
//请求类型
unsigned char cmd[BLK_MAX_CDB];
//data中的数据长度
unsigned int data_len;
//为了跟踪所传输的数据而使用的指针
void *data;
//sense字段的数据长度
unsigned int sense_len;
//指向输出sense缓存区
void *sense;
//请求超时
unsigned int timeout;
/*
* For Power Management requests
*/
//指向电源管理命令所用的结构
struct request_pm_state *pm;
}
请求队列描述符与请求描述符都很复杂,为了简化驱动的设计,内核提供了一个API,供块设备驱动程序来初始化一个请求队列.这就是blk_init_queue().它的代码如下:
//rfn:驱动程序自动提供的操作I/O的函数.对应请求队列的request_fn
//lock:驱动程序提供给请求队列的自旋锁
request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
{
request_queue_t *q;
static int printed;
//申请请求队列描述符
q = blk_alloc_queue(GFP_KERNEL);
if (!q)
return NULL;
//初始化q->request_list
if (blk_init_free_list(q))
goto out_init;
if (!printed) {
printed = 1;
printk("Using %s io scheduler\n", chosen_elevator->elevator_name);
}
//初始化请求队列描述符中的各项操作函数
q->request_fn = rfn;
q->back_merge_fn = ll_back_merge_fn;
q->front_merge_fn = ll_front_merge_fn;
q->merge_requests_fn = ll_merge_requests_fn;
q->prep_rq_fn = NULL;
q->unplug_fn = generic_unplug_device;
q->queue_flags = (1 << QUEUE_FLAG_CLUSTER);
q->queue_lock = lock;
blk_queue_segment_boundary(q, 0xffffffff);
//设置q->make_request_fn函数,初始化等待队对列的定时器和等待队列
blk_queue_make_request(q, __make_request);
//设置max_segment_size,max_hw_segments,max_phys_segments
blk_queue_max_segment_size(q, MAX_SEGMENT_SIZE);
blk_queue_max_hw_segments(q, MAX_HW_SEGMENTS);
blk_queue_max_phys_segments(q, MAX_PHYS_SEGMENTS);
/*
* all done
*/
//设置等待队列的I/O调度程序
if (!elevator_init(q, chosen_elevator))