关注

程序人生-Hello’s P2P

目  录

第1章 概述................................................................................................................ - 4 -

1.1 Hello简介......................................................................................................... - 4 -

1.2 环境与工具........................................................................................................ - 4 -

1.3 中间结果............................................................................................................ - 4 -

1.4 本章小结............................................................................................................ - 4 -

第2章 预处理............................................................................................................ - 5 -

2.1 预处理的概念与作用........................................................................................ - 5 -

2.2在Ubuntu下预处理的命令............................................................................. - 5 -

2.3 Hello的预处理结果解析................................................................................. - 5 -

2.4 本章小结............................................................................................................ - 5 -

第3章 编译................................................................................................................ - 6 -

3.1 编译的概念与作用............................................................................................ - 6 -

3.2 在Ubuntu下编译的命令................................................................................ - 6 -

3.3 Hello的编译结果解析..................................................................................... - 6 -

3.4 本章小结............................................................................................................ - 6 -

第4章 汇编................................................................................................................ - 7 -

4.1 汇编的概念与作用............................................................................................ - 7 -

4.2 在Ubuntu下汇编的命令................................................................................ - 7 -

4.3 可重定位目标elf格式.................................................................................... - 7 -

4.4 Hello.o的结果解析.......................................................................................... - 7 -

4.5 本章小结............................................................................................................ - 7 -

第5章 链接................................................................................................................ - 8 -

5.1 链接的概念与作用............................................................................................ - 8 -

5.2 在Ubuntu下链接的命令................................................................................ - 8 -

5.3 可执行目标文件hello的格式........................................................................ - 8 -

5.4 hello的虚拟地址空间..................................................................................... - 8 -

5.5 链接的重定位过程分析.................................................................................... - 8 -

5.6 hello的执行流程............................................................................................. - 8 -

5.7 Hello的动态链接分析..................................................................................... - 8 -

5.8 本章小结............................................................................................................ - 9 -

第6章 hello进程管理....................................................................................... - 10 -

6.1 进程的概念与作用.......................................................................................... - 10 -

6.2 简述壳Shell-bash的作用与处理流程........................................................ - 10 -

6.3 Hello的fork进程创建过程......................................................................... - 10 -

6.4 Hello的execve过程..................................................................................... - 10 -

6.5 Hello的进程执行........................................................................................... - 10 -

6.6 hello的异常与信号处理............................................................................... - 10 -

6.7本章小结.......................................................................................................... - 10 -

第7章 hello的存储管理................................................................................... - 11 -

7.1 hello的存储器地址空间................................................................................ - 11 -

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 -

7.9动态存储分配管理........................................................................................... - 11 -

7.10本章小结........................................................................................................ - 12 -

第8章 hello的IO管理.................................................................................... - 13 -

8.1 Linux的IO设备管理方法............................................................................. - 13 -

8.2 简述Unix IO接口及其函数.......................................................................... - 13 -

8.3 printf的实现分析........................................................................................... - 13 -

8.4 getchar的实现分析....................................................................................... - 13 -

8.5本章小结.......................................................................................................... - 13 -

结论............................................................................................................................ - 14 -

附件............................................................................................................................ - 15 -

参考文献.................................................................................................................... - 16 -

第1章 概述

1.1 Hello简介

P2P(Program to Process)完整描述了程序从静态代码到动态进程的转化过程。以hello.c程序为例,其生命周期需经历编译系统的四个核心处理阶段:

在预处理阶段,预处理器(cpp)对源代码进行词法级处理,解析所有以#开头的指令。例如#include <stdio.h>将被扩展为标准输入输出库的头文件内容,通过宏替换、条件编译等操作生成预处理文件hello.i。此阶段实现了源代码的语义完整性扩展。

编译阶段由编译器(cc1)执行语义转换,将高级语言代码转换为低级汇编语言表示。通过对语法树构建、中间代码生成和优化等过程,最终输出汇编指令集文件hello.s,完成从抽象逻辑到机器相关指令的转换。

汇编阶段通过汇编器(as)将符号化指令转化为二进制机器码,生成可重定位目标文件hello.o。该文件包含机器指令段、数据段以及符号表等元数据,但尚未解决外部符号引用问题,形成模块化的二进制单元。

链接阶段由链接器(ld)实现多模块协同,将hello.o与预编译库文件(如printf.o)进行符号解析与地址重定位。通过合并代码段、解析函数引用、确定运行时地址空间等操作,最终生成可直接加载执行的可执行文件hello,完成从静态程序到可执行进程映像的质变。

图1.1

   020(From Zero to Zero)指的是用fork()函数在shell中创建子进程,再用exceve语句加载可执行程序hello,此时,操作系统为其分配虚拟内存,映射到物理内存,完成了从无到有。内存管理器和中央处理器在执行过程中调用三级cache、快表(TLB)、内存等行物理内存上的取数据操作,再通过I/O根据代码指令输出。程序执行完后,对其进行回收,操作系统内核把它从操作系统清除,完成了从有到无。这就是整个020的过程。

1.2 环境与工具

列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。

