- 什么是块设备?
块设备是针对存储设备的,比如 SD 卡、EMMC、NAND Flash、Nor Flash、SPI Flash、机械硬盘、固态硬盘等。因此块设备驱动其实就是这些存储设备驱动。 - block_device 结构体
- 注册块设备
int register_blkdev(unsigned int major, const char *name)
- 注销块设备
void unregister_blkdev(unsigned int major, const char *name)
3. gendisk结构体
在该结构体下有一个重要的结构体成员变量struct gendisk ,linux 内核使用 gendisk 来描述一个磁盘设备。
在gendisk结构体下有一个很重要的成员变量const struct block_device_operations *fops;是块设备操作集。
- 申请gendisk
struct gendisk *alloc_disk(int minors)
(2)初始化
(3)将 gendisk 添加到内核
void add_disk(struct gendisk *disk)
(4)删除gendisk
void del_gendisk(struct gendisk *gp)
4. 块设备 I/O 请求过程
(1)请求队列 request_queue
初始化请求队列:
request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
删除请求队列:
void blk_cleanup_queue(struct request_queue *q)
(2)请求 request
请求队列(request_queue)里面包含的就是一系列的请求(request)
blk_fetch_request函数来一次性完成请求的获取和开启
(3)bio 结构
每个 request 里面里面会有多个 bio,bio 保存着最终要读写的数据、地址等信息。
在struct结构体中有两个重要的结构体struct bvec_iter与struct bio_vec,bvec_iter 结构体描述了要操作的设备扇区等信息,bio_vec 数组就是 RAM 信息,比如页地址、页偏移以及长度。
使用请求队列实验程序:
#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/slab.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/errno.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/mach/map.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_gpio.h>
#include <linux/timer.h>
#include <linux/ioctl.h>
#include <linux/input.h>
#include <linux/i2c.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/hdreg.h>
/*定义磁盘大小、内存模式*/
#define RAMDISK_SIZE (2*1024*1024)
#define RAMDISK_NAME "ramdisk"
#define RAMDISK_MINOR 3
struct ramdisk_dev{
int major;
unsigned char *ramdiskbuf;//ramdisk的内存空间,模拟磁盘的空间
struct gendisk *gendisk;
spinlock_t lock; /* 自旋锁 */
struct request_queue *queue;
};
struct ramdisk_dev ramdisk;
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里面的缓冲区:
如果是读:从磁盘里面读取到的数据保存到此缓冲区里面
如果是写:此缓冲区保存着要写入到磁盘里面的数据*/
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);
}
/*请求函数*/
static 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 - 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 const struct block_device_operations ramdisk_fops={
.owner = THIS_MODULE,
.open = ramdisk_open,
.release = ramdisk_release,
.getgeo = ramdisk_getgeo,
};
static int __init ramdisk_init(void)
{
int ret =0;
/*1、先申请内存*/
ramdisk.ramdiskbuf=kzalloc(RAMDISK_SIZE,GFP_KERNEL);
if(ramdisk.ramdiskbuf==NULL){
ret=-EINVAL;
goto ramalloc_fail;
}
printk("%#x\r\n",ramdisk.ramdiskbuf);
/*2、注册块设备*/
ramdisk.major=register_blkdev(0,RAMDISK_NAME);
if(ramdisk.major<0)
{
printk("major_fail\r\n");
goto major_fail;
}
/*3、申请磁盘*/
ramdisk.gendisk=alloc_disk(RAMDISK_MINOR);
if(ramdisk.gendisk==NULL){
goto gendisk_fail;
}
/* 4、初始化自旋锁 */
spin_lock_init(&ramdisk.lock);
/*5、申请并初始化队列*/
ramdisk.queue=blk_init_queue(&ramdisk_request_fn,&ramdisk.lock);
if(ramdisk.queue==NULL){
goto queue_fail;
}
/*6、初始化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); /* 设备容量(单位为扇区) */
add_disk(ramdisk.gendisk);
return 0;
queue_fail:
put_disk(ramdisk.gendisk);
gendisk_fail:
unregister_blkdev(ramdisk.major,RAMDISK_NAME);
major_fail:
kfree(ramdisk.ramdiskbuf);
ramalloc_fail:
return ret;
}
static void __exit ramdisk_exit(void)
{
del_gendisk(ramdisk.gendisk);
put_disk(ramdisk.gendisk);
/* 清除请求队列 */
blk_cleanup_queue(ramdisk.queue);
unregister_blkdev(ramdisk.major,RAMDISK_NAME);
kfree(ramdisk.ramdiskbuf);
printk("ramdisk_exit\r\n");
}
/*模块的加载与卸载*/
module_init(ramdisk_init);
module_exit(ramdisk_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("liuchuanqiang");
本实验通过内存模拟扇区信息,使用kzalloc(RAMDISK_SIZE,GFP_KERNEL);语句申请一个2MB大小的内存空间。使用到请求队列的方式对扇区空间进行读写操作。blk_init_queue(&ramdisk_request_fn,&ramdisk.lock);中的ramdisk_request_fn为请求函数,在该函数中完成对扇区数据的处理。
不使用请求队列实验程序:
#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/slab.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/errno.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/mach/map.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_gpio.h>
#include <linux/timer.h>
#include <linux/ioctl.h>
#include <linux/i2c.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/hdreg.h>
/*定义磁盘大小、内存模式*/
#define RAMDISK_SIZE (2*1024*1024)
#define RAMDISK_NAME "ramdisk"
#define RAMDISK_MINOR 3
struct ramdisk_dev{
int major;
unsigned char *ramdiskbuf;//ramdisk的内存空间,模拟磁盘的空间
struct gendisk *gendisk;
spinlock_t lock; /* 自旋锁 */
struct request_queue *queue;
};
struct ramdisk_dev ramdisk;
/*请求函数*/
static 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)<<9;/* 获取要操作的设备的偏移地址 */
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);
}
set_bit(BIO_UPTODATE, &bio->bi_flags);
bio_endio(bio, 0);
}
/*
* @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 const struct block_device_operations ramdisk_fops={
.owner = THIS_MODULE,
.open = ramdisk_open,
.release = ramdisk_release,
.getgeo = ramdisk_getgeo,
};
static int __init ramdisk_init(void)
{
int ret =0;
/*1、先申请内存*/
ramdisk.ramdiskbuf=kzalloc(RAMDISK_SIZE,GFP_KERNEL);
if(ramdisk.ramdiskbuf==NULL){
ret=-EINVAL;
goto ramalloc_fail;
}
printk("%#x\r\n",ramdisk.ramdiskbuf);
/*2、注册块设备*/
ramdisk.major=register_blkdev(0,RAMDISK_NAME);
if(ramdisk.major<0)
{
printk("major_fail\r\n");
goto major_fail;
}
/*3、申请磁盘*/
ramdisk.gendisk=alloc_disk(RAMDISK_MINOR);
if(ramdisk.gendisk==NULL){
goto gendisk_fail;
}
/* 4、初始化自旋锁 */
spin_lock_init(&ramdisk.lock);
/*5、申请并初始化队列*/
ramdisk.queue=blk_alloc_queue(GFP_KERNEL);
if(ramdisk.queue==NULL){
goto queue_fail;
}
blk_queue_make_request(ramdisk.queue,ramdisk_make_request_fn);
/*6、初始化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);/* 设备容量(单位为扇区) */
add_disk(ramdisk.gendisk);
return 0;
queue_fail:
put_disk(ramdisk.gendisk);
gendisk_fail:
unregister_blkdev(ramdisk.major,RAMDISK_NAME);
major_fail:
kfree(ramdisk.ramdiskbuf);
ramalloc_fail:
return ret;
}
static void __exit ramdisk_exit(void)
{
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("liuchuanqiang");
程序说明:
本实验中改为使用blk_queue_make_request 函数设置“制造请求”函数;其中使用 blk_alloc_queue函数申请一个请求队列;使用blk_queue_make_request函数设置“制造请求”函数。使用bio_for_each_segment函数循环获取bio中的每个段,然后对其每个段进行处理。
运行测试:
将驱动模块加载至开发板内核,然后按照如下步骤进行操作:
1、fdisk -l //查看磁盘信息;可以查看出设备的信息为/dev/ramdisk。
2、格式化/dev/ramdisk,使用mkfs.vfat /dev/ramdisk进行格式化,然后通过语句mount /dev/ramdisk /tmp 将设备进行挂载。
3、挂载成功以后可以像操作U盘那样对该存储设备进行读写。