计算机系统

大作业

题 目程序人生-Hellos P2P

专 业 计算机科学与技术

计算机科学与技术学院

摘 要

本文基于Linux系统下,对hello程序的生命周期展开研究。本文借助相关开发工具,详细阐述了程序由C源文件经过预处理、编译、汇编以及链接四个阶段生成hello可执行文件的过程,并对程序的进程管理阶段进行剖析。通过对hello程序生命周期的研究,有助于深入理解计算机系统,感受计算机系统设计与实现的精妙艺术。

关键词:计算机系统;Linux;汇编;编译;链接;进程

目 录

第1章 概述

1.1 Hello简介

1.2 环境与工具

1.3 中间结果

1.4 本章小结

第2章 预处理

2.1 预处理的概念与作用

2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析

2.4 本章小结

第3章 编译

3.1 编译的概念与作用

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析

3.4 本章小结

第4章 汇编

4.1 汇编的概念与作用

4.2 在Ubuntu下汇编的命令

4.3 可重定位目标elf格式

4.4 Hello.o的结果解析

4.5 本章小结

第5章 链接

5.1 链接的概念与作用

5.2 在Ubuntu下链接的命令

5.3 可执行目标文件hello的格式

5.4 hello的虚拟地址空间

5.5 链接的重定位过程分析

5.6 hello的执行流程

5.7 Hello的动态链接分析

5.8 本章小结

第6章 hello进程管理

6.1 进程的概念与作用

6.2 简述壳Shell-bash的作用与处理流程

6.3 Hello的fork进程创建过程

6.4 Hello的execve过程

6.5 Hello的进程执行

6.6 hello的异常与信号处理

6.7本章小结

第7章 hello的存储管理

7.1 hello的存储器地址空间

7.2 Intel逻辑地址到线性地址的变换-段式管理

7.3 Hello的线性地址到物理地址的变换-页式管理

7.4 TLB与四级页表支持下的VA到PA的变换

7.5 三级Cache支持下的物理内存访问

7.6 hello进程fork时的内存映射

7.7 hello进程execve时的内存映射

7.8 缺页故障与缺页中断处理

7.9动态存储分配管理

7.10本章小结

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

8.2 简述Unix IO接口及其函数

8.3 printf的实现分析

8.4 getchar的实现分析

8.5本章小结

结论

附件

参考文献


第1章 概述

1.1 Hello简介

1.1.1 P2P(From Program to Process)

在Linux系统中,hello的高级语言源程序hello.c首先经过预处理器的预处理,得到修改后的源程序hello.i。然后,hello.i经过编译器进行编译,得到汇编代码文件hello.s。接着,hello.s经过汇编器进行汇编,得到可重定位的目标文件hello.o,在经过链接器进行链接,得到可执行的目标文件hello。在shell的命令行中输入./hello来执行程序。shell会利用fork函数创建子进程,通过execve函数加载并运行hello程序。这样,hello就由源程序变成了正在被执行的进程。

1.1.2 O2O(From Zero-0 to Zero-0)

Shell通过execve函数加载并运行hello程序后,内核为hello进程映射虚拟内存。进入程序入口处开始载入物理内存,进入main函数执行代码。此时,CPU为hello进程分配时间片执行逻辑控制流。程序运行结束之后,shell的父进程回收hello进程,释放掉hello的相关数据结构。这样,hello就完成了O2O的过程。

1.2 环境与工具

硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk

软件环境:Windows11 64位;Vmware 11;Ubuntu 22.04.1 LTS64位

开发与调试工具:gcc,edb,readelf。

1.3 中间结果

hello.i 预处理后得到的修改后的源代码文件

hello.s 经过编译得到的汇编语言文件

hello.o 经过汇编得到的可重定位的目标文件

hello 经过链接得到的可执行的目标文件

hello.elf hello.o的elf格式文件

hello_out.elf hello的elf格式文件

1.4 本章小结


第2章 预处理

2.1预处理的概念与作用

2.1.1预处理的概念

