本文围绕 “程序人生 - Hello’s P2P” 主题,系统剖析了 hello.c 程序从静态代码到运行进程的完整生命周期,以及从无到有再回归无的资源流转过程。研究过程中,依次探究了预处理、编译、汇编、链接四大编译阶段的原理、操作命令及输出结果解析,明确了各阶段文件格式转换逻辑与核心作用。进而深入分析了 hello 程序运行时的进程管理、存储管理与 IO 管理机制,包括进程创建与调度、地址空间转换、内存映射、中断处理及设备 IO 交互等关键环节。通过理论推导与实操验证相结合的方式,完整呈现了程序从文本代码到可执行文件、再到进程运行并释放资源的全流程,揭示了计算机系统中硬件、操作系统与应用程序的协同工作原理,为深入理解计算机系统的底层设计与实现提供了实践参考。
关键词:编译链接;进程管理;存储管理;IO 交互;计算机系统底层原理
目 录
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简介
1.1.1 P2P(Program → Process)过程
Program(程序)形态:初始以hello.c文本文件存在,包含用户编写的逻辑指令,是无执行能力的静态代码集合,依赖编译工具链与操作系统激活。
预处理→编译→汇编→链接:通过编译工具链完成四层转化,实现从文本到可执行二进制文件的蜕变:
预处理:处理#include头文件包含、删除注释,生成纯指令文本.i文件;
编译:将预处理后的代码翻译为汇编语言指令,生成.s汇编文件;
汇编:将汇编指令转换为机器可识别的二进制指令,生成.o可重定位目标文件;
链接:将目标文件与系统库(如printf、sleep依赖的标准库)结合,解决符号引用与地址重定位,生成独立可执行文件,完成 “可运行形态” 构建。
Process(进程)激活与运行:在 BashShell 环境中,操作系统通过fork系统调用创建新进程(分配进程控制块 PCB,记录进程状态、上下文等信息),再通过execve系统调用将可执行文件加载到进程地址空间,通过mmap完成内存映射。操作系统为进程分配时间片,进程在 CPU 上按流水线机制执行取指、译码、执行操作,依托 RAM 存储数据与指令,通过 I/O 设备完成交互,实现循环打印与键盘输入响应。
1.1.2 O2O(Zero→Zero)过程
从 “Zero” 到 “有”:初始状态为无实体的代码文本(Zero),经编译工具链转化与操作系统激活,成为占用 CPU、内存、I/O 资源的运行进程(有实体、有执行能力),实现从 “无” 到 “有” 的突破。
从 “有” 到 “Zero”:当程序完成 10 次循环打印并接收键盘输入后,正常退出;或通过信号触发异常终止时,操作系统回收进程占用的 CPU、内存、文件描述符等所有资源,进程从系统中消亡,回归 “无” 的初始状态。
1.2 环境与工具
软件:Windows 11 家庭中文版,VMware Workstation pro-17.6.4,ubuntu-24.04.3
开发工具:Visual Stdio 2022; ClodeBlocks; gedit+gcc;gdb
1.3 中间结果
hello.i: 预处理后的文件,完成头文件包含、注释删除、宏替换等文本处理,是编译阶段的输入
hello.s:汇编生成的可重定位目标文件,包含机器语言指令与未解析的符号引用,是链接阶段的输入
hello.o: 链接生成的可执行文件,整合目标文件与系统库,解决符号重定位,可被操作系统加载为进程运行。
1.4 本章小结
本章介绍了hello文件的P2P过程和020过程,介绍了作者使用的环境和工具,列出了中间结果的文件
第2章 预处理
2.1 预处理的概念与作用
预处理是编译工具链的首个阶段,预处理器以源文件(如.c)为输入,按照代码中的预处理指令和语法规则,对源代码进行文本替换、条件筛选、文件包含等操作,最终生成预处理文件(.i)。
1.头文件包含(#include)
预处理器会将#include <xxx.h>或#include "xxx.h"指令替换为对应头文件的全部内容,实现代码的模块化复用。
2.宏定义与替换(#define)
对#define定义的宏常量(如#define MAX 100)或宏函数(如#define ADD(a,b) (a+b)),预处理器会进行文本替换,将代码中所有宏名替换为定义的内容。
3.注释删除
预处理器会删除源代码中所有的注释,删除后可减少后续处理的冗余数据。
4.条件编译
预处理器根据条件指令的判断结果,保留或删除指定代码块,实现代码的条件化编译。
5.消除冗余指令,标准化输入
预处理会处理#undef(取消宏定义)、#pragma(编译器指令,如设置对齐方式)等指令,最终生成无预处理指令、注释,且内容完整的.i文件。
2.2在Ubuntu下预处理的命令

图2.2.1
2.3 Hello的预处理结果解析

图2.3.1
hello.i是纯文本文件,保留 C 语言语法结构,但无预处理指令(#开头)、无注释,且包含了头文件的完整内容,文件体积远大于hello.c(因插入了头文件的声明)。
2.4 本章小结
本章介绍了预处理的概念和作用,并且在Ubuntu上对hello.c文件预处理,分析了hello.i
第3章 编译
3.1 编译的概念与作用
指编译器将预处理后的.i文件转换为与目标 CPU 架构对应的汇编语言程序(.s文件)的过程。该阶段会对 C 代码进行语法分析、语义分析、优化,最终生成符合 CPU 指令集规范的汇编指令。
将高级 C 语言代码翻译为与 CPU 架构匹配的汇编语言指令,搭建高级逻辑与硬件指令的桥梁。
3.2 在Ubuntu下编译的命令

图3.2.1
3.3 Hello的编译结果解析
3.3.1 常量
字符串
C 中的字符串常量被编译器存入只读数据段(.rodata),生成标签(.LC1)

图3.3.1
整数
整数常量在编译阶段被直接处理为立即数,无需额外存储到数据段,直接参与指令运算。

图3.3.2
变量
C 中的局部变量(如main中的argc、循环变量i),被编译器分配到栈帧中(通过rbp寄存器的偏移地址访问)

图3.3.3 参数argc
3.3.2 赋值
赋值(如i=0)对应汇编movl $0, -4(%rbp),将i存入了-4(%rbp)中,并赋值为0

图3.3.4
3.3.3 类型转换
C 中的隐式类型转换(如atoi(argv[4])将字符串转为int),通过调用atoi@PLT函数实现,结果存入eax寄存器(movl %eax, %edi)。

图3.3.5
3.3.4算数操作
在汇编语言中加法用add实现,减法用sub实现。i++对应addl $1, -4(%rbp)

图3.3.6
3.3.5关系操作
关系比较(如argc != 5)对应汇编cmpl $5, -20(%rbp)(比较argc与 5),配合条件跳转指令je .L2(相等则跳转到.L2);
循环条件(i <= 9)对应cmpl $9, -4(%rbp),配合jle .L4(小于等于则跳转到循环体.L4)。
![]()
图3.3.7

图3.3.8
3.3.6控制转移
if/else(参数校验)对应cmpl+je的条件跳转
for循环对应 “初始化(movl $0, -4(%rbp))→ 条件判断(cmpl $9, -4(%rbp))→ 循环体(.L4)→ 自增(addl $1, -4(%rbp))” 的汇编指令序列

图3.3.9
3.3.7函数操作
函数调用(如printf)通过call printf@PLT实现,参数通过寄存器(rdi存格式字符串地址,rsi/rdx/rcx存参数)传递
函数参数(argc/argv)通过edi/rsi寄存器传递,存入栈帧(movl %edi, -20(%rbp)、movq %rsi, -32(%rbp))

图3.3.10
3.4 本章小结
本章介绍了编译的概念与作用以及编译代码,在编译这一步对不同数据和操作进行了解读
第4章 汇编
4.1 汇编的概念与作用
指汇编器将汇编语言文件(.s)转换为机器语言格式的可重定位目标文件(.o)的过程。汇编指令与机器指令一一对应,此阶段会将人类可读的汇编代码翻译为 CPU 可直接执行的二进制指令
4.2 在Ubuntu下汇编的命令

图4.2.1
4.3 可重定位目标elf格式
ELF头标识文件格式、告知系统文件类型 / 架构 / 位数,指引工具解析文件内容

图4.3.1 ELF头
Section头

图4.3.2 Section头
重定位节
重定位节是 ELF 格式可重定位目标文件(如.o文件)中的一类特殊节,核心作用是记录代码 / 数据段中需要在链接阶段修正的地址信息,是链接器将多个目标文件合并为可执行文件的关键依据。

图4.3.3 重定位节
4.4 Hello.o的结果解析


图4.3.4 反汇编
机器语言的构成:
每条机器语言由操作码(表示指令类型,如0x48 0x89对应movq)和操作数(表示操作对象,如0x75 0xe0对应-32(%rbp))组成。
映射规则:
汇编指令的 “助记符 + 操作数” 会被汇编器翻译为固定的机器码
操作数的差异:
汇编语言中操作数是 “符号化表示”(如-32(%rbp)、%rsi),而机器语言中是 “二进制编码”(如0x75 0xe0),二者本质是同一地址的不同表示形式。
分支转移和函数调用:
汇编语言中分支转移用标签(如je .L2),函数调用用符号(如call printf),机器语言中分支转移用段内偏移地址(如je 28 <main+0x1c>),函数调用用重定位标记。
4.5 本章小结
本章介绍了汇编的内容,重点列出了可重定位目标elf格式和反汇编解释hello.o的结果
第5章 链接
5.1 链接的概念与作用
指链接器将一个或多个可重定位目标文件(hello.o)与系统库合并,解决符号引用与地址重定位,最终生成可独立执行的 ELF 文件的过程。
符号解析:识别并关联目标文件中的符号(如hello.o中的printf)与系统库 / 其他目标文件中的定义(如libc中printf的实现),解决 “符号未定义” 的问题。
地址重定位:根据目标文件的重定位节(如.rela.text),将代码中 “占位的符号地址” 替换为实际的内存地址(如将hello.o中call printf的占位地址替换为libc中printf的真实地址)。
合并目标文件:将多个目标文件的代码段、数据段等合并为可执行文件的统一段结构,同时去除冗余信息,生成符合操作系统加载规范的 ELF 文件。
5.2 在Ubuntu下链接的命令

图5.2.1
5.3 可执行目标文件hello的格式
ELF头:

图5.3.1
程序头表:

图5.3.2
Section表:


图5.3.3
符号表:

图5.3.4
5.4 hello的虚拟地址空间

图5.4.1
分析发现代码段起始地址0x401000与图5.3.3中init的地址相同,体现了地址一致性。
5.5 链接的重定位过程分析

图5.5.1
Hello可执行文件:段使用固定虚拟地址,没有重定位项,地址已最终确定,有程序头表,供操作系统加载时映射内存
Hello.o:节使用相对偏移,没有固定的虚拟地址,重定位项记录需要修正的地址位置
链接过程就是把分散的目标文件 + 库文件整合成一个可执行文件的过程,核心是符号解析 + 地址重定位 + 段合并。
重定位:
符号解析:将hello.o中未定义的符号(如printf)与libc.so中的符号定义关联;
地址计算:根据重定位类型(如R_X86_64_PLT32),计算符号在可执行文件虚拟地址空间中的实际地址;
地址替换:将hello.o中占位的地址(00 00 00 00)替换为计算后的实际地址,完成重定位。
5.6 hello的执行流程
执行hello,首先程序地址会在0x7f4a:70de91c3处,这是hello使用动态链接库的入口点
然后,程序跳转到_dl_init,初始化后再跳到_start
程序通过call指令跳转到_libc_start_main处,该函数负责调用main函数
程序调用_cxa_atexit函数,设置在程序结束时需要调用的函数表
返回到_libc_start_main继续,调用hello可执行文件中的__libc_csu_init函数
程序返回到__libc_start_main,程序调用动态链接库里的_setjmp函数,设置一些非本地跳转;
返回到__libc_start_main继续,正式开始调用main函数
在进行了若干操作后,程序退出
5.7 Hello的动态链接分析
动态链接前:
plt表:

图5.7.1
Got表:

图5.7.2
GOT 表:内容更新为printf的实际地址,PLT 表内容无变化
5.8 本章小结
本章介绍了链接的过程,以及程序是如何进行重定位操作的
第6章 hello进程管理
6.1 进程的概念与作用
概念:进程是操作系统中正在运行的程序实例,包含程序代码、数据、进程控制块(PCB)等资源。
作用:实现程序的并发执行,提高 CPU 利用率;
隔离不同程序的资源,保证系统稳定性;
提供进程间通信、同步等机制,支持多任务协作。
6.2 简述壳Shell-bash的作用与处理流程
作用:
作为用户与操作系统内核的交互接口,接收用户输入的命令
解析命令、调用对应的程序 / 工具执行
管理命令执行的环境(如环境变量、工作目录)。
处理流程:
等待用户输入命令
解析命令(拆分参数、识别内置命令 / 外部程序)
若为外部程序,通过fork+exec创建进程执行
等待程序执行完成,返回结果并继续等待下一条命令。
6.3 Hello的fork进程创建过程
- 父进程调用fork,内核复制父进程的 PCB、内存空间等资源
- 内核为子进程分配新的 PID(进程 ID)
- fork在父进程中返回子进程的 PID,在子进程中返回 0
- 父、子进程各自继续执行后续代码(此时子进程与父进程共享代码段,数据段独立)。
6.4 Hello的execve过程
- 在fork创建的子进程中调用execve
- 内核加载 “hello” 程序的可执行文件
- 替换当前进程的代码段、数据段、堆栈等,更新 PCB 中的程序信息
- 从 “hello” 程序的入口点(main函数)开始执行,原进程的代码不再运行。
6.5 Hello的进程执行
时间片管理:
操作系统为每个进程分配固定时长的时间片(如 10ms),作为其单次占用 CPU 的最长时间;
时间片由内核的定时器维护,当 “hello” 进程开始运行时,定时器启动计时。
进程调度过程:
操作系统维护进程就绪队列,基于时间片轮转算法选择下一个运行的进程;
“hello” 进程被调度后,获取 CPU 并开始执行,同时消耗时间片;
若 “hello” 进程在时间片耗尽前完成执行,则主动释放 CPU;若时间片耗尽,内核保存其上下文(寄存器、程序计数器等),将其放回就绪队列末尾;
调度器选择下一个就绪进程运行,待 “hello” 进程再次被分配时间片时,恢复上下文继续执行。
用户态与核心态转换:
“hello” 进程默认在用户态执行,此时无法直接访问内核资源;
当调用fork/execve等系统调用,或时间片耗尽触发定时器中断时,触发软 / 硬中断,切换到核心态;
内核完成系统调用处理(或时间片调度)后,再切换回用户态,继续执行 “hello” 程序。
6.6 hello的异常与信号处理
中断异常,产生SIGINT信号,通常终止进程来处理
暂停异常,产生SIGTSTP信号,通常暂停进程来处理
段错误异常,产生SIGSEGV信号,通常终止进程并生成核心转储来处理
浮点错误异常,产生SIGFPE信号,通常终止进程来处理
Ctrl+Z:
查看进程状态:

图6.6.1 ps
查看后台任务:

图6.6.2 jobs
查看进程树:

图6.6.3 pstree
将后台进程调回前台:

图6.6.4 fg
终止进程:

图6.6.5 kill
回车:

图6.6.6
Ctrl+C:

6.7本章小结
本章简述了壳Shell-bash的作用和处理流程,介绍了Hello的fork进程创建过程;介绍了Hello的execve过程,简述了Hello的进程执行过程,并分析了在程序运行过程中键入不同的按键会产生的结果。
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:hello 代码中main函数、printf函数的地址,是程序编译 / 汇编时生成的「段:偏移」形式,仅在程序内部可见。
线性地址:逻辑地址经过段式管理转换后得到的连续地址,是虚拟地址空间的 “中间层”。
虚拟地址(VA):hello 进程运行时看到的地址(与线性地址在现代系统中通常等价),是进程专属的 “私有地址空间”(比如hello的代码段虚拟地址从0x400000开始)。
物理地址(PA):实际硬件内存的地址,hello 的虚拟地址会被页式管理映射到物理地址,物理地址由操作系统和硬件共同管理,进程无法直接操作。
7.2 Intel逻辑地址到线性地址的变换-段式管理
hello 编译后,代码段、数据段等会被标记为「段选择子」(存放在段寄存器中,如CS寄存器)。
CPU 通过段选择子从 GDT 中取出段描述符。
段描述符是描述段属性的一种数据结构,存放在 GDT 或 LDT 中。每个段描述符占 8 个字节,包含了段的基地址、段界限、段属性等信息。
逻辑地址的「偏移量」与段基址相加,得到线性地址(如hello的main函数逻辑地址代码段:0x1130,段基址为0x400000,则线性地址为0x400000 + 0x1130 = 0x401130)。
7.3 Hello的线性地址到物理地址的变换-页式管理
hello 的线性地址(虚拟地址)转物理地址依赖页表:
线性地址被拆分为「页目录索引、页表索引、页内偏移」(以 4KB 页为例,64 位系统拆分为多级索引)。
CPU 通过 CR3 寄存器找到hello进程的页目录基址,逐级查找页表,得到物理页框号。
物理页框号与页内偏移拼接,得到物理地址
7.4 TLB与四级页表支持下的VA到PA的变换
hello 运行时,虚拟地址(VA)转物理地址(PA)的流程(四级页表 + TLB):
CPU 先查TLB(快表):如果hello的 VA 对应的页表项已在 TLB 中缓存,直接取出物理页框号,拼接页内偏移得到 PA(速度极快)。
若 TLB 未命中,查四级页表:
从 CR3 取PML4 表基址,用 VA 的前 9 位索引找到 PML4 表项,得到 PDPT 表基址;
用 VA 的下 9 位索引 PDPT 表,得到 PDT 表基址;
用 VA 的再下 9 位索引 PDT 表,得到 PT 表基址;
用 VA 的再下 9 位索引 PT 表,得到物理页框号;
拼接页内偏移(最后 12 位)得到 PA。
找到 PA 后,将该 VA-PA 映射写入 TLB,供后续访问加速。

图7.4.1
7.5 三级Cache支持下的物理内存访问
Cache(高速缓存)
作用:缓存主存中频繁访问的数据,减少主存访问次数。
特点:容量较小(几MB)、速度快于主存(十纳秒级),采用直接映射、组相联等映射方式。
主存(物理内存)
作用:存储程序和数据的物理空间,是CPU访问数据的最终来源(若Cache未命中)。
特点:容量大(GB级)、速度较慢(百纳秒级),访问需通过内存总线。
hello 访问物理内存时,三级 Cache(L1、L2、L3)会加速访问:
CPU 要读取hello的物理地址,先查L1 Cache:若命中,直接从 L1 读取数据(延迟最低)。
若 L1 未命中,查L2 Cache;L2 未命中则查L3 Cache(L3 是多核共享的 Cache)。
若三级 Cache 都未命中,才从物理内存中读取数据,并将数据逐级写入 L3、L2、L1 Cache,供后续访问复用。
7.6 hello进程fork时的内存映射
当hello进程被fork时,内存映射采用写时复制(Copy-On-Write)策略:
fork后,子进程的虚拟地址空间与父进程(hello)完全相同,页表也直接共享(指向相同的物理页)。
此时父、子进程的页表项被标记为「只读」。
当父 / 子进程修改某页数据时,CPU 会触发页错误,操作系统会为修改的页分配新的物理页,复制原页数据,再更新页表(子进程的该页指向新物理页),实现 “写时复制”。
7.7 hello进程execve时的内存映射
当hello进程调用execve加载新程序时,内存映射会发生完全替换:
原hello进程的虚拟地址空间(代码段、数据段、栈、堆等)被全部释放
操作系统为新程序创建全新的虚拟地址空间,加载新程序的代码段、数据段到对应虚拟地址
初始化新的栈、堆,并将命令行参数、环境变量写入栈空间
最终跳转到新程序的入口点(如main函数)执行,原hello进程的资源不再存在。
7.8 缺页故障与缺页中断处理
触发缺页:若hello访问的虚拟地址未被映射到物理内存(如hello的代码页被换出到磁盘),CPU 触发缺页故障;
中断处理:操作系统暂停hello进程,查找该虚拟地址对应的页是否存在于磁盘(交换分区 / 文件);
内存分配与加载:若页存在,操作系统分配物理页,将磁盘中的页数据读入物理页,更新页表(建立虚拟地址→物理地址映射);
恢复执行:更新 TLB 缓存,恢复hello进程的执行,此时hello可正常访问该地址。
7.9动态存储分配管理
printf调用malloc时,动态内存管理的核心方法与策略:
基本方法:
空闲链表:维护空闲内存块的链表,malloc从链表中找到匹配大小的块分配,free将块放回链表;
伙伴系统:将内存按 2 的幂次划分成块,分配 / 释放时通过 “合并 / 拆分” 伙伴块提高内存利用率。
核心策略:
首次适配:从链表头开始找第一个满足大小的空闲块;
最佳适配:找大小最接近需求的空闲块;
内存对齐:分配的内存按指定字节数(如 8/16 字节)对齐,保证硬件访问效率;
内存回收:free时检查相邻块是否空闲,合并为大的空闲块,减少内存碎片
7.10本章小结
本章主要介绍看hello和操作系统之间的交流方式,介绍了虚拟内存以及相关知识
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
Linux 采用“一切皆文件”的模型管理 I/O 设备:
设备模型化:所有 I/O 设备(如显示器、键盘)都被抽象为文件,通过文件路径(如/dev/tty对应终端)访问;
设备管理接口:通过 Unix I/O 接口(open/read/write/close 等)统一操作设备,屏蔽了不同硬件的差异,进程无需关注设备底层细节。
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
Unix I/O 接口是一套统一的设备操作函数,核心函数包括:
- open:打开设备文件,获取文件描述符(用于后续操作);
- read:从设备读取数据(如从键盘读入字符);
- write:向设备写入数据(如向显示器输出内容);
- close:关闭设备文件,释放资源;
- ioctl:控制设备的特殊功能(如设置终端显示属性)。
8.3 printf的实现分析
hello中printf的实现流程分为用户态处理和内核态驱动两个阶段:
用户态:信息生成与系统调用
调用vsprintf:将格式化字符串(如"Hello %s %s %s\n")与参数(argv[1]等)拼接为最终字符串;
调用write系统函数:将拼接后的字符串传入内核,触发系统调用(通过int 0x80或syscall指令陷入内核态)。
内核态:驱动与硬件输出
字符显示驱动:将字符串中的 ASCII 码映射到字模库,得到每个字符的点阵数据;
写入 VRAM:将点阵数据(含 RGB 颜色信息)写入显示内存(VRAM);
硬件渲染:显示芯片按刷新频率逐行读取 VRAM 中的数据,将每个点的 RGB 分量通过信号线传输到显示器,最终显示出字符。
https://www.cnblogs.com/pianist/p/3315801.html
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
hello程序中getchar()用于读取键盘输入,其实现是硬件、内核与程序协同工作的过程,主要分为三个环节:
1. 键盘输入的中断处理
当用户按下键盘按键时,键盘会向 CPU 发送中断信号,触发内核的 “键盘中断处理子程序”:
首先读取按键对应的 “扫描码”(每个按键的唯一标识);
再将扫描码转换为标准 ASCII 码(例如回车键对应 ASCII 码0x0A);
最后把 ASCII 码存入系统键盘缓冲区(内核中暂存按键数据的内存区域)。
2. getchar 的系统调用过程
getchar()本质是通过read系统调用读取 “标准输入”(键盘对应的文件描述符为 0):
若键盘缓冲区已有数据,内核直接从中取出字符,返回给hello程序;
若缓冲区为空,hello会被内核挂起,进入 “等待状态”,直到有新的按键输入、缓冲区存入数据后,才会被唤醒并获取字符。
3. 行缓冲与回车的作用
getchar()默认采用行缓冲模式:
它会持续读取键盘缓冲区的字符,但不会立即返回,而是等待用户按下 “回车键”;
回车键被按下后,内核会将整行字符(包含回车键)存入缓冲区,getchar()读取这一行的字符并逐个返回,直到读取到回车键时,本次调用完成,hello恢复执行。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章介绍了Linux的IO设备管理方法;简述了Unix IO接口及其函数;分析了printf及getchar的实现。
结论
hello.c 经预处理阶段完成头文件包含、注释删除与宏替换,生成纯文本文件 hello.i;编译阶段将 C 语言代码翻译为适配 X86-64 架构的汇编指令,输出 hello.s;汇编阶段把汇编指令转换为机器语言,生成含未解析符号的可重定位目标文件 hello.o;链接阶段通过符号解析关联系统库、地址重定位修正符号地址,合并生成可执行 ELF 文件 hello,完成从静态代码到可运行文件的蜕变。在 Bash Shell 环境中,操作系统通过 fork 系统调用创建子进程并分配 PCB,经 execve 系统调用加载 hello 文件,通过 mmap 完成内存映射;进程在时间片轮转调度机制下获取 CPU,按流水线执行指令,依托段式管理、页式管理及 TLB、Cache 加速实现地址转换与内存访问,通过 Unix IO 接口完成 printf 输出与 getchar 输入的设备交互。程序从无实体的文本文件(Zero),经编译工具链转化与操作系统激活,成为占用 CPU、内存、IO 资源的运行进程(有);执行完成或异常终止时,操作系统回收进程所有资源,进程消亡回归 Zero 状态,实现资源的完整流转。
通过本次研究,深刻认识到计算机系统是一个高度协同的复杂整体,各层级的设计既相互独立又紧密关联。
附件
hello.i: 预处理后的文件,完成头文件包含、注释删除、宏替换等文本处理,是编译阶段的输入
hello.s:汇编生成的可重定位目标文件,包含机器语言指令与未解析的符号引用,是链接阶段的输入
hello.o: 链接生成的可执行文件,整合目标文件与系统库,解决符号重定位,可被操作系统加载为进程运行。
参考文献
[1] 兰德尔·E,布莱恩特等. 深入理解计算机系统[M]. 北京:机械工业出版社,2016.7(2022.1重印)
[3] 计算机内存访问:TLB、页表与Cache全解析_访存优化 tlb-CSDN博客
[5] 豆包 [EB/OL]. 豆包
转载自CSDN-专业IT技术社区



