首先需要了解下块设备驱动和字符设备驱动区别:
1.字符设备驱动都是以字节流的形式,而块设备驱动是以块读写和操作(比如磁盘是扇区,flash是page为单位)
2.字符设备app可以直接读写,简单快捷。块设备驱动需要中间实现一个buffer 队列,然后实现调度算法(合并,调整顺序等)
磁盘例子:先读后写,调整了顺序。
2. flash 会合并下两个操作。
具体流程如下图所示,app开始对磁盘读写时候,首先会进行系统调用,然后通过文件系统实现对通用块层实现调用。
最终文件系统会调用ll_rw_block.
void ll_rw_block(int rw, int nr, struct buffer_head *bhs[])
submit_bh(WRITE, bh);//提交bh
submit_bio(rw, bio);//再submit_bh中创建并提交bio,
generic_make_request(bio);
__generic_make_request(bio);
request_queue_t q = bdev_get_queue(bio->bi_bdev);//找到请求队列
q->make_request_fn(q, bio);//调用队列中的请求函数。 默认的函数是__make_request
__make_request在request_queue_t * blk_init_queue_node(request_fn_proc *rfn, spinlock_t *lock, int node_id)中构造
make_request 函数中:
el_ret = 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
例子如下:
#include <linux/module.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> static struct gendisk *ramblock_disk; static request_queue_t *ramblock_queue; static int major; static DEFINE_SPINLOCK(ramblock_lock); #define RAMBLOCK_SIZE (1024*1024) static unsigned char *ramblock_buf; 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, }; 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); } } 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); /* 3. 硬件相关操作 */ ramblock_buf = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL); /* 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");
实验会发现:
当copy 文件 到这个ramblock 磁盘分区(实际是ram模拟的),并不会立刻打印,而是呆了一会才开始。当终端敲sync时候,会立刻写入。当umount时候也会立刻写入。