01 计算机系统漫游

本文详细介绍了程序如何存储在计算机中,包括CPU、RAM、总线和I/O设备的作用。从C程序示例出发,阐述了编译过程和执行步骤,涉及预处理、编译、汇编、链接和执行,以及高速缓存和存储器层次结构的重要性。同时讲解了操作系统如何管理和抽象硬件,如进程、虚拟内存和网络概念。
摘要由CSDN通过智能技术生成

1 程序是如何存储与执行的

在这一节中,将介绍:

  • 程序是如何保存在计算机中,
  • 并且如何转换成计算机可识别、可执行的信息,
  • 然后介绍计算机硬件中是如何一步步执行程序的。

所以首先简单介绍计算机的硬件组成,以此作为基础后,一步步介绍程序是如何存储并执行的。

1.1 计算机硬件简介

典型的计算机硬件组成,可分为三部分:CPU、RAM、I/O。

img

下面介绍比较重要的部分:

1.1.1 总线

总线是贯穿各个计算机硬件的桥梁。它携带信息,并且负责将信息在各个部件之间进行传递。总线通常被设计成传送特定长度的字节块,称为 字(Word)。这是一个基本的系统参数,不同系统中各不相同。

总线主要包含:

  • 数据总线:通常用来传输数据。如从主存RAM中传输数据到CPU中
  • 地址总线:主要用于传输地址。如从RAM的地址1000处获取数据,这里的1000就是通过地址总线进行传输的
  • 控制总线:主要传输控制、时序信息。如读写、中断等

1.1.2 I/O设备

I/O设备:是系统和外部世界的联系通道。

每个I/O设备都通过一个 控制器 或 适配器 与I/O总线相连,负责I/O设备和I/O总线间的信息传递。

控制器与适配器的区别Linux控制器(Controller)与适配器(Adapter)

  • 控制器(Controller):集成在CPU主板上,并可以将CPU发来的逻辑指令通过特定协议转换为设备可以识别的控制信号;

  • 适配器(Adapter):独立的外部设备,可以实现和控制器一样的功能,如网卡等;

一般情况下,

  • 对于磁盘来说,控制数据/指令转换的机制都是靠控制器来实现的;

  • 但是对于网卡,USB等设备来说,则是靠适配器来实现的。

1.1.3 主存(RAM)

是一个临时存储设备。

当程序运行时,主要保存程序以及程序处理的数据。基本单位是字节(Byte),从逻辑上看,对每个字节都指定了唯一的地址,这个地址从0开始。

1.1.4 处理器

中央处理器 (英语:Central Processing Unit,缩写:CPU)。功能主要是解释计算机指令以及处理计算机软件中的数据

一个CPU由若干部分组成:

  • 寄存器:通常为8位寄存器,用来保存一个字节的数据。

    CPU中有若干寄存器,每个寄存器都有唯一的地址,用来保存CPU中临时运算结果。其中有两个寄存器比较特殊:

    • 指令地址寄存器(程序计数器):保存当前指令在内存中的地址,每次执行完一条指令后,会对该寄存器的值进行修改,指向下一条指令的地址。
    • 指令寄存器:保存当前从主存中获取的,需要执行的指令。
  • 算术逻辑单元(ALU):主要用来处理CPU中的数学和逻辑运算。

    它包含两个二进制输入,以及一个操作码输入,用来决定对两个输入进行的算数逻辑操作。然后会输出对应的运算结果,以及具有各种标志位,比如结果是否为0、结果是否为负数等等。

    它有一个算术单元(Arithmetic Unit),一个逻辑单元(Logic Unit)

    • 算术单元:负责计算机里的所有数字操作,比如加减法、增量运算(给某个数字+1)等等。
    • 逻辑单元:执行逻辑操作,比如AND、OR和NOT操作,也能做简单的数值测试,比如数字是不是负数。
  • 控制单元:是一系列门控电路,通过门控电路来判断指令寄存器中保存的指令内容然后调整控制 主存 和 寄存器 的读写数据和地址,以及使用ALU进行运算

    简单理解为:一系列门控电路,然后根据你程序的指令来调控CPU中的各种资源。

CPU中执行指令的过程提取解码执行写回

  1. 提取:首先,根据“指令地址寄存器”从内存中获取对应地址的数据;
  2. 解码:然后,将其保存在“指令寄存器”中;
  3. 执行:“控制单元”会对指令内容进行判断,并调用寄存器、ALU等执行指令内容;
  4. 写回:以一定格式将执行阶段的结果简单的写回。(运算结果经常被写进CPU内部的寄存器,以供随后指令快速访问。)
  5. 最后,更新“指令地址寄存器”,使其指向下一个要执行的指令地址。

本小节参考内容:

