文章目录
- 一、块设备驱动简介
- (一)简介
- (二)块设备驱动相关概念
- 二、块设备驱动
- (一)框架图
- 1. 虚拟文件系统(VFS)
- 2. Disk Cache:硬盘的高速缓存
- 3. 映射层(mapping layer)
- 4. Generic Block Layer:通用块层
- 5. I/O Scheduler Layer :I/O调度层
- 6. 块设备驱动层
- (二)块设备驱动框架
- 三、块设备驱动API
- (一)结构体对象
- 1. reque_queue和request及bio的关系
- (二)结构体对象初始化API
- 四、代码示例
一、块设备驱动简介
(一)简介
系统能够随机访问固定大小(1block–512byte)数据片的设备被称之为块设备。
- 注:块设备的访问方式是随机的,即可以直接访问设备上的任何块,而不需要按照顺序读取或写入
块设备文件一般都是以安装(挂载)文件系统的方式使用,这也是块设备通常的访问方式。
- 注:为了使用块设备存储数据,我们需要在其上安装(或挂载)一个文件系统。文件系统是组织和存储文件的一种方式,它定义了如何存储数据、如何命名文件以及如何组织文件的层次结构。安装(挂载)文件系统是将块设备与目录树中的某个点(挂载点)关联起来的过程,之后该目录下的所有文件和目录都将被存储在块设备上
扇区是块设备的基本存储单元,扇区大小一般是2的整数倍,最常见的大小是512字节。所有对块设备的读写操作都是基于扇区进行的。因此,扇区的大小直接决定了块设备能够处理的最小数据量。
扇区是设备的最小可寻址单位,所以块不能比扇区还小,只能数倍与扇区大小。
内核对块大小的要求是:必须是扇区大小的整数倍,并且小于页面的大小,所以块的大小通常是512字节、1K或者4K。
(二)块设备驱动相关概念
磁头:一个磁盘有多少个面就有多少个磁头
磁道:在一个磁头上可以有很多环,这些环就叫做磁道
扇区:磁道上访问数据的最小的单位就是扇区,一个扇区的大小就是512字节
1block = 512字节 1024字节 2048字节 4096字节
1扇区 = 512字节
块设备的能存储的数据 = 磁头 * 磁道 * 扇区 * 512
机械硬盘存放文件时,同一个文件有可能存放在不同的盘片的不同面,且数据可能会分成多个块,并且是无序的存放。因此在读取时,为了减少切换磁头的频率,会先无序的从磁盘读出该文件所有块,然后再对读到内存中的块进行排序后返回给用户空间
- 注:磁盘擅长连续的读数据而非跳跃的读数据
二、块设备驱动
(一)框架图
1. 虚拟文件系统(VFS)
隐藏了各种硬件的具体细节,为用户操作不同的硬件提供了一个统一的接口。屏蔽各个文件系统的差异,其基于不同的文件系统格式,比如EXT,FAT等。用户程序对设备的操作都通过VFS来完成,在VFS上面就是诸如open、close、write和read的函数API。
windows文件系统:ntfs
ubuntu文件系统:ext4
2. Disk Cache:硬盘的高速缓存
用户缓存最近访问的文件数据,如果能在高速缓存中找到,就不必去访问硬盘,毕竟硬盘的访问速度慢很多。
3. 映射层(mapping layer)
这一层主要用于确定文件系统的block size,然后计算所请求的数据包含多少个block。同时调用具体文件系统函数来访问文件的inode,确定所请求的数据在磁盘上面的逻辑地址。
4. Generic Block Layer:通用块层
Linux内核把块设备看做是由若干个扇区组成的数据空间,上层的读写请求在通用块层被构造成一个或多个bio结构
在硬盘上连续存储的每个空间会对应一个BIO结构体
5. I/O Scheduler Layer :I/O调度层
负责将通用块层的块I/O操作进行(电梯调度算法)调度、插入、暂存、排序、合并、分发等操作,对磁盘的操作更为高效。负责将通用块层的块I/O操作进行
将连续的BIO(可能属于不同进程)合并成一个request加入到request队列,因此一个request中有一个或多个BIO结构体
6. 块设备驱动层
在块系统架构的最底层,由块设备驱动根据排序好的请求,对硬件进行数据访问。
(二)块设备驱动框架
user:
open read write close
-------------------(io请求)-----------------------------------
kernel |中间层: (block_device)
| read读1500数据在物理磁盘上三段不连续,构造3个bio结构体
| 将用户的io请求转化成BIO(block,input ,output),
| 在物理内存上连续的bio会被合成request,这个request
| 会被放到内核的一个队列上。
|---------------------------------------------------------
|driver:gendisk
| 1.分配对象
| 2.对象初始化
| 3.初始化一个队列 head----request(read)----request(write)---...
| //4.硬盘设备的初始化
| 5.注册、注销
------------------------------------------------------------------
haredware : 分配的内存(模拟真实的设备)(1M)
- 补充:反汇编命令 objdump
三、块设备驱动API
(一)结构体对象
1. gendisk的结构体对象
struct gendisk {
int major; //块设备的主设备号
int first_minor; //起始的次设备号
int minors; //设备的个数,分区的个数
char disk_name[DISK_NAME_LEN]; //磁盘的名字
struct disk_part_tbl *part_tbl;//磁盘的分区表的首地址
struct hd_struct part0; //part0分区的描述
const struct block_device_operations *fops;//块设备的操作方法结构体
struct request_queue *queue;//队列
void *private_data; //私有数据
};
2. hd_struct分区的结构体:part0分区的描述
struct hd_struct {
sector_t start_sect; //起始的扇区号
sector_t nr_sects; //扇区的个数
int partno; //分区号
};
//块设备的操作方法结构体
struct block_device_operations {
int (*open) (struct block_device *, fmode_t);
int (*release) (struct gendisk *, fmode_t);
int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*getgeo)(struct block_device *, struct hd_geometry *);
//设置磁盘的磁头,磁道,扇区的个数的
};
3. hd_geometry结构体
struct hd_geometry {
unsigned char heads;
unsigned char sectors;
unsigned short cylinders;
unsigned long start;
};
----------------------------------------------------------------
4. request_queue结构体
struct request_queue
{
/*双向链表数据结构,将所有加入到队列的IO请求组建成一个双向链表*/
struct list_head queue_head;
struct list_head requeue_list; //request队列
spinlock_t requeue_lock; //队列自旋锁
unsigned long nr_requests; /* 最大的请求数量 */
unsigned long queue_flags;/*当前请求队列的状QUEUE_FLAG_STOPPED*/
};
5. request结构体
struct request
{
struct list_head queuelist;/* 请求对象中的链表元素*/
struct request_queue *q; /* 指向存放当前请求的请求队列*/
unsigned int __data_len; /* 当前请求要求数据传输的总的数据量 */
sector_t __sector; /* 当前请求要求数据传输的块设备的起始扇区 */
struct bio *bio; /* bio对象所携带的信息转存至请求对象中*/
struct bio *biotail; /* bio链表*/
};
//通常一个request请求可以包含多个bio,一个bio对应一个I/O请求
6. bio结构体
struct bio {
struct bio *bi_next; /* 指向当前bio的下一个对象*/
unsigned long bi_flags; /* 状态、命令等 */
unsigned long bi_rw; /* 表示READ/WRITE*/
struct block_device *bi_bdev; /* 与请求相关联的块设备对象指针*/
unsigned short bi_vcnt; /* bi_io_vec数组中元素个数 */
unsigned short bi_idx; /* 当前处理的bi_io_vec数组元素索引 */
unsigned int bi_size; /* 本次传输需要传输的数据总量,byte(扇区大小整数倍) */
struct bio_vec *bi_io_vec;/* 指向一个IO向量的数组,数组中的内各元素对应一个物理页的page对象 */
};
7. bio_vec结构体
struct bio_vec {
struct page *bv_page; //指向用于数据传输的页面所对应的struct page对象
unsigned int bv_len; //表示当前要传输的数据大小
unsigned int bv_offset;//表示数据在页面内的偏移量
};
1. reque_queue和request及bio的关系
新版本的linux块设备驱动会有多个request_queue,一般是有几个核就有几个request队列;
每个request_queue上有多个request节点,以链表形式链在一起;
每个request节点又包含多个bio结构体(可能属于不同的进程,但是必定是在物理上连续存储的);
每个bio结构体又包含多个bio_vec结构体,因为bio结构体在内存上可能又分成了多个块,bio_vec结构体中存放了页号,偏移量和长度
因此,可以根据bio_vec结构体中的信息获得内存中的线性地址;
然后,根据request节点的起始扇区号,加上dev_addr,得到要操作的物理内存地址;
(二)结构体对象初始化API
1. 初始化结构体
struct gendisk *mydisk;
struct gendisk *alloc_disk(int minors)
//void put_disk(struct gendisk *disk)
//归还引用计数
功能:分配gendisk的内存,然后完成必要的初始化
参数:
@minors:分区的个数
返回值:成功返回分配到的内存的首地址,失败返回NULL
int register_blkdev(unsigned int major, const char *name)
//void unregister_blkdev(unsigned int major, const char *name)
功能:申请设备设备驱动的主设备号
参数:
@major : 0:自动申请
>0 :静态指定
@name :名字 cat /proc/devices
返回值:
major=0 ;成功返回主设备号,失败返回错误码
major>0 :成功返回0 ,失败返回错误码
void set_capacity(struct gendisk *disk, sector_t size)
功能:设置磁盘的容量
struct request_queue *blk_mq_init_sq_queue(struct blk_mq_tag_set *set,
const struct blk_mq_ops *ops,unsigned int queue_depth,unsigned int set_flags)
//void blk_cleanup_queue(struct request_queue *q)
功能:用于在给定队列深度的情况下使用mq ops设置队列的助手,以及通过mq ops标志传递的助手
参数:
@被初始化的tag对象,tag被上层使用,里面包含硬件队列的个数,队列的操作方法结构体,标志位等
@放入到tag中的操作方法结构体
@ tag中指定支持的队列深度
@将tag中队列的处理标志位,例如BLK_MQ_F_SHOULD_MERGE, BLK_MQ_F_BLOCKING等
返回值:成功返回队列指针,失败返回错误码指针
2. 队列处理相关函数
blk_mq_start_request(rq); //开始处理队列
blk_mq_end_request(rq, BLK_STS_OK); //结束队列处理
rq_for_each_segment(bvec, rq, iter) //从request->bio_vec
void* b_buf = page_address(bvec.bv_page) + bvec.bv_offset; //将页地址转换为线性地址(内地址)
rq_data_dir(rq)) //从request获取本次读写的方向 WRITE 1 READ 0
dev_addr+(rq->__sector *512) //磁盘设备的地址
3.注册、注销
void add_disk(struct gendisk *disk)
//注册
void del_gendisk(struct gendisk *disk)
//注销
四、代码示例