Linux驱动开发:块设备驱动

一、块设备的简介

  Linux 三大驱动类型:字符设备驱动、块设备驱动、网络设备驱动。
  块设备是针对存储设备的,比如 SD 卡、 EMMC、 NAND Flash、 Nor Flash、 SPI Flash、机械硬盘、固态硬盘等。因此块设备驱动其实就是这些存储设备驱动,块设备驱动相比字符设备驱动的主要区别如下:

①、块设备只能以块为单位进行读写访问,块是 linux 虚拟文件系统(VFS)基本的数据传输单位。字符设备是以字节为单位进行数据传输的,不需要缓冲。
②、块设备在结构上是可以进行随机访问的,对于这些设备的读写都是按块进行的,块设备使用缓冲区来暂时存放数据,等到条件成熟以后再一次性将缓冲区中的数据写入块设备中。这么做的目的为了提高块设备寿命,大家如果仔细观察的话就会发现有些硬盘或者 NAND Flash
就会标明擦除次数(flash 的特性,写之前要先擦除),比如擦除 100000 次等。因此,为了提高块设备寿命引入了缓冲区,数据先写入到缓冲区中,等满足一定条件后再一次性写入到真正的物理存储设备中,这样就减少了对块设备的擦除次数,提高了块设备寿命。
  字符设备是顺序的数据流设备,字符设备是按照字节进行读写访问的。字符设备不需要缓冲区,对于字符设备的访问都是实时的,而且也不需要按照固定的块大小进行访问。
  块设备结构的不同其 I/O 算法也会不同,比如对于 EMMC、 SD 卡、 NAND Flash 这类没有任何机械设备的存储设备就可以任意读写任何的扇区(块设备物理存储单元)。但是对于机械硬盘这样带有磁头的设备,读取不同的盘面或者磁道里面的数据,磁头都需要进行移动,因此对于机械硬盘而言,将那些杂乱的访问按照一定的顺序进行排列可以有效提高磁盘性能, linux 里面针对不同的存储设备实现了不同的 I/O 调度算法。

二、块设备驱动框架

1、block_device 结构体

  linux 内 核 使 用 block_device 表 示 块 设 备 , block_device 为 一 个 结 构 体 , 定 义 在include/linux/fs.h 文件中,结构体内容如下:
在这里插入图片描述
  这里重点关注一下gendisk结构体指针类型的bd_disk成员变量。内核使用 block_device 来表示一个具体的块设备对象,而gendisk是表示通用磁盘结构的。

2、gendisk 结构体

  linux 内核使用 gendisk 来描述一个磁盘设备,这是一个结构体,定义在 include/linux/genhd.h中,内容如下:
在这里插入图片描述
  简单看一下 gendisk 结构体中比较重要的几个成员变量:
  第 5 行, major 为磁盘设备的主设备号。
  第 6 行, first_minor 为磁盘的第一个次设备号。
  第 7 行, minors 为磁盘的次设备号数量,也就是磁盘的分区数量,这些分区的主设备号一样, 次设备号不同。
  第 21 行, part_tbl 为磁盘对应的分区表,为结构体 disk_part_tbl 类型, disk_part_tbl 的核心是一个 hd_struct 结构体指针数组,此数组每一项都对应一个分区信息。
  第 24 行, fops 为块设备操作集,为 block_device_operations 结构体类型。和字符设备操作集 file_operations 一样,是块设备驱动中的重点!
  第 25 行, queue 为磁盘对应的请求队列,所以针对该磁盘设备的请求都放到此队列中,驱动程序需要处理此队列中的所有请求。

3、block_device_operations 结构体

  在gendisk 结构体中的 block_device_operations 结构体和字符设备的 file _operations 一样,块设备也有操作集,为结构体 block_device_operations,此结构体定义在 include/linux/blkdev.h 中,结构体内容如下:
