以C语言程序hello.c为例,一步步分析其预处理,编译,汇编,链接形成可执行文件的过程,在加载执行hello可执行程序的过程中体会进程管理、存储管理。和IO管理。
关键词:程序人生;预处理;编译;汇编;链接;进程管理;存储管理;IO管理;
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
2.2在Ubuntu下预处理的命令.......................................................................... - 5 -
3.2 在Ubuntu下编译的命令............................................................................. - 6 -
4.2 在Ubuntu下汇编的命令............................................................................. - 7 -
5.2 在Ubuntu下链接的命令............................................................................. - 8 -
5.3 可执行目标文件hello的格式.................................................................... - 8 -
6.2 简述壳Shell-bash的作用与处理流程..................................................... - 10 -
6.3 Hello的fork进程创建过程..................................................................... - 10 -
6.6 hello的异常与信号处理............................................................................ - 10 -
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 -
8.1 Linux的IO设备管理方法.......................................................................... - 13 -
8.2 简述Unix IO接口及其函数....................................................................... - 13 -
第1章 概述
1.1 Hello简介
P2P过程:hello.c的c程序文本,生成可执行文件需要四步,首先要经过预处理,然后编译生成汇编代码,汇编生成目标文件,目标文件链接生成可执行文件hello,由shell新建一个进程来执行。
020过程:shell执行可执行文件时,为其映射出虚拟内存,在开始运行进程的时候分配并载入物理内存,开始执行hello的程序,通过IO机制进行输入输出交互,然后进程结束,shell回收内存空间。
1.2 环境与工具
硬件环境:
X64CPU,2.2GHz。
软件环境:
Windows10 64位 VMware16 Ubuntu 64-2019
开发工具:
objdump gdb
1.3 中间结果
hello.c 源程序
hello.i 预处理后的源程序
hello.s 汇编代码
hello.o 目标程序
hello.od hello.o的反汇编文件
hello 可执行文件
hello.r hello的ELF目标文件
1.4 本章小结
熟悉总体过程和所用工具
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
程序设计领域中,预处理一般是指在程序源代码被翻译为目标代码的过程中,在编译之前,由预处理器对程序源代码文本进行处理,得到的结果再由编译器核心进一步编译。
处理:头文件/文件包含,宏定义,条件编译
例如在hello.c文件中就要对其包含的头文件进行预处理
2.2在Ubuntu下预处理的命令
2.3 Hello的预处理结果解析
生成hello.i文件,头文件内容加入:
2.4 本章小结
给出预处理的概念和作用,展示了预处理的过程和结果。预处理后方便之后的编译。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
编译是将经过预处理之后的程序转换成特定汇编代码的过程。
作用:编译器将高级语言转换成更接近机器语言的汇编语言,使得将高级语言转换成计算机可执行的二进制文件这个操作更加方便。
3.2 在Ubuntu下编译的命令
3.3 Hello的编译结果解析
hello编译的结果(未全列出):
3.3.1数据
hello.c中有一个初始化过的全局变量:
hello.s中:
表示其是全局变量
将sleepsecs定义为对象类型
大小为4字节
定义为长整型数2,划分在.rodata段
hello.c中还有一个局部变量:
在文件中没有找到它的声明,可以看到.L3对应hello.c中的循环
将-4(%rbp)和9比较,小于等于时跳转,这个-4(%rbp)存的应该就是i,是在栈中的。
3.3.2赋值
全局变量赋初值在.data节,局部变量赋值后放在寄存器或者栈中。
3.3.3关系操作
----->
----->
cmp是比较指令,cmp的功能相当于减法指令。它不保存结果,只是影响相应的标志位。其他的指令通过识别这些被影响的标志位来得知比较结果。然后JX指令根据标志位跳转或继续执行。
3.3.4算术操作
hello.c再循环中有一个i++的操作,在hello.s中表现为:
hello.s中还有减法subq以及leaq等。
3.3.5数组/指针/结构操作
取数组的第i位一般是按照取数组头指针加上偏移量来操作的,结构体也是起始地址加上偏移量,访问指针内容的话,寄存器外加括号代表对寄存器存储的地址内容的操作。
3.3.6函数操作
printf() :
puts() :
exit(1); :
sleep(sleepsecs) :
return 0; :
一个函数的返回值一般存在%eax中,如果要设定返回值的话,那就先将返回值传入%eax,然后再用ret语句返回。如果向函数传入参数的话,根据参数个数依次使用rdi,rsi,rdx,rcx,r8,r9传递参数。然后执行一个call跳转语句,跳转到被调用的函数的起始位置。
3.4 本章小结
分析了一个hello程序是如何被编译器编译成一个汇编程序的过程,不同的c语句被翻译成汇编语句之后有各自的表现形式。编译为后续的汇编和链接生成可执行文件做了准备工作。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
把汇编语言翻译成机器语言的过程称为汇编。
作用:将汇编指令转换成机器可以直接读懂的指令。
4.2 在Ubuntu下汇编的命令
生成:hello.o文件
4.3 可重定位目标elf格式
ELF Header:总的描述ELF文件各个部分的信息
▪ 字大小、字节序:16字节
▪ 文件类型(.o,exec,.so)、机器类型、
节头表的位置、条目大小、数量等
Section Headers: 页面大小,虚拟地址内存段(节),段大小
.rel.text 节(可重定位代码)
▪ .text 节的可重定位信息
▪ 在可执行文件中需要修改的指令地址
▪ 需修改的指令
.rela.eh_frame:eh_frame节的重定位信息。
.symtab 节(符号表)
▪ 函数和全局/静态变量名
▪ 节名称和位置
4.4 Hello.o的结果解析
机器语言是直接用二进制代码指令表达的计算机语言,指令是用0和1组成的一串代码,它们有一定的位数,并分成若干段,各段的编码表示不同的含义。
每一条汇编语句被映射为若干二进制指令码,将机器语言的每一条指令符号化:指令码代之以记忆符号,地址码代之以符号地址。在汇编语言中,操作数用十进制表示,而在机器语言中,用十六进制表示,如hello.o中:
机器语言中命令
在汇编语言hello.s中对应为
在汇编语言中,分支跳转、函数调用,使用标签定位位置,而在机器语言中使用地址+偏移量计算要跳转到的实际地址。
如hello.o中:
在hello.s中对应为:
4.5 本章小结
展示了hello.s从汇编指令被转换成hello.o机器指令,并且通过readelf查看了hello.o的ELF文件格式、反汇编的方式查看了hello.o的内容,分析比较其与hello.s之间的差别。了解了汇编指令到机器指令的联系和差异。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
链接是链接器通过符号解析和重定位将多个文件拼接合并成一个可执行文件的过程,链接行为可以在编译/汇编/加载/运行时执行。
作用:由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题,这些问题,都需要经链接程序的处理方能得以解决。链接也使分离编译成为可能。
5.2 在Ubuntu下链接的命令
5.3 可执行目标文件hello的格式
输出到文件中。
显示有27个段。
Section Headers:
Size表示的是对应的节的大小,Address表示的是被载入到虚拟内存地址,Offset表示的是这个节在程序里面的地址偏移量。
5.4 hello的虚拟地址空间
使用edb加载hello可以看到程序从0x400000开始加载:
5.5 链接的重定位过程分析
hello中包含一些外部文件的宏定义、变量、库函数和操作系统的启动代码等,且hello.o文件的.text节从0开始,这是因为编译器采用的是与位置无关的编译方式,而可执行文件.text节并非从0开始。
链接的过程分为两步:符号解析和重定位。重定位又分为两步:
·重定位节和符号定义。在这一步中,链接器将所有相同类型的节合并为同一类型的新的聚合节。例如,来自所有输入模块的.data节被全部合并成一个节,这个节成为输出的可执行目标文件的.data节。然后,链接器将运行时内存地址赋给新的聚合节,赋给输人模块定义的每个节,以及赋给输人模块定义的每个符号。当这一步完成时,程序中的每条指令和全局变量都有唯一的运行时内存地址了。
·重定位节中的符号引用。在这一步中,链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。要执行这一步,链接器依赖于可重定位目标模块中称为重定位条目(relocation entry)的数据结构。
下面以一个例子说明如何计算重定位:
根据这个重定位条目,需要计算将这四个字节0替换为多少:
在hello中可以看到.rodata的地址为0x40200,
根据重定位信息我们计算出来的结果应该是.rodata地址加0x4减下一条PC,也就是0x402004-0x401125,结果是0x0edf,根据小端序,我们填充的应该是df 0e 00 00:
5.6 hello的执行流程
这个我是用gdb。通过hello反汇编的hello.d文件找出每个函数的名称,用gdb打开hello,在每函数名称前设断点,运行,记录执行流程(不传参的)。
传参的:
5.7 Hello的动态链接分析
在调用共享库的时候,编译器没有办法预测这个函数的运行地址,因为定义它的共享模块在运行时可以加载到任意位置。正常的方法是为该引用生成一条重定位记录,然后动态链接器在程序加载的时候再解析它。GNU编译系统使用延迟绑定(lazybinding),将过程地址的绑定推迟到第一次调用该过程时。
延迟绑定通过GOT和PLT实现。GOT是数据段的一部分,而PLT是代码段的一部分。PLT是一个数组,其中的每个条目都是16字节的代码。PLT[0]是一个特殊条目,它跳转到动态链接表。每个被可执行程序调用的库函数都有它自己的PLT条目。每个条目都负责调用一个具体的函数。GOT是一个数组,其中每个条目是8字节地址。和PLT联合使用时,GOT[O]和GOT[1]包含动态链接器在解析函数地址时会使用的信息。GOT[2]是动态链接器在1d-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数,其地址需要在运行时被解析。每个条目都有一个相匹配的PLT条目。
可以看到GOT表位置在调用dll_init之前0x404008后面的16个字节均为0,但是在调用dll_init后他们的字节改变了。其中GOT[0],GOT[1]是动态链接器在1d-linux.so模块的入口点。其中的每个条目对应于一个被调用的函数,改变的GOT表如下图:
当程序调用一个动态链接库内定义的函数时,call指令并没有直接跳转到对应的函数中,取而代之的是控制流会跳转到该函数对应的PLT表,然后通过PLT表将当前将要调用的函数序号压入栈中,下一步调用动态链接器,接下来动态链接器会根据栈中信息执行重定位,将真实的printf的运行地址写入GOT表,取代GOT原来用来跳转到PLT的地址,变成了真正的函数地址。
5.8 本章小结
本章主要介绍了链接的知识,链接可分为符号定义和重定位,了解了可执行文件的elf格式,分析了Hello的虚拟地址空间、重定位过程、执行流程、动态链接过程。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
概念:
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
作用:
由于程序是静态的,我们看到的程序是存储在存储介质上的,它无法反映出程序执行过程中的动态特性,而且程序在执行过程中是不断申请资源,程序作为共享资源的基本单位是不合适的,所以需要引入一个概念,它能描述程序的执行过程而且可以作为共享资源的基本单位,这个概念就是进程。进程解决了系统资源调度等一系列问题。
6.2 简述壳Shell-bash的作用与处理流程
Linux系统中,Shell是一个交互型应用级程序,代表用户运行其他程序(是命令行解释器,以用户态方式运行的终端进程)。其基本功能是解释并运行用户的指令,重复如下处理过程:
(1)终端进程读取用户由键盘输入的命令行。
(2)分析命令行字符串,获取命令行参数,并构造传递给execve的argv向量
(3)检查第一个(首个、第0个)命令行参数是否是一个内置的shell命令
(3)如果不是内部命令,调用fork( )创建新进程/子进程
(4)在子进程中,用步骤2获取的参数,调用execve( )执行指定程序。
(5)如果用户没要求后台运行(命令末尾没有&号)否则shell使用waitpid(或wait...)等待作业终止后返回。
(6)如果用户要求后台运行(如果命令末尾有&号),则shell返回;
6.3 Hello的fork进程创建过程
shell调用fork函数,创建一个子进程,为运行hello做准备。当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
6.4 Hello的execve过程
execve函数在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。需要以下几个步骤:
1.删除已存在的用户区域。删除当前进程虚拟地址的用户部分中的已存在的区域结构。
2.映射私有区域。为hello的代码、数据、bss 和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data区。bss区域是请求二进制零的,映射到匿名文件,其大小包含在hello中。栈和堆区域也是请求二进制零的,初始长度为零。
3.映射共享区域。如果 hello程序与共享对象(或目标)链接,比如标准C库libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
4.设置程序计数器(PC)。execve做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。下一次调度这个进程时,它将从这个入口点开始执行。Linux 将根据需要换入代码和数据页面。
6.5 Hello的进程执行
execve做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点,hello运行在用户模式,当程序收到一个信号时,进入内核模式,运行信号处理程序,之后再返回用户模式。
在hello运行的过程中,cpu不断切换上下文,使hello程序运行过程被切分成时间片,与其他进程交替占用cpu,实现进程的调度。
6.6 hello的异常与信号处理
ctrl-Z:
向进程发送了一个SIGTSTP信号,进程暂时挂起:
ps:
ctrl-C:
向进程发送了一个SIGINT信号,进程直接结束
kill杀死进程:
不停乱按,包括回车:
输入的内容会被留在缓冲区中,当hello执行结束,返回shell中,shell会从缓冲区读取并尝试解析这些内容。
jobs: 查看后台运行的进程
fg: 恢复一个后台进程
pstree: 用进程树的方法把各个进程用树状图的方式连接起来
6.7本章小结
本章介绍了进程的概念和作用,以hello的运行为例解释了什么是shell,以及shell的运行流程。展示了shell是如何fork新建子进程、execve如何加载进程,解释了内核如何接收各种信号和执行信号处理的过程。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:又称相对地址,是程序运行由CPU产生的与段相关的偏移地址部分。他是描述一个程序运行段的地址。
物理地址:程序运行时加载到内存地址寄存器中的地址,内存单元的真正地址。他是在前端总线上传输的而且是唯一的。例如在hello程序中,这个地址就表示了这个程序运行时的一条确切的指令在内存地址上的具体哪一块进行执行。
线性地址:等同于虚拟地址,是经过段机制转化之后用于描述程序分页信息的地址。是对程序运行区块的一个抽象映射。
7.2 Intel逻辑地址到线性地址的变换-段式管理
段式内存管理方式就是将逻辑地址转换成线性地址,其地址的基本组成方式是段号+段内偏移地址。先将逻辑地址分成段选择符+段描述符的判别符(TI)+地址偏移量的形式,然后先判断TI字段,看看这个段描述符究竟是局部段描述符(ldt)还是全局段描述符(gdt),然后再将其组合成段描述符+地址偏移量的形式,这样就转换成线性地址了。
7.3 Hello的线性地址到物理地址的变换-页式管理
将线性地址分为VPN(虚拟页号)+VPO(虚拟页偏移)的形式,然后再将VPN拆分成TLBT(TLB标记)+TLBI(TLB索引)然后去TLB缓存里找所对应的PPN(物理页号)如果发生缺页情况则直接查找对应的PPN,找到PPN之后,将其与VPO组合变为PPN+VPO就是生成的物理地址了。
7.4 TLB与四级页表支持下的VA到PA的变换
i7有四级页表,每个页表512项,索引占9位。虚拟地址48位,首先要在TLB里寻找是否有缓存的页表条目,如果有那就直接拿到页表条目里对应的PPN和PPO组成物理地址。如果没有,那么根据四级页表查找相应的PTE,如果没有的话MMU就会触发缺页异常,执行缺页异常的处理程序。从页表中选取一个牺牲页,如果此页有修改,那么将其写回到磁盘,然后将缺的页读到主存里,更新页表,将组合好的物理地址给到处理器。缺页异常处理程序结束后返回到引起缺页的指令继续执行。
7.5 三级Cache支持下的物理内存访问
物理地址分成CT(标记)+CI(索引)+CO(偏移量),然后在一级cache内部找,根据组索引找对应的组,找到组然后根据标记位找到对应的缓存块,根据偏移量找到对应的字节。如果未能寻找到标记位为有效的字节(miss)的话就去二级和三级cache中寻找对应的字节,找到之后返回结果。
7.6 hello进程fork时的内存映射
shell调用fork函数,创建一个子进程,为运行hello做准备。当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
7.7 hello进程execve时的内存映射
execve函数在当前进程中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。需要以下几个步骤:
1.删除已存在的用户区域。删除当前进程虚拟地址的用户部分中的已存在的区域结构。
2.映射私有区域。为hello的代码、数据、bss 和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello文件中的.text和.data区。bss区域是请求二进制零的,映射到匿名文件,其大小包含在hello中。栈和堆区域也是请求二进制零的,初始长度为零。
3.映射共享区域。如果 hello程序与共享对象(或目标)链接,比如标准C库libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
4.设置程序计数器(PC)。execve做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。下一次调度这个进程时,它将从这个入口点开始执行。Linux 将根据需要换入代码和数据页面。
7.8 缺页故障与缺页中断处理
情况1:段错误:首先,先判断这个缺页的虚拟地址是否合法,那么遍历所有的合法区域结构,如果这个虚拟地址对所有的区域结构都无法匹配,那么就返回一个段错误(segment fault)
情况2:非法访问:接着查看这个地址的权限,判断一下进程是否有读写改这个地址的权限。
情况3:如果不是上面两种情况那就是正常缺页,那就选择一个页面牺牲然后换入新的页面并更新到页表。
7.9动态存储分配管理
C程序运行时在需要额外的存储空间时,一般会使用动态存储器分配器,它维护着一个进程的虚拟存储器区域,称为堆区。堆区是一个请求二进制零的区域,内核为每个进程维护一个变量 brk ,指向堆的顶部。分配器将堆视为一组不同大小的块,每个块为虚拟存储器的一个连续组块,是已分配的或空闲的。
介绍动态内存分配器三种形式:
- 隐式空闲链表
一个块是由一个字的头部、有效载荷,以及填充(可选)组成。头部编码了这个块的大小(包括头部、有效载荷和填充),以及这个块是已分配的还是空闲的。块的头最后一位指明这个块是已分配的还是空闲的。头部后面是应用malloc时请求的有效载荷。有效载荷后面是一片不使用的填充块,其大小可以是任意的。块的格式如图所示,空闲块通过头部块的大小字段隐含的连接着,所以我们称这种结构就隐式空闲链表。
隐式空闲链表找到一个空闲块使用的方法有:首次适配,下一次适配,最佳适配。
合并空闲块:
合并的情况一共分为四种:前空后不空,前不空后空,前后都空,前后都不空。对于四种情况分别进行空闲块合并,我们只需要通过改变头部的信息就能完成合并空闲块,采用边界标记的方式:
- 显示空闲链表
显式空闲链表只维护空闲块链表, 而不是所有块。“下一个” 空闲块可以在任何地方,因此需要存储前/后指针,而不仅仅是大小(size),还需要边界标记,用于块合并。只需跟踪空闲块,因此可以使用有效载荷区域。
- 分离的空闲链表
每个尺寸类 (size class)中的块,构成一个空闲链表,采用分离适配,分配器维护一个空闲链表数组,每个空闲链表和一个大小类关联,链表是显式或隐式的。
当分配器需要一个大小为n的块时:
▪ 搜索相应的空闲链表,其大小要满足m > n
▪ 如果找到了合适的块:
▪ 拆分块,并将剩余部分插入到适当的可选列表中
▪ 如果找不到合适的块, 就搜索下一个更大的大小类的空闲链表
▪ 直到找到为止。
如果空闲链表中没有合适的块:
▪ 向操作系统请求额外的堆内存 (使用sbrk())
▪ 从这个新的堆内存中分配出 n 字节
▪ 将剩余部分放置在适当的大小类中
7.10本章小结
讲述了虚拟地址、物理地址、线性地址、逻辑地址的概念,还有进程fork和execve时的内存映射的内容。描述了系统如何应对那些缺页异常,最后描述了malloc的内存分配管理机制(C语言为例)。通过高速缓存、虚拟内存、动态内存分配,可以实现快速、高校、利用率高的储存空间管理,虚拟内存作为内存管理工具和内存保护工具都是非常有效的,非常巧妙和重要。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
所有的I/O设备都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行。
设备管理:unix io接口
打开和关闭文件、读取和写入文件、改变当前文件的位置。
8.2 简述Unix IO接口及其函数
打开和关闭文件:open()and close()
读写文件:read() and write()
改变当前的文件位置 lseek()
8.3 printf的实现分析
上面是printf的代码,它调用了vsprintf函数和write函数。vsprintf函数可以看出,这个函数的作用是将所有的参数内容格式化之后存入buf,然后返回格式化数组的长度。write函数是将buf中的i个元素写到终端的函数。
https://www.cnblogs.com/pianist/p/3315801.html
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章介绍了linux的I/O设备管理机制,了解了Unix IO接口及相关函数,简单分析了printf和getchar函数的实现方法以及操作过程。
(第8章1分)
结论
C语言程序hello.c,首先经过预处理得到.i文件,然后经过编译得到.s文件,再汇编生成.o文件,链接形成可执行文件hello.out。之后shell_bash为其fork创建进程,execve加载,分配时间片,使程序可以在硬件系统上执行。TLB、4级页表、3级Cache,Pagefile等等存储管理的各种机制,使程序可以流畅运行。IO管理使程序可以与外部设备之间进行交互。
(结论0分,缺失 -1分,根据内容酌情加分)
附件
hello.c 源程序
hello.i hello.c预处理之后的文本
hello.s hello.i编译为汇编语言的文本
hello.o hello.s汇编之后的二进制文件
hello.od hello.s反汇编文件
hello 链接后的可执行文件
hello.d hello的反汇编问价
hello.r hello的elf表
(附件0分,缺失 -1分)