app: open,read,write,“1.txt”
---------------------------------------------------------------------------------文件的读写
文件系统: vfat,ext2,ext3, yaffs2, jffs2 (把文件的读写转换为对扇区的读写)
----------------------------ll_rw_block--------------------------------------扇区的读写
1.把“读写”放入队列
2.调用队列的处理函数(优化/调顺序)
块设备驱动程序
---------------------------------------------------------------------------------
硬件: 硬盘,flash
ll_rw_block 源码分析:
for (i = 0; i < nr; i++) {
struct buffer_head *bh = bhs[i];
submit_bh(rw, bh);
struct bio *bio;
submit_bio(rw, bio);
//通用的构造请求:使用bio来构造请求(request)
generic_make_request(bio);
__generic_make_request(bio);
request_queue *q = bdev_get_queue(bio->bi_bdev);//找到队列
//调用队列的“构造请求函数”
ret = q->make_request_fn(q, bio);
//默认的函数
__make_request
// 先尝试合并
elv_merge(q,&req,bio);
//如果合并不成,使用bio构造请求
init_request_from_bio(req,bio);
//把请求放入队列
add_request(q,req)
//执行队列
generic_unplug_device
//调用队列的“处理函数”
q->request_fn(q);
如何写块设备的驱动程序:
1.分配 gendisk 结构体:alloc_disk
2.设置
2.1.分配、设置队列:reuest_queue_t //提供读写能力
blk_init_queue
2.2设置 gendisk 其他信息 //提供属性
3.注册
==============================================================================================================================
程序书写思路:
1、分配一个gendisk结构体:
static struct gendisk *ramblock_disk;
ramblock_disk = alloc_disk(16);//参数为次设 备号个数(分区个数+1)
在linux内核中,使用gendisk(通用磁盘)来表示一个独立的磁盘设备。同一个磁盘的各个分区共享一个主设备号。而次设备号不同。
2、设置:
2.1首先设置一个队列,它能够提供读写能力
static struct request_queue *ramblock_queue;
ramblock_queue = blk_init_queue(do_ram_block_request, &ram_block_lock);
其中:
do_ram_block_request 请求处理函数,数据的一些操作在本函数中进行
ram_block_lock 访问队列权限自旋锁
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 = &ram_block_fops;
set_capacity(ramblock_disk,RAMBLOCK_SIZE/512);
说明:
>通过注册这个块设备,获得这个设备的主设备号。
>first_minor 为第一个次设备号。
>ram_block_fops 块设备操作结构体:
static struct block_device_operations ram_block_fops = {
.owner = THIS_MODULE,
.getgeo = ramblock_getgeo,
};
>>ramblock_getgeo 这个函数用来填充驱动器信息。
内容如下:
static int ramblock_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
/*容量 = heads*cylinders*sectors*512 */
geo->heads = 2;//面 随便定
geo->sectors = RAMBLOCK_SIZE/2/32/512;//扇区
geo->cylinders =32;//环 随便定
return 0;
}
>>>这个的柱面数、环数可以随便填写(? 真的随便吗?),但是扇区数必须是对的。有一个计算公式:
容量 = heads*cylinders*sectors*512
>set_capacity 设置gendisk容量,参数是扇区个数。块设备的最小寻址单元是扇区,扇区大小一般是2的整数倍,最常见的大小是512个字节,扇区的大小是设备的物理属性,扇区是所有块设备的的基本单元。不管无力设备的真实扇区的大小是多少,内核与块设备进行交互的扇区都以512字节为单位。因此set_capacity 函数也是以512字节为单位的。
3、硬件的相关操作:
由于本次的框架程序是以内存当作块设备,所以硬件的相关操作可以看作对内存空间的申请,因此工作比较简单;
static unsigned char *ramblock_buf;
/*3.硬件相关的操作*/
ramblock_buf = kzalloc(RAMBLOCK_SIZE,GFP_KERNEL);
if (!ramblock_buf)
return -ENOMEM;
4、注册:
add_disk(ramblock_disk);
注意:对add_disk的调用必须发生在驱动程序的初始化工作完成之后并且能够响应磁盘的请求之后。
这样一个驱动程序框架就算完成了,但是具体的工作还没有干,下面填充:do_ram_block_request这个函数,是在设置队列提到的:
static void do_ram_block_request (struct request_queue * q)
{
static int r_cnt = 0;
static int w_cnt = 0;
struct request *req;
while ((req = elv_next_request(q)) != NULL)
{
/*数据传输三要素: 源,长度,目的*/
/*源/目的:*/
unsigned long offset = req->sector<<9;//*512 这个扇区
/*目的/源:*/
//req->buffer
/*长度*/
unsigned len = req->current_nr_sectors<<9;//*512
if(rq_data_dir(req) == READ)
{
// printk("read %d \n",++r_cnt);
memcpy(req->buffer,ramblock_buf+offset,len);
}
else
{
// printk("write %d \n",++w_cnt);
memcpy(ramblock_buf+offset,req->buffer,len);
}
end_request(req, 1); /* wrap up, 0 = fail, 1 = success */
}
}
这里涉及了数据的操作,数据操作一般都会有三个要素,这是韦东山经常说的: 源、目的、长度。
>通过电梯算法,获得这个队列的每个内容。
>获得偏移值:扇区*512
> 获得长度:当前要传送的扇区数目*512
>rq_data_dir这个宏从请求中抽取传送的方向
==============================================================================================================================
测试方法:
1、安装驱动
2、格式化 ramdisk: mkdosfs /dev/ramblock
3、ls /dev/ramblock
4、分区:
输入w生效。
5、可以通过 ls -l /dev/ramblock*察看分区:
brw-rw---- 1 root root 13, 0 Jan 1 00:02 /dev/ramblock
brw-rw---- 1 root root 13, 1 Jan 1 00:02 /dev/ramblock1
brw-rw---- 1 root root 13, 2 Jan 1 00:02 /dev/ramblock2
6、可以通过mount /dev/ramblock1 /tmp/ 将新的分区挂接到 /tmp/上
=======================================================================================================================
完整的驱动程序代码:驱动代码地址