本文主要描述了程序如何从源文件变成变成可执行文件,然后加载进内存执行的全过程。详细分析了计算机在生成hello可执行文件的预处理、编译、汇编、链接、进程管理等整个生命周期。讨论了进程管理,内存管理的原理与进程,异常与信号的机制,并且通过实际操作展示了结果,通过gdb调试验证理论。总体的阐述了计算机系统的工作原理和体系结构
。
关键词:计算机体系结构
第1章 概述
1.1 Hello简介
P2P(From Program to Process) : 即让hello.c从储存在硬盘上的程序变成内存中的运行的进程。为了做到这一点,需要通过预处理,编译,汇编,链接四个步骤,得到可执行文件 hello,然后在shell中运行hello,通过fork得到新的进程,然后用exec加载hellot到内存中,当前指令pc指向入口地址,变成正在运行的进程
O2O(From Zero-0 to Zero-0) : 即最初的内存中没有hello.out的相关信息,而当hello执行结束相关信息被清理。当shell通过fork+exec加载hello到内存开始执行。内存单元MMU会为他分配物理内存并映射到虚拟内存上,建立多级页表,把.text .bss .data 等段分配并且映射到对应的内存中,随后从.text段的入口地址 entry_point 开始执行。当程序结束后,shell父进程通过waitpid回收hello进程,内核清理为其建立的页表等结构,解除hello可执行文件在内存中的映射。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境:
处理器:Intel(R) Core(TM) i7-14650HX 2.2GHz
RAM:32GB
软件环境:
WSL2,Ubuntu22.04
开发与调试工具:
VSCode,objdump,gdb,gcc,readelf
1.3 中间结果
hello.i 通过预处理器cpp得到的预处理文件
hello.s 通过编译器cc1得到的汇编文件
hello.o 通过汇编器as得到的可重定位目标文件
hello.o.asm 反汇编hello.o 得到的汇编文件
hello.asm 反汇编hello 得到的汇编文件
hello.o.elf readelf得到hello.o的elf信息
1.4 本章小结
本章说明了hello的P2P和020的过程,解释了hello的大体执行过程。介绍了本文章使用的硬件软件平台,开发调试工具。与本文章所需要的各种中间结果的名称与作用
;计算机系统;进程管理;内存管理
第2章 预处理
2.1 预处理的概念与作用
预处理的概念:
预处理是指预处理器在编译之前对源文件进行简单的处理过程,包括处理用#开头的指令,例如替换define字符串,将include的文件包含在内,删除注释和多余的空行。
预处理的作用:
预处理主要作用是:1.包含头文件,处理 #include 指令,将后面的文件包含进源代码 2. 处理#define 将宏定义替换为定义的内容 3. 处理 #ifdef #ifundef 等编译前条件判断,删去不需要的代码 4.处理 #pargram 等编译器命令传递给编译器 5. 删除注释等不需要的内容
2.2在Ubuntu下预处理的命令
可以使用gcc -E 或者预处理器cpp

图1 预处理过程图

图 2 预处理结果图
2.3 Hello的预处理结果解析
Hello.c经过预处理后变成3900行的文件,可以看到最下面的用户程序没有改动,仅仅将#include的内容包含在了上面

图3 预处理后源程序
该程序包含了stdio.h,unistd.h,stdlib.h三个头文件
预处理程序会从系统的include目录下查找对应的头文件,例如stdio.h在/usr/include 目录下

图4 include的内容地址
预处理器会将该文件包含进hello.i中,而stdio.h中的头文件也会被递归的进行替换,最终得到没有#include和#define 程序文件
除此之外还存在一些行号等辅助信息
2.4 本章小结
本章主要讲述了linux中对C语言程序进行预处理的过程,与预处理的功能作用。通过hello.c到hello.i 过程的演示和分析,发现预处理程序包含了 #include 的所有内容,并递归的展开他的头文件。而最终的用户程序保持不变。说明预处理器只处理一些简单的文本复制和替换
第3章 编译
3.1 编译的概念与作用
编译的概念:
编译是指把高级程序设计语言翻译为等价的低级程序语言的过程,如C语言翻译为汇编语言
编译的作用:
编译将高级程序设计语言转化为汇编这种低级语言,该过程包含词法分析,语法分析,语义分析等过程。目的是让高级语言转化为更容易处理,可以直接对应到具体指令的汇编语言,方便直接生成目标文件
3.2 在Ubuntu下编译的命令
Ubuntu下使用gcc -S 或者 cc1 指令进行编译


