23.Linux-块设备驱动(详解)
通过上节的块设备驱动分析,本节便通过内存来模拟块设备驱动 ,方便我们更加熟悉块设备驱动框架
参考内核自带的块设备驱动程序:
drivers/block /xd.c
drivers/block /z2ram.c
1.本节需要的结构体如下:
1.1 gendisk磁盘结构体:
struct gendisk { int major; //设备主设备号,等于register_blkdev()函数里的major int first_minor; //起始次设备号,等于0,则表示此设备号从0开始的 int minors; //分区(次设备)数量,当使用alloc_disk()时,就会自动设置该成员 char disk_name[32]; //块设备名称, 等于register_blkdev()函数里的name struct hd_struct **part; /*分区表的信息*/ int part_uevent_suppress; struct block_device_operations *fops; //块设备操作函数 struct request_queue *queue; //请求队列,用于管理该设备IO请求队列的指针* void *private_data; /*私有数据*/ sector_t capacity; /*扇区数,512字节为1个扇区,描述设备容量*/ .... };
1.2 request申请结构体:
struct request { //用于挂在请求队列链表的节点,使用函数elv_next_request()访问它,而不能直接访问
struct list_head queuelist; struct list_head donelist; /*用于挂在已完成请求链表的节点*/ struct request_queue *q; /*指向请求队列*/ unsigned int cmd_flags; /*命令标识*/ enum rq_cmd_type_bits cmd_type; //读写命令标志,为 0(READ)表示读, 为1(WRITE)表示写 sector_t sector; //要提交的下一个扇区偏移位置(offset) ... ... unsigned int current_nr_sectors; //当前需要传送的扇区数(长度) ... ... char *buffer; //当前请求队列链表的申请里面的数据,用来读写扇区数据(源地址) ... ... };
2.本节需要的函数如下:
int register_blkdev(unsigned int major, const char *name);
创建一个块设备,当major==0时,表示动态创建,创建成功会返回一个主设备号
unregister_blkdev(unsigned int major, const char *name);
卸载一个块设备, 在出口函数中使用,major:主设备号, name:名称
struct gendisk *alloc_disk(int minors);
分配一个gendisk结构,minors为分区数,填1表示不分区
void del_gendisk(struct gendisk *disk);
释放gendisk结构,在出口函数中使用,也就是不需要这个磁盘了
request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock);
分配一个request_queue请求队列,分配成功返回一个request_queue结构体
rfn: request_fn_proc结构体,用来执行放置在队列中的请求的处理函数
lock:队列访问权限的自旋锁(spinlock),该锁通过DEFINE_SPINLOCK()来定义
void blk_cleanup_queue(request_queue_t * q);
清除内核中的request_queue请求队列,在出口函数中使用
static DEFINE_SPINLOCK(spinlock_t lock);
定义一个自旋锁(spinlock)
static inline void set_capacity(struct gendisk *disk, sector_t size);
设置gendisk结构体的扇区数(成员copacity), size等于扇区数
该函数内容如下:
disk->capacity = size;
void add_disk(struct gendisk *gd);
向内核中注册gendisk结构体
void put_disk(struct gendisk *disk);
注销内核中的gendisk结构体,在出口函数中使用
struct request *elv_next_request(request_queue_t *q);
通过电梯算法获取申请队列中未完成的申请,获取成功返回一个request结构体,不成功返回NULL
(PS: 不使用获取到的这个申请时,应使用end_request()来结束获取申请)
void end_request(struct request *req, int uptodate);
结束获取申请, 当uptodate==0,表示使用该申请读写扇区失败, uptodate==1,表示成功
static inline void *kzalloc(size_t size, gfp_t flags);
分配一段静态缓存,这里用来当做我们的磁盘扇区用,分配成功返回缓存地址,分配失败会返回0
void kfree(const void *block);
注销一段静态缓存,与kzalloc()成对,在出口函数中使用
rq_data_dir(rq);
获取request申请结构体的命令标志(cmd_flags成员),当返回READ(0)表示读扇区命令,否则为写扇区命令
3.步骤如下:
3.1在入口函数中:
- 1)使用register_blkdev()创建一个块设备
- 2) blk_init_queue()使用分配一个申请队列,并赋申请队列处理函数
- 3)使用alloc_disk()分配一个gendisk结构体
- 4)设置gendisk结构体的成员
- ->4.1)设置成员参数(major、first_minor、disk_name、fops)
- ->4.2)设置queue成员,等于之前分配的申请队列
- ->4.3)通过set_capacity()设置capacity成员,等于扇区数
- 5)使用kzalloc()来获取缓存地址,用做扇区
- 6)使用add_disk()注册gendisk结构体
3.2在申请队列的处理函数中
- 1) while循环使用elv_next_request()获取申请队列中每个未处理的申请
- 2)使用rq_data_dir()来获取每个申请的读写命令标志,为 0(READ)表示读, 为1(WRITE)表示写
- 3)使用memcp()来读或者写扇区(缓存)
- 4)使用end_request()来结束获取的每个申请
3.3在出口函数中
- 1)使用put_disk()和del_gendisk()来注销,释放gendisk结构体
- 2)使用kfree()释放磁盘扇区缓存
- 3)使用blk_cleanup_queue()清除内存中的申请队列
- 4)使用unregister_blkdev()卸载块设备
4.代码如下:
#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 DEFINE_SPINLOCK(memblock_lock); //定义自旋锁 static request_queue_t * memblock_request; //申请队列 static struct gendisk *memblock_disk; //磁盘结构体 static int memblock_major; #define BLOCKBUF_SIZE (1024*1024) //磁盘大小 #define SECTOR_SIZE (512) //扇区大小 static unsigned char *block_buf; //磁盘地址 static int memblock_getgeo(struct block_device *bdev, struct hd_geometry *geo) { geo->heads =2; // 2个磁头分区 geo->cylinders = 32; //一个磁头有32个柱面 geo->sectors = BLOCKBUF_SIZE/(2*32*SECTOR_SIZE); //一个柱面有多少个扇区 return 0; } static struct block_device_operations memblock_fops = { .owner = THIS_MODULE, .getgeo = memblock_getgeo, //几何,保存磁盘的信息(柱头,柱面,扇区) }; /*申请队列处理函数*/ static void do_memblock_request (request_queue_t * q) { struct request *req; unsigned long offset; unsigned long len; static unsigned long r_cnt = 0; static unsigned long w_cnt = 0; while ((req = elv_next_request(q)) != NULL) //获取每个申请 { offset=req->sector*SECTOR_SIZE; //偏移值 len=req->current_nr_sectors*SECTOR_SIZE; //长度 if(rq_data_dir(req)==READ) { memcpy(req->buffer,block_buf+offset,len); //读出缓存 } else { memcpy(block_buf+offset,req->buffer,len); //写入缓存 } end_request(req, 1); //结束获取的申请 } } /*入口函数*/ static int memblock_init(void) { /*1)使用register_blkdev()创建一个块设备*/ memblock_major=register_blkdev(0, "memblock"); /*2) blk_init_queue()使用分配一个申请队列,并赋申请队列处理函数*/ memblock_request=blk_init_queue(do_memblock_request,&memblock_lock); /*3)使用alloc_disk()分配一个gendisk结构体*/ memblock_disk=alloc_disk(16); //不分区 /*4)设置gendisk结构体的成员*/ /*->4.1)设置成员参数(major、first_minor、disk_name、fops)*/ memblock_disk->major = memblock_major; memblock_disk->first_minor = 0; sprintf(memblock_disk->disk_name, "memblock"); memblock_disk->fops = &memblock_fops; /*->4.2)设置queue成员,等于之前分配的申请队列*/ memblock_disk->queue = memblock_request; /*->4.3)通过set_capacity()设置capacity成员,等于扇区数*/ set_capacity(memblock_disk,BLOCKBUF_SIZE/SECTOR_SIZE); /*5)使用kzalloc()来获取缓存地址,用做扇区*/ block_buf=kzalloc(BLOCKBUF_SIZE, GFP_KERNEL); /*6)使用add_disk()注册gendisk结构体*/ add_disk(memblock_disk); return 0; } static void memblock_exit(void) { /*1)使用put_disk()和del_gendisk()来注销,释放gendisk结构体*/ put_disk(memblock_disk); del_gendisk(memblock_disk);
/*2)使用kfree()释放磁盘扇区缓存 */ kfree(block_buf);
/*3)使用blk_cleanup_queue()清除内存中的申请队列 */ blk_cleanup_queue(memblock_request); /*4)使用unregister_blkdev()卸载块设备 */ unregister_blkdev(memblock_major,"memblock"); } module_init(memblock_init); module_exit(memblock_exit); MODULE_LICENSE("GPL");
5.测试运行
insmod ramblock.ko //挂载memblock块设备 mkdosfs /dev/memblock //将memblock块设备格式化为dos磁盘类型 mount /dev/ memblock /tmp/ //挂载块设备到/tmp目录下
接下来在/tmp目录下vi 1.txt文件,最终都会保存在/dev/ memblock块设备里面
cd /; umount /tmp/ //退出/tmp,卸载,同时之前读写的文件也会消失 cat /dev/memblock > /mnt/memblock.bin //在/mnt目录下创建.bin文件,然后将块设备里面的文件追加到.bin里面
然后进入linux的nfs挂载目录中
sudo mount -o loop ramblock.bin /mnt //挂载ramblock.bin, -loop:将文件当做磁盘来挂载
如下图,就可以找到我们之前在开发板上创建的1.txt了
说明这个块设备测试运行无误
6.使用fdisk来对磁盘分区
(fdisk命令使用详解: http://www.cnblogs.com/lifexy/p/7661239.html)
共分了两个分区,如下图所示:
如下图,接下来就可以向上小节那样,分别操作多个分区磁盘了:
7.使用fdisk来设置磁盘分区的系统属性
通过 fdisk -l 查看磁盘分区属性,以SD卡的磁盘(mmc)为例,刚分区出来的磁盘是默认值:
将属性设置为Win95 FAT32 (LBA):
fdisk /dev/mmcblk1
然后输入t 改变磁盘属性,再输入l 列出可以设置的属性表:
找到Win95 FAT32 (LBA)的标签是c
所以接下来输入:
c //选择Win95 FAT32 (LBA) w //保存并退出
再次输入fdisk -l,可以看到磁盘属性已经更改了:
下章学习: 24.Linux-Nand Flash驱动(分析MTD层并制作NAND驱动)
人间有真情,人间有真爱。