计算机系统
目 录
2.2在Ubuntu下预处理的命令............................................................................. - 5 -
5.3 可执行目标文件hello的格式....................................................................... - 8 -
6.2 简述壳Shell-bash的作用与处理流程........................................................ - 10 -
6.3 Hello的fork进程创建过程........................................................................ - 10 -
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 -
8.2 简述Unix IO接口及其函数.......................................................................... - 13 -
第1章 概述
1.1 Hello简介
. P2P:From Program to Process
1.源代码编写
用户在编辑器(Editor)中编写 hello.c,形成源代码(Program)。
2.编译与链接
预处理(Preprocessing):处理宏和头文件。
编译(Compilation):将 C 代码翻译为汇编代码。
汇编(Assembly):将汇编代码转为目标文件(Object File)。
链接(Linking):将目标文件与库文件合并,生成可执行文件(Executable)。
加载与进程创建
3.用户在 Shell(如 Bash 或 CMD)中运行可执行文件。
操作系统(OS)通过 fork(或 CreateProcess)创建新进程(Process),通过 execve(或 LoadImage)加载程序到内存。
内存管理单元(MMU)负责虚拟地址(VA)到物理地址(PA)的映射,涉及 TLB、页表、Cache 等机制。
4.进程调度与执行
OS 为进程分配 CPU 时间片,进程在 CPU 上执行指令(取指、译码、执行)。
进程可进行 IO 操作(如打印、等待输入),OS 负责 IO 管理与信号处理。
5.进程终止与资源回收
程序运行结束,OS 负责回收资源,进程生命周期结束。
O2O:From Zero-0 to Zero-0
1.程序从无到有(Zero-0):从空白到源代码、可执行文件、进程的诞生。
2.程序归于无(Zero-0):进程结束,资源释放,系统恢复到初始状态,仿佛“从零到零”,但留下了学习和体验的痕迹。
1.2 环境与工具
列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
软件环境:我在windows(电脑为amd64位)下使用WSL,使用Ubuntu系统,用vscode软件连接进行调试。
1.3 中间结果
预处理产物,包含所有头文件展开和宏替换后的 C 源代码。用于调试预处理阶段。
hello.s
汇编产物,C 代码被编译为汇编语言。便于分析编译器生成的汇编指令。
hello.o
目标文件,汇编代码被汇编器转换为机器码,但尚未链接。用于后续链接生成可执行文件。
hello
可执行文件,链接器将目标文件与库文件链接后生成,可以直接运行。
a.out
如果编译时未指定 -o 选项,默认生成的可执行文件名。
1.4 本章小结
本章以 hello.c 程序为例,系统梳理了从源代码编写到进程终止的完整生命周期。通过编辑器编写源代码,利用 gcc 工具链完成预处理、编译、汇编和链接,最终生成可执行文件。用户在 shell 中运行程序,操作系统通过进程创建、内存管理和调度机制,实现了程序的加载与执行。整个过程中,hello.c 依次生成了 hello.i、hello.s、hello.o、hello(或 a.out)等中间产物,反映了 C 程序从文本到机器指令的转变。通过本章的实践与分析,不仅加深了对程序开发流程的理解,也体会到操作系统和计算机体系结构在程序运行背后的重要作用。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:
C 语言的预处理是编译过程的第一步,在正式编译源代码之前,由预处理器(如 gcc 的 cpp)对源代码进行扫描和处理。预处理器会根据以 # 开头的指令(如 #include、#define、#ifdef 等)对源文件进行文本替换、文件包含、条件编译等操作,生成一个“纯净”的中间文件,供后续编译器处理。
作用:
1.文件包含:通过 #include 指令将头文件内容插入到源文件中,方便代码复用和模块化。
2.宏定义:通过 #define 定义常量或宏,简化代码书写,提高可维护性。
3.条件编译:通过 #ifdef、#ifndef、#if 等指令,根据不同条件编译不同的代码,便于跨平台开发和调试。
4.去除注释:预处理阶段会移除所有注释,减少编译器负担。
2.2在Ubuntu下预处理的命令

图1:预处理命令程序运行
2.3 Hello的预处理结果解析
生成hello.i文件,具体如下。
- 头文件展开
预处理器会将 #include <stdio.h>、#include <unistd.h> 和 #include <stdlib.h> 所引用的头文件内容全部插入到 hello.i 文件中。这些头文件包含了大量的标准库函数声明和宏定义,使得源文件变得非常庞大。 - 宏替换
如果代码中有 #define 定义的宏,预处理时会将宏名替换为对应的内容。当前 hello.c 没有自定义宏,但如果有,预处理后会直接替换。 - 条件编译处理
如果代码中有 #ifdef、#ifndef、#if 等条件编译指令,预处理器会根据条件决定是否保留相关代码。当前 hello.c 没有条件编译部分,但标准头文件内部有大量条件编译,预处理后只保留满足条件的部分。 - 注释去除
所有的注释(如文件开头的说明)在预处理后都会被删除,生成的 hello.i 文件中不会再有注释内容。 - 最终生成的代码
预处理后的 hello.i 文件是一个纯 C 代码文件,包含了所有头文件内容、宏展开和条件编译处理后的代码,没有注释,供编译器进一步编译。
2.4 本章小结
本章主要介绍了C语言预处理的基本概念、作用及其在Ubuntu下的实际应用。通过对hello.c程序的预处理实验,我们了解到:
预处理是C语言编译的第一步,主要负责头文件展开、宏替换、条件编译和注释去除等工作,为后续编译阶段做好准备。
在Ubuntu下可用gcc -E命令查看预处理结果,有助于理解代码的实际编译内容和结构。
通过分析hello.c的预处理结果,可以直观看到标准库头文件的展开和注释的消失,进一步理解了预处理的作用和意义。
预处理不仅提升了代码的可维护性和可移植性,也为复杂项目的开发和调试提供了便利,是C语言开发中不可或缺的重要环节。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:
在 C 语言的编译流程中,"编译"通常指的是将经过预处理的源文件(如 .i 文件)转换为汇编语言程序(如 .s 文件)的过程。这个阶段由编译器(如 gcc 的 cc1)完成。编译器会对预处理后的 C 代码进行语法分析、语义分析、优化,并最终生成对应的汇编代码。
作用:
语法和语义检查:编译器会检查代码的语法是否正确,变量和函数的使用是否符合语言规范,提前发现错误。
代码优化:编译器会对代码进行一定的优化,提高生成程序的执行效率。
生成汇编代码:将高级的 C 语言代码翻译为底层的汇编语言代码,为后续的汇编和链接阶段做准备。
平台适应性:编译器根据目标平台的架构,生成对应平台的汇编代码,实现跨平台开发。
3.2 在Ubuntu下编译的命令