硬件环境:13th Gen Intel(R) Core(TM) i7-13700H   2.40 GHz,RAM 16.0 GB

软件环境:Windows 11 64位;Ubuntu 20.04 LTS 64;

开发工具:vim、objump、edb、gdb、gcc、readelf

1.3 中间结果

列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。

hello.c:hello程序C语言源文件

hello.i:预处理后生成的文件

hello.s:编译产生的汇编程序文件

hello.o:汇编产生的可重定位目标文件

hello:链接产生的可执行文件

elf.txt:hello.o的elf格式文件

elf1.txt:hello的elf格式文件

asm.txt:hello.o反汇编的结果文件

asm1.txt:hello反汇编的结果文件

1.4 本章小结

本章介绍了hello的P2P以及020的过程,并且介绍了本次大作业的软硬件环境及开发工具。同时还列出了完成大作业的中间结果文件。

第2章 预处理

2.1 预处理的概念与作用

预处理的概念:预处理也叫做预编译,是指在正式开始编译前的操作。预处理器(cpp)根据以字符#开头的命令,修改原始的C程序,读取系统头文件中的内容并把它直接插入程序文本,获得另一个文件后缀为.i的文件。

预处理的作用:

(1)能够完成头文件的包含,将包含的文件插入到程序文本中;(2)可以进行宏替换,把它的符号用实际存在的常量加以替换;(3)实现特殊控制指令;(4)选择符合条件的代码送至编译器编译,完成条件编译,有选择地执行相关操作

2.2在Ubuntu下预处理的命令

预处理的命令为:gcc -m64 -no-pie -fno-PIC -E hello.c -o hello.i

2.3 Hello的预处理结果解析

分别打开hello.c和hello.i两个文件的内容如图所示。.i文件最后的代码段与原来的hello.c源程序的代码是相同的,即预处理没有对代码段进行处理。同时我们发现hello.i文件中删除了.c文件中原本的注释部分,也即预处理会删除注释部分。

从下图我们还能看到,程序的最开始的几行给出了这个对该文件的解释。此外,程序中把stdio.h,unistd.h,stdlib.h三个文件解析出来,找到它们的实际地址,把其中的语句直接插入到hello.i文件中,这也是导致hello.i文件远远多于hello.c的最主要原因。

2.4 本章小结

本章通过gcc -m64 -no-pie -fno-PIC -E hello.c -o hello.i命令对hello.c进行预处理,生成了扩展后的hello.i文件,并解析了其内容。预处理阶段完成了头文件插入、宏替换、条件编译选择等任务,为后续编译阶段提供了去除了冗余信息的C代码。此阶段是程序从源代码到可执行文件的基石,确保编译器的输入完整且符合规范。

                
第3章 编译

3.1 编译的概念与作用

编译的概念:把用高级语言(如C语言)所写出的源程序翻译成汇编语言的目标程序。

编译的作用:编译时,编译器(cc1)把预处理后的hello.i文本文件翻译成汇编语言程序hello.s。把方便程序员编写的顶层高级语言源程序翻译成为更加贴近底层机器指令的汇编语言程序,使得程序员更加清晰地“看到”程序在底层上是如何实现的。同时,编译器会根据编译等级选项对程序进行一些适当的优化,以期提高程序的执行效率。

3.2 在Ubuntu下编译的命令

命令:gcc -m64 -no-pie -fno-PIC -S hello.i -o hello.s

3.3 Hello的编译结果解析

以下格式自行编排,编辑时删除

编译生成.s文件:

3.3.1 数据操作

1. LC0对应一个包含 UTF-8 编码的中文字符串,是局部符号

2. LC1对应英文的局部符号,两个%s对应输入的前两个参数学号和姓名

3. 循环变量i被初始化为0,存储在 -4(%rbp)

4. argc存储在 -20(%rbp)

5. argv在 -32(%rbp)

3.3.2 赋值

3.3.3 类型转换

      1. 算数操作
  1. i+1实现循环控制:

2.对%rax做加法实现数组寻址:

      1. 关系操作

1.将-20(%rbp)中的值和5比较,相等进入L2执行循环:

2.将-4(%rbp)中的值和9进行比较,进行循环条件的控制:

3.3.6 数组/指针操作

使用寄存器rax实现数组的索引,数组存储在-32(%rbp)中

      1. 控制转移

if语句:在3.3.5节中进行的分析,argc是5则跳转,不是5则继续执行下面的语句(即进入if)

for循环:同样在3.3.5中进行了分析,使用局部变量i进行循环控制:

3.3.8 函数

1.最开始调用puts函数:

2. for循环中调用printf语句:

3. 调用atoi函数进行数据格式转换,参数为%rax中的值,作为函数sleep中的参数:

4. sleep函数,将上一个函数的返回值作为参数:

3.4 本章小结

本章利用汇编语言的知识对编译后的.s文件进行了分析,看到了底层代码对寄存器的操作,理解汇编语言和编译器等细节是理解更深的概念的必要知识

第4章 汇编

4.1 汇编的概念与作用

