查看原文
其他

程序员应如何理解CPU:上篇

码农的荒岛求生 码农的荒岛求生 2020-12-18


本节属于操作系统第一章即基础篇,在真正开始操作系统相关章节前回顾一些重要的主题,以下是目录,由于本文篇幅较多因此按上篇、下篇两次发布,目录中黑体为本篇内容。




什么是机器指令

程序语言的演变

CPU是如何工作的

寄存器

程序计数器

栈指针

CPU的工作模式

总结



你可能会想,作为程序员,我们需要理解CPU吗?

是的,如果你想彻底理解操作系统的话。

在后面的课程中你会发现,仅仅依赖软件是无法完成某些特定的功能的,比如,我们在《程序员如何理解内存》这一节中提到的虚拟内存,这项功能的实现需要依赖CPU的配合才能完成。因此在这一节中,将重点讲解一下作为程序员应该知道哪些CPU相关的知识。


什么是机器指令

你肯定已经知道了,CPU是计算机的大脑,程序员写的代码最终都是CPU来执行的。但作为计算机的大脑,CPU并不认识C,C++、Python、Java等语言,这些语言是人类可以认识的,CPU真正能理解的是机器指令。我们写的C/C++程序(包括Java、Python等于语言的解释器)最终被编译器翻译成了机器指令后才被CPU执行的。

你可以把机器指令理解为CPU可以直接执行的代码。那这些CPU可以直接执行的机器指令是什么样子的呢?

我们来看一下C语言中经典的HelloWorld程序:

#include<stdio.h>
int main() { printf("hello world."); return 0;}

以下就是HelloWorld程序在被编译器编译后生成的可执行程序。

40055d: 48 83 ec 08 sub $0x8,%rsp400561: be 04 06 40 00 mov $0x400604,%esi400566: bf 01 00 00 00 mov $0x1,%edi40056b: b8 00 00 00 00 mov $0x0,%eax400570: e8 eb fe ff ff callq 400460 <__printf_chk@plt>400575: b8 00 00 00 00 mov $0x0,%eax40057a: 48 83 c4 08 add $0x8,%rsp40057e: c3 retq40057f: 90 nop

不用担心看不懂这些是什么意思。在这里我们无需理解每一条指令的作用,仅仅给大家展示一下CPU可以认

识的机器指令是什么样子的。如上图所示,其中每一列的含义如下:

  • 第一列是对应的机器指令在内存中的位置(十六进制表示)。

  • 第二列是机器指令(十六进制表示)。

  • 第三列是机器指令对应的汇编语言。

从这里我们可以得出以下事实:

1,可执行程序中每条指令的所在内存地址是已经确定好的了,比如在第一条指令“48 83 ec 08”的地址是40055d(注意这里是十六进制)。你可能会问,程序在运行的时候才会被加载到内存,怎么程序还没有运行就已经确定指令所在内存的地址了呢?这是一个很好的问题,要知道答案就必须理解操作系统的一项魔法,那就是我们之前介绍过的虚拟内存。我们将在《操作系统如何管理内存》这一章来回答这个问题。

2,编译器确实将C程序翻译成了机器指令。

我们以第一条机器指令“48 83 ec 08 ”为例,这里是十六进制表示的,我们把它转换成为二进制:

01001000100000111110110100001000

这就是CPU可以直接执行的机器指令,CPU确实只认识0和1两个数字。

作为程序员我们需要知道,无论多么复杂的软件,最终都是等价的转换为如上所示的一条条简单的01二进制机器指令,CPU只认识这样的指令,CPU不认识if else while for等等人类认识的语言。


程序语言的演变

这里需要大家知道,最开始的程序员就是直接写这种01二进制指令,然后交给CPU执行的。这里不得不佩服一下这些程序员前辈。

