计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机类
学 号 120L010801
班 级 2003009
学 生 张浩哲
指 导 教 师 郑贵滨
计算机科学与技术学院
2021年5月
本篇文章探究了一个简单的程序代码是如何在系统中被处理成可执行文件,然后加载到内存中进行运行,最后运行结束从内存中删除的全过程。通过一个简单程序的运行过程来管窥系统正常运作的方法和过程。
关键词:计算机系统;预处理;编译;汇编;链接;进程;异常;信号;地址;IO
目 录
2.2在Ubuntu下预处理的命令.......................................................................... - 6 -
4.2 在Ubuntu下汇编的命令............................................................................ - 11 -
5.2 在Ubuntu下链接的命令............................................................................ - 15 -
5.3 可执行目标文件hello的格式................................................................... - 15 -
5.5 链接的重定位过程分析............................................................................... - 21 -
6.2 简述壳Shell-bash的作用与处理流程...................................................... - 24 -
6.3 Hello的fork进程创建过程...................................................................... - 24 -
7.2 Intel逻辑地址到线性地址的变换-段式管理............................................. - 30 -
7.3 Hello的线性地址到物理地址的变换-页式管理....................................... - 30 -
7.4 TLB与四级页表支持下的VA到PA的变换.............................................. - 31 -
7.5 三级Cache支持下的物理内存访问........................................................... - 31 -
7.6 hello进程fork时的内存映射................................................................... - 32 -
7.7 hello进程execve时的内存映射............................................................... - 33 -
7.8 缺页故障与缺页中断处理........................................................................... - 33 -
8.1 Linux的IO设备管理方法........................................................................... - 35 -
8.2 简述Unix IO接口及其函数........................................................................ - 35 -
第1章 概述
1.1 Hello简介
P2P过程:从一个hello.c的源程序(Program)开始,经过预处理、编译、汇编、链接生成可执行文件hello,然后运行该可执行文件后,操作系统通过fork创建一个子进程,再利用子进程执行execve加载hello进程(Process)。这就是P2P(Program to Process)过程。
020过程:在加载运行hello后,内核需要为hello进程映射虚拟内存供hello使用,并载入物理内存。在运行结束后,内核将它回收清除,清理程序运行时所占用的内存。这就是020(Zero to Zero)过程。
1.2 环境与工具
硬件环境:Intel(R) Core(TM) i7-10875H CPU @ 2.30GHz;16G RAM;512G SSD
软件环境:Windows 10 64位;VMware;Ubuntu 20.04
开发与调试工具:Visual Studio 2019;CodeBlocks;edb;gcc;gdb
1.3 中间结果
文件名 | 文件作用 |
hello.c | 程序源代码 |
hello.i | hello.c文件预处理后的文本文件 |
hello.s | hello.i文件编译后的汇编文件 |
hello.o | hello.s文件汇编后的可重定位目标文件 |
hello | hello.s文件链接后的可执行文件 |
hello.elf | hello.o文件的elf格式 |
hello_obj.txt | hello.o文件的反汇编文件 |
elf.txt | hello的elf格式 |
hello_obj2.txt | hello的反汇编文件 |
1.4 本章小结
本章主要简单介绍了hello.c的P2P和020过程,以及实验的环境与工具,还简单介绍了实验过程中产生文件的作用。
第2章 预处理
2.1 预处理的概念与作用
概念:将程序中以#开头的命令将代码在编译前做相应的转换,一般有头文件转换、宏转换、条件编译转换等。
作用:
- 完成头文件引用,将头文件内容复制到源程序中实现引用
- 完成宏转换,将程序中的宏用具体数值进行替代
- 处理条件编译,使部分代码不参与编译
- 删除注释
2.2在Ubuntu下预处理的命令
在终端中输入命令对hell.c进行预处理,生成hello.i文件。
2.3 Hello的预处理结果解析
首先在预处理后,原本程序开头的注释被删除了。
另外,hello.i比hello.c内容多了很多,这是因为预处理将#include头文件拷贝至了hello.i中。
2.4 本章小结
在本章中介绍了预处理的概念和功能,并展示了预处理的方法以及预处理后文件与源程序的区别
第3章 编译
3.1 编译的概念与作用
概念:编译是指通过编译器将预处理后文件翻译成汇编语言程序的过程。
作用:编译可以将高级语言程序翻译成汇编语言程序,在这过程中能够发现语法错误,提高代码正确性,并且增强逻辑性,提高程序效率。
3.2 在Ubuntu下编译的命令
在终端中输入命令对hello.i进行编译生成hello.s文件。
3.3 Hello的编译结果解析
3.3.1 数据
1.字符串
字符串存放于主程序之外的只读数据段中。
2.数组
数组的访问是通过数组首地址加偏移量的方式完成的。
3.局部变量
main函数中定义了一个局部变量i,在编译过程中编译器会将i存放在栈中,并利用rbp加偏移量的方式来进行访问。
4.主函数
主函数中传递的参数argc与argv在函数中被从edi与rsi压入栈中,通过rbp加偏移量的方式进行访问。
3.3.2 算数操作
对于hello.c中的算数操作i++,汇编利用addl实现。
3.3.3 赋值操作
对于hello.c中的赋值操作i=0,汇编利用movl实现。
3.3.4 条件运算与控制转移操作
对于hello.c中的条件运算与控制转移操作,如条件argc!=4和循环中的i<8等,汇编利用cmpl与跳转语句实现。
3.3.5 函数操作
对于hello.c中的函数操作,函数调用使用的是call语句。
而函数调用过程中的参数传递则是通过寄存器实现。(参数过多则会将参数压入栈中)
3.4 本章小结
本章介绍了编译的概念和作用,并介绍了预处理后的hello.i文件是如何被编译器编译成hello.s文件的,以及分别对于赋值操作、函数操作、条件运算与控制转移操作等各种操作的不同编译方法。
第4章 汇编
4.1 汇编的概念与作用
概念:汇编器将hello.s文件翻译成机器语言的可重定位目标文件hello.o。
作用:将汇编语言程序转化为机器语言程序。
4.2 在Ubuntu下汇编的命令
在终端中输入命令对hello.s进行汇编生成hello.o文件。
4.3 可重定位目标elf格式
使用readelf -a hello.o > hello.elf命令查看hello.o的ELF格式。
4.3.1 ELF头
ELF头的开头是一个16字节的序列,其描述了生成文件的系统的字大小和字节顺序。另外ELF头还包含了帮助链接器语法分析和解释目标文件的信息:ELF头的大小、目标文件的类型、节头部表的文件偏移等。
4.3.2 节头
节头描述了hello.o中各个节的类型、位置以及所占空间等信息。
4.3.3 重定位节
链接器使用重定位节的重定位条目计算出正确的位置,使得各段引用的外部符号在链接时能对这些位置地址进行修改。
在hello.o中需要重定位的有:puts,printf,sleep,getchar等。
4.3.4 符号表
符号表中存放程序中定义和引用的函数以及全局变量。
4.4 Hello.o的结果解析
利用objdump指令将对hello.o的反汇编结果保存到hello_obj.txt中。反汇编代码由左边的十六进制机器语言指令和右边的汇编指令构成。
汇编代码与反汇编代码大部分都是相同的,只有小部分存在差异。
差异:
1.汇编代码中立即数为十进制,而反汇编代码中立即数为十六进制。
2.在汇编代码中,分支跳转通过.L2、.L3等标记符号实现,而反汇编代码中跳转则是通过主函数地址加偏移量的方式实现。
3.汇编代码中函数调用是通过call 函数名的方式实现的,而反汇编代码中函数调用是通过call 主函数地址加偏移量实现.
4.5 本章小结
本章介绍了汇编的概念和作用,并分析了ELF格式文件中的内容,并比对了汇编代码与反汇编代码的异同。
第5章 链接
5.1 链接的概念与作用
概念:链接是将多个可重定位目标文件合并以生成可执行目标文件的过程,即由hello.o生成hello可执行文件,该文件可被加载至内存中执行。链接过程可以在汇编时、加载时或运行时完成。
作用:链接使得分离编译成为可能,大型程序能够分解为更小、更好管理的多个模块,并且这些模块可以被独立修改并编译。
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的格式
使用readelf -a hello > ./elf.txt命令生成hello的ELF格式。
5.3.1 ELF头
该段信息类似4.3.1,但与可重定位目标有几处不同:
- elf.txt中的类型由REL(可重定向文件)变为了EXEC(可执行文件)。
- elf.txt中入口点地址由0x0变为了0x4010f0,这是因为链接库文件后main函数的起始地址不再是0x0。
- elf.txt中节头数量增多。
5.3.2 节头
该段信息类似4.3.2,不同的是elf.txt中每个节的地址由0变为链接计算后的确定的地址值。
5.3.3 程序头
elf.txt中新增了程序头,用于描述了磁盘上可执行文件的内存布局以及如何映射到内存中。
5.3.4 动态节
elf.txt新增了动态节内容,用于存储共享库地址。
5.3.5 重定向节
该段信息类似4.3.3。
5.3.6 符号表
该段信息类似4.3.4。但elf.txt中多出了.dynym节,用于存放通过动态链接解析出的符号,即程序引用的头文件中的函数。
5.4 hello的虚拟地址空间
使用edb加载hello,可以发现虚拟地址起始为0x400000
通过查看节头可以得到各个节的地址,例如5.3中的.interp节地址为0x4002e0
5.3部分中得到.init节的地址为0x401000
5.3部分中可以得到程序代码段.text的地址为0x4010f0,edb中与之对应
5.5 链接的重定位过程分析
使用命令objdump -d -r hello > hello_obj2.txt生成hello的反汇编文件。接下来比较hello_obj.txt与hello_obj2.txt的区别。
5.5.1 新增函数
hello_obj2.txt中在链接后加入了使用到的库函数,如printf、getchar等。
5.5.2 新增节
hello_obj2.txt中新增了.init、.plt等节。
5.5.3 函数调用和控制跳转地址
原先hello_obj.txt中地址跳转时使用的main函数地址加偏移量跳转方式在hello_obj2.txt中已经被全部重新计算,因为在链接后main函数的地址确定,故原先跳转时的地址已被写为确切的虚拟地址。
5.5.4 主函数地址
原先hello_obj.txt中主函数地址为000000,而在hello_obj2.txt中链接之后主函数有了其确切虚拟地址401125。
hello的重定位过程:
- 链接器将所有类型相同的节合并后,这个节就成为了可执行目标文件的节。然后链接器把运行时的内存地址赋给合并产生的新节、输入模块定义的节以及输入模块定义的符号。
- 接下来连接器修改代码节和数据节中对每个符号的引用地址,使他们指向正确的运行时地址。
5.6 hello的执行流程
子程序名 | 地址 |
hello!_start | 0x4010f0 |
libc-2.31.so!__libc_start_main | 0x00007f8d452befc0 |
libc-2.31.so!__cxa_atexit | 0x00007f8d452e1e10 |
hello!__libc_csu_init | 0x4011c0 |
hello!_init | 0x401000 |
hello!_fini | 0x401238 |
hello!_main | 0x401125 |
hello!_exit | 0x401070 |
5.7 Hello的动态链接分析
当程序调用一个由共享库定义的函数时,编译器无法预测这个函数运行时的地址,因为定义它的共享模块在运行时可以加载到任何位置。GUN编译系统使用延迟绑定来解决该问题,即将过程地址的绑定推迟到第一次调用该过程时。延迟绑定通过全局偏移量表GOT和过程链接表PLT的协作来解析函数的地址。如果一个目标模块调用定义在共享库中的任何函数,那么他就有自己的GOT和PLT。GOT是数据段的一部分,而PLT是代码段的一部分。
先查看elf文件找到.got.plt的地址为0x404000
在dl_init执行前:
在dl_init执行后:
这段数据变化便是GOT表加载了共享库的内容。
5.8 本章小结
本章主要介绍了链接的过程,利用edb、objdump等工具对可执行文件的ELF格式、虚拟地址空间、重定位过程等进行了分析,另外对可执行程序的执行流程以及动态链接进行了初步的分析。
第6章 hello进程管理
6.1 进程的概念与作用
概念:进程是正在运行的程序的实例。
作用:控制系统提供一个假象,使得程序像是系统中唯一运行的程序一样,独占处理器和内存。
6.2 简述壳Shell-bash的作用与处理流程
作用:Shell是一种交互型的应用级程序,是Linux的外壳,提供了一个界面供用户访问操作系统内核。而Shell-bash是一个UNIX shell,也就是LINUX系统下的Shell,可以看作是系统的命令解释器。
处理流程:
- 终端进程读取用户输入的命令行
- 分析命令行,获得命令行参数
- 若命令为内置命令则立即执行
- 否则会调用fork创建子进程,再调用execve在子进程中加载运行目标程序。
- Shell继续接受输入信号,并做出相应的处理
6.3 Hello的fork进程创建过程
在终端输入运行hello的命令后,shell会处理该命令,并判断该命令不是内置命令,于是调用fork函数创建一个新的子进程,子进程几乎但不完全与父进程相同。
6.4 Hello的execve过程
为了运行程序,shell调用execve函数在当前子进程中加载并运行hello。
其中execve函数的参数包括需要执行的程序、参数argv、环境变量envp。
并且在execve运行过程中还要执行以下几步:
- 删除存在的用户区域。
- 映射私有区,为hello的代码、数据、.bss和栈区域创建新的区域结构。
- 映射共享区,例如hello程序与标准C库libc.so链接,将对象动态链接到hello,然后保存在虚拟地址空间中的共享区域内。
- 设置当前进程的上下文中的程序计数器,使之指向代码区域的入口点。
- execve在调用成功的情况下不会返回,只有当出现错误时才会返回到调用程序。
6.5 Hello的进程执行
时间片:时间片是指CPU分配给每个程序的运行时间,每个线程被分配一个时间段,作为他的时间片。微观上一个CPU统一时间只能执行一个时间片,但是因为CPU轮转调度很快,所以在宏观上产生了多个程序并行不悖的效果。这个机制使得程序都产生了自己独占CPU的假象。
上下文的概念:上下文就是内核重新启动一个被抢占的进程所需的状态。它由一些对象的值组成,这些对象包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构,比如描述地址空间的页表、包含有关当前进程信息的进程表,以及包含进程已打开文件的信息的文件表。
用户态与核心态:即用户模式与内核模式。用户模式的进程不允许执行特殊指令,不允许直接引用地址空间中内核区的代码和数据。内核模式进程可以执行指令集中的任何命令,并且可以访问系统中的任何内存位置。异常发生时,控制传递到异常处理程序,由用户模式转变到内核模式,返回至应用程序代码时,又从内核模式转变到用户模式。
调度的过程:在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程。这种决策就叫做调度,是由内核中称为调度器的代码处理的。当内核选择一个新的进程运行时,我们说内核调度了这个进程。在内核调度了一个新的进程运行后,它就抢占当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程。
6.6 hello的异常与信号处理
6.6.1 正常运行
6.6.2 异常类型与处理方式
6.6.3 键盘输入信号
1.Ctrl+Z
进程收到SIGSTP信号,将进程挂起。
输入ps:查看进程PID
输入jobs:查看进程后台job号
输入fg:调回进程至前台
输入kill:终止进程
输入pstree:查看进程树
2.Ctrl+C
进程收到SIGNT信号,结束进程。
3.乱按
将屏幕输入缓存到缓冲区。
6.7本章小结
本章简单介绍了进程的概念与作用,讲解了什么是shell,并分析了hello程序利用fork和execve创建子程序并加载运行程序的过程,并分析了hello的执行过程,最后还介绍hello对于异常及信号的处理方式。
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:逻辑地址是指由程序产生的与段相关的偏移地址部分。
线性地址:线性地址是逻辑地址到物理地址变换之间的中间层。程序hello的代码会产生段中的偏移地址,加上相应段的基地址就生成了一个线性地址。
虚拟地址:使用虚拟寻址时,CPU通过生成一个虚拟地址来访问主存,这个虚拟地址在被送至内存前先转换成适当的物理地址。虚拟地址转化成物理地址的过程叫做地址翻译。在linux中,虚拟地址数值树等于线性地址,即hello中看到的地址加上对应段基地址的值。
物理地址:放在寻址总线上的地址。放在寻址总线上,如果是读,电路根据这个地址每位的值就将相应地址的物理内存中的数据放到数据总线中传输。如果是写,电路根据这个地址每位的值就在相应地址的物理内存中放入数据总线上的内容。
7.2 Intel逻辑地址到线性地址的变换-段式管理
逻辑地址由段标识符:段内偏移量组成。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节。
所有的段由段描述符描述,而多个段描述符能组成一个数组,我们称成功数组为段描述表。段描述符中的BASE字段表示的是包含段的首字节的线性地址,也就是一个段的开始位置的线性地址。为了得到BASE字段,我们利用索引号从GDT(全局段描述表)或LDT(局部段描述符表)中得到段描述符。选择GDT还是LDT取决于段选择符中的T1,若T1等于0则选择GDT,反之选择LDT。这样我们就得到了BASE。最后通过BASE加上段偏移量就得到了线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
页面管理是一种内存空间存储管理技术。页面管理分为静态页面管理和动态页面管理。每个进程的虚拟空间被划分为几个等长的页面。页管理根据页的大小将内存空间划分为片或页,然后建立与页虚拟地址和内存地址相对应的一对一页表,并使用相应的硬件地址转换机制解决离散地址转换问题。页面管理采用页面请求或页面预调整技术,实现内外存储器的统一管理。
7.4 TLB与四级页表支持下的VA到PA的变换
每次CPU产生一个虚拟地址,MMU(内存管理单元)就必须查阅一个PTE(页表条目),以便将虚拟地址翻译为物理地址。在最糟糕的情况下,这会从内存多取一次数据,代价是几十到几百个周期。如果PTE碰巧缓存在L1中,那么开销就会下降1或2个周期。然而,许多系统都试图消除即使是这样的开销,它们在MMU中包括了一个关于PTE的小的缓存,称为翻译后备缓存器(TLB)。
7.4.2 多级页表
将虚拟地址的VPN划分为相等大小的不同的部分,每个部分用于寻找由上一级确定的页表基址对应的页表条目。
7.4.3 VA到PA的变换
虚拟地址VA由虚拟页号VPN和虚拟页偏移VPO组成。若TLB不命中时,VPN被划分为四个片,每个片被用作到一个页表的偏移量。CR3寄存器包含L1页表的物理地址。只有一级页表常驻内存,二三四级页表按需储存。第一二三级页表的PTE指向一个页表,而第四级页表的PTE指向虚拟内存的一个页。VPN1提供到一个L1 PTE的偏移量,这个PTE包含L2页表的基地址。VPN2提供到一个L2 PTE的偏移量,依次类推。最后在L4页表中对应的PTE中取出PPN,与VPO连接,形成物理地址PA。
7.5 三级Cache支持下的物理内存访问
CPU发送一条虚拟地址,随后MMU按照7.4所述的操作获得了物理地址PA。根据cache大小组数的要求,将PA分为CT(标记位)、CI(组索引)、CO(块偏移)。根据CI寻找到正确的组,依次与每一行的数据比较,有效位有效且标记位一致则命中。如果命中,直接返回想要的数据。如果不命中,就依次去L2、L3,主存判断是否命中,命中时将数据传给CPU同时更新各级cache的储存。
7.6 hello进程fork时的内存映射
当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并给他分配一个唯一的PID。并为这个进程创建虚拟内存,创建了mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每一个区域结构都标记位私有的写时复制。当这两个进程中的任何一个后来进行写操作时,写时复制机制就会创建新的页面。
7.7 hello进程execve时的内存映射
在bash的进程中execve执行了对hello的调用后。
- execve函数在当前进程中加载并运行可执行文件hello,并替代了当前bash中的程序。
- 删除已存在的用户区域。
- 映射私有区域。
- 映射共享区域。
- 设置当前进程中的程序计数器到代码入口点。
7.8 缺页故障与缺页中断处理
缺页故障概念:当指令引用一个虚拟地址,而与该地址相对于的物理页面不在内存中,因此必须从磁盘中取出时,就会发送缺页故障。
处理缺页故障是由硬件和操作系统内核协作完成:
- 处理器生成一个虚拟地址,并将它传送给MMU
- MMU生成PTE地址,并从高速缓存/主存请求得到它
- 高速缓存/主存向MMU返回PTE
- PTE中的有效位是0,所以MMU出发了一次异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序。
- 缺页处理程序确认出物理内存中的牺牲页,如果这个页已经被修改了,则把它换到磁盘。
- 缺页处理程序页面调入新的页面,并更新内存中的PTE
- 缺页处理程序返回到原来的进程,再次执行导致缺页的命令。
7.9动态存储分配管理
动态内存分配器维护这一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块来处理。每个块就是一个连续的虚拟内存片,其中已分配的块显式地保留为供应用程序使用,而空闲的块可以用来分配。
动态内存分配主要有两种基本方法与策略:显式分配和隐式分配。显式分配器要求程序显式释放任何一分配的块;隐式分配器可以自动检查一个已分配块何时不再被程序使用,然后自动释放。
7.10本章小结
本章主要介绍了程序的存储结构,通过段式管理从逻辑地址变换为虚拟地址,通过页式管理从虚拟地址变换到物理地址。分析了程序访问过程中高速缓存器cache的结构和多级页表结构等,以及进程是如何加载虚拟内存空间,内存映射和动态内存分配问题。
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:所有的IO设备都被模型化为文件。
设备管理:所有的输入和输出都被当做对相应文件的读和写来执行,这种将设备映射为文件的方式,允许Linux内核引出一个简单,低级的应用接口,称为Unix I/O。
8.2 简述Unix IO接口及其函数
Unix IO接口可实现下面几个操作:
打开文件:一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个 I/O 设备,内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件,内核记录有关这个打开文件的所有信息。而应用程序只需要记住这个描述符。
改变当前文件位置:应用程序能够通过调用函数seek,显式地设置文件的当前位置为k 。
读写文件:一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n,给定一个大小为m字节的而文件,当k>=m时执行读操作时触发EOF。类似一个写操作就是从内存中复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k。
关闭文件:当应用完成了对文件的访问之后,它就通知内核关闭这个文件。
8.3 printf的实现分析
printf开辟一块输出缓冲区,然后vsprintf函数将所有的参数内容格式化之后存入buf,返回格式化数组的长度。write函数将buf中的i个元素写到终端。显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
当程序调用getchar时,程序等待用户按键,用户输入的字符被存放在键盘缓冲区中直到用户按回车。当用户键入回车之后,getchar才开始从stdio流中每次读入一个字符。如用户在按回车之前输入了不止一个字符,其他字符会保留在键盘缓存区中,等待后续getchar调用读取。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章介绍了Linux的I/O设备基本概念和管理方法以及Unix I/O接口函数,并简单介绍了printf和getchar的工作过程。
结论
hello的一生结束了,它生来简单却又复杂。
- hello.c被程序员编写出来。
- hello.c被预处理成hello.i文件,将外部库调入程序。
- hello.i由编译器编译成hello.s文件,语言由高级语言变为汇编语言。
- hello.s由汇编器汇编成为机器语言的hello.o可重定位目标文件。
- hello.o由链接器链接到必要的库进行重定位过程,生成可执行文件hello。
- Shell通过fork函数和execve函数创建子进程并加载运行hello。
- hello运行过程中通过虚拟内存及其分段机制和分页机制映射到物理内存中,以实现程序的运行。
- hello运行过程中会产生异常与信号,这些会对程序运行产生不同的影响。
- hello的输入输出通过I/O端口与外界联系。
- hello被进程终止并回收。
hello.c是一个十分简单的程序,它小到只有一个输出hello world的过程。但是如此小的一个程序能够运行起来,其背后有着庞大且复杂的计算机系统支撑。这些系统之间的功能相互嵌套,相互配合,构筑起了无比复杂的计算机系统,如此才能小到运行hello.c,大到运行各种工程文件。而这一切在几十年前还未出现,是先辈们的创新和努力使得这一切变为了现实,让计算机系统这一万丈高楼平地而起。
附件
文件名 | 文件作用 |
hello.c | 程序源代码 |
hello.i | hello.c文件预处理后的文本文件 |
hello.s | hello.i文件编译后的汇编文件 |
hello.o | hello.s文件汇编后的可重定位目标文件 |
hello | hello.s文件链接后的可执行文件 |
hello.elf | hello.o文件的elf格式 |
hello_obj.txt | hello.o文件的反汇编文件 |
elf.txt | hello的elf格式 |
hello_obj2.txt | hello的反汇编文件 |
参考文献
[1] Randal E,Brynant, David R. O’Hallaron. 深入理解计算机系统(原书第三版). 北京:机械工业出版社,2016.[2] 辛希孟. 信息技术与信息服务国际研讨会论文集:A集[C]. 北京:中国科学出版社,1999.