图5 编译过程
3.3 Hello的编译结果解析
3.3.1 起始部分:

图6 汇编文件头部
其中有.file 表明源程序文件名
.text表示为代码段
.section .rodata.str1.8 为只读数据段,其后为该段的属性
.align 8表示应该对齐到8字节边界
.LC0与.LC1声明一个本地标识,用于后面引用字符串常量
.string 表明在当前位置插入一个字符串
.global main 声明符号main为全局,用于链接器链接
.type main, @function 声明main是一个function类型
3.3.2 数据部分
(1)字符串常量:

图7 字符串常量
字符串常量在起始的.LC0 与 .LC1 部分被嵌入汇编中,后续地址被放入edi/esi寄存器用于传参


图8 字符串常量的使用
(2)参数argc
Argc作为main的第一个参数,在寄存器edi中

图9 argc
(3) 参数argv
argv作为第二个参数被保存在rsi寄存器中,后续被赋值给rbx寄存器
(4) 局部变量 i
局部变量i用于循环,不存在取地址,被放在了寄存器ebp中
![]()

图10 局部变量i
3.3.3 赋值部分
本程序只有对局部变量i的赋值为0,体现在中

图11 赋值
其中movl是传递双字,也就是说i是int变量
3.3.4 算数部分
本程序的算数部分只有循环的i++部分,在汇编中如下
![]()
图12 算数
3.3.4 关系部分
本程序存在两个关系运算,一个为if(argc!=5)
在汇编中为

图13 关系
比较了edi(argc) 与 5是否相等
另一个为for循环中检查i<10
汇编为

图14 关系
比较了9与ebp(i) 的大小
3.3.4 控制转移部分
本程序有两个跳转,一个为if的跳转,另一个为for的跳转

图15 控制转移
这里是if的跳转,如果edi!=5,会跳转到.L6
否则继续执行

图16 转移
这里是for的跳转,如果rbp<=9,就会跳转回.L3继续循环,否则继续向下执行
3.3.5 数组部分
本程序中的数组操作为argv[1],argv[2],argv[3]的取值
汇编中如下

图17 数组操作
这三条语句从上到下分别为argv[2] argv[1] argv[3],movq表示它是一个64位长度的地址
3.3.6 函数部分
本程序共有6个函数调用,分别为printf(被转化为puts),exit,printf,sleep,atoi
第一个printf:
该printf因为没有格式化参数,被转化为puts

图18 puts参数调用
第一个参数放在edi中,内容为.LC0的常量字符串
Exit:
该函数只有一个参数,放在edi中

图19 exit调用
第二个printf:
该printf调用如下

图20 printf 调用
__printf_chk 是 printf的内部实现函数,共计5个参数
查看__printf_chk的调用
![]()
第一个参数2-1=1放在寄存器rdi,第二个参数格式化字符串为常量.LC1处的字符串,放在寄存器esi,第三个参数argv[1]放在rdx寄存器,argv[2]在rcx寄存器,argv[3]在r8寄存器
atoi函数
该函数内部为strtol函数,调用如下

图21 strtol调用
第一个参数为argv[4] 放在rdi寄存器,第二个参数为传出参数endpos,该处没有用到,为0,第三个参数在edx处,为转化的进制,此处为10
sleep函数

图22 sleep调用
该函数有一个参数,放在寄存器edi中,参数的值为上一个函数atoi的返回值,在eax中
getchar函数
该函数内部为getc函数,实际有一个参数stream

