计算机是如何工作的 一篇讲透 CPU 内存 编程语言 与操作系统的博客
这是一篇从"一个电子开关"出发,一步一步把 CPU、内存、编程语言、操作系统全部搭建起来的学习博客。读完之后,计算机对你而言不再是一个打不开的黑箱。全文配有可点击跳转的目录,文中所有表格、真值表、进制换算都来自课堂讲义,并已逐条核对、修正了原稿里的笔误。
目录
- [一 写在前面 我们为什么要理解计算机如何工作](#一 写在前面 我们为什么要理解计算机如何工作)
- [二 计算机发展史](#二 计算机发展史)
- [三 冯诺依曼体系结构](#三 冯诺依曼体系结构)
- [四 认识计算机的祖师爷 冯诺依曼](#四 认识计算机的祖师爷 冯诺依曼)
- [五 从电子开关到逻辑门](#五 从电子开关到逻辑门)
- [六 门电路 与门 或门 非门](#六 门电路 与门 或门 非门)
- [七 算术逻辑单元 ALU](#七 算术逻辑单元 ALU)
- [八 进制的理解](#八 进制的理解)
- [九 算术单元 搭建一个 8 位加法器](#九 算术单元 搭建一个 8 位加法器)
- [十 逻辑单元与 ALU 符号](#十 逻辑单元与 ALU 符号)
- [十一 寄存器 Register 与内存 RAM](#十一 寄存器 Register 与内存 RAM)
- [十二 控制单元 CU](#十二 控制单元 CU)
- [十三 指令 Instruction](#十三 指令 Instruction)
- [十四 CPU 的基本工作流程 指令周期](#十四 CPU 的基本工作流程 指令周期)
- [十五 时钟与 CPU 的诞生](#十五 时钟与 CPU 的诞生)
- [十六 CPU 部分要点小结](#十六 CPU 部分要点小结)
- [十七 编程语言 Program Language](#十七 编程语言 Program Language)
- [十八 程序 Program 与早期编程](#十八 程序 Program 与早期编程)
- [十九 编程语言的发展](#十九 编程语言的发展)
- [二十 操作系统 Operating System](#二十 操作系统 Operating System)
- [二十一 进程与进程控制块 PCB](#二十一 进程与进程控制块 PCB)
- [二十二 CPU 分配与内存分配](#二十二 CPU 分配与内存分配)
- [二十三 进程间通信 IPC](#二十三 进程间通信 IPC)
- [二十四 全文总结](#二十四 全文总结)
一 写在前面 我们为什么要理解计算机如何工作
在这阶段的课程里,我们会从软件工程师的视角来解释计算机是如何工作的。这里先把目标讲清楚:
- 我们的主要目标不是要求大家亲手造出一台计算机;
- 也不是介绍如何编写程序;
- 而是希望大家在了解计算机核心工作机制 之后,打破对计算机的神秘感,并且能理解我们平时编程时一些行为、动作背后的历史渊源。
建议大家配合 Crash Course Computer Science(计算机科学速成课)这套视频做更详尽的学习和了解,文字讲义与视频互为补充,效果最好。
很多人写了几年代码,仍然把计算机当成一个"输入程序就吐出结果"的黑箱。本文要做的,就是把这个黑箱一层层拆开:从最简单的电子开关开始,搭出门电路,再搭出能做加减法的 ALU,配上存储器和控制单元,最终拼出一个 CPU,并顺着它讲清楚编程语言、操作系统、进程这一整套概念到底是从哪里长出来的。
二 计算机发展史
"计算"这个需求,在人类历史中是广泛存在的。它的发展大体经历了一条清晰的脉络:
一般计算工具 → 机械计算机 → 电子计算机
从最早的算筹、算盘这类计算工具,到以齿轮、凸轮驱动的机械计算机,再到今天以电子线路为核心的电子计算机,人类对计算的需求,始终在驱动我们不断地发明、改善计算机。
我们所处的这个时代,是"电子计算机"的时代。它发展的潮流可以概括为三个方向:
- 更快速------单位时间能完成更多运算;
- 更稳定------长时间可靠运行,错误率更低;
- 更微型------体积越来越小,集成度越来越高。
计算机以后会如何发展,正等待着大家的努力去书写。
推荐延伸阅读:《计算机简史》。
三 冯诺依曼体系结构
现代的计算机,大多遵守冯诺依曼体系结构(Von Neumann Architecture)。它把一台计算机抽象成四大类部件,这是我们理解后面所有内容的地基:
- CPU 中央处理器:负责进行算术运算和逻辑判断,是整个计算机的"大脑";
- 存储器 :分为外存 和内存 ,用于存储数据,并且统一使用二进制方式存储;
- 输入设备:用户给计算机发号施令的设备,例如键盘、鼠标;
- 输出设备:计算机向用户汇报结果的设备,例如显示器、打印机。
这四类部件之间,存在两组非常重要的"跷跷板"关系,请记住它们,后文讲内存层级、讲抽象代价时都会回扣:
| 维度 | 大小关系 | 含义 |
|---|---|---|
| 存储空间 | 硬盘 > 内存 >> CPU | 越靠近 CPU 的存储,容量越小 |
| 数据访问速度 | CPU >> 内存 > 硬盘 | 越靠近 CPU 的存储,访问越快 |
也就是说,容量和速度是一对矛盾:CPU 内部寄存器最快却最小,硬盘最大却最慢,内存介于两者之间。整个计算机体系的大量设计,都是在为这对矛盾寻找平衡。
四 认识计算机的祖师爷 冯诺依曼
冯·诺依曼(John von Neumann,1903 年 12 月 28 日 --- 1957 年 2 月 8 日),美籍匈牙利数学家、计算机科学家、物理学家,是 20 世纪最重要的数学家之一。
他是布达佩斯大学的数学博士,是现代计算机、博弈论、核武器和生化武器等多个领域内的科学全才之一,被后人尊称为"现代计算机之父 ""博弈论之父"。
我们今天使用的计算机之所以是今天这个样子------程序和数据都以二进制形式同等地存放在内存里、由 CPU 逐条取出并执行------追根溯源,正是冯诺依曼体系结构奠定的范式。
五 从电子开关到逻辑门
接下来,我们用一个从无到有的过程,一步步搭建出一个 CPU。希望大家借助这个过程,真正理解 CPU、内存等计算机主要部件的工作原理。
第一步,是最底层的电子开关 ,最典型的代表是机械继电器(Mechanical Relay)。
一个电子开关本质上只有两种状态:通 与断 。如果把"通"记作 1(真 / TRUE)、"断"记作 0(假 / FALSE),那么通过控制开关的通断,我们就可以完成 1 位(bit) 的、看似无用的逻辑运算------但至少它工作起来了,不是吗?
如何把这样简陋的电子开关组合出真正有用的逻辑组件,是接下来要解决的问题。
补充:后来的真空管、晶体管,实质上完成的也是类似的工作------控制电流的通断------只是物理原理更加复杂。这里我们聚焦在逻辑层面,不去深入解读它们的物理细节。
六 门电路 与门 或门 非门
接下来我们学习如何使用电子开关构建一些有用的部件------门电路(Gate Circuit) 。门电路可以实现 1 位(bit) 的基本逻辑运算。下面是最基础的三种门,配上它们的真值表。
非门 NOT Gate
非门只有一个输入、一个输出,作用是把输入取反。
| 输入 | 输出 |
|---|---|
| TRUE | FALSE |
| FALSE | TRUE |
输入为真则输出为假,输入为假则输出为真,"非"就是"取反"。
与门 AND Gate
与门有两个输入、一个输出,只有当两个输入都为真时输出才为真。
| 输入 1 | 输入 2 | 输出 |
|---|---|---|
| TRUE | TRUE | TRUE |
| TRUE | FALSE | FALSE |
| FALSE | TRUE | FALSE |
| FALSE | FALSE | FALSE |
可以理解为"串联开关":两个开关都闭合,电路才接通。
或门 OR Gate
或门有两个输入、一个输出,只要至少有一个输入为真,输出就为真。
| 输入 1 | 输入 2 | 输出 |
|---|---|---|
| TRUE | TRUE | TRUE |
| TRUE | FALSE | TRUE |
| FALSE | TRUE | TRUE |
| FALSE | FALSE | FALSE |
可以理解为"并联开关":两个开关里只要有一个闭合,电路就接通。
一个关键的洞见:与、或、非这三种基本门,经过任意组合,就能表达任何逻辑关系。这正是后面能从"开关"一路搭建出"会算数的 CPU"的根本原因。
七 算术逻辑单元 ALU
ALU(Arithmetic & Logic Unit,算术逻辑单元) 是计算机中进行算术运算、逻辑运算的核心部件,堪称计算机的"数学大脑"。
接下来,我们用上一节构建好的逻辑门,来搭建一个属于自己的 ALU,理解它的工作模式,把它作为进一步理解现代计算机工作原理的基石。
业界著名的实物 ALU:Intel 74181 ,它是一个 4 位(bit) 的 ALU。我们后面要搭的,是一个 8 位的,比它更宽。
在正式搭 ALU 之前,我们得先解决一个前置问题:计算机用二进制存储数据,那"进制"到底是什么?
八 进制的理解
我们已经熟悉数字的各种表示了,这里再简单回顾一下进制。
十进制(decimal notation / 10 base notation)
十进制里每一位的"权重"是 10 的幂。以数字 183 为例:
| 10² | 10¹ | 10⁰ |
|---|---|---|
| 100's | 10's | 1's |
| 1 | 8 | 3 |
把它展开:
183 = 1 × 10² + 8 × 10¹ + 3 × 10⁰
= 1 × 100 + 8 × 10 + 3 × 1
二进制(binary notation / 2 base notation)
二进制里每一位的权重是 2 的幂。计算机用 8 位二进制表示一个字节,以 10110111 为例:
| 2⁷ | 2⁶ | 2⁵ | 2⁴ | 2³ | 2² | 2¹ | 2⁰ |
|---|---|---|---|---|---|---|---|
| 128's | 64's | 32's | 16's | 8's | 4's | 2's | 1's |
| 1 | 0 | 1 | 1 | 0 | 1 | 1 | 1 |
把它展开(只把为 1 的位加起来):
263 = 1 × 2⁷ + 1 × 2⁵ + 1 × 2⁴ + 1 × 2² + 1 × 2¹ + 1 × 2⁰
= 1 × 128 + 1 × 32 + 1 × 16 + 1 × 4 + 1 × 2 + 1 × 1
= 263
注:二进制 10110111 对应的十进制正是 263。理解了"每一位按权重展开再求和",任何进制之间的换算都只是同一套方法的反复运用。
有了进制的概念,我们就能用门电路去实现"把两个二进制数相加"的部件了。
九 算术单元 搭建一个 8 位加法器
算术单元(Arithmetic Unit) 负责计算机里所有的数字操作,比如四则运算------当然它能做的远不止这些。
这里我们以一个 8 位(bit)加法器(adder) 为例,演示整个搭建过程,其他运算器就不再逐一展开。
思路是层层递进的:
- 半加器:把两个 1 位二进制数相加,得到"本位和"与"进位"。它只需要一个异或门算本位、一个与门算进位。
- 全加器:在半加器基础上,多接收一个来自低位的"进位输入",从而能处理连续进位。
- 多位加法器(行波进位加法器):把 8 个全加器串起来,低位的进位接到高位的进位输入,一次就能把两个 8 位二进制数相加。
至此,一个 8 位加法器 就被我们从无到有制作了出来。
算术单元支持的操作当然远不止加法:通过继续组合逻辑门,算数单元可以做到加减乘除甚至更多的算术运算,但用一个加法器作为演示已经足够了。
实际上,乘法器和除法器的制作难度要高于加法器、减法器,有兴趣的同学可以尝试做更多了解。
十 逻辑单元与 ALU 符号
逻辑单元(Logic Unit) 主要用来进行逻辑操作 ,最基本的就是与、或、非操作,但不再局限于一位(bit)数的比较,而是可以对整字(如 8 位、32 位)逐位执行这些逻辑运算。
把算术单元和逻辑单元合在一起,就是我们所说的 ALU。在电路图里,ALU 通常用一个组合符号表示:左边是数据输入端和运算控制端,右边是运算结果输出端,中间标注它支持的各种算术与逻辑操作。
经过我们的努力,通过基本的逻辑门电路,一步步做出了一个 8 位 ALU------甚至比 Intel 74181 还要强大(Intel 74181 只是一个 4 位 ALU)。
当然,现代计算机中的 ALU 部件非常强大,复杂度远远超出我们的想象,32 位甚至 64 位 的 ALU 基本已经普及全球。但无论如何,再复杂的 ALU,也是芯片工程师像我们这样,一层又一层、一步又一步地把它抽象出来的。
ALU 是第一次把人类历史上的数学 和逻辑学两门学科有机地结合起来的部件,可以视为人类智慧发展的现代巅峰之一。
十一 寄存器 Register 与内存 RAM
光有 ALU 还远远不够------ALU 算出来的结果、要参与运算的数据,都需要有地方存放。没有存储部件,ALU 寸步难行。所以下面我们利用门电路,简单说明存储是怎么做出来的。
重要提醒:虽然电路图上没有明显标出来,但这些存储电路都必须保持通电 才能维持所存的数据。也就是说,这一类存储都是易失的(volatile)------一断电,数据就没了。这正是内存(RAM)"掉电即失"的根本原因。
存储的最小单元是触发器 / 门锁(Latch):利用门电路构成一个带反馈的回路,可以"锁住" 1 位状态。它有几个关键控制信号:
- 使能线(Enable):决定"现在能不能写"。
- 输入线:要写入的数据。
- 输出线:读出的数据。
工作效果是:使能线置位时 ,输入为 1 就保存 1,输入为 0 就保存 0;使能线不置位时,写入无效,原有数据保持不变。
把许多个这样的存储单元并排放 together,就构成了寄存器(Register)------CPU 内部一小撮速度极快、容量极小的存储单元,用来暂存指令、地址和运算数据。
内存 的构建要比单个寄存器复杂一些,但基本原理一致。用这种方式构建的内存被称为 RAM(Random Access Memory,随机访问存储器) ,它可以支持 O(1) 时间复杂度访问任意位置的数据。
这正是一个重要的硬件解释:为什么数组按下标访问是 O(1) 的------因为底层 RAM 本身就支持随机访问,定位到任何一个地址所花的时间是常数级的。
期间为了聚焦主线,我们隐藏了大量实现细节,对这部分感兴趣的同学可以在课后做深入学习。
十二 控制单元 CU
现在,我们有了 ALU,也有了存储。但这还不足以让计算机真正"工作起来"------总得有一个部件来指挥 ALU 该做哪种运算 。这个部件,就是控制单元 CU(Control Unit)。
关于 CU 如何由门电路从无到有地搭建,这里我们直接做抽象处理。大家只需要理解:CU 可以驱动 ALU 进行具体的计算工作即可。至于 CU 究竟是怎么驱动 ALU 一步步完成动作的,我们在下一节结合"指令"来详细论述。
至此,ALU + 存储 + CU 三大件已经齐备,缺的是把它们串起来的"命令"------也就是指令。
十三 指令 Instruction
在讲 CPU 工作流程之前,先介绍我们要用到的指令(instruction)。
所谓指令,就是指导 CPU 进行工作的命令。一条指令主要由两部分组成:
- 操作码(opcode) :表示这条指令要做什么动作(加?存?读?);
- 被操作数 :本条指令要操作的数据。它可能是一个内存地址,也可能是一个寄存器编号,等等。
这里有一个至关重要的认知:
指令本身也是一个数字,用二进制形式保存在内存的某个区域中。
也就是说,程序(一堆指令)和数据(一堆数字)在内存里是平起平坐、混在一起存放的------这正是冯诺依曼体系的核心特征之一。CPU 取出来的只是一串二进制位,到底把它当指令还是当数据,取决于此刻如何使用它。
下面给出一套用于演示的简化指令集:
| 指令 instruction | 功能说明 | 4 位 opcode | 操作地址或寄存器 |
|---|---|---|---|
| LOAD_A | 从 RAM 的指定地址,将数据加载到 A 寄存器 | 0010 | 4 位 RAM 地址 |
| LOAD_B | 从 RAM 的指定地址,将数据加载到 B 寄存器 | 0001 | 4 位 RAM 地址 |
| STORE_A | 将 A 寄存器的数据写入到指定地址 | (示例中省略) | 地址 |
| ADD | 将 A 和 B 寄存器中的数据相加 | (示例中省略) | 寄存器 ID |
十四 CPU 的基本工作流程 指令周期
接下来,我们演示一条指令运行的一个周期,希望大家掌握这个流程,并能自己把剩余指令跑完。
先给内存预置一批数据(地址 0 起):
| 地址 | 数据 | 地址 | 数据 |
|---|---|---|---|
| 0 | 00101110 | 8 | 00000000 |
| 1 | 00011111 | 9 | 00000000 |
| 2 | 10000100 | 10 | 00000000 |
| 3 | 01001101 | 11 | 00000000 |
| 4 | 00000000 | 12 | 00000000 |
| 5 | 00000000 | 13 | 00000000 |
| 6 | 00000000 | 14 | 00000011 |
| 7 | 00000000 | 15 | 00001110 |
以第一条指令为例,走一遍取指---执行周期
CPU 里有两个关键寄存器:
- PC(程序计数器) :记录"下一条要执行的指令在哪个地址",本例初始
PC = 0; - IR(指令寄存器):从内存取出来、正在被执行的那条指令。
按 PC = 0 去内存取指令,取出地址 0 的内容 00101110,写入 IR。于是 IR = 0010 1110 。现在 CU 来分析这条指令:
- 拆操作码 :高 4 位是
0010,查指令集可得这是 LOAD_A 操作。 - 拆操作数 :低 4 位是
1110,它对应的十进制是 14。 - 执行动作 :把 RAM 中地址为 14 的数据读出来,写入寄存器 A。查表,地址 14 的数据是
00000011,即十进制的 3。 - 写回 :寄存器 A =
0000 0011(即 3),寄存器 B 此时为 0。至此 CU 完成了 LOAD_A 这条指令的动作。
有趣的是:这条 LOAD_A 指令本身并没有用到 ALU。但这只是其中一类指令而已。
自己动手跑完剩下的指令
请大家尝试把剩余的指令自行运行一遍,观察并理解这个过程。比如:
00011111(地址 1):高 4 位0001= LOAD_B,低 4 位1111= 15,于是把地址 15 的数据00001110(即 14)读入寄存器 B;- 随后的 ADD 指令:把寄存器 A(3)与寄存器 B(14)相加,得到 17;
- 再用 STORE_A 把结果写回某个地址......
走完一遍,你会清楚地看到 PC 如何自动递增、指令如何被一条条取出并执行。
执行周期经过的阶段
总结下来,一条指令的执行周期大致经过:
- 取指令(Fetch):按 PC 从内存取指令到 IR,PC 自增;
- 分析指令(Decode):CU 拆分出操作码与操作数;
- 执行指令(Execute):驱动 ALU 运算,或在寄存器、内存之间搬运数据;
- 写回结果(Write back):把结果写入寄存器或内存。
当然,真实电子计算机里的 CPU 可不像我们这样靠人手动推动周期运转,而是靠背后一个时钟(Clock) 来周期性地驱动。延伸思考:知乎上有一个经典问题------"时钟频率是什么概念",值得顺着它理解主频、指令吞吐与性能之间的关系。
十五 时钟与 CPU 的诞生
最后,把这一路搭建的部件合拢:
ALU + CU + 寄存器 + 时钟 = 中央处理器(Central Processing Unit,简称 CPU)
时钟就像 CPU 的心跳,每"跳"一次,就推动整个机器走完一个节拍,让取指、分析、执行、写回有条不紊地向前推进。一个曾经充满神秘感的"中央处理器",此刻完完整整地摊在了我们面前。
十六 CPU 部分要点小结
把本节一些关键结论做成文字总结,务必记牢:
- CPU 中的 PC(程序计数器)寄存器,是决定 CPU 要执行哪一条指令的关键;
- 指令由 动作(操作码)+ 操作对象(被操作数) 组成;
- CPU 眼中只有指令,没有别的概念------它不会"理解"你的程序,它只是机械地取指令、执行指令。
这三句话,是理解一切"程序为什么这样跑""为什么某个写法会卡住 / 会快"的底层出发点。
十七 编程语言 Program Language
有了上一节亲手搭出来的 CPU 和内存,我们终于可以回头看看:我们早已熟悉的编程语言(例如 Java),到底是怎么和 CPU 指令对应起来的。
十八 程序 Program 与早期编程
什么是程序
所谓程序 ,就是一组指令,以及这组指令要处理的数据。
程序 = 指令 + 指令要处理的数据。
狭义上来说,程序对我们而言,通常表现为一组文件。
早期编程:用 0 和 1 写程序
先讲一个流传很广的趣味故事。大意是:很久以前有人 Win98 系统崩溃了,朋友介绍一位"高手"来修电脑。高手没有系统盘,却把电话线接到电脑的一个插孔上,进入 DOS,然后在电话拨号键盘上飞快地只按 0 和 1 两个键,按了好几个小时,最后系统真的被修好了。故事的主人公后来才明白------那位"高手"是用机器语言重新编了一个 98 系统。
这当然只是个段子,事情本身并不真实。但它道出了一个事实:
最早的电脑,要编程,是真的需要用 0、1 来编程的。
下图所示的 Altair 8800 ,是最早的一批微型计算机之一。用户需要通过控制一排排物理开关,一个一个 bit 地把程序"拨"进电脑里。
如果要求所有计算机用户都必须用二进制编程,那大家都要疯掉了------这个门槛实在太高了。于是,编程语言应运而生。
十九 编程语言的发展
汇编语言
为了提升编程效率,人类最早创造了汇编语言的概念。
汇编语言和机器语言(也就是指令)是完全一一对应的 ,只是相对于 0、1 这样的数字,发明了一些帮助人类记忆和理解的符号 把它对应起来------也就是我们前面看到的 LOAD_A、LOAD_B 这些助记符。程序员完成编程之后,需要使用汇编器(assembler) 把汇编语言翻译成机器语言。
高级语言
汇编虽然降低了记忆成本,但仍要求程序员掌握计算机硬件的所有知识;而且随着计算机厂商越来越多,一次编写的程序往往只适用于某一类计算机。这远远不够,于是更高级的语言诞生了。
高级语言屏蔽了硬件细节 ,让程序员可以站在更高的层面上思考业务。这里以 C 语言 为例,程序员写完程序后,需要:
源程序 --编译器(compiler)--> 汇编语言 --汇编器(assembler)--> 机器语言
(其间还要经过连接器 linker 把各模块拼成可执行文件)
借助封装的思想,学习编程变得越来越容易。不过有利则有弊:高度的抽象,导致很多程序员把计算机视为一个黑箱,完全无法理解自己的程序到底是怎么跑起来的。
希望我们大家,都不要做这种把计算机当黑箱的程序员------这也是我们花力气把 CPU 从开关开始搭一遍的意义所在。
我们使用的 Java 语言相对于 C 语言更高级一点,但基本抽象原理上没有太大差异,这里暂不展开。
关键提醒:高级语言的一条语句(Statement),往往要对应很多条指令(Instruction)才能完成。 这正是"为什么看似简单的一行代码,运行起来开销差异巨大"的硬件层面原因之一。
二十 操作系统 Operating System
操作系统 是一组对计算机资源进行管理的软件的统称。目前常见的操作系统有:Windows 系列、Unix 系列、Linux 系列、OSX 系列、Android 系列、iOS 系列、鸿蒙等。
操作系统的定位
操作系统有两个基本功能:
- 防止硬件被失控的应用程序滥用------避免某个程序把 CPU、内存、设备占满或搞坏;
- 向应用程序提供简单、一致的机制,去控制那些复杂且通常大相径庭的低级硬件设备------让程序员不必直接面对千差万别的硬件。
下面我们就以"操作系统到底管理了哪些资源"为主线,重点看进程这个核心概念。
二十一 进程与进程控制块 PCB
什么是进程 / 任务(Process / Task)
每个应用程序运行在现代操作系统之上时,操作系统会提供一种抽象:好像整个系统上只有这一个程序在运行,所有硬件资源都被它独占了。
这种"假象",正是通过抽象出进程(Process) 的概念来完成的。进程可以说是计算机科学中最重要、最成功的概念之一。
一句话定义:进程是操作系统对一个正在运行的程序的一种抽象。 换言之:
- 可以把进程看做程序的一次运行过程;
- 同时在操作系统内部,进程又是操作系统进行资源分配的基本单位。
进程控制块抽象 PCB(Process Control Block)
计算机内部要管理任何现实事物,都需要把它抽象成一组有关联的、互为一体的数据 。在 Java 语言里,我们正好可以用类 / 对象来描述这种特征。
java
// 以下代码是 Java 伪码,重在说明,无法直接运行
class PCB {
// 进程的唯一标识 ------ pid
// 进程关联的程序信息,例如是哪个程序、加载到内存中的哪个区域等
// 分配给该进程使用的各项资源
// 进度调度信息(留待下面讲解)
}
这样,每一个 PCB 对象,就代表着一个实实在在运行着的程序,也就是进程。
操作系统再通过线性表、搜索树等数据结构,把这些 PCB 对象组织起来,方便管理时进行增、删、查、改等操作。
二十二 CPU 分配与内存分配
操作系统对两类最关键的资源------CPU 和内存------采用了两种截然不同的分配模式。
CPU 分配 ------ 进程调度(Process Scheduling)
为了便于讨论和理解,我们大部分场景下都假设是一台单 CPU、单核的计算机。
操作系统对 CPU 资源 的分配,采用的是时间模式 :不同的进程在不同的时间段轮流去使用 CPU。同一瞬间其实只有一个进程真正占用 CPU,靠快速轮转制造出"大家都在同时跑"的并发假象。
内存分配 ------ 内存管理(Memory Manage)
操作系统对内存资源 的分配,采用的是空间模式 :不同进程使用内存中不同的区域,彼此之间互不干扰。
一张对照表帮你记牢两种模式:
| 资源 | 分配模式 | 含义 |
|---|---|---|
| CPU | 时间模式 | 不同进程在不同时间段轮流使用 |
| 内存 | 空间模式 | 不同进程占用内存中不同区域 |
二十三 进程间通信 IPC
如前所述,进程是操作系统进行资源分配的最小单位,这意味着各个进程之间本来是无法感受到对方存在的 ------这正是操作系统抽象出"进程"这一概念的初衷,由此带来了进程之间的隔离性(Isolation)。
但现代应用要完成一个复杂的业务需求,往往无法由单个进程独立完成,总需要进程与进程相互配合才能达成目的。这样一来,进程之间就产生了信息交换 的需求,进程间通信(Inter Process Communication,IPC) 的需求便应运而生。
目前主流操作系统提供的进程通信机制主要有以下几种:
- 管道
- 共享内存
- 文件
- 网络
- 信号量
- 信号
其中,网络 是一种相对特殊的 IPC 机制:它不仅支持同一台主机 上两个进程之间的通信,还支持同一网络内部、不在同一主机上的进程之间进行通信。也正因如此,网络既是进程通信的手段,也是分布式系统、互联网服务得以成立的基石。
二十四 全文总结
让我们把这条从头到尾的主线串起来:
- 从一个电子开关出发,用通断表示 1 位逻辑;
- 用开关组合出与、或、非 三种门电路,它们能表达任意逻辑;
- 用门电路搭出加法器 ,进而构成会算数的 ALU,第一次把数学与逻辑结合起来;
- 用门锁、触发器搭出寄存器与 RAM,给 ALU 配上"记性",并解释了数组下标 O(1) 的由来;
- 用 CU 控制单元 指挥 ALU 干活,用 PC / IR 这两个寄存器把"取指---分析---执行---写回"的指令周期串起来;
- ALU + CU + 寄存器 + 时钟 ,最终合成了我们熟悉的 CPU;
- 程序本就是"指令 + 数据",最早只能用 0、1 编程,于是有了汇编语言 ,又为屏蔽硬件而有了高级语言------但请记住,抽象越高越容易,也越容易把人变成把计算机当黑箱的程序员;
- 操作系统 在硬件之上提供资源管理:用进程 这个抽象制造"独占"假象,用 PCB 描述每个进程,用时间模式 分配 CPU、用空间模式 分配内存,并用管道、共享内存、文件、网络、信号量、信号 等 IPC 机制让隔离的进程彼此协作。
理解了这一切,你对计算机将不再充满神秘感。往后当你写下一行代码、看到一个性能现象、踩到一个并发或内存问题时,你都能想起它背后那条从"一个开关"开始的漫长链路------这,正是一位软件工程师该有的底层视野。