一、剖析一个块设备

块设备最小的可寻址单元是扇区。 扇区大小一般是2的整数倍,最常见的是512字节。

因为各种软件的用途不同,所以他们都会用到自己的最小逻辑可寻址单元----块。块只能基于文件系统,是一种抽象。

而扇区是设备的最小可寻址单元,所以块不能比扇区还小,只能数倍于扇区大小。此外内核要求块是2的整数倍。

至此,块最终要求是:必须是扇区大小的2的整数倍,并且要小于页面大小。所以通常块是512字节、1KB或4KB。

扇区:设备最小寻址单元。别名:硬扇区、设备块

块:文件系统的最小寻址单元。别名:文件块、I/O块

块包含一个或多个扇区,但大小不能超过一个页。所以页可以容纳一个或多个内存中的块。

二、缓冲区和缓冲区头

当一个块被调入内存时,它要存储在一个缓冲区中。每个缓冲区与一个块对应,相当于磁盘块在内存中的表示。

每个缓冲区都有一个对应的描述符。该描述符用buffer_head结构体表示,称作缓冲区头,在文件<linux/buffer_head.h>中定义。

struct buffer_head {
unsigned long b_state; /* buffer state bitmap (see above)缓冲区状态标志位 */
struct buffer_head *b_this_page;/* circular list of page's buffers页面中的缓冲区 */
struct page *b_page; /* the page this bh is mapped to 存储缓冲区的页面*/
sector_t b_blocknr; /* start block number 其实块号*/
size_t b_size; /* size of mapping 映像的大小*/
char *b_data; /* pointer to data within the page 页面内的数据指针*/
struct block_device *b_bdev; /* 相关联的块设备 */
bh_end_io_t *b_end_io; /* I/O completion I/O完成方法*/
void *b_private; /* reserved for b_end_io 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; /* users using this buffer_head 缓冲区使用计数*/
};

struct buffer_head

b_count域表示缓冲区的使用记数,可通过两个定义在文件<linux/buffer_head.h>中内联函数对此域进行增减。

static inline void get_bh(struct buffer_head *bh)
{
atomic_inc(&bh->b_count);
}

get_bh

static inline void put_bh(struct buffer_head *bh)
{
atomic_dec(&bh->b_count);
}

put_bh

合法的标志存放在bh_state_bits枚举中,该枚举在<linux/buffer_head.h>中定义:

enum bh_state_bits {
BH_Uptodate, /* Contains valid data 包含可用数据*/
BH_Dirty, /* Is dirty 脏数据*/
BH_Lock, /* Is locked */
BH_Req, /* Has been submitted for I/O 有IO操作请求*/
BH_Uptodate_Lock,/* Used by the first bh in a page, to serialise
* IO completion of other buffers in the page 被IO操作占用
*/
BH_Mapped, /* Has a disk mapping 映射了磁盘块*/
BH_New, /* Disk mapping was newly created by get_block 缓冲区用get_block刚刚获取的,还不能使用*/
BH_Async_Read, /* Is under end_buffer_async_read I/O 正在被异步读*/
BH_Async_Write, /* Is under end_buffer_async_write I/O 正在被异步写*/
BH_Delay, /* Buffer is not yet allocated on disk 尚未与磁盘关联*/
BH_Boundary, /* Block is followed by a discontiguity 缓冲区在连续块边缘*/
BH_Write_EIO, /* I/O error on write 写入时遇到错误*/
BH_Unwritten, /* Buffer is allocated on disk but not written */
BH_Quiet, /* Buffer Error Prinks to be quiet 禁止错误*/
BH_Meta, /* Buffer contains metadata */
BH_Prio, /* Buffer should be submitted with REQ_PRIO */
BH_Defer_Completion, /* Defer AIO completion to workqueue */
BH_PrivateStart,/* not a state bit, but the first bit available
* for private allocation by other entities
*/ };

bh_state_bits

bh_state_bits还有一个特殊标志,BH_PrivateStart,该标志不是可用状态标志。

三、bio结构体

内核中块I/O操作基本容器由bio结构表示,定义在文件<linux/bio.h>中。

该结构体代表了正在现场的(活动的)以片段(segment)链表形式组织的块I/O操作。

