1.正确理解块设备驱动的概念
【1】块设备和字符设备的差异
- 块和字符是两种不同的访问设备的策略
- 同一个设备可以同时支持块和字符两种访问策略
- 设备本身的物理特性决定了哪一种访问策略更适合
- 块设备本身驱动层支持缓冲区,而字符设备驱动层没有缓冲
- 块设备驱动最适合存储设备
【2】块设备驱动的特点
- 字符设备只能顺序访问(如串口发送数据顺序),而块设备可以随机访问(不连续块访问)
- 传统的机械式块设备(如硬盘、DVD)虽然可以随机访问,但是连续访问效率更高,因此块设备驱动中有排序逻辑将用户的随机访问重新调整成尽量连续访问以提升效率
- Nand、SD卡等随机访问效率等同于顺序访问
【3】块设备相关的几个单位
- 扇区(Sector),概念来自于早期磁盘,在硬盘、DVD中还有用,在Nand/SD中已经没意义了,扇区是块设备本身的特性,大小一般为512的整数倍,因为历史原因很多时候都向前兼容定义为512.
- 块(block),概念来自于文件系统,是内核对文件系统数据处理的基本单位,大小为若干个扇区,常见有512B、1KB、4KB等
- 段(Section),概念来自于内核,是内核的内存管理中一个页或者部分页,由若干个连续为块组成。
- 页(Page),概念来自于内核,是内核内存映射管理的基本单位。linux内核的页式内存映射名称来源于此。
- 总结:块设备驱动对下以Sector为单位管理块设备,对上以Block为单位和文件系统交互。
注意:块设备驱动和字符设备驱动不同,应用层对块设备驱动的访问一般不是直接操作设备文件(/dev/block/xxx,或者/dev/sdax),而是通过文件系统来简洁操作。(思考裸机阶段时刷机烧录SD卡时说过的对SD卡的2种访问:文件系统下访问和扇区级访问)
2.块设备驱动框架简介
【1】块设备驱动框图
- VFS
- 通用块层
- IO调度层(电梯算法)
- 块设备驱动层(真正硬件操作部分)
【2】重点结构体
struct request 对设备的每一次操作(譬如读或者写一个扇区)
struct request_queue request队列
struct bio 通用块层用bio来管理一个请求
struct gendisk 表示一个磁盘设备或一个分区
3.块设备驱动案例分析-------->用内存虚拟出来的硬盘
【1】驱动源码
#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) //等待队列的回调函数,这个函数是驱动提供的用来处理等待队列中的request的函数
{
struct request *req;
static int r_cnt = 0; //实验用,打印出驱动读与写的调度方法
static int w_cnt = 0;
req = blk_fetch_request(q); //函数是IO调度层提供的接口,作用是从request_queue中取出一个请求,取出的请求其实就是当前硬件(块设备)
//最应该去执行的那个读写操作。
while (NULL != req)
{
unsigned long start = blk_rq_pos(req) *512;
unsigned long len = blk_rq_cur_bytes(req);
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)
{
major = register_blkdev(0, "my_ramblock"); //内核提供的注册块设备驱动的注册接口,在块设备驱动框架中的地位
//同于register_chrdev在字符设备驱动框架中的地位
if (major < 0)
{
printk("fail to regiser my_ramblock\n");
return -EBUSY;
}
// 实例化
my_ramblock_disk = alloc_disk(1); //次设备个数 ,分区个数 +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/name
my_ramblock_disk->queue = my_ramblock_queue;
set_capacity(my_ramblock_disk, RAMBLOCK_SIZE / 512);
/* 硬件相关操作 */
my_ramblock_buf = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL);
add_disk(my_ramblock_disk); // 向驱动框架注册一个disk或者一个partation的接口
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");
【2】源码分析
- register_blkdev(kernel/block/genhd.c),内核提供的注册块设备驱动的注册接口,在块设备驱动框架中的地位,等同于register_chrdev在字符设备驱动框架中的地位。
- blk_init_queue用来实例化产生一个等待队列,将来应用层对本块设备所做的所有的读写操作,都会生成一个request然后被加到这个等待队列中来。
- blk_init_queue函数接收2个参数,第一个是等待队列的回调函数,这个函数是驱动提供的用来处理等待队列中的request的函数(IO调度层通过电梯算法从等待队列中取出一个request,就会调用这个回调函数来处理这个请求),第二个参数是一个自旋锁,这个自旋锁是要求我们驱动提供给等待队列去使用的。
- blk_fetch_request函数是IO调度层提供的接口,作用是从request_queue中(按照电梯算法)取出一个(算法认为当前最应该去被执行的一个请求,是被算法排序、合并后的)请求,取出的请求其实就是当前硬件(块设备)最应该去执行的那个读写操作。
4.块设备驱动案例演示
-
编译
-
模块安装
-
查看信息: cat /proc/devices , cat /proc/partitions , ls /dev/ , lsmod
-
挂载测试
-
格式化:
mkfs.ext2 /dev/my_ramblock
- 挂载:
mount -t ext2 /dev/my_ramblcok /tmp
- 读写虚拟磁盘实验
挂载成功之后去/tmp目录下创建文件就可以实现读写。