
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机与电子通信
学 号 2023112643
班 级 23L0512
学 生 马敬崎
指 导 教 师 史先俊
计算机科学与技术学院
2025年5月
本文从hello.c程序的角度出发,探究了其从编译到回收的一生。首先从源程序预处理、编译、汇编、链接这四个基本角度出发,将.c文件转化为可执行目标程序。接着使用shell(bash)执行hello,在运行过程中,CPU、Cache、I/O、主存等与程序紧密配合,在操作系统的调度下hello文件顺利完成创建进程、加载、回收的全过程。本文为理解程序底层运行逻辑提供了完整的分析框架。
关键词:进程管理;存储管理;预处理;编译;汇编;链接;
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第1章 概述
1.1 Hello简介
P2P:
用户使用文本编辑器编写hello.c文件并保存,此时只是一个纯文本文件,存在磁盘上。用户输入指令对hello.c文件进行处理,首先编译器驱动程序读取源程序文件hello.c并进行预处理,预处理器读取系统头文件内容并直接插入程序文本,得到另一个程序hello.i;编译器(cc1)将文本文件翻译成文本文件hello.s,即汇编语言程序;汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成可重定位目标程序,存入hello.o;链接器(ld)将hello程序调用的函数和库文件与hello.o合并,得到了可执行文件hello。用户输入./hello指令,在bash中shell调用fork复制当前shell进程,子进程调用execve加载hello的程序代码,内核分配进程描述符、进程ID、虚拟地址空间、文件描述符表等,内核通过文件系统从磁盘读取hello,建立内存映射,设置执行上下文,最终执行main函数。实现了从Program到Process的过程。
020:
程序从用户编写存入磁盘,到预处理、编译、链接形成了可执行文件,实现了从0到1。用户输入指令执行可执行文件,在bash里操作系统生成子进程、加载进程、建立内存映射、分配时间片,使得程序能够在硬件上执行。
程序执行结束后,内核将进程状态标记为僵死进程,释放部分内存。父进程shell调用waitpid读取子进程退出状态,清除僵死进程条目,内核彻底释放残留的内核资源(进程描述符、PID、页表等),实现了程序从1到0。
1.2 环境与工具
1.2.1软件
Windows11 64位;Vmware14;Ubantu20.04
1.2.2硬件
X64 CPU;16GB RAM;512GB Disk;4.6GHz
1.2.3开发与调试工具
VScode、vim、gcc
1.3 中间结果
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。

图1 中间结果
以上为本论文用到的中间结果文件名字
| dis_hello.s | 从hello.o反汇编得到的文本文件 | 比较重定位前后汇编代码区别 |
| dis_hello2 | 从hello反汇编得到的文本文件 | 比较链接前后汇编代码区别 |
| hello | 可执行目标文件 | 执行hello程序 |
| hello.c | 源程序 | 源程序 |
| hello.i | 预处理得到的程序 | 分析预处理的作用 |
| hello.o | 可重定位目标文件 | 反汇编、链接中使用 |
| hello.s | 汇编程序 | 和.o反汇编比较,分析汇编程序的特点 |
1.4 本章小结
本章主要对hello的P2P、020过程进行了了解和学习,选择本次大作业开发的软硬件和调试工具,汇总中间结果。为后面章节的具体实现打下良好的基础。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
2.1.1概念
预处理是编译过程的第一步,由预处理器(cpp)对源代码进行文本级处理,在实现编译之前进行。能对源程序中以字符#开头的命令进行处理,读取系统头文件内容并直接插入程序文本中,得到了.i文件
2.1.2作用
根据以#开头的命令修改原始的C程序,对代码进行文本替换和宏扩展,生成最终的源代码文件。能够根据环境的不同(操作系统、调试模式)选择性地包含代码。宏替换能有效减少重复代码,提高代码的复用性,整体结构更加清晰,便于维护和调试。
2.2在Ubuntu下预处理的命令
cpp hello.c > hello.i

图2 命令

图3 结果
2.3 Hello的预处理结果解析