图2.编译程序运行图
3.3 Hello的编译结果解析
3.3.1 数据类型与变量
- 局部变量
int i;、int argc;、char *argv[];都是局部变量,分配在栈上。- 汇编如
subq $32, %rsp分配栈空间,-4(%rbp)存放 i,-20(%rbp)存放 argc,-32(%rbp)存放 argv。
- 常量
- 字符串常量如
.LC0、.LC1存放在.rodata只读数据段。
- 字符串常量如
3.3.2 赋值与初始化
- 赋初值
movl %edi, -20(%rbp):将主函数参数 argc 赋值到局部变量。movq %rsi, -32(%rbp):将 argv 赋值到局部变量。movl $0, -4(%rbp):i 初始化为 0。
- 自增
addl $1, -4(%rbp):i++。
3.3.3 类型转换
- atoi
call atoi@PLT:将字符串 argv[4] 转换为 int,属于隐式类型转换(char* → int)。
3.3.4 算术与关系操作
- 加法
addq $8, %rax、addq $16, %rax等:用于指针偏移,访问 argv[i]。
- 比较
cmpl $5, -20(%rbp):判断 argc 是否等于 5。cmpl $9, -4(%rbp):判断 i 是否小于等于 9。
- 条件跳转
je .L2:等于时跳转。jle .L4:小于等于时跳转。
3.3.5 数组/指针操作
- 数组下标与指针
argv[i]通过movq -32(%rbp), %rax取 argv 基址,addq $8, %rax等偏移访问 argv[1]、argv[2]、argv[3]、argv[4]。movq (%rax), %rdx等:取出 argv[i] 指向的字符串地址。
3.3.6 控制转移
- if/else
cmpl $5, -20(%rbp)+je .L2:判断参数个数,错误则输出提示并 exit。
- for 循环
movl $0, -4(%rbp)初始化,.L3:比较,jle .L4进入循环体,addl $1, -4(%rbp)自增,jmp .L3回到条件判断。
- 函数返回
movl $0, %eax:main 返回 0。leave、ret:函数返回。
3.3.7 函数操作
- 参数传递
- 按 x86-64 ABI,前几个参数用
%rdi,%rsi,%rdx,%rcx等寄存器传递。 - 如
movq %rax, %rdi,movq %rax, %rsi等。
- 按 x86-64 ABI,前几个参数用
- 函数调用
call puts@PLT、call printf@PLT、call atoi@PLT、call sleep@PLT、call getchar@PLT、call exit@PLT。
- 局部变量
- 通过栈偏移访问,如
-4(%rbp)、-20(%rbp)、-32(%rbp)。
- 通过栈偏移访问,如
- 函数返回值
movl $0, %eax:main 返回 0。
3.4 本章小结
本章介绍了 C 语言编译阶段(.i 到 .s)的基本概念、命令及实际编译结果的分析。通过对 hello.c 编译生成的 hello.s 汇编文件的解析,我们可以看到:
编译器将 C 语言中的变量、常量、数组、指针、控制结构(如 if、for)、函数调用等高级语法,逐步翻译为底层的汇编指令。
各类数据类型(如 int、char*)在汇编中通过寄存器和栈空间进行管理,赋值、加法、自增、比较等操作都对应具体的汇编指令。
控制流(如条件跳转、循环)通过比较和跳转指令实现,函数调用和参数传递遵循平台调用约定。
通过分析汇编代码,可以更好地理解 C 语言程序的底层实现机制,为后续的程序优化和调试打下基础。
(以下格式自行编排,编辑时删除)
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:
在 C 语言的程序构建流程中,汇编阶段指的是将编译器生成的汇编语言文件(.s 文件)转换为目标文件(.o 文件,即机器语言的二进制程序)的过程。这个过程由汇编器(如 GNU 的 as)完成。汇编器会把人类可读的汇编指令翻译成计算机能够直接执行的机器码,并生成包含符号信息的目标文件。
作用:
指令翻译:将汇编语言指令逐条转换为对应的机器指令,实现从文本到二进制的转变。
符号管理:为变量、函数等分配符号表,便于后续链接阶段查找和重定位。
生成目标文件:输出 .o 目标文件,为最终的可执行文件或库文件的链接做准备。
平台适配:根据不同的硬件架构,生成对应平台的机器码,保证程序能在目标平台上运行。
4.2 在Ubuntu下汇编的命令

图3.汇编程序展示
4.3 可重定位目标elf格式

图4.readelf程序运行与结果展示
分析:
1. ELF 文件头与基本结构
- 类型:
REL (Relocatable file),说明这是一个可重定位目标文件,不能直接运行,需要链接生成可执行文件。 - 架构:
x86-64,适用于 64 位 AMD/Intel 处理器。 - 节区数量:14 个,包含代码、数据、符号表、重定位信息等。
2. 主要节区(Section)说明
- .text:存放程序的机器指令(代码段)。
- .data:已初始化的全局/静态变量(本例为空)。
- .bss:未初始化的全局/静态变量(本例为空)。
- .rodata:只读数据,如字符串常量。
- .rela.text:
.text段的重定位信息,记录需要链接器处理的外部符号引用。 - .symtab:符号表,记录所有全局/外部符号(如函数名、变量名)。
- .strtab:字符串表,存放符号名等字符串。
- .shstrtab:节区名称表。
- .comment、
.note.GNU-stack、.note.gnu.property、.eh_frame等为调试、属性或异常处理相关节区。
3. 重定位项分析(重点)
3.1.rela.text 节(代码段重定位)

含义:这些条目表示 .text 段中有 8 处需要在链接时由链接器填充实际地址。
类型说明:
R_X86_64_PC32:相对地址重定位,通常用于访问只读数据(如字符串常量)。
R_X86_64_PLT32:用于外部函数调用(如 puts、exit、printf、atoi、sleep、getchar),链接时会填入对应函数的实际地址。
符号说明:
.rodata:访问只读数据(字符串常量)。
puts、exit、printf、atoi、sleep、getchar:外部函数,需链接 libc。
2. .rela.eh_frame 节(异常处理帧重定位)

