块设备驱动是用来操作硬盘类的存储设备的

磁头:一个磁盘有多少个面就有多少个磁头
磁道:在一个磁头上可以有很多环,这些环就叫做磁道
扇区:磁道上访问数据的最小的单位就是扇区,一个扇区的大小就是512字节
1block = 512字节 1024字节 2048字节 4096字节
1扇区 = 512字节
块设备的能存储的数据=磁头*磁道*扇区*512
取数据的逻辑和电梯调度的逻辑一样

user:
open read write close
-------------------(io请求)-----------------------------------
kernel |中间层: (block_device)
| 将用户的io请求转化成BIO(block,input ,output),
| 在物理内存上连续的bio会被合成request,这个request
| 会被放到内核的一个队列上。
|---------------------------------------------------------
|driver:gendisk
| 1.分配对象
| 2.对象初始化
| 3.初始化一个队列 head----request(read)----request(write)---...
| //4.硬盘设备的初始化
| 5.注册、注销
------------------------------------------------------------------
haredware : 分配的内存(模拟真实的设备)(1M)
1.gendisk的结构体对象
struct gendisk {
int major; //块设备的主设备号
int first_minor; //起始的次设备号
int minors; // 最大的次设备数,如果不能分区,则为1
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.结构体对象的初始化
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等
返回值:成功返回队列指针,失败返回错误码指针
3.注册、注销
void add_disk(struct gendisk *disk)
//注册
void del_gendisk(struct gendisk *disk)
//注销
#include
#include
#include
#include
#include
#define BLKSIZE (1 * 1024 * 1024) // 1M
#define BLKNAME "mydisk"
struct gendisk* disk;
int major;
char *addr;
int mydisk_open(struct block_device* blkdev, fmode_t mode)
{
printk("%s:%d\n", __func__, __LINE__);
return 0;
}
void mydisk_close(struct gendisk* dk, fmode_t mode)
{
printk("%s:%d\n", __func__, __LINE__);
}
int mydisk_getgeo(struct block_device* blkdev, struct hd_geometry* hd)
{
printk("%s:%d\n", __func__, __LINE__);
// struct hd_geometry {
// unsigned char heads; //磁头个数
// unsigned char sectors; //扇区个数
// unsigned short cylinders; //磁道个数
// };
hd->heads = 4;
hd->cylinders = 16;
hd->sectors = BLKSIZE / hd->heads / hd->cylinders / 512;
return 0;
}
const struct block_device_operations fops = {
.open = mydisk_open,
.release = mydisk_close,
.getgeo = mydisk_getgeo,
};
static int __init mydisk_init(void)
{
// 1.分配对象
disk = alloc_disk(4);
if (disk == NULL) {
printk("alloc gendisk memory error\n");
return -ENOMEM;
}
// 2.初始化对象
major = register_blkdev(0, BLKNAME);
if (major < 0) {
printk("get blkdev number error\n");
return major;
}
disk->major = major;
disk->first_minor = 0;
strcpy(disk->disk_name, BLKNAME);
set_capacity(disk, BLKSIZE >> 9);
disk->fops = &fops;
// disk->queue =
//3.申请1M内存当成硬盘使用
addr = vmalloc(BLKSIZE);
//4.注册
add_disk(disk);
return 0;
}
static void __exit mydisk_exit(void)
{
del_gendisk(disk);
vfree(addr);
unregister_blkdev(major,BLKNAME);
put_disk(disk);
}
module_init(mydisk_init);
module_exit(mydisk_exit);
MODULE_LICENSE("GPL");
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*/
…
};
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请求
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对象 */
};
struct bio_vec {
struct page *bv_page; //指向用于数据传输的页面所对应的struct page对象
unsigned int bv_len; //表示当前要传输的数据大小
unsigned int bv_offset;//表示数据在页面内的偏移量
};
reque_queue和request及bio的关系

