计算机系统
大作业
题目 程序人生-Hello’s P2P
专业 人工智能
学号
班级
学生
指导教师
计算机科学与技术学院
2023年5月
本文通过分析hello程序在计算机中运行的全过程,深入理解了hello程序从源程序经过预处理、编译、汇编、链接到可执行文件的进程管理、存储管理等过程。通过对上述内容的理解和分析,回顾了本学期的课程内容,深入体会了计算机之美。
关键词:计算机系统;程序人生;编译;程序运行
目录
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
Hello的P2P(From Program to(two) Progress)过程指Hello从程序变成电脑上运行进程的过程。Hello的第一个阶段是Hello.c,即程序员编写的源文件。这个源文件经过cpp(预处理器)预处理,形成Hello.i。Hello.i经过ccl(编译器)编译生成汇编程序Hello.s。Hello.s经过as(汇编器)生成可重定位目标程序Hello.o。Hello.o最终通过ld(链接器)静态或者动态地连接之后,形成可执行文件Hello.out。在Bash中,我们可以运行这个可执行文件。在Bash中,这个文件一经执行,OS(进程管理系统)就会fork一个新的Process(进程)。
Hello的020(From Zere-0 to Zero-0)过程指Hello从无(Zero)到被程序员创建出来,经过预处理、编译、汇编、链接形成可执行文件。OS为Hello创建新进程,通过execve装载、mmap分配内存和实现共享内存。此时Hello已经可以实现其功能。在Hello执行结束之后,Bash将会把这个进程回收,内核会从删除Hello的痕迹。此时Hello又归于Zero。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
软件环境:Windows7/10 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位 以上
硬件环境:X64 CPU;2GHz;2G RAM;256GHD Disk 以上
开发和调试工具:gdb;edb;readelf;objdump;Code::Blocks20.02
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
①hello.c: hello的源代码
②hello.i: hello经过预处理的文件,解析了头文件
③hello.s: hello经编译得到的文件,可以辅助汇编
④hello.o: hello经汇编得到的可重定向目标文件,可以方便链接
⑤hello.out:hello经过链接得到的可执行文件,可以执行
1.4 本章小结
本章介绍了hello的P2P过程和020过程、以及在完成该报告时候的环境和工具和过程中产生的所有中间文件。
第2章 预处理
2.1 预处理的概念与作用
预处理的概念:程序设计过程中,预处理一般是指在程序源代码被翻译为目标代码的过程中,生成二进制代码的过程。由预处理器cpp根据以字符#开头的命令,修改原始的C程序。列如替换#define宏定义.最后得到另一个C程序,一般以.i为后缀。
预处理的作用:
①将#include的头文件内容直接替换进程序文本。
②将#define定义的宏直接替换。
③预编译程序将根据有关的文件,将不必要的代码过滤掉。
④删除源代码中的注释语句。
2.2在Ubuntu下预处理的命令
命令:cpp hello.o -o hello.i
截图:
2.3 Hello的预处理结果解析
从hello.c和hello.i的对比中,我们可以看出,预处理器读取了hello.c中应用的头文件,并将他们替换入hello.c中,并执行展开等具体操作。从而形成我们看到的hello.i。
2.4 本章小结
程序源文件的预处理是hello程序人生的第一步。通过预处理,头文件被替换,不需要的内容被省略,形成的hello.i为后面的步骤做好了准备。
第3章 编译
3.1 编译的概念与作用
编译的概念:编译指编译器(ccl)把预处理完的.i文件经过分析优化生成相应的.s汇编代码文件的过程。
编译的作用:
①检查经过预处理的文件是否有语法错误。过程中会通过一系列词法分析、语法分析、语义分析。如果上述任意一个产生了错误,则会给出错误信息,并且不通过编译。
②对经过预处理的文件进行优化。经过优化的程序与源程序等价,但是执行效率更高。
③会对文本语言进行转化。经过预处理的文件会从高级语言转换为汇编程序。
④编译的过程建立了高级语言和汇编语言的桥梁。在编译的帮助下,程序员不再需要深入学习复杂而对编写程序而言并不高效的汇编语言,从而提高了程序员的编程效率。
3.2 在Ubuntu下编译的命令
命令:gcc -S hello.i -o hello.s 由hello.i编译生成hello.s
3.3 Hello的编译结果解析
3.3.1 int类型全局变量:
Int型全局变量sleepsecs被赋值为2。在汇编之中,sleepsecs被放于.rodata段中,并被赋值为2
3.3.2 int类型局部变量:
Int型局部变量argc一开始被保存在寄存器%edi中,并被放入main函数的栈帧中(-20(%rbp))。并且要求argc必须为3,否则会进行一串输出直接退出。
3.3.3 char*类型字符串指针:
Char*类型字符串指针argv一开始被保存在寄存器%rsi中,之后被放入main函数的栈帧中(-32(%rbp))。
3.3.4 字符串:
字符串"Usage: Hello 学号 姓名!\n"和"Hello %s %s\n"被放在.text段,其中文部分被编码为数字。
这两条分别载入了对应的字符串进入寄存器%rdi,为输出函数做好准备。
3.3.5赋值操作:
循环变量i被存在栈帧(-4(%rbp))中,并被赋值为0
3.3.6类型转换:
这里sleepsecs是int型变量,所以在赋值给它一个浮点数的时候,会隐式转化为整形。在汇编中,直接赋值给sleepsecs 2 这个数,2正是2.5转化为整形之后的结果。
3.3.7算术操作:
循环变量i自加在这里完成
3.3.8关系操作:
判断argc是不是等于3,如果等于3,跳转.L2段运行后面的程序,否者输出一段文字然后退出。
3.3.9数组/指针操作:
寄存器%rax负责对argv首地址(存在-32(%rbp)中)进行加偏移量16或8,分别找到 argv[1]和argv[2]的地址,分别存在寄存器%rdx和%rax中。为printf做准备。
3.3.10控制转移:
argc一开始被保存在寄存器%edi中,并被放入main函数的栈帧中(-20(%rbp))。并且要求argc必须为3,此时会跳转,否则会进行一串输出直接退出。
这里在控制循环,每次对栈帧中的循环变量加一,然后判断是不是小于9,符合条件就继续,否则就跳出循环。
3.3.11函数调用:
printf函数:寄存器%rdx和寄存器%rsi中都预先准备好了要输出的数据,然后调用系统函数printf来实现输出。
所有函数都采用了Call的方式来调用。其中sleep函数含有传参,是通过把参数放在寄存器%rax中实现的。
3.4 本章小结
同过将hello.i编译成为hello.s,hello程序已经变成符合机器工作方式的汇编程序了。本章详细讲述了hello.i转变为hello.s的过程,分析了hello.i和hello.s之间的关联。
第4章 汇编
4.1 汇编的概念与作用
汇编的概念:汇编是把汇编代码hello.s转化为可执行的机器语言的过程。过程中会把这些指令打包为可重定向目标程序hello.o。
汇编的作用:将汇编语言程序逐条翻译为机器语言程序,打包成为可重定向目标程序。
4.2 在Ubuntu下汇编的命令
指令:as hello.s -o hello.o
4.3 可重定位目标elf格式
使用指令 readelf -a hello.o > hello.elf , 创建elf文件
4.3.1ELF头
ELF可重定向目标文件的ELF头以一个十六字节的序列开始。这串序列描述了生成该文件的系统字的大小和字节顺序。其他部分包含帮助链接器语法分析和结束目标文件的信息,包括ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,以及节头部表中的条目的大小和数量。
4.3.2节头部表
hello.o文件中不同节的位置和大小是由节头部表描述的,其中目标文件的每个节都有一个固定大小的条目。
4.3.3程序头和动态节
本文件没有程序头和动态节。
4.3.4重定位条目
该文件中由两段重定位节 。一段是.rela.text节,这是一个.text节中位置的列表;另一段是.rela.eh_frame节。列表中标注的位置在链接器链接的时候都是要修改的。其中偏移量表示这个引用的节偏移;信息给出了symbol和type,分别标识被修改引用工改指向的符号,和告知链接器如何修改新的引用。加数是一个有符号常数,在某些类型的重定位中会被使用来对修改引用的值做偏移调整。
4.3.5符号表
符号表含有m定义和引用符号的信息,符号表和每个重定位目标模块一一对应。其中Num是字符表中的字节偏移,指向符号以NULL结尾的字符串名字;Value为符号的地址;Size为目标大小;Type为数据和函数二者中一个;Bind指明符号是本地的还是全局的。
4.4 Hello.o的结果解析
4.4.1机器语言的构成和与汇编语言的映射关系
机器语言由一串16进制的数字组成,从某个位置开始翻译,该序列可以唯一地解码成机器指令的组合,也和汇编代码一一对应。
4.4.2 机器语言和汇编语言操作数的不同点
对比hello.s中的操作数和hello.o的反汇编的操作数。我们可以发现,hello.s操作数按10进制来表示的,hello.o的反汇编操作数按16进制表示。
4.4.3反汇编语言使用重定位条目替换全局变量和函数
通过对上述两部分的观察,我们可以发现,反汇编采用了<函数名+偏移量>来表示跳转地址。例如不再使用跳转至.L2这个做法,而是使用了 <main+0x2f>这种方式。但是在这个时候,文件还未进行重定位,故call那一行对应的后四个字节全为0.
4.5 本章小结
本章主要分析了汇编的过程,尤其对ELF表、机器语言和汇编语言之间的关系和区别进行了分析。Hello程序在这个过程中又迈出了程序人生重要的一步。
第5章 链接
5.1 链接的概念与作用
链接的概念:链接是将各种代码和数据片段收集并组合成为一个单一文件的过程, 这个文件可以被载入内存。这个过程由链接器自动执行。
链接的作用:在链接这个想法的帮助下,分离编译成为了可能。我们不必再把一个大型的应用程序组织未一个非常巨大的源文件,而是可以分解为更小、更便于管理的独立小文件。在修改程序时,我们只需要修改对应的模块,而不需要进行全局的重新修改、编译。
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的格式
5.3.1 ELF头
该部分的在第四章中提到,大体内容相同,部分内容根据变化稍作改动,这部分就不再详细叙述。
5.3.2节头
连接之后增加了许多新信息,但是和第四章大同小异,也不做详细叙述。
5.3.3程序头
程序头对系统准备程序执行所需段或其他信息进行了描述。其中的虚拟地址部分描述了吗每个段的起始位置。
5.3.4段节
存储的内容有关程序段的内容的从属关系。
5.3.5重定位节
标识了重定位信息。
5.3.6符号表
标识了更为详细的符号信息。
5.4 hello的虚拟地址空间
通过观察上述两表,我们可以发现,hello在edb中显示的数据分别与ELF文件中的虚拟地址标识的位置一一对应。
5.5 链接的重定位过程分析
两者有以下几点区别:
①链接后函数数量增加,这是因为共享库中的函数被加入可执行文件中
②增加了一些节,多出来的系统函数在这些节中
③函数调用的地址和跳转指令的参数都变成了虚拟地址,计算了相对距离。
链接由符号解析、重定位节和符号定义几步组成。
hello 的反汇编中每一个hello.o反汇编中没有填入地址的位置都被填上了实际的地址。Jump指令后面的数字也变成了欲跳转指令地址和当前地址的差值,以便程序运行。
5.6 hello的执行流程
通过edb逐步调试,得到hello执行流程如下:
得到调用函数顺序如下:
ld-2.27.so!_dl_start
ld-2.27.so!_dl_init
hello!_start
libc-2.27.so!__libc_start_main
-libc-2.27.so!__cxa_atexit
-libc-2.27.so!__libc_csu_init
hello!_init
libc-2.27.so!_setjmp
-libc-2.27.so!_sigsetjmp
–libc-2.27.so!__sigjmp_save
hello!main
hello!puts@plt
hello!exit@plt
hello!printf@plt
hello!atoi@plt
hello!sleep@plt
hello!getchar@plt
ld-2.27.so!_dl_runtime_resolve_xsave -ld-2.27.so!_dl_fixup
–ld-2.27.so!_dl_lookup_symbol_x
libc-2.27.so!exit
5.7 Hello的动态链接分析
共享库是一个目标模块,在运行或加载时,可以通过动态链接的方式和任意一个程序链接起来。动态链接支持把程序按照模块拆分成各个相对独立的部分,在程序运行时才将它们链接在一起形成一个完整的程序。
.plt:PLT是一个条目数组,其中每个条目是16字节代码。PLT[0]指向动态链接器中。每个被可执行程序调用的库函数都有它自己的PLT条目。每个条目都负责调用一个具体的函数。
.got:GOT也是一个地址数组,其中每个条目是8字节地址。和PLT联合使用时,GOT[O]、GOT[1]包含动态链接器在解析函数地址时会使用的信息。动态链接器在1d-linux.so模块中的入口点存储在GOT[2]中。其余的每个条目和每个被调用的函数一一对应,其地址在运行时会被解析。每个条目都有一个匹配的PLT条目
5.8 本章小结
本章介绍了hello通过链接变成可执行文件的操作。此时hello已经变成了可以在shell中直接运行的程序。
第6章 hello进程管理
6.1 进程的概念与作用
进程的概念:进程是一个执行中的程序的实例。
进程的作用:进程这个概念提供给应用程序的两个关键抽象。
①一个独立的逻辑控制流,给程序独自占用整个CPU的假象
②一个私有的地址空间,给程序独子占用整个内存空间的假象
6.2 简述壳Shell-bash的作用与处理流程
①壳shell-bash的作用:
shell是信号处理的代表,负责各进程与程序加载运行及前后台控制,作业调用,信号发送和管理等。
Shell执行一系列的读/求值步骤然后终止。读步骤读取来自用户的一个命令行,求值步骤解析命令行,并代表用户运行程序。在解析命令之后,如果该命令是内置命令,则解析指令并执行,否则就根据相关文件路径执行可执行文件。
②shell-bash的处理流程:
命令行是一串 由空格分隔的ASCII 码字符。字符串的第一个单词是一个可执行程序或 shell 的内置命令。命令行的其余部分是命令的参数。如果第一个单词是内置命令,shell 会立即在当前进程中执行;否则,shell 会新建子进程,然后再子进程中执行程序。新建的子进程又叫做作业。通常,作业可以由 Unix 管道连接的多个子进程组成。如果命令行以 &符号结尾,那么作业将在后台运行,这意味着在打印提示符并等待下一个命令之前,shell 不会等待作业终止。否则,作业在前台运行,这意味着 shell 在作业终止前不会执行下一条命令行。 因此,在任何时候,最多可以在一个作业中运行在前台。 但是,任意数量的作业可以在后台运行。
Unix shell 支持作业控制的概念,允许用户在前台和后台之间来回移动作业,并更改进程的状态,即运行、停止、终止三者必居其一。在作业运行时,键入 ctrl-c会将 SIGINT 信号传递到前台作业中的每个进程,其默认动作是终止进程;类似地,键入 ctrl-z 会导致 SIGTSTP 信号传递给所有前台进程,其默认操作是停止进程,直到它被 SIGCONT 信号唤醒为止。Unixshell 还提供支持作业控制的各种内置命令。
6.3 Hello的fork进程创建过程
在命令行运行hello之后(输入./hello),由shell判断出不是内置指令,所以通过fork函数创建一个新的子进程。除了pid不同,这个子进程几乎就是父进程的副本,但两者的空间却又相互独立。
6.4 Hello的execve过程
fork函数创建完子进程之后,由execve函数读取之前划分的字符串,加载并运行可执行目标文件hello。同时也会把参数列表argv和环境变量列表envp载入,并将pc指向代码入口。此时hello就已经准备好运行了。
6.5 Hello的进程执行
6.5.1 上下文切换和上下文信息
操作系统内核使用上下文切换的异常控制流来实现多任务。内核会为每个进程维护上下文信息,上下文信息就是内核重启进程所需的状态信息。上下文信息包括目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种数据结构。
6.5.2 并发流和进程时间片
如果一个流在执行时间上和另一个流重叠,那么这两个流被称为并发流或者并发进行。多个流并发执行的情况被称为并发。一个进程和其他进程轮流运行的概念称为多任务,也叫时间分片。Hello的进程往往是由多个时间分组成的。
6.5.3 调度
在程序执行的某些时间,内核可决定抢占当前进程, 并重新开始一个先前被抢占的进程,这就是调度。内核中专门有一个称为调度器的程序负责调度。
调度的过程是采用上下文切换的机制来实现的。
6.5.4 用户模式和内核模式
为了保障系统安全,需要限制应用程序所能访问的地址空间范围。因而产生了用户模式和内核模式来划分权限,内核拥有最高的访问权限。例如运行hello这个进程的时处于用户模式中。如果此时发生中断、故障或者有系统调用等异常,就会转入内核模式,使用内核的权限来处理异常。
6.6 hello的异常与信号处理
6.6.1四类异常
①中断:异步异常,来自处理器外部的I/O设备。异常处理后会执行下一条指令;
②陷阱:同步异常,是执行系统调用函数的结果。函数调用结束后会执行下一条指令;
③故障:同步异常,由错误情况引起,如缺页,浮点异常等等。异常处理成功则重新执行该指令,否则程序终止;
④终止:同步异常,由致命错误造成。该异常将终止程序。
6.6.2信号种类
Ctrl-C对应 SIGINT信号
Ctrl-Z对应SIGTSTP信号
6.6.3hello执行过程的分析:
①程序正常执行:
②不停乱按或者回车
不停乱按或者回车不影响程序执行。
③Ctrl-Z
输入Ctrl-Z会导致内核发送一个SIGTSTP信号到前台进程组的每个信号。默认情况下,结果是挂起前台作业。此时,hello被挂起了
④输入ps,查看各个进程的信息,可以看到各个进程的pid以及名字
⑤输入jobs,显示当前shell环境中已启动的作业的状态。
⑥输入pstree,可以在树形结构中显示程序和进程之间的关系
⑦输入fg,将挂起的指令转移到前台执行,发送信号SIGCONT给被挂起的进程使之继续执行。
⑧输入kill命令,将挂起的hello进程杀死,再输入ps指令,可以看到进程已经结束。
⑨Ctrl-C
内核会发送一个SIGINT信号到前台进程组的每个进程,在默认情况下会终止前台作业。通过ps查看,发现没有进程hello,所以hello已经被回收了。
6.7本章小结
本章介绍了hello程序在linux shell上运行的过程。从shell解析命令行为hello创建新进程,并在新进程中加载和运行hello,到hello执行过程中,通过异常、信号等完成与系统进行交互,再到程序完成之后,其进程被回收。Hello完成了他的一生。
第7章 hello的存储管理
7.1 hello的存储器地址空间
7.1.1逻辑地址
逻辑地址空间:[段地址:偏移地址]。hello执行程序反汇编后的内存地址是逻辑地址。逻辑地址经过段式管理可生成线性地址。
7.1.2线性地址
在Linux中,线性地址等与虚拟地址
7.1.3虚拟地址
CPU通过生成一个虚拟地址来访问主存,这个虚拟地址在被送到内存之前先转换成适当的物理地址。
7.1.4物理地址
物理地址就是内存中每个内存单元的编号,这个编号是顺序排好的,物理地址的大小决定了内存中有多少个内存单元,物理地址的大小由地址总线的位宽决定。
7.2 Intel逻辑地址到线性地址的变换-段式管理
段偏移量加上基地址的和,构成线性地址。其中,段偏移量为逻辑地址的组成部分;基地址存储在段描述符表中,该表存储有多个描述符,每个描述符都描述了某个段的起始位置与大小等信息;而逻辑地址中的另一部分:段标识符的高13位为段选择符,段选择符能对应上段描述表中的一个描述符。
综上,逻辑地址到线性地址的变换过程为,取逻辑地址的段标识符中的段选择符,到段描述表中找到对应的描述符,描述符中存有段开始的线性地址,即段基址;段基址加上逻辑地址中的段偏移量就是线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
线性地址是hello程序虚拟地址空间中的虚拟地址。虚拟内存空间与物理内存空间都被划分为页,每个页都有对应的页号。虚拟地址由虚拟页号 + 虚拟页偏移量组成。页表是建立虚拟页号与物理页号映射关系的表结构,页表项包含有效位、物理页号、磁盘地址等信息。虚拟页号 + 页表起始地址能找到相对应的页表项,页表起始地址存储在页表基址寄存器中。页表项存储的页表状态有三种:未分配,已缓存,未缓存。
当对应状态为已缓存时,说明虚拟页所对应的物理页已经存储在内存中,此时页表项存储的物理页号 + 物理页偏移量即为物理地址,而物理页偏移量与虚拟页偏移量相同,可以从虚拟地址中直接得出。
当页表项中状态为未缓存时,若要读取该页,会引发缺页中断异常,缺页异常处理程序根据页置换算法,选择出一个牺牲页,如果这个页面已经被修改了,则写出到磁盘上,最后将这个牺牲页的页表项有效位设置为0,存入磁盘地址。缺页异常程序处理程序调入新的页面,如果该虚拟页尚未分配磁盘空间,则分配磁盘空间,然后磁盘空间的页数据拷贝到空闲的物理页上,并更新页表项状态为已缓存,更新物理页号,缺页异常处理程序返回后,再回到发生缺页中断的指令处,重新按照页表项命中的步骤执行。
7.4 TLB与四级页表支持下的VA到PA的变换
多级页表可以减小翻译地址的空间开销。多级页表中,页表基址寄存器存储一级页表的地址,1到3的页表的每一项存储的下一级页表的起始地址,4级页表的每一项存储的是物理页号或磁盘地址。解析VA时,其前m位vpn1寻找一级页表中的页表项,接着一次重复k次,在第k级页表获得了页表条目,将PPN与VPO组合获得物理地址PA。
7.5 三级Cache支持下的物理内存访问
以组相联高速缓存为例,判断缓存是否命中,然后取出字的过程分为三步:
①组选择
高速缓存从w的地址中抽取出s个组索引位,这些位被解释为一个对应于一个组号的无符号整数,用于在缓存中进行组选择。
②行匹配
确定了缓存中的组i后,缓存将搜索组中的每一行,直到某行标记位与地址中的标记位一致,如果能找到这样的一行,那么即为命中。如果w不在组中的任何一行,那么就是缓存不命中,缓存会从下一级存储空间(例如L1的下一级为L2)中取出包含这个字的块,并依照特定的行替换策略将该行放入缓存中,行替换策略保证被替换行的被引用概率最低。
③字选择
在命中的行中,使用块偏移选中字w返回给cpu。
7.6 hello进程fork时的内存映射
当fork函数被父进程调用时,内核为子进程创建各种数据结构,并分配它唯一的一个PID。为给这个新进程创建虚拟内存,它创建当前进程的mm_struct、区域结构与页表的原样副本;它将两个进程中的每个页面都标记为只读,并把两个进程中的每个区域结构都标记为私有的写时复制。当fork在子进程中返回时,其现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程的任一者进行后续写操作时,写时复制机制就会创建新页面,也就为每个进程保持私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射
在bash中的进程中执行了如下的execve调用:
execve("hello",NULL,NULL)
execve函数在当前进程中加载并运行包含在可执行文件hello中的程序,用hello替代了当前bash中的程序。
加载并运行hello需要以下几个步骤:
①删除已存在的用户区域。删除当前进程hello虚拟地址的用户部分中的已存在的区域结构。
②映射私有区域。为新程序的代码、数据、bss和栈区域创建新的私有的、写时复制的区域结构。其中,代码和数据区域被映射为hello文件中的.text和.data区。bss区域是请求二进制零的,映射到匿名文件,其大小包含在hello中。栈和堆区域也是请求二进制零的,初始长度为零。
③映射共享区域。若hello程序与共享对象或目标(如标准C库libc.so)链接,则将这些对象动态链接到hello程序,然后再映射到用户虚拟地址空间中的共享区域内。
④设置程序计数器(PC)。exceve做的最后一件事是设置当前进程的上下文中的程序计数器,是指指向代码区域的入口点。
下一次调度这个进程时,他将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。
7.8 缺页故障与缺页中断处理
当指令引用一个地址,而与该地址相应的物理页面不在内存中,即PTE中的有效位是0,所以MMU出发了一次异常,会触发缺页故障,内核调用缺页处理程序。通过查询页表PTE可以知该页在磁盘的位置。缺页处理程序从指定的地址加载页面到物理内存中,然后更新PTE。再将控制返回给引起缺页故障的指令。当该指令再次执行时,相应的物理页面已加载在内存中,因此能够命中。
7.9本章小结
本章详细叙述了几个地址的概念做了介绍,从而分析了hello在运行时的内存空间使用情况。此外还介绍了缺页等特殊情况,为hello的程序人生带来了一抹别样的色彩。
结论
Hello.c进化成可执行文件hello经历了层层关卡:
①hello.c经过预编译,对#处理得到hello.i文本文件
②hello.i经过编译,得到汇编代码hello.s汇编文件
③hello.s经过汇编,得到二进制可重定位目标文件hello.o
④hello.o经过静态和动态链接,生成了可执行文件hello
而hello的实际执行离不开操作系统的调度以及硬件的配合:
①shell bash调用fork函数,生成子进程;这个子进程调用execve函数在当前进程的上下文中加载并运行新程序hello
②hello的变化过程中,会有各种地址,但最终我们真正期待的是PA物理地址,可执行文件中VA需要通过TLB以及页表机制进行转换;而PA的访问也需要内存层次结构的支持才得以快速运行。
③hello再运行时会调用一些函数,比如printf函数,这些函数与linux I/O的设备模拟化密切相关
④hello最终被shell父进程回收,内核会收回为其创建的所有信息,hello完成了他的使命。
这次大作业,带我欣赏了hello的程序人生。在hello短暂但又丰富多彩跌宕起伏的生命周期中,我感受到了计算机神奇的设计和巧妙的构造。每一次hello碰见缺页的情况,我就知道它遇到了生命中的一大困难。但是,计算机的设计是接近完美的,hello在计算机中,一定能度过完美的一生。
附件
hello.c 源代码
hello.i 对#预处理指令做初步处理
hello.s 汇编代码文件
hello.o 可重定位目标程序,二进制文件
hello 可执行目标程序,可用于执行
hello.elf hello.o的elf文件
hello2.elf hello的elf文件
output hello.o的反汇编文件
output1 hello的反汇编文件
参考文献
[1] 百度.www.badu.com
[2] 兰德尔 E.布莱恩特 大卫 R. 奥哈拉论 深入理解计算机系统 机械工业出版社,2016