小孩子又回来啦!
狗哥的疑惑
狗哥是一位大一新生。
老师在C语言课程上写了一段程序:
#include <stdio.h>int main(void){printf("HelloWorld!\n");return 0;}
老师说:这是我们学习的第一个C语言程序,这个程序的功能极其简单,就是向显示器上输出一行字符:“Hello World”。
我们可以打开任意一个文本编辑器,比方说记事本,然后把上述文本复制进去,然后把它以文件的形式保存到硬盘上就算是编程完成了,我们这里可以给这个保存程序的文件任意起个名,比方说就叫helloworld.c
吧(.c这个扩展名也不是必须的,只是别人看到这个扩展名后可以知道这是一个保存着C语言程序的文件罢了)。
狗哥说:我在玩儿英雄联盟的时候,都是直接双击一个叫Client.exe
的程序就可以玩了:

为啥这次双击这个helloworld.c
文件后,却打开了文本编辑器?这个用C语言编的helloworld.c
程序和Client.exe
不是一种程序吗?是不是我把helloworld.c
的后缀名改成exe
就好了呢?
老师说:不不不,把helloworld.c
改成helloworld.exe
仍然不能运行。其实helloworld.c
和Client.exe
不是一种程序。helloworld.c
是给人看的程序,本质上就是一个纯文本文件,而Client.exe
包含若干条机器指令,CPU会读取这些指令并执行它们。
狗哥说:老师,我刚入学,你说的话有点儿难懂喔,什么是CPU,啥是个机器指令,机器指令是干啥的?
老师说:那我们现在就不讲C语言程序了,先来看看编程的本质到底是在干什么。
程序员的视角——存储器
编程其实就像是在玩游戏。
你肯定玩过扫雷游戏吧:

扫雷玩家面对的是许许多多的小格子,其中一些格子下面藏着地雷,有些没有藏,如果你点击某个背后是地雷的小格子的话,就GAME OVER了。
程序员玩家在玩编程游戏时,面对的也是许许多多的类似的小格子,不过这些小格子并不是用来藏地雷的,是用来存储数据的。比方说下边是一个4行×4列小格子示意图:

为了很容易的找出某个格子,我们会给每个格子分配一个编号。比方说我们给上图所示的16个格子分别编一个号:

小贴士:这里需要大家注意一下,在计算机中我们通常都是从0开始编号的。
习惯上,我们把格子的编号称作该格子的地址
,所以上边16个格子的地址分别是0~15
。
另外,在画图展示的时候,我们更习惯将这些格子放到一列中:

这些格子的组合被称作存储器
(memory,更多的地方翻译成内存,不过小孩子觉得存储器
更合适)。
上图中的每一个格子可以被称作存储器的一个存储单元
。存储单元中存储的数据是二进制形式的,每个存储单元可以存储的二进制位个数也被称作存储单元的大小
。存储单元的大小是人为规定的,不过现在用的计算机中存储器的存储单元大小一般是8个二进制位,也称作1个字节。
程序员的任务就是向存储器
中填入内容,填的内容可以被大致分为两类:
1.指令 2. 数据
数据
好理解,比方说我们班级同学的身高、体重、考试成绩这些统统都可以被称作数据
。在计算机中,我们这里所说的数据
本质上数据就是一个二进制数字。由于1个存储单元仅包含8个二进制位,能表示的数字范围太小,如果我们有存储较大数字的需求,可以使用多个存储单元来共同存储1个数字。
但是指令
该怎么理解呢?其实指令
就是用来指挥计算机的硬件设备来做一些事情。除了存储器
,计算机的另一个十分重要的硬件设备就是CPU
,CPU负责将指令从存储器中读出并执行,做一些诸如加减乘除之类的运算操作。
在我们深入了解指令
之前,我们得先了解一下这个CPU对我们程序员有什么影响。
程序员的视角——CPU
CPU的主要功能是完成运算,比方说将两个二进制数加起来得到加和。我们把CPU一次处理数据的最大位数称作CPU的位数,也称作一个字(word)。比方说CPU最多支持一次对两个32位的二进制数进行运算,那么我们就把这个CPU称作32位CPU
。
虽说CPU内部的硬件电路十分复杂,但那是硬件工程师的事情。对于程序员来说,我们只关注CPU内部的若干寄存器。
小贴士:寄存器是一种特殊的存储设备,读写寄存器的速度非常快,比我们前边所说的存储器要强很多。
在32位CPU中,程序员所能使用的每个寄存器的位数也都是32位(即1个寄存器可以存储32个二进制位)。比方说某个CPU中包含16个程序员可以使用的寄存器:

