块设备驱动程序的引入和简单应用
来源:互联网 发布:如何运用办公软件 编辑:程序博客网 时间:2024/06/07 04:00
先说说为什么要引入块设备驱动程序。
以FLASH为例,如果对flash的读写采用字符设备驱动程序的那一套的话,会产生效率低下的问题。flash是以块为单位进行操作的,假如在flash一个块中,要完成对扇区0和扇区1的改写,如果按照字符设备的方式,需要完成以下步骤:
①读出整个块到buffer
②修改buffer的扇区0
③擦除整个块
④烧写整块
⑤读出整个块到buffer
⑥修改buffer的扇区1
⑦擦除整个块
⑧烧写整块
但如果我们引入一些优化:
①读出整个块到buffer
②修改buffer的扇区0和扇区1
③擦除整个块
④烧写整块
这样我们就能够大大提高效率。因此,块设备驱动程序的基本思想就出来了:先不执行读写,而是放入队列;优化之后再执行。
下面介绍一下块设备驱动程序的框架。
app: open,read,write "1.txt"--------------------------------------------- 文件的读写文件系统: vfat, ext2, ext3, yaffs2, jffs2 (把文件的读写转换为扇区的读写)-----------------ll_rw_block----------------- 扇区的读写 1. 把"读写"放入队列 2. 调用队列的处理函数(优化/调顺序/合并) 块设备驱动程序 ---------------------------------------------硬件: 硬盘,flash
下面开始分析ll_rw_block(low level read/write block)
分析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. 设置
2.1 分配/设置队列: request_queue_t 和blk_init_queue// 它提供读写能力
2.2 设置gendisk其他信息 // 它提供属性: 比如容量
3. 注册: add_disk
接下来要写一个真正的驱动程序,分配一块内存,用内存来模拟块设备。
第一个程序:
static struct gendisk *ramblock_disk;static request_queue_t *ramblock_queue;static int major;static DEFINE_SPINLOCK(ramblock_lock);//定义自选锁static struct block_device_operations ramblock_fops = { .owner = THIS_MODULE,};#define RAMBLOCK_SIZE (1024*1024)static void do_ramblock_request(request_queue_t * q){ static int cnt = 0; printk("do_ramblock_request %d\n", ++cnt);}static int ramblock_init(void){ /* 1. 分配一个gendisk结构体 */ ramblock_disk = alloc_disk(16); /* 次设备号个数: 分区个数+1 */ /* 2. 设置 */ /* 2.1 分配/设置队列: 提供读写能力 */ ramblock_queue = blk_init_queue(do_ramblock_request, &ramblock_lock); ramblock_disk->queue = ramblock_queue; /* 2.2 设置其他属性: 比如容量 */ major = register_blkdev(0, "ramblock"); /* 用cat /proc/devices可以看到块设备 */ ramblock_disk->major = major; ramblock_disk->first_minor = 0; sprintf(ramblock_disk->disk_name, "ramblock"); ramblock_disk->fops = &ramblock_fops; set_capacity(ramblock_disk, RAMBLOCK_SIZE / 512);//单位是扇区,在内核层面默认扇区是521字节 /* 3. 注册 */ add_disk(ramblock_disk); return 0;}static void ramblock_exit(void){ unregister_blkdev(major, "ramblock"); del_gendisk(ramblock_disk); put_disk(ramblock_disk); blk_cleanup_queue(ramblock_queue);}module_init(ramblock_init);module_exit(ramblock_exit);MODULE_LICENSE("GPL");
加载驱动之后发现:
ramblock:do_ramblock_request 1
然后就卡住了,无法继续操作。说明确实调用了do_ramblock_request这个函数,但由于我们没有对它进行进一步的处理,所以还需要完善。
static void do_ramblock_request(request_queue_t * q){ static int cnt = 0; struct request *req; printk("do_ramblock_request %d\n", ++cnt); while ((req = elv_next_request(q)) != NULL) { end_request(req, 1); }}
以电梯调度算法取出下一个请求,然后结束请求并返回1表示成功。实际上我们什么都没有做,但这样就不会卡住了。它会打印出如下结果:
ramblock:do_ramblock_request 1
unknown partition table
之所以提示不识别的分区表,是因为我们在实际上我们在do_ramblock_request里什么都没有做。我们只是简单粗暴的把请求拿出来,然后就简单把它当成完成了。
进一步优化:
static void do_ramblock_request(request_queue_t * q){ static int 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;//扇区的长度乘以每个扇区大小 /* 目的/源:当读的时候就是目的,写的时候就是源 */ // req->buffer /* 长度: */ unsigned long len = req->current_nr_sectors * 512;//扇区的长度乘以每个扇区大小 if (rq_data_dir(req) == READ) { memcpy(req->buffer, ramblock_buf+offset, len); } else { memcpy(ramblock_buf+offset, req->buffer, len); } end_request(req, 1); }
其中memcpy(void *dest, const void *src, size_t count)。
测试:
在开发板上:
1. insmod ramblock.ko
2. 格式化: mkdosfs /dev/ramblock
3. 挂接: mount /dev/ramblock /tmp/
4. 读写文件: cd /tmp, 在里面vi文件,比如新建一个1.txt,里面的内容是“hello”
5. cd /; umount /tmp/,卸载设备
6. cat /dev/ramblock > /mnt/ramblock.bin,把文件的内容统统存到/mnt/ramblock.bin,相当于整个磁盘映象。
7. 在PC上查看ramblock.bin
sudo mount -o loop ramblock.bin /mnt,可以把一个普通文件当作块设备文件挂载,输入ls仍然可以看到1.txt。
接下来加入打印语句,可以看到一些有意思的事情:
static void do_ramblock_request(request_queue_t * q){ 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; /* 目的/源: */ // 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); } else { printk("do_ramblock_request write %d\n", ++w_cnt); memcpy(ramblock_buf+offset, req->buffer, len); } end_request(req, 1); }}
还是按照上面的步骤测试,当挂载到/tmp之后,我们执行“cp /etc/inittab /tmp”:
do_ramblock_request write 6
do_ramblock_request write 7
do_ramblock_request write 8
再执行sync
do_ramblock_request write 9
do_ramblock_request write 10
其中,打印的时候会有很长的时间间隔,当输入sync时,会完成全部操作。sync是一个系统调用,是同步的意思。
从这里我们可以看出,块设备的读写不会交叉执行,而是会进行优化,放入队列。
接下来进行下一步的优化,实现分区:
static struct block_device_operations ramblock_fops = { .owner = THIS_MODULE, .getgeo = ramblock_getgeo,};static int ramblock_getgeo(struct block_device *bdev, struct hd_geometry *geo){ /* 容量=heads*cylinders*sectors*512 */ geo->heads = 2;//磁头的个数 geo->cylinders = 32;//柱面的个数 geo->sectors = RAMBLOCK_SIZE/2/32/512; return 0;}
要注意的是,实际上很多磁盘已经不是这种结构了,但为了兼容,必须设置虚拟的参数,这样才能借助分区工具。
加载驱动之后输入fdisk /dev/ramblock。然后输入n新建一个分区,输入p表示主分区,然后输入1表示第一个主分区。
Command (m for help): pDisk /dev/ramblock: 1 MB, 1048576 bytes2 heads, 32 sectors/track, 32 cylindersUnits = cylinders of 64 * 512 = 32768 bytes Device Boot Start End Blocks Id SystemCommand (m for help): nCommand action e extended p primary partition (1-4)pPartition number (1-4): 1First cylinder (1-32, default 1): 1Last cylinder or +size or +sizeM or +sizeK (1-32, default 32): 5Command (m for help): wThe partition table has been altered! ramblock: ramblock1
这样第一个分区就好了。剩下的分区也这样操作,最后别忘记输入“w”把分区写到分区表中。最后可以看到:
Command (m for help): pDisk /dev/ramblock: 1 MB, 1048576 bytes2 heads, 32 sectors/track, 32 cylindersUnits = cylinders of 64 * 512 = 32768 bytes Device Boot Start End Blocks Id System/dev/ramblock1 1 5 144 83 Linux/dev/ramblock2 6 25 640 83 Linux/dev/ramblock3 26 32 224 83 Linux
如果现在输入”ls /dev/ramblock* -l”,会看到:
brw-rw---- 1 0 0 254, 0 Jan 1 00:45 /dev/ramblockbrw-rw---- 1 0 0 254, 1 Jan 1 00:45 /dev/ramblock1brw-rw---- 1 0 0 254, 2 Jan 1 00:45 /dev/ramblock2brw-rw---- 1 0 0 254, 3 Jan 1 00:45 /dev/ramblock3
如果我们想分别格式化的时候就可以直接“fdisk /dev/ramblock2”,还可以直接挂载“mount /dev/ramblock1 /tmp/”。
驱动程序就写到这里,用内存模拟磁盘很简单,可以想象一下,如果块设备也能够像内存一样访问的话,块设备就会非常的简单,但可惜的是,现在还达不到这种水平,硬件的操作太复杂了。但块设备本身的框架并不复杂。
- 块设备驱动程序的引入和简单应用
- 块设备驱动的引入
- 块设备的驱动程序框架
- 块设备驱动程序的框架
- 链表的引入和简单应用
- 10块设备驱动程序的编写
- 块设备驱动程序实现
- 13. 块设备驱动程序
- 块设备驱动程序
- 装载块设备驱动程序
- 块设备驱动程序框架
- 块设备驱动程序
- 块设备驱动程序
- .块设备驱动程序框架
- 块设备驱动程序
- 块设备驱动程序
- 块设备驱动程序之一
- 块设备驱动程序设计
- 几个常用maven插件
- 方维P2P理财版3.6+全新wap手机版+App源代码:新增加息卷+身份认证接口+微信公众平台,全开源,无域名和证书等任何限制
- FMDB - 官方使用文档
- Starling2.x 遮罩Mask的使用
- 数组操作方法归纳总结
- 块设备驱动程序的引入和简单应用
- Python2.7
- qt 多线程的两种实现方式
- 色彩设计在web界面如何应用?
- 2017开始学爬虫
- jvm垃圾收集小记
- 引用型的函数返回值
- 快速排序处理文件
- OpenCV python 学习笔记(三)