在这里插入图片描述
  可以看出, block_device_operations 结构体里面的操作集函数和字符设备的 file_operations操作集基本类似,但是块设备的操作集函数比较少,我们来看一下其中比较重要的几个成员函数:
  第 2 行, open 函数用于打开指定的块设备。
  第 3 行, release 函数用于关闭(释放)指定的块设备。
  第 4 行, rw_page 函数用于读写指定的页。
  第 5 行, ioctl 函数用于块设备的 I/O 控制。
  第 6 行, compat_ioctl 函数和 ioctl 函数一样,都是用于块设备的 I/O 控制。区别在于在 64位系统上, 32 位应用程序的 ioctl 会调用 compat_iotl 函数。在 32 位系统上运行的 32 位应用程序调用的就是 ioctl 函数。
  第 15 行, getgeo 函数用于获取磁盘信息,包括磁头、柱面和扇区等信息。
  第 18 行, owner 表示此结构体属于哪个模块,一般直接设置为 THIS_MODULE。

4、块设备 I/O 请求过程

  大家如果仔细观察的话会在 block_device_operations 结构体中并没有找到 read 和 write 这样的读写函数,那么块设备是怎么从物理块设备中读写数据?这里就引出了块设备驱动中非常重要的 request_queue、 request 和 bio。
  ①请求队列 request_queue:
  内核将对块设备的读写都发送到请求队列 request_queue 中, request_queue 中包含了一系列的request(请求结构体),而 request 又包含了 bio, bio 保存了读写相关数据,比如从块设备的哪个地址开始读取、读取的数据长度,读取到哪里,如果是写的话还包括要写入的数据等。我们先来看一下 request_queue,这是一个结构体,定义在文件 include/linux/blkdev.h 中,由于request_queue 结构体比较长,这里就不列出来了。大家回过头看一下 gendisk结构体就会发现里面有一个 request_queue 结构体指针类型成员变量 queue,也就说在编写块设
备驱动的时候,每个磁盘(gendisk)都要分配一个 request_queue。
  ②请求 request:
  请求队列(request_queue)里面包含的就是一系列的请求(request), request 是一个结构体,定义在 include/linux/blkdev.h 里面,这里就不展开 request 结构体了,太长了。 request 里面有一个
名为“bio”的成员变量,类型为 bio 结构体指针。而真正的数据就保存在 bio 里面,所以我们需要从 request_queue 中取出一个一个的 request,然后再从每个 request 里面取出 bio,最后根据 bio 的描述讲数据写入到块设备,或者从块设备中读取数据。(一步到位处理请求:我们也可以使用 blk_fetch_request 函数来一次性完成请求的获取和开启, blk_fetch_request函数很简单)

5、bio 结构体

  每个 request 里面会有多个 bio, bio 保存着最终要读写的数据、地址等信息。上层应用程序对于块设备的读写会被构造成一个或多个 bio 结构, bio 结构描述了要读写的起始扇区、要读写的扇区数量、是读取还是写入、页偏移、数据长度等等信息。上层会将 bio 提交给 I/O 调度器,I/O 调度器会将这些 bio 构造成 request 结构,而一个物理存储设备对应一个 request_queue,request_queue 里面顺序存放着一系列的 request。新产生的 bio 可能被合并到 request_queue 里现有的 request 中,也可能产生新的 request,然后插入到 request_queue 中合适的位置,这一切都是由 I/O 调度器来完成的。 request_queue、 request 和 bio 之间的关系如下图所示:
在这里插入图片描述
  bio、bvec_iter 以及 bio_vec 这三个结构体之间的关系如下图:(bvec_iter 结构体描述了要操作的设备扇区等信息; bio_vec 就是“page,offset,len”组合, page 指定了所在的物理页, offset 表示所处 页的偏移地址, len 就是数据长度。)
在这里插入图片描述

三、使用请求队列方式的块设备驱动程序

(使用开发板上的 RAM 模拟一段块设备,也就是,然后编写块设备驱动。)

