计算机系统
大作业
题 目 程序人生-Hello’s P2P
专 业 未来技术学院
学 号
班 级
学 生
指 导 教 师
计算机科学与技术学院
2023年4月
Hello程序是每个现代程序员游玩“计算机编程”的新手村村长,但是他们中的大多数总是匆匆忙忙地写完程序,向村长提交任务,之后马不停蹄地赶往下一个地图,却不知道这位新手村村长也有着自己不为人知的传奇经历——从预处理,编译,汇编,再到最后的链接,最后生成可执行文件……今天,就让我们一同走进hello这位新手村村长的传奇一生。
关键词:计算机系统,预处理,编译,汇编,链接,进程
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P:从代码到可执行文件。程序员使用编程软件编写.c文件。之后利用gcc编译器将.c文件处理成可执行文件,具体分为以下四步:hello.c经过预处理器生成hello.i文件,再经过编译器生成hello.s汇编程序,再经过汇编器生成可重新定位文件hello.o,最后经由链接器生成可执行文件hello。得到程序后,系统加载hello到内存,使用fork生成新进程执行该文件。
020:程序从未编写的0状态开始,一直到到运行结束后被回收,内存清理没有剩余的0状态就是020。
1.2 环境与工具
硬件环境:X64 CPU ,2.50GHz , 8G RAM
软件环境:Windows 10 64位,Vmware 14,Ubuntu 16.04 LTS 64 位
开发工具:gcc,Codeblocks ,edb
1.3 中间结果
hello.i 预处理后的文件
hello.s 汇编语言文件
hello.o 可重定位文件
hello 可执行文件
1.4 本章小结
简要概述了P2P与020,列出了实验的硬件环境
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
预处理是代码经由gcc编译器的第一个阶段。它不涉及任何分析操作,只是对原文件进行文本操作,如删除原文件中的注释,插入包含文件袋内容或者替换宏等等。我们将预处理阶段的执行机构成为预处理器cpp。
2.2在Ubuntu下预处理的命令
2.3 Hello的预处理结果解析
Cpp将头文件的数据类型与函数声明等插入到了hello中并做了宏替换等操作,生成.i文件
2.4 本章小结
讲解gcc预处理器的功能与作用,描述了文件前后的变化差异,有助于深入理解预处理的概念与目的
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
编译是指把代码转换为汇编语言的过程。
编译器会将hello.i文件经过词法分析,语法分析,代码优化与生成代码四个阶段,翻译为hello.s文件。hello.s文件中每一个指令都是一条机器语言。
3.2 在Ubuntu下编译的命令
3.3 Hello的编译结果解析
3.3.1 数据
(1)字符串:
源程序中的两个字符串.LC0,.LC1
- 变量
这里以循环变量为例。如图中-4(rbp)代表的就是循环变量i。
- 函数参数
以main函数参数为例。Argc与argv进入主函数时,存在于寄存器edi与rsi中,后被压入栈里。
3.3.2赋值
mov函数体现了变量的赋值,这里是将循环变量i赋值为零
3.3.3条件判断
这里的例子使用cmp指令与跳转指令的组合实现了条件判断功能。
这个例子实现方法的原理与上面的相同,都是使用cmp指令组合跳转指令实现条件判断。这里的图示是循环条件的判断。
3.3.4算术运算
add,sub等代表加法与减法等运算。图中代表循环变量的自增运算。
3.3.5数组访问
以下代码即为数组访问过程下你将原数组首地址存入rax中,之后然后将argv[2](rax+16)保存在rdx,对于argv[1]同理;最后计算argv[3]则保存在了rdi中。
3.3.6条件控制
下图对应的函数即为argc判断后的printf函数,如图将.LC0移动到rax寄存器,再移动到rdi寄存器中,最终进行输出
3.3.7函数
下图是exit函数的调用,将exit的参数1先移动到edi再进行函数调用
printf函数调用。rsi和rdx分别保存argv[1]和argv[2],字符串保存在rdi中,最后调用函数输出字符串内容
下图对应的函数即为argc判断后的printf函数,如图将.LC0移动到rax寄存器,再移动到rdi寄存器中,最终进行输出
getchar函数,此函数直接进行了调用,无参数需求。
下图即为atoi函数和sleep函数的调用过程:atoi调用前先将argv[3]的值存在rdi中,目的是传入函数中;sleep函数的参数即为atoi的返回值,因此需要把eax的值移送到edi中,作为调用sleep的参数。
3.4 本章小结
这一章介绍了编译与汇编代码的内容。展示了汇编代码是如何实现C代码诸如条件、循环以及函数调用等操作。
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
在汇编器作用下,讲汇编文件转化为二进制语言文件的过程。在这一过程中.s文件将转化为.o文件
4.2 在Ubuntu下汇编的命令
4.3 可重定位目标elf格式
- ElF头:ELF头的开头是一个16字节的Magic数,他表示了系统架构,字大小以及小端法字节顺序。ELF头后续的内容包括辅助链接器语法分析的信息等等
- 节头表示各个节的大小、类型、地址、偏移量等详细信息,并且注明了本文件中没有程序头,节组和动态节。
3.重定位节,类型为R_X86_64_PC32的为静态链接,类型
为R_X86_64_PLT32的为动态链接,最后一行为异常处理单元“.rela.eh_frame”
4.符号表,保存函数和全局变量
4.4 Hello.o的结果解析
- hello.o反汇编的操作数是16进制数
- 在条件跳转语句上,hello.o反汇编文件用的是相对偏移量,而hello.s中是函数名。
- hello.s使用.L2和.LC1等段名称进行跳转,而hello.o反汇编文件使用目标代码的虚拟地址跳转。
- hello.s函数调用后为函数名称,在hello.o的反汇编文件中,call的目标地址是当前下一条指令
4.5 本章小结
将hello.o转化为elf文件,对.o文件和.s文件做出详细对比,可以深入了解机器语言和汇编语言的关系
(第4章1分)
第5章 链接
5.1 链接的概念与作用
链接是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。我们不用将大型程序视为一个巨大无比的原文件,而是将他分解为更小的模块,这一点对于程序的模块编写以及后期维护具有极为重要的意义。
5.2 在Ubuntu下链接的命令
5.3 可执行目标文件hello的格式
节头部表里对hello的所有节信息进行了声明,节被重定位到他们最终的运行时的内存地址;没有rel节,原因是可执行文件已经完全链接。
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息。
第一部分为ELF头
此部分为指令装载地址
此部分为文件中相关的字符串常量等
最后是由malloc分配的运行时堆以及用户使用的栈
5.5 链接的重定位过程分析
- 代码量比.o的多,增加了部分函数以及外部库函数
- 重定位后,链接器使得每个符号定义与内存位置关联起来,实现节的重定位,将其部分替换为了准确的地址
5.6 hello的执行流程
ld-2.27.so!_dl_init
hello!_start
libc-2.27.so!__libc_start_main
hello!_init
hello!main
hello!puts@plt
hello!exit@plt
hello!printf@plt
hello!atoi@plt
hello!sleep@plt
hello!getchar@plt
libc-2.27.so!exit
5.7 本章小结
通过分析hello的ELF、虚拟地址空间,执行流程、动态链接,了解了链接的作用以及整个由可重定位文件到可执行文件的过程。
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
进程是一个执行中程序的实例。系统中的每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
进程为程序提供了一种假象,程序好像是独占的使用处理器和内存。处理器好像是无间断地一条接一条地执行我们程序中的指令。
6.2 简述壳Shell-bash的作用与处理流程
shell是一个交互型的应用级程序,也被称为命令解析器,它为用户提供一个操作界面,接受用户的输入命令,并调度相应的应用程序。
- 读取用户输入的命令行
- 分析输入的字符串,得到命令行参数
- 调用函数检查第一个命令行参数是否为shell内置指令,是则执行,不是则创建一个子进程,在子进程中执行所求程序
6.3 Hello的fork进程创建过程
程序调用fork函数,生成的子进程的虚拟地址空间与父进程的虚拟地址空间相同,且是独立的,区别在于它们拥有不同的pid,fork创建的子进程pid为0,可以根据这个区分两者。
6.4 Hello的execve过程
fork创建子进程后分析命令行字符串获取参数并构造argv,然后调用execve来执行程序:函数会先加载Hello,然后会调用相应的启动代码,将虚拟地址的页映射到hello的页大小的片,初始化hello的代码和数据段,然后加载器设置程序计数器指向代码入口。
6.5 Hello的进程执行
内核会为每个进程维持一个重新启动该进程所需的状态,即为上下文,上下文信息里包括一系列寄存器以及程序计数器、栈等数据结构;进程时间片指的是进程执行控制流的一部分的每一时间段;处理器通过某个控制寄存器中的一个模式位来提供限制一个应用可以执行的指令以及它可以访问的地址空间范围的功能。该寄存器描述了当前进程运行的权限。当设置了模式位时,进程就运行在内核模式中。没有设置模式位时,进程就运行在用户模式中。一个运行在内核模式的进程可以执行指令集中的任何指令,并且可以访问系统中的任何内存;而用户模式的进程不允许和执行特权指令、也不允许用户模式中的进程直接引用地址空间中内核区内的代码和数据;一系列程序计数器PC的值的序列叫做逻辑控制流,这些值唯一地对应于包含在程序的可执行目标文件中的指令,或是包含在运行时动态链接到程序的共享对象中的指令。各个进程将轮流使用处理器,在同一个处理器核心中,每个进程执行它的流的一部分后被暂时挂起,然后执行其他进程。
6.6 hello的异常与信号处理
1.异常的种类:
(1)中断:中断是来自处理器外部的I/O设备的信号的结果。硬件中断的异常处理程序被称为中断处理程序。
(2)陷阱:陷阱是有意的异常,是执行一条指令的结果。其重要用途是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用。
(3)故障:故障由错误情况引起,它可能能够被故障处理程序修正。当故障发生时,处理器将控制转移给故障处理程序。如果处理程序能够修正这个错误情况,它就将控制返回到引起故障的指令,从而重新执行它。否则处理程序返回到内核中的abort(),abort()会终止引起故障的应用程序。
(4)终止:终止是不可恢复的致命错误造成的结果,通常是一些硬件错误,比如DRAM或者SRAM位被损坏时发生的奇偶错误。终止处理程序从不将控制返回给应用程序。处理程序将控制返回给abort(),abort会终止这个应用程序。
2.信号处理过程:
(1)正常程序执行状态:
(2)运行时按下Ctrl Z
输入ps和jobs
输入pstree
输入fg可以将转为前台执行:
输入Ctrl C,发送的SIGINT信号会结束进程:
6.7本章小结
本章通过介绍shell处理进程的过程以及fork、execve的使用来理解进程的概念和作用。在展示hello程序执行的具体过程的同时,还介绍了异常信号的处理机制。
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
(1)逻辑地址:程序产生的与段相关的偏移地址,即为.o文件中的地址
(2)线性地址:逻辑地址到物理地址的中间层。即为hello的虚拟内存的地址
(3)虚拟地址:在带有虚拟内存的系统中,CPU在地址空间中生成的虚拟地址,寻址时按照虚拟地址来寻址,地址送到内存前再转为适当的物理地址。即为Hello反汇编显示的地址。
(4)物理地址:主存单元每个字节都有唯一地址称为物理地址。即为hello通过MMU将虚拟地址映射到内存时对应的地址
7.2 Intel逻辑地址到线性地址的变换-段式管理
段式管理即为把程序分成若干个段来进行存储。段式管理需要有段表,段表中记录的信息有段号,装入位,段长,段的起始点等。通过段式管理的映射可以把逻辑地址映射到线性地址,即逻辑地址为段标识符+段内偏移。由逻辑地址到线性地址变换时,将逻辑地址进行分割成:段选择符+偏移。判断段选择符的TI,为0,那么切换到GDT,为1,那么切换到LDT。
7.3 Hello的线性地址到物理地址的变换-页式管理
页式管理需要页表,将虚拟页地址映射到物理页地址。将虚拟地址分成两部分:虚拟地址=VPN+VPO,使用VPN作为索引,在当前页表中寻找虚拟页,返回相应的PPN和PPO,则物理地址=PPN+PPO。
7.4 TLB与四级页表支持下的VA到PA的变换
TLB式翻译后备缓冲器,可以实现虚拟页号到物理页号的映射,式分页内存管理单元中的一个高相连度的集合,且运行均在CPU内部的MMU单元里,速度比较快。
四级页表可以降低内存占用,采用如下结构进行处理:
CR3寄存器指向L1的PT,然后由VPN1作为索引,进行寻找。找到PTE以后,以PTE条目里面的Base作为基址,再以VPN2作为索引,重复上述操作,直到找到L4的PT里面的PTE。以这个作为PPN,并上PPO,作为虚拟地址。
7.5 三级Cache支持下的物理内存访问
Cache为高速缓存,作用是缓解CPU和主存之间的速度差异。通过解析地址得到缓存的索引号和块内偏移,看是否发生命中,命中则直接发送数据给CPU,否则访问下一级取出该字存入当前缓存,然后将数据返回给CPU
地址上主存物理地址PA=CT+CI+CO,通过CI得到组,CO得到偏移。会先在L1 cache中查找,若未命中则逐层下查,直到获取目标字。
7.6 hello进程fork时的内存映射
当fork 函数被shell调用时,内核为hello进程创建各种数据结构,并分配给它一个唯一的PID。为了给hello进程创建虚拟内存,它创建了hello进程的mm_struct 、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。
当fork在hello进程中返回时,hello进程现在的虚拟内存刚好和调用fork 时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制机制就会创建新页面,也就为每个进程保持了私有地址空间的抽象概念。
7.7 hello进程execve时的内存映射
execve 函数在shell中加载并运行包含在可执行目标文件hello中的程序,用hello程序有效地替代了当前程序。加载并运行hello需要以下几个步骤:
1.删除已存在用户区域。删除shell虚拟地址用户部分中的已存在的区域结构。
2.映射私有区域。为hello的代码、数据、bss 和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时复制的。代码和数据区域被映射为hello 文件中的.text和.data 区。.bss 区域是请求二进制零的,映射到匿名文件,其大小包含在hello 中。栈和堆区域也是请求二进制零的,初始长度为零。
3.映射共享区域。如果hello程序与共享对象(或目标)链接,比如标准C 库libc. so, 那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
4.设置程序计数器(PC)。execve 做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向代码区域的入口点。
7.8 缺页故障与缺页中断处理
需要引用的虚拟内存的字不在物理内存中时称为缺页。处理器会生成一个虚拟地址传给MMU进而产生PTEA,在Cache或Mem处得到PTE,若PTE有效位为0则触发缺页异常。缺页异常处理程序将牺牲页替换出磁盘调入新页面,更新PTE然后回到原进程中继续执行指令。
7.9动态存储分配管理
通过堆来实现动态内存的管理,每个进程中的brk指向堆顶。堆中的每个块就是连续的虚拟内存片,空闲块可以用来分配;一个已分配的块可以保持当前分配状态直到被释放。有显式分配器和隐式分配器两种:显式分配器要求应用显式的释放已分配的块,例如C库中的malloc函数用来分配而free函数用来释放;隐式分配要求分配器检测一个已分配块不再被使用则直接释放该块。
7.10本章小结
本章详细分析了hello存储管理中的地址转换,段页管理,fork/execve函数内存映射,缺页处理,动态内存管理等问题。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
将所有IO设备抽象成文件,因而可以将所有输入输出都转变为对文件的操作。这种方式可以允许内核引出简单的应用接口(即为Unix I/O)
8.2 简述Unix IO接口及其函数
(1)Unix I/O接口:
1.打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访间一个I/O 设备。内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件。内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符。
2.Linux shell 创建的每个进程开始时都有三个打开的文件:标准输入(描述符为0) 、标准输出(描述符为1) 和标准错误(描述符为2) 。头文件< unistd.h> 定义了常量STDIN_FILENO 、STOOUT_FILENO 和STDERR_FILENO, 它们可用来代替显式的描述符值。
3.改变当前的文件位置。对于每个打开的文件,内核保持着一个文件位置k, 初始为0。这个文件位置是从文件开头起始的字节偏移量。应用程序能够通过执行seek 操作,显式地设置文件的当前位置为K 。
4.读写文件。一个读操作就是从文件复制n>0 个字节到内存,从当前文件位置k 开始,然后将k增加到k+n 。给定一个大小为m字节的文件,当k~m 时执行读操作会触发一个称为end-of-file(EOF) 的条件,应用程序能检测到这个条件。在文件结尾处并没有明确的“EOF 符号”。类似地,写操作就是从内存复制n>0个字节到一个文件,从当前文件位置k开始,然后更新k 。
5.关闭文件。当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。
(2)Unix I/O函数:
1. int open(char* filename,int flags,mode_t mode):
调用open函数来打开一个存在的文件或是创建一个新文件的。open函数将filename转换为一个文件描述符,并且返回描述符数字
2. int close(fd):
fd为文件的描述符,返回操作结果。
3. ssize_t read(int fd,void *buf,size_t n):
read函数从描述符为fd的当前文件位置赋值最多n个字节到内存位置buf。
4. ssize_t write(int fd,const void *buf,size_t n):
write函数从内存位置buf复制至多n个字节到描述符为fd的当前文件位置。
8.3 printf的实现分析
printf函数:
int printf(const char fmt, …)
{
int i;
char buf[256];
va_list arg = (va_list)((char)(&fmt) + 4);
i = vsprintf(buf, fmt, arg);
write(buf, i);
return i;
}
vsprintf函数:
vsprintf返回要打印出来的字符串的长度。
write的汇编代码:
mov eax, _NR_write
mov ebx, [esp + 4]
mov ecx, [esp + 8]
int INT_VECTOR_SYS_CALL
其中ecx寄存器中的是字符个数,ebx 存放的是第一个字符地址,int INT_VECTOR_SYS_CALLA 指的是调用了 syscall
Sys_call的汇编形式:
call save
push dword [p_proc_ready]
sti
push ecx
push ebx
call [sys_call_table + eax * 4]
add esp, 4 * 3
mov [esi + EAXREG - P_STACKBASE], eax
cli
ret
可知syscall 将字符串中的字节“Hello 2021110522 Mzm 1”从寄存器中复
制到显卡中,字符显示驱动子程序将通过 ASCII 码在字模库中找到点阵信息将点阵信息存储到 vram 中。显示芯片会按照一定的刷新频率逐行读取 vram,并通过信号线向液晶显示器传输每一个点(RGB 分量)。于是我们的打印字符串“Hello 2021110522 Mzm ”就显示在了屏幕上。
8.4 getchar的实现分析
getchar函数:
int getchar(void)
{
static char buf[BUFSIZ];
static char* bb=buf;
static int n=0;
if(n==0)
{
n=read(0,buf,BUFSIZ);
bb=buf;
}
return(–n>=0)?(unsigned char)*bb++:EOF;
}
键盘输入后函数从stdio流中读一个字符,若输入大于一个字符则先保留其他在缓冲区等待后续的读取
异步异常-键盘中断:键盘中断处理子程序,接收输入转成ASCII码并保存到缓冲区,函数通过系统调用读取ascii码,回车键后返回。
8.5本章小结
本章探讨了I/O设备的管理以及Unix接口和函数,深入理解了两个基础函数printf和getchar的工作原理。
(第8章1分)
结论
村长出村预处理,头文替换宏定义,隔壁就是编译器,大C转职汇编意,
前往邻村汇编器,汇编立刻变01,链接呼叫来集合,就此生成可执行,
斜杠哈喽一声吼,fork execve来执行,执行完毕齐收场,要把内存扫干净。
((./hello))
(结论0分,缺失 -1分,根据内容酌情加分)
附件
Hello.c 文本文件,源代码
Hello.i 预处理后的文件
Hello.s 汇编程序
Hello.o 可重定位文件
Hello 链接后的可执行文件
a.out 添加相关编译参数的可执行文件
(附件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分)