1. 最早的数据存储介质——打孔卡与纸带
IBM早在1928年就推出了规格为190x84mm的打孔卡(Punched Card),用长方形孔提高存储密度,通常可以存储80列x12行数据,相当于120字节。后来做成了穿孔纸带。
打孔卡与穿孔纸带在很长一段时间内共存,是机械化存储技术时代的标志。
这种存储介质我们一直在使用。
“0”:表示光没有通过;“1”表示光通过了。为什么早期的计算机会采用打孔卡这样的存储设备,而不用像C或者Python这样的高级计算机语言编写呢?因为早期的计算机或者CPU没有理解这些高级语言的能力。但是,即便是2021年的今天,计算机也只能计算由“0”和“1”组成的二进制,即“机器码”或机器语言(Machine Language),只是计算机对人而言更友好了,它们会自动“翻译”(更准确都说是“编译”)不同的语言。计算机用二进制语言,人用高级计算机语言(例如:英语)。
2. 在计算机指令执行中,CPU做了什么?
CPU(Central Processing Unit)从硬件工程师的角度看,是一个超大规模集成电路(Very Large Scale Integrated Circuit,简写VLSI)。但从软件工程师德角度看,CPU是一个执行各种计算机指令(Intruction),能听懂各种计算机语言,进行计算的设备。但是,不同的CPU能够听懂的计算机语言不太一样,专业术语叫做不同架构的CPU。主要有:
1)以Intel、AMD公司为代表的X86架构的CPU,用的是复杂指令集(Complex Instruction Set Computer,简写CISC),主要用途为个人电脑(Personal Computer,简写PC)、服务器。
2)以Apple、ARM公司为代表的ARM架构的CPU,用的是精简指令集(Reduced Instruction Set Computer,简写RISC),主要用途为各种手机、苹果电脑。
3)以IBM公司为代表的PowerPC架构的CPU,用的也是简指令集(Reduced Instruction Set Computer,简写RISC),主要用途为中高端的嵌入式系统中。
可见PC机上编写的程序不能通过复制到手机上运行,就是因为不同架构的CPU支持的**计算机指令集(Instruction Set)**是不同的。
计算机程序不可能只有一条指令,而是由成千上万的指令组成的。CPU 中不可能一直存放着所有指令,所以计算机程序平时是存储在存储器中的。这种程序指令存储在存储器里面的计算机,就叫作存储程序型计算机(Stored-program Computer)。
其实,早前还有一种非存储程序型计算机,就做“插线板计算机”(Plugboard Computer)
3. 计算机高级语言是如何变成机器码的呢?
以一段C语言程序为例
// test.c
int main()
{
int a = 1;
int b = 2;
a = a + b;
}
如何让这个程序在Linux操作系统 or Windows操作系统 上运行起来呢?
答:需要将C语言翻译成一个汇编语言(Assembly Language,简写ASM),这个过程称为编译(Compile),再由汇编器(Assembler)翻译为机器码。这些由“0”和“1”组成的机器码就是一条条计算机的指令。为了阅读方便(否则太长),用16进制来表示机器码。
在Linux操作系统上,使用gcc和objdump命令将汇编语言和机器码打印出来。
$ gcc -g -c test.c
$ objdump -d -M intel -S test.o
一条C语言指令可以对应一条汇编语言指令,也可以对应多条(2条以上,含2条)汇编语言指令。汇编语言指令与机器码则是一一对应的。
test.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
int main()
{
0: 55 push rbp
1: 48 89 e5 mov rbp,rsp
int a = 1;
4: c7 45 fc 01 00 00 00 mov DWORD PTR [rbp-0x4],0x1
int b = 2;
b: c7 45 f8 02 00 00 00 mov DWORD PTR [rbp-0x8],0x2
a = a + b;
12: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8]
15: 01 45 fc add DWORD PTR [rbp-0x4],eax
}
18: 5d pop rbp
19: c3 ret
汇编语言是给“程序员看的机器码”。
问:在Windows操作系统上,是如何实现的呢?
4. 汇编指令和机器指令的解析
指令可以分为五大类:
1)算术指令:加减乘除。
2)数据传输指令:给变量赋值,在内存里读写数据。
3)逻辑指令:与、或、非、异或。
4)条件分支指令:if…else…,for,while。
5)无条件跳转指令:函数(function)调用,方法(method)调用,goto。
不同的CPU的指令集是不同的,例如Intel的CPU的指令集有2000多条指令。这里选用最简单的MIPS指令集来学习。MIPS指令集是MIPS公司在二十世纪80年代设计的CPU指令集,现在已经开源了,可以点击MIPS官网和这里获得。国产龙芯2E微处理器是一款实现64位MIPSⅢ指令集的通用RISC处理器。
MPIS指令集是32位(bit)的指令。
1)高6位是操作码(Operational Code,即OP code),可以理解为具体的动词,CPU具体的操作指令/行为。剩余的26位有R、I、J三种格式。
2)R指令:用来做算术和逻辑操作,里面有读取和写入数据的寄存器的地址。如果是逻辑位移操作,后面还有位移操作的位移量。而最后的功能码,则是在前面的操作码不够的时候,扩展操作码表示对应的具体指令。
3)I指令:用在数据传输、条件分支,以及在运算的时候使用的并非变量而是常数的时候。这个时候,没有了位移量和操作码,也没有了第三个寄存器,而是把这三部分直接合并成了一个地址值或者一个常数。
4)J指令:跳转指令,高 6 位之外的 26 位都是一个跳转后的地址。
例如:
add $t0,$s1,$s2 #将寄存器t0中的内容变为寄存器S1和寄存器S2中内容的和
1)操作码:汇编指令【add】,机器指令【opcode中的0】。
2)rs,第一个源操作数(OP num):汇编指令【
s
1
】
,
机
器
指
令
【
17
】
,
表
示
寄
存
器
s
1
的
地
址
为
17
(
这
里
用
10
进
制
表
示
了
)
。
3
)
r
t
,
第
二
个
源
操
作
数
:
汇
编
指
令
【
s1】,机器指令【17】,表示寄存器s1的地址为17(这里用10进制表示了)。 3)rt,第二个源操作数:汇编指令【
s1】,机器指令【17】,表示寄存器s1的地址为17(这里用10进制表示了)。3)rt,第二个源操作数:汇编指令【s2】,机器指令【18】,表示寄存器s2的地址为18(如果用16进制表示未0x12)。
4)rd,目标寄存器:汇编指令【$t0】,机器指令【8】,表示目标寄存器t0的地址为8。
5)因为不是移位操作,所以偏移量shamt为0。
6)功能码或函数码为0x20。
如果用打孔卡实现为:
这里我们用的C语言,C语言是一种编译型计算机语言。可以用Python语言实现,Python是一种解释型计算机语言。还可以用Java语言实现,Java语言是一种使用虚拟机的语言。