天津储鑫盛钢材现货供应商 发表于 5 天前

程序人生Hello’s P2P

第1章 概述

1.1 Hello简介

根据Hello的自白,使用盘算机系统的术语,简述Hello的P2P,020的整个过程。
(1) P2P(Program to Process)  
1. 编写(Program):程序员在文本编辑器(如Vim、VS Code)中写下 Hello, World! 的源代码(如C语言)。  
2. 编译(Compilation):编译器(如GCC)将源代码翻译成机器码,生成可执行文件(如 a.out)。  
3. 加载(Loading):当用户运行程序时,操纵系统(OS)通过加载器(Loader)将可执行文件从磁盘读取到内存,并建立进程(Process)。  
4. 执行(Execution):CPU 逐条执行机器指令,调用系统调用(如 write)将字符串输出到终端。  
5. 停止(Termination):程序执行完毕,OS 回收其资源(内存、文件形貌符等),进程竣事。  
(2) O2O(Zero to Zero)  
1. 从“无”开始(Zero):程序最初只是磁盘上的二进制文件(静态存储)。  
2. 运行期间码加载(Load):OS 分配内存,将代码和数据映射到进程地址空间(虚拟内存)。  
3. CPU 执行(Compute):指令被 CPU 表明执行,数据在寄存器、缓存、内存间运动。  
4. I/O 交互(Output):程序通过系统调用与终端交互,显示 Hello, World!。  
5. 回归“无”(Zero):进程停止,内存开释,全部资源归还系统,程序“消失”。  
这一过程显现了盘算机如何将静态代码(Program)酿成动态进程(Process),并终极完成“从无到无”的生命周期(O2O)。
1.2 情况与工具

列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件情况,以及开发与调试工具。
1.2.1 硬件情况
X64 CPU;2GHz;8G RAM;
1.2.2 软件情况
Windows11 64位;Vmware 14;Ubuntu 16.04 LTS 64位;
1.2.3 开发与调试工具
gcc, readelf, vim, edb, gdbt,objdump
1.3 中心效果

列出你为编写本论文,生成的中心效果文件的名字,文件的作用等。
hello.i:hello.c的预处置惩罚文件
hello.s:hello.i的编译文件
hello.o:hello.s的汇编文件
helloo.elf:hello.o文件的ELF格式
hello.objdump:hello.o文件的反汇编文件
hello.elf:hello文件的ELF格式
hello.objdumpp:hello文件的反汇编文件
hello:可执行文件
1.4 本章小结

本章概述了Hello程序的P2P(从代码到进程)和O2O(从无到无)运行过程,详细说明白实验情况以及GCC、GDB等开发调试工具。同时介绍了相干文件及其在分析过程中的作用,为后续研究奠定基础。


第2章 预处置惩罚

2.1 预处置惩罚的概念与作用

 (1)概念:
预处置惩罚是C/C++程序编译的第一个阶段,由预处置惩罚器(如cpp)对源代码举行文本级别的处置惩罚。它在实际编译之前执行,主要任务是解析源代码中以""开头的预处置惩罚指令。
(2)主要作用:
1. 头文件包罗:通过include指令将头文件内容插入到源文件中
2. 宏替换:处置惩罚define界说的宏,举行文本替换
3. 条件编译:根据if、ifdef等条件决定编译哪些代码
4. 符号界说:处置惩罚预界说宏(如__DATE__、__FILE__等)
5. 注释删除:移除全部注释,为编译做预备
6.特点:纯文本处置惩罚,不查抄语法;生成颠末处置惩罚的源代码(可通过gcc E检察)
;为后续编译阶段做好预备
2.2在Ubuntu下预处置惩罚的命令

预处置惩罚命令:
gcc E hello.c o hello.i
https://i-blog.csdnimg.cn/direct/e780607e86a84b139a6ef3020b87453a.png
图2-1.预处置惩罚命令 文件列表
2.3 Hello的预处置惩罚效果解析


Hello.i共3061行
开头时引入外部库.h文件
https://i-blog.csdnimg.cn/direct/9ccd5d5fd95b42b091f1140e1487b977.png
图2-2.hello.i部分代码1
typedef举行数据范例名称替换https://i-blog.csdnimg.cn/direct/38a69e7b1faa46f08ffb9e4a76811454.png
图2-3.hello.i部分代码

引入外部函数:
https://i-blog.csdnimg.cn/direct/ba9935c78ea849aba20438b0eb3fdcb4.png
图2-4.hello.i部分代码
main函数在第3048行,hello.i将头文件中的内容引入,比方声明函数、界说布局体、界说变量、界说宏等内容。预处置惩罚将头文件中的内容全部放到hello.i文件中,方便编译等操纵。
https://i-blog.csdnimg.cn/direct/730fc93995434fda9101ca9c29f99b6f.png
图2-5.hello.i部分代码

2.4 本章小结

本章详细分析了hello.c程序的预处置惩罚过程,通过对生成的hello.i文件的分析发现,原本仅24行的简洁源文件颠末预处置惩罚后扩展为3061行的代码。这一现象直观显现了预处置惩罚的强大功能——它答应开发者编写简洁、模块化的代码,同时自动处置惩罚复杂的底层依赖关系。预处置惩罚机制通过头文件包罗、宏睁开等功能,既保证了代码的可读性和可维护性,又实现了代码的高效复用。正是这种"写时简洁,用时完备"的特性,使得开发者能够专注于核心逻辑的编写,而无需手动维护庞大的代码库,极大地提升了开发效率和代码质量,体现了现代编程语言设计的重要智慧。


第3章 编译

3.1 编译的概念与作用

留意:这儿的编译是指从 .i 到 .s 即预处置惩罚后的文件到生成汇编语言程序
 编译的概念与作用  
(1)概念  
编译是将预处置惩罚后的源代码(如.i文件)转换为汇编代码(.s文件)的过程,由编译器(如GCC的cc1)完成。它是程序构建的核心阶段,负责:  
1. 词法分析:将代码分解为标记(tokens)  
2. 语法分析:构建抽象语法树(AST)  
3. 语义分析:查抄范例、作用域等逻辑准确性  
4. 代码优化:对中心表示(IR)举行性能优化  
5. 代码生成:输出目的平台的汇编指令  
(2)作用  
1. 跨平台适配:将高级语言转换为特定CPU架构的底层指令  
2. 代码优化:通过死代码消除、循环优化等提升执行效率  
3. 错误检测:在运行前发现语法错误和逻辑问题  
4. 抽象封装:隐蔽硬件细节,让开发者专注业务逻辑  
5. 编译过程平衡了可读性(高级语言)与效率(机器码),是软件运行的基础环节。
3.2 在Ubuntu下编译的命令


https://i-blog.csdnimg.cn/direct/8899d8fef0374bc1a2db71c20b4dc082.png
图3-1.编译命令 文件列表
3.3 Hello的编译效果解析

此部分是重点,说明编译器是怎么处置惩罚C语言的各个数据范例以及各类操纵的。应分3.3.1~ 3.3.x等按照范例和操纵举行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操纵,都应解析。https://i-blog.csdnimg.cn/direct/73a03e412330410e814b44a2a928a5b1.png
图3-2汇编语言程序前半部分



https://i-blog.csdnimg.cn/direct/ca4b79828ea94936b7ce5509314bc07b.png
图3-3汇编语言程序后半部分
3.3.1.常量
在hello.c中,“用法: Hello 学号 姓名 手机号 秒数!”为常量。在图32中,我们可以看到被编译为
https://i-blog.csdnimg.cn/direct/c68fe697d9db49fd890a563b356f7817.png
图34汇编语言常量
该效果表示编译对中笔墨符举行了utf8编码,并存储在只读代码区的.rodata节,在程序运行时会直接通过寻址找到常量。
3.3.2.变量
不同范例的变量在不同位置界说,局部变量在堆上举行界说和开释,初始化的全局变量和静态变量界说在只读代码区的.bss节,已初始化的全局和静态变量界说在只读代码区的.data节。
在hello.c中,存在局部变量i,编译器举行编译的时候将局部变量i会放在堆栈中。根据图35可知,局部变量i放在栈上4(%rbp)的位置。
https://i-blog.csdnimg.cn/direct/03e60966ead74bb8827ce7a6f99bf401.png
图3-5汇编语言变量

