码农翻身讲操作系统3:硬盘与键盘的故事

简介: 序 我知道CPU和内存是计算机的核心, 毕竟所有的运算最后都得通过他们俩来完成, CPU从内存里要取一条指令, 做计算,然后再写回内存, 如此周而复始。

我知道CPU和内存是计算机的核心, 毕竟所有的运算最后都得通过他们俩来完成, CPU从内存里要取一条指令, 做计算,然后再写回内存, 如此周而复始。

 

但是这俩货却瞧不起我,说这是什么年代了,还在用机械式操作, 读写数据的时候, 还得一个磁头在多个盘片上滑来滑去,找来找去, 速度慢的要死。


内存说:“CPU比我快100倍, 比你快100万倍,整个系统的速度都被你给拖慢了。”

 

这是典型的五十步笑百步。

 

他俩还嘲笑我很娇气,得真空、密闭、不能有浮尘、运行时不能震动, 一动就坏了。

 

但这俩二货总是会忘记他俩的最大问题,所以我只用一句就把他们俩给噎死: 你们俩断电了怎么办?

 

还有我的容量都是按TB, 甚至PB 来算的, 就你们俩那点容量,还笑我?

 

还有,没有我来存储程序,你们从哪儿得到程序, 难道要像牵牛星(如下图)一样, 手工拨动一排开关来输入程序吗?

其实我也很纳闷,  为什么你们人类造不出来一个能够断电存储的, 大容量的, 访问速度快的,当然还要便宜的硬盘来 ,你们不都上天了吗?要登陆火星了吗? 这些基础的材料怎么还无法突破?

 

我憧憬着这么一天的来临, 如果能制造出来了, CPU就可以直接访问硬盘了, 内存就一边凉快去吧。

 

在制造出来之前, 你们必须得容忍CPU-内存-硬盘之间的速度不匹配, 并且想出办法来解决这种速度的不匹配, 比如用缓存、 直接内存访问、 多进程/线程切换等等方法。

内部结构

其实我的内部是长这个样子的:

看到没有,我有很多个盘片像串糖葫芦一样被串在一个主轴上, 主轴带着他们疯狂的旋转。 

 

每个盘片都有很多一圈一圈的磁道, 每个磁道又分为一个一个的扇区。

 

多个盘片上的同一位置的磁道组成了一个柱面 (需要发挥一下你的想象力)

 

最后每个盘片上都有可以读写数据的磁头。

所以,如果你想访问我的数据,可以说: 把0柱面, 0磁头, 1扇区的数据给我拿来。 

 

我就把磁头挪到您指定的柱面,对每个磁盘来讲其实就是指定的磁道, 所以这叫“寻道时间

 

然后再旋转磁盘,让磁头指向您指定的扇区,这才能开始读取数据, 这叫“旋转时间”,转速快的硬盘能更快的旋转到特定扇区, 所以性能会更好些。

 

文件

当然,对于绝大部分人来说,都不想去了解什么柱面,磁头,扇区这些非人的术语,所以我为懒人们专门提供了一个叫做逻辑块的方式,你看到磁盘就是有一个个“块”组成的,编号为1, 2, 3, ..n 。

 

想取哪一块就取哪一块, 比如你说:把第1024号的“块" 的数据给我取过来, 我在内部就把1024转化成柱面,磁头,扇区, 按照上面说的方法寻道,旋转,读取数据。

 

但是这还远远不够,比方说你想写个文档,输入了很多字和图片,最后想存到我这个硬盘上,你该怎么操作?

 

一种方法是这样的:

: 硬盘,给我找20个空闲的磁盘块, 我想存我的文档

 

:空闲的磁盘块编号是1024, 2048, 2049 ,3000 ,......

 

: 把这些文字和图片存到这些磁盘块上

 

: 好的,存完了, 你得记住这些块啊, 这样下次才能读取。

 

: 拿一支笔把这些磁盘块编号都记到本子上

 

过了几天......

 

: 硬盘, 把1024,2048,2049,3000这些数据给我取出来,我要编辑。

 

: 好的,这是你的数据

 

没有人喜欢这种方式, 太折磨人了!每个人喜欢这么做:   

打开word -> 新建一个文件-> 输入文字和图片-> 保存到C盘“我的文档”目录下。

 

这个所谓的“文件”和“目录”就是我的杰作啊,你再也不需要和烦人的磁盘块打交道, 只需要记住你的文件名和路径, 一切工作交由我和操作系统老大来搞定。

 

