关注

程序人生-Hello’s P2P

摘  要

本报告以 hello 程序为研究对象,采用理论分析与实操验证相结合的方法,围绕计算机系统的核心机制展开研究。通过 gcc 、 gdb 、 objdump 、 readelf 等工具,依次剖析了 hello 程序从C语言源文件到可执行文件的编译链接过程、ELF可执行文件的格式特征,以及程序运行阶段的进程管理、存储管理与IO管理机制,厘清了预处理、编译、汇编、链接各阶段中间产物的作用,揭示了进程创建与调度、虚拟地址到物理地址的变换、IO设备抽象与系统调用的底层逻辑。研究结果表明, hello 程序的运行是计算机系统“应用层-库函数层-操作系统层-硬件层”分层协作的典型体现,其背后的编译链接、内存映射、动态链接、信号处理等机制,构成了Linux系统资源管理与程序执行的核心框架。本研究通过具象化的 hello 程序分析,深化了对计算机系统整体架构与底层原理的理解,为后续系统开发与程序优化提供了理论与实操支撑。

关键词:hello程序;编译链接;ELF格式;进程管理;存储管理;IO管理;Linux系统

第1章 概述

1.1 Hello简介

一、Hello 的 P2P过程

1. Program(程序阶段:静态实体)

用户通过编辑器(Editor)编写hello.c源代码(文本文件),这是 Hello 的 “初始形态”—— 仅为人类可读的高级语言指令集合,未被计算机识别为可执行逻辑,属于静态的 Program。

2. 程序翻译阶段(Program→可执行文件)

预处理:预处理器处理hello.c中的头文件包含、宏定义等,生成hello.i文件,展开代码、去除注释,得到纯 C 代码。

编译:编译器(Compiler)将hello.i翻译为汇编语言文件hello.s,完成高级语言到汇编指令的转换。

汇编:汇编器(AS)将hello.s转化为二进制目标文件hello.o,每个汇编指令对应 CPU 可执行的机器码,但未解决外部符号引用(如printf)。

链接:链接器(LD)将hello.o与标准库(如libc.so)合并,解析外部符号,生成可执行文件(如hello)——Hello 成为可被操作系统(OS)加载的静态程序实体。

3. Process(进程阶段:动态实体)

进程创建:用户在 Shell(Bash)中执行./hello,Shell 通过fork()系统调用创建子进程,子进程通过execve()加载可执行文件,OS 通过mmap()将文件映射到进程虚拟地址空间(VA)。

资源分配与运行:

进程管理:OS 为进程分配时间片,调度 CPU 执行。

存储管理:MMU 负责虚拟地址(VA)到物理地址(PA)的转换,TLB 加速地址转换,多级页表、Cache(L1/L2/L3)优化内存访问,Pagefile(交换分区)提供虚拟内存支持。

硬件执行:CPU 按 “取指 - 译码 - 执行” 流水线执行指令,IO 管理协调显卡、屏幕等外设,使 Hello 的输出(如 “Hello World”)在屏幕呈现。

二、Hello 的 O2O过程

1. 初始态(Zero - 第一个 “无”)

未编写hello.c前,Hello 既无静态程序实体(源代码、可执行文件),也无动态进程,处于完全 “不存在” 的零状态。

2. 中间态(从 “无” 到 “有”)

经预处理→编译→汇编→链接生成可执行文件,再经fork()+execve()创建运行中的进程,Hello 从 “无” 转化为占用 CPU、内存、IO 等系统资源的动态实体,完成 “0→有” 的过渡。

3. 终止态(Zero - 第二个 “无”)

Hello 执行完成(输出任务结束),进程调用exit()系统调用终止,OS 回收其占用的所有资源(CPU 时间片、内存空间、文件描述符等),父进程(Bash)通过wait()回收退出状态。

最终,Hello 的静态程序实体(可执行文件)可保留,但动态进程完全消失,无残留系统资源,回到 “无” 的零状态,实现 “从 0 到 0” 的闭环。

1.2 环境与工具 

一.硬件环境

处理器:Intel Core i5/i7 处理器(x86_64 架构),支持虚拟化技术;

内存:8GB/16GB DDR4 内存,满足虚拟机与编译调试的内存需求;

存储:512GB SSD 固态硬盘,保障Linux镜像与编译文件的快速读写。

二.软件环境

宿主系统:Windows 11 64位操作系统;

虚拟机:VMware Workstation 17 Pro,搭建Ubuntu 20.04 LTS 64位Linux发行版;

编译器:GCC 9.4.0(GNU C Compiler),支持x86_64架构的编译、汇编与链接;

内核版本:Linux 5.4.0-163-generic,提供进程管理、存储管理、IO管理的底层支撑。

三.开发与调试工具

代码编辑:Vim、VS Code(搭配C/C++插件),实现 hello.c 的编写与语法检查;

编译辅助:Makefile(简化编译指令)、objdump(反汇编可执行文件)、readelf(分析ELF文件格式);

调试工具:GDB(GNU调试器),调试进程创建、执行与退出流程;

系统监控:ps、top、pstree(查看进程状态),dmesg(查看内核日志),strace(跟踪系统调用)。

1.3 中间结果

 hello.c , 代码编写, 存放 Hello 程序的C语言源代码,定义程序的核心逻辑(参数校验、循环输出、休眠等)

 hello.i , 预处理阶段,  GCC预处理后的文本文件,展开头文件( stdio.h / stdlib.h )、删除注释,保留预处理后的C代码

 hello.s , 编译阶段, 生成的汇编语言文件,包含x86_64架构的汇编指令,是C代码到机器码的中间桥梁

 hello.o,  汇编阶段, 目标文件(ELF格式),包含二进制机器码但未完成链接,存在未解析的符号(如 printf / sleep )

 hello,  链接阶段, 可执行文件(ELF格式),完成符号解析与重定位,可直接在Linux环境运行

 hello.log,  运行调试, 记录程序运行的输出日志、系统调用信息与进程状态,用于分析程序执行流程

1.4 本章小结

本章围绕 Hello 程序的分析背景与基础准备展开,首先阐述了 Hello 程序从静态代码到动态进程的“从0到0”闭环核心逻辑,即程序通过预处理、编译、汇编、链接生成可执行文件,再由操作系统加载为进程执行,最终释放资源回归零状态。随后梳理了分析过程中使用的软硬件环境,明确x86_64架构的Linux虚拟机为核心实验平台,GCC、GDB等工具为程序分析提供技术支撑;同时列出了各阶段生成的中间文件,这些文件是剖析 Hello 程序底层执行机制的关键载体。本章的准备工作为后续深入分析 Hello 程序的预处理、编译、进程管理等底层机制奠定了基础,明确了实验的技术路径与分析依据。