3.3.3.范例
编译器通过数据范例准确控制内存分配和操纵指令的生成,这是其范例系统的核心体现。对于基本数据范例,编译器会严格按范例大小分配内存空间:char范例分配1字节用于存储字符或小整数,int范例分配4字节存储整型数据,double范例则分配8字节空间存储双精度浮点数。在生成机器指令时,编译器会根据数据范例选择相应的操纵指令,如对char范例使用字节操纵指令,对int范例使用32位操纵指令。这种机制不仅确保了内存使用的准确性,还通过编译时的范例查抄保证了操纵的准确性,比方防止对整型变量使用浮点运算指令。通过这种方式,编译器在底层实现了高级语言中的范例系统,既优化了内存使用效率,又维护了程序的健壮性。比方int i只有4个字节的空间。
3.3.4.赋值
在hello.c中,我们对i赋初值,
根据图36,
对i:
https://i-blog.csdnimg.cn/direct/6aea5abf569b4c43a4485e801d1f9cd6.png
图3-6汇编语言赋值
局部变量赋值接纳MOV指令,根据不同大小的数据范例有movb、movw、movl、movq等。
3.3.5.范例转换
数据范例转换分为隐式和显式两种:隐式转换由编译器自动完成,无需额外声明(如int与char运算时char自动提升为int);显式转换需通过强制范例转换运算符(如(int)3.14)自动指定。前者简化编码,后者提供准确控制,共同确保范例安全与运算准确性。
3.3.6.算术操纵
+转换成add,转换成sub,转换成imul,/转换成div。
在hello.c中,存在i++,根据图37可知,汇编为addl $1, 4(%rbp)。https://i-blog.csdnimg.cn/direct/0f69912070d7424c9ffe54dc4ac72b60.png
图3-7汇编语言加

同时,根据argv首地址获得argv和argv也通过加减操纵:https://i-blog.csdnimg.cn/direct/2c924601211f41bb9f84b251442b8204.png
图3-8汇编语言算数
3.3.7.关系操纵
hello.c中有argc!=4,根据图39可知,汇编语言为cmpl $4, 20(%rbp)。
hello.c中有i<10,根据图39可知,汇编语言为cmpl $8, 4(%rbp)。
以是,编译器通常通过将比较编译为cmp指令实现。根据不同的数据大小,有cmpb、cmpw、cmpl和cmpq。比较之后,通过jmp系列指令跳转。
https://i-blog.csdnimg.cn/direct/35da5df1660245d69c871b39f1ac2c02.png
图3-9汇编语言关系操纵    
   https://i-blog.csdnimg.cn/direct/b4b3b9ce2e414b1ba8f524db343932f9.png
                                                            图3-10汇编语言关系操纵

3.3.8.数组/指针操纵
对数组的索引相当于在第一个元素地址的基础上通过加索引值乘以数据大小来实现。在hello.c中,存在char argv[],根据图37可知(前面有图),根据argv首地址获得argv和argv需要通过加减操纵:
movq 32(%rbp), %rax
addq $16, %rax
movq (%rax), %rdx
movq 32(%rbp), %rax
addq $8, %rax
movq (%rax), %rax
movq %rax, %rsi
3.3.9.控制转移
hello.c中存在if,for语句,在汇编代码中,接纳cmp比较执行条件跳转指令举行执行。由3.9和3.10可知(前面有图)
cmpl $4, 20(%rbp)
je .L2
cmpl $8, 4(%rbp)
jle .L4
3.3.10.函数操纵
hello.c中包括main函数,printf函数,sleep函数,getchar函数。
起首,内核shell获取命令行参数和情况变量地址,执行main函数,在main中需要调用别的函数,在main中为被调用函数分配栈空间。调用函数需要借助栈,先将返回地址压入栈中,并将PC设为被调用函数的起始地址,然后调用。返回时,先从栈中弹出返回地址,再PC设置为该返回地址。return正常返回后,leave恢复栈空间。
由图311可知,分别调用函数为:
call puts@PLT
call exit@PLT
call printf@PLT
call sleep@PLT
call getchar@PLT
https://i-blog.csdnimg.cn/direct/5a6914e1cd694539b22902b8911ab99b.png
 图3-11汇编语言关系函数
调用的atoi函数,%rdi为参数,存放着字符串的首地址,%eax为返回值,为一个整数。
3.4 本章小结

本节详细讲解了编译器将预处置惩罚后的.i文件转换为汇编语言.s文件的编译过程,重点分析了变量声明、赋值操纵、循环控制等基础C语言布局在汇编层面的实现方式。通过对比可以发现,高级语言中简洁的条件判定或循环语句,在汇编层面需要分解为多条指令来实现,包括寄存器操纵、跳转指令等底层细节。这种从高级语言到汇编语言的转换过程,揭示了盘算机执行程序的本质原理。深入明白这一编译机制,不仅有助于我们掌握程序在机器层面的真实执行方式,更能为后续的程序性能优化和调试工作奠定坚实基础,使开发者能够从系统层面思考代码的运行效率问题。


第4章 汇编

4.1 汇编的概念与作用

(1)概念
汇编是指将汇编语言(.s文件)转换为机器语言二进制目的文件(.o文件)的过程,由汇编器(as)完成。这是程序构建的关键步调,实现了从人类可读的汇编指令到盘算机可执行的机器码的转换。
(2)作用
1. 指令转换:将助记符情势的汇编指令(如mov、add)转换为二进制机器码
2. 地址解析:处置惩罚符号引用,建立开端的地址映射关系
3. 生成重定位信息:为链接阶段预备必要的重定位条目
4. 生成目的文件:输出符合特定格式(如ELF)的二进制文件
留意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
4.2 在Ubuntu下汇编的命令https://i-blog.csdnimg.cn/direct/b3f906d13bbc41569325e644298fd9d1.png

图4-1汇编命令 文件列表
4.3 可重定位目的elf格式

分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。https://i-blog.csdnimg.cn/direct/01c5e0c12749461fa9a1df0b5365b816.png
图4-2可重定位目的elf格式
4.3.1 ELF头
ELF文件头包罗关键元数据:系统字长与字节序、文件头大小、文件范例(可重定位/可执行/共享库)、目的架构(如x8664)、节头表条目数目及大小等信息。这些信息完备形貌了目的文件的组织布局和平台特性,是后续链接和执行的基础。https://i-blog.csdnimg.cn/direct/142ac452291c44e68fe9a4a97fe4614e.png
图4-3 ELF头
4.3.2节头
节头表(Section Header Table)是ELF文件中记录各节(section)详细信息的关键数据布局。它包罗了每个节的完备元数据:节名称(如.text、.data)、节大小、范例(如代码段、数据段)、全体大小、内存加载地址、访问权限标志(如可读、可写、可执行)、链接信息(符号表关联)、附加属性信息、文件偏移量以及内存对齐要求等。通过这些信息,链接器和加载器可以准确定位每个节的位置,明白其功能特性,并按照规定的对齐方式和访问权限将各节准确加载到内存中执行。节头表实质上提供了ELF文件的"目录"功能,是实现模块化程序组织的重要基础。https://i-blog.csdnimg.cn/direct/028ca97e1d604bc99788c3163fef1798.png
图4-4节头
4.3.3 重定位节
当链接器把这个目的文件和其他文件组适时,需要修改表中的这些位置。一样平常,调用外部函数或者引用全局变量的指令都需要修改。
由图45可知,在hello.o的重定位节中包罗了main函数调用的puts、exit、printf、sleep、getchar函数以及全局变量sleepsecs,还有只读区域.rodata节。表格记录了它们的偏移量、信息、范例、符号值、符号名称及加数。另用rela.eh_frame记录了.text的信息。
若重界说范例为R_X86_64_PC32,重定位一个使用32位PC相对地址的引用。若若重界说范例为R_X86_64_32,重定位一个使用32位绝对地址的引用。根据重定位条目和重定位算法即可得到相应的重定位位置。https://i-blog.csdnimg.cn/direct/5e2e5262075e4cd681817b6cc3e76971.png
图4-5重定位节
重定位算法:
foreach section s

