> 技术文档 > 哈工大计统大作业 程序人生-Hello’s P2P

哈工大计统大作业 程序人生-Hello’s P2P

计算机系统

大作业

题     目  程序人生-Hello’s P2P 

专       业    计算机与电子通信 

学     号    2023111062   

班     级    23L0501     

学       生     曹博睿    

指 导 教 师    刘宏伟老师     

计算机科学与技术学院

2024年5月

摘  要

本文以经典\"Hello World\"程序为研究对象,系统剖析了计算机系统中程序从源码到进程执行的全生命周期。通过GCC工具链完成预处理、编译、汇编、链接四阶段转换,详细分析了ELF格式、可重定位目标文件地址空间映射等底层机制。在进程管理部分,揭示了Shell创建进程、execve加载程序、信号处理等操作系统核心功能;存储管理章节重点探讨了虚拟内存、页式管理、动态内存分配等关键技术;IO管理部分解析了Linux设备抽象与系统调用实现。本研究通过逐层拆解hello程序的执行流程,验证了计算机系统各层级(编译器、链接器、操作系统、硬件)的协同工作原理,为理解程序在计算机系统中的完整运行机制提供了实践范本。

关键词:计算机系统;程序生命周期;ELF格式;进程管理;虚拟内存;动态链接;                           