/*
* main unit of I/O for the block layer and lower layers (ie drivers and
* stacking drivers)
*/
struct bio {
struct bio *bi_next; /* request queue link 请求链表*/
struct block_device *bi_bdev; /* 相关的块设备 */
unsigned int bi_flags; /* status, command, etc 状态和命令标志 */
int bi_error;
unsigned long bi_rw; /* bottom bits READ/WRITE,
* top bits priority 读还是写
*/ struct bvec_iter bi_iter; /* Number of segments in this BIO after
* physical address coalescing is performed. 结合后的片段数目
*/
unsigned int bi_phys_segments; /*
* 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; atomic_t __bi_remaining; bio_end_io_t *bi_end_io; /* bi io完成方法 */ void *bi_private; /* 拥有者的私有方法 */
#ifdef CONFIG_BLK_CGROUP
/*
* Optional ioc and css associated with this bio. Put on bio
* release. Read comment on top of bio_associate_current().
*/
struct io_context *bi_ioc;
struct cgroup_subsys_state *bi_css;
#endif
union {
#if defined(CONFIG_BLK_DEV_INTEGRITY)
struct bio_integrity_payload *bi_integrity; /* data integrity */
#endif
}; unsigned short bi_vcnt; /* how many bio_vec's bio链表个数*/ /*
* Everything starting with bi_max_vecs will be preserved by bio_reset()
*/ unsigned short bi_max_vecs; /* max bvl_vecs we can hold */ atomic_t __bi_cnt; /* pin count */ struct bio_vec *bi_io_vec; /* the actual vec list bio链表*/ struct bio_set *bi_pool; /*
* 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.内嵌bio向量
*/
struct bio_vec bi_inline_vecs[];
};

struct bio

bi_io_vec是bio_vec的链表指针,共有bi_vcnt个,当前指向bi_idx位置。bi_vcnt域用来描述bi_io_vec所指向的vio_vec数组中的向量数目。

3.1 I/O向量

bi_io_vec域指向一个bio_vec结构体数组,该结构体链表包含一个特定I/O操作所需要使用的所有片段。

每个bio_vec结构都是一个形式为<page, offset, len>的向量,它描述的是一个特定的片段。片段所在的物理页、块在物理页中的偏移位置、从给定偏移量开始的块长度。

/*
* was unsigned short, but we might as well be ready for > 64kB I/O pages
*/
struct bio_vec {
/* 指向这个缓冲区所驻留的物理页 */
struct page *bv_page;
/* 这个缓冲区以字节为单位的大小 */
unsigned int bv_len;
/* 缓冲区所驻留的页中以字节为单位的偏移量 */
unsigned int bv_offset;
};

struct bio_vec

总而言之,每一个I/O请求都通过一个bio结构体表示。每个请求包含一个或多个块,这些块存储在bio_vec结构体数组中。

bi_cnt域记录bio结构体的使用计数,如果该值减为0,就撤销该bio结构体。并释放占用的内存。

void bio_get(struct bio *bio)
void bio_put(struct bio *bio)

计数操作

bi_private域,是一个私有域。只有创建了bio结构的拥有者可以读写。

利用bio结构体代替buffer_head结构体有以下好处:

  • bio结构体很容易处理高端内存,因为它处理的是物理页而不是直接指针。
  • bio结构体既可以代表普通页I/O,同时也可以代表直接I/O
  • bio结构体便于执行分散--集中块I/O操作,操作中的数据可取自多个物理页面。
  • bio结构体相比缓冲区头属于轻量级的结构体。因为它只需要包含块I/O操作所需的信息就行了,不用包含与缓冲区本身相关的不必要信息。

四、请求队列

块设备将他们挂起在块I/O请求保存在请求队列中,该队列有reques_queue结构体表示,定义在文件<linux/blkdev.h>中,包含要给双向请求链表以及相关控制信息。

队列中的请求由request表示,定义在<linux/blkdev.h>中。