预处理也称为预编译,它为编译做预备工作。预处理是在程序源代码被编译之前,由预处理器对程序源代码进行的处理。预处理器根据以字符#开头的命令,修改原来的C程序,进行代码文本的替换工作,得到另一个C程序,通常文件拓展名为.i。预处理阶段所进行的工作仅仅是文本的替换,并不进行计算功能。

2.1.2预处理的作用

预处理可以拓展源代码。合理地使用预处理功能,可以使得程序更加便于阅读、修改、移植和调试,同时,也有利于模块化程序设计。预处理主要包括文件包含、宏定义以及条件编译。

宏定义是指用宏名表示一个字符串,在宏展开时又以该字符串取代宏,以简化编程,提高程序的可读性。宏展开只是简单的字符串替换,不做正确性检测。

文件包含就是在预处理阶段,将#include包含的头文件插入程序文本中,便于进一步的编译。

条件编译可以根据#ifdef、#ifndef以及#endif等语句的不同条件决定需要进行编译的代码部分。[1]

2.2在Ubuntu下预处理的命令

预处理命令为:gcc -E hello.c -o hello.i

图 2-1Ubuntu下预处理结果

2.3 Hello的预处理结果解析

图 2-2预处理结果文件内容

经过预处理,得到了一个以.i为后缀名的一个文本文件。打开文件,发现一共有3000多行。这是因为预处理对源代码进行了拓展,把头文件stdio.h、unist.h以及stdlib.h中的内容插入到源代码中。与此同时,对宏定义#define也进行宏替换处理,并删除源文件中的注释内容。

2.4 本章小结

本章主要介绍了预处理的概念及作用,列出预处理的命令并对结果进行解析。

第3章 编译

3.1 编译的概念与作用

3.1.1编译的概念

编译指编译器(cc1)将预处理后的文本文件翻译为文件拓展名为.s的文本文件的过程,它包含一个汇编语言程序。[2]编译会对预处理后的文件进行词法分析、语法分析以及优化等操作,最终生成汇编代码文件。

3.1.2编译的作用

对源代码进行词法分析、语法分析、语义分析以及目标代码生成与优化。

词法分析这一阶段实现从左到右每个字符依次读入源程序,即对构成源程序的字符流进行扫描然后根据构词规则识别单词。

语法分析是编译过程的一个逻辑阶段。语法分析的任务是在词法分析的基础上将单词序列组合成各类语法短语。

语义分析的任务是对结构上正确的源程序进行上下文有关性质的审查。

目标代码生成与优化这一过程,是将程序进行多种等价变换,并把中间代码变换成目标代码。[3]

3.2 在Ubuntu下编译的命令

编译的命令为:gcc -S hello.i -o hello.s

图 3-1Ubuntu下编译结果

3.3 Hello的编译结果解析

3.3.1 数据

(1)字符串

程序中有两个字符串常量保存在.rodata段中,是只读的,不能够被修改。具体内容如下图所示。

图 3-2hello.s中字符串内容

  1. 函数参数

main函数有两个参数,分别为整型变量argc与char型指针数组argv,分别由寄存器%rdi和%rsi传递。

图 3-3main函数参数分析

由上图可知,这里把函数的两个参数分配到栈中,作为局部变量。其中argc被分配到%rbp-20的位置,argv被分配到%rbp-32的位置。

  1. 局部变量

局部变量i存储在栈上,通过%rbp相对寻址方式赋初值。汇编代码指令如下图所示。

图 3-4局部变量赋值方式

3.3.2 数组

图 3-5数组操作

数组操作部分的汇编代码如上图所示。这里%rax记录偏移量,%rbp-32是指针数组argv的首地址。通过M[%rbp-32+%rax]的寻址方式来找到对应的字符串的首地址。

3.3.3 关系操作

在C源代码中,对应着条件判断的部分有if条件语句以及循环条件的判断。

在if条件语句中,要进行判断argc!=4。对应的汇编代码如下图所示。

图 3-6if条件分支cmp指令

