Linux操作系统有两类主要的设备文件:

1.字符设备:以字节为单位进行顺序I/O操作的设备,无需缓冲区且被直接读写。

2.块设备:只能以块单位接收输入返回,对于I/O请求有对应的缓冲区,可以随机访问,块设备的访问位置必须能够在介质的不同区间前后移动。在块设备中,最小的可寻址单元是扇区,扇区的大小一般是2的整数倍,常见的大小为512个字节。

上图是一个块设备操作的分层实现图

1.当一个进程被Read时,内核会通过VFS层去读取要读的文件块有没有被cache了,这个cache由一个buffer_head结构读取。如果要读取的文件块还没有被cache,则就要从文件系统中去读取,通过一个address_space结构来引用,如果调用文件系统读取函数去读取一个扇区的数据。当它从磁盘读出数据时,将数据页连入到cache中,当下一次在读取时,就不需要从磁盘去读取了。Read完后将请求初始化成一个bio结构,并提交给通用块层。

2.它通过submit_bio()去完成,通用层在调用相应的设备IO调度器,这个调度器的调度算法,将这个bio合并到已经存在的request中,或者创建一个新的request,并将创建的插入到请求队列中,最后就剩下块设备驱动层来完成后面的所有工作。

内核中块得I/O操作的是由bio结构表示的

点击(此处)折叠或打开

struct bio {

sector_t        bi_sector;    /*该BIO结构所要传输的第一个(512字节)扇区*/

struct bio        *bi_next;    /*请求链表*/

struct block_device    *bi_bdev;/*相关的块设备*/

unsigned long        bi_flags;    /*状态和命令的标志*/

unsigned long        bi_rw;        /*读写*/

unsigned short        bi_vcnt;    /* bio_vec的偏移个数 */

unsigned short        bi_idx;        /* bvl_vec */

unsigned short        bi_phys_segments;

/* Number of segments after physical and DMA remapping

* hardware coalescing is performed.

*/

unsigned short        bi_hw_segments;

unsigned int        bi_size;    /* residual I/O count */

/*

* To keep track of the max hw size, we account for the

* sizes of the first and last virtually mergeable segments

* in this bio

*/

unsigned int        bi_hw_front_size;

unsigned int        bi_hw_back_size;

unsigned int        bi_max_vecs;    /* max bvl_vecs we can hold */

struct bio_vec        *bi_io_vec;    /* the actual vec list */

bio_end_io_t        *bi_end_io;

atomic_t        bi_cnt;        /* pin count */

void            *bi_private;

bio_destructor_t    *bi_destructor;    /* destructor */

};此结构体的目的主要是正在执行的I/O操作,其中的bi_io_vecs、bi_vcnt、bi_idx三者都可以相互找到。bio_vec描述一个特定的片段,片段所在的物理页,块在物理页中的偏移页,整个bio_io_vec结构表示一个完整的缓冲区。

点击(此处)折叠或打开

struct bio_vec {

struct page    *bv_page;

unsigned int    bv_len;

unsigned int    bv_offset;

};

当一个块被调用内存时,要储存在一个缓冲区,每个缓冲区与一个块对应,所以每一个缓冲区独有一个对应的描述符,该描述符用buffer_head结构表示

点击(此处)折叠或打开

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 */

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;        /* users using this buffer_head */

};下面来看看块设备的核心ll_rw_block函数

点击(此处)折叠或打开

void ll_rw_block(int rw, int nr, struct buffer_head *bhs[])

{

int i;

for (i = 0; i < nr; i) {

struct buffer_head *bh = bhs[i];

if (!trylock_buffer(bh))

continue;

if (rw == WRITE) {

if (test_clear_buffer_dirty(bh)) {

bh->b_end_io = end_buffer_write_sync;

get_bh(bh);

submit_bh(WRITE, bh);

continue;

}

} else {

if (!buffer_uptodate(bh)) {

bh->b_end_io = end_buffer_read_sync;

get_bh(bh);

submit_bh(rw, bh);

continue;

}

}

unlock_buffer(bh);

}

}请求块设备驱动将多个物理块读出或者写到块设备,块设备的读写都是在块缓冲区中进行。

