目录

  • 一、正确理解块设备驱动的概念
    • 1、块设备和字符设备的差异
    • 2、块设备驱动的特点
    • 3、块设备相关的几个单位
  • 二、块设备驱动框架简介
    • 1、块设备驱动框图
    • 2、重点结构体
    • 三、块设备驱动案例分析
    • 1、块设备驱动案例演示
    • 2、块设备驱动简单分析
    • 3、源码分析

一、正确理解块设备驱动的概念

1、块设备和字符设备的差异

(1)块和字符是两种不同的访问设备的策略,而非指具体的设备
(2)同一个设备可以同时支持块和字符两种访问策略
(3)设备本身的物理特性决定了哪一种访问策略更适合
(4)块设备本身驱动层支持缓冲区,而字符设备驱动层没有缓冲
(5)块设备驱动最适合存储设备

summary:
字符设备:驱动的处理是实时的,应用层传一个处理一个
块设备:因为有缓冲区,可能传多个才处理一次

字符设备:传a,处理a;传b,处理b;传c,处理c;
块设备:传a,传b,传c,处理一次,可以提高效率,不必频繁去写存储设备

2、块设备驱动的特点

(1)字符设备只能顺序访问(如串口发送数据顺序发送abc,接收到的为abc,不可能是cba等其他顺序),而块设备可以随机访问(不连续块访问)

(2)传统的机械式块设备(如硬盘、DVD)虽然可以随机访问,但是连续访问效率更高,因此块设备驱动中有排序逻辑将用户的随机访问重新调整成尽量连续访问以提升效率
  所以对于传统的机械硬盘进行碎片整理,有利于文件在硬盘中顺序存放,从而提高访问效率。
(3)Nand、SD卡等随机访问效率等同于顺序访问

3、块设备相关的几个单位

(1)扇区(Sector),概念来自于早期磁盘,在硬盘、DVD中还有用,在Nand/SD中已经没意义了,扇区是块设备本身的特性,大小一般为512的整数倍,因为历史原因很多时候都向前兼容定义为512.

(2)块(block),概念来自于文件系统,是内核对文件系统数据处理的基本单位,大小为若干个(1/2/4/8等)扇区,常见有512B、1KB、4KB等

(3)段(Section),概念来自于内核,是内核的内存管理中一个页或者部分页,由若干个连续为块组成。

(4)页(Page),概念来自于内核,是内核内存映射管理的基本单位。linux内核的页式内存映射名称来源于此。

  总结:块设备驱动对下以扇区(Sector)为单位管理块设备,对上以块(Block)为单位和文件系统交互。

  注意:块设备驱动和字符设备驱动不同,应用层对块设备驱动的访问一般不是直接操作设备文件(/dev/block/xxx,使用文件系统操作块设备,或者/dev/sdax表示某个硬盘的某个分区),而是通过文件系统来简化操作。

二、块设备驱动框架简介


通用块层:纯软件层,将不同硬件设备的差异封装起来。

bio结构体:封装了对硬件的操作。

io调度层:由于Linux IO调度程序是介于通用块层和块设备驱动程序之间,所以它接收来自通用块层的请求,试图合并请求,并找到最合适的请求下发到块设备驱动程序中。之后块设备驱动程序会调用一个策略函数来相应这个请求。这个策略函数是由设备类型来决定的,例如,如果设备是SCSI硬盘,那么scsi总线层便将scsi_request_fn注册到该策略函数中。io调度层这一块的关键是算法。

块设备驱动层是我们具体写驱动的人完成的

1、块设备驱动框图

(1)VFS(虚拟文件系统)

(2)通用块层
  对块设备进行操作的层次,包括读、写等

(3)IO调度层(电梯算法)
  将通用块层的一些操作进行排序和合并

(4)块设备驱动层(真正硬件操作部分)

2、重点结构体

(1)struct request 对设备的每一次操作(譬如读或者写一个扇区)

