哈工大计算机系统2024大作业——Hello的程序人生

第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个节,节的具体位置和大小由节头部表进一步描述。

  1. 节头部表

节头部表记录了目标文件中各节的属性,包括节的类型、文件偏移量、大小等。作为可重定位文件,hello.o的各节偏移从0开始,供链接器使用。通过节头部表,可定位每个节的起始地址和占用空间。分析显示,代码段(.text)具有可执行属性但不可写;数据段(.data)和只读数据段(.rodata)不可执行,其中.rodata还禁止写入,保障数据完整性。

  1. 重定位节

重定位节记录.text节中需调整的引用位置信息,用于链接器在合并目标文件时修改这些引用。重定位节包含以下字段:

偏移量(Offset):指明需修改的引用在.text节中的位置。

信息(Info):分为符号索引(symbol)和重定位类型(type),前者指引用指向的符号,后者指导链接器如何调整引用。

类型(Type):定义重定位方式,如R_X86_64_PC32(32位PC相对寻址)和R_X86_64_32(32位绝对寻址)。

符号名称(Sym.Name):指明重定位目标的符号名。

加数(Addend):有符号常数,用于某些重定位类型调整引用值的偏移。

  1. 符号表

符号表存储程序中定义或引用的函数和全局变量信息,排除局部变量。包含以下字段:

