哈工大计统大作业-程序人生
本项目以“程序人生-Hello's P2P”为核心,通过编写、预处理、编译、汇编、链接及运行一个简单的Hello程序,系统探讨了计算机系统中程序从代码到进程的全生命周期。实验基于Ubuntu环境,使用GCC工具链完成代码转换,分析了预处理展开头文件、编译生成汇编指令、汇编生成可重定位目标文件、链接生成可执行文件等关键步骤的底层机制。结合进程管理、存储管理和IO管理,深入揭示了操作系统对程序执行的资源分配、地址转换、异常处理及动态链接的支持。项目通过理论与实践结合,完整呈现了程序在编译系统、操作系统和硬件协同下的运行流程,验证了计算机系统分层抽象与模块化设计的高效性,为深入理解系统级编程与优化提供了重要参考。
关键词:预处理;编译;链接;进程管理;存储管理;动态链接;IO管理
目 录
第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简介
Program:在VSCode中编写hello.c
预处理:gcc -E hello.c -o hello.i → 展开头文件
编译:gcc -S hello.i -o hello.s → 生成x86_64汇编
汇编:gcc -c hello.s -o hello.o → 生成ELF可重定位文件
链接:gcc hello.o -o hello → 动态链接glibc 2.38
Process:Shell通过fork()创建子进程,execve()加载hello。
OS分配虚拟内存,CPU执行指令后调用exit_group(0)终止。
020(From Zero to Zero):0→1:文本文件→可执行文件
1→0:进程终止后,OS回收页表、文件描述符等资源。
1.2 环境与工具
硬件 x86_64 CPU, DELL服务器(Ubuntu)
内核 Linux 6.8.0-54-generic
工具链 GCC 13.3.0(-Og)、GNU Binutils 2.41、glibc 2.38
调试 GDB 14.2、strace 6.8、objdump 2.41
编辑器 VSCode
1.3 中间结果
hello.i # 预处理后代码
hello.s # 汇编代码
hello.o # ELF文件
hello # 动态链接可执行文件
1.4 本章小结
本章简述了Hello程序的P2P(从代码到进程)和020(从无到无)生命周期,列出了实验环境和生成的中间文件。后续章节将深入分析预处理、编译、进程运行等细节。
第2章 预处理
2.1 预处理的概念与作用
预处理(Preprocessing)是C程序编译的第一步,由预处理器(cpp)执行,主要完成以下任务:
宏展开:替换#define定义的宏
头文件包含:递归展开#include指令,插入头文件内容
条件编译:处理#if、#ifdef等指令,决定是否包含代码块
删除注释:移除所有/* ... */和//注释
添加行标记:插入#line指令,便于调试时定位源码位置。
作用:
使代码可移植(如通过条件编译适配不同平台)
减少重复代码(通过头文件和宏)
生成“纯净”的C代码(不含预处理指令),供编译器处理
2.2在Ubuntu下预处理的命令
-m64:64位 -E:仅预处理 -o hello.i:输出到hello.i文件。
2.3 Hello的预处理结果解析
头文件展开:
原始代码中的#include被替换为头文件内容:
stdio.h:插入printf、exit等函数声明
unistd.h:插入sleep、getchar等系统调用声明
stdlib.h:插入atoi的函数声明
注释删除:
源码开头的注释被完全移除
行标记:
插入#line指令,标识原始代码位置
2.4 本章小结
本章通过gcc -E命令对hello.c进行预处理,生成hello.i文件,并分析其内容,进行预处理核心任务:头文件展开(插入stdio.h等)、删除注释、添加行标记。原始代码从24行扩展为3180行(主要因头文件插入),保留了所有函数调用(printf、sleep)的声明。
第3章 编译
3.1 编译的概念与作用
概念:
编译(Compilation)指将预处理后的C代码(.i)转换为汇编代码(.s)的过程,由编译器(GCC的cc1组件)完成
作用:
语法分析:检查代码是否符合C语言规范
语义优化:在-Og优化级别下,平衡可读性与性能
生成汇编:将高级C代码转换为低级机器指令的助记符
3.2 在Ubuntu下编译的命令
-m64:64位 -S:生成汇编代码 -Og:启用调试优化
3.3 Hello的编译结果解析
3.3.1 字符串处理
.LC0:
.string "\347\224\250\346\263\225..." # UTF-8编码的中文字符串
.LC1:
.string "Hello %s %s %s\n" # printf格式字符串
3.3.2 函数框架
函数入口:
main:
endbr64 # 对抗ROP攻击的指令
pushq %rbp # 保存基址指针
pushq %rbx # 保存被调用者保存寄存器
subq $8, %rsp # 分配栈空间
3.3.3 参数检查
cmpl $5, %edi # 比较argc和5
jne .L6 # 不等于则跳转错误处理
movq %rsi, %rbx # 保存argv指针
3.3.4 错误处理
.L6:
leaq .LC0(%rip), %rdi # 加载错误字符串地址
call puts@PLT # 调用puts输出
movl $1, %edi
call exit@PLT # 退出程序
3.3.5 主循环结构
循环初始化:movl $0, %ebp # i = 0
循环条件:
.L2:
cmpl $9, %ebp # 比较i和9
jle .L3 # i <= 9时继续循环
循环增量:addl $1, %ebp # i++
3.3.6 函数调用
printf调用:
movq 24(%rbx), %rcx # argv[3]
movq 16(%rbx), %rdx # argv[2]
movq 8(%rbx), %rsi # argv[1]
leaq .LC1(%rip), %rdi # 格式字符串
movl $0, %eax # 清空浮点寄存器
call printf@PLT
其他调用:
call atoi@PLT # 字符串转整数
call sleep@PLT # 延时
call getchar@PLT # 等待输入
3.3.7 函数返回
movl $0, %eax # 返回值0
addq $8, %rsp # 释放栈空间
popq %rbx # 恢复寄存器
popq %rbp
ret
3.4 本章小结
编译器将C代码准确转换为x86_64汇编指令,优化选项-Og保持了代码可读性,完整实现了:参数检查、循环控制、多参数函数调用、系统调用。符合x86_64调用约定。
使用RIP相对寻址访问字符串常量,参数通过寄存器传递(rdi, rsi, rdx, rcx),循环转换为条件跳转结构,完整保留了程序的控制流逻辑。
第4章 汇编
4.1 汇编的概念与作用
概念:
汇编是将汇编代码(.s)转换为机器语言二进制目标文件(.o)的过程,由汇编器完成
作用:
指令编码:将汇编助记符转换为机器码
生成可重定位目标文件:包含代码、数据、符号表和重定位信息,供链接器使用
4.2 在Ubuntu下汇编的命令
-c:仅汇编不链接 -m64:生成64位目标文件
4.3 可重定位目标elf格式
4.3.1 节头基本信息
通过 readelf -S hello.o 分析目标文件结构:
节名 | 类型 | 大小 | 标志 | 说明 |
.text | PROGBITS | 0x75 | AX | 主函数机器指令(可执行代码) |
.rela.text | RELA | 0xc0 | I | .text节的重定位条目(需链接器修正地址) |
.rodata.str1.8 | PROGBITS | 0x30 | AMS | 8字节对齐的只读字符串(中文错误提示.LC0) |
.rodata.str1.1 | PROGBITS | 0x10 | AMS | 1字节对齐的只读字符串(Hello格式串.LC1) |
.eh_frame | PROGBITS | 0x40 | A | 异常处理帧信息(用于栈展开) |
.symtab | SYMTAB | 0x120 | - | 符号表(函数和全局变量名) |
4.3.2 重定位项目分析
通过 readelf -r hello.o 查看重定位条目:
R_X86_64_PC32(本地符号相对地址修正)
作用:加载字符串地址(.LC0)
指令:lea .LC0(%rip), %rdi
公式:最终地址=Symbol地址−Offset−4
R_X86_64_PLT32(外部函数PLT跳转修正)
作用:调用动态库函数(printf、sleep)
指令:call printf@PLT
公式:PLT偏移=PLT表基址+Symbol在PLT中的索引×16−Offset−4
4.4 Hello.o的结果解析
通过 objdump -d -r hello.o 输出的反汇编结果,结合第3章的 hello.s 汇编代码,对 hello.o 的机器语言构成、重定位条目及与汇编的映射关系进行详细分析。
4.4.1汇编与反汇编的对比分析
4.4.1.1 函数入口与栈帧构建
汇编代码(hello.s):
反汇编(hello.o):
分析:
机器码:f3 0f 1e fa:endbr64(Intel CET指令,防御ROP攻击)。
55 53 48 83 ec 08:保存 %rbp、%rbx,分配8字节栈空间。
操作数编码:无重定位需求,偏移量直接编码(sub $0x8,%rsp 对应 48 83 ec 08)。
4.4.1.2 参数检查与错误处理
汇编代码(hello.s):
反汇编(hello.o):
分析:
机器码:83 ff 05:cmpl $5, %edi(比较argc与5)。
75 0a:jne(条件不满足时跳转至偏移量0x19)。
跳转目标:目标地址 0x19 在汇编阶段已计算为绝对偏移,无需重定位。
4.4.1.3 错误处理分支(.L6)
汇编代码(hello.s):
反汇编(hello.o):
分析:
字符串加载(.LC0):
机器码:48 8d 3d 00 00 00 00
lea指令的PC相对寻址,占位符 00 00 00 00 需重定位
重定位条目:R_X86_64_PC32
修正值 = .LC0地址 - 下一条指令地址(0x20) - 4
函数调用(puts@PLT):
机器码:e8 00 00 00 00
call指令的PLT表跳转,占位符需链接器填充
重定位条目:R_X86_64_PLT32
修正值 = puts@PLT入口地址 - 下一条指令地址(0x25) - 4
4.4.1.4 主循环逻辑(.L2-.L3)
汇编代码(hello.s):
反汇编(hello.o):
分析:
(1)参数传递机制
argv访问: %rbx 存储 argv 数组基址,偏移量计算
mov 0x8(%rbx), %rsi # argv[1] = *(argv + 8) 机器码 48 8b 73 08
48:64位操作数前缀 8b 73 08:mov指令编码,源地址为 %rbx + 8
printf参数传递:
寄存器顺序:%rdi(格式串)、%rsi(argv[1])、%rdx(argv[2])、%rcx(argv[3])。
浮点处理:mov $0x0, %eax 表示无向量寄存器参数。
(2)控制流与重定位
循环跳转(jle .L3) 机器码:7e cb
7e:jle操作码 cb:8位偏移量(补码 -53),目标地址为 0x62 - 53 + 2 = 0x2f
无需重定位,偏移量在汇编阶段确定。
外部函数调用(sleep@PLT) 机器码:e8 00 00 00 00
占位符 00 00 00 00 需链接器填充
R_X86_64_PLT32,通过PLT表实现动态链接。
4.4.1.5 程序退出与栈帧回收
汇编代码(hello.s):
反汇编(hello.o):
分析:
栈平衡 addq $8, %rsp 回收栈空间,popq恢复保存的寄存器
返回值 mov $0x0, %eax 设置返回值0
4.4.2 机器语言构成
机器语言由操作码和操作数组成,以二进制形式编码,具有以下特征:
定长操作码 call 对应 e8,movq 对应 48 8b
变长操作数 地址偏移、立即数等长度可变(1-8字节)
指令前缀 48 表示64位操作数,f3 0f 1e fa 表示安全指令 endbr64
4.4.3机器语言与汇编语言的映射关系
汇编指令 | 机器码 | 操作数编码解析 | |
数据操作指令 | movq 8(%rbx), %rsi | 48 8b 73 08 | 48(64位前缀)+ 8b(mov)+ 73 08(%rbx+8 → %rsi) |
lea .LC0(%rip), %rdi | 48 8d 3d 00 00 00 00 | 48 8d(lea)+ 3d(%rdi目标)+ 00 00 00 00(重定位) | |
控制流 | call printf@PLT | e8 00 00 00 00 | e8(call)+ 00 00 00 00(重定位) |
jle .L3 | 7e cb | 7e(jle)+ cb(8位偏移量 -53) |
4.4.4机器语言与汇编语言不一致性分析
分支转移指令 如jle .L3 → 7e cb
汇编语言使用符号标签(.L3)机器语言编码为相对偏移量,与当前指令地址无关。
偏移计算:目标地址 0x2f = 当前地址 0x62 + 偏移 0xcb(-53) + 指令长度 2。
函数调用指令 如call puts@PLT → e8 00 00 00 00
汇编语言显式标注符号(puts@PLT)机器语言使用占位符,依赖重定位条目修正。
重定位修正:根据 R_X86_64_PLT32 类型,链接时填充PLT表偏移。
地址加载指令 如lea .LC0(%rip), %rdi → 48 8d 3d 00 00 00 00
汇编语言使用符号(.LC0)和相对寻址(%rip)
机器语言编码为 PC相对偏移占位符,需重定位修正。
重定位修正:根据 R_X86_64_PC32 类型,计算 .LC0 与 %rip 的相对偏移。
4.5 本章小结
本章分析了可重定位目标文件 hello.o 的生成与结构,揭示了汇编到机器语言的转换机制。目标文件通过 .text 节存储机器指令,.rela.text 记录需重定位的符号地址。机器语言由操作码和操作数构成,分支指令直接编码相对偏移,函数调用通过占位符预留重定位空间。安全设计上,endbr64 指令与ELF属性节增强了控制流完整性。本章为理解链接过程与指令集架构协作提供了底层视角。
第5章 链接
5.1 链接的概念与作用
链接是将多个目标文件和库文件合并为可执行文件的过程,包括:
符号解析:将未定义的符号(如 printf)绑定到动态库中的实际地址
地址重定位:修正代码和数据段的绝对地址,生成可执行内存布局
节合并:整合不同目标文件的 .text、.data 等节,生成最终ELF结构
5.2 在Ubuntu下链接的命令
-dynamic-linker:指定动态链接器路径 crt1.o 提供程序入口 _start
crti.o 和 crtn.o 包含初始化代码 -lc 链接 libc.so,提供标准库函数
5.3 可执行目标文件hello的格式
段名 | 起始地址 | 大小 | 权限 | 说明 |
.interp | 0x4002e0 | 0x1c | R | 存储动态链接器路径 |
.plt | 0x401020 | 0x70 | RX | 过程链接表(PLT),用于跳转到动态库函数 |
.text | 0x4010f0 | 0xaa | RX | 主程序代码,包含 _start、main 和 _dl_relocate_static_pie |
.rodata | 0x402000 | 0x48 | R | 只读数据段,存储字符串常量 |
.got.plt | 0x403fe8 | 0x48 | RW | 全局偏移表(GOT),存储动态库函数的实际地址 |
.data | 0x404030 | 0x4 | RW | 已初始化的全局数据 |
.dynamic | 0x403e38 | 0x1a0 | RW | 动态链接信息,包含依赖库列表和重定位条目 |
5.4 hello的虚拟地址空间
使用edb加载hello,查看虚拟地址空间各段信息,并与5.3对照分析
通过对比edb加载的虚拟地址空间信息与5.3中的ELF段信息,分析如下:
1. 只读代码段(R--/R-X)
内存区域:
对应ELF段:
0x400000-0x401000(R--):包含.interp(0x4002e0)、.note、.hash等只读段,与ELF权限一致
0x401000-0x402000(R-X):包含可执行段.plt(0x401020)、.text(0x4010f0),权限与ELF中标记的RX完全匹配
0x402000-0x403000(R--):对应只读数据段.rodata(0x402000),权限一致
2. 可读写数据段(RW)
内存区域:
对应ELF段:
.dynamic(0x403e38):存储动态链接信息,权限为RW。
.got.plt(0x403fe8):全局偏移表,需运行时修改,权限为RW。
.data(0x404030):已初始化全局数据,权限为RW。
3. 动态链接库与系统区域
内存区域:
[stack]、[vvar]、[vdso]、[vsyscall]为系统保留区域
动态链接库的段不属于hello的ELF文件,但在进程虚拟地址空间中加载
[stack]为运行时栈,[vdso]为内核提供的虚拟动态共享对象,与5.3中的段无关
4. 权限合并与对齐
ELF文件中的多个段(如.interp、.note)被合并到同一内存页(0x400000-0x401000),权限取并集(R--)。
可执行段(.text、.plt)合并到R-X区域,符合代码段的典型权限。
5.5 链接的重定位过程分析
对比 hello.o 和 hello 的反汇编代码,解析重定位过程
1. 函数调用重定位(以 printf 为例)
hello.o 中的未解析调用:
hello 中的重定位结果:
PLT条目(0x4010a0):
GOT条目初始值:首次调用时,GOT[printf] 指向PLT中的解析代码,触发动态链接器加载 printf 的实际地址。
2. 数据地址重定位(以字符串常量为例)
hello.o 中的未解析地址:
hello 中的重定位结果:
字符串常量位置:.rodata 段的 0x402038 存储 "Hello %s %s %s\n"。
5.6 hello的执行流程
1. 动态加载阶段
入口函数:_start(动态链接器)
地址:0x00007ffff7fe4540(位于 ld-linux-x86-64.so.2)
作用:加载程序依赖的共享库(如 libc.so.6),初始化运行环境。
2. 程序启动阶段
入口函数:_start(hello程序)
地址:0x4010f0(.text段)
操作:调用 __libc_start_main,传递 main 函数地址。
调用:call *0x403fd8 ; 调用 __libc_start_main@GLIBC_2.34
3.主函数执行阶段
入口函数:main
地址:0x401125
执行流程:
(1)参数检查:
cmpl $0x5, %edi ; 检查 argc 是否为5
jne 0x40113e ; 跳转到错误处理分支
(2)循环逻辑:
循环计数器:%ebp 从0递增至9。
函数调用链:
函数调用 | 地址 | 作用 |
printf@plt | 0x4010a0 | 输出格式化字符串 |
atoi@plt | 0x4010c0 | 将字符串参数转换为整数 |
sleep@plt | 0x4010e0 | 休眠指定秒数 |
循环终止条件:cmpl $0x9, %ebp
jle 循环体
4. 中断处理阶段
触发条件:用户按下 Ctrl-C(发送 SIGINT 信号)。
调用链:
函数名 | 地址 | 作用 |
__GI___clock_nanosleep | 0x00007ffff7ceca7a | 内核级休眠实现 |
__GI___nanosleep | 0x00007ffff7cf9a27 | 调用 clock_nanosleep |
__sleep | 0x00007ffff7d0ec63 | 封装 nanosleep 系统调用 |
main | 0x401181 | 主函数中的 sleep 调用点 |
5. 程序终止阶段
正常终止: main 返回后,__libc_start_main 调用 exit 终止进程。
中断终止: 内核直接终止进程,释放资源。
5.7 Hello的动态链接分析
1. 动态链接前的GOT状态
在程序首次调用外部函数前,GOT条目指向PLT中的解析代码。
调试命令与输出:
printf@got.plt(0x404008)初始值为 0x00401040,指向 printf@plt+0x10
atoi@got.plt(0x404018)初始值为 0x00401060,指向 atoi@plt+0x10。
2. 动态链接后的GOT状态
首次调用函数后,动态链接器(ld.so)解析实际函数地址并更新GOT。
调试命令与输出:
printf@got.plt(0x404008)更新为0x7ffff7c60100,指向libc.so中的printf实际地址。
atoi@got.plt(0x404018)更新为 0x7ffff7c46660,指向libc.so中的atoi实际地址。
5.8 本章小结
本章分析了hello程序从目标文件到可执行文件的链接过程及其运行机制。链接通过符号解析、地址重定位和节合并生成ELF结构,关键段包括.text(代码)、.rodata(只读数据)和.got.plt(动态函数地址)。虚拟地址空间中,ELF段权限与内存映射一致,系统区域由内核独立管理。重定位修正函数调用至PLT,动态链接通过GOT实现延迟绑定:首次调用触发地址解析并更新GOT为实际地址。程序执行流程从动态加载器ld.so初始化开始,经_start调用main,循环输出信息后通过exit或信号终止。
第6章 hello进程管理
6.1 进程的概念与作用
进程是程序的执行实例,拥有独立的地址空间、代码、数据和系统资源
作用:
资源分配:CPU时间片、内存、文件句柄等由操作系统统一管理
隔离保护:进程间相互隔离,避免数据冲突或非法访问
并发执行:通过进程调度实现多任务并行
6.2 简述壳Shell-bash的作用与处理流程
作用:
命令解析:解析用户输入的命令(如 ./hello)。
进程管理:创建子进程执行程序(fork + execve),支持后台运行(&)。
环境管理:维护环境变量(如 PATH)和输入输出重定向(>、<)。
处理流程:
1.读取输入:从终端或脚本获取命令。
2.解析命令:分割参数,处理管道(|)、重定向等。
3.执行命令:若为内置命令(如 cd),直接执行;否则创建子进程运行程序。
6.3 Hello的fork进程创建过程
调用fork():Shell创建子进程,复制父进程的地址空间、文件描述符等
返回值区分:
父进程:fork() 返回子进程PID
子进程:fork() 返回0
子进程任务:调用 execve() 加载 hello 程序,替换当前进程映像
6.4 Hello的execve过程
功能:加载并执行 hello 程序,替换当前进程的代码段、数据段和堆栈
参数传递:
argv:命令行参数数组(["学号", "姓名", ...])
envp:环境变量数组(如 PATH=/usr/bin)
执行后:
进程的入口点变为 hello 的 _start 函数
6.5 Hello的进程执行
进程上下文:包括寄存器值、程序计数器(PC)、栈指针(SP)等。
时间片调度:操作系统分配CPU时间片(如10ms),时间耗尽后触发时钟中断,切换进程。
用户态与核心态转换:
系统调用(如 sleep()):从用户态切换到核心态,执行内核代码。
中断处理(如 Ctrl-C):内核接管控制权,处理信号后返回用户态。
6.6 hello的异常与信号处理
异常类型 | 触发信号 | 处理方式 |
中断(Ctrl-C) | SIGINT | 终止进程 |
停止(Ctrl-Z) | SIGTSTP | 挂起进程至后台 |
非法操作 | SIGSEGV | 终止进程并生成核心转储 |
1.Ctrl-Z 挂起
2.ps
3.jobs
4.pstree
5.kill
6.fg & Ctrl+C
6.7本章小结
本章探讨了hello程序的进程管理机制。进程作为程序的执行实例,为hello提供了资源分配、隔离保护和并发执行的能力。Shell(bash)通过解析用户命令、创建子进程(fork)并加载程序(execve)来管理hello的执行。fork复制父进程环境,而execve将hello的代码和数据载入内存,开启新的执行流程。进程运行时,操作系统通过时间片调度和上下文切换实现多任务并行,并在系统调用或中断时切换用户态与核心态。此外,hello需处理异常和信号,如SIGINT终止进程、SIGTSTP挂起进程,并通过ps、jobs等工具进行监控和管理。本章揭示了进程从创建到终止的全生命周期,体现了操作系统对程序执行的核心控制作用。
第7章 hello的存储管理
7.1 hello的存储器地址空间
在hello程序的执行过程中,存储器地址通过多级抽象实现高效管理:
逻辑地址:程序段内的偏移地址,由编译器和链接器生成,对应代码段或数据段的局部位置。
hello的main函数在代码段中的逻辑地址为0x4004d6,表示相对于代码段基址的偏移。
线性地址:Intel处理器通过段式管理将逻辑地址转换为全局线性地址(段基址 + 偏移)。若hello的代码段基址为0x08048000,逻辑地址0x100转换为线性地址0x08048100。
虚拟地址:hello进程看到的独立地址空间(0x8048000),通过页表映射到物理地址,实现进程间隔离。
物理地址:实际内存芯片上的硬件地址,由MMU通过页表转换得到。
当hello执行printf时,指令的逻辑地址(如0x4010a0)经段式管理转换为线性地址,再通过页式管理映射到物理地址,最终访问内存中的字符串常量(.LC1位于.rodata段)。
7.2 Intel逻辑地址到线性地址的变换-段式管理
Intel使用段式管理完成逻辑地址到线性地址的转换:
段选择符:代码段寄存器(CS)存储段选择符,指向全局描述符表(GDT)中的段描述符。
段描述符:包含段基址、界限和权限。hello的代码段基址为0x08048000。
线性地址计算:线性地址 = 段基址 + 逻辑偏移量。
hello的main函数逻辑地址0x4004d6,在段基址0x08048000下转换为线性地址0x0804d4d6(假设段基址为0x08048000)。
7.3 Hello的线性地址到物理地址的变换-页式管理
操作系统通过页式管理将虚拟地址(线性地址)映射到物理地址:
分页机制:虚拟地址划分为页号和页内偏移
页表查询:通过页表基址寄存器找到页表,逐级查询四级页表项(PML4→PDPT→PD→PT),获取页框。
物理地址生成:物理地址 = PFN << 12 | Offset
假设hello访问虚拟地址0x402038(存储"Hello %s %s %s\n"),页号为0x402,页内偏移0x038,通过页表查询PFN0x2000,物理地址为0x2000038。
7.4 TLB与四级页表支持下的VA到PA的变换
现代系统通过TLB和四级页表加速地址转换:
四级页表:虚拟地址划分为4级索引(如9+9+9+9+12位),逐级查询页表项
TLB缓存:转换后备缓冲器(TLB)缓存近期页表项。若命中,直接获取PFN;否则遍历四级页表
当hello首次访问0x4010f0(_start函数入口)时,TLB未命中,需4次内存访问完成页表遍历。后续访问同一页时,TLB命中,转换延迟降至1~2周期
7.5 三级Cache支持下的物理内存访问
物理内存访问通过三级Cache优化性能:
缓存层级:L1(指令/数据分离)、L2(统一)、L3(共享)缓存,逐级容量增大、速度降低。
缓存行匹配:物理地址被划分为标记(Tag)、组索引(Set Index)和块偏移(Offset),通过组相联映射定位缓存行。
替换策略:LRU(最近最少使用)算法淘汰旧数据。
hello访问物理地址0x2000038时,若L1 Cache命中(Tag匹配),1~2周期完成读取;否则触发L2/L3访问(约10~40周期)或内存加载(约100周期)。
7.6 hello进程fork时的内存映射
fork创建子进程时采用写时复制(Copy-on-Write)机制:
共享父进程页表:子进程初始共享父进程的物理页,页表项标记为只读。
写时复制:当任一进程尝试修改共享页时,触发页错误,内核分配新物理页并复制内容。
若父进程修改hello的全局变量,子进程将获得独立的物理页副本,保证进程隔离性。
7.7 hello进程execve时的内存映射
execve加载hello程序时重建内存映射:
释放旧地址空间:清除原进程的代码、数据段和堆栈。
加载新程序:将hello的代码段映射到0x8048000,数据段到0x8050000,并初始化堆栈和堆。
动态链接:若使用共享库(如libc.so),通过动态链接器映射到进程空间。
execve后,hello的.text段映射到0x4010f0(可执行代码),.rodata段到0x402000(只读字符串),权限为R-X和R--。
7.8 缺页故障与缺页中断处理
触发条件:访问未加载的虚拟页(页表项无效)或写只读页(如COW场景)。
中断处理:检查虚拟地址合法性。
分配物理页(或从磁盘换入)。
更新页表项并重新执行指令。
hello首次访问堆内存时触发缺页,内核分配零页(全零填充的物理页),避免未初始化数据错误。
7.9动态存储分配管理
printf调用malloc时,动态内存管理策略如下:
隐式空闲链表:通过块头部的大小字段遍历空闲块,合并相邻空闲块减少碎片。
显式空闲链表:维护空闲块链表,加速分配。
伙伴系统:按2的幂划分块,减少外部碎片但可能产生内部碎片。
glibc的malloc为hello分配小内存时,优先从线程本地缓存(tcache)获取,减少锁竞争;大内存则通过mmap系统调用直接映射。
7.10本章小结
本章系统解析了hello程序的存储管理机制。从逻辑地址到物理地址的转换依赖段式与页式管理,TLB和四级页表优化了映射效率。三级Cache通过层级缓存降低内存访问延迟。进程fork时的写时复制与execve的内存重建机制,保障了进程的隔离与灵活性。缺页中断实现按需加载,动态内存管理策略(如malloc)平衡了效率与碎片问题。存储管理是操作系统资源调度的核心,支撑了hello程序从地址抽象到物理执行的全流程。
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
Linux采用统一的“一切皆文件”模型管理IO设备:
设备抽象:所有硬件设备(键盘、显示器、磁盘等)被抽象为文件,位于/dev目录下,如/dev/tty表示终端设备。
设备分类:
字符设备:以字节流形式访问(如键盘、串口),通过字符设备文件(如/dev/input/event0)操作。
块设备:以固定大小数据块访问(如硬盘、SSD),通过块设备文件(如/dev/sda)操作。
设备驱动管理:内核通过设备驱动程序与硬件交互,用户程序通过标准Unix IO接口(如read、write)访问设备,无需关心底层实现。
hello通过printf输出到终端时,实际是向字符设备文件/dev/tty写入数据,由显示驱动处理;getchar则从同一设备读取键盘输入。
8.2 简述Unix IO接口及其函数
Unix IO接口提供以下核心系统调用:
open:打开设备文件(如/dev/tty) 返回文件描述符(fd)。
read:从fd读取数据(如键盘输入) 函数原型:ssize_t read(int fd, void *buf, size_t count)
write:向fd写入数据(如屏幕输出) 函数原型:ssize_t write(int fd, const void *buf, size_t count)
close:关闭fd,释放资源
ioctl:控制设备参数(如设置终端模式)
IO模式:
阻塞IO:默认模式,若设备无数据,进程挂起等待(如getchar等待键盘输入)。
非阻塞IO:通过O_NONBLOCK标志设置,立即返回错误码EAGAIN。
异步IO:通过信号或回调通知数据就绪(如aio_read)。
8.3 printf的实现分析
1.格式化字符串处理:调用vsprintf将格式字符串和参数转换为格式化后的字符串(如"Hello 学号 姓名 手机号 1"),存入用户态缓冲区。
2.系统调用写入:调用write系统调用(函数号SYS_write),触发软中断(如syscall指令或int 0x80),切换到内核态。内核通过终端设备驱动将字符串写入显示缓冲区(vram)。
3.显示驱动处理:字模库映射将ASCII字符转换为像素点阵(16×16点阵),vram更新将像素RGB值写入显存对应位置(每个像素占4字节,格式为ARGB)。
屏幕刷新,显示控制器按刷新率(如60Hz)逐行读取vram,通过信号线输出到显示器。
8.4 getchar的实现分析
1.键盘中断触发:用户按下按键时,键盘控制器发送中断请求(IRQ1),CPU调用键盘中断处理程序(ISR)。
2.扫描码转换:ISR读取键盘扫描码(如回车键为0x1C),转换为ASCII码(如'\n'),存入内核缓冲区tty_read_buf。
3.系统调用读取:getchar调用read系统调用(fd=0),从缓冲区读取ASCII码,若缓冲区为空则阻塞等待。遇到换行符('\n')时,read返回缓冲区内容,用户程序继续执行。
8.5本章小结
本章探讨了hello程序的IO管理机制。Linux通过“一切皆文件”模型将硬件设备抽象为字符设备(如终端/dev/tty)或块设备,用户程序通过标准Unix接口(如read、write)实现设备访问。printf的执行分为三步:vsprintf格式化字符串生成输出内容,write系统调用将数据写入显存(vram),显示驱动将ASCII字符转换为像素矩阵并刷新屏幕。getchar依赖键盘中断机制,中断处理程序将扫描码转为ASCII码存入内核缓冲区,read系统调用阻塞读取直至回车触发返回。
结论
Hello程序的生命周期完整体现了计算机系统的核心设计理念。从代码(Program)到进程(Process)的P2P流程中,编译系统通过预处理、编译、汇编、链接将高级语言转换为可执行文件;操作系统通过进程管理(fork、execve)和存储管理(段页式转换、TLB、Cache)为程序提供执行环境;硬件与内核协作完成指令执行、地址转换和IO操作。
通过本项目,我深切感悟到:
分层抽象:计算机系统通过编译链、操作系统和硬件的分层抽象,实现了从高级语言到物理硬件的无缝衔接,极大提升了开发效率与系统可靠性。
动态性与灵活性:动态链接、写时复制和按需分页等机制,平衡了资源利用率与性能,展现了系统设计的精巧。
优化空间:在工具链配置(如编译器优化选项)、内存管理(如减少缺页中断)和IO效率(如缓冲策略)等方面仍有优化潜力。
本项目不仅巩固了计算机系统核心知识,更启发了对系统设计与实现的创新思考。
附件
文件名 | 作用 |
hello.c | |
hello.i | 预处理后的代码文件,展开所有宏、头文件,并删除注释。 |
hello.s | 汇编代码文件,包含x86-64汇编指令。 |
hello.o | 可重定位目标文件(ELF格式),尚未链接。 |
hello | 最终的可执行文件,包含动态链接库(如libc)。 |
hello_o_dump.txt | hello.o的反汇编输出(objdump -d), 用于分析机器码和汇编指令的对应关系。 |
hello_o_elf.txt | hello.o的ELF结构分析(readelf), 查看节头、符号表、重定位信息等。 |
hello_dump.txt | hello的反汇编输出(objdump -d), 分析可执行文件的机器码和运行时行为。 |
hello_elf.txt | hello的ELF结构分析(readelf), 查看程序入口、动态链接信息等。 |
原始的C语言源代码文件 |
预处理后的代码文件,展开所有宏、头文件,并删除注释。 |
参考文献
[1] Randal E. Bryant ,David O'Hallaron. 深入理解计算机系统(第3版), 机械工业出版社,2016.11
[2] Alfred V. Aho,Monica S.Lam,Ravi Sethi,Jeffrey D. Ullman. 编译原理, 机械工业出版社,2008.12
[3] Andrew S. Tanenbaum. 现代操作系统(第3版),机械工业出版社,2009.7
相关文章:

哈工大计统大作业-程序人生
摘 要 本项目以“程序人生-Hellos P2P”为核心,通过编写、预处理、编译、汇编、链接及运行一个简单的Hello程序,系统探讨了计算机系统中程序从代码到进程的全生命周期。实验基于Ubuntu环境,使用GCC工具链完成代码转换,分析了预处…...

设计模式——装饰器设计模式(结构型)
摘要 文中主要介绍了装饰器设计模式,它是一种结构型设计模式,可在不改变原有类代码的情况下,动态为对象添加额外功能。文中详细阐述了装饰器模式的角色、结构、实现方式、适合场景以及实战示例等内容,还探讨了其与其他设计模式的…...

途景VR智拍APP:开启沉浸式VR拍摄体验
在数字化时代,VR技术以其沉浸式的体验逐渐走进了人们的日常生活。途景VR智拍APP作为一款集看图和拍照于一体的VR软件,为用户带来了全新的视觉体验和便捷的拍摄方式,无论是专业摄影师还是普通用户,都能轻松上手,拍出令人…...

Linux环境搭建MCU开发环境
操作系统版本: ubuntu 22.04 文本编辑器: vscode 开发板: stm32f103c8t6 调试器: st-link 前言 步骤一: 安装交叉编译工具链 步骤二: 创建工程目录结构 步骤三: 调试…...
Android高级开发第一篇 - JNI(初级入门篇)
文章目录 Android高级开发JNI开发第一篇(初级入门篇)🧠 一、什么是 JNI?✅ 为什么要用 JNI? ⚙️ 二、开发环境准备开发工具 🚀 三、创建一个支持 JNI 的 Android 项目第一步:创建新项目项目结构…...
Kubernetes RBAC权限控制:从入门到实战
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言:为什么需要RBAC? 在Kubernetes集群中,权限失控是导致安全漏洞的核心原因之一。试想以下场景: 开发…...
python实战项目71:基于Python的US News世界大学排名数据爬取
python实战项目71:基于Python的US News世界大学排名数据爬取 一、项目背景1.1 研究意义1.2 技术背景1.3 应用场景二、爬虫系统设计与实现2.1 分析页面、寻找数据真实接口2.2 发送请求,获取响应内容2.3 提取数据2.4 保存数据三、完整代码四、总结与展望一、项目背景 1.1 研究…...

