07_块设备驱动
1、回顾字符设备驱动程序
1.1、字符设备驱动程序框架
1.2、字符设备驱动程序的基本形式
1、查询方式:CPU资源占用率太高。
2、休眠-唤醒:如果没有数据则一直在休眠,若休眠被中断服务程序(request_irq来注册)唤醒之后就会调用
copy_to_user()在return。
3、poll机制:也是休眠唤醒,但是1定时时间一到、2有数据就会返回。
4、异步通知:让设备主动上报事件(设备来发信号)。
但是这四种方式写出的驱动程序通用性太差,不适合多人使用。
5、输入子系统:融合1、2、3、4中方式将类似于input_handler和input_device分开,这样就有了较好的通用性。
2、块设备驱动程序的引入
加入块设备驱动程序和字符设备驱动程序一样的话:
例如:
2.1、硬盘
2.2、flash
所以块设备驱动程序有:
把“读写”放入队列
优化后再执行
硬件:硬盘、FLASH
1,以面向对象的思想分配 gendisk 结构体。用 alloc_disk 函数。
2,设置 gendisk 结构体。
2.1,分配/设置一个队列:request_queue_t. (提供读写能力)用 blk_init_queue 函数。
2.2,设置 gendisk 其他信息。(提供磁盘属性:磁盘容量,扇区大小等)
3,注册 gendisk 结构体。用 add_disk 函数。
操作不用我们关心,格式化、读写文件等都是由“文件系统”这一层将文件的读写转换成对扇区的读写的。调用“ll_rw_block”会把读写放到你的队列中去。会调用你“队列请求处理函数”来处理。只要你写好“队列请求处理”函数即可。
3、块设备驱动程序的框架
推荐阅读:<LINUX内核源代码情景分析> 了解块设备驱动程序
3.1、框架
1 app : open,read,write "1.txt"
--------------------------------------------- 文件的读写
文件系统: vfat, ext2, ext3, yaffs2, jffs2 (把文件的读写转换为扇区的读写)
2 内核:-- ll_rw_block(通用的入口)---------- 扇区的读写
1. 把"读写"放入队列
2. 调用队列的处理函数(优化:调顺序/合并)
块设备驱动程序
-------------------------------------------
3 硬件: 硬盘、flash
3.2、分析ll_rw_block
for (i = 0; i < nr; i++) {
struct buffer_head *bh = bhs[i];
submit_bh(rw, bh);
struct bio *bio; // 使用bh来构造bio (block input/output)
submit_bio(rw, bio);
// 通用的构造请求: 使用bio来构造请求(request)
generic_make_request(bio);
__generic_make_request(bio);
request_queue_t *q = bdev_get_queue(bio->bi_bdev); // 找到队列
// 调用队列的"构造请求函数"
ret = q->make_request_fn(q, bio);
// 默认的函数是__make_request
__make_request
// 先尝试合并
elv_merge(q, &req, bio);
// 如果合并不成,使用bio构造请求
init_request_from_bio(req, bio);
// 把请求放入队列
add_request(q, req);
// 执行队列
__generic_unplug_device(q);
// 调用队列的"处理函数"
q->request_fn(q);
怎么写块设备驱动程序呢?
1.分配gendisk结构体 : 使用函数alloc_disk
2.设置gendisk结构体
2.1 分配/设置队列: request_queue_t // 它提供读写能力使用函数blk_init_queue
2.2 设置gendisk结构体其他信息 // 它提供属性: 比如容量
3. 注册: add_disk
4、编程
参考:
drivers\block\xd.c
drivers\block\z2ram.c
5 获得磁盘的几何尺寸函数ramblock_getgeo中
static int ramblock_getgeo(struct block_device *dev, struct hd_geometry *geo)
{
/* 容量 = heads * sectors * cylinders * 512 */
geo->heads = 2;
geo->cylinders = 32;
geo->sectors = RAMBLOCK_SIZE/2/32/512;
return 0;
}
4 在fops结构体block_device_operations中
static struct block_device_operations ramblock_fops = {
.owner = THIS_MODULE,
.getgeo = ramblock_getgeo,
};
3 在处理队列请求函数do_ramblock_request中
注释1:
数据传输三要素:涉及到数据的传输,则离不开“三要素”:块设备数据传输的三要素在结构体“request”中定义:struct request {sector_t sector; /* next sector to submit 下一个要提交的扇区。这就源或是目的*/…unsigned int current_nr_sectors; //当前要处理的扇区个数。这是长度。…char *buffer; //要写时这时目的(从扇区读数据到此),要读时这是源。}块设备操作时是以扇区为单位,即使是写一个字节,也是先读出一个扇区,再修改这个扇区里的那个字节后,再将整个扇区写进
static int r_cnt = 0;
static int w_cnt = 0;
struct request *req;
//printk("do_ramblock_request %d\n", ++cnt);
/* 以电梯调度算法取出队列下一个请求,处理完返回 */
while((req = elv_next_request(q)) != NULL) {
/* 数据传输三要素:源、目的、长度 */
/* 源(读)/目的(写) */
unsigned long offset = req->sector * 512; //偏移值。左移9位相当于乘以512-1个扇区大小.故相当于 req->sector*512
/* 目的(读)/源(写) */
//req->buffer
/* 长度 */
unsigned long len = req->current_nr_sectors * 512; //长度。当前要传输多少个扇区.
if(rq_data_dir(req) == READ)
{
//printk("do_ramblock_request read %d\n", ++r_cnt);
memcpy(req->buffer, ramblock_buf + offset, len); //从磁盘上的源读长度len到目的内存的buffer。
}
else
{
//printk("do_ramblock_request write %d\n", ++w_cnt);
memcpy(ramblock_buf + offset, req->buffer, len); //把内存buffer中的数据写到磁盘上
}
end_request(req, 1);
}
1 在入口函数ramblock_init中
/* 1. 分配一个gendisk结构体 */
/* alloc_disk (int minors)需要参数“minor+s”是指次设备号个数。即“分区个数
* +0”。0 是指整个磁盘。为 1 时,就是把整个磁盘当成一个分区,则在上面不能再创建分
* 区了。如写成 16,则最多可以创建 15 个分区。
*/
ramblock_disk = alloc_disk(16);
/* 2. 设置 */
/* 2.1 分配/设置队列:提供读写能力 */
/* blk_init_queue (request_fn_proc * rfn, spinlock_t * lock) 分配/设置一个队列。
* 参 1,执行处理队列的函数。
* 参 2,一个“自旋锁”。 DEFINE_SPINLOCK (beep_lock)
*/
ramblock_queue = blk_init_queue(do_ramblock_request, &ramblock_lock);
ramblock_disk->queue = ramblock_queue;
注释1:
把设置队列放在“分配队列”那一步骤代码紧跟的之下。就是把队列使用起来,即放到request_queue_t 结构变量 ramblock_queue 里中
注释2:
之前分析把“文件读写”转成“扇区读写”,对“扇区的读写”会放入个队列里面:把“buffer_head”构造为“bio”,把“bio”放入到队列里面,调用队列里面的“q->make_request_fn”函数,这个“make_request_fn”构造请求的函数有默认的函数“__make_request”。
当我们初始化队列时,给我们提供了一个默认构造请求的函数“__make_request”。
当它把这个“请求”放入到“队列”之后,以后会用这个队列里的“q->request_fn()”来处理。这个“request_fn()”就等于“blk_init_queue_node()”函数传进来的参数“形参1:request_fn_proc *rfn”。这个形参 1 会赋给“request_fn”,最后就是我们要定义的一个处理请求的函数。
request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)–>blk_init_queue_node(rfn, lock, -1); -->blk_queue_make_request(q, __make_request);提供了默认构造请求的函数。
对于我们这里这个把内存模拟成硬盘的驱动来说,最终会是我们这里定义的“doramblocrequest”来处理
/* 2.2 设置其他属性:比如容量 */
/* Register_blkdev()相比"register_chrdev()"少了一个形参 3“file_operations”结构体。只不过当形参 1 为“0”时,* register_blkdev()可以自动为块设备分配一个主设备号,且在“cat /proc/device”时可以看到有哪些块设
*/
major = register_blkdev(0, "ramblock"); /* cat/proc/devices */
ramblock_disk->major = major;
ramblock_disk->first_minor = 0; //第一个次设备号写为0,则从0~16都对应这个块设
sprintf(ramblock_disk->disk_name, "ramblock"); //块设备的名字
ramblock_disk->fops = &ramblock_fops; //fops :操作函数。即使是空的操作函数,这个 fops 也要提
set_capacity(ramblock_disk, RAMBLOCK_SIZE / 512);
/* 3. 硬件相关操作 */
ramblock_buf = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL); //容量:设置容量时是以扇区为单位。在内核里,对于文件系统那一层,永远认为扇区是 512
/* 4. 注册 */
add_disk(ramblock_disk);
2 在出口函数ramblock_exit中
unregister_blkdev(major, "ramblock"); //卸载块
del_gendisk(ramblock_disk); ; //清除gendisk
put_disk(ramblock_disk); //释放块设备结构
blk_cleanup_queue(ramblock_queue); //清除队列
kfree(ramblock_buf);//释放内存模拟磁盘的内存空间
5、测试
测试3th,4th:
在开发板上:
1. insmod ramblock.ko
2. 格式化: mkdosfs /dev/ramblock
3. 挂接: mount /dev/ramblock /tmp/
4. 读写文件: cd /tmp, 在里面
创建文件:vi 1.txt
拷贝文件:cp /etc/inittab .
5. cd / ;umount /tmp/ 退出并且取消挂接
重新挂接: mount /dev/ramblock /tmp/
ls /temp里面的文件还在
umount /tmp/
6. cat /dev/ramblock > /mnt/ramblock.bin :把整个磁盘拷到这个文件里面去
7. 在ubuntu上查看ramblock.bin
sudo mount -o loop ramblock.bin /mnt :-o loop可以将一个普通文件当做一个块设备来挂接
cd /work/nfs_root/first_fs
cd /mnt
ls :还会看见那两个文件
测试5th:
insmod ramblock.ko
在这里插入代码片
mkdosfs /dev/ramblock :格式化
mount /dev/ramblock /tmp :再来挂接
都是读操作
4.1 :cp /etc/inittab /tmp :拷贝文件即为写操作
先是读,过了一会才是写;就表示数据不是立刻完成的
4.3 :cp /mnt/ramblock.ko /tmp :没有立刻执行
mount /tmp :取消挂载之后立刻写进去了
分区操作
fdisk /dev/ramblock
提示没有磁头和柱面信息:使用getgeo函数ramblock_getgeo()获得磁盘的几何属性
获得提示信息:
添加函数之后重新实验:
rmmod ramblock.ko
insmod ramblock.ko
ls /dev/ramblock* -l :先看看有多少个block
fdisk /dev/ramblock
Command (m for help):n
p创建主分区之后输入1表示第一个分区
再输入1表示开始柱面是1
在输入5表示结束柱面是5
再来添加一个分区
这样就创建好连个分区了,接下来要输入w保存分区:
对两个分区可以分别格式化和挂接: