第1章 概述
1.1 Hello简介
P2P(Program to Process),指从程序向进程的转变过程。以hello.c文件为例,首先运行C预处理器(cpp),将C的源程序hello.c翻译成一个ASCII码的中间文件hello.i,接下来,驱动程序运行C编译器(cc1),将hello.i翻译成一个汇编语言文件hello.s,然后,驱动程序运行汇编器(as),它将hello.s翻译成一个可重定位目标文件hello.o,最后,它运行链接器程序ld,将hello.o和一些必要的系统目标文件组合起来,创建一个可执行目标文件hello。
O2O过程中,Shell为 hello的代码、数据、.bss和栈区域创建新的区域结构,然后映射共享区域,设置程序计数器,映射虚拟内存,然后加载物理内存。完成后进入main函数,执行目标代码,CPU为运行的hello分配时间片执行逻辑控制流。当程序结束,shell的父进程负责回收hello进程,内核删除相关数据结构。
1.2 环境与工具
Windows 10 Home;VMware Workstation 17.0.1;Ubuntu 64位;Gcc;Gdb;Codeblocks
1.3 中间结果
hello.c 源程序
hello.i 预处理产生的文件
hello.s 编译后产生的汇编语言文件
hello.o 汇编后产生的可重定位目标文件
hello 链接后的可执行文件
helloo.elf hello.o的ELF格式
helloo.txt hello.o的反汇编代码
hello.elf hello的ELF格式
hello.txt hello的反汇编代码
1.4 本章小结
本章简要介绍了hello的P2P、O2O过程,列出了用于实验的硬件软件环境及相关调试工具,最后列出了过程中的中间文件。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:预处理是指对源代码编译之前对其进行错误处理。预处理主要分为三部分:宏定义(#define)、文件包含(#include)、错误指令(#error)。预处理器(cpp)根据以字符#开头的命令修改原始的C程序,读取头文件的内容,并将其字节插入程序文本中,最终得到另一个以.i为文件拓展名的C程序。
作用:根据源代码中的预处理指令对代码进行修改,包括将系统头文件包的头文件源码插入到程序文本中、宏和常量标识符全部用相应的代码和数值替换等,最终得到.i文件
2.2在Ubuntu下预处理的命令
预处理命令:gcc -E -o hello.i hello.c
图1.预处理命令
如图,输入预处理命令后,文件中出现了hello.i文件。
2.3 Hello的预处理结果解析
打开hello.i文件查看如下:
图2.hello.i文件
可以发现,在预编译文件的前面,所有头文件中的函数被写入预编译文件,展开宏,同时去掉了重复的部分。最后部分,main函数和hello.c文件中的形式没有太大区别:
图3.hello.i文件源程序部分
2.4 本章小结
本章简要介绍了预处理的概念和作用以及Ubuntu下预处理的命令,同时对预处理结果hello.i进行了分析,发现hello.i文件是对源程序的补充和替换,是在其基础上进行修改的程序。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:编译是指利用C编译器(cc1)将.i文件翻译成含汇编语言的.s文件。
作用:编译后生成的.s的文件为汇编语言程序,有助于计算机识别理解。主要方式为:1.进行语法分析,用语法分析器对编译后的程序输入的符号进行检查。2.编译器可以产生一种优化的代码,有助于提高程序性能。
3.2 在Ubuntu下编译的命令
编译的指令:gcc -S hello.i -o hello.s
编译结果如图:
图4.编译命令
可以看见产生了一个hello.s文件。
3.3 Hello的编译结果解析
hello.s文件如下图所示:
图5.hello.s文件
3.3.1 宏
.file 声明源文件出自“hello.c”
.text 代码节
.section.rodata 只读数据段
.align 对指令和数据存放地址的对齐方式,此处为8字节对齐
.string 声明一个字符串变量
.global 声明全局变量
.type 声明符号的类型
3.3.2 字符串
只读数据段中有两个字符串,如图所示:
图6.两个字符串
图7.字符串的使用
可以发现,分别将rax设置为两个字符串的起始位置。
3.3.3 变量
代码中使用的变量有局部变量i和参数argc,其中argc是main函数的第一个参数,被存放在寄存器%edi中,由可知,寄存器%edi的地址被压入栈中。
而main函数中声明了局部变量i,根据可知,其被存放在栈上-4(%rbp)的位置。
3.3.4 全局函数
hello.c声明了一个全局函数main,通过汇编代码.global main可知。
3.3.5 赋值操作
hello.c的赋值操作为for循环中的i=0,在汇编代码上用mov指令实现,如:
。因int类型变量为四字节双字,故后缀为l。
3.3.6 算术操作
hello.c中算术操作为for循环后的i++,在汇编代码上用add指令实现,如:
。因int类型变量为四字节双字,故后缀为1。
3.3.7 关系操作
hello.c中有两个关系操作:
- 条件判断语句if(argc!=4),在汇编代码中为:
此处使用cmp比较参数argc和立即数4的大小,并设置了条件码。由条件可知,若不相等则执行该指令后的语句,否则跳转至.L2。
- for循环每次循环结束时判断一次i<8,在汇编代码中为:
与1类似,通过条件码选择跳转至何处。
3.3.8 控制转移指令
通过设置条件码来进行控制转移,该程序中有2个控制转移:
1.
判断argc是否为4,如果不为4则执行if语句,否则执行其他语句。在汇编中,若条件码为1,则跳转至.L2,否则执行cmpl后的指令。
2.
同1、2类似,在for循环每次结束时判断i<8。在汇编中,通过条件码判断是否需要跳转至.L4。
3.3.9 函数操作
1.main函数
该函数参数为int argc和char *argv[]。通过使用call内部指令调用语句进行函数调用,并将要调用的函数地址数据写入栈中。main函数里调用了exit、printf、sleep函数。包含局部变量i。
2.printf函数
该函数参数为argv[1]和argv[2]。
3.exit函数
如图:
将%edi设置为1,再用call调用函数。
4.atoi函数
如图:
将参数argv[3]放入寄存器%rdi中,用call调用函数。
5.sleep函数
如图:
edi存放sleep的参数,使用call调用。
6.getchar函数
无参数传递,直接使用call调用。
3.3.10 类型转换
atoi函数将字符串转换为sleep函数需要的整型参数。
3.4 本章小结
本章主要阐述了编译器是如何处理C语言的各个数据类型以及各类操作的,基本都是先给出原理然后结合hello.c,C程序到hello.s汇编代码之间的映射关系作出合理解释。
编译器将.i的拓展程序编译为.s的汇编代码。经过编译之后,我们的hello自C语言解构为更加低级的汇编语言。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:汇编器(as)将.s文件翻译成二进制机器语言指令,把这些指令打包成可重定位目标程序的格式,并将结果保存到目标文件.o中。.o文件是一个二进制文件。
作用:在汇编过程中,文件格式将由面向阅读友好的文本文件转化为机器可执行的二进制文件,并且将文本文件中的常量转化为对应的二进制补码,同时,汇编过程也将生成可重定位目标文件的结构信息,Linux系统使用可执行可链接格式对目标文件进行组织。
4.2 在Ubuntu下汇编的命令
汇编命令为:gcc -c -o hello.o hello.s
图8.汇编命令
4.3 可重定位目标elf格式
命令如下图所示:
图9.生成hello.o的elf文件
1.ELF头:ELF头描述生成该文件的系统的字的大小和字节顺序、帮助链接器语法分析和解释目标文件的信息。如下图所示:
图10.ELF头
2.节头:记录各节名称、类型、地址、偏移量、大小等等信息。如下图所示:
图11.节头
3.重定位节:一个.text节中位置的列表,包含.text节中需要进行重定位的信息,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。另一方面,调用本地函数的指令则不需要修改。如下图所示:
图12.重定位节
4.符号表:一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。每个可重定位目标文件在.symtab中都有一张符号表。
图13.符号表
4.4 Hello.o的结果解析
生成反汇编文件指令:objdump -d -r hello.o>helloo.txt如下图所示:
图14.生成hello.o反汇编文件
helloo.txt文件如下所示:
图15.helloo.txt文件
hello.o反汇编代码和汇编代码hello.s差不多,所用指令都相同,不同的是:
1.分支转移:反汇编的跳转指令用的不是段名称比如.L3,二是用的确定的地址,因为,因为段名称只是在汇编语言中便于编写的助记符,所以在汇编成机器语言之后显然不存在,而是确定的地址。
2.函数调用:在.s文件中,函数调用之后直接跟着函数名称,而在反汇编程 序中,call的目标地址是当前下一条指令。这是因为 hello.c 中调用的函数 都是共享库中的函数,最终需要通过动态链接器才能确定函数的运行时执 行地址,在汇编成为机器语言的时候,对于这些不确定地址的函数调用,将其call指令后的相对地址设置为全0(目标地址正是下一条指令),然后在.rela.text 节中为其添加重定位条目,等待静态链接的进一步确定。
3.立即数:在反汇编中被自动转换为16进制。
4.反汇编中汇编指令被详细转换为机器代码。
4.5 本章小结
本章介绍了hello从hello.s到hello.o的汇编过程,通过查看hello.o的elf格式和使用objdump得到反汇编代码与hello.s进行比较的方式,间接了解到从汇编语言映射到机器语言汇编器需要实现的转换。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
概念:将程序中调用的库函数合并到hello.o程序中,结果得到一个可执行目标文件,可以被加载到内存中,由系统执行。
作用:链接使得分离编译成为可能,它将巨大的源文件分解成更小、更易于管理的模块,可以独立地修改或编译这些模块,当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用。
5.2 在Ubuntu下链接的命令
链接命令:ld -o hello -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/ctr1.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
如下图所示:
图16.链接
5.3 可执行目标文件hello的格式
生成hello的elf的命令:readelf -a hello>hello.elf
如下图所示:
图17.生成hello.elf
- elf头:
图18.hello.elf的elf头
hello的数据是2补码、小端序;文件类型是EXEC(可执行程序);节数为27。
- 节头:
图19.hello.elf的节头
hello中节头表的条目数多于hello.o中节头表的条目数。值得注意的是每一节都有了实际地址,而不是像在hello.o中那样地址值全为0。这说明了重定位工作已完成。同时,多出的节是为了能够实现动态链接,如.interp这一节包含动态链接器的路径名,动态链接器通过执行一系列重定位工作完成链接任务。
- 符号表:
图20.hello.elf的符号表
与hello.s相比,hello的elf符号表多出了很多内容,但是符号表的列没有变化,这说明在链接工作完成后,重定向文件与系统文件链接,在执行文件中加入了系统函数的机器语言符号。
5.4 hello的虚拟地址空间
图21.edb加载hello(1)
图22.edb加载hello(2)
在edb的Data Dump栏中,可以看出程序的虚拟地址空间为(0X00401000)到(0X00401ff0)。
5.5 链接的重定位过程分析
命令:objdump -d -r hello > hello.txt
打开文件观察:
图23.hello.txt(1)
图24.hello.txt(2)
区别:相比hello.o的反汇编中,hello的反汇编文件多出了很多节,以及很多系统自带的函数。比如,在上图中的_init是程序初始化的代码;.plt是动态链接的过程链接表。
重定位过程:重定位由两步组成:1.重定位节和符号定义。2.重定位节中的符号引用。
5.6 hello的执行流程
图25.hello的执行流程
5.7 Hello的动态链接分析
动态共享库是致力于解决静态库缺陷的一个现代创新产物。共享库是一个目标模块,在运行或加载时,可以加载到任意的内存地址,并和一个程序链接起来,这个过程就是动态链接。
把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。
.plt:PLT是一个数组,其中每个条目是16字节代码。PLT[0]是一个特殊条目,它跳转到动态链接器中。每个被可执行程序调用的库函数都有它自己的PLT条目。每个条目都负责调用一个具体的函数。
.got:GOT是一个数组,其中每个条目是8字节地址。和PLT联合使用时,GOT[O]和GOT[1]包含动态链接器在解析函数地址时会使用的信息。GOT[2]是动态链接器在1d-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数,其地址需要在运行时被解析。每个条目都有一个相匹配的PLT条目。
hello在动态连接器加载前后的重定位是不一样的,在加载之后才进行重定位。
图26.hello的动态链接分析
5.8 本章小结
在本章中主要介绍了链接的概念与作用、hello的ELF格式,分析了hello的虚拟地址空间、重定位过程、执行流程、动态链接过程。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
概念:进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
作用:在运行一个进程时,我们的这个程序好像是系统当中唯一一个运行的程序,进程的作用就是提供给程序两个关键的抽象。一分别是独立的逻辑控制流和私有的地址空间。
6.2 简述壳Shell-bash的作用与处理流程
Shell的作用:Shell是一个用C语言编写的程序,他是用户使用Linux的桥梁。Shell是指一种应用程序,Shell应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。
处理流程:
1. 从终端读入输入的命令。
2. 将输入字符串切分获得所有的参数。
3. 如果是内置命令则立即执行。
4. 否则调用相应的程序为其分配子进程并运行。
5. shell应该接受键盘输入信号,并对这些信号进行相应处理。
6.3 Hello的fork进程创建过程
在程序运行时,Shell就会创建一个新的进程,并且新创建的进程更新上下文,在这个新建进程的上下文中便可以运行这个可执行目标文件。
fork()函数拥有一个int型的返回值。子进程中fork返回0,在父进程中fork返回子进程的Pid。新创建的进程与父进程几乎相同但有细微的差别。子进程得到与父进程虚拟地址空间相同的一份副本(代码、数据段、堆、共享库以及用户栈),并且子进程拥有与父进程不同的Pid。
6.4 Hello的execve过程
当fork之后,子进程调用execve函数(传入命令行参数)在当前进程的上下文中加载并运行一个新程序即hello程序,execve调用驻留在内存中的被称为启动加载器的操作系统代码来执行hello程序,加载器删除子进程现有的虚拟内存段,并创建一组新的代码、数据、堆和栈段。新的栈和堆段被初始化为零,通过将虚拟地址空间中的页映射到可执行文件的页大小的片,新的代码和数据段被初始化为可执行文件中的内容。最后加载器设置PC指向_start地址,_start最终调用hello中的main函数。除了一些头部信息,在加载过程中没有任何从磁盘到内存的数据复制。直到CPU引用一个被映射的虚拟页时才会进行复制,这时,操作系统利用它的页面调度机制自动将页面从磁盘传送到内存。
6.5 Hello的进程执行
6.5.1 逻辑控制流和时间片
进程的运行本质上是CPU不断从程序计数器 PC 指示的地址处取出指令并执行,值的序列叫做逻辑控制流。操作系统会对进程的运行进行调度,执行进程A->上下文切换->执行进程B->上下文切换->执行进程A->… 如此循环往复。 在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这种决策就叫做调度,是由内核中称为调度器的代码处理的。当内核选择一个新的进程运行,我们说内核调度了这个进程。在内核调度了一个新的进程运行了之后,它就抢占了当前进程,并使用上下文切换机制来将控制转移到新的进程。在一个程序被调运行开始到被另一个进程打断,中间的时间就是运行的时间片。
6.5.2用户模式和内核模式
Shell使得用户可以有机会修改内核,所以需要设置一些防护措施来保护内核,如限制指令的类型和可以作用的范围。
6.5.3上下文切换
如果系统调用因为等待某个事件发生而阻塞,那么内核可以让当前进程休眠,切换到另一个进程,上下文就是内核重新启动一个被抢占的进程所需要的状态,是一种比较高层次的异常控制流。
6.5.4调度
在对进程进行调度的过程,操作系统主要做了两件事:加载保存的寄存器,切换虚拟地址空间。
6.5.5用户态与核心态转换
为了能让处理器安全运行,需要限制应用程序可执行指令所能访问的地址范围。因此划分了用户态与核心态。核心态可以说是拥有最高的访问权限,处理器以一个寄存器当做模式位来描述当前进程的特权。进程只有故障、中断或陷入系统调用时才会得到内核访问权限,其他情况下始终处于用户权限之中,保证了系统的安全性。
6.6 hello的异常与信号处理
按下ctrl + z之后,shell父进程收到SIGSTP信号,信号处理函数的逻辑是打印屏幕回显、将hello进程挂起,通过ps命令我们可以看出hello进程没有被回收,此时他的后台job号是1,调用fg 1将其调到前台,此时shell程序首先打印hello的命令行命令,hello继续运行打印剩下的8条info,之后输入字串,程序结束,同时进程被回收。
当按下ctrl + c之后,shell父进程收到SIGINT信号,信号处理函数的逻辑是结束hello,并回收hello进程。键入fg 1可以重新启动Hello进程。可以发现,乱按只是将屏幕的输入缓存到stdin,当getchar的时候读出一个’\n’结尾的字串(作为一次输入),其他字串会当做shell命令行输入。
在ctrl + z挂起hello进程后,若使用kill指令即可杀死进程,再次ps查看进程可以发现进程已被杀死,fg1指令无法令其继续运行。
6.7本章小结
本章首先简要介绍了进程的概念和作用、shell的作用和处理流程,然后了解了hello程序的进程创建、启动和执行过程,最后对hello的异常和运行结果中的各种输入进行了分析。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
物理地址:CPU通过地址总线的寻址,找到真实的物理内存对应地址。CPU对内存的访问是通过连接着CPU和北桥芯片的前端总线来完成的。在前端总线上传输的内存地址都是物理内存地址。
虚拟地址:程序访问存储器所使用的逻辑地址称为虚拟地址。虚拟地址经过地址翻译得到物理地址。
逻辑地址:程序代码经过编译后出现在汇编程序中地址。逻辑地址由选择符(在实模式下是描述符,在保护模式下是用来选择描述符的选择符)和偏移量(偏移部分)组成。
线性地址:线性地址是逻辑地址到物理地址变换之间的一步,在分段部件中逻辑地址是段中的偏移地址,加上基地址就是线性地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
在 Intel 平台下,逻辑地址是 selector:offset 这种形式,selector 是 CS 寄存器的值,offset 是 EIP 寄存器的值。如果用 selector 去 GDT里拿到 segment base address然后加上 offset,这就得到了 linear address。我们把这个过程称作段式内存管理。
一个逻辑地址由段标识符和段内偏移量组成。段标识符是一个16位长的字段。可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。
全局的段描述符,放在“全局段描述符表”中,一些局部的段描述符,放在“局部段描述符表”中。
给定一个完整的逻辑地址段选择符+段内偏移地址,看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,就得到了其基地址。Base + offset = 线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
线性地址即虚拟地址(VA)到物理地址(PA)之间的转换通过分页机制完成,而分页机制是对虚拟地址内存空间进行分页。
系统将虚拟页作为进行数据传输的单元。Linux下每个虚拟页大小为4KB。物理内存也被分割为物理页, MMU(内存管理单元)负责地址翻译,MMU使用页表将虚拟页到物理页的映射,即虚拟地址到物理地址的映射。
n位的虚拟地址包含两个部分:一个p位的虚拟页面偏移(VPO),一个n-p位的虚拟页号(VPN),MMU利用VPN选择适当的PTE,根据PTE,我们知道虚拟页的信息,如果虚拟页是已缓存的,那直接将页表条目的物理页号和虚拟地址的VPO串联起来就得到一个相应的物理地址。VPO和PPO是相同的。如果虚拟页是未缓存的,会触发一个缺页故障。调用一个缺页处理子程序将磁盘的虚拟页重新加载到内存中,然后再执行这个导致缺页的指令。
7.4 TLB与四级页表支持下的VA到PA的变换
Core i7采用四级页表的层次结构。CPU产生虚拟地址VA,VA传送给MMU,MMU使用VPN的高位作为TLBT和TLBI,向TLB中寻找匹配。如果命中,则得到物理地址PA。如果未命中,则MMU查询页表,CR3确定第一级页表的起始地址,VPN1确定在第一级页表中的偏移量,查询出PTE,以此类推,最终在第四级页表中找到PPN,与VPO组合成物理地址PA,添加到PLT。
7.5 三级Cache支持下的物理内存访问
图27.高速缓存存储器组织结构
获得物理地址之后,先取出组索引对应位,在L1中寻找对应组。如果存在,则比较标志位,相等后检查有效位是否为1.如果都满足则命中取出值传给CPU,否则按顺序对L2cache、L3cache、内存进行相同操作,直到出现命中。然后再一级一级向上传,如果有空闲块则将目标块放置到空闲块中,否则将缓存中的某个块驱逐,将目标块放到被驱逐块的位置。
7.6 hello进程fork时的内存映射
当fork函数被shell进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID,为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将这两个进程的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
7.7 hello进程execve时的内存映射
execve函数调用驻留在内核区域的启动加载器代码,在当前进程中加载并运 行包含在可执行目标文件hello中的程序,用 hello 程序有效地替代了当前程序。 加载并运行hello需要以下几个步骤:
删除已存在的用户区域,删除当前进程虚拟地址的用户部分中的已存在的区域结构。
映射私有区域,为新程序的代码、数据、bss和栈区域创建新的区域结构,所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为 hello 文件中的.text 和.data 区,bss 区域是请求二进制零的,映射到匿名 文件,其大小包含在 hello 中,栈和堆地址也是请求二进制零的,初始长 度为零。
映射共享区域,hello程序与共享对象libc.so链接,libc.so是动态链接到这个程序中的,然后再映射到用户虚拟地址空间中的共享区域内。
设置程序计数器(PC),execve做的最后一件事情就是设置当前进程上下文的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
缺页(page fault)指虚拟内存中的DRAM缓存不命中。当CPU请求某个虚拟地址的数据而它恰好不在主存而在磁盘中时(通过检查有效位),就会引发缺页故障,调用内核中的缺页异常处理程序,它会选择一个牺牲页,用所请求的页替换该牺牲页。如果该主存中的牺牲页还被修改过,在替换之前内核还需要将其复制回磁盘。牺牲页的选择因系统而异,常见的替换算法有LRU(Least Recently Used)算法,它选择一个最近最久未使用的页面作为牺牲页。如果一个程序拥有良好的局部性,虚拟内存能够以较好的效率完成任务(典型的页面大小为4KB,这足够抵消从磁盘交换页面进入内存的时间)。但是,如果一个程序的工作集超出了物理内存的大小,就很可能引发抖动(thrashing)现象,这会导致页面从内存和磁盘之间频繁地换入换出,带来极大的时间开销,此时我们就应该设法减小工作集大小来提高程序速度。
7.9本章小结
本章主要介绍了hello的存储器地址空间、intel的段式管理、hello的页式管理,以intel Core7在指定环境下介绍了虚拟地址VA到物理地址PA的转换、物理内存访问,分析了hello进程fork时的内存映射,hello进程execve时的内存映射、缺页故障与缺页中断处理和动态存储分配管理。
(第7章 2分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
首先,由程序员将.c代码写入,然后依次经过以下步骤:
1.预处理(cpp):对.c文件进行预处理,生成一个经过修改的.i文件。
2.编译(cc1):将.i文件翻译成为一个含汇编语言的.s文件。
3.汇编(as):将.s文件翻译成可重定位目标文件.o文件。
4.链接(ld):将.o文件和可重定位目标文件与动态链接库链接起来,生成可执行目标文件
5.运行:在shell中输入命令,运行程序
6.创建进程:终端判断输入指令不是shell内置指令,调用fork函数创建一个新的子进程。
7.加载程序:shell调用execve函数,启动加载器,映射虚拟内存,进入程序入口后程序开始载入物理内存,然后进入main函数。
8.执行指令:CPU为进程分配时间片,在一个时间片中, hello享有CPU资源,顺序执行自己的控制逻辑流。
9.访问内存:MMU将程序中使用的虚拟内存地址通过页表映射成物理地址。
10.动态申请内存:printf会调用malloc向动态内存分配器申请堆中的内存。
11.信号管理:当程序在运行的时候我们输入Ctrl+c,内核会发送SIGINT信号给进程并终止前台作业。当输入Ctrl+z时,内核会发送SIGTSTP 信号给进程,并将前台作业停止挂起。
12.终止:当子进程执行完成时,内核安排父进程回收子进程,将子进程的退出状态传递给父进程。内核删除为这个进程创建的所有数据结构。
通过本次实验,我深切感受到计算机系统的精细和强大,这是人类构建的一整套缜密的,独立于自然的语言系统,是人类智慧的结晶,也是计算思维的具象体现。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
hello.c 源程序
hello.i 预处理产生的文件,用于分析cpp行为
hello.s 编译后产生的汇编语言文件,用于分析cc1行为
hello.o 汇编后产生的可重定位目标文件,分析as行为
hello 链接后的可执行文件,分析链接器行为
helloo.elf hello.o的ELF格式,分析as和链接器行为
helloo.txt hello.o的反汇编代码,分析hello.o
hello.elf hello的ELF格式,分析重定位过程
hello.txt hello的反汇编代码,分析重定位过程
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
- Bryant R E , O'Hallaron D R . Computer Systems:A Programmer's Perspective: International Edition[J]. Pearson Schweiz Ag, 2011, 49(2):7--10.
(参考文献0分,缺失 -1分)