
计算机系统原理
大作业
题 目 程序人生-Hello’s P2P
专 业
学 号
班 级
学 生 yyx
指 导 教 师
计算学部
2025年9月
本文以经典程序 “Hello World” 为研究对象,基于计算机系统原理完整分析其从源码到执行的全生命周期过程。研究覆盖预处理、编译、汇编、链接四大核心环节,深入探讨进程管理、存储管理、IO 管理等系统底层机制,还原 Hello 程序在 Ubuntu 环境下的 “P2P(预处理 - 编译 - 汇编 - 链接)” 与 “O2O(源码到运行)” 全流程。通过实验验证与工具分析,揭示计算机系统软硬件协同工作的内在逻辑,为理解系统底层运行机制提供实践参考。
关键词:计算机系统原理;编译;链接;进程管理;存储管理;IO 机制
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
自媒体发表截图
目 录
第1章 概述
1.1 Hello简介
Hello 程序的“P2P”过程即预处理(Preprocessing)、编译(Compilation)、汇编(Assembly)、链接(Linking)的流水线式处理流程:源码经预处理展开宏与头文件,编译转换为汇编语言,汇编生成机器指令级目标文件,链接整合依赖库形成可执行文件。其 “O2O” 过程则是从源码(Online)到运行(Offline):用户通过 Shell 触发执行,系统完成进程创建、地址映射、IO 交互,最终在终端输出 。
1.2 环境与工具
处理器:Intel(R) Core(TM) i9-14900HX (2.20 GHz)
机带RAM 16.0 GB (15.6 GB 可用)
系统类型 64 位操作系统, 基于 x64 的处理器
操作系统 Windows 11 专业版(搭载 VMware Workstation 17 Pro 虚拟机)
虚拟机系统:Ubuntu 20.04.4 LTS(64 位)
编译工具链:GCC 9.0.4(GNU Compiler Collection)
调试工具:GDB 9.2(GNU Debugger)
分析工具:readelf 2.34、objdump 2.34
1.3 中间结果
hello.i 预处理后文件,展开头文件、宏定义,删除注释
hello.s 编译后汇编文件,将 C 语言转换为 x86-64 汇编指令
hello.o 汇编后可重定位目标文件,包含机器指令与重定位信息
hello.out 最终可执行文件
hello 最终可执行文件
hello 最终可执行文件,经链接整合后可直接运行
1.4 本章小结
明确了 Hello 程序的研究核心的 “P2P” 与 “O2O” 流程,梳理了实验所需的软硬件环境与工具链,介绍了各环节生成的中间文件及功能。
2.1 预处理的概念与作用
预处理是编译过程的第一步,由预处理器完成,作用是对源码进行文本级转换,为编译阶段提供纯净、完整的输入文件。核心功能包括:展开 #include 头文件(将标准库或自定义头文件内容嵌入源码)、替换 #define 宏定义(文本替换,无类型检查)、删除注释、处理条件编译指令。
2.2在Ubuntu下预处理的命令
cpp hello.c > hello.i
![]()
图 1预处理命令
2.3 Hello的预处理结果解析
进行了头文件展开,宏替换,注释删除,完成文本替换。

图 2 预处理结果
2.4 本章小结
本章阐述了预处理的概念与文本转换核心作用,验证了 Ubuntu 环境下 GCC 预处理命令的执行流程,通过对比源码与预处理结果,明确了头文件展开、宏替换等关键操作的效果。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
编译是预处理后到汇编前的关键阶段,由编译器(gcc 的 cc1 组件)完成,核心作用是将预处理后的.i文件(C 语言源码)转换为汇编语言.s文件。该过程包含词法分析(识别关键字、标识符、常量)、语法分析(检查语法正确性,生成抽象语法树)、语义分析(检查类型匹配、变量作用域)、优化(分为机器无关优化如常量传播,机器相关优化如指令重排),最终将高级语言逻辑映射为特定架构的汇编指令。
3.2 在Ubuntu下编译的命令
cc1 hello.i -o hello.s
如果cc1指令不在path中需找到cc1所在位置 用/path/cc1 hello.i -o hello.s

