CSAPP——Hello程序的一生

CSAPP大作业:程序人生-Hello’s P2P

摘 要
程序的一生是怎样的?本篇论文将以hello.c程序为例,带你漫步程序的一生:预处理、编译、汇编、链接、运行和回收;带你结识程序一生中所邂逅的那些“人”:OS、CPU、RAM、IO等;带你见证程序一生中经历的那些事:进程管理、存储管理、IO管理等。

关键词:预处理;编译;汇编;链接;进程管理

目 录

第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本章小结
结论
附件
参考文献

第1章 概述
1.1 Hello简介
Hello的一生从Program开始,你在键盘上敲下的那一行行代码就是源程序文件hello.c,它们先被预处理生成.i格式的文件,接着被编译器编译成汇编文件,然后通过汇编器生成可重定位目标文件(.o文件),最后链接器会将所有的.o文件链接生成一个可执行目标文件(.out文件)。至此一个Program就完成了。
当你运行Program时,Hello程序的人生也就进入了Process阶段,首先进程管理会为Hello程序创建一个进程,接着它就会被等待执行,当CPU选中它后,内存管理会将它的虚拟地址映射到物理地址,然后它就会被逐条指令地执行,当执行完成后,Hello进程以及它所创建的所有子进程都将被销毁,最后OS内核会将它们进行回收。至此,Hello的一生就完美落幕了。
1.2 环境与工具
软硬件环境:Windows11、Ubuntu、Linux
开发与调试工具:gcc、gedit、gdb、objdump
1.3 中间结果
hello.i:预处理文件
hello_test.i:去掉include后的预处理文件
hello.s:汇编文件
hello.o:可重定位目标文件
assemble.txt:hello.o的反汇编文件
hello.out:可执行目标文件
hello.txt:hello.out的反汇编文件
1.4 本章小结
Hello程序的一生可以说是P2P(from Program to Process)、020(from zero to zero)的一生。回顾Hello的一生,OS为它fork、execve、mmap,分给它时间片,让它得以在CPU/RAM/IO上驰骋;TLB、4级页表、3级cache等各显神通为它加速;IO管理与信号处理软硬结合,使得它能在键盘、主板和屏幕间游刃有余;OS与bash在它完美谢幕后为它收尸。Hello程序的一生虽然短暂,却异常精彩。

第2章 预处理
2.1 预处理的概念与作用
2.1.1 预处理的概念
预处理也称为预编译,它为编译做预备工作,主要进行代码文本的替换工作,用于处理#开头的指令。经过预处理器处理的源程序会有所不同,但预处理阶段所进行的工作只是纯粹的替换和展开,没有任何计算功能。
2.1.2 预处理的作用
预处理主要有三大作用:
1)宏定义
宏定义又称为宏代换、宏替换,简称“宏”。预处理会将宏名替换为字符串,即在对相关命令或语句的含义和功能作具体分析之前就要换。
2)文件包含
它可用来把多个源文件连接成一个源文件进行编译,结果生成一个目标文件。
3)条件编译
条件编译允许只编译源程序中满足条件的程序段,使生成的目标程序较短,
从而减少了内存的开销并提高了程序的效率。
2.2在Ubuntu下预处理的命令
1)命令:cpp hello.c > hello.i
CSAPP——Hello程序的一生_第1张图片

图1 hello.i内容展示

2)命令:gcc hello.c -E hello.i
CSAPP——Hello程序的一生_第2张图片

图2 hello.i内容展示

3)cpp hello.c > hello_test.i(去掉include后的预处理文件)
CSAPP——Hello程序的一生_第3张图片

图3 hello_test.i内容展示

2.3 Hello的预处理结果解析
通过将源程序生成的hello.i文件与去掉include语句后生成的hello_test.i文件进行对比,可以发现预处理过程将头文件中所包含库函数的源文件内容也加入进来了,实现了把多个源文件连接成一个源文件的功能。
通过将源程序hello.c与hello.i进行对比,可以发现我们源程序中所有的程序段都是满足条件的,因此预处理器并没有进行条件编译。同时,因为我们的hello.c程序中并未采用宏定义,所以宏替换的功能在此没有体现。
2.4 本章小结
一个只有18行代码的hello.c程序,经过cpp预处理后,得到的预处理文件hello.i有3091行之多,而相差这么多行代码的原因只是因为我们include了三个头文件。从这里我们可以看出预处理功能的强大,它使我们可以通过简单地调用库函数实现我们需要的操作,而cpp会自动将库函数的源代码连接到你的源程序文件中。

