程序人生-Hello’s P2P(2025)

打印 上一主题 下一主题

主题 1798|帖子 1798|积分 5394



计算机系统


大作业



题     目  程序人生-Hellos P2P 
专       业   计算机与电子通信                     
学     号   2023111735                     
班   级   23L0509                     
学       生   杨祥锐               
指 导 教 师    史先俊                 





计算机科学与技术学院

20255

摘  要

本文从hello.c开始,使用计算机系统课上及教材上的知识详细记录了其在预处置惩罚、编译、汇编、链接过程时的一点点变化(P2P),并先容了hello的历程管理和存储管理。报告了一个简单的hello程序从被创建到被回收的历程,它在Linux操作系统的带领下完成了平凡但不平庸的生命历程。

关键词:计算机系统;P2P;Hello;历程;存储                          
(择要0分,缺失-1分,根据内容出色称都酌情加分0-1分







目  录


第1章 概述

1.1 Hello简介

1.2 环境与工具

1.3 中心效果

1.4 本章小结

第2章 预处置惩罚

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

2.2在Ubuntu下预处置惩罚的下令

2.3 Hello的预处置惩罚效果剖析

2.4 本章小结

第3章 编译

3.1 编译的概念与作用

3.2 在Ubuntu下编译的下令

3.3 Hello的编译效果剖析

3.4 本章小结

第4章 汇编

4.1 汇编的概念与作用

4.2 在Ubuntu下汇编的下令

4.3 可重定位目标elf格式

4.4 Hello.o的效果剖析

4.5 本章小结

第5章 链接

5.1 链接的概念与作用

5.2 在Ubuntu下链接的下令

5.3 可实行目标文件hello的格式

5.4 hello的虚拟地址空间

5.5 链接的重定位过程分析

5.6 hello的实行流程

5.7 Hello的动态链接分析

5.8 本章小结

第6章 hello历程管理

6.1 历程的概念与作用

6.2 简述壳Shell-bash的作用与处置惩罚流程

6.3 Hello的fork历程创建过程

6.4 Hello的execve过程

6.5 Hello的历程实行

6.6 hello的异常与信号处置惩罚

6.7本章小结

第7章 hello的存储管理

7.1 hello的存储器地址空间

7.2 Intel逻辑地址到线性地址的变更-段式管理

7.3 Hello的线性地址到物理地址的变更-页式管理

7.4 TLB与四级页表支持下的VA到PA的变更

7.5 三级Cache支持下的物理内存访问

7.6 hello历程fork时的内存映射

7.7 hello历程execve时的内存映射

7.8 缺页故障与缺页停止处置惩罚

7.9动态存储分配管理

7.10本章小结

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

8.2 简述Unix IO接口及其函数

8.3 printf的实现分析

8.4 getchar的实现分析

8.5本章小结

结论

附件

参考文献



第1章 概述

1.1 Hello简介

1.1.1 P2P
P2P,即From Program to Process,指的是从程序到历程的过程。源程序hello.c颠末预处置惩罚器得到文本文件hello.i;接着编译器(ccl)会将hello.i翻译成文本文本hello.s,它包含了一个汇编语言程序;汇编器(as)将hello.s翻译成呆板语言指令,并打包成可冲定位目标程序存在目标文件hello.o中;;链接器(ld)将hello.o和运行时库归并,生成可实行文件hello。在shell中输入./hello后,操作系统通过fork函数为hello创建一个历程,再通过execve实行hello。

图1.从源文件到目标文件的转化

1.1.2 020
020,即zero to zero,是一种“从无到有再到无”的闭环。第一个0是指开始时磁盘上的可实行文件尚未被加载,CPU、内存、I/O设备等处于空闲状态;在0和0之间,历程被创建后会通过 execve() 加载到虚拟内存后,CPU 开始实行其指令直至 调用exit() 或信号终止;第二个0是指历程退出时,父历程会回收该子历程,CPU、内存、I/O设备等再次处于空闲状态。
1.2 环境与工具

硬件环境:
CPU:13th Gen Intel(R) Core(TM) i9-13900HX,RAM:16GB
软件环境:
Windows11 64位
Linux version 6.8.0-59-generic
开辟与调试工具:
Visual Studio 2022、Ubuntu 12.3.0-1ubuntu1~22.04、gdb、edb、gcc、vim


1.3 中心效果

hello.c
hello 的C源文件
hello.i
hello.c颠末预处置惩罚后的预编译文本文件
hello.s
hello.i颠末编译得到的汇编语言文本文件
hello.o
hello.s颠末汇编得到的呆板语言二进制文件
hello_elf.txt
hello.o的ELF格式文本文件
hello_dis.txt
hello.o反汇编的文本文件
hello_elf_1.txt
hello的ELF格式文本文件
hello_dis1.txt
hello的反汇编文本文件
hello
hello.o颠末链接得到的可实行目标文件

列出你为编写本论文,生成的中心效果文件的名字,文件的作用等。
1.4 本章小结

本章起首以Hello程序为例,先容了P2P、O2O的概念,随后提供了大作业中使用的环境及工具,最后以表格形式先容了为编写本论文生成的所有中心文件。

(第1章0.5分)



第2章 预处置惩罚

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

预处置惩罚的概念:预处置惩罚是指预处置惩罚器(ccl)根据以字符#开头的下令,修改原始的C程序(主要完成宏睁开、头文件包含和条件编译等操作),得到修改后的源程序并提供给编译器。
预处置惩罚的作用:在预处置惩罚阶段预处置惩罚的内容包括:扫描以#开头的指令,将宏界说更换为对应文本;睁开 #include 指定的头文件内容并“拼接”到源文件中;根据 #if/#ifdef 等条件选择性地保留或剔除代码;移除解释和实行行控制。完成这些可以令后续程序的修改、调试、移植等更加便利。

2.2在Ubuntu下预处置惩罚的下令


在Ubantu中可以通过gcc -E hello.c -o hello.i下令来举行预处置惩罚,如图所示:

图2-2-1 预处置惩罚下令

图2-2-2预处置惩罚效果


2.3 Hello的预处置惩罚效果剖析

颠末预处置惩罚后,得到的hello.i文件还是文本文件,但已经从原本的24行变成了3092行,在hello.i文件的3079到3092行我们可以瞥见c语言源代码(如图2-3-1所示),它们并没有被修改只是删去了解释的部分。


图2-3-1 hello.i中的源代码部分

文件中的其他部分主要是把stdio.h,unistd.h,stdlib.h这三个我们引用的头文件举行睁开。起首cpp会到Linux系统的环境变量下探求头文件,我们根据hello.i提供的地址找到了引用的三个头文件(如图2-3-2、图2-3-3所示)。

图2-3-2  hello.i中给出的头文件地址

图2-3-3 hello程序引用的头文件

仔细阅读头文件睁开的部分,我们可以发现头文件中的函数以及对指针、类型界说的声明等(如图2-3-4、图2-3-5所示)。

图2-3-4 函数声明示例

图2-3-5 类型界说信息

2.4 本章小结


本章主要围绕预处置惩罚睁开,使我预处置惩罚的概念、作用、下令、剖析举行了深入的理解。预处置惩罚是举行编译器编译前的一步,通过对hello.i的分析可以知道在这个过程中主要包括宏睁开、文件包含、条件编译、解释移除,以便后续的编译等流程。
(第2章0.5分)


第3章 编译


3.1 编译的概念与作用

编译的概念:编译是指编译器将预处置惩罚后的源程序(hello.i)翻译成汇编语言程序(hello.s)的过程。
编译的作用:编译的主要步调包括:编译器对预处置惩罚输出的纯 C 源文本举行语法和语义检查,即确认所有的指令是否都符合语法规则;应用各类优化,即通过调整使代码保持原功能但实行效率更高,常见优化有常量折叠、死代码消除、寄存器分配等;生成与目标处置惩罚器架构紧密对应的汇编指令。通过编译,可以让源代码更加靠近呆板语言。
        

3.2 在Ubuntu下编译的下令

Ubantu下使用gcc -S hello.i -o hello.s下令举行编译。

图3-2 编译下令

3.3 Hello的编译效果剖析

3.3.1数据
3.3.1.1常量
(1)字符串常量
所有的字符串字面量都被收进只读数据段(.rodata)并贴上标签(.LC0、.LC1)。运行时用指令如 leaq .LC1(%rip), %rax 即可拿到它们的地址。

图3-3-1 汇编代码中的字符串常量表现

图3-3-2 源代码中的字符串常量

可见在汇编代码中汉字以utf-8的格式举行编码,每个汉字占3个字节。
(2)整形常量
源代码的整形常量是数字,在汇编代码中可直接表现。

图3-3-3整型常量

3.3.1.2 变量
(1)函数形参

图3-3-4 汇编代码中形参

函数形参 int argc、char *argv[] 放在寄存器%edi、%rsi中,函数调用开始时储存在栈中,当函数返回所占空间被释放。
(2)局部变量
局部变量i被分配在栈上

图3-3-5 局部变量

3.3.2 赋值
i=0,局部变量初始化赋值

图3-3-6 赋值

3.3.3 算术操作
局部变量i在循环时的自增,每次循环都通过addl指令实现+1。

图3-3-7 算术操作

3.3.4关系操作
源代码中有两处关系操作
第一处:cmpl指令代表比力,根据下面可得知如果相等则跳转,不相等则继承实行下一行。

图3-3-8 argc与5的比力

第二处:局部变量i与9相比,i<=9则跳转到L2(循环),i>9则跳出循环

图3-3-9  i与9的比力

3.3.5 数组/指针/结构操作
源代码中char *argv[]是字符串指针数组,对于指针char*大小为8字节,因此每个参数字符串地址相差8个字节,以-32(%rbp)为基址,接纳基址-变址寻址法访问指针数组。

图3-3-10 字符串指针数组

3.3.6控制转移
编译器使用jump指令举行跳转,实际该指令有多种形式,本文件中有je、jmp、jle三种,它们会根据前面的表达式跳转到相应的地址。

图3-3-11 控制转移

3.3.7 函数操作
(1)main函数:
参数通报:源代码中main函数有参数通报:int argc,char *argv[],在汇编代码中表现为 movl  %edi, -20(%rbp) ,movq  %rsi, -32(%rbp),可知第一个参数和第二个参数分别通过寄存器EDI和寄存器RSI通报,两个参数被写入栈。
函数调用:由系统函数调用
函数返回:通过EAX寄存器返回

图3-3-12 main函数的函数返回

(2)printf函数
参数通报:需要输出的字符串及格式
函数调用:通过call+函数地址调用
参数返回:返回打印的字符数

图3-3-13 第一次printf

通过汇编代码得知,编译器在第一次实际上调用的是puts函数,这是因为此次调用printf函数不需要通报额外的参数,因此编译器做了一点等价的更换。

图3-3-14第二次printf

第二次调用则是调用了四个参数,所以不能用puts函数直接代替,查察汇编代码可得知rcx、rdx、rsi三个寄存器通报了字符串的首地址,而rax通报了标签LC1对应的字符串的首地址。
(3)exit函数
参数通报:退出码,即存在寄存器edi的数字“1”
函数调用:call exit@PLT

图3-3-15 exit函数

(4)sleep函数
参数通报:将atoi函数的返回值存入寄存器edi中作为sleep函数的参数,代表就寝秒数
函数调用:sleep  @PLT
函数返回:返回值被忽略(返回的是实际休眠时间)

图3-3-16 sleep函数

(5)atoi函数
参数通报:字符串地址,即存入寄存器rdi的argv[4]
函数调用:call  atoi@PLT
函数返回:转换的效果,存入寄存器eax中

图3-3-17 atoi函数

(6)getchar函数
函数调用:call  getchar@PLT

图3-3-18 getchar函数

此函数不需要参数,因此不需要寄存器来传参

3.4 本章小结

通过本章的实践与思考,我对于编译的概念与作用有了认知,在一次次查察分析hello.s该汇编文件的过程中,理解了C语言的各种数据与操作是如安在汇编代码中表现的,尤其感受了函数传参、调用以及在程序中的跳转语句带来的头脑碰撞,使我对编译的理解更加深刻。
(第3章2分)


第4章 汇编


4.1 汇编的概念与作用

汇编的概念:汇编是指汇编器将汇编语言翻译成呆板语言的过程。在这个过程中会将呆板语言指令打包成可重定位目标程序的格式并生存在目标文件hello.o中,hello.o文件是一个二进制文件。
汇编的作用:将汇编代码翻译为计算机可以直接实行的二进制指令,组织程序的代码段、数据段等,为链接阶段提供标准的目标文件格式。
4.2 在Ubuntu下汇编的下令

使用gcc -c hello.s -o hello.o下令举行汇编

图4-1汇编下令


4.3 可重定位目标elf格式

分析hello.o的ELF格式,用readelf等列出其各节的根本信息,特别是重定位项目分析。
4.3.1 readelf
通过readelf -a hello.o > hello_elf.txt下令将hello.o的ELF格式转化为txt文档便于分析

图4-2 readelf


4.3.2 ELF头

ELF头以一个16字节的序列开始,这个序列形貌了生成该文件的系统的字的大小和字节序次。剩下的部分包含帮助链接器语法分析息争释目标文件的信息。其中包括ELF头的大小(64字节),目标文件的类型(可重定位目标文件),呆板类型(AMD X86-64),节头部表的文件偏移,以及节区头数目(14个)和节区头大小(64字节)。


图4-3 ELF头

4.3.3节头

该部分形貌了各节的根本信息,包括节的名称、类型、大小等

图4-4 节头

4.3.4 重定位节

在编译生成的目标文件阶段,代码和数据的地址通常是相对的,因此需要在链接时或运行时,将这些相对地址转化为实际的物理或虚拟地址,以便程序可以或许精确运行,这就是重定位。重定位节用于存放重定位表,记录目标代码中哪些位置存在相对地址,需要在链接或运行时修正。


图4-5 重定位节

4.3.5符号表
可以只包含一个自界说函数 main,别的均为外部符号(UND),需链接 libc 时剖析,如exit、sleep函数等。

图4-6符号表

4.4 Hello.o的效果剖析


图4-7hello.o的反汇编文件

在目标文件中,呆板指令由一系列字节序列构成:前缀、操作码字节、ModR/M 字节(要操作的寄存器/内存位置)、位移(disp8/disp32)或立即数(imm8/imm32)字段(用于详细的常量或偏移量)。CPU 可以直接辨认这种呆板指令并实行相应的操作。
hello.s与hello.o的反汇编代码之间有着一一对应的关系,也就是说汇编语言中的每一条伪指令在呆板码里都有对应的字节编码。好比 mov %rsp,%rbp 对应的就是前缀 48、操作码 89 和 ModR/M E5;而 subq $0x20,%rsp 则是前缀 48、操作码 83、ModR/M EC 和立即数 20。
固然比力分析之下二者也有一些差别:
操作数:hello.s中的操作数均为十进制,而反汇编文件呆板码中的操作数被转换成十六进制
分支跳转:分支跳转在 hello.s 中写作 je .L2、jmp .L3、jle .L4 等,以标签 .L2、.L3、.L4 为目标,完全是符号化、可读的。汇编器会根据标签相对当前位置自动计算偏移,帮你选择短跳还是长跳。而在反汇编文件中看到的是真实的呆板码,没有标签,只有操作码字节和紧跟的相对偏移字节。
函数调用:函数调用方面,在 hello.s 中使用“call 函数名@PLT”的方式,清晰标明“调用外部函数,通过 PLT”来举行函数调用。并且省略了立即数参数和PLT 地址,知识由汇编器留一个占位并在重定位节里记录要做一个 PLT32 重定位的位置。在反汇编文件中的呆板码都先表现为零位移的占位形式,每条 E8(相当于CALL)指令后面的 4 字节立即数都为 00 00 00 00,这是汇编时留的“待填充值”。真正要调用哪个函数、跳到 PLT 表哪里,在 .rela.text 里有重定位条目,链接时才把这些零填成指向 PLT 条目标实际偏移。




4.5 本章小结


在本章中先容了汇编的概念与作用,对可重定位目标文件hello.o的ELF格式举行了分析,将hello.o的反汇编代码与hello.s之间比力分析,展现了二者之间的联系与差别。在汇编阶段会把汇编语言翻译成呆板语言,呆板语言是计算机可以直接实行的二进制指令,也是链接阶段需要的目标文件形式。在hello.o的ELF格式中包括ELF头、节头、重定位节、符号表,可以从中获取各节的根本信息、重定位表等。通过本章学习,我对汇编、重定位、呆板码有了更深刻的理解。
(第4章1分)


5链接


5.1 链接的概念与作用

链接的概念:链接是将各种代码和数据片段收集并组合成为了一个单一文件的过程,这个文件可被加载(复制)到内存并实行。链接包括静态链接和加载时共享库的动态链接。
连接的作用:在链接过程中链接器会剖析目标文件中的符号表,将所有未界说的外部符号举行匹配,并依照重定位表修正用于函数调用而引用的地址占位,使呆板指令中的相对偏移和绝对地址指向精确的目标地址。同时,链接器还负责按照目标平台的内存布局规范,归并并重新定位test、data等各节区,为可实行文件生成程序头表,以便操作系统能精确地将程序映射到内存中。
5.2 在Ubuntu下链接的下令

使用下令:ld -o hello -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/x86_64-linux-gnu/crtn.o /usr/lib/x86_64-linux-gnu/libc.so hello.o

图5-1 链接下令

5.3 可实行文件hello的格式

使用下令:readelf -a hello > hello_elf_1.txt
将hello的ELF格式转化为txt文档便于分析

图5-2 hello的ELF

5.3.1节头
节头中表现了各段的起始地址,大小等信息,比方data段的大小是0x04,地址是0x404048。

图5-3 节头

5.3.2 程序头

图5-4 程序头

程序头界说了加载器怎样将文件映射到内存段,包括哪些部分可实行、哪些部分可写、哪些部分是只读。可实行文件被分为十二个段,我们可以通过程序头获得相关信息如偏移、虚拟地址等。以PHDR段为例,有读权限,开始于内存地址0x000040处,总共的内存大小为0x2a0字节。
5.4 hello的虚拟地址空间

使用gdb加载hello。   

图5-5 gdb

输入info files指令界面如图:

图5-5 gdb加载界面

如图,可以看出不同节的首尾地址,我们以.data为例,始尾地址为0x404048和0x40404c,二者之差为0x04,与hello的ELF格式中一致。

图5-6 对比举例

输入i proc map 指令

图5-7 i proc map

如图可以看到各段信息,通过于程序头的信息对比也有不同,好比第一个LOAD段文件偏移 0x0 大小 0x5f0,但仅映射 0x1000 对齐页,这是因为当动态链接器用 mmap 把第一个 LOAD 段映射到历程地址空间时,会遵循页对齐规则,而不是按节区或文件中的精确大小映射, 段在文件中的实际大小是 0x5f0(1520 B),但文件映射必须覆盖完整的页。因此,内核会把从偏移 0x0 开始、长度至少到下一个页面界限(0x1000)的整个区域映射进来 。  

5.5 链接的重定位过程分析

使用下令:objdump -d -r hello > hello_dis1.txt

图5-8 反汇编下令


图5-9 反汇编效果

链接的过程:链接过程就是把目标文件( hello.o)与运行时的支持库(CRT、libc 等)以及重定位信息归并,按段对齐打包,同时剖析和修正所有对外部符号和常量的地址占位,生成一个包含入口点、运行时启动/终结逻辑、PLT/GOT 支持、动态链接表、程序头表的完整可实行文件。这个过程在反汇编里表现为——占位变为真实偏移;PLT/GOT 代码段与寄存器间接跳转逻辑出现;以及新增 _start、.init、.fini 等多段启动和析构代码等。
(1)占位变为真实偏移

图5-10 表现1

同一条指令已经被重定位为真实的相对偏移,这也是举行重定位的过程的一部分。
(2)代码段与寄存器间接跳转逻辑,这也是举行重定位的过程的一部分。

图5-11 表现2

链接后可实行文件新增了完整的 PLT 段和 GOT 间接跳转,比方在 .plt 段,这些间接跳转在 hello.o 中并不存在。
(3)新增启动和析构代码

图5-12 表现3


5.6 hello的实行流程

使用edb,查察symbol

图5-13 edb效果

子程序名
地址(16进制)
hello! init
0x401000
hello!puts@plt
0x401030
hello! printf chk@plt
0x401050
hello!exit@plt
0x401060
hello!sleep@plt
0x401070
hello!getc@plt
0x4010f0
hello! start
0x4010a0
hello! fini
0x401260

5.7 Hello的动态链接分析

 动态链接通过全局偏移量表(GOT)和过程链接表(PLT)的协同机制实现对外部库函数的地址剖析。对于程序内部的变量,可通过代码段与数据段的相对位置固定原则直接计算地址;而对外部库函数的调用,则需依赖 PLT 和 GOT 的协作机制。PLT 中初始存放的是一组跳转代码,其目标指向 GOT 中对应的条目。初次调用库函数时,GOT 条目默认指向 PLT 中的第二条指令,此时程序会将函数标识符和重定位表地址压入栈中,并跳转至 PLT[0] 实行公共桩代码,进而调用动态链接器。链接器通过栈中的信息剖析函数实际地址,回填至 GOT 相应条目,再将控制权转移给目标函数。此后再次调用同一函数时,PLT 的跳转指令会直接通过 GOT 中已更新的地址实现高效间接跳转,无需重复剖析。
在ELF文件中找到got.plt的位置:

结合edb查察动态链接前:

动态链接之后:

可以看出调用了dl_init之后字节改变了。
动态链接通过 PLT(过程链接表)和 GOT(全局偏移量表)协同实现延迟绑定:程序初次调用共享库函数时,从 PLT 跳到 GOT 条目(初值指向 PLT 本身),再经 PLT[0] 进入动态链接器。链接器剖析出函数实际地址后写入 GOT,随后跳回实行目标函数。此后再调用时,PLT 直接从 GOT 读取真实地址跳转,无需再次进入链接器。全局变量则直接使用数据段内的固定偏移。如许既包管首调用时精确剖析共享库,又使后续调用仅需一次间接跳转。



5.8 本章小结

本章起首阐述了链接的根本概念和作用,展示了使用下令链接生成hello可实行文件,观察了hello文件ELF格式下的内容,使用edb观察了hello文件的虚拟地址空间使用环境,最后以hello程序为例对重定位过程、实行过程和动态链接举行分析。

(第5章1分)



6hello历程管理


6.1 历程的概念与作用

历程的概念:历程的经典界说是一个实行中程序的实例,系统的每个程序都运行在某个历程的上下文。上下文是由程序精确运行所需的状态组成的,这个状态包括存放在内存里的程序的代码和数据,它的栈,通用目标寄存器的内容,程序计数器,环境变量以及打开文件形貌符的聚集。

历程的作用:历程是资源管理的最小单位,具有独立的虚拟地址空间、代码、数据、堆、栈、文件形貌符等。通过历程,操作系统可以实现对多个程序的并发运行,同时保障历程之间的隔离与安全。

6.2 简述壳Shell-bash的作用与处置惩罚流程

Shell-bash的作用:Shell是操作系统提供的一种下令行解释器,是用户与操作系统内核交互的桥梁。用户通过 Shell 输入下令,Shell 负责剖析下令,查找并启动程序,将用户的需求转化为操作系统可以实行的系统调用。Shell 同时提供了脚本编程本事、环境变量管理、管道、重定向等功能,支持用户自动化管理系统,举行批量任务处置惩罚,控制历程的实行和管理。
处置惩罚流程:
(1)读取下令
Shell 等待并读取用户从终端输入的下令行。
(2)下令剖析与预处置惩罚
内容包括:使用空格、Tab、换行等分隔符将输入拆分为 tokens;处 理别名更换、~ 用户目录更换、环境变量($VAR)更换;处置惩罚下令替 换与算术表达式;再次分割下令,举行通配符睁开;删除解释,完成 最终下令与参数的确定。

(3)下令查找与分类

优先检查是否为 Shell 内置下令,如果是内置下令,直接实行。如果 是外部程序,Shell 依据环境变量查找对应的可实行文件路径。

(4)创建历程并实行

起首Shell 调用 fork() 创建一个新的子历程,然后子历程调用execve() 实行目标程序(如 hello),乐成后历程映像被更换。父历程会判断命 令是前台还是后台实行,若是前台Shell 使用 wait() 等待子历程竣事, 若是后台Shell 不阻塞,继承等待下一条用户输入,同时吸收SIGCHLD 处置惩罚子历程退出。

(5)输入输出重定向与信号处置惩罚
在实行前,Shell会设置好输入输出重定向,并且保持相应用户键盘信 号(如 Ctrl+C, Ctrl+Z),举行相应处置惩罚,如终止、挂起子历程。
(6)循环等待下一条下令
当当前下令实行竣事,Shell 会清算资源并返回提示符,预备吸收下 一条输入。
6.3 Hello的fork历程创建过程

当用户在 Shell 输入下令 ./hello 后,Shell 会辨认该下令不是内置指令,因此会调用 fork()创建一个新的子历程。在该过程中,操作系统会为子历程分配一个新的历程号(PID),并复制父历程的用户级虚拟地址空间,包括代码段、数据段等。父子历程还会共享打开的文件形貌符,因此子历程可以继承读写父历程打开的文件。
只管虚拟地址空间雷同,但父子历程的物理页面是相互独立的。fork() 在父历程中返回子历程的 PID,而在子历程中返回 0,因此父子历程可以基于返回值区分各自实行的逻辑分支。完成 fork() 后,子历程会进一步调用 execve() 更换自身映像为 hello 程序,而父历程会判断下令的前后台类型,若为前台则调用 wait() 等待子历程竣事。
6.4 Hello的execve过程

在由 Shell 创建的子历程中,子历程会调用 execve 函数加载并运行 hello 可实行文件,同时通报参数列表 argv 和环境变量列表 envp。execve 不会创建新的历程,而是用 hello 程序覆盖当前子历程的地址空间,保持 PID 不变,并继承所有已打开的文件形貌符。
execve 的实行流程包括:
1. 删除当进步程已有的用户空间,包括代码段、数据段、堆、栈等;
2. 映射 hello 程序的私有区域,如代码段、数据段、.bss、堆和新的栈区域,这些区域接纳写时复制;
3. 映射共享区域,如共享库(比方 libc.so);
4. 初始化用户栈,将参数 argc、argv、envp 等通报到新程序的 main 函数;
5. 最后设置程序计数器(PC)指向新程序的入口 _start,从而转交控制权实行 hello 程序;execve 只有在加载失败时才会返回到调用的 Shell 子历程,而正常环境下 execve 加载乐成后,控制权不再返回,而是直接进入新程序的实行流程。
6.5 Hello的历程实行

在 Linux 系统中,Hello的历程实行离不开历程调治、上下文切换、用户态与内核态的切换等机制的和谐配合。
6.5.1逻辑控制流:
即使在系统中通常有许多其他程序在运行,历程也可以向每个程序提供一种假象,好像它在独占地使用处置惩罚器。如果想用调试器单步实行程序,我们会看到一系列的程序计数器(PC)的值,这些值唯一地对应于包含在程序的可实行目标文件中的指令,或是包含在运行时动态链接到程序的共享对象中的指令。这个PC 值的序列叫做逻辑控制流,大概简称逻辑流。

图6-1 逻辑控制流

6.5.2时间片与时间分片:
一个逻辑流的实行在时间上与另一个流重叠,称为并发流。多个流并发地实行的一般征象被称为并发。一个历程和其他历程轮流运行的概念称为多任务。一个历程实行它的控制流的一部分的每一时间段叫做时间片。因此,多任务也叫做时间分片。
6.5.3上下文:
上下文就是内核重新启动一个被抢占的历程所需要恢复的原来的状态,由寄存器、程序计数器、用户栈、内核栈和内核数据结构等构成。上下文转换包括1)生存以进步程的上下文2)恢复新恢复历程被生存的上下文,3)将控制通报给这个新恢复的历程。

