深入 CPU 谈程序的运行机制

概述

计算机的属性反映的是人类创造者的本性。
其内部复杂的系统,依赖于底层原理来驱动,很多计算机的原理是相同的,不同的编程语言,复杂的业务逻辑等等,都是在讲述同一个故事。
本篇从物理硬件上的 CPU 如何支撑源代码运行的角度来窥探计算机硬件和软件的合作关系。

首先明确程序的概念。程序是指令和数据的组合体。例如,C语言“printf ("你好"); ”这个简单的程序中,printf是指令,"你好"是数据。
你好 和 printf从人的视角是很容易理解的,但计算机只能理解0101 的二进制机器码。
所以编程代码是如何运行的呢。

本文分两层讲述:

第一层:概述高级语言如何变成机器码.

第二层:解释机器码如何驱动 CPU 运行。

两者结合就解释了程序运行的原理。

第一层: 概述高级语言如何变成机器码

计算机作为被造者,必然会朝向对人类越来越友好的轨迹发展,所谓的高级语言是从人类的视角出发,编程更友好。

从最开始通过纸带有孔无孔来表示 0 和 1。
到后来,汇编语言采用助记符(memonic)来编写程序,每一个原本是0101的机器语言指令都会有一个与其相应的助记符,助记符通常为指令功能的英语单词的简写。
而汇编指令与原有的二进制指令几乎保持着一一对应的关系。
所以汇编语言的产生,但依赖于 0101 机器码的年代,是一个很大的生产力提升。
但纵使这样,汇编编程在大型程序中,晦涩难懂,很为开发维护。因此后续衍生了 C 语言,再后来从面向程序的语言进化到更高级的语言。必然从这种趋势上看,未来肯定会有更好的语言,将编程的门槛降到更低。
但以上是建立在人类的视角,在计算机的视角,尤其是 CPU 和内存,纵使你千遍万换,CPU 能直接识别并使用的语言,对应的指令信息只有二进制的机器码。
所以要想人类编写的语言能让 CPU 真正执行,就需要层层剥离对人类友好的抽象,还原本真,变成机器码。

目前的高级语言可以分为两类: 解释型语言编译型语言,这两种分别需要通过解释器和编译器变成汇编语言。然后借助汇编语言与机器码的映射关系,变为 CPU 可识别的程序。

所以总结下来,第一层就是从高级语言的源码层变成 CPU 能识别的机器码。

源代码到机器码.png

第二层: 解释机器码如何驱动 CPU 运行

前面讲到,程序中包含指令和数据。以上两个概念,在 CPU 中是怎么表示的呢。

这里先对 CPU 的物理结构简单展开以便于后续原理解释。

CPU 的物理结构和运行过程

物理结构上看,CPU 和内存都是由一堆具有 ON/OFF 开关功能的晶体管组成的电子器件。
从功能的角度,CPU的内部由寄存器、控制器、运算器、时钟四个部分构成,各部分之间由电流信号相互连通。

  • 寄存器可用来暂存指令、数据等处理对象,可以将其看作是内存的一种。根据种类的不同,一个CPU内部会有20~100个寄存器。
  • 控制器负责把内存上的指令、数据等读入寄存器,并根据指令的执行结果来控制整个计算机。
  • 运算器负责运算从内存读入寄存器的数据。
  • 时钟负责发出CPU开始计时的时钟信号。不过,也有些计算机的时钟位于CPU的外部。
程序运行流程示例
CPU的四个构成部分
  • CPU 的运行过程大致可以解释为:
    指令和数据存储在内存中,程序启动后,根据时钟信号,控制器会从内存中读取指令和数据。通过对这些指令加以解释和运行,运算器就会对数据进行运算,控制器根据该运算结果来控制计算机。

接下来就深入到寄存器内部,层层剥开机器码驱动 CPU 的运行的神秘面纱。

考虑到写一堆由 01 组成的机器码对于阅读和理解过于晦涩。
而汇编语言采用助记符(memonic)来编写程序,每一个原本是01的机器语言指令都会有一个与其相应的助记符,助记符通常为指令功能的英语单词的简写。例如,mov和add分别是数据的存储(move)和相加(addition)的简写。汇编语言和机器语言基本上是一一对应的。
我们利用汇编语言与机器码一一对应的关系来说明机器码驱动 CPU 运行的原理。

CPU 中的寄存器

寄存器根据功能的不同,分类也不同。
寄存器就是程序运行时,指令和数据的真实物理载荷。

寄存器的种类