图23 getc调用
此处第一个参数为stdin,存放在rdi
3.4 本章小结
本章解析了hello.c生成的汇编代码hello.s。展示了转化到汇编代码的过程,说明了汇编代码的功能。同时分析汇编代码和c语言语句的一一对应,解释了数据,赋值,跳转,条件,关系,函数调用等方面,分析了汇编代码是怎样实现这些操作的。
第4章 汇编
4.1 汇编的概念与作用
汇编的概念:
汇编是通过汇编器as将汇编语言翻译为机器语言,同时打包生成一个可重定位的目标文件格式,生成目标文件.o
汇编的作用:
提供可重定位的目标文件.o,将汇编语言翻译为机器语言
4.2 在Ubuntu下汇编的命令
使用as或者gcc -c


图24 汇编过程
4.3 可重定位目标elf格式
使用readelf -a hello.o > hello.o.elf 得到elf的信息
(1)ELF头

图25 hello.o ELF头
ELF头包含了该ELF的基本信息,开头是16字节的magic number用于识别elf格式,其他部分有data的存储格式,该elf为补码小端序,os/abi信息,elf类型,机器指令架构,入口信息以及程序各个段的大小
(2) 节头部信息

图26 hello.o 节头
Section Header包含各个节的信息,内容有节名称,类型,地址,偏移量,大小,标志,链接,信息和对齐
(3) 重定位节

图27 重定位节
该节用于链接器和其他的文件链接时提供信息,表明需要修改这些地址。
一般调用外部函数或者引用全局变量的指令都需要修改,而调用本地函数的指令则不需修改
该节有两个字符串,六个函数调用为动态库,Type为PLT32,两个字符串常量需要将.LC0和.LC1变为真实地址,Type为32,stdin是全局变量,Type为PC32
(4)符号表

图28 符号表
.symtab包含elf的所有符号,包含程序定义和引用的全局变量和函数的信息。
4.4 Hello.o的结果解析
![]()
与hello.s的对照分析

图29 hello.o.asm
1.hello.o.asm的起始就是main函数,没有了.s的头部的file与.LC0等信息。
2.每个指令转化为对应的机器语言,如sub $0x8 %rsp 转化为 48 83 ec 08
3.分支跳转被改变,跳转的位置不是辅助的label,而是相对于main的便宜,如
4.函数调用从直接call 函数名,变为了等待重定位处理,目前都是call 0x00000000 后面是具体的重定位条目
5.立即数变为16进制,所有的立即数从十进制变为16进制
6.常量字符串也等待重定位,如

图30 重定位条目
变为了一个重定位条目
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
4.5 本章小结
本章解释了汇编的概念与作用,并演示了从hello.s生成hello.o的过程。随后通过Readelf生成hello.o.elf。分析了elf文件的内容,分析文件中每个节。通过分析hello.o的反汇编代码和hello.s的区别和相同点,解释了汇编代码如何转化为机器语言,以及机器为了链接而做的准备工作。
第5章 链接
5.1 链接的概念与作用
链接的概念:
链接是指是将各种代码和数据片段收集并组合为一个单一文件的过程。最终得到一个可以直接加载进内存里的执行的文件。链接可以在编译时,加载时或者运行时
链接的作用:
链接让程序员编译大型程序无需放在一个巨大的源文件里,每次修改都需要整体的重新编译。而是可以分解为更小的模块,只按需修改该模块重新链接即可。
5.2 在Ubuntu下链接的命令
ld -m elf_x86_64 -o hello --dynamic-linker /lib64/ld-linux-x86-64.so.2 /lib/x86_64-linux-gnu/crt1.o /lib/x86_64-linux-gnu/crti.o /lib/x86_64-linux-gnu/crtn.o hello.o /usr/lib/x86_64-linux-gnu/libc.so.6
图31 链接过程
5.3 可执行目标文件hello的格式
(1) ELF头

图32 hello的ELF头
用readelf 得到elf的文件信息,查看文件头发现从可重定位目标文件变成了可执行文件,同时入口地址变成了实际的入口地址
(2) 节头


