
计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 计算学部
学 号 2023111039
班 级 23L0512
学 生 刘雨昕
指 导 教 师 史先俊
计算机科学与技术学院
2025年5月
本文根据Hello的自白及所给程序,按顺序描述其预处理、编译、汇编、链接、进程管理、存储管理等内容,从而对计算机系统有了更深刻的认识。结合图片和专业术语,我们可以更好地了解各步骤的概念和作用,并进行分析。
关键词:预处理;编译;汇编;链接;进程管理;存储管理。
目 录
第1章 概述
1.1 Hello简介
P2P,Program to Process。对program,jiq源程序hello.c进行预处理得到hello.i,编译得到汇编语言文件hello.s,汇编得到可重定位文件hello.o,链接得到可执行文件hello,即Process。
020,Zero-0 to Zero-0。运行进程,内核为其分配虚拟地址空间,发生缺页故障和冷不命中,hello载入内存。内核通过异常控制流对hello进程进行调度。程序终止,父进程将其回收,内核删除相关数据结构,进程结束。
1.2 环境与工具
软件:VMware 17 pro Ubuntu 22.04 LTS
硬件:CPU:13th Gen Intel(R) Core(TM) i9-13900H
开发与调试工具:gedit,gcc,gdb,edb,codeblocks。
1.3 中间结果
hello.c:源文件
hello.i:预处理文件
hello.s:汇编语言文件
hello.o:可重定位文件
hello:可执行文件
disa_hello.s.txt对hello.o的反汇编结果
disa_hello.txt:对hello的反汇编结果
1.4 本章小结
本章介绍了hello的p2p和020,环境与工具,生成的中间结果文件等内容。
第2章 预处理
2.1 预处理的概念与作用
概念:
将源文件编译为可执行文件时,系统自动调用预处理程序对源程序中的预处理部分作处理,处理完毕再进行编译、汇编、链接,最终得到可执行目标文件。
作用:
- 将外部文件(如头文件)的内容插入到当前文件中。
- 定义常量、函数宏或代码片段,预处理时直接替换。
- 根据条件决定是否编译某段代码
- 出现问题时强制终止编译并报错
2.2在Ubuntu下预处理的命令
gcc -E hello.c -o hello.i

图1 预处理命令
2.3 Hello的预处理结果解析
以下为hello.i文件内容及其对应作用,如图,分别为头文件引用,类型定义,外部函数,主函数。

图2 头文件引用

图3 类型定义

图4 外部函数

图5 主函数
2.4 本章小结
本章介绍了预处理的概念及作用,预处理命令,以及所得文件中的结果解析。
第3章 编译
3.1 编译的概念与作用
概念:
编译是将高级编程语言(如C、C++、Java等)编写的源代码转换为计算机可执行的低级语言(机器码或字节码)的过程,此处指预处理后的文件转换为汇编语言程序。
作用:
计算机只能执行二进制指令,编译器将人类可读的代码(如 if-else、for)转换为CPU能理解的机器码,并优化代码性能,检查语义错误。
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s

图6 编译命令
3.3 Hello的编译结果解析
3.3.1常量
对代码中的常量进行了统一定义。

图7 常量
3.3.2 main函数
定义main函数,规定其调试信息。

图8 main函数起始部分
3.3.3栈帧初始化
建立栈帧并分配空间。

图9 栈帧初始化
3.3.4函数主体
如图。L2为循环计数器,L3为循环条件判断,L4为printf()和sleep()部分。

图10 函数主体
3.4 本章小结
本章介绍了编译的概念及作用,编译命令,以及hello.s中的编译结果解析。
第4章 汇编
4.1 汇编的概念与作用
概念:
将机器指令翻译成二进制代码,此处指编译后的文件生成为机器语言二进制程序。
作用:
生成二进制文件,使机器理解并执行其中的指令。
4.2 在Ubuntu下汇编的命令
gcc -c -m64 -no-pie -fno-PIC hello.s -o hello.o