这里的数据,可以分为用于运算的数值表示内存地址的数值两种。
当然,数据类型不同,存储该数值的计数器也不同。
用于运算的数值放在累加寄存器中,表示内存地址的数值则放在基址寄存器和变址寄存器中。(所以值类型和引用类型在寄存器的存储方式就有区别)

所以综上能看出,CPU是具有各种功能的寄存器的集合体。其中,程序计数器、累加寄存器、标志寄存器、指令寄存器和栈寄存器都只有一个,其他的寄存器一般有多个。

程序员严重的 CPU

寄存器的运作原理

程序计数器

程序运行启动后,操作系统会将硬盘中保存的程序复制到内存中,让 CPU 根据指令和数据信息来执行。
程序计数器也叫做指令计数器,是用于存放下一条指令所在单元的地址的地方。
当执行一条指令时,首先需要根据程序计数器中存放的指令地址,将指令由内存取到指令寄存器中,此过程称为取指令。与此同时,PC中的地址或自动加1或由转移指针给出下一条指令的地址。此后经过分析指令,执行指令。完成第一条指令的执行,而后根据程序计数器取出第二条指令的地址,如此循环,执行每一条指令。

为保证程序能连续自动执行下去,CPU 必须具有某些手段来确定下一条指令的地址,程序计数器就提供了物理基础。在程序开始执行前,必须将它的起始地址,即程序的第一条指令所在的内存单元地址送入程序计数器,因此程序计数器的内容即是从内存提取的一条指令的地址。当执行指令时,CPU 将自动修改程序计数器的内容,即每执行一条指令程序计数器增加一个量,这个量等于指令所含的字节数,以便使其保持的总是将要执行的下一条指令的地址。由于大多数指令都是按顺序来执行的,所以修改的过程通常只是简单的对程序计数器加1。
但是,当遇到转移指令如JMP(跳转、外语全称:JUMP)指令时,后继指令的地址(即PC的内容)必须从指令寄存器中的地址字段取得。在这种情况下,下一条从内存取出的指令将由转移指令来规定,而不像通常一样按顺序来取得。因此程序计数器的结构应当是具有寄存信息和计数两种功能的结构。

根据顺序,选择和循环不同方式,往程序计数器中送入不同的指令。

函数调用机制和函数调用堆栈

函数调用处理也是通过把程序计数器的值设定成函数的存储地址来实现的。不过,这和条件分支、循环的机制有所不同,函数的调用过程更为复杂,尤其对于嵌套函数的调用,单纯的跳转指令无法实现函数的调用。 函数的调用需要在完成函数内部的处理后,处理流程再返回到函数调用点(函数调用指令的下一个地址)。因此,如果只是跳转到函数的入口地址,处理流程就不知道应该返回至哪里了.

具体用一段程序讲解

其流程是:

int a = 123;
int b = 456;

c = MyFunc(a, b);

d = Nextfunc(a, b);

int Myfunc(int a, int b) {
    if a > b {
        return a - b;
    } else {
        return a + b
    }
}

int Nextfunc(int a, int b) {
    return a - b;
}
函数调用原理01.jpg
函数调用原理02.jpg

除了程序计数器来保证指令的调用之外,对于函数调用内部的函数,需要通过栈寄存器来记录并保存返回值。

机器语言的call指令和return指令能够解决这个问题。建议大家把二者结合起来来记忆。函数调用使用的是call指令,而不是跳转指令。在将函数的入口地址设定到程序计数器之前,call指令会把调用函数后要执行的指令地址存储在名为栈的主存内(此代码中代码的是 Nextfunc的指令)。函数处理完毕后,再通过函数的出口来执行return命令。return命令的功能是把保存在栈中的地址(Nextfunc的指令)设定到程序计数器中,继续执行。

简单来说,函数的调用会转化为 call 指令,将该函数的调用地址设定到程序计数器中;
函数结束会转换成 return 指令,将返回目的地的地址(下一条指令的地址)设定在程序计数器上,这样程序就可以流畅运行了。

至于栈寄存器的运行原理这里不作展开。

汇编语言和机器语言的种类

通过 CPU 的描述不难懂得,其实 CPU 依赖的硬件是有限的。所以任何复杂的逻辑,翻译成对应的机器之类,也就几种。

类型 功能
数据转送指令 寄存器和内存,内存和内存,寄存器和外围设备之间的读写操作
运算指令 用累加寄存器执行算术运算,逻辑运算,比较运算和移位运算
跳转指令 实现条件分治,循环,强制跳转等
call/return 指令 函数的调用/返回函数的地址

总结

本文主要希望通过对程序的运行机制有一个整体宏观的描述能让大家更充分的理解编程,让抽象的世界不再那么晦涩难懂,给你恍然大悟的感觉。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345