计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算机与电子通信类
学 号 2023111664
班 级 23L0503
学 生 陶唯先
指 导 教 师 刘宏伟
计算机科学与技术学院
2024年5月
本报告详细阐述了程序“Hello's P2P”从源代码到可执行文件,再到进程执行的全生命周期过程。通过分析预处理、编译、汇编、链接等阶段,揭示了程序如何在计算机系统中被转换为可执行文件。进一步探讨了进程管理、存储管理及I/O管理等操作系统核心机制,展示了程序从静态代码到动态进程的完整流程。
报告首先介绍了P2P和O2O的概念,描述了程序从文本文件到内存中进程的转变,以及资源从初始状态到最终释放的过程。随后,通过具体实验环境,逐步分析了预处理、编译、汇编和链接的中间结果,并对比了可重定位目标文件与可执行目标文件的差异。
在进程管理部分,报告详细说明了Shell的作用、fork和execve的流程,以及进程执行中的异常与信号处理。存储管理部分则涵盖了地址空间转换、页式管理、TLB与Cache机制等内容。最后,简要介绍了Linux的I/O设备管理方法及Unix I/O接口函数。
通过本次实验,深入理解了计算机系统各层次的协同工作,从底层硬件到操作系统,再到高级语言程序的完整执行流程,体现了计算机系统设计的复杂性与精密性。
关键词:预处理;编译;链接;进程管理;存储管理;I/O管理
目 录
第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 -
参考文献....................................................................................... - 16 -
第1章 概述
1.1 Hello简介
P2P即From Program to Process,他包括了Program阶段,Process阶段。Program阶段即从源码到可执行文件阶段,包括了预处理,编译,汇编,链接四个步骤;Process阶段即进程的诞生与执行阶段,包括了创建进程,内存管理,CPU调度,I/O与信号四个过程。
O2OFrom即Zero-0 to Zero-0,他包括了From Zero阶段,To Zero阶段。Hello从源码即文本文件,初始状态为“0”开始,历经编译、链接、加载,最终成为内存中的进程。进程执行完毕后,所有资源被释放,内存状态归“0”。
Hello的P2P和O2O过程展现了程序从静态代码到动态进程的生命周期,以及计算机系统的协同工作。揭示了底层技术的复杂性,而用户只需简单命令即可触发这一系列精密操作。
1.2 环境与工具
硬件:13th Gen Intel(R) Core(TM) i7-13700H 2.40 GHz
软件:Windows 11 64 位;VMware Workstation;Ubuntu 18.04
开发和调试工具:Visual Studio 2022 64位;CodeBlocks;vim;objump;edb;gcc ;readelf等
1.3 中间结果
文件 | 文件的作用 |
hello.o | 汇编后得到的可重定位目标文件 |
hello.s | 编译后得到的汇编语言文件 |
hello.i | 预处理后得到的文本文件 |
hello.elf | hello.o的elf文件 |
hello.sam | hello.o的反汇编文件 |
hello01.elf | 可执行文件hello的elf文件 |
hello.o_asm.txt | hello.o的反汇编文本文件 |
hello_asm.txt | 可执行文件hello的反汇编文本文件 |
1.4 本章小结
本章首先介绍了hello程序的P2P和020过程,通过两过程的详细介绍,阐述了程序在计算机中的生命周期,然后列出了为了撰写论文使用的软、硬件环境和开发与调试工具,最后列出了为编写本论文,生成的中间结果文件的名字,文件的作用。
第2章 预处理
2.1 预处理的概念与作用
概念:预处理是C/C++程序编译的第一个阶段,由预处理器Preprocessor在正式编译之前对源代码进行文本级别的处理。预处理器根据源代码中的预处理指令即以#开头的命令,如#include、#define,并对代码进行修改或扩展,生成一个预处理后的中间文件即.i文件,供后续编译阶段使用。
作用:(1)将指定的头文件内容直接插入到当前文件中。
(2)定义符号常量或宏函数,预处理器会将代码中的宏名称替换为对应的值或表达式。
(3)预处理器会移除所有注释,减少编译阶段的无关内容。
(4)处理特殊指令。
2.2在Ubuntu下预处理的命令
gcc -E hello.c -o hello.i
图2-1 Ubuntu下预处理命令
2.3 Hello的预处理结果解析
图 2-2-1 预处理结果
图 2-2-2 预处理结果
(1)第一部分为头文件的拼接
(2).c文件最开始的注释部分被删除
2.4 本章小结
本章论述了预处理的概念和作用,预处理就是主要对预处理指令进行处理,生成一个预处理后的中间文件即.i文件,在后续编译阶段中,将 .i 文件转变为 .s 文件即预处理后的文件到生成汇编语言程序。
第3章 编译
3.1 编译的概念与作用
概念:在 C/C++ 程序的编译过程 中,编译阶段(Compilation Proper) 是指将 预处理后的源代码(.i 文件) 转换为 汇编代码(.s 文件) 的过程
作用:将输入的预处理后的 .i 文件输出为包含汇编代码的.s 文件即机器指令的低级表示。包括以下四个核心步骤用:
(1)语法分析:检查代码是否符合语言规范。
(2)语义分析:检查变量类型、作用域等是否正确。
(3)优化:简化代码结构,提高执行效率。
(4)生成汇编代码:将高级语言转换为 CPU 可理解的汇编指令。
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s
图 3-1 Ubuntu下编译命令
3.3 Hello的编译结果解析
3.3.1 数据
(1)字符串
图 3-2-1 字符串
- 常量int argc
图 3-2-2 常量
被存放在栈上-20(%rbp)的位置。
(3)数组char *argv[]
图 3-2-3 数组
argv的首地址是-32(%rbp)
(4)局部变量 int i
图 3-2-4 局部变量
被存放在栈上-4(%rbp)的位置。
3.3.2 赋值
(1)i = 0
图 3-2-5 赋值语句
(2)各类MOV
图 3-2-6 各类MOV指令
movl传送双字;movq传送四字。
3.3.3 算术操作
(1)i++
图 3-2-7 算数操作i++
3.3.4 关系操作
(1)argc!=5
图 3-2-8 关系操作argc!=5
不等于5进行跳转。
(2)i<10
图 3-2-9 关系操作i<10
大于9进行跳转。
3.3.6 控制转移
(1)for循环
图 3-2-11 for循环
.L2为循环开始前的为i赋值的语句。.L3为for循环的判断语句,.L4为循环体中的循环部分,根据判断语句可知需要循环10次。
(2)if语句
图 3-2-12 if语句
cmpl指令将栈上的argv值和5进行比较,je为判断相等条件。如果为0,说明argv==5,则跳转进入for循环,否则执行if中的输出语句。
- 跳转指令
图 3-2-13 跳转语句
je判断是否相等,相等跳转。jmp间接跳转。jle判断小于等于,小于等于跳转。
3.3.7 函数操作
(1)main函数
main函数负责外部传入的参数为数组元素,分别存储在寄存器%rdi,%rsi中。并使用函数为分配和释放内存分配栈空间,结束时恢复栈空间
- printf函数
图 3-2-14 printf函数
(3)exit函数
图 3-2-15 exit函数
(4)getchar函数
图 3-2-16 getchar函数
(5)sleep函数
图 3-2-17 sleep函数
3.4 本章小结
本章首先介绍了了编译的概念和作用。然后结合hello.i的编译结果,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。
第4章 汇编
4.1 汇编的概念与作用
概念:汇编是由汇编器完成,将 汇编代码即.s文件 转换为 机器码即.o目标文件 的过程。
作用:(1)将汇编指令转换为机器码。
- 为标签(Label)和函数名分配临时地址。
- 生成重定位信息并标记需要链接器修正的地址。
(4)输出符合系统的目标文件。
4.2 在Ubuntu下汇编的命令
gcc -O hello.s -o hello.o
图 4-1 Ubuntu下汇编命令
4.3 可重定位目标elf格式
命令:readelf -a hello.o > hello.elf
图 4-2 Ubuntu下生成elf文件命令
- ELF头
图 4-3-1 elf头内容
ELF头描述了一个标准的x86-64 Linux共享库/动态链接可执行文件的结构布局。
- 节头
图 4-3-2 节头部分内容
节头描述了各个节的名称、索引、大小、偏移量等,并给明了节头标志。当链接器链接时,将各个文件的相同段合并成一个大段,并且根据这个大段的大小以及偏移量重新设置各个符号的地址。
- 程序头
图 4-3-3 程序头内容
程序头定义了 可执行文件在内存中的布局,明确了程序头数量有9 个,程序头大小为每个 56 字节,程序头偏移从文件偏移 0x40开始,操作系统加载器使用它来映射文件到进程的虚拟地址空间。
- 重定位节
图 4-3-4 重定位节内容
重定位节(Relocation Sections)存储了 动态链接过程中需要修改的地址信息,分为 .rela.dyn 和 .rela.plt 两部分。.rela.dyn偏移量为0x4b8,条目数为8;rela.plt偏移量为0x578,条目数为6。
- 符号表
符号表包含了动态符号表.dynsym以及完整符号表.symtab。动态符号表有12个条目用于动态链接,记录程序依赖的外部符号和导出的符号,动态符号表只包含动态链接所需的必要符号,在运行时由动态链接器使用且无法被strip命令移除,内容包括引用的外部函数、程序导出的全局符号以及特殊链接符号。完整符号表有68个条目包含程序所有的符号信息,主要用于调试和链接,完整符号表包含比.dynsym更全面的符号信息,可以被strip命令移除而不影响程序运行并且可以在静态链接和调试时使用。内容包括所有节区定义、局部符号和全局符号、函数和变量的详细地址信息以及调试信息。
图 4-3-5 符号表内容
- 版本控制节
版本控制节(.gnu.version)有12个条目,用于记录符号的版本信息,与.dynsym表一一对应。
图 4-3-6 版本控制节内容
- 版本需求节
声明程序依赖的库版本要求。
图 4-3-7 版本需求节内容
- 注释节
包含.note.ABI-tag和.note.gnu.build-id,主要存储元数据信息。
ABI标签节用于标识文件遵循的ABI规范,内容包括操作系统类型为Linux、ABI主版本号为3.2.0以及其他ABI相关标志。
ID节用于提供文件的唯一标识。
图 4-3-8 注释节内容
4.4 Hello.o的结果解析
命令:objdump -d -r hello.o
图 4-4-1 反汇编指令
图 4-4-2 部分反汇编内容
1. 机器语言的构成
机器语言是由二进制编码组成的计算机可直接执行的指令系统,在反汇编中通常以十六进制形式展现。其基本构成要素包括:
(1)操作码:1-3字节,指定操作类型
(2)操作数:0-4个,指定操作对象
(3)前缀:可选,修改指令行为
- 机器语言构成与汇编映射
2.1 指令格式差异
图 4-5-1 指令格式
汇编语言: sub $0x8,%rsp
机器码: 48 83 ec 08
48 : REX前缀
83 : 操作码(sub立即数到r/m)
ec : ModR/M(%rsp作为操作数)
08 : 立即数8
2.2 操作数差异
图 4-5-2 操作数差异
汇编语言: mov $0x1,%edi
机器码: bf 01 00 00 00
bf:mov edi, imm32
01 00 00 00:小端表示的1
2.3分支转移函数调用差异
图 4-5-3 分支转移函数调用差异
汇编语言: jne 798 <__do_global_dtors_aux+0x38>
机器码: 75 2f
75:jne操作码
2f:相对偏移量
- hello.o与hello.s对比
通过对比发现两者在指令结构和程序逻辑上高度一致,主要差异体现在以下几个方面:
(1)反汇编显示了每条指令的十六进制表示。
(2)从符号标签变为具体地址或偏移量。
(3)未解析的引用使用临时占位符。
(4)按最终内存布局排列。
3.1操作数差异
hello.s中的操作数:subq $32, %rsp
ello.o中的操作数:7ae: 48 83 ec 20 sub $0x20,%rsp
差异说明:汇编代码使用十进制数,反汇编中使用十六进制数。
3.2分支转移函数调用差异
hello.s中的条件跳转:jne .L2
hello.o反中的条件跳转:75 ea jne 7de <main+0x34>
差异说明:汇编代码使用标签,反汇编使用计算好的相对偏移。
4.5 本章小结
本章首先介绍了汇编的概念与作用,然后以hello.s的汇编文件为例,说明如何将其汇编成可重定位目标文件hello.o,再将其链接成ELF格式的可执行文件 hello.elf,展示了汇编语言与机器语言的关系。通过分析 hello.o 文件的反汇编代码以及原始汇编文件 hello.s 的区别和相同点,可以清晰地理解操作系统如何将人类可读的代码转化为机器可执行的二进制程序。
第5章 链接
5.1 链接的概念与作用
概念:链接是程序编译过程中的一个重要步骤,它将多个文件和库文件合并成一个可执行文件。链接的主要任务是解析符号引用如函数和变量,并将它们与实际的地址关联起来,从而生成一个完整的、可运行的程序。
作用:
(1)查找程序中所有未定义的符号如函数或变量,确保它们在目标文件或库中有定义。
(2)为程序中的代码和数据分配最终的内存地址,将分散在多个目标文件中的代码和数据合并到统一的地址空间中。
(3)根据最终的内存布局调整文件中的代码和数据的临时地址,确保程序运行时能正确访问所有符号。
(4)将多个目标文件合并成一个可执行文件,并解决它们之间的依赖关系。
(5)将程序所需的静态库或动态库中的代码整合到最终的可执行文件中。
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-1 Ubuntu下链接命令
5.3 可执行目标文件hello的格式
命令:readelf -a hello > hello01.elf
图 5-2 生成可执行文件elf格式的命令
(1)elf头
给出基本信息,和之前的elf头相比,还给出了程序的入口点,程序头,表示已经完成了链接。
图 5-3-1 elf头内容
- 节头
第一列按地址顺序列出了各段的名称及大小,第三列列出来各段的起始地址,最后一列列出来各段的偏移量
图 5-3-2 部分节头内容
- 程序头
程序头定义了 可执行文件在内存中的布局,明确了程序头数量有8 个,程序头大小为每个 56 字节,程序头偏移从文件偏移 0x40开始。
图 5-3-2 程序头内容
5.4 hello的虚拟地址空间
命令:edb --run hello
图 5-4 edb运行hello可执行文件
(1)在edb的symbol窗口,可以查看各段对应的名称以及各段的起始位置与结束的位置,与5.3中所展示出来的elf格式展示出来的相对应。
图 5-4-1edb中内容与elf格式中的比较
(2)Data Dump是从地址0x400000开始的,并且该处有elf的标识,可以判断从可执行文件加载的信息(只读代码段,读/写段)是从地址0x400000处开始的。相关信息可以从程序头处读取。
起始位置:
ELF的标识:
(3)PDHR起始位置为0x400040 大小为0x1c0
(4)INTERP起始位置为0x400200 大小为0x1c
5.5 链接的重定位过程分析
objdump -d -r hello > hello_asm.txt
图 5-5-1 edb可执行文件 hello 进行反汇编命令
1.重定位过程分析
(1)重定位的目标
修正代码中的地址引用:将目标文件中的临时地址替换为最终内存地址。
合并相同类型的节区:例如将所有目标文件的 .text 合并到可执行文件的 .text 段。
(2)重定位表
在 hello.o 中,重定位表指示哪些位置需要修正。
图 5-5-1 重定位表
- 具体内容比较
图 5-5-2 图5-5-3内容比较
(1)代码量增加
打开反汇编代码的文本文件,查看两个文件代码量,发现hello.o_asm.txt只有55行,而hello_asm.txt有164行。
(2)函数增加
在hello.o的反汇编程序中,只有main函数;经过链接过程后,原来调用的C标准库中的代码都被插入了代码中,并且每个函数都被分配了各自的虚拟地址。
(3)指令分配虚拟地址
在hello.o的反汇编程序中,main函数中的所有语句前面的地址都是从0开始依次递增的;经过链接后,每一条语句都被分配了虚拟地址,再依次递增。
(4)字符串常量的引用
在hello.o的反汇编程序中,字符串常量的位置是用0加%rip的值来表示的;而在hello的反汇编程序中,因为字符串常量都有了相应的位置,所以用实际的相对下一条语句的偏移量加%rip的值来描述其位置。
链接前:
链接后
(5)函数调用与跳转指令
在hello.o的反汇编程序中,由于当时函数未被分配地址,所以调用函数的位置都用call加下一条指令地址来表示;而在hello的反汇编程序中,由于各函数已拥有了各自的虚拟地址,所以在call后加其虚拟地址来实现函数调用。
链接前:
链接后:
5.6 hello的执行流程
命令:edb --run ./hello 2023111664 陶唯先 18546951228 3
图 5-6-1 执行流程
在edb运行程序后在入口处单击鼠标右键,选择analyze here。得到调用与跳转的各个子程序名或程序地址
图 5-6-2 子程序名或程序地址
5.7 Hello的动态链接分析
1.使用ldd hello命令查看hello程序的动态链接库依赖:
图 5-7-1 ldd hello 命令
2.使用(gdb) info proc mappings、(gdb) info proc mappings命令观察到动态链接库已经被加载到内存中。
图 5-7-2(gdb) info proc mappings、(gdb) info proc mappings命令与内容
(3)动态链接库
动态链接库(如libc.so)的函数地址在程序运行时才确定,因此编译器无法在编译期直接生成调用指令。为了避免运行时修改代码段(影响安全性和共享性),Linux采用延迟绑定(Lazy Binding)机制,通过 PLT(过程链接表) 和 GOT(全局偏移表) 协作实现。
过程链接表(PLT):PLT是一个数组,其中每个条目是16字节代码。PLT [0]是一个特殊条目,它跳转到动态链接器中。每个被可执行程序调用的库函数都有它自己的PLT条目。每个条目都负责调用一个具体的函数。每个条目都负责调用一个具体的函数。
全局偏移量表(GOT):GOT是一个数组,其中每个条目是8字节地址。和PLT联合使用时,GOT [0]和GOT [1]包含动态链接器在解析函数地址时会使用的信息。GOT [2]是动态链接器在1d-linux.so模块中的入口点。其余的每个条目对应于一个被调用的函数,其地址需要在运行时被解析。每个条目都有一个相匹配的PLT条目。
使用objdump查看GOT:
objdump -d -j .got hello
图 5-7-3 使用objdump查看GOT
可知got首地址为601000,通过edb查看对应内存出的内容:
程序运行前:
图 5-7-4 程序运行前
程序运行后:
图 5-7-5 程序运行后
5.8 本章小结
本章首先介绍了链接的概念与作用、在Ubuntu下链接的命令,以及可执行目标文件hello的格式。然后分析了hello的虚拟空间地址,详细分析可执行目标文件和可重定位目标文件的区别。最后分析了链接的重定位过程,探讨了hello的动态链接。
第6章 hello进程管理
6.1 进程的概念与作用
概念:进程是正在执行的程序的实例,是操作系统进行资源分配和调度的基本单位。它不仅包含程序的代码(指令),还包括运行时的状态(如寄存器值、内存分配、打开的文件等)。每个进程拥有独立的地址空间,确保彼此隔离。
作用:
(1)操作系统通过快速切换多个进程 实现多任务并发,让用户感觉多个程序在同时运行。
(2) 每个进程拥有独立的虚拟地址空间,防止一个进程错误访问或破坏其他进程的内存达到资源隔离与保护目的。
(3) 操作系统以进程为单位完成分配CPU时间、内存、I/O设备等资源的目的。
6.2 简述壳Shell-bash的作用与处理流程
1. Shell 的作用
Shell 是用户与操作系统内核之间的命令行接口,能够实现解释并执行用户命令;将用户输入的命令转换为系统调用,交给内核执行;支持编写自动化脚本,完成批量任务;维护环境变量、进程控制、I/O 重定向;管理前台/后台进程等作用。
2. Shell 的命令处理流程
当用户在终端输入命令并按下回车时,Shell的处理流程如下:
(1)从标准输入(键盘)或脚本文件读取命令字符串。
(2)将输入拆分为 tokens。
(3)Shell 自身处理内置命令。若是外部命令则通过创建子进程来完成命令。
(4)处理重定向与管道
(5)返回结果
6.3 Hello的fork进程创建过程
当shell运行hello时,父进程调用fork函数生成这个程序的子进程。子进程得到与父进程相同的虚拟地址空间的副本,包括代码,数据段,堆,共享库以及用户栈。根据fork()函数的返回值不同,子进程与父进程的PID不同。fork()函数创建新进程的过程:
(1)给新进程分配一个标识符;
(2)在内核中分配一个PCB,将其挂在PCB表上;
(3)复制它的父进程的环境(PCB中大部分的内容);
(4)为其分配资源(程序、数据、栈等);
(5)复制父进程地址空间里的内容(代码共享,数据写时拷贝);
(6)将进程置成就绪状态,并将其放入就绪队列,等待CPU调度。
6.4 Hello的execve过程
参数验证后检查 ./hello 是否存在、是否具有可执行权限。然后解析文件格式并释放原进程资源、清空当前进程的代码段、数据段、堆、栈等内存区域但保留 PID、文件描述符等。接着加载新程序。通过读取 ELF 头部确定程序入口、段布局。
映射代码段,将 .text 段加载到只读内存区域。初始化数据段、设置堆和栈、动态链接,最后跳转到程序入口。在过程中要设置程序计数器。设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。下一次调度这个进程时,它将从这个入口点开始执行。
6.5 Hello的进程执行
1.操作系统为进程提供的核心抽象
(1)有独立的逻辑控制流
进程通过程序计数器(PC)的线性序列执行指令,用户感知为"独占CPU"。
(2)有私有的地址空间
进程拥有独立的虚拟内存布局(代码段、堆、栈等),仿佛独占物理内存。
2.进程调度的核心机制
(1)时间片
每个进程分得固定 CPU 时间,超时后触发调度。
(2)上下文切换
时间片耗尽、进程主动放弃 CPU或更高优先级进程就绪进行上下文切换。
hello 的上下文切换:printf 触发 write 系统调用 → 保存用户态寄存器 → 进入内核态 → 返回时可能切换进程。
(3)进程状态转换
由用户态转换至内核态:系统调用或者中断。
由内核态转换至用户态:通过 iret 指令恢复用户进程现场。
6.6 hello的异常与信号处理
1.四种异常类型
中断:中断是来自处理器外部的I/O设备的信号的结果。
陷阱:陷阱是有意的异常,是执行一条指令的结果。就像中断处理程序一样, 陷阱处理程序将控制返回到下一条指令。
故障:故障由错误情况引起,它可能能够被故障处理程序修正。当故障发生时,处理器将控制转移给故障处理程序。如果处理程序能够修正这个错误情况,它就将控制返回到引起故障的指令,从而重新执行它。否则处理程序返回到内核中的abort例程,abort例程会终止引起故障的应用程序。
终止:终止是不可恢复的致命错误造成的结果,通常是一些硬件错误,比如DRAM或者SRAM位被损坏时发生的奇偶错误。
2.常见信号及处理
SIGINT:用户输入Ctrl-C,使进程终止,Shell 显示 ^C
SIGTSTP:用户输入Ctrl-Z,使进程进入后台Shell 显示 [1]+ 已停止
SIGSEGV:段错误,如非法内存访问使进程崩溃
SIGCONT :用户输入fg 或 bg 命令,恢复被 Ctrl-Z 暂停的进程
(1)外部输入
乱按键盘、回车
图 6-6-1 乱按键盘、回车
进程没有影响但是会将输入的内容存到缓冲区,作为接下来的命令行输入。
(2)Ctrl-C
图 6-6-2 Ctrl-C
会使内核发送一个SIGINT信号,信号处理程序会回收子进程
(3)Ctrl-Z
图 6-6-3 Ctrl-Z
会使内核发送一个sigstp信号,信号处理程序会挂起hello进程,shell显示屏幕提示信息。
(4)ps、jobs命令
图 6-6-4 ps、jobs命令
通过ps命令查看进程状态,通过jobs命令显示当前 Shell 会话中 所有后台任务,并支持管理这些任务
(5)pstree命令
图 6-6-5 pstree命令
图 6-6-6 pstree命令
图 6-6-7 pstree命令
pstree命令将所有进程以树状图显示。
(6)fg命令
图 6-6-8 fg命令
将被暂停的hello进程再次调到前台执行,shell首先打印hello的命令行命令,进程再从挂起处继续运行,打印剩下语句,然后正常结束。
- kill命令
先通过ps命令查看进程PID,然后可以用kill命令杀死进程。
图 6-6-9 psl命令
图 6-6-10 kill命令
6.7本章小结
本章首先介绍了进程的定义和作用、shell的作用和处理流程,以及在执行hello时fork()函数和execve()函数的过程。最后分析hello的进程执行和常见异常的发生与异常产生的信号的处理过程。
第7章 hello的存储管理
7.1 hello的存储器地址空间
(1)虚拟地址:是由程序产生的由段选择符和段内偏移地址组成的地址。这2部分组成的地址并不能直接访问物理内存,而是要通过分段地址的变化处理后才会对应到相应的物理内存地址。
(2)逻辑地址:指由程序产生的段内偏移地址。逻辑地址与虚拟地址二者之间没有明确的界限。
(3)线性地址:指虚拟地址到物理地址变换的中间层,是处理器可寻址的内存空间(称为线性地址空间)中的地址。程序代码会产生逻辑地址,或者说段中的偏移地址,加上相应段基址就成了一个线性地址。如果启用了分页机制,那么线性地址可以再经过变换产生物理地址。若是没有采用分页机制,那么线性地址就是物理地址。
(4)物理地址:指内存中物理单元的集合,他是地址转换的最终地址,进程在运行时执行指令和访问数据最后都要通过物理地址来存取主存。
7.2 Intel逻辑地址到线性地址的变换-段式管理
在 Intel x86/x86-64 体系结构中,逻辑地址到线性地址的转换是通过分段机制完成的。这一过程涉及 段选择符(Segment Selector)、段描述符(Segment Descriptor)、全局描述符表(GDT)/局部描述符表(LDT) 等关键组件。
1. 分段机制的核心概念
1.1 逻辑地址的组成
在分段模式下,逻辑地址由两部分组成:
(1)段选择符(Segment Selector)(16位):
Index(13位):描述符在 GDT/LDT 中的索引。
TI(Table Indicator,1位):
当TI=0 时,使用全局描述符表;当TI=1时,使用局部描述符表。
RPL(Requested Privilege Level,2位):请求特权级(0=内核,3=用户)。
偏移量(Offset,32/64位):段内的字节偏移,决定访问的具体位置。
图 7-2-1 段选择符
(2) 段描述符(Segment Descriptor)
每个段选择符对应一个段描述符(8字节),存储在GDT或LDT中。
1.2 逻辑地址到线性地址的转换流程
地址结构:
逻辑地址由段号与段内偏移量两部分组成。
图 7-2-2 段描述符
基本思想:
程序按内容或过程(函数)关系分成段,每段有自己的名字。一个用户作业或进程所包含的段对应于一个二维线性虚拟空间,也就是一个二维虚拟存储器。
段式管理程序以段为单位分配内存,然后通过地址映射机构把段式虚拟地址转换成实际的内存物理地址。
转换过程如下:
CPU 解析逻辑地址:提取段选择符和偏移量。然后查找段描述符检查 TI 位:若TI=0 则从 GDTR(GDT 寄存器)获取GDT基址。若TI=1 则从 LDTR(LDT寄存器)获取 LDT基址。然后检查段描述符与 DPL。再检查偏移量是否溢出。
计算线性地址:
从段描述符获取段基址。线性地址 = 基地址 +偏移量。
7.3 Hello的线性地址到物理地址的变换-页式管理
页式管理是现代操作系统内存管理的核心机制,它将虚拟地址空间和物理内存划分为固定大小的页,通过页表建立映射关系,实现高效、安全的内存访问。在页式存储管理方式中地址结构由两部构成,前一部分是虚拟页号(VPN),后一部分为虚拟页偏移量(VPO)。
7.4 TLB与四级页表支持下的VA到PA的变换
在现代x86-64体系结构中,虚拟地址(VA)到物理地址(PA)的转换通过四级页表和TLB(转换后备缓冲器)协同完成。
地址转换全流程:
进行TLB查询,若TLB命中则直接获取物理页帧号。否则进行PML4查询,从CR3获取PML4基址,计算PML4项地址:通过读取PML4项获取PDPT基址。通过基址和索引计算PDPT项地址,然后读取PDPT项来获取PD基址,同理获得PT基址,再计算PT项地址,以此读取PT项来获取物理页帧号(PPN)。最后将新映射(VPN→PPN)插入TLB,则后续访问可直接命中。
7.5 三级Cache支持下的物理内存访问
CPU发出请求后将物理地址发送到总线,总线将物理地址传送到L1 Cache进行查询,若命中则直接返回数据,未命中则触发L2 Cache查询,若命中则直接返回数据,未命中则触发L3 Cache查询, L3 Cache查询多核共享,检查其他核心的缓存。若所有Cache未命中,通过内存控制器读取DRAM,然后按照Cache等级将内存数据逐级回填到L3 CacheL2 CacheL1 Cache,数据最后通过总线回到CPU。
7.6 hello进程fork时的内存映射
当hello程序调用fork()创建子进程时,操作系统通过特定的内存管理技术实现高效的进程复制。原理如下:父进程的页表项被标记为只读,然后子进程共享父进程的物理页,而非立即复制当任一进程尝试写入共享页时,触发缺页异常,内核再分配新物理页并复制内容。
fork()时的具体内存映射流程:
首先复制页表,由内核创建与父进程相同的子进程的页表结构,将所有可写页的PTE标记为只读,然后共享只读页最后更新引用计数,共享的物理页引用计数+1。代码段直接共享数据段写时复制,堆写时复制,栈写时复制,共享库通过动态链接库的全局共享机制。
7.7 hello进程execve时的内存映射
当hello程序通过execve()系统调用执行时,操作系统会完全重建进程的内存映射,替换当前进程的地址空间为新程序的布局。
进程execve时的过程如下:
首先验证文件,检查目标ELF文件的权限和格式,然后销毁旧空间释放当前进程的所有内存映射如代码/数据/堆/栈等,构建新映射根据ELF程序头创建新内存区域,初始化栈设置新栈空间,压入环境变量和参数,寄存器重置设置PC寄存器指向ELF入口点。
7.8 缺页故障与缺页中断处理
1.缺页故障
缺页故障是操作系统内存管理的核心机制之一,当程序访问的虚拟内存页未映射到物理内存时触发。
缺页故障分为硬缺页、软缺页以及权限缺页。硬缺页发生在首次访问代码/数据页,此时MMU发现PTE=0;软缺页发生在访问已分配但未提交的页,此时PTE.P=1但无物理页关联;权限缺页发生在用户态访问内核页或写只读页,此时PTE.RW/US权限检查失败。
2.缺页中断处理
(1)保留进程上下文:当CPU执行指令希望访问一个不在内存的页面时,将产生缺页中断,系统开始运行中断处理程序。此时指令计数器的值尚未来得及增加就被压入堆栈。
(2)判断内存是否有空闲帧:如果有空闲帧,则获取一个帧号,转到启动I/O过程。如果没有空闲帧,则需要腾出一个空闲帧。
(3)页面置换:调用置换算法,选择一个淘汰页。如果淘汰页曾修改过,则请求外存交换区上的一个空闲块,并将该页写到外存上。
(4)启动I/O过程:按页表中提供的缺页外存位置,启动I/O,将缺页装入空闲帧中。
(5)修改页表:修改页表中该页的驻留位和内存地址。
(6)恢复进程执行:结束中断处理,恢复进程执行。
7.9动态存储分配管理
动态存储分配管理通过动态内存分配器管理进程的堆内存,将堆视为一组已分配块和空闲块的集合。
1. 动态内存分配器的核心任务
分配内存:响应 malloc 请求,返回合适大小的内存块。
回收内存:通过 free 释放不再使用的内存块。
管理碎片:内部碎片与外部碎片。
2. 动态内存分配策略
(1)空闲块组织方式
空闲块组织方式分为隐式空闲链表、显式空闲链表、分离空闲链表。隐式空闲链表空闲块通过头部中的大小字段隐含地连接着。分配器可以通过遍历堆中所有的块,从而间接地遍历整个空闲块的集合。遍历所有块包括头部信息,查找合适空闲块。分配速度慢,适合小内存请求。显式空闲链表显式的把所有的空闲块通过链表的形式维护起来,加速查找。分离空闲链表维护多个空闲链表,其中每个链表中的块有大致相等的大小。
(2)分配算法
首次适应算法选择第一个足够大的空闲块,速度快,但可能增加碎片;最佳适应算法选择最小的足够大的空闲块,减少碎片,但搜索开销大;最差适应算法选择最大的空闲块,增加碎片,适合特定场景。
(3)合并策略
立即合并:free 时直接合并相邻空闲块。
延迟合并:暂不合并,留待后续分配时处理。
7.10本章小结
本章先后介绍了hello的存储器地址空间、intel的段式管理、hello的页式管理,以TLB与四级页表支持下的VA到PA的变换、物理内存访问,然后分析了hello进程fork时的内存映射、hello进程execve时的内存映射,以及缺页故障与缺页中断处理。最后介绍了动态存储分配管理,简单阐述了堆的结构以及malloc和free对堆的管理。
第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章1分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
一、Hello程序的生命周期
1.从源码到可执行文件
(1)预处理:展开宏、头文件,删除注释,生成.i文件。
命令:gcc -E hello.c -o hello.i
(2)编译:将C代码翻译为汇编指令,完成语法分析、优化。
命令:gcc -S hello.i -o hello.s
(3)汇编:将汇编指令转为机器码,生成可重定位目标文件。
命令:gcc -c hello.s -o hello.o
(4)链接:合并目标文件与库,解析符号引用,分配虚拟地址,生成可执行文件hello。
命令:ld -o hello hello.o -lc
2.从进程创建到终止(Zero to Zero)
(1)进程创建:Shell调用fork()创建子进程,复制父进程的虚拟地址空间。
(2)程序加载:子进程通过execve()加载hello,重建内存映射,设置入口点为_start。
(3)进程执行:CPU从_start开始执行,调用main()函数。
(4)信号处理:Ctrl-C终止进程、Ctrl-Z挂起进程、kill或正常结束释放资源,内存状态归零。
(6)进程终止:程序执行完后,父进程回收子进程,内核删除为这个进程创建的所有数据结构。
3.存储与I/O管理
(1)地址转换:逻辑地址 → 线性地址→ 物理地址。
(2)动态内存:malloc/free管理堆空间,通过隐式/显式空闲链表减少碎片。
I/O操作
二、计算机系统设计的感悟
Hello程序虽小,却贯穿了计算机系统的核心机制:编译工具链的协作;操作系统的进程调度、内存管理、异常处理;硬件的地址转换、多级缓存、指令执行。
通过此实验,深刻体会到系统设计中抽象与效率的平衡,以及软硬件协同的精密性。
附件
文件 | 文件的作用 |
hello.o | 汇编后得到的可重定位目标文件 |
hello.s | 编译后得到的汇编语言文件 |
hello.i | 预处理后得到的文本文件 |
hello.elf | hello.o的elf文件 |
hello.sam | hello.o的反汇编文件 |
hello01.elf | 可执行文件hello的elf文件 |
hello.o_asm.txt | hello.o的反汇编文本文件 |
hello_asm.txt | 可执行文件hello的反汇编文本文件 |