汇编器(as)将 hello.s 翻译成机器语言指令,把这些指令打包成一种叫做可重定位目标程序(relocatable object program)的格式,并将结果保存在目标文件 hello.o 中。hello.o 文件是一个二进制文件,如果我们在文本编辑器中打开 hello.o文件,将看到一堆乱码。

汇编的概念:汇编是指经过汇编器(as)把汇编语言程序(hello.s)翻译成机器指令,并把这些指令打包成可重定位目标程序的形式,并保存在hello.o文件中。

汇编的作用:把汇编语言翻译成计算机能够直接执行的0、1机器语言,把文本文件转化成二进制文件。

4.2 在Ubuntu下汇编的命令

以下格式自行编排,编辑时删除

命令:gcc -m64 -no-pie -fno-PIC -c hello.s -o hello.o

4.3 可重定位目标elf格式

分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。

ELF结构:

使用readelf命令,将elf结果保存到elf.txt中:

  1. ELF头

ELF 头(ELF header)以一个 16 字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF 头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。其中包括 ELF 头的大小、目标文件的类型(如可重定位、可执行或者共享的)、机器类型(如 X86-64)、节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量。不同节的位置和大小是由节头部表描述的,其中目标文件中每个节都有一个固定大小的条目(entry)

  1. 节头部表

节头表描述了.o文件中每一个节出现的位置、大小,目标文件中的每一个节都有一个固定大小的条目。

3. 重定位项目分析

使用readelf -r hello.o查看重定位条目.rela.text 节包含8个重定位条目,示例如下:

重定位类型解析:

R_X86_64_32:32位绝对地址引用,用于访问.rodata中的字符串常量。

R_X86_64_PLT32:32位PLT(过程链接表)相对偏移,用于动态链接库函数调用(如printf)。

.rela.eh_frame 节(异常处理帧重定位)包含1个条目,修正.eh_frame节对.text节的引用:

  1. 符号表(.symtab)解析

5. 其他关键节

.eh_frame:存储异常处理信息,用于栈展开(Stack Unwinding)。

.note.gnu.property:包含处理器特性标记(如 IBT 和 SHSTK),用于控制流完整性保护。

                  

4.4 Hello.o的结果解析

使用objdump -d hello.o >hello.asm查看hello.o的反汇编,保存在hello.asm中

objdump -d -r hello.o  分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。

4.4.1机器指令与汇编代码映射

1、以下是关键指令的机器码与汇编代码对比分析:

函数入口与栈分配

hello.s 代码

pushq   %rbp

movq    %rsp, %rbp

subq    $32, %rsp

hello.o 反汇编

0:  endbr64

4:  55                    push   %rbp

5:  48 89 e5              mov    %rsp,%rbp

8:  48 83 ec 20           sub    $0x20,%rsp

操作码:55 对应 push %rbp,48 89 e5 对应 mov %rsp,%rbp。

栈分配:sub $0x20,%rsp 对应 subq $32, %rsp(0x20=32),为局部变量分配空间。

2、参数检查与错误处理

hello.s 代码

cmpl    $5, -20(%rbp)

je      .L2

movl    $.LC0, %edi

call    puts

hello.o 反汇编

13: 83 7d ec 05           cmpl   $0x5,-0x14(%rbp)

17: 74 14                 je     2d <main+0x2d>

19: bf 00 00 00 00        mov    $0x0,%edi       ; R_X86_64_32 .rodata

1e: e8 00 00 00 00        callq  23 <main+0x23>  ; R_X86_64_PLT32 puts

条件跳转:cmpl $0x5 对应 argc != 5 检查,je 2d 对应跳转至 .L2。

重定位条目:.rodata 字符串地址通过 R_X86_64_32 绝对地址修正。puts 调用通过 R_X86_64_PLT32 修正为 PLT 表偏移。

4.4.2控制转移与循环结构

1、for 循环实现

hello.s 代码

jmp     .L3

.L4:

addl    $1, -4(%rbp)

.L3:

cmpl    $9, -4(%rbp)

jle     .L4

hello.o 反汇编

34: eb 51                 jmp    87 <main+0x87>

83: 83 45 fc 01           addl   $0x1,-0x4(%rbp)

87: 83 7d fc 09           cmpl   $0x9,-0x4(%rbp)

8b: 7e a9                 jle    36 <main+0x36>

循环计数器:addl $0x1 对应 i++,cmpl $0x9 对应 i < 10。

跳转偏移:jle 36 的机器码 7e a9 中,a9 是补码偏移量(实际偏移为 -0x57,指向 0x36)。

4.4.3函数调用与参数传递

1、printf 调用

hello.s 代码

movq    (%rsi), %rdx      # argv[1]

movq    8(%rsi), %rcx     # argv[2]

movq    16(%rsi), %r8     # argv[3]

call    printf

hello.o 反汇编

5a: bf 00 00 00 00     mov    $0x0,%edi       ; R_X86_64_32 .rodata+0x30

5f: b8 00 00 00 00     mov    $0x0,%eax

64: e8 00 00 00 00     callq  69 <main+0x69>  ; R_X86_64_PLT32 printf