第3章 编译
3.1 编译的概念与作用
3.1.1 编译的概念
编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析以及优化后生成相应的汇编代码文件。
3.1.2 编译的作用
编译主要有五大作用:
1)词法分析
词法分析会根据用户之前描述好的词法规则将输入的字符串分割成一个个记号,然后放到对应的表中。
2)语法分析
语法分析器根据用户给定的语法规则,将词法分析产生的记号序列进行解析,
然后将它们构成一棵语法树。对于不同的语言,语法规则也不一样。
3)语义分析
有的语句在语法上是合法的,但是却是没有实际意义的,比如说两个指针做
乘法运算,这个时候就需要进行语义分析。
4)中间代码生成
对于一些在编译期间就能确定的值,编译器是会将它进行优化的,但是直接
在语法上进行优化的话比较困难,这时优化器会将语法树转换成中间代码。
5)目标代码生成与优化
代码生成器将中间代码转换成目标代码,最后代码优化器对目标代码进行优
化,比如用移位来代替乘除法、删除多余的指令等。
3.2 在Ubuntu下编译的命令
1)命令:/usr/lib/gcc/x86_64-linux-gnu/11/cc1 hello.i -o hello.s
(注:不同机器cc1的目录位置不同)
CSAPP——Hello程序的一生_第4张图片

图4 hello.s结果展示

2)命令:gcc hello.c -S hello.s
CSAPP——Hello程序的一生_第5张图片

图5 hello.s结果展示

3.3 Hello的编译结果解析
3.3.1 常量
如果是整型数据,编译器不会将它的值保存在寄存器或存储器中,而是以立即数的形式给出,立即数的书写方式是’$’后面跟一个用标准C表示法表示的整数,比如我们hello.s中的$8、$0等。
CSAPP——Hello程序的一生_第6张图片

图6 hello.s中的.L3代码段

如果是字符串类型的数据,编译器会将它们存放在指定的内存单元中,比如我们hello.s程序中的"Usage: Hello 2021110894 Yangjunbo seconds!“和"Hello %s %s\n”。
CSAPP——Hello程序的一生_第7张图片

图7 hello.s中的.LC0和.LC1代码段

3.3.2 全局变量
全局变量是在函数外部定义的变量,可以在程序任何地方创建,其作用域是从声明处到文件的结束。在我们的Hello程序中,有两个全局变量:argc和argv,其中argc是一个整型数,用来存放参数个数;argv是一个字符串数组,用来存放从命令行获得的参数。编译器在编译时,将argc的值存到了 r b p − 20 处 , 通 过 直 接 寻 址 方 式 就 可 以 获 得 a r g c 的 值 ; 将 a r g v [ 0 ] 、 a r g v [ 1 ] 、 a r g v [ 2 ] 、 a r g v [ 3 ] 的 地 址 分 别 存 放 到 了 rbp-20处,通过直接寻址方式就可以获得argc的值;将argv[0]、argv[1]、argv[2]、argv[3]的地址分别存放到了 rbp20argcargv[0]argv[1]argv[2]argv[3]rbp-32, r b p − 24 , rbp-24, rbp24rbp-16,$rbp-8处,如果想要使用这些参数的值,需要通过间接寻址的方式获得。
CSAPP——Hello程序的一生_第8张图片

图8 hello.s中的.L4代码段

3.3.3 局部变量
局部变量是由某对象或某个函数创建的变量,只能被内部引用。在我们的hello.c源程序中,只有一个局部变量i,它在循环体中被多次使用。编译器在编译时,将i的值存到了$rbp-4处。如果想要获得i的值,通过直接寻址的方式即可。
CSAPP——Hello程序的一生_第9张图片

图9 hello.c源程序

在这里插入图片描述

图10 hello.s中的.L2代码段

3.3.4 算术操作
算术操作包括:+ - * / % ++ – 取正/负± 复合“+=”等。Hello程序中涉及的算术操作只有++,其中“=”属于赋值,并不属于算术操作。对于++操作,编译器的处理方式也很简单,即使用add $1, -4(%rbp),这里-4(%rbp)表示的是i的值,于是就实现了i++的功能。

