计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 英才学院计算机专业
学 号 7203610822
班 级 2036013
学 生 奚大又
指 导 教 师 刘宏伟
计算机科学与技术学院
2021年5月
本报告围绕基本程序到进程、进程的开始到结束讨论hello.c程序的一生(P2P、020)。
从刚敲入电脑的简单的C程序、经过预处理、编译、汇编、链接,被创建进程、最终被回收,本报告以简单的代码hello.c文件为例,展示这看似轻松简单的过程中一步步复杂的过程,对于每一章节,都会介绍hello.c程序人生一个加工环节的更多细节。
关键词:程序 进程 预处理 编译 汇编 链接 程序人生
目 录
第1章 概述
1.1 Hello简介
(1)P2P过程
P2P过程,是指hello.c从程序(program)到进程(project)的全过程。期间,GCC编译器驱动程序读取hello.c并把它翻译成可执行目标程序hello(在Linux系统下即为hello.out,在windows系统下即为PE格式)这一过程主要分为四大阶段:
第一阶段,hello.c是我们使用编程工具写好并存储在磁盘上的程序文本。那么首先C预处理器(cpp)处理hello.c,一方面插入所有用#include命令指定的头文件,另一方面扩展所有用#define声明指定的宏。也就是说,在这一阶段所做的事情有四:头文件的包含、注释的删除、宏定义的替换、条件编译的选择。预处理之后,得到的就是hello.i(修改、拓展之后的原文件,仍为文本格式)。
第二阶段,由编译器(ccl)处理hello.i,产生汇编文件hello.s,编译器所做的主要是词法分析、语法分析。Hello.s实际上也是文本形式的文件,还不是二进制的机器码格式。
第三阶段,由汇编器(as)处理hello.s,将汇编代码hello.s转化为二进制可重定位目标程序hello.o(二进制),目标代码是一种机器代码,它包含所有指令的二进制表示,但是还没有填入全局值的地址。
第四阶段,最后由链接器(ld)将若干可重定位目标文件链接起来,得到hello可执行文件,hello可以直接被加载到内存里,由系统执行。这一过程主要解决的是“某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数等等【1】”的问题,hello程序调用了库函数printf,而printf函数存在于一个单独预编译好的目标文件printf.o中,那么链接器就必须将它合并到我们的hello.o中。链接器的相关工作完成后,hello可执行文件就成功生成了,在shell中用命令打开它,即可为它创建进程( process ) 。
下图是P2P过程的一个图解。图片来源:参考文献[2]
(2)020过程
Zero to zero,表示的是从程序开始被运行到其最终运行结束被回收的过程。shell中使用命令以运行程序后,shell调用fork函数为hello创建进程,再调用execve 加载运行hello,建立虚拟内存空间映射,CPU为运行的hello分配时间片,执行逻辑控制流。当执行结束,此进程被父进程回收,内核擦除进程有关痕迹,hello的进程被完美“收尸”。
1.2 环境与工具
电脑硬件环境:
软件环境:
Win10 (64位企业版); VMware Workshop Pro16 (64位);
Ubuntu 20.04.4 LTS(64 位);
开发与调试工具:
Visual Studio 2019(64位); Codeblocks 64位;
vi/vim/gedit+gcc
cpu-z
1.3 中间结果
为编写本论文,生成的中间结果文件的名字及文件的作用:
hello.c
源程序
hello.i
作用:是hello.c预处理得到的文本文件,是修改了的源程序(上文已提到,不再赘述)
hello.s
作用:是hello.i经过编译器编译得到的文本文件,是汇编程序文本。
hello.o
作用:是hello.s经过汇编器汇编而成的可重定位目标文件,是二进制格式的文件。
hello
作用:是hello.o经过链接器处理得到的可执行目标程序,为二进制格式,可直接加载到内存里并创建进程执行它。
1.4 本章小结
本章首先简要介绍了hello.c由程序(program)一步步经过cpp、ccl、as、ld转换为可执行目标文件,并创建进程执行(成为process)的过程,亦即P2P过程;然后本章又给出了进程从创建到最终回收的020过程,展现了hello的生命周期。
另外,本章给出了为了编写本文而用到的软硬件环境,以及产生的中间结果。
在后续的章节里,我们将针对本章的每一个涉及的环节进行更详细的介绍。
第2章 预处理
2.1 预处理的概念与作用
什么是预处理?
答:“预”处理,顾名思义,是在代码编译之前对.c代码文件作相应转换,使之能被编译器直接地准确高效处理的过程。该过程由预处理器cpp完成。
预处理的作用
预处理中会展开以#起始的行,试图解释为预处理指令。如果说这样表述有些抽象和不够准确,那么我们可以把预处理的工作内容概括为以下四部分:
- 插入头文件:例如#include<stdio.h>这句预处理指令使得预处理器插入stdio.h头文件的内容到程序文本中。
- 宏的替换:对#define这一宏定义预处理指令,预处理将所有涉及宏定义的地方用其真正的内容或数值来替换掉原有符号。
- 注释及无关空白的删除:删除注释等对程序文本本身不必要的部分。
- 条件编译的处理:例如#ifdef这一条件编译预处理指令,预处理器可以根据这一语句确定程序需要执行的代码段,而滤去不必要的代码段。
常见的预处理命令【4】
2.2在Ubuntu下预处理的命令
预处理的ubuntu命令:gcc -E hello.c -o hello.i
可以看到在同一文件夹下多了一个预处理好的hello.i文件(图在下小节中)。
2.3 Hello的预处理结果解析
一方面,hello.i的代码行数由原来.c文件的23行变为了3060行。另一方面,hello.i的文件大小由原来的535字节增加到64.7KB。也可以看出注释信息在hello.i中也已经不存在了。
解析:hello.c的代码不涉及宏替换以及条件编译的处理,所以预处理hello.c的工作之一是:剔除了注释部分和无关空白,所以hello.i中不再含有注释部分。预处理的另外一个也是更重要的工作是根据以下三行语句,插入了这三个头文件的全部信息,由于三个头文件都相对较大,行数较多,所以hello.i展现出的代码行数和文件大小都远大于hello.c。
2.4 本章小结
本章节较第一章更为详细地介绍了预处理的概念、作用,以hello.c预处理生成hello.i的过程为例,讲解了预处理过程中发生的直观变化,简要分析了这些直观变化的内在原因,便于更深入地理解预处理的工作内容和主要过程。
第3章 编译
3.1 编译的概念与作用
编译的概念
编译即利用编译程序从预处理后的文件(.i)产生汇编语言程序(.s)的过程。
编译的作用
其工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。主要是进行词法分析和语法分析,又称为源程序分析,分析过程中发现有语法错误,给出提示信息。因此,编译是将高级语言程序转化为机器可直接识别处理执行的的机器码的至关重要的中间步骤。
3.2 在Ubuntu下编译的命令
ubuntu编译命令:gcc -S hello.i -o hello.s
可以看到文件夹下产生了hello.s的文件。
3.3 Hello的编译结果解析
此部分是重点,应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。
3.3.1 hello.s的最基本伪指令代码解析
(1).file
第一行,.file "hello.c",用于声明源文件
- .text
位于第二行,含义是“代码节”
- .section .rodata
只读代码段
- .align
第四行,.align 8,指令或者数据的存放地址进行对齐的方式。
表示调整位置计数器,让它在 8 的倍数边界上。如果已经在 8 的倍数边界上,那么编译器就不用改变了。
- .type
用于声明符号是数据类型或函数类型,例如第11行“.type main, @function”
3.3.2 数据——字符串
两个.string语句对应hello.c中的两次printf内容,可以看出,字符串常量以uft-8格式编码并存储在.rodata段,用符号.LC0和.LC1表示两个字符串的首地址。由于.section .rodata代表只读代码段,所以也就是说对两个字符串是只读的。
3.3.3 数据——局部变量,常量
常量以立即数形式表示,即$常数,如涉及的0,4,8等,均以立即数形式表示。
本hello程序中涉及的局部变量是int i,作为循环计数变量使用,局部变量i存储在栈或寄存器里。在本实验中,可以看出编译器将i存储在栈空间-4(%rbp)中。第31、53行就是这一判断的证据。
3.3.4 数据——函数参数
涉及的函数参数:int argc,char *argv[]
argv数组的首地址被存放在栈中,实际上两个参数都存在栈上,根据下图可知argc存放在%rbp-20的位置。argv数组存放在%rbp-32的位置。(见22、23两行)。这两行也是main函数传参操作的含义。
3.3.5 操作——赋值操作
涉及的赋值操作:循环时赋值i=0。
利用第31行语句 “movl $0, -4(%rbp)”,将0数值赋给存储于栈空间-4(%rbp)的局部变量i,因为i是int整型,占4字节,所以使用的是MOV系列操作中的movl。
3.3.6 操作——算术操作
涉及的算术运算:循环过程中i自加1的运算。在编译过程中被编译器翻译为ADD类指令。
在编译的结果中,第51行,对i被存储的栈空间进行addl 加上立即数1,表示循环中i自加1的过程。
3.3.7 操作——关系操作与控制转移
hello程序涉及的关系操作与算术转移:共有两处,一处是if(argc!=4)条件判断,一处是for(i=0;i<8;i++)循环条件判断。
前者的汇编代码是:
该片段的设计本意是增加程序鲁棒性,检查输入的参数个数是否符合要求。
cmpl $4, -20(%rbp) 代表argc!=3 条件判断语句,后面的je跳转指令表示分支跳转,cmpl具有设定条件码的功能,而条件码的数值将决定是否要跳转入分支,或者跳转到哪个分支。
后者的设计本意是使得本实验中的for循环用于循环输出8次同样的内容,其汇编代码为:
核心在于cmpl $7,-4(%rbp) ,该语句与前面那个分支判断其实是同理的,只不过这次是计算i-7以设置条件码,下一步的je同样地是根据条件码进行跳转的操作。
3.3.8 操作——其他函数操作(函数调用、返回)
任何函数必须被call才能执行,就以主函数为例,call指令将下一条指令的地址压栈,然后跳转到main 函数,完成对main函数的调用。
程序结束时,使用leave指令清空栈空间,然后 ret 表示返回,结束程序。
3.4 本章小结
本章节介绍了编译(即从.i到.s)的过程,给出了编译的基本概念及其作用,以hello编译过程为例,分析了汇编语言程序文本中各条关键语句所代表的含义,说明了编译器是怎么处理C语言的各个数据类型以及各类操作的,给出了C中各种数据类型和操作对应着怎样的汇编代码和逻辑。
第4章 汇编
4.1 汇编的概念与作用
汇编的概念与作用
此处的汇编,是指as将hello文件从 .s 到 .o(即编译后的文件到生成机器语言二进制程序的过程)。汇编器将.s 汇编程序翻译成机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存在.o 目标文件中,.o文件是一个二进制文件,它包含程序的指令编码。
4.2 在Ubuntu下汇编的命令
4.3 可重定位目标elf格式
4.3.1引言
ELF,是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件的文件格式,是Linux的主要可执行文件格式。由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。
根据CSAPP的指导,我们有如下图解:
.text:已编译程序的机器代码
.rodata:只读数据
.data:已初始化的全局和静态C变量
.bss:未初始化的全局和静态C变量
.symtab:符号表
.rel.text:代码段重定位信息表
.rel.data:数据段重定位信息表
.debug:调试符号表
.line:C代码行号与机器码行号映射表
.strtab:字符串表
4.3.2终端命令行
readelf -a hello.o > ./elf.txt
4.3.3第一部分:ELF头
ELF头以一个16字节的序列开始,这个序列描述了生成该文件系统下的字的大小以及一些其他信息。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息:包括ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,以及节头部表中条目的大小和数量。
4.3.4程序头表
没有程序头表。
4.3.5节(重定位节)、符号表
重定位节的内容是在代码中使用的一些外部变量及其偏移量等。链接时,根据重定位节的信息修稿这些变量符号。在重定位节表的每行最后,列出了所有需要重定位的符号名称。在链接过程,我们将利用重定位节表中信息来算出正确的地址。
本程序需要重定位的信息有:.rodata中的模式串,puts,exit,printf,atoi,sleep,getchar。
4.3.6节头表
节头表可以描述每个节的大小和位置。
4.4 Hello.o的结果解析
【命令行】objdump -d -r hello.o
【反汇编结果】
【与第3章的 hello.s进行对照分析】
总体而言,二者大同小异,然而,还是有以下若干条显著差异:
- 反汇编显示的结果不仅有汇编代码(显示于右侧),还有附加的机器码(显示在左侧)。这并不奇怪,因为反汇编的代码本来就源于.o的机器码。两者行与行的完美对应也恰恰证明了机器语言跟汇编语言之间的映射关系。
- 最显著的差别之一:分支转移时,hello.s(下面两张图中的浅色图片)借助了.L2这条伪指令来表示分支跳转;然而,hello.o反汇编的结果却是将原来的伪指令替代成了真正的地址(见紫色图片)。
这一点也很好理解,因为段名称只是在汇编语言中便于编写的助记符,在汇编成机器语言之后显然不再有用,取而代之的是确定的地址(相当于数学里的“代入”)。
- 立即数:在汇编文件中是表示为十进制(例如$7),但是在反汇编里表示为16进制(如$0x7)。
这是因为16进制更加接近2进制,.o本身是二进制机器码文件。所以在.o的反汇编文件中,立即数都用16制表示,方便格式的转化。
- 函数调用:在汇编文件.s里,函数调用时,call后面直接加的是函数名(下方浅色图)。但是在.o反汇编显示中,call后面是一条重定位条目指引的信息,是下一条指令,而不是原来的具体函数名(下方紫色图)。
这是因为这些函数都是共享库函数,地址是不确定的,因此call指令将相对地址设置为全0,然后在.rela.text节中为其添加重定位条目,等待链接的进一步确定。
4.5 本章小结
本章节介绍了汇编(.s经过as转化为.o的过程)的概念及其作用,先查看并分析了hello.o的ELF格式,用readelf等列出其各节的基本信息,简要解读了其含义。
此外,本章对比了hello.s与hello.o反汇编二者的异同点,分析了这些差异产生的内在原因,更详细地理解了机器语言跟汇编语言之间的映射关系,理解了汇编的过程和产生的变化。
第5章 链接
5.1 链接的概念与作用
此处的链接是指从 hello.o 到hello生成过程。
链接是通过链接器ld将若干可重定向目标代码文件(.o,即as汇编产生的文件)和实现库函数(例如printf)的代码合并,最终产生一个可执行文件的历程。在这个实验中,涉及的待链接.o文件主要是hello.o以及printf.o等。链接产生的可执行文件可以加载到内存来执行。链接使得分离编译成为可能,即可以仅编译修改的文件。链接还有利于构建共享库。
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.3 可执行目标文件hello的格式
命令行readelf -a hello > ./elf2.txt
打开elf2.txt,得到以下结果。
由于我们指定了动态链接,于是可以在节头表里出现了.plt,.got.plt 等节,这些节会用于延迟绑定的部分。
5.3.1 ELF头
5.3.2 节头表
各段的基本信息,包括各段的起始地址,大小等信息如下:
5.3.3程序头表
5.3.4 符号表(有两组)
5.3.5 节
5.4 hello的虚拟地址空间
让我们观察edb的Data Dump窗口,窗口显示虚拟地址由0x401000开始,到0x402000结束,这之间的每一个节对应5.3中的每一个节头表的声明。
以节头表中的.init节为例,它从401000开始,在data dump里也轻易地找到了它(第一行)。其他的节也同理。
5.5 链接的重定位过程分析
分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
【命令行】objdump -d -r hello
【运行效果】得到了hello的反汇编文件
【Hello与hello.o异同比较】
1.增加了节的数量,例如.init节、.plt节,就是新增的节。节中定义了一些新的函数。
2.链接加入了hello.c中用到了库函数,亦即增加了许多外部链接的共享函数库,例如printf@plt等。
3.Hello实现了调用函数时的重定位,因此在调用函数时调用的地址已经是函数确切的虚拟地址,取代了原来的0x0。(如,从0x40000开始)。在跳转时,调用的地址也是函数确切的虚拟地址。那些未被解析的地址都变成了确切的虚拟地址。(见图中call/je后面跟的内容)
4.增加了添加动态库的入口,因为我们可以看到在.text段,增设了_start入口和一些其他函数,其中一些函数就是由动态链接库提供的。
【链接的重定位过程分析】
- 早在汇编器的处理中,当汇编器遇到一个最终位置未知的引用时,它简单地将立即数0x0放入引用处,并为这个引用生成一个重定位条目放在 .rel .text 中(为链接做铺垫).而这个重定位条目拥有足够多的信息指导链接器,在链接时将0x0修改为正确的数值。.data和.text中文件的大小、偏移都可以查节头表、符号表计算得知。
例如,ELF里包含像printf、getchar这些被调用的函数的符号地址,它是相对寻址,偏移量也由ELF表已知。依据这些信息,链接器可以计算出这些printf、getchar真正的call 操作数,将函数调用地址改成确切的虚拟地址。
2.内存分配:为新的合并出来的数据和代码节分配内存,并映射虚拟内存地址。
3.修改符号的引用:例如,在链接时,链接器已经赋予了main等函数运行时地址,现在我们要利用它们结合重定位条目的信息计算并修改引用位置的0x0。
这样,重定位过程就完成了。
5.6 hello的执行流程
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
函数名 | 地址 |
_start | 0x4010f0 |
__libc_csu_init | 0x4011c0 |
_init | 0x401000 |
main | 0x401125 |
atoi@plt | 0x4010c0 |
puts@plt | 0x401090 |
printf@plt | 0x4010a0 |
sleep@plt | 0x4010e0 |
getchar@plt | 0x4010b0 |
exit@plt | 0x4010d0 |
_libc_csu_fini | 0x401230 |
_fini | 0x401238 |
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。
dl_init前(如下图)
dl_init后(如下图)
0404000-0404010的16字节发生了变化。这是由于GOT表中加载了共享库的内容的缘故。
5.8 本章小结
本章介绍了链接的概念和作用,观察了虚拟地址空间;通过对比观察hello的反汇编和ELF,理解了链接过程发生的变化,最后使用edb分析了hello的执行流程和动态链接分析。
第6章 hello进程管理
6.1 进程的概念与作用
进程的概念
进程是正在运行的程序的实例,进程是一个实体。首先,每一个进程都有它自己的地址空间,一般情况下,包括文本区域、数据区域和堆栈。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。
进程的作用
提供了两种抽象:独立逻辑控制流、私有地址空间。这两种抽象提供了“我们的程序好像是独占的使用处理器和内存,处理器好像是无间断的执行”这一假象,使得程序好像是在独自使用内存。
6.2 简述壳Shell-bash的作用与处理流程
作用
Linux系统中,Shell是一个交互型应用级程序,代表用户运行其他程序(是命令行解释器,以用户态方式运行的终端进程)。提供操作界面及内核服务,从用户的输入读取命令,将命令传给系统内核,从而调动内核执行各种操作。简言之,它是用户与系统交互的中介。
处理流程[8]
1.终端进程读取用户由键盘输入的命令行。
2.分析命令行字符串,获取命令行参数,并构造传递给execve的argv向量
3.检查第一个(首个、第0个)命令行参数是否是一个内置的shell命令
4.如果不是内部命令,调用fork( )创建新进程/子进程
5.在子进程中,用步骤2获取的参数,调用execve( )执行指定程序。
6.如果用户没要求后台运行(命令末尾没有&号)否则shell使用waitpid(或wait...)等待作业终止后返回。
7.如果用户要求后台运行(如果命令末尾有&号),则shell返回;
6.3 Hello的fork进程创建过程
fork 函数创建一个几乎但不完全与父进程相同的子进程,子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,但与父进程PID不同。在运行期间父、子进程可以任意交替,父进程会默认等待子进程结束并回收。Fork函数被调用一次却返回两次。
6.4 Hello的execve过程
调用fork函数之后,在子进程中加载execve函数,载入并运行hello函数,包括对应的参数(argv)。覆盖当前进程的代码,数据,栈。保留相同的PID,继承已打开的文件描述符和信号上下文。为hello的代码、栈区、数据等创建新的结构,最后execve设置程序计数器,程序计数器指向_start入口。该函数当且仅当读取出现错误时才会返回-1到调用程序。
6.5 Hello的进程执行
上下文信息
上下文就是内核重新启动一个被抢占的进程所需要的状态。本质上它是以下这些对象的值组成的,如通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构,甚至是描述地址空间页表等。
进程时间片
分时操作系统分配给每个正在运行的进程微观上的一段CPU时间。若以单CPU为例,这些进程“看起来像”同时运行的,实则是轮番穿插地运行,由于时间片通常很短,在人的角度上看无法察觉,就仿佛是多个进程在同时运行一样,实际上却是每个进程交替地使用时间间隔很短的时间片。
一个系统中所有的进程被分配到的时间片长短并不是相等的,根据睡眠和运行这两个状态时间的长短,决定每个进程的“交互性”,交互性和“静态优先级”共同决定着分配给每个进程时间片的长短。
进程调度的过程
进程调度的过程:无论是在批处理系统还是分时系统中,用户进程数一般都多于处理机数、这将导致它们互相争夺处理机。另外,系统进程也同样需要使用处理机。这就要求进程调度程序按一定的策略,动态地把处理机分配给处于就绪队列中的某一个进程,以使之执行。当内核选择一个新的进程运行时,我们就说内核调度了这个进程,内核使用上下文切换的机制来控制转移到新的进程:保存当前进程的上下文,恢复某个先前被强占的进程被保存的上下文,将控制传递给这个新恢复的进程。
轮转法分时调度:系统将所有就绪进程按FIFO规则排队,按一定的时间间隔把处理机分配给队列中的进程。这样,就绪队列中所有进程均可获得一个时间片的处理机而运行。
用户态与核心态的转换
当运行在用户模式的应用程序需要输⼊输出、申请内存等比较底层的操作时,就必须调用操作系统提供的 API 函数,从而进入内核模式;操作完成后,继续执行应用程序的代码,就又回到了用户模式。
6.6 hello的异常与信号处理
hello执行过程中会出现哪几类异常?
答:中断,系统调用,故障和终止。下面将通过一个小实验来说明hello的异常与处理。
6.6.1【正常执行】(下图)
6.6.2【程序执行过程中乱按、按回车、按CtrlC、CtrlZ】(下列各图)
- 不停地按回车(下图)
输出结果中间夹杂着回车产生的换行,进程输出结束之后还有回车的响应(体现在下方新增的命令行显示上)
- 按Ctrl-Z(下图)
只按下一次CtrlZ,进程就直接停止。因为该操作向进程发送了一个SIGSTP信号,将进程挂起。
- 按Ctrl-C(下图)
输出中断,产生^C符号后停止。因为向进程发送了一个SIGINT信号,进程处理后结束hello并将其回收。
- 乱按其他按键(下图)
可以看出对输出并没有什么实质性的影响,只是把乱按的字符显示在了屏幕上而已。
6.6.3【Ctrl-Z后运行ps jobs pstree fg kill 等命令】
- Ps(下图)
说明按下Ctrl-Z后,虽然进程被挂起(体现为输出的中断),但是进程并未被回收。
- Jobs(下图)
- Pstree(下图)
pstree可以展示hello的进程树,就是上图所示。
- Fg(下图)
fg把hello进程没有进行完的部分继续进行,直到程序结束、进程被回收。
- Kill(下图)
6.7本章小结
本章介绍了进程的概念与作用、壳的作用与处理流程,fork进程创建过程和execve过程。此外,介绍了进程执行时调度的概念、作用与详细过程,给出了相关概念和分时调度的一二简单描述。最后,本章通过在程序输出时按各种按键的小实验展示了信号的异常与处理的有关内容。
第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本章小结
结论
hello的一生是简单的,简单到一键enter,几秒之内它就已实现自己的价值。
hello的一生是复杂的,因为从预处理时扩展宏与头,到编译时生成目标代码,再到汇编时将汇编代码转化为二进制的可重定向目标文件,再到链接时转化为可执行文件,hello才算成为了一个可以加载到内存执行的文件。fork为它创建进程,系统为它费心思分时间片,这期间的每一步都微妙至极,犹如细胞和原子一样有条不紊地执行步骤。
本报告使我对整本CSAPP和整个ICS课程的梗概有了更深的认识,将ICS课程的整体性呈现在我眼前。程序的产生和执行,归根结底需要依靠底层的这些东西,一切的算法,逻辑,最终都要与hello走过同样的有条不紊的程序人生。
学ICS,学的是什么?是底层的运行机理,是关于程序最本源和本质的东西,正如hello踏踏实实的程序人生,我们所要做的也是做好我们自身人生的“预处理”和“编译”,积累更多底层相关知识,在理解每一个“what”的背后,都能理解“why”。
附件
hello.c
源程序
hello.i
作用:是hello.c预处理得到的文本文件,是修改了的源程序(上文已提到,不再赘述)
hello.s
作用:是hello.i经过编译器编译得到的文本文件,是汇编程序文本。
hello.o
作用:是hello.s经过汇编器汇编而成的可重定位目标文件,是二进制格式的文件。
hello
作用:是hello.o经过链接器处理得到的可执行目标程序,为二进制格式,可直接加载到内存里并创建进程执行它。
参考文献
[1] c语言编译器ccl全英,C语言的预处理、编译、汇编、链接
,https://blog.csdn.net/weixin_28836989/article/details/117058766
[2] C语言编译过程详解,https://www.cnblogs.com/CarpenterLee/p/5994681.html
[3] 深入理解计算机系统,(美)Randal E. Bryant,(美)David O'Hallaron著;龚奕利,雷迎春译
[4]C语言基础十-预处理命令大全
,https://www.bilibili.com/read/cv12559290
[6] https://www.cnblogs.com/pianist/p/3315801.html
[7]https://www.cnblogs.com/bobwuming/articles/14931042.html
[8]https://wenku.baidu.com/view/a2b0ec4abb4ae45c3b3567ec102de2bd9605de9d.html
[9]fork创建进程过程(底层实现)和写实拷贝
https://wenku.baidu.com/view/c07dc32293c69ec3d5bbfd0a79563c1ec5dad7b6.html