这里通过cmpl指令实现。同时设置条件码,便于下一步跳转指令的实现。

在循环体中,需要判断i是否小于9。对应汇编代码如下图所示。

图 3-7循环判断cmp指令

同样通过cmpl指令实现,设置条件码,便于下一步jle条件跳转指令判断是否跳转。

3.3.4 控制转移

C源代码中的if结构与循环体结构,在汇编代码中通过条件跳转来实现。汇编代码如下图所示。

图 3-8条件跳转

上图对应着C源代码中的if结构,这里表明如果M4[%rbp-20]的数值,也就是参数argc的值等于4,则跳转到.L2处。否则输出相应的字符串提示信息,程序终止。

图 3-9条件跳转

上图对应着C源代码中循环体的循环次数控制部分。如果满足M4[%rbp-4]≤8,即局部变量i≤8,则跳转到.L4处继续循环,否则跳出循环。

3.3.5 赋值

关于赋值操作,此程序中包括对局部变量i赋值为1的操作。在汇编代码的体现如下图所示。

图 3-10赋值操作

这里使用了movl指令,对局部变量i进行赋值。

3.3.6算术操作

由C源代码可知,程序中的算术仅有i++。对比汇编代码,这里通过addl指令实现循环体变量i的增加,如下图所示。

图 3-11算术操作

3.3.7 函数操作

使用call指令进行函数调用,通过寄存器%rdi,%rsi,%rdx,%rcx,%r8以及%r9传递参数,若参数超过6个,则其余参数被分配到栈中。汇编代码如下图所示。

图 3-12函数调用

call指令将首地址压入栈中,为局部变量与函数参数建立栈帧,转移到对应函数的地址。最后使用ret指令返回。

3.4 本章小结

本章介绍了编译的概念与作用,给出编译的命令,基于hello.s文件详细分析了编译的结果。

第4章 汇编

4.1 汇编的概念与作用

4.1.1汇编的概念

汇编指汇编器将.s文件翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在以.o为后缀的目标文件的过程。

4.1.2汇编的作用

汇编器将汇编代码翻译成机器语言,便于机器直接读取分析。

4.2 在Ubuntu下汇编的命令

汇编的命令:gcc hello.s -c -o hello.o

图4-1汇编结果展示

4.3 可重定位目标elf格式

首先用指令readelf -a hello.o > hello.elf生成elf格式的文件。

图 4-2ELF头的信息

ELF头的信息如上图所示,其中的16字节的序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。其中包括ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是由节头部表描述的,其中目标文件中每个节都有一个固定大小的条目。

图 4-3节头表的内容

节头表的内容如上图所示。节头表描述了每一个节的名称、在文件中的偏移量以及对齐方式等信息。

图 4-4重定位节的信息

重定位节的信息如上图所示。重定位节中保存了.text节中需要被重定位的信息,在链接过程中,链接器把这个可重定位的目标文件与其他目标文件结合时,就需要修改这些信息。这里展示的重定位信息有.rodata节中的模式串,puts函数,exit函数,printf函数,sleep函数,getchar函数以及atoi函数。

图 4-5符号表的内容

符号表的内容如上图所示。符号表存放着定义和引用的函数和全局变量的信息。其中,name是字符串表中的字节偏移,value是符号的地址,size是目标的大小,type是数据或者函数,bind表示符号是本地的还是全局的。

4.4 Hello.o的结果解析

采用objdump -d -r hello.o指令进行反汇编,得到结果如下图所示。

图 4-6hello.o反汇编代码