图6-2 上下文切换

6.3.4调治:
在历程实行的某些时刻,内核可以决定抢占当进步程,并重新开始一个先前被抢占的历程,这种决策称为调治,是由内核中的调治器代码处置惩罚的。当内核选择一个新的历程运行,我们说内核调治了这个历程。在内核调治了一个新的历程运行了之后,它就抢占了当进步程,并使用上下文切换机制来将控制转移到新的历程。
比方Hello 历程在实行过程中,由于调用了如 sleep、read 等系统调用,历程会因等待 IO 等事件而进入阻塞状态,此时内核调治器会实行调治决策,将 Hello 历程挂起,生存其完整的上下文(包括寄存器、PC、用户栈、内核栈、内核数据结构等),并从就绪队列中选择下一个历程恢复其上下文,交由 CPU 继承实行。
当等待事件完成或休眠竣事,调治器再次将 Hello 历程唤醒,恢复其上下文继承实行。
    6.3.5 用户态与核心态转换
为了包管系统安全,需要限制应用程序所能访问的地址空间范围。因而存在用户态与核心态的划分,处于核心态的历程可以实行指令集中的任何指令,并且可以访问系统中的任何内存位置。Linux 通过处置惩罚器的模式位区分用户态与内核态,历程默认运行在用户态,只有在发生系统调用、停止或故障时,才会由硬件将当进步程切换到内核态,内核先在内核态实行相关服务或异常处置惩罚,之后再将历程切换回用户态继承实行 Hello 程序,这种机制确保用户历程无法直接访问内核的核心代码和数据,防止系统被破坏,提升了系统的稳定性与安全性。