图 3 编译命令
3.3 Hello的编译结果解析
编译出结果为下:

图 4 编译结果
3.3.1字符串常量的处理
常量:
字符串常量
存储在只读数据段.rodata中

图 5 编译结果分析
变量:局部变量在栈帧中分配

图 6 编译结果分析
3.3.2赋值操作
赋值操作:
![]()
图 7 编译结果分析
比较与跳转:

3.3.3函数调用的处理
call实现函数调用

图 8 函数调用
3.3.4函数栈帧的构建与销毁
main 函数的栈帧操作:
return进行销毁

图 9 函数构建
3.3.5分支与跳转

控制循环:

3.4 本章小结
展示编译阶段从.i文件到.s文件的转换,揭示了编译器对字符串常量、整型数据、函数调用、栈帧操作的处理逻辑,体现了高级语言语法到汇编指令的映射规则,以及编译器对其的优化。编译阶段是连接高级语言与机器指令的关键,其生成的汇编代码直接决定了程序的底层执行逻辑。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
汇编是编译后的核心步骤,由汇编器(as)完成,核心作用是将汇编语言.s文件转换为机器语言二进制的可重定位目标文件.o。该过程将汇编指令翻译为对应架构的机器码,同时处理汇编伪指令,生成包含代码段、数据段、重定位表、符号表等结构的ELF文件。.o文件中的指令已为机器可执行格式。
4.2 在Ubuntu下汇编的命令
as hello.s -o hello.o
![]()
图 10 汇编命令
4.3 可重定位目标elf格式
图 11 elf查看
图 12 elf分析
节头表详细解读:
(NULL):占位符节
.text(核心:代码段)
.rela.text(核心:代码段重定位表)
.data(数据段:已初始化全局 / 静态变量)
.bss(数据段:未初始化全局 / 静态变量)
.rodata.str1.8(只读数据:8 字节对齐字符串)
.eh_frame(异常处理帧段)
.rela.eh_frame(异常帧重定位表)
.symtab(核心:符号表)
.strtab(字符串表:符号名称)

图 13 elf分析
readelf -r 输出了两个重定位节(.rela.text、.rela.eh_frame),共 10 个重定位条目。
重定位的核心意义是:解决 .o 文件中未解析的地址 / 符号,为链接阶段将 hello.o 与 libc.so 绑定做准备。偏移量:该条目对应目标节(如 .text)中的偏移地址(即需要修正的指令 / 数据位置);信息:高16位 = 符号表索引(对应 .symtab 中的符号),低 16 位 = 重定位类型;类型:R_X86_64_xxx,x86-64 架构专属重定位类型,决定链接阶段的地址修正方式;符号值:符号在 .symtab 中的初始值(.o 文件中多为 0,链接后修正为实际虚拟地址);符号名称 + 加数:需要绑定的目标符号,加数为额外地址偏移(通常为0或-4,用于指令偏移补偿)。
4.4 Hello.o的结果解析
从 objdump 输出中,每一行指令都对应一组完整的机器语言指令,机器语言由操作码和操作数两部分构成,部分指令可无操作数(如栈操作、返回指令)。
55 push %rbp 栈操作:将 %rbp 寄存器值压入栈中
c3 retq 函数返回:将程序执行流交还给调用者
e8 callq 函数调用:跳转到目标函数地址执行
48 8d lea 地址加载:计算内存地址并放入寄存器
48 89 e5 mov %rsp,%rbp e5(对应 %rbp)、隐含 %rsp 寄存器操作数
b8 00 00 00 00 mov $0x0,%eax 00 00 00 00(对应 $0x0)、%eax 立即数 + 寄存器操作数
48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi 00 00 00 00(对应 0x0)、%rip、%rdi 内存地址(相对偏移)+ 寄存器操作数
e8 00 00 00 00 callq 10 <main+0x10> 00 00 00 00对应偏移量 分支转移目标地址(相对偏移)
所以汇编语言是机器码的符号化表示。
hello.s是汇编源代码文件,而objdump的反汇编是目标文件反汇编。
两者的核心执行逻辑(main 函数的栈帧操作、参数传递、函数调用、返回)完全一样。差异集中在两个与重定位相关的指令上,恰好对应 objdump -r 显示的 2 个重定位项。