图11 汇编命令
4.3 可重定位目标elf格式
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
4.3.1 ELF头
该部分描述了系统的字的大小和顺序。包括ELF头的大小(64字节),目标文件的类型,机器类型,节头部表的文件偏移,以及节头部表中条目的大小和数量。

图12 ELF头
4.3.2节头部表
每一节的类型、地址等。

图13 节头部表
4.3.3符号表
函数及全局变量的信息。

图14 符号表
4.3.4重定位条目
重定位条目的偏移量和位置信息。

图15 重定位条目
4.4 Hello.o的结果解析

图16 结果
每条汇编指令都对应了一段16进制的机器指令这样才能被计算机理解与执行。操作数从汇编代码的十进制变为16进制操作数。未采取.L3 .L2这样的段名称这样的助记符,而是直接使用了相对地址。
汇编代码文件直接调用函数名称,反汇编文件则直接call地址,但这个相对地址目前就是下一条指令即0,在后面留下了虚拟地址,需要在链接后才能变为确定的地址,对共享库的函数调用要在执行时才能被动态链接器确定。Leaq指令需要读取rodata段地址也采用了这种形式。
4.5 本章小结
本章介绍了汇编的概念及作用,汇编命令,可重定位目标elf格式,以及汇编结果解析。
第5章 链接
5.1 链接的概念与作用
概念:
链接是将多个目标文件和库文件合并,生成最终的可执行文件或动态库。此处指从 hello.o 到hello生成过程。
作用:
将代码、数据、库函数合并为一个可直接运行的文件。
5.2 在Ubuntu下链接的命令

图17 链接过程
5.3 可执行目标文件hello的格式
5.3.1 ELF头
该部分描述了系统的字的大小和顺序。包括ELF头的大小(64字节),目标文件的类型,机器类型,节头部表的文件偏移,以及节头部表中条目的大小和数量。

图18 ELF头
5.3.2节头部表
每一节的类型、地址等。

图19 节头部表
5.4 hello的虚拟地址空间

图20 虚拟地址空间
其中每一节之间的地址差值在readelf中和gdb中都相同。
5.5 链接的重定位过程分析

图21 反汇编代码
新增了大量函数,增加分节,每段指令对应分配好的在虚拟内存中的地址。
函数调用和例如leaq指令的重定向条目均定向为具体的虚拟内存中的地址。条件跳转的指令变为绝对地址。
5.6 hello的执行流程
从加载hello到_start:程序先调用_init函数,之后是puts、printf等库函数,最后调用_start函数。
从_start到call main:程序先调用__libc_csu_init等函数,完成初始化工作,随后调用main函数。
从main函数到程序终止:程序执行main函数调用main函数用到的一些函数,main函数执行完毕之后调用__libc_csu_fini、_fini完成资源释放和清理的工作。
5.7 Hello的动态链接分析
由节头表可知.got.plt(实际上其本质是从.got表中拆除来的一部分,当开启延迟绑定(Lazy Binding)时,会将plt表中的长跳转(函数)的重定位信息单独放到此表中,以满足后续实际的延迟绑定)是在0x0004000位置。
5.8 本章小结
本章中介绍了链接的概念与作用,分析了hello的ELF格式,分析了hello的虚拟地址空间、重定位过程、执行流程和动态链接过程。
第6章 hello进程管理
6.1 进程的概念与作用
概念:
进程是计算机是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
作用:
实现多任务并发,隔离程序,提高稳定性,资源分配与管理,支持用户与系统交互等。
6.2 简述壳Shell-bash的作用与处理流程
Shell 是用户与操作系统内核之间的命令行接口,负责解析用户输入的命令并协调内核执行。Bash是Linux/Unix中最常用的Shell,主要作用包括:命令解释与执行;解析用户输入的命令,调用对应的程序或内置功能;脚本编程;环境管理;维护环境变量;输入输出重定向;控制命令的输入/输出流;作业控制等。
bash 的处理流程为读取输入,解析命令,扩展与替换,执行命令,返回结果。
6.3 Hello的fork进程创建过程
输入./hello 2023111039 刘雨昕 18335686495 0后,调用fork()函数创建子进程。子进程得到与父进程虚拟地址空间相同的但独立的一份副本,包括代码、数据段、堆、共享库以及用户栈,子进程获得与父进程任何打开文件描述符相同的副本。
子进程有不同于父进程的PID,fork()被调用一次,返回两次。子进程返回0,父进程返回子进程的PID。
6.4 Hello的execve过程
创建子进程之后,会调用execve函数,在进程的上下文中加载并运行hello,调用_start创建新的且被初始化为0的栈等,随后将控制给主函数main,并传入参数列表和环境变量列表。
6.5 Hello的进程执行
当开始运行hello时,内存为hello分配时间片,如一个系统运行着多个进程,那么处理器的一个物理控制流就被分成了多个逻辑控制流,逻辑流的执行是交错的,它们轮流使用处理器,会存在并发执行的现象。其中,一个进程执行它的控制流的一部分的每一时间段叫做时间片。然后在用户态下执行并保存上下文。
发生了异常或系统中断时,内核会休眠该进程,并在核心态中进行上下文切换,控制将交付给其他进程。
执行到sleep时,hello会休眠,再次上下文切换,控制交付给其他进程,一段时间后再次上下文切换,恢复hello在休眠前的上下文信息,控制权回到 hello 继续执行。
循环后,程序调用getchar() ,hello从用户态进入核心态,并再次上下文切换,控制交付给其他进程。最终,内核从其他进程回到 hello 进程,在return后进程结束。
6.6 hello的异常与信号处理
6.6.1乱按