图4 预处理结果
经过预处理后得到的hello.i文件在代码行数上远远多于最初的hello.c文件,这是因为在预处理过程中,将#开头的头文件程序、宏定义、特殊符号等插入到了代码当中,而原来的main函数则在.i文件的最末端。程序先对stdio.h、unistd.h、stdlib.h中的内容进行展开,如果头文件中仍然有以#开头的内容则继续展开,直到文本文件中没有头文件、宏定义等。
2.4 本章小结
本章针对预处理的概念和执行过程进行了分析,并在虚拟机中将hello.c预处理为hello.i程序,通过观察对比两文件中的内容加深了对预处理文本替换和宏扩展等作用的理解。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
3.1.1编译的概念
编译是指编译器(cc1)将预处理后的源代码.i文件转换为汇编代码.s的过程,它包含一个汇编语言程序。主要完成对词法、语法、语义的分析,代码优化,以及最后的生成目标平台的汇编程序。
3.1.2编译的作用
编译可以实现跨平台适配,将高级语言转化为与硬件架构相关的汇编代码,使得程序能够在不同的CPU上运行;编译可以实现代码优化,通过编译器的优化能够有效提升代码性能;编译可以实现错误检查,在编译阶段暴露语法错误,避免运行时崩溃;编译可以为汇编器提供输入,生成的.s文件是人类可读的汇编代码,便于调试、修改,了解编译器优化的内容等。
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s

图5 命令

图6 结果
3.3 Hello的编译结果解析

图7 汇编程序
3.3.1数据
3.3.1.1常量

图8 字符串常量
这里定义了两个字符串常量,分别为"用法: Hello 学号 姓名 手机号 秒数!\n"和"Hello %s %s %s\n"。

![]()
图9 立即数常量
.s文件中还存在许多立即数常量,比如这里的9和0。
3.3.1.2变量

图10 变量
这里可以看出局部变量i存放在栈中,在for循环中每次加一,条件满足就跳转,这对应着原C程序中int i和for(i=0;i<10;i++)
3.3.1.3表达式
对于原C语言中的表达式argc!=5,在.s文件中表示为
cmpl $5, -20(%rbp)
je .L2
将5与栈中数值进行比较,满足条件就跳转。
对于C程序中的i=0和i<10,.s文件中表示为movl $0, -4(%rbp)和cmpl $9, -4(%rbp)。
3.3.1.4类型
类型解析可以通过汇编指令的选择和操作数大小来实现,针对不同长度的数选用的汇编指令大不相同。


图11 mov指令的不同
如这里的movq和movl语句分别传送四字和双字。
3.3.2赋值
程序中i=0的赋值如下:

图12 赋值
将参数的值从寄存器移到栈上:

图13 存栈
3.3.3算术操作
程序中的加法操作体现在每次循环对i进行加一操作,.s文件中该操作如下:
![]()
图14 加法
3.3.4关系操作
程序中的关系操作体现在argc!=5的判断,和i<10的判断,.s文件中该操作如下:
![]()
![]()
图15 关系操作
其中和10的比较变成和9的比较(因为是判断小于10)
3.3.5数组、指针、结构操作
main函数中使用了argv数组,需要依次访问argv[1]/argv[2]/argv[3]/argv[4]

图16 数组
3.3.9控制转移
在for循环中,当i小于10会退出循环,在.s文件中体现如下:

图17 控制转移
每次对i加一,最后和9比较大小(因为是小于10),当i不满足小于等于9就退出循环。
3.3.10函数操作
3.3.10.1传递参数
puts调用将字符串地址(错误提示信息)存入 %rdi(第一个参数)
![]()
图18 puts传参
exit调用时先将退出状态码1存入%edi
![]()
图19 exit传参
printf调用时先将argv数组的值传入寄存器

图20 printf传参
atoi调用时先将argv的值传入寄存器

图21 atoi传参
3.3.10.2函数调用
结合前文所述,从.s中观察,调用所有函数如下puts、exit、printf、atoi、sleep、getchar。其中getchar和sleep调用如下:

图22 函数调用
3.3.10.3函数返回
ret 指令用于将程序的控制权返回到调用该函数的位置,.cfi_endproc 表示函数返回。

图23 函数返回
atoi函数调用返回值在%eax中传入%edi(sleep的参数):
![]()
图24 atoi返回
3.4 本章小结
本章针对汇编程序.s的概念和作用进行了系统的分析,通过与原始hello.c文件进行对比,细致地分析了C语言中数据和操作是如何在汇编语言中实现的。对汇编语言中各种指令、寄存器、内存的读写、函数调用等有了更深的认识。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
4.1.1汇编的概念
汇编是指将汇编代码.s文件转化为机器码(.o目标文件)的过程。它是编译过程的最后一步,主要由汇编器完成,最终生成可重定位的目标文件,供后续链接器使用。
4.1.2汇编的作用
汇编可以将汇编语言变成机器码,汇编代码是机器指令的人类可读形式,而汇编器会将其转换为二进制机器码;汇编可以生成可执行文件,目标文件包含代码段、数据段、符号表、重定位信息等;汇编可以处理伪指令,部分伪指令不会被直接翻译成机器码,而是指导汇编器如何组织目标文件。
4.2 在Ubuntu下汇编的命令
as hello.s -o hello.o

图25 命令

图26 结果
4.3 可重定位目标elf格式

图27 ELF文件
4.3.1文件头
先查看ELF头,其包含了ELF头大小、目标文件类型、机器类型、入口点地址、标志、节表偏移等。首先以一个16字节的序列开始,描述了生成该文件的系统的字大小和字节顺序。

图28 文件头
4.3.2节头部表
节头部表中描述了hello.o文件中各个节,包括节的名称、类型、地址、偏移量等。

图29 节头部表
4.3.3符号表
在符号表中存放程序定义和引用的函数和全局变量的信息,可以看到hello中的函数,例如puts、exit、printf、atoi、sleep、getchar。

图30 符号表
4.3.4重定位条目
当汇编器生成一个目标模块时,并不知道数据和代码最终存放在内存的什么位置,也不知道这个模块引用的任何外部定义的函数或者全局变量的位置。当汇编器遇到一个对最终位置未知的目标引用,他就会生成一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。
R_X86_64_PC32是重定位时使用一个32位PC相对地址的引用。将在指令中编码的32位值加上PC的当前运行值得到有效地址。R_X86_64_32是重定位时使用一个32位的绝对地址的引用,通过绝对寻址CPU直接使用在指令中编码的32位值作为有效地址,不需要进一步修改。

图31 重定位节
4.4 Hello.o的结果解析
先生成反汇编文件,指令如下:
![]()
图32 指令
得到反汇编文件如下:

图33 反汇编文件
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
代码末尾指令基本相同,.s是由汇编语言组成的,而反汇编得到的代码既有汇编代码也有机器语言代码,即多包含了二进制编码。
例如当进行分支转移时,.s文件是跳转代码段,例如L1、L2等,反汇编文件中是直接跳转到当前地址加上偏移量得到目标代码地址。在函数调用时,.s文件的call指令后面跟函数名字,而反汇编文件是函数名和函数地址的信息,跳转到重定位条目所指示的目标地址。在数据访问时,汇编语言会使用[array]变量名,而反汇编文件使用二进制内存地址。
4.5 本章小结
本章通过将.s转换为.o文件,分析了汇编的概念及其作用。通过查看可重定位ELF格式,对不同的节、ELF头、节头部表等都有了更深的理解。最后将.o文件利用objdump反汇编得到反汇编文件,对比分析.s文件与反汇编文件,更能发现重定位对程序的作用和影响,了解到汇编语言和机器语言之间的映射关系。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
5.1.1链接的概念
链接是将多个目标文件(.o)和库文件合并,生成可执行文件或者动态库的过程,它的核心任务是解析符号引用、分配内存地址,最终生成可以被操作系统加载运行的二进制程序。
5.1.2链接的作用
链接可以对.o中未定义的符号进行解析,找到符号的实际地址,并合并相同的符号;链接可以进行地址分配,将目标文件中的相对地址转换为最终的绝对内存地址;链接可以合并库文件,通过静态链接将库代码直接复制到可执行文件中,通过动态链接仅记录依赖关系,运行时由操作系统加载。
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