[深度人工智障:读书笔记]《计算机科学速成课》—5 算术逻辑单元-ALU

[深度人工智障:读书笔记]《计算机科学速成课》—6 寄存器和内存

[深度人工智障:读书笔记]《计算机科学速成课》—7 中央处理器CPU

1.2 程序存储和执行

以最简单的C程序为例:

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

这段代码需要保存在一个文件中,称为源文件,这是这段程序生命周期的开始。然而计算机只认识二进制的0和1,它根本不认识文本里面到底写的什么。

所以大部分的现代计算机系统都会使用ASCII标准来表示这些文本,简单来说就是给每个字符都指定一个唯一的单字节大小的编号,然后将文本中的字符都根据ASCII标准替换成对应的编号后,就转换成了字节序列,所以该源文件是以字节序列的形式保存在文件中的。

我们可以通过OD打印上面程序的所有字符的ASCII码(方法演示),结果如下所示:

PS D:\E\VSOCED> od -Ax -tcd1 .\test.c
000000   /   /       t   e   s   t   .   c  \r  \n   #   i   n   c   l
         47   47   32  116  101  115  116   46   99   13   10   35  105  110   99  108
000010   u   d   e       <   s   t   d   i   o   .   h   >  \r  \n   i
        117  100  101   32   60  115  116  100  105  111   46  104   62   13   10  105
