计算机是如何工作的
- 1. 计算机发展史
- 2. 计算机的基本组成
- 2.1 冯诺依曼体系结构
- 2.2 CPU的内部结构
- 2.3 指令
- 2.3.1 指令表
- 2.3.1.1 寄存器
- 2.3.2 CPU的工作流程
- 2.4 小结
- 3. 操作系统
- 3.1 核心功能
- 3.2 操作系统的软硬件结构
- 3.3 什么是进程 / 任务
- 3.4 进程管理
- 3.4.1 管理
- 3.4.2 PCB : 进程控制块
- 3.4.3 PID
- 3.5 进程里面的属性
- 3.5.1 内存指针
- 3.5.2 文件描述符表
- 3.6 进程的调度
- 3.6.1 进程的优先级
- 3.6.2 进程的状态
- 3.6.3 进程的记账信息
- 3.6.4 进程的上下文
- 3.7 进程的虚拟地址空间
- 3.8 进程间通信
1. 计算机发展史
人类历史上很多被发明的东西,最早的目的都是为了军事
计算机诞生于"普林斯顿大学",最初计算机的诞生是用来计算"弹道导弹"的轨迹 . 后来 , 就想能不能把这个成果给老百姓使用 , 这就成为了最初一代的计算机 .
2. 计算机的基本组成
2.1 冯诺依曼体系结构
- 有的设备既是输入设备也是输出设备
比如可触摸的显示屏,网卡
- CPU 也叫做中央处理器,它是计算机能够运行程序,能够进行算术,能够进行逻辑运算等操作的核心,CPU 可以理解为是计算机的大脑
CPU 是人类科技水平的巅峰之作,能和 CPU 相提并论的只有氢弹
之前的中美贸易战,限制的就是芯片的进出口
其实国内也可以做 CPU ,但是只不过没有世界一线 CPU 水平高,造芯片的过程中,需要一个重要的工具"光刻机",中国本来要向荷兰购买,却被美国从中作梗,就没买成
- CPU 和 显卡的区别
显卡和CPU本质上也没有什么差别 , 目前能够制造显卡的公司只有英伟达和 AMD ,其实英特尔也出了 , 效果还不知道怎么样?
CPU叫做通用计算芯片,可以应对各种计算场景
而显卡叫做专用计算芯片,特别擅长计算大规模浮点数,尤其是矩阵的运算
显卡还会被经常拿来"挖矿"
2.2 CPU的内部结构
我的 CPU 的规模是这样的
- CPU 内部本质上是由一系列的"门电路"来构成的
门电路通过半导体元件组成
上面这三张图 , 具体是怎么回事我们不用管 , 我们要知道 , CPU 是可以通过一系列的 “门电路” 构造出来的 .
那么有了加法器 , 我们的减法 乘法 除法都可以计算了
通过上面的"门电路" , 就构造出了"计算单元" , 那么"控制单元" "存储单元"也就都可以被构造出来 .
- 为什么 CPU 里面的制程越小越好 ?
一个 CPU 芯片的面积是固定的 , 如果制程越小 , 单个门电路体积就越小 , 整个 CPU 上面能够搭载的门电路数量就越多 , CPU上的功能模块就越多 , 算的就越快 .
也不是说越小越好 , 如果设计到 nm 级别 , 经典力学就已经处在失效的边缘了 .
"量子计算机"在咱们有生之年应该还是可以看到的 . 他的算力比经典计算机的算力还提升了好多个数量级
现在的 CPU 已经不仅仅追求小了 , 还追求要多核 , 实现并发编程 .
那为什么不直接造一个大一点的 CPU , 为啥还要把多个 CPU 拼起来呢 ?
这是因为 CPU 制作的越大,我们的合格率就相对越低。
总结一下 : CPU 由许多门电路组成 , 集成的程度会对算力有很大影响
2.3 指令
指令就是人给计算机发布的指令
举个栗子 : 你的室友让你去食堂带个包子, 如果有卖鸡蛋的 , 就买三个
其实这句话想表达的意思是带份包子 , 有卖鸡蛋的 , 再买三个鸡蛋
但是 , 有可能最后买了三个包子回去
所以如果你把这句话交代给计算机 , 计算机按照指令进行 , 但是他也搞不懂了 , 就会摆烂 , 停止运行 .
因此 , 你要是想让计算机准确无误的执行指令 , 那么你就要保证你的指令清楚无异议 , 把他要干的活指定好 , 其实这个过程就是"编程".
咱们写好的程序 , 里面就包含了许多的 “指令” , 咱们写的 C++ / Java 等程序这样的编程语言构成的代码 , 更接近于自然语言 , 但是计算机不认识这样的语言 , 计算机只认识二进制代码 , 所以就有一个环节 , 把人写的代码(高级语言)翻译成计算机能认识的指令(机器语言) .
那么 , 二进制指令到底长啥样呢 ?
2.3.1 指令表
这是一个简化版本 , 实际上计算机(CPU)上面支持的指令有很多
以上的规则是咱们自己设定的 , 真实的计算机也是有一个套路 , 只不过会更复杂一些 .
那么这里面有个寄存器的概念
2.3.1.1 寄存器
寄存器的功能和内存差不多 , 都是存储数据的组件 , 断电后消失
只不过寄存器不是一个单独的组件 , 而是长在 CPU 上面的一个部件 .
寄存器相对于内存来说 , 存储空间更小 , 访问速度更快 .
内存有的可以有几个G , 但是寄存器 , 一般就是几百个字节(也就是几K)
访问速度比内存又快了3-4个数量级
寄存器之所以这么小 , 是因为它的成本比内存还要高很多
寄存器起到的效果 , 只是辅助程序运行 , 存储程序运行中产生的一些中间结果
2.3.2 CPU的工作流程
CPU的计算是针对寄存器当中的值进行的
比如想要计算"10 + 20" , 需要以下几步 :
- 从内存中读取10到寄存器A
- 从内存中读取20到寄存器B
- 执行相加指令 , 把两个寄存器的值相加 , 保存到寄存器A当中
- 把寄存器A中的值写回内存
那么提到了内存 , 我们还需要了解一下什么是内存地址 ?
内存地址 : 其实就是把内存进行了编号
我们可以把内存想象成宿舍楼里面的寝室号 , 假如从0开始依次累加 , 那么对应的房间号 , 就可以看做内存地址
平时我们所说的程序 , 大多指的是一个可执行程序(.exe) , 存放在硬盘上的 , 是一个文件 , 当我们双击 exe 文件运行的时候 , 操作系统就会读取这个文件 , 把里面的关键信息加载到内存中 .
Java 代码也是如此 , 我们编写一个 Java 文件 , 他其实就是一个后缀名为 .java 的文件 , 编译之后生成 .class 文件 , JVM 就去加载 .class 文件 , .class文件里面保存的也是一些二进制文件(但是并不是原生的指令 , 还需要 JVM 翻译一下) , 然后就会被放到内存中
那么我们举个具体的例子 :
读取指令 -> 解析指令 -> 执行指令
我们人类的计算 , 直接 1+1 就出结果了 , 但是计算机需要经历这么多的步骤
但是要注意的是 , 这只是我们理想化出来的模型 , 实际上计算机运行的过程类似于流水线 , 看起来好像 CPU 读一个指令 , 再解析指令 , 再执行指令 , 然后再读下一条指令 … 但是实际上并不是 .
流水线的过程中 , 就涉及到一个关键环节 , 谁是第二条指令 ?
如果只是单纯的顺序语句 , 第二条指令在第一条指令的基础上往后累加即可
但是如果涉及到了判断 循环 函数调用等等 , 程序计数器就不是单纯的累加 , 就可能需要跳过许多地址
CPU 就想了一个办法 , 我虽然不知道下一条指令是什么 , 但是我可以去猜一下 .
猜中了 , 就继续工作 . 没猜中 , 再按照实际情况再去读一遍指令就行了
这就叫做分支预测
CPU就是按照流水线的方式进行工作的 , 各个流水线之间互相协作 , 而时钟周期就可以保证他们有节奏的配合 , 比如拔河的时候 , 负责指挥 / 喊口号的人就是时钟周期 .
1.8 GHz 代表 CPU 1s就有18亿个时钟周期
每个指令的执行都需要消耗几个时钟周期的 , 但是在流水线下 , 我们就可以简单地看成一个指令消耗了一个时钟周期(因为同一时刻执行多条指令)
2.4 小结
- CPU 本身是由一大堆的 "门电路"构成
- CPU 内部的集成程度越高 , 就可以认为它的计算能力越强
- CPU 上面还包含了寄存器 , 可以存储一些运算的中间结果
- CPU 执行程序的过程 , 可以分解成三个步骤 :
- 读取指令
- 解析指令
- 执行指令
3. 操作系统
CPU、存储器、输入设备、输出设备都是硬件 , 用户想要直接操作 , 并不是一件很方便的事,那么程序员发明了一个软件,通过这个软件可以更方便的操作硬件 , 这个软件就叫做"操作系统" .
3.1 核心功能
操作系统主要有两个核心功能 :
- 对下 : 要管理各种硬件设备
- **对上 : 要给各种软件提供稳定的运行环境 **
Windows Mac Android IOS 他们几个都是操作系统 , 虽然厂商不同 , 实现的细节有差别 , 但是核心功能都是这两条
3.2 操作系统的软硬件结构
3.3 什么是进程 / 任务
操作系统是一个搞管理的软件 , 它要管理许多东西 , 比如 :
内存管理 , 文件管理 , 设备管理 , 进程管理 …
那么我们来了解一下进程管理 , 那首先 , 我们要了解一下 “进程” 这个概念 .
进程 (process) , 有的操作系统也叫做任务(task) (比如风河系统 , 就把进程叫做任务) . 进程是操作系统对一个正在运行的程序的一种抽象,换言之,可以把进程看做程序的一次运行过程 .
比如 : 我们的 Typora 正在完成博客的书写
那么 , 大家要区分一组概念 . 进程 和 程序(可执行文件) 是两组不同的概念
一个可执行文件 , 是"静态的",当我们双击运行的时候,操作系统就会把这个可执行文件当中的关键信息加载到内存中 , 并且开始运行里面的代码,这就形成了一个进程。一个计算机上硬盘上可能会有很多的可执行文件 , 同一时刻他们可能只有一小部分在运行 , 正在运行的就变成了进程。(意思就是我们安装的程序有很多 , 但是正在运行的没有几个 , 正在运行的其实就是进程)
3.4 进程管理
进程管理的意思就是在一个操作系统上 , 同时跑了很多的进程 , 那么操作系统需要把他们的工作都分明白。
那么接下来给大家讲解一下进程是怎样管理的 .
3.4.1 管理
- 先描述 : 使用一个类 / 结构体,把这个东西有什么特征都表示出来
那么我们大部分情况下为什么不直接使用类呢 ? 类不是要比结构体厉害很多 ?
进程管理是操作系统内核的功能 . 操作系统又是C语言实现的 , C语言当中是没有类这个语法的. C语言的结构体其实就是低配版的类。
- 再组织 : 使用一个数据结构,把很多个这样的对象 / 结构体给整理到一起。
3.4.2 PCB : 进程控制块
PCB 也叫 进程控制块。它的作用是操作系统表示进程的属性的结构体 , 这个结构体里就包含了一些表示进程的核心信息。
操作系统内核中就把若干的 PCB 串成了一个双向链表。
那么 PCB 里面都有什么信息?
最好的办法就是看看操作系统里面内核的代码
但是我们Windows系统是闭源的,虽然Linux系统是开源的,但是并不好找操作系统内核的代码。而且我们也阅读不明白。
所以就简单的给大家介绍一下。
3.4.3 PID
进程还有一个唯一的身份标识 : PID
我们可以把 PID 理解为身份证号
每次创建一个进程,都会生成一个唯一标识,在程序运行时 PID 是不会改变的,但进程终止后会被回收
3.5 进程里面的属性
3.5.1 内存指针
操作系统要把一些必要的数据加载到内存中。
这个必要的数据是指有些要执行的指令(代码) , 有些是运行时依赖的数据(全局变量)
内存指针就描述了该进程的内存中哪些部分是指令,哪些部分是数据。
我们可以理解为内存指针描述了进程持有哪些内存资源。
3.5.2 文件描述符表
文件描述符表 表示了当前进程都打开了哪些文件
我们之前在C语言阶段学过,在代码中打开一个文件使用 fopen 函数。
就会在进程的文件描述符表里给这个文件分配一个 表项。
文件描述符表,我们可以把它理解为成是一个顺序表,每个元素代表一个打开的文件,对应的下标就是"文件描述符"。
内存指针 和 文件描述符表描述了进程持有哪些系统的资源 . 也认为 进程 是操作系统中"资源分配"的基本单位。
举个栗子 :
有疫情的时候导致了封城 , 那么封城或者封小区的 , 他们都吃什么?
所以政府就会给大家发菜。要么以家庭为单位发财,要么以人为单位发菜。
这里面的家庭或者人,我们就可以把它理解为是一个进程
3.6 进程的调度
进程里面还有一组比较关键的属性,用来实现进程的调度。
那么我们为什么要进行进程的调度呢?
本质的问题就是当前计算机CPU是有限的,但是进程数量是比较多的。
比如我的机器上 CPU 核心数只有六核,但是进程数量非常多 , 几十个,甚至上百个。
这就造成了一个局面 : 狼多肉少。
那么操作系统就要做到尽可能的公平,让每一个狼都有吃肉的机会,就需要进行调度。
其实我们也可以想象成火车站 , 铁轨的数量远远小于火车的数量 , 但是通过火车站的调度 , 就能有条不紊的运行。
所以操作系统采取的策略就是 : 轮流吃(具体叫做时间片轮转)
这里的轮转速度是非常快的,我的电脑上 CPU 主频是1.8GHz。那么就是 1s 有18亿个时钟周期。在这个一秒钟之内都轮转好几圈儿了。因此我们人类是感知不到这样的轮转的,
并发式的执行 : 站在宏观角度来看,好像这些进程都在同时执行一样。但是微观上并不是同时执行,而是"轮流"的方式占用 CPU 执行 . 简而言之,意思就是宏观上同时执行,微观上不是同时执行(靠 CPU 快速切换)。
并行式的执行 : 站在宏观角度来看,好像这些进程都在同时执行一样。在微观上来看,由于CPU上有多个核心,每个核心上都可以跑一个进程。在某一时刻,两个进程就是在两个CPU核心上同时执行的。简而言之,意思就是宏观上同时执行,微观上也是同时执行。(并行 : 同时执行)
在实际的开发中,我们并不会对这两个概念做出明确的区分。其实从人的宏观角度上来看,是看不出来任何区别的。所以我们往往使用并发来概括表示并发和并行。
"并发编程"就是针对并发场景下的编程。(这里面的并发既包括了并发,又包括了并行。)
我们来举一个栗子 :
假设我是一个妹子,我的追求者很多,那么我选男朋友的标准有两条 :
- 有钱
- 长得帅。
(设定一个前提条件,没有哪个追求者是同时具备有钱而且长得帅的特点。)
所以这两个特点我如果都想要的话,我就需要同时谈多个男朋友。
因此我选择同时和三个人交往。
第一个人 : 王源 , 有钱
第二个人 : 蔡徐坤 , 长得帅
第三个人 : 易烊千玺 , 会说话 , 会舔
我虽然就是在和三个人交往,但是我不能让他们三个人之间有交集,避免露馅儿。
所以我就成为了时间规划大师。规划出一个时间表。规定什么时间和谁干什么
比如 :
那么在上述这个时间表的安排下,就可以看做是三个进程在 CPU 下并发执行。(轮流吃)
但是我们从宏观角度和微观角度分析一下。
比如看这一年,我是跟三个人同时在处对象。(并行)
但是看某一天我是跟一个人处对象。(并发)
周一 : 我和王源去逛个街,购个物。
周二 : 我和蔡徐坤一起去吃饭,一起去打篮球。
周三 : 我和易烊千玺一起去上课。
在这个例子中,大家就把我当成一个CPU。王源 蔡徐坤 易烊千玺就把它当成是三个进程。
进程的调度分为4点 : 进程的优先级,进程的状态,进程的记账信息,进程的上下文。
下面我们分别来讲述一下。
3.6.1 进程的优先级
进程的优先级就是我在安排时间表的时候先派谁后排谁 , 给谁多排点儿,给谁少排点儿,这就是进程的优先级。
优先给王源安排时间,把我这一周很大一部分的时间都安排给王源 , 给王源最好的体验。
然后再去给蔡徐坤安排时间见面,
最后易烊千玺就看还有没有时间。
3.6.2 进程的状态
进程的状态是指安排时间表的时候,要考虑到每个人当前的特定情况。
比如王源这周要出差两个礼拜。那么这两个礼拜我是空着的。
那么我把这个空闲的两个礼拜的时间分给蔡徐坤和易烊千玺
进程有很多的状态,其中最典型的有两种。
- 就绪状态 : 进程是准备就绪的,随时可以在CPU上执行。
- 阻塞状态 : 进程在等待某个任务完成(比如 : 读写磁盘) , 完成之后才能在CPU上继续执行,完成之前就没有办法继续执行。
正常情况下,王源,蔡徐坤,易烊千玺都是就绪状态。(我随便安排时间 , 他们都是随叫随到)
那么王源出差了(相当于进入了阻塞状态) , 我就没有办法再给王源安排时间了。
其实我们可以理解为易烊千玺一直在就绪状态,因为它是最大的舔狗。
3.6.3 进程的记账信息
安排时间表的时候,要考虑到一些历史记录,也就是以往的安排。操作系统在安排进程的时候,也会记录每个进程以往在CPU上执行的时间(也有可能是以执行的次数为基准)。如果发现某个进程被安排的太少,就会适当的调整策略。
我发现 , 连续好久都给易烊千玺安排的时间很少。那么我就很有可能失去易烊千玺 , 所以我决定接下来多花时间陪陪他。
3.6.4 进程的上下文
对于进程来说,上下文具体指的就是 CPU 的一堆寄存器里面的值。上下文就会在进程被切出 CPU 的时候,把寄存器里的状态保存到 PCB 里面。下次进程回到 CPU 上,就把 PCB 里面的上下文读取出来,恢复到 CPU 寄存器中。
有一次我跟王源说 教教我怎么思谋克吧(大家联想一下就是)
跟蔡徐坤说 , 让他送给我个篮球。
当我之后遇到王源的时候,我就可以问他给没给我买 思谋克?
当我之后遇到蔡徐坤的时候就可以问他 , 你的篮球什么时候送给我?
但是我很有可能说错话 , 比如我问王源篮球的事情。这就很容易穿帮 , 所以在每次约会之后,我就会记录一些关键信息。
尤其是这次约会还没处理完的一些事情,下次再处理的话,就需要明确的记好当前处理的进度以及中间结果 , 这样下次约会的时候就可以从上次记录的结果中继续向下进行了。
其实也类似于读档存档 ,存档保存的信息就是我上次退出之前的游戏状态,这个就是"上下文"
进程在调度的时候也是一样。进程很可能执行某个操作执行一半儿 , CPU 就被调度走了。过一段时间CPU还是要回来继续执行的,那么回来就需要从之前上次执行到的位置 , 继续向下执行。
3.7 进程的虚拟地址空间
这是和进程非常相关的一组概念。
进程需要使用一些系统资源,其中内存资源是一个很关键的资源。
为了让各个进程之间不要相互干扰,操作系统就引入了虚拟地址空间这样的概念。
每个进程都只能访问到自己的虚拟地址空间,相互之间就不会有影响了。
哪怕你指针指错 , 操作系统也能及时发现,不会影响到其他的进程。
就算出问题,问题也被限制到进程的内部了。
因为存在虚拟地址空间,进程就有了一个重要的特性 : 隔离性。一个进程的运行一般不会影响到另一个进程,尤其是一个进程崩溃,也不会影响到另一个进程。
每个进程都有一个虚拟地址空间。那系统里进程又这么多,这些虚拟地址空间加到一起比物理内存大了,该怎么办呢?
虽然系统里的进程这么多,但是实际上
- 同一时刻执行的进程没有几个。
- 即使同一时刻有好几个进程在执行,这些进程也不是同时把所有的虚拟地址空间的内存都使用上了。
假设同时是六个进程执行,很可能每个进程只需1M的内存空间。虽然每个进程的虚拟空间很大,但是实际使用的内存只有一小部分,物理内存只需要把真实使用的这部分内存数据给表示出来即可。
- 极端情况下确实同时跑的这几个进程同时吃了很多的真实内存 , 导致物理内存不够
出现这种情况算bug,因此程序员就需要想办法优化一下内存占用或者扩容换一个内存更大的机器。
举个栗子 :
银行吸收存款,然后也放贷款
假如银行吸收一个亿的存款,那么银行就可能贷款贷出去9000万。
那实际上可能柜台里面只有1000万 , 1000万对应的是一个亿份额的储户储蓄。
根据经验规律,这些储户不会同时来取所有的钱。我们类比一下 : 我们的物理地址就相当于1000万,但是我们的虚拟内存地址就相当于9000万,正是因为在这一时间段,虚拟内存地址不会全部被占满。
极端情况下,比如战争等因素导致大家纷纷来取钱,银行就容易破产。
那么进程引入了隔离性,这使系统更加稳定了,但是也产生了别的问题。那么多个进程之间想要配合工作就很麻烦了 , 所以操作系统又引入了"进程间通信"
3.8 进程间通信
操作系统提供进程间通信方式有很多种,但是本质上都是一样的。搞一个多个进程之间都能访问到的公共资源,借助公共资源来进行通信。
类似于"无接触配送"
外卖小哥把外卖直接放到保安室。然后点外卖的人去保安室。
我们之后会学习网络编程,网络通信本质上也是一种进程间通信,而且是现在是最主要的使用方式。