块设备驱动程序概念介绍
来源:互联网 发布:c语言判断字母大小写 编辑:程序博客网 时间:2024/05/21 09:38
系统中能够随机访问特定大小数据片的设备被称作块设备,这些数据片就称作块。最常见的块设备是硬盘。注意,它们都是以安装文件系统的方式使用的——这也是块设备通常的访问方式。
另一种基本的设备类型是字符设备。字符设备按照字符流的方式被有序访问,像串口和键盘就属于字符设备。
这两种设备的根本区别在于它们是否可以被随机访问。内核管理块设备比字符设备复杂的多,有一个专门的子系统来管理块设备和对块设备的请求,这一部分在内核中被称作块I/O层。
我们在应用层对文件的读写访问,比如我们读写一个 1.txt,这个文件实际是存储在块设备上的,那么是谁完成了文件读写到具体块设备扇区的读写转换呢?是文件系统!我们内核中支持 vfat,ext2,ext3,yaffs2,jffs2 等非常多种文件系统,然而为了统一多种文件系统,内核抽象出了VFS虚拟文件系统,实际的文件系统向上为VFS提供统一的接口。比如一个 write 操作大体的操作流程如下图所示:
整个块设备的框架:
一、基本概念
块设备中最小的可寻址单元是扇区,扇区大小一般是2的整数倍,而最常见的大小是512字节。内核执行的所有磁盘操作都是按照块进行的,由于扇区是块设备的最小可寻址单元,所以块要比扇区2的整数倍,所以通常是 512 字节 、1k 、2k 。
和磁盘相关的其它术语还有——簇、柱面、磁头等,这些术语都和具体的块设备相关,一般情况下用户空间的软件都用不到这些概念。
二、缓冲区和缓冲区头
当一个块被调入内存时(也就是说,在读入后或等待写出时),它要存储在一个缓冲区中,每个缓冲区与一个块对应,它相当于是磁盘块在内存中的表示。由于内核在处理数据时需要一些相关的控制信息,比如属于哪一个块设备、块对应于哪个缓冲区等,所以内一个缓冲区都有一个对应的描述符,该描述符用 buffer_head 结构体表示,被称作缓冲区头。
struct buffer_head {unsigned long b_state;/* 缓冲区状态标志 */struct buffer_head *b_this_page;/* 页面中的缓冲区 */struct page *b_page;/* 存储缓冲区的页面 */sector_t b_blocknr;/* 逻辑块号 */size_t b_size;/* 块大小 */char *b_data;/* 页面中的缓冲区 */struct block_device *b_bdev; <span style="font-family: Arial, Helvetica, sans-serif;">/* 所属的块设备 */</span>bh_end_io_t *b_end_io;/* I/O 完成方法 */ void *b_private;/* reserved for b_end_io */struct list_head b_assoc_buffers; /* associated with another mapping */struct address_space *b_assoc_map;/* mapping this buffer is associated with */atomic_t b_count;/* 引用计数 */};与缓冲区对应的磁盘物理块由 b_blockbr 索引,该值是 b_bdev 所指明的块设备中的逻辑块号。
与缓冲区对应的内存物理页由 b_page 表示,另外,b_data 直接指向相应的块(它位于 b_page),块大小由 b_size 表示。
缓冲区头的目的在于描述磁盘和物理内存缓冲区之间的映射关系。
缓冲区头仅仅能描述一个缓冲区,当作为所有I/0操作的容器使用时,就会变成对多个 buffer_head 结构体进行操作,势必会造成不必要的负担和空间浪费,因此引入了一个灵活且轻量级的容器——bio 结构体。
三、bio 结构体
每一个块 I/O 请求都通过一个 bio 结构体表示。每一个请求包含一个或者多个块,这些块存储在 bio_vec 结构体数组中,这些结构体描述了每一个片段在物理页面中的实际位置,并像向量一样被组织在一起。I/O 操作的第一个片段由 b_io_vec 结构体所指向,其他的片段在其后依次放置,共有 bi_vcnt 个片段。当块 I/O 层开始执行请求,需要使用各个片段时,bi_idx 会不断更新,从而总指向当前的片段。
struct bio {//该bio结构所要传输的第一个(512字节)扇区:磁盘的位置 sector_t bi_sector; struct bio *bi_next; //请求链表 struct block_device *bi_bdev; //相关的块设备 unsigned long bi_flags; //状态和命令标志 unsigned long bi_rw; //读写 unsigned short bi_vcnt; //bio_vesc偏移的个数 unsigned short bi_idx; //bi_io_vec的当前索引 unsigned short bi_phys_segments; //结合后的片段数目 unsigned short bi_hw_segments; //重映射后的片段数目 unsigned int bi_size; <span style="white-space:pre"></span> //I/O计数 unsigned int bi_hw_front_size; //第一个可合并的段大小; unsigned int bi_hw_back_size; //最后一个可合并的段大小 unsigned int bi_max_vecs; //bio_vecs数目上限 struct bio_vec *bi_io_vec; //bio_vec链表:内存的位置 bio_end_io_t *bi_end_io;//I/O完成方法 atomic_t bi_cnt; //使用计数 void *bi_private; //拥有者的私有方法 bio_destructor_t *bi_destructor;//销毁方法};
struct bio_vec {struct page*bv_page; // 这个缓冲区所在的物理页面unsigned intbv_len; // 这个缓冲区的大小unsigned intbv_offset; // 缓冲区在页面的偏移};
struct gendisk { int major; //主设备号 int first_minor; //第一个从设备号 int minors;/* 描述被磁盘使用的设备号的成员.一个驱动器必须使用最少一个次编号. *如果你的驱动会是可分区的,但是(并且大部分应当是),你要分配一个次编号给每个可能 的分区.次编号的一个普通的值是 16, *它允许"全磁盘"设备盒 15 个分区. 一些磁盘驱动使用 64 个次编号给每个设备. */ char disk_name[32]; //应当被设置为磁盘驱动器名子的成员. 它出现在 /proc/partitions 和 sysfs. struct hd_struct **part; /* [indexed by minor] */ struct block_device_operations *fops;// 设备操作集合. struct request_queue *queue;//被内核用来管理这个设备的 I/O 请求的结构; void *private_data;//块驱动可使用这个成员作为一个指向它们自己内部数据的指针. sector_t capacity;// 这个驱动器的容量,以512-字节扇区来计.sector_t类型可以是64位宽.驱动不应当直接设置这个成员;相反,传递扇区数目给set_capacity. int flags;// 一套标志(很少使用),描述驱动器的状态.如果你的设备有可移出的介质,// 你应当设置GENHD_FL_REMOVABLE.CD-ROM驱动器可设置 GENHD_FL_CD. // 如果, 由于某些原因, 你不需要分区信息出现在 /proc/partitions, 设置 GENHD_FL_SUPPRESS_PARTITIONS_INFO. struct device *driverfs_dev; // FIXME: remove struct device dev; struct kobject *holder_dir; struct kobject *slave_dir; struct timer_rand_state *random; int policy; atomic_t sync_io; /* RAID */ unsigned long stamp; int in_flight; #ifdef CONFIG_SMP struct disk_stats *dkstats; #else struct disk_stats dkstats; #endif struct work_struct async_notify;};
七、内核 ll_rw_block 函数调用
分析ll_rw_block for (i = 0; i < nr; i++) { struct buffer_head *bh = bhs[i]; submit_bh(rw, bh); struct bio *bio; // 使用bh来构造bio (block input/output) submit_bio(rw, bio); // 通用的构造请求: 使用bio来构造请求(request) generic_make_request(bio); __generic_make_request(bio); request_queue_t *q = bdev_get_queue(bio->bi_bdev); // 找到队列 // 调用队列的"构造请求函数" ret = q->make_request_fn(q, bio); // 默认的函数是__make_request __make_request // 先尝试合并 elv_merge(q, &req, bio); // 如果合并不成,使用bio构造请求 init_request_from_bio(req, bio); // 把请求放入队列 add_request(q, req); // 执行队列 __generic_unplug_device(q); // 调用队列的"处理函数" q->request_fn(q);
- 块设备驱动程序概念介绍
- 1.字符设备驱动程序概念介绍
- 嵌入式linux之块设备驱动程序概念,框架
- 块设备驱动程序实现
- 13. 块设备驱动程序
- 块设备驱动程序
- 装载块设备驱动程序
- 块设备驱动程序框架
- 块设备驱动程序
- 块设备驱动程序
- .块设备驱动程序框架
- 块设备驱动程序
- 块设备驱动程序
- 块设备驱动程序之一
- 块设备驱动程序设计
- 块设备驱动程序<一>
- 块设备驱动程序<二>
- 块设备驱动程序
- yum rpm 常用命令
- C/C++中产生随机数(rand和srand的用法)
- 《岛上书店》书评
- 比赛总结+近期总结
- effective stl 第46条:考虑使用函数对象而不是函数作为STL算法的参数
- 块设备驱动程序概念介绍
- 利用APT实现Android编译时注解
- 浅谈扩展BaseAdapter的优化
- Linux进程间控制编程
- 算法总结
- angular.js学习(2)--service和provider
- 汉诺塔C++实现(递归调用)
- 负载均衡之Haproxy配置详解
- 比赛总结+近期总结