深入浅出计算机组成原理
徐文浩
bothub创始人
立即订阅
13019 人已学习
课程目录
已完结 62 讲
0/4登录后,你可以任选4讲全文学习。
入门篇 (5讲)
开篇词 | 为什么你需要学习计算机组成原理?
免费
01 | 冯·诺依曼体系结构:计算机组成的金字塔
02 | 给你一张知识地图,计算机组成原理应该这么学
03 | 通过你的CPU主频,我们来谈谈“性能”究竟是什么?
04 | 穿越功耗墙,我们该从哪些方面提升“性能”?
原理篇:指令和运算 (12讲)
05 | 计算机指令:让我们试试用纸带编程
06 | 指令跳转:原来if...else就是goto
07 | 函数调用:为什么会发生stack overflow?
08 | ELF和静态链接:为什么程序无法同时在Linux和Windows下运行?
09 | 程序装载:“640K内存”真的不够用么?
10 | 动态链接:程序内部的“共享单车”
11 | 二进制编码:“手持两把锟斤拷,口中疾呼烫烫烫”?
12 | 理解电路:从电报机到门电路,我们如何做到“千里传信”?
13 | 加法器:如何像搭乐高一样搭电路(上)?
14 | 乘法器:如何像搭乐高一样搭电路(下)?
15 | 浮点数和定点数(上):怎么用有限的Bit表示尽可能多的信息?
16 | 浮点数和定点数(下):深入理解浮点数到底有什么用?
原理篇:处理器 (18讲)
17 | 建立数据通路(上):指令+运算=CPU
18 | 建立数据通路(中):指令+运算=CPU
19 | 建立数据通路(下):指令+运算=CPU
20 | 面向流水线的指令设计(上):一心多用的现代CPU
21 | 面向流水线的指令设计(下):奔腾4是怎么失败的?
22 | 冒险和预测(一):hazard是“危”也是“机”
23 | 冒险和预测(二):流水线里的接力赛
24 | 冒险和预测(三):CPU里的“线程池”
25 | 冒险和预测(四):今天下雨了,明天还会下雨么?
26 | Superscalar和VLIW:如何让CPU的吞吐率超过1?
27 | SIMD:如何加速矩阵乘法?
28 | 异常和中断:程序出错了怎么办?
29 | CISC和RISC:为什么手机芯片都是ARM?
30 | GPU(上):为什么玩游戏需要使用GPU?
31 | GPU(下):为什么深度学习需要使用GPU?
32 | FPGA和ASIC:计算机体系结构的黄金时代
33 | 解读TPU:设计和拆解一块ASIC芯片
34 | 理解虚拟机:你在云上拿到的计算机是什么样的?
原理篇:存储与I/O系统 (17讲)
35 | 存储器层次结构全景:数据存储的大金字塔长什么样?
36 | 局部性原理:数据库性能跟不上,加个缓存就好了?
37 | 高速缓存(上):“4毫秒”究竟值多少钱?
38 | 高速缓存(下):你确定你的数据更新了么?
39 | MESI协议:如何让多核CPU的高速缓存保持一致?
40 | 理解内存(上):虚拟内存和内存保护是什么?
41 | 理解内存(下):解析TLB和内存保护
42 | 总线:计算机内部的高速公路
43 | 输入输出设备:我们并不是只能用灯泡显示“0”和“1”
44 | 理解IO_WAIT:I/O性能到底是怎么回事儿?
45 | 机械硬盘:Google早期用过的“黑科技”
46 | SSD硬盘(上):如何完成性能优化的KPI?
47 | SSD硬盘(下):如何完成性能优化的KPI?
48 | DMA:为什么Kafka这么快?
49 | 数据完整性(上):硬件坏了怎么办?
50 | 数据完整性(下):如何还原犯罪现场?
51 | 分布式计算:如果所有人的大脑都联网会怎样?
应用篇 (5讲)
52 | 设计大型DMP系统(上):MongoDB并不是什么灵丹妙药
53 | 设计大型DMP系统(下):SSD拯救了所有的DBA
54 | 理解Disruptor(上):带你体会CPU高速缓存的风驰电掣
55 | 理解Disruptor(下):不需要换挡和踩刹车的CPU,有多快?
结束语 | 知也无涯,愿你也享受发现的乐趣
免费
答疑与加餐 (5讲)
特别加餐 | 我在2019年F8大会的两日见闻录
FAQ第一期 | 学与不学,知识就在那里,不如就先学好了
用户故事 | 赵文海:怕什么真理无穷,进一寸有一寸的欢喜
FAQ第二期 | 世界上第一个编程语言是怎么来的?
特别加餐 | 我的一天怎么过?
深入浅出计算机组成原理
登录|注册

