计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 人工智能领域方向
学 号 2023110738
班 级 23WLR13
学 生 赵君威
指 导 教 师 吴锐
计算机科学与技术学院
2025年5月
Hello作为每个程序员的第一个程序,表面虽然只有几行简单而冰冷的代码,但在程序编译、创建进程的背后却蕴含着计算机系统复杂的设计原理和秩序井然的执行过程。如果不了解预处理器、编译器等基础部件的工作原理,可能就无法深入地理解Hello程序的具体执行过程。本文主要根据深入理解计算机系统原书第3版(CS:APP)的内容,基于Linux系统下的C语言,利用gcc, gdb, edb等工具,通过对Hello程序的预处理、编译、汇编、链接、进程管理、存储管理、IO管理进行解读,深入理解Hello程序的完整生命周期,即P2P的过程。在研究Hello程序的过程中,整理了一系列中间产物,对程序的完整执行过程进行了详细的阐述,有助于计算机系统的学习和理解。
关键词:汇编;预处理;进程;链接;编译;存储;
(摘要0分,缺失-1分,根据内容精彩程度酌情加分0-1分)
目 录
2.2在Ubuntu下预处理的命令.......................................................................... - 6 -
3.2 在Ubuntu下编译的命令............................................................................. - 9 -
4.2 在Ubuntu下汇编的命令........................................................................... - 17 -
5.2 在Ubuntu下链接的命令........................................................................... - 24 -
5.3 可执行目标文件hello的格式.................................................................. - 24 -
5.5 链接的重定位过程分析............................................................................... - 27 -
6.2 简述壳Shell-bash的作用与处理流程..................................................... - 35 -
6.3 Hello的fork进程创建过程..................................................................... - 35 -
6.6 hello的异常与信号处理............................................................................ - 37 -
第7章 hello的存储管理............................................................................... - 41 -
7.1 hello的存储器地址空间............................................................................ - 41 -
7.2 Intel逻辑地址到线性地址的变换-段式管理............................................ - 41 -
7.3 Hello的线性地址到物理地址的变换-页式管理...................................... - 42 -
7.4 TLB与四级页表支持下的VA到PA的变换............................................. - 42 -
7.5 三级Cache支持下的物理内存访问.......................................................... - 43 -
7.6 hello进程fork时的内存映射.................................................................. - 43 -
7.7 hello进程execve时的内存映射.............................................................. - 44 -
7.8 缺页故障与缺页中断处理........................................................................... - 44 -
8.1 Linux的IO设备管理方法.......................................................................... - 46 -
8.2 简述Unix IO接口及其函数....................................................................... - 46 -
第1章 概述
1.1 Hello简介
Hello几乎是每个程序员的第一个程序,其整个生命周期体现了计算机系统各层次的精密协作,从源代码(Program)的"0"开始,最终又回归到进程(Process)终止的"0"状态,完成了一次完整的P2P和020的旅程。
P2P是指从程序到进程的的完整生命周期,主要包括程序编译和进程创建两个阶段。首先用户创建hello.c源文件,完成C语言代码的编写,然后按顺序进行以下四个过程:预处理,编译,汇编,链接。编写完的hello.c源程序,先经过预处理器处理宏和头文件,生成hello.i文件。再经过编译器,将C代码编译为汇编代码hello.s。之后汇编器将汇编代码转为可重定位目标程序hello.o,最后链接器合并库函数生成可执行文件hello.out, 即最终的hello文件。这就完成了程序的编译阶段。
得到可执行文件后,就可以进行进程的创建。Bash通过fork()创建子进程,execve()加载可执行文件,替换进程映像,加载器通过mmap()将程序段映射到虚拟地址空间,CPU 开始执行main(),程序正式成为一个活跃的进程,完成P2P的过程。
020 是指hello 程序从无到有再到无的完整生命周期。hello 程序从 "0"(未运行)开始,由 Shell 调用 fork() 创建子进程,execve() 加载其代码段和数据段,OS 为其分配虚拟内存、页表和 CPU 时间片;CPU 通过取指-译码-执行流水线运行指令,MMU 借助 TLB 和页表完成地址转换,Cache 和 RAM 协作加速数据访问,最终 printf 通过系统调用在屏幕输出 "Hello ";程序终止后,OS 回收其内存、文件描述符等资源,进程状态回归 "0",不留痕迹,完成020的过程。
1.2 环境与工具
硬件环境:
处理器:Intel(R) Core(TM) i5-9300HF CPU @ 2.40GHz 2.40 GHz
机带RAM:8.00 GB
系统类型:64 位操作系统, 基于 x64 的处理器
软件环境:Windows 11 (版本号:23H2),VMware Workstation, Ubuntu 24.04.1 LTS
开发与调试工具:gcc, vim, objdump, edb, gdb, cat等工具和指令
1.3 中间结果
hello.c:初始hello程序的C语言源程序
hello.i:经过预处理的hello源程序
hello.s:由预处理代码生成的汇编程序
hello.o:二进制可重定位目标程序
hello:经过链接后的可执行目标程序
hello_dasm.txt:反汇编hello.o得到的反汇编文件
hello1_dasm.txt:反汇编hello可执行文件得到的反汇编文件
1.4 本章小结
本章通过对hello的P2P和020过程进行解释,梳理了hello程序从创建到执行,再到结束的过程。之后总结了本次任务用到的软件、硬件环境与开发调试工具,以及所有具体的中间结果文件。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
预处理(Preprocessing)是C语言在编译前对源程序进行的文本处理工作,即编译过程的第一个阶段,由预处理器执行,主要的任务包括:
宏展开:替换 #define 定义的宏。
头文件插入:递归插入 #include 指定的头文件内容。
条件编译:处理 #ifdef、#if、#else 等指令,决定哪些代码参与编译。
删除注释:移除所有 // 和 /* */ 注释。
添加行标记:保留 #line 指令,用于调试和错误定位。
预处理后的文件(hello.i)仍然是文本文件,但已无宏和头文件依赖,可直接交给编译器处理。
2.2在Ubuntu下预处理的命令
根据编译要求gcc -m64 -Og -no-pie -fno-stack-protector -fno-PIC
输入编译指令gcc -m64 -Og -no-pie -fno-stack-protector -fno-PIC -E hello.c -o hello.i
![]()
图1 预处理命令
2.3 Hello的预处理结果解析
用cat查看hello.i的内容,

图2 hello.i的开头部分

图3 hello.i的最后一部分代码