点击(此处)折叠或打开

int submit_bh(int rw, struct buffer_head * bh)

{

struct bio *bio;

int ret = 0;

BUG_ON(!buffer_locked(bh));

BUG_ON(!buffer_mapped(bh));

BUG_ON(!bh->b_end_io);

BUG_ON(buffer_delay(bh));

BUG_ON(buffer_unwritten(bh));

/*

* Only clear out a write error when rewriting

*/

if (test_set_buffer_req(bh) && (rw & WRITE))

clear_buffer_write_io_error(bh);

/*

* from here on down, it's all bio -- do the initial mapping,

* submit_bio -> generic_make_request may further map this bio around

*/

bio = bio_alloc(GFP_NOIO, 1);

bio->bi_sector = bh->b_blocknr * (bh->b_size >> 9);

bio->bi_bdev = bh->b_bdev;

bio->bi_io_vec[0].bv_page = bh->b_page;

bio->bi_io_vec[0].bv_len = bh->b_size;

bio->bi_io_vec[0].bv_offset = bh_offset(bh);

bio->bi_vcnt = 1;

bio->bi_idx = 0;

bio->bi_size = bh->b_size;

bio->bi_end_io = end_bio_bh_io_sync;

bio->bi_private = bh;

bio_get(bio);

submit_bio(rw, bio);

if (bio_flagged(bio, BIO_EOPNOTSUPP))

ret = -EOPNOTSUPP;

bio_put(bio);

return ret;

这个函数主要是调用submit_bio,最终调用generic_make_request去完成将bio传递给驱动去处理。

点击(此处)折叠或打开

void generic_make_request(struct bio *bio)

{

struct bio_list bio_list_on_stack;

if (!generic_make_request_checks(bio))

return;

if (current->bio_list) {

bio_list_add(current->bio_list, bio);

return;

}

BUG_ON(bio->bi_next);

bio_list_init(&bio_list_on_stack);

current->bio_list = &bio_list_on_stack;

do {

struct request_queue *q = bdev_get_queue(bio->bi_bdev);

q->make_request_fn(q, bio);

bio = bio_list_pop(current->bio_list);

} while (bio);

current->bio_list = NULL; /* deactivate */

}这个函数主要是取出块设备相应的队列中的每个设备,在调用块设备驱动的make_request,如果没有指定make_request就调用内核默认的__make_request,这个函数主要作用就是调用I/O调度算法将bio合并,或插入到队列中合适的位置中去。

整个流程为:

那么request_fn指向那个函数呢?在内核中搜搜request_fn,发现

点击(此处)折叠或打开

request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)

{

return blk_init_queue_node(rfn, lock, -1);

}

EXPORT_SYMBOL(blk_init_queue);

request_queue_t *

blk_init_queue_node(request_fn_proc *rfn, spinlock_t *lock, int node_id)

{

request_queue_t *q = blk_alloc_queue_node(GFP_KERNEL, node_id);

if (!q)

return NULL;

q->node = node_id;

if (blk_init_free_list(q)) {

kmem_cache_free(requestq_cachep, q);

return NULL;

}

if (!lock) {

spin_lock_init(&q->__queue_lock);

lock = &q->__queue_lock;

}

q->request_fn        = rfn;

q->prep_rq_fn        = NULL;

q->unplug_fn        = generic_unplug_device;

q->queue_flags        = (1 << QUEUE_FLAG_CLUSTER);

q->queue_lock        = lock;

blk_queue_segment_boundary(q, 0xffffffff);

blk_queue_make_request(q, __make_request);

blk_queue_max_segment_size(q, MAX_SEGMENT_SIZE);

blk_queue_max_hw_segments(q, MAX_HW_SEGMENTS);

blk_queue_max_phys_segments(q, MAX_PHYS_SEGMENTS);

q->sg_reserved_size = INT_MAX;

/*

* all done

*/

if (!elevator_init(q, NULL)) {

blk_queue_congestion_threshold(q);

return q;

}

blk_put_queue(q);

return NULL;

}原来request_queue的make_request_fn指向__make_request()函数,这个函数复杂I/O调度,并对bio做些合并等。下面来看看__make_request()做了些什么?

