计算机的中央处理单元CPU
计算机也有自己的心脏,不同于人类,计算机的心脏是莫得感情的。
Central Processing Unit 中央处理单元
CPU的主要功能就是执行程序,比如浏览器,word或是游戏。而这些程序都是由一系列单个指令 instruction组成的,指令指示计算机应该做什么。比如数学指令,加/减,呢么CPU会用他的ALU去做数学运算,而内存指令,则会使CPU和内存进行通信。
一个指令的phase
- fetch 取指令
- decode 解码
- execute 执行
下面我们通过一张图对CPU如何取执行程序来做简短的解释
Control Unit 是CPU的控制单元,其中的操作稍显复杂,这里我们简单的理解成是CPU的功能控制,里面有2个重要的寄存器
指令地址寄存器 instruction address register (用于去追踪程序运行到什么位置了)
指令寄存器 instruction register (用于记录当前指令)
当计算机启动时 所有寄存器初始都为0,在运行一个程序时,首先程序加载在RAM中,我们进入指令执行的一个周期,
-
fetch(取指令)
首先指令地址寄存器会链接到RAM,寄存器的值为0,从RAM 0处返回的值,将值存入指令寄存器 -
decode(解码)
现在指令寄存器里已经存入了一个指令,这时候我们需要根据CPU的约束进行解码,例如加载指令, 我们把前4位作为操作码,后4位作为RAM地址
加法指令,我们把前4位作为操作码,56位作为第一个寄存器地址,78位作为第二个寄存器地址 -
execute(执行)
控制单元根据指令去操作数据,管理RAM读取数据到寄存器,或是调用ALU进行计算等等。
在执行完指令的一个周期后,指令地址寄存器会+1然后去调用第二条指令并重新进入循环。
当然CPU不会像人一样知道是否该执行下一条指令,所以负责CPU到下一阶段的任务落在了一个叫做 时钟 clock 的组件上。
顾名思义,时钟以精准和固定的间隔 ,触发电信号,其信号被控制单元用于推进CPU的内部操作,保持一切操作的同步。
CPU在执行"fetch - decode - execute"的速度叫做时钟速度 Clock Speed(时钟频率)以赫兹 Hertz作为单位,1Hz表示每秒一个周期。
intel 4004是intel1971年推出的第一个单芯片4-bit CPU ,它的时钟速度达到了740kHz,每秒74万次。
1MHz=100万个时钟周期/秒。例如我现在的电脑i7 8700 是3.2GHz。
例如超频就是修改时钟,加快CPU的速度。而超频也会带来温度的上升,或是信号跟不上时钟,产生乱码。
有超频,那么肯定也有降频,比如intel的酷睿处理器就会在系统负载小时自动降低CPU的频率来降低功耗,在负载高时提升频率提高性能,就是我们常在酷睿CPU上看到的睿频介绍。叫做动态时钟频率调整。
汇编语言
汇编语言是最接近原生机器语言的语言,有点绕,但通过前面的例子,相信你一定很容易理解。
在上面的例子中,CPU的控制单元去RAM里读取指令,比如0100 0110指令,机器对寄存器A 和 寄存B进行相加,但这0100 0110对于人类几乎是不可读的,很难看出机器做了什么,为了解决这个问题和加快编辑的速度,就诞生了汇编语言。
比如0100就是加法 01就是寄存器A 10就是寄存器B,那么刚才那段指令翻译成汇编语言则是 ADD A B。
当然,如今的CPU比上面的描述复杂了很多。比如我们可以并行的进行取指令,解码,执行。就好像洗衣服的洗衣机和烘干机,要通过先洗后烘干,那么我烘干的时候又可以加入一批衣服进入洗衣机而不用等待上一批衣服烘干。又比如在洗衣房有多个成套的洗衣烘干,多核处理器,双筒洗衣机,多线程处理。
还有CPU内缓存等等。
编程
计算机只能理解二进制,如果要用机器语言写程序,那工作量简直是宏伟的。
而如何让程序员用汇编语言去写程序呢?
那就是让机器读懂汇编语言,那么可以预先用二进制语言写一个程序让机器去翻译文字指令,理解汇编语言,然后自动转成二进制,这种程序称为汇编器 Assembler。它读入汇编语言然后转成机器码。隐藏复杂性来完成更复杂的工作。
虽然汇编完成了大部分工作也解决了一部分可读性,但是还是需要程序员去关注底层,比如用哪个寄存器,如何退出程序等等,如果需要修改一个额外的数,就可能要改很多代码。
汇编语言和机器语言是一一对应的,而为了解决上述问题,往上就产生了高级语言,但一行高级语言可能会转换成几十条二进制指令,为了做到这种复杂的转换就需要用到编译器 Compiler,这个程序负责把高级语言的代码转换成低级语言。
这里我们需要提到一下解释器 interpreter,很多人将编译器和解释器混淆,但它们并不是一个东西,我们可以这样去理解这两个概念,现在有一本英文书籍,而你只看得懂中文,编译器会将这本英文书籍翻译成中文的译本拿给你,而解释器会在你阅读的时候解释给你。
代码虽然可以用文本编辑器去编写,但一般来说现代软件开发者会使用特定的应用程序来编程。因为它集成了许多工具来编写,组织,编译和测试代码,因为集成了你需要的东西,所以它们又叫做 集成开发环境 integrated development environments IDE。
所有IDE都提供了一些有用的功能,例如代码高亮,语法检查 等等。
编程时尤其要注意注释和文档的编写,文档通常写在一个README的文件里,注释通常在代码中,它不仅可以让别人更容易理解你的代码,也可以帮助自己在过了几个月甚至更久能及时读懂自己原来的代码。
除了IDE,还有一个很重要的软件可以帮助团队协作大型项目。源代码管理 source control,也叫版本控制 version control。例如在Apple,Google这类大型软件公司,会把代码集中放在服务器上,叫做代码仓库 repository。
当然,写代码和调试也是密不可分的,一般由个人或小团队完成。更全面的调试叫做质量保证测试 quality assurance QA。QA会严格测试软件的各个方面,模拟各种可能的情况,看软件会不会崩溃。
操作系统
在早期的计算机上,程序员需要针对不同的硬件开发不同的软件,比如打印一串文本可能需要对不同打印机进行不同编码。而往往在测试时是无法保证有所有可供支持的打印机进行测试,程序员只能通过文档来进行编码,祈祷程序能够运行。
为了解决在不同的硬件上产生的问题,出现了操作系统 Operating Systems,一处编码,处处运行。操作系统针对不同的硬件进行封装,向上暴露应用程序接口 application programming interface API,来使得软件程序员能不再关心运行的平台硬件差异,操作系统充当了软件和硬件通信的媒介。更具体的说,操作系统提供API来抽象硬件,叫做设备驱动程序 deveice drivers,做到即插即用,使得程序可以使用标准化机制和输入输出硬件 input & output I/O进行交互。
多任务处理
假设计算机在进行打印程序,而需要打印设备打印完成后回馈,那么这段时间计算机在等待打印设备回馈打印完成,这时,计算机处于阻塞状态,只能呆呆的等待打印设备完成。这无疑会浪费很多时间和资源,所以与其等待,不如先把打印程序休眠,然后运行另一个程序,等待打印设备反馈可以继续打印程序。
操作系统的这种能力叫做多任务处理。
动态内存分配
计算机在进行多任务处理的时候,我们要保证程序切换回来数据还是原来的,那么我们就要保证数据不会丢失。为了解决这个问题,计算机会将内存分块,例如0-999给程序A,1000-1999给程序B。
这种灵活性还不赖,但是会出现一个很奇怪的后果,比如程序A需要更多的内存,那么程序A会被分到不连续的内存块,比如0-999和2000-999。然而真正的程序可能会被分配到内存的数十个地方,这对于程序员是非常难处理的。
为了隐藏这种复杂性,操作系统会 虚拟化 内存位置,一致且连续,而实际的内存物理地址被操作系统隐藏和抽象,叫做 动态内存分配 dynamic memory allocation,对于程序简化了其中的过程,位操作系统同行运行多个程序提供了极大的灵活性。比如一个程序破坏了自己的内存,那它只能影响到自己,不会影响到其他程序,可以理解成保护内存。这在防止恶意软件或是病毒也是十分有效的。
内核
UNIX把操作系统划成两部分:
- 首先是操作系统的核心部分,比如内存管理,多任务,I/O处理,也就是所谓的内核 kernel。
- 另一部分是一堆有用的工具,但它们并不是内核的一部分,比如程序和运行库。