队列处理相关的函数(参考块设备驱动代码文件夹)
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) //磁盘设备的地址
#include
#include
#include
#include
#include
#include
#include
#define BLKSIZE (1 * 1024 * 1024) // 1M
#define BLKNAME "mydisk"
struct gendisk* disk;
int major;
struct request_queue* queue;
struct blk_mq_tag_set tag;
char* devaddr = NULL;
int mydisk_open(struct block_device* blkdev, fmode_t mode)
{
printk("%s:%d\n", __func__, __LINE__);
return 0;
}
void mydisk_close(struct gendisk* dk, fmode_t mode)
{
printk("%s:%d\n", __func__, __LINE__);
}
int mydisk_getgeo(struct block_device* blkdev, struct hd_geometry* hd)
{
printk("%s:%d\n", __func__, __LINE__);
// struct hd_geometry {
// unsigned char heads; //磁头个数
// unsigned char sectors; //扇区个数
// unsigned short cylinders; //磁道个数
// };
hd->heads = 4;
hd->cylinders = 16;
hd->sectors = BLKSIZE / hd->heads / hd->cylinders / 512;
return 0;
}
const struct block_device_operations fops = {
.open = mydisk_open,
.release = mydisk_close,
.getgeo = mydisk_getgeo,
};
blk_status_t queue_handle(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;
struct bio_vec bvec;
struct req_iterator iter;
loff_t pos = blk_rq_pos(rq) << SECTOR_SHIFT;//宏为9 相当于除以2^9(512)
// 1.开始处理队列
blk_mq_start_request(rq);
// 2.队列处理过程
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
//devaddr + pos:磁盘的地址
//b_buf :用户的地址
//b_len :本次写的长度
memcpy(devaddr + pos, b_buf, b_len);
else // READ
//b_buf :用户的地址
//devaddr + pos:磁盘的地址
//b_len :本次写的长度
memcpy(b_buf, devaddr + pos, b_len);
pos += b_len;
}
// 3.队列处理完成
blk_mq_end_request(rq, status);
return status; // always return ok
}
const struct blk_mq_ops rq_ops = {
.queue_rq = queue_handle,
};
static int __init mydisk_init(void)
{
// 1.分配对象
disk = alloc_disk(4);
if (disk == NULL) {
printk("alloc gendisk memory error\n");
return -ENOMEM;
}
// 2.初始化对象
major = register_blkdev(0, BLKNAME);
if (major < 0) {
printk("get blkdev number error\n");
return major;
}
queue = blk_mq_init_sq_queue(&tag, &rq_ops, 2, BLK_MQ_F_SHOULD_MERGE);
if (IS_ERR(queue)) {
printk("init queue error\n");
return PTR_ERR(queue);
}
disk->major = major;
disk->first_minor = 0;
strcpy(disk->disk_name, BLKNAME);
set_capacity(disk, BLKSIZE >> 9);
disk->fops = &fops;
disk->queue = queue;
// 3.申请1M内存当成硬盘使用
devaddr = vmalloc(BLKSIZE);
if (devaddr == NULL) {
printk("vmalloc memory error\n");
return -ENOMEM;
}
// 4.注册
add_disk(disk);
return 0;
}
static void __exit mydisk_exit(void)
{
del_gendisk(disk);
vfree(devaddr);
blk_cleanup_queue(queue);
unregister_blkdev(major, BLKNAME);
put_disk(disk);
}
module_init(mydisk_init);
module_exit(mydisk_exit);
MODULE_LICENSE("GPL");
1.安装块设备驱动
sudo insmod mydisk.ko
2.查看设备节点
ls -l /dev/mydisk
brw-rw---- 1 root disk 252, 0 6月 22 14:14 /dev/mydisk
3.查看磁盘相关的命令
sudo fdisk -l
Disk /dev/mydisk:1 MiB,1048576 字节,2048 个扇区
单元:扇区 / 1 * 512 = 512 字节
扇区大小(逻辑/物理):512 字节 / 512 字节
I/O 大小(最小/最佳):512 字节 / 512 字节
4.分区
sudo fdisk /dev/mydisk
m 获取帮助
d 删除分区
p 打印分区表
n 添加新分区
w 将分区表写入磁盘并退出
q 退出而不保存更改
在dev下产生/dev/mydisk1节点
5.分区格式化
sudo mkfs.ext2 /dev/mydisk1
6.挂载硬盘到某个目录下
sudo mount -t ext2 /dev/mydisk1 ~/udisk
7.向磁盘中存放文件
sudo cp ~/work/day10/02mydisk/mydisk.c .
8.取消挂载
cd
sudo umount udisk
9.将块设备驱动的数据读取到ram.bin文件中
sudo cat /dev/mydisk1 > ram.bin
10.重新挂载查看
sudo mount -o loop ram.bin ~/udisk
去udisk目录下查看是否有刚才拷贝的文件,如果有就说明成功了