参数传递:%rdi 存储格式字符串地址(.rodata+0x30 对应 .LC1)。%rsi、%rdx、%rcx 分别传递 argv[1]、argv[2]、argv[3]。

重定位:printf 调用地址需通过 PLT 表修正。

2、sleep 调用

hello.s 代码

movl    %eax, %edi        # seconds → %edi

call    sleep

hello.o 反汇编

7c: 89 c7                 mov    %eax,%edi

7e: e8 00 00 00 00        callq  83 <main+0x83>  ; R_X86_64_PLT32 sleep

参数存储:atoi 返回值通过 %eax 传递至 %edi。

调用机制:sleep 地址由链接器解析并填充。

4.4.4操作数与重定位差异

1、绝对地址引用

mov    $0x0,%edi ; R_X86_64_32 .rodata

汇编代码:直接使用符号 .LC0。

机器码:占位符 00 00 00 00 需链接时替换为 .rodata 的实际地址。

2、相对地址跳转

jle    36 <main+0x36> ; 机器码 7e a9

偏移计算:0x8b(当前地址) + 0xa9(偏移) → 0x36(目标地址)。

补码表示:0xa9 对应十进制 -87,实际跳转偏移为 0x8b - 0x55 = 0x36。

说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。

4.4.5机器语言与汇编语言映射总结

4.4.6关键重定位类型解析

4.5 本章小结

本章介绍了从汇编到编译的过程,比较了前后的结果,为逆向工程等工作提供了基础

5章 链接

5.1 链接的概念与作用

链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载到内存并执行。链接可以执行于编译时、加载时或运行时。

在现代系统中,链接是由叫做链接器的程序自动执行的。链接器使得分离编译成为可能,无需将大型的应用程序组织成为一个巨大的源文件,而是可以把它分解为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其他文件。

5.2 在Ubuntu下链接的命令

Linux下的链接命令为

ld-ohello-dynamic-linker/lib64/ld-linux-x86-64.so.2/usr/lib/x86_64-linux-gnu/crt1.o/usr/lib/x86_64-linux-gnu/crti.ohello.o/usr/lib/x86_64-linux-gnu/libc.so/usr/lib/x86_64-linux-gnu/crtn.o

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

    通过readelf -a hello > hello_elf2.txt指令,显示hello文件的各种信息并保存至hello_elf2.txt中。

5.3.1 ELF头(ELF header)

ELF头中包含了描述ELF文件整体结构和属性的信息,包括ELF标识、目标体系结构、节表偏移、程序头表偏移等信息

4.3.2 节头

ELF 文件中的节头是描述文件中各个节的关键元数据结构。每个节头对应一个节,记录了该节的名称、类型、地址、大小、对齐方式等属性,如图5.3所示。

可以看出,链接后的可执行文件相比未链接的目标文件,其节头中新增了动态链接相关节​​(如 .interp 指定动态加载器、.dynsym 动态符号表、.plt 和 .got 实现延迟绑定)、​​运行时初始化节​​(如 .init 和 .fini 定义全局构造/析构函数)、​​去除了重定位节​​(如 .rela.text 已被链接器处理),同时所有节的虚拟地址​​(Address 字段)被分配为具体值,且文件类型从 REL(可重定位)变为 EXEC(可执行),表明其已具备完整的加载和执行能力。链接器通过合并代码、解析外部符号、绑定动态库函数,将原始目标文件转化为可直接加载到内存并运行的独立程序。

5.4 hello的虚拟地址空间

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

使用gdb加载可执行文件hello,在主函数处设置断点并运行至断点处

  

通过下图中地址列的地址与上图的地址相对应可知虚拟地址空间内各段信息

5.5 链接的重定位过程分析

通过对比 hello.o 与 hello 的反汇编结果,结合重定位条目,可深入理解链接器如何修正符号地址并生成可执行文件。

1. 符号解析与地址修正

hello.o 中的未解析符号:

callq  0x0 <main+0x23>      ; R_X86_64_PLT32 puts-0x4

反汇编特征:callq 的操作数为 0x0(占位符),需通过 .rela.text 中的重定位条目修正。

重定位条目:

 Offset: 0x1f   Type: R_X86_64_PLT32   Symbol: puts

hello中的修正结果:

callq  0x401090 <puts@plt>

修正逻辑:

链接器根据 R_X86_64_PLT32 类型,计算 puts 的 PLT 表入口地址(0x401090)。

将 callq 指令的操作数替换为 0x401090 - 当前指令地址(0x401143) - 4 的相对偏移。

2. 关键重定位类型解析

R_X86_64_32(绝对地址引用)

hello.o

mov    $0x0,%edi        ; R_X86_64_32 .rodata

hello

mov    $0x402008,%edi   ; 修正为 .rodata 中字符串的实际地址

机制:链接器将 .rodata 段的起始地址(0x402000)与偏移量(0x8)相加,得到绝对地址 0x402008。

R_X86_64_PLT32(动态链接函数调用)

示例:printf 的 PLT 调用修正。

hello.o

callq  0x0 <main+0x69>  ; R_X86_64_PLT32 printf