3.3.5 关系操作
关系操作包括:== != > < >= <= 等。Hello程序中涉及的关系操作有两个:“!=” 和 “<”。对于“!=”操作,编译器使用了如下两条语句来实现:
cmpl $4, -20(%rbp)
je .L2
比较argc和4的大小,如果相等则跳转到.L2,否则继续执行后面的语句。对于“<”操作,编译器使用了如下两条语句来实现:
cmpl $8, -4(%rbp)
jle .L4
比较i和8的大小,如果i<=8,即i<9,则跳转到.L4,否则继续执行后面的语句。
3.4 本章小结
通过上面对于hello.s汇编文件的分析,我们可以发现编译器是很聪明的。它只通过操作数和操作指令的组合便可以将我们的预处理文件转化成机器语言文件。同时,它也经常会对我们编写的代码进行不同程度的优化。因此,cc1可以说是Hello程序人生旅途上的一个贵人。

第4章 汇编
4.1 汇编的概念与作用
4.1.1 汇编的概念
把汇编语言翻译成机器语言的过程称为汇编。在机器语言中,用操作码代替助记符,用地址码代替地址符号或标号。这样用机器语言的二进制码代替符号,就把汇编语言变成了机器语言。
4.1.2 汇编的作用
汇编主要有三大作用:
1)将每一条可执行汇编语句转化成对应的机器指令;
2)处理源程序中出现的伪指令;
3)求出操作数区各操作数的值(用二进制表示)。
4.2 在Ubuntu下汇编的命令
1)命令:as hello.s -o hello.o
CSAPP——Hello程序的一生_第10张图片

图11 生成hello.o截图展示

2)命令:gcc hello.s -o hello.o
CSAPP——Hello程序的一生_第11张图片

图12 生成hello.o截图展示

4.3 可重定位目标elf格式
hello.o是一个可重定位目标文件,其ELF格式主要包括:ELF头、.text节、.rodata节、.data节、.bss节、.symtab节、.strtab节等。

CSAPP——Hello程序的一生_第12张图片

图13 ELF头的基本信息

ELF头包含了帮助链接器语法分析和解释目标文件的信息,如目标文件的类型、节头部表的文件偏移以及节头部表中的条目大小和数量等。
CSAPP——Hello程序的一生_第13张图片
在这里插入图片描述
CSAPP——Hello程序的一生_第14张图片

图14 各节的基本信息

其中.text节包含了已编译程序的机器代码,.rodata节包含了只读数据,.data节包含了已初始化的全局C变量,.bss节包含了未初始化的全局C变量,.symtab节是一个符号表,.strtab节是一个字符串表。

CSAPP——Hello程序的一生_第15张图片

图15 符号表节中的项

从图15中可以看出,.symtab节存放了在程序中定义和引用的函数和全局变量的信息,但是它不包含局部变量的条目。

CSAPP——Hello程序的一生_第16张图片

图16 重定位节的基本信息

从图16中可以看出,hello.o中包含了两个重定位节 ‘.rela.dyn’ 和 ‘.rela.plt’ ,‘.rela.dyn’ 节中包含了8个条目,在相对偏移量为0x5f0处;‘.rela.plt’ 节包含了6个条目,在相对偏移量为0x6b0处。我们需要的库函数puts()、printf()、getchar()、atoi()、exit()和sleep()的符号解析在’.rela.plt’ 节中都可以找到。

4.4 Hello.o的结果解析
首先用命令objdump -d hello.o > assemble.txt生成hello.o的反汇编文件assemble.txt,如下图所示。
CSAPP——Hello程序的一生_第17张图片

图17 生成的assemble.txt文件

从assemble.txt文件中可以看出机器语言是直接用二进制代码指令表达的计算机语言,而指令是用0和1组成的一串代码,它们有一定的位数,并分成若干段,各段的编码表示不同的含义。
与hello.s对比分析,可以发现je的操作码是74,ret的操作码是c3,leave的操作码是c9等,每一条指令都对应有唯一的操作码。对操作数进行分析,可以发现机器语言中采用的是小端序,并且立即数的操作数统一都是8位,如$0x1用机器语言表示就是01 00 00 00。同时机器语言中没有寄存器引用和存储器引用这两种操作数类型。再对分支转移进行分析,我们会发现机器语言所提供的控制转移指令只有无条件跳转、条件跳转、进入子程序和从子程序返回等最基本的几种。用它们来构造循环、形成分支、调用函数和过程要事先做许多的准备工作。
4.5 本章小结
汇编过程不仅将其它源文件的代码拷贝到了一个文件中,而且通过将汇编语言转换成机器语言实现了从文本文件向二进制文件的过渡。Hello程序经过汇编后虽然已经变得“面目全非”,但它离展示自己的日子(执行阶段)也越来越近了。

