一、剖析一个块设备

块设备最小的可寻址单元是扇区。 扇区大小一般是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. android 程序的运行步骤(备忘)

    java代码: public class HelloWorld { public static void main(String[] args) { System.out.println(" ...

  2. vue启动流程

    继上一篇vue环境的搭建(在D盘新建文件夹vue_cli,把(我已经上传到了文件下)资料下tpls解压完后的所有文件都复制到D盘vue_cli下) 目录如图: 1.webstorm设置为了提高webS ...

  3. 001-Django简介与项目创建

    简介 django,是用python语言写的开源web开发框架,并遵循MVC设计 主要目的是简便.快速的开发数据库驱动的网站 强调代码复用,有很多第三方插件,强调快速开发和DRY(DoNotRepea ...

  4. jmeter链接数据库操作

    jmeter链接数据库操作步骤 首先要先下载mysql-connector-java-5.1.39-bin.jar驱动包 链接:https://pan.baidu.com/s/14F4rp4uH1hX ...

  5. 20191127 Spring Boot官方文档学习(4.14-4.17)

    4.14.使用RestTemplate调用REST服务 如果需要从应用程序调用远程REST服务,则可以使用Spring Framework的RestTemplate类.由于RestTemplate实例 ...

  6. linux 文件属性文件权限

    权限 -rw-------. root root Mar : anaconda-ks.cfg drwxr-xr-x root root May : dir1 drwxr-xr-x root root ...

  7. Java语言的发展历程

    前言 自1946年2月14日世界上首款计算机ENAC问世,第一代计算机语言“机器语言”便诞生了,它使用的是最原始的穿孔卡片,这种卡片上使用的语言只有专家才能理解,与人类语言差别极大.这种语言本质上是计 ...

  8. codeforces 597 div 2

    A 题意: 有无限个方块,上面分别是0,1,2......若方块i可以被表示为ax+by(x,y>0),则方块为白色,否则为黑色,给出a,b.问黑方块是否有无限个. 分析: 1:若(a,b)=1 ...

  9. Burp Suite详细使用教程-Intruder模块详3

    Burp Suite使用详细教程连载的第三章.0×02 Intruder—内置有效负荷测试使用技巧内置有效负荷测试选择项如下图: 今天的小技巧使用的是 numbers,给大伙科普下:Numbers 数 ...

  10. rest_framework框架的认证、权限

    REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.TokenAuth", ] ...