超级块对象
super_block->s_inodes 表示所有的inode。
super_block->s_dirty 表示脏的inode。
super_block->s_io 表示等待被写入磁盘inode。
super_block->s_fs_info 指向特定超级块信息的指针
super_block->s_op表示超级块的方法
inode会复制磁盘索引节点的包含的一些数据
inode->i_hash 用于散列链表的指针(索引节点会被存放在一个inode_hashtable散列表中)
inode->i_list用于描述索引节点当前状态的链表指针
放在下列链表之一
有效未使用的索引节点链表(镜像有效的索引节点未被进程使用)
正在使用的索引节点链表(镜像有效的索引节点且被进程使用)
脏索引节点的链表(链表中的首元素和尾元素由相应的超级块对象s_dirty引用)
inode->i_sb_list 超级块索引节点的链表的指针(超级块会链接所有对应的inode)
inode->i_dentry 引用索引节点的目录项对象的链表头
inode->i_ino 索引节点号
inode->i_count 引用此索引节点的计数器
inode->i_op 索引节点的操作函数集包括创建删除等操作
inode->i_fop 缺省的文件操作函数
inode->i_sb 指向超级块对象的指针
目录项对象
dentry->d_hash 用于散列链表指针
dentry->d_lru 未使用的链表指针
dentry->d_subdirs 对目录而言子目录项链表的头
dentry->d_alias 用于对同一索引节点相关的目录项链表指针
dentry->d_inode 目录项关联的索引节点
dentry->d_op 目录项方法(名称散列函数、比较两个文件名称、释放目录项函数)
目录项对象在磁盘上没有对应的镜像
一个处于正在使用、未使用、负状态的目录项对象的集合
一个散列表,能够快速的根据名称找到对应的目录项,如果对象不存在返回空值
所有未使用的目录项都保存在lru双向链表中,存放在dentry_unused
正在使用的dentry插入到inode->i_dentry链表
当目录项变成负状态的dentry会被加入到lru里去(dentry_hashtable)。
进程描述符的fs_struct fs
fs->count 共享这个表的进程个数
fs->root根目录的目录项
fs->pwd 当前工作目录目录项
fs->altroot模拟根目录的目录项
fs->rootmnt 根目录所安装的文件系统对象
fs->pwdmnt 当前目录所安装的文件系统对象
fs->altrootmnt 模拟根目录所安装的文件系统对象
进程描述符的files_struct files
files->count 引用该表的进程数目
files->max_fds 文件对象的当前最大数目
files->max_fdset 文件描述符的当前最大数目(存放位图的位数)
files->next_fd 分配的最大文件描述符+1
files->fd 文件对象指针的数组指针
files->fd_array 文件对象指针初始化数组
files->close_on_exec指向执行exec需要关闭的文件描述符的指针
files->open_fds指向打开文件描述符的指针
files->close_on_exec_init 执行exec时需要关闭的文件描述符初始集合
files->open_fds_init 文件描述符的初始集合(当前已经打开文件的文件描述符位图)
每个注册的文件系统使用类型为file_system_type对象表示
所有的文件系统类型都插入到一个单向的链表中file_systems作为表头
file_system_type->name 文件系统名称
file_system_type->fs_flags文件系统类型标志
file_system_type->get_sb读超级块的方法
file_system_type->kill_sb删除超级块的方法
file_system_type->next 指向文件系统类型链表的下一个元素
file_system_type->fs_supers具有相同文件系统类型的超级块对象链表头
get_fs_type()扫描已注册的文件系统链表以查询文件系统类型的name字段并返回file_system_type对象
task_struct结构体里的 struct namespace变量
namespace->count引用计数
namespace->root命名空间根目录已安装文件系统描述符
namesapce->list 所有属于此命名空间已安装文件系统描述符链表的头
namespace->sem 保护此数据结构的读写信号量
已安装文件系统描述符vfsmount
vfsmount->mnt_hash 用于散列链表的指针
vfsmount->mnt_parent 指向父文件系统(覆盖目录的文件系统)
vfsmount->mnt_mountpoint 指向文件系统安装点目录的dentry
vfsmount->mnt_root 指向这个文件系统的根目录dentry
vfsmount->mnt_sb 指向这个文件系统的超级块对象
vfsmount->mnt_mounts 包含所有文件系统描述符链表的头(这个文件系统的子文件系统)
vfsmount->mnt_child用于已安装文件系统的链表的指针
vfsmount->mnt_count 引用计数器
vfsmount->mnt_flag 标志
vfsmount->mnt_expiry_mark 为true标志文件系统到期。设置了此标记且引用计数为0则会自动卸载此文件系统
vfsmount->mnt_devname 设备名称
vfsmount->mnt_fslink 具体文件系统到期的链表指针
vfsmount->mnt_namespace 指向安装了文件系统的进程命名空间的指针
由父文件系统描述符地址和安装点目录的目录项对象的地址作为索引散列表中,散列表存放在mount_hashtable。
对于每个命名空间属于此命名空间的已安装文件系统被放在namespace->list
所有已安装文件系统放在vfsmount->mnt_mounts
alloc_vfsmnt(const char *name)分配和初始化一个已安装文件系统描述符
free_vfsmnt(struct vfsmount *mnt) 释放由mnt指向的已安装文件系统描述符
lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)在根据父文件系统的地址和安装点目录项地址在散列表查找vfsmnt
安装rootfs
1、init_rootfs 主要是注册 rootfs文件系统,register_filesystem(&rootfs_fs_type)
2、init_mount_tree :主要是初始化namespace以及初始化mnt和init_task的以及其子线程的namespace
页高速缓存
1、address_space对象被inode->i_mapping指向,如果所有者是个文件则inode->i_data指向address_space
2、address_space->host指向这个inode
address_space->radix_tree_root
struct radix_tree_root {
unsigned int height;//树的当前深度
int gfp_mask;//分配内存标志
struct radix_tree_node *rnode;//指向树中第一层节点
};
struct radix_tree_node {
unsigned int count;//非空指针数量的计数器
void *slots[RADIX_TREE_MAP_SIZE];//保存指向页描述符的指针
unsigned long tags[RADIX_TREE_TAGS][RADIX_TREE_TAG_LONGS];//二维标志数组
};
find_get_page根据address_space和index索引值搜索指定的页描述符,找到返回也描述符地址找不到返回NULL.,此函数会增加页描述符使用计数。
此函数依赖radix_tree_lookup函数进行搜索。radix_tree_lookup函数会在深度范围内搜索。
find_lock_page函数与find_get_page非常类似只是其会额外设置page的PG_locked标志,即以互斥访问页描述符对应的页。此函数使用了lock_page函数,lock_page会等待标志位释放且用uninterruptiable等待,当内核其他路径使用unlock_page函数对页面进行解锁的时候会唤醒等待队列上的睡眠进程。
find_trylock_page与find_lock_page类似但是不会阻塞,如果请求不到锁则出错返回。
add_to_page_cache函数是插入到指定索引值并且根据需要会建立相应的node。
remove_from_page_cache从address_space删除指定的page,由于page里存储了address_space和index,所以此函数只接收一个参数
read_cache_page:读取页并且更新,如果页不存在则分配并插入到address_sapce和lru。并且判断PG_uptodate标志是否设置如果没有设置则调用传入参数filler更新page。
对于基树引入了PG_dirty和PG_writeback标志
设置标记函数radix_tree_tag_set和清除标志radix_tree_tag_clear
radix_tree_delete函数会删除指定的page且更新page原所在路径的标志位
radix_tree_tagged 判断整个树是否含有指定标志
find_get_pages_tag 函数是查找指定tag的页描述符
块缓存个页
struct buffer_head {
/* First cache line: /
unsigned long b_state; / buffer state bitmap (see above) */
对于b_state表示缓冲区首部通用标志
BH_Uptodate 缓冲区包含有效数据时被置位
BH_Dirty 缓冲区脏就置位
BH_Lock 缓冲区加锁置位
BH_Req缓冲区已经初始化请求数据传输置位
BH_Mapped缓冲区被映射到磁盘就置位
BH_New 相应的块刚被分配而还没被访问过就置位
BH_Async_Read 异步读缓冲区就置位
BH_Async_Write 异步写缓冲区就置位
BH_Delay 如果还没在磁盘上分配缓冲区就置位
BH_Boundary 如果相邻的块在一个提交之后不在相邻就置位
BH_write_EIO 如果写块时出现I/O错误就置位
BH_Ordered 如果必须严格的把块写到它之前提交的块的后面就置位
BH_Eopnotsupp 如果块的设备驱动程序不支持所请求的操作就置位
struct buffer_head *b_this_page;/* circular list of page's buffers */
struct page *b_page; /* the page this bh is mapped to /
atomic_t b_count; / users using this block /
u32 b_size; / block size */
sector_t b_blocknr; /* block number */
char *b_data; /* pointer to data block */
b_data字段存储的分两种情况,第一种如果page是高端内存则存储的是相对
地址页起始位置,第二种针对page是非高端内存,则存储的是线性地址。
struct block_device *b_bdev;
bh_end_io_t *b_end_io; /* I/O completion */
void *b_private; /* reserved for b_end_io */
b_private一般指向完成方法数据的指针
struct list_head b_assoc_buffers; /* associated with another mapping */
};
1、缓冲区有自己的slab高速缓存
2、一个页作为缓冲区页使用,那么与它的块缓冲区相关的所有缓冲区首部都被收集到一个单项链表中,链表中的第一个块设备缓冲区被页的private指向。
3、每个缓冲区首部放在b_this_page字段中,该字段指向链表中下一个缓冲区首部的指针
4、b_page存放缓冲区所在页描述符地址
5、页描述符的PG_private标志被设置且页描述符的privaet指向有效数据则该页是个缓冲区页
grow_buffers函数接收三个参数 bdev block size
1、计算数据页在所请求块设备中的偏移量index
2、调用grow_dev_page函数(bdev、block、index、size)
grow_dev_page函数
1、根据传入的index在(bdev->bd_inode->i_mapping)address_space中搜索,找不到则直接返 回NULL(表示也分配不到内存页了则可能是内存不足),且会对页面加锁设置PG_locked标志。
2、如果判断page的private有效且设置了PG_private标志则表示是缓冲页
3、如果页面没有buffer_head则调用alloc_page_buffers分配对应数量(PAGE_SIZE /size)的bh
4、调用link_dev_buffers将bh链接到page
5、调用init_page_buffers初始化分配的bh,注意这里的block是连续的,后续的bh是block++
3、释放lock标志以及减少page引用计数(grow_dev_page函数里的find_or_create_page->find_lock_page会加锁且增加引用计数),返回1成功
释放缓冲区
int try_to_release_page(struct page *page, int gfp_mask)
1、首先判断是否加锁,没加锁则直接报错
2、判断是否为脏页(PG_writeback),如果是则直接返回0
3、如果存在mapping && mapping->a_ops->releasepage则调用
4、调用之后直接返回
5不存在mapping || mapping->a_ops->releasepage、调用try_to_free_buffers(page)函数
try_to_free_buffers函数:
1、判断脏页以及锁标志
2、如果没被链接到address_space里则直接调用drop_buffers函数(解绑buffer_head,buffers_to_free = head)转第5步骤
3、加锁mapping->private_lock
4、调用drop_buffers函数(解绑buffer_head,buffers_to_free = head)
5、清除高速缓树的脏页标志
6、如果存在buffers_to_free 则释放buffers_to_free ,返回
查找指定块号的块缓冲页
_find_get_block(struct block_device *bdev, sector_t block, int size)
1、内核维持了一个小的percpu变量的磁盘高速缓冲数组bh_lrus,数组大小默认是BH_LRU_SIZE=8
2、static inline struct buffer_head *
lookup_bh_lru(struct block_device *bdev, sector_t block, int size)
此函数根据传入的bdev、block、size参数查找对应的bh,如果找到则将bh放入此cpu的bh_lrus数组中的第一个,其余之前的后移。增加bh使用计数,返回bh
3、static struct buffer_head *
__find_get_block_slow(struct block_device *bdev, sector_t block, int unused)
此函数根据块设备计算索引然后调用find_get_page参数查找页高速缓存里对应的页。找不到直接返回NULL。
a、找到了判断是否已经映射了块设备数据,如果没有则直接返回NULL
b、如果已经映射了快设备页面,获取此页面的bh头,按照bh->b_this_page单项链表查找匹配的bh,找到了则返回bh。
c、递减页描述符的使用计数,因为find_get_page函数会增加引用计数
4、找到bh后调用static void bh_lru_install(struct buffer_head *bh)函数
a、获取bh_lrus数组判断数组首元素是否是查找到的bh
b、如果不是,则增加bh引用计数,剩下步骤就是将刚刚查找到的bh放在数组的首位置,其他的放在其之后,如果数组原有的与此bh都不相等,则需要调用void __brelse(struct buffer_head * buf)函数去释放
5、调用touch_buffer标记bh对应的页描述符标记为访问过。
struct buffer_head *
__getblk(struct block_device *bdev, sector_t block, int size)
1、调用_find_get_block函数查找,如果找不到则说明对应的块号不在高速缓冲页里
2、调用static struct buffer_head * __getblk_slow(struct block_device *bdev, sector_t block, int size)函数去分配新的块缓冲页
a、循环做__find_get_block查找,找不到则调用grow_buffers函数去分配新的块缓冲页
b、如果grow_buffers函数失败则调用free_more_memory去释放内存
c、跳转到a步骤继续循环查找,查找到了bh直接返回bh
3、返回bh
struct buffer_head *
__bread(struct block_device *bdev, sector_t block, int size)
1、调用__getblk函数去获取bh
2、判断bh的BH_Uptodate标志是否建立,即判断是否被映射数据
3、如果没有被映射数据则调用static struct buffer_head *__bread_slow(struct buffer_head *bh)函数
a、加锁bh,再次判断BH_Uptodate标志
b、如果依旧没有设置BH_Uptodate标志则增加bh引用计数,利用end_buffer_read_sync函数初始化bh->b_end_io
c、调用submit_bh函数去下发io请求
d、 调用wait_on_buffer函数将当前进程插入到不可中断的等待队列中等待
e、检查BH_Uptodate标志,如果标志设置则返回bh否则调用brelse递减bh使用计数直接返回NULL
块的约束
1、块大小必须是2的幂且不能超过一个叶匡
2、必须是扇区大小的整数倍
通用块层
struct bio_vec {
struct page *bv_page; 段所在页描述符的指针
unsigned int bv_len; 段的字节长度
unsigned int bv_offset; 段数据的偏移量
};
struct bio {
sector_t bi_sector; 块io操作的第一个磁盘扇区
struct bio bi_next; / request queue link */链接请求队列的下一个bio
struct block_device *bi_bdev;指向块设备描述符的指针
unsigned long bi_flags; /* status, command, etc /bio的状态标志
unsigned long bi_rw; / bottom bits READ/WRITE,
* top bits priority
*/
unsigned short bi_vcnt;
bio数组中段的数目
unsigned short bi_idx;
bio数组中断段的当前索引值
unsigned short bi_phys_segments;
合并之后的bio中物理段的数目
unsigned short bi_hw_segments;
合并之后的bio中硬件段的数目
unsigned int bi_size;
需要传输的字节数
unsigned int bi_hw_front_size;
硬件段合并算法使用
unsigned int bi_hw_back_size;
硬件段合并算法使用
unsigned int bi_max_vecs;
bio的bio_vec数组中允许的最大段数
struct bio_vec *bi_io_vec;
当前bio的bio_vec数组指针
bio_end_io_t *bi_end_io;
bio的io操作结束时调用的方法
atomic_t bi_cnt;
bio的引用计数
void *bi_private;
通用块层和块设备驱动程序的io完成方法使用的指针
bio_destructor_t *bi_destructor;
释放
bio的析构方法
struct bio_set *bi_set;
用于内存池设置
};
在块设备dma传送不能全部完成,则bio->bi_idx会不断被更新指向待传送的第一个段
bio_for_each_segment宏可以用于从bio->bi_idx开始遍历
磁盘与磁盘分区
struct gendisk {
int major; 磁盘主设备号
int first_minor; 与磁盘关联的第一个次设备号
int minors; 与磁盘关联的次设备号范围(用于分区)
char disk_name[32]; 磁盘的名称
struct hd_struct *part; 磁盘分区的描述符数组
struct block_device_operations *fops; 指向块设备操作表的指针
struct request_queue *queue; 指向磁盘请求队列的指针
void *private_data; 块设备驱动层序的私有数据
sector_t capacity; 磁盘内存区的大小
int flags;
描述磁盘类型的标志
char devfs_name[64];
devfs特殊文件系统
int number;
不再使用
struct device *driverfs_dev;
指向磁盘硬件设备的对象
struct kobject kobj;
内嵌的kobject对象指针
struct timer_rand_state *random;
内核内置的随机数发生器使用
int policy;
磁盘只读标志置1,否则置0
atomic_t sync_io;
写入磁盘的扇区计数器仅仅位raid使用
unsigned long stamp, stamp_idle;
统计磁盘队列使用情况的时间戳
int in_flight;
正在进行的io操作数
#ifdef CONFIG_SMP
struct disk_stats *dkstats; 磁盘使用情况统计
#else
struct disk_stats dkstats;
#endif
};
struct block_device_operations {
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long);(使用大内核锁)
long (*compat_ioctl) (struct file *, unsigned, unsigned long);(不使用大内核锁)
int (*media_changed) (struct gendisk *);
int (*revalidate_disk) (struct gendisk *); 检查块设备是否存在有效数据
struct module *owner;
};
struct hd_struct {
sector_t start_sect; 起始扇区
sector_t nr_sects; 扇区数
struct kobject kobj; 内嵌的kobject
unsigned reads, 对分区发出的读操作次数
read_sectors, 从分区读取的扇区数
writes, 对分区发出的写操作数
write_sectors; 写进分区的扇区数
int policy, partno; 分区是否只读、分区号
};
void generic_make_request(struct bio *bio)函数
1、获取bio里的请求扇区数,获取bio指定设备的最大描述符
2、判断请求的描述符是否合理
3、如果不合理则调用static void handle_bad_sector(struct bio *bio)函数处理跳转到第5步
a、 打印错误消息
b、 设置bio->bi_flags标志BIO_EOF
4、获取io请求队列,如果获取不到请求队列,则调用void bio_endio(struct bio *bio, unsigned int bytes_done, int error)函数以EIO错误调用,在跳出循环直接返回
a、如果存在bio->bi_end_io则调用bio->bi_end_io函数
5、获取到了io请求队列,判断请求的数目是否大于请求队列最大处理数目,如果大于则跳转到第4步
6、判断io请求队列标志QUEUE_FLAG_DEAD如果设置了此标志跳转到第4步
7、调用block_wait_queue_running函数等待io调度程序运行
8、调用static inline void blk_partition_remap(struct bio *bio)函数处理是分区的情况
9、调用队列的请求函数q->make_request_fn处理
blk_init_queue函数会调用blk_queue_make_request初始化使用__make_request初始化q->
make_request_fn函数,
static int __make_request(request_queue_t *q, struct bio *bio)
1、如果需要调用blk_queue_bounce设置个回弹缓冲区
2、调用io调度程序的elv_queue_empty函数判断队列是否为空,如果为空则跳转到第4步
3、调用elv_merge函数去判断是否能合并bio请求
函数返回三个值ELEVATOR_BACK_MERGE或者ELEVATOR_FRONT_MERGE 调用elv_merged_request函数合并请求,然后跳转到第5步
4、不能合并则调用get_request函数申请一个新的req,初始化req,调用add_request函数将新的请求加入到对列中
5、如果freereq不为空则表示新申请的队列被合并,则新申请的freereq没被使用则释放掉,判断BIO_RW_SYNC标志是否被设置,如果被设置了则调用__generic_unplug_device解除队列阻塞
void blk_queue_bounce(request_queue_t *q, struct bio **bio_orig)
1、判断分配标志指定分配页框内存池
2、调用__blk_queue_bounce函数去创建回弹
static void __blk_queue_bounce(request_queue_t *q, struct bio **bio_orig,mempool_t *pool)
1、根据分配标志对于大于q->bounce_pfn的页框号分配一个新的页框
2、如果分配了新的页框则会分配新的bio,更新bio中页框,如果是写,则将旧的页框内容拷贝到新的页框中
3、将旧的bio中的合法的页框地址保存到新的bio页框中
4、使用旧的bio里的内容更新新的bio
5、更新新的bio里bi_end_io方法:bio->bi_end_io
6、将旧的bio保存在新的bio->private中,并将新的bio返回到出参中
void blk_plug_device(request_queue_t *q)//戒严当前的队列,使其不在继续运行。
1、测试和设置QUEUE_FLAG_STOPPED标志
2、启动q->unplug_timer,此定时器在blk_queue_make_request函数中被关联为blk_unplug_timeout函数
static void blk_unplug_timeout(unsigned long data)
1、函数很简单就是启动q->unplug_work工作队列,依旧在blk_queue_make_request函数中可以看到工作队列关联的函数是blk_unplug_work
static void blk_unplug_work(void *data)
1、此函数也就是调用 q->unplug_fn,在blk_init_queue函数可以看到此函数被初始化为generic_unplug_device
void generic_unplug_device(request_queue_t *q)
1、此函数也就是加q->queue_lock锁调用__generic_unplug_device函数
void __generic_unplug_device(request_queue_t *q)
1、调用blk_remove_plug函数解除阻塞
2、调用 q->request_fn处理下一个请求
总结:也就是调用了阻塞函数大概3ms后会调用阻塞函数继续处理队列请求
struct request_queue
{
/*
*Together with queue_head for cacheline sharing
*/
struct list_head queue_head;
//待处理请求的链表
struct request *last_merge;
//指向队列中首先可能合并的请求描述符
elevator_t *elevator;
//指向elevator
对象的指针
/*
* the queue request freelist, one for reads and one for writes
*/
struct request_list rq;
//为分配请求描述符所使用的数据结构
request_fn_proc *request_fn;
//实现驱动程序策略入口点的方法
merge_request_fn *back_merge_fn;
//检查是否可以将bio请求合并到请求队列最后一个请求中的方法
merge_request_fn *front_merge_fn;
//检查是否可以将bio请求合并到请求队列第一个请求中的方法
merge_requests_fn *merge_requests_fn;
//试图合并请求队列相邻请求的方法
make_request_fn *make_request_fn;
//将一个新请求插入到请求队列时调用的方法
prep_rq_fn *prep_rq_fn;
//该方法把这个处理请求的命令发送给硬件的方法
unplug_fn *unplug_fn;
//去掉块设备的方法
merge_bvec_fn *merge_bvec_fn;
//当增加一个新段的时候,该方法返回可插入到某个已存在的bio结构中的字节数
activity_fn *activity_fn;
//将某个请求加入到请求队列时调用的方法
issue_flush_fn *issue_flush_fn;
//刷新请求队列时调用的方法
prepare_flush_fn *prepare_flush_fn;
//刷新前的准备
end_flush_fn *end_flush_fn;
//刷新后的回调
/*
* Auto-unplugging state
*/
struct timer_list unplug_timer;//插入设备时使用的动态定时器
int unplug_thresh; //如果请求队列中待处理请求数大于该值
unsigned long unplug_delay; //去掉设备之前的时间延迟
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;//指向块设备驱动程序的私有指针
void *activity_data;//activity_fn
方法使用的私有数据
/*
* queue needs bounce pages for pages above this limit
*/
unsigned long bounce_pfn;//大于该页框号时必须使用回弹缓冲区
unsigned int bounce_gfp;//回弹缓冲区使用的内存分配标志
/*
* various queue flags, see QUEUE_* below
*/
unsigned long queue_flags;//描述请求队列状态的标志
/*
* protects queue structures from reentrancy. ->__queue_lock should
* _never_ be used directly, it is queue private. always use
* ->queue_lock.
*/
spinlock_t __queue_lock;//自旋锁
spinlock_t *queue_lock;//自旋锁指针
/*
* queue kobject
*/
struct kobject kobj;
/*
* queue settings
*/
unsigned long nr_requests; //请求队列允许的最大请求数
unsigned int nr_congestion_on;//队列是否拥挤的阈值
unsigned int nr_congestion_off;//队列是否拥挤的阈值,低于认为不拥挤
unsigned int nr_batching;//即使队列满,任然可以用batcher进程提交的待处理请求的最大值
unsigned short max_sectors;//单个请求所能处理最大的扇区数(可调)
unsigned short max_hw_sectors;//单个请求所能处理的最大扇区数(硬约束)
unsigned short max_phys_segments;//单个请求所能处理的最大物理段数
unsigned short max_hw_segments;//单个请求所能处理的最大硬段数
unsigned short hardsect_size;//扇区中以字节为单位的大小
unsigned int max_segment_size;//物理段的最大长度
unsigned long seg_boundary_mask;//段合并的内存边界屏蔽字
unsigned int dma_alignment;//dma缓冲区的起始地址与长度对齐位图
struct blk_queue_tag *queue_tags;//空闲、忙标记位图
atomic_t refcnt;//请求队列的引用计数器
unsigned int in_flight;//请求队列中待处理请求数
/*
* sg stuff
*/
unsigned int sg_timeout;//用户定义的命令超时
unsigned int sg_reserved_size;//基本上没使用
struct list_head drain_list;//临时延时的请求链表的首部
/*
* reserved for flush operations
*/
struct request *flush_rq;//刷新队列
unsigned char ordered;
};
struct request_list {
int count[2]; //read、write请求的请求描述符描述符数
int starved[2];//用于标记read、write请求分配是否失败
mempool_t *rq_pool;//指向请求描述符的内存池
wait_queue_head_t wait[2];//两个等待队列为了获得空闲的读、写请求描述符而睡眠的进程
wait_queue_head_t drain;//等待请求队列被刷新的进程
};
用于从一个特定请求队列内存池里获取一个描述符
struct request *blk_get_request(request_queue_t *q, int rw, int gfp_mask)//gfp_mask用于判断是否允许睡眠等待,成功返回对应的描述符,失败返回NULL,
如果指定了对应的__GFP_WAIT标志,则请求进程如果暂时分配不到描述符则进行TASK_UNINTERRUPTIBLE阻塞。
void blk_put_request(struct request *req) //释放一个请求描述符
long blk_congestion_wait(int rw, long timeout) //函数会挂起当前的进程,直到所有的请求队列都变为不用塞或超时已到。
1、队列链表中元素的排序方式对每个块设备驱动程序是特定的。io调度程序会提供几种预先确定好的元素排序方式
2、backing_dev_info字段是一个backing_dev_info类型的小对象,他存放了关于基本硬件块设备的io数据流量信息,保存了关于预读以及关于请求队列拥塞状态信息
3、每个块设备的待处理请求都是用一个请求描述符来表示
struct request {
struct list_head queuelist; //链接到请求描述符队列的链表指针
unsigned long flags; //请求标志。重要的标志如REQ_RW表示请求数据传送方向,详细见代码enum rq_flag_bits注释说明
sector_t sector;
//要传送的下一个扇区号
unsigned long nr_sectors;
//整个请求中需要传输的扇区数目
/* no. of sectors left to submit in the current segment */
unsigned int current_nr_sectors;
//如注释所言,当前bio段中剩余要传输的扇区数目
sector_t hard_sector;
//要传送的下一个扇区号
unsigned long hard_nr_sectors;
//整个请求中要传送的扇区数(由通用块层更新)
/* no. of sectors left to complete */
/* no. of sectors left to complete in the current segment */
unsigned int hard_cur_sectors;
//当前bio所在段中要传送的扇区数(由通用块层更新)
struct bio *bio;
//请求中第一个没有我完成传送的操作bio
struct bio *biotail;
//请求末尾的bio
void *elevator_private;
//指向io调度程序私有数据的指针
int rq_status;
//请求状态,RQ_ACTIVE、RQ_INACTIVE
struct gendisk *rq_disk;
//请求所引用的磁盘描述符
int errors;
//用于当前传送中io错误次数
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;//请求的相关标记
char *buffer;//指向当前数据传送的内存缓冲区的指针
int ref_count;//请求的使用计数器
request_queue_t *q;//指向包含此请求的请求队列描述符
struct request_list *rl;//指向request_list 指针
struct completion *waiting;//等待数据传送终止的completion结构
void *special;//对硬件设备发出特殊命令的请求
/*
* when request is used as a packet command carrier
*/
unsigned int cmd_len;//cmd字段中的命令长度
unsigned char cmd[BLK_MAX_CDB];//由请求队列的prep_rq_fn方法准备好的预先内置命令所在的缓冲区
unsigned int data_len;//指向data指向的缓冲区里的数据长度
void *data;//驱动程序为了跟踪所传送的数据而使用的指针
unsigned int sense_len;//由于sense字段指向的缓冲区的长度
void *sense;//指向输出sense命令的缓冲区指针
unsigned int timeout;//请求的超时
/*
* For Power Management requests
*/
struct request_pm_state *pm;//电源管理的数据结构包括suppend、resume等
/*
* completion callback. end_io_data should be folded in with waiting
*/
rq_end_io_fn *end_io;//请求完成的函数
void *end_io_data;//完成函数的参数
};