linux系统块设备驱动简介

块设备

数据传输以块为单位,而字符以字节为单位块设备对数据请求有缓冲区,因此可以调整响应请求的顺序牵涉到内核组件,主要与内核文件系统打交道。常见的块设备硬盘、光盘、SD卡、U盘。
块设备基本概念
扇区(Sectors):
块设备硬件对数据管理的基本单位。通常,1个扇区的大小为512byte。
块(Blocks):
由Linux制定对内核或文件系统等数据处理的基本单位。是内核与块设备驱动数据交互的单位。
扇区、块与驱动
块驱动是基于扇区(sector)来访问底层物理磁盘,基于块(block)与上层文件系统交互,块驱动由多个组件构成。

块设备驱动程序框架

通用块层
主要完成块设备的核心功能,完成与文件系统的数据交互。在Linux中,内核文件子系统通过统一接口与该层进行数据传输,在通用块层该数据用struct bio结构体表示。
通用块层提供了一个结构体struct request,该结构体包含了多个bio请求块,以链表的方式进行管理此外该层维护了一个结构体request_queue(请求队列),请求队列作为一个容器,用来缓存多个request数据请求,及request_queue管理request数据。
I/O调度层
主要对块设备请求队列中的请求进行调度,最大程度优化硬件操作的性能该层的I/0调度器,操作bio数据并对请求进行合并、调整顺序等操作,减少磁头移动的距离该层包含了多个I/O调度器,如No-op、Anticipatory、Deadline等,内核block目录中的noop-iosched.c、as-iosched.c等实现了上述算法I/O调度层的结果就是生成了优化后的reques数据,该数据被添加到request_queue之中。
块设备驱动层
该层完成具体的硬件操作,实现对request数据的处理块设备驱动层是我们学习的重点,我们写块驱动的任务量也主要集中在这一层。块设备驱动层可以用一个gendisk结构体表示,该结构体包涵了整个块设备的信息,如设备名、主从设备号、扇区大小扇区数等等,并提供了标准的操作接口
函数(类似字符中的file_operations),前面提到的request_queue结构体也定义在该结构体中 。gendisk结构体代表了一个具体的磁盘或者磁盘上的一个具体的分区,实现块设备驱动的本质就是构造并注册gendisk结构体。

块设备的数据结构

 

gendisk结构体
内核使用gendisk结构来表示一个独立的磁盘 设备与分区 该结构体记录块设备的信息,定义在
#include<linux/genhd.h>头文件里。
在gendisk结构中的许多成员必须由内核接口进行初始化。
结构体信息如下:
struct gendisk {
    int major;
    //主分区号
    int first_minor;
    //起始从设备号
    int minors;
    //支持的从设备号总数
    char disk_name[DISK_NAME_LEN];
    //dev下设备名称
    char *(*devnode)(struct gendisk *gd, mode_t*mode);
    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;
    struct kobject *slave_dir;
    struct timer_rand_state *random;
    atomic_t sync_io;
    struct work_struct async_notify;
    #ifdef CONFIG_BLK_DEV_INTEGRITY
    struct blk_integrity *integrity;
    #endif
    int node_id;
};
block_device_operations结构体
open() 完成对设备的打开操作,例如光驱、软驱等仓门的打开。
release() 完成对设备的关闭操作。
ioctl() 提供对设备的控制,高层块设备代码处理了绝多数控制,具体驱动中我们只需实现设备相关的控制。
getgeo() 获取块设备的物理参数,通过hd_geometry结构体返回磁盘的磁头、柱面、每磁道的扇区等物理参数。
结构体格式如下:

check_media_change()、revalidate()用于支持可移动存储设备(如floppy disk、CD-ROM)。 check_media_change用于检查自从上次检查以来设备是否发生变化revalidate在检查块设备被替换之后重新初始化驱动状态。

块设备驱动程序框架