图4 hello.c源代码
在经过预处理之后的hello.i文件中,代码的行数增加了很多。可以发现,原先的注释已经消失。在代码中,前面包含一部分加载到程序中的头文件的代码,最后一部分的代码与源程序相同。由于篇幅有限,中间的大量代码并未展示。
2.4 本章小结
本章总结了预处理的概念和作用,并且在乌班图平台对源程序进行了预处理,对比了hello.i和hello.c的代码内容,分析了预处理对程序的作用机制。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
编译(Compilation)是将高级程序设计语言编写的源程序转化为目标机器能理解的表示形式或机器语言的过程,也就是将预处理后的 hello.i 文件转换为汇编代码hello.s的过程,由编译器(cc1)完成,主要作用包括:
语法分析:检查代码是否符合 C 语言规范。
语义分析:验证变量类型、作用域等逻辑正确性。
代码优化:根据 -Og(优化等级)等选项调整指令。
生成汇编:将高级 C 代码转换为目标架构(如 x86-64)的汇编指令。
编译阶段不涉及内存布局,仅生成低级指令。汇编代码仍为文本文件,可人工阅读和调试。
3.2 在Ubuntu下编译的命令
编译指令:gcc -m64 -Og -no-pie -fno-stack-protector -fno-PIC -S hello.i -o hello.s
![]()
图5 编译指令
3.3 Hello的编译结果解析
3.3.1 基础知识

图6 C语言各数据类型在x86-64的大小
图6展示了不同数据类型在x86-64的字节大小,图7展示了整数寄存器的存储方式,图8体现了不同的整数算数操作。

图7 整数寄存器(CS:APP第三版)

图8 整数算数操作(CS:APP第三版)
3.3.2 hello.s完整代码
用vim查看hello.s的完整代码,如下图。



图9 hello.s完整代码
下面将分别从伪代码,局部变量,字符串常量,立即数,赋值操作,参数传递,数组,函数调用,for循环,指针等角度对汇编代码进行解释。
3.3.3 伪代码

图10 伪代码
在文件开头,以.开头的代码段通常是指导汇编器和连接器工作的伪指令。对我们而言一般不必理会。
3.3.4 局部变量

图11 局部变量i
在main函数中,定义了int类型的局部变量i,并且在后面参与了for循环。

图12 变量 i在%ebp中
movl $0, %ebp ; i = 0(初始化)
addl $1, %ebp ; i++(每次循环递增)
cmpl $9, %ebp ; 比较 i <= 9
3.3.5 字符串常量

图13 字符串常量
字符串常量存储在只读数据段(.rodata段),其中中文部分以UTF-8编码(中文字符转义),格式化字符串定义在.LC1标签下。
3.3.6 立即数

图14 立即数
立即数为$加数字表示。
3.3.7 赋值操作
![]()
图15 变量i赋值
movl $0, %ebp ; i = 0
movq %rsi, %rbx ; 保存argv指针到%rbx
3.3.8 参数传递
(1)printf 参数

图16 printf 参数
movq 16(%rbx), %rcx ; argv[3] -> %rcx(第4参数)
movq 8(%rbx), %rdx ; argv[2] -> %rdx(第3参数)
movl $.LC1, %esi ; 格式化字符串 -> %rsi(第2参数)
(2)sleep 参数
![]()
图17 sleep 参数
movl %eax, %edi ; atoi(argv[4])的结果 -> %edi(第1参数)
3.3.9 数组

图18 数组
这里通过基址寄存器 %rbx(存储 argv 地址)和偏移量访问数组元素。
movq 8(%rbx), %rdx ; argv[2](偏移+8)
movq 16(%rbx), %rcx ; argv[3](偏移+16)
movq 24(%rbx), %r8 ; argv[4](偏移+24)
3.3.10 指针
(1)指针作为数组访问(argv)

图19 指针作为数组访问
movq %rsi, %rbx ; 保存 argv 的基地址到 %rbx
movq 8(%rbx), %rdx ; argv[1](偏移+8)
movq 16(%rbx), %rcx ; argv[2](偏移+16)
movq 24(%rbx), %r8 ; argv[3](偏移+24)
movq 32(%rbx), %rdi ; argv[4](偏移+32)
%rbx 存储 argv 的基地址(即 argv[0] 的地址)。通过 偏移量(8, 16, 24, 32)访问不同元素,每个指针占 8 字节
(2)指针作为函数参数传递
1.printf 的格式化字符串:
![]()
图20 printf 的格式化字符串
movl $.LC1, %esi ; .LC1 是字符串 "Hello %s %s %s\n" 的地址
2.puts的错误提示字符串:
![]()
图21 puts 的错误提示字符串
movl $.LC0, %edi ; .LC0 是字符串 "用法: Hello..." 的地址
call puts
3.指针数组的传递
![]()
图22 指针数组的传递
main:
movq %rsi, %rbx ; 保存 argv 指针到 %rbx
argv 通过 %rsi 传递给 main 函数。
4.动态内存指针(stdin)
![]()
图23 动态内存指针(stdin)
movq stdin(%rip), %rdi ; 获取 stdin 的文件指针
call getc ; 调用 getc(stdin)
stdin 是全局变量,通过 %rip 相对寻址获取其地址。
(3)指针运算的隐含操作
字符串到整数的转换(strtol)

图24 字符串到整数的转换
movq 32(%rbx), %rdi ; argv[4] -> %rdi(字符串指针)
movl $10, %edx ; 基数=10
call strtol ; 调用 strtol(argv[4], NULL, 10)
strtol 的第一个参数是 char* 类型,指向待转换的字符串(argv[4])。
![]()
图25 指针解引用
movq 8(%rbx), %rdx ; argv[1] -> %rdx(printf的第二个%s)
在 printf 调用中,argv[1]、argv[2] 等作为 %s 参数时,实际传递的是指针的值(即字符串地址)。
3.3.11 for循环

图26 for循环
.L2:
cmpl $9, %ebp ; 比较 i <= 9
jle .L3 ; 满足条件跳转到循环体
.L3:
addl $1, %ebp ; i++
jmp .L2 ; 跳回条件判断
3.3.12 函数调用

