本文旨在分析hello.c在预处理、编译、汇编、链接等过程中得到的文件,回顾本学期计算机系统学到的知识,并通过实践加深理解,了解每一篇代码的运行过程,由表入里,由浅入深地走进程序运行的每一步。
关键词:计算机系统;From Program to Process;预处理; 编译;汇编;链接;IO;
目 录
第1章 概述
1.1 Hello简介
P2P:一开始,hello.c只是一个在磁盘上的程序,通过预处理器cpp被翻译为了hello.i,接着被ccl翻译成了hello.s,然后汇编器as将其翻译为了hello.o,经过链接器的链接,它就变成了可执行程序,此时就可以在shell中调用命令生成进程,执行该程序。这就是P2P(program to process)的过程。
O2O:shell通过fork生成进程后,通过execve把hello的内容加载到了子进程的地址空间内,CPU为其分配时间片后对该进程进行处理。进程结束后,shell中父进程回收hello进程,内核删除其相关的数据结构。这就是O2O(from 0 to 0)的过程。
1.2 环境与工具
1.2.1 硬件环境
Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz 2.59 GHz
机带 RAM 16.0 GB (15.8 GB 可用)
1.2.2 软件环境
Windows 10 64位/虚拟机Vmware 16.2.3 build-19376536;Ubuntu 20.04LTS
1.2.3 开发工具
Visual studio 2022 64位;Codeblocks 64位;gcc
1.3 中间结果
hello.c:C语言的源程序,是文本文件。
hello.i:预处理以后的源程序,也是文本文件。
hello.s:编译后得到的汇编文件,也是文本文件。
hello.o:汇编后产生的可重定位的目标文件,是二进制文件。
hello:链接产生的可执行的目标文件,是二进制文件。
1.4 本章小结
本章简述了hello.c程序的P2P与O2O过程,展示了硬软件环境,以及文件运行的中间结果,简要概括了hello.c到可执行目标文件hello的大致过程。
第2章 预处理
2.1 预处理的概念与作用
预处理概念:预处理也叫预编译,会根据该程序的以“#”开头的命令对程序进行修改,把引用的头文件代码进行展开,对定义的全局变量进行替换等等。将原来为了简单易读而写的程序处理为完全状态,变成完整的文本文件。
预处理作用:1.处理头文件,将头文件中的定义函数全部加入到程序文本中。2.将#define定义变量进行替换。3.处理条件编译指令,决定哪些代码要进行处理,过滤不必的代码。
2.2在Ubuntu下预处理的命令
在终端输入gcc hello.c -E -o hello.i,就得到了hello.i文件
图 1 由hello.c文件生成hello.i文件
2.3 Hello的预处理结果解析
图 2 hello.c文件
图 3 hello.i文件部分截图
这是hello.c经过预处理以后得到的hello.i文件,可以发现其行数增加到了3060行,宏全部都被展开,增加了极其多的内容。
2.4 本章小结
本章简述了预处理的概念与作用,通过hello.c文件进行预处理得到hello.i来展示预处理的过程,对预处理实际内容有了具体形象的体验。
第3章 编译
3.1 编译的概念与作用
编译的概念:编译指编译器将hello.i翻译成汇编语言文件hello.s的过程
编译的作用:把高级语言编写的源程序翻译成汇编语言写的目标程序,使机器能够识别,此时也会对程序进行测试,给出程序可能存在的语法错误,而且编译器会对代码本身进行一定优化,提升机器执行效率。
3.2 在Ubuntu下编译的命令
在终端中输入命令:gcc -S hello.i -o hello.s,即可通过编译器从hello.i生成hello.s。
图 4 由hello.i得到hello.s文件
3.3 Hello的编译结果解析
3.3.1数据
- 局部变量i(出现于.c文件第11行)
局部变量i在汇编语言中被定义在了栈-4(%rbp)上。
图 5 此处对应循环初始化的i=0
图 6 此处对应了循环中的“<8”和“i++”
- 字符串“用法: Hello 7203610107 黄托朴森 好几个3600秒!\n”和“Hello %s %s\n”。
图 7 字符串在汇编语言中储存于数据段上
图 8 printf使用的字符串
- 数组:char *argv[]
argv[]被放在-32(%rbp)上,在L4里被三次调用。
图 9 对应循环里三次调用的argv[]
- 整形argc
argc放在-20(%rbp)上,有一次调用判断。
图 10 判断argc!=4
3.3.2算数操作++
.c文件第17行i++,截图在上文图6
3.3.3关系操作与控制转移
.c文件第13行if,第17行有i<8,分别对应上文图10和图6。
3.3.4数组/指针/结构操作
.c文件第10行char *argv[],有三次调用,对应在上文图9
3.3.5函数操作
在hello.c中,使用了printf,exit,atoi,sleep,getchar
图 11 调用函数
3.3.6类型转换
atoi会将字符串转换为整形,如果失败会返回0.
3.4 本章小结
本章介绍了编译的概念与作用,然后以hello.i为例子生成hello.s,详细分析了编译结果中的详情,通过前后的文件内容对照来详细阐述编译的具体步骤与内容
第4章 汇编
4.1 汇编的概念与作用
汇编的概念:汇编指的是汇编器将汇编语言翻译成机器语言的过程,它将汇编指令文件打包成可重定位目标文件,以.o为文件类型,是一个二进制文件。
汇编的作用:将汇编语言文件变成可重定位目标文件。
4.2 在Ubuntu下汇编的命令
在终端输入gcc hello.s -c -o hello.o,就能从hello.s生成hello.o文件
图 12 从hello.s生成hello.o文件
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
4.3.1使用readelf -h hello.o查看ELF头
图 13 ELF头信息
ELF头包含信息:
Magic描述了hello.o文件的字的大小和字节顺序。还有一些辅助链接分析目标文件的信息,包括文件类型,版本,数据类型,本头的大小(this header),Section头的个数和大小等等。
4.3.2使用命令readelf -S hello.o查看Section头
图 14 Section头信息
Section头处于ELF头和Section头部表之间,其中包含每个节的类型,位置,大小等等信息。
4.3.3使用命令readelf -s hello.o查看符号表
图 15 符号表
符号表内Name包含了在hello内出现的所有符号名称
4.3.4使用readelf -r hello.o查看可重定位节
图 16 重定位节
偏移量是需要被修改的节偏移,符号值指向被修改引用应该指向的符号。类型引导链接器如何修改新的引用,加数是一个有符号常数,用于偏移调整。
4.4 Hello.o的结果解析
(以下格式自行编排,编辑时删除)
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
反汇编出的程序与汇编语言相似,但汇编语言中的操作数被翻译成机器语言后较为难分辨理解。机器语言是二进制数据,汇编语言可以被汇编成机器语言,机器语言也可以反汇编成汇编语言,说明汇编语言和机器语言有一一对应关系。
分支转移和函数调用和汇编语言差别较大,汇编语言会通过标识符或函数名进行跳转,而机器语言直接跳转到地址上。
4.5 本章小结
本章主要致力于解释hello.s到hello.o的过程,查看了ELF头,Section头,符号表和重定位节,还将汇编语言和机器语言进行比较,了解了机器能识别的语言和汇编语言的差别。
第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生成hello文件
图 17 生成hello文件
5.3 可执行目标文件hello的格式
图 18 hello的ELF头信息
和之前的hello.o文件的ELF头相比,其文件类型变为了可执行文件,程序头的大小和数量都发生了变化,Section头也变得更多。
图 19 hello文件的Section头信息
图 20 hello文件的符号表信息
符号表内的符号数量大幅度增加,应该是链接后加入了其他库的函数符号。
图 21 hello的重定位节信息
5.4 hello的虚拟地址空间
图 22 edb下查看虚拟地址空间各段信息
Edb显示虚拟地址从0x401000开始到0x402000结束,实际上能查看到的地址是处于0x401ff0结束。通过查看Section头的信息,可以发现程序中的每一节的地址都是处于这个范围之内的。
5.5 链接的重定位过程分析
在终端输入objdump -d -r hello并新建一个.s文件方便查看
链接后文件里增加了一些原函数中使用的其他库的函数,链接使用了汇编产生的重定位的指令,将每一个符号引用和符号定义关联,将符号定义和内存空间也关联起来,将离散的内存片段组成完整的程序内容。
5.6 hello的执行流程
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
选择使用偏移量表got,查ELF得到got.plt地址为0x404000(图19)
图 24 got在调用函数前
图 25 got在调用函数后
5.8 本章小结
本章介绍了链接的概念与作用,使用hello.o链接生成可执行文件hello,同时详细介绍了hello的ELF格式和各个节的含义并且与上一章ELF文件进行对比,还分析了hello的虚拟地址空间、重定位过程、执行流程、动态链接过程。
6.1 进程的概念与作用
进程的概念:进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
进程的作用:进程作为一个执行中程序的实例,系统中每个程序都运行在某个进程的上下文中,上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。我们的程序好像是独占的使用处理器和内存,处理器好像是无间断的执行我们程序中的指令。
6.2 简述壳Shell-bash的作用与处理流程
shell的作用:shell的功能主要是命令解释,Linux系统上的所有可执行文件都可以作为shell命令来执行,同时shell也包含一些内置命令。此外,shell还包括通配符、命令补全、命令历史、重定向、管道、命令替换等功能。
处理流程:从终端读入输入的命令。将其切分获得参数,如果是内置命令则立即执行,否则调用相应的程序为其分配子进程并运行,shell还接受键盘输入信号,并对这些信号进行相应处理
6.3 Hello的fork进程创建过程
父进程调用fork函数创造子进程,子进程得到和父进程相同的虚拟地址空间副本,得到父进程的数据空间等各种资源。fork函数在创建成功时对于子进程返回0,对于父进程返回子进程ID,出错则返回-1。
6.4 Hello的execve过程
execve函数运行可执行文件hello,会有两个数据代入,一个是参数列表argv,另一个是环境变量列表envp,它指调用一次且不会返回。
execve会删除已存在的用户区域,创建新的代码、数据、堆和栈,有时还需映射共享区域,最后设置当前进程的上下文程序计数器。
6.5 Hello的进程执行
Hello在运行时处于用户态,当它所属的时间片结束,它会sleep并返回核心态,等到再次分配给它是时间片,它会变回用户态不断重复,和其他进程交替占用CPU,实现进程的调度。
6.6 hello的异常与信号处理
异常可以分为四类:中断、陷阱、故障和终止。
1.正常运行
图 26 正常运行,每六秒输出一次
在运行hello时,根据提示会输入以上信息,程序正常运行,每6秒输出一次我们输入的信息。
- Ctrl+Z
按下Ctrl+Z后,进程收到SIGSTP信号,此时前台作业被挂起,并打印了相关的挂起信息,但hello进程还没有被回收,而是在后台运行。输入ps可以发现程序还在后台。
图 27 hello在后台挂起
输入job可以发现进程已经停止,但仍然存在。
图 28 输入jobs
- Fg
图 29 fg
输入fg以后将我们之前ctrl+z挂起的程序又一次放到了前台运行
- Kill
图 30 kill
输入kill -9 和hello的pid来杀死hello进程。
- Ctrl+C
输入Ctrl+C以后,内核发送一个SIGINT信号到前台进程组的每个进程,终止前台作业。进程收到SIGINT信号,结束hello。
图 31 Ctrl+C
输入ps查看进程发现hello也已经不存在。
图 32 ps查看hello
- 瞎按
图 33 瞎按
瞎按并不影响进程的执行。
异常与信号的处理
对于CTRL-C或者CTRL-Z,键盘键入后,内核就会发送SIGINT或者SIGSTP信号。SIGINT信号默认终止前台作业,即终止程序hello,SIGSTP默认挂起前台的hello作业。
对于fg信号,内核发送SIGCONT信号,将挂起的程序hello重新在前台运行。
对于kill -9 。内核发送SIGKILL信号给我们指定的pid(hello程序),杀死了hello程序。
6.7本章小结
本章首先简单介绍了进程的概念和作用,并在此基础上简述了壳Shell-bash的作用与处理流程。然后对可执行文件hello进行了分析,分别分析了hello的fork过程、execve过程、进程执行过程,最后分析了hello在执行过程中可能遇到的异常和信号处理,进行了测试。
第7章 hello的存储管理
7.1 hello的存储器地址空间
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。
(1)逻辑地址:
逻辑地址指的是机器语言中用来指定一个操作数或者一条指令的地址,由一个段标识符和一个指定段的地址偏移量构成。例如hello.s中使用的就是逻辑地址。
- 线性地址:是逻辑地址到物理地址变换的中间层,对hello.o进行反汇编得到的反汇编文件中看到的逻辑地址的偏移量,和其对应段的基地址就是hello中的线性地址。
- 虚拟地址:虚拟地址是程序运行在保护模式下,程序访问存储器所使用的逻辑地址称为虚拟地址。虚拟地址无论怎么偏移都在虚拟地址中,不会影响到其他程序。
- 物理地址:物理地址是放在寻址总线上的地址。Hello在运行时,访问内存需要通过翻译虚拟地址得到物理地址,通过物理地址访问内存的位置。
7.2 Intel逻辑地址到线性地址的变换-段式管理
8086处理器的寄存器是16位的,后来引入了段寄存器,可以访问更多的地址空间但不改变寄存器和指令的位宽。8086共设计了20位宽的地址总线,逻辑地址为段寄存器左移4位加上偏移地址得到20位地址。
所有的段由段描述符描述,而多个段描述符能组成一个数组,我们称成功数组为段描述表。段描述符中的BASE字段对我们翻译线性地址至关重要的。
BASE字段表示的是包含段的首字节的线性地址,也就是一个段的开始位置的线性地址。
为了得到BASE字段,利用索引号从GDT(全局段描述表)或LDT(局部段描述符表)中得到段描述符。选择GDT还是LDT取决于段选择符中的T1,若T1等于0则选择GDT,反之选择LDT。这样就得到了BASE字段。最后通过BASE加上段偏移量就得到了线性地址
图 34 段式管理
7.3 Hello的线性地址到物理地址的变换-页式管理
页式管理是指对虚拟内存的空间进行分页管理。Linux将虚拟内存组织成段的集合,段之外的虚拟内存就不在需要记录了,虚拟页就是指系统将段分为固定大小的块,使用页表来实现虚拟内存和物理内存的映射。
7.4 TLB与四级页表支持下的VA到PA的变换
TLB就是页表的Cache,其中存储了当前最可能被访问到的页表项,其内容是部分页表项的一个副本。只有在TLB无法完成地址翻译任务时,才会到内存中查询页表,这样就减少了页表查询导致的处理器性能下降。
首先虚拟地址VA被传送给MMU,MMU处理后将其在TLB中匹配,如果没有匹配中,则向页表中查询,查询到以后加入到TLB中。
7.5 三级Cache支持下的物理内存访问
介于CPU和主存储器间的高速小容量存储器,由静态存储芯片SRAM组成,容量较小但比主存DRAM技术更加昂贵而快速, 接近于CPU的速度。CPU往往需要重复读取同样的数据块, Cache的引入与缓存容量的增大,可以大幅提升CPU内部读取数据的命中率,从而提高系统性能。通常由高速存储器、联想存储器、地址转换部件、替换部件等组成。如图所示。
图 35 cache的基本结构
联想存储器:根据内容进行寻址的存储器(冯氏模型中是按照地址进行寻址,但在高速存储器中往往只存有部分信息,此时需要根据内容进行检索)
地址转换部件:通过联想存储器建立目录表以实现快速地址转换。命中时直接访问Cache;未命中时从内存读取放入Cache
替换部件:在缓存已满时按一定策略进行数据块替换,并修改地址转换部件
早期采用外部(Off-chip)Cache,不做在CPU内而是独立设置一个Cache。现在采用片内(On-chip)Cache,将Cache和CPU作在一个芯片上,且采用多级Cache,同时使用L1 Cache和L2 Cache,甚至有L3 Cache。
一般L1 Cache都是分立Cache,分为数据缓存和指令缓存,可以减少访存冲突引起的结构冒险,这样多条指令可以并行执行;内置;其成本最高,对CPU的性能影响最大
多级Cache的情况下,L1 Cache的命中时间比命中率更重要
一般L2 Cache都是联合Cache,这样空间利用率高
没有L3 Cache的情况下,L2 Cache的命中率比命中时间更重要(缺失时需从主存取数,并要送L1和L2 cache)
L3 Cache多为外置,在游戏和服务器领域有效;但对很多应用来说,总线改善比设置L3更加有利于提升系统性能
图 36
上图显示了最简单的缓存配置。它对应着最早期使用CPU cache的系统的架构。CPU内核不再直接连接到主内存。所有的数据加载和存储都必须经过缓存。CPU核心与缓存之间的连接是一种特殊的快速连接。在一个简化的表示中,主存和高速缓存连接到系统总线,该系统总线也可用于与系统的其他组件进行通信。我们引入了系统总线(现代叫做“FSB”)。
引入缓存后不久,系统变得更加复杂。高速缓存和主存之间的速度差异再次增大,使得另一个级别的高速缓存不得不被添加进来,它比第一级高速缓存更大且更慢。出于经济原因,仅增加第一级缓存的大小不是一种选择。今天,甚至有机器在生产环境中使用了三级缓存。带有这种处理器的系统如图下所示。随着单个CPU的内核数量的增加,未来的缓存级别数量可能会增加。现在已经出现了拥有四级cache的处理器了。
图 37
上图展示了三级缓存的结构。L1d是一级数据cache,L1i是一级指令cache。请注意,这只是一个示意图; 现实中的数据流从core到主存的过程中不需要经过任何更高级别的cache。CPU设计人员有很大的自由来设计cache的接口。对于程序员来说,这些设计选择是不可见的。
另外,我们有拥有多个core的处理器,每个core可以有多个“线程”。核心和线程之间的区别在于,独立的核心具有所有硬件资源的独立的副本,早期的多核处理器,甚至具有单独的第二级缓存而没有第三级缓存。核心可以完全独立运行,除非它们在同一时间使用相同的资源,例如与外部的连接。另一方面,线程们共享几乎所有的处理器资源。英特尔的线程实现只为线程提供单独的寄存器,甚至是有限的,还有一些寄存器是共享的。
一个现代CPU的完整概貌如图所示。
图 38
7.6 hello进程fork时的内存映射
hello调用fork函数时,内核给它分配一个唯一的PID,并创建了各种的数据结构,为了给其分配虚拟内存,内核创建了当前进程的mm_struct,区域结构和页表的原样副本,且将其标记为只读,将区域结构标记为私有的写时复制,当这两个进程进行写操作时,会创建新页面,也就保证了每个内存的私有地址空间。
7.7 hello进程execve时的内存映射
hello进程execve需要以下步骤:
删除已存在的用户区域,删除当前进程虚拟地址的用户部分中的已存在的区域结构。
映射私有区域,为新程序的代码、数据、bss 和栈区域创建新的区域结构。代码和数据区域被映射为 hello 文件中的.text 和.data 区,bss 区域。映射到匿名文件映射共享区域, hello 程序与共享对象 libc.so 链接,然后再映射到用户虚拟地址空间中的共享区域内。
设置程序计数器(PC),execve 做的最后一件事情就是设置当前进程上下文的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
首先检查虚拟内存地址是否合法,如果不合法就触发错误,终止处理。再检查是否有读写该区域页面的权限,没有的话触发错误,终止处理。前两个步骤都无误后,内核选择一个牺牲页面,满足条件的话将其交换出去,换入新页面更新页表,然后将控制转回触发缺页故障的程序,再次执行触发故障的指令。
7.9动态存储分配管理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。假设堆是一个请求二进制零的区域,紧接在未初始化数据区域后开始,向上生长。对每个进程,内核维护一个全局变量brk指向堆顶。分配器将堆视为一组不同大小的块的集合来维护。
分配器的具体操作过程:
放置已分配块:当一个应用请求一个k字节的块时,分配器搜索空闲链表。查找一个足够大可以放置所请求的空闲块。执行这种搜索的常见策略包括首次适配、下一次适配和最佳适配等。
分割空闲块:一旦分配器找到了匹配的空闲块,需要决定分配这个空闲块中多少空间。可以选择用整个块,但会造成额外的内部碎片;也可以选择将空闲块分割为两部分,第一部分变成已分配块,剩下的变成新的空闲块。
获取额外的堆内存:如果分配器不能为请求块找到空闲块,分配器通过调用sbrk函数,向内核请求额外的堆内存。分配器将额外的内存转化成一个大的空闲块,将这个块插到空闲链表中,然后被请求的块放在这个新的空闲块中。
合并空闲块:分配器释放一个已分配块时,要合并相邻的空闲块。分配器决定何时执行合并,可以选择立即合并或者推迟合并。合并时需要合并当前块和前面以及后面的空闲块。
10本章小结
本章介绍了各类地址的概念,以及其是如何转换的,然后分析了段页式管理。紧接着对TLB和四级页表下支持的虚拟内存到物理内存的变换分析了地址翻译的过程。然后分析了三级cache支持下的物理内存访问,然后对hello进程的fork和execve的内存映射进行了分析,最后分析了缺页故障的处理和动态内存管理。
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
所有的I/O设备都被模型化为文件,所有的输入输出都被当作对相应文件的读和写来执行。
设备管理:unix io接口
Linux内核引出一个简单、低级的应用接口,称为Unix I/O。这使得所有的输入和输出都能以一种统一且一致的方式来执行。
8.2 简述Unix IO接口及其函数
Unix IO接口:使得所有输入和输出都以一种统一且一致的方式执行。
Unix IO函数:
open()打开文件,create()创建文件,close()关闭打开的文件,lseek()为一个打开的文件设置偏移量,读写操作从偏移量处开始,read()读取文件,读到文件尾,write()写入文件。
8.3 printf的实现分析
int printf(const char *fmt, ...)
{
int i;
char buf[256];
va_list arg = (va_list)((char*)(&fmt) + 4);
i = vsprintf(buf, fmt, arg);
write(buf, i);
return i;
}
printf接受格式化的命令,把匹配的数据格式化输出。vsprintf就是帮助格式化输入,使其能被printf读取识别。
8.4 getchar的实现分析
int getchar(void)
{
static char buf[BUFSIZ];
static char* bb=buf;
static int n=0;
if(n==0)
{
n=read(0,buf,BUFSIZ);
bb=buf;
}
return (--n>=0)?(unsigned char)*bb++:EOF;
}
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章介绍了unix IO以及printf和getchar函数的实现。
结论
Hello的经历:
编写:C语言代码编写出了最初的hello.c。
预处理:预处理器解析宏定义,条件编译等,生成ASCII码的中间文件hello.i。
编译:编译器将C语言翻译成汇编语言,得到ASCII汇编语言文件hello.s。
汇编:汇编器把汇编语言翻译为机器语言,生成重定位信息,得到可重定位目标文件hello.o。
链接:连接器进行符号解析,重定位,动态链接等操作,创建出可执行文件hello。
fork创建进程:shell调用fork函数创建子进程。
execve:子进程调用execve函数,加载hello程序。
运行:内核对其进行调度,处理产生的异常和信号。MMU、TLB、多级页表、cache、DRAM内存、动态内存分配器完成对其的内存管理。Unix IO保证其与文件的交互。
终止:进程运行结束,shell回收进程,内核删除所有数据结构。
感悟:虽然本次程序并不是我们所熟知的helloworld程序,但却以hello命名,让我体验了一个看似简单程序的创建到运行到结束,我们平常所编写的程序或许比这次大作业复杂的多很多,但是我们却从来没有想过,一个程序到底从头到尾经历了什么,计算机在短短几秒内就完成了如此复杂的操作,不得不感叹机器的精密与美妙,我们以hello为载体,或许也是一种不忘初心,是一种来自最初的纯净,让我们体会到了程序的美妙与神奇。
附件
hello.i : hello.c预处理后的结果。
hello.s : hello.i 编译得到的结果。
hello.o : hello.s 汇编后的结果。
hello : 可执行文件。
hello链接后反汇编.s : hello链接后反汇编的结果
参考文献
[1] 林来兴. 空间控制技术[M]. 北京:中国宇航出版社,1992:25-42.
[2] 辛希孟. 信息技术与信息服务国际研讨会论文集:A集[C]. 北京:中国科学出版社,1999.
[3] 赵耀东. 新时代的工业工程师[M/OL]. 台北:天下文化出版社,1998 [1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).
[4] 谌颖. 空间交会控制理论与方法研究[D]. 哈尔滨:哈尔滨工业大学,1992:8-13.
[5] KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.
[6] CHRISTINE M. Plant Physiology: Plant Biology in the Genome Era[J/OL]. Science,1998,281:331-332[1998-09-23]. http://www.sciencemag.org/cgi/ collection/anatmorp.
[7] 百度百科,进程(一段程序的执行过程)_百度百科
[8] 段页式访存——逻辑地址到线性地址段页式访存——逻辑地址到线性地址的转换 - 简书
[9] linux 分页Linux_分页管理机制(线性地址转换到物理地址) - 沙漏哟 - 博客园
[10] 页式储存管理 操作系统——页式存储管理 - 王陸 - 博客园
[11] TLB的作用及工作原理 TLB的作用及工作原理 - AlanTu - 博客园
[12]CPU 与 Memory 内存之间的三级缓存的实现原理http://t.csdn.cn/NUNjh
[13] printf函数实现的深入剖析