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

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

drivers/block /xd.c

drivers/block /z2ram.c


1.本节需要的结构体如下:

1.1 gendisk磁盘结构体:

  1. struct gendisk {
  2. int major; //设备主设备号,等于register_blkdev()函数里的major
  3. int first_minor; //起始次设备号,等于0,则表示此设备号从0开始的
  4. int minors; //分区(次设备)数量,当使用alloc_disk()时,就会自动设置该成员
  5. char disk_name[]; //块设备名称, 等于register_blkdev()函数里的name
  6.  
  7. struct hd_struct **part; /*分区表的信息*/
  8. int part_uevent_suppress;
  9. struct block_device_operations *fops; //块设备操作函数
  10. struct request_queue *queue; //请求队列,用于管理该设备IO请求队列的指针*
  11. void *private_data; /*私有数据*/
  12. sector_t capacity; /*扇区数,512字节为1个扇区,描述设备容量*/
  13. ....
  14. };

1.2 request申请结构体:

  1. struct request {
  2. //用于挂在请求队列链表的节点,使用函数elv_next_request()访问它,而不能直接访问
  3. struct list_head queuelist;
  4. struct list_head donelist; /*用于挂在已完成请求链表的节点*/
  5. struct request_queue *q; /*指向请求队列*/
  6.  
  7. unsigned int cmd_flags; /*命令标识*/
  8.  
  9. enum rq_cmd_type_bits cmd_type; //读写命令标志,为 0(READ)表示读, 为1(WRITE)表示写
  10.  
  11. sector_t sector; //要提交的下一个扇区偏移位置(offset)
  12. ... ...
  13. unsigned int current_nr_sectors; //当前需要传送的扇区数(长度)
  14. ... ...
  15.  
  16. char *buffer; //当前请求队列链表的申请里面的数据,用来读写扇区数据(源地址)
  17. ... ...
  18. };

2.本节需要的函数如下:

  1. int register_blkdev(unsigned int major, const char *name);

创建一个块设备,当major==0时,表示动态创建,创建成功会返回一个主设备号

  1. unregister_blkdev(unsigned int major, const char *name);

卸载一个块设备, 在出口函数中使用,major:主设备号, name:名称

  1. struct gendisk *alloc_disk(int minors);

分配一个gendisk结构,minors为分区数,填1表示不分区

  1. void del_gendisk(struct gendisk *disk);

释放gendisk结构,在出口函数中使用,也就是不需要这个磁盘了

  1. request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock);

分配一个request_queue请求队列,分配成功返回一个request_queue结构体

rfn: request_fn_proc结构体,用来执行放置在队列中的请求的处理函数

  lock:队列访问权限的自旋锁(spinlock),该锁通过DEFINE_SPINLOCK()来定义

  1. void blk_cleanup_queue(request_queue_t * q);

清除内核中的request_queue请求队列,在出口函数中使用

  1. static DEFINE_SPINLOCK(spinlock_t lock);   

定义一个自旋锁(spinlock)

  1. static inline void set_capacity(struct gendisk *disk, sector_t size);

设置gendisk结构体扇区数(成员copacity), size等于扇区数

该函数内容如下:

disk->capacity = size;

  1. void add_disk(struct gendisk *gd);

向内核中注册gendisk结构体

  1. void put_disk(struct gendisk *disk);

注销内核中的gendisk结构体,在出口函数中使用

  1. struct request *elv_next_request(request_queue_t *q);

通过电梯算法获取申请队列中未完成的申请,获取成功返回一个request结构体,不成功返回NULL

(PS: 不使用获取到的这个申请时,应使用end_request()来结束获取申请)

  1. void end_request(struct request *req, int uptodate);

结束获取申请, 当uptodate==0,表示使用该申请读写扇区失败, uptodate==1,表示成功

  1. static inline void *kzalloc(size_t size, gfp_t flags);

分配一段静态缓存,这里用来当做我们的磁盘扇区用,分配成功返回缓存地址,分配失败会返回0

  1. void kfree(const void *block);

