linux块设备驱动,Linux块设备驱动(示例代码)

推荐书:《Linux内核源代码情景分析》

1.字符设备驱动和使用中等待某一事件的方法

①查询方式

②休眠唤醒,但是这种没有超时时间

③poll机制,在休眠唤醒基础上加一个超时时间

④异步通知,异步通知实际上就是发信号

⑤输入子系统,这样比较通用

2.块设备相对于字符设备驱动逻辑的变化

①对于硬盘对读写的优化

假如要读磁头0的扇区0,然后写磁头1的扇区0,然后读磁头0的扇区1,若像字符设备那样,就会机械山跳转2次,效率低。

优化:

先不执行,放入队列,优化后再执行,这里的优化是指调整顺序。

②对于flash

假如要写同一个块的扇区0,然后再写扇区1,若是字符设备的做法,写扇区0时需要先把整块读取出来,然后修改此块中

扇区0的数据,然后烧写整个这个块。写扇区1时也是需要先把整块读取出来,然后修改此块中扇区1的数据,然后烧写整个这个块。

优化:

先不执行,放入队列,优化后再执行,这里的优化是指合并相同块的写请求。

所以块设备不能向字符设备一样直接提供读写函数,而是需要先放入队列之中,优化后再执行。

3.块设备驱动程序框架

App:open, read, write "1.txt"

----------------------------------------------- 文件的读写

文件系统:vfat,tfat, ext2, ext3, yaffs2, jffs2 作用:把文件的读写转换为扇区的读写

------------统一的入口:ll_rw_block()---------- 扇区的读写

1.把“读写”放入队列中(可能这一步就有优化)

2.调用队列的处理函数(优化/调整顺序/合并)

块设备驱动程序: 主要是读写块设备函数的实现和属性的提供

-----------------------------------------------

硬件:硬盘,flash,eMMC

4.分析ll_rw_block()

ll_rw_block //fs/buffer.c, 位于fs下,说明是所有文件系统的一个通用的.c文件

for (i = 0; i < nr; i++)

submit_bh(op, op_flags, bh);//fs/buffer.c

struct bio *bio;

通用的构造请求,使用bio来构造请求

submit_bio(bio);

generic_make_request(bio);struct request_queue *q = bio->bi_disk->queue; //block/blk-core.c 找对队列

ret = q->make_request_fn(q, bio); //调用队列的构造请求函数,默认的设置函数是:make_request_fn

q->make_request_fn在blk_queue_make_request block/Blk-settings.c中被赋值,

blk_queue_make_request在blk_init_allocated_queue block/blk-core.c中被赋值为blk_queue_bio,即make_request_fn=blk_queue_bio

blk_queue_bio//默认是这个

elv_merge(q, &req, bio) //block/blk-core.c 以电梯调用算法尝试合并这个请求

如果合并不成功,调用get_request使用bio构造请求,将请求放入队列中

get_request(q, bio->bi_opf, bio, GFP_NOIO);

blk_init_request_from_bio(req, bio);

blk_flush_plug_list(plug,false);if(q) queue_unplugged(q, depth, from_schedule);

__blk_run_queue

q->request_fn(q); //调用队列的处理函数,就是块设备驱动实际的读写函数

5.写块设备驱动

1.分配构造struct request_queue,用于提供读写能力

2.设备描述,提供属性

......

====>内核指定了一个结构体:gendisk

驱动框架:

1.分配gendisk: alloc_disk

2.设置

2.1 分配/设置struct request_queue结构,blk_init_queue

2.2 设置gendisk其它信息

3.注册gendisk

6.块设备的操作是以扇区为单位的,内核机制决定的,就算是使用内存模拟的块设备也不例外。

7.对于一块全0的内存模拟的磁盘,未格式化会报"unknow partation table",因为其分区表为空

8.测试

cat /proc/devices 查看设备号

格式化磁盘:# mkfs /dev/ramblock eg: mkdosfs /dev/ramdisk

挂载:# mount /dev/ramblock /tmp

之后就可以通过访问/tmp目录操作磁盘了

9.创建磁盘映像

# cat /dev/ramblock > /ramblock.bin 然后再格式化磁盘再echo进去应该也是可以的。

在Ubuntu里面:# sudo mount -o loop ramblock.bin /mnt loop选项可以把一个普通文件当作块设备进行挂载。loop把它当作一个回环设备。

10.把对内存的操作打印出来可以对比App的读写和实际的IO操作发生的时机的区别。

11.分区

# ls /dev/ramblock* -l 次设备号是0表示整个磁盘,不是分区。起始是0应该是first_minor=0决定的。