由上图可知,对hello.o反汇编处理不仅得到了汇编代码,还有机器语言。机器语言是用二进制代码表示的计算机能直接识别和执行的一种机器指令的集合,它由操作代码与操作数构成。机器语言与汇编语言是一一对应关系,两者建立起一个双射。[4]通过对比反汇编的代码内容和hello.s中的汇编语言,不同之处主要为以下几点:

  1. 操作数表示不同。在反汇编代码中,操作数用十六进制表示;而hello.s中的汇编代码操作数是用十进制表示的。
  2. 分支跳转。在反汇编代码中,跳转目标地址是通过main函数的起始地址加上偏移量计算得到;而在hello.s中的汇编代码中,分支跳转的目标地址是使用段名称进行跳转的,如.L3。
  3. 函数调用。在反汇编代码中,call指令中地址是使用main函数起始地址加上偏移量计算而来,并且指令下面还有重定位信息,在链接之后通过重定位计算出函数的地址信息。在hello.s的汇编代码中,call指令是直接使用函数名称。

4.5 本章小结

本章从汇编的概念与作用出发,给出Ubuntu系统下对于hello.s汇编的指令,利用readelf命令分析介绍了elf文件的格式。此外,对于hello.o文件利用objdump指令进行反汇编,分析了机器语言与汇编语言的关系,并对反汇编代码与hello.s进行比较,分析两者的不同之处。

5链接

5.1 链接的概念与作用

5.1.1链接的概念

链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可以复制到内存中执行。

5.1.2链接的作用

链接使得分离编译成为可能。对于大型应用程序而言,不必将它组织成一个大型的源文件,而是可以把它分解为更小的、更好管理的模块,可以独立地修改和编译这些模块。若要改变其中的一个模块,只需要编译它并重新链接应用,不必重新编译其他文件。[2]

5.2 在Ubuntu下链接的命令

链接的命令:ld -o hello -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o hello.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o

图 5-1链接指令与结果

5.3 可执行目标文件hello的格式

使用终端命令:readelf -a hello > hello_out.elf

分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

ELF头的信息如下图所示。其中,类型是EXEC(可执行文件),入口点地址也已经确定为0x4010f0,重定位已经完成,有27个节。

图 5-2ELF头的信息

节头部表的信息如下图所示。这里列出了各节的名称、类型、地址以及偏移量等信息。可见,这些节的地址都已经确定,而不是0,可通过偏移量计算得到最终的地址。

图 5-3节头部表的信息

重定位节的内容如下图所示。

图 5-4重定位节的内容

符号表的内容如下图所示。

图 5-5符号表的内容

5.4 hello的虚拟地址空间

使用edb加载hello,在data dump窗口查看本进程的虚拟地址空间。虚拟地址部分信息如下图所示。

图 5-6虚拟地址空间信息

由5.3中所分析的ELF节的内容可知,.text节地址为0x4010f0,在edb中查看该处信息,如下图所示。

图 5-7.text节内容

利用同样的方法,.rodata节的地址为0x402000,在edb中找到信息如下图所示。

图 5-8.rodata节内容

可知,.rodata中包含了printf的格式串内容。

.init节的地址为0x401000,通过edb查看虚拟地址空间的信息。

图 5-9.init节内容

.interp节的地址是0x4002e0,它的虚拟地址空间的信息如下图所示。

图 5-10.interp节内容

可知,这里保存了共享库的信息。

5.5 链接的重定位过程分析

使用指令objdump -d -r hello进行反汇编。下面分析hello与hello.o反汇编结果的不同之处。

(1)hello的反汇编结果出现了程序中所用到的库函数,例如getchar以及sleep等函数,在链接时进行了符号解析与重定位。而hello.o中的反汇编结果中没有。

图 5-11调用的库函数

  1. 在hello中,jmp以及call等指令使用确定地址。而在hello.o中,地址是不确定的。这是因为在链接时完成了重定位,使得运行时的地址被确定。

图 5-12寻址方式分析

  1. 在hello中,新增了.init与.plt节的内容。

图 5-13.init与.plt节的部分内容

综上所述,链接的过程就是将编译好的把编译好的目标文件和其他的一些目标文件和库链接在一起,形成最终的可执行文件的过程。此过程包含符号解析和重定位。

链接器在完成符号解析以后,就把代码中的每个符号引用和正好一个符号定义关联起来。在重定位的过程中,链接器将所有链接文件中相同的节合并,链接器将运行时地址赋给新的聚合节,依次进行赋予定义的每个节、每个符号。然后修改hello.o中的代码节和数据节中符号的引用,使得它们指向正确的运行地址。