6.6 hello的异常与信号处置惩罚

6.6.1异常
异常就是控制流中的突变,用来相应处置惩罚器状态中的某些变化。一共有四种异常:
(1)停止
停止是异步发生的,是来自处置惩罚器外部的I/O设备的信号的效果。硬件停止不是由任何一条专门的指令造成的,从这个意义上来说它是异步的。停止处置惩罚流程如图:

图6-3 停止处置惩罚

(2)陷阱和系统调用
陷阱是有意的异常,是实行一条指令的效果。就像停止处置惩罚程序一样,陷阱处置惩罚程序将控制返回到下一条指令。陷阱最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用。陷阱处置惩罚如图:

图6-4 陷阱处置惩罚

(3)故障
故障由错误环境引起,它可能可以或许被故障处置惩罚程序修正。当故障发生时, 处置惩罚器将控制转移给故障处置惩罚程序。如果处置惩罚程序可以或许修正这个错误环境,它就将控制返回到引起故障的指令,从而重新实行它。否则,处置惩罚程序返回到内核中的abort 例程,例程会终止引起故障的应用程序。
故障处置惩罚如图:

图6-5 故障处置惩罚

(4)终止
终止是不可恢复的致命错误造成的效果,通常是一些硬件错误。终止处置惩罚如图:

图6-6 终止处置惩罚

6.6.2信号
一个信号就是一条小消息,它关照历程系统中发生了一个某种类型的事件。每种信号类型都对应于某种系统事件。
Linux信号的种类如图(30种):

图6-7 Linux信号

6.6.3异常与信号处置惩罚
(1)不绝乱按,包括回车
在程序运行时我们输入的东西不会阻止程序的正常运行,只是显示在屏幕上,shell会正常输出本应输出的,但输出十次也就是运行完毕后shell会把我们随意输的乱码当做指令去实行。
效果如图所示:

图6-8 不绝乱按效果

(2)Ctrl-Z
按下Ctrl+Z之后,历程会收到一个SIGSTP 信号,使得当前的hello历程被挂起。用ps指令查察其历程PID,可以发现hello的PID是6767;再用jobs查察此时hello的后台 job号是1,调用指令fg %1将其调回前台继承完成按Ctrl+Z时还没完成的任务。

效果如图:

图6-9 ctrl z效果


图6-10 ps、jobs、fg

通过kill可将挂起的历程终止,如图:

图6-11 kill

(3)Ctrl-C
按下Ctrl+Z之后,历程会收到一个SIGINT信号,使得当前的hello历程被终止,输入jobs也看不到历程。
效果如图:

图6-12 Ctrl-C