1、经过第“二”部分的讲解总结,可以得出驱动程序的核心部分的编写流程,如下:

  ①、和字符设备驱动一样,我们需要向内核注册新的块设备、申请设备号,块设备注册函数为register_blkdev,函数原型如下:

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

但major形参设置为0,那么表示由系统自动分配主设备号,当返回负值表示注册失败。(注销块设备:void unregister_blkdev(unsigned int major, const char *name))
  ②、使用 gendisk 之前要先申请, allo_disk 函数用于申请一个 gendisk,函数原型如下:

struct gendisk *alloc_disk(int minors)

(删除gendisk:void del_gendisk(struct gendisk *gp))


  ③初始化gendisk结构体的内部属性:

  Ⅰ、(blk_init_queue 函数完成了请求队列的申请以及请求处理函数的绑定,这个一般用于像机械硬盘这样的存储设备,需要 I/O 调度器来优化数据读写过程。)将内核对块设备的读写的操作的地方 “请求队列“ 进行初始化,使用 blk_init_queue 函数来完成request_queue 的申请与初始化,函数原型如下:

request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)

rfn: 请求处理函数指针,每个 request_queue 都要有一个请求处理函数,请求处理函数request_fn_proc 原型: void (request_fn_proc) (struct request_queue *q), 请求处理函数需要驱动编写人员自行实现。

  Ⅱ、blk_fetch_request 函数来一次性完成请求的获取和开启,其函数原型如下:

struct request *blk_fetch_request(struct request_queue *q)

  Ⅲ、获取到request后,那么就要使用request去获取bio中的数据缓冲区的数据,使用bio_data函数,其函数原型如下:

static inline void *bio_data(struct bio *bio)

用这个函数我们就知道我们当前操作的内存数据位置了


  ④、使用 alloc_disk 申请到 gendisk 以后系统还不能使用,必须使用 add_disk 函数将申请到的gendisk 添加到内核中, add_disk 函数原型如下:

void add_disk(struct gendisk *disk)

2、驱动程序文件

/* 
 *  根据linux内核的程序查找所使用函数的对应头文件。 
 */  
#include <linux/module.h>       //MODULE_LICENSE,MODULE_AUTHOR  
#include <linux/init.h>         //module_init,module_exit  
#include <linux/kernel.h>       //printk  
#include <linux/fs.h>           //struct file_operations  
#include <linux/slab.h>         //kmalloc, kfree  
#include <linux/uaccess.h>      //copy_to_user,copy_from_user  
#include <linux/io.h>           //ioremap,iounmap  
#include <linux/cdev.h>         //struct cdev,cdev_init,cdev_add,cdev_del  
#include <linux/device.h>       //class  
#include <linux/of.h>           //of_find_node_by_path  
#include <linux/of_gpio.h>      //of_get_named_gpio  
#include <linux/gpio.h>         //gpio_request,gpio_direction_output,gpio_set_number  
#include <linux/atomic.h>       //atomic_t  
#include <linux/of_irq.h>       //irq_of_parse_and_map
#include <linux/interrupt.h>    //request_irq
#include <linux/timer.h>        //timer_list
#include <linux/jiffies.h>      //jiffies
#include <linux/atomic.h>       //atomic_set
#include <linux/input.h>        //input
#include <linux/platform_device.h>  //platform
#include <linux/delay.h>        //mdelay
#include <linux/i2c.h>          //i2c
#include <linux/input/mt.h>     //input multi touch
#include <linux/blkdev.h>       //register_blkdev,alloc_disk,blk_init_queue,set_capacity,add_disk
#include <linux/hdreg.h>        //hd_geometry

/* 2.1 定义磁盘大小,内存模拟 */
#define RAMDISK_SIZE    (2 * 1024 * 1024)   /* 大小2MB */
#define RAMDISK_NAME    "ramdisk"           /* 名字 */
#define RAMDISK_MINOR   3                   /* 次设备号数量,也就是gendisk对应的分区数量,表示3个分区 */

/* 2.2 ramdisk设备结构体 */
struct ramdisk_dev{
    int major;                      /* 主设备号 */
    unsigned char *ramdiskbuf;      /*ramdisk的内存空间,模块块设备的磁盘空间 */
    struct gendisk *gendisk;        /* linux 内核使用 gendisk 来描述一个磁盘设备 */
    struct request_queue *queue;    /* 请求队列 */
    spinlock_t spinlock;            /* 自旋锁 */
};

struct ramdisk_dev ramdisk; /* 实例化ramdisk_dev结构体 */

/* 5.2 具体的数据处理过程 */
static void ramdisk_transfer(struct request *req)
{
    /* 数据传输三要素:源,       目的,         长度 
     *              内存地址,  块设备地址,    长度 */
    unsigned long start = blk_rq_pos(req) << 9; //获取到的块设备扇区地址,左移9位转换为字节地址,这里的9位指的是一个扇区512字节
	unsigned long len  = blk_rq_cur_bytes(req); //长度
    /* bio 中的数据缓冲区
    * 读:从磁盘读取到的数据存放到 buffer 中
    * 写: buffer 保存这要写入磁盘的数据
    */   
    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); 
}

/* 3.3.1 请求处理函数 */
static void ramdisk_request(struct request_queue *q)
{
    /******************* 5.1 请求处理函数的实现 *******************/
	struct request *req;
    int err = 0;
    /* 循环处理请求队列中的每个请求 */
	req = blk_fetch_request(q); //该函数具有电梯调度算法,可以适配机械硬盘那些
	while (req) {
        /* 处理request,也就是具体的读写操作 */
        ramdisk_transfer(req);
        /* 判断是否为最后一个请求,如果不是的话就获取下一个请求
         * 循环处理完请求队列中的所有请求。
         */
		if (!__blk_end_request_cur(req, err))
			req = blk_fetch_request(q);    
    }
    /*************************************************************/
}

/* 3.4.1.1 打开块设备 */
static int ramdisk_open(struct block_device *bdev, fmode_t mode)
{
    printk("ramdisk open\r\n");
    return 0;
}

/* 3.4.1.2 释放块设备 */
static void ramdisk_release(struct gendisk *disk, fmode_t mode)
{
    printk("ramdisk release\r\n");
}

/* 3.4.1.3 获取机械磁盘信息,参数1:块设备,参数2:模式 */
static int ramdisk_getgeo(struct block_device *dev, struct hd_geometry *geo)
{   
    printk("ramdisk getgeo\r\n");
    /* 磁盘信息 */
    geo->heads = 2; //磁头
    geo->cylinders = 32;    //柱面
    geo->sectors = RAMDISK_SIZE / (2 * 32 *512); //一个磁道上的扇区数量
    return 0;
}

/* 3.4.1 块设备操作集 */
static const struct block_device_operations ramdisk_fops =
{
	.owner		= THIS_MODULE,
	.open		= ramdisk_open,
	.release	= ramdisk_release,
    .getgeo     = ramdisk_getgeo,
};

/* 1.2 设备驱动模块加载函数 */
static int __init ramdisk_init(void)
{
    int ret = 0;
    printk("ramdisk_init\r\n");

    /* 2.3 申请ramdisk内存 */
    ramdisk.ramdiskbuf = kzalloc(RAMDISK_SIZE,GFP_KERNEL);
    if(ramdisk.ramdiskbuf == NULL) {
        ret = -EINVAL;
        goto ramalloc_fail;
    }

    /* 3.1 注册块设备 */
    ramdisk.major = register_blkdev(0, RAMDISK_NAME);   //参数1:写0表示由系统自动分配未使用打主号码(1-255)
    if(ramdisk.major < 0) {
        ret = -EINVAL;
        goto ramdisk_register_blkdev_fail;
    }
    printk("ramdisk major = %d\r\n",ramdisk.major);

    /* 3.2 申请gendisk */
    ramdisk.gendisk = alloc_disk(RAMDISK_MINOR);
    if (!ramdisk.gendisk) {
        ret = -EINVAL;
        goto gendisk_alloc_fail;;
    }

    /* 3.3 申请并初始化请求队列 */
    spin_lock_init(&ramdisk.spinlock);  //初始化自旋锁
    ramdisk.queue = blk_init_queue(ramdisk_request,&ramdisk.spinlock);   //参数1:请求处理函数 参数2:自旋锁
    if (!ramdisk.queue) {
        ret = -EINVAL;
        goto blk_queue_fail;
    }

    /* 3.4 初始化gendisk */
    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); //设置gendisk容量,单位扇区512字节
    add_disk(ramdisk.gendisk);    //添加到内核中
  
	return 0;
	 
