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

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

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. ubuntu16.04下源码安装onos1.0.2

    由于工作需要,下载安装onos1.0.2的版本,大家看需求可以下载安装更高级的版本 参考链接:http://www.sdnlab.com/14650.html 1.系统环境 Ubuntu16.04 L ...

  2. javaScript数组去重方法

    在JAvascript平时项目开发中经常会用到数组去重的操作.这时候就要用到JS数组去重的方法了. demo1: 第一种:JS数组去重操作方法是利用遍历原数组,利用数组的indexOf()方法来来判断 ...

  3. Azure Powershell对ARM资源的基本操作

    本分主要介绍Windows Azure Powershell对ARM资源的基本操作 1.登陆ARM模式,命令:Login-AzureRmAccount -EnvironmentName AzureCh ...

  4. web服务的三大主流架构

    1.远程过程调用 2.服务导向架构 3.表述性状态转移 今天在自学Spring架构时貌似发现 rest的表现.与RPC方式的最大差别,SOA方式更加关注如何去连接服务而不是去特定某个实现的细节.而re ...

  5. 【★】致全球第一批全帧3D游戏!

    图一 游戏片头 致逝去的青春记忆. 好久没人玩Ballance了吧,贴吧里貌似早已冷掉了. 作为一款经典游戏,Ballance的宣传却做得不到位,官方的介绍甚至没能展现出它的全部诱人之处.所以笔者决 ...

  6. Bootstrap框架的了解和使用(一)

      前  言 Bootstrap 什么是 Bootstrap?Bootstrap 是一个用于快速开发 Web 应用程序和网站的前端框架.Bootstrap 是基于 HTML.CSS.JavaScrip ...

  7. 团队作业4——第一次项目冲刺(Alpha版本)7th day

    一.Daily Scrum Meeting照片 二.燃尽图 三.项目进展 在计时模式下能够记录用户的用户名和成绩,没有弄登录功能, 将程序定义为单机的 未完成的卡片为登录功能和使用QQ登录. 四.困难 ...

  8. 201521123093 java 第八周总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容. 1.2 选做:收集你认为有用的代码片段 1.泛型简介:同一个代码可以被不同的对象重用 2.使用泛型的好处:允许 ...

  9. 201521123007《Java程序设计》第2周学习总结

    1.本周学习总结 类名第一个字母大写,类名下的方法如main第一个字母要小写: Java有三种基本数据类型:整型(byte,short,int,long,char),浮点型(float,double) ...

  10. 201521123087《Java程序设计》第14周学习总结

    1. 本周学习总结 2. 书面作业 1. MySQL数据库基本操作 建立数据库,将自己的姓名.学号作为一条记录插入.(截图,需出现自己的学号.姓名)在自己建立的数据库上执行常见SQL语句(截图)-参考 ...