hello

callq  0x4010a0 <printf@plt>

机制:

链接器在 .plt.sec 节中为 printf 分配 PLT 入口(0x4010a0)。

修正 callq 指令的偏移量,使其跳转到 PLT 表项,通过延迟绑定解析实际函数地址。

3. 数据段与代码段合并

hello 的虚拟地址布局:

.text 起始于 0x401000,.rodata 起始于 0x402000。

重定位影响:所有对 .rodata 的引用(如字符串地址)均被修正为绝对地址。

4. 动态链接与静态链接差异

静态链接:所有库函数代码被直接合并到可执行文件中,无 PLT/GOT 表。

动态链接(本例):

函数调用通过 PLT 表跳转(如 printf@plt),首次调用时触发动态链接器解析真实地址并更新 GOT 表。

重定位条目中 R_X86_64_PLT32 类型支持延迟绑定,降低启动开销。

5.6 hello的执行流程

通过 gdb 跟踪执行流程:

加载阶段:内核加载 hello 到内存,动态链接器解析 .interp 并加载依赖库。

入口函数:_start(在 crt1.o 中定义)调用 __libc_start_main。

主函数执行:__libc_start_main 初始化环境后调用 main。

终止阶段:main 返回后调用 exit 系统调用(SYS_exit)。

5.7 Hello的动态链接分析

1. 动态链接的核心机制

PLT(过程链接表):存储跳转到GOT的指令,首次调用函数时触发动态链接器解析实际地址。

GOT(全局偏移表):存储函数实际地址,初始指向PLT解析代码,解析后更新为真实函数地址。

2. 动态链接过程验证(以printf为例)

由图可知执行printf前与执行后0x404020的值发生变化5.7.1 动态连接过程

动态链接是程序运行时由动态链接器(如ld-linux.so)完成的,主要过程如下:

​​加载可执行文件​​

操作系统加载可执行文件(如hello),读取其头部信息,确定依赖的共享库(如libc.so)。

​​加载共享库​​

动态链接器递归加载所有依赖的共享库到内存,并为它们分配地址空间。

​​符号解析与重定位​​

解析可执行文件和共享库中的未定义符号(如printf),找到其在共享库中的实际地址。

修改可执行文件中的​​全局偏移表(GOT)​​和​​过程链接表(PLT)​​,将符号的地址填充到GOT中。

​​延迟绑定(Lazy Binding)​​

首次调用函数(如puts)时,通过PLT跳转到动态链接器的解析函数,解析符号地址并更新GOT,后续调用直接跳转到目标地址。

5.7.2 hello动态链接前后动态链接项目的内容变化

通过gdb加载hello程序,并在main函数和printf函数调用前分别设置断点

观察反汇编代码,printf函数对应GOT条目地址为0x404008,运行程序,在第一个断点处用x指令查看printf函数对应GOT条目内容

继续运行程序,在第二个断点处单步运行程序至调用printf函数后,再次用x指令查看printf函数对应GOT条目内容

可以看到,动态链接前后printf函数对应的GOT条目发生变化,动态链接前GOT条目指向动态链接器的解析逻辑,动态链接后更新为函数实际地址。

5.8 本章小结

链接中包含了许多提高程序执行效率、减小空间浪费的措施,如静态链接库、动态链接共享库等,为编写高效的程序提供了思路。经过链接,已经得到了一个可执行文件,接下来只需要在 shell 中调用命令就 可以为这一文件创建进程并执行该文件。

6章 hello进程管理

6.1 进程的概念与作用

异常是允许操作系统内核提供进程(process)概念的基本构造块,进程是计算机科学中最深刻、最成功的概念之一。

进程的经典定义就是一个执行中程序的实例。系统中的每个程序都运行在某个进程的上下文(context)中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合

6.2 简述壳Shell-bash的作用与处理流程

以下格式自行编排,编辑时删除

Shell是一个命令行解释器,它输出一个提示符,等待输入一个命令行,然后执行这个命令。如果该命令行的第一个单词不是一个内置的Shell命令,那么Shell就会假设这是一个可执行文件的名字,它将加载并运行这个文件(shell 就会创建一个新的进程,然后在这个新进程的上下文中运行这个可执行目标文件)。在运行Hello时,Shell将加载并运行Hello程序,然后等待程序终止。程序终止后,Shell随后输出一个提示符,等待下一个输入的命令行。

6.3 Hello的fork进程创建过程

#include <sys/types.h>

#include <unistd.h>

pid_t fork(void);

// 返回:子进程返回 0,父进程返回子进程的 PID,如果出错,则为 -1

fork只被调用一次,却会返回两次:一次是在调用进程(父进程)中,一次是在新创建的子进程中。在父进程中,fork 返回子进程的 PID。在子进程中,fork返回 0。因为子进程的 PID 总是为非零,返回值就提供一个明确的方法来分辨程序是在父进程还是在子进程中执行。

父进程和子进程还有以下的特点:

并发执行。父进程和子进程是并发运行的独立进程。内核能够以任意方式交