{

foreach relocation entry r

{

refptr = s + r.offset;

if (r.type == R_386_PC32)

{

refaddr = ADDR(s) + r.offset;

refptr = (unsigned) (ADDR(r.symbol) + refptr  refaddr);

}

if (r.type = R_386_32)

refptr = (unsigned)(ADDR(r.symbol) + refptr);

{

} 4.3.4 符号表
用于存放程序中界说和引用的函数和全局变量的信息。https://i-blog.csdnimg.cn/direct/7a63a2bedcb649f2bb48eece52767c36.png
图4-6符号表

4.4 Hello.o的效果解析https://i-blog.csdnimg.cn/direct/93ecff1573e449708a3752163c303780.png

图47反汇编文件和指令https://i-blog.csdnimg.cn/direct/8c53a1e0e5fa45378f71d3876ad4d318.png
图4-8反汇编代码
将图48与图32,33对比发现,大多数代码是一样的,有以下几点不同:
(1)机器语言构成。操纵码字段:13字节,决定指令范例和操纵(如0x89对应mov)。寻址模式字节(ModR/M):指定操纵数寻址方式。比例索引基址字节(SIB):复杂内存寻址时使用。立即数字段:指令中直接包罗的数值(如mov $5, %eax中的5),位移量字段:地址偏移值(如movl %eax, 4(%rbp)中的4)。
(2)机器语言与汇编语言映射关系。通过objdump d r hello.o分析可见,机器语言与汇编语言存在严格映射关系:操纵码对应指令范例,ModR/M字节编码操纵数寻址方式。关键差别体现在地址处置惩罚上,汇编代码使用符号标签,而机器码使用暂时占位地址(全零)共同重定位标记,等待链接器终极解析。简单指令(如push/nop)实现1:1映射,复杂指令(如call/mov)则通过多字节编码实现操纵数传递,体现从高级抽象到底层执行的自然过渡。
(3)机器语言中的操纵数与汇编语言不划一。全局变量访问,在.s 文件中,访问 rodata(printf 中的字符串),使用段名称+%rip,在反汇编代码中 0+%rip,由于 rodata 中数据地址也是在运行时确定,故访问也需要重定位。以是在汇编成为机器语言时,将操纵数设置为全 0 并添加重定位条目。
(4)hello.s中包罗.type .size .align以及.rodata只读数据段等信息,而hello.objdump中只有函数的相干内容。
(5)分支转移。hello.s主要借助.L0,.L1等转移,而hello.objdump直接借助地址举行跳转(未链接的地址)反汇编代码跳转指令的操纵数使用的不是段名称如.L3,由于段名称只是在汇编语言中便于编写的助记符,以是在汇编成机器语言之后显然不存在,而是确定的地址。
(6)函数调用。hello.s中直接调用函数的名称,而hello.objdump中使用下一条地址相对函数起始地址的偏移量,链接重定位后才能确定地址。在.s 文件中,函数调用之后直接跟着函数名称,而在反汇编序中,call 的目的地址是当前下一条指令。这是由于 hello.c 中调用的函数都是共享库中的函数,终极需要通过动态链接器才能确定函数的运行时执行地址,在汇编成为机器语言的时候,对于这些不确定地址的函数调用,将其 call 指令后的相对地址设置为全 0(目的地址正是下一条指令),然后在.rela.text 节中为其添加重定位条目,等待静态链接的进一步确定。

4.5 本章小结

本章通过分析hello.s到hello.o的汇编过程,揭示了汇编器如何将汇编代码转换为机器语言。通过对比hello.o的ELF格式和objdump反汇编效果与原始hello.s,可以观察到:固然代码情势从文本指令变为二进制编码,但程序逻辑和布局保持稳定。汇编器需要处置惩罚符号解析、地址盘算、指令编码等核心转换工作,将助记符映射为机器码,同时生存节区、重定位等元信息。这种情势转换体现了编译过程中"内容改变但本质稳定"的特点——高级语义通过不同表示层得以完备传递。



第5章 链接

5.1 链接的概念与作用

(1)链接的概念
链接(Linking)是将多个可重定位目的文件(如 .o 文件)和库文件合并,生成一个可执行目的文件(如 hello)的过程。比方,hello.o 需要与 libc.so(C 标准库)等文件链接,才能终极生成可执行的 hello 程序。
(2)链接的作用
1. 符号解析(Symbol Resolution)
将符号(如函数名、全局变量)的引用与其界说关联。比方,hello.o 调用了 printf,链接器需要找到 printf 在 libc.so 中的实现。如果找不到界说,会报错(如 undefined reference)。
2. 重定位(Relocation)
合并多个 .o 文件,调整代码和数据的地址,使它们在终极的可执行文件中具有准确的内存位置。比方,main 函数的地址在 hello.o 中是暂时的,链接后会被修正为进程运行时的实际地址。
3. 合并相同范例的节(Section Merging)
将不同 .o 文件中的相同节(如 .text、.data)合并,优化存储布局。
4. 解析静态/动态库依赖
静态链接(Static Linking):将库代码直接复制到可执行文件(如 libc.a)
动态链接(Dynamic Linking):运行时加载共享库(如 libc.so),减少磁盘和内存占用。
5.2 在Ubuntu下链接的命令

ld o hello \   dynamiclinker /lib64/ldlinuxx8664.so.2 \
>    /usr/lib/x86_64linuxgnu/crt1.o \
>    /usr/lib/x86_64linuxgnu/crti.o \
>    hello.o \
>    /usr/lib/x86_64linuxgnu/libc.so \
>    /usr/lib/x86_64linuxgnu/crtn.o

https://i-blog.csdnimg.cn/direct/09ae349c50824a27aaf5b3fa6bc3b29d.png
图5-1链接命令
5.3 可执行目的文件hello的格式

分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
(1)在 ELF 格式文件中,Section Headers 对 hello 中全部的节信息举行了声明,此中包括大小 Size 以及在程序中的偏移量 Offset,因此根据 Section Headers 中的信息我们就可以用 HexEdit 定位各个节所占的区间(起始位置,大小)。此中 Address是程序被载入到虚拟地址的起始地址。
https://i-blog.csdnimg.cn/direct/23be8b281b3d455abe43264a4de3d475.png
图5-2各节起始地址和大小
(2)ELF Header:以16B的序列Magic开始,Magic形貌了生成该文件的系统的字的大小和字节顺序,ELF头剩下的部分包罗帮助链接器语法分析和表明目的文件的信息,此中包括ELF头的大小、目的文件的范例、机器范例、字节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数目等信息。
与链接前的ELF header比较,可见除系统决定的基本信息稳定外,section header和program header均增长,并获得了入口地址。
https://i-blog.csdnimg.cn/direct/2eea86b880e84bf6bf918d1125eea177.png
图5-3 ELF头
(3)程序头部表(Program Header Table),ELF文件头布局就像是一个总览图,形貌了整个文件的布局情况。因此在ELF文件头布局答应的数值范围内,整个文件的大小是可以动态增减的。告诉系统如何创建进程映像。
https://i-blog.csdnimg.cn/direct/ae7d3e23e9a74d31a9ebdd7715593d45.png
图5-4 程序头部表
https://i-blog.csdnimg.cn/direct/fd10d399e01f49df894bdfe574a0e58a.png
图5-5 段映射
(4)Dynamic section,如果目的文件参与动态链接,则其程序头表将包罗一个范例为 PT_DYNAMIC 的元素。此段包罗 .dynamic 节。特别符号 _DYNAMIC 用于标记包罗以下布局的数组的节。
     https://i-blog.csdnimg.cn/direct/5d9d38fdc98f477cb8b88c49b3bd8db5.png
图5-6 Dynamic section
(5)重定位节.rela.text ,一个.text 节中位置的列表,包罗.text 节中需要举行重定位的信息,当链接器把这个目的文件和其他文件组适时,需要修改这些位置。六条重定位信息,分别形貌了原hello的函数main、标准头文件的函数puts、函数printf、函数getchar、函数exit、函数sleep的重定位声明。
    https://i-blog.csdnimg.cn/direct/8a5ea75051d64380ae21ec6ddac797d9.png
图5-7 重定位节



[*](6)符号表节(symbol table),目的文件的符号表包罗定位和重定位程序的符号界说和符号引用所需的信息。符号表索引是此数组的下标。索引 0 指定表中的第一项并用作未界说的符号索引。
动态符号表 (.dynsym) 用来生存与动态链接相干的导入导出符号,不包括模块内部的符号。https://i-blog.csdnimg.cn/direct/29fb675543a54ea8b6c72e06aafeaa45.png
图5-8 符号表节1

此中 .symtab 段只生存函数名和变量名等基本的符号的地址和长度等信息。https://i-blog.csdnimg.cn/direct/396cc29621ac4360be2b1a28c302950b.png
图5-8 符号表节2
5.4 hello的虚拟地址空间

使用edb加载hello,检察本进程的虚拟地址空间各段信息,并与5.3对照分析说明。   

https://i-blog.csdnimg.cn/direct/87926600746d4f3e8e19983deec7da46.png
图5-9ELF内容
由图54 程序头部表,可知包罗:
PHDR:程序头表
INTERP:程序执行前需要调用的表明器
LOAD:程序目的代码和常量信息
DYNAMIC:动态链接器所使用的信息
NOTE::辅助信息
GNU_STACK:使用系统栈所需要的权限信息
GNU_RELRO:生存在重定位之后只读信息的位置
别的的节的内容是存放在0x00400fff背面。
5.5 链接的重定位过程分析


https://i-blog.csdnimg.cn/direct/166c5f88416a4b97ba367724752da24c.png
图5-10 链接的重定位

objdump d r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
(1)不同
1. hello中包罗init,puts,pringtf,getchar等函数,hello.o中只有.text。
2. hello.o中是相对偏移地址,hello为虚拟内存地址。
3. hello中增长了外部函数。
4. hello中调用为call+函数名,hello.o中为call+相对偏移地址。
(2)链接重定位的过程
链接器(ld)将各个目的文件(各种.o文件)组装在一起,文件中的各个函数段按照一定规则累积在一起。从.o提供的重定位条目将函数调用和控制流跳转的地址填写为终极的地址。链接器完成符号解析后,会将代码中的每个符号引用与对应的符号界说(来自输入目的模块的符号表条目)建立关联。此时,链接器已经掌握了全部输入目的模块中代码段和数据段的具体大小,随即开始重定位过程。在 hello 到 hello.o 中,起首是重定位节和符号界说,链接器将全部输入到 hello 中相同范例的节合并为同一范例的新的聚合节。在这个阶段,链接器起首合并全部输入模块,将相同范例的段(如.text、.data等)聚合为新的单一节段,比方将全部输入模块的.data段合并为可执行文件的.data段。接着,链接器为这些聚合段、各输入模块界说的段以及每个符号分配运行时内存地址。完成这一步后,程序中的每条指令和全局变量都获得了唯一的运行时地址。最后,链接器会修正代码段和数据段中对这些符号的全部引用,确保它们指向准确的运行时地址,从而完成整个重定位过程。
5.6 hello的执行流程https://i-blog.csdnimg.cn/direct/fed2801b55ea474ca1b0081e2df97f57.png

图5-11 执行流程(此中一部分)

程序名称
程序地址
ld2.27.so!_dl_start
0x7fce 8cc38ea0
ld2.27.so!_dl_init
0x7fce 8cc47630
hello!_start
0x400500
libc2.27.so!__libc_start_main
0x7fce 8c867ab0
libc2.27.so!__cxa_atexit
0x7fce 8c889430
libc2.27.so!__libc_csu_init
0x4005c0
hello!_init
0x400488
libc2.27.so!_setjmp
0x7fce 8c884c10
libc2.27.so!_sigsetjmp
0x7fce 8c884b70
libc2.27.so!__sigjmp_save
0x7fce 8c884bd0
hello!main
0x400532
hello!puts@plt
0x4004b0
hello!exit@plt
0x4004e0
ld2.27.so!_dl_runtime_resolve_xsave
0x7fce 8cc4e680
ld2.27.so!_dl_fixup
0x7fce 8cc46df0
ld2.27.so!_dl_lookup_symbol_x
0x7fce 8cc420b0
libc2.27.so!exit
0x7fce 8c889128


5.7 Hello的动态链接分析

在举行动态链接前,起首举行静态链接,生成部分链接的可执行目的文件 hello。此时共享库中的代码和数据没有被合并到 hello 中。只有在加载 hello 时,动态链接器才对共享目的文件中的相应模块内的代码和数据举行重定位,加载共享库,生成完全链接的可执行目的文件。
PLT:PLT是一个数组,此中每个条目是16字节代码。PLT是一个特别条目,它跳转到动态链接器中。每个被可执行程序调用的库函数都有它自己的PLT条目。每个条目都负责调用一个具体的函数。
GOT:GOT是一个数组,此中每个条目是8字节地址。和PLT联合使用时,GOT和GOT包罗动态链接器在解析函数地址时会使用的信息。GOT是动态链接器在1dlinux.so模块中的入口点。别的的每个条目对应于一个被调用的函数,其地址需要在运行时被解析。每个条目都有一个相匹配的PLT条目。
检察plt段
https://i-blog.csdnimg.cn/direct/138be8439d094bdcb2433f936fa69e1c.png
图5-12 调用 dl_init 之前的全局偏移表https://i-blog.csdnimg.cn/direct/478ec927371b4181acb86f37ffc76727.png
图5-13 调用 dl_init 之后的全局偏移表
5.8 本章小结

本章主要介绍了链接器如何将hello.o可重定向文件与动态库函数链接起来,分析可执行文件hello的ELF格式及其虚拟地址空间,并分析了重定位的过程。



第6章 hello进程管理

6.1 进程的概念与作用

(1)概念
进程是操纵系统举行资源分配和调理的基本单位,是程序在盘算机中的一次动态执行过程。每个进程都拥有独立的虚拟地址空间、文件形貌符、安全属性等系统资源,并通过进程控制块(PCB)来维护其运行状态和上下文信息。操纵系统通过进程管理机制实现多任务并发执行,为每个运行中的程序创建独立的进程实例,使其在受保护的情况中执行。
(2)作用
进程的核心作用体现在三个方面:起首,它实现了程序的隔离性,确保不同程序不会相互干扰;其次,通过时间片轮转等调理算法,操纵系统可以在多个进程间快速切换,形成并行执行的假象;最后,进程作为资源容器,系统可以准确控制CPU、内存、I/O设备等资源的分配和使用。现代操纵系统还通过进程间通讯(IPC)机制和线程技术,在保持进程隔离优势的同时,实现了更高效率的任务协作。当用户执行可执行文件(如hello)时,操纵系统会为其创建新进程,加载代码和数据,建立运行时情况,终极完成程序的执行生命周期。
6.2 简述壳Shellbash的作用与处置惩罚流程

(1)作用
在盘算机科学中,Shell俗称壳(用来区别于核),是指“为使用者提供操纵界面”的软件(命令解析器)。它接收用户命令,然后调用相应的应用程序。Shell 是一个用 C 语言编写的程序,他是用户使用 Linux 的桥梁。Shell 是指一种应用程序,Shell 应用程序提供了一个界面,用户通过这个界面访问操纵系统内核的服务。
(2)处置惩罚流程
1. 从终端读入命令
2. 切分字符串得到各种参数
3.判定是否为内置命令
如果,立即执行。 若不是,调用相应的程序为其分配子进程并运行
4. 担当键盘输入信号,并对这些信号举行相应处置惩罚
6.3 Hello的fork进程创建过程

当hello程序调用fork()时,Linux内核通过clone()系统调用创建子进程。内核起首复制父进程的task_struct,分配唯一的PID,并通过写时复制(COW)机制共享父进程的虚拟内存空间(页表标记为只读,修改时触发物理页复制)。子进程继承父进程的文件形貌符、信号处置惩罚表及寄存器上下文,但内核强制其fork()返回0,而父进程返回子进程PID。随后,父子进程被加入调理队列独立运行。若hello调用execve()加载新程序(如/bin/date),子进程将清空原有地址空间并加载目的程序代码。整个过程通过COW和惰性复制优化性能,确保进程隔离性,是Linux多任务的基础机制。

https://i-blog.csdnimg.cn/direct/3d76910ca93b4c8790dd7e656e54f2d8.png
图6-1进程创建

6.4 Hello的execve过程

当Hello程序通过fork创建子进程后,子进程调用execve函数加载并运行新程序hello,这一过程会彻底重构进程的执行情况。起首,execve会清除子进程原有的虚拟内存段,包括代码段、数据段、堆和栈,然后操纵系统驻留在内存中的启动加载器会基于hello的ELF文件格式创建全新的内存布局。新的代码段和数据段通过内存映射技术直接关联到可执行文件的对应区域,堆栈段则被初始化为零值空间。值得留意的是,这一过程接纳了耽误加载机制:除了必要的ELF头部信息外,实际的可执行内容并不会立即从磁盘复制到内存,而是通过操纵系统的按需分页机制,只有当CPU真正访问某个虚拟页面时,才会触发缺页非常并将对应的磁盘内容加载到物理内存。最后,加载器将程序计数器(PC)设置为hello程序的入口点_start,颠末一系列初始化后终极跳转到main函数执行。这种设计既保证了进程执行情况的完全替换,又通过耽误加载优化了性能,是Unix/Linux系统程序加载的核心机制。
https://i-blog.csdnimg.cn/direct/c467370ae023446395ab6476849e3683.png
图6-2 execve过程


6.5 Hello的进程执行

(1)逻辑控制流
一系列程序计数器PC的值的序列叫做逻辑控制流,进程是轮流使用处置惩罚器的,在同一个处置惩罚器核心中,每个进程执行它的流的一部分后被抢占(暂时挂起),然后轮到其他进程。
(2)时间片
一个进程执行它的控制流的一部分的每一时间段叫做时间片。
(3)用户模式和内核模式
处置惩罚器通常使用一个寄存器提供两种模式的区分,该寄存器形貌了进程当前享有的特权,当没有设置模式位时,进程就处于用户模式中,用户模式的进程不答应执行特权指令,也不答应直接引用地址空间中内核区内的代码和数据;设置模式位时,进程处于内核模式,该进程可以执行指令集中的任何命令,而且可以访问系统中的任何内存位置。
(4)上下文信息
上下文就是内核重新启动一个被抢占的进程所需要的状态,它由通用寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据布局等对象的值构成。
(5)上下文切换
上下切换是由内核中调理器完成的,当内核调理新的进程运行后,它就会抢占当前进程,并举行
1.生存以前进程的上下文
2.恢复新恢复进程被生存的上下文
3.将控制传递给这个新恢复的进程来完成上下文切换。
(6)Hello:当hello运行到sleep函数时,是在用户模式,然后调用sleep函数,进入内核模式,内核处置惩罚休眠请求,并开始计时。当时间到了后,内核担当到恢复hello运行的信号,再举行调理,切换上下文,返回用户模式。
https://i-blog.csdnimg.cn/direct/0e308acc9a1d4c8c8400bc793cce8542.png
图6-3 hello执行过程
6.6 hello的非常与信号处置惩罚

Hello 在执行的过程中,可能会出现处置惩罚器外部 I/O 设备引起的非常,执行指令导致的陷阱、故障和停止。第一种被称为外部非常,常见的偶然钟中断、外部设备的 I/O 中断等。第二种被称为同步非常。陷阱指的是有意的执行指令的效果,故障是非有意的可能被修复的效果,而停止是非故意的不可修复的致命错误。
在发生非常时会产生信号。比方缺页故障会导致 OS 发生 SIGSEGV 信号给用户进程,而用户进程以段错误退出。常见信号种类如下所示。
ID
名称
默认行为

相应事故

2
SIGINT
停止
来自键盘的中断
9
SIGKILL
停止
杀死程序
11
SIGSEGV
停止
无效内存引用
14
SIGALRM
停止
定时器信号
17
SIGCHILD
忽略
子进程克制或停止
(1)正常执行hello程序的效果:当程序执行完成之后(以键入回车竣事),进程回收。
https://i-blog.csdnimg.cn/direct/fe24197bf5e3470499f5cfad13e83513.png
图6-4 正常执行加回车
(2)在程序输出2条info之后按下ctrlz :
当按下 ctrlz之后,shell 父进程收到 SIGSTP 信号,信号处置惩罚函数的逻辑是打印屏幕回显、将hello进程挂起。
通过 ps 命令和jobs我们可以看出 hello 进程没有被回收,此时他的后台job 号是1。
调用fg 1将其调到前台,此时 shell 程序起首打印hello的命令行命令,hello继承运行打印剩下的8条info,之后输入字串,程序竣事,同时进程被回收。
https://i-blog.csdnimg.cn/direct/88508c9a681f409db0243af3f8fee032.png
图6-5 正常执行加Ctrlz  ps jobs
https://i-blog.csdnimg.cn/direct/94b375c24ceb4cd3aa21cb013f34268e.png
图6-6 正常执行加Ctrlz fg
https://i-blog.csdnimg.cn/direct/e21d4fbbc84c481cbacc61950f01bdb4.png
图6-7正常执行加Ctrlz pstree1
prtree效果也如下
https://i-blog.csdnimg.cn/direct/3f5f5e6a488840f9911653b17a309618.png
图6-8 正常执行加Ctrlz pstree2
https://i-blog.csdnimg.cn/direct/200ead1838d743019e2fe1aad2efc524.png
图6-9 正常执行加Ctrlz pstree3

(3)在程序输出2条info之后按下ctrlc :当按下ctrlc之后,shell父进程收到SIGINT信号,信号处置惩罚函数的逻辑是竣事hello,并回收hello进程。https://i-blog.csdnimg.cn/direct/663e43fd882f4a3eb9cf4612f0c67590.png
图6-10 正常执行加Ctrlc
(4)乱按

[*]在程序运行中途乱按的效果:乱按只是将屏幕的输入缓存到 stdin,当getchar的时候读出一个’\n’结尾的字串(作为一次输入),hello竣过后,stdin中的其他字串会当做shell若干条命令行输入。https://i-blog.csdnimg.cn/direct/a2c9fa130687443d93e76d158043dd83.png
图6-11 乱按
6.7本章小结

本章介绍了进程的界说与作用,Shell的处置惩罚流程,通过fork创建进程,execve执行hello程序,以及hello的执行过程和非常信号处置惩罚。


第7章 hello的存储管理

7.1 hello的存储器地址空间

(1)逻辑地址:程序代码中使用的地址(如hello中的printf函数地址),由段选择符和段内偏移量构成,是CPU生成的地址。
(2)线性地址(虚拟地址):通过段式管理将逻辑地址转换得到的32/64位平坦地址空间中的地址(如hello的.text段地址0x8048000)。至于虚拟地址,只关注 CSAPP 讲义中提到的虚拟地址,实际上就是这里的线性地址。
(3)物理地址:实际DRAM芯片上的内存单位地址,由页式管理机制将线性地址转换得到(如hello的指令终极被加载到物理地址0x12345000)。https://i-blog.csdnimg.cn/direct/b4b78e12f8a1484cbb3a19168462c4b5.png
图7-1 地址空间
7.2 Intel逻辑地址到线性地址的变换段式管理


一个逻辑地址由两部分构成,段标识符和段内偏移量。段标识符由 16 位字段构成,前 13 位为索引号。索引号是段形貌符的索引,很多个形貌符,构成了一个数组,叫做段形貌表,可以通过段形貌标识符的前 13 位,在这个表中找到一个具体的段形貌符,这个形貌符就形貌了一个段,每个段形貌符由八个字节构成。base 字段,形貌了段开始的线性地址,一些全局的段形貌符,放在全局段形貌符表中,一些局部的则对应放在局部段形貌符表中。由 T1 字段决定使用哪个。https://i-blog.csdnimg.cn/direct/4fca28aa506f4ea088cd9d76f0d34658.png
图7-2 段式管理
步调
(1)输入逻辑地址,格式:[段选择符(16位): 偏移量(32位)]
段选择符布局:高13位 → 索引号(在GDT/LDT中的位置),TI(1位) → 0=GDT(全局形貌符表),1=LDT(局部形貌符表),RPL(2位) → 请求特权级(用于权限查抄)
(2)选择形貌符表(GDT/LDT),根据 TI位 决定使用哪个表:
TI=0 → 从 GDTR寄存器 获取 GDT基址;TI=1 → 从 LDTR寄存器 获取 LDT基址
(3)查找段形貌符,用 13位索引 在形貌符表中定位 8字节的段形貌符
段形貌符关键字段:
Base(32位) → 段的起始线性地址,Limit(20位) → 段的大小限制,访问权限(12位) → 控制段的读写/执行权限
(4)盘算线性地址,线性地址 = Base(段基址) + 偏移量
偏移量盘算方式(取决于寻址模式):
直接偏移:直接使用给定的偏移量,复杂寻址(如 ):终极效果作为 有用偏移量
7.3 Hello的线性地址到物理地址的变换页式管理

1. 获取页目录基地址
从 CR3寄存器 中获取当前进程的 页目录基地址(取前20位,低12位通常为0)。
2. 查找页目录项(PDE)
线性地址的前 10位(位3122)作为 页目录索引。
盘算PDE的物理地址:
PDE地址 = 页目录基地址 + (页目录索引 × 4)  
读取该地址的 4字节 内容,得到 二级页表基地址(取前20位)。
3. 查找页表项(PTE)
线性地址的中心 10位(位2112)作为 页表索引。
盘算PTE的物理地址:
PTE地址 = 二级页表基地址 + (页表索引 × 4)  
读取该地址的 4字节 内容,得到 物理页框基地址(取前20位)。
4. 盘算终极物理地址
线性地址的最后 12位(位110)作为 页内偏移量。
终极物理地址盘算方式:
物理地址 = 物理页框基地址 + 页内偏移量  
访问该地址即可获取目的数据。

https://i-blog.csdnimg.cn/direct/b18f2d6ed90d42d6bd5c6c7aa323b4cf.png
图7-3 页式管理
7.4 TLB与四级页表支持下的VA到PA的变换


https://i-blog.csdnimg.cn/direct/877714c1eb064a279d65850b09ce1966.png
图7-4 TLB

TLB通过虚拟地址VPN部分举行索引,分为索引(TLBI)与标记(TLBT)两个部分。这样,MMU在读取PTE时会直接通过TLB,如果不掷中再从内存中将PTE复制到TLB。
https://i-blog.csdnimg.cn/direct/2f47fae673be409f8d7e69af36f41346.png
图7-5 页表翻译
根据图74,我们可知Core i7 MMU 如何使用四级页表来将虚拟地址翻译成物理地址。36 位的 VPN 划分为 4 个 9 位的片,每个片对应一个页表的偏移量。CR3寄存器存有 L1 页表的物理地址。VPN1 提供一个到 L1 PET 的偏移量,这个 PTE包罗 L2 页表的基地址。终极颠末L4后,将页的物理地址给PPN。VPN2 提供一个到 L2PET 的偏移量。
7.5 三级Cache支持下的物理内存访问https://i-blog.csdnimg.cn/direct/39cc2c3baeef4dd183ecc268f3f41deb.png

图7-6 物理内存访问
(1)地址转换阶段:
MMU起首在TLB中查找该虚拟地址的转换条目
如果TLB掷中,立即获得对应的物理地址
如果TLB未掷中,需要通过多级页表查询来获取物理地址,并将新的映射关系存入TLB
(2)数据访问阶段:
使用获得的物理地址起首在L1缓存中查找数据
如果L1掷中,数据立即返回给CPU
如果L1未掷中,则依次查询L2、L3缓存
如果全部缓存都未掷中,则需要从主内存加载数据
https://i-blog.csdnimg.cn/direct/a492ed0701e0435c93c0681a1b173c22.png
图7-7 三级Cache支持下的物理内存访问
读取数据分为三种:
(1)直接映射高速缓存
现代CPU接纳多级缓存架构来加快数据访问。对于直接映射高速缓存,每个内存地址唯一对应一个缓存组。当CPU读取数据时,起首通过地址中心的索引位确定目的缓存组,然后查抄该组唯一缓存行的有用位和标记位是否匹配。若匹配成功,则根据地址的块偏移量从缓存行中提取所需数据;若不匹配,则发生缓存未掷中,需要从主存加载包罗目的数据的整个缓存块。新加载的缓存块会替换组中原有内容,同时将请求的数据返回给CPU。这种直接映射机制实现简单,但由于每个内存块只能存储在特定缓存组,容易产生冲突。整个访问过程分为组选择、行匹配和字选择三个步调,确保快速定位和检索数据。
(2)组相联高速缓存

组相联高速缓存通过增长每个组的缓存行数来提升性能。在这种设计中,一个内存地址可以映射到特定组的多个缓存行。当CPU发起读请求时,起首根据地址索引位确定目的组,然后并行查抄组内全部行的标记位。相比直接映射缓存,这种设计显著降低了冲突率。如果找到匹配的有用行,立即返回数据;若未掷中,系统会查抄是否有空闲行:有空行时直接加载数据(冷不掷中),否则需根据替换策略(如LRU或LFU)选择牺牲行举行替换(冲突不掷中)。这种折中方案既缓解了直接映射的冲突问题,又避免了全相联缓存的高本钱,是现代CPU缓存的典型实现方式。
(3)全相联高速缓存
全相联高速缓存接纳完全机动的映射方式,全部缓存行构成一个大的存储池。在这种设计中,内存数据可以存放在恣意缓存行中,地址仅包罗标记位和块偏移量,无需组选择过程。当CPU访问数据时,需要并行比较全部行的标记位来寻找匹配项。这种布局完全消除了组冲突问题,理论上能达到最高掷中率,特别得当小容量关键缓存(如TLB)。但由于需要同时比较全部行的标记位,硬件实现复杂,功耗和本钱较高,通常仅用于特定场景。其查找过程固然简单直接,但规模扩展性较差,因此现代CPU主要接纳折中的组相联方案。
写入数据时,有两种方法来更新w在层次布局中紧接着低一层中的副本,分别是直写和写回:
(1)直写
立即将w的高速缓存块写回到紧挨着的低一层中。固然简单,但是每次写都会引起总线流量。其处置惩罚不掷中的方法是非写分配,即避开高速缓存,直接将这个字写到低一层去。
(2)写回
尽可能地推迟更新,只有当替换算法要驱逐这个更新过的块时,才把它写到紧接着的低一层中。固然能显著地减少总线流量,但是增长了复杂性,必须为每个高速缓存行增长一个额外的修改位,表明是否被修改过。写回处置惩罚不掷中的方法是写分配,加载相应低一层中的块到高速缓存中,然后更新这个高速缓存块,试图使用写的空间局部性,但会导致每次不掷中都会有一个块从低一层传到高速缓存。
7.6 hello进程fork时的内存映射https://i-blog.csdnimg.cn/direct/f79c341e86f54df0a6e699a2e1001ac3.png

图7-8 fork进程
当 fork 函数被 shell 调用时,内核为 hello 进程创建各种数据布局,并分配给它一个唯一的 PID 。为了给 hello 进程创建虚拟内存,它创建了 hello 进程的 mm_struct 、区域布局和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域布局都标记为私有的写时复制。当 fork 在 hello 进程中返回时,hello 进程现在的虚拟内存刚好和调用 fork 时存在的虚拟内存相同。当这两个进程中的任一个后来举行写操纵时,写时复制机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。
当进程调用fork()时,内核会创建子进程的完备内存映射,主要接纳写时复制(CopyOnWrite)机制优化性能。具体过程如下:
1. 内核复制父进程的页表布局,但保持全部物理页面仍由父子进程共享
2. 将全部共享页面标记为只读,并设置COW标志
3. 当任一进程尝试写入共享页面时触发页错误
4. 内核捕捉该错误,分配新的物理页,复制原页内容
5. 更新故障进程的页表指向新页,并恢复可写权限
7.7 hello进程execve时的内存映射https://i-blog.csdnimg.cn/direct/8f1d70038a9848948337c8d23083ef3e.png

图7-9 execve

(1)内存空间重置
清除原进程全部用户空间内存映射
开释相干资源(如动态分配的内存区域)
(2)私有区域构建
代码段:映射hello文件的.text节,设为只读/私有(COW)
数据段:映射.data节,私有可写(COW)
BSS段:创建匿名映射,初始化为0
堆/栈:建立零初始化的匿名映射,动态扩展
(2)共享库处置惩罚
解析动态依赖(如libc.so)
将共享对象映射到共享区域
完成符号重定位
(3)执行情况初始化
设置程序计数器指向入口点(如_start)
初始化寄存器状态
构建参数和情况变量栈帧
7.8 缺页故障与缺页中断处置惩罚

缺页故障是一种常见的故障,当指令引用一个虚拟地址,在 MMU 中查找页表时发现与该地址相对应的物理地址不在内存中,因此必须从磁盘中取出的时候就会发生故障。https://i-blog.csdnimg.cn/direct/2b0acb8c7abd4eaeb07ed51a4e5d789c.png
图7-10 中断处置惩罚过程

缺页中断处置惩罚:缺页处置惩罚程序是系统内核中的代码,选择一个牺牲页面,如果这个牺牲页面被修改过,那么就将它交换出去,换入新的页面并更新页表。当缺页处置惩罚程序返回时,CPU 重新启动引起缺页的指令,这条指令再次发送 VA 到MMU,这次 MMU 就能正常翻译 VA 了。
7.9动态存储分配管理

动态内存管理是程序运行时通过malloc/free等函数在堆空间分配和开释内存的机制,主要包罗以下核心方法与策略:

https://i-blog.csdnimg.cn/direct/384fec635aa94051a39f39005c70aa36.png
图7-11 使用边界标记的堆块
1. 基本管理方法
 显式分配器:由程序员手动控制内存分配/开释(如C的malloc/free)
 隐式分配器:依赖垃圾回收器自动管理(如Java的GC)
 底层系统调用:基于brk/sbrk扩展堆空间,或mmap创建匿名映射区
2. 分配策略
 初次适应(Firstfit):从空闲链表头部开始查找第一个可用块
 最佳适应(Bestfit):遍历选择最小满足需求的空闲块
 最差适应(Worstfit):总是分配最大的空闲块
 分离空闲链表:按大小分类维护多个空闲链表(如glibc的bins)
3. 性能优化技术
 块合并/分割:合并相邻空闲块避免碎片;分割大块进步使用率
 边界标记:在内存块头部/尾部存储元数据(大小、分配状态)
 缓存机制:维护快速分配的小块缓存(如tcache)
 耽误归还:暂不立即将free的内存返还系统,优先复用
4. 典型实现(如glibc)
 对于小请求(<64KB):使用brk扩展的堆空间+分离空闲链表
 对于大请求(≥64KB):直接mmap映射独立内存区
 多线程优化:接纳线程本地缓存(tcache)减少锁竞争
当printf调用malloc时,可能触发上述机制:起首查抄线程本地缓存,若无合适块则搜索对应的空闲链表,必要时扩展堆空间或创建新映射区。现代分配器通过智能策略平衡内存使用率与分配速度,但碎片问题仍需开发者留意。
7.10本章小结

本章综合第六章和第九章,系统性地阐述了现代操纵系统的内存管理机制。起首解析了地址空间的核心概念,包括逻辑地址、线性地址、虚拟地址和物理地址的转换关系及其在x86架构中的具体实现。其次深入探讨了进程内存映射的关键操纵:fork时接纳的写时复制(COW)优化机制,以及execve加载新程序时的完备内存空间重构过程。在非常处置惩罚方面,详细说明白系统对缺页非常的相应流程,包括页表查询、物理页分配等关键步调。最后,分析了动态内存分配管理机制,从malloc/free的底层实现原理到各种分配策略(如初次适应、最佳适应等)的性能特点,全面展示了系统如何高效管理堆内存空间。这些知识点共同构成了现代操纵系统内存管理的完备知识体系。


第8章 hello的IO管理

8.1 Linux的IO设备管理方法

  1. 设备的模子化:文件
Linux系统接纳"一切皆文件"的设计理念,将全部I/O设备抽象为文件情势举行管理。这种模子化处置惩罚体现在三个关键方面:
 统一接口:设备被表示为/dev目录下的特别文件(如/dev/sda表示磁盘)
 文件操纵语义:通过标准文件操纵接口(open/read/write/close)访问设备
 设备分类:
   字符设备(无缓冲,按字节访问,如键盘)
   块设备(带缓冲,按块访问,如磁盘)
   网络设备(特别socket接口)
 2. 设备管理:Unix I/O接口
Linux继承并发展了Unix的I/O管理架构:
核心机制:
 设备文件节点:通过mknod创建立备文件,关联主/次设备号
 VFS抽象层:虚拟文件系统屏蔽不同设备的具体差别
 设备驱动模子:
   内核模块化驱动框架
   支持动态加载/卸载驱动
   统一的设备树(Device Tree)形貌
 3. 现代扩展特性
 udev动态设备管理:相应热插拔事故
 sysfs虚拟文件系统:袒露设备属性(/sys目录)
 cgroups设备控制:限制设备访问权限
这种基于文件的设备管理方法,既保持了接口的简洁性,又通过分层设计实现了强大的扩展本领,是Linux系统可移植性和机动性的重要基础。
8.2 简述Unix IO接口及其函数

 8.2 Unix I/O 接口及其函数详解

 一、Unix I/O 核心设计头脑
1. 统一接口原则
    全部I/O操纵抽象为文件操纵
    设备、管道、socket等均使用文件形貌符(fd)标识
    遵循"打开读写关闭"的标准流程
2. 层次化架构
   mermaid
   graph TD
   A[用户空间] >|系统调用| B
   B > C[字符设备驱动]
   B > D[块设备驱动]
   B > E[网络协议栈]
   
https://i-blog.csdnimg.cn/direct/8f172a93c8d648b8984a1a8aaefd73ab.png
图8-1 层次化架构

 二、关键系统调用函数
1. 文件形貌符管理
   int open(const char path, int flags, mode_t mode);
   // flags: O_RDONLY/O_WRONLY/O_RDWR/O_CREAT等
   // 返回最小的未使用文件形貌符
   int close(int fd);  // 开释文件形貌符
2. 数据传输
   ssize_t read(int fd, void buf, size_t count);
   // 返回实际读取字节数,0表示EOF,1表示错误

   ssize_t write(int fd, const void buf, size_t count);
   // 返回实际写入字节数
  3. 控制函数
   int ioctl(int fd, unsigned long request, ...);
 三、高级I/O机制
1. 文件偏移控制
   off_t lseek(int fd, off_t offset, int whence);
   // whence: SEEK_SET/SEEK_CUR/SEEK_END
  2. 多路复用I/O
     int select(int nfds, fd_set readfds,
             fd_set writefds, fd_set exceptfds,
             struct timeval timeout);
   3. 内存映射
   void mmap(void addr, size_t length, int prot,
              int flags, int fd, off_t offset);
 四、现代扩展
1. 异步I/O (libaio)
2. epoll机制 替代select/poll
3. O_DIRECT标志 绕过缓冲区缓存
这种简洁而强大的I/O接口设计,使得Unix/Linux系统能够以统一的方式处置惩罚各种输入输出操纵,同时保持高度的机动性和效率。
8.3 printf的实现分析

 一、实现层次架构
1. 格式化处置惩罚层
    调用vsprintf()完成格式解析
    处置惩罚%d、%s等格式标记
    生成终极字符串缓冲区
2. 系统调用层
   // 典型调用路径
   printf > vprintf > write > syscall
    通过int 0x80或syscall进入内核态
    触发SYS_write系统调用(x86_64为1号调用)
3. 设备驱动层
    字符设备驱动接收数据
    调用tty_write()写入终端缓冲区

 二、显示渲染流程
1. 字符编码转换
    ASCII码查询字模库(font bitmap)
    获取8x16像素矩阵数据
2. 显存映射
   mermaid
   graph LR
   A[字符数据] > B[显卡驱动]
   B > C
   C > D
   
https://i-blog.csdnimg.cn/direct/3be520d6453149178f0b4bf653d36c60.png
图8-2 显存映射

3. 硬件渲染
    显卡按60Hz频率扫描VRAM
    通过TMDS/DisplayPort协议输出RGB信号
    每个像素点包罗:
     
     struct pixel {
         uint8_t r, g, b;
     };
 三、关键性能优化
1. 行缓冲机制:遇\n才触发实际写入
2. 写时合并:对同一终端的多条写操纵合并处置惩罚
3. 直接渲染:Xorg等图形系统绕过文本缓冲
 四、现代演进
1. 图形终端模拟器(如xterm)增长UTF8支持
2. Wayland协议下的终端重定向
3. GPU加快的笔墨渲染(如FreeType库)
该实现体现了Unix"一切皆文件"的设计哲学,通过分层抽象将简单的格式化输出转化为复杂的硬件交互过程。
8.4 getchar的实现分析


 一、键盘输入处置惩罚流程
1. 硬件中断阶段
    键盘控制器产生IRQ1中断(0x21向量)
    CPU生存现场并跳转至键盘中断服务程序(ISR)
    读取键盘扫描码(0x60端口)
   assembly
   in al, 0x60  ; 获取扫描码
   2. 编码转换层
    扫描码转ASCII:
        // 典型键码映射表
     static char keymap = {
          = 'a',
          = 'b',
          = '1'
     };
    处置惩罚特别键(Shift/CapsLock状态)
3. 缓冲管理
    环形缓冲区存储输入字符
     struct kbd_buf {
       char data;
       int head, tail;
   };
   
 二、getchar函数调用链

1. 库函数层
      // glibc实现简例
   int getchar(void) {
       unsigned char c;
       return (read(0, &c, 1) == 1 ? c : EOF;
   }
 2. 系统调用路径
   getchar > read > sys_read > tty_read
                ↓
           等待缓冲区数据
   
3. 阻塞机制
    若缓冲区空,进程进入TASK_INTERRUPTIBLE状态
    通过wait_queue实现事故唤醒
 三、关键数据布局
1. 终端控制布局
   struct tty_struct {
       struct kbd_buf buf;
       struct wait_queue_head wait;
       spinlock_t lock;
   };
2. 系统调用参数
    // read系统调用参数
   ssize_t read(int fd, void buf, size_t count);
   // fd=0表示标准输入

 四、特别处置惩罚逻辑
1. 行缓冲规则
    遇回车键('\n',ASCII 0x0A)才返回数据
    支持退格键(0x08)处置惩罚
2. 信号处置惩罚
    Ctrl+C产生SIGINT信号
    Ctrl+Z产生SIGTSTP信号
3. 原始模式
   tcgetattr(fd, &oldterm);
   newterm = oldterm;
   newterm.c_lflag &= ~(ICANON | ECHO);
   tcsetattr(fd, TCSANOW, &newterm);
    禁用行缓冲,实时获取按键
 五、性能优化步伐
1. 中断合并:处置惩罚一连按键时耽误相应
2. 批处置惩罚:单次中断处置惩罚多个扫描码
3. 无锁设计:使用RCU机制保护缓冲区
该实现显现了从硬件中断到用户空间函数的完备链路,体现了Unix设备管理的分层设计头脑。
8.5本章小结

本章介绍了Unix I/O,通过LinuxI/O设备管理方法以及Unix I/O接口及函数相识系统级I/O的底层实现机制,并通过对printf和getchar函数的底层解析加深对Unix I/O以及非常中断等的相识。
结论

hello的一生主要颠末一下过程:
1. 程序员通过 I/O 设备在编译器中通过高级语言写下 hello.c,并存储在内存中;
2. 预处置惩罚器通过对头文件、注释的处置惩罚得到修改了的文本文件 hello.i;
3. 编译器翻译成汇编语言得到 hello.s;
4. 汇编器处置惩罚得到可重定位目的文件 hello.o;
5. 链接器将 hello.o 和如 printf.o 的其他可重定位目的文件链接得到可执行目的文件 hello;
6.在 shell 里运行hello程序;
7. fork 创建子进程,shell 调用;
8. 运行程序,调用 execve;
9. 执行指令,为 hello 分配时间片,hello 执行自己的逻辑控制流;
10.三级cache访问内存,将虚拟地址映射成物理地址;
11. 信号、非常控制流,hello 对不同的信号会执行不同操纵;
12. kill hello,回收子进程;
Hello的"生命"完备显现了从源代码到进程执行的盘算机系统全栈协作:编辑器构建文本、工具链完成转化、OS管理资源、硬件执行指令。每个看似简单的输出背后,都是编译系统、操纵系统、硬件架构的精密共同。固然Hello程序看似低级,但它蕴含着进程管理、存储体系、I/O处置惩罚等核心概念。正如文中所言,真正明白系统的大佬终极都会回归对这些基础原理的深刻认知,这正是盘算机教育的"第一性原理"。
现代AI、物联网等复杂应用,其本质仍是Hello程序所体现的基本盘算模子(存储程序、进程抽象)的延伸发展。明白Hello的完备生命周期,就是掌握盘算机系统设计的元认知。在追逐新技术时,不应忘记盘算机系统的基础原理,正是这些看似简单的机制支撑着全部复杂应用的运行。Hello程序的代价不在于它的输出效果,而在于它完备揭示了程序与系统交互的本质过程。


附件

列出全部的中心产物的文件名,并予以说明起作用。
hello.i:hello.c的预处置惩罚文件
hello.s:hello.i的编译文件
hello.o:hello.s的汇编文件
helloo.elf:hello.o文件的ELF格式
hello.elf:hello文件的ELF格式
hello:可执行文件
helloo.objdmp:Hello.o 的反汇编代码
hello.objdmp:Hello 的反汇编代码


参考文献

 林来兴. 空间控制技术. 北京:中国宇航出版社,1992:2542.
 辛希孟. 信息技术与信息服务国际研讨会论文集:A集. 北京:中国科学出版社,1999.
 赵耀东. 新期间的工业工程师. 台北:天下文化出版社,1998 . http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).
 谌颖. 空间交会控制理论与方法研究. 哈尔滨:哈尔滨工业大学,1992:813.
  KANAMORI H. Shaking Without Quaking. Science,1998,279(5359):20632064.
  CHRISTINE M. Plant Physiology: Plant Biology in the Genome Era. Science,1998,281:331332. http://www.sciencemag.org/cgi/ collection/anatmorp.


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 程序人生Hello’s P2P