系统编程和网络编程初步
学习 Linux 是为了在 Linux 下进行系统级别和网络级别的编程。Linux 只是操作系统的代表,其他的 Windows、MacOS等也可以进行类似的编程模式,Linux 因为开源,内核源码公开,所以从 Linux 入手能更好地理解这一块。其他的闭源操作系统只能学习相关接口的调用。
系统级代码的特点:这些代码的背后很多都是操作系统或网络的知识。之前的代码都是围绕某个问题进行编程,而系统级代码都是围绕如何更好地利用 OS 的特性展开。
冯诺依曼体系结构
计算机领域有2位祖师爷,一位是冯 $\cdot$ 诺依曼,另一位是艾伦 · 图灵。
在计算机领域还有很多可以称祖师爷的,比如雷纳斯 · 托瓦兹是开源的祖师爷。
当代的计算机架构就是由冯诺依曼祖师爷定义的。这个计算机架构被现在的计算机采用,于是旧采用冯诺依曼的名字命名。
什么是体系结构?在《计算机组成原理》的书籍和相关课程中会提到。计算机组成即计算机由哪些硬件组成,部分场景会将体系结构说成是芯片架构,因为芯片是最核心的组件、硬件。
芯片架构目前主要用的硬件是 x86或x86/x64,这些都是因特尔构建的计算机体系,其他的比如手机用的是 ARM。
无论是常见的计算机,如笔记本;还是不常见的计算机,如服务器,甚至电子设备如遥控器,大部分都遵守冯诺依曼体系。
截至目前,我们所认识的计算机,都是由一堆硬件、组件组成:
-
输入单元:
包括键盘,鼠标,话筒,摄像头,扫描仪,写字板,USB,磁盘、SSD、声卡、网卡、显卡等。
关于磁盘:磁盘即硬盘,是外部设备的一种,且是永久存储介质。现代的笔记本电脑不再是磁盘,而是SSD(Solid State Disk或Solid State Drive,固态硬盘)。
SSD和磁盘在物理结构上不同,简单来说磁盘的原理是机械运动来刻录数据,而SSD是通过半导体芯片和电流结合存储信息。因为机械结构在运行速度上始终比不上电流(可以无限接近光速),所以SSD大面积替代了磁盘作为笔记本电脑的永久存储介质。
-
中央处理器(CPU):
含有运算器和控制器,以及各种寄存器和各种级别的缓存。关于CPU的学习重点在寄存器上。
-
输出单元:
用于展示计算机的处理信息,例如显示器,喇叭,打印机,磁盘,网卡,显卡等。
-
存储器:
指的是内存,可以理解为带电临时存储介质(非专业术语,不可用于学术论文),一旦电源断开内存存储的数据就会消失。
这些单元通过一系列的总线例如数据总线、地址总线和控制总线进行连接。且几乎所有的输入、输出设备都可以以某种方式插入主板中。
若对电脑进行拆机,可以查看主板的结构,可以看到风扇、内存条等构件,还可以看到主板提供的外置内存条的插口。
外置内存条最直接的应用是插卡游戏机,但游戏卡只能读。
为什么历史选择了冯氏结构
计算机的本质其实就是把数据给计算机,它在自身的内部做计算,计算完之后把结果再显示出来,冯诺依曼体系结构就是按这样的设计来进行复合的。
同一时期的体系结构比如哈佛结构(程序和数据分开存储)、神经网络模型(如M-P模型,人工智能的模型)等,却只有冯氏结构在竞争中胜出。原因很多,这里选其中一个方面进行描述。
从存储的角度看体系结构
CPU其实是有数据的临时存储能力的,包括少量的寄存器,还有对应的各种各样的cache缓存。
内存可以叫做掉电易失性存储单元,也就是说它一旦掉电了,那么最终我们的数据也就没了。
其他外设(外部设备),比如说磁盘, SSD、显卡、网卡和其他外设,也有数据的存储能力。只不过它们的存储能力是比较小的啊。而且其他的设备本身自带了一些类似于CPU、寄存器的核心部件,一般称作叫端口。
寄存器,缓存,内存,磁盘,固态硬盘,这些都有数据的存储能力。
所以计算机里几乎所有的设备都有存储数据的能力:
-
CPU也有寄存器和各种级别的缓存。
-
内存可以叫做掉电易失性存储单元,即它一旦掉电了,则内存存储的数据也就没了。
-
其他的设备,它们本身也是具有数据的存储能力的。设备本身自带了一些类似于CPU寄存器的东西,一般把它叫端口。
为什么我们要谈存储呢?因为所有的设备之间交互的时候,本质就是把数据从一个设备拷贝到另一个设备,所以存储的效率就直接决定了拷贝的效率,也就直接决定了设备和设备之间通信的效率。
拷贝效率本身就是设备和设备之间通信的效率,如果它的效率很高的话,我们的整机的计算机的效率就会比较高啊。
存储分级
计算机的存储体系存在存储分级的概念。即距离CPU越近(或和CPU交互越多)的存储单元呢,它的存储效率是越高的,那么距离CPU越远的,它的存储效率是越低的。
这里参考c语言的常用关键字-CSDN博客中的 register 的描述。可以看到,计算机中的存储结构存在很明显的存储分级以及金字塔的结构。这里我们的主角是 CPU 和内存。
一般存储效率高的存储单元(点名CPU、寄存器),往往成本比较高,即需要花更多的钱、更多的技术才能制造这个零件;而存储的效率比较低的存储单元(点名硬盘、各种外部设备),造价会比较便宜啊。内存呢是相对适中的。
CPU在进行通信的时候,更多的是从寄存器直接读取数据。所有的数据在被计算机拿到后,先得拿到到寄存器里,CPU从寄存器拿出这个过程对于人的感觉来说是很快的。但CPU再拿其他数据的话,有可能从内存里呢啊,然后再考虑从外设去拿。
CPU的速度非常快,若直接去外设里拿数据的话,那么它的效率会比从寄存器中拿低一些。
为什么要用存储分级
现代CPU的数据处理速度基本上是纳秒级别的,那么计算、存储、读取的时候都是这个级别的处理速度,而内存可以简单地理解成微秒或者是纳米级别。
输入、输出单元这些外设可以理解成它是毫秒级别,甚至秒级别。
从极端的角度考虑,例如全用效率高的和全用效率低的:
若全部用存取数据的效率快的零件组装计算机,就像《名侦探柯南》里乌丸莲耶的黄昏别馆,墙壁、地基等全部由纯金打造:
由上面的这些结论,如果我们订做一款计算机,都设计成大比例的寄存器或告诉缓存,不要内存,不要什么磁盘,那么此时所有的计算机零部件全部采用高价的。这种高效率的计算机设计方式,在技术上它是肯定可以的,无非就是请更多的工程师花更多钱去做这台计算机。
如果所有的计算机都这样搞了,计算机会变得非常贵啊,普通老百姓就买不起了,就不会有这么多的人有电脑。不会有这么多人有电脑,就不会有这么多人上网,不会有这么多人上网,就不会存在用户量有上亿的,上十亿的大型互联网公司存在,也就不会有现在欣欣向荣的各种互联网经济。
所以互联网的蓬勃发展本质的原因,其中有一个重要的因素就是这个计算机一效率要不差,并且它还不能贵啊。那么这样的话才能够让我们的互联网那么到来,然后然后给我们提供各种便利的服务。
虽然外设的数据存取很慢,但若全部把计算机的部件搞成便宜货(即使用造价便宜的CPU)来匹配现在的外设,效率很慢,并没有满足用户需求。
在数据层面上,当代CPU一般是不和外设进行交互。尽管确实存在有将外设的数据读到CPU中,经过处理后再读到内存,但现在不是,现在已经有了独立的各种各样的设备,CPU只要发一个信号,那些设备自动帮我们去搬运数据。
从木桶原理考虑:
木桶原理:由木片构成的桶,能装的水取决于最短的那块木板。若今天允许CPU直接和外设交互的话,那么整机的效率是最慢的,整机的效率最后肯定和各种外设的效率是保持一致的。
CPU一般不和外设交互呢,主要原因是受木桶原理的影响,导致整机效率降低。
所以CPU一般它在拿数据时不考虑寄存器,不考虑什么缓存,它至少要能从内存里拿数据。
不管学任何编程语言,不管学的是 C++ 还是 Java,包括汇编,会发现所有的编程定义变量、对象全部都是在内存当中进行申请的。申请完之后,这个变量、对象,要经过CPU去执行访问。
所以所有的变量都是在内存里,原因就在这里,因为硬件层面上, CPU只能和内存直接交互。
若要让CPU去进行对一个,或一批,或一段,或一大堆数据进行对应的计算操作的话,我们一定要想办法先把数据从对应的输入单元,”搬运“ 到内存中,那么在输入单元往内存里 ”搬运“ 数据的同时,CPU可以同时在进行着其他的计算,当它计算完之后,内存中的数据也”搬运“ 好了,此时 CPU 和内存之间的工作时间就交叉起来了,时间就互相重叠了,效率自然而然就高了。
内存可是有 4 GB、8 GB 的存储空间,而CPU可能最多只能计算100 MB 的数据。然后操作系统识别到在这个用计算机里,未来可能还会要用第 200 到第 300 MB 的数据,所以操作系统直接把几千 MB 的数据 “搬运” 过来。
也就是在拷贝数据从输入设备到内存的同时,CPU也在跟内存打交道,
所以他的这个时间上呢,就可能出现重叠,从而提高效率。
最终CPU是整个系统最快的核心部件,又因为CPU不再和外设打交道了,它直接和内存打交道,内存虽然不是整个体结构里最快的,但是它也不慢。
也就是说在不违反木桶原理这个客观规律的基础上,直接仿问硬件造成的短板比直接访问内存的短板短,即使效率依旧赶不上纯CPU这样的效率,但相比较于纯外设,它的效率就提高了。
所以 CPU 一般优先和内存进行数据交流。
内存既跟外设打交道,又跟CPU打交道,正是因为它的存在呢,在纯硬件层面就把内存当个超大的缓存,至于这个缓存谁来维护,什么时候把数据放到内存里,什么时候把数据显示到输出设备,这些软件逻辑方面的工作由操作系统来做。
内存的角色定位
内存就是一个硬件级别的缓存。所有的外设,包括输出、输入设备,把数据 ”搬运“ 到内存的同时,内存也在将数据送进 CPU 进行计算,不同设备工作的时间就重叠了。
当 CPU 计算出结果,CPU可以将数据写到内存里,然后定期把数据输出到外设并刷新数据,刷新的时候呢,再进行IO访问的同时,CPU可以也进行正常的数据计算,这就是我们所对应的数据缓存。
写进度条的时候,会发现数据是你如果不把它强制刷新或者输出
\n或\r,它数据就是出不来。原因就是因为CPU把数据先写到内存了,而不是 “ 数据直接从CPU送到外设” ,然后再由内存做刷新。在类似的场景刷新的时机就很重要,比如说这个计算机不忙了,就顺便做一做。
绝大部分的现代计算机基于冯诺依曼体系结构,本质是用比较少的钱,做出来效率不错的计算机。比较少的钱就体现在输入输出单元,它可以很慢了。然后呢,那么内存那么价格也不能接受。
至于CPU,用少量的寄存器即可完成日常所需的运算速度,也能很好地控制整个电脑的价格,但同时整机的一个效率达不到CPU,但也不至于差到外设的这种级别,而是以内存的速度为主。使用这样的存储分级结构,就能用少量的钱做出效率不错的计算机。这就叫做性价比比较高。正因为它的性价比比较高,所以才为我们当代计算机提供了效率不差并且还不贵的计算机。
所以内存充当 CPU 和各种外设沟通的 ”中间角色“ 。更细节的地方后续有机会再介绍。
存储分级的现实意义
程序在运行之前得先加载到内存,因为程序里面呢无非就是代码或者是数据。就是程序员写的类,写的函数啊,写的循环,写的判断,这叫做程序员写的代码;而数据就是你在栈上开辟的空间。定的全局变量,静态成员,这些都叫做数据,只要是代码加数据,最终都要由CPU来进行执行或处理。
可以理解为程序运行起来,就是CPU在运行程序的代码。但其实 CPU 只认识二进制。
CPU 能够处理的前提条件是 CPU 先读取到这些代码和数据,而 CPU 只和内存有数据层面(二进制代码或状态)的交互。
所以代码形成了可执行程序(拓展名一般为 .exe),可执行程序本质就是一个文件,而这个文件在磁盘也就是外设中保存。而CPU最终是要来执行处理代码数据的,但是CPU只和内存打交道,所以就注定了必须在硬件层面上把程序从磁盘调动并将数据等加入内存,这个时候程序才能跑起来,
所以本质是因为叫做体系结构决定的。数据放到磁盘上则是通过文件操作进行。
所以编译代码(比如 c 语言到可执行程序)的本质就是:编译器把代码文本文件打开,然后把文件里面的代码全部读到编辑程序内部,编译器编译程序内部的代码,将代码翻译成二进制,然后再写回到磁盘的二进制文件。等需要时再执行这个二进制文件。
总结
冯诺依曼体系结构是在通用性、简单性和成本效益之间取得的最佳平衡点。它不是最快的,也不是最并行的,但它是最普适、最容易实现和普及的。
体系结构代入现象进行理解
QQ聊天
假设甲、乙两人,假设甲在西安,乙在北京,他们都登上QQ这个软件,打开联系对方的对话框。然后甲把和乙对话的对话框打开之后,然后紧接着甲给乙发了一条消息说 “在吗” 。
在硬件层面上,解释一下 “在吗” 在设备层面上的数据流动,即从开始甲从键盘里输这个消息,最后这个消息打在了乙的显示器上,整个的过程的梳理:
这里假设没人学过网络,所以忽略掉网络啊,把网络的就当成一个简单的自动处理信息的黑盒,知道它有传送信息的功能就可以。
首先甲乙都登上 QQ 这个软件的前提条件是甲乙各自都得有一台电脑(不考虑无法安装 QQ 的计算机),现在就假设甲乙各自有一台电脑,本质就是双方各自有一个冯诺依曼结构的计算机(其他结构也可以)。
信息 “在吗” 在数据流动的时候,肯定要在甲的电脑上就流动,然后到网络,再到乙的电脑(假设没学过网络,所以忽视网络的传输细节)。
首先甲在输入 “在吗” 的时候肯定是通过输入设备(至于是键盘还是语音识别即声卡不重要,都是输入设备),这个信息最终输入到甲的计算机的内存中。
甲首先得启动QQ这个软件, 本质是QQ这个软件在甲的电脑上再运行起来了。当它先运行起来之后,QQ便加载到内存里。
所以输入信息给QQ,其实就是输入信息给内存。
再然后,“在吗” 的数据就从键盘输入到了存储器。但到存储器的信息不止 “你好”,还包括昵称,头像,发送时间等等诸多信息。所以需要把多个信息组合起来,形成一个完整的信息对象(结构体或类),然后再发送给乙。这个组合的过程至少得有类似于字符串拷贝拼接的功能。
所以这里就注定了信息要由CPU参与计算,形成一个可以发送的 “在吗” 的消息及其他信息的组合对象。根据冯诺依曼体系结构,一定是从存储器读进入CPU进行处理,处理完再写回内存,之后再把 “在吗” 的消息对象输出到对应的一个叫做输出设备上,这个输出设备就只能是网卡(和网络有关才能发送信息出去)。
数据经过网络的过程到网络时再做相关介绍。然后乙从网络里收到了 “在吗” 这条消息,这个消息也是通过乙的电脑的输入设备得到,而且和网络有关的很可能也是网卡。之后网卡将 “在吗” 这个信息对象输入存储器中,而且还是内存中。再之后就是经过 CPU 对这个 “你好” 对象的解包装,之后再把它写回存储器,再把有关的信息输出到乙的电脑的输出设备上,这个输出设备就是显示器(因为最终目的是让乙看到信息)。
而且 “在吗“ 不止通过网卡和网络发送出去,还会打印在屏幕上,所以输出设备不一定只有一个,包装好的数据是一式多份进行输出的。根据这个原理,我们甚至可以做一个简单的聊天软件(以后有机会再尝试)。
举这个案例是为了能意识到其他设备也会充当输入设备,只要是输入设备,数据一定是优先先给内存的,而不是直接到CPU上的。
活学活用QQ的案例
这里就不举更多的案例了。
往后在思考或者是解释一些计算机现象的时候,觉得自己描述不清楚,只要能够想到硬件层面上的一些现象,并借助这些现象进行解释,就可以将问题化繁为简。在计算机体系结构(大部分情况是冯诺依曼,不排除少部分特殊体系结构)的计算机下的所有的软件(这里只举例多台设备的通信软件)都在这个硬件的逻辑当中去思考。
类似防洪治沙,最后水怎么走,完全全取决于河道它怎么修,修好了,不管河怎么走,水怎么流啊,都绕不开这个河道。