bio使用例程:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/genhd.h>
#include <linux/string.h>
#include <uapi/linux/major.h>
#include <linux/blkdev.h> #define SIMP_BLKDEV_DISK_NAME "simp_blkdev"
#define SIMP_BLKDEV_DEVICEMAJOR COMPAQ_SMART2_MAJOR7;
#define SIMP_BLKDEV_BYTES (16*1024*1024) static struct gendisk *simp_blkdev_disk;
static struct request_queue *simp_blkdev_queue;
unsigned char simp_blkdev_data[SIMP_BLKDEV_BYTES]; static void simp_blkdev_do_request(struct request_queue *q); struct block_device_operations simp_blkdev_fops = {
.owner = THIS_MODULE,
}; static void simp_blkdev_do_request(struct request_queue *q)
{
struct request *req;
struct bio *req_bio;
struct bio_vec *bvec;
char *disk_mem;
char *buffer;
int i = ;
//get request from queue
while((req = blk_fetch_request(q))!=NULL) {
//request ok?
if((blk_rq_pos(req) << ) + blk_rq_cur_bytes(req) > SIMP_BLKDEV_BYTES) {
printk(KERN_ERR SIMP_BLKDEV_DISK_NAME
":bad request:block=%llu,count=%u\n",
(unsigned long long)blk_rq_pos(req),
blk_rq_cur_bytes(req));
blk_end_request_all(req, -EIO);
continue;
}
//get memory position
disk_mem = simp_blkdev_data + (blk_rq_pos(req) << );
req_bio = req->bio; switch(rq_data_dir(req)) {
case READ:
while(req_bio != NULL) {
for(i=;i<req_bio->bi_vcnt;i++) {
bvec = &(req_bio->bi_io_vec[i]);
buffer = kmap(bvec->bv_page) + bvec->bv_offset;
memcpy(buffer, disk_mem, bvec->bv_len);
kunmap(bvec->bv_page);
disk_mem += bvec->bv_len;
}
req_bio = req_bio->bi_next;
}
__blk_end_request_all(req, );
break;
case WRITE:
while(req_bio != NULL) {
for(i=;i<req_bio->bi_vcnt;i++) {
bvec = &(req_bio->bi_io_vec[i]);
buffer = kmap(bvec->bv_page) + bvec->bv_offset;
memcpy(disk_mem, buffer, bvec->bv_len);
kunmap(bvec->bv_page);
disk_mem += bvec->bv_len;
}
req_bio = req_bio->bi_next;
}
__blk_end_request_all(req, );
break;
default:
break;
}
}
} static int __init init_base(void)
{
int ret;
printk("----Hello World----\n");
/* init queue */
simp_blkdev_queue = blk_init_queue(simp_blkdev_do_request, NULL);
if(!simp_blkdev_queue) {
ret = -ENOMEM;
goto err_init_queue;
} /* init gendisk */
simp_blkdev_disk = alloc_disk();
if(!simp_blkdev_disk) {
ret = -ENOMEM;
goto err_alloc_disk;
}
strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISK_NAME);
simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;
simp_blkdev_disk->first_minor = ;
simp_blkdev_disk->fops = &simp_blkdev_fops;
simp_blkdev_disk->queue = simp_blkdev_queue;
set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>); add_disk(simp_blkdev_disk);
return ; err_alloc_disk:
blk_cleanup_queue(simp_blkdev_queue);
err_init_queue:
return ret;
} static void __exit exit_base(void)
{
blk_cleanup_queue(simp_blkdev_queue);
put_disk(simp_blkdev_disk);
del_gendisk(simp_blkdev_disk);
printk("----exit ----\n");
} module_init(init_base);
module_exit(exit_base); MODULE_LICENSE("GPL");
MODULE_AUTHOR("chen");
MODULE_DESCRIPTION("bio test");

mybio

参考资料:https://lwn.net/Articles/736534/

https://www.xuebuyuan.com/3227084.html

https://blog.csdn.net/cxy_chen/article/details/80998510

http://bbs.chinaunix.net/thread-2017377-1-1.html