5.6 hello的执行流程

表 1子程序名与地址

子程序名

地址

hello!_start

0x4010f0

hello!_init

0x401000

hello!main

0x401125

hello!puts@plt

0x401030

hello!printf@plt

0x401040

hello!getchar@plt

0x401050

hello!atoi@plt

0x401060

hello!exit@plt

0x401070

hello!sleep@plt

0x401080

hello! fini

0x4011c0

libc.so.6!nanosleep

0x7fa0290fb619

libc.so.6!printf

0x7f4bc4244770

libc.so.6!exit

0x7fa02903ad92

5.7 Hello的动态链接分析

通过查看elf文件,得到.got.plt节的地址是0x404000。在edb中找到对应的地址部分,查看调用dl_init前后的内容变化如下图所示。

图 5-14调用dl_init前.got.plt节内容

图 5-15调用dl_init后.got.plt节内容

通过对比可知,调用dl_init函数前后,内容发生了明显变化。这是因为动态链接采用了延时绑定的策略,将过程地址的绑定推迟到第一次调用该过程时。延迟绑定通过全局偏移量表(GOT)和过程链接表(PLT)的协同工作实现函数的动态链接,其中GOT中存放函数目标地址,PLT使用GOT中地址跳转到目标函数。

过程链接表PLT是一个数组,每个条目是16字节代码。PLT[0]是一个特殊条目,它会跳转到动态链接器中。每个被可执行程序调用的库函数都有它自己的PLT条目。

全局偏移量表GOT是一个数组,每个条目为8字节地址。和PLT联合使用时,GOT[0]和GOT[1]包含动态链接器在解析函数地址时会使用的信息,GOT[2]是动态链接器在1d-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数,其地址需要在运行时被解析。每个条目都有一个相匹配的PLT条目。在函数第一次被调用时,动态链接器会修改相应的GOT条目。之后对函数的调用就可以直接根据GOT中的值进行跳转。[2]

5.8 本章小结

本章首先介绍了链接的概念和作用,给出了Ubuntu下链接的命令。分析可执行目标文件hello的ELF格式,比较hello的反汇编代码与hello.o的反汇编代码的不同之处。此外,本章还分析了重定位过程、程序执行流程以及动态链接过程。

第6章 hello进程管理

6.1 进程的概念与作用

6.1.1进程的概念

进程的经典定义就是一个执行中程序的实例。进程的广义定义是指一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。每一个进程都有它自己的地址空间,一般情况下,包括文本区域、数据区域和堆栈。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。[5]

6.1.2进程的作用

进程提供了一种假象,好像我们的程序是系统中当前运行的唯一的程序一样。我们的程序好像是独占地使用处理器和内存,处理器就好像是无间断地一条接一条地执行我们程序中的指令。我们程序中的代码和数据好像是系统内存中唯一的对象。[2]

6.2 简述壳Shell-bash的作用与处理流程

Shell是一个交互型的应用级程序,它执行一系列的读、求值步骤,解析命令行,并代表用户运行其他程序。

Shell的处理流程如下:

(1)终端进程读取用户由键盘输入的命令行。

(2)分析命令行字符串,获取命令行参数,并构造传递给execve的argv向量。

(3)检查第一个命令行参数是否是一个内置的shell命令。

(4)如果不是内置命令,调用fork函数创建子进程。

(5)在子进程中,用步骤2获取的参数,调用execve函数执行指定程序。

(6)如果用户没要求后台运行,即命令末尾没有&号,否则shell使用waitpid函数等待作业终止后返回。

(7)如果用户要求后台运行,即命令末尾有&号,则shell返回。

6.3 Hello的fork进程创建过程

当在命令行中输入./hello时,命令行判断该命令不是内置命令,于是shell将hello视为可执行目标文件。使用fork函数创建子进程。新创建的子进程得到与父进程虚拟地址空间相同但独立的一份副本,包括代码和数据段、堆、共享库以及用户栈。fork函数调用一次,返回两次,一次返回到父进程,一次返回到新创建的子进程。父进程与子进程能够并发运行。

