块设备驱动初探

前言

研究IO也很久了,一直无法串联bio和块设备驱动,只知道bio经过IO调度算法传递到块设备驱动,怎么过去的,IO调度算法在哪里发挥作用,一直没有完全搞明白,查看了很多资料,终于对块设备驱动有所理解,也打通了bio到块设备。

一、传统块设备

我们先来实现一个基于内存的传统块设备驱动。

1.1 初始化一些东西

//暂时使用COMPAQ_SMART2_MAJOR作为主设备号,防止设备号冲突
#define SIMP_BLKDEV_DEVICEMAJOR   COMPAQ_SMART2_MAJOR
//块设备名
#define SIMP_BLKDEV_DISKNAME "simp_blkdev"//用一个数组来模拟一个物理存储
#define SIMP_BLKDEV_BYTES (16*1024*1024)
unsigned char simp_blkdev_data[SIMP_BLKDEV_BYTES];static struct request_queue *simp_blkdev_queue;//请求队列
static struct gendisk *simp_blkdev_disk;//块设备struct block_device_operations simp_blkdev_fops = {//块设备的操作函数.owner = THIS_MODULE, 
};

1.2 加载驱动

整个过程
1.创建request_queue(每个块设备一个队列),绑定函数simp_blkdev_do_request
2.创建一个gendisk(每个块设备就是一个gendisk)
3.将request_queue和gendisk绑定
4.注册gendisk

static int __init simp_blkdev_init(void)
{int ret;//初始化请求队列simp_blkdev_queue = blk_init_queue(simp_blkdev_do_request, NULL);//这个方法将会在1.5仔细分析simp_blkdev_disk = alloc_disk(1);//申请simp_blkdev_disk//初始化simp_blkdev_diskstrcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME);//设备名simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;//主设备号simp_blkdev_disk->first_minor = 0;//副设备号simp_blkdev_disk->fops = &simp_blkdev_fops;//块设备操作函数指针simp_blkdev_disk->queue = simp_blkdev_queue; //设置块设备的大小,大小是扇区的数量,一个扇区是512Bset_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);add_disk(simp_blkdev_disk);//注册simp_blkdev_diskreturn 0;
}

1.3 simp_blkdev_do_request

1.调用调度算法的elv_next_request方法获得下一个处理的request
2.如果是读,将simp_blkdev_data拷贝到request.buffer,
3.如果是写,将request.buffer拷贝到simp_blkdev_data
4.调用end_request通知完成

static void simp_blkdev_do_request(struct request_queue *q) 
{struct request *req;while ((req = elv_next_request(q)) != NULL) {//根据调度算法获得下一个requestswitch (rq_data_dir(req)) {//判断读还是写case READ:memcpy(req->buffer, simp_blkdev_data + (req->sector << 9), req->current_nr_sectors << 9);end_request(req, 1);//完成通知break;case WRITE:memcpy(simp_blkdev_data + (req->sector << 9),req->buffer, req->current_nr_sectors << 9); end_request(req, 1);//完成通知break;default:/* No default because rq_data_dir(req) is 1 bit */break;}
}

1.4 卸载驱动

static void __exit simp_blkdev_exit(void)
{del_gendisk(simp_blkdev_disk);//注销simp_blkdev_diskput_disk(simp_blkdev_disk);//释放simp_blkdev_diskblk_cleanup_queue(simp_blkdev_queue);//释放请求队列
}

千万别忘记下面代码

module_init(simp_blkdev_init);
module_exit(simp_blkdev_exit);

1.5 blk_init_queue

看了上面的代码,可能还是无法清晰的了解request_queue如何串联bio和块设备驱动,我们深入看一下

simp_blkdev_queue = blk_init_queue(simp_blkdev_do_request, NULL);//调用blk_init_queuestruct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
{return blk_init_queue_node(rfn, lock, NUMA_NO_NODE);//跳转1.5.1
}
EXPORT_SYMBOL(blk_init_queue);//1.5.1
struct request_queue *
blk_init_queue_node(request_fn_proc *rfn, spinlock_t *lock, int node_id)
{struct request_queue *q;q = blk_alloc_queue_node(GFP_KERNEL, node_id, lock);if (!q)return NULL;q->request_fn = rfn;//也就是simp_blkdev_do_requestif (blk_init_allocated_queue(q) < 0) {//转1.5.2blk_cleanup_queue(q);return NULL;}return q;
}
EXPORT_SYMBOL(blk_init_queue_node);//1.5.2
int blk_init_allocated_queue(struct request_queue *q)
{...blk_queue_make_request(q, blk_queue_bio);//转1.5.3if (elevator_init(q))//初始化IO调度算法goto out_exit_flush_rq;return 0;...
}
EXPORT_SYMBOL(blk_init_allocated_queue);//1.5.3
void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)
{...q->make_request_fn = mfn;//mfn也就是blk_queue_bio...
}
EXPORT_SYMBOL(blk_queue_make_request);static blk_qc_t blk_queue_bio(struct request_queue *q, struct bio *bio)//完成bio如何插入到request_queue
{//IO调度算法发挥作用的地方
}
整个调用完成之后,会绑定当前块设备的request_queue两个重要方法
q->make_request_fn = blk_queue_bio;//linux默认实现
q->request_fn = simp_blkdev_do_request;//驱动自己实现
1.5.1 make_request_fn(struct request_queue *q, struct bio *bio)

