目录
2.2.4 add_disk:将已初始化好的gendisk指针添加到内核
1、块设备简介
1、系统中能够随机访问固定大小(1block 512byte)数据片的设备被称之为块设备。块设备文件一般都是以安装文件系统的方式使用,这也是块设备通常的访问方式。块设备的方式访问方式是随机的。
2、块设备中小的可寻址单位是扇区,扇区大小一般是2的整数倍。常见的大小是512字节。扇区的大小是块设备的物理属性,扇区是所有块设备的基本单元,块设备无法对比扇区更小的单位进行寻址和操作。
3、块是文件系统的一种抽象,只能基于块来访问文件系统。物理磁盘寻址是按照扇区的级别进行的,内核访问的所有磁盘操作又都是按照块进行的。扇区是设备的小可寻址单位,所以快不能比扇区还小,只能数倍于扇区大小。
4、内核对块大小的要求是:必须是扇区大小的整数倍,并且小于页面的大小,所以块的大小通常是512字节、1K或者4K。
5、块设备的结构:
磁头:一个磁盘有多少个面就有多少个磁头
磁道:在一个磁头上可以有很多环,这些环就叫做磁道
扇区:磁道上访问数据的最小的单位就是扇区,一个扇区的大小就是512字节
块设备的能存储的数据=磁头*磁道*扇区*512
2、块设备驱动API
2.1 block_device
2.1.1 register_blkdev:注册块设备
int register_blkdev(unsigned int major, const char *name)
/*
功能:注册块设备,申请设备设备驱动的主设备号
参数:
@major : 0:自动申请
>0 :静态指定
@name :名字 cat /proc/devices
返回值:
major=0 ;成功返回主设备号,失败返回错误码
major>0 :成功返回0 ,失败返回错误码
*/
2.1.2 unregister_blkdev:注销块设备
void unregister_blkdev(unsigned int major, const char *name)
/*
功能:注销块设备,释放设备号
参数:
@major:主设备号
@name:名字
*/
2.2 gendisk
2.2.1 gendisk 结构体
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; //私有数据
};
分区的结构体
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 *);
//设置磁盘的磁头,磁道,扇区的个数的。hd_geometry
}
2.2.2 alloc_disk:申请gensisk
struct gendisk *alloc_disk(int minors)
/*
功能:分配gendisk的内存,然后完成必要的初始化
参数:
@minors:分区的个数
返回值:成功返回分配到的内存的首地址,失败返回NULL
*/
2.2.3 put_disk:释放disk
void put_disk(struct gendisk *disk)
/*
功能:释放申请的disk内存
参数:
@disk:申请的gendisk 结构体
*/
2.2.4 add_disk:将已初始化好的gendisk指针添加到内核
void add_disk(struct gendisk *disk)
/*
功能:使用alloc_disk申请到 gendisk以后系统还不能使用,
必须使用 add_disk函数将申请到的gendisk添加到内核中,
参数:
@disk:申请的gendisk 结构体
*/
2.2.5 del_gendisk:删除gendisk
void del_gendisk(struct gendisk *disk)
/*
功能:注销磁盘设备
参数:
@disk:申请的gendisk 结构体
*/
2.2.6 set_capacity:设置磁盘容量
void set_capacity(struct gendisk *disk, sector_t size)
/*
功能:设置磁盘的容量
参数:
@disk:申请的gendisk 结构体
@size:磁盘的容量
*/
2.3 request_queue
2.3.1 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链表
};
2.3.2 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
…
};
2.3.3 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对象
};
2.3.4 bio_vec
struct bio_vec {
struct page *bv_page; //指向用于数据传输的页面所对应的struct page对象
unsigned int bv_len; //表示当前要传输的数据大小
unsigned int bv_offset;//表示数据在页面内的偏移量
};
2.3.5 blk_mq_init_sq_queue
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)
/*
功能:用于在给定队列深度的情况下使用mq ops设置队列的助手,以及通过mq ops标志传递的助手
参数:
@set:被初始化的tag对象,tag被上层使用,里面包含硬件队列的个数,队列的操作方法结构体,标志位等
使用时直接初始化一个set放进去就好,是给上层用的
@ops:放入到tag中的操作方法结构体
@queue_depth:中指定支持的队列深度
@set_flags:将tag中队列的处理标志位,例如BLK_MQ_F_SHOULD_MERGE,BLK_MQ_F_BLOCKING等
返回值:
成功返回队列指针,失败返回错误码指针
*/
2.3.6 blk_cleanup_queue
void blk_cleanup_queue(struct request_queue *q)
/*
功能:该函数完成将请求队列返回给系统的任务,一般在块设备驱动模块卸载函数中使用。
参数:
@q:请求队列
*/
3、驱动程序
#define BLKSIZE (1 * 1024 * 1024) // 1M
#define DISKNAME "mydisk"
struct gendisk* mydisk;
int major = 0;
struct request_queue* q;
struct blk_mq_tag_set set;
char* disk_addr = NULL; //磁盘的首地址
blk_status_t mydisk_queue_rq(struct blk_mq_hw_ctx* hctx,
const struct blk_mq_queue_data* bd)
{
blk_status_t status = BLK_STS_OK;
struct request* rq = bd->rq;
loff_t pos = blk_rq_pos(rq) << 9;
struct bio_vec bvec;
struct req_iterator iter;
//开始处理队列
blk_mq_start_request(rq);
//循环从requet中获取bio_vec
rq_for_each_segment(bvec, rq, iter)
{
unsigned long b_len = bvec.bv_len;
//获取用户相关的线性地址
void* b_buf = page_address(bvec.bv_page) + bvec.bv_offset;
//校验越界
if ((pos + b_len) > BLKSIZE)
b_len = (unsigned long)(BLKSIZE - pos);
if (rq_data_dir(rq)) // WRITE
memcpy(disk_addr + pos, b_buf, b_len);
else // READ
memcpy(b_buf,disk_addr + pos, b_len);
pos += b_len;
}
//队列处理结束
blk_mq_end_request(rq, status);
return status;
}
struct blk_mq_ops mqops = {
.queue_rq = mydisk_queue_rq,
};
int mydisk_open(struct block_device* blkdev, fmode_t mode)
{
return 0;
}
int mydisk_getgeo(struct block_device* blkdev, struct hd_geometry* hd)
{
hd->heads = 4;
hd->cylinders = 16;
hd->sectors = BLKSIZE / hd->heads / hd->cylinders / 512;
return 0;
}
void mydisk_close(struct gendisk* disk, fmode_t mode)
{
}
struct block_device_operations fops = {
.open = mydisk_open,
.release = mydisk_close,
.getgeo = mydisk_getgeo,
};
static int __init mydisk_init(void)
{
// 1.分配gendisk
mydisk = alloc_disk(4);
// 2.对象初始化
major = register_blkdev(0, DISKNAME);
//设置磁盘的容量
set_capacity(mydisk, BLKSIZE >> 9);
//初始化IO请求队列
q = blk_mq_init_sq_queue(&set, &mqops, 2, BLK_MQ_F_SHOULD_MERGE);
mydisk->major = major;
mydisk->first_minor = 0;
//这是一个数组,数组只有在定义的时候才能赋初值
strcpy(mydisk->disk_name, DISKNAME);
mydisk->fops = &fops;
mydisk->queue = q;
// 3.分配1M的内存当成硬盘使用
disk_addr = vmalloc(BLKSIZE);
// 4.注册
add_disk(mydisk);
return 0;
}
static void __exit mydisk_exit(void)
{
del_gendisk(mydisk);
vfree(disk_addr);
blk_cleanup_queue(q);
unregister_blkdev(major, DISKNAME);
put_disk(mydisk);
}
module_init(mydisk_init);
module_exit(mydisk_exit);