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保存分区:
在这里插入图片描述
对两个分区可以分别格式化和挂接:
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

「已注销」

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

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

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

打赏作者

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

抵扣说明:

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

余额充值