blk_queue_fail:
    put_disk(ramdisk.gendisk);
gendisk_alloc_fail:
    unregister_blkdev(ramdisk.major,RAMDISK_NAME);  //注销块设备
ramdisk_register_blkdev_fail:
    kfree(ramdisk.ramdiskbuf);  //释放内存
ramalloc_fail:
    return ret;
}

/* 1.3 设备驱动模块卸载函数 */
static void __exit ramdisk_exit(void)
{
    /* 4.1 删除内核中的gendisk */
    del_gendisk(ramdisk.gendisk);   
    /* 4.2 释放ramdisk.gendisk */
    put_disk(ramdisk.gendisk);
    /* 4.3 清除请求队列 */
    blk_cleanup_queue(ramdisk.queue);
    /* 4.4 注销块设备 */
    unregister_blkdev(ramdisk.major,RAMDISK_NAME);
    /* 4.5 释放内存 */
    kfree(ramdisk.ramdiskbuf); 
}  

/* 1.4 注册驱动模块加载与驱动模块卸载 */
module_init(ramdisk_init);
module_exit(ramdisk_exit);

/* 1.1 许可证和作者 */
MODULE_LICENSE("GPL");  
MODULE_AUTHOR("djw");  

3、驱动程序运行测试

①加载驱动模块
在这里插入图片描述
②查看 ramdisk 磁盘(可以看出, ramdisk 已经识别出来了,大小为 2MB,但是同时也提示/dev/ramdisk没有分区表,因为我们还没有格式化/dev/ramdisk)
在这里插入图片描述
③使用 mkfs.vfat 命令格式化/dev/ramdisk,将其格式化成 vfat 格式,操作如下:
在这里插入图片描述
④格式化完成以后就可以挂载/dev/ramdisk 来访问了,挂载点可以自定义,我这里将其挂载到根目录下创建的/tmp 目录下,然后在tmp目录下编写一个test.txt文件,可以看见test.txt文件已经创建了,再卸载/dev/ramdisk,那么/tmp目录下的test.txt文件就不见了,当再次挂载/dev/ramdisk ,/tmp目录下的test.txt文件又会出现。这里我们用的内存空间是emmc内存空间虚拟出来的,也就是说只要板子重启后就不存在了。
在这里插入图片描述

四、使用分配请求队列并绑定制造请求函数方式的块设备驱动程序

1、驱动编写流程一些修改

对于 EMMC、 SD 卡这样的非机械设备,可以进行完全随机访问,所以就不需要复杂的 I/O 调度器了。对于非机械设备我们可以先申请 request_queue,然后将申请到的 request_queue 与“制造请求”函数绑定在一起。

  ①request_queue 申请函数 blk_alloc_queue,函数原型如下:

struct request_queue *blk_alloc_queue(gfp_t gfp_mask)

  ②我们需要为 blk_alloc_queue 函数申请到的请求队列绑定一个“制造请求”函数(其他参考资料将其直接翻译为“制造请求”函数)。这里我们需要用到函数 blk_queue_make_request,函数原型如下:

void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)

  参数mfn:需要绑定的“制造”请求函数,函数原型“void (make_request_fn) (struct request_queue *q, struct bio *bio)”,“制造请求”函数需要驱动编写人员实现。