第2章 预处理

2.1 预处理的概念与作用

预处理是GCC编译流程的第一个阶段,由预处理器(cpp)完成,处理对象为C语言源代码中的预处理指令(以 # 开头)、注释与宏定义等,不涉及语法分析与代码编译。

其核心作用可分为4类:

1. 头文件包含:通过 #include 指令将标准库头文件(如 stdio.h 、 stdlib.h )或自定义头文件的内容直接嵌入源代码中,解决函数声明、宏定义的引用问题。

2. 宏处理:展开 #define 定义的宏常量与宏函数,同时处理 #undef 等宏撤销指令。

3. 条件编译:根据 #if 、 #ifdef 、 #ifndef 等指令筛选编译代码,实现代码的条件性保留或剔除(本实验 hello.c 未涉及条件编译,仅做基础处理)。

4. 注释删除:移除源代码中的单行注释( // )与多行注释( /* */ ),简化代码结构,减少后续编译阶段的处理量。

预处理的最终目标是生成纯C语言文本文件( .i 后缀),为后续的编译阶段(生成汇编代码)提供标准化的输入

2.2在Ubuntu下预处理的命令

2.3 Hello的预处理结果解析

1. 头文件展开

源代码中 #include <stdio.h> 与 #include <stdlib.h> 被完全展开, hello.i 文件开头会包含上千行的标准库代码,涵盖 printf 、 exit 、 sleep 、 getchar 等函数的声明,以及 NULL 、 EXIT_FAILURE 等宏定义。例如 stdio.h 会展开输入输出函数的原型, stdlib.h 会展开内存管理、进程退出相关的函数声明。

2. 注释删除

 hello.c 中以 // 开头的注释(如 // 大作业的hello.c程序 、 // 秒数=手机号%5 )被全部移除, hello.i 中仅保留有效代码逻辑,无任何注释内容。

3. 代码结构保留

预处理未改变 hello.c 的核心代码逻辑, main 函数中的参数校验、循环输出、休眠与阻塞等待等代码完整保留在 hello.i 文件末尾,仅去除了注释并补充了头文件的声明内容。

2.4 本章小结

本章围绕 Hello 程序的预处理阶段展开分析,首先明确了预处理是GCC编译流程的起始环节,其核心是处理预处理指令、展开头文件与删除注释,为后续编译提供标准化的C代码;其次给出了Ubuntu环境下对 hello.c 进行预处理的GCC指令,并说明了实操流程与截图要求;最后解析了 hello.i 的预处理结果,验证了头文件展开、注释删除与代码逻辑保留的核心特征。


第3章 编译

3.1 编译的概念与作用

编译是GCC编译流程的第二个阶段,负责把预处理后的纯C文件( .i )转换成汇编语言文件( .s )。简单来说,就是编译器把人类容易读的C语言代码,“翻译”成计算机硬件更易理解的汇编指令,为后续生成机器码做准备。

3.2 在Ubuntu下编译的命令

3.3 Hello的编译结果解析 

3.3.1 数据相关编译解析

(1)变量(局部)

截图中汇编指令 -4(%rbp) 对应C代码中 for 循环的局部变量 i , -32(%rbp) 对应存储 argv 指针的栈空间,局部变量均通过栈帧基址 %rbp +偏移量分配内存,符合x86_64架构下局部变量的栈存储规则。

例如: addl $1, -4(%rbp) 是对 i++ 的汇编实现,直接对栈中 i 的存储地址执行加1操作。

(2)常量与类型

数值常量: cmpl $9, -4(%rbp) 中的 $9 对应C代码循环条件 i<10 的常量10(汇编中以9为比较阈值,通过 jle 实现小于等于判断); movl $0, %eax 中的 $0 是给寄存器赋0的整型常量。

类型转换: call atoi@PLT 对应C代码中 atoi(argv[4]) 的显式类型转换(字符串→整型), atoi 的返回值存入 %eax 寄存器,后续通过 movl %eax, %edi 将整型值传入 sleep 函数,完成从寄存器到函数参数的类型传递。

3.3.2 操作符相关编译解析

(1)赋值操作( = )

C代码中 sec = atoi(argv[4]) 的赋值逻辑,在汇编中拆解为:

1.  movq -32(%rbp), %rax :从栈中取出 argv 基地址;

2.  addq $32, %rax :偏移32字节( argv[4] ,x86_64下指针占8字节, 4*8=32 );

3.  movq (%rax), %rax :取出 argv[4] 的字符串指针;

4.  movq %rax, %rdi :将指针传入 atoi 函数;

5.  call atoi@PLT :调用 atoi 完成转换,返回值存入 %eax ,即完成“字符串→整型”的赋值。

(2)算术操作( ++ )

addl $1, -4(%rbp) 是C代码 i++ 的汇编实现,属于自增操作符的直接映射,通过 addl 指令对栈中 i 的存储值执行加1,无额外内存访问开销。

(3)关系操作( < )

C代码 for 循环的条件 i<10 ,在汇编中通过 cmpl $9, -4(%rbp) + jle .L4 实现:

 cmpl $9, -4(%rbp) :比较 i ( -4(%rbp) )与9;

 jle .L4 :若 i 小于等于9(即 i<10 ),则跳转到循环体标签 .L4 ,属于小于关系操作的汇编等价实现。

3.3.3 控制转移相关编译解析

(1)for循环

C代码 for(i=0;i<10;i++) 的完整汇编逻辑在截图中体现为**“初始化→条件判断→循环体→自增”**的指令链:

1. 初始化:隐式通过 movl $0, -4(%rbp) 完成 i=0 (截图前序指令);

2. 条件判断: cmpl $9, -4(%rbp) + jle .L4 ,判断是否进入循环;

3. 循环体:执行 printf 、 atoi 、 sleep 等核心逻辑;

4. 自增: addl $1, -4(%rbp) 完成 i++ ,之后回到条件判断环节。

(2)函数内的顺序执行

截图中 printf → atoi → sleep → getchar 的指令调用顺序,与C代码中函数执行的顺序完全一致,汇编通过**指令逐条执行+函数调用 call **实现程序的顺序控制转移。

3.3.4 函数操作编译解析

(1)参数传递(值传递/地址传递)

地址传递: printf 、 atoi 的参数为字符串指针(如 argv[1] / argv[4] ),汇编中通过 movq / leaq 将指针地址传入 %rdi / %rsi 寄存器,属于地址传递;

值传递: sleep 的参数为 atoi 返回的整型值( %eax ),通过 movl %eax, %edi 将数值传入寄存器,属于值传递。

(2)函数调用( call 指令)

截图中所有库函数调用均通过 call 函数名@PLT 实现:

call printf@PLT :调用 printf 函数,通过**过程链接表(PLT)**实现动态链接,避免直接绑定库函数地址;

call atoi@PLT / call sleep@PLT / call getchar@PLT :同理,均通过PLT调用对应的标准库函数,符合Linux下动态链接的汇编实现规则。

(3)函数返回与程序退出

 函数返回: ret 指令对应C代码中 main 函数的 return 0 , movl $0, %eax 先将返回值0存入 %eax (x86_64下函数返回值默认存在 %eax ),再通过 ret 返回到调用者(Bash);

栈帧清理: leave 指令用于释放 main 函数的栈帧(恢复 %rbp 和 %rsp ),是函数退出前的标准操作。

3.4 本章小结

本章围绕“编译(.i → .s)”展开:首先明确编译是“C代码→汇编指令”的翻译过程;其次掌握了Ubuntu下生成汇编文件的命令与实操;最后重点分析了 hello.s 里各类C元素的编译逻辑——编译器把抽象的C代码,拆解成了具体的汇编指令序列。这一步是“高级语言到硬件指令”的关键过渡,为后续分析“汇编→机器码”打下基础。 (第3章2分)


第4章 汇编

4.1 汇编的概念与作用

汇编是GCC编译流程的第三个阶段,负责将汇编语言文件( .s )转换为可重定位目标文件( .o ,ELF格式),本质是把人类可读的汇编指令翻译成计算机能直接识别的机器语言二进制指令(机器码),同时完成汇编指令到内存地址的初步映射。

4.2 在Ubuntu下汇编的命令

4.3 可重定位目标elf格式

4.4 Hello.o的结果解析

对照分析:

1. 指令映射层面: hello.s 中的符号化汇编指令(如 push %rbp 、 mov %rsp, %rbp ),在 hello.o 的反汇编结果中均对应唯一的机器码(如 push %rbp 对应 55 、 mov %rsp, %rbp 对应 48 89 e5 ),机器码是汇编指令的二进制编码形式,二者呈现一一映射的关系,机器码长度由指令复杂度决定,简单指令占1~3字节,函数调用类指令占5字节。

2. 操作数表示层面: hello.s 中用符号名或标签表示操作数(如调用库函数写为 call printf@PLT ,引用字符串写为 leaq .LC0(%rip), %rdi ),具备直观的可读性;而 hello.o 中未解析符号的操作数仅显示临时偏移量,且指令旁标注重定位标记(如 callq 6d <main+0x6d> 旁标 R_X86_64_PLT32 printf-0x4 ),无实际的物理地址,需链接阶段完成地址修正。

3. 分支转移层面: hello.s 用标签(如 .L2 、 .L4 )表示分支跳转的目标,便于人类理解代码逻辑; hello.o 的反汇编结果中,分支指令的操作数为基于当前指令的相对偏移量(如 jle 对应机器码 7e a7 , a7 即为偏移量),机器码直接编码该偏移量,实现硬件层面的分支跳转。

4. 重定位项层面: hello.s 中不存在重定位相关信息,仅体现程序的业务逻辑;hello.o反汇编结果会在指令旁标注重定位类型(如 R_X86_64_PC32 、 R_X86_64_PLT32 )与关联符号,清晰标记出需要在链接阶段修正地址的未定义符号(如 puts 、 printf 、 sleep ),这是可重定位目标文件的核心特征。

4.5 本章小结

本章围绕 Hello 程序的汇编阶段展开分析,首先明确汇编是“汇编指令→机器码”的转换过程,生成的 hello.o 是ELF格式的可重定位目标文件;其次掌握了Ubuntu下汇编操作的核心指令与实操流程;接着通过 readelf 工具解析了 hello.o 的ELF格式,明确了段结构、符号表与重定位项的核心特征;最后利用 objdump 反汇编,对比 hello.s 与 hello.o 的指令差异,揭示了机器语言的构成与汇编指令的映射关系。


5链接

5.1 链接的概念与作用

链接是GCC编译流程的第四个阶段,核心是将可重定位目标文件( hello.o )与系统库文件(如 libc.so 、动态链接器)合并,生成可执行目标文件( hello )。本实验中的链接为动态链接,区别于静态链接(直接合并库代码),动态链接仅在程序运行时加载库函数,大幅减小可执行文件体积。

链接的核心作用包含三方面:

1. 符号解析:识别并绑定 hello.o 中未定义的符号(如 printf 、 sleep 等库函数名)与系统库中的对应函数实现;

2. 地址重定位:修正 hello.o 中符号的临时偏移地址,分配唯一的虚拟内存地址,使程序能正确访问函数与数据;

3. 段合并与加载:将 hello.o 的 .text 、 .data 等段与库文件的对应段合并,按ELF格式规范构建可执行文件的段结构,并指定程序入口地址。

 链接完成后, hello 成为可直接在Linux环境运行的ELF可执行文件,具备独立执行的能力。

5.2 在Ubuntu下链接的命令

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

使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件

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

    分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。

5.4 hello的虚拟地址空间

对照分析:

(1)地址偏移与ASLR机制

ELF静态虚拟地址: readelf -l 显示 .text 段的静态虚拟地址为 0x401000 , .data 段为 0x404048 ;

实际加载地址: info proc mappings 显示程序代码段的实际加载起始地址为 0x400000 ,与静态地址完全匹配,未出现ASLR偏移(本环境中ASLR机制可能被关闭,或程序编译时禁用了地址随机化)。

若开启ASLR,实际加载地址会在 0x555555554000 附近,与静态地址产生固定偏移,这是Linux系统的地址空间随机化保护机制,用于防止缓冲区溢出攻击。

(2)段的映射关系匹配

ELF 文件的 LOAD 段虚拟地址 0x400000 对应实际映射的 0x400000~0x405000 区域,完全覆盖 .text / .data / .plt 等核心段,说明操作系统严格按照程序头表的规则加载段;

动态库 libc.so 与 ld.so 被映射到高地址区( 0x7ffff7 开头),与程序自身的段地址( 0x400000 开头)隔离,符合Linux的内存布局规则(程序代码在低地址,动态库在高地址)。

(3)段权限的一致性

ELF文件中 .text 段的 AX (读、执行)权限,对应实际映射的 r-x 权限; .data 段的 WA (读、写)权限,对应实际映射的 rw- 权限;

栈区的 rw- 权限与ELF文件中 GNU_STACK 段的 RW 标志一致,且栈区无执行权限,符合Linux的 W^X (写与执行互斥)内存保护策略。

5.5 链接的重定位过程分析

1. 未定义符号的地址绑定变化

重定位前的 hello.o 中,调用 puts 、 printf 、 atoi 等库函数的指令,仅标注临时偏移量与重定位标记(如 callq 25 <main+0x25> 旁标 R_X86_64_PLT32 puts-0x4 ),无实际内存地址;重定位后的 hello 中,这些函数调用指令已绑定到PLT表的固定地址(如 callq 401090 <puts@plt> 、 callq 4010a0 <printf@plt> ),链接器将未定义符号与 libc.so 中的函数实现关联,完成了符号到实际地址的绑定。

2. 重定位项的消长变化

hello.o 的反汇编结果中,.text 段的核心指令旁均附带 R_X86_64_PC32 、 R_X86_64_PLT32 类型的重定位项,标识需要修正的地址位置;而 hello 的反汇编结果中,所有重定位项完全消失,指令操作数均为最终的内存偏移或绝对地址,说明链接器已按重定位规则完成所有地址修正。

3. 指令操作数的实质性修正

 hello.o 中 call 类指令的操作数为 00 00 00 00 (临时占位值),如 call puts@PLT 对应机器码 e8 00 00 00 00 ;链接后的 hello 中,该操作数被修正为指向PLT表的PC相对偏移量(如 call puts@plt 对应机器码 e8 46 ff ff ff ),通过偏移量计算可精准跳转到函数入口,实现指令的正确执行。

4. 动态链接段的生成变化

 hello.o 中不存在 .plt (过程链接表)、 .got (全局偏移表)相关段与指令;链接后的 hello 生成了 .plt 、 .plt.sec 段,包含 puts@plt 、 printf@plt 等动态链接入口(如 401090 <puts@plt> 、 4010a0 <printf@plt> ),并通过 bnd jmpq *0x2fd(%rip) 等指令关联GOT表,实现了库函数的延迟绑定,这是动态链接重定位的核心特征。

5.6 hello的执行流程

5.6.1 程序加载与 main 函数入口

1. 调试准备与程序启动

在Ubuntu终端启动 gdb 并加载 hello 可执行文件,通过 break main 在 main 函数入口设置断点,执行 run 2024113240 吴博峰 15734569513 3 传入命令行参数,程序触发断点并停在 main 函数第一行( int main(int argc,char *argv[]) ),终端显示 Breakpoint 1, main (argc=5, argv=0x7fffffffdf88) at hello.c:11 ,表明程序完成动态链接与进程环境初始化,成功进入用户代码核心入口。

2. 参数传递验证

执行 print argc 输出 $1 = 5 ,验证命令行参数数量符合要求;执行 print argv[1] 输出 $2 = 0x7ffffffe369 "2024113240" ,确认学号、姓名、手机号、秒数等参数已正确传递到 main 函数。

5.6.2  main 函数的参数校验逻辑

执行 next ( n )单步调试,程序执行 if(argc!=5) 的条件判断:因 argc=5 ,条件不成立,程序跳过错误提示分支( fprintf(stderr, "用法: Hello 学号 姓名 手机号 秒数!\n") 与 exit(1) ),直接进入后续的循环输出逻辑,完成参数合法性的初步校验。

5.6.3 循环输出与函数调用的核心执行

1.  for 循环的初始化与首次执行

执行 next 进入 for(i=0;i<10;i++) 循环,首次循环中:

执行 printf("Hello %s %s %s\n", argv[1], argv[2], argv[3]) ,终端输出 Hello 2024113240 吴博峰 15734569513 ,完成问候信息打印;

执行 sleep(atoi(argv[4])) , atoi 将传入的秒数参数 3 转为整型, sleep 函数使程序休眠3秒,休眠结束后继续执行。

2. 循环的迭代执行

执行 continue ( c )让程序持续执行循环逻辑,终端依次输出10次问候信息(符合 i<10 的循环条件),每次输出间隔3秒,验证了循环次数与休眠逻辑的正确性。循环过程中, i 从0自增到9,满足条件时重复执行输出与休眠操作,不满足时跳出循环。

5.6.4 程序的阻塞等待与退出流程

1.  getchar 的阻塞执行

循环结束后,程序执行 getchar() 函数,进入阻塞状态,等待用户在终端输入字符并按下回车,该步骤用于防止程序执行完毕后直接退出,便于观察运行结果。

2. 程序的正常退出

用户输入回车后, getchar() 返回,程序执行 return 0 ,将返回值 0 存入寄存器 %eax (表示程序正常退出)。随后 __libc_start_main 调用 exit 函数,终止 hello 进程,操作系统回收进程占用的CPU时间片、内存空间、文件描述符等资源,终端显示 [Inferior 1 (process ****) exited normally] ,标志着程序执行流程完全结束。

5.6.5 执行流程的关键函数与地址总结

 main   0x4011de  程序核心逻辑 实现参数校验、循环输出与退出控制

 printf   0x4010a0  循环输出 打印问候信息到终端

 atoi   0x4010c0  循环输出 将字符串秒数转为整型数值

 sleep   0x4010b0  循环输出 实现程序的时间休眠

 getchar   0x4010d0  程序收尾 阻塞等待用户输入,防止程序直接退出

 exit   0x401080  程序退出 终止进程并释放系统资源

5.7 Hello的动态链接分析

5.7.1 动态链接核心机制与实操准备

动态链接是 hello 程序运行的关键特性——编译时未将 libc.so (C标准库)的代码嵌入可执行文件,而是在程序运行时由动态链接器 /lib64/ld-linux-x86-64.so.2  加载 libc.so 并完成库函数地址的绑定。本次实操通过 gdb 调试追踪PLT/GOT表的状态变化,核心分析对象为PLT入口 0x401050 与对应的GOT表地址 0x404010 。

5.7.2 动态链接前的PLT/GOT表状态

PLT表状态:在 gdb 中对PLT入口 0x401050 设置断点,程序首次触发该断点时,PLT表仅存储跳转到GOT表的临时指令(如 bnd jmpq *0x2fe3(%rip) ),未关联 libc.so 中库函数的实际地址。

GOT表状态:执行 x 0x404010 查看GOT表对应项,输出为 x86 汇编指令而非内存地址,表明GOT表此时仅存储指向PLT表的临时跳转标记,动态链接器尚未完成库函数地址的解析与绑定,这是“延迟绑定”机制的典型特征——程序启动时不解析所有库函数地址,仅在首次调用时完成绑定。

5.7.3 动态链接器的解析与执行过程

程序触发 0x401050 的PLT断点后,输入 c ( continue )让程序继续执行,动态链接器启动以下核心操作:

1. 库函数地址解析:动态链接器扫描 libc.so 的符号表,找到 printf 、 sleep 、 atoi 等库函数的实际内存地址;

2. GOT表地址写入:将解析得到的库函数地址写入GOT表地址 0x404010 ,完成“临时跳转标记→实际地址”的替换;

3. PLT表的二次跳转:后续调用库函数时,PLT表通过GOT表中已绑定的实际地址,直接跳转到 libc.so 中的函数实现,无需重复解析。

这一过程实现了延迟绑定,减少了程序启动时间,同时让多个进程可共享 libc.so 的内存镜像,节省系统资源。

5.7.4 动态链接完成的结果验证

功能验证: printf (打印信息)、 sleep (程序休眠)、 atoi (字符串转整型)等库函数均正常执行,证明动态链接器已成功解析并绑定库函数地址,PLT/GOT表的跳转逻辑生效;

流程验证:程序按预期完成10次循环输出,无报错或执行中断,说明动态链接不仅完成了地址绑定,还保证了库函数与用户代码的协同执行。

5.7.5 动态链接的核心特征总结

1. 延迟绑定:程序启动时仅初始化PLT/GOT表的临时标记,首次调用库函数时才由动态链接器完成地址解析,降低了程序启动开销;

2. 库共享机制: libc.so 作为共享库被映射到 hello 进程的虚拟地址空间,多个进程可复用同一份 libc.so 的内存镜像,大幅节省系统内存;

3. 灵活性:更新 libc.so 时无需重新编译 hello 程序,仅替换库文件即可,提升了程序的可维护性。

5.8 本章小结

本章通过实操工具与理论分析结合的方式,完整解析了“可重定位文件→可执行文件→进程运行”的全流程机制,揭示了ELF格式、链接、加载、动态链接等计算机系统核心概念的实际应用,为理解程序在操作系统中的运行原理提供了实操支撑。                  


6hello进程管理

6.1 进程的概念与作用

进程是操作系统进行资源分配和调度的基本单位,是程序的一次运行实例。对于 hello 程序而言,当在终端执行 ./hello 2024113240 wbf 15734569513 3 时,操作系统会为其创建一个独立的 hello 进程,核心作用体现在:

1. 资源隔离: hello 进程拥有独立的虚拟地址空间、文件描述符、CPU时间片等资源,与其他进程相互隔离,避免资源竞争与干扰;

2. 指令执行:进程作为指令的执行载体,按序执行 hello 程序的机器指令,完成参数校验、循环输出、休眠等核心逻辑;

3. 系统交互:通过系统调用(如 printf 调用 write 、 sleep 调用 nanosleep )与内核交互,实现终端输出、程序休眠等功能。

hello 进程的生命周期包含创建( fork )、加载( execve )、执行、暂停/终止、资源释放等阶段,是操作系统进程管理的典型体现。

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

Bash(Bourne-Again Shell)是Linux系统的交互式命令解释器,也是用户与内核之间的“中介”,对 hello 程序的处理流程如下:

1. 命令解析:用户在Bash终端输入 ./hello 2024113240 wbf 15734569513 3 后,Bash首先解析命令行参数,识别出可执行文件路径 ./hello 与传入的参数列表;

2. 进程创建:Bash调用 fork() 系统调用创建子进程,子进程继承Bash的环境变量、文件描述符等资源;

3. 程序加载:子进程调用 execve() 系统调用,将自身的地址空间替换为 hello 程序的ELF可执行文件,加载代码段、数据段并跳转到 _start 入口执行;

4. 等待与回收:Bash通过 wait() / waitpid() 系统调用等待 hello 子进程执行完毕,子进程退出后,Bash回收其进程资源(如僵尸进程的PCB),并返回终端控制权。

此外,Bash还提供命令补全、管道、重定向、作业控制等功能,是操作 hello 进程的核心交互工具。

6.3 Hello的fork进程创建过程

fork() 会复制当前进程(父进程,如Bash)的PCB(进程控制块)、虚拟地址空间、文件描述符等资源,生成一个几乎完全相同的子进程;子进程与父进程的唯一区别是 pid (进程ID)、 ppid (父进程ID)与 fork() 的返回值(父进程得到子进程PID,子进程得到0)。

6.4 Hello的execve过程

1.  execve 的调用格式:

 execve("./hello", argv, envp) ,其中 argv 为 hello 的参数列表( ["hello", "2024113240", ...] ), envp 为环境变量(如 PATH 、 HOME );

2. 核心加载步骤:

销毁当前进程的代码段、数据段、堆、栈等资源,保留PID、文件描述符等核心信息;

解析 hello 的ELF可执行文件,将代码段( .text )、数据段( .data / .bss )映射到进程的虚拟地址空间;

初始化程序的栈帧,将命令行参数与环境变量压入栈中;

跳转到ELF文件头定义的入口地址 0x4010f0 ,开始执行 hello 的指令。

3. 与 fork 的协同:

 fork 创建的子进程是“空壳”进程,需通过 execve 加载 hello 的可执行代码,才能成为真正执行 hello 逻辑的进程,二者结合是Linux启动新程序的标准方式。

6.5 Hello的进程执行

1. 进程调度与时间片:

Linux采用分时调度策略,为 hello 进程分配固定的时间片(如10ms)。当 hello 进程获得CPU使用权时,开始执行指令;时间片耗尽后,内核的调度器会暂停 hello 进程,保存其上下文(寄存器、程序计数器等),并将CPU分配给其他就绪进程;待再次被调度时,恢复上下文继续执行,实现多进程的并发运行。

2. 用户态与核心态的转换:

 hello 进程的执行分为用户态与核心态:

用户态:执行 main 函数中的普通指令(如循环、变量运算),此时进程只能访问自身的虚拟地址空间,无法直接操作内核资源;

核心态:当调printf 、sleep 、 getchar 等库函数时,会触发 write 、 nanosleep 、 read 等系统调用,进程从用户态陷入核心态,由内核完成硬件操作(如终端输出、定时器控制),操作完成后返回用户态继续执行。

特权级转换是进程与内核交互的核心方式,保证了系统资源的安全访问。

6.6 hello的异常与信号处理

6.6.1 常见信号与触发方式

操作键 发送信号 信号含义  hello 进程的默认处理行为

 Ctrl-C   SIGINT (2) 中断进程 立即终止 hello 进程,退出运行

 Ctrl-Z   SIGTSTP (20) 暂停进程 暂停 hello 进程,进入后台停止状态

非法指令/内存访问  SIGSEGV (11) 段错误 终止进程并生成核心转储文件(core dump)

6.6.2 信号处理的实操指令与结果

1.  Ctrl-Z 暂停 hello 进程后:

执行 jobs :查看后台作业,输出 [1]+  Stopped                 ./hello 2024113240 wbf 15734569513 3 ,显示 hello 进程处于停止状态;

执行 ps -aux | grep hello :查看进程状态, STAT 列显示为 T (暂停),记录 hello 的PID(如 5800 );

执行 pstree -p :查看进程树,可看到 bash 父进程下的 hello 子进程(PID:5800)处于暂停状态;

执行 fg %1 :将后台暂停的 hello 进程调回前台继续执行,终端恢复 hello 的循环输出;

执行 kill -9 5800 :发送 SIGKILL (9)信号强制终止 hello 进程,执行 ps 可查看到进程已消失。

2.  Ctrl-C 中断 hello 进程后:

执行 ps -aux | grep hello ,无 hello 进程的输出,证明 SIGINT 信号触发了进程的正常终止。

6.6.3 信号处理的核心机制

Linux内核为每个进程维护信号掩码与信号处理函数:当信号到达时,若进程未屏蔽该信号,会按默认行为(终止、暂停、忽略)处理,或执行用户自定义的信号处理函数; hello 程序未自定义信号处理逻辑,因此采用内核的默认处理规则。

6.7本章小结

本章围绕 hello 进程的全生命周期,解析了进程的基础概念、Shell的调度作用、 fork 与 execve 的进程创建加载机制、进程执行的调度与特权级转换,以及信号驱动的异常处理。

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


7hello的存储管理

7.1 hello的存储器地址空间

1. 逻辑地址:由段选择子和段内偏移量组成,是程序编译链接时生成的地址(如 hello.o 中 .text 段的 0x0 偏移)。在x86_64的平坦地址空间中,段选择子被弱化,逻辑地址几乎等同于段内偏移量, hello 的汇编指令中直接使用的偏移地址(如 0x4010f0 )本质是逻辑地址的简化形式。

2. 线性地址(虚拟地址):是进程视角下的连续地址空间, hello 进程拥有独立的64位虚拟地址空间(范围 0x0 ~ 0x7fffffffffff ),包含代码段( 0x400000 开始)、数据段、栈区( 0x7fffffff 开始)、动态库映射区( 0x7ffff7 开始)等区域,与物理内存无直接关联,由MMU(内存管理单元)完成地址转换。

3. 物理地址:是实际物理内存的硬件地址(由内存条的物理芯片决定),Linux内核通过页表将 hello 的虚拟地址映射到物理地址,实现程序对物理内存的间接访问。

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

1. 段式管理的核心结构:

内核为 hello 进程维护段描述符表(GDT/LDT),其中代码段描述符、数据段描述符的基地址设为0,段限长设为 0xffffffffffffffff ,意味着代码段、数据段的逻辑地址偏移量直接等同于线性地址。

2. 变换过程:

当 hello 执行指令访问内存时,CPU从段选择子中提取段描述符索引,在GDT中找到对应段描述符,由于基地址为0,线性地址 = 逻辑地址的段内偏移量(如 hello 的 main 函数逻辑地址 0x4011de 直接映射为线性地址 0x4011de )。

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

1. 分页粒度与地址划分:

x86_64默认采用4KB分页,将线性地址划分为5个部分:PML4索引(9位)、PDPT索引(9位)、PD索引(9位)、PT索引(9位)、页内偏移(12位)。以 hello 的线性地址 0x4010f0 为例,按位划分后得到各级页表的索引与页内偏移。

2. 4级页表的查找流程:

CPU从 cr3 寄存器读取 hello 进程的PML4表物理基地址;

用PML4索引找到PDPT表的物理地址;

用PDPT索引找到PD表的物理地址;

用PD索引找到PT表的物理地址;

用PT索引找到物理页框的基地址,加上页内偏移,最终得到物理地址。

3.  hello 的页表映射特征:

 hello 的代码段( .text )、数据段( .data )被映射到连续的物理页框,而栈区、动态库映射区则按需求动态映射,体现了页式管理“按需分配”的特性。

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

1. TLB的工作原理:

TLB缓存了虚拟地址到物理地址的映射关系(含页表项的核心信息)。当 hello 访问某虚拟地址时,CPU首先在TLB中查找对应的映射关系:

TLB命中:直接从TLB中获取物理地址,无需访问4级页表,地址变换耗时仅数纳秒;

TLB未命中:按4级页表流程查找物理地址,并将新的映射关系写入TLB,供后续访问使用。

2. TLB对 hello 的优化效果:

 hello 的循环输出逻辑会反复访问 printf 、 sleep 等函数的虚拟地址,这些地址的映射关系会被缓存到TLB中,大幅减少页表查找的次数,提升程序执行效率。

3. 四级页表与TLB的协同:

四级页表实现了虚拟地址的精细映射,而TLB则弥补了多级页表的性能损耗,二者结合让 hello 进程在享受虚拟内存隔离性的同时,保持高效的地址变换速度。

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

1. 三级Cache的层级特征:

L1 Cache:分为指令Cache和数据Cache,容量小(如32KB)、速度最快(访问耗时~1ns),紧邻CPU核心;

L2 Cache:容量中等(如256KB)、速度次之(~3ns),为单个CPU核心私有;

L3 Cache:容量大(如12MB)、速度稍慢(~10ns),为所有CPU核心共享。

2. Cache对 hello 的访问优化:

 hello 的代码段指令被加载到L1指令Cache,CPU执行循环指令时直接从L1读取,无需访问物理内存;

 hello 的全局变量、栈变量被缓存到L1/L2数据Cache,频繁的变量读写操作通过Cache完成,大幅降低物理内存的访问频率。

3. 缓存一致性与 hello 的执行:

若 hello 进程被多CPU核心调度,MESI协议保证三级Cache的缓存一致性,避免因缓存数据不一致导致的程序执行错误,确保 hello 的逻辑正确运行。

7.6 hello进程fork时的内存映射

1. fork后的初始映射状态:

父进程(如Bash)的页表被复制给 hello 子进程,父子进程的虚拟地址指向同一份物理页框,且这些物理页框被标记为“只读”。此时 hello 子进程并未实际复制物理内存,仅共享父进程的内存资源。

2. 写时复制的触发:

当 hello 子进程执行写操作(如修改全局变量、栈变量)时,CPU检测到对只读物理页的写访问,触发缺页中断。内核为该物理页分配新的物理页框,将原页的数据复制到新页,并更新子进程的页表,使子进程的虚拟地址指向新的物理页,之后解除新页的只读限制,允许写操作。

3.  hello 的fork内存映射特征:

 hello 的代码段为只读属性,父子进程始终共享同一份代码段物理页;而数据段、栈区在发生写操作时才会触发复制,大幅节省了 fork 时的内存开销,让子进程快速创建。

7.7 hello进程execve时的内存映射

1. ELF文件的段映射:

内核解析 hello 的ELF文件头与程序头表,将不同的段按权限映射到虚拟地址空间:

代码段( .text ):映射为 只读+执行 权限,关联到ELF文件的代码段偏移;

数据段( .data / .bss ):映射为 可读+可写 权限, .data 关联到ELF文件的数据段偏移, .bss 则映射为匿名页(初始化为0);

动态链接段( .plt / .got ):映射为 可读+可写 权限,为动态链接提供支撑。

2. 按需映射与缺页中断:

内核并非一次性将 hello 的所有段加载到物理内存,而是先建立虚拟地址与ELF文件的映射关系。当 hello 执行指令访问某段时,触发缺页中断,内核才将对应的ELF文件内容加载到物理页框,实现“按需加载”。

3. 动态库的映射:

内核同时映射 libc.so 等动态库到 hello 的虚拟地址空间( 0x7ffff7 开头),动态库的代码段为共享映射,多个进程可复用同一份物理页,节省内存资源。

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

1. 可恢复缺页故障(按需加载/写时复制):

按需加载缺页: hello 执行 execve 后,首次访问代码段虚拟地址时,该地址未映射到物理内存,触发缺页中断。内核从 hello 的ELF文件中读取对应数据,加载到物理页框,更新页表后恢复进程执行;

写时复制缺页: hello 子进程首次写数据段时,触发缺页中断,内核按COW机制分配新物理页、复制数据、更新页表,恢复执行。

2. 不可恢复缺页故障(段错误):

若 hello 访问非法虚拟地址(如 0x0 空指针),内核无法找到对应的映射关系,会发送 SIGSEGV (段错误)信号给 hello 进程,默认处理行为是终止进程并生成核心转储文件, hello 程序会立即退出并提示段错误。

7.9动态存储分配管理

1. 堆区的管理方式:

 malloc 通过 brk() 或 mmap() 系统调用向内核申请内存:

小内存分配(<128KB):通过 brk() 调整堆顶指针,扩展堆区空间,采用空闲链表(如双向链表、伙伴系统)管理空闲内存块;

大内存分配(≥128KB):通过 mmap() 映射匿名页,分配独立的内存区域,释放后直接归还给内核。

2. 内存分配的优化策略:

内存池: malloc 会预分配一块内存作为内存池,小内存申请直接从内存池分配,避免频繁调用系统调用;

空闲块合并:当 free 释放内存块时,会将相邻的空闲块合并,减少内存碎片,提升内存利用率。

3.  hello 的动态内存使用:

 printf 会动态分配缓冲区存储格式化字符串, getchar 会分配输入缓冲区,这些内存由 malloc 管理,使用完毕后通过 free 释放(或由库函数自动释放),保证内存资源的循环利用。

7.10本章小结

本章围绕 hello 进程的存储管理展开分析,从地址空间划分、地址变换机制,到缓存优化、内存映射、缺页处理、动态存储分配,完整揭示了Linux系统对内存资源的调度逻辑。


8hello的IO管理

8.1 Linux的IO设备管理方法

1. 设备的模型化:文件抽象

Linux将键盘、显示器、磁盘等IO设备抽象为设备文件,存储在 /dev 目录下(如显示器对应 /dev/tty 、键盘对应 /dev/input/event0 )。 hello 程序调用 printf 输出内容到显示器、调用 getchar 从键盘读取输入,本质是对这些设备文件的读写操作,无需关注设备的硬件细节,由内核的设备驱动完成硬件指令的转换。

2. 设备管理的核心方式

MMIO(内存映射IO):将设备的寄存器映射到进程的虚拟地址空间,CPU通过访问内存地址的方式操作设备寄存器。例如显示器的显示缓冲区被映射到虚拟地址, hello 的输出内容通过MMIO写入显示缓冲区,实现屏幕显示;

IO端口(IO Port):针对部分外设(如早期键盘控制器),Linux通过专门的IO端口地址(如 0x60 )访问设备,内核通过 in / out 指令读写IO端口,完成设备数据的交互。 hello 的键盘输入读取过程中,内核通过IO端口获取键盘扫描码,再转换为ASCII码传递给程序。

8.2 简述Unix IO接口及其函数

Unix IO接口是Linux系统IO操作的基础规范,定义了一套统一的函数接口用于访问文件(含设备文件), hello 程序的IO操作基于这些接口实现

1.  open(const char *path, int flags, mode_t mode) 

打开文件/设备文件,返回文件描述符(非负整数)。 hello 程序运行时,内核会默认打开标准输入( STDIN_FILENO=0 )、标准输出( STDOUT_FILENO=1 )、标准错误( STDERR_FILENO=2 )三个设备文件,分别对应键盘、显示器、终端错误输出,无需显式调用 open 。

2.  read(int fd, void *buf, size_t count) 

从文件描述符 fd 对应的设备/文件中读取 count 字节数据到缓冲区 buf 。 hello 的 getchar 函数底层调用 read(0, buf, 1) ,从标准输入(键盘)读取1字节的字符数据。

3.  write(int fd, const void *buf, size_t count) 

将缓冲区 buf 中的 count 字节数据写入文件描述符 fd 对应的设备/文件。 hello 的 printf 函数底层调用 write(1, buf, len) ,将格式化后的字符串写入标准输出(显示器)。

4.  close(int fd) 

关闭文件描述符,释放对应的设备资源。 hello 程序退出时,内核自动关闭标准输入、输出、错误的文件描述符,完成设备资源的回收。

5.  lseek(int fd, off_t offset, int whence) 

调整文件的读写偏移量,主要用于磁盘文件操作, hello 的IO操作(键盘/显示器为字符设备)无需调用该函数。

8.3 printf的实现分析

1. 格式化字符串生成: vsnprintf 的作用

 printf 是可变参数函数,首先调用 vsnprintf 函数处理格式化字符串与参数列表:

vsnprintf 根据 %s 等格式符,将 argv[1] (学号)、 argv[2] (姓名)、 argv[3] (手机号)等参数按格式拼接为完整的字符串(如 Hello 2024113240 wbf 15734569513\n );

将拼接后的字符串写入用户态缓冲区,为后续的系统调用做准备。

2. 系统调用层: write 的触发

格式化字符串生成后, printf 通过系统调用陷入核心态,调用 write(STDOUT_FILENO, buf, len) :

内核接收 write 请求后,根据文件描述符 1 (标准输出)找到对应的设备驱动(终端驱动 tty );

终端驱动将字符串数据转换为符合终端显示规范的格式(如ASCII码对应的字符显示指令)。

3. 硬件驱动层:显示器的显示流程

终端驱动通过MMIO将显示数据写入显示器的显存(帧缓冲区),显存中的数据按像素格式(RGB)存储每个字符的显示信息;

显示芯片按刷新频率(如60Hz)从显存读取数据,通过信号线将像素的RGB信息传输到显示器,最终在屏幕上显示 hello 的输出内容。

8.4 getchar的实现分析

1. 键盘的硬件中断触发

当用户按下键盘按键时,键盘控制器会向CPU发送硬件中断请求(IRQ1),CPU暂停 hello 进程的执行,转而执行内核的键盘中断处理程序:

中断处理程序读取键盘的扫描码(如按下回车键的扫描码 0x1C );

将扫描码转换为对应的ASCII码(如回车键对应 \n 的ASCII码 0x0A ),并将ASCII码写入内核的键盘输入缓冲区。

2.  read 系统调用的阻塞与读取

 getchar 底层调用 read(STDIN_FILENO, buf, 1) ,该调用具有阻塞特性:

若内核键盘缓冲区为空, read 调用会使 hello 进程进入睡眠状态,放弃CPU使用权,直到有键盘输入触发中断并写入缓冲区;

当缓冲区中有数据时,内核将缓冲区中的ASCII码读取到 hello 进程的用户态缓冲区, read 调用返回, getchar 获取到输入的字符。

3. 输入的终止与进程恢复

当用户按下回车键(ASCII码 0x0A ), getchar 读取到该字符后返回, hello 进程从阻塞状态恢复,继续执行后续的 return 0 语句,最终退出程序。

8.5本章小结

本章围绕 hello 程序的IO操作( printf 输出、 getchar 输入),解析了Linux系统的IO设备管理方法与Unix IO接口的核心机制。

结论

本次实验以 hello 程序为研究对象,从编译链接、ELF格式、进程管理、存储管理、IO管理五个核心维度,完整剖析了“C语言源程序→可执行文件→进程运行”的全流程机制,深入理解了计算机系统的分层设计思想与核心原理。 

一、实验过程的核心收获

1. 编译链接的本质是“语言转换与符号绑定”:预处理、编译、汇编、链接四个阶段将人类可读的C语言转换为机器可执行的ELF文件,其中链接器的重定位过程实现了未定义符号的地址绑定,是可重定位文件转换为可执行文件的关键;

2. 进程是程序运行的载体: hello 程序的运行本质是一个独立进程的创建、调度与消亡过程,Linux通过 fork + execve 实现进程创建,通过分时调度与信号机制实现进程的管控,体现了操作系统“资源分配与调度”的核心功能;

3. 虚拟内存是内存管理的核心:x86_64架构的段页式管理实现了虚拟地址到物理地址的映射,写时复制、按需加载、缺页中断等机制让内存资源得到高效利用,TLB与三级Cache则优化了内存访问的效率;

4. IO操作是程序与硬件的桥梁:Linux的“一切皆文件”思想将IO设备抽象为文件对象,Unix IO接口实现了IO操作的标准化, printf 与 getchar 的底层实现涉及系统调用、硬件中断、设备驱动等多层级协作,揭示了程序与硬件交互的复杂逻辑。

二、对计算机系统设计与实现的感悟

1. 分层设计的重要性:计算机系统采用“应用层→库函数层→操作系统层→硬件层”的分层架构,每层封装自身的细节,向上提供简洁的接口(如 printf 封装了复杂的显示驱动逻辑),降低了程序开发的复杂度;

2. 抽象与复用的设计思想:“一切皆文件”的设备抽象、动态链接的库共享机制、虚拟内存的地址抽象,均体现了计算机系统“抽象化、复用化”的设计原则,大幅提升了系统的可维护性与资源利用率;

3. 效率与安全的平衡:分时调度、TLB缓存、写时复制等机制优化了系统效率,而用户态与核心态的隔离、内存权限的管控、信号的异常处理则保证了系统的安全性,操作系统始终在“效率”与“安全”之间寻求平衡。

三、创新思考与改进方向

1. 实验拓展方向:可进一步分析 hello 程序的多线程实现,探究线程与进程的资源共享差异,以及内核的线程调度机制;也可研究动态链接库的手动编写与加载,深入理解动态链接的底层原理;

2. 技术优化思考:针对 hello 程序的循环输出逻辑,可通过内存映射文件替代标准输出,提升大篇幅内容的输出效率;也可利用信号量机制实现 hello 进程与其他进程的同步通信,拓展进程间交互的实现方式。


附件

1.  hello.i 

作用:是预处理阶段的产物。预处理会展开源文件中的头文件(如  #include <stdio.h>  会把  stdio.h  的内容全部展开)、替换宏定义、处理条件编译(如  #if  等),最终生成仅包含纯 C 代码的文件,为后续编译成汇编代码做准备。

2.  hello.s 

作用:是编译阶段的产物。编译过程会把预处理后的纯 C 代码( hello.i )转换成汇编语言代码,能看到程序对应的汇编指令(如  mov 、 call  等指令),是从高级语言(C)向机器码过渡的中间形式。

3.  hello.o 

作用:是汇编阶段的产物。汇编过程会把汇编代码( hello.s )转换成二进制目标文件(包含机器能识别的指令和数据,但还没完成“链接”步骤)。 hello.o  本身无法直接运行,因为它还缺少对库函数(如  printf  所在的  libc  库)或其他目标文件的链接。


参考文献

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

[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.

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

原文链接:https://blog.csdn.net/wbf2024113240/article/details/156511085

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

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