Linux 三大驱动分别是:字符设备驱动、块设备驱动、网络设备驱动。
块设备是针对存储设备的,比如 SD 卡、EMMC、NAND Flash、Nor Flash、SPI Flash、机械硬盘、固态硬盘等。因此块设备驱动其实就是这些存储设备驱动,块设备驱动相比字符设备驱动的主要区别如下:
①、块设备只能以块为单位进行读写访问,块是 linux 虚拟文件系统(VFS)基本的数据传输
单位。字符设备是以字节为单位进行数据传输的,不需要缓冲。
②、块设备在结构上是可以进行随机访问的,对于这些设备的读写都是按块进行的,块设备使用缓冲区来暂时存放数据,等到条件成熟以后再一次性将缓冲区中的数据写入块设备中。
这么做的目的为了提高块设备寿命,大家如果仔细观察的话就会发现有些硬盘或者 NAND
Flash 就会标明擦除次数(flash 的特性,写之前要先擦除),比如擦除 100000 次等。因此,为了提高块设备寿命而引入了缓冲区,数据先写入到缓冲区中,等满足一定条件后再一次性写入到真正的物理存储设备中,这样就减少了对块设备的擦除次数,提高了块设备寿命。
字符设备是顺序的数据流设备,字符设备是按照字节进行读写访问的。字符设备不需要缓
冲区,对于字符设备的访问都是实时的,而且也不需要按照固定的块大小进行访问。
块设备结构的不同其 I/O 算法也会不同,比如对于 EMMC、SD 卡、NAND Flash 这类没
有任何机械设备的存储设备就可以任意读写任何的扇区(块设备物理存储单元)。但是对于机械硬盘这样带有磁头的设备,读取不同的盘面或者磁道里面的数据,磁头都需要进行移动,因此对于机械硬盘而言,将那些杂乱的访问按照一定的顺序进行排列可以有效提高磁盘性能,linux 里面针对不同的存储设备实现了不同的 I/O 调度算法。
块设备驱动框架
Linux 内核使用 block_device 结构体表示块设备
24 struct block_device {
25 sector_t bd_start_sect;
26 struct disk_stats __percpu *bd_stats;
27 unsigned long bd_stamp;
28 bool bd_read_only; /* read-only policy */
29 dev_t bd_dev;
30 int bd_openers;
31 struct inode * bd_inode; /* will die */
32 struct super_block * bd_super;
33 void * bd_claiming;
34 struct device bd_device;
35 void * bd_holder;
36 int bd_holders;
37 bool bd_write_holder;
38 struct kobject *bd_holder_dir;
39 u8 bd_partno;
40 spinlock_t bd_size_lock; /* for bd_inode->i_size updates */
41 struct gendisk * bd_disk;
42
43 /* The counter of freeze processes */
44 int bd_fsfreeze_count;
45 /* Mutex for freeze */
46 struct mutex bd_fsfreeze_mutex;
47 struct super_block *bd_fsfreeze_sb;
48
49 struct partition_meta_info *bd_meta_info;
50 #ifdef CONFIG_FAIL_MAKE_REQUEST
51 bool bd_make_it_fail;
52 #endif
53
54 ANDROID_KABI_RESERVE(1);
55 ANDROID_KABI_RESERVE(2);
56 ANDROID_KABI_RESERVE(3);
57 ANDROID_KABI_RESERVE(4);
58 } __randomize_layout;
对于 block_device 结构体,我们重点关注一下第 41 行的 bd_disk 成员变量,此成员变量为 gendisk 结构体指针类型,内核使用 block_device 来表示一个具体的块设备对象,比如一个硬盘或者分区,如果是硬盘的话,bd_disk 就指向通用磁盘结构 gendisk。
注册块设备:和字符设备驱动一样,我们需要向内核注册新的块设备、申请设备号,块设备注册函数为 register_blkdev
注销块设备:和字符设备驱动一样,如果不使用某个块设备了,那么就需要注销掉,函数为
unregister_blkdev
Linux 内核使用 gendisk 结构体来描述一个磁盘设备
121 struct gendisk {
122 /* major, first_minor and minors are input parameters only,
123 * don't use directly. Use disk_devt() and disk_max_parts().
124 */
125 int major; /* major number of driver */
126 int first_minor;
127 int minors; /* maximum number of minors, =1 for
128 * disks that can't be partitioned. */
129
130 char disk_name[DISK_NAME_LEN]; /* name of major driver */
131
132 unsigned short events; /* supported events */
133 unsigned short event_flags; /* flags related to event processing */
134
135 struct xarray part_tbl;
136 struct block_device *part0;
137
138 const struct block_device_operations *fops;
139 struct request_queue *queue;
140 void *private_data;
141
142 int flags;
143 unsigned long state;
144 #define GD_NEED_PART_SCAN 0
145 #define GD_READ_ONLY 1
146 #define GD_DEAD 2
147
148 struct mutex open_mutex; /* open/close mutex */
149 unsigned open_partitions; /* number of open partitions */
150
151 struct backing_dev_info *bdi;
152 struct kobject *slave_dir;
153 #ifdef CONFIG_BLOCK_HOLDER_DEPRECATED
154 struct list_head slave_bdevs;
155 #endif
156 struct timer_rand_state *random;
157 atomic_t sync_io; /* RAID */
158 struct disk_events *ev;
159 #ifdef CONFIG_BLK_DEV_INTEGRITY
160 struct kobject integrity_kobj;
161 #endif /* CONFIG_BLK_DEV_INTEGRITY */
162 #if IS_ENABLED(CONFIG_CDROM)
163 struct cdrom_device_info *cdi;
164 #endif
165 int node_id;
166 struct badblocks *bb;
167 struct lockdep_map lockdep_map;
168 u64 diskseq;
169
170 ANDROID_KABI_RESERVE(1);
171 ANDROID_KABI_RESERVE(2);
172 ANDROID_KABI_RESERVE(3);
173 ANDROID_KABI_RESERVE(4);
174 };
major 为磁盘设备的主设备号。first_minor 为磁盘的第一个次设备号。minors 为磁盘的此设备号数量,也就是磁盘的分区数量,这些分区的主设备号一样,此设备号不同。fops 为块设备操作集,为 block_device_operations 结构体类型。和字符设备操作集 file_operations 一样,是块设备驱动中的重点!queue 为磁盘对应的请求队列,所以针对该磁盘设备的请求都放到此队列中,驱动程序需要处理此队列中的所有请求。
编写块的设备驱动的时候需要分配并初始化一个 gendisk,linux 内核提供了一组 gendisk 操作函数:
申请 gendisk:
struct gendisk *alloc_disk(int minors)
删除 gendisk:
void del_gendisk(struct gendisk *gp)
将 gendisk 添加到内核:
void add_disk(struct gendisk *disk)
设置 gendisk 容量:
void set_capacity(struct gendisk *disk, sector_t size)
调整 gendisk 引用计数:
truct kobject * get_disk_and_module (struct gendisk *disk)
void put_disk(struct gendisk *disk)
和字符设备的 file_operations 一样,块设备也有操作集,为结构体 block_device_operations
1935 struct block_device_operations {
1936 blk_qc_t (*submit_bio) (struct bio *bio);
1937 int (*open) (struct block_device *, fmode_t);
1938 void (*release) (struct gendisk *, fmode_t);
1939 int (*rw_page)(struct block_device *, sector_t, struct page *, unsigned int);
1940 int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
1941 int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
1942 unsigned int (*check_events) (struct gendisk *disk,
1943 unsigned int clearing);
1944 void (*unlock_native_capacity) (struct gendisk *);
1945 int (*getgeo)(struct block_device *, struct hd_geometry *);
1946 int (*set_read_only)(struct block_device *bdev, bool ro);
1947 /* this callback is with swap_lock and sometimes page table lock held */
1948 void (*swap_slot_free_notify) (struct block_device *, unsigned long);
1949 int (*report_zones)(struct gendisk *, sector_t sector,
1950 unsigned int nr_zones, report_zones_cb cb, void *data);
1951 char *(*devnode)(struct gendisk *disk, umode_t *mode);
1952 struct module *owner;
1953 const struct pr_ops *pr_ops;
1954
1955 /*
1956 * Special callback for probing GPT entry at a given sector.
1957 * Needed by Android devices, used by GPT scanner and MMC blk
1958 * driver.
1959 */
1960 int (*alternative_gpt_sector)(struct gendisk *disk, sector_t *sector);
1961
1962 ANDROID_KABI_RESERVE(1);
1963 ANDROID_KABI_RESERVE(2);
1964 ANDROID_OEM_DATA(1);
1965 };
open 函数用于打开指定的设备。release 函数用于关闭(释放)指定的块设备。rw_page 函数用于读写指定的页。ioctl 函数用于块设备 I/O 控制。compat_ioctl 函数与 ioctl 函数一样,都是用于块设备的 I/O 控制。区别在于在 64位系统上,32 位应用程序的 ioctl 会调用 compat_iotl 函数。在 32 位系统上运行的 32 位应用程序调用的就是 ioctl 函数。getgeo 函数用于获取磁盘信息,包括磁头、柱面和扇区等信息。owner 表示此结构体属于哪个模块,一般直接设置为 THIS_MODULE。
块设备 I/O 请求过程
大家如果仔细观察的话会在 block_device_operations 结构体中并没有找到 read 和 write 这样的读写函数,那么块设备是怎么从物理块设备中读写数据?这里就引处理块设备驱动中非常重要的 request_queue、request 和 bio。
内核将对块设备的读写都发送到请求队列 request_queue 中,request_queue 中是大量的 request(请求结构体),而 request 又包含了 bio,bio 保存了读写相关数据,比如从块设备的哪个地址开始读取、读取的数据长度,读取到哪里,如果是写的话还包括要写入的数据等。
gendisk 结构体里面有一个 request_queue 结构体指针类型成员变量 queue,在编写块设备驱动的时候,每个磁盘(gendisk)都要分配一个 request_queue。