Linux驱动开发:块设备驱动

Linux驱动开发:块设备驱动目录 1 块设备简介 2 块设备驱动 API 2 1 block device 2 1 1 register blkdev 注册块设备 2 1 2 unregister blkdev 注销块设备 2 2 gendisk 2 2 1 gendisk 结构体 2 2 2 alloc disk 申请 gensisk

大家好,我是讯享网,很高兴认识大家。

目录

1、块设备简介

2、块设备驱动API

2.1 block_device

2.1.1 register_blkdev:注册块设备

2.1.2 unregister_blkdev:注销块设备

2.2 gendisk

2.2.1 gendisk 结构体

2.2.2 alloc_disk:申请gensisk

2.2.3 put_disk:释放disk

2.2.4 add_disk:将已初始化好的gendisk指针添加到内核 

2.2.5 del_gendisk:删除gendisk

2.2.6 set_capacity:设置磁盘容量

2.3 request_queue

2.3.1 request

2.3.2 request_queue

2.3.3 bio

2.3.4 bio_vec 

2.3.5 blk_mq_init_sq_queue

2.3.6 blk_cleanup_queue

3、驱动程序


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);

小讯
上一篇 2025-02-17 07:00
下一篇 2025-02-20 17:49

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/28869.html