图22 乱按
6.6.2 Ctrl-Z

图23 Ctrl-C
6.6.3 Ctrl-C

图24 Ctrl-C
6.7本章小结
本章介绍了进程的概念与作用,壳Shell-bash的作用与处理流程,Hello的fork进程创建过程,execve过程,进程执行,信号异常与处理。
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:由程序产生的与段相关的偏移地址部分。在这里指的是hello.o中的内容。一个逻辑地址由两部份组成,段标识符和段内偏移量。
线性地址:是逻辑地址到物理地址变换之间的中间层。hello的代码产生的段中的偏移地址,加上相应段的基地址构成一个线性地址。
虚拟地址:就是在程序在虚拟内存中对应的位置,虚拟内存是现代操作系统为了方便管理主存和对进程提供私有内存空间抽象的一种对主存的抽象。我们之前看到hello程序在内存中的地址是0x40开头的,这就是一段虚拟地址对应虚拟内存中的位置。
物理地址: 是指出现CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。
7.2 Intel逻辑地址到线性地址的变换-段式管理
机器语言指令中出现的内存地址,都是逻辑地址,需要转换成线性地址,再经过MMU(CPU中的内存管理单元)转换成物理地址才能够被访问。
分段机制将逻辑地址转化为线性地址的步骤:使用段选择符中的偏移值(段索引)在GDT或LDT表中定位相应的段描述符(仅当一个新的段选择符加载到段寄存器中是才需要这一步);利用段选择符检验段的访问权限和范围,以确保该段可访问;把段描述符中取到的段基地址加到偏移量(也就是上述汇编语言汇中直接出现的操作地址)上,最后形成一个线性地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
Intel处理器从线性地址到物理地址的变换通过页式管理实现。
由计算机系统处理器层次的相关知识可知,内存其实是磁盘的缓存。磁盘上的数据被分割成块,作为其主存之间的传输单元。虚拟内存系统将虚拟内存分割称为虚拟页,物理内存分割称为物理页。
7.4 TLB与四级页表支持下的VA到PA的变换
现代 CPU 都包含一张名为 TLB(Transfer Look-aside Table),叫做快表,或者高速地址变址缓存,以加速对于页表的访问。TLB通常有高度的相联度。用于组选择和行匹配的索引和标记字段是从虚拟地址中的虚拟页号中提取出来的。如果TLB有T=2^t个组,那么TLB索引(TLBI)是由VPN的t个最低位组成的,而TLB标记(TLBT)是由VPN中剩余的位组成的。
若TLB命中,会经历如下步骤:CPU产生一个虚拟地址;MMU从TLB中取出相应的PTE;MMU将这个虚拟地址翻译成一个物理地址,并且将它发送到高速缓存/主存;高速缓存/主存将所请求的数据字返回给CPU。
若TLB不命中,对于四级页表来说虚拟地址被划分成4个VPN和1个VPO,VPN的每个片表示一个到第i级页表的索引,即偏移量,CR3寄存器包含L1页表的物理地址余下的页表中,第j级页表中的每个PTE,1≤j≤3,都指向j+1级的某个页表的基址。最后在L4页表中对应的PTE中取出PPN,与VPO连接,从而形成物理地址PA。
经过四级页表支持下的VA到PA的变换,虽然所经历的步骤更多,但如果一级页表的一个PTE是空的,对应的二级页表就不会存在,因此可以节省大量未被使用的空间。
7.5 三级Cache支持下的物理内存访问
处理器对物理内存中数据的访问,同样需要经过缓存,即Cache,主流的处理器通常采用三级Cache。层与层之间按照以下原则进行读与写:
读取数据时,首先在高速缓存中查找所需字的副本。如果命中,立即返回字给CPU。如果不命中,从存储器层次结构中较低层次中取出包含字的块,将这个块存储到某个高速缓存行中(可能会驱逐一个有效的行),然后返回字。
7.6 hello进程fork时的内存映射
当终端进程调用fork时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID,它创建了当前进程的mm_struct、区域结构和页表的原样副本,将这两个进程的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
7.7 hello进程execve时的内存映射
映射新的私有区域。代码和初始化数据映射到.text和.data区(执行可执行文件提供),.bss映射到匿名文件,共享对象由动态链接映射到本进程共享区域,设置PC,指向代码区域的入口点。栈中从栈底到栈顶是参数和环境字符串,再往上是指针数组,每个指针指向刚才的环境变量和参数字符串。栈顶是系统启动函数libc_start_main的栈帧和预留的未来函数的栈帧。
7.8 缺页故障与缺页中断处理
缺页故障:虚拟内存在DRAM缓存不命中即为缺页故障。
缺页中断处理:发生缺页故障时启动缺页处理程序。缺页处理程序确认出物理内存中的牺牲页,如果这个页已经被修改了,则把它换到磁盘。缺页处理程序页面调入新的页面,并更新内存中的PTE。缺页处理程序返回到原来的进程,再次执行导致缺页的命令。[1]
7.9动态存储分配管理
Printf会调用malloc,请简述动态内存管理的基本方法与策略。(此节课堂没有讲授,选做,不算分)
7.10本章小结
本章介绍hello的存储管理及其相关内容。
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
(以下格式自行编排,编辑时删除)
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
(以下格式自行编排,编辑时删除)
8.3 printf的实现分析
(以下格式自行编排,编辑时删除)
[转]printf 函数实现的深入剖析 - Pianistx - 博客园
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall等.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
8.4 getchar的实现分析
(以下格式自行编排,编辑时删除)
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
(以下格式自行编排,编辑时删除)
(第8章 选做 0分)
结论
2、编译器编译hello.i生成hello.s汇编文本文件
3、汇编器汇编hello.s生成可重定位目标文件hello.o
4、链接器链接hello.o和其引用的动态链接库中的代码等生成可执行文件hello
5、shell中输入./hello 2023111039 刘雨昕 18335686495 0,父进程调用fork,生成子进程
6、子进程调用execve函数加载并运行程序hello通过传递的参数,操作系统为这个进程分配虚拟内存空间
7、内核通过异常控制流调度hello进程
8、hello调用printf函数和getchar等函数进行IO
9、hello最终被父进程回收
hello的一生经过完整的计算机处理的流程,在屏幕上短短的一行输出却经由无数层抽象。
附件
hello.c:源文件
hello.i:预处理文件
hello.s:汇编语言文件
hello.o:可重定位文件
hello:可执行文件
disa_hello.s.txt对hello.o的反汇编结果
disa_hello.txt:对hello的反汇编结果
参考文献
[1] KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/abccddeffgg/article/details/148199149