图27 函数调用
call __printf_chk ; 安全版本的printf
call strtol ; 字符串转整数
call sleep ; 休眠
call getc ; 读取字符
函数的返回地址自动压栈,通过ret指令恢复。
3.4 本章小结
本章通过hello.i编译得到hello.s,将hello.s和源程序进行对比,对汇编代码分别从伪代码,局部变量,字符串常量,立即数,赋值操作,参数传递,数组,函数调用,for循环,指针等角度进行解释,阐述了汇编代码的具体实现过程。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
汇编(Assembly)是一种面向机器的低级程序设计语言的实现,也是将汇编代码(.s)转换为机器语言二进制目标程序(.o)的过程,由汇编器(as)完成,主要作用包括:
指令编码:将助记符转换为机器码。
符号解析:标记全局符号的地址。
生成重定位信息:为链接阶段提供未解析符号的修正信息。
4.2 在Ubuntu下汇编的命令
汇编指令gcc -m64 -Og -no-pie -fno-stack-protector -fno-PIC -c hello.s -o hello.o
![]()
图28 汇编指令
4.3 可重定位目标elf格式
1.输入readelf -h hello.o查看elf头,

图29 hello.o中的elf头
ELF头是ELF文件开头的关键部分,包含魔数、类别、数据编码、版本、OS/ABI、类型、机器类型、入口点地址等信息,还包括程序头表和节头表的相关数据,它为系统加载器和链接器提供了文件的属性、结构等必要信息,以保证ELF文件能在目标系统中被正确解析、加载和运行。上图Data部分可以看出为小端序,Type为REL重定位目标文件。
2.输入readelf -S hello.o查看section头,

图30 hello.o中的section头
可以看出,hello.o中有15个节,有10个重定位条目。下面对各个节进行具体分析。
表1 hello.o中各节名称及用途
| 节名称 | 用途 |
| .text | 存放程序的可执行代码 |
| .rela.text | 存储代码段的重定位信息,用于链接时修正符号引用 |
| .data | 存放已初始化的全局变量和静态变量 |
| .bss | 存放未初始化的全局变量和静态变量,不占用文件空间 |
| .rodata.str1.8 | 存放只读字符串常量 |
| .rodata.str1.1 | 存放只读字符串常量 |
| .comment | 存放编译器或其他工具生成的注释信息 |
| .note.GNU-stack | 存放与 GNU 栈相关的注释信息 |
| .note.gnu.property | 存放 GNU 相关的属性注释信息 |
| .eh_frame | 存放异常处理相关的帧信息 |
| .rela.eh_frame | 存储异常处理帧段的重定位信息 |
| .symtab | 存放程序中的符号信息,如变量、函数的名称和地址 |
| .strtab | 存放符号表中符号名称的字符串 |
| .shstrtab | 存放节头表中节名称的字符串 |
3.输入readelf -s hello.o查看符号表,

图31 hello.o中的符号表
符号表包含 13 个符号条目,有 4 个局部符号,包括表示文件的符号(hello.c),以及表示.text、.rodata.str1.8、.rodata.str1.1节的符号,这些符号仅在目标文件内部可见,用于文件内部的管理和引用。
有8个全局符号,其中main函数是用户定义的全局符号,大小为 135 字节,位于.text节。其余7个全局符号(puts、exit、__printf_chk、strtol、sleep、stdin、getc)是外部引用符号,当前目标文件依赖这些符号的定义,且这些符号在链接时需要从其他目标文件或库中解析。
符号类型包含NOTYPE(无特定类型)、FILE(表示源文件)、SECTION(表示节)、FUNC(表示函数)等类型。
4. 输入readelf -r hello.o查看可重定位段信息,

图32 hello.o中的可重定位段信息
(1).rela.text 重定位段:
位于文件偏移量 0x310 处,包含 9 个重定位条目。重定位类型主要有R_X86_64_32(用于 32 位绝对地址的重定位)、R_X86_64_PLT32(与过程链接表相关的 32 位重定位,通常用于解析外部函数调用)、R_X86_64_PC32(基于程序计数器的 32 位重定位)。
重定位条目主要涉及对.rodata.str1.8、.rodata.str1.1节的引用,以及对puts、exit、__printf_chk、strtol、sleep、stdin、getc等外部符号的引用,在链接时需要修正这些引用以指向正确的地址。
(2).rela.eh_frame 重定位段:
位于文件偏移量0x3e8处,包含1个重定位条目。重定位类型为R_X86_64_PC32,是对.text节的引用,用于修正异常处理帧相关的地址。
4.4 Hello.o的结果解析
输入objdump -d -r hello.o 对hello.o进行反汇编,并与第3章中的 hello.s进行对照分析。


图33 hello.o反汇编
反汇编得到的结果与汇编代码大致相同,但在每条指令前面有一组16进制的代码,即机器码。机器语言由二进制代码组成,是计算机能够直接识别和执行的语言。这些二进制代码可分为操作码和操作数两部分,操作码指示计算机执行何种操作,如加法、减法;操作数则是操作的对象,比如参与运算的数据或数据所在的内存地址。
汇编语言是机器语言的助记符表示,二者存在一一对应的映射关系。汇编语言用助记符代替机器语言的操作码,用符号地址代替二进制的操作数地址,方便程序员读写和理解。汇编器可将汇编语言程序翻译成机器语言程序,使得计算机能够执行。
此外,机器语言中的操作数与汇编语言不一致,主要体现在分支转移、函数调用等方面。

图34 机器语言与汇编语言分支转移对比
汇编语言只需要在后面加标识符即可跳转到标识符所在位置,机器语言则通过程序计数器PC相对地址进行跳转。
2. 函数调用