第5章 链接
5.1 链接的概念与作用
5.1.1 链接的概念
链接就是将不同部分的代码和数据收集和组合成为一个单一文件的过程,这个文件可被加载或拷贝到内存并执行。
5.1.2 链接的作用
链接主要有两大作用:
1)符号解析
目标文件需要定义和引用符号,而符号解析的目的就是将每个符号引用和一个符号定义联系起来。
2)重定位
编译器和汇编器产生从地址零开始的代码和数据节。链接器通过把每个符号定义与一个存储器地址联系起来,然后修改所有对这些符号的引用,使得它们指向某个存储器地址,从而重定位这些节。
5.2 在Ubuntu下链接的命令
在这里插入图片描述

图18 使用ld的链接命令生成hello.out文件

注:除需链接libc库(-lc)外,还需链接c的运行时库crt*。如果不指定连接crt库,如$ ld -o hello hello.o -lc,则会报以下错误:ld: 警告: 无法找到项目符号 _start; 无法设置起始地址。这是因为编译程序的默认起始地址为_start,它在crt库中。虽然可以通过-e选项进行修改,但C库的初始化等都需要crt先执行。
5.3 可执行目标文件hello的格式
hello.out是一个可执行目标文件,其ELF格式类似于hello.o的格式,主要包括:ELF头、.init节、.text节、.rodata节、.data节、.bss节、.symtab节、.strtab节等。因为可执行文件是完全链接的(已被重定位了),所以它不再需要.rel节。

CSAPP——Hello程序的一生_第18张图片

图19 ELF头的基本信息

ELF头部描述文件的总体格式,它还包括程序的入口点,也就是程序运行时要执行的第一条指令的地址。
CSAPP——Hello程序的一生_第19张图片
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
CSAPP——Hello程序的一生_第20张图片
CSAPP——Hello程序的一生_第21张图片

图20 各节的基本信息

.text、.rodata和.data节和可重定位目标文件中的节是相似的,除了这些节已经被重定位到它们最终的运行时存储器地址以外。.init节定义了一个小函数,叫做_init,程序的初始化代码会调用它。
5.4 hello的虚拟地址空间
CSAPP——Hello程序的一生_第22张图片

图21 edb的加载界面

CSAPP——Hello程序的一生_第23张图片

图22 使用edb查看hello进程的虚拟地址空间

5.5 链接的重定位过程分析
CSAPP——Hello程序的一生_第24张图片

图23 hello.out的反汇编文件

CSAPP——Hello程序的一生_第25张图片

图24 hello.o的反汇编文件

通过将hello.txt与assemble.txt进行对比,可以发现hello.out与hello.o的机器指令是基本一样的,但是地址完全不一样。原因是重定位的过程中,ELF可执行文件hello.out的连续的片会被映射到连续的存储器段,因此hello.out中的地址会指向内存中的物理地址。
5.6 hello的执行流程
下面分为三个过程来解析hello的执行流程。
1)从加载hello到_start
在此过程中.init节中的_init函数对hello.out进行了初始化,.plt节声明了我们调用的库函数:puts、printf、getchar等。
2)从_start到call main
在此过程中,程序对通用寄存器及全局变量进行了处理,为调用主函数做好了准备工作。
3)从main函数到程序终止
这里是hello程序的主体部分,CPU顺序执行hello程序的指令,并执行相应的跳转和调用。在此过程中,hello程序会调用以下六个子程序:
call 401090 puts@plt
call 4010d0 exit@plt
call 4010a0 printf@plt
call 4010c0 atoi@plt
call 4010e0 sleep@plt
call 4010b0 getchar@plt
CSAPP——Hello程序的一生_第26张图片

图25 使用edb运行hello.out

5.7 Hello的动态链接分析
CSAPP——Hello程序的一生_第27张图片

图26 dl_init前的项目内容

CSAPP——Hello程序的一生_第28张图片

图27 dl_init后的项目内容

通过将图26和图27对比发现,dl_init之后,hello程序中需要动态链接的函数代码都被重定位到hello程序代码中了,接下来hello程序就可以正常调用这些函数运行了。
5.8 本章小结
通过本章的探索,你会发现链接生成的hello.out文件与hello.o文件有类似的ELF格式,只是缺少了.rel节。你还会发现关于动态链接的一个有趣的事实:动态链接可以使程序在加载或运行时再进行重定位。通过动态链接,可以减少共享库占用的内存空间大小,并且便于进行共享库的维护。