我和老大商量好了,文件对人类来说是最小存储单位, 你想存任何东西,无论多么小,非得建个文件不可。

 

此外为了让这个世界整洁有序, 多个文件可以放到一个目录(其实也是个特殊的文件)里, 目录之上还可以有目录,形成一个树的结构。

 

文件这个东西是个伟大的发明, 我估计你们还得再用100年。

文件的存放

我日常的主要工作就是对目录和文件做操作,当然需要操作系统老大的配合,好吧,其实是老大在主导。

 

这其中最重要的一个问题怎么去记录各个文件都用到哪些磁盘块?

 

内存给我支了一招:你可以采用连续记录的方式啊, 就像这样。

文件1 占据磁盘块 1-3

文件2 占据磁盘块 8-12

文件3 占据磁盘块 15-20

 

内存说: 这种方法在随机访问文件是效率极好,因为你只要知道了开头和长度,就像数组一样可以随便访问, 就像CPU访问我一样, 只要给出地址,立刻就能定位到指定的位置。

 

我仔细想了想, 内存出的是一个损招, 比如说内存磁盘块 4 -7 ,以及13-14怎么没用?  

 

那是因为之前那里也有文件,后来被删除了,留下了空洞, 如果之后没有大小合适的文件过来,他们就永远空在那里了。

 

对我来说这是严重的浪费, 这是我不能容忍的。 

 

我说:“小样你以为我看不出来啊,你不就是嫉妒我容量大,让我浪费一点嘛”

 

内存坏笑了一下又说: 不喜欢也没关系嘛,试试采用链式啊:

 

这个文件从第一块磁盘开始,形成一个链 1->9->18->8->3   , 每一块空闲的磁盘都会得到充分的利用, 效率非常高。

 

我心想: 这些码农说的数据结构和算法还真是有用啊, 这里也用上链表了。

 

可是这种方式随机的访问效果太差, 每次都得从第一块开始,沿着绳子往后找,太痛苦了。

 

现在内存已经嘲笑我慢了,用这种很慢的办法,还不得笑死我?

 

操作系统老大说:“别听内存在那里BB了, 用索引式!”

例如第16号磁盘块专门用来存放文件属性以及该文件所使用的磁盘块。

 

老大把这个磁盘块叫做inode,通过它可以轻松的找到这个文件所使用的所有磁盘块, 无论是顺序访问还是随机访问都很快。

 

唯一的缺点是得用额外的磁盘块单独存放inode 。

 

我觉得挺好,没有十全十美的东西,折中达到平衡最重要 ! 就是它了!

我问老大:每个文件都需要有个inode来描述,每个目录是不是也需要一个?

”这是自然,和文件一样,每个目录也是一个inode, 其中有目录的属性,还有存放这个目录内容的磁盘块号,在磁盘块中才真正的存放着目录下的内容“

 

“举个例子来说吧: 有人要读取 /tmp/test.log这个文件, 查找次序是这样的:根目录inode-根目录磁盘块-tmp目录inode-tmp目录磁盘块-test.log的 inode-读取磁盘块”

 

(点击看大图)

内存说: “我赛,这也太绕了吧,比CPU访问我的数据麻烦多了, 硬盘,你要小心点, 这要是操作不当的很容易出乱子的。”

我心想内存这次没坑我,他提醒的对, 这操作确实有点复杂,读数据的时候还行, 如果是修改,尤其是删除就很容易出事,例如想删除上面的文件/tmp/test.log,需要这些步骤:

(1) 在目录中删除文件

(2) 释放inode 到空闲的节点池, 这样可以复用

(3) 将磁盘块释放到空闲的磁盘块池

在操作某一步的时候出现系统崩溃,那我这些个目录和文件就凌乱了, 可能会出现空间无法释放的情况。

老大说: “这确实比较烦, 不过也能解决,听说过数据库是怎么办的吗: 记录日志! ”

”就是把要做的事记录下来?“

“是的, 在做操作之前, 记录要做的事情,形成日志, 把他们成功写入磁盘以后再正式动手操作, 等到所有步骤都搞完,才可以擦除日志项。 你想想,如果执行到某一步崩溃, 系统重启时检查日志项,就知道哪些没做,哪些已经做了, 对于没做的日志,重新来一遍就是了” 。

其实说的很轻松,实施起来还是挺难的, 重新执行就意味着那些操作一定是可以重复执行, 并且不会带来破坏才行。

(码农翻身注:这叫做日志(Journal)文件系统)

管理空闲块