用于异常处理和栈展开,通常由编译器自动生成。
4.4 Hello.o的结果解析

图5. Objdump程序运行以及结果
1. 机器语言与汇编语言的映射
每一条汇编指令(如 mov, call, jmp 等)都对应一组机器码(如 48 89 e5、e8 00 00 00 00)。
汇编语言是对机器码的助记符表达,更易于人类理解;机器码是 CPU 能直接执行的二进制指令。
2. 操作数与重定位
访问外部符号(如字符串常量、库函数)时,机器码中的地址部分用 0x0 占位,实际地址由重定位项(如 R_X86_64_PC32, R_X86_64_PLT32)在链接时填充。
例如:
lea 0x0(%rip),%rax(1c: R_X86_64_PC32 .rodata-0x4)对应访问只读数据段的字符串常量。
call 0x0(24: R_X86_64_PLT32 puts-0x4)对应调用外部函数 puts,链接时填入实际地址。
3. 分支与函数调用
分支跳转
汇编如 je 32 <main+0x32>,机器码为 74 19,19 是相对偏移。
函数调用
汇编如 call printf@PLT,机器码为 e8 00 00 00 00,重定位项指向 printf,链接时填入实际地址。
4.与hello.s的对照分析

图6.在hello.s中的汇编指令与hello.o的对照。
分析机器语言中的操作数与汇编不一致之处
汇编中直接写符号名(如 printf@PLT),机器码中用相对偏移或占位符,实际地址由重定位项和链接器决定。
分支和调用的目标地址在机器码中是相对偏移或 0,占位,需链接时修正。
4.5 本章小结
本章介绍了汇编阶段(.s 到 .o)的基本概念、命令及其在 Ubuntu 下的实际操作。通过对 hello.o 的 ELF 格式和重定位项的分析,以及对 objdump 反汇编结果与 hello.s 汇编代码的对照,我们深入理解了:
(1)汇编器将汇编语言文件翻译为机器语言的目标文件(.o),实现了从人类可读到机器可执行的关键一步。
(2)目标文件采用 ELF 格式,包含代码段、数据段、符号表、重定位信息等多个节区,为后续链接和平台适配做好准备。
(3)重定位项记录了所有需要在链接阶段由链接器处理的外部符号和数据引用,保证最终可执行文件能正确访问常量和调用库函数。
(4)汇编语言与机器语言高度对应,但涉及外部符号时,机器码中用重定位项占位,链接时再填充实际地址。
(5)通过 readelf、objdump 等工具,可以直观了解目标文件的结构和底层实现细节。
本章内容帮助我们建立了从汇编语言到机器语言的桥梁,理解了汇编器和目标文件在整个程序构建流程中的重要作用。(第4章1分)
第5章 链接
5.1 链接的概念与作用
链接是将一个或多个目标文件(如 hello.o)与系统标准库结合,生成最终可执行文件(如 hello)的过程。这个过程由链接器(如 GNU 的 ld)完成,是程序构建的最后一个重要环节。
作用:
1.符号解析与重定位
hello.o 中包含对外部函数(如 printf、sleep、exit、atoi、getchar)的引用,但这些函数的实际实现在标准库中。
链接器负责找到这些函数在标准库中的位置,并将 hello.o 中的重定位项(如前面看到的 R_X86_64_PLT32 项)替换为实际的函数地址。
2.合并和整合代码段与数据段
将 hello.o 的代码段(.text)、数据段(.data、.rodata)与系统库的相应段合并。
为各段分配适当的内存布局,确保程序运行时能正确访问代码和数据。
3.处理所有重定位项
解析 hello.o 中的重定位表(.rela.text),填充所有外部符号引用。
例如,将 call 0x0 指令(带有 printf 的重定位项)修改为指向实际 printf 函数的地址。
4.创建可执行文件头和程序入口
生成 ELF 可执行文件头,标明入口点和段布局。
添加必要的运行时初始化代码,确保程序能被操作系统加载和执行。
以 hello.c 为例:
hello.c 使用了多个标准库函数(printf、exit、sleep、atoi、getchar)。
编译后生成的 hello.o 包含对这些函数的调用,但它们的地址是通过重定位项标记的。
链接时,这些函数调用被重定向到 libc.so(C标准库)中的实际实现。
5.2 在Ubuntu下链接的命令

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

图8. Readelf执行情况以及各代码段位置
5.4 hello的虚拟地址空间

图9.使用gbd(info froc mappings命令)
对应关系分析:
代码段 (.text):
ELF 文件中地址:0x401080
虚拟内存映射:0x401000 - 0x402000 范围内
权限:r-x(只读可执行)
包含 main 函数的机器码
只读数据段 (.rodata):
ELF 文件中地址:0x402000
虚拟内存映射:0x402000 - 0x403000 范围内
权限:r--(只读)
包含字符串常量 "用法: Hello 学号 姓名 手机号 秒数!\n" 和 "Hello %s %s %s\n"
数据段 (.data) 和 BSS 段 (.bss):
ELF 文件中地址:0x404010 (.data),0x404020 (.bss)
虚拟内存映射:0x404000 - 0x405000 范围内
权限:rw-(读写)
包含全局变量和未初始化数据
动态链接信息 (.dynamic):
ELF 文件中地址:0x403e10
虚拟内存映射:0x403000 - 0x404000 范围内
包含动态链接时需要的信息
共享库映射:
虚拟内存中还映射了程序依赖的共享库,如 libc.so
这些不是 ELF 文件的一部分,而是运行时动态链接的
栈区域:
虚拟地址:0x7ffffffde000 - 0x7ffffffff000
不属于 ELF 文件,由操作系统在运行时分配
存储局部变量、函数参数、返回地址等
5.5 链接的重定位过程分析