6.4 Hello的execve过程

execve函数的作用是在当前进程的上下文中加载并运行程序。execve函数加载并运行可执行目标文件hello,且带参数列表argv和环境变量envp,只用当出现错误时,execve函数才会返回,调用成功则不会返回。execve调用启动加载器来执行hello程序。加载器首先删除子进程现有的虚拟内存段,并创建新的代码段、数据段、堆和栈段。代码和数据段被映射为hello文件的.txt和.data区,堆和栈被置空。最后加载器将PC指向hello程序的起始位置。

6.5 Hello的进程执行

上下文信息:上下文就是内核重新启动一个被抢占的进程所需的状态。它由一些对象的值组成,包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构。

进程时间片:一个进程执行它的控制流的一部分的每一时间段叫做时间片。

内核态:处理器通过设置某个控制寄存器的模式位,限制应用可以执行的指令以及它可以访问的地址空间范围。当设置了模式位时,进程就运行在内核模式中。一个运行在内核模式的进程可以执行指令集中的任何指令,并且可以访问系统中的任何内存位置。

用户态:没有设置模式位时,进程运行在用户态中。用户模式的进程不允许执行特权指令,也不允许直接引用地址空间中内核区的代码和数据。

进程调度:当内核选择一个新的进程运行时,内核调度了这个进程。在内核调度了一个新的进程运行时,它就抢占当前的进程,并使用上下文切换的机制将控制转移到新的进程。上下文切换的实现包括以下三个步骤:

  1. 保存当前进程的上下文;
  2. 恢复某个先前被抢占的进程被保存的上下文;
  3. 将控制偿传递给这个新恢复的进程。[2]

当内核代表用户执行系统调用时,可能会发生上下文切换。当hello程序调用sleep函数时,内核可以选择进行上下文切换,进程由用户态转变为内核态,运行另外一个进程并切换为用户态。此外,当分配给进程的时间片用尽,内核也会执行上下文切换。

6.6 hello的异常与信号处理

6.6.1异常的种类及处理方式

下表给出了异常的四种类别。Hello在执行过程中以下几种异常都可能会发生。

表 2异常的类别

类别

原因

异步/同步

返回行为

中断

来自I/O设备的信号

异步

总是返回到下一条指令

陷阱

有意的异常

同步

总是返回到下一条指令

故障

潜在的可恢复的错误

同步

可能返回到当前指令

终止

不可恢复的错误

同步

不会返回

出现异常时,首先将控制传递给异常处理程序,运行异常处理程序。当异常处理程序执行完毕后,类型为中断或陷阱的异常会返回到下一条指令;类型为故障的异常,如果故障被成功解决,那么返回到当前指令,如果故障未能解决,则程序终止;类型为终止的异常,不会返回。

6.6.2正常运行的程序

在命令行中输入./hello 2021111902 滕浩 2。其中“2”表示每间隔两秒输出一次“Hello 2021111902 滕浩”信息。运行时状况如下图所示。

图 6-1正常运行时程序输出

6.6.3键入Ctrl-C

键入Ctrl-C之后,内核向进程发送SIGINT信号,使得前台进程被终止。

图 6-2键入Ctrl-C之后程序输出

6.6.4键入Ctrl-Z

键入Ctrl-Z之后,内核向前台进程发送SIGTSTP信号,使得前台进程被暂时挂起。

图 6-3键入Ctrl-Z之后程序状态

由上图可知,使用ps命令,可以找到hello进程的pid,说明hello进程并没有终止。使用jobs命令,看到hello程序的状态是停止状态。

图 6-4使用fg命令继续运行进程

使用fg命令,使得被暂时挂起的hello程序再次在前台运行。

6.6.5乱按键盘

如果乱按键盘,如果不按回车,输入的字符串仅会被读入缓冲区;如果按回车,则会把之前读入的字符串作为shell命令执行。

