目录
一、块设备驱动的引入
- 对于字符设备驱动,应用层read/write就会调用到底层的read/write,若对于块设备这样执行会有什么后果,例如:
硬盘,一个一个盘面组成,正面为磁头0,背面磁头1,磁头中有很多环组成即柱面,每个柱面中有很多扇区,假设先读0磁头的扇区0,后写2磁头的扇区0,再读0磁头扇区1,对于磁盘里的机械结构,首先要定位到0磁头的扇区0读,再跳到2磁头的扇区0写,再跳回0磁头扇区1写,跳了2次,对于磁盘来说,读写是很快的,慢在机械结构的跳动,因此效率低,因此需要优化,先读0磁头的扇区0,再读0磁头扇区1,后写2磁头的扇区0,这样就只跳动了一次
Flash,多个块组成,每一块有多个扇区,假设红色区域为一个块,先写扇区0,再写扇区1,对于Flash的特性要先擦除才能写,而且擦除的时候是以整块为单位进行擦除,所以对于写扇区0,需要先读出整个块到buffer中,修改buffer中的扇区0数据,擦除整块,再烧写整块,对于扇区1也是同样的操作,效率一样低,因此需要优化,先读出整块,修改buffer中的两个扇区数据,在擦除烧写
总结:1.先不执行,放入队列; 2.优化后(对于磁盘对调整顺序,对于Flash是合并)再执行,因此块设备不能像字符设备那样直接提供读写函数,而是把读写放入队列,优化后再执行
二、块设备驱动程序的框架
应用程序读写一个普通文件,最终会转换成操作硬件,硬件又由块设备驱动程序来操作,而普通文件怎么转换为对硬件的读写是由文件系统把文件的读写转换为对扇区的读写,会调用到ll_rw_block函数,而ll_rw_block为通用的入口,把"读写"放入队列,调用队列的处理函数(优化/调顺序/合并),也有可能在放入队列过程中就优化了
app | open,write,read("1.txt") | |
文件系统 | vfat. ext2. ext3. yaffs2. jffs2.. | |
ll_rw_block | ||
块设备驱动程序 | ||
硬件 | 硬盘, flash |
分析底层ll_rw_block函数:点击这里
写块设备驱动程序:
1. 分配gendisk: alloc_disk
2. 设置
2.1 分配/设置队列: request_queue_t(提供读写能力)
blk_init_queue
2.2 设置gendisk其他信息(提供属性: 比如容量)
3. 注册: add_disk
三、编写块设备驱动程序
- 目的:分配一块内存来模拟硬盘
3.1 编写驱动框架
参考内核源码:drivers\block\xd.c,drivers\block\z2ram.c
分配一个gendisk结构体,minors为次设备号个数:分区个数+1,/dev/sda表示整个磁盘,主分区sda1,sd2,扩展分区sda5
struct gendisk *alloc_disk(int minors)
static struct gendisk *ramblock_disk;
ramblock_disk = alloc_disk(16);
分配队列,返回request_queue_t结构体,rfn为执行队列的函数,lock为自旋锁,在blk_init_queue函数初始化队列后提供了默认的构造请求函数__make_request,最终会调用到q->request_fn(q),而request_fn就等于blk_init_queue中传递的参数rfn
request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
static DEFINE_SPINLOCK(ramblock_lock);
ramblock_queue = blk_init_queue(do_ramblock_request, &ramblock_lock);
参考源码设置属性,主设备号由register_blkdev函数返回值提供,注册之后可以使用cat /proc/devices查看设备信息,经过试验,即使ramblock_fops结构体中什么都没有,也需要提供,不然会出错
#define RAMBLOCK_SIZE (1024*1024)
static struct block_device_operations ramblock_fops = { //提供空的操作函数
.owner = THIS_MODULE,
};...
ramblock_disk->queue = ramblock_queue;
major = register_blkdev(0, "ramblock");
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); //设置容量的时候,是以扇区为单位的,在内核中认为扇区是512字节
参考z2ram.c写出出口函数,参考xd.c写队列执行函数(这里假设已经执行完),elv_next_request(q)是利用电梯调度算法取出下个请求,在这里先认为处理完毕,如果不加这请求,加载驱动会卡住
static void ramblock_exit(void)
{
unregister_blkdev(major, "ramblock");
del_gendisk(ramblock_disk);
put_disk(ramblock_disk);
blk_cleanup_queue(ramblock_queue);
}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表示成功 0表示失败
}
}
代码:
#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);
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;
struct request *req;
printk("do_ramblock_request %d\n", ++cnt);
while ((req = elv_next_request(q)) != NULL) {
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. 注册 */
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");
编译驱动后加载到开发板,加载驱动成功
3.2 完善代码分配内存模拟硬盘
static unsigned char *ramblock_buf;
static int ramblock_init(void)
{
...
ramblock_buf = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL);
...
}
因为是用内存模仿硬件,所以可以用memcpy函数来读写,修改do_ramblock_request函数,在request结构体中含有数据传输三要素,其中buffer在写的时候就是目的,在读的时候就是源
static void do_ramblock_request(request_queue_t * q)
{
static int cnt = 0;
struct request *req;
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);
}
}
/*
* try to put the fields that are referenced together in the same cacheline
*/
struct request {
...
sector_t sector; /* next sector to submit */
sector_t hard_sector; /* next sector to complete */
unsigned long nr_sectors; /* no. of sectors left to submit */
unsigned long hard_nr_sectors; /* no. of sectors left to complete */
/* no. of sectors left to submit in the current segment */
unsigned int current_nr_sectors;
...
char *buffer;
...
};
编译驱动后加载到开发板,由于新分配的内存都是清零的,不含有分区表
测试:
对于新买的U盘需要格式化,在这里也需要先将内存格式化
这里使用mkdosfs进行格式化,挂接ramblock设备,在里面读写文件,取消挂接,重新挂接依然存在
# mkdosfs /dev/ramblock
mkdosfs 2.11 (12 Mar 2005)
unable to get drive geometry, using default 255/63#
# mount /dev/ramblock /tmp/
# cd /tmp/
# vi 1.txt
hello
# cp /etc/inittab .
# cd /
# umount /tmp
# mount /dev/ramblock /tmp/
# cd /tmp/
# ls
1.txt inittab
#cat /dev/ramblock > /mnt/ramblock.bin //把整个磁盘拷到rambloc.bin中去
在ubuntu上挂接ramblock.bin文件可以看到一样的内容,在开发板中/mnt目录是开发板利用nfs来挂接ubuntu的目录,跟ubuntu中mnt不一样
book@www.100ask.org:/work/nfs_root/first_fs$ sudo mount -o loop ramblock.bin /mnt //-o loop可以把一个普通文件当作一个块设备来挂接
book@www.100ask.org:/work/nfs_root/first_fs$ cd /mnt/
book@www.100ask.org:/mnt$ ls
1.txt inittab
3.3 添加打印语句
由于只是内存模仿硬盘,对于真正的硬盘来说,读写操作会更加复杂,但是框架并不复杂,在队列函数读写中添加打印语句
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);
}
}
编译加载新驱动,重新格式化挂接,执行到cp /etc/inittab /tmp的时候,并没有立刻写,对于块设备的读写操作会先放在队列中,等了一会才会写,执行sync就可以立刻写进去,sync是系统调用(同步),在操作中不是读就是写,不会交叉运行,根据电梯调度算法来实现
# mkdosfs /dev/ramblock
mkdosfs 2.11 (12 Mar 2005)
do_ramblock_request read 2
do_ramblock_request read 3
unable to get drive geometrdo_ramblock_request write 1
do_ramblock_request write 2
do_ramblock_request write 3
do_ramblock_request write 4
do_ramblock_request write 5
# mount /dev/ramblock /tmp/
do_ramblock_request read 4
do_ramblock_request read 5
do_ramblock_request read 6
do_ramblock_request read 7
do_ramblock_request read 8
do_ramblock_request read 9
do_ramblock_request read 10
do_ramblock_request read 11
do_ramblock_request read 12
do_ramblock_request read 13
do_ramblock_request read 14
do_ramblock_request read 15
do_ramblock_request read 16
do_ramblock_request read 17
do_ramblock_request read 18
do_ramblock_request read 19
do_ramblock_request read 20
do_ramblock_request read 21
do_ramblock_request read 22
do_ramblock_request read 23
do_ramblock_request read 24
do_ramblock_request read 25
do_ramblock_request read 26
do_ramblock_request read 27
do_ramblock_request read 28
do_ramblock_request read 29
do_ramblock_request read 30
do_ramblock_request read 31
do_ramblock_request read 32
do_ramblock_request read 33
do_ramblock_request read 34
do_ramblock_request read 35
do_ramblock_request read 36
do_ramblock_request read 37
do_ramblock_request read 38
do_ramblock_request read 39
do_ramblock_request read 40
do_ramblock_request read 41
do_ramblock_request read 42
# cp /etc/inittab /tmp
do_ramblock_request read 43
# do_ramblock_request write 6
do_ramblock_request write 7
do_ramblock_request write 8
do_ramblock_request write 9# cp /etc/init.d/rcS /tmp
# sync
do_ramblock_request write 10
do_ramblock_request write 11
do_ramblock_request write 12
do_ramblock_request write 13
do_ramblock_request write 14
3.4 分区
对于买到的磁盘需要分区,这里来尝试分区 ,提示不知道柱面数,现在的磁盘可能已经没有磁头柱面的结构,为了兼容fdisk老工具,可以假装自己有多少磁头和柱面,在块设备的操作函数中getgeo设置
# fdisk /dev/ramblock
do_ramblock_request read 44
do_ramblock_request read 45
Unknown value(s) for: cylinders (settable in the extra functions menu)
设置几何属性,由于是内存模拟硬件这里假装有磁头柱面等,容量等于heads(磁头2面)*cylinders(柱面)*sectors*512
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,
};
加载新驱动,并测试分区
# fdisk /dev/ramblock
Device contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabel
Building a new DOS disklabel. Changes will remain in memory only,
until you decide to write them. After that the previous content
won't be recoverable.Warning: invalid flag 0x00,0x00 of partition table 4 will be corrected by w(rite)
Command (m for help): m //输入m获取帮助
Command Action
a toggle a bootable flag
b edit bsd disklabel
c toggle the dos compatibility flag
d delete a partition
l list known partition types
n add a new partition
o create a new empty DOS partition table
p print the partition table
q quit without saving changes
s create a new empty Sun disklabel
t change a partition's system id
u change display/entry units
v verify the partition table
w write table to disk and exit
x extra functionality (experts only)Command (m for help): n //输入n分区
Command action
e extended
p primary partition (1-4)
p //输入p设置主分区
Partition number (1-4): 1
First cylinder (1-32, default 1): 1 //输入1从柱面1开始
Last cylinder or +size or +sizeM or +sizeK (1-32, default 32): 5 //输入5从柱面5结束Command (m for help): n
Command action
e extended
p primary partition (1-4)
p
Partition number (1-4): 2
First cylinder (6-32, default 6): 6
Last cylinder or +size or +sizeM or +sizeK (6-32, default 32): Using default value 32Command (m for help): p //查看分区
Disk /dev/ramblock: 1 MB, 1048576 bytes
2 heads, 32 sectors/track, 32 cylinders
Units = cylinders of 64 * 512 = 32768 bytesDevice Boot Start End Blocks Id System
/dev/ramblock1 1 5 144 83 Linux
/dev/ramblock2 6 32 864 83 LinuxCommand (m for help): w //输入w分区才会写到分区表去,分区表是磁盘中的第一个扇区
The partition table has been altered!
ramblock: ramblock1 ramblock2Calling ioctl() to re-read partition table
分区完后可以对新分区分别挂接
Calling ioctl() to re-read partition table
# ls /dev/ramblock* -l
brw-rw---- 1 0 0 254, 0 Jan 1 00:07 /dev/ramblock
brw-rw---- 1 0 0 254, 1 Jan 1 00:07 /dev/ramblock1
brw-rw---- 1 0 0 254, 2 Jan 1 00:07 /dev/ramblock2
3.5 完整代码
#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");