图35 机器语言与汇编语言函数调用对比
在hello.s汇编代码里,函数调用直接使用函数名称。这是因为汇编语言采用助记符书写,以方便程序员编写和理解,函数名只是助记符形式,此时尚未确定函数实际执行地址。
反汇编代码中,call指令的目标地址呈现为当前指令下一条指令的地址。这是由于hello.c调用的函数在编译生成汇编代码或目标文件阶段,其实际内存地址尚未确定。这些库函数存于共享库,在链接阶段才能分配到确切地址,所以在未链接的目标文件中,无法给出准确的函数入口地址。
为解决上述函数地址不确定的问题,机器语言层面采用重定位机制。编译生成的目标文件里,对于这类不确定地址的函数调用,会先将调用指令后续应跳转的相对地址设为0。同时,在.rel.text(重定位节)中为每个不确定地址的函数调用添加重定位条目,记录相关重定位信息。等到链接阶段,链接器根据重定位条目,结合共享库中函数实际地址,修正函数调用指令的目标地址,确保程序执行时能正确跳转至对应函数入口 。
4.5 本章小结
本章将hello.s汇编为可重定位目标文件hello.o,解释了汇编的概念和作用,以及可重定位目标文件hello.o的结构。通过将汇编代码和反汇编代码进行对比,体现出分支转移、函数调用方面二者的不同之处。
(第4章1分)
第5章 链接
5.1 链接的概念与作用
链接(Linking)是将多个可重定位目标文件(.o)和库文件合并生成可执行文件的过程,主要作用包括:
符号解析:绑定全局符号到定义地址。
重定位:修正代码段和数据段的绝对/相对地址。
合并节(Section):整合所有文件的 .text、.data 等节。
动态链接处理:标记需要运行时解析的共享库函数。
将多个目标文件和库文件整合在一起,解决了程序中不同模块之间的相互引用问题,使程序能够作为一个整体在计算机上正确运行;可以将程序中使用的标准库函数和其他外部模块链接到程序中,避免了重复编写代码,提高了代码的复用性。
5.2 在Ubuntu下链接的命令
使用ld手动链接,输入: ld -o hello -dynamic-linker /lib64/ld-linux-x86-64.so.2 \
/usr/lib/x86_64-linux-gnu/crt1.o \
/usr/lib/x86_64-linux-gnu/crti.o \
hello.o \
/usr/lib/x86_64-linux-gnu/libc.so \
/usr/lib/x86_64-linux-gnu/crtn.o

图36 链接指令
5.3 可执行目标文件hello的格式
5.3.1 elf头
输入readelf -h hello查看elf头,

图37 hello的elf头
可以看出小端序,Type为EXEC可执行目标文件。
5.3.2 section头
输入readelf -S hello查看section头,


图38 hello的section头
这里对hello的信息进行声明,包括起始地址,大小,偏移量等信息,根据这些信息,就可以计算出每个节所在的区域。
5.4 hello的虚拟地址空间
下面将使用gdb/edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。
在VMware中打开edb, 加载hello,

图39 edb加载hello

图40 edb信息
可以看出,hello的代码段.text起始地址为0x401000. 由5.3节可知各段的偏移量,从初始地址0x400000开始,由此可计算出各段的绝对地址。绝对地址在hello的section头中也可直接读出。
表2 各段偏移量及地址
| 段名 | 偏移量 | 绝对地址 |
| .interp | 02e0 | 0x4002e0 |
| .init | 1000 | 0x401000 |
| .text | 10f0 | 0x4010f0 |
| .data | 3030 | 0x403030 |
| .bss | 3034 | 0x403034 |
根据表2地址可以分别查看各段具体信息。
用gdb查看进程内存布局,

图41 gdb查看进程内存布局
与 5.3 的对比可以看出,链接时确定的地址(如 .text 在 0x401000)与运行时内存映射一致。动态库在运行时加载到随机地址。
5.5 链接的重定位过程分析
用objdump -d -r hello查看hello的反汇编信息,




图42 hello反汇编信息
Hello的反汇编代码与hello.o的反汇编代码整体类似,但在虚拟地址,反汇编节数,跳转指令三方面存在差异。
1.虚拟地址

图43 hello.o与hello地址对比
hello.o的反汇编代码的虚拟地址从0开始,而hello的反汇编代码虚拟地址从400000开始,即hello.o给出的是相对地址,而hello给出的是绝对地址。
2.反汇编节数
hello.o作为目标文件,它仅包含 .text节,且在 .text节中只存有main函数的反汇编代码。这是因为目标文件是编译源文件后得到的结果,此时还未进行链接操作,仅保留了与当前源文件直接相关的代码部分。
hello是经过链接后的可执行文件。在链接过程中,由于程序中调用了其他函数以及涉及到数据的处理,为了实现重定位,会添加多个节。像 .init 节通常包含程序初始化代码,.plt和 .plt.sec 等节用于处理函数的动态链接,使得程序能够正确调用共享库中的函数。所以,可执行文件的反汇编代码包含了更多节的内容,其结构更为复杂。

图44 hello.o与hello跳转指令对比
在hello.o中,跳转指令后面跟随的主要是汇编代码块前的标号。标号是程序员在编写汇编代码时为了标识特定代码位置而定义的符号,它在目标文件阶段方便了代码的编写和理解,因为此时还未确定最终的内存地址。
对于hello可执行文件,跳转指令后面则是具体的地址。这是因为在链接过程中,所有函数和代码段的地址都已确定,链接器会将跳转指令的目标修改为实际的内存地址。不过,相对地址没有发生变化,这是为了保证程序在不同内存位置加载时,代码之间的相对跳转关系仍然正确,确保程序的逻辑能够按预期执行。
5.6 hello的执行流程
下面将使用gdb执行hello程序,说明从加载hello到_start,到call main,以及程序终止的所有过程(主要函数)。并列出其调用与跳转的各个子程序名或程序地址。
在终端中打开gdb,并设置观察点,

图45 设置断点
首次暂停,找到动态链接器入口,

图46 首次暂停点
地址0x7ffff7fe4540是动态链接器ld-linux-x86-64.so.2的入口点_start。由内核加载器直接调用,负责初始化动态链接环境。
指令mov %rsp,%rdi将当前栈指针%rsp的值保存到%rdi寄存器。后续%rdi将作为参数传递给_dl_start函数.
执行nexti单步观察后续操作,见图47,

图47 函数_dl_start
调用动态链接器的函数_dl_start, 地址为0x7ffff7fe51d0。接下来查看寄存器状态。

图48 查看寄存器
对 _dl_start 函数进行了反汇编操作,可以查看其汇编代码结构。

图49 反汇编查看
之后继续执行程序,程序又命中了 hello 可执行文件自身的 _start 断点。

图50 _start 断点
先前设置为等待状态的 __libc_start_main 断点被命中,此时程序进入该函数。

图51 __libc_start_main 函数
使用 finish 命令让 __GI__IO_puts 函数执行完毕并返回,期间输出了该函数要打印的字符串内容。

图52 __GI__IO_puts 函数
返回 main 函数后,继续单步执行,程序执行到调用 exit 函数的指令处。最后继续执行程序,程序以代码01退出,整个调试过程结束。