块设备驱动注册步骤(模块初始化函数)
1.注册块设备(获取主设备号)
2.定义gendisk结构体(采用内核接口函数)
3.初始化gendisk结构体(主设备号、从设备号、请求队列、设备名、设备操作结构体、数据
处理函数等等)
4.注册gendisk结构体
块设备驱动卸载步骤(模块卸载函数)
1.注销gendisk结构体
2.删除gendisk结构体
3.释放占用的系统资源(如中断、kmalloc空间信号量、自旋锁等等)
4.注销块设备
驱动程序注册与清除函数
int register_blkdev(unsigned int major, const char *name);
int unregister_blkdev(unsigned int major, const char *name);
gendisk结构体分配和删除
struct gendisk *alloc_disk(int minors)
void put_disk(struct gendisk *disk)
gendisk结构体初始化
该部分主要任务是设置主从设备号、设备名、 扇区信息以及request_queue的配置(有关
request_queue的内容下一节进行讲解)
块设备注册与注销
注册:void add_disk(struct gendisk *disk)
注销:void del_gendisk(struct gendisk *gp)

块设备的数据传输

块设备驱动的I/O请求处理有两种方式,分别是:
使用请求队列和不使用请求队列,也就是采用 I/O调度和不采用I/O调度层。
使用请求队列对于传统的磁盘设备有助于提高系统的性能,主要任务由内核提供的__make_request()函数完成。
但对于一些完全可随机访问的块设备(如:Ram盘等)使用请求队列并不能获得多大的益处,反而浪费了时 间和资源。这时候,块设备驱动层需要提供一个制造 请求函数,来代替内核默认的__make_request
使用请求队列:

请求队列初始化函数:
struct request_queue* blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
返回值:已初始化好的请求队列
rfn: 绑定请求处理函数的指针
lock:自旋锁变量,给块组件使用
编写自己的请求处理函数
 
void vrd_request(struct request_queue *q)
参数q:由内核传递
在该函数中我们需要亲自完成request_queue中的每一个request数据请求
请求处理函数处理流程

不使用请求队列:

手动创建请求队列相关函数
struct request_queue *blk_alloc_queue(gfp_t gfp_mask)
返回值:申请成功的请求队列
void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)
参数q:已申请的请求队列
参数mfn:自己实现的make_request函数
 编写自己的“make request”处理函数
int vrd_make_request(struct request_queue *q, struct bio *bio)
在该函数中用户需要自行处理bio数据,与使用请
求队列不同的是此时request_queue中没有具体的
request数据请求,request_queue只是个空壳
“make request”数据读写流程

bio相关数据结构
bio为内核传过来的数据请求块,bio中包含多个 bio_vec,bio_vec用来描述此次请求块对应的内存中数据的存储位置
bio结构体几个重要域
bio_->bi_sector: 请求的起始扇区号
bio->bi_size: 请求数据大小
bio->bi_bdev->bd_disk->private_data:自定义数据
bio_vec结构体几个重要域
bvec->bv_page: 获得文件系统读/写数据起始page地址
bvec->bv_offset:获得文件系统读/写数据的offset地址(page+offser就是数据操作起始地址)
bvec->bv_len: 需要处理数据的大小
 
bio相关操作接口
宏bio_for_each_segment(bvec, bio, i) 该宏可以提取bio中的每个具体bvec成员
此宏本身为一个for循环,bio为数据请求块,bvec用来存获取到的每个元素,i为循环参数。
bio_data_dir(bio)该函数使用bio变量获取处理方向方向值(READ、WRITE),表示读和写。
bio_endio(bio,0)正常结束了处理后,向内核通知该消息。
bio_io_error(bio)如果处理失败,向内核通知该消息。

使用块设备

通常,我们需要使用块设备上的文件系统对块设备进行操作
1.编译、插入块设备驱动,/dev下生成相应设备文件。
2.格式化块设备#mkfs.vfat /dev/ramdiskX
3.挂载块设备到指定目录下mount [–t vfat] 驱动程序节点 挂载路径以自己实现的ramdisk驱动为例:mount –t vfat /dev/ramdisk_driver /mnt

实战

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值