6.7本章小结

本章先容了历程的概念与作用,并以hello历程为例阐述了shell对hello的处置惩罚全过程,包括创建、运行、加载、异常和信号的处置惩罚管理。在hello运行的过程中,内核对其举行历程管理,决定何时举行历程调治,在吸收到不同的异常、信号时,还要实时地举行对应的处置惩罚。
(第6章2分)


7hello的存储管理


7.1 hello的存储器地址空间

逻辑地址:逻辑地址是CPU 在程序实行时生成的“段:偏移”格式的地址,代表了程序在编译或链接时需要引用的代码和数据的地址。如在 hello.o 的反汇编里的 mov %edi, -0x14(%rbp) 等指令所用的偏移,都是基于逻辑地址计算。
线性地址:线性地址是段式管理后的地址,是逻辑地址颠末段基址相加得到的效果。线性地址空间是连续非负整数地址的有序聚集。
虚拟地址:开启分页后线性地址即为历程的虚拟地址空间中的地址,
物理地址:物理地址是MMU 将线性/虚拟地址映射到的实际 DRAM 地址,也就是内存单位的绝对地址,用于实际的内存读写操作。
7.2 Intel逻辑地址到线性地址的变更-段式管理

在 x86 架构中,每个内存引用起首以逻辑地址的形式出现在程序指令或数据访问中。逻辑地址由一个段选择符和一个段内偏移组成,段选择符指示该地址属于哪个段(如代码段、数据段、堆栈段等),段内偏移指定在该段内的详细位置 。 当处置惩罚器处于掩护模式下,逻辑地址起首由分段单位处置惩罚,硬件从段寄存器(CS/DS/SS/ES/FS/GS)中的段选择符取出相应的段形貌符,该形貌符生存在全局形貌符表(GDT)或本地形貌符表(LDT)中,其内容包含段的基址(Base)、段限长(Limit)和访问权限等信息。处置惩罚器会验证偏移是否在段限长内且访问类型(读/写/实行)是否合法,若检查失败则抛出掩护异常,否则将段基址与偏移相加产生线性地址,也称为“虚拟地址”。
在实模式下,段寄存器直接存放段值,CPU 读取段寄存器后左移 4 位与偏移相加,得到 20 位的线性地址,该地址同时也是物理地址;在今世 64 位模式中,为简化管理通常接纳 Flat 段模子,将所有段基址设为 0,从而逻辑地址可以直接作为线性地址使用,无段界限检查 。