这16个寄存器分别被命名为R0~R15
,这些寄存器的组合也被称作寄存器文件
(Register File)或者寄存器组
(Register Set)。
程序员的任务
存储器
(memory)和寄存器文件
(register file)都属于存储二进制位的设备,对于程序员来说,大家只需要知道它们的两种区别就好了:
•寄存器文件的读写速度非常快,它是CPU内部的存储设备;存储器的读写速度比寄存器文件慢很多,一般在CPU外部单独存在。
•寄存器文件不能做的很大,一般CPU中包含1个、几个、十几个或者几十个供程序员使用的寄存器;存储器
的容量一般很大,可以存储几十亿甚至上百亿字节的数据。
下边就该计算机指令登场了!指令本质上也是一串二进制数据,通常可以分为操作码
和操作数
两个部分:

其中操作码
表示该条指令是要完成什么功能,操作数
用来表示该指令操作的数据。操作码
是一条指令中必须的,有的指令可以没有操作数,有的指令也可以有多个操作数。
一条指令占用的存储空间大小可以是固定的,也可以是不固定的。比方说在现实生活中MIPS32的CPU的指令就占用固定的4个字节大小,而Intel公司的x86系列CPU的指令就占用1~15个字节不等。
CPU提供的指令主要完成下边4个方面的任务:
加载任务
简称load:将存储器
中的数据加载到寄存器文件
中的某个寄存器中。
很显然,为完成加载任务
,我们需要在指令中至少指定2种操作数:
•要读取存储器中哪个地址的数据。
•要将存储器中的数据加载到哪个寄存器中。
比方说在MIPS32中,我们经常使用下边的指令来完成任务1
:

其中op部分代表操作码,rt部分代表要将数据写入哪个寄存器中。base和offset部分共同决定该指令是要读取哪个CPU地址处的数据。我们详细看一下这些指令的作用:
•lb指令:全称为Load Byte。表示加载一个字节的有符号数据到目的寄存器中。
小贴士:
由于MIPS32中的寄存器都是32位的,加载一个字节的数据到寄存器的低8位后,其余高位需要使用符号扩展进行填充。
•lbu指令:全称为Load Byte Unsigned。表示加载一个字节的无符号数据到目的寄存器中。
•lh指令:全称为Load Halfword。表示加载半个字,也就是2个字节的有符号数到目的寄存器中。
•lhu指令:全称为Load Halfword Unsigned。表示加载半个字,也就是2个字节的无符号数到目的寄存器中。
•lw指令:全称为Load Word。表示加载1个字,也就是4个字节的数据到目的寄存器。由于目的寄存器也是4个字节大小,所以就不需要进行高位扩展了。
写回任务
简称store:将寄存器文件
中某个寄存器中的数据写回到存储器
中。
比方说MIPS32提供了sb、sbu、sh、slhu、sw等指令。
跳转任务
简称jump:完成某个需求可能需要很多条指令,有时需要直接跳转到某条指令执行,或者先判断一下某些条件是否成立,如果成立再跳转到某条指令执行。
比方说MIPS提供了jr、beq、blez等指令。
运算任务
简称operate:对寄存器文件或者存储器中的数据进行运算,结果再写回寄存器文件或存储器中。
运算任务
属于CPU最核心的任务,就是进行运算,这里包括算术运算和逻辑运算,我们看一下MIPS32中支持的一些逻辑运算指令:

我们分别看一下它们的用途:
•and指令用于将rs和rt寄存器中的数据进行与运算,将结果写到rd寄存器中。
•or指令用于将rs和rt寄存器中的数据进行或运算,将结果写到rd寄存器中。
•xor指令用于将rs和rt寄存器中的数据进行异或运算,将结果写到rd寄存器中。
•nor指令用于将rs和rt寄存器中的数据进行同或运算,将结果写到rd寄存器中。
•andi指令用于将16位立即数imm进行0扩展为32位,然后与rs寄存器中的数据进行与运算,将结果写到rd寄存器中。
•ori指令用于将16位立即数imm进行0扩展为32位,然后与rs寄存器中的数据进行或运算,将结果写到rd寄存器中。
•xori指令用于将16位立即数imm进行0扩展为32位,然后与rs寄存器中的数据进行异或运算,将结果写到rd寄存器中。
小贴士:
某些CPU,比如x86系列CPU,还支持对寄存器和某个CPU地址对应的数据进行运算的指令;某些指令集体系结构,比如PDP系列,还支持对两个CPU地址对应的数据进行运算的指令。而以MIPS仅支持对两个寄存器中的数据进行运算,这样做的目的是为了让CPU的硬件设计更加简单,单条指令的执行速度也更快。
程序员任务小结
我们这里篇幅有限,并不想深入介绍完全每个指令的格式以及它们的用途,大家只需要知道CPU提供了完成load、store、jump和operate四大任务相应的指令,我们只需要将这些指令填充到存储器
的存储单元中,CPU就可以自动的读取这些指令并执行它们。
由于存储器中的指令和数据可以随时改变,存储器
中填入的指令和数据也可以被称作软件
,而我们将指令和数据写入存储器中的过程也被称作编程
,进行编程的人也被称作程序员
,也就是屏幕前的你。
编译器
狗哥说:我现在已经大致理解了编程的本质,就是把指令和数据写到存储器中而已,指令用于完成load、store、jump和operate这几项任务。直接写指令就能编程的话,我们为啥还要学文本形式的C语言程序呢?
老师说:你还记得我们上边说的进行and运算的指令的操作码是啥码?
狗哥说:不记得了,这个得再返回头看看。
老师说:是吧,刚讲过就忘了,可见直接使用计算机指令编程序是多么麻烦的一件事情,所以之后有人就写了一个称作编译器
的软件,它可以读取我们写的文本形式的C语言程序,然后产生CPU可以执行的计算机指令。
狗哥说:那我明白了,就是我们写的C语言程序需要被翻译翻译。
最后一个疑惑
狗哥说:有一个疑惑啊,程序不是以文件的形式存储在硬盘里的么,这个硬盘就是我们上边所说的存储器
吗?
老师说:不是的哈,读写硬盘的速度十分缓慢,所以CPU一般不直接从硬盘中读取指令并执行。像硬盘这样的速度超慢,但容量大、价格低的存储设备我们一般将其称作辅助存储器
(Secondary Memory),简称辅存
,或者外存
。而CPU其实是从所谓的主要存储器
(Main Memory),简称主存,或者内存
中读取指令和数据的,你可以把它当作我们平时网上购买的内存条
。
狗哥说:那这里又有问题了,既然程序原先是保存在外存
中的,那怎么把它弄到内存
呢?
老师说:你还有完没完,该下课了。这涉及到CPU和I/O设备如何通信的问题,下次再说,拜拜>_>
小孩子新书雏形
想了解CPU是怎样运行的同学可以看一下小孩子的掘金小册《计算机是怎样运行的:从根儿上理解计算机》,从初中物理知识出发,带你一步一步搭建计算机。

扫描下方二维码可跳转到《计算机是怎样运行的:从根儿上理解计算机》