图 14 objdump -d -r hello.o的输出结果
图 15 hello.s汇编源代码文件
4.5 本章小结
本章阐述了汇编阶段的核心作用,验证了 Ubuntu 下汇编命令的执行流程,通过 readelf 和 objdump 工具分析了 hello.o 的 ELF 结构、重定位表与反汇编结果。汇编过程实现了汇编指令到机器码的转换,生成的.o文件包含完整的机器指令,但需链接阶段修复外部符号引用。机器语言与汇编语言存在一一对应的映射关系,重定位项则为后续链接提供了符号修复的关键信息。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
链接是汇编后的关键步骤,由链接器(ld)完成,核心作用是将可重定位目标文件(hello.o)与所需的系统库文件(如 libc.so)合并,解决符号引用(如puts函数),生成可执行目标文件hello。该过程包含符号解析(绑定符号定义与引用)、重定位(修正.o文件中未确定的地址)、段合并(将多个.o文件的同名段如.text、.rodata合并),最终生成结构完整、地址确定、可被系统加载执行的 ELF 文件,解决了目标文件间的依赖关系与地址引用问题。
5.2 在Ubuntu下链接的命令
ld -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 /usr/lib/gcc/x86_64-linux-gnu/9/crtbegin.o hello.o -lc /usr/lib/gcc/x86_64-linux-gnu/9/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o -z relro -o hello.out

图 16 链接命令
5.3 可执行目标文件hello的格式
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

图 17 readelf结果

图 18 readelf结果
5.4 hello的虚拟地址空间
图 19 虚拟地址空间查询
可发现与 5.3 节 ELF 段的虚拟地址一致,验证了程序头表对虚拟地址分配的定义。
5.5 链接的重定位过程分析
hello 拥有完整的动态链接相关段(.plt、.plt.got、.plt.sec),而 hello.o 无这些段,仅包含核心.text 段;
hello 的指令地址是最终有效虚拟地址(main函数位于0x1149,puts@plt 位于 0x1050),而 hello.o 的 main/puts 相关指令地址为临时偏移;
hello 中对外部函数(如 puts)的调用已绑定到 PLT(过程链接表)(callq 1050 <puts@plt>),而 hello.o 中对 puts 的调用是未解析的,指令中包含重定位占位符(通常是 0x0 或临时偏移,需后续重定位修正);
hello 包含程序运行的完整入口流程(_start0x1060,调用__libc_start_main 启动main),而 hello.o 仅包含 main 函数本身,无程序启动 / 终止的完整逻辑。

图 20 链接重定位分析
hello.o 的 .text 段从 0x0 开始(临时偏移地址,无实际运行意义,仅为编译占位);
存在两个明确的重定位项均位于 main 函数内,对应两条未完成地址修正的指令;
重定位项旁标注了关联符号和偏移修正值,这是链接器后续处理的核心依据。
lea 指令的目标是获取 .rodata 段中字符串常量的有效地址,并传递给 puts 函数。但 hello.o 编译阶段,.rodata 段的最终虚拟地址尚未确定(链接时才会分配),因此需要通过 R_X86_64_PC32 重定位,让链接器在链接阶段直接修正占位符 00 00 00 00 为有效相对偏移地址,完成静态重定位。
5.6 hello的执行流程
使用gdb/edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程(主要函数)。请列出其调用与跳转的各个子程序名或程序地址。

图 21 入口查询
入口为0x1060

图 22 准备查询
_start:
初始化进程运行环境(栈、寄存器、命令行参数等);
调用 __libc_start_main,传递 main 函数地址作为参数;
不会直接调用 main,而是通过 __libc_start_main 间接启动 main。
利用gdb中的lay asm ,bt指令

图 23 查看栈
验证完成_start → __libc_start_main → main 的完整调用链。
5.7 Hello的动态链接分析


图 24 查看got前后变化
链接前后GOT 的核心变化,也是动态链接的本质:GOT 地址的内容更新。