000020   n   t       m   a   i   n   (   )  \r  \n   {  \r  \n
        110  116   32  109   97  105  110   40   41   13   10  123   13   10   32   32
000030           p   r   i   n   t   f   (   "   h   e   l   l   o    
         32   32  112  114  105  110  116  102   40   34  104  101  108  108  111   32
000040   w   o   r   l   d   \   n   "   )   ;  \r  \n
        119  111  114  108  100   92  110   34   41   59   13   10   32   32   32   32
000050   r   e   t   u   r   n       0   ;  \r  \n   }
        114  101  116  117  114  110   32   48   59   13   10  125
00005c

从上面可知,系统中的所有信息都是由一串比特表示的,区分不同数据对象的唯一方法就是上下文比如:

  • 在一串数字中,0x90表示154
  • 在一串机器码中,0x90表示nop指令
  • 在 一串字符串中,0x90表示一个特殊的字符

为了人们能够读懂程序的功能,所以使用了高级语言如c写了这段程序。但是对于机器而言,它只能执行指令集中包含的指令。所以,为了机器能够在系统中运行这段程序,我们:

  • 首先,需要将每句c语句都转换成一系列的低级机器语言指令;
  • 然后,将这些指令按照可执行目标程序的格式打包好后,以二进制磁盘文件形式保存起来,该文件称为目标文件。

这种,从源文件 ——》目标文件 的过程由编译器驱动。该过程主要分为4阶段:(也可参考:Linux编译过程

img

1.2.1 预处理阶段

*.c ——》*.i

C预处理器主要负责:文本替换(text substitution)、注释剥离(stripping comments)、文件包含(file inclusive)。

  • 在我们的源代码中使用预处理指令来请求 文本替换 和 文件包含。在代码中使用**“#”**来表示这是预处理指令。

    • 第一个是头文件stdio.h,他是包含在源文件中的。(文件包含),即将#include <stdio.h>替换成头文件stdio.h中的内容。
    • 第二个是字符串替换。(文本替换)

1.2.2 编译阶段

*.i ——》*.s

在这个阶段中,Gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,Gcc把代码翻译成汇编语言。

这样做的好处在于,通过为不同语言 不同系统上配置 不同的编译器,能够提供通用的汇编语言,这样对于相同的语言,就能兼容不同的操作系统,而对于同一个系统上,通过安装不同语言的编译器,也能运行不同语言写的程序了。

而汇编语言相对C语言更加低级,它对机器码进行了修饰,为每一个操作码提供了更加简单、容易记的助记符,并且提供了很多机器码不具有的功能,比如自动解析JUMP指令地址等等。该语言的编写和底层硬件连接很密切,程序员仍需要思考使用什么寄存器和内存地址。

我们这里使用指令集架构来提供对实际处理器硬件的抽象,这样机器代码就好像运行在一个一次只执行一条指令的处理器上。

可参考:[深度人工智障:读书笔记]《计算机科学速成课》—11 编程语言发展史

1.2.3 汇编阶段

*.s——》*.o

汇编阶段是把编译阶段生成的汇编代码文件”*.s”转成二进制目标代码(机器可读的机器语言)。

1.2.4 链接阶段

链接是产生可执行程序文件或 可与其他目标文件结合产生可执行文件的最后阶段。

我们写代码时通常会使用C标准库中提供的函数,但是我们代码中并没有这些函数的具体实现,所以就需要在链接阶段将该函数的具体实现合并到我们的hello.o。比如我们程序中使用了printf函数,而该函数存在于一个单独预编译好的目标文件printf.o中,所以我们只需要将该文件合并到我们的hello.o中,就能正确使用该函数了。

最终得到的hello文件就是可执行目标文件,可以被加载到内存中,由系统执行。

1.2.5 执行 可执行程序

通过以上的编译过程,我们从由C语言的源文件hello.c编译得到了可执行目标文件hello,接下来我们就可以运行该目标文件了

  1. shell读入我们输入的字符./hello后,将其逐一读入到CPU的寄存器中,然后再将其存放到主存中
  2. 输入回车后,shell执行一系列指令将hello目标文件中的代码和数据从磁盘复制到主存
  3. CPU开始执行hello的main程序中的机器指令,它将hello, world\n字符串中的字节从主存复制到CPU寄存器,再从CPU寄存器复制到显示设备。

通过以上过程,我们就完成了程序的保存和执行的完整过程。

可参考:[深度人工智障:读书笔记]《计算机科学速成课》—7 中央处理器CPU

2 高速缓存

在运行程序的过程中,发现一个很重要的问题:系统花费了大量的时间把信息从一个地方挪到另一个地方。

在上面实例中:test程序的机器指令开始是放在磁盘上

  • 程序加载时,它们被复制到主存(从磁盘到内存)
  • 程序运行时,它们被复制到处理器(从内存到处理器)

"hello, world\n"数据最开始在磁盘,后来复制到内存,然后是显示设备。

这些复制就是开销,减缓了程序的工作。怎么才能使这些复制工作尽快完成呢?

答:处理器和内存的速度差异非常大,系统设计者系统设计者采用了更小、更快的存储设备,即高速缓存存储器(简称高速缓存),作为暂时的集结区域,用来存放处理器近期可能会需要的信息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uQD8SLrc-1615013591794)(https://github.com/xxg1413/CSAPP/raw/master/Chapter01/image/1-5-1.png)]

在单处理器系统中,一般含有二级缓存,最小的L1高速缓存速度几乎和访问存储器相当,大一些的L2高速缓存通过特殊总线连接到处理器,虽然比L1高速缓存慢,但是还是比直接访问主存来的快。在多核处理器中,还有一个L3高速缓存,用来共享多个核之间的数据。

问:为什么需要n级缓存呢?

答:因为系统可以获得一个很大的存储器,同时访问速度也很快,原因是利用了高速缓存的局部性原理,即程序具有访问局部区域里的数据和代码的趋势。通过让高速缓存里存放可能经常访问的数据的方法,大部分的存储器操作都能在快速的高速缓存中完成。

一般利用了高速缓存的程序会比没有使用高速缓存的程序的性能提高一个数量级。

3 存储器层次结构

实际上,每个计算机系统中的存储设备都被组织成了一个存储器层次结构,下图所示:

img

存储器层次结构的主要思想是一层上的存储器作为低一层存储器的高速缓存。因此:

  • 寄存器文件就是L1的高速缓存,
  • L1是L2的高速缓存,
  • L2是L3的高速缓存,
  • L3是主存的高速缓存,
  • 而主存又是磁盘的高速缓存。
  • 在某些具有分布式文件系统的网络系统中,本地磁盘就是存储在其他系统中磁盘上的数据的高速缓存。

4 操作系统管理硬件

操作系统的出现避免了程序员直接去操作硬件(主存、处理器、I/O设备),它可以看成是应用程序和硬件之间的一层软件。

给程序员提供硬件的抽象:

  • 比如将正在运行的程序抽象为进程
  • 将程序操作的主存抽象为虚拟内存
  • 将各种I/O设备抽象为文件的形式,让程序员能够直接通过这层软件很好地调用硬件,避免了过多的硬件细节。

接下来将简单介绍这三层抽象。

4.1 进程

进程是操作系统对一个正在运行的程序的一种抽象。

计算机执行程序时,需要将程序对应的指令保存在内存中,并且使用CPU和I/O设备,但是单核计算机一个时刻只能处理一个程序。但是从我们的视角来看,计算机像在同时处理好多程序,比如你可以在shell中运行hello,此时就运行了shell程序和hello程序。

为了方便对运行程序时所需的硬件进行操作,操作系统对正在运行的程序提供了一种抽象——进程

**提供了一种错觉:**一个系统上可以同时运行多个进程,而每个进程好像在独占地使用硬件。这样程序员就无需考虑程序之间切换所需操作的硬件,这些由操作系统的内核进行管理。

**内核:**操作系统常驻内存的部分,不是一个独立的进程,而是管理全部进程所用代码和数据结构的集合。

并发运行:操作系统通过交错执行若干个程序的指令,不断地在进程间进行切换来提供这种错觉,这个称为并发运行。

4.1.1 上下文切换

首先,当进程A要切换到进程B时,进程A通过系统调用,将控制权递给操作系统,然后操作系统会保存进程A所需的所有状态信息,称为上下文,比如寄存器以及内存内容,然后创建进程B及其上下文,然后将控制权递给进程B。当进程B终止后,操作系统就会恢复进程A的上下文,并将控制权还给进程A,这样进程A就能从断点处继续执行。这个过程都是由操作系统的内容进行控制的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PpX6HVpA-1615013591799)(https://github.com/xxg1413/CSAPP/raw/master/Chapter01/image/1-7-1.png)]

4.1.2 线程

现代系统中,一个进程中可以并发多个线程,每条线程并行执行不同的任务,线程是操作系统能够进行运算调动的最小单位,是进程中的实际运作单位。每个线程运行在进程的上下文中,并共享相同的代码和全局数据。

**优点:**多线程之间比多进程之间更容易共享数据,并且效率更高。

4.2 虚拟内存

**虚拟存储器(虚拟内存)**是一个抽象概念,它为每个进程提供了一个假象,即每个进程都在独占地使用主存。

每个进程看到的是一致的存储器,称为虚拟地址空间

计算机会将多个程序的指令和数据保存在内存中。当某个程序的数据增加时,可能不会保存在内存的连续地址中,这就使得代码需要对这些在内存中非连续存储的数据进行读取,会造成很大困难。如何解决?

答:为了解决这个问题,操作系统对内存和I/O设备进行抽象——虚拟内存。**它提供了一种错觉:**程序运行在从0开始的连续虚拟内存空间中,而操作系统负责将程序的虚拟内存地址投影到对应的真实物理内存中。这样使得程序员能直接对连续的空间地址进行操作,而无需考虑非连续的物理内存地址。

操作系统将进程的虚拟内存划分为多个区域,每个区域都有自己的功能,接下来从最低的地址开始介绍:

  • **程序代码和数据:**对所有进程来说,代码都是从同一固定地址开始(如下图的“固定地址”就是程序开始的地方),然后是C全局变量。这部分在进程一开始运行时就被指定大小了
  • :当调用类似C中的mallocfree标准库函数时,堆会在进程运行时动态扩展和伸缩。
  • **共享库:**用来存放像C标准库和数学库这样公共库的代码和数据的区域。
  • **栈:**位于用户虚拟内存顶部,编译器用来实现函数调用,当调用函数时,栈就增长,当返回一个函数时,栈就缩小。
  • **内核虚拟内存:**地址空间顶部的区域为内核保留,不允许应用程序读写这个区域的内容或者直接调用内核代码定义的函数。

img

4.3 文件

操作系统将所有I/O设备看成是文件,而文件是字节序列,这样系统中的所有输入输出都可以调用系统函数来读写文件实现,简化了对各种各样的I/O设备的操作。

5. 网络

从一个单独的系统来看,网络可以看成一个I/O设备,当系统从主存复制一串字节到网络适配器时,计算机就会自动将其发送到另一台机器。在后续的课程会详细介绍。

6 并发与并行

**并发(Concurrency)**指一个同时具有多个活动的系统。**并行(Paralleism)**指的是用并发来时一个系统运行得更快。并行可以在计算机系统的多个抽象层次上运用。

  • 线程级并发:单处理器系统,一个CPU,进程间快速切换的方式实现;

  • 多处理器系统:多个CPU,要求程序运行更快,就需程序多线程 超线程,允许一个CPU执行多个控制流的技术;

  • 指令集并行:处理器可以同时执行多条指令;( 超标量处理器:处理器可以达到比一个周期一条指令更快的执行速率)。

    参考:[深度人工智障:读书笔记]《计算机科学速成课》—9 高级CPU设计

  • 单指令,多数据并行:许多现代处理器拥有特殊的硬件,允许一条指令产生多个可以并行执行的操作,这种方式称为单指令、多数据 ,即SIMD并行。

7 抽象 [补充]

计算机系统中的抽象: 如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ReB6PQq-1615013591801)(https://github.com/xxg1413/CSAPP/raw/master/Chapter01/image/1-9-1.png)]

  • 文件是对I/O的抽象
  • 虚拟存储器(虚拟内存)是对程序存储器的抽象
  • 进程是对一个正在运行程序的抽象
  • 虚拟机是对整个计算机的抽象

相关文章

  1. https://zhuanlan.zhihu.com/p/103969274

  2. https://github.com/xxg1413/CSAPP/blob/master/Chapter01/1.0.md

  3. 控制器、适配器:https://blog.csdn.net/u014753393/article/details/50259207

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值