注销一段静态缓存,与kzalloc()成对,在出口函数中使用

  1. 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.代码如下:

  1. #include <linux/module.h>
  2. #include <linux/errno.h>
  3. #include <linux/interrupt.h>
  4. #include <linux/mm.h>
  5. #include <linux/fs.h>
  6. #include <linux/kernel.h>
  7. #include <linux/timer.h>
  8. #include <linux/genhd.h>
  9. #include <linux/hdreg.h>
  10. #include <linux/ioport.h>
  11. #include <linux/init.h>
  12. #include <linux/wait.h>
  13. #include <linux/blkdev.h>
  14. #include <linux/blkpg.h>
  15. #include <linux/delay.h>
  16. #include <linux/io.h>
  17.  
  18. #include <asm/system.h>
  19. #include <asm/uaccess.h>
  20. #include <asm/dma.h>
  21.  
  22. static DEFINE_SPINLOCK(memblock_lock);        //定义自旋锁
  23. static request_queue_t * memblock_request; //申请队列
  24. static struct gendisk *memblock_disk;   //磁盘结构体
  25. static int memblock_major;
  26.  
  27. #define BLOCKBUF_SIZE (1024*1024)      //磁盘大小
  28. #define SECTOR_SIZE (512) //扇区大小
  29. static unsigned char *block_buf; //磁盘地址
  30.  
  31. static int memblock_getgeo(struct block_device *bdev, struct hd_geometry *geo)
  32. {
  33. geo->heads =; // 2个磁头分区
  34. geo->cylinders = ; //一个磁头有32个柱面
  35. geo->sectors = BLOCKBUF_SIZE/(**SECTOR_SIZE); //一个柱面有多少个扇区
  36. return ;
  37. }
  38.  
  39. static struct block_device_operations memblock_fops = {
  40. .owner = THIS_MODULE,
  41. .getgeo = memblock_getgeo, //几何,保存磁盘的信息(柱头,柱面,扇区)
  42. };
  43.  
  44. /*申请队列处理函数*/
  45. static void do_memblock_request (request_queue_t * q)
  46. {
  47. struct request *req;
  48. unsigned long offset;
  49. unsigned long len;
  50. static unsigned long r_cnt = ;
  51. static unsigned long w_cnt = ;
  52.  
  53. while ((req = elv_next_request(q)) != NULL) //获取每个申请
  54. {
  55. offset=req->sector*SECTOR_SIZE; //偏移值
  56. len=req->current_nr_sectors*SECTOR_SIZE; //长度
  57.  
  58. if(rq_data_dir(req)==READ)
  59. {
  60. memcpy(req->buffer,block_buf+offset,len); //读出缓存
  61. }
  62. else
  63. {
  64. memcpy(block_buf+offset,req->buffer,len); //写入缓存
  65. }
  66. end_request(req, ); //结束获取的申请
  67. }
  68. }
  69.  
  70. /*入口函数*/
  71. static int memblock_init(void)
  72. {
  73. /*1)使用register_blkdev()创建一个块设备*/
  74. memblock_major=register_blkdev(, "memblock");
  75.  
  76. /*2) blk_init_queue()使用分配一个申请队列,并赋申请队列处理函数*/
  77. memblock_request=blk_init_queue(do_memblock_request,&memblock_lock);
  78.  
  79. /*3)使用alloc_disk()分配一个gendisk结构体*/
  80. memblock_disk=alloc_disk(); //不分区
  81.  
  82. /*4)设置gendisk结构体的成员*/
  83. /*->4.1)设置成员参数(major、first_minor、disk_name、fops)*/
  84. memblock_disk->major = memblock_major;
  85. memblock_disk->first_minor = ;
  86. sprintf(memblock_disk->disk_name, "memblock");
  87. memblock_disk->fops = &memblock_fops;
  88.  
  89. /*->4.2)设置queue成员,等于之前分配的申请队列*/
  90. memblock_disk->queue = memblock_request;
  91.  
  92. /*->4.3)通过set_capacity()设置capacity成员,等于扇区数*/
  93. set_capacity(memblock_disk,BLOCKBUF_SIZE/SECTOR_SIZE);
  94.  
  95. /*5)使用kzalloc()来获取缓存地址,用做扇区*/
  96. block_buf=kzalloc(BLOCKBUF_SIZE, GFP_KERNEL);
  97.  
  98. /*6)使用add_disk()注册gendisk结构体*/
  99. add_disk(memblock_disk);
  100. return ;
  101. }
  102. static void memblock_exit(void)
  103. {
  104. /*1)使用put_disk()和del_gendisk()来注销,释放gendisk结构体*/
  105. put_disk(memblock_disk);
  106. del_gendisk(memblock_disk);
  107. /*2)使用kfree()释放磁盘扇区缓存 */
  108. kfree(block_buf);
  109. /*3)使用blk_cleanup_queue()清除内存中的申请队列 */
  110. blk_cleanup_queue(memblock_request);
  111.  
  112. /*4)使用unregister_blkdev()卸载块设备 */
  113. unregister_blkdev(memblock_major,"memblock");
  114. }
  115.  
  116. module_init(memblock_init);
  117. module_exit(memblock_exit);
  118. MODULE_LICENSE("GPL");

