通过上节的块设备驱动分析,本节便通过内存来模拟块设备驱动 ,方便我们更加熟悉块设备驱动框架

参考内核自带的块设备驱动程序:

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[]; //块设备名称, 等于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个磁头分区
geo->cylinders = ; //一个磁头有32个柱面
geo->sectors = BLOCKBUF_SIZE/(**SECTOR_SIZE); //一个柱面有多少个扇区
return ;
} 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 = ;
static unsigned long w_cnt = ; 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, ); //结束获取的申请
}
} /*入口函数*/
static int memblock_init(void)
{
/*1)使用register_blkdev()创建一个块设备*/
memblock_major=register_blkdev(, "memblock"); /*2) blk_init_queue()使用分配一个申请队列,并赋申请队列处理函数*/
memblock_request=blk_init_queue(do_memblock_request,&memblock_lock); /*3)使用alloc_disk()分配一个gendisk结构体*/
memblock_disk=alloc_disk(); //不分区 /*4)设置gendisk结构体的成员*/
/*->4.1)设置成员参数(major、first_minor、disk_name、fops)*/
memblock_disk->major = memblock_major;
memblock_disk->first_minor = ;
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 ;
}
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驱动)

23.Linux-块设备驱动(详解)的更多相关文章

  1. Linux块设备驱动详解

    <机械硬盘> a:磁盘结构 -----传统的机械硬盘一般为3.5英寸硬盘,并由多个圆形蝶片组成,每个蝶片拥有独立的机械臂和磁头,每个堞片的圆形平面被划分了不同的同心圆,每一个同心圆称为一个 ...

  2. 【转】草根老师的 linux字符设备驱动详解

    Linux 驱动 之 模块化编程 Linux 驱动之模块参数和符号导出 Linux 设备驱动之字符设备(一) Linux 设备驱动之字符设备(二) Linux 设备驱动之字符设备(三)

  3. Linux dts 设备树详解(一) 基础知识

    Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 1 前言 2 概念 2.1 什么是设备树 dts(device tree)? 2. ...

  4. linux块设备驱动之实例

    1.注册:向内核注册个块设备驱动,其实就是用主设备号告诉内核这个代表块设备驱动 sbull_major  =  register_blkdev(sbull_major, "sbull&quo ...

  5. Linux dts 设备树详解(二) 动手编写设备树dts

    Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 前言 硬件结构 设备树dts文件 前言 在简单了解概念之后,我们可以开始尝试写一个 ...

  6. linux块设备驱动---程序设计(转)

    块设备驱动注册与注销 块设备驱动中的第1个工作通常是注册它们自己到内核,完成这个任务的函数是 register_blkdev(),其原型为:int register_blkdev(unsigned i ...

  7. linux块设备驱动---相关结构体(转)

    上回最后面介绍了相关数据结构,下面再详细介绍 块设备对象结构 block_device 内核用结构block_device实例代表一个块设备对象,如:整个硬盘或特定分区.如果该结构代表一个分区,则其成 ...

  8. Linux 块设备驱动 (一)

    1.块设备的I/O操作特点 字符设备与块设备的区别: 块设备只能以块为单位接受输入和返回输出,而字符设备则以字符为单位. 块设备对于I/O请求有对应的缓冲区,因此它们可以选择以什么顺序进行响应,字符设 ...

  9. Linux块设备驱动(一) _驱动模型

    块设备是Linux三大设备之一,其驱动模型主要针对磁盘,Flash等存储类设备,本文以3.14为蓝本,探讨内核中的块设备驱动模型 框架 下图是Linux中的块设备模型示意图,应用层程序有两种方式访问一 ...

随机推荐

  1. ln命令详解

    ln命令 (全称:link) ln命令用来为文件创建一个连接,类似于Windows的快捷方式,连接类型分为硬连接和软连接(符号连接)两种,默认的连接类型是硬连接.如果要创建符号连接必须使用" ...

  2. nhibernate教程(4)--条件查询(Criteria Query)

    NHibernate之旅(4):探索查询之条件查询(Criteria Query) 2008-10-16 18:20 by 李永京, 44341 阅读, 43 评论, 收藏,  编辑 本节内容 NHi ...

  3. H5-html基础

    什么是 HTML? HTML 是用来描述网页的一种语言. HTML 指的是超文本标记语言 (Hyper Text Markup Language) HTML 不是一种编程语言,而是一种标记语言 (ma ...

  4. 201521123084 《Java程序设计》第6周学习总结

    1. 本周学习总结 1.1 面向对象学习暂告一段落,请使用思维导图,以封装.继承.多态为核心概念画一张思维导图,对面向对象思想进行一个总结. 注1:关键词与内容不求多,但概念之间的联系要清晰,内容覆盖 ...

  5. 【集美大学1411_助教博客】个人作业2——英语学习APP案例分析 成绩

    个人作业2--英语学习APP案例分析,截止发稿时间全班31人,提交31,未提交0人.有一名同学已经写了作业但忘记提交了,这次给分了,但下不为例.由于助教这周有点忙,所以点评得非常不及时,请同学们见谅. ...

  6. 201521123100 《Java程序设计》第4周学习总结

    1. 本章学习总结 1.1 尝试使用思维导图总结有关继承的知识点. 1.2 使用常规方法总结其他上课内容. (1)多态性:相同的形态,不同的行为 (2)类型转换与强制类型转换(cast) 2. 书面作 ...

  7. 201521123040《Java程序设计》第13周学习总结

    1. 本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 书面作业 1. 网络基础 1.1 比较ping www.baidu.com与ping cec.jmu ...

  8. 201521123115《Java程序设计》第14周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多数据库相关内容. 2. 书面作业 1. MySQL数据库基本操作 建立数据库,将自己的姓名.学号作为一条记录插入.(截图,需出现自 ...

  9. python 输出颜色的与样式的方法

    上次遇到这个问题就想写下来,其实当时我也不怎么会,老师说这个东西不需要理解,只需要死记硬背,写的多了就记住了,所以今天搜集了几篇文章,加上自己的理解,写下了这篇python 输出颜色的样式与方法的文章 ...

  10. Mybatis第八篇【一级缓存、二级缓存、与ehcache整合】

    Mybatis缓存 缓存的意义 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题. myba ...