/** try to put the fields that are referenced together in the same cacheline.* if you modify this structure, be sure to check block/blk-core.c:rq_init()* as well!*/
struct request {struct list_head queuelist;struct call_single_data csd;struct request_queue *q;unsigned int cmd_flags;enum rq_cmd_type_bits cmd_type;unsigned long atomic_flags;int cpu;/* the following two fields are internal, NEVER access directly */unsigned int __data_len;  /* total data len */sector_t __sector;      /* sector cursor */struct bio *bio;struct bio *biotail;struct hlist_node hash;  /* merge hash *//** The rb_node is only used inside the io scheduler, requests* are pruned when moved to the dispatch queue. So let the* completion_data share space with the rb_node.*/union {struct rb_node rb_node;  /* sort/lookup */void *completion_data;};/** Three pointers are available for the IO schedulers, if they need* more they have to dynamically allocate it.*/void *elevator_private;void *elevator_private2;void *elevator_private3;struct gendisk *rq_disk;unsigned long start_time;
#ifdef CONFIG_BLK_CGROUPunsigned long long start_time_ns;unsigned long long io_start_time_ns;    /* when passed to hardware */
#endif/* Number of scatter-gather DMA addr+len pairs after* physical address coalescing is performed.*/unsigned short nr_phys_segments;unsigned short ioprio;int ref_count;void *special;      /* opaque pointer available for LLD use */char *buffer;     /* kaddr of the current segment if available */int tag;int errors;/** when request is used as a packet command carrier*/unsigned char __cmd[BLK_MAX_CDB];unsigned char *cmd;unsigned short cmd_len;unsigned int extra_len;  /* length of alignment and padding */unsigned int sense_len;unsigned int resid_len; /* residual count */void *sense;unsigned long deadline;struct list_head timeout_list;unsigned int timeout;int retries;/** completion callback.*/rq_end_io_fn *end_io;void *end_io_data;/* for bidi */struct request *next_rq;
};

(2)struct request_queue request形成的队列
将所有的请求列成一个队列

struct request_queue
{/** Together with queue_head for cacheline sharing*/struct list_head   queue_head;struct request       *last_merge;struct elevator_queue   *elevator;/** the queue request freelist, one for reads and one for writes*/struct request_list rq;request_fn_proc      *request_fn;make_request_fn     *make_request_fn;prep_rq_fn     *prep_rq_fn;unplug_fn       *unplug_fn;merge_bvec_fn        *merge_bvec_fn;prepare_flush_fn *prepare_flush_fn;softirq_done_fn       *softirq_done_fn;rq_timed_out_fn        *rq_timed_out_fn;dma_drain_needed_fn    *dma_drain_needed;lld_busy_fn       *lld_busy_fn;/** Dispatch queue sorting*/sector_t       end_sector;struct request       *boundary_rq;/** Auto-unplugging state*/struct timer_list   unplug_timer;int            unplug_thresh;  /* After this many requests */unsigned long     unplug_delay;   /* After this many jiffies */struct work_struct unplug_work;struct backing_dev_info backing_dev_info;/** The queue owner gets to use this for whatever they like.* ll_rw_blk doesn't touch it.*/void           *queuedata;/** queue needs bounce pages for pages above this limit*/gfp_t           bounce_gfp;/** various queue flags, see QUEUE_* below*/unsigned long        queue_flags;/** protects queue structures from reentrancy. ->__queue_lock should* _never_ be used directly, it is queue private. always use* ->queue_lock.*/spinlock_t        __queue_lock;spinlock_t     *queue_lock;/** queue kobject*/struct kobject kobj;/** queue settings*/unsigned long        nr_requests;    /* Max # of requests */unsigned int     nr_congestion_on;unsigned int       nr_congestion_off;unsigned int      nr_batching;void            *dma_drain_buffer;unsigned int      dma_drain_size;unsigned int     dma_pad_mask;unsigned int       dma_alignment;struct blk_queue_tag  *queue_tags;struct list_head    tag_busy_list;unsigned int      nr_sorted;unsigned int      in_flight[2];unsigned int       rq_timeout;struct timer_list    timeout;struct list_head    timeout_list;struct queue_limits    limits;/** sg stuff*/unsigned int       sg_timeout;unsigned int     sg_reserved_size;int            node;
#ifdef CONFIG_BLK_DEV_IO_TRACEstruct blk_trace  *blk_trace;
#endif/** reserved for flush operations*/unsigned int       ordered, next_ordered, ordseq;int           orderr, ordcolor;struct request     pre_flush_rq, bar_rq, post_flush_rq;struct request      *orig_bar_rq;struct mutex       sysfs_lock;#if defined(CONFIG_BLK_DEV_BSG)struct bsg_class_device bsg_dev;
#endif
};

(3)struct bio 通用块层用bio来管理一个请求

