块设备
块设备是针对存储设备的,比如SD卡、EMMC、NAND Flash、NorFlash、SPI Flash、机械硬盘、固态硬盘等
块设备只能以块为单位进行读写访问,块是linux虚拟文件系统(VFS)基本的数据传输单位。字符设备是以字节为单位进行数据传输的,不需要缓冲
块设备在结构上是可以进行随机访问的,对于这些设备的读写都是按块进行的,块设备使用缓冲区来暂时存放数据,等到条件成熟以后在一次性将缓冲区中的数据写入块设备中。
驱动
使用结构体block_device表示设备
struct block_device {
dev_t bd_dev; /* not a kdev_t - it's a search key */
int bd_openers;
struct inode * bd_inode; /* will die */
struct super_block * bd_super;
struct mutex bd_mutex; /* open/close mutex */
struct list_head bd_inodes;
void * bd_claiming;
void * bd_holder;
int bd_holders;
bool bd_write_holder;
#ifdef CONFIG_SYSFS
struct list_head bd_holder_disks;
#endif
struct block_device * bd_contains;
unsigned bd_block_size;
struct hd_struct * bd_part;
/* number of times partitions within this device have been opened. */
unsigned bd_part_count;
int bd_invalidated;
struct gendisk * bd_disk;
struct request_queue * bd_queue;
struct list_head bd_list;
/*
* Private data. You must have bd_claim'ed the block_device
* to use this. NOTE: bd_claim allows an owner to claim
* the same device multiple times, the owner must take special
* care to not mess up bd_private for that case.
*/
unsigned long bd_private;
/* The counter of freeze processes */
int bd_fsfreeze_count;
/* Mutex for freeze */
struct mutex bd_fsfreeze_mutex;
};
重点是bd_disk结构体, 表示一个磁盘对象, 类型为struct gendisk
int register_blkdev(unsigned int major, const char *name)
注册块设备, major:主设备号。name:块设备名字。如果major为0的话表示由系统自动分配主设备号,那么返回值就是系统分配的主设备号(1~255),如果返回负值那就表示注册失败
void unregister_blkdev(unsigned int major, const char *name)
注销块设备, major:要注销的块设备主设备号。name:要注销的块设备名字
- gendisk结构体
描述某一个磁盘设备
struct block_device_operations包含操作函数
open函数用于打开指定的块设备。
release函数用于关闭(释放)指定的块设备。
rw_page函数用于读写指定的页。
ioctl函数用于块设备的I/O控制。
compat_ioctl函数和ioctl函数一样,都是用于块设备的I/O控制。区别在于在64位系统上,32位应用程序的ioctl会调用compat_iotl函数。在32位系统上运行的32位应用程序调用的就是ioctl函数。
getgeo函数用于获取磁盘信息,包括磁头、柱面和扇区等信息。
owner表示此结构体属于哪个模块,一般直接设置为THIS_MODULE。
struct gendisk *alloc_disk(int minors)
申请一个结构体, minors:次设备号数量,也就是gendisk对应的分区数量。返回值:成功:返回申请到的gendisk,失败:NULL。
void del_gendisk(struct gendisk *gp)
删除结构体gp:要删除的gendisk。返回值:无。
void add_disk(struct gendisk *disk)
添加到内核, disk:要添加到内核的gendisk。返回值:无。
void set_capacity(struct gendisk *disk, sector_t size)
设置磁盘的容量, disk:要设置容量的gendisk。size:磁盘容量大小,注意这里是扇区数量。块设备中最小的可寻址单元是扇区,一个扇区一般是512字节
truct kobject *get_disk(struct gendisk *disk)
void put_disk(struct gendisk *disk)
内核会通过get_disk和put_disk这两个函数来调整gendisk的引用计数,根据名字就可以知道,get_disk是增加gendisk的引用计数,put_disk是减少gendisk的引用计数
- struct request_queue: 请求队列
请求队列, 内核把读写请求发送到request_queue中, request_queue中是大量的request(请求结构体),而request又包含了bio,bio保存了读写相关数据
request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
申请并初始化一个request_queue, rfn:请求处理函数指针,每个request_queue都要有一个请求处理函数
void (request_fn_proc) (struct request_queue *q)
, lock:自旋锁指针,需要驱动编写人员定义一个自旋锁,然后传递进来。,请求队列会使用这个自旋锁, 主要用于机械硬盘, 会使用电梯算法在绑定的函数中申请并进行处理
void blk_cleanup_queue(struct request_queue *q)
卸载块设备驱动的时候我们还需要删除掉前面申请到的request_queue, q:需要删除的请求队列。返回值:无。
- 请求request
request里面有一个名为“bio”的成员变量,类型为bio结构体指针。前面说了,真正的数据就保存在bio里面
request *blk_peek_request(struct request_queue *q)
依次获取每个request
void blk_start_request(struct request *req)
处理这个请求
structrequest *blk_fetch_request(structrequest_queue *q)
也可以使用blk_fetch_request函数来一次性完成请求的获取和开启
struct request_queue *blk_alloc_queue(gfp_t gfp_mask)
用于只申请一个队列头
void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)
q:需要绑定的请求队列,也就是blk_alloc_queue申请到的请求队列。mfn:需要绑定的“制造”请求函数,函数原型如下
void (make_request_fn) (struct request_queue *q, struct bio *bio)
, 一般blk_alloc_queue和blk_queue_make_request是搭配在一起使用的,用于那么非机械的存储设备、无需I/O调度器,比如EMMC、SD卡等。
- bio结构体
每个request里面里面会有多个bio,bio保存着最终要读写的数据、地址等信息
上层会将bio提交给I/O调度器,I/O调度器会将这些bio构造成request结构,而一个物理存储设备对应一个request_queue,request_queue里面顺序存放着一系列的request。新产生的bio可能被合并到request_queue里现有的request中,也可能产生新的request
有可能会使用电梯调度算法, 主要是对硬盘使用, SD和emmc可以直接随机读取
1 struct bio {
2 struct bio *bi_next;/* 请求队列的下一个bio*/
3 struct block_device *bi_bdev;/* 指向块设备*/
4 unsigned long bi_flags;/* bio状态等信息*/
5 unsigned long bi_rw;/* I/O操作,读或写*/
6 struct bvec_iter bi_iter;/* I/O操作,读或写*/
7 ....
30 structbio_vec *bi_io_vec;/* bio_vec列表*/
31 ...
33 };
包含的总要结构体:
bvec_iter结构体描述了要操作的设备扇区等信息
1 struct bvec_iter {
2 sector_t bi_sector;/*I/O请求的设备起始扇区(512字节)*/
3 unsignedintbi_size;/* 剩余的I/O数量*/
4 unsignedintbi_idx;/* blv_vec中当前索引*/
5 unsignedintbi_bvec_done;/*当前bvec中已经处理完成的字节数*/
6 };
bio_vec结构体
1 struct bio_vec {
2 structpage *bv_page;/* 页*/
3 unsignedintbv_len;/* 长度*/
4 unsignedintbv_offset;/* 偏移*/
5 };
#define __rq_for_each_bio(_bio, rq) \
if ((rq->bio)) \
for (_bio = (rq)->bio; _bio; _bio = _bio->bi_next)
遍历请求中所有bio并进行处理。这是一个宏
#define bio_for_each_segment(bvl, bio, iter) \
__bio_for_each_segment(bvl, bio, iter, (bio)->bi_iter)
bio包 含了 最 终 要操 作的 数 据 ,因此还需要遍历bio中的所有段此函数也是一个宏
unsigned long start = blk_rq_pos(req) << 9;
获取操作的设备块的扇区首地址, 左移九位就是实际的地址
unsigned long len = blk_rq_cur_bytes(req);
获取处理的数据的长度
void *buffer = bio_data(req->bio);
获取bio的信息, 如果是读数据就会把读取到的数据保存在这个缓冲区中
如果是写的话这个缓冲区保存要写入的数据
rq_data_dir(req)
获取读写的方向
- 不使用电梯函数
struct bio_vec bvec;
struct bvec_iter iter;
offset = bio->bi_iter.bi_sector; //要操作的扇区偏移
bio_for_each_segment(bvec, bio, iter); //循环处理每一个段,两个数据结构体
这是用来处理每一个bio的函数
机械硬盘结构
盘片, 一个机械硬盘有多个盘片, 每一个盘片都有对应的读写磁头, 一般在5之内, 盘上面一圈一圈的叫磁道, 从圆心划线可以把他分割为多个弧段, 每个磁道上的一个弧段称为一个扇区, 扇区是最小单位, 每一个有512字节, 每一个盘面的编号相同的磁道组成的圆柱叫做柱面
容量的计算 = 磁头数 x 磁道数 x 每道扇区数 x 每一个扇区的字节数
使用
初始化
//注册块设备
ramdisk.major = register_blkdev(0, RAMDISK_NAME);
if(ramdisk.major<0)
{
ret = -EINVAL;
goto fail_ramdisk_register_file;
}
printk("major = %d\r\n", ramdisk.major);
//申请gendisk
ramdisk.gendisk = alloc_disk(RAMDISK_MINOR);
if(!ramdisk.gendisk)
{
ret = -EINVAL;
goto gendisk_fail;
}
//初始化自旋锁
spin_lock_init(&ramdisk.lock);
申请处理
- 使用电梯处理函数
//申请并初始化队列
ramdisk.queue = blk_init_queue(ramdisk_request_fn, &ramdisk.lock);
if(!ramdisk.queue)
{
ret = -EINVAL;
goto blk_queue_fail;
}
//具体的数据处理过程
static void ramdisk_transfer(struct request *req)
{
unsigned long start = blk_rq_pos(req) << 9; //获取要操作的扇区的首地址
unsigned long len = blk_rq_cur_bytes(req);
void *buffer = bio_data(req->bio);
if(rq_data_dir(req)==READ)
{
memcpy(buffer, ramdisk.ramdiskbuf + start, len);
}else
{
memcpy(ramdisk.ramdiskbuf + start, buffer, len);
}
}
- 不使用
ramdisk.queue = blk_alloc_queue(GFP_KERNEL);
if(!ramdisk.queue)
{
ret = -EINVAL;
goto blk_queue_fail;
}
//绑定制造请求函数
blk_queue_make_request(ramdisk.queue, ramdisk_make_request);
//制造请求函数
static void ramdisk_make_request(struct request_queue *queue, struct bio *bio)
{
int offset;
struct bio_vec bvec;
struct bvec_iter iter;
offset = bio->bi_iter.bi_sector<<9; //要操作的扇区偏移
bio_for_each_segment(bvec, bio, iter){
char *ptr = page_address(bvec.bv_page) + bvec.bv_offset; //获取缓冲区
int len = bvec.bv_len;
//循环处理每一个段
if(bio_data_dir(bio)==READ)
{
memcpy(ptr, ramdisk.ramdiskbuf + offset, len);
}else
{
memcpy(ramdisk.ramdiskbuf + offset, ptr, len);
}
offset += len;
}
set_bit(BIO_UPTODATE, &bio->bi_flags);
bio_endio(bio, 0); //告知处理结束
}
设置参数
ramdisk.gendisk->major = ramdisk.major;
ramdisk.gendisk->first_minor = 0;
ramdisk.gendisk->fops = &ramdisk_fops;
ramdisk.gendisk->private_data = &ramdisk;
ramdisk.gendisk->queue = ramdisk.queue;
sprintf(ramdisk.gendisk->disk_name, RAMDISK_NAME);
set_capacity(ramdisk.gendisk, RAMDISK_SIZE/512); //设置容量
add_disk(ramdisk.gendisk);
return 0;
blk_queue_fail:
put_disk(ramdisk.gendisk);
gendisk_fail:
unregister_blkdev(ramdisk.major, RAMDISK_NAME);
fail_ramdisk_register_file:
kfree(ramdisk.ramdiskbuf);
ramalloc_fail:
return ret;
其他
static int ramdisk_getgeo(struct block_device *dev, struct hd_geometry *geo)
{
//用来获取硬盘的信息
geo->heads = 2; //磁头
geo->cylinders = 32; //柱面
geo->sectors = RAMDISK_SIZE/(2*32*512); //一个磁道的扇区数量
return 0;
}