700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > linux内核的块设备驱动框架详解

linux内核的块设备驱动框架详解

时间:2022-11-10 00:12:09

相关推荐

linux内核的块设备驱动框架详解

1、块设备和字符设备的差异

(1)块设备只能以块为单位接受输入和返回输出,而字符设备则以字节为单位。大多数设备是字符设备,因为它们不需要缓冲而且不以固定块大小进行操作;

(2)块设备对于 I/O 请求有对应的缓冲区,因此它们可以选择以什么顺序进行响应,字符设备无须缓冲且被直接读写;

(3)字符设备只能被顺序读写,而块设备可以随机访问;

2、块设备驱动的特点

(1)块和字符是两种不同的访问设备的策略,通一个设备可以同时支持块和字符两种访问策略,但是效率会有差别;

(2)设备本身的物理特性决定了采用块和字符两种访问策略效率会有差别,块设备驱动最适合存储设备,因为存储设备本身就是以块为单位进行读写操作;

(3)虽然块设备可随机访问,但是对于磁盘这类机械设备而言,顺序地组织块设备的访问可以提高性能;而对 SD 卡、 RamDisk 等块设备而言,不存在机械上的原因,进行这样的调整没有必要

3、块设备相关的重要概念

(1)扇区(Sector),概念来自于早期磁盘,在硬盘、DVD中还有用,在Nand/SD中已经没意义了,扇区是块设备本身的特性,大小一般为512的整数倍,因为历史原因很多时候都向前兼容定义为512.

(2)块(block),概念来自于文件系统,是内核对文件系统数据处理的基本单位,大小为若干个扇区,常见有512B、1KB、4KB等

(3)段(Section),概念来自于内核,是内核的内存管理中一个页或者部分页,由若干个连续为块组成,称为段页式访问。

(4)页(Page),概念来自于内核,是内核内存映射管理的基本单位。linux内核的页式内存映射名称来源于此。

总结:块设备驱动对存储硬件以扇区为单位进行交换数据,对上层文件系统以块为单位交换数据;

4、磁原理存储设备和电原理存储设备的区别

(1)磁原理存储设备是早于电原理存储设备出现,磁原理存储设备包括机械硬盘、CD、磁带等,电原理存储设备主要是flash存储,包括NandFlash、iNand、SD卡、eMMC卡、NorFlash等;

(2)上面提到的扇区、磁道等概念都是磁原理存储设备才有的,但是现在的内核中,电原理存储设备也会使用这些概念,主要是为了兼容性和统一性;

(3)磁原理存储设备由于物理限制,连续读相邻扇区的速度比读不连续扇区的速度要快,主要原因是读不连续的扇区需要移动磁头,而移动磁头是机械工作比较耗时;而电原理存储设备不存在这个问题,无论读哪块扇区速度都是一样的;

(4)基于第三点讲到的原因,磁原理存储设备会调用内核的IO调用系统,去优化读写扇区的顺序,而电原理存储设备不需要IO调度系统,在后面会具体介绍;

(5)使用请求队列对于一个机械的磁盘设备而言的确有助于提高系统的性能,但是对于电原理存储设备,无法从高级的请求队列逻辑中获益;(请求队列实际就是对于IO调度系统)

5、块设备的访问方式

(1)操作字符设备是通过操作设备节点进行,把设备节点当做一个文件进行操作,调用open、close、ioctl等函数接口;

(2)块设备驱动也会生成设备节点,但是并不像字符设备一样调用open、close、ioctl等函数接口去操作设备节点,而是通过mount挂载的方式,以文件系统的方式去访问块设备;

(3)块设备被mount挂载前先要格式化,也就是把块设备按照某种文件系统格式进行初始化,初始化之后挂载到VFS的某个目录下,就可以像操作普通文件一样去操作块设备里的数据;

6、块设备驱动框图

(1)在mount成功后,应用程序通过I/O系统调用来访问块设备的文件,和应用程序交互的是VFS虚拟文件系统,但是块设备可能是EXT3、EXT4等文件系统,中间会做从VSF文件系统到块设备文件系统的转换;

(2)块设备层:

<1>块设备层有缓冲区,如果要读取的块在缓冲区能找到就可以直接返回,不必再去访问块设备;

<2>如果要访问的块不在缓冲区中,则将要访问的块数据添加到对应块设备的请求队列之中;

(3)I/O调度层:

<1>I/O调度层负责管理请求队列,决定请求的处理顺序,也会根据情况合并请求,提高访问效率;

<2>I/O调度层主要是针对机械式存储设备,因为机械式存储设备访问不相邻的扇区需要移动磁头,这个动作是比较耗时的;而电原理存储设备不存在这个问题,所以电原理存储设备可以不需要I/O调度层;

<3>I/O调度层处理请求是回调设备驱动层注册的请求处理函数;

(4)设备驱动层:

<1>设备驱动层是和具体设备相关的,不同的存储设备对应不同的设备驱动程序;

<2>虽然设备驱动程序不同,但是整体框架都是按照内核块设备驱动框架提供的接口实现的;

7、I/O调度层

(1)I/O调度层是优化对块设备的访问顺序,提供访问效率,对块设备的请求进行排序、合并等操作;

(2)I/O调度层采用电梯算法去优化请求的访问顺序,因为对块设备的请求很类似于电梯的情况,可以把读块设备的请求看做电梯上行,把写块设备的请求看做电梯下行,最终效果就是要让电梯运行最少距离把所有需要的楼层都访问一遍;

(3)内核实现了四种电梯算法,具体参见上图;

8、块设备相关的结构体

8.1、struct gendisk

1 struct gendisk2 {3 int major; /* 主设备号 */4 int first_minor; /*第 1 个次设备号*/5 int minors; /* 最大的次设备数,如果不能分区,则为 1*/6 char disk_name[32]; /* 设备名称,将来显示在/proc/partitions、sysfs中 */7 struct hd_struct **part; /* 磁盘上的分区信息 */8 struct block_device_operations *fops; /*块设备操作结构体*/9 struct request_queue *queue; /*请求队列*/10 void *private_data; /*私有数据*/11 sector_t capacity; /*扇区数, 512 字节为 1 个扇区*/1213 int flags;14 char devfs_name[64];15 int number;16 struct device *driverfs_dev;17 struct kobject kobj;1819 struct timer_rand_state *random;20 int policy;2122 atomic_t sync_io; /* RAID */23 unsigned long stamp;24 int in_flight;25 #ifdef CONFIG_SMP26 struct disk_stats *dkstats;27 #else28 struct disk_stats dkstats;29 #endif30 };

(1)struct gendisk结构体在内核中用于表示块设备或者分区,如果是同一块设备的不同分区,则各个分区共享同一主设备号,次设备号不同;

(2)向内核注册块设备就是构建一个struct gendisk结构体,然后调用注册函数进行注册;

8.2、struct block_device_operations

1 struct block_device_operations2 {3 int(*open)(struct inode *, struct file*); //打开4 int(*release)(struct inode *, struct file*); //释放5 int(*ioctl)(struct inode *, struct file *, unsigned, unsigned long); //ioctl函数一般比较简单,因为不少请求都被块设备层处理6 long(*unlocked_ioctl)(struct file *, unsigned, unsigned long);7 long(*compat_ioctl)(struct file *, unsigned, unsigned long);8 int(*direct_access)(struct block_device *, sector_t, unsigned long*);9 int(*media_changed)(struct gendisk*); //被内核调用来检查是否驱动器中的介质已经改变10 int(*revalidate_disk)(struct gendisk*); //被调用来响应一个介质改变,它给驱动一个机会来进行必要的工作以使新介质准备好11 int(*getgeo)(struct block_device *, struct hd_geometry*);//获得驱动器信息12 struct module *owner; //模块拥有者,通常被初始化为 THIS_MODULE13 };

(1)struct block_device_operations结构体类似于字符设备的struct file_operations结构体,都是定义的一些操作设备的方法;

(2)但是块设备的block_device_operations结构体中没有read、write方法,块设备的读写通过请求队列的方式实现;

8.3、struct request_queue

1 struct request_queue2 {3 ...4 /* 保护队列结构体的自旋锁 */5 spinlock_t _ _queue_lock;6 spinlock_t *queue_lock;78 /* 队列 kobject */9 struct kobject kobj;1011 /* 队列设置 */12 unsigned long nr_requests; /* 最大的请求数量 */13 unsigned int nr_congestion_on;14 unsigned int nr_congestion_off;15 unsigned int nr_batching;1617 unsigned short max_sectors; /* 最大的扇区数 */18 unsigned short max_hw_sectors;19 unsigned short max_phys_segments; /* 最大的段数 */20 unsigned short max_hw_segments;21 unsigned short hardsect_size; /* 硬件扇区尺寸 */22 unsigned int max_segment_size; /* 最大的段尺寸 */2324 unsigned long seg_boundary_mask; /* 段边界掩码 */25 unsigned int dma_alignment; /* DMA 传送的内存对齐限制 */2627 struct blk_queue_tag *queue_tags;2829 atomic_t refcnt; /* 引用计数 */3031 unsigned int in_flight;3233 unsigned int sg_timeout;34 unsigned int sg_reserved_size;35 int node;3637 struct list_head drain_list;3839 struct request *flush_rq;40 unsigned char ordered;41 };

(1)在块设备驱动框架中,将应用对块设备的操作用请求来表达,然后用一个请求队列来管理这些请求;

(2)请求队列会保存用于描述这个设备能够支持的请求的类型信息、它们的最大大小、多少不同的段可进入一个请求、硬件扇区大小、对齐要求等参数,需要保证不会向设备提交一个不能处理的请求;

(3)请求队列和IO调度器紧密相关,I/O调度器会对请求队列中的请求进行排序或者合并,使得以最优的性能向设备驱动提交I/O请求;

8.4、struct request

1 struct request2 {3 struct list_head queuelist; /*链表结构*/4 unsigned long flags; /* REQ_ */56 sector_t sector; /* 要传送输的下一个扇区 */7 unsigned long nr_sectors; /*要传送的扇区数目*/8 9 unsigned int current_nr_sectors;/*当前要传送的扇区数目*/1011 sector_t hard_sector; /*要完成的下一个扇区*/12 unsigned long hard_nr_sectors; /*要被完成的扇区数目*/13 14 unsigned int hard_cur_sectors;/*当前要被完成的扇区数目*/1516 struct bio *bio; /*请求的 bio 结构体的链表*/17 struct bio *biotail; /*请求的 bio 结构体的链表尾*/1819 void *elevator_private; unsigned short ioprio;2223 int rq_status;24 struct gendisk *rq_disk;25 int errors;26 unsigned long start_time;2728 /*请求在物理内存中占据的不连续的段的数目, scatter/gather 列表的尺寸*/29 unsigned short nr_phys_segments;3031 /*与 nr_phys_segments 相同,但考虑了系统 I/O MMU 的 remap */32 unsigned short nr_hw_segments;3334 int tag;35 char *buffer; /*传送的缓冲,内核虚拟地址*/3637 int ref_count; /* 引用计数 */38 ...39 };

在 Linux 块设备驱动中,使用request结构体来表征等待进行的 I/O 请求;

8.5、struct bio

8.5.1、结构体定义

1 struct bio2 {3 sector_t bi_sector; /* 要传输的第一个扇区 */4 struct bio *bi_next; /* 下一个 bio */5 struct block_device *bi_bdev;6 unsigned long bi_flags; /* 状态、命令等 */7 unsigned long bi_rw; /* 低位表示 READ/WRITE,高位表示优先级*/8struct bvec_iter bi_iter; /* 迭代器,标明数据要操作的块设备的位置 */9 unsigned short bi_vcnt; /* bio_vec 数量 */10 unsigned short bi_idx; /* 当前 bvl_vec 索引 */1112 /*不相邻的物理段的数目*/13 unsigned short bi_phys_segments;1415 /*物理合并和 DMA remap 合并后不相邻的物理段的数目*/16 unsigned short bi_hw_segments;1718 unsigned int bi_size; /* 以字节为单位所需传输的数据大小 */1920 /* 为了明了最大的 hw 尺寸,我们考虑这个 bio 中第一个和最后一个21 虚拟的可合并的段的尺寸 */22 unsigned int bi_hw_front_size;23 unsigned int bi_hw_back_size;2425 unsigned int bi_max_vecs; /* 我们能持有的最大 bvl_vecs 数 */2627 struct bio_vec *bi_io_vec; /* 实际的 vec 列表 */2829 bio_end_io_t *bi_end_io;30 atomic_t bi_cnt;3132 void *bi_private;3334 bio_destructor_t *bi_destructor; /* destructor */3536/*37 * We can inline a number of vecs at the end of the bio, to avoid38 * double allocations for a small number of bio_vecs. This member39 * MUST obviously be kept at the very end of the bio.40 */41struct bio_vecbi_inline_vecs[0];42 };

(1)一个bio对应一个 I/O 请求,I/O调度算法可将连续的bio合并成一个请求,所以一个struct request结构体可以包含多个bio;

(2)bio的核心成员是bio_vec,不过我们不应该直接访问bio的bio_vec成员,而应该使用bio_for_each_segment()宏来进行这项工作,可以

用这个宏循环遍历整个bio中的每个段;

(3)bi_iter是迭代器,标明此次数据要写到块设备的哪个位置;

(4)bio_vec结构体指明数据再内存中的位置,bi_iter标明数据在块设备中的位置;

(5)bi_inline_vecs:这是一个数组成员个数为零的数组,是不占用内存的,相当于一个标号,标明bio结构体的结束地址,具体用法参考博客:《在结构体最后定义一个成员数为0的数组的意义以及工作当中的使用》;

8.5.2、bi_inline_vecs和bi_io_vec

(1)在bio结构体中有bi_inline_vecs和bi_io_vec两个变量来描述bio_vec,在申请 struct bio结构体内存时会多申请4个bio_vec结构体的内存跟在bio结构体的后面,这里的作用是用牺牲内存的方式来换取效率。

(2)当bio中所包含的bio_vec个数小于4个时,就直接保存到bio结构体的尾部,bi_io_vec变量也指向bi_inline_vecs处,两个变量指向同一位置;

(3)当bio中所包含的bio_vec个数大于4个时,需要重新申请内存去保存bio_vec,然后把申请内存的指针赋值给bi_io_vec,此时之前申请的bio尾部的4个bio_vec结构体的内存就浪费了,但是申请内存是比较耗时的;

8.6、struct bio_vec

1 struct bio_vec2 {3 struct page *bv_page; /* 页指针 */4 unsigned int bv_len; /* 传输的字节数 */5 unsigned int bv_offset; /* 偏移位置 */6 };

bio_vec表明数据在内存中的位置;

9、结构体之间的关联

9.1、request_queue、request、bio三者联系

(1)把应用程序操作块设备的动作用request来表达,每个块设备都有一个request_queue队列来管理对块设备的request;

(2)由于I/O调度器的优化,可能合并请求,所以一个request可能包含多个bio,也就是一个请求包含多个I/O操作;

9.2、bio、bio_vec数组、bvec_iter之间的联系

10、gendisk结构体相关操作函数

11、请求队列相关操作函数

12、bio相关的操作函数

12.1、bio_for_each_segment()宏

1 #define _ _bio_for_each_segment(bvl, bio, i, start_idx) \2 for (bvl = bio_iovec_idx((bio), (start_idx)), i = (start_idx); \3 i < (bio)->bi_vcnt; \4 bvl++, i++)56 #define bio_for_each_segment(bvl, bio, i) \7 _ _bio_for_each_segment(bvl, bio, i, (bio)->bi_idx

循环遍历整个 bio 中的每个段

12.2、__rq_for_each_bio()宏

#define __rq_for_each_bio(_bio, rq)\if ((rq->bio))\ for (_bio = (rq)->bio; _bio; _bio = _bio->bi_next)//_bio 是遍历出来的每个 bio,为 bio 结构体指针类//rq 是要进行遍历操作的请求,为 request 结构体指针类型

12.3、bio相关函数集合

13、块设备的注册和卸载

int register_blkdev(unsigned int major, const char *name);int unregister_blkdev(unsigned int major, const char *name);

(1)major:在注册时填0则内核自动分配一个未使用的主设备号,如果major是1-255范围的一个数,则表示向内核申请该主设备号,如果已经被占用则返回失败;

(2)name:设备名,注册成功后在"/proc/devices"中可以查看;

14、块设备驱动的注册过程

(1)分配、初始化请求队列,绑定请求队列和请求函数;

(2)分配、初始化 gendisk,给 gendisk 的 major、 fops、 queue 等成员赋值,最后添加 gendisk;

(3)注册块设备驱动;

(4)内核的块设备驱动框架会调用块设备驱动绑定的请求处理函数,去处理应用下发的对块设备的I/O请求;

15、块设备驱动程序示例代码

(1)这里用内存来模拟块设备,申请一块内存当做块设备存储介质;

(2)分别用请求队列和不用请求队列来编写块设备驱动示例程序,其中用请求队列是对应磁原理存储设备,不用请求队列对应电原理存储设备;

(3)使用请求队列:就是要调用内核的I/O调度器,每次给块设备驱动处理的请求由I/O调度器决定;

(4)不使用请求队列:就是不调用内核的I/O调度器,每次给块设备驱动处理的请求中可以包含多个bio,一次性处理多个I/O请求

15.1、使用请求队列的ramdisk代码

#include <linux/module.h>#include <linux/slab.h>#include <linux/errno.h>#include <linux/interrupt.h>#include <linux/mm.h>#include <linux/fs.h>#include <linux/kernel.h>#include <linux/timer.h>#include <linux/genhd.h>#include <linux/hdreg.h>#include <linux/ioport.h>#include <linux/init.h>#include <linux/wait.h>#include <linux/blkdev.h>#include <linux/blkpg.h>#include <linux/delay.h>#include <linux/io.h>#include <asm/system.h>#include <asm/uaccess.h>#include <asm/dma.h>#define RAMBLOCK_SIZE (1024*1024) // 1MB,2048扇区static struct gendisk *my_ramblock_disk;// 磁盘设备的结构体static struct request_queue *my_ramblock_queue;// 等待队列static DEFINE_SPINLOCK(my_ramblock_lock);static int major;static unsigned char *my_ramblock_buf;// 虚拟块设备的内存指针static void do_my_ramblock_request(struct request_queue *q){struct request *req;static int r_cnt = 0; //实验用,打印出驱动读与写的调度次数static int w_cnt = 0;//从请求队列中取出一个请求req = blk_fetch_request(q);while (NULL != req){//要传输的数据所在位置,blk_rq_pos返回要传送输的下一个扇区unsigned long start = blk_rq_pos(req) *512;//获取要操作的数据的长度unsigned long len = blk_rq_cur_bytes(req);//判断此次I/O操作是读还是写if(rq_data_dir(req) == READ){// 读请求memcpy(req->buffer, my_ramblock_buf + start, len); //读操作,printk("do_my_ramblock-request read %d times\n", r_cnt++);}else{// 写请求memcpy( my_ramblock_buf+start, req->buffer, len); //写操作printk("do_my_ramblock request write %d times\n", w_cnt++);}//判断是否是最后一个请求,如果不是则获取下一个请求if(!__blk_end_request_cur(req, 0)) {req = blk_fetch_request(q);}}}static int blk_ioctl(struct block_device *dev, fmode_t no, unsigned cmd, unsigned long arg){return -ENOTTY;}static int blk_open (struct block_device *dev , fmode_t no){printk("11111blk mount succeed\n");return 0;}static int blk_release(struct gendisk *gd , fmode_t no){printk("11111blk umount succeed\n");return 0;}static const struct block_device_operations my_ramblock_fops ={.owner = THIS_MODULE,.open = blk_open,.release = blk_release,.ioctl = blk_ioctl,};static int my_ramblock_init(void){//申请块设备的主设备号,注册成功可以在/proc/devices中看到major = register_blkdev(0, "my_ramblock");if (major < 0){printk("fail to regiser my_ramblock\n");return -EBUSY;}// 实例化my_ramblock_disk = alloc_disk(1);//alloc_disk()传参=分区个数 + 1,填1就代表块设备不分区//分配设置请求队列,绑定请求处理函数my_ramblock_queue = blk_init_queue(do_my_ramblock_request, &my_ramblock_lock);//设置硬盘属性 my_ramblock_disk->major = major;//主设备号my_ramblock_disk->first_minor = 0;//次设备号的起始值my_ramblock_disk->fops = &my_ramblock_fops;//块设备的操作函数sprintf(my_ramblock_disk->disk_name, "my_ramblcok");// 块设备的设备节点名字,注册成功后在/dev/目录下可以看到my_ramblock_disk->queue = my_ramblock_queue;//绑定请求队列set_capacity(my_ramblock_disk, RAMBLOCK_SIZE / 512);//设置块设备的容量,以扇区为单位// 硬件相关操作,这里申请的内存用来模拟块设备的存储空间my_ramblock_buf = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL);// 向驱动框架注册一个disk或者一个partation的接口add_disk(my_ramblock_disk);return 0;}static void my_ramblock_exit(void){unregister_blkdev(major, "my_ramblock");del_gendisk(my_ramblock_disk);put_disk(my_ramblock_disk);blk_cleanup_queue(my_ramblock_queue);kfree(my_ramblock_buf); }module_init(my_ramblock_init);module_exit(my_ramblock_exit);MODULE_LICENSE("GPL");

15.2、不使用请求队列的ramdisk代码

#define RAMDISK_SIZE(2 * 1024 * 1024) /* 容量大小为2MB */#define RAMDISK_NAME"ramdisk"/* 名字 */#define RADMISK_MINOR3/* 表示有三个磁盘分区!不是次设备号为3! *//* ramdisk设备结构体 */struct ramdisk_dev{int major;/* 主设备号 */unsigned char *ramdiskbuf;/* ramdisk内存空间,用于模拟块设备 */spinlock_t lock;/* 自旋锁 */struct gendisk *gendisk; /* gendisk */struct request_queue *queue;/* 请求队列 */};struct ramdisk_dev ramdisk;/* ramdisk设备 *//* 打开块设备 */int ramdisk_open(struct block_device *dev, fmode_t mode){printk("ramdisk open\r\n");return 0;}/* 释放块设备 */void ramdisk_release(struct gendisk *disk, fmode_t mode){printk("ramdisk release\r\n");}/* 获取磁盘信息 */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;}/* 块设备操作函数 */static struct block_device_operations ramdisk_fops ={.owner = THIS_MODULE,.open = ramdisk_open,.release = ramdisk_release,.getgeo = ramdisk_getgeo,};/* “制造请求”函数 */void ramdisk_make_request_fn(struct request_queue *q, struct bio *bio){int offset;struct bio_vec bvec;struct bvec_iter iter;unsigned long len = 0;offset = bio->bi_iter.bi_sector * 512;/* 获取要操作的设备的偏移地址 *//* 处理bio中的每个段 */bio_for_each_segment(bvec, bio, iter){char *ptr = page_address(bvec.bv_page) + bvec.bv_offset;len = bvec.bv_len;if(bio_data_dir(bio) == READ)/* 读数据 */memcpy(ptr, ramdisk.ramdiskbuf + offset, len);else if(bio_data_dir(bio) == WRITE)/* 写数据 */memcpy(ramdisk.ramdiskbuf + offset, ptr, len);offset += len;}set_bit(BIO_UPTODATE, &bio->bi_flags);bio_endio(bio, 0);}/* 驱动出口函数 */static int __init ramdisk_init(void){int ret = 0;printk("ramdisk init\r\n");/* 1、申请用于ramdisk内存 */ramdisk.ramdiskbuf = kzalloc(RAMDISK_SIZE, GFP_KERNEL);if(ramdisk.ramdiskbuf == NULL) {ret = -EINVAL;goto ram_fail;}/* 2、初始化自旋锁 */spin_lock_init(&ramdisk.lock);/* 3、注册块设备 */ramdisk.major = register_blkdev(0, RAMDISK_NAME); /* 由系统自动分配主设备号 */if(ramdisk.major < 0) {goto register_blkdev_fail;} printk("ramdisk major = %d\r\n", ramdisk.major);/* 4、分配并初始化gendisk */ramdisk.gendisk = alloc_disk(RADMISK_MINOR);if(!ramdisk.gendisk) {ret = -EINVAL;goto gendisk_alloc_fail;}/* 5、分配请求队列 */ramdisk.queue = blk_alloc_queue(GFP_KERNEL);if(!ramdisk.queue){ret = -EINVAL;goto blk_allo_fail;}/* 6、设置“制造请求”函数 */blk_queue_make_request(ramdisk.queue, ramdisk_make_request_fn);/* 7、添加(注册)disk */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_allo_fail:put_disk(ramdisk.gendisk);//del_gendisk(ramdisk.gendisk);gendisk_alloc_fail:unregister_blkdev(ramdisk.major, RAMDISK_NAME);register_blkdev_fail:kfree(ramdisk.ramdiskbuf); /* 释放内存 */ram_fail:return ret;}/* 驱动出口函数 */static void __exit ramdisk_exit(void){printk("ramdisk exit\r\n");/* 释放gendisk */del_gendisk(ramdisk.gendisk);put_disk(ramdisk.gendisk);/* 清除请求队列 */blk_cleanup_queue(ramdisk.queue);/* 注销块设备 */unregister_blkdev(ramdisk.major, RAMDISK_NAME);/* 释放内存 */kfree(ramdisk.ramdiskbuf); }module_init(ramdisk_init);module_exit(ramdisk_exit);MODULE_LICENSE("GPL");

16、参考资料

(1)/Chuangke_Andy/article/details/122498865;

(2)《朱老师核心课程》、《LINUX 设备驱动开发详解》 、《静态linux设备驱动程序开发》;

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。