由上面可以分析得出,其中有一个很重要的结构体

点击(此处)折叠或打开

struct request {

struct list_head queuelist;//连接这个请求到请求队列.

//追踪请求硬件完成的扇区的成员. 第一个尚未被传送的扇区被存储到 hard_sector, 已经传送的扇区总数在 ha//rd_nr_sectors, 并且在当前 bio 中剩余的扇区数是 hard_cur_sectors. 这些成员打算只用在块子系统; 驱动//不应当使用它们.

struct request_queue *q;

sector_t hard_sector;

unsigned long hard_nr_sectors;

unsigned int hard_cur_sectors;

struct bio *bio;//bio 是给这个请求的 bio 结构的链表. 你不应当直接存取这个成员; 使用 rq_for_each_bio(后面描述) 代替.

unsigned short nr_phys_segments;//被这个请求在物理内存中占用的独特段的数目, 在邻近页已被合并后

char *buffer;//随着深入理解,可见到这个成员仅仅是在当前 bio 上调用 bio_data 的结果.

};request_queue只是一个请求队列,通过可以找到requeue,然后通过bio结构体对应的page读取物理内存中的信息。

下面看看内核使用的块设备的例子

点击(此处)折叠或打开

static int jsfd_init(void)

{

static DEFINE_SPINLOCK(lock);

struct jsflash *jsf;

struct jsfd_part *jdp;

int err;

int i;

if (jsf0.base == 0)

return -ENXIO;

err = -ENOMEM;

//1. 分配gendisk: alloc_disk

for (i = 0; i < JSF_MAX; i++) {

struct gendisk *disk = alloc_disk(1);

if (!disk)

goto out;

jsfd_disk[i] = disk;

}

//2.设置,分配/设置队列: request_queue_t  // 它提供读写能力

if (register_blkdev(JSFD_MAJOR, "jsfd")) {

err = -EIO;

goto out;

}

jsf_queue = blk_init_queue(jsfd_do_request, &lock);

if (!jsf_queue) {

err = -ENOMEM;

unregister_blkdev(JSFD_MAJOR, "jsfd");

goto out;

}

//3.设置gendisk其他信息             // 它提供属性: 比如容量

for (i = 0; i < JSF_MAX; i++) {

struct gendisk *disk = jsfd_disk[i];

if ((i & JSF_PART_MASK) >= JSF_NPART) continue;

jsf = &jsf0;    /* actually, &jsfv[i >> JSF_PART_BITS] */

jdp = &jsf->dv[i&JSF_PART_MASK];

disk->major = JSFD_MAJOR;

disk->first_minor = i;

sprintf(disk->disk_name, "jsfd%d", i);

disk->fops = &jsfd_fops;

set_capacity(disk, jdp->dsize >> 9);

disk->private_data = jdp;

disk->queue = jsf_queue;

//4 注册: add_disk

add_disk(disk);

set_disk_ro(disk, 1);

}

return 0;

out:

while (i--)

put_disk(jsfd_disk[i]);

return err;

}