图33 hello的节头
节头变长了很多,多出了.got .got.plt等用于动态连接的节,以及.init_array和.fini_array这些用于libc_start的节,结构和hello.o的节头一致,描述了节偏移和大小。同时多了.interp用于记录动态链接器的节
(3) 程序头

图34 程序头
程序头记录了系统准备程序执行所需的段或其他信息。
(4) 动态节

图35 动态节
动态节包含了一系列 Elf64_Dyn 结构,用于告诉动态链接器(dynamic linker)如何处理该可执行文件或共享库的动态链接需求
(5). 重定位节

图36 重定位节
重定位节由链接器在生成可执行文件或共享库时创建,存放了运行时需要修正的地址信息
(6). 符号表

图37 符号表
程序的符号表存放了程序中用到的所有符号的名称
5.4 hello的虚拟地址空间
使用gdb/edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。

图38 地址空间
根据5.3的节头部分,.text段应该是在0x4010f0处,调试发现确实



图39 gdb调试的内存部分
而.rodata在0x402000处,可以看到确实存在Hello等常量字符串
.interp 在0x400318处
5.5 链接的重定位过程分析
链接后得到hello.asm
可以发现多出了plt函数

图40 PLT函数
这些函数地址的功能是跳转到一个rip的相对地址,也就是got表,然后got表中通过动态链接器查询到对应函数的真实地址,跳转到真实的函数入口进行执行

图41 调用plt函数
Call 指令变为偏移值,如e8 97 fe ff ff的即为call -0x169 也就是401090 的puts的plt处,call的值为下一条指令和目标指令的差值
重定位过程(引用自CSAPP):
(1)重定位节和符号定义。在这一步中,链接器将所有相同类型的节合并为同一类型的聚合节。然后链接器将运行时的内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号。至此程序中每条指令和全局变量都有唯一的运行内存地址。
(2)重定位节中的符号引用。这一步中链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。要执行这一步,链接器依赖于可重定位目标模块中称为重定位条目的数据结构。
5.6 hello的执行流程

进入main时候查看函数堆栈,main之前有_start,__libc_start_main,__libc_start_call_main
main中间执行了函数 printf,strtol,sleep,getc
最后调用_exit 退出
子程序名与程序地址:
_start 0x4010f0
__libc_start_main 0x7ffff7db0dc0
__libc_start_call_main 0x7ffff7db0d10
main 0x4011d6
__printf_chk@plt 0x4010b0
sleep@plt 0x4010d0
getc@plt 0x4010e0
strtol@plt 0x4010a0
exit@plt 0x4010c0
5.7 Hello的动态链接分析
动态链接是指在程序运行时才将所需要的函数库链接在一起形成一个完整的程序。
编译时,调用外部函数的时候编译器没有办法预测这个函数的运行时地址,所以编译器为该引用生成一条重定位记录,然后动态链接器在程序加载的时候再获取它的真实地址。
该机制通过GOT 和PLT实现,got表在0x403ff0位置处

运行时

最开始的时候的got表没有任何内容