目录和文件的存储问题解决了, 接下来我需要一个大管家,把那些没有使用的、空白的、数量上亿的磁盘块给管理起来,只有这样, 新的文件来的时候,才能分配空间存储。

操作系统老大给我推荐了两位, 第一位主张是链式大法好, 无非就是把空闲磁盘块组成一个链表(又是链表!) ,但是我心里盘算了一下: 如果磁盘块号是32位的,每个块都得花费我32位的空间,如果我有5亿个空闲块, 那仅仅为了记录他们就要占用接近2G的磁盘空间! 这浪费可是有点大啊。

 

还有一位主张位图法, 这个方法更简单, 对每个磁盘块,如果已经被使用,那就标记为1, 没被使用就是0。 这样整个磁盘块就形成了一个由0和1组成的一个大位图。

 

由于每个磁盘块只用一个位来表示, 非常节省空间, 这个方案我喜欢!

文件系统

扯了这么多,是时候看一看全局了, 在你们程序员的眼中, 其实我是长这个样子的(拿你们崇拜的Linux ext2为例):

 

(点击看大图)

我这个硬盘主要由MBR(Master Boot Record)和各个磁盘分区组成。

MBR中的有 引导代码 和 磁盘分区表, 分区表中记录了每个分区的起始位置,以及哪个磁盘分区是活动分区, 这样系统就会找到它, 然后装载这个分区中的引导块,并执行之。

引导块将会装载存存储在本分区的操作系统。 需要注意的是,每个分区都有一个引导块,不管这个分区有没有操作系统,这是各大厂商的约定,是一种标准。

每个分区除了必须的引导块之外,又被分成多个块组。

在每个块组中你能看到熟悉的磁盘块位图和inode位图, 不用解释估计你也知道是干嘛的。

还有inode表(当然是存放文件和目录的inode 了)和真正的数据块。

对了, 我的磁盘分区表只有64个字节, 而每个分区项占用16个字节, 所以只能容纳4个分区。

如果你想用多于4个分区, 你就需要把其中一个设为扩展分区, 然后在其中继续划分成逻辑分区,想划几个就划分几个。

一般情况下,大家都喜欢把我划分成一个主分区+一个扩展分区, 在扩展分区中再需要划分。

(完)

 

 

二等公民

这个世界存在阶级歧视的, 我确信。

 

CPU和内存很明显是第一等公民, 这一对好基友占据着二环内最核心、最金贵的土地,居高临下对外发号施令, 从各处“抢劫”二进制数据到自己的地界来。

 

其他的都被归类到二等公民, 居住在5环以外, 统称为输入输出(I/O)设备。 

 

有些设备居无定所,通过USB临时接入到计算机,待个三五天就走,典型的“北漂”。

 

哦对了,我必须得说明, 硬盘的地位有点特殊,虽然它也是个I/O设备, 但是它存储着所有的程序和数据,包括操作系统老大!

 

虽然经常被CPU和内存嘲笑, 但硬盘应该属于1.5 等子民, 住在三环以里。 

 

(码农翻身注: 参见《我是一块硬盘》)

 

我是一个键盘,和我的兄弟鼠标一样,是个典型的输入设备,  像我这样的I/O设备多如牛毛,  比如显卡,声卡,网卡,打印机,扫描仪, CD-ROM等等。

 

操作系统老大把我们这些二等公民笼统的划分为两类:块设备和字符设备。

 

硬盘,CD-ROM,U盘是典型的块设备, 数据存储在固定大小的块中, 每个块都有一个地址,就样门牌号那样。 

 

像我,鼠标,打印机很明显就是字符设备,哪有什么块结构? 就是用一个个字符组成的流而已,也没有什么地址。

 

还有一种分法是存储设备(硬盘),传输设备(网卡,调制解调器)、人机交互设备(我和鼠标,显示器也算), 不管怎么划分,我们二等公民的身份都无法更改,也住不到二环去, 那里房价实在太贵了。

 

夜深人静的时候,我和二等公民朋友们经常探讨这个经久不衰的问题: 咱都是人,为啥就住不到二环去?

 

鼠标说: 这都是命啊, 计算机刚发明的时候,只有最基本的计算功能和存储功能,哪有什么显卡,声卡,网卡?    CPU和内存的祖先占据了二环,并且一直在那里经营至今, 现在都不知道是多少代了。 

 

我说: 唉,也是, 我们的祖先还是出现晚了, 没有占据好地界儿。 

 

网卡说: 这个不对的, 关键是我们没本事,干不了CPU的活啊。 

 