替执行它们的逻辑控制流中的指令。在我们的系统上运行这个程序时,父进程先完成它的 printf 语句,然后是子进程。然而,在另一个系统上可能正好相反。

相同但是独立的地址空间。如果能够在 fork 函数在父进程和子进程中返回

后立即暂停这两个进程,我们会看到两个进程的地址空间都是相同的。每个进程有相同的用户栈、相同的本地变量值、相同的堆、相同的全局变量值,以及相同的代码。因此,在我们的示例程序中,当 fork 函数在第 6 行返回时,本地变量 x 在父进程和子进程中都为 1。然而,因为父进程和子进程是独立的进程,它们都有自己的私有地址空间。后面,父进程和子进程对 x 所做的任何改变都是独立的,不会反映在另一个进程的内存中。这就是为什么当父进程和子进程调用它们各自的 printf 语句时,它们中的变量 x 会有不同的值。

共享文件。当运行这个示例程序时,我们注意到父进程和子进程都把它们的输出显示在屏幕上。原因是子进程继承了父进程所有的打开文件。当父进程调用 fork 时,stdout 文件是打开的,并指向屏幕。子进程继承了这个文件,因此它的输出也是指向屏幕的。

6.4 Hello的execve过程

execve 函数在当前进程的上下文中加载并运行一个新程序

#include <unistd.h>

int execve(const char *filename, const char *argv[], const char *envp[]);

// 如果成功,则不返回,如果错误,则返回 -1。

execve 函数加载并运行可执行目标文件 filename,且带参数列表 argv 和环境变量列表 envp。只有当出现错误时,例如找不到 filename,execve 才会返回到调用程序。所以,与 fork—次调用返回两次不同,execve 调用一次并从不返回。

6.5 Hello的进程执行

结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。内核为每个进程维持一个上下文(context)。上下文就是内核重新启动一个被抢占的进程所需的状态。它由一些对象的值组成,这些对象包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构,比如描述地址空间的页表、包含有关当前进程信息的进程表,以及包含进程已打开文件的信息的文件表。

在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程。这种决策就叫做调度(scheduling),是由内核中称为调度器(scheduler)的代码处理的。当内核选择一个新的进程运行时,我们说内核调度了这个进程。在内核调度了一个新的进程运行后,它就抢占当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程,上下文切换

1)保存当前进程的上下文,

2)恢复某个先前被抢占的进程被保存的上下文,

3)将控制传递给这个新恢复的进程

一个进程执行它的控制流的一部分的每一时间段叫做进程时间片

上下文切换的原因有很多,如:(1)内核代表用户执行系统调用时可能发生上下文切换(2)中断也可能引发上下文切换

6.6 hello的异常与信号处理

6.6.1 异常的分类

异常可以分为四类:中断(interrupt),陷阱(trap)、故障(fault)和终止(abort)。下表对这些类别的属性做了小结

下图展示了不同异常的处理流程:

6.6.2 hello运行分析

1. 正常运行

2.  运行时按回车、随机字符串:

发现没有影响,会正常显示输入的字符

运行时按下Ctrl+C

会发送SIGINT信号,向子进程发送SIGKILL信号使进程终止并回收

运行时按下Ctrl+Z

会产生SIGTSTP信号,使子进程被挂起,结果不同

此时分别输入:

(1). ps:会显示所有的进程及其状态

可以发现hello是被挂起的状态

(2). jobs:可以显示暂停的进程

发现hello确实被挂起

(3). pstree 可以显示进程树,显示所有进程的情况

(4). 输入fg:使第一个后台作业变成前台作业,这里hello是第一个后台作业,所以变为前台执行:

(5). 输入kill

结合ps,输入:kill -9 13066,杀死hello进程

 hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。

 程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps  jobs  pstree  fg  kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。

6.7本章小结

本章结合linux的进程操作的命令对计算机系统的异常、进程、信号等知识进行了学习,以及在上下文中切换用户态和内核态等

7章 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址(logical address)

包含在机器语言指令中用来指定一个操作数或一条指令的地址。它促使程序员把程序分成若干段。每一个逻辑地址都由一个段(segment)和偏移量(offset)组成,偏移量指明了从段开始的地方到实际地址之间的距离。对应于hello.o中的相对偏移地址。

线性地址(Linear Address)

逻辑地址到物理地址变换之间的中间层。程序代码会产生逻辑地址,或说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。如果启用了分页机制,那么线性地址能再经变换以产生一个物理地址。若没有启用分页机制,那么线性地址直接就是物理地址。

虚拟地址(Virtual Address)

程序运行中程序中使用的逻辑地址,程序通常运行在虚拟地址空间中。由于是段式存储模式,所以虚拟地址是二维的,用段基址和段内位移表示。

物理地址(Physical Address)

线性地址经过页式变换得到的实际内存地址,定位实际要访问的内存单元。这个操作由内存管理单元MMU执行。计算机系统的贮存被组织称一个有M个连续的字节大小的单员组成的数组。每个字节都有一个唯一的物理地址。