图 25 查询链接库的使用
动态链接的核心:编译链接时做准备,运行时才解析绑定,不打包动态库代码,依赖系统共享库;
hello 程序的动态链接核心依赖:libc.so.6(提供核心函数)和 ld-linux-x86-64.so.2(动态链接器)。
5.8 本章小结
本章详细分析了链接的概念、命令与核心流程,通过工具验证了可执行文件的 ELF 结构与虚拟地址空间分布,展示了重定位过程中符号解析与地址修正的逻辑,以及动态链接的延迟绑定机制。链接阶段解决了目标文件与库文件的依赖关系,生成了地址确定、可加载执行的文件,是程序从二进制目标文件到可运行程序的关键。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程是操作系统对运行中程序的抽象,是资源分配与调度的基本单位。进程本质是程序执行的上下文集合。其核心作用包括:隔离资源(通过地址空间隔离不同进程的数据与代码)、实现并发执行(操作系统通过进程切换模拟多任务)、响应用户请求(将程序逻辑转化为实际运行结果)。hello 程序被执行时,操作系统会为其创建独立进程,分配 CPU、内存等资源,直至程序终止后回收资源。
6.2 简述壳Shell-bash的作用与处理流程
在bash中用户与操作系统内核交互的命令行解释器,核心作用是解析用户输入的命令,触发对应程序的执行。
1.读取命令:通过键盘缓冲区读取用户输入的./hello字符串;命令解析:bash 解析命令为程序路径与参数;
2.创建子进程:调用fork()系统调用创建子进程,子进程复制 bash 的地址空间;
3.加载程序:子进程调用execve()系统调用,销毁原有地址空间,加载 hello 程序的 ELF 文件;
4.等待执行:父进程(bash)调用wait()系统调用,阻塞等待子进程终止;
5.回收资源:子进程执行完毕后,内核发送SIGCHLD信号,父进程回收子进程资源,bash 恢复等待用户输入。
6.3 Hello的fork进程创建过程
1.为新进程创建进程控制块(包含进程 ID、状态、优先级等信息),分配唯一 PID。
2.地址空间复制:子进程共享父进程(bash)的代码段。
3.初始化上下文:子进程继承父进程的寄存器状态,修改程序计数器(rip)为fork()返回后的指令地址,设置返回值为0。
4.进程状态转换:子进程创建后进入就绪态,等待 CPU 调度;父进程则进入等待态,等待子进程执行。
6.4 Hello的execve过程
子进程创建后,通过execve("./hello", NULL, NULL)系统调用加载 hello 程序。
1.销毁原有地址空间:子进程销毁从父进程继承的 bash 地址空间(代码段、数据段、栈等);
2.解析 ELF 文件:读取 hello 的 ELF 头部,获取程序头表信息,确定段的加载地址与权限;
3.建立新地址空间:为 hello 程序分配虚拟地址空间,将 ELF 中的.text、.rodata 等段映射到对应虚拟地址;
4.初始化栈与寄存器:设置栈指针(rsp)指向用户栈底部,将程序入口地址写入程序计数器(rip);
5.动态链接初始化:若为动态链接程序,触发动态链接器加载依赖库,完成符号绑定;
6.开始执行:execve()调用返回后,CPU 开始执行 hello 程序的指令,进程进入运行态。
6.5 Hello的进程执行
hello 进程的执行过程本质是操作系统的进程调度与上下文切换过程:
1.调度就绪:hello 进程创建后处于就绪态,等待 CPU 时间片分配;
2.上下文切换:当 CPU 空闲时,调度器选择 hello 进程,保存当前运行进程的上下文(寄存器、PC 等),加载 hello 进程的上下文;
3.用户态执行:CPU 切换至用户态,执行 hello 的用户态代码(main 函数、puts 函数等),此时进程处于运行态;
4.系统调用切换:当执行到puts函数中的write系统调用时,进程触发陷阱(trap),切换至核心态,由内核执行系统调用逻辑;
5.时间片耗尽:若 hello 进程的时间片耗尽,调度器暂停其执行,保存上下文,切换至其他就绪进程;
6.重复调度:直至 hello 程序执行完毕,进程状态转为终止态。
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
6.6 hello的异常与信号处理
hello执行过程中会出现执行过程中,异常主要分为两类核心类型,涵盖主动触发、程序错误、系统干预,会产生SIGINT,SIGTSTP,SIGSEGV,SIGCHLD等信号。
对于 SIGINT(Ctrl+C):默认行为是终止 hello 进程,内核回收进程占用的 CPU、内存、文件描述符等资源,进程直接退出,无额外输出。
对于 SIGTSTP(Ctrl+Z):默认行为是暂停 hello 进程,将进程从运行态切换为停止态,进程暂停所有执行操作,但资源不会被回收,终端会输出 [1]+ Stopped ./hello 提示。暂停后的进程可通过 fg <作业ID> 命令恢复运行,或通过 bg <作业ID> 命令转入后台运行。
对于 SIGCHLD:默认行为是忽略该信号,bash 会通过 wait() 系列系统调用主动回收 hello 进程的僵尸资源,避免产生僵尸进程。
对于 SIGKILL(强制终止):默认行为是立即强制终止 hello 进程,且无法被进程拦截,内核直接回收进程所有资源,这是处理无响应进程的最终手段。
程序运行过程中可以按键盘产生结果如下:
Ctrl-Z