图 6-5乱按键盘情形结果展示

6.7本章小结

本章介绍了进程的概念与作用,分析了shell的执行流程。着重论述了用fork函数创建新进程和execve函数加载hello程序的过程。此外,本章分析了hello的执行过程以及异常与信号处理。

7hello的存储管理

7.1 hello的存储器地址空间

7.2 Intel逻辑地址到线性地址的变换-段式管理

7.3 Hello的线性地址到物理地址的变换-页式管理

7.4 TLB与四级页表支持下的VA到PA的变换

7.5 三级Cache支持下的物理内存访问

7.6 hello进程fork时的内存映射

7.7 hello进程execve时的内存映射

7.8 缺页故障与缺页中断处理

7.9动态存储分配管理

7.10本章小结

8hello的IO管理

8.1 Linux的IO设备管理方法

8.2 简述Unix IO接口及其函数

8.3 printf的实现分析

8.4 getchar的实现分析

8.5本章小结

结论

hello程序的一生包括以下阶段。

  1. 预处理阶段,预处理器将hello.c转化为hello.i文件。
  2. 编译阶段,编译器将hello.i转化为hello.s汇编语言文件。
  3. 汇编阶段,汇编器将hello.s文件转化为hello.o可重定位的目标文件。
  4. 链接阶段,链接器将hello.o与其他目标文件进行链接,得到可执行文件hello。
  5. 程序加载阶段,在shell中输入./hello 2021111902 滕浩 3,shell就会通过fork函数创建子进程,调用execve函数加载并执行hello程序。
  6. 程序执行阶段,调用execve函数时,execve 调用启动加载器,内核为hello进程映射虚拟内存。进入程序入口处开始载入物理内存,进入main函数执行相应代码。
  7. 上下文切换:当执行到调用sleep函数的时候,会触发陷阱异常进行系统调用。这时进程进入内核态,发生上下文切换,将控制转移给其他进程。直到收到某种信号或者sleep的休眠时间结束,内核再将控制转移给hello进程。
  8. 信号处理:用户通过键盘键入Ctrl-Z,将会导致SIGTSTP信号发送给当前进程,使得进程停止;用户通过键盘键入Ctrl-C,将会导致SIGINT信号发送给当前进程,使得进程终止。
  9. 终止:子进程执行完成后,发送信号SIGCHLD给shell,shell回收当前进程,并且删除hello的所有数据结构。

通过计算机系统课程的学习,我认识到计算机系统的设计十分的伟大精妙,是计算机科学家的杰作,智慧的结晶。从最底层的设计出发,我理解了计算机中数据的表示,流水线的原理,存储器层次结构等等知识,明白如何编写编译器友好,局部性优良的程序。学习这门课程也让我有很深的感触,一名优秀的程序员不仅要关注顶层的实现,同时还要理解底层的设计,做到软件与硬件的融会贯通,这样才能编写性能更加优秀的程序。


附件

hello.i 预处理后得到的修改后的源代码文件

hello.s 经过编译得到的汇编语言文件

hello.o 经过汇编得到的可重定位的目标文件

hello 经过链接得到的可执行的目标文件

hello.elf hello.o的elf格式文件

hello_out.elf hello的elf格式文件

参考文献

[1]laolitou_ping. C语言——预处理[DB/OL]. CSDN,2021.05.21. http://t.csdn.cn/dja6H.

[2]兰德尔 E. 布莱恩特,大卫 R. 奥哈拉伦. 深入理解计算机系统(第3版)[M]. 北京:机械工业出版社. 2016.7.

[3] JackYang. 词法分析、语法分析、语义分析[DB/OL].博客园,2016-01-07.https://www.cnblogs.com/BlogNetSpace/p/5108845.html.

[4] anton_99.机器语言概念[DB/OL]. CSDN,2021-08-13.http://t.csdn.cn/SQKU9

[5] 叫我小者呀. 进程的概念[DB/OL].CSDN,2021-05-06.http://t.csdn.cn/ficbV.