相信看过我写的文章的会大吃一惊!
为什么突然由 2 到了 5,这也跨度太大了点吧,呵呵,不是的,因为前面的国嵌的几天的课程真的看不懂!你懂得!现在水平的确有限!
DM9000 和 PCI 串口,这两天的内容只能暂时放下了,没有很好地教程,自己看了两天“瓶颈”又很多,所以~~
好了,从今天开始就是块设备的驱动了!
努力!
哼哼!
1、块设备简介
块设备将数据存储在固定大小的块中,每个块的大小通常在 512 字节到 32768 字节之间。磁盘、SD 卡都是常见的块设备。
块设备和字符设备的区别:
①、块设备和字符设备最大的区别在于读写数据的基本单元不同。块设备读写数据的基本单元为块,例如磁盘通常为一个扇区,而字符设备饿基本单元为字节。
②、块设备能够随机访问,而字符设备则只能顺序访问。
2、块设备结构
如图:
①、虚拟文件系统 VFS:
VFS 是对各种具体文件系统的一种封装,为用户程序访问文件提供统一的接口。
②、Disk Cache:
说白了就是一个缓存!
当用户发起文件访问请求的时候,首先回到 Disk Cache 中寻找文件是否被缓存了,如果在 Cache 中,则直接从 Cache 中读取。如果数据不再缓存中,就必须要到具体的文件系统中读取数据了。
③、Mapping Layer:
首先确定文件系统的大小,然后计算所请求的数据包含多少个块,然后调用具体文件函数来访问文件的 inode,确定所请求的数据在磁盘上的逻辑块地址。
④、Generic Block Layer:
Linux 内核为块设备抽象了统一的模型,把块设备看作是由若干个扇区组成的数据空间。上层的读写请求在通用块层(Generic Block Layer)被构造成一个或者多个 bio 结构。
⑤、I/O Sxheduler Layer:
I/O 调度层负责将 I/O 操作进行排序,采用某种算法(主要是:电梯调度算法)来高效地处理操作(如果对于 ramdisk、U 盘等自己分配就哦了)。
⑥、Block Device Driver:
块设备驱动程序通过发送命令给磁盘控制器实现真正的数据传输。
3、块设备驱动
①、设备描述:
Linux 内核使用 struct gendisk(定义与 <linux/genhd.h>)来描述块设备。
struct gendisk
{
Int major; /* 主设备号 */
Int fistr_minor; /* 次设备号 */
Int minors; /* 次设备数 */
Char disk_name[DISK_NAME_LEN]; /* 驱动名 */
Struct block_device_operations *fops; /* 块设备操作集合 */
Struct request_queue *queue; /* 请求队列 */
…
Int node_id;
};
②、设备注册
Linux 内核使用 add_disk 函数向内核注册块设备驱动。
void add_disk(struct gendisk *gd);
③、设备操作
字符设备通过 file-operations 结构来定义使它所支持的操作,块设备使用一个类似的结构:
Struct block_device_operations。
Struct block_device_operations
{
Int (*open)(struct block_device *,fmode_t);
Int (*release)(struct gendisk *,fmode_t);
Int (*ioctl)(struct block_device *,fmode_t,ussigned,unsigned long);
…
};
④、IO 请求
在 Linux 内核中,使用 struct request 来表示等待处理的块设备 I/O 请求。
struct request
{
struct list_head questlist; /* 链表结构 */
sector_t sector; /* 要操作的首个扇区 */
unsigned long nr_sectors; /* 要操作的扇区数目 */
struct bio *bio; /* 请求的 bio 结构体的链表 */
struct bio *biotail; /* 请求的 bio 结构体的链表尾 */
....
};
简单的说,请求队列就是 IO 请求 request 所形成的队列,在 LInux 内核中 struct request_queue 描述。
⑤、请求队列
内核提供了一系列函数用来操作请求队列:
▲:struct request_queue *blk_init_queue(request_fn_proc *rfn,spinlock_t *lock)
初始化请求队列,一般在块设备驱动的模块加载函数中调用。
其中 rfn 是个函数指针。当有数据请求来的时候就会调用 rfn 这个函数。
注意:
使用 blk_init_queue() 函数来初始化一个请求队列,默认会将请求队列的 make_request_fn 方法设置为内核缺省的处理函数 __make_request()。
该函数会使用 IO 调度器将多个 bio 的访问书序进行优化,调整,合并为一个请求放入请求队列,但是对于 ramdisk、U 盘、记忆棒之类的设备,并不存在磁盘所面临的寻到时间。
因此对这样的“块设备”而言,一个 I/O 调度器不但发挥不了作用,反而其本省将白白耗掉不少内存和 CPU。因此对于此类块设备的驱动,需要实现自己的 make_request_fn 函数,通过函数 blk_alloc_queue() 我们可以分配一个请求队列结构而不将 make_request_fn 方法设置为默认的处理函数 __make_request()。
▲:void blk_cleanup_queue(request_queue_t *q)
清除请求队列,这个函数完成将请求队列返回给系统的任务,一般在块设备驱动模块卸载函数中调用。