图 27 ctrl-z
jobs

图 28 jobs
pstree

图 29 pstree
fg

图 30 fg
Kill

图 31 kill
异常与信号的处理:Linux 中信号处理通过 “中断驱动” 实现:当信号产生时(如键盘按键),内核暂停当前进程的执行,保存上下文,跳转到对应的信号处理程序);处理完成后,恢复进程上下文,继续执行原指令。
6.7本章小结
本章阐述了进程的概念与核心作用,梳理了 bash shell 处理命令的流程,详细分析了 hello 进程的创建(fork)、程序加载(execve)、执行调度与信号处理机制。进程管理是操作系统实现程序并发执行的基础,信号机制则为进程间通信与异常处理提供了高效方式,确保 hello 程序能够有序执行并响应外部交互。
(第6章2分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
hello 程序运行时涉及四种核心地址类型:
- 逻辑地址:程序源码中使用的地址,是编译器生成的相对地址,未经过段式或页式转换;
- 线性地址:逻辑地址经段式管理转换后的 32 位或 64 位地址,是段式与页式转换的中间地址;
- 虚拟地址:进程地址空间中的地址,由操作系统分配,与物理内存地址无直接关联,进程通过虚拟地址访问内存,无需关注物理地址分配;
- 物理地址:物理内存芯片的实际地址,是 CPU 通过地址总线访问内存的最终地址,由内核通过页式管理将虚拟地址转换得到。
hello 程序的虚拟地址空间范围已在第5章4节明确(如.text 段),物理地址则由内核动态分配,进程不可见。
7.2 Intel逻辑地址到线性地址的变换-段式管理
x86-64 架构下,段式管理的核心作用是权限控制与地址隔离,逻辑地址到线性地址的转换过程如下:
- 逻辑地址结构:逻辑地址由段选择符(16 位)与偏移量(64 位)组成;
- 段选择符解析:段选择符的高 13 位索引指向 GDT中的段描述符,段描述符包含段的基地址、大小、权限等信息;
- 地址计算:x86-64 架构下,段基地址默认为 0,因此线性地址 = 逻辑地址偏移量(用户态进程的逻辑地址与线性地址等价);
- 权限检查:内核验证当前进程是否有权访问该段,若权限不匹配则触发通用保护异常。
7.3 Hello的线性地址到物理地址的变换-页式管理
页式管理是将线性地址(虚拟地址)转换为物理地址的核心机制,x86-64 架构下采用 4KB 页大小,转换过程:
- 虚拟地址拆分:64 位虚拟地址拆分为页目录指针表索引(9 位)、页目录表索引(9 位)、页表索引(9 位)、页内偏移(12 位);
- 页表查找:CPU 通过 CR3 寄存器找到当前进程的页目录指针表(PML4)基址,依次通过各级索引查找页目录表(PDPT)、页目录表(PD)、页表(PT),最终找到页表项(PTE);
- 物理地址计算:页表项(PTE)存储该虚拟页对应的物理页框基地址(4KB 对齐),物理地址 = 物理页框基址 + 页内偏移;
- 缺页检查:若页表项标记为 “未分配”(P 位 = 0),则触发缺页中断(#PF),内核分配物理页框并更新页表项。
7.4 TLB与四级页表支持下的VA到PA的变换
解决四级页表查找的性能开销,CPU 通过 TLB(快表)缓存近期使用的虚拟页 - 物理页映射关系,转换流程优化如下:
- TLB 查找:CPU 先查询 TLB,若虚拟地址对应的页表项已缓存(TLB 命中),直接从 TLB 获取物理页框基址,跳过四级页表查找,耗时仅 1-2 个 CPU 周期;
- TLB 未命中:若 TLB 中无该映射(TLB 未命中),CPU 执行四级页表查找,获取物理页框基址,同时将该映射写入 TLB,供后续访问复用;
- 四级页表协同:TLB 与四级页表形成 “缓存 - 主存” 层级,TLB 缓存热点映射,四级页表存储完整映射,平衡性能与存储开销。
hello 程序的 main 函数与 puts 函数被频繁访问,其虚拟地址到物理地址的映射会被缓存到 TLB,大幅提升执行效率。
7.5 三级Cache支持下的物理内存访问
CPU 的三级 Cache(L1、L2、L3)用于缓存物理内存中的数据与指令,减少 CPU 访问内存的延迟(Cache 访问延迟为 ns 级,内存为百ns级),访问流程:
- Cache查找:CPU 通过物理地址查询 L1 Cache(按数据 / 指令分离缓存,hello 的指令缓存于L1I,数据缓存于L1D);
- Cache命中:若数据 / 指令已在 Cache 中(命中),直接从 Cache 读取,无需访问物理内存;
- Cache未命中:若未命中,依次查询 L2、L3 Cache,若均未命中则访问物理内存,将读取的数据 / 指令写入Cache(按 Cache 行大小,通常64字节),供后续访问复用;
- 缓存策略:采用 LRU(最近最少使用)算法替换 Cache 中的旧数据,确保热点数据(如hello的循环指令、频繁访问的变量)常驻Cache。
7.6 hello进程fork时的内存映射
hello 进程通过fork()创建时,内核采用写时复制(Copy-On-Write)机制进行内存映射,核心流程:
- 共享页映射:fork 初期,子进程(hello 进程)与父进程(bash)共享所有虚拟页的物理映射(代码段、数据段、库文件等),页表项标记为“只读”;
- 写时复制触发:当子进程执行execve()加载 hello 程序时,需要修改虚拟地址空间,此时触发写时复制 —— 内核为子进程分配新的物理页框,复制原页数据,更新子进程的页表项,将权限改为 “可写”;
- 独立地址空间:写时复制完成后,子进程拥有独立的物理内存映射,与父进程的地址空间完全隔离,后续修改不会影响父进程。
7.7 hello进程execve时的内存映射
execve()系统调用加载 hello 程序时,内核重新构建进程的虚拟地址空间映射,核心步骤:
- 销毁旧映射:释放子进程从父进程继承的虚拟页映射(bash 的代码段、数据段等);
- 加载 ELF 段:根据 hello 的 ELF 程序头表,将.text、.rodata 等段映射到对应的虚拟地址,设置权限;
- 映射库文件:动态链接器加载 libc.so 等依赖库,将库的代码段、数据段映射到进程虚拟地址空间的高地址区;
- 建立栈映射:分配用户栈空间,映射为R-W权限,初始化栈帧(包含命令行参数、环境变量指针);
- 建立堆映射:分配堆空间,用于动态内存分配。
7.8 缺页故障与缺页中断处理
缺页故障(Page Fault)是虚拟地址对应的物理页未分配或未加载到物理内存时触发的异常,hello 程序运行时的缺页中断处理流程:
- 缺页触发:CPU 执行指令时,若虚拟地址对应的页表项P位(存在位)为0,触发#PF(缺页中断);
- 中断响应:内核暂停 hello 进程,切换至核心态,保存进程上下文;
- 原因判断:内核检查缺页原因(如页未分配、页被换出到磁盘、权限错误);
- 处理缺页:分配页:内核分配物理页框,更新页表项(设置 P 位 = 1,写入物理页基址);页被换出:从磁盘交换分区加载页到物理内存,更新页表项;
- 恢复执行:缺页处理完成后,内核恢复 hello 进程的上下文,返回触发缺页的指令,重新执行(此时页已存在,可正常访问)。
7.9动态存储分配管理
7.10本章小结
本章详细阐述了 hello 程序运行过程中的地址类型与转换机制,包括段式管理的权限控制、页式管理的地址转换、TLB 与 Cache 的性能优化,以及 fork/execve 时的内存映射、缺页中断处理与动态内存分配策略。存储管理的核心是通过虚拟地址空间隔离进程资源,通过页式转换与缓存机制平衡内存使用效率与访问性能,确保 hello 程序能够高效、安全地访问内存资源。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
Linux 操作系统将所有 IO 设备(如键盘、显示器、磁盘)抽象为文件,采用 “文件描述符(FD)” 统一管理,核心方法包括:
- 设备文件化:每个 IO 设备对应 /dev 目录下的一个设备文件(如显示器对应 /dev/tty),进程通过操作设备文件间接操作 IO 设备;
- 统一 IO 接口:所有设备的 IO 操作(读、写、打开、关闭)均通过标准 Unix IO 函数(open、read、write、close)实现,设备差异由内核驱动隐藏;
- 驱动程序隔离:设备驱动程序是内核与硬件的桥梁,负责将用户态的 IO 请求转换为硬件指令(如向显示器发送显示信号),进程无需关注硬件细节;
- 缓冲区机制:内核为 IO 设备设置缓冲区(如键盘缓冲区、显示器输出缓冲区),减少 CPU 与设备的交互次数,提升 IO 效率。
8.2 简述Unix IO接口及其函数
Unix IO 接口是 Linux 系统提供的标准 IO 函数集合,核心函数及功能:
open(const char *path, int flags, mode_t mode):打开文件(包括设备文件),返回文件描述符(FD),flags 指定打开模式(如 O_RDONLY 读、O_WRONLY 写),mode 指定文件权限;
read(int fd, void *buf, size_t count):从文件描述符 fd 指向的文件中读取 count 字节数据,存入buf缓冲区,返回实际读取字节数;
write(int fd, const void *buf, size_t count):将 buf 缓冲区中的 count 字节数据写入 fd 指向的文件,返回实际写入字节数;
close(int fd):关闭文件描述符,释放内核分配的文件资源;
ioctl(int fd, unsigned long request, ...):执行设备特定的控制操作,适用于特殊 IO 需求。
Linux 系统默认分配三个标准文件描述符:0(STDIN,标准输入,对应键盘)、1(STDOUT,标准输出,对应显示器)、2(STDERR,标准错误,对应显示器)。hello 程序的 printf 输出默认使用 STDOUT(FD=1)。
8.3 printf的实现分析
printf 函数的核心功能是格式化输出字符串,其底层实现依赖 Unix IO 接口与系统调用,完整流程:
格式化字符串生成:printf 接收格式化字符串(如 "% s\n")与参数(如 "Hello World"),调用vsprintf函数将参数按格式嵌入字符串,生成最终要输出的字符串(如 "Hello World\n"),存储在用户态缓冲区;
触发系统调用:printf 检查输出缓冲区,若缓冲区满或包含换行符(\n),调用write系统调用,将缓冲区数据写入 STDOUT(FD=1);
系统调用处理:内核接收write请求,切换至核心态,根据 FD=1 找到对应的设备文件(/dev/tty),调用显示器驱动程序;
驱动程序执行:显示器驱动程序将字符串的 ASCII 码转换为字模库中的点阵数据(如每个字符的 RGB 颜色信息),写入显示内存(VRAM);
硬件显示:显示芯片按刷新频率逐行读取 VRAM 中的点阵数据,通过信号线传输至液晶显示器,最终在屏幕上显示字符串。
8.4 getchar的实现分析
getchar 函数的核心功能是从标准输入读取一个字符,底层依赖键盘中断与 Unix IO 接口,实现流程:
键盘中断触发:用户按下键盘按键时,键盘控制器发送中断信号(IRQ1),CPU 暂停当前进程(如 hello 程序),切换至核心态,执行键盘中断处理子程序;
扫描码转换:中断处理子程序读取键盘发送的扫描码(如按键 'a' 对应扫描码 0x1e),将其转换为 ASCII 码('a' 对应 0x61),存入内核的键盘缓冲区;
函数调用阻塞:getchar 函数调用read(0, buf, 1)系统调用,若键盘缓冲区为空,内核将 hello 进程设置为阻塞态,等待键盘输入;
数据读取:当用户按下回车键(表示输入完成),内核唤醒 hello 进程,将键盘缓冲区中的 ASCII 码读取到用户态 buf 缓冲区;
返回结果:getchar 函数从 buf 中取出第一个字符,作为返回值返回,完成字符读取。
8.5本章小结
本章阐述了 Linux IO 设备的文件化管理方法,梳理了 Unix IO 核心函数的功能,深入分析了 printf(输出)与 getchar(输入)的底层实现流程。IO 管理的核心是 “抽象与隔离”:通过文件描述符统一 IO 接口,通过驱动程序隔离硬件细节,通过缓冲区与中断机制提升 IO 效率。hello 程序的 IO 操作本质是用户态函数调用、内核态系统调用、硬件驱动执行的协同过程,体现了操作系统对 IO 设备的高效管控。
(第8章 1分)
结论
hello 程序全生命周期总结:
- 预处理阶段:展开头文件与宏定义,删除注释,生成纯净源码文件hello.i,为编译提供输入;
- 编译阶段:通过词法、语法、语义分析将 C 语言转换为汇编指令hello.s,完成高级语言到汇编的映射;
- 汇编阶段:将汇编指令翻译为机器码,生成可重定位目标文件hello.o,包含代码段、数据段与重定位信息;
- 链接阶段:解析外部符号引用,修正地址,合并段与库文件,生成可执行文件(hello),解决依赖关系;
- 进程创建与加载:Shell 通过 fork 创建子进程,execve 加载 hello 程序,内核构建虚拟地址空间与内存映射;
- 进程执行与调度:操作系统通过进程调度分配 CPU 时间片,实现虚拟地址到物理地址的转换,执行机器指令;
- IO 交互:通过 printf 调用 write 系统调用,将字符串输出到显示器;若包含 getchar 则等待键盘输入;
- 进程终止:程序执行完毕后,内核回收进程资源,返回 Shell,生命周期结束。
计算机系统设计与实现的感悟:
通过剖析 hello 程序的 “程序人生”,深刻体会到计算机系统 “分层抽象、协同工作” 的核心设计思想:
分层抽象降低复杂度:从高级语言(C)到汇编、机器码,从虚拟地址到物理地址,从 IO 函数到硬件驱动,每一层都通过抽象隐藏底层细节,让开发者与进程无需关注底层实现,仅需调用上层接口;
软硬件协同是核心:软件层面(编译器、链接器、内核)负责逻辑组织与资源管理,硬件层面(CPU、内存、IO 设备)负责指令执行与数据存储,二者通过指令集、地址总线、中断机制紧密协同,缺一不可;
基于本次研究,提出创新设计思路:
内存映射预加载:针对 hello 这类小型程序,可在 execve 阶段预加载所有段到物理内存,避免运行时缺页中断,提升启动速度;对于大型程序,则采用 “热点页优先加载” 策略,兼顾内存占用与启动效率。
(结论0分,缺失-1分)
附件
hello.c 原始源码文件
hello.i 预处理后文件,展开头文件、宏定义,删除注释
hello.s 编译后汇编文件,将 C 语言转换为 x86-64 汇编指令
hello.o 汇编后可重定位目标文件,包含机器指令与重定位信息
hello.out 最终可执行文件
hello 最终可执行文件
参考文献
为完成本次大作业你翻阅的书籍与网站等
[1] https://www.cnblogs.com/pianist/p/3315801.html.
[2] Randal E. Bryant, David R. O'Hallaron.深入理解计算机系统[M]. 北京:机械工业出版社.
(参考文献0分,缺失 -1分)
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2401_87726207/article/details/156565440