(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分

目  录

第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简介

hello程序是计算机科学中最经典的入门示例,其核心功能是通过printf向标准输出打印字符串。从计算机系统视角看,这个简单的程序完整展现了计算机系统的核心机制:高级语言代码(hello.c)经过预处理、编译、汇编和链接等步骤,转化为可执行文件(hello),最终由操作系统加载为进程,通过CPU执行指令和系统调用实现输出。整个过程涉及多层级协作,包括编译器的代码翻译、链接器的符号解析、操作系统的进程管理和硬件指令执行,体现了计算机系统\"抽象分层\"和\"跨层级交互\"的核心思想。作为编程教育的标志性起点,hello程序不仅帮助理解程序如何从文本变为进程,更揭示了计算机系统各组件(软件、OS、硬件)如何协同工作。

1.2 环境与工具

硬件环境

软件环境:Windows11 64位 VMware Ubuntu 24.04.2 amd64

       开发与调试工具:visual studio2022  gcc gdb

1.3 中间结果

源代码文件 hello.c  C语言源代码文件

预处理后的文件 hello.i 展开所有宏定义和头文件,删除注释,生成纯C代码

汇编代码文件 hello.s 将C代码转换为机器相关的汇编指令

目标文件 hello.o 包含机器码和重定位信息,但未解析外部依赖

可执行文件 hello 完全链接后的程序,可直接由操作系统加载执行。

ELF.txt hello.o的elf格式

hello.txt hello.o的反汇编代码

Hello.elf hello的elf格式

Hello.txt hello的反汇编代码

1.4 本章小结

本章系统介绍了hello程序的全生命周期及计算机系统原理。阐述了该程序的重要意义及其编译执行过程中各系统层级的协同工作;说明了实验环境配置;梳理了编译过程中的关键中间产物及其分析文档。本章为后续深入分析奠定了理论基础。

(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

预处理的概念:在计算机编程中,预处理是在源代码正式编译之前对代码进行的一系列预处理操作。预处理通常由预处理器完成,它会根据预处理指令(如宏、条件编译等)修改或扩展源代码,生成一个中间代码供编译器使用。

主要作用:

  1. 文件包含(#include):将头文件(如stdio.h)的内容插入到当前文件中,实现文件复用。
  2. 宏替换(#define):定义常量或宏函数,预处理器会直接替换代码中的宏。
  3. 条件编译(#ifdef,#if,#endif等):根据条件决定是否编译某段代码。
  4. 其他

2.2在Ubuntu下预处理的命令

通过gcc命令gcc -E hello.c -o hello.i对hello.c进行预处理

然后通过文本编辑器查看预处理结果

2.3 Hello的预处理结果解析

1.头文件的展开:预处理器打开并插入所有被 #include 引用的头文件内容。即stdio.h、unistd.h 和 stdlib.h:

2.宏替换与注释处理:因为hello.c文件中并没有定义宏,所以没有进行宏替换操作,但注释会在预处理的过程中被删掉。

3.代码部分处理:代码部分的函数等都保持原样不变。

2.4 本章小结

本章围绕预处理展开,系统性地介绍了预处理的概念、作用。接着通过gcc -E hello.c -o hello.i对hello.c进行预处理得到了hello.i文件,并对与处理的结果进行了内容的解析。预处理不仅是代码转换的第一步,更是优化程序结构、提高可移植性的重要手段。

(第2章0.5分)

第3章 编译

3.1 编译的概念与作用       

1.编译的概念:编译是将预处理后的高级语言代码(如C语言)转换为目标机器代码或汇编代码的过程。在GCC等编译器中,这一阶段由编译器完成。

2.编译的作用:编译可以将高级语言转换为低级语言,将人类可读的文件转换为机器可读的文件,同时在转换的过程中进行语法和语义的检查,代码优化,生成可重定位文件等操作。

3.2 在Ubuntu下编译的命令

通过gcc语句 gcc -S hello.i -o hello.s完成对hello.i文件的编译

3.3 Hello的编译结果解析

3.3.1 数据

       1.变量处理:main函数中有三个局部变量,分别为传递的两个参数argc和*argv,还有一个定义的整型变量i。

 

       由这里可以看出将函数的第一个参数变量和第二个参数变量分别保存在了栈中的这两个位置。

             

       在这里可以看出将局部变量i保存在了栈的这个位置。

       2.常量处理:在main函数中使用了两个字符串常量,常量的部分被存储在了只读区域 ,如下图

    数值常量则用立即数嵌入了指令之中,如下图:

       这里就将数值常量5以立即数形式放在比较指令中。

3.3.2 表达式和操作符:

       1.加法:被编译为add操作

例如这里将循环控制中的i++编译为上述代码。

2.赋值:赋值操作被编译为了mov操作

例如这里将i=0编译为了上述mov代码。

3.3.3 关系操作:

       1.不等!=判断:被编译为cmpl操作。

       例如这里将argc!=5的判断语句编译为上述cmpl语句。

       2.小于比较<:将比较的判断语句编译为cmpl和jle跳转语句。

例如这里将i的条件判断编译为上述语句。

3.3.4: 控制转移:

       1.if语句中的控制转移:

       例如上图,将参数argc与5相比较,如果相等就跳转到L2处的语句。

       2.for循环中的控制转移:

例如上图,将i与9比较,如果小于等于9就跳转到L4处的语句,否则就直接执行60行处的语句。

3.3.5 函数操作:

       1.函数的调用:函数的调用通过call语句来实现:

       例如这里有pringtf和exit两个函数,则在.s文件中用了call文件来调用这两个函数,如下图:

       2.参数的传递:当参数不超过6个时,编译器会将参数按rdi,rsi,rdx,rcx,r8,r9的顺序传递:

       例如上文提到过的main函数所调用的两个参数就分别存储在edi和rsi中,再分别通过mov语句放置在栈不同的位置中。

3.3.6 数组操作:

       整个函数中仅有argv[]一个数组:

       这里是通过对地址的计算来取得argv数组中的元素并作为参数传递给pringf函数。

3.4 本章小结

本章围绕编译的核心过程展开,介绍了编译的概念、作用,接着在ubuntu中通过gcc语句gcc -S hello.i -o hello.s完成了对文件的编译实际操作,然后详细分析了对编译结果的解析,了解了编译器是如何对函数操作,运算,结构控制,数据等进行的编译,为后续的汇编、链接阶段分析奠定基础。编译阶段的优化和处理直接影响程序的性能与可靠性,是计算机系统开发中的核心环节。

(第32分)

第4章 汇编

4.1 汇编的概念与作用

1.汇编的概念:汇编是将汇编代码(.s 文件)转换为机器指令(二进制目标文件)的过程,由汇编器完成。汇编器的主要任务包括:指令翻译,符号解析,生成目标文件等。

2.汇编的作用:生成机器可执行代码,生成可重定位文件.o文件。

4.2 在Ubuntu下汇编的命令

通过as -o hello.o hello.s完成对hello.s文件的汇编操作。

4.3 可重定位目标elf格式

4.3.1 生成elf文件

在shell中输入readelf -a hello.o > hello.elf 指令获得 hello.o 文件的 ELF 格式:

4.3.2结构分析

     ELF头:ELF头以一个16字节的Magic值开始,用于描述生成该文件的系统字长和字节顺序。ELF头的其余部分包含帮助链接器进行语法分析和解释目标文件的信息,诸如ELF头的大小、目标文件类型、机器类型、节头表的文件偏移量,以及节头表中条目的大小和数量等详细信息。

节头:

节头部分中一共有13个节,描述了各个节的位置,内容,意义等。

       重定位节:是程序在编译和链接过程中,用来处理地址和符号的调整与修正的部分。

符号表:它存放在程序中定义和引用的函数和全局变量的信息。

4.4 Hello.o的结果解析

通过objdump -d -r hello.o完成对hello.o的反汇编

反汇编结果如下:

在对比 hello.s(汇编代码)和 hello.o(反汇编代码)时,可以观察到以下几个关键差异,这些差异反映了从汇编语言到机器码的转换过程以及链接器的作用:

1.操作数表示方式

hello.s 中的操作数以十进制形式呈现(如 movl $5, %edi),而反汇编代码(hello.o)中则以十六进制表示(如 movl $0x5, %edi),这更贴近机器码的二进制存储形式。

2.全局变量和字符串常量的访问

在 hello.s 中,访问字符串常量(如printf的格式化字符串)使用段名称+ %rip(如 .LC0(%rip)),而在反汇编代码中,由于地址尚未确定,统一用0(%rip) 表示,并在.rela.text节中添加重定位条目,等待链接时修正。

3.分支转移的实现

hello.s 中的跳转目标以标签(如 .L2)表示,便于编写和阅读;而反汇编代码中,跳转目标被转换为具体的偏移地址(如 main+0x24),因为标签在汇编成机器码后会被替换为实际地址。由于目标文件尚未链接,跳转地址可能暂时为0,需通过重定位修正。

4.函数调用的处理

在 hello.s 中,函数调用(如 call printf)直接使用函数名,而在反汇编代码中,call 指令后跟的是 下一条指令的地址(如 call 0x0),实际地址在链接时通过 .rela.text 中的重定位条目确定。对于动态库函数(如 printf),还需通过过程链接表在运行时解析。

4.5 本章小结

本章围绕汇编阶段展开,介绍了汇编的概念、作用,通过gcc语句完成了对hello.s的汇编操作,得到了hello.o文件,并对hello.o文件的ELF格式进行了分析,了解了ELF文件各个部分的内容等。最后objdump实现了对hello.o文件的反汇编,并将.o文件和.s文件的汇编代码格式等进行了对比,了解了二者之间的区别,例如对数值的表达方式不同,分支转移的跳转目标表示方式不同等等。让我们理解到汇编是机器码生成的桥梁,将符号化的汇编指令转换为二进制指令,但地址和外部符号需链接阶段确定。

(第41分)

第5章 链接

5.1 链接的概念与作用

链接的概念:链接是将多个可重定位目标文件(.o)和库文件合并,生成最终可执行文件或共享库的过程。链接器的核心任务包括:符号解析,地址重定位,合并目标文件。

链接的作用: 将分散编译的模块(如 main.o、lib.o)合并为一个完整的程序。解决跨文件的函数调用(如 printf)和全局变量引用。为代码、数据、堆栈等分配运行时内存地址。消除未使用的代码,合并静态库(.a 文件)中的必要模块。

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 hello.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o

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

通过命令readelf -a hello > Hello.elf 生成 hello 程序的 ELF 格式文件,保存为Hello.elf

ELF头:

节头:包含各个节的基本信息

程序头:

符号表:

5.4 hello的虚拟地址空间

通过语句edb --run ./hello启动 EDB 并加载 hello 可执行文件:

由上面可以看出,前四个部分分别对应Hello.elf中的三个LOAD和一个NOTE。

其中第一个LOAD中包含了ELF头、程序头、.interp部分。

第二个LOAD中包含了代码段,第三个LOAD中包含了只读数据,第四个LOAD中包含了读写数据。

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

5.5 链接的重定位过程分析

在Shell中使用命令objdump -d -r hello > Hello.asm生成反汇编文件Hello1.asm

通过 objdump -d -r 反汇编生成的 hello.asm 和 hello.o 的反汇编结果,可以观察到以下关键差异:

  1. 首先是地址表示的差异,hello.o从 0x0 开始的相对地址在hello中转换为了确定的虚拟地址(如 0x401125):

  1. 其次是外部符号引用的差异,hello.o使用占位符 0x0,依赖重定位条目,hello 则替换为实际地址(如 puts@plt)

  1. 还有函数调用部分,hello.o中需要重定位,但hello中则进行PLT跳转。

  1. 相比于hello.o,hello链接添加了许多函数如printf,getchar,sleep 等。

链接器通过 hello.o 中的重定位条目(如 .rela.text、.rela.plt),在生成 hello 可执行文件时完成以下操作:

符号解析:将未定义的符号(如 puts)绑定到动态库(libc.so.6)中的实际地址。

地址修正:根据目标文件的布局,修改指令或数据中的相对地址或绝对地址。

5.6 hello的执行流程

       主要过程如下:

_start(4010f0) → __libc_start_main

使用gdb/edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程(主要函数)。请列出其调用与跳转的各个子程序名或程序地址。

5.7 Hello的动态链接分析

通过GOT和过程链接表PLT的协作来解析函数的地址。在加载时,动态链接器会重定位GOT中的每个条目,使它包含正确的绝对地址,而PLT中的每个函数负责调用不同函数。那么,通过观察edb,便可发现dl_init后.got.plt节发生的变化。

在elf文件中查看.got.plt的内容

链接之前:

链接之后:

5.8 本章小结

本章主要探讨了链接的概念、作用及其在程序执行中的关键过程。通过分析hello程序的链接与执行流程,深入理解了链接器如何将多个目标文件合并为可执行文件,并完成符号解析、地址重定位等任务。本章通过分析hello的ELF结构、重定位过程和动态链接机制,揭示了链接器如何解决跨模块依赖、分配内存地址,并为程序运行提供基础支持。

(第51分)

第6章 hello进程管理

6.1 进程的概念与作用

进程的概念:进程是计算机中正在运行的程序的一个实例,是操作系统进行资源分配和调度的基本单位。每个进程拥有独立的地址空间、代码、数据和系统资源(如文件描述符、CPU时间等)。

进程的作用:进程的存在使得操作系统能够并发执行多个任务,并隔离不同程序的执行环境,主要作用包括:资源分配与管理,并发执行,程序隔离与保护,提供抽象接口。

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

Shell-bash的作用:Shell-bash是用户与操作系统内核之间的接口,主要作用包括:命令解释与执行,脚本编程,环境管理,IO重定向与作业控制等等。

处理流程:以执行 ./hello 为例,当用户在终端输入该命令后,Shell 首先会读取并解析命令内容,识别出这是一个外部可执行文件而非内置命令,随后通过 fork() 系统调用创建子进程,在子进程中使用 execve() 加载并执行 hello 程序,同时 Shell 父进程会通过 wait() 等待子进程执行完毕。在这个过程中,Shell 还会处理可能存在的 I/O 重定向或管道操作,在 fork() 之后、execve() 之前调整子进程的文件描述符表,最终在子进程结束后恢复 Shell 的交互界面,等待下一条命令输入。

6.3 Hello的fork进程创建过程

当用户在Shell中输入./hello命令时,Shell首先通过fork()系统调用创建一个与自身完全相同的子进程,该子进程继承了Shell的所有内存映像、文件描述符和环境变量;接着子进程通过execve()系统调用加载并执行hello程序,此时操作系统会清空子进程原有的内存空间,替换为hello的代码段、数据段等信息,同时保留原先的文件描述符和进程ID,最终hello程序开始执行其main()函数。在此期间,父进程Shell通过wait()系统调用暂停自身运行,等待子进程执行结束,待hello程序运行终止后,Shell会回收子进程资源并恢复命令提示符,从而完成整个进程创建与程序执行的流程。这一机制通过fork()的写时复制优化和execve()的内存替换,既保证了进程间的隔离性,又实现了高效的程序加载。

6.4 Hello的execve过程

当Shell子进程调用execve(\"./hello\", argv, envp)执行hello程序时,内核首先验证可执行文件的权限和格式,随后清空子进程原有的内存空间,根据hello的ELF文件头重新构建内存映射,包括代码段(.text)、数据段(.data/.bss)和堆栈空间;若程序依赖动态库,则加载动态链接器并解析所有依赖关系,初始化程序运行环境后,将执行权转移到ELF入口点_start,最终通过构建的初始栈帧将参数传递给main函数开始执行。

6.5 Hello的进程执行

当hello程序开始执行时,操作系统通过时间片轮转机制对其进行调度管理,每个时间片内hello在用户态执行程序代码,当发生系统调用(如printf)、硬件中断或时间片耗尽时,CPU会切换到内核态处理相应事件。内核会保存hello的进程上下文(包括寄存器状态、内存映射等),并根据调度算法决定是否继续执行hello或切换其他进程。hello在执行过程中可能因I/O操作主动让出CPU,也可能被更高优先级进程抢占,每次上下文切换都涉及用户态与内核态的转换。当程序终止时,内核会回收其资源并通知父进程,整个过程通过写时复制、延迟绑定等机制优化性能,在保证进程隔离性的同时实现了CPU资源的高效共享。

6.6 hello的异常与信号处理

1.正常运行时:

2.不停乱按时:在程序输出时乱按键盘,会发现通过键盘输出的符号(包括空格)与程序本来的输出叠在一起,并没有影响程序的正常运行。

在乱按键盘的时候会发生中断异常,hello进程会进入内核模式,将控制转移给中断异常处理程序。键盘的中断处理程序,会从键盘控制器的寄存器读取扫描码并翻译成ASCII码,并存入键盘缓冲区。

3.按Ctrl-C时:

按下了Ctrl+C,然后hello程序直接终止,shell返回到接受命令行的状态。

4.按下Ctrl-Z时:

当按下Ctrl+Z时,hello进程会收到SIGTSTP信号,导致进程被挂起并转入后台运行状态。此时shell会立即释放终端控制权,重新获得前台控制权并显示命令提示符,允许用户继续输入其他命令。

5.按下Ctrl-Z后:

6.7本章小结

本章围绕进程管理和程序执行展开,详细分析了从Shell解析命令到hello程序运行结束的完整生命周期。本章揭示了程序如何从静态的ELF文件转变为动态执行的进程,并阐明了操作系统通过进程管理实现资源分配、并发执行和安全隔离的核心原理。

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

(第61分)

第7章 hello的存储管理

7.1 hello的存储器地址空间

1. 逻辑地址

逻辑地址是指由程序产生的与段相关的偏移地址部分,也称为相对地址。在hello程序的反汇编代码中看到的所有地址都是逻辑地址,它们需要通过段机制的计算或变换才能得到内存中的实际有效地址。例如,hello的反汇编代码中main函数的地址0x401125就是一个逻辑地址,它需要与代码段(CS)的基地址相加才能转换为线性地址。

2. 线性地址

线性地址是逻辑地址到物理地址变换的中间层。hello程序的代码产生的逻辑地址(如0x401125)中的偏移量,加上对应段(如代码段)的基地址,便得到了线性地址。在现代操作系统中,由于分段机制被弱化,逻辑地址通常直接等于线性地址。例如,hello的main函数的逻辑地址0x401125经过段基址(通常为0)相加后,线性地址仍然是0x401125。

3. 虚拟地址

虚拟地址有时也称为逻辑地址,因为它与虚拟内存空间的概念类似,是程序视角中的地址,与实际物理内存容量无关。hello程序运行时,CPU发出的内存访问请求使用的是虚拟地址(如0x401125),这些地址通过页表映射转换为物理地址。虚拟地址的存在使得每个进程都拥有独立的地址空间,例如hello进程和Shell进程可以同时使用相同的虚拟地址0x401125,但它们会映射到不同的物理地址。

4. 物理地址

物理地址是指出现在CPU外部地址总线上的、用于寻址物理内存的地址信号,是地址转换的最终结果。在hello程序运行时,CPU生成的虚拟地址(如0x401125)会通过内存管理单元(MMU)的页表转换为物理地址(如0x7fa3b125),然后通过物理地址访问实际的内存位置。物理地址是硬件级别的地址,对进程透明,由操作系统统一管理。

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

1. 段式管理的基本原理

在Intel x86架构中,逻辑地址到线性地址的转换通过段式管理机制实现。每个逻辑地址由两部分组成:

段选择符:16位,指向段描述符表中的条目。

偏移量:32位(32位模式下)或64位(64位模式下),表示段内偏移地址。

转换公式为

线性地址 = 段基址+偏移量

2. 段描述符与段寄存器

段描述符表:

全局描述符表(GDT:存放所有进程共享的段描述符。

局部描述符表(LDT:存放进程私有的段描述符。

段描述符:每个描述符8字节,包含段基址、段限长、访问权限等信息。

段寄存器:

CS(代码段)、DS(数据段)、SS(堆栈段)等。

存储段选择符,用于索引GDT/LDT中的段描述符。

3. 地址转换流程

以hello程序访问代码段为例:

CPU生成逻辑地址:如CS:EIP = 0x08:0x401125(0x08为代码段选择符,0x401125为偏移量)。

查询段描述符:

根据CS的值(0x08)在GDT中找到对应的段描述符。

从段描述符中提取段基址(如0x00000000)。

计算线性地址:

段基址0x00000000 + 偏移量0x401125 = 线性地址0x00401125。

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

在hello程序的执行过程中,操作系统通过页式管理机制完成线性地址到物理地址的转换:CPU生成的线性地址(如0x401125)首先由MMU通过CR3寄存器定位当前进程的四级页表(PGD→PUD→PMD→PTE),经过多级索引查询获得物理页帧号后,与页内偏移组合形成最终物理地址;该过程由TLB缓存加速,当发生缺页异常时内核动态分配物理页并更新页表,同时采用写时复制等技术优化性能,使得hello程序在无需感知实际物理内存布局的情况下,通过虚拟地址空间安全高效地访问内存资源。

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

在hello程序执行过程中,虚拟地址(VA)到物理地址(PA)的转换通过TLB和四级页表协同完成,具体过程如下:

当hello进程访问虚拟地址(如0x401125)时,CPU首先查询TLB这个专用缓存,若命中则直接获取物理地址;若未命中则启动四级页表查询流程:CR3寄存器指向顶级页目录(PGD),MMU依次解析虚拟地址中的各级索引(PGD→PUD→PMD→PTE),最终在页表项(PTE)中获得物理页帧号,与页内偏移组合形成物理地址,同时将该映射缓存至TLB以加速后续访问。整个转换过程由硬件自动完成,并通过PCID(进程上下文ID)避免进程切换时的TLB刷新开销,而缺页异常机制则确保按需分配物理页,使得hello程序在拥有独立4级页表(48位地址空间)的同时,获得接近物理内存的直接访问性能。

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

在hello程序访问物理内存时,CPU采用三级缓存(L1/L2/L3)层次结构进行高效数据存取:首先查询速度最快但容量最小的L1缓存(4周期延迟),若未命中则依次查询L2(12周期)和共享的L3缓存(30-40周期),最后才访问主存(200+周期)。该机制通过硬件预取预测程序访问模式、采用写直达(L1)与写回(L3)混合策略以及MESI一致性协议,使得hello程序在95%以上缓存命中率时,平均访存延迟可控制在10个时钟周期内,而perf工具可监测实际运行时的缓存失效率,其中频繁的printf调用可能因跨缓存行访问导致局部性下降。这种分层缓存设计在硅面积与访问延迟间实现优化平衡,大幅提升了hello程序的内存访问效率。

7.6 hello进程fork时的内存映射

当Shell通过fork()创建hello子进程时,内核采用写时复制(COW)机制实现高效内存映射:子进程初始共享父进程的所有物理内存页,仅复制页表结构并标记为COW;当hello进程首次尝试修改数据段或堆栈等可写内存区域时,会触发缺页异常,内核此时才为其分配独立物理页并复制内容,而代码段等只读区域则始终保持共享。这种机制使得fork()操作仅需复制页表项(微秒级完成),避免了立即复制整个地址空间的开销,同时确保进程间内存隔离性,实测中hello进程启动时实际占用的物理内存仅为真正被修改的部分,大幅提升了系统整体性能与资源利用率。

7.7 hello进程execve时的内存映射

当hello进程通过execve()系统调用加载新程序时,内核会彻底重构其内存映射:首先清空原进程除保留区域外的所有内存页(包括代码、数据、堆栈等),然后根据ELF文件头信息重新建立映射——将.text节映射为只读代码段,.data和.bss节映射为可读写数据段,并初始化堆和栈空间;同时动态链接器ld.so会解析依赖库(如libc.so),将其代码段以共享方式映射到进程空间,而数据段则为私有。该过程通过延迟加载和按需分页机制优化性能,最终hello进程获得全新的独立地址空间,其虚拟内存布局完全由新程序的ELF结构定义,且所有物理页分配都延迟到首次访问时通过缺页异常处理完成,从而在保证执行环境纯净性的同时实现高效的内存使用。

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

在hello程序执行过程中,当CPU访问的虚拟地址没有对应的有效物理页映射时,会触发缺页故障(Page Fault),此时硬件自动保存现场并转入内核的缺页中断处理程序:内核首先检查访问地址的合法性,若属于合法范围(如malloc分配但未初始化的堆空间),则分配物理页、建立页表映射并重新执行引发异常的指令;若为非法访问(如空指针解引用),则向hello进程发送SIGSEGV信号终止其运行。该机制通过按需分配策略(如hello首次访问全局变量时才分配实际物理页)和写时复制(COW)优化(fork后父子进程共享页仅在写入时触发缺页分配新页),实现了内存资源的延迟分配和高效复用,使得hello进程的物理内存占用始终与实际使用需求保持同步,避免不必要的预分配开销。

7.9动态存储分配管理

当hello调用printf时可能触发malloc进行动态内存分配。现代分配器(如glibc)采用以下核心机制:

  1. 分层管理:

fast bins(<64B)快速分配小对象

small/large bins管理不同尺寸块

top chunk动态扩展堆空间

  1. 分配策略:

首次适应/最佳适应算法匹配空闲块

块分割与合并减少碎片

  1. 优化技术:

通过sbrk/mmap申请系统内存

使用位掩码标记块状态

空闲链表加速搜索

内存分配器在速度与空间利用率间权衡,valgrind工具可检测hello运行时的内存错误。该机制保障了程序高效使用堆内存,同时避免泄漏和碎片问题。

7.10本章小结

本章系统分析了hello程序从编译到执行过程中涉及的地址空间转换和内存管理机制,揭示了操作系统如何通过硬件与软件协同实现高效、安全的内存访问。

(第7 2分)

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

Linux采用统一的设备管理架构,其核心思想是将所有硬件设备抽象为文件系统中的特殊文件,通过标准文件操作接口实现设备访问。具体实现包含以下关键机制:

  1. 设备文件抽象

所有设备以文件形式呈现于/dev目录下

字符设备(如终端)和块设备(如磁盘)采用相同访问接口

设备文件包含主次设备号标识具体设备实例

  1. 层次化I/O子系统

系统调用层:提供统一的open/read/write等接口

虚拟文件系统(VFS):抽象各类设备的共性操作

设备驱动层:实现与具体硬件交互的底层操作

  1. 动态设备管理

udev机制实现设备节点的动态创建

sysfs文件系统暴露设备属性和配置

模块化驱动支持热插拔设备

  1. 性能优化特性

块设备采用请求队列和I/O调度算法

支持内存映射(mmap)访问设备寄存器

提供异步I/O和非阻塞操作模式

8.2 简述Unix IO接口及其函数

Unix IO接口:Unix的I/O接口是处理文件、设备和网络交互的核心。通过文件描述符,Unix提供了多种系统调用,用于执行低级文件操作。除了基本的文件操作,Unix还支持缓冲I/O和无缓冲I/O,允许程序在后台执行I/O操作而不阻塞进程。同时,标准库I/O接口(如stdio.h)提供了更高层次的便捷功能。此外,Unix还支持异步I/O、管道和命名管道等机制,增强了进程间通信的能力。所有输入输出操作都通过文件描述符抽象实现。
       基本函数:

open() 用来打开文件/设备,返回文件描述符

read() 用来从文件描述符读取数据到缓冲区

write() 用来将缓冲区数据写入文件描述符

close() 用来关闭文件描述符,释放资源

lseek() 用来移动文件读写偏移量(对块设备有效)。

8.3 printf的实现分析

printf的实现代码:

分析:通过可变参数机制(...)和va_start初始化参数列表,调用vsprintf完成字符串格式化;使用固定大小的栈缓冲区存储格式化结果,最终通过write系统调用将结果输出到标准输出(stdout),返回写入的字节数,

vsprintf的实现代码:

 

分析:通过va_list处理可变参数,使用va_arg逐个提取参数值;逐字符解析格式字符串,当遇到%时识别格式说明符(支持%d、%s、%c、%x等基本类型);对整数调用itoa进行字符串转换,字符串直接拷贝,字符则直接写入;利用传入的缓冲区存储结果并自动维护指针位置,最终返回生成字符串的长度。整个过程完成了从格式化指令到实际字符串的转换,为后续输出提供了基础处理能力。

8.4 getchar的实现分析

getchar的代码实现:

分析:直接通过read系统调用从标准输入(fd=0)逐字节读取数据,带缓冲的实现则通过静态缓冲区批量读取数据(BUFSIZ大小),使用bufptr指针和计数器n管理缓冲区内字符位置,

8.5本章小结

本章深入剖析了Linux系统中hello程序的I/O管理机制,揭示了从用户态接口到内核态设备驱动的完整处理链条,

(第81分)

结论

Hello程序经过的过程体现了计算机系统从 程序(Program) 到 进程(Process)(P2P)及 零到零(020)生命周期 的核心机制,具体过程如下:

1. 预处理阶段

目标:处理源代码中的 # 开头的预编译指令。

操作:

宏替换(如 #define 展开)、头文件包含(#include 插入文本)、删除注释、处理条件编译(#ifdef 等)。

生成 .i 文件

2. 编译阶段

目标:将预处理后的代码翻译为汇编语言(.s 文件)。

操作:

词法/语法/语义分析:解析代码结构,检查语法错误。

优化:生成高效的低级代码(如循环优化、指令选择)。

输出:与硬件架构相关的汇编指令(如 x86-64)。

3. 汇编阶段

目标:将汇编代码转换为机器指令,生成可重定位目标文件(.o 文件)。

操作:

每条汇编指令对应一条机器码(二进制形式),生成 ELF 格式 文件(包含代码段、数据段等节)。

符号表:记录函数和变量的地址(未解析的外部引用)。

4. 链接阶段

目标:合并目标文件和库文件,生成可执行文件。

操作:

符号解析:解决跨模块的函数/变量引用(如 printf 来自 libc)。

重定位:调整代码和数据的地址,使其在内存中连续。

生成 可执行 ELF 文件,包含虚拟地址布局和入口点(main 函数)。

5. 加载与执行阶段

加载到内存:

Shell 调用 execve:操作系统创建新进程,映射可执行文件到虚拟地址空间。

内存分段:代码段(.text)、数据段(.data/.bss)加载到物理内存(可能通过 DMA 技术)。

CPU 从 main 函数开始逐条执行机器指令。

系统调用:如 printf 触发 write 系统调用,将字符串写入标准输出

6. 进程管理与资源回收

进程创建:

Shell 调用 fork:创建子进程运行 hello,父进程(Shell)等待完成。

上下文切换:操作系统保存父进程状态,切换至子进程。

终止与回收:

程序执行完毕(return 0)后,操作系统释放内存、关闭文件描述符。

Shell 通过 waitpid 回收子进程,更新进程表

7. I/O 与异常处理

输出显示:

字符串数据通过 寄存器→显示器 传输(涉及显存映射和 GPU 渲染)。

异常处理:

若发生缺页中断(访问未加载的虚拟页),操作系统从磁盘加载页帧到内存

我的感想:

在完成本次大作业的过程之中,我深刻感受到了计算机系统的强大及复杂,一个非常小的hello程序,竟然需要经过如此多的步骤,环环相扣来完成,每个步骤都是不可缺少的,十分精妙的,在我完成每个章节的过程中都遇到了很多的问题,在去解决这些问题的过程中我增加了我的知识储备,对课上学到的一些知识有了更加深刻的印象和理解。同时我也深刻震撼于计算机中的庞大体系,对一个小小的程序实现都需要如此科学细致的过程,无不都是前人的知识结晶所在,让我叹为观止,收获颇丰。

(结论0分,缺失 -1分,根据内容酌情加分)

附件

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

源代码文件 hello.c  C语言源代码文件

预处理后的文件 hello.i 展开所有宏定义和头文件,删除注释,生成纯C代码

汇编代码文件 hello.s 将C代码转换为机器相关的汇编指令

目标文件 hello.o 包含机器码和重定位信息,但未解析外部依赖

可执行文件 hello 完全链接后的程序,可直接由操作系统加载执行。

ELF.txt hello.o的elf格式

hello.txt hello.o的反汇编代码

Hello.elf hello的elf格式

Hello.txt hello的反汇编代码

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

参考文献

为完成本次大作业你翻阅的书籍与网站等

[1]  Bryant, Randal E., and David R. O’Hallaron. Computer Systems: A Programmer’s Perspective. 3rd ed., Pearson, 2015.

[2]  Kernighan, Brian W., and Dennis M. Ritchie. The C Programming Language. 2nd ed., Prentice Hall, 1988.

[3]  Randal E. Bryant;David R.O’Hallaron.深入理解计算机系统.背景:机械工业出版社,2016.7

[4]  Raiter, Brian. \"A Whirlwind Tutorial on Creating Really Teensy ELF Executables for Linux.\" 2002, A Whirlwind Tutorial on Creating Really Teensy ELF Executables for Linux.

[5]  Intel Corporation. *Intel 64 and IA-32 Architectures Software Developer’s Manual*. 2023, https://software.intel.com/en-us/articles/intel-sdm.

(参考文献0分,缺失 -1分)