submit_bio会调用make_request_fn将bio封装成request插入到request_queue,默认会使用linux系统实现的blk_queue_bio。如果我们替换make_request_fn,会导致IO调度算法失效,一般不会去改。

1.5.2 request_fn(struct request_queue *q)

这个方法一般是驱动实现,也就是simp_blkdev_do_request,从request_queue中取出合适的request进行处理,一般会调用调度算法的elv_next_request方法,获得一个推荐的request。

1.5.3 bio-块设备

通过make_request_fn和request_fn,我们将bio和块设备驱动串联起来了。
而且IO调度算法会在这两个函数发挥作用。

给自己挖了两个坑
1.整个过程中受到了IO调度算法,IO调度算法如何发挥作用?
2.make_request_fn之后如何触发request_fn?

二、超高速块设备

传统块设备访问是通过磁头,IO调度算法可以优化多个IO请求的时候移动磁头的顺序。

IO调度算法

假如你是图书管理员,十个人找你借十本书,在图书馆的不同角落,你肯定会选择一条最短的线路去拿这十本书。其实这就是IO调度算法

超高速块设备

假如这个图书馆只有一个窗口,借书的人只要说出书名,书就会从窗口飞出来,这样子还需要什么管理员,更不需要什么IO调度算法,这个图书馆就是超高速块设备。

上面写的基于内存的块设备不就是一个超高速块设备嘛,我们能不能写一个没有中间商的驱动

2.1 simp_blkdev_init

我们需要重写一下init代码,不调用blk_init_queue。直接用下面的2.1.1和2.1.2的方法。
init之后,我们会将make_request_fn设置成simp_blkdev_make_request

static int __init simp_blkdev_init(void)
{int ret;//初始化请求队列simp_blkdev_queue = blk_alloc_queue(GFP_KERNEL);//2.1.1//将simp_blkdev_make_request绑定到request_queue的make_request_fn。blk_queue_make_request(simp_blkdev_queue, simp_blkdev_make_request);//2.1.2simp_blkdev_disk = alloc_disk(1);//申请simp_blkdev_disk//初始化simp_blkdev_diskstrcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME);//设备名simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;//设备号simp_blkdev_disk->first_minor = 0;simp_blkdev_disk->fops = &simp_blkdev_fops;//块设备操作函数指针simp_blkdev_disk->queue = simp_blkdev_queue; set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);//设置块设备的大小,大小是扇区的数量,一个扇区是512Badd_disk(simp_blkdev_disk);//注册simp_blkdev_diskreturn 0;err_alloc_disk:blk_cleanup_queue(simp_blkdev_queue);
err_alloc_queue:return ret;
}

2.2 simp_blkdev_make_request

跳过中间商,直接将simp_blkdev_data拷贝到bio的page,调用bio_endio通知读写完成,
从头到尾request_queue和request就没有用到

static int simp_blkdev_make_request(struct request_queue *q, struct bio *bio) {struct bio_vec *bvec;int i;void *dsk_mem;//获得块设备内存的起始地址,bi_sector代表起始扇区dsk_mem = simp_blkdev_data + (bio->bi_sector << 9);bio_for_each_segment(bvec, bio, i) {//遍历每一个块void *iovec_mem;switch (bio_rw(bio)) {case READ:case READA://page代表高端内存无法直接访问,需要通过kmap映射到线性地址iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;//页数加偏移量获得对应的内存地址memcpy(iovec_mem, dsk_mem, bvec->bv_len);//将数据拷贝到内存中kunmap(bvec->bv_page);//归还线性地址break;case WRITE:iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset; memcpy(dsk_mem, iovec_mem, bvec->bv_len); kunmap(bvec->bv_page);break;default:printk(KERN_ERR SIMP_BLKDEV_DISKNAME": unknown value of bio_rw: %lu\n", bio_rw(bio));
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)bio_endio(bio, 0, -EIO);//报错
#elsebio_endio(bio, -EIO);//报错
#endifreturn 0;}dsk_mem += bvec->bv_len;//移动地址}#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)bio_endio(bio, bio->bi_size, 0);
#elsebio_endio(bio, 0);
#endifreturn 0;
}