struct bio {sector_t     bi_sector;  /* device address in 512 bytesectors */struct bio       *bi_next;   /* request queue link */struct block_device *bi_bdev;unsigned long      bi_flags;   /* status, command, etc */unsigned long     bi_rw;      /* bottom bits READ/WRITE,* top bits priority*/unsigned short       bi_vcnt;    /* how many bio_vec's */unsigned short     bi_idx;     /* current index into bvl_vec *//* Number of segments in this BIO after* physical address coalescing is performed.*/unsigned int        bi_phys_segments;unsigned int       bi_size;    /* residual I/O count *//** To keep track of the max segment size, we account for the* sizes of the first and last mergeable segments in this bio.*/unsigned int        bi_seg_front_size;unsigned int      bi_seg_back_size;unsigned int       bi_max_vecs;    /* max bvl_vecs we can hold */unsigned int      bi_comp_cpu;    /* completion CPU */atomic_t        bi_cnt;     /* pin count */struct bio_vec       *bi_io_vec; /* the actual vec list */bio_end_io_t       *bi_end_io;void         *bi_private;
#if defined(CONFIG_BLK_DEV_INTEGRITY)struct bio_integrity_payload *bi_integrity;  /* data integrity */
#endifbio_destructor_t  *bi_destructor; /* destructor *//** We can inline a number of vecs at the end of the bio, to avoid* double allocations for a small number of bio_vecs. This member* MUST obviously be kept at the very end of the bio.*/struct bio_vec      bi_inline_vecs[0];
};

(4)struct gendisk 表示一个磁盘设备或一个分区

struct gendisk {/* major, first_minor and minors are input parameters only,* don't use directly.  Use disk_devt() and disk_max_parts().*/int major;         /* major number of driver */int first_minor;int minors;                     /* maximum number of minors, =1 for* disks that can't be partitioned. */char disk_name[DISK_NAME_LEN];    /* name of major driver */char *(*devnode)(struct gendisk *gd, mode_t *mode);/* Array of pointers to partitions indexed by partno.* Protected with matching bdev lock but stat and other* non-critical accesses use RCU.  Always access through* helpers.*/struct disk_part_tbl *part_tbl;struct hd_struct part0;const struct block_device_operations *fops;struct request_queue *queue;void *private_data;int flags;struct device *driverfs_dev;  // FIXME: removestruct kobject *slave_dir;struct timer_rand_state *random;atomic_t sync_io;      /* RAID */struct work_struct async_notify;
#ifdef  CONFIG_BLK_DEV_INTEGRITYstruct blk_integrity *integrity;
#endifint node_id;
};

三、块设备驱动案例分析

文件链接:链接:https://pan.baidu.com/s/10AIbjmju0WWed8nxcwC-5g
提取码:no9a
--来自百度网盘超级会员V5的分享

1、块设备驱动案例演示

(1)驱动简单介绍
  使用该驱动操作我们的内存,ramdsik 内存盘,用内存虚拟出来的一个磁盘,可读可写。

(2)编译
交叉编译,使用之前编译驱动的Makefile进行编译,但要进行简单的修改

(3)模块安装
  自己将编译生成的模块复制到开发板进行安装。insmod 模块

(4)查看信息

cat /proc/devices
cat /proc/partitions  #查看分区
ls /dev/
lsmod

(5)挂载测试

2、块设备驱动简单分析

(1)如何证明块设备驱动真的工作了: 格式化、挂载

格式化成ext2格式的:mkfs.ext2 /dev/my_ramblock
挂载(选择一个空白目录挂载):mount -t ext2 /dev/my_ramblcok /tmp
卸载:umount /tmp

  这块内存是在insmod申请分配的,所以只要没rmmod,mount、umount得到的都是同一块内存,其中所建立的文件也不会改变

(2)注意各种打印信息

(3)体会块设备驱动的整体工作框架

3、源码分析

