计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机科学与技术
学 号 2021112665
班 级 2103102
学 生 朱甜乐
指 导 教 师 刘宏伟
计算机科学与技术学院
2022年5月
一个程序的一生是迅速而转瞬即逝的,我们仅仅点击一个编译符号,在命令行中输入参数,它就运行,而后结束一生;但若是我们细看这一过程中的每一个里程点,这一生将是漫长而富有生命力的。本文通过一个hello.c源代码和它生成的hello程序,分析了hello程序从预处理、编译、汇编、链接而生,到创建进程、加载进程、虚拟内存映射而大放光彩,最后运行结束回收进程、释放内存而走尽一生的整个过程。在这个P2P、020的过程中,我们将感受到一个程序莫大的生命力。
关键词:hello.c;hello;进程;虚拟内存;P2P;生命
目 录
2.1 预处理的概念与作用........................................................................... - 5 -
2.2在Ubuntu下预处理的命令.................................................................. - 5 -
2.3 Hello的预处理结果解析...................................................................... - 5 -
3.1 编译的概念与作用............................................................................... - 6 -
3.2 在Ubuntu下编译的命令..................................................................... - 6 -
3.3 Hello的编译结果解析.......................................................................... - 6 -
4.1 汇编的概念与作用............................................................................... - 7 -
4.2 在Ubuntu下汇编的命令..................................................................... - 7 -
4.3 可重定位目标elf格式......................................................................... - 7 -
5.1 链接的概念与作用............................................................................... - 8 -
5.2 在Ubuntu下链接的命令..................................................................... - 8 -
5.3 可执行目标文件hello的格式............................................................. - 8 -
5.4 hello的虚拟地址空间........................................................................... - 8 -
5.5 链接的重定位过程分析....................................................................... - 8 -
5.7 Hello的动态链接分析.......................................................................... - 8 -
第6章 hello进程管理............................................................................. - 10 -
6.1 进程的概念与作用............................................................................. - 10 -
6.2 简述壳Shell-bash的作用与处理流程.............................................. - 10 -
6.3 Hello的fork进程创建过程................................................................ - 10 -
6.4 Hello的execve过程........................................................................... - 10 -
6.6 hello的异常与信号处理..................................................................... - 10 -
第7章 hello的存储管理......................................................................... - 11 -
7.1 hello的存储器地址空间..................................................................... - 11 -
7.2 Intel逻辑地址到线性地址的变换-段式管理.................................... - 11 -
7.3 Hello的线性地址到物理地址的变换-页式管理............................... - 11 -
7.4 TLB与四级页表支持下的VA到PA的变换.................................... - 11 -
7.5 三级Cache支持下的物理内存访问................................................. - 11 -
7.6 hello进程fork时的内存映射............................................................ - 11 -
7.7 hello进程execve时的内存映射........................................................ - 11 -
7.8 缺页故障与缺页中断处理................................................................. - 11 -
7.9动态存储分配管理.............................................................................. - 11 -
第8章 hello的IO管理........................................................................... - 13 -
8.1 Linux的IO设备管理方法................................................................. - 13 -
8.2 简述Unix IO接口及其函数.............................................................. - 13 -
8.4 getchar的实现分析............................................................................. - 13 -
第1章 概述
1.1 Hello简介
P2P:P2P是指From Program To Process,即从程序到进程。我们编写的程序最初是以hello.c的文本文件存储的,我们称之为源程序;当我们想运行一个程序时,需要先获得源程序的可执行文件hello,这个过程有以下四个步骤:首先通过预处理器(cpp)修改源程序,得到hello.i文本文件,称为修改了的源程序;接着通过编译器(ccl)将修改了的源程序翻译成文本文件hello.s,称为汇编程序;然后通过汇编器(as)讲汇编程序翻译成机器语言指令,得到可重定位目标程序格式的二进程文件hello.o;最后链接器(ld)讲hello.o文件与其用到的库函数链接,得到可执行文件hello。当我们想运行hello程序时,在命令行输入命令,shell会解析命令行参数,初始化环境变量,通过fork函数创建一个新的子进程,调用execve函数加载并运行hello程序。至此,hello.c实现了由Program到Progress的转变,即P2P。
020:020是指From Zero-0 to Zero-0,我们通过上述步骤使hello程序从无到有拥有了自己的进程,execve函数加载并运行可执行目标文件hello后,操作系统为其分配虚拟内存空间,在物理内存与虚拟内存之间建立映射,I/O与信号相配合共同实现了hello的输入输出;在hello程序结束后,shell进程对hello程序所在的进程回收,于是hello去后和来时一样没有占用任何内存空间和进程。我们称这整个过程为From Zero-0 to Zero-0,即020。
1.2 环境与工具
硬件环境:X86-64 CPU,2.2GHz,8G RAM,466G HD Disk
软件环境:Windows 10 64位,VMware 16,Ubuntu 18.04 64位
开发与调试工具:gcc,Visual Studio Code,Notepad++,objdump
1.3 中间结果
hello.i 预处理得到的修改了的源程序
hello.s 编译得到的汇编程序
hello.o 汇编得到的可重定位目标程序
hello 链接得到的可执行目标文件
asm.txt 对hello.o反汇编得到的反汇编代码
asm2.txt 对hello反汇编得到的反汇编代码
hello.elf hello.o的ELF格式文件
hello2.elf hello的ELF格式文件
1.4 本章小结
本章介绍了P2P和020两个概念,列举了大作业环境和工具,列举了中间结果,为接下来的内容做了概述。
第2章 预处理
2.1 预处理的概念与作用
2.1.1预处理的概念
预处理是编译的第一个阶段,编译器调用预处理器根据以字符#开头的命令,修改原始的C程序,将头文件的内容插入到程序文本中,同时删除程序的注释。
2.1.2预处理的作用
预处理为编译做准备工作,通过处理以字符#开头的指令完成文件包含、条件编译、布局控制和宏替换,同时删除程序的注释。
2.2在Ubuntu下预处理的命令
预处理的命令:cpp -o hello.i hello.c或gcc -E -o hello.i hello.c
如图所示:
2.3 Hello的预处理结果解析
源程序中以字符#开头的命令有:
于是预处理阶段将这三个头文件里的内容插入到源文件中。
打开hello.i可以看到多了很多行文本,这是对源程序里以字符#开头的命令修改程序的结果:
main函数的位置在最结尾:
同时我们可以发现源程序里的注释都被删除了。
2.4 本章小结
本章详细介绍了编译的第一个阶段——预处理阶段,内容包含预处理的概念,预处理的作用,以及在Ubuntu下进行预处理操作的指令,同时对hello.c源程序预处理得到的hello.i文件的进行了结果解析。预处理阶段对源程序里以字符#开头的命令修改程序,为接下来的编译阶段做好了准备。
第3章 编译
3.1 编译的概念与作用
3.1.1编译的概念
编译是编译器将预处理产生的文本文件翻译为汇编语言程序的过程,即将hello.i文件翻译成文本文件hello.s。
3.1.2编译的作用
编译的目的是将高级语言编写的程序转化为汇编语言,这样不同的高级语言均转化至汇编语言,在生成机器指令的时候就只有汇编语言一条来源,从而实现不同高级语言在机器上的统一。
3.2 在Ubuntu下编译的命令
编译的命令:cc -S -o hello.s hello.i或gcc -S -o hello.s hello.c
如图所示:
3.3 Hello的编译结果解析
3.3.1数据
①常量
hello源程序中含有两个字符串常量,都是printf函数的第一个参数:
汇编器直接将其表示为字符常量,其在汇编程序中对应以下部分:
②变量
hello源程序中含有一个局部变量:
编译器将局部变量i放在了栈中存储,其在汇编程序中对应以下部分:
③函数参数
在main函数中,argc与argv数组作为参数:
编译器将第一个参数放在%edi寄存器中,将第二个参数放在%rsi寄存器中:
其中第21行是第一个参数argc的地址,第22行是第二个参数argv的首地址,第二个参数通过首地址加偏移量的形式取值。
3.3.2赋值
hello源程序中有一个赋值操作:
编译器直接将立即数0传给i:
3.3.3类型转换
hello源程序中调用了一次类型转换函数atoi:
编译器将argv[3]作为参数直接调用atoi函数:
3.3.4算数操作
hello源程序中有一处算数操作:
编译器每次给i所在地址的值增加1:
3.3.5关系操作
hello源程序中有两处关系操作:
对于不等于操作,编译器直接将立即数4与argc比较,若相等则跳转,若不相等则执行if语句内的代码:
对于小于操作,编译器将i与8比较,并利用跳转指令做出相应行为;若i小于等于8则继续执行循环,若i大于8则结束循环:
3.3.6数组操作
hello源程序中有取数组元素的值一种操作:
编译器通过数组首地址+偏移量的方式取值:
-32(%rbp)为数组首地址,-32(%rbp)+16为第三个元素的地址,-32(%rbp)+8为第二个元素的地址,即-32(%rbp)+8k为argv[k]的地址,然后再从地址中取值。
3.3.7控制转移
hello源程序中存在两处控制转移:
对于if语句,编译器直接将argc与立即数4比较,若相等则利用跳转指令跳转,若不相等则执行if语句内的代码:
对于for循环,编译器每次比较i与8的值,若i小于等于8则继续跳转至循环内的代码执行,并在执行结束后加1;若i大于8则不跳转,循环结束。
3.3.8函数操作
①函数调用
hello源程序中调用了5个函数:
当函数没有参数时,编译器直接调用函数:
当函数有一个参数时,编译器将参数放在%rdi寄存器中,调用函数:
当函数有两个参数时,编译器把第一个参数放在%rdi寄存器中,第二个参数放在%rsi寄存器中,调用函数:
②函数返回
hello源程序中main函数具有返回值0:
编译器将返回值放到%rax寄存器中,然后使用ret返回:
3.4 本章小结
本章详细介绍了编译的第二个阶段——编译阶段,内容包含编译的概念,编译的作用,以及在Ubuntu下进行编译操作的指令,同时对hello.c源程序编译得到的hello.s文件进行了详细的解析,分析了编译器是如何用汇编代码实现源程序的每一步操作。通过编译阶段,不同高级语言均转换至汇编语言,为接下来生成机器指令做好了准备。
第4章 汇编
4.1 汇编的概念与作用
4.1.1汇编的概念
汇编是编译的第三个阶段,在这个过程中,汇编器将汇编程序翻译成机器语言指令,并把这些指令打包成可重定位目标程序的格式,得到二进制形式的可重定位目标程序;即由hello.s文件到hello.o文件。
4.1.2汇编的作用
汇编的作用是将汇编代码翻译为机器可执行的机器指令。
4.2 在Ubuntu下汇编的命令
汇编的命令:gcc -c hello.c或as -o hello.o hello.s
如图所示:
4.3 可重定位目标elf格式
使用指令查看hello.o的ELF格式:
ELF文件的内容:
①ELF头
ELF头以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序;剩下的部分包扩ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移以及节头部表中条目的大小和数量。
②节头部表
③重定位节
当汇编器遇到对最终位置未知的目标引用时,会生成一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。
offset:需要被修改的引用的节偏移。
symbol:标识被修改的引用应该指向的符号。
type:告知链接器如何修改新的引用。
addend:一个有符号常数,一些类型的重定位要使用它对被修改引用的值做偏移调整。
以下是重定位节的内容:
④符号表
每个可重定位目标模块都有一个符号表,它包含可重定位目标模块定义和引用的符号的信息。
4.4 Hello.o的结果解析
hello.o的反汇编如下:
机器指令是全由二进制组成的,每一条汇编指令都对唯一对应着一条机器指令,因此实现机器代码和汇编代码之间的转换。
通过与hello.s文件对比发现,反汇编代码与hello.s文件里的大部分内容是一样的,而不同之处在于以下部分:
①函数调用
hello.s文件中函数调用是直接call+函数名,而反汇编代码中的函数名都用一个地址来替代了,这是因为机器指令无法识别函数名,只能识别一个地址。
②分支转移
hello.s文件中条件转移跳转的地址是用伪代码来表示的,同样,机器指令无法识别伪代码,在机器指令中跳转的地址直接用地址来表示。
③立即数的表示
hell.s文件中立即数用十进制表示,反汇编代码中立即数用十六进制表示。
4.5 本章小结
本章详细介绍了编译的第三个阶段——汇编阶段,内容包含汇编的概念,汇编的作用,以及在Ubuntu下进行汇编操作的指令,对可重定位ELF文件进行了详细的查看,分析了ELF文件的各组成部分;同时查看了hello.o文件的反汇编代码,通过与hello.s文件作比较,分析了hello.s汇编代码与反汇编代码的不同之处。通过汇编阶段,我们得到了可重定位目标程序,为接下来的链接做好了准备。
第5章 链接
5.1 链接的概念与作用
5.1.1链接的概念
链接是编译的第四个阶段,也是编译的最后一个阶段。链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,在这个过程中,链接器将源程序的可重定位目标程序与静态库和动态库链接,最后得到可执行目标文件,即由hello.o文件得到最终的可执行文件hello。
5.1.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.3 可执行目标文件hello的格式
使用指令查看hello的ELF格式:
其内容包含以下部分:
①ELF头
与.o文件的ELF格式文件的内容相同。
②节头部表
节头部表存放所有节的基本信息,包含名称、类型、大小、地址基址和偏移量等,与.o文件的ELF格式文件的内容相同。
③程序头部表
ELF可执行文件被设计得很容易加载到内存,可执行文件的连续的片被映射到连续的内存段,程序头部表描述了这种映射关系。
④动态链接段
⑤重定位段
⑥符号表
5.4 hello的虚拟地址空间
在edb中加载hello如下:
由5.3中各段的起始地址,可以在data dump中查看相应段。以.text段为例,其在节头部表中的信息为:
由此可得其起始地址为0x400550,大小为0x132字节,在data dump中找到其对应的内容为:
5.5 链接的重定位过程分析
5.5.1hello与hello.o的不同
对hello反汇编:
分析hello的反汇编代码与hello.o反汇编代码,可以发现hello与hello.o的不同之处主要在于以下部分:
①hello文件中增加了链接之后增加的函数,这些函数都是hello所调用的。
②hello文件中增加了.init节和.plt节。
③hello文件中所有的地址都被替换成了运行是的实际地址,hello.o文件中的地址都是从0开始的。
5.5.2重定位过程
重定位项目的格式为:
typedef struct{
long offset; //需要被修改的引用的节偏移
long type:32; //重定位类型
symbol:32; //被修改的引用指向的符号
long addend; //偏移调整量
}Elf64_Rela;
在重定位过程中,链接器根据重定位类型的不同得到不同的便宜调整量,通过节偏移和重定位条目的地址就可以计算出重定位后的地址;最基本的重定位类型有两种,分别是重定位一个使用32位PC相对地址的引用和重定位一个使用32位绝对地址的引用。
5.6 hello的执行流程
程序从加载hello到_start,到call main,以及程序终止的所有过程为先执行_start、_libc_start_main,之后执行_main、_printf、_exit、_atoi、_sleep、_getchar,最后退出。
调用与跳转的各个子程序名及程序地址为:
_start 0x400550
_libc_start_main 0x7f4274e11090
_main 0x400582
_printf 0x400500
_exit 0x400530
_atoi 0x400520
_sleep 0x400540
_getchar 0x400510
5.7 Hello的动态链接分析
在调用共享库函数时,编译器没有办法预测这个函数的运行时地址,因为定义它的共享模块在运行时可以加载到任意位置。正常的方法是为该引用生成一条重定位记录,然后动态链接器在程序加载的时候再解析它;GNU编译系统使用延迟绑定,将过程地址的绑定推迟到第一次调用该过程时。
延迟绑定是通过GOT和PLT实现的。GOT是数据段的一部分,而PLT是代码段的一部分。
在节头部表中查看GOT的起始地址:
在edb中查看GOT的内容:
在dl_init之后再次查看GOT内容:
发现0x601008和0x601010两个字节发生变化,出现了两个地址0x7f7f256f8170和0x7f7f254e4820,这就是GOT[1]和GOT[2]的地址。
查看GOT[1]内容如下:
查看GOT[2]内容如下:
5.8 本章小结
本章详细介绍了编译的第四个阶段——链接阶段,内容包含链接的概念,链接的作用,以及在Ubuntu下进行链接操作的指令,详细查看了可执行文件的ELF格式,分析了ELF文件的各组成部分;同时查看了hello文件的反汇编代码,通过与hello.o文件的反汇编代码作比较,对重定位过程的操作进行了详细的分析;并且详细分析了hello文件的执行过程,以及它的动态链接分析。通过链接阶段,我们终于得到了可执行目标文件,使运行程序成为可能。
第6章 hello进程管理
6.1 进程的概念与作用
6.1.1进程的概念
进程是一个执行中程序的实例,系统中每个程序都是运行在某个进程的上下文中;在我们运行程序的时候,通过进程给我们营造了一种我们的程序是系统中当前运行的唯一程序的假象。
6.1.2进程的作用
系统中的每个程序都运行在某个进程的上下文中,上下文由程序正确运行所需的状态组成,包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。由此可知,进程为一个程序的运行提供了不可或缺的支持。
6.2 简述壳Shell-bash的作用与处理流程
6.2.1Shell-bash的作用
shell是操作系统的最外层,是一个用户跟操作系统之间交互的命令解释器; 独立于内核,是链接内核和应用程序的桥梁,通俗来讲shell是内核周围的外壳。shell将用户命令解析为操作系统所能理解的指令,实现用户与操作系统的交互;当需要重复执行若干命令,可以将这些命令集合起来,加入一定的控制语句,编辑成为shell脚本文件,交给shell批量执行。
bash是shell的一个实现。
6.2.2Shell-bash的处理流程
①从命令行读取命令字符串。
②将字符串切分,获得命令行输入的参数。
③终端进程调用fork( ) 来创建子进程,自身则用系统调用wait( ) 来等待子进程完成。
④当子进程运行时,它调用execve( ) 根据命令的名字指定的文件到目录中查找可行性文件,调入内存并执行这个命令。
⑤接收从键盘而来的信号并做出相应反应。
6.3 Hello的fork进程创建过程
①父进程通过调用fork函数创建一个新的运行的子进程。
② fork函数调用一次返回两次,在父进程中返回子进程的pid,在子进程中返回0;父进程和子进程间最大的区别在于它们有不同的pid。
③新建的子进程几乎但不完全与父进程相同,子进程得到与父进程用户级虚拟地址空间相同但独立的一份副本,包括代码和数据段、堆、共享库以及用户栈;子进程还获得与父进程任何打开文件描述符相同的副本,即子进程可以读写父进程中打开的任何文件。
6.4 Hello的execve过程
①execve函数加载并运行可执行目标文件filename,且带参数列表argv和环境变量列表envp。
②execve函数调用一次从不返回,只有遇到错误,如找不到filename时,execve函数才会返回到调用程序。
④execve加载了filename后,调用_start启动代码。启动代码设置栈,并将控制传递给新程序的主函数。
6.5 Hello的进程执行
6.5.1相关概念
①上下文
上下文由程序正确运行所需的状态组成,包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
②进程时间片
一个进程执行它的控制流的一部分的每一时间段叫做时间片,多任务也叫做时间分片。
③逻辑控制流
即使在系统中有许多程序在运行,进程也可以向每个程序提供一种假象,好像它在独占地使用处理器。如果想用调试器单步执行程序,就会看到一系列的程序计数器的值,这些值唯一地对应于包含在程序的可执行目标文件中的指令。或是包含在运行时动态链接到程序的共享对象中的指令。这个PC值得序列称为逻辑控制流,或者简称逻辑流。
6.5.2进程调度的过程
内核为每个进程维持一个上下文,上下文就是内核重新启动一个被抢占的进程所需的状态。
在进程执行的某些时刻,内核可以决定抢占当前的进程,并重新开始一个先前被抢占了的进程,这种决策就称为调度,是由内核中称为调度器的代码处理的。
进程上下文切换如图所示:
在hello程序中,上下文切换和进程调度主要发生在sleep函数处,执行sleep前hello进程会处于用户模式并顺序执行,当执行sleep函数以后会被切换到到内核模式进入休眠,加入等待序列;当休眠结束后引起中断并切换到用户模式执行被挂起的程序,直至下次上下文切换。
6.5.3用户态与核心态的转换
处理器通常是用某个控制寄存器中的一个模式位来提供这种功能的,该寄存器描述了进程当前享有的特权。当设置了模式位时,进程就运行在内核模式中;当没有设置模式位时,进程就运行在用户模式中。
6.6 hello的异常与信号处理
6.6.1hello执行过程中的异常信号
①中断:来自I/O设备的信号
异常处理:当前指令执行结束后,处理器发现中断引脚的电压变高了,于是调用中断处理程序,处理结束后返回到下一条指令。
如下图所示:
②陷阱:调用sleep函数
异常处理:控制传递给处理程序,处理结束后返回到下一条指令。
如下图所示:
③故障:缺页故障
异常处理将控制传递给缺页处理程序,缺页处理程序从磁盘中加载适当的页面,然后将控制返回给引起故障的指令。
如下图所示:
④终止:DRAM或SRAM位被损坏时发生奇偶错误
异常处理:将控制传给终止处理程序,终止hello程序。
6.6.2从键盘信号输入示例
①回车和乱按字符
hello程序运行不受影响,待其运行结束后命令行处理这些回车和字符。
②Ctrl-Z
进程挂起,接下来执行ps、jobs、pstree、fg、kill命令
(1)ps:
(2)jobs:
列出了当前工作集中的工作。
(3)pstree:
会打印进程树。
(4)fg
会再从头运行一遍。
(5)kill
可以通过kill发送信号。
③Ctrl-C
直接中断程序运行。
6.7本章小结
本章介绍了进程的概念和进程的作用,简述了shell的作用和处理流程,并详细介绍了fork进程创建的过程和execve进程加载的过程,同时分析了hello的进程执行,以及执行过程中遇到的异常和信号及相应处理。通过这一章,我们更深入得认识了hello函数在进程中的表现。
第7章 hello的存储管理
7.1 hello的存储器地址空间
结合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动态存储分配管理
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
7.10本章小结
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
8.3 printf的实现分析
[转]printf 函数实现的深入剖析 - Pianistx - 博客园
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
结论
hello所经历的过程
①预处理:预处理器预处理hello.c源程序,得到处理后的源程序hello.i。
②编译:编译器编译hello.i文件,得到汇编程序hello.s。
③汇编:汇编器汇编hello.s文件,得到可重定位目标程序hello.o。
④链接:链接器将hello.o文件与库函数链接,得到可执行目标文件hello。
⑤创建进程:在shell中运行hello,shell为hello创建子进程。
⑥加载进程:子进程中调用execve函数,加载并运行hello。
⑦执行指令:hello进程顺序执行指令,对遇到的异常和信号做出反应。
⑧回收进程:hello执行结束后,shell回收进程,系统释放进程占用的内存。
回顾hello的一生,它是迅速而转瞬即逝的,我们仅仅点击一个编译符号,在命令行中输入参数,它就运行,而后结束一生。
回顾hello的一生,它又是漫长而充满生命力的,我们一步一步的对hello一生中的每一个里程点做操作,一步一步体会它的一生。
hello的一生承载着我对计算机系统这门课程半年之久的学习记忆,同时也有些遗憾没有学完CSAPP这本书中未能学完的后半部分,此刻寄希望于不断进步的我,将hello未完成的一生一步一步完善。
最后的最后我想说,CSAPP值得捧读。
附件
hello.i 预处理得到的修改了的源程序
hello.s 编译得到的汇编程序
hello.o 汇编得到的可重定位目标程序
hello 链接得到的可执行目标文件
asm.txt 对hello.o反汇编得到的反汇编代码
asm2.txt 对hello反汇编得到的反汇编代码
hello.elf hello.o的ELF格式文件
hello2.elf hello的ELF格式文件
参考文献
[1] 《深入理解计算机系统》
[2] https://blog.csdn.net/qq_36299025/article/details/90927980
[3] https://www.jianshu.com/p/5092d6d5caa3
[4] https://blog.csdn.net/m0_53263647/article/details/126681711
[5] https://www.cnblogs.com/noticeable/p/9310798.html
[6] 解析GNU风味的linker options - 知乎
[7] https://blog.csdn.net/weixin_61567666/article/details/124249840