总的来说,一般 blk_alloc_queue 和 blk_queue_make_request 是搭配在一起使用的,用于那么非机械的存储设备、无需 I/O 调度器,比如 EMMC、 SD 卡等。 blk_init_queue 函数会给请求队列分配一个 I/O 调度器,用于机械存储设备,比如机械硬盘等

2、驱动程序文件

/* 
 *  根据linux内核的程序查找所使用函数的对应头文件。 
 */  
#include <linux/module.h>       //MODULE_LICENSE,MODULE_AUTHOR  
#include <linux/init.h>         //module_init,module_exit  
#include <linux/kernel.h>       //printk  
#include <linux/fs.h>           //struct file_operations  
#include <linux/slab.h>         //kmalloc, kfree  
#include <linux/uaccess.h>      //copy_to_user,copy_from_user  
#include <linux/io.h>           //ioremap,iounmap  
#include <linux/cdev.h>         //struct cdev,cdev_init,cdev_add,cdev_del  
#include <linux/device.h>       //class  
#include <linux/of.h>           //of_find_node_by_path  
#include <linux/of_gpio.h>      //of_get_named_gpio  
#include <linux/gpio.h>         //gpio_request,gpio_direction_output,gpio_set_number  
#include <linux/atomic.h>       //atomic_t  
#include <linux/of_irq.h>       //irq_of_parse_and_map
#include <linux/interrupt.h>    //request_irq
#include <linux/timer.h>        //timer_list
#include <linux/jiffies.h>      //jiffies
#include <linux/atomic.h>       //atomic_set
#include <linux/input.h>        //input
#include <linux/platform_device.h>  //platform
#include <linux/delay.h>        //mdelay
#include <linux/i2c.h>          //i2c
#include <linux/input/mt.h>     //input multi touch
#include <linux/blkdev.h>       //register_blkdev,alloc_disk,blk_init_queue,set_capacity,add_disk
#include <linux/hdreg.h>        //hd_geometry

/* 2.1 定义磁盘大小,内存模拟 */
#define RAMDISK_SIZE    (2 * 1024 * 1024)   /* 大小2MB */
#define RAMDISK_NAME    "ramdisk"           /* 名字 */
#define RAMDISK_MINOR   3                   /* 次设备号数量,也就是gendisk对应的分区数量,表示3个分区 */

/* 2.2 ramdisk设备结构体 */
struct ramdisk_dev{
    int major;                      /* 主设备号 */
    unsigned char *ramdiskbuf;      /*ramdisk的内存空间,模块块设备的磁盘空间 */
    struct gendisk *gendisk;        /* linux 内核使用 gendisk 来描述一个磁盘设备 */
    struct request_queue *queue;    /* 请求队列 */
    spinlock_t spinlock;            /* 自旋锁 */
};

struct ramdisk_dev ramdisk; /* 实例化ramdisk_dev结构体 */

/* 3.3.3 制造请求函数 */
static void ramdisk_make_request(struct request_queue *queue, struct bio *bio)
{
	int offset, len;
	struct bio_vec bvec;
	struct bvec_iter iter;    

	offset = bio->bi_iter.bi_sector << 9;    //要操作的 I/O请求的设备起始扇区(512 字节) 偏移地址,左移9位转换为字节地址,这里的9位指的是一个扇区512字节
    bio_for_each_segment(bvec, bio, iter) { //循环处理每一片段
        /* bio 中的数据缓冲区
        * 读:从磁盘读取到的数据存放到 ptr 中
        * 写: ptr 保存这要写入磁盘的数据
        */         
        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                            //写操作
            memcpy(ramdisk.ramdiskbuf + offset, ptr, len);  
        
        offset += len;
    }
	set_bit(BIO_UPTODATE, &bio->bi_flags);
	bio_endio(bio, 0);
}

/* 3.4.1.1 打开块设备 */
static int ramdisk_open(struct block_device *bdev, fmode_t mode)
{
    printk("ramdisk open\r\n");
    return 0;
}