第6章 hello进程管理
6.1 进程的概念与作用
6.1.1 进程的概念
进程就是一个执行中的程序的实例。系统中的每个程序都是运行在某个进程的上下文中的。
6.1.2 进程的作用
进程提供给应用程序两个关键抽象:
1)一个独立的逻辑控制流
进程提供一个抽象,好像我们的程序独占地使用处理器。
2)一个私有的地址空间
进程提供一个抽象,好像我们的程序独占地使用存储器系统。
6.2 简述壳Shell-bash的作用与处理流程
6.2.1 shell的作用
shell是操作系统中管理进程和运行程序的程序。所有常用的shell都有三个主要作用:(1)运行程序:grep、date、ls等都是一些普通的程序,shell将它们载入内存并运行它们;(2)管理输入和输出:使用< ,>和 | 符号可以将输入、输出重定向。这样就可以告诉shell将进程的输入和输出连接到一个文件或是其它的进程;(3)可编程:shell同时也是带有变量和流程控制的编程语言。
6.2.2 shell的处理流程
1)从stdin来读取用户的command;
2)对command进行解析,并判断是否为built_in command;
3)如果是built_in command则执行built_in command;
4)如果非built_in command,则执行fork命令,然后在fork的子进程中执行execve创建新的program,其中还涉及到对输入command进行parse的简单过程;
5)如果要对进程的切换fg,bg等,需要涉及到signal切换等。
6.3 Hello的fork进程创建过程
由fork创建的新进程被称为子进程。该函数调用一次,返回两次。fork使子进程得到返回值 0。fork之后,父子进程拥有相同的 data段、text段、堆、栈、环境变量、全局变量、进程工作目录位置、信号处理方式等。且子进程与父进程完全一样地拥有fork()前后的资源,但子进程没有机会执行fork前的函数,只能从fork后执行。
当一次创建多个进程时,会发现进程运行不是按照创建的顺序进行的,而是随机序列出现。对操作系统而言,这几个子进程几乎是同时出现的,它们和父进程一起争夺cpu。
6.4 Hello的execve过程
在execve加载了hello.out之后,它调用启动代码。启动代码设置栈,并将控制传递给新程序的主函数。当main开始执行时,用户栈从栈底(高地址)到栈顶(低地址)会依次存放参数和环境字符串,以null结尾的envp[]数组,以null结尾的argv[]数组,envp,argv,argc。
CSAPP——Hello程序的一生_第29张图片

图28 用户栈的典型组织结构

6.5 Hello的进程执行
在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程,这种决定就叫做调度。在内核调度了一个新的进程运行后,它就抢占当前进程,并使用一种称为上下文转换的机制来将控制转移到新的进程,上下文转换1)保存当前进程的上下文。2)恢复某个先前被抢占的进程被保存的上下文,3)将控制传递给这个新恢复的进程。
当内核代表用户执行系统调用时,可能会发生上下文切换。在我们的Hello程序中,一个exit系统调用会使我们的Hello进程终止执行,此时内核可以选择执行上下文切换,运行另外一个进程;一个sleep系统调用,显式地请求让Hello进程休眠。当休眠结束时,内核会重新调度Hello进程。
中断也可能引发上下文切换。在我们的Hello进程运行的过程中,如果运行时间超出了系统分给它的进程时间片,那么内核就会判定Hello进程已经运行了足够长的时间了,它会切换到一个新的进程。
6.6 hello的异常与信号处理
如果正常执行我们的hello程序,不加外界干扰,会出现两类异常:
1)中断
中断是异步发生的,是来自处理器外部的I/O设备的信号的结果。在hello进程执行的过程中,如果分给它的时间片已经用完,定时器芯片会通过向处理器芯片上的一个引脚发信号,并将异常号放到系统总线上,以触发中断,这个异常号标识了引起中断的设备。在当前指令执行完成后,处理器会调用适当的中断处理程序。处理程序返回时,它就将控制返回给下一条指令。结果是程序继续执行,就好像没有发生过中断一样。
2)陷阱和系统调用
陷阱是有意的异常,是执行一条指令的结果。陷阱最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用。我们的hello程序在运行过程中,有两个系统调用函数:sleep和exit。对于陷阱的处理,与中断类似,不同之处在于处理器会调用陷阱处理程序。
如果在hello程序执行过程中,从键盘输入外界干扰信号,效果如下图。
CSAPP——Hello程序的一生_第30张图片

