关于块设备架构就讲解这些,接下来我们使用开发板上的 RAM 模拟一段块设备,也就是ramdisk,然后编写块设备驱动。
首先是传统的使用请求队列的时候,也就是针对机械硬盘的时候如何编写驱动。先分段分析一下驱动代码。
1.重要的数据结构及宏定义
#define RAMDISK_SIZE (2 * 1024 * 1024) //容量大小为2MB
#define RAMDISK_NAME "ramdisk" //名字
#define RAMDISK_MINOR 3 //表示有3个磁盘,不是次设备号
//ramdisk设备结构体
struct ramdisk_dev{
int major; //主设备号
unsigned char *ramdiskbuf; //ramdisk内存空间,用于模拟块设备
spinlock_t lock; //自旋锁
struct gendisk *gendisk; //gendisk(linux 内核使用gendisk来描述一个磁盘设备)
struct request_queue *queue; //请求队列
}
struct ramdisk_dev ramdisk; //ramdisk设备
2.驱动模块的加载与卸载
static int __init ramdisk_init(void)
{
int ret = 0;
printk("ramdisk init\r\n");
//1.申请用于ramdisk内存
//大小RAMDISK_SZIE,GFP_KERNEL用在可以睡眠的场合(使用GFP_KERNEL会引起休眠)!
ramdisk.ramdiskbuf = kzalloc(RAMDISK_SZIE,GFP_KERNEL);
if(NULL == ramdisk.ramdiskbuf)
{
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(RAMDISK_MINOR);
if(!ramdisk.gendisk)
{
ret = -EINVAL;
goto gendisk_alloc_fail;
}
//5.分配并初始化请求队列
ramdisk.queue = blk_init_queue(ramdisk_request_fn,&ramdisk.lock);
if(!ramdisk.queue)
{
ret = EINVAL;
goto blk_init_fail;
}
//6.添加(注册)disk
ramdisk.gendisk->major = ramdisk.major; //主设备号
ramdisk.gendisk->first_minor = 0; //第一个次设备号(起始次设备号)
ramdisk.gendisk->ops = &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(磁盘设备),gendisk初始化完成后就可以使用add_disk函数将gendisk添加到内核中,也就是向内核添加一个磁盘设备。
add_disk(ramdisk.gendisk);
return 0;
blk_init_fail:
put_disk(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)
{
//释放gendisk
printk("ramdisk exit \r\n");
del_gendisk(ramdisk.gendisk);
put_disk(ramdisk.gendisk);
//清除请求队列
blk_cleanup_queue(ramdisk.queue);
//注销块设备
unregister_blkdev(ramdisk.major);
//释放内存
kfree(ramdisk.ramdiskbuf);
}
(1)kzalloc 函数详解
(2)注册函数
int register_blkdev(unsigned int major,const char *name);
major
:主设备号
name
:块设备名字
如果参数major在1~255之间额话表示自己定义主设备号,那么返回0,表示注册成功,返回负值表示注册失败;
如果参数major为0表示由系统自动分配主设备号,那么返回值就是系统分配的主设备号(1~255),如果返回负值那么就表示注册失败。
(3)初始化请求队列
我们首先需要申请一个request_queue
,然后在初始化gendisk的时候将这个request_queue地址赋值给gendisk的queue成员变量,使用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)
请求处理函数需要驱动编写人员自行实现,具体的块设备读写操作就在此函数中完成。
lock
:自选锁指针,需要驱动编写人员定义一个自旋锁,然后传递进来,请求队列会使用这个自旋锁。
返回值:如果为NULL的话表示失败,成功的话就返回申请到的request_queue
的地址。
(4)设置块设备容量大小
使用set_capacity
函数设置本块设备容量大小,注意这里的大小是扇区数
,不是字节数,一个扇区是521字节。
块设备操作函数
//打开块设备
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->head = 2; //磁头
geo->cylinders = 32; //柱面
geo->sector = RAMDISK_SIZE/(2 * 32 *512); //磁道上的扇区数量
return 0;
}
static struct block_device_operations ramdisk_fops =
{
.owner = THIS_MODULES,
.open = ramdisk_open,
.release = ramdisk_release,
.getgeo = ramdisk_getgeo,
};
(1)硬盘基本知识(磁头、磁道、扇区、柱面)
磁盘信息保存在geo结构体中,此结构体为hd_geometry
类型
struct hd_geometry
{
unsigned char heads; //磁头
unsigned char sector; //一个磁道上的扇区数量
unsigned short cylinders; //柱面
unsigned long start;
}
存储容量 = 磁头数 × 磁道(柱面)数 × 每道扇区数 × 每扇区字节数
请求处理函数
//处理传输过程
static void ramdisk_transfer(struct request *req)
{
//blk_rq_pos获取到的是扇区地址(操作的块设备的扇区地址),左移9位转换为字节地址
unsigned long start = blk_rq_pos(req) << 9;
//获取请求要操作的数据长度
unsigned long len = blk_rq_cur_bytes(req); //大小
//bio中的数据缓冲区
//读:从磁盘读取到的数据存放到buffer中
//写:buffer保存着要写入磁盘的数据
void *buffer = bio_data(req->bio); //bio_data()获取请求中的bio保存的数据
//rq_data_dir(函数判断当前是读还是写
if(rq_data_dir(req) == READ) //读数据
{
memcpy(buffer,ramdisk.ramdiskbuf + start, len)
}
else if (rq_data_dir(req) == WRITE) //写数据
{
memcpy(ramdisk.ramdiskbuf + start,buf,len)
}
}
//请求处理函数
void ramdisk_request_fn(struct request_queue *q)
{
int err = 0;
struct request *req;
//循环处理请求队列中的每个请求
req = blk_fetch_request(q); //获取请求队列中的第一个请求
while(req != NULL)
{
//针对请求做具体的传输处理
ramdisk_transfer(req);
//判断是否为最后一个请求,如果不是的话,就获取下一个请求
//循环处理完请求队列中的所有请求。
if(!__blk_end_request_cur(req,err))
{
req = blk_fetch_request(q);
}
}
}
请求处理函数的重要内容就是完成从块设备中读取数据,或者向块设备中写入数据。ramdisk_request_fn函数就是请求处理函数,此函数只有一个参数q
,为request_queue
结构体指针类型,也就是请求队列,因此ramdisk_request_fn
函数的主要工作就是依次处理请求队列中的所有请求。
blk_fetch_request
函数获取的就是队列中的第一个请求,如果请求不为空的话,就调用ramdisk_transfer
函数进行对请求做进一步的处理,然后就是while
循环依次处理完请求队列中的每个请求。
__blk_end_request_cur
函数检查是否为最后一个请求,如果不是的话就继续获取下一个,直至整个请求队列处理完成。
几个概念的理解:
(1)请求处理函数:就是处理请求的函数;
(2)请求队列:装有请求的队列;
(3)请求:请求就是使用调度算法对多个bio进行了排序,以便提高读写性能;
(4)bio:装有要处理的数据。
完整代码及其测试
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/hdreg.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define RAMDISK_SIZE (2 * 1024 * 1024) /* 容量大小为2MB */
#define RAMDISK_NAME "ramdisk" /* 名字 */
#define RADMISK_MINOR 3 /* 表示有三个磁盘分区!不是次设备号为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设备 */
/*
* @description : 打开块设备
* @param - dev : 块设备
* @param - mode : 打开模式
* @return : 0 成功;其他 失败
*/
int ramdisk_open(struct block_device *dev, fmode_t mode)
{
printk("ramdisk open\r\n");
return 0;
}
/*
* @description : 释放块设备
* @param - disk : gendisk
* @param - mode : 模式
* @return : 0 成功;其他 失败
*/
void ramdisk_release(struct gendisk *disk, fmode_t mode)
{
printk("ramdisk release\r\n");
}
/*
* @description : 获取磁盘信息
* @param - dev : 块设备
* @param - geo : 模式
* @return : 0 成功;其他 失败
*/
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,
};
/*
* @description : 处理传输过程
* @param-req : 请求
* @return : 无
*/
static void ramdisk_transfer(struct request *req)
{
unsigned long start = blk_rq_pos(req) << 9; /* blk_rq_pos获取到的是扇区地址,左移9位转换为字节地址 */
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 if(rq_data_dir(req) == WRITE) /* 写数据 */
memcpy(ramdisk.ramdiskbuf + start, buffer, len);
}
/*
* @description : 请求处理函数
* @param-q : 请求队列
* @return : 无
*/
void ramdisk_request_fn(struct request_queue *q)
{
int err = 0;
struct request *req;
/* 循环处理请求队列中的每个请求 */
req = blk_fetch_request(q);
while(req != NULL) {
/* 针对请求做具体的传输处理 */
ramdisk_transfer(req);
/* 判断是否为最后一个请求,如果不是的话就获取下一个请求
* 循环处理完请求队列中的所有请求。
*/
if (!__blk_end_request_cur(req, err))
req = blk_fetch_request(q);
}
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
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_init_queue(ramdisk_request_fn, &ramdisk.lock);
if(!ramdisk.queue) {
ret = EINVAL;
goto blk_init_fail;
}
/* 6、添加(注册)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_init_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;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
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");
MODULE_AUTHOR("zuozhongkai");
测试结果
1.查看我们创建的ramdisk分区
可以看出来,ramdisk已经识别出来了,大小为2MB,但是同时也提示/dev/ramdisk
没有分区表,因为我们还没有格式化/dev/ramdisk
。
2.格式化/dev/ramdisk
使用mkfs.vfat命令格式化/dev/ramdisk,将其格式化成vfat,输入如下命令mkfs.vfat /dev/ramdisk
3.格式化完成后就可以挂载/dev/ramdisk来访问了,挂载点可以定义。mount /dev/ramdisk /tmp
挂载成功以后就可以通过/tmp来访问了ramdisk这个磁盘了,进入/tmp目录中,可以通过vi命令新建一个txt文件来测试磁盘访问是否正常。
4.检查