# fdisk /dev/ramblock m n p 1(此时显示1-32 sylinder就是驱动中在ramblock_getgeo()中配置的) 1 5,再创建一个分区n p 2 6 32 w

(w表示将配置写到分区表中,分区表就是第一个扇区)

# ls /dev/ramblock* -l 次设备号是0表示整个磁盘,次设备号是1表示第一个主分区,次设备号是2表示第二个主分区。

此时也可以分别格式化每一个主分区:

eg: # mkdosfs /dev/ramblock1

eg: # mkdosfs /dev/ramblock2

分别挂载:

eg: # mount /dev/ramblock1 /mnt

eg: # mount /dev/ramblock2 /tmp

使用# fdisk /dev/ramdisk 报错: Unknow value(s) for: Cylinder 不知道柱面数,fdisk是个老工具了,使用它需要驱动告诉它柱面数信

息(目前很多块设备都不使用这个储存方式了)

12.内存模拟块设备驱动代码

/*参考: driverslockz2ram.c*/#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include#include

static struct gendisk *ramblock_disk;static request_queue_t *ramblock_queue;static intmajor;staticDEFINE_SPINLOCK(ramblock_lock);#define RAMBLOCK_SIZE (1024*1024) /*使用1M内存来模拟磁盘*/

static unsigned char *ramblock_buf;/*这些信息在使用fdisk工具分区的时候会用到*/

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;

}static struct block_device_operations ramblock_fops ={

.owner=THIS_MODULE,

.getgeo=ramblock_getgeo,

};/*目前内核中elv_next_request已经不存在了*/

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

", ++cnt);

while ((req = elv_next_request(q)) !=NULL) {/*数据传输三要素: 源,目的,长度*/

/*源/目的:*/unsignedlong offset = req->sector * 512;/*目的/源:*/

//req->buffer

/*长度:*/unsignedlong len = req->current_nr_sectors * 512;if (rq_data_dir(req) == READ) /*也就是: req->cmd_flags & 1*/{

printk("do_ramblock_request read %d", ++r_cnt);

memcpy(req->buffer, ramblock_buf+offset, len); /*这里是使用memcpy来模拟复杂的IO操作*/}else{/*加了这个打印,可以看出来当向设备进行写的时候没有立即调用,而是过来一小会才调用的,

与算法有关,先放到队列中,然后才执行。

# cp /etc/fstab /tmp(挂载目录),发现过来一会也没有调用这个函数

# sync 立即就打印了

# cp /etc/fstab /tmp 发现没有立即写

# umount /tmp/ 发现立即打印了*/printk("do_ramblock_request write %d", ++w_cnt);

memcpy(ramblock_buf+offset, req->buffer, len);

}

end_request(req,1);

}

}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); /*arg1: 执行实际读写io操作的函数*/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"); /*区别设置这两个名字进行测试*/ /*仿照scsi/sd.c sd_format_disk_name测试一下,对比sd卡的,它是什么/dev/下的设备节点名就是什么*/ramblock_disk->fops = &ramblock_fops;

set_capacity(ramblock_disk, RAMBLOCK_SIZE/ 512); /*设置磁盘容量,是以扇区为单位的*/

/*3. 硬件相关操作*/ramblock_buf= kzalloc(RAMBLOCK_SIZE, GFP_KERNEL); /*这个设备全为0,分区表为空,所以在insmod驱动的时候会报"unknow partation table",需要格式化*/

/*4. 注册*/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);

kfree(ramblock_buf);

}

module_init(ramblock_init);

module_exit(ramblock_exit);

MODULE_LICENSE("GPL");

新内核读写函数应替换为:

static void do_ramblock_request(struct request_queue *q)

{struct request *req;char *bio_buffer;while ((req = blk_fetch_request(q)) !=NULL) {

unsignedlong offset = blk_rq_pos(req) << 9; /*return rq->__sector; the current sector,右移9也即乘以512*/unsignedlong len = blk_rq_cur_bytes(req); /*bytes left in the current segment*/

if (offset + len >RAMBLOCK_SIZE) {

printk("do_ramblock_request read %d", ++r_cnt);

len= RAMBLOCK_SIZE-offset;

}

bio_buffer= bio_data(req->bio); /*拷贝到这里面返回给用户*/

if (rq_data_dir(req) ==READ)

memcpy(bio_buffer, ramblock_buf+offset, len);elsememcpy(ramblock_buf+offset, bio_buffer, len);

}

}

根据do_z2_request(), 就算是这个函数是void类型的也能向外部传递操作失败的状态。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值