虽然这种01指令可以直接交给CPU执行,但是对人类来说很不友好,为解决这个问题才发明了汇编语言,通过汇编语言程序员可以直观的理解一条机器指令的作用,现在仍然有许多领域需要程序员理解汇编语言,比如操作系统程序员,操作系统的一小部分需要依赖汇编语言来完成。但是汇编语言依然对人类不是很友好,因此C语言就被发明了出来。编译器把C语言程序翻译成汇编语言,然后再把汇编语言转换为对应的机器指令。C语言的出现极大的提高了程序员的生产力,但是使用C语言需要程序员理解内存等硬件以及指针的概念,因此Java语言被发明了出来,Java语言的程序员完全不需要理解内存指针等概念就能很好的进行软件研发,生产力得到进一步提高。现在用Python等语言所写的程序已经非常接近普通英语句式了,因此对程序员的要求进一步降低,这使得更多的人无需对计算机有深刻的理解就可以进行软件创作。

你会发现编程语言的发展史就是程序员生产力不断提高的过程。

让我们回到CPU的话题。


CPU是如何工作的

在理解了什么是机器指令后,我们来看一下CPU是如何工作的。

我们首先来明确一点,那就是CPU要执行的指令是存放在内存中的,这就是为什么程序在开始运行之前要被拷贝到内存当中的原因,大家还记得这张图吧。可执行程序一般保存在磁盘当中,运行时需要拷贝到内存,这被称之为程序加载。然后操作系统告诉CPU机器指令所在内存的起始地址,然后CPU从该地址开始执行我们写的程序。


本质上CPU的工作非常简单,CPU依次从内存中取出指令,然后对指令进行解码,所谓“解码”就是弄清楚这条指令要做些什么,比如在上一小节中,对指令“01001000100000111110110100001000”解码后,发现这是一个进行减法操作的指令,得到这些信息后CPU开始执行这条指令,执行完成后继续取出下一条指令继续执行。因此你会发现CPU的工作过程就是这样的:

从内存中取出机器指令对指令进行解码执行指令,执行完毕后回到1


寄存器

我们在《程序员应如何理解内存》一节中介绍了内存,还记得吗,内存就是由一堆可以装0或者1的小盒子组成,但是相对于CPU的速度来说,CPU会觉得从内存读取和写入是很慢的。CPU在执行过程中会产生的临时数据,如果这些临时数据的保存借助内存的话无疑会降低CPU执行指令的速度。为了加快指令的处理速度,CPU需要自己的“工作区域”而不借助内存。CPU在自己的“工作区域”中快速处理指令,然后把结果写回慢吞吞的内存。

这里CPU自己的“工作区域”就是寄存器。你可以把寄存器理解为放在CPU中的特殊内存,所谓特殊指的是读写速度非常快,速度快到足以匹配CPU。也就是说你可把寄存器简单的理解为读写速度飞快的内存,注意寄存器是和CPU放在一起的。

因此,寄存器是一种制作在CPU中的高速存储介质。本质上寄存器和内存是一样的,都是存放0或1的盒子,但是寄存器读写非常快,同时容量相对于内存小很多,现在内存容量通常已经达到GB,而寄存器容量通常是MB甚至KB。

有同学可能会问了,既然寄存器有这么多优点,为什么不用寄存器来制作内存呢?答案是money。寄存器造价相对昂贵,因此当前的寄存器容量都很小。

通常情况下,寄存器对程序员都是不可见的,所谓对程序员不可见也就是说程序员不能利用编程语言来操作这些寄存器。只有汇编语言才能操作寄存器,高级语言比如C/C++、Java都不能直接对寄存器进行编程。

CPU中的寄存器有很多类型,每种类型都有特定的功能,虽然大部分程序员无需关心寄存器,但是有几种类型的寄存器需要我们理解其功能,因为这对于理解操作系统大有裨益,下面我们就来看一下。

后续内容将在《程序员应如何理解CPU:下篇》中继续。



操作系统系列

基础篇
1,什么是程序

2,程序?进程?傻傻分不清

3,程序员应如何理解内存:上篇
4,程序员应如何理解内存:中篇
5,程序员应如何理解内存:下篇


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存