【基础算法】高精度(加、减、乘、除)
文章目录 什么是高精度1. 高精度加法解题思路代码实现 2. 高精度减法解题思路代码实现 3. 高精度乘法解题思路代码实现 4. 高精度除法 (高精度 / 低精度)解题思路代码实现 什么是高精度 我们平时使用加减乘除的时候都是直接使用 - * / 这些符号,前提是进行运算的数…...
跨平台开发框架electron
桌面端开发框架有很多,比如C#的WPF和Winform,Dart的Flutter,JS的Electron,Rust的Tauri。 目前应用比较广的是Electron,比如我们常见的开发工具VsCode,就是基于Electron开发的。 所以这篇文章我们就来聊聊Electron。 简…...

Windows最快速打开各项系统设置大全
目录 一、应用背景 二、设置项打开方法 2.1 方法一界面查找(最慢) 2.2 方法二cmd命令(慢) 2.3 方法三快捷键(快) 2.4 方法四搜索栏(快) 2.5 方法五任务栏(最快&am…...

嵌入式编译工具链熟悉与游戏移植
在自己的虚拟机Ubuntu系统下,逐步编译 mininim源码(波斯王子重制开源版) 指令流程 sudo apt-get remove liballegro5-dev liballegro-image5-dev \liballegro-audio5-dev liballegro-acodec5-dev liballegro-dialog5-dev sudo apt-get install automak…...

DeepSeek-R1-0528,官方的端午节特别献礼
DeepSeek:端午安康!刻在国人骨子里的浪漫 2025 年 05 月 28 日 | DeepSeek 端午特别献礼 当粽叶飘香时,DeepSeek 悄然带来一份节日惊喜 版本号 DeepSeek-R1-0528 正式上线 官方赋予它的灵魂是: 思考更深 推理更强 用户通过官网…...
LNMP环境中php7.2升级到php7.4
以下是 CentOS 7 上从 PHP 7.2 升级到 PHP 7.4 的详细步骤,结合知识库中的方法和注意事项: 1.备份现有环境 #备份 PHP 配置文件 cp /etc/php.ini /etc/php.ini.bak cp -r /etc/php.d /etc/php.d.bak#备份网站文件和数据库 tar -czvf website_backup.tar…...

001 flutter学习的注意事项及前期准备
在学习flutter之前,还需要进行一些初始的配置,然后才可以学习flutter 1.安装flutter 国内官网:https://flutter.cn 国际官网:https://flutter.dev 安装完成后,按照官网上面的操作步骤进行配置…...
FactoryBean 接口
Spring 框架中 FactoryBean 接口的特性,这是 Spring 提供的一种特殊机制,用于创建和管理复杂 Bean。让我通过示例和解释帮您理解这个概念。 一、FactoryBean 是什么? FactoryBean 是 Spring 框架提供的一个工厂接口,用于创建复杂…...

CS144 - Lecture 1 记录
CS144 - Lecture 1 由于没讲义,全看课了,系统性的总结有点难,记一些有趣的东西吧。 数据链路和网络层的传输 我们可以看见,对于发送方,我们的数据链路层为我们的网络层提供服务,在经过路由的时候…...
【Redis】大key问题详解
目录 1、什么是大key2、大key的危害【1】阻塞风险【2】网络阻塞【3】内存不均【4】持久化问题 3、如何发现大key【1】使用内置命令【2】使用memory命令(Redis 4.0)【3】使用scan命令【4】监控工具 4、解决方案【1】拆分大key【2】使用合适的数据结构【3】…...

【数据结构】——二叉树--链式结构
一、实现链式结构二叉树 二叉树的链式结构,那么从名字上我们就知道我们这个二叉树的底层是使用链表来实现的,前面我们的二叉树是通过数组来实现的,那么在其是完全二叉树的情况下,此时我们使用数组来实现就会使得其空间浪费较少&a…...
TKernel模块--杂项
TKernel模块–杂项 1.DEFINE_HARRAY1 #define DEFINE_HARRAY1(HClassName, _Array1Type_) \ class HClassName : public _Array1Type_, public Standard_Transient { \public: …...

充电便捷,新能源汽车移动充电服务如何预约充电
随着新能源汽车的普及,充电便捷性成为影响用户体验的关键因素之一。传统的固定充电桩受限于地理位置和数量,难以完全满足用户需求,而移动充电服务的出现,为车主提供了更加灵活的补能方式。通过手机APP、小程序或在线平台ÿ…...
laya3的2d相机与2d区域
2d相机和2d区域都继承自Sprite。 2d相机必须作为2d区域的子节点,且2d相机必须勾选isMain才能正常使用。 2d区域下如果没有主相机,则他和Sprite无异,他的主要操作皆是针对主相机。 2d相机可以调整自己的移动范围,是否紧密跟随&a…...
2024 CKA模拟系统制作 | Step-By-Step | 19、题目搭建-升级集群
目录 免费获取题库配套 CKA_v1.31_模拟系统 一、题目 二、考点分析 1. Kubernetes 升级策略 2. 节点维护操作 3. 组件升级技术 4. 权限与访问控制 三、考点详细讲解 1. Kubernetes 升级流程 2. 组件版本兼容性 3. drain 操作深度解析 四、实验环境搭建步骤 五、总…...
47道ES67高频题整理(附答案背诵版)
1.ES5、ES6(ES2015)有什么区别? ES5(ECMAScript 5)和ES6(也称为ECMAScript 2015)是JavaScript语言的两个版本,它们之间有一些重要的区别和改进: let 和 const 关键字: …...
Lauterbach TRACE32专栏
官方培训视频 trace32使用技巧博文 系统崩溃分析 - vmcore 加载到 Trace32 Trace 32 离线 dump 分析环境搭建方法 内核trace分析工具入门 如何用Trace32分析内核死机 trace32调试攻略 TRACE32调试:基础调试技巧之SystemMode、SNOOPer https://cloud.tencent…...

基于 Chrome 浏览器扩展的Chroma简易图形化界面
简介 ChromaDB Manager 是基于 Chrome 浏览器扩展的一款 ChromaDB(一个流行的向量数据库)的数据查询工具。提供了一个用户友好的界面,可以直接从浏览器连接到本地 ChromaDB 实例、查看集合信息和分片数据。本工具特别适合开发人员快速查看和…...
python打卡day41
简单CNN 数据增强卷积神经网络定义的写法batch归一化:调整一个批次的分布,常用与图像数据特征图:只有卷积操作输出的才叫特征图调度器:直接修改基础学习率 一、数据增强 在图像数据预处理环节,为提升数据多样性&#x…...

IM系统的负载均衡
1.IM场景的负载均衡 2.方案总览 SDK层想要连接一个TCP网关或者WebSocket网关的方案 SDK单地址:在SDK中写死某个网关的IP或者域名,缺点是更换地址需要重新打包SDK SDK多地址:防止某一个地址嗝屁了写上多个地址用足保持高可用 暴露接口给客户端:SDK层访问接口动态获得地址 注…...
前端八股 tcp 和 udp
都是传输层协议 udp 数据报协议 不可靠面向数据包对于应用层传递的报文加上UDP首部就传给网络层 tcp 传输控制协议 可靠 会将报文分段进行传输 区别: 1.tcp 可靠 udp 不可靠 2.tcp 面向连接 三握四挥 udp 无连接 3.tcp面向字节流 udp面向报文 4.效率低 效率高…...

使用 Zabbix 监控 MySQL 存储空间和性能指标的完整实践指南
目录 引言 一、最终目标支持功能 二、监控方案设计 2.1 技术选型 2.2 设计思路 三、实现步骤 3.1 准备工作 3.11 创建 MySQL 监控账号 3.12 配置 .my.cnf 文件 3.2 编写统一脚本 3.3 配置 Zabbix Agent UserParameter 3.4 Zabbix 前端配置建议 四、总结 引言 MySQL …...

【技能拾遗】——家庭宽带单线复用布线与配置(移动2025版)
📖 前言:在家庭网络拓扑中,客厅到弱电箱只预埋了一根网线,由于已将广电的有线电视取消并改用IPTV。现在需要解决在客厅布置路由器和观看IPTV问题,这里就用到单线复用技术。 目录 🕒 1. 拓扑规划ὕ…...