2.2 没有中间商

因为我们直接把数据的访问实现在make_request_fn,也就是simp_blkdev_make_request。
这样子就摆脱了request_queue和IO调度算法。没有中间商,访问速度杠杠的。

kernel中的zram设备就是基于内存没有中间商赚差价的块设备,代码很类似,有兴趣的可以看一下。

三、总结

经过那么长时间的学习,捅破层层的窗户纸,终于把IO打通了,但是文件系统,IO调度算法,每一模块都是值得我深入仔细研究,真正的挑战才刚刚开始。

代码参考

写一个块设备驱动.pdf

资料参考

《Linux内核设计与实现》
《Linux内核完全注释》
Linux.Generic.Block.Layer.pdf
https://zhuanlan.zhihu.com/c_132560778

阅读原文获得完整代码

  回复「 篮球的大肚子」进入技术群聊

回复「1024」获取1000G学习资料

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/467800.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java打war包or打jar包

//一个jar包可以包含多个entry&#xff0c;这样就能实现下面功能1.I/O 读文件流步骤 File filenew File(filePath);InputStreamReader read new InputStreamReader(new FileInputStream(file));BufferedReader bufferedReader new BufferedReader(read);String lineTxt…

Linux 块设备,Block Layer层架构演变

前言Block Layer层在整个I/O中负责承上启下&#xff0c;上接文件系统&#xff0c;下接块驱动。我不想直接讨论代码&#xff0c;希望从一个架构的演变来初探一下Block Layer层。一、1.0版本首先我们来了解几个重要的数据结构1.1 biobio代表了一次I/0请求&#xff0c;代表一个块设…

回溯 皇后 算法笔记_算法笔记-回溯法

(1)0-1背包问题思路&#xff1a;构造一个二叉树&#xff0c;每个商品都有两种状态&#xff0c;要或者不要。如果要就在这个节点的左枝挂子节点&#xff0c;如果不要就在右节点挂子节点。如果全部商品都分配完状态之后就回溯&#xff0c;回溯到一个还有其他选择的节点&#xff0…

Quartz集群

前言 前面说到过项目使用到了Quartz&#xff0c;当项目部署到多节点后&#xff0c;同样的调度任务会被重复执行&#xff0c;这时候就需要用到集群了。 集群配置 quartz.properties # # Configure Main Scheduler Properties # org.quartz.scheduler.instanceName me #ID设置为…

matalotlib(2)

文章目录注释文字Tex公式区域填充极坐标注释 import matplotlib.pyplot as plt import numpy as np xnp.arange(-10,11,1) yx*x plt.plot(x,y)plt.annotate(this is the bottom,xy(0,1),xytext(0,20),arrowpropsdict(facecolorr,frac0.2)) plt.show()文字 import matplotlib…

去华为吗?

昨晚的这条朋友圈很多人给我回复&#xff0c;支持去华为的人很多&#xff0c;但是也有几个反对的&#xff0c;一个说&#xff0c;怕是有命赚钱&#xff0c;没命花钱吧。还有一个说&#xff0c;自己拿到了华为offer&#xff0c;但是拒绝了&#xff0c;去了一个做开关电源的公司做…

plsql存过声明游标_plsql--游标用法

1.游标概念在 PL/SQL 块中执行 SELECT、INSERT、DELETE 和 UPDATE 语句时&#xff0c;ORACLE 会在内存中为其分配上下文区(Context Area)&#xff0c;即缓冲区。游标是指向该区的一个指针&#xff0c;或是命名一个工作区(Work Area)&#xff0c;或是一种结构化数据类型。它为应…

objectid.go源码阅读

