一. 什么是块设备、
1.1. 一种具有一定结构的随机存取设备,对这种设备的读写是按块进行的,他使用缓冲区来存放暂时的数据,待条件成熟后,从缓存一次性写入设备或者从设备一次性读到缓冲区。可以随机访问,块设备的访问位置必须能够在介质的不同区间前后移动
1.2. 块设备与字符设备差异
1.2.1. 块和字符是两种不同的访问设备的策略
1.2.2. 同一个设备可以同时支持块和字符两种访问策略
1.2.3. 块设备本身驱动层支持缓冲区,而字符设备驱动层没有缓冲
1.2.4. 块设备驱动最适合存储设备
1.3. 与块设备相关的几个单位
1.3.1. 扇区(Sectors):概念来自于早期磁盘,在硬盘、DVD中还有用,在Nand/SD中已经没意义了,但为了设备的统一,扇区是任何块设备硬件对数据处理的基本单位。通常,1个扇区的大小为512byte或512的整数倍。(对设备而言)
1.3.2. 块 (Blocks):由Linux制定对内核或文件系统等数据处理的基本单位。通常,1个块由1个或多个扇区组成。(对Linux操作系统而言)
1.3.3. 段(Segments):由若干个相邻的块组成。是Linux内存管理机制中一个内存页或者内存页的一部分。
1.3.4. 页(Page),概念来自于内核,是内核内存映射管理的基本单位。linux内核的页式内存映射名称来源于此。
二. 块设备的驱动框图
2.1. 框图从宏观分为三层
2.1.1. 虚拟文件系统层(VFS)
a. VFS是linux系统内核的软件层,由内核开发者已经编写完成。
b. VFS是文件系统和Linux 内核的接口,VFS以统一数据结构管理各种逻辑文件系统,接受用户层对文件系统的各种操作
c. 向上,对应用层提供一个标准的文件操作接口;
d. 对下,对文件系统提供一个标准的接口,以便其他操作系统的文件系统可以方便的移植到Linux上;
2.1.2. 内核空间层
2.1.2. 内核空间层又细分为三层
a. 通用块层(generic Block Layer)
负责维持一个I/O请求在上层文件系统与底层物理磁盘之间的关系。在通用块层中,通常用一个bio结构体来对应一个I/O请求
b. IO调度层
当多个请求提交给块设备时,执行效率依赖于请求的顺序。如果所有的请求是同一个方向(如:写数据),执行效率是最大的。内核在调用块设备驱动程序例程处理请求之前,先收集I/O请求并将请求排序,然后,将连续扇区操作的多个请求进行合并以提高执行效率(内核算法会自己做,不用你管),对I/O请求排序的算法称为电梯算法(elevator algorithm)。电梯算法在I/O调度层完成。内核提供了不同类型的电梯算法,电梯算法有
1 noop(实现简单的FIFO,基本的直接合并与排序),
2 anticipatory(延迟I/O请求,进行临界区的优化排序),
3 Deadline(针对anticipatory缺点进行改善,降低延迟时间),
4 Cfq(均匀分配I/O带宽,公平机制)
PS:其实IO调度层(包括请求合并排序算法)是不需要用户管的,内核已经做好。
映射层(Mapping Layer):起映射作用,将文件访问映射为设备的访问。
VFS:对各种文件系统进行统一封装,为用户程序访问文件提供统一的接口,包含ext2,FAT,NFS,设备文件。
磁盘缓存(Caches):将访问频率很高的文件放入其中。
相关数据结构
block_device: 描述一个分区或整个磁盘对内核的一个块设备实例
gendisk: 描述一个通用硬盘(generic hard disk)对象。
hd_struct: 描述分区应有的分区信息
bio: 描述块数据传送时怎样完成填充或读取块给driver
request: 描述向内核请求一个列表准备做队列处理。
request_queue: 描述内核申请request资源建立请求链表并填写BIO形成队列。
c. 块设备驱动
块设备驱动:在Linux中,驱动对块设备的输入或输出(I/O)操作,都会向块设备发出一个请求,在驱动中用request结构体描述。但对于一些磁盘设备而言请求的速度很慢,这时候内核就提供一种队列的机制把这些I/O请求添加到队列中(即:请求队列),在驱动中用request_queue结构体描述。在向块设备提交这些请求前内核会先执行请求的合并和排序预操作,以提高访问的效率,然后再由内核中的I/O调度程序子系统来负责提交 I/O 请求,调度程序将磁盘资源分配给系统中所有挂起的块 I/O 请求,其工作是管理块设备的请求队列,决定队列中的请求的排列顺序以及什么时候派发请求到设备。
三. 实例分析
3.1. 实例中用ram虚拟出一个块设备,并非真正的块设备
3.2. do_my_ramblock_request函数用于响应request
#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) { unsigned long start = blk_rq_pos(req) *512; unsigned long len = blk_rq_cur_bytes(req); if(rq_data_dir(req) == READ) { // 读请求 memcpy(req->buffer, my_ramblock_buf + start, 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 //分配设置请求队列,提供读写能力 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/name my_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");