图7-1 段式管理表示图

7.3 Hello的线性地址到物理地址的变更-页式管理

操作系统将整个线性地址空间切分成大小雷同的“虚拟页”,而物理内存也被划分为雷同大小的“物理页帧”。如许,内存管理就可以以固定大小的页为单位举行分配和回收,可有效使用内存,并为后续的映射与更换提供了根本单位。
每个历程维护一个多级页表,其中最底层的页表条目(PTE)包含一个“有效位”和一个“物理页号(PPN)”字段。当有效位为 1 时,PPN 指示该虚拟页当前映射到哪个物理页帧;若为 0,则表明该页要么未分配,要么被换出到磁盘。
CPU 在访问内存时,将 n 位的线性地址分为两部分:高(n − p)位作为“虚拟页号(VPN)”,用于在页表中查找映射;低 p 位作为“页内偏移(VPO)”,用于定位物理页帧内的详细字节。如许,每次地址转换都只需支付一次页表或快表查询的开销,而页内偏移可直接重用,效率更高。
为了加速 VPN→PPN 的转换,CPU 内部提供了翻译后备缓冲器(TLB),它缓存了最近使用的页表条目。每次访问先查询 TLB,若掷中即可立即获得物理页号,无需访问页表;若未掷中,则进入多级页表遍历阶段,同时将查到的效果回填到 TLB,供下一次访问使用。
当 TLB 未掷中时,MMU 会依次访问页目录、页上级目录直至最底层页表:它先使用 VPN 的最高位索引 PML4(或页目录),取得下一层页表的物理地址,再用中心位索引下一层,直到在最后一级页表中找到包含目标物理页号的页表条目。从最底层页表条目中取得 PPN 后,MMU 将物理页号左移 p 位并与原 VPO 按位或运算,生成最终的物理地址。同时,MMU 会将这一(VPN→PPN)对写入 TLB,以便下次遇到同一虚拟页时可以直接掷中,减少时延。
如果在任何一级页表查找中发现对应条目标有效位为 0,MMU 会触发缺页异常。此时,操作系统会检查该虚拟页是否属于合法映射区域:若合法则将对应内容从磁盘读入内存或分配新页,更新页表并重试指令;若非法则向历程发送 SIGSEGV,从而实现安全掩护。
对于 Hello 程序来说,其 .text、.data、在实行时都会映射到各自的虚拟页。每当 Hello 访问任一地址(如读取格式字符串或写入局部变量),CPU 便按上述流程将线性地址逐级转换为物理地址,并通过 L1/L2/L3 缓存最终访问 DRAM,从而确保 Hello 在独立、受掩护的虚拟空间中精确高效地运行。