/*http://docs.mongodb.org/manual/reference/object-id/ObjectId 按照字节顺序&#xff0c;一次代表&#xff1a;ObjectId is a 12-byte BSON type, constructed using:4个字节代表1970年元月一日到现在毫秒数 UNIX时间戳a 4-byte value representing the seconds since the …

实例

文章目录函数积分图散点条形图球员能力值函数积分图 import matplotlib.pyplot as plt import numpy as np from matplotlib.pyplot import Polygon def func(x):return -(x-2)*(x-8)40 xnp.linspace(0,10) yfunc(x) axplt.subplot() plt.plot(x,y,r,linewidth2)a2 b9 ax.set_…

闲来无事,拆个示波器玩玩。

首先要解释一下何为混合域示波器&#xff0c;既然说到这个话题就不得不说一下示波器进化史了&#xff0c;接下来大概讲一下示波器进化简史。第一代示波器——模拟示波器(ART-analog real time oscilloscope )纯模拟机器&#xff0c;使用示波管显示X-Y扫描成像显示波形&#xff…

r roc函数_R绘制ROC曲线 | Public Library of Bioinformatics

ROC曲线&#xff0c;受试者工作特征曲线 (receiver operating characteristic curve&#xff0c;简称ROC曲线)&#xff0c;又称为感受性曲线(sensitivity curve)。ROC曲线是根据一系列不同的二分类方式(分界值或决定阈)&#xff0c;以真阳性率(灵敏度)为纵坐标&#xff0c;假阳…

机器算法1)

SKLEARN Scikit-learn与特征工程 “数据决定了机器学习的上限&#xff0c;而算法只是尽可能逼近这个上限”&#xff0c;这句话很好的阐述了数据在机器学习中的重要性。大部分直接拿过来的数据都是特征不明显的、没有经过处理的或者说是存在很多无用的数据&#xff0c;那么需要…

老罗直播——只要给你一个机会,你就伸双手去接!

昨天&#xff0c;4月1日&#xff0c;罗永浩在抖音上直播卖货。一时间舆论纷纷&#xff0c;有吐槽老罗状况频出的&#xff0c;也有感叹老罗为了挣钱能屈能伸的。总之&#xff0c;有人讨论&#xff0c;有人关注&#xff0c;这个事件已经成功了一大半。老罗与抖音签约费是6000万&a…

segmenter.go

//Go中文分词package segoimport ("bufio""fmt""log""math""os""strconv""strings""unicode""unicode/utf8")const (minTokenFrequency 2 // 仅从字典文件中读取大于等于此频率的…

我在MTK平台下调试音频ALSA

#前言前言我就随便写了&#xff0c;因为是项目的需要&#xff0c;我需要在我们的MTK8167S平台上面调试音频。包括录音和播放。#硬件原理图因为是我们公司的项目&#xff0c;我就不能把完整的原理图给出来。因为两个MIC不涉及机密&#xff0c;跟MTK的公版是一样的。可以给出来大…

java 左边补0_java 数字左补齐0

NumberFormat nf NumberFormat.getInstance();//设置是否使用分组nf.setGroupingUsed(false);//设置最大整数位数nf.setMaximumIntegerDigits(2);nf.setMinimumIntegerDigits(2);//可以左补齐两位数的数字//以下是查询当前天数的所有日期String nowDaygetNowYMD();String curD…

一切不怕从零开始

不知道大家有没有看过吴京题为<<一切不怕从零开始>>的演讲。我曾经刷微博的时候看到过,印象和触动最深的是他的那句"我走过很多的路,换过很多的方向,不敢说有什么成就,到今天我觉得唯一能够让我拿出来炫耀的可能就是,我不害怕从头开始"。这样的话,对年龄…

60、二叉搜索树的第k个结点

一、题目 给定一颗二叉搜索树&#xff0c;请找出其中的第k大的结点。例如&#xff0c; 5 / \ 3 7 /\ /\ 2 4 6 8 中&#xff0c;按结点数值大小顺序第三个结点的值为4。 二、解法 1 package algorithm7;2 3 public class KthNode62 {4 public static void main(String[] ar…

机器学习算法3

文章目录转换器与估计器分类算法-K近邻算法一个例子弄懂k-近邻计算距离公式sklearn.neighborsMethodk近邻实例k-近邻算法案例分析对Iris数据集进行分割对特征数据进行标准化朴素贝叶斯概率论基础联合概率与条件概率联合概率条件概率如果每个事件相互独立拉普拉斯平滑sklearn朴素…

嵌入式杂谈之文件系统

文件系统可以说是嵌入式中的一大块&#xff0c;也是绕不过的一部分。之前我对文件系统认知一直停留在在U盘格式的理解上&#xff0c;直到接触了嵌入式Linux才发现这里面大有文章&#xff0c;以Linux启动挂载根文件系统为例&#xff0c;这个文件系统可以是真正的存储设备上的文件…