图34 链接命令

图35 结果
5.3 可执行目标文件hello的格式
5.3.1ELF头
可以观察到节头数量、进程头大小等发生了变化。

图36 ELF头
5.3.2节头表
节头表展示了27个节的名称、类型、地址、偏移量、大小等信息,节头表中包含了部分动态链接生成的段,基本信息如下:


图37 节头表
5.4 hello的虚拟地址空间
使用edb加载hello,得到hello进程虚拟地址空间的各段信息。hello的虚拟地址从00401000开始。

图38虚拟地址起始
为了与5.3对比分析,我们选择.data节,从5.3可知.data段的偏移为3048,虚拟地址为00404048,我们在edb中找到对应的虚拟地址,可以找到与.data段有关的数据信息在右侧栏中。

图39 .data虚拟地址
同理我们也能找到.rodata段和.text段的位置:


图40 其他段虚拟地址
5.5 链接的重定位过程分析
对hello进行反汇编
![]()
图41 反汇编指令

图42 反汇编结果
不同之处在于,本次反汇编文本中新增了_init、getchar、printf等函数,新增了.init节、.plt节、.plt.sec节。且通过call进行函数调用时直接显示函数的绝对地址和函数名,没有关于重定位类型和偏移量的说明。
在反汇编文件中,每行指令都有唯一的虚拟地址,但是hello.o的反汇编只有偏移地址,原因是hello已经经过链接器链接后完成了重定位,每条指令的虚拟地址都被确定。
链接首先要求链接器扫描所有的.o文件,建立全局符号表,检查未定义符号是否有定义。接着对节进行合并,合并到最终的可执行文件,修改代码中的临时地址为实际运行时的地址(利用绝对地址重定位和相对地址重定位)
在hello.o中并没有链接,所以需要告诉链接器在链接时采用的链接方式和执行的动作,指示在对应位置发生重定位动作。
5.6 hello的执行流程
以下为hello的执行流程
hello!_init地址为0x0000000000401000
hello!start地址为0x0000000000401100
hello!main地址为0x0000000000401125
hello!_fini地址为0x0000000000401140
主要函数包括puts、printf、getchar、atoi、exit、sleep

图43 主要函数
使用gdb/edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程(主要函数)。请列出其调用与跳转的各个子程序名或程序地址。
5.7 Hello的动态链接分析
在进行动态链接前首先需要进行静态链接,生成部分链接的可执行文件hello,如果要分析动态链接项目的变化,可以查看动态链接前后的GOT/PLT变化,由前文节头表可知,.got.plt位于00404000处。利用edb查看动态链接前的段内容如下:

图44 静态链接
在动态链接后,.got.plt段的内容如下:

图45 动态链接
在动态链接前,.got.plt中的函数地址条目初始指向PLT中对应的指令序列,而.plt条目则被设计为首次调用时触发动态链接器解析。当程序首次调用共享库函数时,控制流通过PLT跳转到GOT中的初始地址,这会触发动态链接器(通过PLT[0])解析实际函数地址,并将结果回填到对应的GOT条目中。所以动态链接前后.got.plt的内容发生了变化,这种延迟绑定保证了位置无关代码的灵活性,又最小化了运行时性能损耗。
5.8 本章小结
本章通过连接生成可执行文件hello,分析了链接的概念和作用;对比可执行文件ELF与可重定向文件ELF的差异分析了重定位和链接的流程。通过比较静态连接时和动态链接后段内容的变化分析了动态链接的独特之处。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
6.1.1进程的概念
进程是计算机中正在运行的程序实例,是操作系统进行资源分配和调度的基本单位。每个进程都拥有独立的地址空间、代码、数据和系统资源(如文件、内存、CPU时间等),所以看起来好像进程独享处理器和内存。
6.1.2进程的作用
进程是程序运行的载体,操作系统通过进程管理程序的执行流程;不同进程拥有独立的内存空间,防止相互干扰;操作系统通过进程调度可以实现多任务并行;进程可以运行在不同的权限级别,如用户进程和内核进程,提高系统安全性。
6.2 简述壳Shell-bash的作用与处理流程
6.2.1壳的作用
shell是用户与操作系统内核之间的桥梁,可以解析用户输入的命令,调用对应的程序执行;可以运行自动化脚本(.sh),支持流程控制;维护环境变量,影响程序行为;启动、停止、管理进程,例如&后台运行,jobs查看任务;进行I/O重定向,管理输入输出流。
6.2.2壳的处理流程
当用户在终端输入指令后,bash先从标准输入或脚本文件读取命令。接着bash会将命令拆分为单词进行解析,进行变量扩展、通配符扩展、命令替换等。然后执行命令,判断是否为内置命令,如果是内置命令就直接由shell处理,如果是外部程序需要构建参数和环境变量,fork创建子进程、execve加载程序替换自身、wait等待子进程结束回收。命令执行后会返回相应的状态码,0表示成功,非0表示错误.
6.3 Hello的fork进程创建过程
当用户在shell上输入指令,例如./hello,这不是一个内置命令,shell会认为这是一个可执行目标文件,所以shell进程调用fork( ),创建一个子进程。子进程是父进程虚拟地址空间的完整副本,包括代码段、数据段、堆、共享库、用户栈。在父进程中fork返回子进程的PID,子进程中fork返回0。采用写时复制的方法,子进程共享父进程的内存空间,仅在修改时触发复制。
6.4 Hello的execve过程
子进程调用exec函数,加载hello程序。删除已存在的用户区域,丢弃原shell子进程的代码和数据;映射私有区域,为新程序的代码、数据、bss和栈区域创建新的区域结构,加载hello的.text代码、.data数据到内存;映射共享区域,如果程序与共享对象链接,例如libc.so,这些对象都是动态链接到这个程序的,再映射到用户虚拟地址空间的共享区域内;设置程序计数器,使之指向代码区域的入口点。exec()成功后,子进程PID不变,内容完全变成hello程序。如果exec()失败,子进程调用exit()终止。如果命令未使用后台运行符&,shell会调用wait()阻塞,直到子进程结束。当子进程退出后,shell回收其资源并提示用户继续输入。
6.5 Hello的进程执行
当初始化堆栈和程序计数器指向main入口后,进程调度器会根据时间片轮转算法,将新创建的hello进程加入到就绪队列中等待CPU调度。当hello进程获得CPU时间片开始执行。每个进程都用自己的上下文信息,包括寄存器状态、进程控制块等,是独立的副本。当进程hello获得时间片时,调度器会保存当前进程的上下文,加载下一个运行进程hello的上下文。当进程在执行用户代码时处在用户态,如果进程调用系统函数时,会从用户态转换为核心态。当调用结束后又会触发中断返回用户态。当时间片结束或者I/O发生时,中断会触发从用户态向核心态的转换,由操作系统进行内存调度。
当hello程序执行完毕后,进程调用exit()函数,通知操作系统进程已经结束。从用户态切换到核心态,操作系统清理子进程部分资源,向父进程(shell)发送子进程终止信号,父进程对僵死进程进行回收。
6.6 hello的异常与信号处理
hello执行过程中会产生用户中断输入异常,产生SIGINT信号,会直接终止进程;会产生挂起异常,产生SIGTSTP信号,暂停进程;会产生子进程终止异常,产生SIGCHLD信号,默认忽略。

图46 不停乱按
不停乱按、回车,字符暂时存在终端输入缓冲区,程序不主动读取则不会处理,所以乱按并不会影响./hello的执行。当进程执行结束后,shell会将回车前输出的字符串当做命令