“那算啥, 我的GPU运算速度已经很厉害了, 很多超级计算机还用我做运算部件呢,知道不?” 显卡说。

 

鼠标说: 别想那么多,其实二环生活也不轻松,你看看一开机,CPU阿甘和内存就忙的不可开交, 累得要死。 像我和键盘, 尤其是你键盘, 除了码农写程序, 半天都不用一下, 还是知足吧。 

总线 和 端口

虽然我们是住在五环外的二等公民,但是CPU还得和我们打交道, 那CPU是怎么和我们联系的?

 

一种办法就是CPU和每个I/O设备之间都扯一根线,  有多少个设备就扯多少根,组成了一个以CPU为中心的星型布局, 很明显这样太麻烦了, 尤其是来了新设备怎么办? 

 

后来我们采用了“总线”这个概念,  大家都挂到这一条“总线”上, CPU想找谁了,就在上面吼一声。

 

当然这种方式也有缺点, 当一个人在总线上吼叫的时候会霸占总线, 其他人都得等待。 

 

还有这么多设备, CPU怎么知道谁是谁?   

 

首先肯定得给每个设备编号,比方说硬盘(更准确一点是,硬盘控制器)的编号是320 ,  图形控制器的编号是 3D0 ,  这个编号就被称为 IO端口。 

 

有时候CPU会更懒, 他和内存商量好, 把我们这些IO端口映射到内存中去, 这样CPU访问我们的时候,就像访问内存地址一样了。  称为 内存映射I/O

 

轮询(程序式I/O)

现在CPU知道了我们在哪儿,还知道我们的编号, 接下来就可以和我们通信了。 

 

这时候我们又遇到了那个老问题: CPU太快,而我们I/O设备太慢, 毕竟机械设备的速度是无法和电子设备相媲美的。 

 

比如说有个进程要读取硬盘上的文件, CPU代表该进程向磁盘控制器发出指令:

 

CPU: 硬盘硬盘, 把你第1023689号磁盘块的内容给我拿过来

 

硬盘: 好的

 

CPU :弄好了没有?

 

硬盘: 还没有

 

CPU :  弄好了没有? 

 

硬盘: 还没有还没有

 

CPU :  到底弄好没有? 快点

 

硬盘:都说过没弄好了 !

 

CPU :  弄好了没有? 

 

硬盘 : @#¥%……&&

 

CPU一直霸占着总线,不厌其烦的问硬盘弄好了没有, 别的啥事也不做, 这叫做轮询 , 或者叫做程序控制的I/O

 

由于CPU比硬盘快百万倍, 很明显CPU被浪费了。 

 

中断

大家都觉得这样不合适, 因为CPU忙着和硬盘“卿卿我我”,  把别的I/O都抛弃了, 像我,鼠标即使有什么数据等着CPU读取,他也不搭理我们。 

 

后来就改成了这种方式:

 

CPU: 硬盘硬盘, 把你第1023689号磁盘块的内容给我拿过来, 你弄好了以后告儿我一声。

 

硬盘: 我怎么告诉你啊

 

CPU: 我有一个中断请求线, 你弄完了可以往这个地方发信号, 我每次执行完一个指令都会检查。

 

硬盘 :好的

 

(CPU干别的事儿去了,当前进程A阻塞,另外一个就绪的进程B开始执行)

 

过了不知道多少纳秒。。。。。

 

硬盘: CPU, 数据好了,赶紧过来取走。

 

CPU : 稍等,我把当前的进程B给保存了, 然后就去处理。 

 

(CPU执行中断处理程序, 读取数据.....)

 

这就是“中断”方式,有了中断以后, 这些平时都不怎么露面的I/O设备都跳出了抢着发中断, “调戏”CPU,  CPU乱成了一团, 系统也乱成了一团。

 

CPU不胜其烦, 后来专门找了一个叫中断控制器的家伙专门负责协调,  这家伙确实厉害,一上场就说:

只有我才能给CPU发中断,  你们的中断请求统统发给我啊,我来裁决谁的优先级高,谁能“调戏”CPU。 

 

这样一来系统清净了。 

 

(码农翻身注: 这种“中断”的方式,其实就是一种异步的、事件驱动的处理思想,在计算机软硬件上应用非常广泛, 例如Node.js, AJAX等等 )

DMA

但是我知道, CPU和内存才是系统的核心,  CPU运算时候只认内存这个好基友,所以所有的数据不管是谁产生的, 不管是1.5等公民硬盘,还是2等公民键盘,鼠标等,  数据统统都得搬到内存去。  

 