图53 调试结束
5.7 Hello的动态链接分析
分析hello程序的动态链接项目,通过edb/gdb调试,分析在动态链接前后,这些项目的内容变化。
程序调用共享库函数时,因共享库加载位置不定,编译器无法预知函数运行地址,故采用延迟绑定,将函数地址绑定推迟到首次调用。
此机制依赖全局偏移表(GOT)和过程链接表(PLT)。PLT 是数组,每项 16 字节,PLT [0] 跳转动态链接器,各库函数有对应条目。GOT 也是数组,每项8字节,GOT [0] 和 GOT [1] 存解析信息,GOT [2] 指向链接器入口,其余条目对应函数,需运行时解析,且与 PLT 条目一一对应。
首次调用函数,程序经 PLT 和 GOT 跳转至动态链接器确定函数地址。后续调用无需此过程。对于hello程序,重定位在动态链接器加载后进行。
在hello的section头可以看到关于got和plt的信息。

图54 section关于got和plt的信息
.got.plt在0x403fe8. 在VMware中打开edb, 加载hello,找到位置0x403fe8,在动态链接加载之后,对比图55和图56,可以观察到.got.plt的内容发生了变化。

图55 动态链接加载前

图56 动态链接加载后
5.8 本章小结
本章将hello.o链接为可执行目标文件hello,查看了hello的elf 头,section头的信息,用edb和gdb工具查看了hello的虚拟地址空间,分析了链接的重定位过程,hello的执行流程,以及hello的动态链接分析,对hello的链接过程有了更加深入的认识。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程是程序的一次执行实例,包含独立的地址空间、资源和调度状态。
作用包括:实现多任务并发执行,隔离程序错误(一个进程崩溃不影响其他进程),通过进程间通信(IPC)实现协作。
6.2 简述壳Shell-bash的作用与处理流程
Shell-bash的作用包括:
- 命令行解释器:解析用户输入的命令
- 进程管理:创建、终止、前后台切换进程
- 环境变量维护
Shell-bash的处理流程:
1. 读取输入:readline()获取命令("./hello")
2. 解析命令:识别程序路径、参数、重定向等
3. 创建进程:fork()复制Shell进程
4. 加载程序:子进程调用execve()加载hello
5. 等待结束:父进程(Shell)通过waitpid()等待
6.3 Hello的fork进程创建过程
fork函数调用后会创建一个与父进程几乎完全相同的子进程。当父进程执行到fork函数调用时,内核会为子进程分配必要的资源。这包括复制父进程的进程控制块(PCB),其中包含了进程的各种信息,如进程 ID、状态、打开的文件描述符。
子进程会获得与父进程相同的内存映像(代码段、数据段、堆、栈)的副本,但实际上在现代操作系统中,这通常是通过写时复制(Copy-On-Write)技术实现的。即父子进程共享相同的物理内存页,只有当其中一个进程试图修改某一页时,才会为该页创建一个单独的副本。

图57 CS:APP中对Fork的解释(CS:APP第三版)
fork 函数在父进程和子进程中都会返回。在父进程中,fork 返回子进程的进程 ID(一个大于 0 的整数);在子进程中,fork 返回0。通过检查 fork 的返回值,父进程和子进程可以区分自己的身份并执行不同的代码逻辑。
6.4 Hello的execve过程
execve 函数用于在当前进程中加载并执行一个新的程序。在执行execve后,当前进程的内存空间会被新程序的内容所覆盖,原有的代码、数据等会被替换。
execve 函数需要三个参数:要执行的程序文件名、一个参数数组(传递给新程序的命令行参数)和一个环境变量数组。
当调用 execve 时,内核会进行一系列检查,包括文件的权限、是否存在等。如果检查通过,内核会为新程序分配新的内存空间,并将新程序的代码和数据加载到内存中。
加载完成后,内核会设置新程序的执行上下文,包括设置程序计数器(指向新程序的入口处)、初始化栈和堆等。然后,新程序开始执行,原进程的执行被替换。
6.5 Hello的进程执行
下面将结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等。
每个进程都有自己的进程上下文,包含了进程的各种状态信息,如程序计数器(PC)、寄存器值、内存管理信息等。在进程执行过程中,这些信息会随着进程的运行而变化。当进程被调度执行时,内核会恢复其上下文;当进程被暂停或切换时,内核会保存其上下文。

图58 进程的上下文切换(CS:APP第三版)
在Linux系统中,通常采用时间片轮转的调度算法。每个进程被分配一个时间片,在这个时间片内,进程可以占用 CPU 执行。当时间片用完时,内核会进行进程调度,选择另一个进程执行,并将当前进程的上下文保存起来。
当进程执行普通的用户程序代码时,处于用户态。当进程需要执行一些特权操作(例如系统调用)时,会通过中断或异常机制切换到核心态。例如,当调用 fork 或 execve 等系统调用时,进程会从用户态切换到核心态,由内核来完成相应的操作。内核执行完系统调用后,会将结果返回给进程,并将进程切换回用户态继续执行。
6.6 hello的异常与信号处理
下面将说明hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,分别给出各命令及运行结截屏,说明异常与信号的处理。
6.6.1异常类型
在操作系统中,对程序执行流程意外改变的响应机制主要分为四大类:
中断(Interrupt)由外部硬件设备异步触发,CPU 暂停当前任务,通过中断向量表跳转至对应处理程序,处理完成后恢复原任务,常用于实现时间片调度、外设通信。
陷阱(Trap)由程序主动触发,同步执行,用于用户态到内核态的切换,执行内核服务后返回结果,包括文件操作和调试断点。
故障(Fault)由执行错误(如缺页、除零)同步触发,多数可恢复。CPU 保存上下文并跳转至处理程序,若问题修复则重新执行原指令,否则终止程序。
终止(Abort)因严重的、不可恢复的硬件或软件错误触发,直接终止程序或重启系统,不可恢复。
6.6.2信号产生
Ctrl-C:发送 SIGINT 信号给前台进程组中的所有进程。这个信号通常用于终止进程的执行。
Ctrl-Z:发送 SIGTSTP 信号给前台进程组中的所有进程,用于暂停进程的执行。
其他信号:进程在执行过程中还可能会收到其他信号,如 SIGTERM(正常终止进程)、SIGKILL(强制终止进程,无法被捕获或忽略)等。
6.6.3 信号处理
1. 不停乱按

图59 不停乱按
不停乱按(包括回车)并没有改变printf的输出,输入的信息在程序执行结束后出现在了shell的输入行内。
2. Ctrl-Z

图60 Ctrl-Z
按下Ctrl-Z会发送 SIGTSTP 信号给前台进程组中的所有进程,用于暂停进程的执行,作业将会挂起,但不会回收。

