通俗视角说系统(Operation System)
各位好,我是回声,今天我们聊一聊操作系统。
开始之前,我仍然需要负责人的和各位说,在此文章处于誊写的状态时,我不能保证文章所有概念、术语以及专业的正确性。
在此文章写完以及发出的一段时间里,我会把它呈递给我的同学、前辈等同行业爱好者进行阅览,并即时改正其中的问题,若是问题过多,我则会删除该文章以防止它过度的误导其他学习者。当然阅读的各位也可以在评论区中书写你所认定的错误之处,只是烦请各位指正错误的时候不要焦躁,讨论技术咱们有话好说,切勿以带着任何令人不悦的口吻来讨论。我虽然才疏学浅但并不缺可以和谐讨论并共同学习的朋友、前辈与师长。
当然,虽然我尚不能说我的理解都是正确的,但在我自己的认知体系里可以做到自圆其说,如果它的存在能为一些同行业者带来不错的启事或者为一些学习者带来一个不错的理解体验,那都是最令我开心的事。
这篇文章我姑且放在了我的【深入浅出C++】专栏里,但是今天我一点都不打算谈C++的事,甚至有关代码的事我也只会寥寥几句带过。在这篇文章中,我的内容中会尽量使用很通俗的说法,以至于这之中专业术语肯定会出现不严谨的地方,但不会影响理解,还望各位谅解。
好,那既然前话都说完了,让咱们正式开始:
程序员有三大浪漫,分别对应着计算机行业三个可以说是最难的专业方向,从我自己的角度上说,我会加一个服务器开发,这样一来总结出来的四个专业方向:
操作系统开发
编译器研发
图形学
高性能服务器开发
几乎可以说得上是计算机行业中,最常有大神大佬涉足的领域。一般的,从事这四个领域的程序员(其实能完美驾驭这四个领域的人,应该用专业的工程师来称呼了)收入也都不低。
对于我个人而言哈,这四个领域我肯定是完完全全的门外汉,跟那些耕耘其中的工程师相比,说是渣渣也不为过。但作为一个C++的学习者和爱好者,修炼内功建立底层的基本认知并助力我们平日里对开发的深层次理解却是一件美事。
学习的过程里,咱们总是遇上形形色色的,完全不会的东西。打个比方,就像最初做算法题的时候,虽然死活就是不会吧,但似乎脑子里还能隐约有这个问题的解决方向,似乎使使劲就可以将之攻破,哪怕你无法实操,却也能猜出其大致的运作原理,我会称呼这种问题为可预知的问题。而自然另一类问题,则似乎完全被封装在了一个黑盒中,从原理到概念上就完全不知道其内部是怎么运作的,想破脑袋都想不出个所以然,我一般称呼其为不可预知的问题。今天要聊的操作系统,在很长的一段时间里,对我而言都是这样的不可预知的问题。试想一下,凭什么咱们最初书写的hello world代码最后能成为一个大型的,带精妙算法的,带各种华丽视觉特效的程序?甚至可以说,你写的那个hello world都不是靠纯粹的编程语言写出来的,而是所谓系统调用。假设一个语言保留语法却删除了所有系统调用后,让显示器输出几个简单字符的程序又应该如何实现?
幸运的是,虽然我主攻的专业是C++的程序开发,却也担任着半个电气工程师的职务,以至于我得以有限的接触硬件,虽然不及嵌入式或是电力电子那般底层吧,但我个人觉得,这些硬件的基础认知还是帮助我形成了现在我对操作系统的一个初步的概念。
对于大多数人而言,咱们誊写的程序无论多么的复杂,多么的高级,但简略的概括程序组成其实就两部分:【由编程语言的语法提供的内存操作】和【系统调用】。当然,这个时候各位不要跟我较真什么泛型编程之类的东西,那玩意本质上都是编译器帮咱们写代码,而代码的内容最终超不过上述的两个范畴。
以编译型语言来说,将上述的内容转换到汇编的代码中后,【系统调用】的部分仍然被封装了起来,【内存的数学操作】则全部转换成汇编代码,总结一下汇编代码的所有内容,其实就是下面几个:
内存及寄存器数学操作,如mov、lea、add等
代码跳转,如jmp等
数学比较,如cmp等
(如pop、push、ret、call等其实也都可以用上面那些基础指令实现)
其中数学比较和代码跳转配合起来就是十分基本的if else语法,也包括for、while循环(本质上for就是拿while写的)。
咱们上C语言的第一节课时就被教过:计算机,准确的说是CPU执行的是二进制代码。因为二进制代码是可以和汇编一一对应的,咱们暂且将汇编当成CPU最终执行的语言,那么凭借这些基本的操作,系统调用的功能是怎么实现的?难道说汇编还有其他的语法,隐藏了它直接控制硬件的一些隐性操作?
这当然是否定的,CPU能执行的代码跳出大圈了就那几个,可凭借这些个东西,凭什么显示器上能打印hello world?凭什么内存里的数字摆来摆去就能让显示器又干这又干那?那同为系统调用的网络通信呢?线程管理呢?
在聊这个之前,咱们回归一些本质且简单的“公理”,那就是:计算机是一台电子、或者电气设备。
电气设备是什么东西?当我思考最基础的电子和电气设备时,我想到的就是一个灯泡或是说LED,你给他通上电,它就会亮,映射为其他硬件其实也是,你给他通电,或者用更专业的说法,叫使能,你给了设备一个使能,这个硬件就开始以他电路设计的功能开始工作。
不要小看这个似乎谁都知道的公理,毕竟计算机归根结底仍然是一个电气设备,它是要符合这个公理的。当能够理解消化这个公理的时候,就不难理解为什么计算机被设计成了二进制。它可不想人类一般有阴晴圆缺,而就是一个二极管,对于某一个电气部件而言,它要么有电,要么没电,这即是1或者0。请不要较真说这世界上有一个叫‘模拟量’的存在,因为任何模拟量在进入系统内部的时候,都会经历一个叫做AD转换的过程转换成数字量。
以这个公理为出发点,我们回头看一看CPU,最终编译成的二进制代码就是这台计算机的大脑可以执行的最终物。那么,为什么CPU可以执行代码?
在系统编程课程上,我们经常会遇到那些使用起来特别蹩手蹩脚的系统调用,它的参数或者返回可能是一个四字节整形,但用意却绝对不是让我们把他当成整形用,而是将之视作一个32位的比特序列,进而通过指定位是0(false)还是1(true)来判断、设定系统运行的情况亦或是某种状态。
结合上述系统调用的这种思想,再去思考CPU的运行,代码的最终展示如果都是二进制的话,那么组成它内容的就会是true和false的通断序列,对于一个硬件而言,这不就是一个输入阵列吗?学过数字电路的同学肯定不难理解,如果将CPU当做是一个有引脚的芯片(虽然它就是),那么每一条指令都会标记处它的输入引脚何处应该是0何处应该是1。换句话说就是:每一条指令都会承载这个CPU芯片的输入引脚何处应该使能何处不应该使能的记录。最终让代码执行的过程就可以理解为:为CPU这个硬件的指定引脚进行使能的过程,而不同的使能组合,则可以让CPU的运行产生不同的结果,咱们所誊写的代码,最终以这样的形式,让CPU按步骤让指定的引脚通断电来运行其不一样的功能。
需要注意,我们简单的将CPU看作是一个有许多引脚的芯片,也可以将它看作是一个有一大堆switch钮的设备,代码最终编译成的二进制代码成为了一个使能序列,CPU执行某一次代码的过程就可以视作它按着那个使能序列拨动了指定的switch钮。不需要将模型复杂化的考虑它的内部构造,如控制器或是运算器等,因为如果只是将之抽象成为一个拨下指定switch钮就可以运行指定功能的设备时,它作为硬件的内部构造都是一个前文中所说的可预知的问题。
对于任何一个简单的设备,芯片而言,它有输入,就会有输出,他的【输入】如果是【数个输入引脚上有电和无电的有规则组合】的话,【输出】则自然而然就是【数个输出引脚有电和无电的有规则组合】。请注意,这些被【】括起的所谓什么什么规则的组合,与使能组合、使能序列、通断组合、通断序列这些前面出现的名词都是同义词。
如果是学过数字电路的同学,应该能知道我所说的是一件很简单就能理解的事。从原理上说,当你想设计一个芯片【有怎样的输入就会产生有某种规律的输出时】,都可以通过列真值表和卡诺图等数学手段推导最终这些个输入引脚应该以什么样的电气方式和与或非门等门电路进行连接并最终接到输出引脚上。
在电路和硬件上,一个芯片负责一个简单的运算功能,它的输出完全可以作为输入给到另一个芯片上,或是某一个输出引脚直接借由继电器原理(即某两个触点通电后能控制一个开关的通断)控制某个高功率设备的通电。借由这样几乎是套娃的组合,每个芯片、硬件之间各个处理好自己的工作并将结果传给下一个芯片、硬件,最终使一个复杂功能的设备就可以组合起来,而后来,我们将之称呼为模块。
上面说了不少东西,是为了让各位能够理解,将CPU视作上述的硬件时,它就会是一个虽然复杂但是可以理解其原理的模型了。
现在我们把注意力拉回来,如果CPU得到了一个指令,这些指令的本质是让CPU这个芯片的特定引脚置为了ture。我们假设,CPU按着某个指令将特定引脚设定为true后,它执行了一个功能,这个功能是【将寄存器A的值写入内存地址B】。有些同学不用感到疑惑,因为寄存器A和内存地址B的具体信息,全被以输入的形式囊括在了CPU执行的那段机械码里,指定引脚的通断最终只能让寄存器A和内存地址B得到相应的操作。
计算机基础里有一个应该很多人都见过的存储速度排名:
寄存器 > cache > 内存(主存) > 外设(如硬盘等) > 分布式存储系统
CPU可以很容易获得寄存器A的值,因为寄存器这硬件就长在CPU上,但CPU想要将这个值写在内存里就会麻烦了。它需要借助总线来完成这个操作。可以说,CPU和所有它要控制的硬件,几乎都是使用总线来连接的。也同时可以说,CPU的所有输出操作,最后都会落实在总线上。我们到此为止就可以用一个简单的模型来概括CPU的工作:
它通过二进制的指令获得输入引脚的通断序列,在经过一系列的内部电路运作之后,最终控制总线向其他设备输出电信号,而那些设备通过总线得到了信息后,在经历一部分自己的硬件需要进行的操作后,还会返回电信号给CPU,自然而然也是通过总线。这不难理解,CPU通过总线相向内存要某一个地址的数据,内存自然而然就得返回这个地址的数据,这是一个标准的【读操作】,不难想象,哪怕是【写操作】,CPU也需要从这些设备中得到它们是否运行成功。
写到这里,各位不觉得这似乎已经是client和server之间的最基础架构了不是吗?但在接着说之前,我们借由总线来聊一聊电线。
在电学中,电线只有两个用途:
1、通电,或者说使能
2、通信
通电太常见了,我用两根电线,给一个灯泡一正一副接在电源上,它亮了,因为我给它通电了,我给它使能了,大部分的电缆、电线基本都是这个。
通信则是传了一段电信号,这段持续的电信号可能忽高忽低,可以被硬件识别成0或者1的序列,这些序列能承载一定的信息和内容,并被硬件接受后放在某个存储区或是缓存区里,这就是通信,像网线、串口线,基本都是用来通信的电线。
那么,总线是什么线呢?它能是通电线或者使能线吗?
总线当然不是使能线,总线是通信线。
毕竟,通信线能够以更少的物理线路传递更多的信息。使能线的通断终究只能承载0或者1的信息,当CPU与诸多外设连接的时候,仅能承载真假两个状态信息的使能线在海量需要传递的信息面前是不够用的。CPU的大小就那么大,需要吧使能线设计的多么纤细,又或者需要多少使能线,才能构筑出CPU与所有外设的连接。而如果是用使能线,CPU又需要在内部额外设计多少电路结构?这些是我的个人想法。
文章誊写至此还未将所讨论内容完全讲述清楚,考虑到本文涉及的内容可能既有难度,又难以完全的验证,而且内容还十分的多且杂乱,所以我这里会先更新一部分,并最终通过文章修改的方式让此篇专栏更新修改至最终版。
文中的说法我仍然不能保证完全的正确,在文章发布和更新的期间,我会以各种方式来审查文章中的不严谨或是错误的地方以即时更改,各位同好也可以在评论区中讨论各位认为有错误的地方。当错误得到验证后我会更新文章,对于曾经极有误导性的内容,我会标注修改记录,但烦请各位礼貌讨论以及提意见。