#include <linux/module.h>
#include <linux/slab.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>#define RAMBLOCK_SIZE (1024*1024)             // 1MB,2048扇区static struct gendisk *my_ramblock_disk;        // 磁盘设备的结构体
static struct request_queue *my_ramblock_queue; // 等待队列
static DEFINE_SPINLOCK(my_ramblock_lock);
static int major;
static unsigned char *my_ramblock_buf;          // 虚拟块设备的内存指针static void do_my_ramblock_request(struct request_queue *q)
{struct request *req;static int r_cnt = 0;             //实验用,打印出驱动读与写的调度方法static int w_cnt = 0;req = blk_fetch_request(q);//取出一个请求while (NULL != req)//是否取到请求{// blk_rq_pos(req)得到扇区号,*512转换成字节数unsigned long start = blk_rq_pos(req) *512;unsigned long len = blk_rq_cur_bytes(req);if(rq_data_dir(req) == READ){// 读请求,因为是内存虚拟出来的,故可使用memcpymemcpy(req->buffer, my_ramblock_buf + start, len);   //读len字节printk("do_my_ramblock-request read %d times\n", r_cnt++);}else{// 写请求memcpy( my_ramblock_buf+start, req->buffer, len);     //写操作printk("do_my_ramblock request write %d times\n", w_cnt++);}if(!__blk_end_request_cur(req, 0)) {req = blk_fetch_request(q);}}
}static int blk_ioctl(struct block_device *dev, fmode_t no, unsigned cmd, unsigned long arg)
{return -ENOTTY;
}static int blk_open (struct block_device *dev , fmode_t no)
{printk("11111blk mount succeed\n");return 0;
}
static int blk_release(struct gendisk *gd , fmode_t no)
{printk("11111blk umount succeed\n");return 0;
}static const struct block_device_operations my_ramblock_fops =
{.owner         = THIS_MODULE,.open        = blk_open,.release    = blk_release,.ioctl       = blk_ioctl,
};static int my_ramblock_init(void)
{major = register_blkdev(0, "my_ramblock");if (major < 0){printk("fail to regiser my_ramblock\n");return -EBUSY;}// 实例化my_ramblock_disk = alloc_disk(1);//次设备个数 ,分区个数 +1,这个是次设备个数为1,0分区//分配设置请求队列,提供读写能力my_ramblock_queue = blk_init_queue(do_my_ramblock_request, &my_ramblock_lock);//设置硬盘属性 my_ramblock_disk->major = major;my_ramblock_disk->first_minor = 0;my_ramblock_disk->fops = &my_ramblock_fops;sprintf(my_ramblock_disk->disk_name, "my_ramblcok");//写入/dev/namemy_ramblock_disk->queue = my_ramblock_queue;set_capacity(my_ramblock_disk, RAMBLOCK_SIZE / 512);//设置容量/* 硬件相关操作 */my_ramblock_buf = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL);add_disk(my_ramblock_disk);                // 向驱动框架 注册一个disk或者一个partation的接口return 0;
}static void my_ramblock_exit(void)
{unregister_blkdev(major, "my_ramblock");del_gendisk(my_ramblock_disk);put_disk(my_ramblock_disk);blk_cleanup_queue(my_ramblock_queue);kfree(my_ramblock_buf);
}module_init(my_ramblock_init);
module_exit(my_ramblock_exit);MODULE_LICENSE("GPL");

(1)register_blkdev(kernel/block/genhd.c,属于通用块层),内核提供的注册块设备驱动的注册接口,在块设备驱动框架中的地位,等同于register_chrdev在字符设备驱动框架中的地位。

(2)blk_init_queue 用来实例化产生一个等待队列,将来应用层对本块设备所做的所有的读写操作,都会生成一个request然后被加到这个等待队列中来。

(3)blk_init_queue函数接收2个参数,第一个是等待队列的回调函数(do_my_ramblock_request),这个函数是驱动提供的用来处理等待队列中的request的函数(IO调度层通过电梯算法从等待队列中取出一个request,就会调用这个回调函数来处理这个请求),第二个参数是一个自旋锁,这个自旋锁是要求我们驱动提供给等待队列去使用的。

(4)blk_fetch_request函数是IO调度层提供的接口,作用是从request_queue中(按照电梯算法)取出一个(算法认为当前最应该去被执行的一个请求,是被算法排序、合并后的)请求,取出的请求其实就是当前硬件(块设备)最应该去执行的那个读写操作。一个请求对应一次读写操作。

注:本资料大部分由朱老师物联网大讲堂课程笔记整理而来并且引用了部分他人博客的内容,如有侵权,联系删除!水平有限,如有错误,欢迎各位在评论区交流。

