嵌入式Linux系统开发

嵌入式Linux系统开发

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。

Linux 块设备驱动-LMLPHP

Linux 块设备驱动-LMLPHP

03-18 07:28