执行之后这里被修改了,指向函数的真实地址
当调用系统函数的时候,通过call指令进入到对应函数的plt函数内容,通过相对于pc的偏移跳转到got表的地址,如果got表未被初始化,会触发一个trap,调用动态链接器修改got,随后的plt就会得到正确的地址。
5.8 本章小结
本章解释了链接的概念和作用,展示了如何使用命令ld链接生成hello可执行文件。查看并解释了hello文件ELF格式下的内容,利用gdb观察了hello文件的虚拟地址空间使用情况,最后以hello程序为例对重定位过程、执行过程和动态链接进行分析。
第6章 hello进程管理
6.1 进程的概念与作用
进程的概念:
进程的就是内存中的一个执行中程序。进程是计算机中的程序的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
进程的作用:
进程为执行的应用程序提供一个虚拟,执行的程序可以认为自己是独占所有的处理器和cpu进行运行,而无需担心其他的程序的影响。彷佛是处理器正在一条一条的执行他的指令。
6.2 简述壳Shell-bash的作用与处理流程
Shell-bash的作用:
Shell是一个交互型应用级程序,它为用户提供一个操作界面,接受用户输入的命令,并调度相应的应用程序。
Shell-bash的处理流程:
从tty读,对输入的命令进行解析,如果该命令为内置命令,则立即执行命令,否则调用fork创建一个新的子进程,在该子进程的上下文中执行指定的程序。判断该程序为前台程序还是后台程序,如果为前台程序则等待程序执行结束,若为后台程序则将其放回后台并返回。在过程中shell可以接受从键盘输入的信号并对其进行处理。
6.3 Hello的fork进程创建过程
用户通过终端输入指令 ./hello 2023113236 杨程铄 13730192086 1
Shell首先判断该命令不是内建命令,随后调用fork创建一个新的进程。该子进程复制父进程的虚拟地址空间的内容,但是具有不同的pid,同时fork函数在父进程返回子进程的pid,而子进程中fork中返回为0。用于区分那个是父进程
6.4 Hello的execve过程
execve函数在当前进程的上下文中加载并运行一个程序。函数声明如下:
int execve(const char *filename, const char *argv[], const char *envp[]);
该函数加载filename指向的可执行文件,传递参数argv和环境变量envp。如果找不到filename,则execve报错返回到主程序,否则不返回,也就是调用一次返回零次。此时新的程序加载进内存,并加载.text等内容
6.5 Hello的进程执行
(以下格式自行编排,编辑时删除)
Hello执行的时候,操作系统内核会为他建立一个进程控制块PCB,储存进程的相关信息,包含进程标识符(PID)、程序计数器、CPU 寄存器内容、进程状态、优先级及分配的时间片等信息
当程序的PCB被建立时,他的PCB 会被加入到就绪队列中,等待调度
当调度到该程序的时候,内核会加载该程序的上下分,随后刷新TLB
当该进程时间片耗尽,内核会将当前进程的状态(寄存器、程序计数器、内存映射等)保存到 PCB,然后加载另一个进程的 PCB 以恢复其状态
当进程在用户态执行系统调用或发生硬件中断时,CPU 会切换到内核态以执行相应的内核代码。
完成系统调用后,内核将返回到用户态,并继续执行进程的下一条用户指令
6.6 hello的异常与信号处理
所有异常的种类:
- 中断 来自IO设备,异步,会返回到下一条指令
- 陷阱 来自用户程序,同步,会返回到下一条指令
- 故障 可恢复的错误,同步,不一定返回到当前指令
- 终止 不可恢复的错误,同步,不返回
所有信号的种类:

图42 信号表
Hello执行过程中的异常:
Hello执行中会出现IO的中断和系统调用产生的陷阱异常,IO中断由操作系统处理发送给程序,返回到下一条指令,陷阱在操作系统处理之后将相应的返回值返回用户程序
Hello执行过程中的信号:
正常运行:

图43 正常运行
打印10次信息,然后键入任意字符终止
按下Ctrl+C:

图44 Ctrl+C
发送SIGINT信号,由shell捕获并且发送给用户程序,用户程序终止后shell回收用户程序
按下Ctrl+Z:

图45 Ctrl+Z
shell捕获到SIGSTP信号,挂起用户进程
使用PS:

图46 Ps
Ps显示出了暂停的程序hello
使用jobs:

图47 jobs
使用pstree:

图48 Pstree
输入fg %1:

图49 fg %1
会继续执行
输入kill :
会杀死执行的hello进程
不停乱按:

图50 不停乱按
乱按的内容会被放在stdin的缓冲区,最后按下enter会填入getchar中,其他的字符会传回shell
6.7本章小结
本章的解释了计算机系统中的进程概念与作用和shell概念与作用,通过一个hello程序,简要介绍了进程的概念和作用、shell的作用和处理流程,分析了hello程序的进程创建、启动和执行过程,最后,本章对hello程序中的异常,收到的信号以及运行结果中的各种输入说明了他们的处理流程。
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:
逻辑地址是 CPU 在执行指令时生成的地址,访问地址的指令给出的地址。该地址是相对地址,需要经过寻址方式的计算和变换得到真实地址。逻辑地址是由一个段标识符加上一个指定段内相对地址的偏移量。
线性地址:
线性地址是逻辑地址到虚拟地址变换之间的中间步骤,例如程序hello的代码会产生逻辑地址,在分段部件中逻辑地址是段中的偏移地址,加上基地址就是线性地址。
虚拟地址:
程序访问的地址一般为虚拟地址,虚拟地址经过页表的翻译得到物理地址。与实际物理内存容量无关,hello中访问的地址就是虚拟地址
物理地址:
在存储器里以字节为单位存储信息,每一个字节单元给一个唯一的存储器地址,这个地址称为物理地址,是hello程序最终访问的地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
在段式管理中,一个程序被划分为若干逻辑段(如代码段、数据段、堆栈段、共享段等),每个段都作为一个独立的实体存放。操作系统通过段表记录各段的信息,包括段起始地址、段长度和装入标志等,从而实现对不同段的访问与保护。
进程访问内存时使用的逻辑地址由两部分组成:段选择子与段内偏移。当 CPU 发出一条带有段选择子和偏移量的逻辑地址时,分段单元按以下步骤计算出线性地址:
从段选择子中提取索引,定位到某个段描述符,读取该段描述符的基址,然后将偏移量与基址相加。如果偏移量超过了描述符中记录的段界限,则触发段越界异常;否则得到合法的线性地址,供后续分页机制使用
7.3 Hello的线性地址到物理地址的变换-页式管理
页式管理下,进程的线性地址空间被划分为大小相同的虚拟页,每个虚拟页都可映射到物理内存中的任意物理页框。操作系统为每个进程维护一个页表,该表由一系列页表项构成。
当 hello 程序访问内存时,CPU 首先将所生成的线性地址划分为页号和页内偏移。MMU 会先在TLB中查询该虚拟页号对应的物理页框号,若命中则可直接拼接页内偏移得到物理地址。否则则逐级查询得到物理地址
7.4 TLB与四级页表支持下的VA到PA的变换
四级页表支持下,CPU产生虚拟地址VA,虚拟地址VA传送给MMU,MMUq取出VA的高位送入TLB查询,若TLB中存在,则得到物理地址。否则,MMU查询页表,通过寄存器中的内容确定第一级页表的起始地址,VPN1确定在第一级页表中的偏移量,查询出PTE,以此类推,最终在第四级页表中找到PPN,与VPO组合成物理地址,返回给应访问的内存处理部分
7.5 三级Cache支持下的物理内存访问
三级Cache下高速缓存的结构将地址位划分成了标记位,组索引位和块偏移位。如果选中的组存在一行有效并且Tag与地址中的Tag一致,则去除对应的结果作为最终结果。否则需要从存储器层次结构的下一层中取出被请求的块,然后将新的块存储在组索引位指示组中的一个高速缓存行中
7.6 hello进程fork时的内存映射
当进程调用 fork() 时,内核首先为新进程分配一个唯一的 PID,并复制父进程的 task_struct、mm_struct 以及关联的虚拟内存区域和页表结构。这种复制并非将所有物理页立即拷贝到子进程,而是生成相同的页表副本,并将所有可写页面和内核映射标记为只读,以便后续处罚写时复制机制
在父或子进程试图写入某个页面时,MMU 捕获到该只读页面的写入操作,触发缺页中断;内核随后为该页面分配新的物理页,将原内容复制过去,并更新对应进程的页表,使写入操作在自己的私有页上进行