在hello程序中,需要将各个指令的虚拟地址变为物理地址并完成各种操作,具体的过程为:先将hello虚拟地址或逻辑地址通过运算映射等方式得到线性地址,而后线性地址再通过页式管理变换的方式转变为物理地址,从而实现hello程序的相关执行。

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

以下格式自行编排,编辑时删除

按照程序自身的逻辑关系划分为若干个段,每个段都有一个段名(在低级语言中,程序员使用段名来编程),每段从0开始编址。

分段系统的逻辑地址结构由段号(段名)和段内地址(段内偏移量)所组成。

段号(段描述符)是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节。通过段标识符的前13位,可以直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段。

段描述符有三类,

全局的段描述符放在全局段描述符表(GDT)中

局部的段描述符放在局部段描述符表(LDT)中

最后中断的描述符放在中断描述符表(IDT)中

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

使用虚拟寻址,CPU 通过生成一个虚拟地址(Virtual Address,VA,即线性地址)来访问主存,这个虚拟地址在被送到内存之前先转换成适当的物理地址就像异常处理一样,地址翻译需要 CPU 硬件和操作系统之间的紧密合作。CPU 芯片上叫做内存管理单元(Memory Management Unit,MMU)的专用硬件,利用存放在主存中的查询表来动态翻译虚拟地址,该表的内容由操作系统管理磁盘(较低层)上的数据被分割成块,这些块作为磁盘和主存(较高层)之间的传输单元。VM 系统通过将虚拟内存分割为称为虚拟页(Virtual Page,VP)的大小固定的块来对较低层的块进行映射同时,VM使用页表实现虚拟页和物理页的映射

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

TLB:(Translation Lookaside Buffer)翻译后备缓冲器,用于加速地址的翻译

使用TLB时翻译地址的一般处理流程:

1)CPU 产生一个虚拟地址。

2)MMU 从 TLB 中取出相应的 PTE。

3)MMU 将这个虚拟地址翻译成一个物理地址,并将它发送到高速缓存/主存。

4)高速缓存/主存将所请求的数据字返回给 CPU。

多级页表:将原来的一级页表拆分为多级,每一级保存着下一级页表的索引,减小内存的压力和资源的浪费。

四级页表会将虚拟地址的VPN部分分为4部分,每部分对应一级页表,MMU会依次查询每一级页表,最后查询得到物理地址的偏移量PPN,和虚拟地址的低p位拼接得到完整的物理地址

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

物理地址由块偏移(CO)、组索引(CI)、标记(CT)三部分组成,用于进行面向Cache的组选择和行匹配。首先在一级Cache下找,若发生不命中,则到下一级缓存即二级Cache下找,若不命中则到三级Cache下访问。

7.6 hello进程fork时的内存映射

当 fork 函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的 PID。为了给这个新进程创建虚拟内存,它创建了当前进程的 mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。

7.7 hello进程execve时的内存映射

execve函数加载并运行 a.out 需要以下几个步骤:

删除已存在的用户区域。删除当前进程虚拟地址的用户部分中的已存在的区域结构。

映射私有区域。为新程序的代码、数据、bss 和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为 a.out 文件中的. text 和. data 区。bss 区域是请求二进制零的,映射到匿名文件,其大小包含在 a.out 中。栈和堆区域也是请求二进制零的,初始长度为零。图 9-31 概括了私有区域的不同映射。

映射共享区域。如果 a.out 程序与共享对象(或目标)链接,比如标准 C 库 libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。

设置程序计数器(PC)。execve 做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。

7.8 缺页故障与缺页中断处理

当出现缺页故障时,即DRAM缓存不命中,此时调用缺页处理程序,内存会确定一个牺牲页,若页面被修改,则换出到磁盘,再将新的目标页替换牺牲页写入,缺页处理程序页面调入新的页面,并更新内存中的 PTE。缺页处理程序返回到原来的进程,重启导致缺页的指令。

7.9动态存储分配管理

动态内存分配器维护着一个进程的虚拟内存区域,称为堆。系统之间的细节不同,但是不失通用性,假设堆是一个请求二进制零的区域,它紧接在未初始化的数据区域后开始,并向上生长。对于每个进程,内核维护着一个变量brk,它指向堆的顶部。

分配器将堆视为一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可以用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放。这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。

动态内存分配包含分配空间和回收空间两个部分

1. 内存分配器的基本思路的实现:

对于分配器有一下若干要求:

处理任意请求序列:一个应用可以有任意的分配请求和释放请求序列,只要满足约束条件:每个释放请求必须对应于一个当前已分配块,这个块是由一个以前的分配请求获得的。因此,分配器不可以假设分配和释放请求的顺序。例如,分配器不能假设所有的分配请求都有相匹配的释放请求,或者有相匹配的分配和空闲请求是嵌套的。

立即响应请求:分配器必须立即响应分配请求。因此,不允许分配器为了提高性能重新排列或者缓冲请求。

只使用堆:为了使分配器是可扩展的,分配器使用的任何非标量数据结构都必须保存在堆里。

对齐块(对齐要求):分配器必须对齐块,使得它们可以保存任何类型的数据对象。