linux 内核块设备驱动,linux之块设备驱动相关推荐

  1. linux内核源码实战_3.2理解设备驱动和文件系统

    linux内核源码实战_3.2理解设备驱动和文件系统 linux内核源码实战_理解设备驱动和文件系统 理解设备驱动和文件系统 理解设备驱动和文件系统详解 7-文件系统-proc文件系统实现 总结 li ...

  2. Linux内核网络数据发送(六)——网络设备驱动

    Linux内核网络数据发送(六)--网络设备驱动 1. 前言 2. 驱动回调函数注册 3. `ndo_start_xmit` 发送数据 4. `igb_tx_map` 1. 前言 本文主要介绍设备通过 ...

  3. linux内核培训广州,嵌入式Linux驱动开发高级培训班-华清远见嵌入式培训中心

    课程目标 本课程以案例教学为主,系统地介绍Linux下有关FrameBuffer.MMC卡.USB设备的驱动程序开发.参加本课程学习的学员,因为具备了Linux设备驱动开发基础,所以本课程针对性较强, ...

  4. Linux内核网络栈1.2.13-网卡设备的初始化流程

    参考资料 <<linux内核网络栈源代码情景分析>> 网卡设备的初始化 本文主要描述一下网卡设备的整个初始化的过程,该过程主要就是根据设备的硬件信息来获取与传输网络数据,注册相 ...

  5. 一文了解linux内核,一文了解Linux的系统结构

    什么是 Linux ? 如果你以前从未接触过Linux,可能就不清楚为什么会有这么多不同的Linux发行版.在查看Linux软件包时,你肯定被发行版.LiveCD和GNU之类的术语搞晕过.初次进入Li ...

  6. Linux内核入门-如何获取Linux内核源代码、生成配置内核

    如何获取Linux内核源代码 如何获取Linux内核源代码 下载Linux内核当然要去官方网站了,网站提供了两种文件下载,一种是完整的Linux内核,另一种是内核增量补丁,它们都是tar归档压缩包.除 ...

  7. Linux内核开发_1_编译LInux内核

    目录 1. 准备工作 1.1 学习环境 1.2 下载Linux内核源码 1.3 解压Linux内核 1.4 目录结构介绍 2. Linux内核配置 2.1 配置选项 1. make config 2. ...

  8. linux内核启动分析 三,Linux内核分析 实验三:跟踪分析Linux内核的启动过程

    贺邦 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1000029000 一. 实验过程 ...

  9. linux内核 lts长期演进,Linux Kernel 4.19 将成为下一个LTS(长期支持)系列

    最近Linux内核开发人员和维护人员Greg Kroah-Hartman透露,Linux Kernel 4.19将下一个长期支持的Linux内核系列. 现在Linux Kernel 4.17已经达到使 ...

  10. 查看linux内核的编译时间,linux内核编译步骤

    linux内核编译步骤 对于linux新手来说,编译内核相对有一些难度,甚至不知道如何入手,我通过在网上收集这方面的资料,最终编译成功.现在我归纳了一下,写出这一篇还算比较详细的步骤,希望能对各位新手 ...

最新文章

  1. java基础(五) String性质深入解析
  2. ubuntu终端按ctrl+s就卡住怎么办?(按ctrl+q)(锁住)(锁屏)(暂停打印)
  3. java 肌汉模式_设计模式之原型模式详解(附源代码)
  4. wuhan2020开源项目协作流程发布 征集专家人士
  5. JpaSpecificationExecutor
  6. js ajax 浏览器兼容,JS跨浏览器兼容,一点点总结
  7. php设置表单的字体,php表单标题怎么设置字体
  8. 6月,回忆我失去的爱情
  9. 论文阅读9-Fine-tuning Pre-Trained Transformer Language Models to(远程监督关系抽取,ACL2019,GPT,长尾关系,DISTRE)
  10. android搭建opencv开发环境,Android Studio搭建opencv开发环境
  11. java线程切换速度_为什么说线程太多,cpu切换线程会浪费很多时间?
  12. php网站服务器500,php服务器错误500
  13. mysql 创建表字段长度范围_老板要我把这份MySQL规范贴在工位上!
  14. linux apktool使用教程,简单介绍ubuntu下apktool的使用与配置
  15. 使用GDAL构造OpenCV的图像格式
  16. 迅捷cad_迅捷数组
  17. windows server 2016安装网卡驱动【引用】
  18. Parallels将Win10引入Apple Silicon,实测运行效果糟糕
  19. 谷歌Play马甲包检测逻辑推测及应对措施
  20. java小游戏实训目的_Java弹球小游戏实验报告.doc

热门文章

  1. java 读取html字符串替换字符
  2. 样式中的url加载探疑
  3. Google Python 编程风格指南
  4. 图片轮流翻转,一直循环
  5. python里的正则表达式
  6. Linux命令-关机命令详解
  7. 190529每日一句,放胆去梦想,努力去奋斗
  8. Leap手心发射线,碰撞点用小球表示,并用Line Renderer画出来
  9. 190412每日一句
  10. unity 打开摄像头(图像倒立的变换) 和显示所有摄像头的名称