Linux0.12内核源码解读(9)-blk_dev_init和chr_dev_init
作者:小牛呼噜噜 | https://xiaoniuhululu.github.io
计算机内功、源码解析、科技故事、项目实战、面试八股等更多硬核文章,首发于公众号「小牛呼噜噜」
哈喽,大家好呀,我是呼噜噜,好久没有更新old linux了,本文接着上一篇文章图解计算机中断,继续我们的内核探索之路
我们来接着看看main.c后面的几个初始化函数blk_dev_init()
、chr_dev_init()
1 | // init/main.c |
blk_dev_init
blk_dev_init()
是块设备初始化函数,那什么是块设备呢?
所谓的块设备block device,是计算机I/O设备的一种,就是以固定大小的数据块为单位来存储数据的设备,其传输速率较高,通常为每秒钟几兆位;数据块的大小通常在512字节到32768字节
之间
每个块都有自己的地址,可以进行寻址操作和随机访问操作,而且每个块都能独立于其它块而进行读写,比较常见的有硬盘、软盘设备和闪存等
与之对应的就是字符设备character device,字符设备是一种只能以字节为最小单位,进行数据输入和输出的设备,其传输速率较低,通常为几个字节至数千字节
它无法进行寻址操作,只能按照字节流的方式被有序访问,字符设备的种类繁多,比如打印机、键盘、串口、网络接口和终端设备等。另外字符设备在输入输出时,常采用中断来驱动。
我们先来看下它源码:
1 | // /kernel/blk_drv/ll_rw_blk.c |
NR_REQUEST
其实定义在kernel/blk_drv/blk.h
,#defineNR_REQUEST 32
,表示request数组有32项,也是有讲究的,能够较好地平衡电梯调度算法的效率(电梯调度算法我们下文再介绍)
我们再来看下request结构体的定义:
1 | struct request { |
那这段代码的作用,是给request数组的所有项,dev和next元素赋值,其实就是进行初始化;
然后就没了,许多人看到这会感觉很懵逼,这都是啥呀?
要想知道blk_dev_init()
是如何初始化块设备的?那么得先了解最典型的块设备硬盘的构造了,比如常见的机械硬盘的组成部分:
我们都知道计算机世界中,只有0和1,在之前的文章漫谈从RS触发器到D触发器的发展历程我们介绍过一种储存数据的方式:RS触发器,让时序逻辑电路具有储存记忆的能力,它是内存用来储存数据的核心原理
而机械硬盘则是另一种方式,机械硬盘中最重要的结构就是磁盘,在工作时会高速旋转;一块磁盘一般有多个盘片组成,盘片的表面涂有磁性物质,可以被磁化以及改变磁极,这两个磁极就分别表示了计算机二进制中的0和1
上图来源于:Magnetic storage
写入数据时,磁头产生局部强磁场来磁化盘片上磁性物质的极性,来储存数据;当读出数据时,则通过读取元件来识别磁性物质的极性,并将其还原成0和1。也就是说这些磁性物质可以用来记录二进制数据,并通过电磁转换完成读、写操作
因为正反两面都可涂上磁性物质,故一个盘片可能会有两个盘面。每个盘面都会有对应的磁头,所有磁头”共进共退”,因为会有一个磁臂固定它们。
每个盘面划分成了一圈一圈的磁道track,最外圈是0磁盘。然后每个磁盘又划分为了一个个小块,小块叫做扇区sector。扇区大小固定,是512个字节。另外由于最内侧磁道上的扇区面积最小,因此数据密度最大;最后所有盘面中相对位置相同的磁道组成柱面cylinder
扇区大小为啥固定512字节?这是1956年由industry trade organization, International Disk Drive Equipment和Materials Association三家机构确定的行业标准, 大家都默认会遵守的标准,久而久之,如果改变的话,兼容各种情况的代价非常大。
而有了柱面,磁头,扇区这3者,就能够定位磁盘上任意一块数据,其中如果知道哪个磁头,就可以知道数据在哪个盘面上,然后读取哪个柱面上的第几扇区,这也叫CHS模式
由此我们可知request
这个结构体是非常重要的,是和块设备打交道的核心数据结构
本文以硬盘来举例子:request
结构体中,dev
就是表示设备号,cmd
表示是读命令还是写命令,sector和nr_sectors
配合找到对应的扇区;buffer
缓冲区,是为了减少对块设备的访问,在数据以块的形式传入内存后,通过buffer缓冲区进行读取,不是直接和物理磁盘读取。字符设备可以直接物理磁盘读取,不经过buffer缓冲区。
bh
就是缓冲区头指针,waiting
则表示任务等待操作执行完成的地方,next是指向下一请求项,说明request数组内部是链表。也就是说request数组
其实就是块设备的请求队列,新的读写请求,进入队列排队
另外由于内存和硬盘存取数据的速度差异是非常大的,也就是说访问硬盘等块设备的操作比较耗时,影响操作系统的性能;而且硬盘的读写磁头移动到对应的磁道,这种磁盘调度也是非常耗时的。
linux0.12采用的是scan扫描算法,也叫做电梯调度算法,尽可能通过减小磁头移动的距离来提高磁盘的读写效率,将新的请求项插入到磁头移动距离最小的请求队列位置处
电梯调度算法的原理:磁头在一个方向上移动,访问所有未完成的请求,直到磁头到达该方向上最后的磁道,才调换方向;
scan算法,上图来源于磁盘调度算法
除此以外还有其他常见的算法FCFS,SSF:
先来先服务算法FCFS:不管磁头初始在什么位置,都是按照服务队列的先后顺序,来依次处理进程。优点是简单,公平,但缺点就是平均寻道长度会很长,效率不高,相邻两次请求可能会造成最内到最外的柱面寻道,使磁头反复移动
最短寻道时间优先算法SSF:其实就是贪心算法,优先选择处理距离磁头位置最近的进程,处理完成后再处理距离当前磁道最近的进程,直到所有的进程被处理
不像先来先服务算法FCFS,追求极致的公平,效率较低;也不像最短寻道时间优先算法SSF,过于注重效率而忽视公平(磁头有可能只在一个小区域内来回移动,导致某些访问请求长期等待得不到服务的情况);电梯调度算法能够较好地兼顾公平与效率。我们这里就不做过多展开,感兴趣地自行去了解
blk_dev_init
函数,其实是给request数组的所有项的dev设置为-1,表示无设备请求,next设置为null,表示没有下一请求项,实现对块设备请求队列的初始化。等待内核向块设备的控制器发起命令,至于后面的一系列操作,我们在后面的文章,会结合具体的源码再详细展开
chr_dev_init
chr_dev_init
是字符设备初始化函数,上面刚讲完块设备的初始化,字符设备的初始化不就来了嘛,我们先来看下源码:
1 | // /kernel/chr_drv/tty_io.c |
咳咳,是空的,大佬linus在这个版本暂未开发,那我们就直接跳过了
先到这吧,上周末语雀挂掉,导致有部分文字没有能保存下来~
参考资料:
聊聊x86计算机启动发生的事?
https://en.wikipedia.org/wiki/Hard_disk_drive
https://en.wikipedia.org/wiki/Magnetic_storage
操作系统:磁盘调度算法_操作系统磁盘调度算法
作者:小牛呼噜噜 ,首发于公众号「小牛呼噜噜」
感谢阅读,原创不易,如果有收获的话,就点个免费的[赞]or[转发],你的支持会激励我输出更高质量的文章,感谢!