值(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个节。

  1. 节头部表

节头部表记录hello中各节的元数据,如名称、类型、虚拟地址、文件偏移和大小等。虚拟地址指明节加载到内存的起始位置,偏移量指示节在文件中的位置。可通过工具如HexEdit根据这些信息定位节的起始和大小。

  1. 程序头部表

程序头部表描述可执行文件如何映射到内存段,包含每个程序头的偏移量、虚拟地址、对齐要求、文件及内存中的段大小,以及运行时权限(如读、写、执行)。它指导操作系统加载程序到虚拟地址空间。

  1. 段节

段节定义了程序在内存中的布局,包含代码、数据等内容,由程序头部表描述其映射关系。

  1. 动态节

动态节存储动态链接所需的信息,如共享库依赖和符号解析数据,供动态链接器在程序加载时使用。

  1. 重定位节

重定位节列出.text节中需调整的函数引用信息,包含偏移量、符号名和重定位类型等,供链接器在合并文件时修改引用地址,确保函数调用指向正确位置。

  1. 动态符号表

动态符号表记录与动态链接相关的导入和导出符号,排除模块内部符号,用于运行时符号解析。

  1. 符号表

符号表存储程序定义和引用的函数及全局变量信息,包括符号的偏移、类型、大小和绑定属性(局部或全局),以数组索引形式组织,辅助链接和调试。

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.关闭文件:释放文件数据结构,回收描述符,进程终止时自动关闭。

函数

  1. int open(char* filename, int flags, mode_t mode):打开或创建文件,返回最小未用描述符,flags指定访问模式,mode设置权限。
  2. int close(int fd):关闭描述符,重复关闭会报错。
  3. ssize_t read(int fd, void *buf, size_t n):从fd读取最多n字节到buf,返回读取字节数,0表示EOF,-1表示错误。
  4. 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所经历的过程。

你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。

从编写到终止的完整过程:

  1. 源代码编写
    hello.c以C语言编写,包含main函数,接受命令行参数argc和argv,调用printf输出字符串,调用sleep暂停执行,调用getchar读取输入,最终终止。
  2. 预处理
    编译器(gcc)运行预处理器(cpp),处理#include <stdio.h>等指令,展开标准库头文件,生成预处理文件hello.i,包含printf等函数声明。
  3. 编译
    编译器将hello.i翻译为汇编代码hello.s,将C语言结构(如循环、函数调用)转换为x86_64指令,使用符号标签(如.L1)表示跳转目标。
  4. 汇编
    汇编器(as)将hello.s转换为机器代码,生成可重定位目标文件hello.o(ELF格式),包含.text(代码段,虚拟地址0x4010d0)、.data(初始化数据,0x404040)、.rodata(只读数据)等节,符号表记录函数和变量引用,重定位条目标记需调整的地址。
  5. 链接
    链接器(ld)合并hello.o与标准库(如libc.o),解析符号引用,分配运行时虚拟地址,生成可执行文件hello(ELF格式)。程序头部表定义内存映射,重定位节(如.rel.text)指导地址修正。
  6. 加载
    shell(bash)解析命令./hello 1190200109 刘文卓,识别为非内置命令,调用fork创建子进程,复制shell的虚拟地址空间(代码、数据、栈、共享库),分配唯一PID。子进程通过execve加载hello,映射.text、 .data等节到虚拟地址空间,设置程序计数器指向main。
  7. 动态链接
    动态链接器(ld-linux.so)解析printf、sleep等共享库函数,更新全局偏移表(GOT,地址0x404000)和过程链接表(PLT)。如printf的GOT条目初始指向PLT,运行时更新为libc.so中的实际地址。
  8. 执行
    CPU在用户模式执行hello的逻辑控制流,运行main,通过printf输出10行"Hello 1190200109 刘文卓",调用sleep进入内核模式处理休眠,调用getchar读取键盘输入。内核通过上下文切换调度多进程。
  9. 内存管理
    虚拟地址通过四级页表和TLB转换为物理地址,访问.text、argv等数据。缺页故障触发内核从磁盘加载页面。三级缓存(L1/L2/L3)优化物理内存访问,使用LRU替换策略处理未命中。
  10. I/O 操作
    printf通过vsprintf格式化字符串,write系统调用将数据写入显存,显示器渲染输出。getchar通过read从键盘缓冲区读取ASCII码,响应用户输入。
  11. 异常与信号处理
    键盘中断(如Ctrl-C触发SIGINT)终止进程,Ctrl-Z触发SIGTSTP挂起进程,缺页故障触发SIGSEGV。若argc != 5,程序以退出码1终止,发送SIGCHLD给shell。
  12. 终止
    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下预处理的命令 ![](https://i-blog.csdnimg.cn/direct/ffda0d3d0a8b408589e75accc3397ed3.png) ![](https://i-blog.csdnimg.cn/direct/674c760025d647bc9cb93586520a661e.png)应截图,展示预处理过程! ### 2.3 Hello的预处理结果解析 ![](https://i-blog.csdnimg.cn/direct/e8147cec5ed041a8ad5bb60fed24dc9d.png) ### 经过预处理后,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下编译的命令 ![](https://i-blog.csdnimg.cn/direct/4d3da0d0d73f4d2492404c5ad3515991.png) ![](https://i-blog.csdnimg.cn/direct/4139a2aacb8847869ab9daa84988147e.png)应截图,展示编译过程! ### 3.3 Hello的编译结果解析 ![](https://i-blog.csdnimg.cn/direct/515f58f826ab431d9ce607b28d07b35b.png) (*以下格式自行编排,编辑时删除*) 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下汇编的命令 ![](https://i-blog.csdnimg.cn/direct/6cfaa2bf67e34ebd88dcb25d12d576d8.png)![](https://i-blog.csdnimg.cn/direct/fa09ee7ab8a84c41900d288209dae621.png)应截图,展示汇编过程! ### 4.3 可重定位目标elf格式 ![](https://i-blog.csdnimg.cn/direct/4731d9c2133147e5aa302d86e20cbb15.png)![](https://i-blog.csdnimg.cn/direct/f1155e6451bf40e6b8ac158c08e9f936.png)![](https://i-blog.csdnimg.cn/direct/f8e62274792f42389d9e13d7df4afd6d.png)![](https://i-blog.csdnimg.cn/direct/f7e26e93628148238d857027350805f2.png)使用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的结果解析 ![](https://i-blog.csdnimg.cn/direct/847bf8a9809f4593b091f0de9f33d894.png) ![](https://i-blog.csdnimg.cn/direct/ada709c21ffd42de9f0d6d7376ac7b60.png) 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下链接的命令 ![](https://i-blog.csdnimg.cn/direct/34e85ddbd20946119d6632fbf73dd276.png) 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的格式 ![](https://i-blog.csdnimg.cn/direct/051b62c2f0a94214a361348327b7ba82.png) ![](https://i-blog.csdnimg.cn/direct/6874615363bf46cb865f7284470d94ed.png) ![](https://i-blog.csdnimg.cn/direct/d3ebd905d0bb4bedbe0b3ddc45034eb0.png)通过执行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的虚拟地址空间 ![](https://i-blog.csdnimg.cn/direct/e100965448054bcca3b6a6c0ba6ba1a3.png)使用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 链接的重定位过程分析 ![](https://i-blog.csdnimg.cn/direct/9258cfac9d4a4510b63f1640a4e56844.png) ![](https://i-blog.csdnimg.cn/direct/2c01e3e5969e44dbb5fa67ed502f7bd4.png) ![](https://i-blog.csdnimg.cn/direct/9b0eae4fdbbf4eedb9d9c2ac4879fd2b.png) ![](https://i-blog.csdnimg.cn/direct/aaee5dab31834e16964c5d576f2de2af.png)![](https://i-blog.csdnimg.cn/direct/2ed8f9de4f8646b2a510131e65fd01db.png) 使用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的执行流程 ![](https://i-blog.csdnimg.cn/direct/c7f229fef63541dfaa079ae5cf9aaf04.png)![](https://i-blog.csdnimg.cn/direct/dfffed65cc2d4648bc33ce5a656223fb.png)![](https://i-blog.csdnimg.cn/direct/ccd21e4b4c2f4172bb223a855d9bd502.png)![](https://i-blog.csdnimg.cn/direct/2e979e340cee45d2a49693550fab628a.png)![](https://i-blog.csdnimg.cn/direct/444bc7cc95d74ee5adee9080329607da.png)![](https://i-blog.csdnimg.cn/direct/9025a2e2e6f84f44a321cf50b2ca178f.png)![](https://i-blog.csdnimg.cn/direct/9380efcf8b37427e8206e59fdfe5f0fc.png)![](https://i-blog.csdnimg.cn/direct/e2c70aabf0b94dd38996b95699777f34.png)(*以下格式自行编排,编辑时删除*) 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的异常与信号处理 ![](https://i-blog.csdnimg.cn/direct/9a2ff51b123f4fbeb82bc6cbd2844433.png) 乱按:Shell会将回车前输出的字符串当作命令。 ![](https://i-blog.csdnimg.cn/direct/fa12a28104a84b5fbec9dd4aa166a99b.png) Ctrl+C: 会立即终止进程,通过ps命令发现hello进程被回收。 ![](https://i-blog.csdnimg.cn/direct/bc5b7ed6e7b1460ebf89d55f8a940d9f.png) ### 在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.关闭文件:释放文件数据结构,回收描述符,进程终止时自动关闭。 **函数**: 1. int open(char\* filename, int flags, mode_t mode):打开或创建文件,返回最小未用描述符,flags指定访问模式,mode设置权限。 2. int close(int fd):关闭描述符,重复关闭会报错。 3. ssize_t read(int fd, void \*buf, size_t n):从fd读取最多n字节到buf,返回读取字节数,0表示EOF,-1表示错误。 4. 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 - 博客园](https://www.cnblogs.com/pianist/p/3315801.html "[转]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所经历的过程。 你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。 从编写到终止的完整过程: 1. **源代码编写** : hello.c以C语言编写,包含main函数,接受命令行参数argc和argv,调用printf输出字符串,调用sleep暂停执行,调用getchar读取输入,最终终止。 2. **预处理** : 编译器(gcc)运行预处理器(cpp),处理#include \等指令,展开标准库头文件,生成预处理文件hello.i,包含printf等函数声明。 3. **编译** : 编译器将hello.i翻译为汇编代码hello.s,将C语言结构(如循环、函数调用)转换为x86_64指令,使用符号标签(如.L1)表示跳转目标。 4. **汇编** : 汇编器(as)将hello.s转换为机器代码,生成可重定位目标文件hello.o(ELF格式),包含.text(代码段,虚拟地址0x4010d0)、.data(初始化数据,0x404040)、.rodata(只读数据)等节,符号表记录函数和变量引用,重定位条目标记需调整的地址。 5. **链接** : 链接器(ld)合并hello.o与标准库(如libc.o),解析符号引用,分配运行时虚拟地址,生成可执行文件hello(ELF格式)。程序头部表定义内存映射,重定位节(如.rel.text)指导地址修正。 6. **加载** : shell(bash)解析命令./hello 1190200109 刘文卓,识别为非内置命令,调用fork创建子进程,复制shell的虚拟地址空间(代码、数据、栈、共享库),分配唯一PID。子进程通过execve加载hello,映射.text、 .data等节到虚拟地址空间,设置程序计数器指向main。 7. **动态链接** : 动态链接器(ld-linux.so)解析printf、sleep等共享库函数,更新全局偏移表(GOT,地址0x404000)和过程链接表(PLT)。如printf的GOT条目初始指向PLT,运行时更新为libc.so中的实际地址。 8. **执行** : CPU在用户模式执行hello的逻辑控制流,运行main,通过printf输出10行"Hello 1190200109 刘文卓",调用sleep进入内核模式处理休眠,调用getchar读取键盘输入。内核通过上下文切换调度多进程。 9. **内存管理** : 虚拟地址通过四级页表和TLB转换为物理地址,访问.text、argv等数据。缺页故障触发内核从磁盘加载页面。三级缓存(L1/L2/L3)优化物理内存访问,使用LRU替换策略处理未命中。 10. **I/O** **操作** : printf通过vsprintf格式化字符串,write系统调用将数据写入显存,显示器渲染输出。getchar通过read从键盘缓冲区读取ASCII码,响应用户输入。 11. **异常与信号处理** : 键盘中断(如Ctrl-C触发SIGINT)终止进程,Ctrl-Z触发SIGTSTP挂起进程,缺页故障触发SIGSEGV。若argc != 5,程序以退出码1终止,发送SIGCHLD给shell。 12. **终止** : 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下预处理的命令 ![](https://i-blog.csdnimg.cn/direct/bb4aef17057c4fa49d0ce086937a5021.png) ![](https://i-blog.csdnimg.cn/direct/000e84e271c140afacd80596b9419b91.png)应截图,展示预处理过程! ### 2.3 Hello的预处理结果解析 ![](https://i-blog.csdnimg.cn/direct/cb8c1521b4c143c588e09e9cb0030caf.png) ### 经过预处理后,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下编译的命令 ![](https://i-blog.csdnimg.cn/direct/8e04a6cece9f4085afc5cdd878e1ef72.png) ![](https://i-blog.csdnimg.cn/direct/f223e92f92a64134947ba48c421ea5c1.png)应截图,展示编译过程! ### 3.3 Hello的编译结果解析 ![](https://i-blog.csdnimg.cn/direct/d8c4088b5b28492a99dda470344fffcc.png) (*以下格式自行编排,编辑时删除*) 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下汇编的命令 ![](https://i-blog.csdnimg.cn/direct/263c1dfadef743dd80f56feba1df1402.png)![](https://i-blog.csdnimg.cn/direct/e58dc6f625854a729e9e205ed5fa1d07.png)应截图,展示汇编过程! ### 4.3 可重定位目标elf格式 ![](https://i-blog.csdnimg.cn/direct/2c06ccb3186f4e83b647f82c12118dfd.png)![](https://i-blog.csdnimg.cn/direct/67ad7e1966034ab5ba576f60fef46d61.png)![](https://i-blog.csdnimg.cn/direct/26aa4c585a17471994f8a4305dfd7c02.png)![](https://i-blog.csdnimg.cn/direct/5aa349a39c244d7585e4f91a50eaffb3.png)使用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的结果解析 ![](https://i-blog.csdnimg.cn/direct/5285ce3b767440439f4564c7a6c700a4.png) ![](https://i-blog.csdnimg.cn/direct/9fe66488cfc24150861989b737ca7271.png) 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下链接的命令 ![](https://i-blog.csdnimg.cn/direct/3b160e50c3944270a49f990bee63525e.png) 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的格式 ![](https://i-blog.csdnimg.cn/direct/1bbe27978c8742998a2bf9835d993858.png) ![](https://i-blog.csdnimg.cn/direct/b5131365dba246219b09c0dcc9c50b0f.png) ![](https://i-blog.csdnimg.cn/direct/7d4ef12df5db485c902d3d57f3830f7b.png)通过执行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的虚拟地址空间 ![](https://i-blog.csdnimg.cn/direct/43821f9d6e3747838ac298ced38aa921.png)使用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 链接的重定位过程分析 ![](https://i-blog.csdnimg.cn/direct/a6046aa4fdb64af3b2a27ca473f33264.png) ![](https://i-blog.csdnimg.cn/direct/5af3992155ef49bd897ed5a5a25023a3.png) ![](https://i-blog.csdnimg.cn/direct/97b1c40efb7248b3b066e1af52ce64f7.png) ![](https://i-blog.csdnimg.cn/direct/3366f383e1e4498e87a938a12bd84744.png)![](https://i-blog.csdnimg.cn/direct/2b31b13d3e50463fb3e1c1aabd50c13a.png) 使用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的执行流程 ![](https://i-blog.csdnimg.cn/direct/d0f932c3559f4a748b2d48bf2966934f.png)![](https://i-blog.csdnimg.cn/direct/b6230f909e324f9a8de14c87c253864e.png)![](https://i-blog.csdnimg.cn/direct/7d32dc2d3d31465dbe5713b719acb514.png)![](https://i-blog.csdnimg.cn/direct/be9c714a34c4476bbab208cc8ab58adf.png)![](https://i-blog.csdnimg.cn/direct/5ee0935e87fd45779c5a199550453f1c.png)![](https://i-blog.csdnimg.cn/direct/4b5ca1c4991f4e0385df5ade5d9849f4.png)![](https://i-blog.csdnimg.cn/direct/77f38881835c41e3ad13c22b3f1d15de.png)![](https://i-blog.csdnimg.cn/direct/fc3c9b6fd9b44a3a98fb71ec9400e4d9.png)(*以下格式自行编排,编辑时删除*) 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的异常与信号处理 ![](https://i-blog.csdnimg.cn/direct/50d5b7bca016487c839d93edaa33bdee.png) 乱按:Shell会将回车前输出的字符串当作命令。 ![](https://i-blog.csdnimg.cn/direct/e12987fba6bf4e1aa1e22b7219e00ad3.png) Ctrl+C: 会立即终止进程,通过ps命令发现hello进程被回收。 ![](https://i-blog.csdnimg.cn/direct/3e6f5c26c7f94cf284098ca3c8180ee8.png) ### 在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.关闭文件:释放文件数据结构,回收描述符,进程终止时自动关闭。 **函数**: 1. int open(char\* filename, int flags, mode_t mode):打开或创建文件,返回最小未用描述符,flags指定访问模式,mode设置权限。 2. int close(int fd):关闭描述符,重复关闭会报错。 3. ssize_t read(int fd, void \*buf, size_t n):从fd读取最多n字节到buf,返回读取字节数,0表示EOF,-1表示错误。 4. 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 - 博客园](https://www.cnblogs.com/pianist/p/3315801.html "[转]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所经历的过程。 你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。 从编写到终止的完整过程: 1. **源代码编写** : hello.c以C语言编写,包含main函数,接受命令行参数argc和argv,调用printf输出字符串,调用sleep暂停执行,调用getchar读取输入,最终终止。 2. **预处理** : 编译器(gcc)运行预处理器(cpp),处理#include \等指令,展开标准库头文件,生成预处理文件hello.i,包含printf等函数声明。 3. **编译** : 编译器将hello.i翻译为汇编代码hello.s,将C语言结构(如循环、函数调用)转换为x86_64指令,使用符号标签(如.L1)表示跳转目标。 4. **汇编** : 汇编器(as)将hello.s转换为机器代码,生成可重定位目标文件hello.o(ELF格式),包含.text(代码段,虚拟地址0x4010d0)、.data(初始化数据,0x404040)、.rodata(只读数据)等节,符号表记录函数和变量引用,重定位条目标记需调整的地址。 5. **链接** : 链接器(ld)合并hello.o与标准库(如libc.o),解析符号引用,分配运行时虚拟地址,生成可执行文件hello(ELF格式)。程序头部表定义内存映射,重定位节(如.rel.text)指导地址修正。 6. **加载** : shell(bash)解析命令./hello 1190200109 刘文卓,识别为非内置命令,调用fork创建子进程,复制shell的虚拟地址空间(代码、数据、栈、共享库),分配唯一PID。子进程通过execve加载hello,映射.text、 .data等节到虚拟地址空间,设置程序计数器指向main。 7. **动态链接** : 动态链接器(ld-linux.so)解析printf、sleep等共享库函数,更新全局偏移表(GOT,地址0x404000)和过程链接表(PLT)。如printf的GOT条目初始指向PLT,运行时更新为libc.so中的实际地址。 8. **执行** : CPU在用户模式执行hello的逻辑控制流,运行main,通过printf输出10行"Hello 1190200109 刘文卓",调用sleep进入内核模式处理休眠,调用getchar读取键盘输入。内核通过上下文切换调度多进程。 9. **内存管理** : 虚拟地址通过四级页表和TLB转换为物理地址,访问.text、argv等数据。缺页故障触发内核从磁盘加载页面。三级缓存(L1/L2/L3)优化物理内存访问,使用LRU替换策略处理未命中。 10. **I/O** **操作** : printf通过vsprintf格式化字符串,write系统调用将数据写入显存,显示器渲染输出。getchar通过read从键盘缓冲区读取ASCII码,响应用户输入。 11. **异常与信号处理** : 键盘中断(如Ctrl-C触发SIGINT)终止进程,Ctrl-Z触发SIGTSTP挂起进程,缺页故障触发SIGSEGV。若argc != 5,程序以退出码1终止,发送SIGCHLD给shell。 12. **终止** : 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** **分)**

相关推荐
wnq08122 小时前
【HIT-CSAPP 哈尔滨工业大学计算机系统期末大作业】程序人生-Hello‘s P2P
计算机系统
nianniannnn2 小时前
HNU计算机系统期中题库详解(一)计算机组成原理(CPU、指令系统、存储器、运算基础)
计算机系统
qeen873 小时前
【算法笔记】前缀和经典题目解析
c语言·c++·笔记·学习·算法
三品吉他手会点灯3 小时前
C语言学习笔记 - 2.C概述 - HelloWorld程序举例
c语言·笔记·学习
Felven3 小时前
D. Zero Remainder Array
c语言
孬甭_3 小时前
内存函数以及数据在内存中的存储
c语言
卢锡荣4 小时前
单芯双 C 盲插,一线通显电 ——LDR6020P 盲插 Type‑C 显示器方案深度解析
c语言·开发语言·ios·计算机外设·电脑
legendary_1634 小时前
PD显示器方案新维度:Type-C充电,投屏,显示技术革新
c语言·开发语言·计算机外设
码农爱学习4 小时前
用简单的例子,来理解C指针
c语言·开发语言