计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算学部
学 号 2023112327
班 级 23L0509
学 生 朱永久
指 导 教 师 史先俊
计算机科学与技术学院
2025年5月
本文深入剖析了 Hello 程序从源代码到运行进程的完整生命周期,涵盖 P2P(Program to Process) 和 020(Zero to Zero) 两个核心阶段。在 P2P 阶段,Hello 经历预处理、编译、汇编和链接,最终生成可执行文件;运行时通过 fork 和 execve 加载并执行。020 阶段描述了程序从创建到退出的全过程,涉及存储管理、进程调度和异常处理。实验基于 AMD Ryzen 7 7840H 处理器、16GB RAM 和 Ubuntu 18.04 LTS 环境,使用 GCC、EDB 和 Objdump 等工具。通过对预处理、编译、汇编、链接及运行时存储管理的详细分析,揭示了计算机系统各层次的协同工作,帮助读者理解程序执行机制。
关键词:进程管理;链接管理;进程与加载;
目 录
第1章 概述................................................................................... - 4 -
1.1 Hello简介............................................................................ - 4 -
1.2 环境与工具........................................................................... - 4 -
1.3 中间结果............................................................................... - 4 -
1.4 本章小结............................................................................... - 4 -
第2章 预处理............................................................................... - 5 -
2.1 预处理的概念与作用........................................................... - 5 -
2.2在Ubuntu下预处理的命令................................................ - 5 -
2.3 Hello的预处理结果解析.................................................... - 5 -
2.4 本章小结............................................................................... - 5 -
第3章 编译................................................................................... - 6 -
3.1 编译的概念与作用............................................................... - 6 -
3.2 在Ubuntu下编译的命令.................................................... - 6 -
3.3 Hello的编译结果解析........................................................ - 6 -
3.4 本章小结............................................................................... - 6 -
第4章 汇编................................................................................... - 7 -
4.1 汇编的概念与作用............................................................... - 7 -
4.2 在Ubuntu下汇编的命令.................................................... - 7 -
4.3 可重定位目标elf格式........................................................ - 7 -
4.4 Hello.o的结果解析............................................................. - 7 -
4.5 本章小结............................................................................... - 7 -
第5章 链接................................................................................... - 8 -
5.1 链接的概念与作用............................................................... - 8 -
5.2 在Ubuntu下链接的命令.................................................... - 8 -
5.3 可执行目标文件hello的格式........................................... - 8 -
5.4 hello的虚拟地址空间......................................................... - 8 -
5.5 链接的重定位过程分析....................................................... - 8 -
5.6 hello的执行流程................................................................. - 8 -
5.7 Hello的动态链接分析........................................................ - 8 -
5.8 本章小结............................................................................... - 9 -
第6章 hello进程管理.......................................................... - 10 -
6.1 进程的概念与作用............................................................. - 10 -
6.2 简述壳Shell-bash的作用与处理流程........................... - 10 -
6.3 Hello的fork进程创建过程............................................ - 10 -
6.4 Hello的execve过程........................................................ - 10 -
6.5 Hello的进程执行.............................................................. - 10 -
6.6 hello的异常与信号处理................................................... - 10 -
6.7本章小结.............................................................................. - 10 -
第7章 hello的存储管理...................................................... - 11 -
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 -
7.9动态存储分配管理.............................................................. - 11 -
7.10本章小结............................................................................ - 12 -
第8章 hello的IO管理....................................................... - 13 -
8.1 Linux的IO设备管理方法................................................. - 13 -
8.2 简述Unix IO接口及其函数.............................................. - 13 -
8.3 printf的实现分析.............................................................. - 13 -
8.4 getchar的实现分析.......................................................... - 13 -
8.5本章小结.............................................................................. - 13 -
结论............................................................................................... - 14 -
附件............................................................................................... - 15 -
参考文献....................................................................................... - 16 -
第1章 概述
1.1 Hello简介
P2P(Program to Process)描述了源代码到运行时进程的完整转换链条:从hello.c的预处理、编译、汇编、链接四个构建阶段,到Shell通过fork-execve机制创建子进程,再到进程运行时涉及的存储管理、指令执行,最终由父进程回收资源的全过程。这一概念聚焦程序如何被转化为活的进程并在系统中运作。
020(Zero to Zero)则从生命周期视角出发:程序从磁盘上的虚无状态(Zero)开始,经编译链接获得实体;被加载为进程后进入存在状态(Running);通过内存分配、CPU调度实现功能;最终随进程终止返回虚无(Zero)。其核心在于揭示程序从诞生到消亡的完整存在周期,包括资源申请、使用与释放的全过程。
1.2 环境与工具
1.2.1硬件环境
处理器: AMD Ryzen 7 7840H w/ Radeon 780M Graphics 3.80 GHz
RAM:16GB
系统类型:64 位操作系统, 基于 x64 的处理器
1.2.2软件环境
Windows11 64位
VMware,Ubnuntu 18.04LTS
1.2.3开发与调试工具
Gcc,edb,objdump
1.3 中间结果
hello.c:原始hello程序的C语言代码
hello.i:预处理过后的hello代码
hello.s:由预处理代码生成的汇编代码
hello.o:二进制目标代码
hello:进行链接后的可执行程序
1.4 本章小结
本章首先分析 hello 程序的生命周期,包括 编译链接(P2P) 和 进程运行至终止(020) 的机制;随后,给出实验的 运行环境配置,并解释各 中间产物(如 .i, .s, .o 文件)的作用。
第2章 预处理
2.1 预处理的概念与作用
概念:
预处理是C/C++编译过程的第一个阶段,由预处理器对源代码进行文本级别的处理,生成纯C代码的中间文件.i文件,为后续编译阶段做准备。
作用:
头文件包含:将#include指定的头文件内容完整插入到当前文件中。
宏替换:将程序中所有的宏定义(#define)进行文本替换。
条件编译:根据#if、#ifdef等条件编译指令选择性地包含或排除代码块。
注释删除:移除所有注释(//和/* */)。
特殊指令处理:执行#pragma等编译器特定指令。
2.2在Ubuntu下预处理的命令
图1.预处理指令
2.3 Hello的预处理结果解析
图2.hello.i预处理
解析:展开所有头文件内容,如 stdlib.h 的函数声明。
处理宏和条件编译 ,删除未启用的代码块。
删除注释 ,仅保留有效代码。
保留原始逻辑,main() 函数代码基本不变。
2.4 本章小结
本章系统阐述了在Linux环境下对C语言程序进行预处理操作的完整流程。首先详细介绍了预处理指令的使用方法及其在编译过程中的关键作用,随后以hello.c程序为实例,具体演示了在Ubuntu系统中执行预处理操作的全过程。通过对预处理生成文件hello.i的深入解析,可以观察到该文件不仅包含了原始程序代码,还融合了标准输入输出库stdio.h的全部内容,同时整合了各类宏定义、常量声明等编译元素。特别值得注意的是,预处理后的文件中完整保留了源代码的行号标记和条件编译指令,这些信息对于后续的编译和调试工作具有重要价值。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:
编译是C/C++程序构建的核心阶段,由编译器将预处理后的中间代码(.i文件)转换为汇编语言代码(.s文件),完成从高级语言到低级语言的转换。
作用:
语法分析:检查代码是否符合C/C++语法规范。
语义分析:验证变量声明、类型匹配等语义正确性。
代码优化:对程序进行基础优化以提高执行效率。
生成汇编代码:将高级语言转换为目标架构的汇编指令。
3.2 在Ubuntu下编译的命令
图3.编译指令
3.3 Hello的编译结果解析
图4.hello.s编译
图5.hello.s编译(续)
解析:
- 节区定义
text:存放程序指令(main函数代码)
rodata:只读数据段(存储字符串常量)
note.GNU-stack:栈不可执行标记
note.gnu.property:GNU特性标识
- main函数框架
图6.main函数框架
endbr64 // 控制流强制完整性保护
pushq %rbp // 保存调用者栈帧指针
movq %rsp, %rbp // 建立新栈帧
subq $32, %rsp //分配局部变量空间
- 参数处理
argc存储在-20(%rbp)
argv指针存储在-32(%rbp)
严格遵循System V AMD64调用约定
图7.参数处理
cmpl $5, -20(%rbp) // argc与5比较
je .L2 // 条件跳转
leaq .LC0(%rip), %rdi // RIP相对寻址加载字符串
call puts@PLT // 优化为puts调用
- 循环结构实现
图8.循环结构实现
movl $0, -4(%rbp) // 循环计数器i=0
.L3:
cmpl $9, -4(%rbp) // 循环条件检查
jg .L4 // 条件跳转
- 参数访问模式
使用基址偏移量访问argv元素
movq -32(%rbp), %rax // argv基址
addq $24, %rax // argv[3]偏移
- 函数调用优化
printf调用前正确设置参数寄存器:
movq %rax, %rsi // argv[1]
movq %rdx, %rdi // argv[2]
movq %rcx, %rdx // argv[3]
- 系统调用封装
使用PLT即过程链接表实现延迟绑定:
call sleep@PLT // 通过PLT调用sleep
- 高级优化
指令选择优化:
使用leaq替代mov实现高效地址计算
条件跳转指令优化为jg而非jle
安全增强:
endbr64指令支持Intel CET
栈帧指针验证(CFI指令)
数据访问优化:
字符串常量使用RIP相对寻址(.LC0(%rip))
局部变量通过栈帧精确访问
3.4 本章小结
本章系统阐述了程序构建过程中的汇编阶段。通过执行gcc -S -o hello.s hello.i命令,我们实现了从预处理后的中间代码(.i文件)到汇编代码(.s文件)的关键转换。这一转换过程本质上是将高级程序设计语言转化为处理器可直接理解的底层指令集,在计算机体系结构的抽象层次中扮演着承上启下的重要角色。
第4章 汇编
4.1 汇编的概念与作用
概念:
汇编(Assembly)是程序编译过程中的关键阶段,指将汇编语言源代码(.s文件)转换为机器可执行的二进制目标代码(.o文件)的过程。这一转换由汇编器(Assembler)完成,其本质是建立机器指令与符号化汇编代码之间的一一映射关系。
作用:
指令转换
将助记符(如mov、call)转换为二进制机器码
实现汇编指令到特定架构(如x86、ARM)指令集的精确映射
符号解析
建立标签与内存地址的对应关系
处理局部符号的引用关系
数据转换
将汇编中的数据定义转换为二进制形式
处理字符串常量的存储格式
重定位信息生成
标记需要链接阶段处理的地址引用
生成符号表和重定位表
4.2 在Ubuntu下汇编的命令
图9.汇编指令
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
图10.hello.o的ELE头
使用readelf -h hello.o查看ELF头,ELF头以一个16字节的序列(Magic,魔数)开始,这个序列描述了生成文件的系统的字的大小和字节顺序。ELF头剩下部分的信息包含帮助连接器语法分析和解释目标文件的信息。其中包括ELF头的大小、目标文件的类型和机器类型等,如上图
4.3.2Section头
图11.hello.o的Section头
使用命令readelf -S hello.o查看节头,夹在ELF头和节头部表之间的都为节,包含了文件中出现的各个节的语义,包括节
的类型、位置和大小等信息。
名称 | 内容 |
.text | 程序的可执行代码 |
.rela.text | 与.text相关的重定位信息 |
.data | 程序的初始化数据 |
.bss | 程序的未初始化数据 |
.rodata | 只读数据 |
.comment | 编译器或工具链生成的注释信息 |
.note.GNU-stack | 指示栈是否可执行 |
.note. gnu.propert NOTE | 包含GNU特定的属性信息 |
.eh_frame | 异常处理框架信息 |
.rela.eh_frame | 与.eh_frame相关的重定位信息 |
.symtab | 程序中定义和引用的符号信息 |
.strtab | 符号表中符号名称的字符串 |
.shstrtab | 段头表中段名称的字符串 |
4.4.3符号表
图12.符号表
使用命令readelf -s hello.o查看符号表,在符号表中,Num为某个符号的编号,Name是符号的名称。Size表示他是一个位于.text节中偏移量为0处的146字节函数。Bind表示这个符号是本地的还是全局的,由上图可知main函数名称这个符号变量是本地的
4.3.4可重定位信息
图13.重定位信息
使用readelf -r hello.o查看可重定位段信息,offset是需要被修改的引用的节偏移,Sym.标识被修改引用应该指向的符号。Type告知连接器如何修改新的引用,Addend是一个有符号常数,一些类型的重定位要使用它对被修改的引用的值做偏移调整。
4.4 Hello.o的结果解析
图14.hello.o反汇编
使用objdumo -d -r hello.o命令对hello.o可重定位文件进行反汇编
在程序编译过程中,汇编阶段生成的机器代码与原始的汇编代码存在一些重要区别。通过objdump工具反汇编.o文件可以看到,每条汇编指令前面都有一组十六进制的机器代码,这些二进制编码才是CPU真正能够执行的指令。
机器代码与汇编代码的主要差异体现在地址处理方式上。在汇编代码中,分支跳转使用直观的标签,而机器代码中则转换为具体的相对偏移地址。对于函数调用,汇编代码直接使用函数名,但在机器代码中表现为call指令后跟着0x0占位,这是因为这些外部函数的实际地址需要等到链接阶段才能确定。
重定位机制是这一过程的关键。汇编器会在.o文件中生成重定位条目,记录哪些地址需要在链接时修正。例如,对于printf这样的库函数调用,汇编器会留下空白地址,并在.rela.text节中添加重定位信息,由后续的链接器负责填充正确的函数地址。这种设计实现了灵活的模块化编程,使得代码可以在不同内存地址加载执行。
图15.分支转跳
图16.分支转条(续)
伪指令部分:反汇编代码中,出现在汇编代码开头的伪指令消失了
4.5 本章小结
本章系统性地阐述了程序构建过程中的汇编阶段。首先明确了汇编的基本概念及其在编译流程中的关键作用,详细剖析了可重定位目标文件(Relocatable Object File)的组成结构。通过实际反汇编操作,深入分析了机器代码与汇编代码的对应关系及其差异特征。最终生成的hello.o文件包含了完整的机器指令、符号表和重定位信息,为后续链接阶段的地址解析和代码合并奠定了必要基础。
第5章 链接
5.1 链接的概念与作用
概念:
链接是程序构建过程的最终阶段,由链接器(Linker)将多个可重定位目标文件(.o文件)和库文件(.a/.so)合并,生成可执行文件(Executable)或共享库(Shared Library)。链接的核心任务是解析符号引用,合并代码与数据段,并分配运行时内存地址,使程序能够正确加载和执行。
作用:
符号解析(Symbol Resolution)
将每个符号引用(如函数、变量)与其定义关联起来。
若某个符号未定义,链接器会报错(如undefined reference to 'printf')。
地址重定位(Relocation)
将目标文件中的相对地址转换为最终的内存地址。
合并代码与数据(Section Merging)
将不同目标文件的.text(代码)、.data(全局变量)、.rodata(只读数据)等段合并。
库文件处理(Library Handling)
静态链接(Static Linking):将库代码(如libc.a)直接复制到可执行文件。
动态链接(Dynamic Linking):运行时加载共享库(如libc.so),减少内存占用。
生成可执行文件(Executable Creation)
最终生成的文件包含ELF头(描述文件结构)、程序头(加载信息)和完整的机器代码。
5.2 在Ubuntu下链接的命令
图17.hello.o链接指令
5.3 可执行目标文件hello的格式
5.3.1ELF头
图18.hello的ELE头
使用readelf -h hello查看,可以看到hello的ELF头中Type处显示的是EXEC,表示时可执行目标文件,这与hello.o不同。hello中的节的数量为30个。
5.3.2Section头
图19.hello的Section头
输入readelf -S hello查看Section头, 节头部表对hello中所有信息进行了声明,包括了大小(Size)、偏移量(Offset)、起始地址(Address)以及数据对齐方式(Align)等信息。根据始地址和大小,我们就可以计算节头部表中的每个节所在的区域。
5.3.3符号表
图20.hello的符号表
使用readelf -s hello查看符号表发现经过链接之后符号表的符号数量陡增,说明经过连接之后引入了许多其他库函数的符号,一并加入到了符号表中。
5.3.4可重定位信息
图21.hello的可重定位段信息
使用readeld -r hello查看可重定位段信息
5.4 hello的虚拟地址空间
图22.hello的虚拟地址
在edb中打开hello,可看到起始地址为0x401000,结束地址为0x401ff0。
根据5.3.2节里面的Section头部表,我们可以找到对应的节的其实空间对应位置,例如.init初始化节,起始位置地址为0x401000在edb中有其对应位置
图22.hello的虚拟地址(续)
5.5 链接的重定位过程分析
图23.hello的反汇编
使用objdump -d -r hello查看hello可执行文件的反汇编条目
可以观察到,hello的反汇编代码与hello.o的返汇编代码在结构和语法上是基本相同的,只不过hello的反汇编代码多了非常多的内容,我们通过比较不同来看一下区别
- 虚拟地址不同,hello.o的反汇编代码虚拟地址从0开始,而hello的反汇编代码虚拟地址从0x400000开始。这是因为hello.o在链接之前只能给出相对地址,而hello在链接之后得到的是绝对地址。
图24.Hello的反汇编(续)
图25.Hello的反汇编(续)
- 反汇编节数不同, hello.o只有.text节,里面只有main函数的反汇编代码。而hello在main函数之前加上了链接过程中重定位而加入的各种在hello中被调用的函数、数据,增加了.init,.plt,.plt.sec等节的反汇编代码。
- 跳转指令不同,hello.o中的跳转指令后加的主要是汇编代码块前的标号,而hello中的跳转指令后加的则是具体的地址,但相对地址没有发生变.
5.6 hello的执行流程
图25.程序初始位置
首先,程序停在0x7fa5:8bfed100处,这是hello使用的动态链接库ld-2.2.27.so的入口点_dl_start,然后,程序跳转到_dl_init,在经过了一系列初始化后,跳到hello的程 序入口点_start,然后程序通过call指令跳到动态链接库ld-2.27.so的_libc_start_main 处,这个函数会进行一些必要的初始化,并负责调用main函数,下一步,程序调用动态链接库中的__cxa_atexit函数,它会设置在程序结束时需要调用的函数表,然后返回到__libc_start_main继续,然后调用hello可执行文件中的__libc_csu_init函数,这函数是由静态库引入的,也是做一些初始化的工作,然后程序返回到__libc_start_main继续,紧接着程序调用动态链接库里的_setjmp函数,设置一些非本地跳转,然后返回到__libc_start_main继续,正式开始调用main函数,由于我们在edb运行hello的时候并未给出额外的命令行参数,因此它会在第一个if处通过exit(1)直接结束程序,通过hello本身携带的exit函数,程序会跳转;
之后,在进行了若干操作后,程序退出。
图27.程序退出
5.7 Hello的动态链接分析
程序调用一个有共享库定义的函数时,编译器无法acacas预测函数在运行时的具体地址,因为定义这个函数的共享模块可能可以被加载到任何位置。因此,编译系统采用延迟绑定,将过程地址的绑定推迟到第一次调用该过程的时候。
延迟绑定需要用到两个数据结构:GOT(Global Offset Table,全局偏移表)和PLT(Procedure Linkage Table,过程链接表)。
.plt:PLT是一个数组,其中每个条目是16字节代码。PLT[0]是一个特殊条目,它跳转到动态链接器中。每个被可执行程序调用的库函数都有它自己的PLT条目。每个条目都负责调用一个具体的函数。
.got:GOT是一个数组,其中每个条目是8字节地址。和PLT联合使用时,GOT[O]和GOT[1]包含动态链接器在解析函数地址时会使用的信息。GOT[2]是动态链接器在1d-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数,其地址需要在运行时被解析。每个条目都有一个相匹配的PLT条目。
hello在动态连接器加载前后的重定位是不一样的,在加载之后才进行重定位
5.8本章小结
本章介绍了连接的过程。解释了程序是如何进行重定位的操作,把相同类型的数据放在同一个节的过程,同时也说明了链接的工作原理
第6章 hello进程管理
6.1 进程的概念与作用
概念: 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础
作用: 在运行一个进程时,我们的这个程序好像是系统当中唯一一个运行的程序,进程的作用就是提供给程序两个关键的抽象。一分别是独立的逻辑控制流和私有的地址空间
6.2 简述壳Shell-bash的作用与处理流程
作用: Shell是命令语言解释器,是一个应用程序,它连接了用户和 Linux 内核,让用户能够更加高效、安全、低成本地使用 Linux 内核。
处理流程:首先对命令行参数求值,判断命令行是否为空,如果不为空则判断第一个命令行参数是不是一个内置的命令,如果是一个内置命令则直接执行,否则检查是否是一个应用程序。之后在搜索路径里寻找这些应用程序,如果键入的命令不是一个内部命令并且路径里没有找到这个可执行文件,则会显示一条错误信息。如果能够成功找到命令,那么该内部命令或者应用程序将会被分解为系统调用并传递给linux内核
6.3 Hello的fork进程创建过程
在Linux系统中,用户可以通过 ./ 指令来执行一个可执行目标文件。在程序运行时,Shell就会创建一个新的进程,并且新创建的进程更新上下文,在这个新建进程的上下文中便可以运行这个可执行目标文件。fork()函数拥有一个int型的返回值。子进程中fork返回0,在父进程中fork返回子进程的Pid。新创建的进程与父进程几乎相同但有细微的差别。子进程得到与父进程虚拟地址空间相同的一份副本,并且子进程拥有与父进程不同的Pid
6.4 Hello的execve过程
当你在终端中输入 ./hello 并按下回车时,shell(如 Bash)会调用 execve 系统, 控制权从用户空间转移到内核空间, 调用execve会将这个进程执行的原本的程序完全替换,它会删除已存在的用户区域,包括数据和代码;然后,映射私有区:为Hello的代码、数据、.bss和栈区域创建新的区域结构,所有这些区域都是私有的、写时才复制的;之后映射共享区;最后把控制传递给当前的进程的程序入口
6.5 Hello的进程执行
6.5.1逻辑控制流
逻辑控制流是一个PC值的序列,PC值就是程序计数器的值,这些值与可执行目标文件的指令或者包含在运行时动态链接到程序的共享对象中的指令一一对应。
6.5.2时间流
进程轮流使用处理器,每个进程执行它的流的一部分,两个流如果在执行的时间上有所重叠,那么这两个流是并发流, 一个进程与其他进程轮流运行的概念称为多任务。一个进程执行其控制流一部分的每一个时间段叫做时间片,多任务也就被称作是时间分片
6.5.3用户模式与内核模式
用户模式: 用户模式中,进程不允许执行特权指令,例如发起一个I/O操作等,更重要的是不允许直接引用地址空间中内核区内的代码和数据。如果在用户模式下进行这样的非法命令执行,会引发致命的保护故障。
内核模式: 内核模式下,进程的指令执行相对没有限制
6.5.4进程上下文切换
上下文就是内核重新启动一个被抢占的进程所需要的状态,它由通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构等对象的值构成。进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这样的决策叫做调度,由内核中的调度器的代码处理。在这个抢占过程中需要用到上下文切换,上下文切换保存当前进程的上下文,恢复先前某个被抢占的上下文,并将控制传递给新恢复的进程。
6.6 hello的异常与信号处理
6.6.1异常
类型 | 原因 | 异步/同步 | 返回行为 |
中断 | 来自I/O设备的信号 | 异步 | 返回到下一条行为 |
陷阱 | 有意的异常 | 同步 | 返回到下一条行为 |
故障 | 潜在可恢复的错误 | 同步 | 可能返回到当前指令 |
终止 | 不可恢复的错误 | 同步 | 不返回 |
6.6.2信号
SIGINT:当用户按下Ctrl+C时发送,通常用于中断程序。
SIGTSTP:当用户按下Ctrl+Z时发送,用于暂停程序。
SIGTERM:请求程序终止的正常信号。
6.6.3处理
(1)乱按
图28.乱按输出
在键盘中乱打字并没有改变printf的输出,不影响程序的正常运行
(2)ctrl+z
图29.ctrl+z输出
Ctrl+Z的功能是向进程发送SIGSTP信号,进程接收到该信号之后会将该作业挂起,但不会回收
图30.ps输出
使用ps可知hello进程仍在运行中
图31.jobs输出
使用jobs,可知hello的后台job id=1
图32.fg输出
使用fg指令让后台作业运行,可知挂起前后共输出10次
(3)ctrl+c
图33.ctrl+c输出
直接终止前台作业
(4)不停按回车
图34.回车输出
不影响程序运行,但是回车不仅在printf时显示,在hello执行完毕后,回车同样进行了多次
6.7本章小结
本章概述了hello进程大致的执行过程,阐述了进程、shell、fork、execve等相关概念,之后从逻辑控制流、时间分片、用户模式/内核模式、上下文切换等角度详细分析了进程的执行过程。并在运行时尝试了不同形式的命令和异常,每种信号都有不同处理机制,针对不同的shell命令,hello会产生不同响应。
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:程序代码经过编译后出现在汇编程序中地址。逻辑地址由选择符(在实模式下是描述符,在保护模式下是用来选择描述符的选择符)和偏移量(偏移部分)组成。
线性地址:线性地址空间是一个非负整数的集合。逻辑地址经过段机制后转化为线性地址,为描述符:偏移量的组合形式。在调试hello时,gdb中查看到的就是线性地址,或者虚拟地址。
虚拟地址:虚拟地址空间是0到N的所有整数的集合(N是正整数),是线性地址空间的有限子集。分页机制以虚拟地址为桥梁,将硬盘和物理内存联系起
7.2 Intel逻辑地址到线性地址的变换-段式管理
intel平台下,逻辑地址的格式为段标识符:段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节。分段机制将逻辑地址转化为线性地址的步骤如下:首先使用段选择符中的偏移值在GDT或LDT表中定位相应的段描述符,然后利用段选择符检验段的访问权限和范围,以确保该段可访问,最后把段描述符中取到的段基地址加到偏移量上,最后形成一个线性地址
7.3 Hello的线性地址到物理地址的变换-页式管理
线性地址(VA)到物理地址(PA)之间的转换通过分页机制完成。分页机制类似主存和Cache之间的分块机制,分页机制对虚拟地址和物理内存进行分页,页的大小通常是4KB到2M(因时而异,时过境迁,页的大小有所不同)。在x86-64机器上,虚拟地址空间的N是2的48次方,有256TB,比正常的硬盘大得多。
在分页机制中,硬盘空间的每个字节到虚拟地址空间的每个字节存在映射关系,且这个映射是单射。虚拟地址空间和硬盘空间都以字节为单位,从0开始编地址号。设硬盘空间为H,虚拟地址空间为V,设他们之间的映射关系为,f是单射,则于是,我们知道了物理地址中某个地址所在页与虚拟空间的页的对应关系,也就知道了物理地址中某个地址所在页与硬盘中某个页的对应关系。
物理地址中某个地址所在页与虚拟空间的页的对应关系要通过什么来记录呢?分页机制中使用一个叫做页表的数据结构来记录这些关系,页表也是存储在内存中的,是由操作系统维护的。其实DRAM到Cache中也是类似机制,只不过DRAM到Cache的高速缓存机制是用硬件实现的。
每个进程都有一个页表,页表中的每一项,即PTE(页表条目),记录着该对应的虚拟地址空间的那一页是否有效(即是否有对应的物理内存上的页),物理页的起始位置或磁盘地址,访问权限等信息。PTE根据不同的映射状态也被划分为三种状态:未分配、未缓存、已缓存
7.4 TLB与四级页表支持下的VA到PA的变换
页表是 PTE(页表条目)的数组,它将虚拟页映射到物理页,每个 PTE 都有一个有效位和一个 n 位地址字段,有效位表明该虚拟页是否被缓存在 DRAM 中。虚拟地址分为两个部分,虚拟页号(VPN)和虚拟页面偏移量(VPO)。其中VPN需要在PTE中查询对应,而VPO则直接对应物理地址偏移(PPO)
在从VA翻译得到PA的过程中,MMU首先用VPN向TLB申请请求对应的PTE,如果命中,那么直接跳过后面的步骤;之后MMU生成PTE地址,从高速主存请求得到PTE,高速缓存或主存会向MMU返回PTE。若PTE有效位为0,说明缺页,MMU触发缺页异常,缺页处理程序确定物理内存中的牺牲页(若页面修改,则换出到磁盘)。之后缺页处理程序调入新的页面,并更新PTE。之后却也处理程序返回原进程,并重新执行导致缺页的指令
7.5 三级Cache支持下的物理内存访问
在寻找一个虚拟地址时,CPU会优先到TLB中寻找,查看VPN是否已经缓存。如果页命中的话,就直接获取PPN;如果没有命中的话就需要查询多级页表,得到物理地址PA,之后再对PA进行分解,将其分解为标记(CT)、组索引(CI)、块便宜(CO),之后再检测物理地址是否在下一级缓存中命中。若命中,则将PA对应的数据内容取出返回给CPU;若不命中,则重复上述操作,直到找到
7.6 hello进程fork时的内存映射
当fork函数被调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本,并将两个进程中的每个界面都标记为只读,将两个进程中的每个区域结构都标记为私有的写时复制。
当fork在新进程中返回时,新进程现在的虚拟内存搞好和调用fork时存在的虚拟内存相同。这两个进程中的任意一个后来进行写操作时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念
7.7 hello进程execve时的内存映射
execve 函数调用驻留在内核区域的启动加载器代码,在当前进程中加载并运 行包含在可执行目标文件 hello 中的程序,用 hello 程序有效地替代了当前程序。 加载并运行 hello 需要以下几个步骤:
删除已存在的用户区域,删除当前进程虚拟地址的用户部分中的已存在的区域结构。
映射私有区域,为新程序的代码、数据、bss 和栈区域创建新的区域结构。
映射共享区域, hello 程序与共享对象 libc.so 链接,libc.so 是动态链 接到这个程序中的,然后再映射到用户虚拟地址空间中的共享区域内。
设置程序计数器(PC),execve 做的最后一件事情就是设置当前进程 上下文的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
缺页故障:一个虚拟页没被缓存在DRAM中,即DRAM缓存不命中被称为缺页。当CPU引用了一个页表条目中的一个字,而该页表条目并未被缓存在DRAM中,地址翻译硬件从内存中读取该页表条目,从有效位为0可以判断尚未被缓存,进而触发缺页异常
缺页中断处理: 程序会选择一个牺牲页,将其在物理内存中删除,加入所需要访问的VP3。随后返回重新执行原指令,则页命中。这种策略称为按需页面调度。
7.9本章小结
本章介绍了Hello和操作系统之间的交流方式。介绍了hello的存储器地址空间、intel的段式管理、hello的页式管理,介绍了Hello是如何经过地址翻译从而找到最终的物理地址。阐释了TLB加速地址翻译、多级缓存相关的要点。
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
(以下格式自行编排,编辑时删除)
8.2 简述Unix IO接口及其函数
(以下格式自行编排,编辑时删除)
8.3 printf的实现分析
(以下格式自行编排,编辑时删除)
8.4 getchar的实现分析
(以下格式自行编排,编辑时删除)
8.5本章小结
(以下格式自行编排,编辑时删除)
(第8章 选做 0分)
结论
- 预处理阶段(cpp):hello.c源文件经过预处理器处理,展开所有宏定义和包含的头文件,生成扩展后的hello.i文件。
- 编译阶段(ccl):编译器将预处理后的hello.i文件转换为汇编语言,输出hello.s文件。
- 汇编阶段(as):汇编器将hello.s中的汇编指令转换为机器码,生成可重定位目标文件hello.o。
- 链接阶段(ld):链接器将hello.o与必要的库文件合并,解析所有外部引用,最终生成可执行的hello文件。
- 程序执行:在shell中输入"./hello 2022112040 qdx 15845895165 4"命令并执行。
- 进程创建:shell识别到外部命令,通过fork()系统调用创建子进程。
- 程序加载:通过execve()系统调用启动加载器,建立虚拟内存映射,将程序载入内存并跳转到入口点。
- 指令执行:CPU为进程分配时间片,hello进程在其时间片内顺序执行指令流。
- 内存访问:MMU通过页表将程序使用的虚拟地址转换为物理内存地址。
- 信号处理:运行时,Ctrl+c会发送SIGINT信号终止进程,Ctrl+z则发送SIGTSTP信号挂起进程。
- 进程终止:子进程结束后,父进程回收其资源,内核清除所有相关数据结构。
这个看似简单的程序执行过程,实际上展现了计算机系统工作的精妙机制。它提醒我们,在技术领域,越是基础的操作往往蕴含着越深刻的设计智慧。就像hello程序一样,我们应当追求在简单中见深度,在平凡处显不凡。现代计算机系统的优雅之处,正是体现在这些基础组件的完美协作上,这也是我们作为学习者应该领悟和追求的工程美学。
附件
列出所有的中间产物的文件名,并予以说明起作用。
文件名 | 功能 |
Hello.c | 源代码 |
Hello.i | 预处理后产生的文件 |
Hello.s | 汇编语言文件 |
Hello.o | 可重定位目标文件 |
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.
(参考文献0分,缺失 -1分)