这就给我们带来了一个挑战: 数据的搬运。

 

中断的方式对于小数据量传输是有效的, 像我是一个键盘,每次你按下一个键以后, 我就会发出一个中断告诉CPU,CPU就能发出指令,把这个一个键对应的字符搬到内存。

 

但是对于大数据量传输尤其是像硬盘这样的, CPU还得花费大量的时间和精力不断的发出指令,让磁盘控制器把数据从硬盘搬到内存去, 这相当于又陷入了程序式I/O的陷阱。

 

对于类似这样的情况,我们也有办法处理, 就是用一个DMA 控制器, 使用这个专用的处理器进行I/O设备和内存之间直接的数据传输, 脏活累活都被这个DMA给处理了。 

 

CPU: 硬盘硬盘, 把你第1023689号磁盘块的内容发送到内存的xyz 地址去,弄完了告诉我, 我去干别的事儿去了。

 

硬盘 : 好咧, DMA, 我这儿有数据要传输, 数据一共有4096个字节, 要传输到内存的xyz地址去。

 

DMA : 没问题  !

 

(DMA控制器开始哼哧哼哧的干活,把这4096个字节复制到xyz地址)

 

DMA : CPU, 数据已经在内存中了, 可以用了啊

 

CPU : 怪不得刚才有时候我没法使用总线, 是不是你小子霸占着啊?

 

DMA : 我不用总线怎么搬运数据到内存?   我也没有挪用几个时钟周期啊, 再说了你还能使用你的一级缓存和二级缓存不是?

 

CPU : 好吧, 看在你帮我干了这么多苦活累活,就算了吧。

键盘的工作原理

说了这么多别人的事儿, 也该说说我自己了。 

 

我和鼠标一样,是个非常简单的I/O 设备, 每次你按下和释放键盘上某个键的时候, 我就会产生一个扫描码,例如你按下A , 扫描码是"1E" ,  释放A ,扫描码是"9E"

 

我把这个扫描码放到0x60端口, 然后向CPU发中断, 当然得通过中断控制器了。   

 

CPU 会调用中断处理程序, 读这个0x60端口,取到扫描码, 翻译成ASCII码就可以使用了。 

 

那么问题来了, 假设系统中有好几个进程都在等待键盘的输入,  这个中断处理程序获得 ASCII 码发给谁呢?   怎么发过去呢?  

 

这个问题留给你了, 欢迎给我回复留言,正确答案有奖励啊。 


微信公众号【黄小斜】大厂程序员,互联网行业新知,终身学习践行者。关注后回复「Java」、「Python」、「C++」、「大数据」、「机器学习」、「算法」、「AI」、「Android」、「前端」、「iOS」、「考研」、「BAT」、「校招」、「笔试」、「面试」、「面经」、「计算机基础」、「LeetCode」 等关键字可以获取对应的免费学习资料。 


                     wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

 

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
9月前
|
存储 JavaScript 算法
重学操作系统----15 | 中断和中断向量:Java/js 等语言为什么可以捕获到键盘输入?
当你拿到一个问题时,需要冷静下来思考和探索解决方案。你可以查资料、看视频或者咨询专家,但是在这之前,你先要进行一定的思考和梳理,有的问题可以直接找到答案,有的问题却需要继续深挖寻找其背后的理论支撑。
131 0
|
9月前
|
Shell
编写脚本/root/bin/systeminfo.sh,显示当前主机系统信息,包括主机名,IPv4地址,操作系统版本,内核版本, CPU型号,内存大小,硬盘大小。
编写脚本/root/bin/systeminfo.sh,显示当前主机系统信息,包括主机名,IPv4地址,操作系统版本,内核版本, CPU型号,内存大小,硬盘大小。
119 0
|
开发框架 固态存储 NoSQL
小白如何选择阿里云服务器地域、操作系统、硬盘、带宽等配置
本文主要讲述的是自定义购买过程中选择地域、操作系统、硬盘、带宽等配置的建议及注意事项,可供新手用户选购阿里云服务器时参考。
236 0
小白如何选择阿里云服务器地域、操作系统、硬盘、带宽等配置
|
开发框架 固态存储 NoSQL
新手用户如何选择阿里云服务器地域、操作系统、硬盘、带宽等配置
本文主要讲述的是自定义购买过程中选择地域、操作系统、硬盘、带宽等配置的建议及注意事项,可供新手用户选购阿里云服务器时参考。
212 0
新手用户如何选择阿里云服务器地域、操作系统、硬盘、带宽等配置