05 | 计算机指令:让我们试试用纸带编程

徐文浩 2019-05-03
你在学写程序的时候,有没有想过,古老年代的计算机程序是怎么写出来的?
上大学的时候,我们系里教 C 语言程序设计的老师说,他们当年学写程序的时候,不像现在这样,都是用一种古老的物理设备,叫作“打孔卡(Punched Card)”。用这种设备写程序,可没法像今天这样,掏出键盘就能打字,而是要先在脑海里或者在纸上写出程序,然后在纸带或者卡片上打洞。这样,要写的程序、要处理的数据,就变成一条条纸带或者一张张卡片,之后再交给当时的计算机去处理。
上世纪 60 年代晚期或 70 年代初期,Arnold Reinold 拍摄的 FORTRAN 计算程序的穿孔卡照片,图片来源
你看这个穿孔纸带是不是有点儿像我们现在考试用的答题卡?那个时候,人们在特定的位置上打洞或者不打洞,来代表“0”或者“1”。
为什么早期的计算机程序要使用打孔卡,而不能像我们现在一样,用 C 或者 Python 这样的高级语言来写呢?原因很简单,因为计算机或者说 CPU 本身,并没有能力理解这些高级语言。即使在 2019 年的今天,我们使用的现代个人计算机,仍然只能处理所谓的“机器码”,也就是一连串的“0”和“1”这样的数字。
那么,我们每天用高级语言的程序,最终是怎么变成一串串“0”和“1”的?这一串串“0”和“1”又是怎么在 CPU 中处理的?今天,我们就来仔细介绍一下,“机器码”和“计算机指令”到底是怎么回事。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《深入浅出计算机组成原理》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(71)

  • ack
    opcode(6位)
    000000 10001 10010 01000 00000 100000
    =0000 0010 0011 0010 0100 0000 0010 0000(对应纵向打孔带)
    =0X02324020
    2019-05-29
    3
    40
  • lzhao
    机器码不是二进制吗?为什么gcc把汇编编译成16进制?

    作者回复: l'hao同学你好,是二进制,16进制只是为了显示方便,毕竟一串0和1在显示上太没有效率了

    2019-05-03
    18
  • 郭江伟
    gjw@gjw:~/csapp/000$ cat simplest.c
    int main(){
    int a=1,b=20;
    int c=a+b;
    }
    生成的汇编及其具体解释:
    push %rbp 压栈 ,基址指针
    mov %rsp,%rbp 将堆栈寄存器内容移动到基址寄存器
    movl $0x1,-0xc(%rbp) 将栈基地址偏移12字节设为1,对应变量a=1
    movl $0x14,-0x8(%rbp) 将栈基地址偏移8个字节设为20(16进制14=10进制20),变量b=20
    mov -0xc(%rbp),%edx 将栈基地址偏移12字节的数据移动到edx寄存器
    mov -0x8(%rbp),%eax 将栈基地址偏移8个字节的数据移动到eax寄存器
    add %edx,%eax edx 数据和eax数据相加,结果保存到eax寄存器
    mov $0x0,%eax 重置eax寄存器
    pop %rbp 弹出栈数据
    retq main函数返回并退出
    2019-05-22
    1
    17
  • 梨子🍐
    没有理解 `0X02324020` 是如何计算出来的?

    作者回复: 梨子同学你好,你把把上一行的二进制,四位四位一读,每四位当成是一个16进制数,就会得到这个结果

    2019-05-03
    1
    10
  • 老妖
    老师请教个问题,

    像python这种解释型的语言,需要一行行地编译,那它就不能综合所有的源文件来考虑了,像什么重定向这些怎么实现?或者跳转到某个地址,怎么实现,我担心的是一行一行的解释,看不到程序的全貌,怎么知道我要跳到哪个地址?

    谢谢!
    2019-05-12
    9
  • 冰激凌的眼泪
    打孔卡要纵向读,建议Opcode补足6个0,这样好和打孔卡对照
    汇编是不是可以看做机器码的助记符?
    2019-05-12
    1
    7
  • 程斌
    老师你好,通篇看下来,有一个地方没有怎么懂得。
    “对应的 MIPS 指令里 opcode 是 0,rs 代表第一个寄存器s1的地址是17,rt代表第二个寄存器s2的地址是18,rd代表目标的临时寄存器t0的地址,是8.”
    2019-06-15
    3
    6
  • Ant
    最后的卡片 02324020 ,为啥不是从最底下或最上面开始, 为啥3 打了俩孔, 为啥4 又 往上了一行

    作者回复: 顺序是根据用什么样的机器读取来的,我们这里假设就是从左往右一列一列读取的。

    一列卡片代表一个数,白色代表没有打孔,黑色代表打孔。3换算成二进制就是 0011,也就是上面两个不打孔,下面两个打孔,4就换算成 0100,也就是第二个打孔,其他的不打孔

    2019-05-10
    1
    6
  • coder
    老师您好,说到指令,让我想起来困惑我本科时期很久的一个问题:

    用C或者其他的高级语言可以实现更多的语言,那么这么一直追问下去,就有个类似蛋生鸡的问题,第一个编程语言是怎么来的?

    编程语言一般会有"自举"的功能,那么自举是怎么实现的?比如说Go语言就是自己实现自己,那么在没有Go编译器的基础上,怎么做到自己编译自己的?

    作者回复: json同学你好,第一台计算机ENIAC,如果你去计算机历史博物馆看一下真机就会明白,他的各种输入都是一些旋钮,可以认为是类似用机器码在编程,后来才有了汇编、C这样越来越高级的语言。

    编程语言是自举的,并不需要第一个编译器就是用自己这个语言来写的。通常是先有了别的语言写好的编译器,然后再用自己来写自己语言的编译器。

    更详细的关于鸡蛋问题,可以直接看Wikipedia上,讲了多种这个问题的解决方案
    https://en.wikipedia.org/wiki/Bootstrapping_(compilers)

    2019-05-05
    6
  • 二星球
    老师您好,我想问一个问题,C语言>汇编语言>机器语言 一般是这样的编译顺序,为什么不是 C语言>机器语言 一步到位这样编译呢?

    作者回复: 杨怀同学你好,其实有一步到位的,就是两个步骤都通过一个命令先后执行,顺序完成,gcc现在就可以一个命令直接变成可执行的binary。

    只是为了方便debug,你可以认为通过机器语言我们也可以反推出汇编语言长什么样子。

    2019-05-04
    6
  • 周曙光爱学习
    指令和机器码的关系是?可以理解为cpu指令类似数学公式,我们写的程序在套用这些公式,然后公式+参数生成机器码?

    作者回复: 👍,周曙光爱学习同学你好,这个理解和比喻很形象

    2019-05-09
    4
  • 一步
    我们说过,不同的 CPU 有不同的指令集,也就对应着不同的汇编语言和机器码 这句话中,在不同的指令集中 汇编语言和机器码 的关系是怎么对应的呢? 还有一个问题就是在高级语言转换为机器码的时候 是不是要读取CPU的具体的型号呢? 然后在转换为 对应CPU型号的机器码。 如果是,那么物理机器是不是要维护一个很大的对应关系表???

    作者回复: 一步同学你好,

    不同指令集里,对应的汇编代码会对应这个指令集的机器码呀。

    大家不要把“汇编语言”当成是像C一样的一门统一编程语言。

    “汇编语言”其实可以理解成“机器码”的一种别名或者书写方式,不同的指令集和体系结构的机器会有不同的“机器码”

    高级语言在转换成为机器码的时候,是通过编译器进行的,需要编译器指定编译成哪种汇编/机器码。

    物理机自己执行的时候只有机器码,并不认识汇编代码。

    编译器如果支持编译成不同的体系结构的汇编/机器码,就要维护很多不同的对应关系表,但是这个表并不会太大。以最复杂的Intel X86的指令集为例,也只有2000条不同的指令而已。

    2019-05-04
    4
  • Geek_guo
    希望老师可以把指令执行后的整个过程在分析下,不然现在还是不理解这个指令对于一条加法有什么作用

    作者回复: Geek_guo同学你好,加法在电路层面怎么执行,我们会在加法器里面讲解。

    我们的指令运行,怎么和ALU这样的算术逻辑单元串联起来,我们会在CPU里面讲解,尽请期待啊。

    2019-05-04
    3
  • ginger
    看到指令,联想到上一讲的cpu性能和指令数的关系,这里想提问下:
    是否可以通过将指令更加细分(功能上的细分,比如指令ab完成a+b,指令abc完成a+b+c)
    来实现一个高级语言本身需要对应到10条指令时候,变成了只需要对应7条指令的效果,

    我想这个一定是可行的,但应该没什么意义,因为cpu的指令集,发展了这么多年了,应该也是没有优化的空间了吧.

    作者回复: 可以的,这个就是历史上的CISC和RISC的争论。

    其实指令集都在不断更新微调。而体系结构最近RISC-V又火起来了。

    因为纯粹靠提升频率硬件的方法已经没有什么空间了,所以其实又进入了优化指令集乃至整个体系结构的阶段了。

    2019-05-24
    2
  • Sharongo
    计算机小白想问一个很蠢的问题:为什么CPU不能放着所有指令,为什么要区分cpu和内存以及其它硬件不可以把功能都放在一个器件上吗?

    作者回复: Sharongo同学你好,

    因为放不下啊,也放不起啊,而且这样怎么做到前面讲的“可编程”和“可存储”呢?

    2019-05-20
    2
  • 子杨
    徐老师,每一条计算机指令由 CPU 执行的时候,实际上是不是都是电路的连通或关闭?

    作者回复: 是啊,我们在讲解CPU的时候,你可以从电路的角度来理解指令是怎么在硬件层面执行的。

    2019-05-18
    2
  • Nevermore
    老师shell脚本是一步编译成机器码的吗?还是转换成汇编再编译成机器码

    作者回复: 如果在这个映射关系里面,可以认为shell脚本是被bash解释器来运行的,调用对应的编译好的二进制可执行文件相当于bash解释器在做对应的“翻译”动作

    2019-05-12
    2
  • 给你力量
    1. 老师我对汇编语言的意义还是不太理解,是不是可以将汇编语言的意义理解为对机器码的封装,提高对于程序员的可阅读性呢?
        就是所有的代码,都可以归纳为一组特定组合的机器码操作,可以直接将高级语言代码编译为机器码执行,但是这样子的话,程序员就无法看出这行代码到底进行了什么操作。
        如果把特定作用的机器码封装为汇编代码,高级语言先编译成可读性高的汇编代码,这样就能理解代码实际执行了什么机器码操作。
         所以汇编语言是基于机器语言的基础上生成的。

         不知道我这个理解对不对,希望老师能指点一下。

    2. 如何区分R、I、J指令,是通过opcode吗?如果是的话,是不是就三种opcode?

    谢谢老师的分享!

    作者回复: 给你力量同学你好,

    1. 你的理解是对的。不过其实历史上汇编不只是为了让程序员可以更容易读代码,也是为了让程序员更容易写代码。毕竟无论是打孔带,旋钮还是其他的更“古老”的方式来编程都太没有效率了。

    2. 是的,应该说是三大类opcode,每一类里面有很多不同的具体的opcode。

    2019-05-08
    2
  • Kelly.W
    老师您好,课程里说汇编代码和机器码是一一对应的,我现在知道在不同平台的机器码是不一样的(linux/windows),那么不同平台也有不同的高级语言->汇编代码的对应规则吗?

    作者回复: 机器码的差异不在于操作系统(也就是不在于Linux/Windows)。而是在于体系结构(Intel X86/ARM/MIPS)。
    不同平台的汇编语言也是不同的,所以同样的高级语言编译器在不同的平台上编译出来的代码也是不一样的。

    2019-05-04
    1
    2
  • 发条橙子 。
    刷了两遍大致清晰了 ,又学到不少知识 , 看完感觉从高级语言-到汇编-机器码的转换实际上不是很复杂 ,重要的是刻纸袋是个体力活 。

    老师, 这边还有两个细节点想请教一下:
    1. 前面的文章讲冯诺依曼体系结构的时候说到 。 运算器、控制器、存储器、输入输出设备 , 这里将Cpu指令集存储在存储器 , 这里的存储器指的是硬盘么 ?
    2. 汇编语言和Cpu指令集先后的问题 , 老师上面 add 的例子是从 汇编 推导出 MIPS 对应的二进制 Cpu指令 。 按照历史发展 ,是MIPS公司先推出了一款自己的CPU指令集, 然后对应的汇编语言厂商在按照这个指令集开发相应的汇编语言么 ?

    希望老师能指点一二 ,另外老师五一节快乐 (虽然快过完了)

    作者回复: 发条橙子同学你好,也希望你过了一个愉快的五一啊。

    CPU的“指令集”是一个抽象的概念,你可以认为实际是被变成了CPU的电路。

    我们具体的一个个程序的指令,是存在内存里的,或者说,是程序用一个文件格式存在磁盘上,然后通过装载器加载到内存里面,再一条条加载到指令寄存器里面,然后执行的。

    关于汇编语言,你可以这样认为,不过厂商一般会直接提供对应的汇编语言和汇编器,但是也的确有支持多种指令集的第三方汇编语言和汇编器。

    2019-05-04
    2
收起评论
71
返回
顶部