图29 输入回车+Ctrl-z

CSAPP——Hello程序的一生_第31张图片

图30 输入回车、任意字符和Ctrl-c

输入ps命令,可以查看当前系统中的进程(包括僵死进程)。
CSAPP——Hello程序的一生_第32张图片

图31 ps命令结果截图

输入jobs命令,可以查看进程的信息。这里我们看到有一个被停止的hello进程。
在这里插入图片描述

图32 jobs命令结果截图

输入pstree命令,可以查看所有的父子进程。
CSAPP——Hello程序的一生_第33张图片

图33 pstree命令结果截图

输入fg命令,会在前台继续运行已停止的hello进程,将剩下的信息打印出来。
CSAPP——Hello程序的一生_第34张图片

图34 fg命令结果截图

输入kill命令,会杀死一个进程。
CSAPP——Hello程序的一生_第35张图片

图35 kill命令结果截图

我们重新运行一个hello程序,再将它挂起,随后用kill命令杀死它后,会发现再次fg时,它就不能被再次执行了。

使用man 7 signal命令可以查看Linux系统上支持的信号类型。
CSAPP——Hello程序的一生_第36张图片

图36 man 7 signal命令结果截图

结合上图的信号类型,分析我们前面的过程,当我们键入Ctrl-z时,会发送SIGINT信号给进程系统,告知系统一个来自键盘的中断;当我们键入Ctrl-c时,会发送SIGQUIT信号给进程系统,告知系统一个来自键盘的退出。当我们输入kill命令时,会发送SIGKILL给进程系统,告知系统杀死一个程序。
6.7本章小结
经过本章的探索,相信你已经体验到Hello程序变为Process之后是怎么在系统上运行的。进程可以说是计算机科学中最深刻最成功的概念之一,异常是其基本构造块,它包括低层异常机制(进程上下文切换)和高层异常机制(信号)。有了它们的陪伴,Hello程序的人生之路也增添了许多亮色。

结论
一、Hello的一生
Hello的一生是坎坷的一生,但也是不平凡的一生。它的一生大致可分为以下11个阶段。
1)hello.c源文件经过预处理,得到hello.i文本文件。
2)hello.i经过编译,得到hello.s汇编文件。
3)hello.s经过汇编,得到二进制可重定位目标文件hello.o。
4)hello.o经过链接,最终生成了可执行文件hello.out。
5)在shell键入运行hello.out程序的命令,hello.out程序正式开始运行。
6)shell调用fork函数,为hello.out生成子进程。
7)shell调用execve函数,execve启动加载器,加载映射虚拟内存,进入程序入口后程序开始载入物理内存,然后进入main函数。
8)在执行hello中的sleep函数时,进程会陷入内核模式,内核会处理休眠请求并进行上下文切换,将前台进程控制权交给其他进程。当定时器再次发送中断信号时,内核又会切换回hello进程。
9)在执行hello中的printf函数时,还会调用malloc函数进行动态内存分配。
10)在运行hello程序过程中,若我们用户键入ctrl+c、ctrl+z等指令,内核会发送相应信号给进程并对进程进行相应的处理。
11)最终当hello进程执行完毕,父进程会回收子进程,删除该进程创建的所有数据结构,hello的一生最终结束。
二、感悟
我们要成为优秀的程序员、工程师,单单关注顶层的实现是远远不够的,对于底层的原理我们也必须清楚。学习计算机系统让我对计算机有了更加深入的理解,同时,也让我感受到了前人设计计算机时的智慧,对我计算思维的培养有很大的帮助。

附件
hello.c:源程序文件
hello.i:预处理文件
hello_test.i:去掉include后的预处理文件
hello.s:汇编文件
hello.o:可重定位目标文件
assemble.txt:hello.o的反汇编文件
hello.out:可执行目标文件
hello.txt:hello.out的反汇编文件

参考文献
[1] 《深入理解计算机系统》 Randal E.Bryant David R.O’Hallaron 机械工业出版社
[2] 博客园 linux用户栈内核栈的设置——进程的创建:fork/execve https://www.cnblogs.com/sky-heaven/p/5800297.html
[3] 博客园 在嵌入式上使用ldd命令查看链接的库文件
https://www.cnblogs.com/bluettt/p/15354355.html

你可能感兴趣的:(linux,ubuntu,c语言)