图51 fork后的地址空间
7.7 hello进程execve时的内存映射
当进程调用 execve() 时,内核会执行驻留在内核区域的启动加载器代码,将当前进程的用户态程序完全替换为hello所包含的程序,以 hello 程序替代当前程序。
首先,内核会删除已存在的用户区域,也就是移除当前进程虚拟地址空间中所有旧的 VMA结构,令该进程在用户态的地址空间变为空。
接着,内核映射私有区域:为新的 .text、.data、.bss以及用户栈和堆分别创建新的 VMA
.text 和 .data 区直接通过私有映射关联到 hello 可执行文件中的相应段,这样写操作才会触发写时复制;.bss、堆和栈则映射为匿名区域,内存页面初始内容全零
然后,内核 映射共享区域,将 hello 程序所依赖的动态库加载到各自的共享 VMA 中。从而在用户虚拟空间中产生对应的共享段
7.8 缺页故障与缺页中断处理
出现缺页故障的时候,产生故障异常,内核调用缺页处理程序。进行如下步骤:
(1)检查虚拟地址是否合法,如果不合法则触发一个段错误终止进程。
(2)检查进程是否有读、写或执行该区域页面的权限,如果不具有则触发保护异常,程序终止。
(3)后面内核选择一个待转移的页面,如果该页面被修改过则将其交换出去,换入新的页面并更新页表。然后将控制转移给hello进程
7.9本章小结
本章介绍了hello的存储器地址空间、intel的段式管理、hello的页式管理,以及虚拟地址到物理地址通过页表的转换,Cache的访问。同时分析了hello进程fork时的内存映射、execve时的内存映射、缺页故障与缺页中断处理的过程
结论
hello所经历的过程:
从硬盘上的文本文件开始,需要经过以下过程
1、预处理:将hello.c进行预处理,将文件调用的所有外部库文件嵌入,替换文本,生成一个hello.i。
2、编译:将hello.i文件翻译成为汇编语言文件hello.s。
3、汇编:将hello.s翻译机器语言,然后生成可重定位目标文件hello.o。
4、链接:将hello.o文件和动态链接库链接起来,生成一个可执行目标文件hello。
5、运行。在shel1中输入./hello 2023113236 杨程铄 13730192086 1。
6、创建进程。终端调用fork函数创建一个新的子进程。
7、加载程序。Fork出的进程调用execve函数,映射虚拟内存,加载文件内容,进入程序入口
8、执行指令:CPU为进程分配时间片,在一个时间片中,hello享有CPU资源,顺序执行自己的指令
9、访问内存:先从线性地址转化为虚拟地址,然后MMU将程序中使用的虚拟内存地址通过页表映射成物理地址。
10、信号管理:当程序在运行的时候。当输入Ctrl+z时,内核会发送SIGTSTP信号给进程,并将前台作业停止挂起。输入Ctrl+C,内核会发送SIGINT信号给进程并终止前台作业
11、终止:当子进程执行完成时,内核安排父进程回收子进程,将子进程的退出状态传递给父进程。内核删除为这个进程创建的所有数据结构。
感悟:
我感受到程序执行既简单又复杂,简单在每一部分执行的内容都是清晰明了的,模块化的完成自己的工作。复杂在所有的模块组合起来会互相影响,使得想要窥见这个过程全貌的人无从下手。而这门课程正是提供了一个机会,一步步剖析过程,让我能了解到计算机执行程序的过程
附件
hello.i 通过预处理器cpp得到的预处理文件
hello.s 通过编译器cc1得到的汇编文件
hello.o 通过汇编器as得到的可重定位目标文件
hello.o.asm 反汇编hello.o 得到的汇编文件
hello.asm 反汇编hello 得到的汇编文件
hello.o.elf readelf得到hello.o的elf信息
hello.elf readelf得到hello的elf信息
参考文献
[1] Randal E.Bryant David R.O'Hallaron.深入理解计算机系统(第三版).机械工业出版社,2016.
[2] https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
[3] https://en.wikipedia.org/wiki/Page_table
[4] linux2.6 内存管理——逻辑地址转换为线性地址(逻辑地址、线性地址、物理地址、虚拟地址) - 刁海威 - 博客园
[5] https://en.wikipedia.org/wiki/Dynamic_linker
1243