图61 ps
Ps可以查看当前系统中的进程信息。可以看到此时hello的pid为219.

图62 jobs
Jobs可查看当前shell会话中被暂停或在后台运行的作业。

图63 pstree
pstree用于以树状结构显示进程的父子关系。可以查看hello进程在进程树中的位置以及它的父进程和子进程。

图64 fg
fg用于将后台作业或暂停的作业恢复到前台运行。在 Ctrl-Z 暂停 hello 进程后,运行 fg 可以将 hello 进程恢复到前台继续执行。

图65 kill
kill用于向进程发送信号。可以使用 kill <pid> 来向指定进程 ID 的进程发送信号,默认发送 SIGTERM 信号。此时hello的pid为222,可以使用 kill -9 222 来发送 SIGKILL 信号强制终止进程。
3. Ctrl-C

图66 Ctrl-C
输入Ctrl-C,就终止了前台作业。
6.7本章小结
本章阐述了进程与shell的基本信息,通过对Hello的fork进程,execve过程和进程执行进行解释,研究了hello的异常与信号处理,包括Ctrl-Z,Ctrl-C等命令的执行,加深了对程序进程的理解。
(第6章2分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:是指在源程序中使用的地址。对于hello程序来说,它是由编译器在编译时为变量、函数等分配的地址。这些地址是相对的,基于程序的逻辑结构,与实际的物理内存位置无关。例如,在hello程序中定义一个变量int a,编译器会为a分配一个逻辑地址,程序员在编写代码时通过这个逻辑地址来访问变量a。
线性地址:在Intel处理器的模式下,逻辑地址通过段式管理机制转换为线性地址。它是一个中间地址空间,为后续转换为物理地址做准备。对于hello程序,线性地址是在段式管理过程中,将逻辑地址中的段内偏移与段基址相加得到的地址。
虚拟地址:虚拟地址是hello程序所看到的地址空间。它为每个进程提供了一个独立的、连续的地址空间,使得进程可以在自己的虚拟地址空间中自由地分配和使用内存,而无需关心实际的物理内存布局。每个进程的虚拟地址空间看起来都是一样的,从 0 开始到一个很大的地址范围。例如,hello程序中的函数调用、变量访问等都是基于虚拟地址进行的。
物理地址:这是实际的内存芯片上的地址。它对应着计算机硬件中的物理内存位置。虚拟地址最终需要通过地址转换机制映射到物理地址,才能真正访问到实际的内存数据。对于hello程序,当它访问某个变量或执行某个函数时,经过一系列的地址转换,最终会将虚拟地址转换为物理地址来读取或写入数据。

图67 物理地址与虚拟地址的映射(CS:APP第三版)
7.2 Intel逻辑地址到线性地址的变换-段式管理
在 Intel 处理器中,逻辑地址由段选择子和段内偏移两部分组成。段式管理通过段表来实现逻辑地址到线性地址的转换。段表中存储了每个段的基址、界限和属性等信息。对于hello程序,当访问一个逻辑地址时,首先根据段选择子在段表中找到对应的段描述符,然后将段内偏移与段基址相加,得到线性地址。这样可以实现不同段之间的隔离和保护,例如代码段、数据段等可以有不同的访问权限。
1.段描述符与段寄存器
每个段(代码段、数据段等)对应一个段描述符,存储在全局描述符表(GDT)或局部描述符表(LDT)中。
段寄存器(%cs、%ds)存储段选择符,指向段描述符。
2.转换过程
步骤1:CPU从逻辑地址中提取段选择符和偏移量。
步骤2:通过GDTR/LDTR寄存器找到段描述符,获取段基址(现代系统通常为0)。
步骤3:线性地址 = 段基址 + 偏移量 → 平坦模式下线性地址=偏移量。
7.3 Hello的线性地址到物理地址的变换-页式管理
页式管理将线性地址空间和物理地址空间都划分为大小相等的页。hello程序的线性地址通过页表来映射到物理地址。页表中存储了页号与物理页框号的对应关系。当hello程序访问一个线性地址时,硬件会将线性地址分为页号和页内偏移,通过页号在页表中查找对应的物理页框号,然后将物理页框号与页内偏移组合起来,得到物理地址。页式管理可以有效地利用内存空间,并且方便进行内存的分配和回收。

图68 利用页表寻址(CS:APP第三版)
7.4 TLB与四级页表支持下的VA到PA的变换
TLB(Translation Lookaside Buffer):是一种高速缓存,用于加速进行虚拟地址到物理地址的转换。它存储了最近使用过的页表项,当hello程序访问虚拟地址时,硬件首先会在TLB中查找,如果找到对应的页表项,就可以直接得到物理地址,而无需访问内存中的页表,从而大大提高了地址转换的速度。

图69 TLB命中和不命中(CS:APP第三版)
为了管理大量的虚拟地址空间,现代处理器采用了四级页表结构。hello程序的虚拟地址被分为多个部分,分别用于索引四级页表中的各级页表项。通过四级页表的逐级查找,最终可以找到对应的物理地址。这种结构可以有效地减少页表占用的内存空间,同时也提高了地址转换的灵活性。

图70 多级页表(CS:APP第三版)
7.5 三级Cache支持下的物理内存访问
当hello程序通过地址转换得到物理地址后,要访问物理内存中的数据。为了提高访问速度,计算机系统采用了三级高速缓存Cache。Cache 中存储了最近访问过的内存数据和指令。当hello程序访问物理地址时,首先会在 Cache 中查找,如果数据在 Cache 中,就可以直接从 Cache 中读取,而无需访问低速的物理内存。如果数据不在 Cache 中,就会发生 Cache 缺失,此时需要从物理内存中读取数据,并将其加载到 Cache 中,以便后续访问。
7.6 hello进程fork时的内存映射
当hello程序调用fork函数创建子进程时,子进程会继承父进程的地址空间。实际上,操作系统会为子进程创建一份与父进程相同的页表,但是这些页表所指向的物理内存页面通常是共享的,采用写时复制技术。也就是说,在父进程和子进程没有对共享的内存页面进行写操作之前,它们共享相同的物理内存页面。当某个进程对共享页面进行写操作时,操作系统会为该页面分配一个新的物理内存页面,并将原页面的数据复制到新页面中,然后让该进程的页表指向新的页面,从而实现父子进程之间的内存隔离。
7.7 hello进程execve时的内存映射
当hello程序调用execve函数加载并执行一个新的程序时,操作系统会重新为进程建立内存映射。首先,它会释放原来hello程序占用的内存空间,包括代码段、数据段等。然后,根据新程序的文件格式,将程序的代码和数据加载到内存中,并建立相应的页表映射。新程序的代码段和数据段会被映射到不同的虚拟地址空间区域,并且根据程序的需要进行初始化。例如,将全局变量初始化为指定的值,将代码段设置为可执行权限。
7.8 缺页故障与缺页中断处理
当hello程序访问一个虚拟地址,而对应的物理页面不在内存中时,就会发生缺页故障。此时,硬件会触发缺页中断,操作系统会接管控制权并进行缺页中断处理。操作系统首先会在磁盘上的交换空间或程序文件中找到对应的页面数据,然后分配一个空闲的物理内存页面,将磁盘上的页面数据加载到物理内存中,并更新页表,使虚拟地址与新分配的物理页面建立映射关系。最后,操作系统会恢复hello程序的执行,让它重新访问引发缺页故障的虚拟地址,此时就可以从新加载的物理页面中获取数据。
7.9动态存储分配管理
常见的内存分配算法有首次适应算法、最佳适应算法、最坏适应算法等。首次适应算法会从内存空闲链表的起始位置开始查找,找到第一个能够满足分配请求大小的空闲块,然后将其分配给请求者。最佳适应算法会遍历整个空闲链表,找到最适合请求大小的空闲块进行分配,即找到大小大于等于请求大小且差值最小的空闲块。最坏适应算法则是选择最大的空闲块进行分配,这样可以尽量保留较小的空闲块,以便满足后续较小的分配请求。
操作系统会维护一个空闲链表,用于记录内存中的空闲块信息。每个空闲块包含块的大小、指向下一个空闲块的指针等信息。当进行内存分配时,会在空闲链表中查找合适的空闲块;当内存释放时,会将释放的内存块插入到空闲链表中,并根据需要进行合并操作,将相邻的空闲块合并成一个更大的空闲块,以提高内存的利用率。
动态内存分配可能会导致内存碎片的产生。内部碎片是指分配的内存块中未被使用的部分,例如当分配一个较大的内存块给一个较小的请求时,剩余的部分就成为内部碎片。外部碎片是指内存中存在许多分散的、较小的空闲块,这些空闲块由于太小而无法满足较大的分配请求。为了解决内存碎片问题,可以采用内存紧缩技术,将分散的空闲块移动到一起,形成较大的连续空闲块,但这需要一定的开销。
7.10本章小结
本章围绕hello程序介绍了存储器地址空间的相关概念,包括逻辑地址、线性地址、虚拟地址和物理地址,以及它们之间的转换关系。通过段式管理和页式管理实现了从逻辑地址到物理地址的变换,并且介绍了 TLB、四级页表、三级 Cache 等技术对地址转换和内存访问的优化作用。同时,阐述了hello进程在fork和execve时的内存映射变化,以及缺页故障和缺页中断处理机制。还对动态存储分配管理进行讨论,包括内存分配算法、空闲链表管理和内存碎片问题。图71以Core i7为例,展示了地址翻译的原理和过程。

图71 Core i7的地址翻译(CS:APP第三版)
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
在 Linux 系统中,将各类 IO 设备都抽象为文件进行管理。这种模型化方式为用户和应用程序提供了统一的操作接口,隐藏了不同设备的硬件差异。无论是磁盘、键盘、显示器等设备,都被看作是文件,通过文件系统的接口进行访问。这使得对设备的操作就如同对普通文件的操作一样,例如打开、关闭、读写等,极大地简化了设备管理和应用程序开发的复杂度。
Unix IO 接口是 Linux 系统中用于管理设备和进行 IO 操作的核心机制。它提供了一组标准的函数,如open、close、read、write等,这些函数是用户程序与内核设备驱动程序之间交互的桥梁。应用程序通过调用这些接口函数,以一种统一的方式与不同的设备进行通信。内核则根据设备的类型和具体情况,将这些调用转换为对相应设备驱动程序的操作,实现对设备的控制和数据传输。
8.2 简述Unix IO接口及其函数
open函数:用于打开一个文件或设备,返回一个文件描述符(一个非负整数),后续的 IO 操作将通过这个文件描述符进行。它的参数包括要打开的文件名、打开模式(如只读、只写、读写等)以及一些可选的权限设置。例如int fd = open("test.txt", O_RDONLY);,以只读模式打开名为test.txt的文件,并将文件描述符赋值给fd。
close函数:用于关闭一个已经打开的文件描述符,释放相关的系统资源。当不再需要对文件或设备进行操作时,应调用close函数。例如close(fd);关闭之前打开的文件。
read函数:从指定的文件描述符所对应的文件或设备中读取数据。它的参数包括文件描述符、用于存储读取数据的缓冲区以及要读取的字节数。返回值为实际读取的字节数,如果返回 0 表示到达文件末尾,返回 -1 表示发生错误。如下面的程序,
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
int fd = open("example.txt", O_RDONLY);
if (fd < 0) {
perror("open failed");
return EXIT_FAILURE;
}
char buffer[1024] = {0};
int n = read(fd, buffer, sizeof(buffer));
if (n < 0) {
perror("read failed");
close(fd);
return EXIT_FAILURE;
} else if (n == 0) {
printf("文件为空\n");
} else {
if (n < sizeof(buffer)) {
buffer[n] = '\0';
}
printf("读取了 %d 字节: %s\n", n, buffer);
}
close(fd);
return 0;
}
从文件描述符fd对应的文件中读取最多1024字节的数据到buffer缓冲区。
write函数:向指定的文件描述符所对应的文件或设备中写入数据。参数包括文件描述符、要写入的数据缓冲区以及要写入的字节数。返回值为实际写入的字节数,返回 -1 表示发生错误。例如int m = write(fd, "Hello, World!", 13);,将字符串"Hello, World!"写入到文件描述符fd对应的文件中。
8.3 printf的实现分析
printf函数接受一个格式化字符串fmt和可变数量的参数。首先,通过va_list类型的变量获取可变参数列表。vsprintf函数根据格式化字符串fmt对可变参数进行格式化处理,将格式化后的结果存储在指定的缓冲区中。例如,当格式化字符串中有%d时,vsprintf会从可变参数列表中取出对应的整数,并将其转换为字符串形式存储到缓冲区中;遇到%x时,会将整数转换为十六进制字符串存储。在示例代码中,vsprintf函数实现了对十六进制数的格式化处理,如itoa(tmp, *((int*)p_next_arg)); strcpy(p, tmp);将整数转换为十六进制字符串并复制到缓冲区。
write 系统函数:vsprintf完成格式化后,printf函数调用write函数将缓冲区中的格式化数据输出。在 Linux 系统中,write函数是一个系统调用函数,其第一个参数通常为文件描述符,printf中一般使用标准输出的文件描述符;第二个参数是要输出的数据缓冲区;第三个参数是缓冲区中数据的长度。write函数负责将数据从用户空间复制到内核空间,并最终传递给设备驱动程序进行实际的输出操作。
陷阱 - 系统调用 int 0x80 或 syscall 等:当应用程序调用write等系统调用函数时,会触发一个陷阱(trap),在 x86 架构中通常通过int 0x80指令(较旧的方式)或syscall指令(较新的方式)进入内核态。这会导致 CPU 控制权转移到内核的系统调用处理程序。内核根据系统调用号(在eax寄存器中)来确定要执行的具体系统调用函数。系统调用处理程序会进行一系列的检查和准备工作,包括保存当前进程的状态等,然后调用相应的内核函数来完成实际的设备操作。
字符显示驱动子程序:在内核中,负责字符显示的驱动子程序接收到要显示的数据(如从write系统调用传递过来的字符串)后,会将字符的 ASCII 码转换为对应的字模。字模库存储了每个字符对应的图形表示信息。然后,驱动程序将这些字模数据写入显示 VRAM,VRAM 中存储了每一个点的 RGB 颜色信息,用于控制显示器的像素显示。
显示芯片工作过程:显示芯片按照设定的刷新频率逐行读取 VRAM 中的数据,并通过信号线向液晶显示器传输每一个点的 RGB 分量。这样,液晶显示器就能逐行显示出字符,从而实现了printf函数的最终输出效果。
8.4 getchar的实现分析
异步异常-键盘中断的处理:当用户按下键盘上的按键时,会产生一个键盘中断。这是一个异步异常事件,会打断 CPU 正在执行的当前任务,使 CPU 跳转到键盘中断处理子程序。键盘中断处理子程序接收到按键的扫描码后,将其转换为 ASCII 码,并保存到系统的键盘缓冲区中。扫描码是键盘硬件产生的表示按键位置的代码,不同的按键有不同的扫描码,键盘中断处理子程序负责将其转换为系统能够识别的 ASCII 码。
getchar 等调用 read 系统函数:getchar函数用于从标准输入读取一个字符。它实际上是调用了read系统函数,通过系统调用进入内核态。read函数从标准输入设备(通常是键盘)对应的文件描述符中读取数据。在读取过程中,read函数会检查键盘缓冲区是否有数据。如果没有接收到回车键,read函数会一直等待,直到有数据可读。当键盘缓冲区中有数据且接收到回车键时,read函数会从缓冲区中读取一个ASCII 码字符,并返回给getchar函数,getchar函数再将这个字符返回给调用它的程序。
8.5本章小结
本章围绕 Linux 系统中的 IO 管理展开,介绍了 Linux 对 IO 设备的管理方法,即通过将设备模型化为文件,并使用 Unix IO 接口进行统一管理。阐述了 Unix IO 接口中的常用函数及其功能。分析了printf和getchar函数的实现过程,printf从格式化数据生成到最终在显示器上显示涉及多个环节,包括vsprintf的格式化、write系统调用以及内核中的显示驱动处理和显示芯片的工作等。
(第8章 选做 0分)
结论
从计算机系统的视角来看,Hello 程序的诞生与运行经历了一系列复杂的过程。在预处理阶段,通过对源文件中宏定义、头文件的展开,消除了代码冗余,为后续处理奠定基础;编译过程将 C 语言代码转化为汇编代码,完成语法语义分析与优化;汇编阶段生成可重定位目标文件,将汇编代码转换为计算机可识别的机器码;链接过程整合各目标文件与库函数,解决符号引用,形成完整的可执行文件。
程序运行时,Shell 通过 Fork 创建子进程,再利用 Execve 加载并执行 Hello 程序。在执行过程中,存储管理模块通过段式、页式管理机制,实现逻辑地址到物理地址的转换,结合 TLB、Cache 等关键部分,保障内存访问的高效性。同时,I/O 管理模块借助 UNIX I/O 接口,实现 printf、getchar 等函数的功能,完成程序与用户的交互。整个过程环环相扣,充分展现了计算机系统各组件的协同工作。
通过对 Hello 程序的研究,能深刻体会到计算机系统是一个追求效率与平衡的艺术。各层机制看似独立却紧密关联,从指令集架构到操作系统,从硬件加速到软件优化,每一处设计都凝聚着人们的智慧。
在未来的计算机系统设计方面,可以探索更灵活的分层抽象机制,打破现有软硬件边界,实现更高效的协同优化。例如,将部分软件功能下沉到硬件加速模块,或利用可编程硬件实现动态功能适配。在实现层面,借鉴新兴的计算理念,融合不同架构处理器优势,提升系统整体性能。此外,结合人工智能技术,让系统能够根据运行状态动态调整资源分配策略,实现自适应的性能优化,为计算机系统的未来发展开辟新路径。
(结论0分,缺失-1分)
附件
列出所有的中间产物的文件名,并予以说明其作用。
hello.c:初始hello程序的C语言源程序
hello.i:经过预处理的hello源程序
hello.s:由预处理代码生成的汇编程序
hello.o:二进制可重定位目标程序
hello:经过链接后的可执行目标程序
hello_dasm.txt:反汇编可重定位目标文件hello.o得到的反汇编文件
hello1_dasm.txt:反汇编可执行目标文件hello可执行文件得到的反汇编文件
(附件0分,缺失 -1分)
参考文献
为完成本次大作业你翻阅的书籍与网站等
- Randal E.Bryant, David R.O'Hallaron. 深入理解计算机系统(原书第3版).机械工业出版社,2016.
- https://blog.csdn.net/ruerdaikou/article/details/136410924
- 袁春风. 计算机系统基础(第二版). 机械工业出版社,2019.
- https://yebd1h.smartapps.cn/pages/blog/index?blogId=139120171&_swebfr=1&_swebFromHost=baiduboxapp
- https://www.cnblogs.com/pianist/p/3315801.html
(参考文献0分,缺失 -1分)
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2301_79657056/article/details/147936370