不修改已分配的块:分配器只能操作或者改变空闲块。特别是,一旦块被分配了,就不允许修改或者移动它了。因此,诸如压缩已分配块这样的技术是不允许使用的。

使用不同的链表逻辑来实现不同性能的分配器:

(1)隐式空闲链表:通过一个单向的链表实现

(2)显式空闲链表:通过一个带有前驱和后继的双向链表实现,缩短查找空闲块的时间

(3)分离的空闲链表

2. 垃圾回收机制

一般使用free函数实现申请空间的回收,为了回收那些申请了但是没有显式的回收的空间,使用垃圾回收机制。垃圾回收机制直观上使用一个有向可达图来判断一个内存块是否需要回收。

                       

Printf会调用malloc,请简述动态内存管理的基本方法与策略。(此节课堂没有讲授,选做,不算分)

7.10本章小结

本章简述了计算机中的各类地址及他们之间的相互转换,并探究了计算机的虚拟内存系统内部的工作原理,从内存角度重新审视了前面的若干知识,如fork、execve函数,最后讲述了动态内存分配的相关知识

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分)

结论

hello程序在计算机系统中的执行解析

  1. 预处理:预处理器(cpp)执行宏展开与文件包含,将#指令转化为纯C代码(hello.i),建立完整编译上下文。
  2. 编译:编译器(cc1)通过词法/语法分析构建中间表示(IR),经指令选择与寄存器分配生成目标架构的汇编代码(hello.s),完成高级语言到机器语义的映射。
  3. 汇编:汇编器(as)将符号化指令编码为二进制机器码,生成包含代码/数据段及重定位信息的ELF格式目标文件(hello.o)。
  4. 链接:链接器(ld)解析符号引用,合并多个.o文件并完成地址绑定,构建可执行文件hello的进程映像,实现逻辑地址到虚拟内存的映射。
  5. 加载:操作系统通过execve系统调用将hello载入虚拟地址空间,建立代码/数据/堆栈段,完成从静态文件到进程控制块(PCB)的实例化。
  6. 执行:CPU流水线按取指-译码-执行周期处理机器指令,MMU通过页表实现虚实地址转换,TLB加速内存访问,中断机制处理I/O事件。
  7. 终止:进程通过exit系统调用释放资源,内核更新进程状态并触发调度器进行上下文切换。

系统设计感悟与创新构想
现代计算机系统的层次化抽象与硬件/软件协同令人惊叹:编译器通过中间表示实现跨平台兼容,虚拟内存构建连续地址空间幻觉,流水线与超标量架构隐藏计算延迟。这些设计体现着"Simplicity is sophistication"的哲学——用复杂底层机制换取上层开发的简洁性。

创新方向可聚焦于:

  1. 智能化编译框架:引入深度学习模型优化代码生成,通过程序语义理解自动选择最优指令序列,实现编译策略的动态适配。
  2. 异构内存管理:设计NUMA-aware的页置换算法,结合新型存储介质(如SCM)构建分级内存体系,通过热区预测实现数据智能迁移。
  3. 安全增强型进程模型:在硬件层面集成内存加密与控制流验证单元,构建从编译到执行的信任链,实现漏洞的实时检测与隔离。
  4. 量子-经典混合计算架构:设计支持量子比特操作的指令集扩展,开发混合编程模型编译器,使经典进程可调用量子协处理器加速特定计算。

系统设计的本质在于创造高效的抽象与精妙的妥协,未来需在性能、安全、能效间寻求新平衡点,通过跨层优化释放计算系统的终极潜力。

附件

列出所有的中间产物的文件名,并予以说明起作用。

hello.i        预处理后文件

hello.s        汇编文件

hello.o        可重定位的可执行文件

hello          链接后的可执行文件

hello_elf.txt    hello.o readelf结果

hello_elf2.txt   hello readelf结果

hello_obj.txt    hello.o反汇编结果

hello_obj2.txt   hello 反汇编结果

参考文献

[1]  林来兴. 空间控制技术[M]. 北京:中国宇航出版社,1992:25-42.

[2]  辛希孟. 信息技术与信息服务国际研讨会论文集:A集[C]. 北京:中国科学出版社,1999.

[3]  赵耀东. 新时代的工业工程师[M/OL]. 台北:天下文化出版社,1998 [1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).

[4]  谌颖. 空间交会控制理论与方法研究[D]. 哈尔滨:哈尔滨工业大学,1992:8-13.

[5]  KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.

[6]  CHRISTINE M. Plant Physiology: Plant Biology in the Genome Era[J/OL]. Science,1998,281:331-332[1998-09-23]. http://www.sciencemag.org/cgi/ collection/anatmorp.

[7] (美)兰德尔. E. 布莱恩特等著;龚奕利,贺莲译.深入理解计算机系统(原书第3版).北京:机械工业出版社, 2016.7

[8]  [SHELL(bash)脚本编程六:执行流程-腾讯云开发者社区-腾讯云](SHELL(bash)脚本编程六:执行流程-腾讯云开发者社区-腾讯云)

转载自CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2301_80718854/article/details/148215792

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--