您还未登录! 登录 | 注册 | 帮助  

您的位置: 首页 > 软件开发专栏 > 系统/运维 > 正文

计算机的 CPU、操作系统与内存管理

发表于:2021-06-11 作者:神说要有光zxg 来源:神光的编程秘籍

一些关于计算机原理的思考,大家随意看看就行。

硬布线到冯诺伊曼

其实最早的计算机是没有存储机制的,都是通过硬布线来编程,编程就是布线,后来的电子计算机有了存储的机制,能够存储一些指令和数据,这样就有了能够复用的程序的存在基础,通过电和磁来存储二进制信息,替代掉了硬布线。

计算机之所以能存储是因为它是图灵机模型的实现,存储的部分对应了图灵机模型中的纸带,冯诺伊曼体系结构(现代计算机的体系结构)是图灵机的实现,它设计了用二进制表示机器码、存储以及计算机五大部件:输入、输出、控制、运算、存储。

现在的计算机有 cpu 内的寄存器和 L1、L2、L3 缓存 ,还有内存和硬盘的存储方式,这 6 级存储介质就是计算机的存储体系。计算机发展到现在的存储体系,存储已经是一个常识了。存储是程序复用的基础,可以重复执行一些编写好的代码。而且编译的出现和发展,使得我们不用输入机器码,可以用字符构成的高级语言编程,然后编译为二进制的机器语言。

cpu 与时钟和 jump

程序最终是 cpu 执行的,而 cpu 执行什么指令是由 CS + IP 寄存器控制,cpu 会不断的取这俩寄存器所表示的内存地址的指令,通过数据总线读入 cpu,用译码器译码之后用不同电路执行。取指令、译码、执行指令,这是 cpu 的一个始终周期,cpu 会无限循环这个过程,指令周期的执行频率受时钟控制,有一定的频率,有的计算机可以支持短时间的超频,就是提高了这个时钟的频率。cpu 是不断循环时钟周期的,那么总要有个初始地址,这个地址一般是固定的地址,放计算机通电以后加载的第一条指令,这部分代码叫做 BIOS(基础输入输出系统),它是后续一切程序执行的基础。指令支持跳转到另一个地址,通过这种方式支持了控制流。就像前面所说,cpu 是不断循环时钟周期的,那么总要有种方式来控制它转向,控制转向的功能就是通过跳转指令, 所以说 jump 指令是 cpu 的方向盘。而指令和数据可以放在内存的任何位置,通过 jump 的方向盘控制 cpu 跑到那里。

cpu 是一辆汽车,那么时钟就是引擎,控制着 cpu 执行指令的频率,而执行到哪条指令是存在 CS + IP 寄存器里的,它只会往前开,也就是只会不断的取下一条指令,而转向(也就是控制流)是由 jump 指令控制的,跑到内存中的任何地方取指令来到 cpu 上跑,而最开始的一段路就是 BIOS 程序所在的地方。

BIOS、裸机和操作系统

BIOS 程序其实一般不会自己写,除非做嵌入式,直接在裸机(没有操作系统的计算机)上开发,那需要自己做程序的引导,自己可以控制把指令存在哪,数据存在哪,从哪里取。这种逻辑太过通用,而且只有一个控制流,只有一条路线和风景,不能充分利用 cpu ,为了解决这个问题,出现了操作系统,它能多到多个控制流,也就是汽车有多条行进路线,可以看到不同的风景。