块设备驱动介绍(浅析)相关推荐

  1. linux块设备驱动(一)——块设备概念介绍

    linux块设备驱动(一)--块设备概念介绍 本文来源于: 1. http://blog.csdn.net/jianchi88/article/details/7212370 2. http://bl ...

  2. [转]linux 块设备驱动

    转自 linux块设备IO栈 http://www.sysnote.org/2015/08/06/linux-io-stack/ linux块设备IO流程 驱动 https://www.cnblogs ...

  3. Linux块设备驱动总结

    <Linux设备驱动程序>第十六章 块设备驱动程序读书笔记 简介 一个块设备驱动程序主要通过传输固定大小的随机数据来访问设备 Linux内核视块设备为与字符设备相异的基本设备类型 Linu ...

  4. 转载:谢谢原作者:块设备驱动实战基础篇四 (逐渐成型,加入ioctl通信机制)

    1.6介绍一种内核与用户空间通信的方法-misc设备ioctl机制 块设备驱动开发中往往需要配合用户态的管理程序工具,管理我们的块设备,此时我们需要涉及用户空间程序与块设备通信的方法,ioctl机制就 ...

  5. 转载:谢谢原作者: 块设备驱动实战基础篇二 (继续完善170行过滤驱动代码至200行)

    1.3块设备驱动关键数据结构及函数API详细剖析 经过上节四个步骤我们已经熟悉并实战了一个最基本的过滤块设备驱动的设计技巧,我们这一节先不继续实战,我们本节把上节170行代码中接触到的块设备核心数据结 ...

  6. 转载:谢谢原作者:块设备驱动实战基础篇一 (170行代码构建一个逻辑块设备驱动)

    1   内核块设备驱动基础学习与实战 1.1 设备驱动IO架构初探 操作系统是如何将数据读到缓冲区的,发生了什么?我们带着这样的问题,粗略走一下read调用系统过程,希望这个初探,可以唤起大家研究操作 ...

  7. [转]写一个块设备驱动(第八章)

    第8章 +---------------------------------------------------+ |                 写一个块设备驱动                 ...

  8. [转]写一个块设备驱动(第一章)

    写一个块设备驱动1,2(转) 2009/09/03 15:42 第1章 +---------------------------------------------------+ |          ...

  9. 【转】 bio 与块设备驱动

    原文地址: bio 与块设备驱动    系统中能够随机访问固定大小数据片(chunk)的设备被称作块设备,这些数据片就称作块.块设备文件都是以安装文件系统的方式使用,此也是块设备通常的访问方式.块设备 ...

最新文章

  1. 德国图宾根大学发布可扩展「对抗黑盒攻击」,仅通过观察决策即可愚弄深度神经网络
  2. delete from t引发的血案
  3. sql性能分析(explain关键字)
  4. GRDB使用SQLite的WAL模式
  5. django写原生sql语句
  6. mysql xtrabackup 主从_使用 Xtrabackup 在线对MySQL做主从复制
  7. java if else过多_Spring Boot中如何干掉过多的if else!
  8. python报表自动化系列 - 获取某个时间段内所有日期
  9. [新整理] CAD高级模拟考题
  10. 图片导入ppt后模糊_PPT另存为图片不清晰|为什么PPT导出图片不清晰
  11. 2013 中国15大云平台
  12. chrome主页被毒霸网址大全劫持解决办法
  13. EDA 电子设计自动化VHDL系列课程1--加【减】法器的设计
  14. php whois查询,php whois查询API制作方法
  15. 更新:2022 京东双11活动一键自动完成任务脚本app来了
  16. ppt编辑图片进阶功能
  17. python条件语句作用_Python 条件语句
  18. 荣耀6plus android版本号,荣耀6PLUS升级安卓6.0(emui4.0)感受
  19. unity图像压缩算法原理
  20. 微信备份聊天记录,显示连接错误,终极解决办法

热门文章

  1. 业务变革与架构双驱动的多项目管理︱海康威视流程变革顾问/专家张燕飞
  2. 电磁波,和光成像原理
  3. 简单说一下servlet的生命周期?
  4. 某宝APP接口抓包与X-sign教程
  5. LaTeX 书写 argmax and argmin 公式
  6. tar 排除指定的目录 ,或者指定文件类型
  7. CC Debugger调试下载接口蓝牙模块接线图
  8. 从行为心理角度论证罗马数字是抄袭中国汉字、算筹的可能性
  9. 艾尔之光中刀光的ogre实现
  10. mysql数据入库时间的统计_MySQL按时间统计数据的方法总结