本报告以"Hello's P2P"程序为例,系统探究了计算机系统中程序从源代码到进程执行的完整生命周期。通过分析Hello.c程序的预处理、编译、汇编、链接过程,剖析了进程管理、存储管理和IO管理机制。在Ubuntu环境下,采用GCC、GDB、readelf等工具进行实战操作,生成并解析了各阶段中间文件,验证了ELF格式、动态链接、虚拟地址转换等核心概念。Hello程序通过Shell的fork和execve机制转化为进程,在OS调度下借助段页式存储管理和Unix IO接口实现高效执行。
关键词:hello,预处理,编译,汇编,链接,进程 ,存储,I/O
目 录
6.2 简述壳Shell-bash的作用与处理流程... - 11 -
6.3 Hello的fork进程创建过程... - 11 -
7.2 Intel逻辑地址到线性地址的变换-段式管理... - 12 -
7.3 Hello的线性地址到物理地址的变换-页式管理... - 12 -
7.4 TLB与四级页表支持下的VA到PA的变换... - 12 -
7.5 三级Cache支持下的物理内存访问... - 12 -
7.6 hello进程fork时的内存映射... - 12 -
7.7 hello进程execve时的内存映射... - 12 -
第1章 概述
1.1 Hello简介
用计算机系统的语言总结hello所经历的过程:
1.从程序到进程(P2P):hello.c作为源代码,经预处理、编译、汇编、链接,生成可执行目标文件。在Shell中通过execve系统调用加载为进程(Process),实现了从静态代码到动态生命的转变;
2.预处理阶段:预处理器处理宏和头文件,生成扩展的.i文件,消除了源代码的依赖关系。
3.编译阶段:编译器将.i文件转换为汇编代码.s文件,优化了数据操作(如类型转换、控制转移)和函数调用。
4.汇编阶段:汇编器将.s文件翻译为机器语言.o文件,生成可重定位目标格式,包含节头表和重定位条目。
5.链接阶段:链接器解析.o文件中的符号引用,合并库函数,生成可执行文件hello,并分配虚拟地址空间。
6.进程管理:Shell通过fork创建子进程,execve加载hello到内存,进程调度器分配时间片,支持用户态与内核态切换。
7.存储管理:MMU通过段式管理和页式管理,将逻辑地址转换为物理地址,TLB和四级页表加速地址变换,三级Cache优化内存访问。
8.IO管理:printf和getchar通过Unix IO接口与设备交互,依赖中断机制和驱动程序完成数据显示和键盘输入。
9.进程终止:hello执行完毕后,Shell回收进程资源,实现了Hello“从0到0”的完整周期。
1.2 环境与工具
软件环境:Windows 11 64位,Ubuntu22.04-2 64位
硬件环境:Intel(R) Core(TM) i9-14900HX (2.20 GHz);16G RAM
开发,调试工具:gcc,gdb,edb,readelf
1.3 中间结果
1.hello.c,源程序;
2.hello.i,预处理后的文件;
3.hello.s,编译后的文件;
4.hello.o,汇编后的文件;
5.hello,链接之后的可执行目标文件;
6.hello.o.ob.txt:hello.o的反汇编文本文件;
7.hello.ob.txt:hello的反汇编文本文件。
1.4 本章小结
本章介绍了大作业的大致内容,进行大作业的软硬件设备和完成大作业产生的中间结果,大致作用。
第2章 预处理
2.1 预处理的概念与作用
概念:预处理是编译过程的初始阶段,由预处理器处理源文件中的预处理指令,包括宏定义(#define)、文件包含(#include)、条件编译(#ifdef)等。
作用:处理预处理指令,比如1.处理宏展开:处理#define定义的宏,进行文本替换;2.处理文件包含:将#include指令指定的头文件内容插入到源文件中;3.根据#ifdef、#ifndef等指令选择性地编译代码块;4.移除注释,等等。
2.2在Ubuntu下预处理的命令
Ubuntu中使用GCC编译器进行预处理:
![]()
图2.1 预处理命令
2.3 Hello的预处理结果解析
在hello.i中首先可以看到三个头文件的展开内容,包含了标准函数的声明和一些类型定义等等,这些内容使编译器能识别Hello程序中的函数调用,说明预处理完成了展开头文件的任务;
然后原始的hello.c代码被保留在文件末尾(去除了注释):

图2.2 hello.i中的原始代码
2.4 本章小结
本章通过预处理操作,把hello.c转换为了hello.i,验证了预处理在编译流程中的基础作用,这为后续的编译阶段流程奠定了基础。
第3章 编译
3.1 编译的概念与作用
概念:编译是将高级语言翻译成汇编语言的过程,由编译器执行。
作用包括:1.验证代码语法正确性;2.语义分析,检查类型匹配、作用域等;3.对代码进行优化,提高执行效率;4.生成汇编代码,产生与硬件架构相关的汇编指令,等等。
3.2 在Ubuntu下编译的命令

图3.1 编译命令
3.3 Hello的编译结果解析


图3.2 hello.s
3.3.1 数据处理分析
1.常量处理
第23行cmpl $5, -20(%rbp) (常量5用于比较argc)
第27行movl $1, %edi (常量1作为exit参数)
第40行movl $0, -4(%rbp) (常量0初始化循环变量i)
第58行movl $0, %eax (常量0作为返回值)
这里常量直接嵌入指令中作为立即数操作数,在汇编中以立即数形式出现。
2.变量处理
第21行movl %edi, -20(%rbp) (argc存储在栈帧位置-20)
第22行movq %rsi, -32(%rbp) (argv存储在栈帧位置-32)
第30行movl $0, -4(%rbp) (初始化i在栈帧位置-4)
局部变量存储在栈帧中,通过基址指针(RBP)相对寻址访问,不同类型的变量使用不同大小的存储空间。
3.表达式处理
第20行subq $32, %rsp (算术表达式:RSP – 32,栈分配)
第25行leaq .LC0(%rip), %rdi (地址计算表达式:获取字符串地址)
第34行addq $24, %rax (地址表达式:argv + 3)
第37行addq $16, %rax (地址表达式:argv + 2)
第40行addq $8, %rax (地址表达式:argv + 1)
第43行leaq .LC1(%rip), %rdi (地址计算表达式:获取格式字符串地址)
第47行addq $32, %rax (地址表达式:argv + 4)
第53行addl $1, -4(%rbp) (算术表达式:i = i + 1)
使用立即数与寄存器或内存操作数结合的计算方式
4.类型处理
第21行movl %edi, -20(%rbp) (32位整数操作argc)
第22行movq %rsi, -32(%rbp) (64位指针操作argv)
第40行movl $0, -4(%rbp) (32位整数操作i)
通过指令后缀区分数据类型,不同类型变量在栈帧中有不同的偏移量。
3.3.2 赋值操作分析
第21行movl %edi, -20(%rbp) (argc = edi,寄存器到内存赋值)
第22行movq %rsi, -32(%rbp) (argv = rsi,指针赋值)
第30行movl $0, -4(%rbp) (i = 0,立即数赋值)
第51行movl %eax, %edi (参数赋值,atoi结果传递给sleep)
第58行movl $0, %eax (return 0的返回值赋值)
3.3.3 类型转换分析
第50行call atoi@PLT (char* 转换为 int)
3.3.4 算术操作分析
第20行subq $32, %rsp (RSP – 32)
第34行addq $24, %rax (argv + 3)
第37行addq $16, %rax (argv + 2)
第40行addq $8, %rax (argv + 1)
第47行addq $32, %rax (argv + 4)
第53行addl $1, -4(%rbp) (i = i + 1)
3.3.5 关系操作分析
第23行cmpl $5, -20(%rbp) (argc 和 5 的比较)
第24行je .L2 (相等时跳转)
第55行cmpl $9, -4(%rbp) (i <= 9 的比较)
第56行jle .L4 (小于等于时跳转)
使用cmpl指令进行32位整数比较,根据比较结果选择跳转方向。
3.3.6 数组/指针操作分析
1.数组操作
第34行addq $24, %rax (计算argv[3]地址)
第35行movq (%rax), %rcx (获取argv[3]值)
第37行addq $16, %rax (计算argv[2]地址)
第38行movq (%rax), %rdx (获取argv[2]值)
第40行addq $8, %rax (计算argv[1]地址)
第41行movq (%rax), %rsi (获取argv[1]值)
第47行addq $32, %rax (计算argv[4]地址)
第48行movq (%rax), %rax (获取argv[4]值)
2.指针操作
第22行movq %rsi, -32(%rbp) (存储指针argv)
第25行leaq .LC0(%rip), %rdi (取字符串地址的&操作)
第33行等movq -32(%rbp), %rax (加载指针argv)
第41行等movq (%rax), %rsi (指针解引用的*操作)
3.3.7 控制转移分析
1.条件分支
第23行cmpl $5, -20(%rbp) (if (argc != 5) 的条件检查)
第24行je .L2 (条件为假时跳转到正常执行)
第25行leaq .LC0(%rip), %rdi (条件为真时的代码,错误处理)
第26行call puts@PLT
第27行movl $1, %edi
第28行call exit@PLT
第29行.L2: (条件为假时的标签,正常执行)
2.循环结构
第30行movl $0, -4(%rbp) (初始化i = 0)
第31行jmp .L3 (跳转到条件检查)
第32行.L4: (循环体开始标签)
第53行addl $1, -4(%rbp) (更新i=i++)
第54行.L3: (条件检查标签)
第55行cmpl $9, -4(%rbp) (条件:i <= 9)
第56行jle .L4 (条件满足时继续循环)
3.3.8 函数操作分析
1.函数调用
第26行call puts@PLT (调用puts函数)
第28行call exit@PLT (调用exit函数)
第45行call printf@PLT (调用printf函数)
第50行call atoi@PLT (调用atoi函数)
第52行call sleep@PLT (调用sleep函数)
第57行call getchar@PLT (调用getchar函数)
2.参数传递
第25行leaq .LC0(%rip), %rdi (puts参数:错误字符串)
第27行movl $1, %edi (exit参数:退出码)
第43行leaq .LC1(%rip), %rdi (printf格式字符串)
第51行movl %eax, %edi (sleep参数:atoi结果)
3.4 本章小结
本章通过对hello.s汇编代码的全面解析,我们分析并验证了编译阶段把高级C语言代码转换为机器级汇编代码,为后续的汇编和链接阶段奠定了基础。
第4章 汇编
4.1 汇编的概念与作用
概念:汇编指将汇编语言源代码转换为机器语言目标文件的过程。汇编器会处理汇编指令,逐行解析助记符和操作数,生成二进制机器码。
作用:1. 将汇编指令映射为机器可执行的二进制指令,包括操作码和操作数编码;2. 输出文件包含代码、数据及重定位信息;3. 处理局部符号和部分重定位条目,为链接阶段提供基础,等等。
4.2 在Ubuntu下汇编的命令
![]()
图4.1 汇编命令
4.3 可重定位目标elf格式
1.ELF头分析,执行readelf -h hello.o命令获取ELF头信息如下:

图4.2 ELF头信息
2.节头部表分析
通过readelf -S hello.o命令获取的节头部表显示,hello.o包含13个节区,其中.rela.text节是重定位信息节。

图4.3 节头部表显示
3.查看重定位条目详细信息,包含如下8个条目:

图4.4 重定位条目信息
4.4 Hello.o的结果解析


图4.5 hello.o的反汇编
与hello.s的对照分析:
1. 在hello.s中,代码使用标签(如.L2、.LC0)和函数名(如printf)作为符号引用;反汇编代码中,这些符号被转换为具体地址(如2b、5f)或重定位条目(如R_X86_64_PLT32 printf-0x4),表明汇编器已将符号解析为相对偏移或占位符,留待链接阶段完成最终地址绑定。
2. 反汇编代码明确标注了指令的十六进制机器码(如55对应push %rbp),而汇编代码仅保留助记符。
3.分支指令存在差异,例如hello.s中的je .L2在反汇编中变成je 2b,其中2b是目标地址的偏移量,而不是符号标签。
4. 机器语言中的操作数是一个临时值,指向PLT条目,实际函数地址在运行时才解析,而汇编语言直接引用符号,掩盖了底层重定位过程。比如hello.s中调用call printf@PLT使用PLT符号,在反汇编中对应callq 69并附带重定位条目65: R_X86_64_PLT32 printf-0x4。
机器语言的构成:
机器语言由二进制指令序列构成,在反汇编中表现为十六进制字节码,每个字节对应特定的操作码和操作数。操作码指定操作类型,如push对应55,mov对应48 89;操作数指定操作对象,可能为寄存器、立即数或内存地址,例如mov %rsp, %rbp的机器码48 89 e5中,e5编码了源和目标寄存器。
与汇编语言的映射关系:
汇编语言与机器语言之间存在一一映射关系,但汇编指令中的符号在机器码中会被转换为数值地址或相对偏移。多数指令可直接转换为机器码,例如addl $1, -4(%rbp)映射为机器码83 45 fc 01。同时操作数的映射往往不一致,在汇编代码中操作数使用符号标签,而机器码中使用相对地址或重定位条目,比如在汇编代码中,分支指令"je .L2"使用标签.L2;在反汇编中,该指令表现为"je 2b",其中"2b"是相对偏移地址,而不是符号名。
操作数不一致:
分支转移:在汇编代码中,循环和条件分支使用标签(如.L3、.L4)来提高可读性。例如"jle .L4"表示如果条件满足则跳转到.L4标签处。在反汇编代码中,该指令变为"jle 34",其中"34"是地址0x34的简写,对应机器码中的相对偏移。这种不一致是因为机器指令直接编码偏移量,而汇编器在编译阶段将标签转换为数值偏移。
函数调用:函数调用操作数的不一致涉及动态链接。在文汇编代码中,调用"call printf@PLT"使用PLT符号;在反汇编中,对应"callq 69",附带重定位条目"65: R_X86_64_PLT32 printf-0x4"。这表示机器语言中的操作数是一个临时值,指向PLT条目,实际函数地址在运行时才解析。相比之下,汇编语言则直接引用符号。
4.5 本章小结
本章涉及汇编阶段的操作步骤和结果分析,将hello.s转换为hello.o,生成可重定位目标文件,ELF格式分析展示了文件结构及其对重定位的支持,而反汇编解析明确了机器语言与汇编语言的映射关系。这个阶段为链接提供了标准化的中间文件。
第5章 链接
5.1 链接的概念与作用
概念:链接将多个可重定位目标文件和库文件组合成一个可执行目标文件。
作用:1.解析目标文件中的符号引用,将其与定义符号的目标文件关联起来;2.将每个符号的地址从相对地址转换为可执行文件中的绝对虚拟地址,确保程序在加载后能正确运行;3.将程序所需的静态库或动态库集成到可执行文件中。
5.2 在Ubuntu下链接的命令

图5.1 链接的命令
5.3 可执行目标文件hello的格式

图5.2 ELF头信息
下图是用readelf列出各段的基本信息,包括起始地址,大小等,一共25段:


图5.3 节头部表信息
5.4 hello的虚拟地址空间

图5.4 使用gdb加载hello查看虚拟地址空间各段信息
与5.3对照分析,ELF节区头表中指定的虚拟地址与进程实际内存映射地址是一致的,验证了链接器地址分配的正确性。同时操作系统将多个小节区合并为内存页,减少了页表项数量,提高了内存使用效率。
5.5 链接的重定位过程分析




图5.5 hello的反汇编
hello.o 与 hello 的不同点分析:
1. 地址表示方式:hello.o使用相对地址,例如 0000000000000000 <main> 。这是因为目标文件尚未分配绝对内存地址,地址是相对于段基址的偏移量。而hello:使用绝对地址(基于虚拟内存布局),例如 400582 <main> 。链接器在链接过程中分配了固定的运行时地址。
2. 段和节区结构:hello.o只包含 .text 段的部分反汇编,没有显示其他段,重定位条目直接嵌入在代码中(比如R_X86_64_PC32 )。而hello:包含完整的可执行文件结构,有多个段(.init .plt .text .fini等),链接器添加了这些运行时必需的段。
3. 函数调用和符号解析:hello.o函数调用是未解析的,使用占位符地址(比如callq 21 <main+0x21> ),并附带重定位条目(比如1d: R_X86_64_PLT32 puts-0x4 )。这表明符号尚未绑定到具体地址。hello中所有函数调用已解析,直接指向 PLT 条目或绝对地址(比如callq 4004f0 <puts@plt> )。PLT 允许延迟绑定到动态库。
4. 重定位条目:hello.o有多个重定位条目,指示链接器如何修改代码以解决符号引用。而hello没有显式重定位条目,因为链接器已在链接阶段解决所有重定位,地址已经固定了。
链接过程说明:
1.符号解析,链接器收集所有符号并且解析定义;
2.地址分配,链接器为每个段分配绝对虚拟地址;
3.重定位:修改代码中的相对引用,使其指向绝对地址。
4.插入初始化代码以处理动态链接。
结合 hello.o 的重定位项目分析 hello 中的重定位:
1. 字符串地址重定位( .rodata 引用):
hello.o在地址为15的lea指令中,有重定位18: R_X86_64_PC32 .rodata-0x4 。这表示需要将字符串地址重定位到.rodata段。在hello地址400597,lea指令变为 lea 0x10a(%rip),%rdi,其中0x10a是相对于 RIP 的偏移,指向绝对地址4006a8(.rodata中的字符串)。链接器计算了字符串的实际地址并编码为 PC-相对偏移。
2. 函数调用重定位(PLT 条目):
hello.o:有多个 R_X86_64_PLT32 重定位,用于函数调用:
1d: R_X86_64_PLT32 puts-0x4 :对应callq 21。
27: R_X86_64_PLT32 exit-0x4 :对应callq 2b。
65: R_X86_64_PLT32 printf-0x4 :对应callq 69。
78: R_X86_64_PLT32 atoi-0x4 :对应callq 7c。
7f: R_X86_64_PLT32 sleep-0x4 :对应callq 83。
8e: R_X86_64_PLT32 getchar-0x4 :对应callq 92。
hello:每个调用都被重定位到 PLT 条目:
puts调用hello.o的callq 21变为hello的callq 4004f0 <puts@plt>。printf调用指向400500,exit指向400530,等等。
当函数首次调用时,PLT 通过动态链接器解析实际地址,后续调用直接跳转。
3. 重定位计算:
比如puts调用,在hello.o中,指令e8 00 00 00 00(callq)的偏移为0,等待重定位。重定位类型R_X86_64_PLT32表示链接器应计算 PLT 入口的地址。在hello中,PLT 入口puts@plt位于4004f0。链接器将callq的偏移替换为e8 4d ff ff ff,其中4d ff ff ff是相对于下一条指令的偏移,指向4004f0。这确保了运行时正确跳转。
5.6 hello的执行流程
1. 加载和入口点(_start)
当hello被加载到内存时,系统加载器解析ELF头,确定入口点地址为0x400550(_start函数)。-
_start函数(地址:0x400550):
xor %ebp,%ebp、mov %rdx,%r9等,用于初始化栈和参数。
调用callq *0x200a76(%rip),这是间接调用__libc_start_main。参数设置如下:
rdi设置为0x400582(main函数的地址)。
rsi从栈中获取(argc和argv)。
rdx设置为环境变量指针。
rcx设置为0x400620(__libc_csu_init函数)。
r8设置为0x400690(__libc_csu_fini函数)。
2. 初始化阶段(由__libc_start_main驱动)
__libc_start_main调用:这个函数是程序执行的控制器。它会按顺序调用以下函数:
_init函数(地址:0x4004c0):用于全局初始化。
__libc_csu_init函数(地址:0x400620):推送寄存器、初始化动态段,并循环调用初始化函数。处理动态链接的初始化,确保库函数可用。
3. 主程序执行(call main)
main函数(地址:0x400582):__libc_start_main在初始化后直接调用main。
执行细节:
参数检查:比较argc与5(cmpl $0x5,-0x14(%rbp)),如果不等,调用puts输出错误消息并退出。
循环结构:使用局部变量(-0x4(%rbp))实现10次循环。每次循环中:
调用printf(地址:0x400500)格式化输出argv[1]、argv[2]、argv[3];
调用atoi(地址:0x400520)将argv[4]转换为整数;
调用sleep(地址:0x400540)暂停程序;
循环结束后调用getchar(地址:0x400510)等待输入。
mov $0x0,%eax后,leaveq和retq,返回值为0。
4. 程序终止和清理
main返回后,控制权返回给__libc_start_main,它会调用清理函数:
__libc_csu_fini函数(地址:0x400690):执行动态链接的清理工作。
_fini函数(地址:0x400694):用于全局清理。
运行终止例程,释放资源。
最后,__libc_start_main调用exit系统调用(地址0x400530)终止程序。如果main中调用exit,则会直接跳转至此。
调用和跳转的各个子程序名和程序地址如上。
5.7 Hello的动态链接分析

图5.6 .plt段的起始地址(0x400000)和got.plt段的起始地址(0x600000)。

图5.7 动态链接前的.plt段

图5.8 动态链接后的.plt段
5.8 本章小结
本章解析了 hello 程序的链接过程,包括概念、命令、ELF 格式、虚拟地址空间、重定位、执行流程和动态链接。链接将多个目标文件整合为可执行文件,通过符号解析和地址重定位确保程序正确运行。实验中使用 Ubuntu 环境下的 ld 和 gcc 命令,结合 readelf、objdump 和 gdb 工具,验证了链接的各个环节。
第6章 hello进程管理
6.1 进程的概念与作用
概念:进程是计算机系统中程序执行的基本单位,是操作系统进行资源分配和调度的核心实体。每个进程拥有独立的地址空间、代码、数据及系统资源,进程是程序的动态执行实例。
作用:实现程序的并发执行,提高系统资源利用率,并隔离不同任务以确保系统稳定性和安全性。
6.2 简述壳Shell-bash的作用与处理流程
作用:Shell-bash是用户与操作系统内核之间的命令解释器,其作用包括解析用户输入、管理进程执行、提供管道和重定向等功能。对于Hello程序,Shell-bash是启动和控制的入口。Bash接收用户命令,解析参数并触发系统调用(如fork和execve)来创建进程。
处理流程:
1.命令解析:Bash读取终端输入,分割字符串为命令和参数。
2.路径搜索:检查命令是否为内置命令或外部程序,若为外部程序,则在PATH环境变量中查找可执行文件。
3.进程创建:通过fork系统调用创建子进程,子进程复制父进程的上下文。
4.程序加载:在子进程中,使用execve加载Hello程序到内存,替换当前进程映像。
5.进程管理:Bash监控子进程状态,支持作业控制。
6.3 Hello的fork进程创建过程
在Bash中执行Hello时,Bash首先调用fork()创建子进程。fork通过复制当前进程(父进程)生成一个几乎相同的子进程。子进程拥有独立的PID,但继承父进程的代码、数据和环境。父进程(Bash)和子进程从fork返回点继续执行,但返回值不同:父进程返回子进程PID,子进程返回0。
6.4 Hello的execve过程
execve系统调用用于加载新程序到当前进程空间,替换原有代码和数据,但保留PID和打开的文件描述符。
Hello中,execve的作用是将Hello可执行文件加载到内存,解析ELF格式,设置代码段、数据段和堆栈,并跳转到入口点。execve接受程序路径、参数数组和环境变量启动Hello,通过mmap将程序段映射到虚拟地址空间,并动态链接库。若execve失败则子进程终止,Bash报告错误;成功则Hello开始执行main函数。
6.5 Hello的进程执行
进程执行涉及CPU调度、上下文切换和状态转换。Hello进程在时间片轮转下运行,经历用户态与核心态转换,直至结束。
进程上下文包括寄存器值、程序计数器、堆栈指针等。当Hello进程被调度时,OS恢复其上下文,从main开始执行。
OS分配CPU时间片,Hello的循环中,每次printf和sleep可能引发上下文切换:sleep时进程阻塞,OS调度其他进程;唤醒后继续执行。
调用hello进程后,输出hello和输入的内容,然后执行sleep函数,系统调用进程休眠,内核会去执行其他进程,这时就发生上下文切换。每次间隔atoi(argv[4])秒,内核会恢复hello进程的上下文从而继续执行hello进程,重复这个过程。循环10次结束后,执行getchar函数读取数据,数据被读取到缓冲区后发生中断,内核切换回hello进程并继续执行。
6.6 hello的异常与信号处理
会出现4类异常,分别是中断,陷阱,故障,停止。
1.中断,原因是来自I/O设备的信号,此时会中断当前程序对其进行处理,处理返回后继续执行中断前等待执行的下一条指令。
2.陷阱,原因是有意的异常,如系统调用,应用程序执行一次系统调用,调用后也返回继续执行中断前等待执行的下一条指令。
3.故障,原因是潜在的可恢复的错误,可能被修正,在OS处理后继续执行,否则会终止程序。
4.终止,原因是不可恢复的错误。处理程序会将控制返回到一个abort例程,然后该例程会终止这个应用程序。
常见信号:SIGINT(Ctrl-C)终止进程,SIGTSTP(Ctrl-Z)暂停进程,SIGCONT继续进程,等等。
不停乱按(包括回车):

图6.1 不停乱按
输入的字符被保存在缓冲区,被认为是额外的命令,在程序结束后执行。
按Crtl-C:

图6.2 按Crtl-C
进程会收到SIGINT信号,进程结束。
按Crtl-Z:

图6.3 按Crtl-Z
Crtl-Z会暂停进程。
按ps会列出进程,显示Hello状态为T(暂停)。
按jobs会显示后台作业。
按pstree会显示可视化进程树,显示Bash和Hello的关系。

图6.4 按fg
按fg会把hello带回前台继续执行。

图6.5 按kill
按kill终止进程。
6.7本章小结
本章解析了Hello程序的进程管理全流程,从Shell-bash的启动到进程终止,通过fork和execve,Hello实现了从程序到进程的转化,并在OS调度下执行。异常与信号处理体现了系统的健壮性,如Ctrl-Z和Ctrl-C的控制。如ps、jobs命令等的操作步骤强化了理论与实践的结合,揭示了进程上下文、状态转换和资源管理的复杂性。
第7章 hello的存储管理
7.1 hello的存储器地址空间
在计算机系统中,存储器地址空间是进程可访问的内存区域的抽象。结合hello程序来分析地址概念:
逻辑地址:由程序生成的地址,在hello.c中表现为变量和函数的地址,例如argc、argv的地址。这些地址在编译时由编译器生成,属于程序内部的偏移量。
线性地址:在Intel架构中,逻辑地址通过段式管理转换为线性地址。hello程序运行时,代码段和数据段的逻辑地址被映射到线性地址空间。
虚拟地址:现代操作系统使用虚拟内存机制,线性地址等价于虚拟地址。hello进程的虚拟地址空间包括代码段、数据段、堆栈段等,每个段有独立的虚拟地址范围。
物理地址:虚拟地址通过页式管理映射到物理内存地址。hello程序执行时,CPU和MMU协作将虚拟地址转换为物理地址,从而访问实际RAM。
7.2 Intel逻辑地址到线性地址的变换-段式管理
Intel x86架构使用段式管理将逻辑地址转换为线性地址。
逻辑地址结构:逻辑地址由段选择符和偏移量组成。例如在hello程序中,main函数的地址,段选择符指向代码段描述符,偏移量表示函数在段内的位置。
段描述符表:OS维护全局描述符表或局部描述符表。hello进程启动时,OS加载段描述符,其中包含段基地址和界限。
地址转换:CPU使用段选择符从GDT中获取段基地址,然后加上偏移量得到线性地址。公式为:线性地址 = 段基地址 + 偏移量。
比如说,hello的代码段基地址为0x400000,偏移量0x1000,则线性地址为0x401000。
7.3 Hello的线性地址到物理地址的变换-页式管理
页式管理将线性地址映射到物理地址。
1.分页机制:虚拟地址空间被划分为固定大小的页,物理内存被划分为页帧。hello进程的虚拟页通过页表映射到物理页帧。
2.地址划分:虚拟地址分为页号(高位)和页内偏移(低位)。例如,64位系统中,虚拟地址可能被划分为多级页表索引。
3.页表查询:MMU使用页表基地址寄存器(如CR3)定位页表。hello进程的页表由OS在加载时创建。过程:虚拟地址 → 页表项(PTE) → 物理页帧号 + 页内偏移 → 物理地址。
比如说,hello程序访问变量i时,虚拟地址通过页表找到物理地址,若页不在内存中,就会触发缺页中断。
7.4 TLB与四级页表支持下的VA到PA的变换
为加速地址变换,系统使用了TLB和多级页表。
TLB是高速缓存,存储最近使用的虚拟页到物理页的映射。hello程序运行时,MMU先查询TLB,若命中则直接获取物理地址,从而减少页表访问开销。
四级页表:在64位系统中,虚拟地址空间大,所以使用四级页表(PML4、PDP、PD、PT)减少内存占用。
hello进程的页表层级为:
1.PML4表:由CR3寄存器指向。
2.逐级查询:虚拟地址被分割为多个索引,每级页表项指向下一级表。
最终获得物理页帧号。
变换步骤:MMU检查TLB,若缺失则访问页表;使用虚拟地址的高位索引四级页表;结合页内偏移得到物理地址。
TLB和四级页表降低了内存访问延迟,hello程序的内存操作更高效。
7.5 三级Cache支持下的物理内存访问
物理内存访问通过Cache层次优化。现代CPU有三级Cache(L1、L2、L3),L1最小最快,L3最大最慢。hello程序的数据和指令被缓存以提高速度。
访问流程:CPU生成物理地址后,先查询L1 Cache;若L1缺失,查询L2 Cache;若L2缺失,查询L3 Cache;若所有Cache缺失,访问主内存。
映射策略:Cache使用组相联或直接映射。hello的变量如i可能被缓存到L1数据Cache。
在hello循环执行printf时,指令和字符串常量会被缓存,减少内存访问次数。
7.6 hello进程fork时的内存映射
当shell执行fork创建hello子进程时,内存映射过程为:
1.复制进程上下文:fork系统调用复制父进程(shell)的页表、虚拟地址空间和寄存器状态。hello子进程获得与父进程相同的内存映射。
2.写时复制:为节省内存,父子和hello进程共享物理页,直到任一进程尝试写入页时,OS才复制该页并更新映射。
比如说,hello进程的代码段只读,因此共享;数据段如argv在修改时触发复制。
3.页表更新:OS为hello子进程创建新页表,但初始指向父进程的物理页。
写时复制避免了不必要的内存复制,提高了fork的效率。
7.7 hello进程execve时的内存映射
execve系统调用加载hello程序并替换当前进程内存的过程:
1.释放旧空间:OS销毁原进程的虚拟地址空间和页表。
2.加载新程序:OS解析hello可执行文件,根据ELF格式创建新内存区域:代码段(映射到只读页),数据段(映射到可读写页)堆栈段(动态分配)。
3.页表重建:OS为hello进程建立新页表,虚拟地址映射到物理内存或文件(如动态库)。
4.动态链接:若hello使用共享库,OS通过内存映射将库代码加载到共享区域。
7.8 缺页故障与缺页中断处理
hello执行过程中可能触发缺页故障,比如页未加载到内存,需从磁盘读取的硬缺页,或者页在内存但未映射的软缺页。
缺页中断处理步骤:
1.CPU检测缺页,触发缺页中断(异常)。
2.OS中断处理程序保存现场,分析故障地址。
3.OS检查页表项:若页无效,则从磁盘(如交换空间)加载页;若因写时复制,则复制页。
4.更新页表项和TLB,恢复进程执行。
7.9动态存储分配管理
(以下格式自行编排,编辑时删除)
Printf会调用malloc,请简述动态内存管理的基本方法与策略。(此节课堂没有讲授,选做,不算分)
7.10本章小结
本章解析了hello程序的存储管理过程。从地址空间概念出发逐步分析了段式管理、页式管理、TLB与Cache优化、进程内存映射(fork和execve)和缺页处理,展示了OS和硬件如何协作管理内存,从而确保hello进程高效安全运行。存储管理是计算机系统的核心,hello的案例也体现了虚拟内存、共享和保护机制的实际应用。
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
Linux系统所有设备(如键盘、显示器、磁盘等)都被模型化为文件,统一通过Unix IO接口进行访问。设备文件通常位于/dev目录下。这种管理方法简化了IO操作,从而允许用户使用标准的文件操作函数(如read、write)与设备交互。
在hello.c程序中,printf和getchar的IO操作也是基于此,printf会输出到标准输出(通常映射到显示器设备文件),getchar会从标准输入(键盘设备文件)读取数据。
8.2 简述Unix IO接口及其函数
Unix IO接口提供了统一的系统调用(如open、read、write、close),屏蔽了硬件的差异。
主要函数包括:
open:打开设备文件,返回文件描述符;
read:从文件描述符读取数据到缓冲区;
write:将缓冲区的数据写入文件描述符;
close:关闭文件描述符;
等等。
8.3 printf的实现分析
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章解析了hello程序涉及的IO管理操作。通过分析Linux设备文件模型、Unix IO接口函数、printf和getchar的实现步骤,展示了IO从用户空间到硬件设备的完整路径。
结论
用计算机系统的语言总结hello所经历的过程:
1.从程序到进程(P2P):hello.c作为源代码,经预处理、编译、汇编、链接,生成可执行目标文件。在Shell中通过execve系统调用加载为进程(Process),实现了从静态代码到动态生命的转变;
2.预处理阶段:预处理器处理宏和头文件,生成扩展的.i文件,消除了源代码的依赖关系。
3.编译阶段:编译器将.i文件转换为汇编代码.s文件,优化了数据操作(如类型转换、控制转移)和函数调用。
4.汇编阶段:汇编器将.s文件翻译为机器语言.o文件,生成可重定位目标格式,包含节头表和重定位条目。
5.链接阶段:链接器解析.o文件中的符号引用,合并库函数,生成可执行文件hello,并分配虚拟地址空间。
6.进程管理:Shell通过fork创建子进程,execve加载hello到内存,进程调度器分配时间片,支持用户态与内核态切换。
7.存储管理:MMU通过段式管理和页式管理,将逻辑地址转换为物理地址,TLB和四级页表加速地址变换,三级Cache优化内存访问。
8.IO管理:printf和getchar通过Unix IO接口与设备交互,依赖中断机制和驱动程序完成数据显示和键盘输入。
9.进程终止:hello执行完毕后,Shell回收进程资源,实现了Hello“从0到0”的完整周期。
感悟:
Hello可以说是计算机最简单的程序之一,但是它从诞生到结束的一生又几近完整地展示了计算机系统的内在原理。计算机通过一个个部件的默契配合赋予了Hello短暂,复杂,但又完美,精彩的生命。我像一名见习的法医,层层剖开Hello程序,通过简单的它学习了计算机运行的本质。无论以后再编写什么复杂的程序,学习什么深入的计算机知识,我可能都忘不了这一天这个简单的Hello程序带给我的体验。它是一位无名的引路人,一名燃烧的传薪者。
附件
附件1:hello.c,源程序;
附件2:hello.i,预处理后的文件;
附件3:hello.s,编译后的文件;
附件4:hello.o,汇编后的文件;
附件5:hello,链接之后的可执行目标文件;
附件6:hello.o.ob.txt:hello.o的反汇编文本文件;
附件7:hello.ob.txt:hello的反汇编文本文件。
参考文献
[1] RANDALE.BRYANT, DAVIDR.O‘HALLARON. 深入理解计算机系统[M]. 机械工业出版社
[2] https://www.csdn.net/
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2502_91150647/article/details/156577437