图10 objdump -d -r hello的运行结果
- 首先是hello.o中的重定位项被解析。
重定位过程分析
地址空间分配
hello.o 中的地址都是相对于 0 的偏移
hello 中的地址是绝对地址,如 main 函数位于 0x4011a0
字符串常量重定位
hello.o 中:lea 0x0(%rip),%rax 和重定位项 R_X86_64_PC32 .rodata-0x4
hello 中:lea 0xe45(%rip),%rax 指向 0x402008,这是字符串常量在内存中的位置
函数调用重定位
hello.o 中:call 0x0 和重定位项如 R_X86_64_PLT32 printf-0x4
hello 中:函数调用变为 call 401060 <printf@plt> 等,指向 PLT(程序链接表)项
PLT 和 GOT 机制
链接器没有直接填入库函数的绝对地址,而是创建了 PLT 表项
PLT 表中的条目(如 <printf@plt>)会通过 GOT(全局偏移表)进行动态解析
这实现了动态链接,库函数的实际地址在运行时才解析
5. 重定位总结
链接过程中对 hello.o 的重定位包括以下主要步骤:
1.空间分配:为代码段、数据段等分配虚拟内存地址
2.符号解析:查找所有外部符号(如 printf)的定义位置
3.重定位项处理:
对于字符串常量,计算 RIP 相对寻址的偏移量
对于库函数调用,创建 PLT 入口并计算调用偏移
4.PLT/GOT 设置:
创建 PLT(过程链接表)条目指向动态链接器存根
设置 GOT(全局偏移表)用于运行时解析函数地址
5.6 hello的执行流程
1. 操作系统加载程序
2. 动态链接器初始化 (/lib64/ld-linux-x86-64.so.2)
3. _start (0x401000)
4. __libc_start_main
|-> __libc_csu_init (0x401150)
|-> _init
5. main (0x4010f8)
|-> printf@plt (0x4010a0)
|-> exit@plt (0x401090) [如果参数错误]
|-> printf@plt (0x4010a0) [循环内]
|-> atoi@plt (0x4010c0) [循环内]
|-> sleep@plt (0x4010d0) [循环内]
|-> getchar@plt (0x4010b0)
6. [main返回] -> __libc_start_main
7. __libc_csu_fini (0x401180)
|-> _fini
8. exit
9. _exit 系统调用
5.7 Hello的动态链接分析
1.分析PLT条目

图11.使用地址查看PLT条目
- PLT 入口 (0x4010a0):包含对外部函数的调用代码
- 跳转指令:jmp *0x2f5e(%rip),跳转到 GOT 表中存储的地址
- GOT 表项地址 (0x404008):保存 printf 函数的实际地址
- 查看初始GOT表项
(gdb) x/gx 0x404008
0x404008 <printf@got.plt>: 0x00000000004010aa
分析:初始时,GOT 表项指向 PLT 中的第三条指令 (0x4010aa),而不是 libc 中 printf 的实际地址。这是延迟绑定机制的体现。
- 动态链接过程分析
执行流程:
1)程序调用 0x4010a0 (printf@plt)
执行 endbr64 指令(防止 ROP 攻击的保护指令)
执行 jmp *0x2f5e(%rip),跳转到 GOT 表项指向的地址
初始时,这个地址指向 PLT 中的第三条指令
第三条指令是 nopw 0x0(%rax,%rax,1),这与经典的延迟绑定模式不同
接下来会执行未显示的代码,跳转到动态链接器进行符号解析
2)符号解析:
动态链接器找到 printf 在 libc 中的实际地址
更新 GOT 表项为这个实际地址
跳转到实际的 printf 函数执行
4.第一次调用printf的过程与动态链接后的状态