图7-2 页式管理



7.4 TLB与四级页表支持下的VA到PA的变更

将虚拟地址(Virtual Address,VA)转换为物理地址(Physical Address,PA)依赖于两大关键机制:TLB和四级页表。
每当 CPU 要访问内存时,它起首生成一个 48 位或更少位宽的线性地址。为了只管减少对内存的访问次数,MMU 在硬件中包含了一个小型缓存——TLB,用来生存最近使用的“虚拟页号→物理页号”映射。当 CPU 发送线性地址时,MMU 先在 TLB 中查找相应的页表项;若掷中,便可在 1–2 个周期内立即获得物理页号,险些无需额外延迟 。
如果 TLB 未掷中,MMU 必须实行一个多级页表遍历。起首,MMU 将线性地址划分为五个字段:四个虚拟页号(VPN₄、VPN₃、VPN₂、VPN₁,各 9 位)和一个页内偏移(Offset,12 位)。每个 VPN 索引对应级别的页表,以此查找下一层的页表基址,直到获得最终的物理页号(PPN) 。
四级页表由 PML4、PDPT、PD 和 PT 四层组成,每层都有 512 条 8 字节的页表项。MMU 起首使用 VPN₄ 索引 PML4 表,以获得 PDPT 的物理基址;随后用 VPN₃ 索引 PDPT 得到 PD 基址;再用 VPN₂ 索引 PD,最后用 VPN₁ 索引 PT,从而取得包含 PPN 的页表项。如果任何一级表项的“Present”位为 0,则表明该页不在内存中,需要触发缺页异常。一旦从最底层页表项中读取到物理页号 PPN,MMU 便将其左移 12 位,并与线性地址的 12 位页内偏移归并,生成完整的物理地址。随后,该物理地址被送入 L1→L2→L3 缓存层级,最后访问 DRAM 。
在完成页表遍历并获得 PPN 后,MMU 通常将此 VPN→PPN 映射写入 TLB,使将来对同一虚拟页的访问可以或许直接掷中 TLB、跳过昂贵的多级遍历,从而显著提升访问速度。
若任何级别的页表项不在内存(Present 位为 0),MMU 会触发缺页异常(Page Fault)。操作系统的缺页处置惩罚程序随后检查该虚拟页是否属于合法的映射范围,若合法则将该页从磁盘或交换区载入内存、更新页表并重试指令;否则向历程发送 SIGSEGV,以实现内存访问掩护。

7.5 三级Cache支持下的物理内存访问

当 MMU 最终生成了一个PA后,CPU 会将该地址根据缓存结构参数拆分为三个字段——块内偏移、组索引和标志位。其中,块内偏移用于在缓存块内部确定详细的字节位置;组索引用来在多组数的缓存中选定一个组;标志位则与该组内各路所存储的标志举行逐一比力,以判断数据是否掷中该缓存行。
CPU 起首在最快速的 L1 高速缓存 中举行数据查找。硬件同时读出该组所有路的标志位和有效位,然后将它们与物理地址的标志位字段并行比力;若找到某一路既有效又与 Tag 匹配,即为 L1 掷中,CPU 随即根据块内偏移读取出所需字节,极大减少了访问延迟,通常仅需几周期即可完成。
若 L1 未掷中,CPU 会自动在容量更大但速度稍慢的 L2 缓存 中实行雷同的组—标志—偏移查找流程。L2 缓存一般为 4–16 路聚集关联结构,块大小和组数根据详细实现而异。若在 L2 中掷中,数据不仅被返回给 CPU,同时还会回写(填充,Fill)至 L1,以提高下一次访问的掷中率。
当 L2 仍未掷中时,CPU 继承退到更大但更慢的 L3 缓存(往往是多核共享)举行同样的查找。L3 的容量通常数倍于 L2,可存储更多数据,减少对主存的直接访问。若 L3 掷中,同样会依次回填到 L2 和 L1,实现多级缓存的协同效应。
仅当三级缓存均未掷中时,CPU 才发起对 DRAM 主存 的访问。数据返回后,先填充到 L3,再逐级填充到 L2 和 L1。在每一级缓存中,若目标组中存在有效位为 0 的空闲行,则直接写入;否则,缓存控制器会根据更换策略。选择一条“最久未访问”或“最不频繁使用”的行举行驱逐,然后将新数据写入,以确保热门数据能迅速留驻于高速缓存中。
7.6 hello历程fork时的内存映射