5.测试运行

  1. insmod ramblock.ko //挂载memblock块设备
  2.  
  3. mkdosfs /dev/memblock //将memblock块设备格式化为dos磁盘类型
  4.  
  5. mount /dev/ memblock /tmp/ //挂载块设备到/tmp目录下

接下来在/tmp目录下vi 1.txt文件,最终都会保存在/dev/ memblock块设备里面

  1. cd /; umount /tmp/ //退出/tmp,卸载,同时之前读写的文件也会消失
  2.  
  3. cat /dev/memblock > /mnt/memblock.bin //在/mnt目录下创建.bin文件,然后将块设备里面的文件追加到.bin里面

然后进入linux的nfs挂载目录中

  1. 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):

  1. fdisk /dev/mmcblk1    

然后输入t 改变磁盘属性,再输入l 列出可以设置的属性表:

找到Win95 FAT32 (LBA)的标签是c

所以接下来输入:

  1. c       //选择Win95 FAT32 (LBA)
  2. 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. FPGA学习:VHDL设计灵活性&不同设计思路比较

    概要 由于VHDL编程实现数字电路具有很高的灵活性,为多种不同的思路编写实现同一种功能提供了可能.这些不同的设计思路,在耗费资源,可靠性,速度上也有很大的差异,往往需要我们根据实际需求和资源条件选择适 ...

  2. Window下SVN服务器搭建以及客户端使用

    一.下载 上一篇博客是关于Jenkins的内容,在Jenkins自动化编译时可能会自动获取版本更新进行build,那就需要用到版本更新的工具.这里使用VisualSVN Server来作为搭建svn的 ...

  3. 软件工程(GZSD2015)第三次作业提交进度

    第三次作业题目请查看这里:软件工程(GZSD2015)第三次作业 开始进入第三次作业提交进度记录中,童鞋们,虚位以待哈... 2015年4月19号 徐镇.尚清丽,C语言 2015年4月21号 毛涛.徐 ...

  4. 【Alpha阶段】第二次scrum meeting

    每日任务: ·1.本次会议为第二次Meeting会议: ·2.本次会议于今日上午08:30第五社区五号楼下召开,会议时长15min. 一.今日站立式会议照片: 二.每个人的工作: 三.工作中遇到的困难 ...

  5. 团队作业10--Beta阶段项目复审

    小组的名字和链接 优点 缺点 最终排名 油炸咸鱼 http://www.cnblogs.com/24app/ 基本功能实现,能够完成预期达到的大部分功能,并能够修复所有自己提出的bug,界面也还行,博 ...

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

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

  7. 201521123045-----《Java程序设计》第3周学习总结

    ---恢复内容开始--- 1. 本章学习总结 2. 书面作业 1. 代码阅读 public class Test1 { private int i = 1;//这行不能修改 private stati ...

  8. 201521123098 JAVA课程设计

    1.团队课程设计博客链接 http://www.cnblogs.com/agts/p/7067948.html 2.个人负责模块或任务说明 个人任务:实现初始界面中的登录.注册模块,以及数据库的连接和 ...

  9. evak购物车-课程设计(201521123034陈凯欣)

    1.团队课程设计博客链接 https://i.cnblogs.com/EditPosts.aspx?postid=7047127 2.个人负责模块或任务说明 1.Java 编写商品类Goods,商品属 ...

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

    1. 本章学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常与多线程相关内容. 1. 创建线程方式: 定义Thread的子类 定义实现Runnable接口的类,实现run() 2. 调用s ...