图12.第一次调用printf以及动态链接后的GOT表项
分析:现在GOT表项已更新为libc中printf的真实地址0x00007ffff7e05100
5.8 本章小结
本章深入分析了链接过程,即从目标文件(.o)到可执行文件(hello)的转换过程,以及程序的执行机制。通过实际操作和分析,我们了解了以下核心内容:
1. 链接的概念与作用
链接是将编译生成的目标文件与系统库连接起来,生成可执行文件的过程。它的主要任务是解析符号引用、合并代码段和数据段、处理重定位项,最终生成可由操作系统加载执行的可执行文件。
2. Ubuntu 下的链接命令
我们学习了在 Ubuntu 系统下使用 ld 命令进行链接的方法,掌握了各种链接选项的作用,如指定输出文件、链接动态库、指定动态链接器等。实际操作中,我们通过直接链接 hello.o 生成了 hello 可执行文件。
3. 可执行目标文件的 ELF 格式
通过 readelf 工具分析了 hello 的 ELF 格式,包括 ELF 头、程序头表和节区头表等。我们了解了各个段(如 .text、.data、.rodata、.bss 等)的地址、大小和属性,以及它们在可执行文件中的组织方式。
4. 虚拟地址空间
使用 GDB 分析了 hello 进程的虚拟地址空间布局,看到了代码段、数据段、堆、栈等在内存中的映射情况。这帮助我们理解了程序在内存中的实际组织形式与 ELF 文件的关系。
5. 重定位过程
通过对比 hello.o 和 hello 的反汇编结果,我们深入了解了链接器如何处理重定位项,特别是外部函数调用(如 printf、sleep、exit 等)和全局数据引用的重定位过程。
6. 程序执行流程
使用 GDB 追踪了 hello 从程序加载到终止的完整执行流程,包括从 _start 到 main 的调用链,以及程序终止时的清理过程。这帮助我们理解了 C 程序运行时环境的初始化和终止机制。
7. 动态链接分析
通过 GDB 详细分析了 hello 程序的动态链接机制,特别是 PLT(过程链接表)和 GOT(全局偏移表)的工作原理。我们观察到动态链接前后 GOT 表项的变化:从初始指向解析桩(0x401040)到更新为 libc 中函数的实际地址(0x7ffff7e05100)。这种延迟绑定机制提高了程序的启动速度和执行效率。
通过本章的学习,我们不仅掌握了链接的基本概念和操作方法,还深入理解了可执行文件的结构、内存映射、动态链接等高级主题。这些知识为我们理解现代操作系统中程序的加载和执行机制,以及进行系统级程序开发和调试提供了重要基础。(以下格式自行编排,编辑时删除)
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程的概念
进程是操作系统中一个正在执行的程序的实例,是系统资源分配和调度的基本单位。每个进程都有独立的地址空间、资源和状态信息。从系统角度看,进程是对正在运行程序的抽象,包括程序代码、数据、堆栈以及各种系统资源的集合。
具体到我们的 hello 程序,当执行 ./hello 2023113384 张璇 13559854354 3 命令时,操作系统会创建一个新的进程,加载 hello 程序的代码和数据,分配运行所需的内存空间,然后开始执行。
进程的作用
进程作为现代操作系统的核心概念,具有以下几个重要作用:
1.资源隔离与保护:
每个进程拥有独立的地址空间,相互隔离
防止一个进程错误影响其他进程或系统
例如,即使 hello 程序崩溃,也不会影响其他正在运行的程序
2.并发执行:
允许多个程序同时运行
提高系统资源利用率和响应速度
用户可以同时运行 hello 程序和其他程序,如注释中提到的 ps、jobs、pstree 等
3.资源分配与回收:
进程创建时分配必要的系统资源
进程结束时回收所有资源
hello 程序结束时,其占用的内存和其他资源会被操作系统回收
4.进程控制:
提供进程创建、终止、暂停等操作
支持信号机制进行进程间通信
如注释中提到的 Ctrl-Z(SIGTSTP 信号,暂停进程)和 Ctrl-C(SIGINT 信号,终止进程)
6.2 简述壳Shell-bash的作用与处理流程
Shell 的基本概念和作用
Shell(壳)是操作系统的用户界面,提供了用户与内核交互的接口。在 Linux 系统中,bash (Bourne Again Shell) 是最常用的 Shell 之一。Shell 的主要作用包括:
1)命令解释器:解释用户输入的命令并执行
2)程序启动器:创建新进程来运行用户请求的程序
3)脚本语言:提供编程语言功能,支持条件判断、循环、函数等
4)环境配置:管理环境变量,控制程序运行环境
5)作业控制:管理前台和后台进程
bash 的处理流程
当用户在终端中输入命令(如运行 hello 程序)时,bash 的处理流程如下:
1) 读取命令
bash 从标准输入(通常是终端)读取命令行:
2) 命令解析
bash 将命令行分解为多个部分:
程序名:./hello
参数列表:2023113384、张璇、13559854354、3
3) 变量展开和通配符处理
bash 处理命令中的任何变量(如 $PATH)和通配符(如 *.txt)。
4) 查找可执行文件
bash 确定命令的类型:
内建命令(如 cd、echo)
外部命令(如我们的 hello 程序)
对于 hello,bash 确认这是当前目录下的可执行文件。
5) 创建子进程
对于外部命令,bash 使用 fork() 系统调用创建一个子进程,这个子进程是 bash 进程的副本。
6) 执行程序
子进程使用 execve() 系统调用,将自身替换为指定的程序(hello):
加载 hello 程序到内存
初始化进程环境
传递命令行参数
现在,原来的子进程已变成 hello 进程,开始执行 hello 程序的代码。
7) 等待程序完成
bash 进程(父进程)使用 wait() 或 waitpid() 系统调用等待子进程完成。在这期间,bash 可能会被阻塞,除非命令在后台运行(使用 &)。
8) 处理作业控制命令
当 hello 程序运行时,用户可以使用特定的组合键进行作业控制:
Ctrl-Z:发送 SIGTSTP 信号,暂停程序
Ctrl-C:发送 SIGINT 信号,终止程序
这些信号由终端驱动程序发送给前台进程组的所有进程。
9) 提供作业控制命令
当 hello 程序被暂停后,bash 提供多个命令管理作业:
jobs:列出当前 shell 会话中的所有作业
fg:将后台作业带到前台继续执行
bg:在后台继续执行暂停的作业
例如,如果用户按下 Ctrl-Z 暂停 hello 程序,bash 会显示作业已停止的消息。用户可以使用 fg 命令恢复执行。
6.3 Hello的fork进程创建过程
hello 程序的创建过程分析
当用户在终端输入 ./hello 2023113384 张璇 13559854354 3 命令时,进程创建的完整流程如下:
1) bash 处理输入命令
bash 读取并解析用户输入的命令行
确认 hello 是一个可执行程序
准备创建新进程执行这个程序
2) bash 调用 fork() 创建子进程
这一步创建了一个 bash 的副本(子进程),两个进程从 fork() 返回后并发执行。
6.4 Hello的execve过程
当 bash 的子进程调用 execve("./hello", argv, envp) 时,系统会执行以下步骤:
1) 程序验证与权限检查
检查 hello 文件是否存在
验证执行权限(x 权限)
确认文件格式(ELF 可执行文件)
2) 释放原进程资源
释放当前进程的虚拟内存映射(代码段、数据段、堆等)
保留进程标识信息(PID、PPID 等)
保留已打开的文件描述符(除非设置了 FD_CLOEXEC 标志)
3) 加载新程序
解析 hello 程序的 ELF 头部
创建新的虚拟内存映射:
代码段(.text):包含 hello 程序的指令
数据段(.data):包含已初始化的全局变量
BSS 段(.bss):包含未初始化的全局变量
加载动态链接器(/lib64/ld-linux-x86-64.so.2)
建立程序栈和堆
4) 初始化程序执行环境
构建程序栈帧,添加:
命令行参数:argv[0]="./hello", argv[1]="2023113384", argv[2]="张璇", argv[3]="13559854354", argv[4]="3", argv[5]=NULL
环境变量:PATH、HOME、USER 等
设置辅助向量(auxv):包含操作系统信息、程序入口点等
5) 执行动态链接
动态链接器解析程序依赖的共享库(libc.so.6)
加载共享库到进程地址空间
解析符号引用(如 printf、sleep、atoi 等函数)
6) 启动程序执行
跳转到程序入口点(通常是 _start)
系统库代码完成底层初始化
最终调用 hello 程序的 main 函数,传递命令行参数
6.5 Hello的进程执行
1) 程序启动阶段
进程处于用户态执行初始化代码
检查参数数量,若不符合要求:
调用 printf() →触发 write() 系统调用→切换到核心态
内核执行写操作→返回用户态
调用 exit(1) →触发 exit_group() 系统调用→进程终止
2) 主循环执行阶段
每次循环执行:
printf 阶段:
用户态:格式化字符串
系统调用切换到核心态:write() 输出到终端
返回用户态
sleep 阶段:
用户态:调用 atoi() 转换参数为整数
系统调用切换到核心态:nanosleep()
进程状态变为 WAITING,进程上下文被保存
调度器选择其他进程执行
sleep 时间到期后,进程状态变为 READY
当被再次调度时,恢复进程上下文,返回用户态
3) 等待输入阶段
getchar 调用:
用户态调用 getchar()
系统调用切换到核心态:read()
进程状态变为 WAITING,等待输入
当用户按下键盘,触发键盘中断
内核处理中断,将数据传递给等待的进程
进程状态变为 READY,当被调度时恢复执行
返回用户态,getchar() 返回读取的字符
程序结束:
执行 return 0 相当于调用 exit(0)
系统调用切换到核心态:exit_group()
内核清理进程资源,通知父进程(bash)
进程终止
6.6 hello的异常与信号处理
1. Linux 信号基础
信号是发送给进程的异步通知,表示某个事件已经发生。在 hello 程序执行过程中,最常见的信号包括:
SIGINT (2): 中断信号,通常由 Ctrl-C 生成
SIGTSTP (20): 终端停止信号,通常由 Ctrl-Z 生成
SIGCONT (18): 继续执行信号,用于恢复被停止的进程
每个进程对信号都有默认的处理方式,也可以通过系统调用自定义处理函数。
2. hello 程序的默认信号处理
hello.c 代码中没有自定义信号处理函数,因此使用默认处理方式:
默认情况下:
Ctrl-C 会终止 hello 程序
Ctrl-Z 会暂停 hello 程序,将其置于后台3. 信号的产生与传递过程
当用户在 hello 程序运行时按下 Ctrl-C 或 Ctrl-Z,整个信号处理流程如下:
3.1 信号产生阶段
用户按下特殊键组合(如 Ctrl-C)
键盘设备驱动生成中断
内核处理中断,识别为特殊控制字符
终端驱动将控制字符转换为相应信号(Ctrl-C → SIGINT)
内核向前台进程组中的所有进程(包括 hello)发送该信号
3.2 信号传递阶段
内核在进程的 PCB (Process Control Block) 中标记信号待处理
当进程下次从内核态返回用户态前,内核检查是否有待处理信号
如果有待处理信号,内核调用相应的信号处理函数
3.3 信号处理阶段
对于 hello 程序的默认处理:SIGINT (Ctrl-C) 处理流程:
内核终止进程执行
清理进程资源
向父进程(bash)发送 SIGCHLD 信号
将进程退出状态设为 130(128 + 2)
SIGTSTP (Ctrl-Z) 处理流程:
内核暂停进程执行
保存进程当前上下文
将进程状态更改为 TASK_STOPPED
向父进程(bash)发送 SIGCHLD 信号
bash 显示作业控制信息 [1]+ Stopped ./hello ...
- 各命令运行结果截屏