当调用fork创建子历程时,Linux 内核使用虚拟内存和写时复制技术,为父子历程提供私有且独立的地址空间。
当父历程实行fork时,内核不会立即复制所有物理页,而是为子历程创建一个新的 mm_struct(历程地址空间形貌符)以及与父历程雷同的 vm_area_struct(虚拟内存区域列表)副本,继承完整的虚拟地址布局。随后,父子历程共享同一套页表页,所有共享页面在两者的页表中均被标志为“只读”,引用计数增加,并开启写时复制模式。在此阶段,读操作完全共享,不发生任何物理页复制。
只有当父或子历程尝试向某个共享页面写入时,MMU 会因为缺少写权限而触发一次页故障,内核的 COW 处置惩罚程序才会为该写操作分配新的物理页面,将原内容复制过去,并在故障历程的页表中启用写权限,别的历程依然继承共享原页面。

7.7 hello历程execve时的内存映射

在 Linux 中,execve会在当进步程上下文中卸载旧的可实行映像并映射新的可实行文件及其依赖库,从而重新构建历程的虚拟地址空间。步调如下:
(1)内核删除原有的用户内存区域(包括代码段、数据段、堆和栈)
(2)依据 ELF 文件的程序头将新的 .text 和 .data 段私有映射到各自的内存区域,同时为 .bss、堆和栈创建映射
(3)内核通过动态链接器将所有共享库(如 libc.so)映射到共享区域
(4)设置程序计数器(PC)指向入口点,启动新的程序。
整个过程中,所有新创建的区域默认采取写时复制策略,只有在初次写入时才真正分配物理页。
7.8 缺页故障与缺页停止处置惩罚

当历程访问某个虚拟地址 A 对应的页表项(PTE)“缺失”或权限不足时,MMU 会产生缺页异常(Page Fault),将控制权切换至内核的 page_fault_handler。内核老师存用户态寄存器和程序计数器等现场信息,为后续恢复实行做预备。
内核检查地址 A 是否落在任何已界说的虚拟内存区域范围内,若不在则发送 SIGSEGV 终止历程;若在但访问类型(读/写/实行)与区域权限不符,则发送 Protection Fault 并终止历程;否则以为这是一次合法的缺页,可进一步处置惩罚。
对于有效缺页,若该页在磁盘或交换区存在,内核先根据更换算法选出一个物理页帧(若该页帧已“脏”则写回磁盘),再从后备存储读取目标页;若页已在内存中仅需更新 PTE,则直接标志为 Present。完成后,内核恢复现场并重新实行导致缺页的指令,此次访问即可精确掷中。

图7.3 缺页处置惩罚

7.9动态存储分配管理

以下格式自行编排,编辑时删除
Printf会调用malloc,请简述动态内存管理的根本方法与策略。(此节讲堂没有教学,选做,不算分)
7.10本章小结

本章先容了虚拟内存相关的知识,从逻辑地址、虚拟地址、物理地址出发,报告了段式管理、页式管理、TLB与四级页表支持下的VA到PA的变更。并且阐述了三级cache对物理内存访问的支持、hello历程在fork和execve时的内存映射以及缺页故障与缺页停止处置惩罚。通过这些我们可以深刻体会到今世计算机系统在存储方面的精妙计划。

(第7章 2分)


8hello的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分)

结论

hello的履历:hello的一生从预处置惩罚阶段开始,随着头文件内容的插入,从hello.c变成了hello.i;接下来是编译阶段(hello.s)和汇编阶段(hello.o),hello被进一步转为了汇编代码和呆板码,此时的hello已经是计算机能读懂的二进制代码了;紧接着是链接阶段,链接器将hello.o和标准库中的目标代码链接,生成了可实行文件hello。在这之后我们在终端输入./hello,shell调用fork创建子历程,在子历程中调用execve运行hello,这其中操作系统会将程序抽象为历程并用异常控制流控制历程的运行、用虚拟内存实现数据到物理内存的映射,最后随着历程被回收,hello的一生竣事了。
(结论0分,缺失-1分)

附件

hello.c

hello 的C源文件

hello.i

hello.c颠末预处置惩罚后的预编译文本文件

hello.s

hello.i颠末编译得到的汇编语言文本文件

hello.o

hello.s颠末汇编得到的呆板语言二进制文件

hello_elf.txt

hello.o的ELF格式文本文件

hello_dis.txt

hello.o反汇编的文本文件

hello_elf_1.txt

hello的ELF格式文本文件

hello_dis1.txt

hello的反汇编文本文件

hello

hello.o颠末链接得到的可实行目标文件

(附件0分,缺失 -1分)


参考文献



  • https://blog.csdn.net/General_zy/article/details/126445351?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522680f8af78782f7705996b311301fd924%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=680f8af78782f7705996b311301fd924&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-126445351-null-null.142^v102^control&utm_term=%E8%99%9A%E6%8B%9F%E5%86%85%E5%AD%98&spm=1018.2226.3001.418
  • 深入理解计算机系统(原书第三版).呆板工业出书社, 2016.
  • https://blog.csdn.net/qq_42570601/article/details/116668310?ops_request_misc=%257B%2522request%255Fid%2522%253A%25227bec2b41300700d4f90ace4f5dfc2d0d%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=7bec2b41300700d4f90ace4f5dfc2d0d&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-116668310-null-null.142^v102^control&utm_term=%E5%BC%82%E5%B8%B8%E6%8E%A7%E5%88%B6%E6%B5%81&spm=1018.2226.3001.4187
  • https://blog.csdn.net/qq_42570601/article/details/123721693?ops_request_misc=&request_id=&biz_id=102&utm_term=csapp%E5%AD%98%E5%82%A8&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-0-123721693.142^v102^control&spm=1018.2226.3001.4187
(参考文献0分,缺失 -1分)



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

农妇山泉一亩田

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表