操作系统也有自己的 BIOS 程序,是 cpu 最开始的一段路,把 cpu 带到操作系统所在的地方,操作系统一般都提供了进程的机制,也把内存管理了起来,不再是随意的乱用,而是需要需要申请,就像我们企业内会把管理起来的东西做审批流一样,申请之后才会分配内存,进程的程序就放在这块内存上。逻辑是一条控制流,进程则是可以有多条控制流,虽然一辆汽车(一个 cpu)只能同时跑一个地方,但是却可以在其它代码不需要它的时候去给别的进程跑跑,就像顺风车一样不用的时候带带别人一样。cpu 不是一直在跑么,为啥会有不用它的时候,因为磁盘或者网卡等设备都有自己的控制器,叫做 DMA,当跑到那个地方的时候,会让 DMA 去搬运东西,就像叫了货拉拉但自己搬东西一样。当 DMA 搬运设备数据到内存的过程中,cpu 需要等,就闲下来了。这时候不如去跑跑别的代码,当然也可以啥也不干就等在那(阻塞的等待)。cpu 是可以在 io 的时候跑别的代码,跑的路线叫做控制流,控制流也有两种类型,一种是进程、一种是线程,线程可以访问进程分配的内存等资源,而不同进程有各自的分配的资源,不能互相访问,需要用其他内存来做中转。

我们知道,裸机时程序可以访问和操作所有内存,由程序自己控制,但是有了操作系统,就要遵循操作系统的规范了,操作系统对于可执行文件有格式的要求,只有一定的格式才能作为机器码来执行,windows 上是 exe 格式,linux 等系统上是 elf 格式。elf 格式会把文件分为代码段 (text)、数据段(data)等不同的部分,来放不同的数据,这样文件加载到内存中会放在不同的地方,然后会从 text 段取代码执行,从 data 段取全局数据,运行时还有调用栈和堆所占用的内存。不同进程在各自的内存上加载自己的可执行文件,然后在执行时会形成不同的控制流,会在栈和堆上放不同的数据。这是受操作系统限制的内存管理,由操作系统决定,相比裸机少了一些自由(必须做成可执行文件的格式),但多了各种操作系统提供的能力(系统调用和标准库)。

操作系统和 vm 的内存管理

操作系统给可执行文件跑起来的进程分配了内存,然后这个进程可以内部再做划分,比如 vm,会在自己的内存上再做一次内存管理,各种解释型语言就是跑在这块内存上,因为自己管理,就不用不同操作系统有不同的内存格式了,可以统一成一种(能跨平台)。比如 jvm 有方法区、静态域、堆、栈等等, 而 js vm 有全局作用域(类似静态域)、调用栈(函数上下文的栈)、堆等。这些 vm 因为自己又做了一层内存管理,所以上面跑的字节码等受到的内存管理方式不是操作系统的,而是 vm 自己设计的。但是不管怎么设计,总还是要存放代码、数据的,动态分配基本都是在堆上,全局数据放静态域,有调用栈维护上下文,这个基本是通用的设计了。

内存管理主要是管理代码和数据在内存某个位置的写入和修改,通过变量的方式引用某块内存,不同的变量类型引用不同大小的内存。从裸机上的随意修改内存,到操作系统上进程受限制的操作内存,再到 vm 上对内存做的进一步设计。不同层次都会做自己的设计,但是总归是交给 cpu 来跑的,当 cpu 跑到这块内存,用 jump 来控制转向,同时利用操作系统提供的线程能力,可以跑不同的路线(控制流)。

总结

本文主要是梳理了一些计算机的历史和原理,从硬布线到冯诺伊曼体系,到 6 级的存储体系的发展过程是发展历史的部分,而 cpu 的指令周期与时钟频率是 cpu 的工作方式,cpu 不断执行指令周期,跳到别的地方(改变控制流)是通过 jump 指令实现的,上层高级语言基于 jump 封装出了 if、else、函数调用等。

计算机最开始执行的引导程序是 BIOS,裸机和操作系统都需要,操作系统提供了进程管理的能力,可能同时有多个控制流,而且提供了各种软硬件管理能力,暴露了系统调用和标准库。操作系统可以加载一定格式的可执行文件,不同系统有自己的进程的内存管理方式,而 vm 则是在操作系统分配的内存上自己做了一层内存管理,现在很多跨平台软件都是基于这种跑在 vm 上的语言的。虽然不同 vm 的内存管理的设计有不同,但堆栈等设计确是通用的。

注:虽然我们做前端开发不需要了解太多计算机原理,但是理解计算机是提升内功,内功提升了,做任何开发都会有更深入的认识。这篇是随便写的一些想法,希望能抛砖引玉,引起大家对计算机原理的一些思考。