/* 3.4.1.2 释放块设备 */
static void ramdisk_release(struct gendisk *disk, fmode_t mode)
{
    printk("ramdisk release\r\n");
}

/* 3.4.1.3 获取机械磁盘信息,参数1:块设备,参数2:模式 */
static int ramdisk_getgeo(struct block_device *dev, struct hd_geometry *geo)
{   
    printk("ramdisk getgeo\r\n");
    /* 磁盘信息 */
    geo->heads = 2; //磁头
    geo->cylinders = 32;    //柱面
    geo->sectors = RAMDISK_SIZE / (2 * 32 *512); //一个磁道上的扇区数量
    return 0;
}

/* 3.4.1 块设备操作集 */
static const struct block_device_operations ramdisk_fops =
{
	.owner		= THIS_MODULE,
	.open		= ramdisk_open,
	.release	= ramdisk_release,
    .getgeo     = ramdisk_getgeo,
};

/* 1.2 设备驱动模块加载函数 */
static int __init ramdisk_init(void)
{
    int ret = 0;
    printk("ramdisk_init\r\n");

    /* 2.3 申请ramdisk内存 */
    ramdisk.ramdiskbuf = kzalloc(RAMDISK_SIZE,GFP_KERNEL);
    if(ramdisk.ramdiskbuf == NULL) {
        ret = -EINVAL;
        goto ramalloc_fail;
    }

    /* 3.1 注册块设备 */
    ramdisk.major = register_blkdev(0, RAMDISK_NAME);   //参数1:写0表示由系统自动分配未使用打主号码(1-255)
    if(ramdisk.major < 0) {
        ret = -EINVAL;
        goto ramdisk_register_blkdev_fail;
    }
    printk("ramdisk major = %d\r\n",ramdisk.major);

    /* 3.2 申请gendisk */
    ramdisk.gendisk = alloc_disk(RAMDISK_MINOR);
    if (!ramdisk.gendisk) {
        ret = -EINVAL;
        goto gendisk_alloc_fail;;
    }

    /* 3.3.1 申请请求队列 */    
    spin_lock_init(&ramdisk.spinlock);  //初始化自旋锁
    ramdisk.queue = blk_alloc_queue(GFP_KERNEL);
    if (!ramdisk.queue) {
        ret = -EINVAL;
        goto blk_queue_fail;
    }
    /* 3.3.2 绑定“制造请求”函数 */
    blk_queue_make_request(ramdisk.queue, ramdisk_make_request);

    /* 3.4 初始化gendisk */
    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); //设置gendisk容量,单位扇区512字节
    add_disk(ramdisk.gendisk);    //添加到内核中
  
	return 0;
	 
blk_queue_fail:
    put_disk(ramdisk.gendisk);
gendisk_alloc_fail:
    unregister_blkdev(ramdisk.major,RAMDISK_NAME);  //注销块设备
ramdisk_register_blkdev_fail:
    kfree(ramdisk.ramdiskbuf);  //释放内存
ramalloc_fail:
    return ret;
}

/* 1.3 设备驱动模块卸载函数 */
static void __exit ramdisk_exit(void)
{
    /* 4.1 删除内核中的gendisk */
    del_gendisk(ramdisk.gendisk);   
    /* 4.2 释放ramdisk.gendisk */
    put_disk(ramdisk.gendisk);
    /* 4.3 清除请求队列 */
    blk_cleanup_queue(ramdisk.queue);
    /* 4.4 注销块设备 */
    unregister_blkdev(ramdisk.major,RAMDISK_NAME);
    /* 4.5 释放内存 */
    kfree(ramdisk.ramdiskbuf); 
}  

/* 1.4 注册驱动模块加载与驱动模块卸载 */
module_init(ramdisk_init);
module_exit(ramdisk_exit);

/* 1.1 许可证和作者 */
MODULE_LICENSE("GPL");  
MODULE_AUTHOR("djw");  

3、驱动程序运行测试
与上面测试方法一样,略…

  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邓家文007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值