这一章不准备介绍太多,以后还会补充,主要介绍一下块设备驱动的大致流程。
一、 磁盘和磁盘分区的表示
磁盘是一个由通用块层处理的逻辑块设备。通常一个磁盘对应一个硬件块设备,如硬盘,软盘或光盘。磁盘是由gendisk对象描述。
struct gendisk {
01 int major;
02 int first_minor;
03 int minors;
04 char disk_name[DISK_NAME_LEN]; /* name of major driver */
05 struct disk_part_tbl *part_tbl;
06 struct hd_struct part0;
07 struct block_device_operations *fops;
08 struct request_queue *queue;
09 void *private_data;
10 int flags;
11 struct device *driverfs_dev; // FIXME: remove
12 struct kobject *slave_dir;
13
14 struct timer_rand_state *random;
15
16 atomic_t sync_io; /* RAID */
17 struct work_struct async_notify;
18 int node_id;
19};
第1行Major磁盘主设备号
第2行与磁盘关联的第一个次设备号
第3行与磁盘关联的次设备号范围
第4行磁盘的标准名称
第6行磁盘的分区描述符数组
第7行指向块设备操作表的指针
第8行指向磁盘请求队列的指针
第9行块设备驱动程序的私有数据
第10行描述磁盘类型的标志
第11行指向磁盘的硬件设备的device对象的指针
第12行内嵌的kobject结构
第14行该指针指向的这个数据结构记录磁盘中断的定时;由内核内置的随机数发生器使用
第16行写入磁盘的数计数器,仅为RAID使用
fops所指向的字段是gendisk对象的操作方法,和字符设备一样,它也提供了一些文件操作的接口。其方法如下
struct block_device_operations {
01 int (*open) (struct block_device *, fmode_t);
02 int (*release) (struct gendisk *, fmode_t);
03 int (*locked_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
04 int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
05 int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
06 int (*direct_access) (struct block_device *, sector_t,
07 void **, unsigned long *);
08 int (*media_changed) (struct gendisk *);
09 int (*revalidate_disk) (struct gendisk *);
10 int (*getgeo)(struct block_device *, struct hd_geometry *);
11 struct module *owner;
12};
第1行打开块设备文件
第2行关闭对块设备文件的最后一个引用
第4行在块设备文件上发出ioctl系统调用
第5行在块设备文件上发出ioctl系统调用(不使用大内核锁)
第8行检查可移动介质是否已经变化
第9行检查块设备是否有有效数据。
通常情况下,磁盘被分成几个逻辑分区,每个块设备文件要代表整个磁盘,要么代表磁盘中的某一个分区。如果一个磁盘分成几个分区,那么其分区表保存在hd_struct结构的数组中,该数组的地址存放在gendisk对象的part字段中。hd_struct描述符如下。
struct hd_struct {
01 sector_t start_sect;
02 sector_t nr_sects;
03 struct device __dev;
04 struct kobject *holder_dir;
05 int policy, partno;
06 unsigned long stamp;
07 int in_flight;
08 struct disk_stats dkstats;
09 struct rcu_head rcu_head;
10};
第1行磁盘中分区的起始扇区
第2行分区的长度
第3行内嵌的device
第4行内嵌的kobject
第5行磁盘中分区的相对索引
当内核发现系统中一个新的磁盘时,就会调用alloc_disk函数,该函数分配并初始化一个新gendisk对象,如果新磁盘被分成了几个分区,那么alloc_disk()函数将新的gendisk对象插入到通用块层的数据结构中。
二、 块设备驱动程序
块设备驱动程序是Linux块子系统中的最低层组件,从I/O调度程序中获得请求,然后按要求处理这些请求。每个块设备驱动程序对应一个device_driver类型的描述符,一个块设备驱动程序可能处理几个块设备。而且,每个磁盘通常是被分区的,每个分区又可以被看作一个逻辑块设备。每个块设备是由一个block_device结构的描述符来表示。
struct block_device {
01 dev_t bd_dev; /* not a kdev_t - it's a search key */
02 struct inode * bd_inode; /* will die */
03 struct super_block * bd_super;
04 int bd_openers;
05 struct mutex bd_mutex; /* open/close mutex */
06 struct semaphore bd_mount_sem;
07 struct list_head bd_inodes;
08 void * bd_holder;
09 int bd_holders;
10 struct block_device * bd_contains;
11 unsigned bd_block_size;
12 struct hd_struct * bd_part;
13 unsigned bd_part_count;
14 int bd_invalidated;
15 struct gendisk * bd_disk;
16 struct list_head bd_list;
17 struct backing_dev_info *bd_inode_backing_dev_info;
18 unsigned long bd_private;
19 int bd_fsfreeze_count;
20 struct mutex bd_fsfreeze_mutex;
21};
第1行块设备的主设备号和次设备号
第2行指向bdev文件系统中块设备对应的文件索引节点的指针
第3行指向bdev文件系统是块设备对应的超级块的指针
第4行计数器,统计块设备已经打开了多少次
第5行保护块设备的打开和关闭的互斥量
第6行禁止在块设备上进行新安装的信号量
第7行已打开的块设备文件的索引节点链表的首部
第8行块设备描述符的当前所有者
第9行计数器,统计对bd_holder字段多次设备的次数
第10行如果块设备是一个分区,则指向整个磁盘的块设备描述符
第11行块大小
第12行指向分区描述符的指针
第13行计数器,统计包含在块设备中的分区已经被打开了多少次
第14行当需要读块设备的分区表时设置的标志
第15行指向块设备中基本磁盘的gendisk结构的指针
第16行用于块设备描述符链表的指针。
第17行指向块设备的专门描述符backing_dev_info的指针
第18行指向块设备持有者的私有数据的指针
所有的块设备描述符被插入一个全局链表中,链表首部是由变量all_bdevs表示的;链表链接所有的指针位于块设备描述符的bd_list字段中。如果块设备描述符对应一个磁盘分区,bd_contains字段指向与整个磁盘相关的块设备描述符,而bd_part字段指向bd_struct分区描述符。否则,或块设备描述符对应整个磁盘,它的bd_contains字段指向块设备描述符本身,bd_part_count字段用于记录磁盘上的分区已经被打开了多少次。
下图对应的是一个整盘,它说明了块设备描述符是如何被链接到块I/O子系统的其它重要数据结构上的。
三,总结
从用户态进程到最后磁盘的读写,是一个比较复杂的过程,加上本人水平有限,只做了简单的介绍,可以阅读代码,进行很好的理解,阅读代码,比看资料和书都理解的快。从VFS的读写,然后到具体的文件系统,然后经过页高速缓冲,向通用块层提交缓冲区头部,在通过块层,向I/O调度层提交bio,然后把bio加到请求当中,再把请求放到请求队列中,根据IO调用器(IO调度算法)选择请求。最后到gendisk对象,可以看上图的结构。下图给的是一个基本的流程。