第1章 概述
1.1 Hello简介
用户在编辑器中编写hello.c,包含经典的printf("Hello, World!\n")代码,生成源代码文件。这是Hello的“出生”,即程序的静态表示。源代码经过预处理(处理#include、宏等)、编译(生成汇编代码)、汇编(生成目标代码)、链接(生成可执行文件),形成可执行的二进制文件hello。在壳(Bash)中运行./hello,操作系统(OS)通过fork()系统调用创建一个新进程,复制父进程的上下文。新进程通过execve()加载hello的可执行文件,替换进程的内存映像,初始化程序计数器(PC)、堆栈等。OS的存储管理为进程分配虚拟地址空间(VA),通过内存管理单元(MMU)将虚拟地址映射到物理地址(PA),涉及页表(Page Table)、TLB(Translation Lookaside Buffer)等机制。OS的进程管理分配时间片,调度Hello进程在CPU上运行。CPU通过取指、译码、执行等流水线步骤运行程序指令。存储管理确保代码和数据通过Cache(L1/L2/L3)、主存(RAM)高效访问,可能涉及页面置换和Pagefile。IO管理处理程序的输入输出,printf通过系统调用与设备驱动交互,将“Hello, World!”输出到屏幕(显卡、显示器)。Hello进程完成输出后退出,OS回收其资源(内存、文件描述符等),通过exit()系统调用“收尸”,进程状态清空,归于无。Hello最初仅为用户脑海中的想法,敲入hello.c后成为静态程序,尚无生命。通过编译、链接、加载、执行,Hello从静态程序转变为动态进程,在OS和硬件(CPU、RAM、IO)的支持下短暂“绽放”,输出“Hello, World!”。进程终止后,Hello的运行时状态被OS清理,内存释放,文件关闭,程序回归静态状态或被遗忘,如“挥一挥手,不带走一片云彩”。
1.2 环境与工具
硬件环境:X86-64 Intel i7 10th 16 GB RAM 512 GHD Disk,软件环境:Windows 10 VMware 16 Ubuntu 20.04 LTS MobaXTerm 开发调试工具:GDB,EDB,Visual Studio Code,Vim,gcc
1.3 中间结果
hello.c:原始hello程序的C语言代码
hello.i:预处理过后的hello代码
hello.s:由预处理代码生成的汇编代码
hello.o:二进制目标代码
hello:进行链接后的可执行程序
hello_disassembly.txt:反汇编hello.o得到的反汇编文件
helloobjdump.txt:反汇编hello可执行文件得到的反汇编文件
1.4 本章小结
Hello的P2P过程是从静态的hello.c到动态进程的转变,涉及编辑、编译、链接、进程创建、调度、内存管理、IO操作等计算机系统核心机制。O2O则体现了其生命周期的短暂性,从无到有(创建、运行),再从有到无(终止、资源回收),展现了程序在计算机系统中的完整旅程。CS(计算机系统)见证了Hello的“生与死”,而其简单背后蕴含了OS、硬件、编译器等的复杂协作。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
预处理是编程语言(如C、C++)在正式编译前的一个必要阶段,由预处理器负责执行。它通过处理源代码中以#开头的预处理指令(如宏定义、文件包含、条件编译等),对源代码进行文本层面的修改和扩展,生成供编译器进一步处理的中间代码。
作用:具体来说,预处理通过#include指令将头文件内容插入到当前源文件中,实现代码复用和模块化开发;利用#define定义宏,简化常量使用或代码片段的重复书写;通过条件编译指令(如#ifdef)控制代码的编译范围,适应不同环境需求。预处理阶段不涉及语法分析和代码逻辑处理,仅完成文本替换和文件整合,最终生成供编译器进一步处理的中间代码。这一过程不仅提升了开发效率,还增强了代码的可维护性和跨平台适应性。
2.2在Ubuntu下预处理的命令

应截图,展示预处理过程!
2.3 Hello的预处理结果解析

经过预处理后,hello.c文件生成了hello.i文件,打开hello.i文件后可见文件从原本的28行扩展到了3000多行。原文件中包含的头文件stdio.h、unistd.h、stdlib.h的内容被插入到了该文件中。
2.4 本章小结
预处理作为编译流程的前置阶段,通过预处理器对源代码中以#开头的指令进行文本级操作,为后续编译构建基础。其核心机制包括文件包含指令(如#include)实现代码模块的整合与复用,宏定义指令(如#define)完成常量替换和代码片段的抽象化表达,以及条件编译指令(如#ifdef)提供编译环境的动态适配能力。这些功能不仅简化了重复代码的编写,还通过集中化管理提升了代码的可维护性,同时借助条件编译机制有效屏蔽了跨平台开发中的差异性需求。预处理本质上是源代码的预处理层扩展,通过文本替换与指令解析将程序逻辑与编译配置解耦,最终生成可直接供编译器处理的中间代码,为高效、灵活的软件开发提供了底层支持。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
编译是指编译器做词法分析、语法分析、语义分析等,在检查无错误后,将代码翻译成汇编语言的过程。编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。
作用:进行词法分析和语法分析,分析过程中发现有语法错误,给出提示信
息。将文本文件转化为汇编语言的形式,为后续的汇编操作奠定基础。汇编语言是非常有用的,它为不同的高级语言的不同编译器提供了通用的输出语言。最后生成一个汇编语言程序.s文件。除了基本功能之外,编译程序还具备调试措施、修改手段、覆盖处理、目标程序优化、不同语言合用等重要功能。
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
3.2 在Ubuntu下编译的命令

应截图,展示编译过程!
3.3 Hello的编译结果解析

(以下格式自行编排,编辑时删除)
3.3.1 数据类型
局部变量:在main函数中,局部变量i(int类型)由编译器分配在栈上,通常位于栈帧的偏移位置(如%rbp-4)。初始化时,编译器使用mov指令将i赋值为0(如mov DWORD PTR [rbp-4], 0),确保其在循环中使用前有明确值。
全局变量:程序定义了全局变量sleepsecs(int类型),赋值为2,存储在.rodata段中,作为只读数据。编译器在汇编代码中通过符号引用(如.LC0)访问其值。
立即数:立即数(如循环条件中的10、赋值中的0)直接嵌入汇编指令中,例如cmp DWORD PTR [rbp-4], 10中的10,作为常量参与比较或算术操作。
参数 int argc:argc作为main函数的第一个参数,初始存储在寄存器%edi(x86_64调用约定),随后被保存到栈上(如%rbp-20),以便在条件判断中使用,如cmp DWORD PTR [rbp-20], 5。
数组 char *argv[]:argv是main函数的第二个参数,一个指向字符串指针的数组(char **),初始存储在寄存器%rsi,并保存到栈上(如%rbp-32)。访问argv[1]到argv[3]时,编译器通过偏移计算(如mov rax, [rsi+8])获取字符串地址。
字符串:程序中的字符串常量(如"Hello %s %s %s\n"和"用法: Hello 学号 姓名 手机号 秒数!\n")存储在.rodata段,编译器生成地址引用(如lea rdi, .LC0)供printf使用。
3.3.2 赋值
赋值操作包括全局变量sleepsecs赋值为2和局部变量i赋值为0。编译器为sleepsecs在.rodata段分配固定值2,通过符号引用访问。为i赋值0则使用movl指令(如movl $0, [rbp-4]),直接将立即数0写入栈上变量位置。
3.3.3 类型转换
程序中sleepsecs定义为int,但初始化为2.5(浮点数)。编译器执行隐式类型转换,将2.5截断为2(int),并存储在.rodata段。这种转换在汇编中体现为直接使用整数值2(如mov eax, 2),忽略小数部分。
3.3.4 算术操作
在for循环中,i++是主要的算术操作。编译器将其转换为addl指令(如add DWORD PTR [rbp-4], 1),每次将i的值增加1,更新存储在栈上的值。
3.3.5 关系操作
关系操作1:argc != 5用于检查输入参数数量。编译器生成cmpl指令比较argc(如[rbp-20])与立即数5(如cmpl $5, [rbp-20]),设置条件码,并通过jne指令跳转到错误处理块(调用printf和exit)。
关系操作2:i < 10控制for循环。编译器使用cmpl指令比较i(如[rbp-4])与10(如cmpl $10, [rbp-4]),根据条件码通过jl或jle指令决定是否跳转到循环体。
3.3.6 数组操作
char *argv[]是一个指针数组,存储在栈上(如%rbp-32)。访问argv[1]、argv[2]、argv[3]时,编译器计算偏移量(如[rsi+8]、[rsi+16]),使用movq指令获取字符串指针地址,传递给printf作为参数。
3.3.7 控制转移
控制转移1:if(argc != 5)通过cmpl和jne实现。编译器比较argc与5,设置条件码,若不相等则跳转到错误处理代码,执行printf和exit。
控制转移2:for(i = 0; i < 10; i++)循环通过初始化(movl $0, [rbp-4])、比较(cmpl $10, [rbp-4])和跳转(jl .Lloop)实现。循环体结束后,i++更新计数器,jmp回跳检查条件。
3.3.8 函数操作
参数传递:main函数接收argc(%edi)和argv(%rsi),保存到栈上(如%rbp-20和%rbp-32)。printf调用传递格式字符串(.rodata地址)和argv[1-3](通过%rdi、%rsi、%rdx等)。sleep调用传递sleepsecs(从.rodata加载到%rdi)。atoi和getchar类似,参数通过寄存器传递。
函数调用:printf、sleep、atoi、getchar和exit通过call指令调用。编译器按System V ABI约定准备参数(如mov rdi, rax),然后调用函数(如call printf)。
函数返回:main函数返回0(mov eax, 0),存储在%rax寄存器中。其他函数(如atoi、getchar)的返回值同样通过%rax返回,供后续使用(如sleep使用atoi的返回值)。
通过以上处理,编译器将C语言的类型和操作转换为汇编指令,确保程序逻辑在底层高效执行。
3.4 本章小结
本章通过分析hello.c的编译过程,展示了C语言数据类型(如int、char *argv[]、字符串)和操作(如赋值、循环、函数调用)如何转化为汇编代码。编译器将变量分配在栈或.rodata段,处理关系操作、控制转移和参数传递,生成高效指令。程序的P2P和O2O旅程体现了计算机系统从源代码到进程执行的协同机制。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
汇编语言是一种低级语言,使用助记符表示机器指令,直接与CPU指令集对应,便于程序员控制硬件。汇编的作用在于将高级语言(如C)编译生成的中间表示转换为机器码,优化性能,管理寄存器、内存和指令流,支持底层操作如设备驱动和操作系统开发,同时为调试和逆向工程提供桥梁。
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
4.2 在Ubuntu下汇编的命令

应截图,展示汇编过程!
4.3 可重定位目标elf格式



使用readelf -a hello.o > hellooelf.txt命令生成hello.o的ELF文件信息并重定向到hellooelf.txt文件中。以下对ELF文件的各个部分进行分析,基于其结构和功能,结合hello.o的特点进行简要说明。
1. ELF头
ELF头是目标文件的起始部分,以16字节的Magic序列开头,标识文件的字长和字节序(如大端或小端)。它还包含关键元数据,如ELF头大小、文件类型(此处为可重定位目标文件)、目标机器架构、节头部表的文件偏移量,以及节头部表中条目的数量和大小等。根据ELF头信息,hello.o为可重定位文件,包含14个节,节的具体位置和大小由节头部表进一步描述。
2. 节头部表
节头部表记录了目标文件中各节的属性,包括节的类型、文件偏移量、大小等。作为可重定位文件,hello.o的各节偏移从0开始,供链接器使用。通过节头部表,可定位每个节的起始地址和占用空间。分析显示,代码段(.text)具有可执行属性但不可写;数据段(.data)和只读数据段(.rodata)不可执行,其中.rodata还禁止写入,保障数据完整性。
3. 重定位节
重定位节记录.text节中需调整的引用位置信息,用于链接器在合并目标文件时修改这些引用。重定位节包含以下字段:
偏移量(Offset):指明需修改的引用在.text节中的位置。
信息(Info):分为符号索引(symbol)和重定位类型(type),前者指引用指向的符号,后者指导链接器如何调整引用。
类型(Type):定义重定位方式,如R_X86_64_PC32(32位PC相对寻址)和R_X86_64_32(32位绝对寻址)。
符号名称(Sym.Name):指明重定位目标的符号名。
加数(Addend):有符号常数,用于某些重定位类型调整引用值的偏移。
4. 符号表
符号表存储程序中定义或引用的函数和全局变量信息,排除局部变量。包含以下字段:
值(Value):符号相对于所在节的偏移量,在可执行文件中可能为运行时绝对地址。
大小(Size):符号对应目标的大小。
类型(Type):标识符号类别,如数据、函数、文件、节或未定义(NOTYPE)。
绑定(Bind):区分符号是局部(local)还是全局(global)。
名称(Name):符号的名称,用于链接和调试。
通过以上分析,ELF文件的结构清晰展现了hello.o的组织方式,为后续链接和生成可执行文件提供了基础。
4.4 Hello.o的结果解析


objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
差异分析
跳转指令:在hello.s中,跳转指令(如jmp、jne)使用符号标签(如.L1、.L2)作为目标,代表代码中的逻辑位置,由汇编器和链接器解析为实际地址。而disa_hello.s(反汇编生成)中,跳转指令直接指向具体指令地址,地址以相对于函数起始地址的偏移量形式标注(如<+0x10>),通过程序计数器(PC)值与目标地址的偏移计算得出,反映了反汇编时已解析的内存布局。
函数调用:hello.s中的函数调用(如call printf)仅使用函数名,依赖链接器后续填充实际地址。而disa_hello.s中,call指令后不仅包含函数名,还附带地址信息(如0x400123)和重定位条目类型(如R_X86_64_PLT32),表明反汇编时已解析函数的符号引用或重定位信息,提供了更明确的调用目标细节。
4.5 本章小结
本章通过分析hello.c从源代码到ELF目标文件hello.o的编译过程,阐明了C语言数据类型(如int、char *argv[])和操作(如赋值、循环、函数调用)如何转换为汇编指令。ELF文件结构(包括ELF头、节头部表、重定位节、符号表)为链接提供了关键信息。hello.s与disa_hello.s的跳转指令和函数调用差异,体现了汇编与反汇编在符号处理和地址解析上的不同,揭示了程序从高级语言到机器码的转换机制。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
在软件开发流程中,链接是整合不同代码与数据模块以生成可执行文件的核心步骤。该过程将分散的编译单元(如目标文件)合并为单一文件,确保程序能够被操作系统加载至内存并运行。链接的时机可分为三类:编译时链接(在源代码转换为机器码时同步完成)、加载时链接(由加载器在程序载入内存前动态执行)以及运行时链接(由应用程序在执行过程中按需触发)。早期计算机系统依赖人工干预完成链接,而现代系统则通过自动化工具(即链接器)实现高效处理。
以hello程序为例,其调用的printf函数属于标准C库(如libc)的组成部分。该函数已预先编译为独立的目标文件(如printf.o),需通过链接器与hello程序的主目标文件(如hello.o)合并。链接器会解析函数调用与符号引用关系,消除未定义引用,并生成包含完整地址映射的可执行文件(如hello)。此文件可直接由操作系统加载至内存执行,其内部结构包含代码段、数据段及必要的运行时信息,确保程序能够正确调用外部库函数并完成预期功能。
注意:这儿的链接是指从 hello.o 到hello生成过程。
5.2 在Ubuntu下链接的命令
ld -o hello -dynamic-linker /lib64/ld-linux-aarch64.so.2 /usr/lib/aarch64-linux-gnu/crt1.o /usr/lib/aarch64-linux-gnu/crti.o hello.o /usr/lib/aarch64-linux-gnu/libc.so /usr/lib/aarch64-linux-gnu/crtn.o使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
5.3 可执行目标文件hello的格式
通过执行readelf -a hello > helloelf.txt,生成hello可执行文件的ELF信息并重定向至helloelf.txt。以下对ELF文件的各部分进行分析。
1. ELF头
ELF头以16字节Magic序列开头,定义了文件的字节序和字长,包含文件类型、入口点地址、程序头部表和节头部表的偏移及条目信息等。相较于hello.o(可重定位文件),hello为可执行文件,入口点地址、程序头部表起始位置及大小非零,节头部表起始位置不同,且包含27个节。
2. 节头部表
节头部表记录hello中各节的元数据,如名称、类型、虚拟地址、文件偏移和大小等。虚拟地址指明节加载到内存的起始位置,偏移量指示节在文件中的位置。可通过工具如HexEdit根据这些信息定位节的起始和大小。
3. 程序头部表
程序头部表描述可执行文件如何映射到内存段,包含每个程序头的偏移量、虚拟地址、对齐要求、文件及内存中的段大小,以及运行时权限(如读、写、执行)。它指导操作系统加载程序到虚拟地址空间。
4. 段节
段节定义了程序在内存中的布局,包含代码、数据等内容,由程序头部表描述其映射关系。
5. 动态节
动态节存储动态链接所需的信息,如共享库依赖和符号解析数据,供动态链接器在程序加载时使用。
6. 重定位节
重定位节列出.text节中需调整的函数引用信息,包含偏移量、符号名和重定位类型等,供链接器在合并文件时修改引用地址,确保函数调用指向正确位置。
7. 动态符号表
动态符号表记录与动态链接相关的导入和导出符号,排除模块内部符号,用于运行时符号解析。
8. 符号表
符号表存储程序定义和引用的函数及全局变量信息,包括符号的偏移、类型、大小和绑定属性(局部或全局),以数组索引形式组织,辅助链接和调试。
5.4 hello的虚拟地址空间
使用edb查看hello的内存区域,虚拟地址空间从0x400000到0x405000。根据节头部表,.text节地址为0x4010d0(大小0x135),.data节地址为0x404040(大小0x8),.rodata节地址为0x402000(大小0x2f)。通过edb的data dump,可验证各节的地址和大小,其他节类似查询。
程序头部表进一步描述段信息,如:
PHDR:存储程序头部表。
INTERP:指定动态链接器(如ld-linux.so),如地址0x4002e0,大小0x1c。
LOAD:映射代码、常量数据等段。
DYNAMIC:包含动态链接信息。
NOTE:存储辅助信息。
GNU_STACK:定义栈权限(如是否可执行)。
GNU_RELRO:标记重定位后需设为只读的内存区域。
5.5 链接的重定位过程分析



使用objdump -d -r hello > helloobjdump.txt生成反汇编代码。相比hello.o(地址为0,未重定位),hello包含确定的虚拟地址,完成重定位,新增.init(初始化)、.plt(动态链接表)、.fini(终止)等节。链接过程合并可重定位文件,包含:
符号解析:关联符号引用与定义。
重定位:
合并同类型节,分配运行时地址。
根据.rel.text和.rel.data中的重定位条目(如偏移、类型R_X86_64_32/R_X86_64_PC32)调整引用地址。
5.6 hello的执行流程

![]()

![]()



(以下格式自行编排,编辑时删除)
hello执行涉及以下顺序:
ld-2.31.so!_dl_start:启动链接器。
ld-2.31.so!_dl_init:初始化环境。
hello!_start:程序入口。
ld-2.31.so!_libc_start_main:调用main。
ld-2.31.so!_cxa_atexit:注册终止函数。
ld-2.31.so!_libc_csu_init:初始化C库。
ld-2.31.so!_setjmp:设置跳转。
hello!main:用户代码。
ld-2.31.so!exit:程序退出。
5.7 Hello的动态链接分析
动态链接在运行时组合模块,延迟符号解析至加载或首次调用。编译器为共享库函数(如puts)生成重定位记录,由动态链接器解析。GNU采用延迟绑定,通过GOT(全局偏移表,地址0x404000)和PLT(过程链接表)实现:
PLT:16字节条目,PLT[0]跳转至链接器,PLT[1]调用__libc_start_main,后续条目对应库函数。
GOT:8字节地址,GOT[0-1]存储链接器信息,GOT[2]为ld-linux.so入口,其余对应函数地址。
edb显示,调用_dl_init前,0x404008后16字节为0;调用后,更新为链接器信息和入口点。puts调用前,GOT条目指向PLT第二条指令;调用后,更新为实际函数地址。
5.8 本章小结
本章分析了链接原理,详述hello的ELF格式(头、节、程序头等)、虚拟地址空间(0x400000至0x405000)、重定位(符号解析和地址分配)、执行流程(从_dl_start到exit)及动态链接(GOT/PLT延迟绑定),揭示了可执行文件生成与运行的完整过程。
(以下格式自行编排,编辑时删除)
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程的概念:
进程是程序在执行时的实例,每个程序运行在特定进程的上下文中。上下文包括程序的代码、数据、栈、寄存器、程序计数器(PC)、环境变量及打开的文件描述符,共同构成程序运行所需的状态。
进程的作用:
进程为程序提供独立运行的假象,仿佛独占处理器和内存。处理器看似连续执行程序指令,代码和数据看似内存中唯一对象,这种抽象隔离了多程序运行的复杂性。
6.2 简述壳Shell-bash的作用与处理流程
作用:
Shell(如bash)是用户与内核交互的接口,接受用户命令并传递给内核执行。作为命令解释器,shell解析用户输入,将其转换为机器码执行。shell还支持编程语言,具备循环、分支等结构,允许用户编写脚本,功能等同于其他应用程序。
处理流程:
终端读取用户输入的命令行。
解析命令行,提取参数,构建execve所需的argv向量。
检查首参数是否为内置命令。
若为内置命令,直接执行;若非内置,调用fork创建子进程。
子进程通过execve加载并运行目标程序。
若命令无&(前台运行),shell用waitpid等待子进程结束;若有&(后台运行),shell直接返回。
6.3 Hello的fork进程创建过程
shell解析命令,识别为非内置命令,调用fork创建子进程。子进程复制父进程的虚拟地址空间(包括代码、数据、栈、共享库),但拥有独立副本和唯一PID。父子进程并发执行,内核可任意调度其指令流,父进程默认等待子进程完成。
6.4 Hello的execve过程
当通过系统调用创建子进程后,该子进程会调用execve函数以在当前执行环境中直接加载并运行目标程序hello。execve函数会解析传入的参数列表argv和环境变量列表envp,并尝试加载对应的可执行文件,若文件不存在或格式错误导致加载失败,函数才会返回错误码至调用方;否则其执行过程表现为单向不可逆的进程替换,即成功加载后不会返回调用点。
execve的核心作用在于原地替换当前进程的上下文:新程序hello的代码和数据会完全覆盖原进程的地址空间,但进程标识符(PID)保持不变,且所有已打开的文件描述符会被继承至新程序。在此过程中,新程序的栈段和堆段会被初始化为全零状态,而代码段和数据段则直接映射自磁盘上的可执行文件内容。加载时仅程序头信息(如段加载地址、权限标志等元数据)会被从磁盘复制到内存的对应区域,实际代码和数据则通过内存映射机制实现按需加载,从而避免不必要的磁盘I/O开销。这种原地替换机制使得程序能够高效完成热更新或进程功能切换,同时保持系统资源的连续性。
6.5 Hello的进程执行
进程为应用程序构建了双层抽象机制:其通过模拟独占CPU的假象形成逻辑控制流隔离性,使程序在运行时仿佛独占处理器资源,当调试器逐行执行时程序计数器按序遍历目标文件指令形成连续的PC值序列,若多个逻辑流在时间轴上重叠则构成并发流,而每个进程在CPU上的分时执行片段称为时间片;同时通过虚拟化技术实现私有地址空间隔离,进程为每个程序分配独立虚拟内存区域,使得程序默认仅能访问自身地址空间的数据,这种隔离通过内存管理单元的地址映射实现。为保障进程抽象的完整性,处理器通过用户模式和内核模式的特权级划分进行权限控制,用户模式下进程禁止执行特权指令和访问内核数据,而内核模式则提供完全权限,通常由系统调用或中断触发模式切换。进程切换依赖上下文保存与恢复机制,包括硬件寄存器状态、栈帧信息和内核数据结构等核心内容,当内核调度新进程时,会通过上下文切换保存当前进程状态、恢复目标进程上下文并切换地址空间完成控制权转移。以hello进程为例,其启动时通过execve加载程序并分配虚拟地址空间,执行用户输入时初始运行于用户模式,调用sleep或getchar等系统调用时会触发模式切换——内核处理休眠或输入请求后将进程状态改为等待并移出运行队列,待定时器或输入完成中断触发时,内核重新调度进程并恢复其上下文,最终在用户模式下继续执行,这一过程完整展示了进程抽象如何通过虚拟化、特权级管理和上下文切换机制实现多任务并发与状态透明保存。
。6.6 hello的异常与信号处理

乱按:Shell会将回车前输出的字符串当作命令。

Ctrl+C: 会立即终止进程,通过ps命令发现hello进程被回收。

在hello程序运行期间可能遭遇多种异常场景:其一为中断,这类异常通常由外部I/O设备触发,例如硬件外设发送的信号可能中断程序执行流程;其二为陷阱,属于程序主动触发的可控异常,如hello调用sleep函数时,系统会通过陷阱机制将进程转入内核态以完成休眠操作;其三为故障,典型如缺页异常,当程序访问未映射至物理内存的虚拟地址时,操作系统会捕获该异常并触发页表更新或磁盘I/O操作;其四为终止类错误,例如DRAM/SRAM存储器出现奇偶校验错误导致不可恢复的数据损坏时,系统会直接终止进程。当发生缺页故障时,操作系统会向hello进程发送SIGSEGV信号,导致进程以段错误退出;若main函数参数个数非3,进程会以状态码1终止并触发SIGCHLD信号,父进程默认会忽略该信号。正常执行时,若输入命令为./hello 2023112664 李懿也,程序会输出10次指定内容,并在用户输入字符串后结束。用户交互过程中,按下Ctrl-C会触发SIGINT信号终止进程,此时ps命令无法查询到该进程;按下Ctrl-Z会将进程挂起至后台,通过ps命令可观察到其处于停止状态,通过jobs命令可查看其后台作业编号,通过fg <编号>命令可将其恢复至前台继续执行;按下回车键时,输入内容会被缓存至标准输入缓冲区,当程序调用getchar时会读取缓冲区内容,若未输入有效字符则可能读入空字符串并正常终止;用户随机输入时,字符同样被缓存至缓冲区,程序会持续读取直至遇到换行符,多余字符将作为后续shell命令的输入,最终程序完成执行后由系统回收资源。
6.7本章小结
本章阐述了进程的定义与功能,分析了shell的命令处理机制,详细描述了hello的fork创建、execve加载、执行流程及异常信号处理,揭示了进程管理的核心机制与操作系统支持。
(第6章2分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:逻辑地址(相对地址)由段基址和段内偏移组成,表示程序使用的地址,需通过地址转换生成物理地址,通常以段地址:偏移量形式表示。
线性地址(虚拟地址):线性地址是逻辑地址到物理地址的中间层,32位地址支持4GB空间。分页机制下,地址分为页号和偏移量(如4KB页:高10位页目录,中间10位页表,低12位偏移)。若无分页,线性地址即物理地址。
物理地址:物理地址是CPU地址总线上的实际内存地址,从0开始线性递增,以字节为单位。MMU将虚拟地址转换为物理地址,确保精确寻址内存单元。
7.2 Intel逻辑地址到线性地址的变换-段式管理
逻辑地址由段选择符(16位,前13位为索引,T1指明GDT/LDT)和偏移量组成。段描述符(8字节,含Base字段)存储于GDT(全局描述符表)或LDT(局部描述符表),描述段的线性地址起点。转换步骤:
根据T1(0为GDT,1为LDT)定位描述符表,地址存于gdtr或ldtr寄存器。用段选择符前13位索引段描述符,获取Base地址。Base加偏移量生成线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
分页机制实现虚拟地址到物理地址的转换。虚拟内存按虚拟页(4KB)组织,物理内存按物理页(页帧)划分。MMU通过页表(由操作系统管理)动态翻译地址,页表条目(PTE)包含有效位和地址字段,有效位为1时指向物理页基址,否则表示未分配或在磁盘。
CPU生成虚拟地址,MMU用虚拟页号(VPN)查询PTE。若命中,PTE提供物理页号(PPN),与虚拟页偏移(VPO)组合生成物理地址;若未命中,触发缺页故障,加载磁盘页面到内存。
7.4 TLB与四级页表支持下的VA到PA的变换
以Intel Core i7为例,支持48位虚拟地址、52位物理地址,4KB页,四级页表(每级512条目,9位索引),CR3指向第一级页表,TLB(4路16组)缓存PTE。
转换过程:虚拟地址(36位VPN+12位VPO)送至MMU。TLB用4位TLBI(VPN低4位)定位组,32位TLBT匹配标记。若命中,获取PPN;未命中,MMU按VPN1-4逐级查询页表,获取PPN,更新TLB。PPN与VPO组合成物理地址。在hello中,访问.text节(如0x4010d0)通过TLB快速获取PPN,若未命中,查询四级页表确保地址转换。
7.5 三级Cache支持下的物理内存访问
Cache组织为S=2^s组,每组E行,每行B=2^b字节,含有效位和标记位,总大小C=S×E×B。物理地址分标记位、组索引、块偏移。
访问过程:L1 Cache(64组,8行,64字节/块):6位组索引定位组,40位标记匹配行。命中时,6位块偏移提取数据;未命中,从L2或主存获取,替换L1块(LRU策略)。在hello中,访问.text指令(如0x4010d0)通过L1 Cache快速获取,若未命中,逐级访问L2、L3或主存。
7.6 hello进程fork时的内存映射
调用fork时,内核为子进程分配唯一PID,复制父进程的mm_struct、区域结构和页表,标记页面为只读,区域为私有写时复制,确保父子进程内存独立。
7.7 hello进程execve时的内存映射
execve加载hello,执行以下步骤:
删除当前进程用户区域的现有结构。
创建新区域(代码、数据、bss、栈),映射hello的.text和.data节,bss和栈初始化为零,标记为私有写时复制。
映射共享库(如libc.so)到共享区域。
设置程序计数器指向代码入口。
7.8 缺页故障与缺页中断处理
缺页故障:指令引用虚拟地址,物理页不在内存时触发。MMU通过PTE定位磁盘页面,加载到内存,更新PTE,重试指令。
处理过程:
CPU生成虚拟地址,MMU查询PTE。
有效位为0,触发异常。
选择牺牲页,换出至磁盘(若修改)。
加载新页面,更新PTE。
重试指令,命中内存。
在hello中,访问未缓存的.data地址可能触发缺页,内核加载页面后继续执行。
7.9动态存储分配管理
动态分配器管理堆(紧接未初始化数据区,向上增长,brk指向顶部),维护已分配和空闲块。显式分配器(如malloc/free)由程序控制释放;隐式分配器(如垃圾收集)自动回收。
方法与策略:
隐式空闲链表:
块包含头部(大小、分配状态)、有效载荷、填充,堆为块序列,终止于零大小头部。
分配:搜索空闲块(首次/最佳适配),分割大块减少碎片。
扩展:若无合适块,调用sbrk扩展堆。
合并:释放块时合并相邻空闲块,边界标记(脚部复制头部)支持常数时间合并。
显式空闲链表:
空闲块存储前驱/后继指针,形成双向链表。
LIFO顺序下,分配和合并为常数时间;地址顺序提高内存利用率,释放需线性时间。
In hello, printf调用malloc分配缓冲区,采用隐式空闲链表,搜索空闲块,必要时扩展堆,确保动态内存分配高效。
Printf会调用malloc,请简述动态内存管理的基本方法与策略。(此节课堂没有讲授,选做,不算分)
7.10本章小结
本章分析了hello的存储管理,涵盖逻辑、线性、物理地址转换,TLB与四级页表优化,Cache访问机制,fork和execve内存映射,缺页故障处理及动态内存分配原理,揭示了内存管理的核心机制。
(以下格式自行编排,编辑时删除)
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
Linux将I/O设备(如磁盘、终端、网络)建模为文件,所有输入输出操作视为文件读写。这种统一抽象通过Unix I/O接口实现,确保操作简洁一致。
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
接口:
1.打开文件:内核分配描述符(非负整数)标识文件,记录文件状态。
2.初始文件:进程启动时具有标准输入(0)、输出(1)、错误(2)。
3.文件位置:内核维护文件偏移量k(初始为0),可通过seek修改。
4.读写操作:读从k复制字节到内存,写从内存复制到k,更新k。读到文件末尾返回EOF。
5.关闭文件:释放文件数据结构,回收描述符,进程终止时自动关闭。
函数:
- int open(char* filename, int flags, mode_t mode):打开或创建文件,返回最小未用描述符,flags指定访问模式,mode设置权限。
- int close(int fd):关闭描述符,重复关闭会报错。
- ssize_t read(int fd, void *buf, size_t n):从fd读取最多n字节到buf,返回读取字节数,0表示EOF,-1表示错误。
- ssize_t write(int fd, const void *buf, size_t n):将buf中最多n字节写入fd,返回写入字节数。
8.3 printf的实现分析
Windows下printf实现:
c
Copy
int printf(const char *fmt, ...)
{
int len;
char buffer[256];
va_list args = (va_list)((char*)&fmt + 4); // 定位可变参数
len = vsprintf(buffer, fmt, args); // 格式化字符串
write(buffer, len); // 输出到终端
return len;
}
va_list为字符指针,指向可变参数。vsprintf解析格式字符串fmt,根据%s、%x等格式化参数,生成字符串存入buffer,返回字符串长度。
write实现:将buffer中len字节写入终端,参数存入寄存器(ebx为首地址,ecx为字节数),通过int INT_VECTOR_SYS_CALL调用sys_call。
sys_call实现:将字符串从寄存器复制到显卡显存(存储ASCII码),字符驱动程序根据ASCII在字模库查找点阵,存入VRAM,显示器按刷新频率读取,显示字符串。Linux下printf实现类似,不再赘述。
[转]printf 函数实现的深入剖析 - Pianistx - 博客园
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
键盘中断处理:用户按键触发键盘接口生成扫描码,产生中断,中断处理程序将扫描码转换为ASCII码,存入系统键盘缓冲区。
getchar源码:
c
Copy
int getchar(void)
{
static char buf[BUFSIZ];
static char *ptr = buf;
static int n = 0;
if (n == 0)
{
n = read(0, buf, BUFSIZ);
ptr = buf;
}
return (n-- > 0) ? (unsigned char)*ptr++ : EOF;
}
getchar通过read系统调用从键盘缓冲区读取ASCII码,直至回车,返回首个字符。
8.5 本章小结异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章介绍了Linux的IO设备管理方法及Unix I/O接口,分析了printf和getchar的底层实现原理,阐明了IO操作的统一性和高效性。(第8章 选做 0分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
从编写到终止的完整过程:
- 源代码编写:
hello.c以C语言编写,包含main函数,接受命令行参数argc和argv,调用printf输出字符串,调用sleep暂停执行,调用getchar读取输入,最终终止。 - 预处理:
编译器(gcc)运行预处理器(cpp),处理#include <stdio.h>等指令,展开标准库头文件,生成预处理文件hello.i,包含printf等函数声明。 - 编译:
编译器将hello.i翻译为汇编代码hello.s,将C语言结构(如循环、函数调用)转换为x86_64指令,使用符号标签(如.L1)表示跳转目标。 - 汇编:
汇编器(as)将hello.s转换为机器代码,生成可重定位目标文件hello.o(ELF格式),包含.text(代码段,虚拟地址0x4010d0)、.data(初始化数据,0x404040)、.rodata(只读数据)等节,符号表记录函数和变量引用,重定位条目标记需调整的地址。 - 链接:
链接器(ld)合并hello.o与标准库(如libc.o),解析符号引用,分配运行时虚拟地址,生成可执行文件hello(ELF格式)。程序头部表定义内存映射,重定位节(如.rel.text)指导地址修正。 - 加载:
shell(bash)解析命令./hello 1190200109 刘文卓,识别为非内置命令,调用fork创建子进程,复制shell的虚拟地址空间(代码、数据、栈、共享库),分配唯一PID。子进程通过execve加载hello,映射.text、 .data等节到虚拟地址空间,设置程序计数器指向main。 - 动态链接:
动态链接器(ld-linux.so)解析printf、sleep等共享库函数,更新全局偏移表(GOT,地址0x404000)和过程链接表(PLT)。如printf的GOT条目初始指向PLT,运行时更新为libc.so中的实际地址。 - 执行:
CPU在用户模式执行hello的逻辑控制流,运行main,通过printf输出10行“Hello 1190200109 刘文卓”,调用sleep进入内核模式处理休眠,调用getchar读取键盘输入。内核通过上下文切换调度多进程。 - 内存管理:
虚拟地址通过四级页表和TLB转换为物理地址,访问.text、argv等数据。缺页故障触发内核从磁盘加载页面。三级缓存(L1/L2/L3)优化物理内存访问,使用LRU替换策略处理未命中。 - I/O操作:
printf通过vsprintf格式化字符串,write系统调用将数据写入显存,显示器渲染输出。getchar通过read从键盘缓冲区读取ASCII码,响应用户输入。 - 异常与信号处理:
键盘中断(如Ctrl-C触发SIGINT)终止进程,Ctrl-Z触发SIGTSTP挂起进程,缺页故障触发SIGSEGV。若argc != 5,程序以退出码1终止,发送SIGCHLD给shell。 - 终止:
main返回后,调用exit终止进程,内核回收资源,关闭文件描述符,shell通过waitpid回收子进程。
对计算机系统设计与实现的感悟与创新理念
hello程序的执行过程展现了计算机系统设计的精妙之处,其层次化架构将复杂性分解为多个抽象层,从高级语言到机器指令、从虚拟地址到物理内存、从用户模式到内核模式,每一层都以简洁的接口隐藏底层细节,为程序员提供了高效、可靠的开发环境。hello的编译过程(预处理、编译、汇编、链接)体现了工具链的模块化设计,将C代码逐步转化为可执行文件,优化了开发效率。链接阶段通过静态和动态链接整合代码,平衡了模块化与性能。加载和执行阶段,fork和execve展示了进程管理的灵活性,虚拟内存通过页表和TLB实现了地址隔离与高效转换,三级缓存加速了数据访问,动态链接器通过延迟绑定优化了程序启动时间。I/O操作中,printf和getchar通过系统调用和中断处理实现了用户与硬件的交互,异常和信号机制(如SIGINT、SIGTSTP)确保了系统的健壮性。然而,这一过程也暴露了性能瓶颈,如上下文切换的延迟、TLB未命中、缓存未命中以及动态链接的首次调用开销,提示我们在设计上需权衡抽象的便捷性与执行效率。
基于此,我提出以下创新理念与实现方法:首先,针对动态链接的延迟绑定开销,可设计自适应动态链接机制,通过运行时分析或静态预测高频调用的库函数,提前解析并填充GOT,减少首次调用延迟,低频函数保留延迟绑定以节省内存。其次,针对hello的固定4KB页面管理,提出混合页面大小模型,结合小页面(512B)用于栈和堆分配、大页面(2MB)用于代码和大数据,内核根据访问模式动态调整页面类型,减少TLB未命中和碎片。第三,针对缓存性能,设计基于机器学习的智能预取机制,CPU集成轻量神经网络,分析指令和数据访问模式,预测下一块数据并提前加载到L1缓存,提升命中率。第四,改进异常处理,设计上下文感知信号框架,内核根据程序状态(如循环深度、I/O进度)动态选择信号响应策略,例如延迟SIGINT终止以保存数据,提高用户体验。第五,在动态内存分配方面,针对printf调用的malloc碎片问题,提出分层堆管理,分别为小对象、数组和大块分配专用子堆,结合垃圾收集和手动释放,编译器分析对象生命周期以优化分配策略。第六,针对getchar的同步I/O阻塞,设计异步I/O框架,用户态维护线程池预处理I/O请求,内核通过事件通知异步返回数据,减少上下文切换开销。这些创新可通过编译器扩展、内核模块修改和硬件加速单元实现,如在gcc中添加调用频率分析、在内核中扩展页表结构、在CPU中集成预测单元,从而在保持系统模块化的同时提升性能和灵活性,为现代计算机系统设计注入新的活力。
(结论0分,缺失-1分)
附件
列出所有的中间产物的文件名,并予以说明起作用。
hello.c:原始hello程序的C语言代码
hello.i:预处理过后的hello代码
hello.s:由预处理代码生成的汇编代码
hello.o:二进制目标代码
hello:进行链接后的可执行程序
hello_disassembly.txt:反汇编hello.o得到的反汇编文件
helloobjdump.txt:反汇编hello可执行文件得到的反汇编文件
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
[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.
(参考文献0分,缺失
第1章 概述
1.1 Hello简介
用户在编辑器中编写hello.c,包含经典的printf("Hello, World!\n")代码,生成源代码文件。这是Hello的“出生”,即程序的静态表示。源代码经过预处理(处理#include、宏等)、编译(生成汇编代码)、汇编(生成目标代码)、链接(生成可执行文件),形成可执行的二进制文件hello。在壳(Bash)中运行./hello,操作系统(OS)通过fork()系统调用创建一个新进程,复制父进程的上下文。新进程通过execve()加载hello的可执行文件,替换进程的内存映像,初始化程序计数器(PC)、堆栈等。OS的存储管理为进程分配虚拟地址空间(VA),通过内存管理单元(MMU)将虚拟地址映射到物理地址(PA),涉及页表(Page Table)、TLB(Translation Lookaside Buffer)等机制。OS的进程管理分配时间片,调度Hello进程在CPU上运行。CPU通过取指、译码、执行等流水线步骤运行程序指令。存储管理确保代码和数据通过Cache(L1/L2/L3)、主存(RAM)高效访问,可能涉及页面置换和Pagefile。IO管理处理程序的输入输出,printf通过系统调用与设备驱动交互,将“Hello, World!”输出到屏幕(显卡、显示器)。Hello进程完成输出后退出,OS回收其资源(内存、文件描述符等),通过exit()系统调用“收尸”,进程状态清空,归于无。Hello最初仅为用户脑海中的想法,敲入hello.c后成为静态程序,尚无生命。通过编译、链接、加载、执行,Hello从静态程序转变为动态进程,在OS和硬件(CPU、RAM、IO)的支持下短暂“绽放”,输出“Hello, World!”。进程终止后,Hello的运行时状态被OS清理,内存释放,文件关闭,程序回归静态状态或被遗忘,如“挥一挥手,不带走一片云彩”。
1.2 环境与工具
硬件环境:X86-64 Intel i7 10th 16 GB RAM 512 GHD Disk,软件环境:Windows 10 VMware 16 Ubuntu 20.04 LTS MobaXTerm 开发调试工具:GDB,EDB,Visual Studio Code,Vim,gcc
1.3 中间结果
hello.c:原始hello程序的C语言代码
hello.i:预处理过后的hello代码
hello.s:由预处理代码生成的汇编代码
hello.o:二进制目标代码
hello:进行链接后的可执行程序
hello_disassembly.txt:反汇编hello.o得到的反汇编文件
helloobjdump.txt:反汇编hello可执行文件得到的反汇编文件
1.4 本章小结
Hello的P2P过程是从静态的hello.c到动态进程的转变,涉及编辑、编译、链接、进程创建、调度、内存管理、IO操作等计算机系统核心机制。O2O则体现了其生命周期的短暂性,从无到有(创建、运行),再从有到无(终止、资源回收),展现了程序在计算机系统中的完整旅程。CS(计算机系统)见证了Hello的“生与死”,而其简单背后蕴含了OS、硬件、编译器等的复杂协作。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
预处理是编程语言(如C、C++)在正式编译前的一个必要阶段,由预处理器负责执行。它通过处理源代码中以#开头的预处理指令(如宏定义、文件包含、条件编译等),对源代码进行文本层面的修改和扩展,生成供编译器进一步处理的中间代码。
作用:具体来说,预处理通过#include指令将头文件内容插入到当前源文件中,实现代码复用和模块化开发;利用#define定义宏,简化常量使用或代码片段的重复书写;通过条件编译指令(如#ifdef)控制代码的编译范围,适应不同环境需求。预处理阶段不涉及语法分析和代码逻辑处理,仅完成文本替换和文件整合,最终生成供编译器进一步处理的中间代码。这一过程不仅提升了开发效率,还增强了代码的可维护性和跨平台适应性。
2.2在Ubuntu下预处理的命令

应截图,展示预处理过程!
2.3 Hello的预处理结果解析

经过预处理后,hello.c文件生成了hello.i文件,打开hello.i文件后可见文件从原本的28行扩展到了3000多行。原文件中包含的头文件stdio.h、unistd.h、stdlib.h的内容被插入到了该文件中。
2.4 本章小结
预处理作为编译流程的前置阶段,通过预处理器对源代码中以#开头的指令进行文本级操作,为后续编译构建基础。其核心机制包括文件包含指令(如#include)实现代码模块的整合与复用,宏定义指令(如#define)完成常量替换和代码片段的抽象化表达,以及条件编译指令(如#ifdef)提供编译环境的动态适配能力。这些功能不仅简化了重复代码的编写,还通过集中化管理提升了代码的可维护性,同时借助条件编译机制有效屏蔽了跨平台开发中的差异性需求。预处理本质上是源代码的预处理层扩展,通过文本替换与指令解析将程序逻辑与编译配置解耦,最终生成可直接供编译器处理的中间代码,为高效、灵活的软件开发提供了底层支持。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
编译是指编译器做词法分析、语法分析、语义分析等,在检查无错误后,将代码翻译成汇编语言的过程。编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。
作用:进行词法分析和语法分析,分析过程中发现有语法错误,给出提示信
息。将文本文件转化为汇编语言的形式,为后续的汇编操作奠定基础。汇编语言是非常有用的,它为不同的高级语言的不同编译器提供了通用的输出语言。最后生成一个汇编语言程序.s文件。除了基本功能之外,编译程序还具备调试措施、修改手段、覆盖处理、目标程序优化、不同语言合用等重要功能。
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
3.2 在Ubuntu下编译的命令

应截图,展示编译过程!
3.3 Hello的编译结果解析

(以下格式自行编排,编辑时删除)
3.3.1 数据类型
局部变量:在main函数中,局部变量i(int类型)由编译器分配在栈上,通常位于栈帧的偏移位置(如%rbp-4)。初始化时,编译器使用mov指令将i赋值为0(如mov DWORD PTR [rbp-4], 0),确保其在循环中使用前有明确值。
全局变量:程序定义了全局变量sleepsecs(int类型),赋值为2,存储在.rodata段中,作为只读数据。编译器在汇编代码中通过符号引用(如.LC0)访问其值。
立即数:立即数(如循环条件中的10、赋值中的0)直接嵌入汇编指令中,例如cmp DWORD PTR [rbp-4], 10中的10,作为常量参与比较或算术操作。
参数 int argc:argc作为main函数的第一个参数,初始存储在寄存器%edi(x86_64调用约定),随后被保存到栈上(如%rbp-20),以便在条件判断中使用,如cmp DWORD PTR [rbp-20], 5。
数组 char *argv[]:argv是main函数的第二个参数,一个指向字符串指针的数组(char **),初始存储在寄存器%rsi,并保存到栈上(如%rbp-32)。访问argv[1]到argv[3]时,编译器通过偏移计算(如mov rax, [rsi+8])获取字符串地址。
字符串:程序中的字符串常量(如"Hello %s %s %s\n"和"用法: Hello 学号 姓名 手机号 秒数!\n")存储在.rodata段,编译器生成地址引用(如lea rdi, .LC0)供printf使用。
3.3.2 赋值
赋值操作包括全局变量sleepsecs赋值为2和局部变量i赋值为0。编译器为sleepsecs在.rodata段分配固定值2,通过符号引用访问。为i赋值0则使用movl指令(如movl $0, [rbp-4]),直接将立即数0写入栈上变量位置。
3.3.3 类型转换
程序中sleepsecs定义为int,但初始化为2.5(浮点数)。编译器执行隐式类型转换,将2.5截断为2(int),并存储在.rodata段。这种转换在汇编中体现为直接使用整数值2(如mov eax, 2),忽略小数部分。
3.3.4 算术操作
在for循环中,i++是主要的算术操作。编译器将其转换为addl指令(如add DWORD PTR [rbp-4], 1),每次将i的值增加1,更新存储在栈上的值。
3.3.5 关系操作
关系操作1:argc != 5用于检查输入参数数量。编译器生成cmpl指令比较argc(如[rbp-20])与立即数5(如cmpl $5, [rbp-20]),设置条件码,并通过jne指令跳转到错误处理块(调用printf和exit)。
关系操作2:i < 10控制for循环。编译器使用cmpl指令比较i(如[rbp-4])与10(如cmpl $10, [rbp-4]),根据条件码通过jl或jle指令决定是否跳转到循环体。
3.3.6 数组操作
char *argv[]是一个指针数组,存储在栈上(如%rbp-32)。访问argv[1]、argv[2]、argv[3]时,编译器计算偏移量(如[rsi+8]、[rsi+16]),使用movq指令获取字符串指针地址,传递给printf作为参数。
3.3.7 控制转移
控制转移1:if(argc != 5)通过cmpl和jne实现。编译器比较argc与5,设置条件码,若不相等则跳转到错误处理代码,执行printf和exit。
控制转移2:for(i = 0; i < 10; i++)循环通过初始化(movl $0, [rbp-4])、比较(cmpl $10, [rbp-4])和跳转(jl .Lloop)实现。循环体结束后,i++更新计数器,jmp回跳检查条件。
3.3.8 函数操作
参数传递:main函数接收argc(%edi)和argv(%rsi),保存到栈上(如%rbp-20和%rbp-32)。printf调用传递格式字符串(.rodata地址)和argv[1-3](通过%rdi、%rsi、%rdx等)。sleep调用传递sleepsecs(从.rodata加载到%rdi)。atoi和getchar类似,参数通过寄存器传递。
函数调用:printf、sleep、atoi、getchar和exit通过call指令调用。编译器按System V ABI约定准备参数(如mov rdi, rax),然后调用函数(如call printf)。
函数返回:main函数返回0(mov eax, 0),存储在%rax寄存器中。其他函数(如atoi、getchar)的返回值同样通过%rax返回,供后续使用(如sleep使用atoi的返回值)。
通过以上处理,编译器将C语言的类型和操作转换为汇编指令,确保程序逻辑在底层高效执行。
3.4 本章小结
本章通过分析hello.c的编译过程,展示了C语言数据类型(如int、char *argv[]、字符串)和操作(如赋值、循环、函数调用)如何转化为汇编代码。编译器将变量分配在栈或.rodata段,处理关系操作、控制转移和参数传递,生成高效指令。程序的P2P和O2O旅程体现了计算机系统从源代码到进程执行的协同机制。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
汇编语言是一种低级语言,使用助记符表示机器指令,直接与CPU指令集对应,便于程序员控制硬件。汇编的作用在于将高级语言(如C)编译生成的中间表示转换为机器码,优化性能,管理寄存器、内存和指令流,支持底层操作如设备驱动和操作系统开发,同时为调试和逆向工程提供桥梁。
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
4.2 在Ubuntu下汇编的命令

应截图,展示汇编过程!
4.3 可重定位目标elf格式



使用readelf -a hello.o > hellooelf.txt命令生成hello.o的ELF文件信息并重定向到hellooelf.txt文件中。以下对ELF文件的各个部分进行分析,基于其结构和功能,结合hello.o的特点进行简要说明。
1. ELF头
ELF头是目标文件的起始部分,以16字节的Magic序列开头,标识文件的字长和字节序(如大端或小端)。它还包含关键元数据,如ELF头大小、文件类型(此处为可重定位目标文件)、目标机器架构、节头部表的文件偏移量,以及节头部表中条目的数量和大小等。根据ELF头信息,hello.o为可重定位文件,包含14个节,节的具体位置和大小由节头部表进一步描述。
2. 节头部表
节头部表记录了目标文件中各节的属性,包括节的类型、文件偏移量、大小等。作为可重定位文件,hello.o的各节偏移从0开始,供链接器使用。通过节头部表,可定位每个节的起始地址和占用空间。分析显示,代码段(.text)具有可执行属性但不可写;数据段(.data)和只读数据段(.rodata)不可执行,其中.rodata还禁止写入,保障数据完整性。
3. 重定位节
重定位节记录.text节中需调整的引用位置信息,用于链接器在合并目标文件时修改这些引用。重定位节包含以下字段:
偏移量(Offset):指明需修改的引用在.text节中的位置。
信息(Info):分为符号索引(symbol)和重定位类型(type),前者指引用指向的符号,后者指导链接器如何调整引用。
类型(Type):定义重定位方式,如R_X86_64_PC32(32位PC相对寻址)和R_X86_64_32(32位绝对寻址)。
符号名称(Sym.Name):指明重定位目标的符号名。
加数(Addend):有符号常数,用于某些重定位类型调整引用值的偏移。
4. 符号表
符号表存储程序中定义或引用的函数和全局变量信息,排除局部变量。包含以下字段:
值(Value):符号相对于所在节的偏移量,在可执行文件中可能为运行时绝对地址。
大小(Size):符号对应目标的大小。
类型(Type):标识符号类别,如数据、函数、文件、节或未定义(NOTYPE)。
绑定(Bind):区分符号是局部(local)还是全局(global)。
名称(Name):符号的名称,用于链接和调试。
通过以上分析,ELF文件的结构清晰展现了hello.o的组织方式,为后续链接和生成可执行文件提供了基础。
4.4 Hello.o的结果解析


objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
差异分析
跳转指令:在hello.s中,跳转指令(如jmp、jne)使用符号标签(如.L1、.L2)作为目标,代表代码中的逻辑位置,由汇编器和链接器解析为实际地址。而disa_hello.s(反汇编生成)中,跳转指令直接指向具体指令地址,地址以相对于函数起始地址的偏移量形式标注(如<+0x10>),通过程序计数器(PC)值与目标地址的偏移计算得出,反映了反汇编时已解析的内存布局。
函数调用:hello.s中的函数调用(如call printf)仅使用函数名,依赖链接器后续填充实际地址。而disa_hello.s中,call指令后不仅包含函数名,还附带地址信息(如0x400123)和重定位条目类型(如R_X86_64_PLT32),表明反汇编时已解析函数的符号引用或重定位信息,提供了更明确的调用目标细节。
4.5 本章小结
本章通过分析hello.c从源代码到ELF目标文件hello.o的编译过程,阐明了C语言数据类型(如int、char *argv[])和操作(如赋值、循环、函数调用)如何转换为汇编指令。ELF文件结构(包括ELF头、节头部表、重定位节、符号表)为链接提供了关键信息。hello.s与disa_hello.s的跳转指令和函数调用差异,体现了汇编与反汇编在符号处理和地址解析上的不同,揭示了程序从高级语言到机器码的转换机制。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
在软件开发流程中,链接是整合不同代码与数据模块以生成可执行文件的核心步骤。该过程将分散的编译单元(如目标文件)合并为单一文件,确保程序能够被操作系统加载至内存并运行。链接的时机可分为三类:编译时链接(在源代码转换为机器码时同步完成)、加载时链接(由加载器在程序载入内存前动态执行)以及运行时链接(由应用程序在执行过程中按需触发)。早期计算机系统依赖人工干预完成链接,而现代系统则通过自动化工具(即链接器)实现高效处理。
以hello程序为例,其调用的printf函数属于标准C库(如libc)的组成部分。该函数已预先编译为独立的目标文件(如printf.o),需通过链接器与hello程序的主目标文件(如hello.o)合并。链接器会解析函数调用与符号引用关系,消除未定义引用,并生成包含完整地址映射的可执行文件(如hello)。此文件可直接由操作系统加载至内存执行,其内部结构包含代码段、数据段及必要的运行时信息,确保程序能够正确调用外部库函数并完成预期功能。
注意:这儿的链接是指从 hello.o 到hello生成过程。
5.2 在Ubuntu下链接的命令
ld -o hello -dynamic-linker /lib64/ld-linux-aarch64.so.2 /usr/lib/aarch64-linux-gnu/crt1.o /usr/lib/aarch64-linux-gnu/crti.o hello.o /usr/lib/aarch64-linux-gnu/libc.so /usr/lib/aarch64-linux-gnu/crtn.o使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
5.3 可执行目标文件hello的格式
通过执行readelf -a hello > helloelf.txt,生成hello可执行文件的ELF信息并重定向至helloelf.txt。以下对ELF文件的各部分进行分析。
1. ELF头
ELF头以16字节Magic序列开头,定义了文件的字节序和字长,包含文件类型、入口点地址、程序头部表和节头部表的偏移及条目信息等。相较于hello.o(可重定位文件),hello为可执行文件,入口点地址、程序头部表起始位置及大小非零,节头部表起始位置不同,且包含27个节。
2. 节头部表
节头部表记录hello中各节的元数据,如名称、类型、虚拟地址、文件偏移和大小等。虚拟地址指明节加载到内存的起始位置,偏移量指示节在文件中的位置。可通过工具如HexEdit根据这些信息定位节的起始和大小。
3. 程序头部表
程序头部表描述可执行文件如何映射到内存段,包含每个程序头的偏移量、虚拟地址、对齐要求、文件及内存中的段大小,以及运行时权限(如读、写、执行)。它指导操作系统加载程序到虚拟地址空间。
4. 段节
段节定义了程序在内存中的布局,包含代码、数据等内容,由程序头部表描述其映射关系。
5. 动态节
动态节存储动态链接所需的信息,如共享库依赖和符号解析数据,供动态链接器在程序加载时使用。
6. 重定位节
重定位节列出.text节中需调整的函数引用信息,包含偏移量、符号名和重定位类型等,供链接器在合并文件时修改引用地址,确保函数调用指向正确位置。
7. 动态符号表
动态符号表记录与动态链接相关的导入和导出符号,排除模块内部符号,用于运行时符号解析。
8. 符号表
符号表存储程序定义和引用的函数及全局变量信息,包括符号的偏移、类型、大小和绑定属性(局部或全局),以数组索引形式组织,辅助链接和调试。
5.4 hello的虚拟地址空间
使用edb查看hello的内存区域,虚拟地址空间从0x400000到0x405000。根据节头部表,.text节地址为0x4010d0(大小0x135),.data节地址为0x404040(大小0x8),.rodata节地址为0x402000(大小0x2f)。通过edb的data dump,可验证各节的地址和大小,其他节类似查询。
程序头部表进一步描述段信息,如:
PHDR:存储程序头部表。
INTERP:指定动态链接器(如ld-linux.so),如地址0x4002e0,大小0x1c。
LOAD:映射代码、常量数据等段。
DYNAMIC:包含动态链接信息。
NOTE:存储辅助信息。
GNU_STACK:定义栈权限(如是否可执行)。
GNU_RELRO:标记重定位后需设为只读的内存区域。
5.5 链接的重定位过程分析



使用objdump -d -r hello > helloobjdump.txt生成反汇编代码。相比hello.o(地址为0,未重定位),hello包含确定的虚拟地址,完成重定位,新增.init(初始化)、.plt(动态链接表)、.fini(终止)等节。链接过程合并可重定位文件,包含:
符号解析:关联符号引用与定义。
重定位:
合并同类型节,分配运行时地址。
根据.rel.text和.rel.data中的重定位条目(如偏移、类型R_X86_64_32/R_X86_64_PC32)调整引用地址。
5.6 hello的执行流程

![]()

![]()



(以下格式自行编排,编辑时删除)
hello执行涉及以下顺序:
ld-2.31.so!_dl_start:启动链接器。
ld-2.31.so!_dl_init:初始化环境。
hello!_start:程序入口。
ld-2.31.so!_libc_start_main:调用main。
ld-2.31.so!_cxa_atexit:注册终止函数。
ld-2.31.so!_libc_csu_init:初始化C库。
ld-2.31.so!_setjmp:设置跳转。
hello!main:用户代码。
ld-2.31.so!exit:程序退出。
5.7 Hello的动态链接分析
动态链接在运行时组合模块,延迟符号解析至加载或首次调用。编译器为共享库函数(如puts)生成重定位记录,由动态链接器解析。GNU采用延迟绑定,通过GOT(全局偏移表,地址0x404000)和PLT(过程链接表)实现:
PLT:16字节条目,PLT[0]跳转至链接器,PLT[1]调用__libc_start_main,后续条目对应库函数。
GOT:8字节地址,GOT[0-1]存储链接器信息,GOT[2]为ld-linux.so入口,其余对应函数地址。
edb显示,调用_dl_init前,0x404008后16字节为0;调用后,更新为链接器信息和入口点。puts调用前,GOT条目指向PLT第二条指令;调用后,更新为实际函数地址。
5.8 本章小结
本章分析了链接原理,详述hello的ELF格式(头、节、程序头等)、虚拟地址空间(0x400000至0x405000)、重定位(符号解析和地址分配)、执行流程(从_dl_start到exit)及动态链接(GOT/PLT延迟绑定),揭示了可执行文件生成与运行的完整过程。
(以下格式自行编排,编辑时删除)
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程的概念:
进程是程序在执行时的实例,每个程序运行在特定进程的上下文中。上下文包括程序的代码、数据、栈、寄存器、程序计数器(PC)、环境变量及打开的文件描述符,共同构成程序运行所需的状态。
进程的作用:
进程为程序提供独立运行的假象,仿佛独占处理器和内存。处理器看似连续执行程序指令,代码和数据看似内存中唯一对象,这种抽象隔离了多程序运行的复杂性。
6.2 简述壳Shell-bash的作用与处理流程
作用:
Shell(如bash)是用户与内核交互的接口,接受用户命令并传递给内核执行。作为命令解释器,shell解析用户输入,将其转换为机器码执行。shell还支持编程语言,具备循环、分支等结构,允许用户编写脚本,功能等同于其他应用程序。
处理流程:
终端读取用户输入的命令行。
解析命令行,提取参数,构建execve所需的argv向量。
检查首参数是否为内置命令。
若为内置命令,直接执行;若非内置,调用fork创建子进程。
子进程通过execve加载并运行目标程序。
若命令无&(前台运行),shell用waitpid等待子进程结束;若有&(后台运行),shell直接返回。
6.3 Hello的fork进程创建过程
shell解析命令,识别为非内置命令,调用fork创建子进程。子进程复制父进程的虚拟地址空间(包括代码、数据、栈、共享库),但拥有独立副本和唯一PID。父子进程并发执行,内核可任意调度其指令流,父进程默认等待子进程完成。
6.4 Hello的execve过程
当通过系统调用创建子进程后,该子进程会调用execve函数以在当前执行环境中直接加载并运行目标程序hello。execve函数会解析传入的参数列表argv和环境变量列表envp,并尝试加载对应的可执行文件,若文件不存在或格式错误导致加载失败,函数才会返回错误码至调用方;否则其执行过程表现为单向不可逆的进程替换,即成功加载后不会返回调用点。
execve的核心作用在于原地替换当前进程的上下文:新程序hello的代码和数据会完全覆盖原进程的地址空间,但进程标识符(PID)保持不变,且所有已打开的文件描述符会被继承至新程序。在此过程中,新程序的栈段和堆段会被初始化为全零状态,而代码段和数据段则直接映射自磁盘上的可执行文件内容。加载时仅程序头信息(如段加载地址、权限标志等元数据)会被从磁盘复制到内存的对应区域,实际代码和数据则通过内存映射机制实现按需加载,从而避免不必要的磁盘I/O开销。这种原地替换机制使得程序能够高效完成热更新或进程功能切换,同时保持系统资源的连续性。
6.5 Hello的进程执行
进程为应用程序构建了双层抽象机制:其通过模拟独占CPU的假象形成逻辑控制流隔离性,使程序在运行时仿佛独占处理器资源,当调试器逐行执行时程序计数器按序遍历目标文件指令形成连续的PC值序列,若多个逻辑流在时间轴上重叠则构成并发流,而每个进程在CPU上的分时执行片段称为时间片;同时通过虚拟化技术实现私有地址空间隔离,进程为每个程序分配独立虚拟内存区域,使得程序默认仅能访问自身地址空间的数据,这种隔离通过内存管理单元的地址映射实现。为保障进程抽象的完整性,处理器通过用户模式和内核模式的特权级划分进行权限控制,用户模式下进程禁止执行特权指令和访问内核数据,而内核模式则提供完全权限,通常由系统调用或中断触发模式切换。进程切换依赖上下文保存与恢复机制,包括硬件寄存器状态、栈帧信息和内核数据结构等核心内容,当内核调度新进程时,会通过上下文切换保存当前进程状态、恢复目标进程上下文并切换地址空间完成控制权转移。以hello进程为例,其启动时通过execve加载程序并分配虚拟地址空间,执行用户输入时初始运行于用户模式,调用sleep或getchar等系统调用时会触发模式切换——内核处理休眠或输入请求后将进程状态改为等待并移出运行队列,待定时器或输入完成中断触发时,内核重新调度进程并恢复其上下文,最终在用户模式下继续执行,这一过程完整展示了进程抽象如何通过虚拟化、特权级管理和上下文切换机制实现多任务并发与状态透明保存。
。6.6 hello的异常与信号处理

乱按:Shell会将回车前输出的字符串当作命令。

Ctrl+C: 会立即终止进程,通过ps命令发现hello进程被回收。

在hello程序运行期间可能遭遇多种异常场景:其一为中断,这类异常通常由外部I/O设备触发,例如硬件外设发送的信号可能中断程序执行流程;其二为陷阱,属于程序主动触发的可控异常,如hello调用sleep函数时,系统会通过陷阱机制将进程转入内核态以完成休眠操作;其三为故障,典型如缺页异常,当程序访问未映射至物理内存的虚拟地址时,操作系统会捕获该异常并触发页表更新或磁盘I/O操作;其四为终止类错误,例如DRAM/SRAM存储器出现奇偶校验错误导致不可恢复的数据损坏时,系统会直接终止进程。当发生缺页故障时,操作系统会向hello进程发送SIGSEGV信号,导致进程以段错误退出;若main函数参数个数非3,进程会以状态码1终止并触发SIGCHLD信号,父进程默认会忽略该信号。正常执行时,若输入命令为./hello 2023112664 李懿也,程序会输出10次指定内容,并在用户输入字符串后结束。用户交互过程中,按下Ctrl-C会触发SIGINT信号终止进程,此时ps命令无法查询到该进程;按下Ctrl-Z会将进程挂起至后台,通过ps命令可观察到其处于停止状态,通过jobs命令可查看其后台作业编号,通过fg <编号>命令可将其恢复至前台继续执行;按下回车键时,输入内容会被缓存至标准输入缓冲区,当程序调用getchar时会读取缓冲区内容,若未输入有效字符则可能读入空字符串并正常终止;用户随机输入时,字符同样被缓存至缓冲区,程序会持续读取直至遇到换行符,多余字符将作为后续shell命令的输入,最终程序完成执行后由系统回收资源。
6.7本章小结
本章阐述了进程的定义与功能,分析了shell的命令处理机制,详细描述了hello的fork创建、execve加载、执行流程及异常信号处理,揭示了进程管理的核心机制与操作系统支持。
(第6章2分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:逻辑地址(相对地址)由段基址和段内偏移组成,表示程序使用的地址,需通过地址转换生成物理地址,通常以段地址:偏移量形式表示。
线性地址(虚拟地址):线性地址是逻辑地址到物理地址的中间层,32位地址支持4GB空间。分页机制下,地址分为页号和偏移量(如4KB页:高10位页目录,中间10位页表,低12位偏移)。若无分页,线性地址即物理地址。
物理地址:物理地址是CPU地址总线上的实际内存地址,从0开始线性递增,以字节为单位。MMU将虚拟地址转换为物理地址,确保精确寻址内存单元。
7.2 Intel逻辑地址到线性地址的变换-段式管理
逻辑地址由段选择符(16位,前13位为索引,T1指明GDT/LDT)和偏移量组成。段描述符(8字节,含Base字段)存储于GDT(全局描述符表)或LDT(局部描述符表),描述段的线性地址起点。转换步骤:
根据T1(0为GDT,1为LDT)定位描述符表,地址存于gdtr或ldtr寄存器。用段选择符前13位索引段描述符,获取Base地址。Base加偏移量生成线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
分页机制实现虚拟地址到物理地址的转换。虚拟内存按虚拟页(4KB)组织,物理内存按物理页(页帧)划分。MMU通过页表(由操作系统管理)动态翻译地址,页表条目(PTE)包含有效位和地址字段,有效位为1时指向物理页基址,否则表示未分配或在磁盘。
CPU生成虚拟地址,MMU用虚拟页号(VPN)查询PTE。若命中,PTE提供物理页号(PPN),与虚拟页偏移(VPO)组合生成物理地址;若未命中,触发缺页故障,加载磁盘页面到内存。
7.4 TLB与四级页表支持下的VA到PA的变换
以Intel Core i7为例,支持48位虚拟地址、52位物理地址,4KB页,四级页表(每级512条目,9位索引),CR3指向第一级页表,TLB(4路16组)缓存PTE。
转换过程:虚拟地址(36位VPN+12位VPO)送至MMU。TLB用4位TLBI(VPN低4位)定位组,32位TLBT匹配标记。若命中,获取PPN;未命中,MMU按VPN1-4逐级查询页表,获取PPN,更新TLB。PPN与VPO组合成物理地址。在hello中,访问.text节(如0x4010d0)通过TLB快速获取PPN,若未命中,查询四级页表确保地址转换。
7.5 三级Cache支持下的物理内存访问
Cache组织为S=2^s组,每组E行,每行B=2^b字节,含有效位和标记位,总大小C=S×E×B。物理地址分标记位、组索引、块偏移。
访问过程:L1 Cache(64组,8行,64字节/块):6位组索引定位组,40位标记匹配行。命中时,6位块偏移提取数据;未命中,从L2或主存获取,替换L1块(LRU策略)。在hello中,访问.text指令(如0x4010d0)通过L1 Cache快速获取,若未命中,逐级访问L2、L3或主存。
7.6 hello进程fork时的内存映射
调用fork时,内核为子进程分配唯一PID,复制父进程的mm_struct、区域结构和页表,标记页面为只读,区域为私有写时复制,确保父子进程内存独立。
7.7 hello进程execve时的内存映射
execve加载hello,执行以下步骤:
删除当前进程用户区域的现有结构。
创建新区域(代码、数据、bss、栈),映射hello的.text和.data节,bss和栈初始化为零,标记为私有写时复制。
映射共享库(如libc.so)到共享区域。
设置程序计数器指向代码入口。
7.8 缺页故障与缺页中断处理
缺页故障:指令引用虚拟地址,物理页不在内存时触发。MMU通过PTE定位磁盘页面,加载到内存,更新PTE,重试指令。
处理过程:
CPU生成虚拟地址,MMU查询PTE。
有效位为0,触发异常。
选择牺牲页,换出至磁盘(若修改)。
加载新页面,更新PTE。
重试指令,命中内存。
在hello中,访问未缓存的.data地址可能触发缺页,内核加载页面后继续执行。
7.9动态存储分配管理
动态分配器管理堆(紧接未初始化数据区,向上增长,brk指向顶部),维护已分配和空闲块。显式分配器(如malloc/free)由程序控制释放;隐式分配器(如垃圾收集)自动回收。
方法与策略:
隐式空闲链表:
块包含头部(大小、分配状态)、有效载荷、填充,堆为块序列,终止于零大小头部。
分配:搜索空闲块(首次/最佳适配),分割大块减少碎片。
扩展:若无合适块,调用sbrk扩展堆。
合并:释放块时合并相邻空闲块,边界标记(脚部复制头部)支持常数时间合并。
显式空闲链表:
空闲块存储前驱/后继指针,形成双向链表。
LIFO顺序下,分配和合并为常数时间;地址顺序提高内存利用率,释放需线性时间。
In hello, printf调用malloc分配缓冲区,采用隐式空闲链表,搜索空闲块,必要时扩展堆,确保动态内存分配高效。
Printf会调用malloc,请简述动态内存管理的基本方法与策略。(此节课堂没有讲授,选做,不算分)
7.10本章小结
本章分析了hello的存储管理,涵盖逻辑、线性、物理地址转换,TLB与四级页表优化,Cache访问机制,fork和execve内存映射,缺页故障处理及动态内存分配原理,揭示了内存管理的核心机制。
(以下格式自行编排,编辑时删除)
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
Linux将I/O设备(如磁盘、终端、网络)建模为文件,所有输入输出操作视为文件读写。这种统一抽象通过Unix I/O接口实现,确保操作简洁一致。
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
接口:
1.打开文件:内核分配描述符(非负整数)标识文件,记录文件状态。
2.初始文件:进程启动时具有标准输入(0)、输出(1)、错误(2)。
3.文件位置:内核维护文件偏移量k(初始为0),可通过seek修改。
4.读写操作:读从k复制字节到内存,写从内存复制到k,更新k。读到文件末尾返回EOF。
5.关闭文件:释放文件数据结构,回收描述符,进程终止时自动关闭。
函数:
- int open(char* filename, int flags, mode_t mode):打开或创建文件,返回最小未用描述符,flags指定访问模式,mode设置权限。
- int close(int fd):关闭描述符,重复关闭会报错。
- ssize_t read(int fd, void *buf, size_t n):从fd读取最多n字节到buf,返回读取字节数,0表示EOF,-1表示错误。
- ssize_t write(int fd, const void *buf, size_t n):将buf中最多n字节写入fd,返回写入字节数。
8.3 printf的实现分析
Windows下printf实现:
c
Copy
int printf(const char *fmt, ...)
{
int len;
char buffer[256];
va_list args = (va_list)((char*)&fmt + 4); // 定位可变参数
len = vsprintf(buffer, fmt, args); // 格式化字符串
write(buffer, len); // 输出到终端
return len;
}
va_list为字符指针,指向可变参数。vsprintf解析格式字符串fmt,根据%s、%x等格式化参数,生成字符串存入buffer,返回字符串长度。
write实现:将buffer中len字节写入终端,参数存入寄存器(ebx为首地址,ecx为字节数),通过int INT_VECTOR_SYS_CALL调用sys_call。
sys_call实现:将字符串从寄存器复制到显卡显存(存储ASCII码),字符驱动程序根据ASCII在字模库查找点阵,存入VRAM,显示器按刷新频率读取,显示字符串。Linux下printf实现类似,不再赘述。
[转]printf 函数实现的深入剖析 - Pianistx - 博客园
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
键盘中断处理:用户按键触发键盘接口生成扫描码,产生中断,中断处理程序将扫描码转换为ASCII码,存入系统键盘缓冲区。
getchar源码:
c
Copy
int getchar(void)
{
static char buf[BUFSIZ];
static char *ptr = buf;
static int n = 0;
if (n == 0)
{
n = read(0, buf, BUFSIZ);
ptr = buf;
}
return (n-- > 0) ? (unsigned char)*ptr++ : EOF;
}
getchar通过read系统调用从键盘缓冲区读取ASCII码,直至回车,返回首个字符。
8.5 本章小结异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章介绍了Linux的IO设备管理方法及Unix I/O接口,分析了printf和getchar的底层实现原理,阐明了IO操作的统一性和高效性。(第8章 选做 0分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
从编写到终止的完整过程:
- 源代码编写:
hello.c以C语言编写,包含main函数,接受命令行参数argc和argv,调用printf输出字符串,调用sleep暂停执行,调用getchar读取输入,最终终止。 - 预处理:
编译器(gcc)运行预处理器(cpp),处理#include <stdio.h>等指令,展开标准库头文件,生成预处理文件hello.i,包含printf等函数声明。 - 编译:
编译器将hello.i翻译为汇编代码hello.s,将C语言结构(如循环、函数调用)转换为x86_64指令,使用符号标签(如.L1)表示跳转目标。 - 汇编:
汇编器(as)将hello.s转换为机器代码,生成可重定位目标文件hello.o(ELF格式),包含.text(代码段,虚拟地址0x4010d0)、.data(初始化数据,0x404040)、.rodata(只读数据)等节,符号表记录函数和变量引用,重定位条目标记需调整的地址。 - 链接:
链接器(ld)合并hello.o与标准库(如libc.o),解析符号引用,分配运行时虚拟地址,生成可执行文件hello(ELF格式)。程序头部表定义内存映射,重定位节(如.rel.text)指导地址修正。 - 加载:
shell(bash)解析命令./hello 1190200109 刘文卓,识别为非内置命令,调用fork创建子进程,复制shell的虚拟地址空间(代码、数据、栈、共享库),分配唯一PID。子进程通过execve加载hello,映射.text、 .data等节到虚拟地址空间,设置程序计数器指向main。 - 动态链接:
动态链接器(ld-linux.so)解析printf、sleep等共享库函数,更新全局偏移表(GOT,地址0x404000)和过程链接表(PLT)。如printf的GOT条目初始指向PLT,运行时更新为libc.so中的实际地址。 - 执行:
CPU在用户模式执行hello的逻辑控制流,运行main,通过printf输出10行“Hello 1190200109 刘文卓”,调用sleep进入内核模式处理休眠,调用getchar读取键盘输入。内核通过上下文切换调度多进程。 - 内存管理:
虚拟地址通过四级页表和TLB转换为物理地址,访问.text、argv等数据。缺页故障触发内核从磁盘加载页面。三级缓存(L1/L2/L3)优化物理内存访问,使用LRU替换策略处理未命中。 - I/O操作:
printf通过vsprintf格式化字符串,write系统调用将数据写入显存,显示器渲染输出。getchar通过read从键盘缓冲区读取ASCII码,响应用户输入。 - 异常与信号处理:
键盘中断(如Ctrl-C触发SIGINT)终止进程,Ctrl-Z触发SIGTSTP挂起进程,缺页故障触发SIGSEGV。若argc != 5,程序以退出码1终止,发送SIGCHLD给shell。 - 终止:
main返回后,调用exit终止进程,内核回收资源,关闭文件描述符,shell通过waitpid回收子进程。
对计算机系统设计与实现的感悟与创新理念
hello程序的执行过程展现了计算机系统设计的精妙之处,其层次化架构将复杂性分解为多个抽象层,从高级语言到机器指令、从虚拟地址到物理内存、从用户模式到内核模式,每一层都以简洁的接口隐藏底层细节,为程序员提供了高效、可靠的开发环境。hello的编译过程(预处理、编译、汇编、链接)体现了工具链的模块化设计,将C代码逐步转化为可执行文件,优化了开发效率。链接阶段通过静态和动态链接整合代码,平衡了模块化与性能。加载和执行阶段,fork和execve展示了进程管理的灵活性,虚拟内存通过页表和TLB实现了地址隔离与高效转换,三级缓存加速了数据访问,动态链接器通过延迟绑定优化了程序启动时间。I/O操作中,printf和getchar通过系统调用和中断处理实现了用户与硬件的交互,异常和信号机制(如SIGINT、SIGTSTP)确保了系统的健壮性。然而,这一过程也暴露了性能瓶颈,如上下文切换的延迟、TLB未命中、缓存未命中以及动态链接的首次调用开销,提示我们在设计上需权衡抽象的便捷性与执行效率。
基于此,我提出以下创新理念与实现方法:首先,针对动态链接的延迟绑定开销,可设计自适应动态链接机制,通过运行时分析或静态预测高频调用的库函数,提前解析并填充GOT,减少首次调用延迟,低频函数保留延迟绑定以节省内存。其次,针对hello的固定4KB页面管理,提出混合页面大小模型,结合小页面(512B)用于栈和堆分配、大页面(2MB)用于代码和大数据,内核根据访问模式动态调整页面类型,减少TLB未命中和碎片。第三,针对缓存性能,设计基于机器学习的智能预取机制,CPU集成轻量神经网络,分析指令和数据访问模式,预测下一块数据并提前加载到L1缓存,提升命中率。第四,改进异常处理,设计上下文感知信号框架,内核根据程序状态(如循环深度、I/O进度)动态选择信号响应策略,例如延迟SIGINT终止以保存数据,提高用户体验。第五,在动态内存分配方面,针对printf调用的malloc碎片问题,提出分层堆管理,分别为小对象、数组和大块分配专用子堆,结合垃圾收集和手动释放,编译器分析对象生命周期以优化分配策略。第六,针对getchar的同步I/O阻塞,设计异步I/O框架,用户态维护线程池预处理I/O请求,内核通过事件通知异步返回数据,减少上下文切换开销。这些创新可通过编译器扩展、内核模块修改和硬件加速单元实现,如在gcc中添加调用频率分析、在内核中扩展页表结构、在CPU中集成预测单元,从而在保持系统模块化的同时提升性能和灵活性,为现代计算机系统设计注入新的活力。
(结论0分,缺失-1分)
附件
列出所有的中间产物的文件名,并予以说明起作用。
hello.c:原始hello程序的C语言代码
hello.i:预处理过后的hello代码
hello.s:由预处理代码生成的汇编代码
hello.o:二进制目标代码
hello:进行链接后的可执行程序
hello_disassembly.txt:反汇编hello.o得到的反汇编文件
helloobjdump.txt:反汇编hello可执行文件得到的反汇编文件
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
[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.
(参考文献0分,缺失 -1分)
第1章 概述
1.1 Hello简介
用户在编辑器中编写hello.c,包含经典的printf("Hello, World!\n")代码,生成源代码文件。这是Hello的“出生”,即程序的静态表示。源代码经过预处理(处理#include、宏等)、编译(生成汇编代码)、汇编(生成目标代码)、链接(生成可执行文件),形成可执行的二进制文件hello。在壳(Bash)中运行./hello,操作系统(OS)通过fork()系统调用创建一个新进程,复制父进程的上下文。新进程通过execve()加载hello的可执行文件,替换进程的内存映像,初始化程序计数器(PC)、堆栈等。OS的存储管理为进程分配虚拟地址空间(VA),通过内存管理单元(MMU)将虚拟地址映射到物理地址(PA),涉及页表(Page Table)、TLB(Translation Lookaside Buffer)等机制。OS的进程管理分配时间片,调度Hello进程在CPU上运行。CPU通过取指、译码、执行等流水线步骤运行程序指令。存储管理确保代码和数据通过Cache(L1/L2/L3)、主存(RAM)高效访问,可能涉及页面置换和Pagefile。IO管理处理程序的输入输出,printf通过系统调用与设备驱动交互,将“Hello, World!”输出到屏幕(显卡、显示器)。Hello进程完成输出后退出,OS回收其资源(内存、文件描述符等),通过exit()系统调用“收尸”,进程状态清空,归于无。Hello最初仅为用户脑海中的想法,敲入hello.c后成为静态程序,尚无生命。通过编译、链接、加载、执行,Hello从静态程序转变为动态进程,在OS和硬件(CPU、RAM、IO)的支持下短暂“绽放”,输出“Hello, World!”。进程终止后,Hello的运行时状态被OS清理,内存释放,文件关闭,程序回归静态状态或被遗忘,如“挥一挥手,不带走一片云彩”。
1.2 环境与工具
硬件环境:X86-64 Intel i7 10th 16 GB RAM 512 GHD Disk,软件环境:Windows 10 VMware 16 Ubuntu 20.04 LTS MobaXTerm 开发调试工具:GDB,EDB,Visual Studio Code,Vim,gcc
1.3 中间结果
hello.c:原始hello程序的C语言代码
hello.i:预处理过后的hello代码
hello.s:由预处理代码生成的汇编代码
hello.o:二进制目标代码
hello:进行链接后的可执行程序
hello_disassembly.txt:反汇编hello.o得到的反汇编文件
helloobjdump.txt:反汇编hello可执行文件得到的反汇编文件
1.4 本章小结
Hello的P2P过程是从静态的hello.c到动态进程的转变,涉及编辑、编译、链接、进程创建、调度、内存管理、IO操作等计算机系统核心机制。O2O则体现了其生命周期的短暂性,从无到有(创建、运行),再从有到无(终止、资源回收),展现了程序在计算机系统中的完整旅程。CS(计算机系统)见证了Hello的“生与死”,而其简单背后蕴含了OS、硬件、编译器等的复杂协作。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
预处理是编程语言(如C、C++)在正式编译前的一个必要阶段,由预处理器负责执行。它通过处理源代码中以#开头的预处理指令(如宏定义、文件包含、条件编译等),对源代码进行文本层面的修改和扩展,生成供编译器进一步处理的中间代码。
作用:具体来说,预处理通过#include指令将头文件内容插入到当前源文件中,实现代码复用和模块化开发;利用#define定义宏,简化常量使用或代码片段的重复书写;通过条件编译指令(如#ifdef)控制代码的编译范围,适应不同环境需求。预处理阶段不涉及语法分析和代码逻辑处理,仅完成文本替换和文件整合,最终生成供编译器进一步处理的中间代码。这一过程不仅提升了开发效率,还增强了代码的可维护性和跨平台适应性。
2.2在Ubuntu下预处理的命令

应截图,展示预处理过程!
2.3 Hello的预处理结果解析

经过预处理后,hello.c文件生成了hello.i文件,打开hello.i文件后可见文件从原本的28行扩展到了3000多行。原文件中包含的头文件stdio.h、unistd.h、stdlib.h的内容被插入到了该文件中。
2.4 本章小结
预处理作为编译流程的前置阶段,通过预处理器对源代码中以#开头的指令进行文本级操作,为后续编译构建基础。其核心机制包括文件包含指令(如#include)实现代码模块的整合与复用,宏定义指令(如#define)完成常量替换和代码片段的抽象化表达,以及条件编译指令(如#ifdef)提供编译环境的动态适配能力。这些功能不仅简化了重复代码的编写,还通过集中化管理提升了代码的可维护性,同时借助条件编译机制有效屏蔽了跨平台开发中的差异性需求。预处理本质上是源代码的预处理层扩展,通过文本替换与指令解析将程序逻辑与编译配置解耦,最终生成可直接供编译器处理的中间代码,为高效、灵活的软件开发提供了底层支持。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
编译是指编译器做词法分析、语法分析、语义分析等,在检查无错误后,将代码翻译成汇编语言的过程。编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。
作用:进行词法分析和语法分析,分析过程中发现有语法错误,给出提示信
息。将文本文件转化为汇编语言的形式,为后续的汇编操作奠定基础。汇编语言是非常有用的,它为不同的高级语言的不同编译器提供了通用的输出语言。最后生成一个汇编语言程序.s文件。除了基本功能之外,编译程序还具备调试措施、修改手段、覆盖处理、目标程序优化、不同语言合用等重要功能。
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
3.2 在Ubuntu下编译的命令

应截图,展示编译过程!
3.3 Hello的编译结果解析

(以下格式自行编排,编辑时删除)
3.3.1 数据类型
局部变量:在main函数中,局部变量i(int类型)由编译器分配在栈上,通常位于栈帧的偏移位置(如%rbp-4)。初始化时,编译器使用mov指令将i赋值为0(如mov DWORD PTR [rbp-4], 0),确保其在循环中使用前有明确值。
全局变量:程序定义了全局变量sleepsecs(int类型),赋值为2,存储在.rodata段中,作为只读数据。编译器在汇编代码中通过符号引用(如.LC0)访问其值。
立即数:立即数(如循环条件中的10、赋值中的0)直接嵌入汇编指令中,例如cmp DWORD PTR [rbp-4], 10中的10,作为常量参与比较或算术操作。
参数 int argc:argc作为main函数的第一个参数,初始存储在寄存器%edi(x86_64调用约定),随后被保存到栈上(如%rbp-20),以便在条件判断中使用,如cmp DWORD PTR [rbp-20], 5。
数组 char *argv[]:argv是main函数的第二个参数,一个指向字符串指针的数组(char **),初始存储在寄存器%rsi,并保存到栈上(如%rbp-32)。访问argv[1]到argv[3]时,编译器通过偏移计算(如mov rax, [rsi+8])获取字符串地址。
字符串:程序中的字符串常量(如"Hello %s %s %s\n"和"用法: Hello 学号 姓名 手机号 秒数!\n")存储在.rodata段,编译器生成地址引用(如lea rdi, .LC0)供printf使用。
3.3.2 赋值
赋值操作包括全局变量sleepsecs赋值为2和局部变量i赋值为0。编译器为sleepsecs在.rodata段分配固定值2,通过符号引用访问。为i赋值0则使用movl指令(如movl $0, [rbp-4]),直接将立即数0写入栈上变量位置。
3.3.3 类型转换
程序中sleepsecs定义为int,但初始化为2.5(浮点数)。编译器执行隐式类型转换,将2.5截断为2(int),并存储在.rodata段。这种转换在汇编中体现为直接使用整数值2(如mov eax, 2),忽略小数部分。
3.3.4 算术操作
在for循环中,i++是主要的算术操作。编译器将其转换为addl指令(如add DWORD PTR [rbp-4], 1),每次将i的值增加1,更新存储在栈上的值。
3.3.5 关系操作
关系操作1:argc != 5用于检查输入参数数量。编译器生成cmpl指令比较argc(如[rbp-20])与立即数5(如cmpl $5, [rbp-20]),设置条件码,并通过jne指令跳转到错误处理块(调用printf和exit)。
关系操作2:i < 10控制for循环。编译器使用cmpl指令比较i(如[rbp-4])与10(如cmpl $10, [rbp-4]),根据条件码通过jl或jle指令决定是否跳转到循环体。
3.3.6 数组操作
char *argv[]是一个指针数组,存储在栈上(如%rbp-32)。访问argv[1]、argv[2]、argv[3]时,编译器计算偏移量(如[rsi+8]、[rsi+16]),使用movq指令获取字符串指针地址,传递给printf作为参数。
3.3.7 控制转移
控制转移1:if(argc != 5)通过cmpl和jne实现。编译器比较argc与5,设置条件码,若不相等则跳转到错误处理代码,执行printf和exit。
控制转移2:for(i = 0; i < 10; i++)循环通过初始化(movl $0, [rbp-4])、比较(cmpl $10, [rbp-4])和跳转(jl .Lloop)实现。循环体结束后,i++更新计数器,jmp回跳检查条件。
3.3.8 函数操作
参数传递:main函数接收argc(%edi)和argv(%rsi),保存到栈上(如%rbp-20和%rbp-32)。printf调用传递格式字符串(.rodata地址)和argv[1-3](通过%rdi、%rsi、%rdx等)。sleep调用传递sleepsecs(从.rodata加载到%rdi)。atoi和getchar类似,参数通过寄存器传递。
函数调用:printf、sleep、atoi、getchar和exit通过call指令调用。编译器按System V ABI约定准备参数(如mov rdi, rax),然后调用函数(如call printf)。
函数返回:main函数返回0(mov eax, 0),存储在%rax寄存器中。其他函数(如atoi、getchar)的返回值同样通过%rax返回,供后续使用(如sleep使用atoi的返回值)。
通过以上处理,编译器将C语言的类型和操作转换为汇编指令,确保程序逻辑在底层高效执行。
3.4 本章小结
本章通过分析hello.c的编译过程,展示了C语言数据类型(如int、char *argv[]、字符串)和操作(如赋值、循环、函数调用)如何转化为汇编代码。编译器将变量分配在栈或.rodata段,处理关系操作、控制转移和参数传递,生成高效指令。程序的P2P和O2O旅程体现了计算机系统从源代码到进程执行的协同机制。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
汇编语言是一种低级语言,使用助记符表示机器指令,直接与CPU指令集对应,便于程序员控制硬件。汇编的作用在于将高级语言(如C)编译生成的中间表示转换为机器码,优化性能,管理寄存器、内存和指令流,支持底层操作如设备驱动和操作系统开发,同时为调试和逆向工程提供桥梁。
注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。
4.2 在Ubuntu下汇编的命令

应截图,展示汇编过程!
4.3 可重定位目标elf格式



使用readelf -a hello.o > hellooelf.txt命令生成hello.o的ELF文件信息并重定向到hellooelf.txt文件中。以下对ELF文件的各个部分进行分析,基于其结构和功能,结合hello.o的特点进行简要说明。
1. ELF头
ELF头是目标文件的起始部分,以16字节的Magic序列开头,标识文件的字长和字节序(如大端或小端)。它还包含关键元数据,如ELF头大小、文件类型(此处为可重定位目标文件)、目标机器架构、节头部表的文件偏移量,以及节头部表中条目的数量和大小等。根据ELF头信息,hello.o为可重定位文件,包含14个节,节的具体位置和大小由节头部表进一步描述。
2. 节头部表
节头部表记录了目标文件中各节的属性,包括节的类型、文件偏移量、大小等。作为可重定位文件,hello.o的各节偏移从0开始,供链接器使用。通过节头部表,可定位每个节的起始地址和占用空间。分析显示,代码段(.text)具有可执行属性但不可写;数据段(.data)和只读数据段(.rodata)不可执行,其中.rodata还禁止写入,保障数据完整性。
3. 重定位节
重定位节记录.text节中需调整的引用位置信息,用于链接器在合并目标文件时修改这些引用。重定位节包含以下字段:
偏移量(Offset):指明需修改的引用在.text节中的位置。
信息(Info):分为符号索引(symbol)和重定位类型(type),前者指引用指向的符号,后者指导链接器如何调整引用。
类型(Type):定义重定位方式,如R_X86_64_PC32(32位PC相对寻址)和R_X86_64_32(32位绝对寻址)。
符号名称(Sym.Name):指明重定位目标的符号名。
加数(Addend):有符号常数,用于某些重定位类型调整引用值的偏移。
4. 符号表
符号表存储程序中定义或引用的函数和全局变量信息,排除局部变量。包含以下字段:
值(Value):符号相对于所在节的偏移量,在可执行文件中可能为运行时绝对地址。
大小(Size):符号对应目标的大小。
类型(Type):标识符号类别,如数据、函数、文件、节或未定义(NOTYPE)。
绑定(Bind):区分符号是局部(local)还是全局(global)。
名称(Name):符号的名称,用于链接和调试。
通过以上分析,ELF文件的结构清晰展现了hello.o的组织方式,为后续链接和生成可执行文件提供了基础。
4.4 Hello.o的结果解析


objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
差异分析
跳转指令:在hello.s中,跳转指令(如jmp、jne)使用符号标签(如.L1、.L2)作为目标,代表代码中的逻辑位置,由汇编器和链接器解析为实际地址。而disa_hello.s(反汇编生成)中,跳转指令直接指向具体指令地址,地址以相对于函数起始地址的偏移量形式标注(如<+0x10>),通过程序计数器(PC)值与目标地址的偏移计算得出,反映了反汇编时已解析的内存布局。
函数调用:hello.s中的函数调用(如call printf)仅使用函数名,依赖链接器后续填充实际地址。而disa_hello.s中,call指令后不仅包含函数名,还附带地址信息(如0x400123)和重定位条目类型(如R_X86_64_PLT32),表明反汇编时已解析函数的符号引用或重定位信息,提供了更明确的调用目标细节。
4.5 本章小结
本章通过分析hello.c从源代码到ELF目标文件hello.o的编译过程,阐明了C语言数据类型(如int、char *argv[])和操作(如赋值、循环、函数调用)如何转换为汇编指令。ELF文件结构(包括ELF头、节头部表、重定位节、符号表)为链接提供了关键信息。hello.s与disa_hello.s的跳转指令和函数调用差异,体现了汇编与反汇编在符号处理和地址解析上的不同,揭示了程序从高级语言到机器码的转换机制。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
在软件开发流程中,链接是整合不同代码与数据模块以生成可执行文件的核心步骤。该过程将分散的编译单元(如目标文件)合并为单一文件,确保程序能够被操作系统加载至内存并运行。链接的时机可分为三类:编译时链接(在源代码转换为机器码时同步完成)、加载时链接(由加载器在程序载入内存前动态执行)以及运行时链接(由应用程序在执行过程中按需触发)。早期计算机系统依赖人工干预完成链接,而现代系统则通过自动化工具(即链接器)实现高效处理。
以hello程序为例,其调用的printf函数属于标准C库(如libc)的组成部分。该函数已预先编译为独立的目标文件(如printf.o),需通过链接器与hello程序的主目标文件(如hello.o)合并。链接器会解析函数调用与符号引用关系,消除未定义引用,并生成包含完整地址映射的可执行文件(如hello)。此文件可直接由操作系统加载至内存执行,其内部结构包含代码段、数据段及必要的运行时信息,确保程序能够正确调用外部库函数并完成预期功能。
注意:这儿的链接是指从 hello.o 到hello生成过程。
5.2 在Ubuntu下链接的命令
ld -o hello -dynamic-linker /lib64/ld-linux-aarch64.so.2 /usr/lib/aarch64-linux-gnu/crt1.o /usr/lib/aarch64-linux-gnu/crti.o hello.o /usr/lib/aarch64-linux-gnu/libc.so /usr/lib/aarch64-linux-gnu/crtn.o使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
5.3 可执行目标文件hello的格式
通过执行readelf -a hello > helloelf.txt,生成hello可执行文件的ELF信息并重定向至helloelf.txt。以下对ELF文件的各部分进行分析。
1. ELF头
ELF头以16字节Magic序列开头,定义了文件的字节序和字长,包含文件类型、入口点地址、程序头部表和节头部表的偏移及条目信息等。相较于hello.o(可重定位文件),hello为可执行文件,入口点地址、程序头部表起始位置及大小非零,节头部表起始位置不同,且包含27个节。
2. 节头部表
节头部表记录hello中各节的元数据,如名称、类型、虚拟地址、文件偏移和大小等。虚拟地址指明节加载到内存的起始位置,偏移量指示节在文件中的位置。可通过工具如HexEdit根据这些信息定位节的起始和大小。
3. 程序头部表
程序头部表描述可执行文件如何映射到内存段,包含每个程序头的偏移量、虚拟地址、对齐要求、文件及内存中的段大小,以及运行时权限(如读、写、执行)。它指导操作系统加载程序到虚拟地址空间。
4. 段节
段节定义了程序在内存中的布局,包含代码、数据等内容,由程序头部表描述其映射关系。
5. 动态节
动态节存储动态链接所需的信息,如共享库依赖和符号解析数据,供动态链接器在程序加载时使用。
6. 重定位节
重定位节列出.text节中需调整的函数引用信息,包含偏移量、符号名和重定位类型等,供链接器在合并文件时修改引用地址,确保函数调用指向正确位置。
7. 动态符号表
动态符号表记录与动态链接相关的导入和导出符号,排除模块内部符号,用于运行时符号解析。
8. 符号表
符号表存储程序定义和引用的函数及全局变量信息,包括符号的偏移、类型、大小和绑定属性(局部或全局),以数组索引形式组织,辅助链接和调试。
5.4 hello的虚拟地址空间
使用edb查看hello的内存区域,虚拟地址空间从0x400000到0x405000。根据节头部表,.text节地址为0x4010d0(大小0x135),.data节地址为0x404040(大小0x8),.rodata节地址为0x402000(大小0x2f)。通过edb的data dump,可验证各节的地址和大小,其他节类似查询。
程序头部表进一步描述段信息,如:
PHDR:存储程序头部表。
INTERP:指定动态链接器(如ld-linux.so),如地址0x4002e0,大小0x1c。
LOAD:映射代码、常量数据等段。
DYNAMIC:包含动态链接信息。
NOTE:存储辅助信息。
GNU_STACK:定义栈权限(如是否可执行)。
GNU_RELRO:标记重定位后需设为只读的内存区域。
5.5 链接的重定位过程分析



使用objdump -d -r hello > helloobjdump.txt生成反汇编代码。相比hello.o(地址为0,未重定位),hello包含确定的虚拟地址,完成重定位,新增.init(初始化)、.plt(动态链接表)、.fini(终止)等节。链接过程合并可重定位文件,包含:
符号解析:关联符号引用与定义。
重定位:
合并同类型节,分配运行时地址。
根据.rel.text和.rel.data中的重定位条目(如偏移、类型R_X86_64_32/R_X86_64_PC32)调整引用地址。
5.6 hello的执行流程

![]()

![]()



(以下格式自行编排,编辑时删除)
hello执行涉及以下顺序:
ld-2.31.so!_dl_start:启动链接器。
ld-2.31.so!_dl_init:初始化环境。
hello!_start:程序入口。
ld-2.31.so!_libc_start_main:调用main。
ld-2.31.so!_cxa_atexit:注册终止函数。
ld-2.31.so!_libc_csu_init:初始化C库。
ld-2.31.so!_setjmp:设置跳转。
hello!main:用户代码。
ld-2.31.so!exit:程序退出。
5.7 Hello的动态链接分析
动态链接在运行时组合模块,延迟符号解析至加载或首次调用。编译器为共享库函数(如puts)生成重定位记录,由动态链接器解析。GNU采用延迟绑定,通过GOT(全局偏移表,地址0x404000)和PLT(过程链接表)实现:
PLT:16字节条目,PLT[0]跳转至链接器,PLT[1]调用__libc_start_main,后续条目对应库函数。
GOT:8字节地址,GOT[0-1]存储链接器信息,GOT[2]为ld-linux.so入口,其余对应函数地址。
edb显示,调用_dl_init前,0x404008后16字节为0;调用后,更新为链接器信息和入口点。puts调用前,GOT条目指向PLT第二条指令;调用后,更新为实际函数地址。
5.8 本章小结
本章分析了链接原理,详述hello的ELF格式(头、节、程序头等)、虚拟地址空间(0x400000至0x405000)、重定位(符号解析和地址分配)、执行流程(从_dl_start到exit)及动态链接(GOT/PLT延迟绑定),揭示了可执行文件生成与运行的完整过程。
(以下格式自行编排,编辑时删除)
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程的概念:
进程是程序在执行时的实例,每个程序运行在特定进程的上下文中。上下文包括程序的代码、数据、栈、寄存器、程序计数器(PC)、环境变量及打开的文件描述符,共同构成程序运行所需的状态。
进程的作用:
进程为程序提供独立运行的假象,仿佛独占处理器和内存。处理器看似连续执行程序指令,代码和数据看似内存中唯一对象,这种抽象隔离了多程序运行的复杂性。
6.2 简述壳Shell-bash的作用与处理流程
作用:
Shell(如bash)是用户与内核交互的接口,接受用户命令并传递给内核执行。作为命令解释器,shell解析用户输入,将其转换为机器码执行。shell还支持编程语言,具备循环、分支等结构,允许用户编写脚本,功能等同于其他应用程序。
处理流程:
终端读取用户输入的命令行。
解析命令行,提取参数,构建execve所需的argv向量。
检查首参数是否为内置命令。
若为内置命令,直接执行;若非内置,调用fork创建子进程。
子进程通过execve加载并运行目标程序。
若命令无&(前台运行),shell用waitpid等待子进程结束;若有&(后台运行),shell直接返回。
6.3 Hello的fork进程创建过程
shell解析命令,识别为非内置命令,调用fork创建子进程。子进程复制父进程的虚拟地址空间(包括代码、数据、栈、共享库),但拥有独立副本和唯一PID。父子进程并发执行,内核可任意调度其指令流,父进程默认等待子进程完成。
6.4 Hello的execve过程
当通过系统调用创建子进程后,该子进程会调用execve函数以在当前执行环境中直接加载并运行目标程序hello。execve函数会解析传入的参数列表argv和环境变量列表envp,并尝试加载对应的可执行文件,若文件不存在或格式错误导致加载失败,函数才会返回错误码至调用方;否则其执行过程表现为单向不可逆的进程替换,即成功加载后不会返回调用点。
execve的核心作用在于原地替换当前进程的上下文:新程序hello的代码和数据会完全覆盖原进程的地址空间,但进程标识符(PID)保持不变,且所有已打开的文件描述符会被继承至新程序。在此过程中,新程序的栈段和堆段会被初始化为全零状态,而代码段和数据段则直接映射自磁盘上的可执行文件内容。加载时仅程序头信息(如段加载地址、权限标志等元数据)会被从磁盘复制到内存的对应区域,实际代码和数据则通过内存映射机制实现按需加载,从而避免不必要的磁盘I/O开销。这种原地替换机制使得程序能够高效完成热更新或进程功能切换,同时保持系统资源的连续性。
6.5 Hello的进程执行
进程为应用程序构建了双层抽象机制:其通过模拟独占CPU的假象形成逻辑控制流隔离性,使程序在运行时仿佛独占处理器资源,当调试器逐行执行时程序计数器按序遍历目标文件指令形成连续的PC值序列,若多个逻辑流在时间轴上重叠则构成并发流,而每个进程在CPU上的分时执行片段称为时间片;同时通过虚拟化技术实现私有地址空间隔离,进程为每个程序分配独立虚拟内存区域,使得程序默认仅能访问自身地址空间的数据,这种隔离通过内存管理单元的地址映射实现。为保障进程抽象的完整性,处理器通过用户模式和内核模式的特权级划分进行权限控制,用户模式下进程禁止执行特权指令和访问内核数据,而内核模式则提供完全权限,通常由系统调用或中断触发模式切换。进程切换依赖上下文保存与恢复机制,包括硬件寄存器状态、栈帧信息和内核数据结构等核心内容,当内核调度新进程时,会通过上下文切换保存当前进程状态、恢复目标进程上下文并切换地址空间完成控制权转移。以hello进程为例,其启动时通过execve加载程序并分配虚拟地址空间,执行用户输入时初始运行于用户模式,调用sleep或getchar等系统调用时会触发模式切换——内核处理休眠或输入请求后将进程状态改为等待并移出运行队列,待定时器或输入完成中断触发时,内核重新调度进程并恢复其上下文,最终在用户模式下继续执行,这一过程完整展示了进程抽象如何通过虚拟化、特权级管理和上下文切换机制实现多任务并发与状态透明保存。
。6.6 hello的异常与信号处理

乱按:Shell会将回车前输出的字符串当作命令。

Ctrl+C: 会立即终止进程,通过ps命令发现hello进程被回收。

在hello程序运行期间可能遭遇多种异常场景:其一为中断,这类异常通常由外部I/O设备触发,例如硬件外设发送的信号可能中断程序执行流程;其二为陷阱,属于程序主动触发的可控异常,如hello调用sleep函数时,系统会通过陷阱机制将进程转入内核态以完成休眠操作;其三为故障,典型如缺页异常,当程序访问未映射至物理内存的虚拟地址时,操作系统会捕获该异常并触发页表更新或磁盘I/O操作;其四为终止类错误,例如DRAM/SRAM存储器出现奇偶校验错误导致不可恢复的数据损坏时,系统会直接终止进程。当发生缺页故障时,操作系统会向hello进程发送SIGSEGV信号,导致进程以段错误退出;若main函数参数个数非3,进程会以状态码1终止并触发SIGCHLD信号,父进程默认会忽略该信号。正常执行时,若输入命令为./hello 2023112664 李懿也,程序会输出10次指定内容,并在用户输入字符串后结束。用户交互过程中,按下Ctrl-C会触发SIGINT信号终止进程,此时ps命令无法查询到该进程;按下Ctrl-Z会将进程挂起至后台,通过ps命令可观察到其处于停止状态,通过jobs命令可查看其后台作业编号,通过fg <编号>命令可将其恢复至前台继续执行;按下回车键时,输入内容会被缓存至标准输入缓冲区,当程序调用getchar时会读取缓冲区内容,若未输入有效字符则可能读入空字符串并正常终止;用户随机输入时,字符同样被缓存至缓冲区,程序会持续读取直至遇到换行符,多余字符将作为后续shell命令的输入,最终程序完成执行后由系统回收资源。
6.7本章小结
本章阐述了进程的定义与功能,分析了shell的命令处理机制,详细描述了hello的fork创建、execve加载、执行流程及异常信号处理,揭示了进程管理的核心机制与操作系统支持。
(第6章2分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:逻辑地址(相对地址)由段基址和段内偏移组成,表示程序使用的地址,需通过地址转换生成物理地址,通常以段地址:偏移量形式表示。
线性地址(虚拟地址):线性地址是逻辑地址到物理地址的中间层,32位地址支持4GB空间。分页机制下,地址分为页号和偏移量(如4KB页:高10位页目录,中间10位页表,低12位偏移)。若无分页,线性地址即物理地址。
物理地址:物理地址是CPU地址总线上的实际内存地址,从0开始线性递增,以字节为单位。MMU将虚拟地址转换为物理地址,确保精确寻址内存单元。
7.2 Intel逻辑地址到线性地址的变换-段式管理
逻辑地址由段选择符(16位,前13位为索引,T1指明GDT/LDT)和偏移量组成。段描述符(8字节,含Base字段)存储于GDT(全局描述符表)或LDT(局部描述符表),描述段的线性地址起点。转换步骤:
根据T1(0为GDT,1为LDT)定位描述符表,地址存于gdtr或ldtr寄存器。用段选择符前13位索引段描述符,获取Base地址。Base加偏移量生成线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
分页机制实现虚拟地址到物理地址的转换。虚拟内存按虚拟页(4KB)组织,物理内存按物理页(页帧)划分。MMU通过页表(由操作系统管理)动态翻译地址,页表条目(PTE)包含有效位和地址字段,有效位为1时指向物理页基址,否则表示未分配或在磁盘。
CPU生成虚拟地址,MMU用虚拟页号(VPN)查询PTE。若命中,PTE提供物理页号(PPN),与虚拟页偏移(VPO)组合生成物理地址;若未命中,触发缺页故障,加载磁盘页面到内存。
7.4 TLB与四级页表支持下的VA到PA的变换
以Intel Core i7为例,支持48位虚拟地址、52位物理地址,4KB页,四级页表(每级512条目,9位索引),CR3指向第一级页表,TLB(4路16组)缓存PTE。
转换过程:虚拟地址(36位VPN+12位VPO)送至MMU。TLB用4位TLBI(VPN低4位)定位组,32位TLBT匹配标记。若命中,获取PPN;未命中,MMU按VPN1-4逐级查询页表,获取PPN,更新TLB。PPN与VPO组合成物理地址。在hello中,访问.text节(如0x4010d0)通过TLB快速获取PPN,若未命中,查询四级页表确保地址转换。
7.5 三级Cache支持下的物理内存访问
Cache组织为S=2^s组,每组E行,每行B=2^b字节,含有效位和标记位,总大小C=S×E×B。物理地址分标记位、组索引、块偏移。
访问过程:L1 Cache(64组,8行,64字节/块):6位组索引定位组,40位标记匹配行。命中时,6位块偏移提取数据;未命中,从L2或主存获取,替换L1块(LRU策略)。在hello中,访问.text指令(如0x4010d0)通过L1 Cache快速获取,若未命中,逐级访问L2、L3或主存。
7.6 hello进程fork时的内存映射
调用fork时,内核为子进程分配唯一PID,复制父进程的mm_struct、区域结构和页表,标记页面为只读,区域为私有写时复制,确保父子进程内存独立。
7.7 hello进程execve时的内存映射
execve加载hello,执行以下步骤:
删除当前进程用户区域的现有结构。
创建新区域(代码、数据、bss、栈),映射hello的.text和.data节,bss和栈初始化为零,标记为私有写时复制。
映射共享库(如libc.so)到共享区域。
设置程序计数器指向代码入口。
7.8 缺页故障与缺页中断处理
缺页故障:指令引用虚拟地址,物理页不在内存时触发。MMU通过PTE定位磁盘页面,加载到内存,更新PTE,重试指令。
处理过程:
CPU生成虚拟地址,MMU查询PTE。
有效位为0,触发异常。
选择牺牲页,换出至磁盘(若修改)。
加载新页面,更新PTE。
重试指令,命中内存。
在hello中,访问未缓存的.data地址可能触发缺页,内核加载页面后继续执行。
7.9动态存储分配管理
动态分配器管理堆(紧接未初始化数据区,向上增长,brk指向顶部),维护已分配和空闲块。显式分配器(如malloc/free)由程序控制释放;隐式分配器(如垃圾收集)自动回收。
方法与策略:
隐式空闲链表:
块包含头部(大小、分配状态)、有效载荷、填充,堆为块序列,终止于零大小头部。
分配:搜索空闲块(首次/最佳适配),分割大块减少碎片。
扩展:若无合适块,调用sbrk扩展堆。
合并:释放块时合并相邻空闲块,边界标记(脚部复制头部)支持常数时间合并。
显式空闲链表:
空闲块存储前驱/后继指针,形成双向链表。
LIFO顺序下,分配和合并为常数时间;地址顺序提高内存利用率,释放需线性时间。
In hello, printf调用malloc分配缓冲区,采用隐式空闲链表,搜索空闲块,必要时扩展堆,确保动态内存分配高效。
Printf会调用malloc,请简述动态内存管理的基本方法与策略。(此节课堂没有讲授,选做,不算分)
7.10本章小结
本章分析了hello的存储管理,涵盖逻辑、线性、物理地址转换,TLB与四级页表优化,Cache访问机制,fork和execve内存映射,缺页故障处理及动态内存分配原理,揭示了内存管理的核心机制。
(以下格式自行编排,编辑时删除)
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
Linux将I/O设备(如磁盘、终端、网络)建模为文件,所有输入输出操作视为文件读写。这种统一抽象通过Unix I/O接口实现,确保操作简洁一致。
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
接口:
1.打开文件:内核分配描述符(非负整数)标识文件,记录文件状态。
2.初始文件:进程启动时具有标准输入(0)、输出(1)、错误(2)。
3.文件位置:内核维护文件偏移量k(初始为0),可通过seek修改。
4.读写操作:读从k复制字节到内存,写从内存复制到k,更新k。读到文件末尾返回EOF。
5.关闭文件:释放文件数据结构,回收描述符,进程终止时自动关闭。
函数:
- int open(char* filename, int flags, mode_t mode):打开或创建文件,返回最小未用描述符,flags指定访问模式,mode设置权限。
- int close(int fd):关闭描述符,重复关闭会报错。
- ssize_t read(int fd, void *buf, size_t n):从fd读取最多n字节到buf,返回读取字节数,0表示EOF,-1表示错误。
- ssize_t write(int fd, const void *buf, size_t n):将buf中最多n字节写入fd,返回写入字节数。
8.3 printf的实现分析
Windows下printf实现:
c
Copy
int printf(const char *fmt, ...)
{
int len;
char buffer[256];
va_list args = (va_list)((char*)&fmt + 4); // 定位可变参数
len = vsprintf(buffer, fmt, args); // 格式化字符串
write(buffer, len); // 输出到终端
return len;
}
va_list为字符指针,指向可变参数。vsprintf解析格式字符串fmt,根据%s、%x等格式化参数,生成字符串存入buffer,返回字符串长度。
write实现:将buffer中len字节写入终端,参数存入寄存器(ebx为首地址,ecx为字节数),通过int INT_VECTOR_SYS_CALL调用sys_call。
sys_call实现:将字符串从寄存器复制到显卡显存(存储ASCII码),字符驱动程序根据ASCII在字模库查找点阵,存入VRAM,显示器按刷新频率读取,显示字符串。Linux下printf实现类似,不再赘述。
[转]printf 函数实现的深入剖析 - Pianistx - 博客园
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
键盘中断处理:用户按键触发键盘接口生成扫描码,产生中断,中断处理程序将扫描码转换为ASCII码,存入系统键盘缓冲区。
getchar源码:
c
Copy
int getchar(void)
{
static char buf[BUFSIZ];
static char *ptr = buf;
static int n = 0;
if (n == 0)
{
n = read(0, buf, BUFSIZ);
ptr = buf;
}
return (n-- > 0) ? (unsigned char)*ptr++ : EOF;
}
getchar通过read系统调用从键盘缓冲区读取ASCII码,直至回车,返回首个字符。
8.5 本章小结异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章介绍了Linux的IO设备管理方法及Unix I/O接口,分析了printf和getchar的底层实现原理,阐明了IO操作的统一性和高效性。(第8章 选做 0分)
结论
用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
从编写到终止的完整过程:
- 源代码编写:
hello.c以C语言编写,包含main函数,接受命令行参数argc和argv,调用printf输出字符串,调用sleep暂停执行,调用getchar读取输入,最终终止。 - 预处理:
编译器(gcc)运行预处理器(cpp),处理#include <stdio.h>等指令,展开标准库头文件,生成预处理文件hello.i,包含printf等函数声明。 - 编译:
编译器将hello.i翻译为汇编代码hello.s,将C语言结构(如循环、函数调用)转换为x86_64指令,使用符号标签(如.L1)表示跳转目标。 - 汇编:
汇编器(as)将hello.s转换为机器代码,生成可重定位目标文件hello.o(ELF格式),包含.text(代码段,虚拟地址0x4010d0)、.data(初始化数据,0x404040)、.rodata(只读数据)等节,符号表记录函数和变量引用,重定位条目标记需调整的地址。 - 链接:
链接器(ld)合并hello.o与标准库(如libc.o),解析符号引用,分配运行时虚拟地址,生成可执行文件hello(ELF格式)。程序头部表定义内存映射,重定位节(如.rel.text)指导地址修正。 - 加载:
shell(bash)解析命令./hello 1190200109 刘文卓,识别为非内置命令,调用fork创建子进程,复制shell的虚拟地址空间(代码、数据、栈、共享库),分配唯一PID。子进程通过execve加载hello,映射.text、 .data等节到虚拟地址空间,设置程序计数器指向main。 - 动态链接:
动态链接器(ld-linux.so)解析printf、sleep等共享库函数,更新全局偏移表(GOT,地址0x404000)和过程链接表(PLT)。如printf的GOT条目初始指向PLT,运行时更新为libc.so中的实际地址。 - 执行:
CPU在用户模式执行hello的逻辑控制流,运行main,通过printf输出10行“Hello 1190200109 刘文卓”,调用sleep进入内核模式处理休眠,调用getchar读取键盘输入。内核通过上下文切换调度多进程。 - 内存管理:
虚拟地址通过四级页表和TLB转换为物理地址,访问.text、argv等数据。缺页故障触发内核从磁盘加载页面。三级缓存(L1/L2/L3)优化物理内存访问,使用LRU替换策略处理未命中。 - I/O操作:
printf通过vsprintf格式化字符串,write系统调用将数据写入显存,显示器渲染输出。getchar通过read从键盘缓冲区读取ASCII码,响应用户输入。 - 异常与信号处理:
键盘中断(如Ctrl-C触发SIGINT)终止进程,Ctrl-Z触发SIGTSTP挂起进程,缺页故障触发SIGSEGV。若argc != 5,程序以退出码1终止,发送SIGCHLD给shell。 - 终止:
main返回后,调用exit终止进程,内核回收资源,关闭文件描述符,shell通过waitpid回收子进程。
对计算机系统设计与实现的感悟与创新理念
hello程序的执行过程展现了计算机系统设计的精妙之处,其层次化架构将复杂性分解为多个抽象层,从高级语言到机器指令、从虚拟地址到物理内存、从用户模式到内核模式,每一层都以简洁的接口隐藏底层细节,为程序员提供了高效、可靠的开发环境。hello的编译过程(预处理、编译、汇编、链接)体现了工具链的模块化设计,将C代码逐步转化为可执行文件,优化了开发效率。链接阶段通过静态和动态链接整合代码,平衡了模块化与性能。加载和执行阶段,fork和execve展示了进程管理的灵活性,虚拟内存通过页表和TLB实现了地址隔离与高效转换,三级缓存加速了数据访问,动态链接器通过延迟绑定优化了程序启动时间。I/O操作中,printf和getchar通过系统调用和中断处理实现了用户与硬件的交互,异常和信号机制(如SIGINT、SIGTSTP)确保了系统的健壮性。然而,这一过程也暴露了性能瓶颈,如上下文切换的延迟、TLB未命中、缓存未命中以及动态链接的首次调用开销,提示我们在设计上需权衡抽象的便捷性与执行效率。
基于此,我提出以下创新理念与实现方法:首先,针对动态链接的延迟绑定开销,可设计自适应动态链接机制,通过运行时分析或静态预测高频调用的库函数,提前解析并填充GOT,减少首次调用延迟,低频函数保留延迟绑定以节省内存。其次,针对hello的固定4KB页面管理,提出混合页面大小模型,结合小页面(512B)用于栈和堆分配、大页面(2MB)用于代码和大数据,内核根据访问模式动态调整页面类型,减少TLB未命中和碎片。第三,针对缓存性能,设计基于机器学习的智能预取机制,CPU集成轻量神经网络,分析指令和数据访问模式,预测下一块数据并提前加载到L1缓存,提升命中率。第四,改进异常处理,设计上下文感知信号框架,内核根据程序状态(如循环深度、I/O进度)动态选择信号响应策略,例如延迟SIGINT终止以保存数据,提高用户体验。第五,在动态内存分配方面,针对printf调用的malloc碎片问题,提出分层堆管理,分别为小对象、数组和大块分配专用子堆,结合垃圾收集和手动释放,编译器分析对象生命周期以优化分配策略。第六,针对getchar的同步I/O阻塞,设计异步I/O框架,用户态维护线程池预处理I/O请求,内核通过事件通知异步返回数据,减少上下文切换开销。这些创新可通过编译器扩展、内核模块修改和硬件加速单元实现,如在gcc中添加调用频率分析、在内核中扩展页表结构、在CPU中集成预测单元,从而在保持系统模块化的同时提升性能和灵活性,为现代计算机系统设计注入新的活力。
(结论0分,缺失-1分)
附件
列出所有的中间产物的文件名,并予以说明起作用。
hello.c:原始hello程序的C语言代码
hello.i:预处理过后的hello代码
hello.s:由预处理代码生成的汇编代码
hello.o:二进制目标代码
hello:进行链接后的可执行程序
hello_disassembly.txt:反汇编hello.o得到的反汇编文件
helloobjdump.txt:反汇编hello可执行文件得到的反汇编文件
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
[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.
(参考文献0分,缺失 -1分)
转载自CSDN-专业IT技术社区