Linux内核设计与实现 总结笔记(第十四章)块I/O层的更多相关文章

  1. Linux内核设计与实现 总结笔记(第四章)进程调度

    进程调度 调度程序负责决定将哪个进程投入运行,何时运行以及运行多长时间. 调度程序没有太复杂的原理,最大限度地利用处理器时间的原则是,只要有可以执行的进程,那么就总会有进程正在执行. 一.多任务 多任 ...

  2. 《Linux内核设计与实现》读书笔记(十四)- 块I/O层

    最近太忙,居然过了2个月才更新第十四章.... 主要内容: 块设备简介 内核访问块设备的方法 内核I/O调度程序 1. 块设备简介 I/O设备主要有2类: 字符设备:只能顺序读写设备中的内容,比如 串 ...

  3. Linux内核设计与实现 总结笔记(第五章)系统调用

    系统调用 内核提供了用户进程和内核交互的接口,使得应用程序可以受限制的访问硬件设备. 提供这些接口主要是为了保证系统稳定可靠,避免应用程序恣意妄行. 一.内核通信 系统调用在用户空间进程和硬件设备之间 ...

  4. Linux内核设计与实现 总结笔记(第十三章)虚拟文件系统

    一.通用文件系统接口 Linux通过虚拟文件系统,使得用户可以直接使用open().read().write()访问文件系统,这种协作性和泛型存取成为可能. 不管文件系统是什么,也不管文件系统位于何种 ...

  5. Linux内核设计与实现 总结笔记(第六章)内核数据结构

    内核数据结构 Linux内核实现了这些通用数据结构,而且提倡大家在开发时重用. 内核开发者应该尽可能地使用这些数据结构,而不要自作主张的山寨方法. 通用的数据结构有以下几种:链表.队列.映射和二叉树 ...

  6. Linux内核设计与实现 总结笔记(第十一章)定时器和时间管理

    时间管理在内核中占用非常重要的地位,内核中有大量的函数都需要基于时间驱动的,内核对相对时间和绝对时间都非常需要. 一.内核中的时间概念 内核必须在硬件的帮助下才能计算和管理时间,系统定时器以某种频率自 ...

  7. Linux内核设计与实现 总结笔记(第七章)中断和中断处理

    中断和中断处理 处理器的速度跟外围硬件设备的速度往往不再一个数量级上,因此,如果内核采取让处理器向硬件发出一个请求. 然后专门等待回应的办法,如果专门等待回应,明显太慢.所以等待期间可以处理其他事务, ...

  8. Linux内核设计与实现 总结笔记(第三章)进程

    进程管理 进程:处于执行期的程序. 线程:在进程中活动的对象 虚拟机制 虚拟处理器:多个进程分享一个处理器 虚拟内存:多个线程共享虚拟内存 一.进程描述符和任务结构 进程存放在双向循环链表中(队列), ...

  9. 《Linux内核设计与实现》课本第十八章自学笔记——20135203齐岳

    <Linux内核设计与实现>课本第十八章自学笔记 By20135203齐岳 通过打印来调试 printk()是内核提供的格式化打印函数,除了和C库提供的printf()函数功能相同外还有一 ...

  10. Linux内核设计与实现 读书笔记 转

    Linux内核设计与实现  读书笔记: http://www.cnblogs.com/wang_yb/tag/linux-kernel/ <深入理解LINUX内存管理> http://bl ...

随机推荐

  1. 【算法与数据结构】并查集 Disjoint Set

    并查集(Disjoint Set)用来判断已有的数据是否构成环. 在构造图的最小生成树(Minimum Spanning Tree)时,如果采用 Kruskal 算法,每次添加最短路径前,需要先用并查 ...

  2. CSS3—— 分页 框大小 弹性盒子 多媒体查询 多媒体查询实例

    分页 网站有很多个页面,就需要使用分页来为每个页面做导航 点击及鼠标悬停分页样式 圆角样式 悬停过度效果 带边框的分页 分页间隔 分页字体大小 居中分页 面包屑导航 框大小 box-sizing 属性 ...

  3. 项目测试完成后,总结典型性bug,以测试的角度,应该怎么筛选bug

    一个wap端改版项目完结了,总结下测试过程中的典型性bug:应该从哪个角度去总结? 有点疑问?不知道是以bug的影响度去总结,还是以优先级去总结(好像优先级和影响度是成正比的,优先级比较高的bug,影 ...

  4. Django-DRF组件学习-其他学习

    1.认证Authentication 可以在配置文件中配置全局默认的认证方案 REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_ ...

  5. sql语句经验

    1:拼接字段模糊查询 where  aaa(字段) not like CONCAT(DATE_FORMAT(new(),"%Y-%m-%d"),'%完成%'): 2:备份表中数据导 ...

  6. Docker Toolbox虚拟机文件地址修改 以及镜像加速

    Docker Toolbox虚拟机文件地址修改  默认情况下,docker-machine创建的虚拟机文件,是保存在C盘的C:\Users\用户名\.docker\machine\machines\d ...

  7. python+selenium浏览器截图

    from selenium import webdriverfrom time import sleep driver = webdriver.Firefox() # 指定和打开浏览器driver.g ...

  8. Spring中的常见注解

    @Controller 标识一个该类是Spring MVC controller处理器,用来创建处理http请求的对象.   @RestController Spring4之后加入的注解,原来在@Co ...

  9. The order of a Tree

    The order of a Tree Problem Description As we know,the shape of a binary search tree is greatly rela ...

  10. adb 配置连接

    一. adb环境安装 1.1. windown 驱动安装 1. 下载驱动(ADB Kits):http://adbshell.com/downloads 2. adb 测试 <1>. 解压 ...