图47 Ctrl+Z
当程序执行过程中按下Ctrl Z,进程收到SIGSTP信号,暂时挂起hello进程,向父进程发送SIGCHLD。

图48 ps
当进程被挂起时输入ps会展示进程的运行状态。

图49 jobs
当进程被挂起时输入jobs命令可以看到后台作业。

图50 pstree
当挂起时输入pstree可以查看进程树

图51 fg
当挂起时输入fg可以恢复挂起进程,使其回到前台继续执行

图52 kill
当挂起时输入kill可以杀死对应进程,此时再次查看jobs内容可以发现只剩进程1
![]()
图53 jobs
当进程执行过程中输入Ctrl C,内核会发送SIGINT信号终止进程

图54 Ctrl+C
通过ps查看可以发现hello进程被回收。

图55 ps
6.7本章小结
本章主要分析如何管理hello进程,包括进程的概念、作用,shell(bash)的作用和执行流程,hello的fork和execve过程,以及hello进程的执行过程。详细分析了进程执行过程中可能会产生的异常和信号,通过实际操作观察不同信号的处理机制,对不同shell命令,hello的响应也大不相同。
(第6章2分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址由两部分组成,段标识符和段内偏移。逻辑地址是程序代码中直接使用的原始内存地址,在编译器生成机器码时决定。一个逻辑地址,是由一个段标识符加上一个指定段内相对地址的偏移量,表示为 [段标识符:段内偏移量]。例如当我们访问一个全局变量时,mov eax, [global],global的逻辑地址为.data段偏移量。
线性地址跟逻辑地址类似,也是一个不真实的地址,是由逻辑地址经过分段机制转换后的32/64位连续地址空间,线性地址=段基地址+段偏移地址。
虚拟地址在分页机制启用时与线性地址等价,是操作系统和开发者视角的同一地址空间。当一个程序运行时,变量和函数的地址都是在虚拟地址空间中的位置,虚拟地址空间是操作系统为程序分配的独有区域。
物理地址是实际的硬件内存地址,虚拟地址由MMU通过页表进行地址转换,映射到物理地址得到。物理地址用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。
7.2 Intel逻辑地址到线性地址的变换-段式管理
在段式内存管理中,逻辑地址到线性地址的转换是通过段寄存器、段描述符、段偏移量三者共同完成的。段寄存器(CS/SS/DS等)存储16位的段选择符,段选择符有三个字段,高13为表示索引,索引段描述符(段表项)在全局描述符表GDT或局部描述符表LDT中的位置。第二位TI,指示使用GDT(TI=0)还是LDT(TI=1),最低两位RPL指定请求特权级,第0级表示内核态,3表示用户态。段描述符是8字节的数据结构,包含32位段基址、20位段限长以及关键控制位。当CPU访问内存时,硬件首先通过段选择符定位段描述符,提取段基址后与32位段偏移量相加形成线性地址。段访问可能出现不命中的情况,包括段不存在(段描述符P=0,可能未加载也可能磁盘上没有)、权限违规、偏移越界、无效段选择符。
7.3 Hello的线性地址到物理地址的变换-页式管理
这里的线性地址可以视作虚拟地址,hello程序的线性地址到物理地址转换是通过多级页表机制完成的,分页机制将线性地址分解为虚拟页面偏移量和虚拟页号,根据虚拟页面偏移量先查快表TLB,TLB命中则取出物理页号和虚拟页面偏移量连接成物理地址。如果快表TLB不命中,则查询四级页表,找出物理页号。如果页表不命中就需要从磁盘写入物理内存,更新页表,重复上面的动作。
7.4 TLB与四级页表支持下的VA到PA的变换
7.4.1TLB
TLB是快表,是一个小的、虚拟内存的缓存。查询快表时间极短,甚至可以忽略不计。其中每一行保存着一个由单个PTE组成的块。当处理器给出虚拟地址后,先查询快表中是否有对应目标,如果命中则和虚拟页面偏移链接成物理地址。
7.4.2四级页表
单级页表占用内存十分严重,所以采用层次结构的页表来压缩页表,一级页表中每个PTE负责映射虚拟地址空间中一个4MB的片,每个片都是由连续的页面组成。如果片i中每个页面都没分配,那么一级PTE i就为空。对于四级页表,虚拟地址被划分为4个VPN和1个VPO。第四级页表中的每个PTE包括着某个物理页面的PPN,或者一个磁盘块的地址。为了构造物理地址,在能够确定PPN之前,MMU必须访问4个PTE,PPO和VPO相同。
7.4.3转换过程
CPU首先在TLB中查找虚拟地址的映射,如果命中则直接使用缓存的物理地址,如果未命中,则需要进行页表查找。
利用VPN1在一级页表中查找,得到的结果加上VPN1*8在二级页表中查找,如此下去最终在第四级中查找到PPN,将PPN与PPO相连得到物理地址。
物理地址=物理页号+物理页面偏移量
7.5 三级Cache支持下的物理内存访问
在CPU通过页表转换后获得物理地址后,需要经过三级Cache的协同工作来访问物理内存。
一级Cache最接近CPU,分为指令Cache和数据Cache,一级Cache大小通常很小,但速度极快。二级Cache紧接着一级,容量比一级大,速度稍慢于一级Cache。三级Cache是多核心共享的,大小达到了MB级别,速度慢于二级。
当CPU发出内存访问请求时,CPU生成一个物理地址来访问数据,首先在L1缓存中查找对应物理地址,如果命中则返回数据给CPU,不命中则发送请求给二级缓存。二级缓存查找物理地址,如果命中则返回数据给CPU,同时替换一级缓存中的块。如果二级未命中则在三级缓存中查找物理地址,如果命中则三级缓存返回数据给CPU,同时将块与L2、L1替换,如果三级未命中则发送请求给主存。
主存返回数据给CPU,同时将数据块依次填入L3、L2、L1。
当多核并发访问同一个物理地址是,MESI协议保证缓存一致性,当前核心独占修改权,写回内存后才能被其他核心读取;当前核心独占缓存,数据与内存一致;多核心共享只读缓存;缓存行无效,需要重新加载。
7.6 hello进程fork时的内存映射
当进程调用fork时,操作系统会创建新的子进程,并将父进程所有资源复制到子进程。对于hello进程来说,shell就是它的父进程。当子进程获得父进程完整虚拟地址空间的副本,当这两个进程中有一个进程试图进行写操作时,写时复制机制会创建新的页面。
通过写时复制技术,父子进程会共享相同的物理内存,当有一个进程进行写操作时CPU触发缺页异常,内核动态分配新物理页并复制原内容,更新当前进程的页表指向新副本,另一进程仍保留原映射。这种机制既避免了fork()时立即拷贝全部内存的开销,又确保了进程间内存隔离性(如全局变量修改互不影响)。
7.7 hello进程execve时的内存映射
当调用execve()加载./hello程序时,Linux内核会完全重置进程的内存空间。销毁原内存布局,释放进程现有的所有内存映射(代码、数据、堆栈),但保留文件描述符、信号处理等非内存属性。
接着程序读取hello可执行文件的ELF头部,识别代码段、数据段、未初始化数据段等区域。然后建立新内存映射,将代码,数据映射到对应区域,共享对象由动态链接映射到进程共享区域,设置PC指向代码区域入口点,最终完成从旧进程到hello的切换。
通过这种方式execve可以在当前进程的上下文中运行一个新程序,而不需要创建新进程,既能彻底替换进程镜像,又能高效利用物理内存。
7.8 缺页故障与缺页中断处理
当进程访问的虚拟地址未映射到物理内存时(页表有效位为0)会发生缺页中断。此时内核会首先检查访问的合法性,如果是合法访问那么就从磁盘加载数据,选择一个牺牲页换入新的页面并更新页表,异常处理程序返回,CPU重新启动引起缺页的指令。这次MMU就能正常翻译虚拟地址而不会产生缺页中断了。
如果是访问一个不存在的页面,异常处理程序会触发段错误,终止进程。如果违反许可,写一个只读的页面,异常处理程序触发一个保护异常终止进程。
7.9动态存储分配管理
动态内存管理通过malloc和free函数在堆区实现内存的按需分配与释放,主要采用空闲链表和内存池两种策略。malloc首次调用时会通过brk或mmap系统调用向操作系统申请大块内存,并基于隐式或显式空闲链表管理空闲块,分配时采用首次适配、最佳适配或分离空闲链表等算法快速查找合适内存块,同时通过边界标记和合并空闲块减少碎片;free释放内存时仅标记空闲状态,延迟归还操作系统以提升重用效率。printf等函数调用malloc时触发内存分配,高效的管理策略能显著降低频繁小内存申请的性能开销。
7.10本章小结
本章从hello存储器地址空间的四种地址出发,分别探讨了段式管理和页式管理实现地址转换的方式。接着研究了虚拟地址到物理地址转换的具体步骤,查TLB表、查四级页表。取到物理地址后进一步研究物理内存三级Cache访问的过程。在明晰了翻译和访存流程和虚拟地址的逻辑后,重新探讨了hello进程的fork和execve在虚拟内存中的作用。最后研究全过程中可能会出现的异常以及处理流程,在面对动态存储分配时程序也有一套分配管理机制。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
(以下格式自行编排,编辑时删除)
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
(以下格式自行编排,编辑时删除)
8.3 printf的实现分析
(以下格式自行编排,编辑时删除)
[转]printf 函数实现的深入剖析 - Pianistx - 博客园
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
(以下格式自行编排,编辑时删除)
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
(以下格式自行编排,编辑时删除)
(第8章 选做 0分)
结论
hello程序先经过预处理阶段,从hello.c变为hello.i,#后的头文件加入到源程序中。
对hello.i文件进行编译,从hello.i变为hello.s,生成可阅读的汇编语言程序。
对hello.s文件进行汇编,从hello.s变为hello.o,生成机器码、ELF重定位条目。
对hello.o文件和其他.o文件进行链接(静态或者动态.so),变为hello可执行文件。
使用fork和execve为hello创建、加载进程。
printf调用malloc进行堆分配。
异常处理程序、信号对进程进行异常处理。
当hello执行完毕后exit,内核回收部分资源,父进程回收僵死子进程。
在设计与实现计算机系统的过程中,我切身感受到了层次化设计的威力,不论是在提高速度、增大内存方面均起到了强大作用。L1、L2、L3三级Cache和CPU、内存实现了访存的高效化。TLB、四级页表、虚拟地址、物理地址、磁盘实现了地址翻译的高效化。
通过本次大作业,我对书本上的知识有了更系统的理解,独立的章节之间也有着极强的关联性,在未来的学习生活中我将在此基础上继续深耕,更加透彻、全面的了解计算机的世界。
(结论0分,缺失-1分)
附件

图 56 中间产物
以上为本论文用到的中间产物文件名字
| dis_hello.s | 从hello.o反汇编得到的文本文件 | 比较重定位前后汇编代码区别 |
| dis_hello2 | 从hello反汇编得到的文本文件 | 比较链接前后汇编代码区别 |
| hello | 可执行目标文件 | 执行hello程序 |
| hello.c | 源程序 | 源程序 |
| hello.i | 预处理得到的程序 | 分析预处理的作用 |
| hello.o | 可重定位目标文件 | 反汇编、链接中使用 |
| hello.s | 汇编程序 | 和.o反汇编比较,分析汇编程序的特点 |
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] [转]printf 函数实现的深入剖析 - Pianistx - 博客园
[2] https://blog.youkuaiyun.com/zoomdy/article/details/80700750
[3] https://blog.youkuaiyun.com/TYUTyansheng/article/details/108148566
[4] 深入理解计算机系统 第三版
(参考文献0分,缺失 -1分)
1137