图13.jobs运行结果

图14.fg运行结果

图15.ps运行结果

图16.pstree运行结果

图16.kill运行结果
通过对 hello 程序异常与信号处理的分析,我们可以得出以下结论:
信号是进程通信的基本机制:通过键盘快捷键和命令,可以向进程发送不同类型的信号,控制其行为
默认信号处理行为:没有自定义信号处理函数的 hello 程序使用操作系统提供的默认处理方式,如 SIGINT 终止进程,SIGTSTP 暂停进程
进程状态转换:信号能够导致进程在不同状态间转换,如运行、暂停、终止
作业控制:shell 提供的作业控制命令(jobs、fg、bg、kill 等)是通过信号机制实现对进程的管理
进程层次结构:通过 ps、pstree 等命令可以查看进程的层次结构和状态,帮助理解进程间的关系
hello 程序虽然没有显式的信号处理代码,但通过操作系统的默认信号处理机制,仍然能够响应各种异常情况和用户交互,展示了操作系统进程管理的基本原理和能力。
6.7本章小结
本章深入探讨了进程管理的核心概念,通过分析 hello 程序的执行过程来理解现代操作系统中进程的创建、执行、调度和信号处理机制。
1.进程的基本概念与作用
进程是操作系统中运行的程序实例,是系统资源分配和调度的基本单位。每个进程拥有独立的地址空间、系统资源和执行上下文。进程的关键作用包括资源隔离、实现并发执行、提供保护机制以及支持多任务操作系统。
2.Shell 的角色与处理流程
Shell(如 bash)作为用户与操作系统内核的接口,承担着命令解释器和作业控制的重要角色。当用户输入 ./hello 2023113384 张璇 13559854354 3 等命令时,bash 会解析命令、创建子进程、执行程序,并提供作业控制功能,让用户能够通过 jobs、fg 等命令与运行中的进程进行交互。
3.进程的创建与加载
hello 程序的创建涉及两个关键系统调用:fork() 和 execve()。fork() 创建一个新进程,该进程是当前进程的副本;而 execve() 则将新进程的内存内容替换为 hello 程序的代码和数据。这种"fork-exec"模型是 Unix/Linux 进程创建的标准方式,提供了灵活性和安全性。
4.进程的执行与调度
hello 进程的执行受到操作系统调度器的控制。进程在用户态和核心态之间切换,时间片用尽或系统调用时会发生上下文切换。特别是当 hello 程序调用 printf()、sleep() 和 getchar() 时,会从用户态切换到核心态,并可能触发进程调度,让其他进程有机会执行。
5.信号处理与异常
hello 程序展示了 Linux 信号机制的工作方式。用户通过 Ctrl-C、Ctrl-Z 等键盘组合键可以向进程发送 SIGINT、SIGTSTP 等信号,从而控制程序的执行。尽管 hello.c 没有包含自定义的信号处理函数,但操作系统提供的默认处理机制使程序能够适当响应这些信号。
6.实际应用与观察
通过使用 ps、jobs、pstree、fg 等命令,我们可以观察和管理 hello 进程的状态。这些工具展示了进程层次结构、父子关系、执行状态以及资源使用情况,帮助我们理解进程在系统中的行为。
7.综合理解
hello 程序虽然简单,但通过它我们能够全面理解 Linux 系统中进程的生命周期:从创建、加载、执行、调度、信号处理到最终终止的整个过程。这种理解对于系统编程、应用开发和系统管理都具有重要意义。进程管理是操作系统的核心功能之一,它使多任务并发执行成为可能,提高了系统资源利用率,也为用户提供了灵活控制程序执行的能力。hello 程序作为一个简单但完整的示例,完美地展示了这些概念和机制的实际应用。
(第6章2分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
1.地址空间的基本概念
在现代计算机系统中,程序和操作系统使用不同层次的地址来访问内存。对于 hello 程序,从源代码到实际执行,涉及到四种关键的地址概念:逻辑地址、线性地址、虚拟地址和物理地址。这些地址概念构成了现代计算机系统中内存管理的层次结构。
2.逻辑地址
逻辑地址是程序代码中使用的地址,也称为相对地址,它与实际的物理内存位置无关。
在 hello 程序中的体现:
当我们在 hello.c 中引用变量 i 或参数 argv 时,编译器生成的机器代码使用相对于某个基址(如栈指针或帧指针)的偏移量来访问这些变量。这些偏移量就是逻辑地址。例如,局部变量 i 可能是相对于栈帧基址的偏移量 -4 或 -8。
逻辑地址的特点:
独立于物理内存布局
程序编译时确定
通常表示为段内偏移量
3.线性地址
线性地址是逻辑地址经过段式地址转换后得到的地址,也称为虚拟地址。它是一个连续的地址空间。
在 x86-64 架构下,hello 程序运行时:逻辑地址 → 段基址 + 偏移量 = 线性地址例如,如果 i 变量的逻辑地址是相对于栈帧的偏移量 -8,而当前栈帧的基址是 0x7fffffffe000,那么 i 的线性地址就是 0x7fffffffdff8。
线性地址的特点:
形成一个统一的地址空间
在 x86-64 的长模式下,逻辑地址到线性地址的转换通常是简单的加法操作
在现代操作系统中,线性地址通常与虚拟地址相同
4.虚拟地址
虚拟地址是操作系统提供给进程的独立地址空间,每个进程都拥有自己的完整虚拟地址空间,与其他进程隔离。在 x86-64 系统中,线性地址通常就是虚拟地址。
hello 程序的虚拟地址空间布局:
高地址 +------------------+
| 内核空间 | 通常在 0xffff800000000000 以上
+------------------+
| 栈 | 包含 main 函数的参数、局部变量 i
| ↓ |
| | 未使用的虚拟地址空间
| ↑ |
| 堆 | 如果程序使用 malloc,会在这里分配
+------------------+
| BSS 段 | 未初始化的全局变量
+------------------+
| 数据段 | 初始化的全局变量
+------------------+
| 代码段 | hello 程序的机器代码,如 main 函数
低地址 +------------------+
5.物理地址
物理地址是内存芯片中实际的硬件地址,虚拟地址通过页表映射到物理地址。
当 hello 程序访问变量 i 时,处理器会执行以下转换:
虚拟地址 → MMU页表转换 → 物理地址
物理地址的分配由操作系统内核管理,对用户程序通常是不可见的。即使是相同的虚拟地址,在不同时刻可能会映射到不同的物理地址。
物理地址的特点:
对应实际的硬件内存位置
由内存管理单元 (MMU) 和页表管理
用户程序通常无法直接访问或查看物理地址
7.2 Intel逻辑地址到线性地址的变换-段式管理
段式管理的基本概念
段式内存管理是 Intel 处理器体系结构中的基础内存管理机制,它将程序的逻辑地址空间划分为多个命名的段(segment),如代码段、数据段、栈段等。每个段都有其基址、长度和访问权限,这些信息存储在段描述符中。
在段式管理中,逻辑地址由两部分组成:段选择子(Segment Selector)和段内偏移(Offset)。处理器通过段选择子找到对应的段描述符,然后将段基址与偏移相加,得到线性地址。
Intel x86-64 的段式管理特点
虽然 x86-64 架构(我们的 hello 程序编译时使用了 -m64 选项)主要依赖页式管理,但段式管理仍然存在,只是在长模式(64 位模式)下其作用有所简化:
扁平内存模型:在 64 位模式下,所有段的基址通常设置为 0,段限长通常会被忽略
主要用于权限控制:段描述符主要用于区分代码和数据,以及用户态和内核态
固定的段寄存器使用:CS(代码段)、DS(数据段)、SS(栈段)等
7.3 Hello的线性地址到物理地址的变换-页式管理
hello 程序的地址转换过程
以 hello 程序中的变量访问为例,分析虚拟地址到物理地址的转换过程:
示例:访问栈变量 i
假设变量 i 的虚拟地址是 0x7fffffffdff8,我们来分析其转换为物理地址的过程:
拆分虚拟地址:
页偏移:0xFF8 (最低 12 位)
PT 索引:0x3DF (接下来的 9 位)
PMD 索引:0x1FF (接下来的 9 位)
PUD 索引:0x1FF (接下来的 9 位)
PGD 索引:0x1FF (最高使用的 9 位)
使用 CR3 寄存器找到 PGD:
CR3 寄存器指向当前进程的页全局目录指针表的物理地址
例如 CR3 = 0x10A000
多级页表查询:
PGD[0x1FF] → 找到 PUD 的物理地址,例如 0x20B000
PUD[0x1FF] → 找到 PMD 的物理地址,例如 0x30C000
PMD[0x1FF] → 找到 PT 的物理地址,例如 0x40D000
PT[0x3DF] → 找到物理页帧号,例如 0x50E000
计算最终物理地址:
物理地址 = 物理页帧号 + 页偏移
= 0x50E000 + 0xFF8
= 0x50EFF8
这个过程由 CPU 的内存管理单元 (MMU) 自动完成。如果页表中某一级查询失败(页面不在物理内存中),会触发缺页异常(Page Fault),由操作系统处理。
页表项的结构
7.4 TLB与四级页表支持下的VA到PA的变换
VA 到 PA 转换的整体流程
在现代 x86-64 架构下,虚拟地址(VA)到物理地址(PA)的转换是通过硬件内存管理单元(MMU)和操作系统共同完成的。这个过程利用了 TLB (Translation Lookaside Buffer) 和四级页表结构,整体流程如下:
1.查询 TLB 缓存
2.如果 TLB 未命中,执行四级页表遍历
3.更新 TLB 缓存
4.返回物理地址
7.5 三级Cache支持下的物理内存访问
L1 Cache查询
CPU首先访问最快的L1 Cache,若命中(数据存在)则直接返回,延迟最低(约1-4周期)。
L2 Cache查询
若L1未命中,访问L2 Cache。命中后数据返回并填充L1,延迟较高(约10-20周期)。
L3 Cache查询
若L2未命中,访问共享的L3 Cache。命中后数据逐级填充L2和L1,延迟进一步增加(约30-50周期)。
访问主存
若L3仍未命中,CPU通过内存控制器访问物理内存(DRAM),获取数据后依次填充L3、L2、L1,延迟显著(约100-300周期)。
7.6 hello进程fork时的内存映射
运行 ./hello ... 时,shell 先 fork 出一个子进程。
这个子进程的内存映射最初和 shell 完全一样。
7.7 hello进程execve时的内存映射
子进程调用 execve("./hello", argv, envp),用 hello 程序替换自己原有的进程映像。
原 shell 子进程的内存映射全部被清空。
加载 hello 程序的代码段、数据段、堆、栈、共享库等,形成新的内存映射。
进程号(PID)不变,但内容已完全变成 hello 程序。
7.8 缺页故障与缺页中断处理
1. 概念说明
缺页故障(Page Fault):当进程访问的虚拟地址在页表中没有有效的物理页映射时,CPU 会触发缺页异常(page fault)。
缺页中断处理:操作系统内核捕获到缺页异常后,负责查找或分配物理页,并建立虚拟页到物理页的映射关系。
2. 发生时机
以 hello.c 程序为例,进程刚被 execve 加载时,只有部分代码和数据页被实际加载到内存。
当程序运行过程中访问尚未加载的代码、数据、栈或共享库页时,就会发生缺页故障。
3. 处理流程
CPU 检测到缺页
进程访问了尚未映射的虚拟地址,CPU 产生缺页异常,切换到内核态。
操作系统内核处理
检查访问是否合法(如是否越界、权限是否允许)。
如果合法,分配物理页,将所需内容(如代码、数据、栈、共享库等)从磁盘加载到物理内存。
更新进程的页表,建立虚拟地址到物理地址的映射。
恢复进程执行
缺页处理完成后,进程返回用户态,继续执行刚才的指令。
4. 结合 hello.c 的实际情况
当你第一次运行 ./hello ... 时,hello 程序的代码段、数据段、栈、共享库等并不会一次性全部加载到内存。
只有在实际访问到某个虚拟页时,才会触发缺页故障,由操作系统按需加载。
比如:
第一次执行 printf 时,相关代码页和数据页可能会发生缺页故障。
第一次访问 argv、i 等变量时,栈页可能会发生缺页故障。
第一次调用 sleep、getchar 时,相关的共享库页也可能会缺页。
7.9本章小结
1.多级地址转换
hello 程序访问变量(如 i、argv)时,编译器生成的机器码使用逻辑地址(相对偏移),CPU 通过段式管理将逻辑地址转换为线性地址(虚拟地址),再通过多级页表和 TLB 转换为物理地址,实现虚拟内存到物理内存的映射。
2.虚拟地址空间布局
hello 进程拥有独立的虚拟地址空间,包括代码段、数据段、BSS 段、堆、栈和共享库映射区,各区域相互隔离,保证进程安全。
3.按需分配与缺页中断
程序运行时,只有实际访问的虚拟页才会分配物理内存。首次访问未分配的页会触发缺页中断,由操作系统分配物理页并建立映射,提高了内存利用率。
4.进程创建与内存映射变化
shell 运行 hello 时,先 fork 创建子进程,子进程初始内存映射与 shell 相同(写时复制),随后 execve 加载 hello,内存映射变为 hello 程序自身的结构。
5.高效访问机制
CPU 通过 TLB 和多级 Cache(L1/L2/L3)加速虚拟地址到物理地址的访问,减少主存访问延迟,提升程序运行效率。
6.实验意义
hello 程序展示了现代操作系统的虚拟内存、地址转换、按需分配、进程隔离等核心机制,有助于理解操作系统的存储管理原理。(第7章 2分)
第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章 选做 0分)
结论
1.hello 程序在计算机系统中的全过程总结
命令解析与进程创建
用户在 shell 输入 ./hello ...,shell 解析命令后通过 fork() 创建子进程,子进程通过 execve() 加载 hello 程序,原有进程空间被 hello 替换。
程序加载与虚拟内存分配
操作系统为 hello 分配独立的虚拟地址空间,加载代码段、数据段、BSS 段、堆、栈和共享库映射区,形成进程隔离的基础。
参数传递与栈初始化
main 函数的参数(argc, argv)和局部变量(如 i)被压入栈区,编译器生成的机器码通过栈帧基址和偏移量(逻辑地址)访问这些变量。
地址转换与访问优化
CPU 通过段式和页式管理,将逻辑地址转换为线性地址(虚拟地址),再通过多级页表和 TLB 转换为物理地址。多级 Cache(L1/L2/L3)加速数据访问。
按需调页与缺页中断
hello 运行时,只有实际访问的虚拟页才分配物理内存。首次访问未分配页时,触发缺页中断,由操作系统分配物理页并建立映射。
系统调用与进程控制
程序运行过程中多次调用系统调用(如 printf、sleep、getchar),用户可通过 Ctrl-C、Ctrl-Z 等信号控制进程,也可用 ps、jobs、pstree 等命令观察进程状态。
进程终止与资源回收
hello 程序执行完毕或被信号终止后,操作系统回收其所有资源,包括虚拟内存、文件描述符等。
2.对计算机系统设计与实现的感悟
分层与抽象:从用户命令到硬件执行,系统通过进程、虚拟内存、系统调用等多层抽象,极大提升了安全性、可扩展性与开发效率。
虚拟化与隔离:虚拟内存和进程隔离机制让每个程序都像独占计算机一样运行,提升了系统的稳定性和安全性。
懒加载与高效资源利用:按需调页、写时复制等机制让系统资源利用率最大化,减少了不必要的内存和 I/O 开销。
硬件与软件协同:TLB、Cache、MMU 等硬件机制与操作系统软件协同优化,显著提升了程序运行效率。
3.创新理念与展望
智能资源调度:结合 AI/ML 技术,动态预测进程行为,实现更智能的内存、CPU、I/O 调度,提高系统整体性能。
细粒度安全隔离:探索基于硬件的微隔离(如内存标签、能力硬件)和软件沙箱,提升系统安全性。(结论0分,缺失-1分)
附件
hello.i
预处理产物,包含所有头文件展开和宏替换后的 C 源代码。用于调试预处理阶段。
hello.s
汇编产物,C 代码被编译为汇编语言。便于分析编译器生成的汇编指令。
hello.o
目标文件,汇编代码被汇编器转换为机器码,但尚未链接。用于后续链接生成可执行文件。
hello
可执行文件,链接器将目标文件与库文件链接后生成,可以直接运行。
a.out
如果编译时未指定 -o 选项,默认生成的可执行文件名。


2172

被折叠的 条评论
为什么被折叠?



