计算机系统
大作业
计算机科学与技术学院
2021年6月
底层实现在计算机领域至关重要,而在平时又难以直观感受,观察并研究hello.c程序在Linux环境下的底层编译、执行过程,了解汇编,链接,加载,执行等过程的概念和实现原理,对今后的学习非常有帮助
关键词:计算机系统;汇编语言;预处理;编译;汇编;链接;OS;
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
2.2在Ubuntu下预处理的命令.......................................................................... - 5 -
3.2 在Ubuntu下编译的命令............................................................................. - 6 -
4.2 在Ubuntu下汇编的命令............................................................................. - 7 -
5.2 在Ubuntu下链接的命令............................................................................. - 8 -
5.3 可执行目标文件hello的格式.................................................................... - 8 -
6.2 简述壳Shell-bash的作用与处理流程..................................................... - 10 -
6.3 Hello的fork进程创建过程..................................................................... - 10 -
6.6 hello的异常与信号处理............................................................................ - 10 -
7.1 hello的存储器地址空间............................................................................ - 11 -
7.2 Intel逻辑地址到线性地址的变换-段式管理............................................ - 11 -
7.3 Hello的线性地址到物理地址的变换-页式管理....................................... - 11 -
7.4 TLB与四级页表支持下的VA到PA的变换............................................. - 11 -
7.5 三级Cache支持下的物理内存访问.......................................................... - 11 -
7.6 hello进程fork时的内存映射.................................................................. - 11 -
7.7 hello进程execve时的内存映射.............................................................. - 11 -
7.8 缺页故障与缺页中断处理........................................................................... - 11 -
8.1 Linux的IO设备管理方法.......................................................................... - 13 -
8.2 简述Unix IO接口及其函数....................................................................... - 13 -
第1章 概述
1.1 Hello简介
1.P2P:Program to Progress, 文本格式的hello.c文件经过cpp的预处理生成hello.i预处理文件,之后交给ccl进行编译,生成hello.s汇编文件。再将汇编文件交给as生成可重定位文件hello.o,最后经由链接器ld,生成可执行文件hello,由上述过程生成可执行目标文件。
2.O2O:From Zreo-O to Zero-O。shell执行可执行目标文件,管理hello进程,对其进行存储管理,分配映射虚拟内存、分配物理内存,输出结果到显示器,最后结束hello进程,回收其内存空间。
1.2 环境与工具
1.2.1 硬件环境
Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz ;16GB RAM;
1.2.2 软件环境
Windows 10 64位;VMware:Ubuntu 20.04.2 LTS 64位VMware
1.2.3 开发工具
Vim 8.1.2269 64位;Visual studio 2019 64位;CodeBlocks 20.03 64位
1.3 中间结果
1.hello.c: C语言源文件。
2.hello.i:预处理文本文件。
3.hello.s:汇编代码文件。
4.hello.o:可重定位目标文件。
5.hello:可执行目标文件。
6.hello.asm:由hello.o反汇编生成的汇编代码。
8.hello_o.asm:由hello反汇编生成的汇编代码文本。
7.hello.elf:hello.o的elf信息文本。
9.hello_o.elf:hello的elf信息文本。
1.4 本章小结
解释了P2P和O2O的概念和过程,以及本次实验的实验环境即过程中的中间文件
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
概念:预处理器根据以字符#开头的命令,修改原始的C程序,如头文件、宏定义和条件编译。生成一个.i文本文件。
作用:预处理器加入头文件、执行宏替换和条件编译。再进行正式的编译工作,便于编译的高效进行。
2.2在Ubuntu下预处理的命令
图 2-1
图 1-2
2.3 Hello的预处理结果解析
源文件hello.c内容如下
图 2-3
预处理文件hello.i内容如下
图 2-4
可以看到预处理文件比源文件多出几千行,其内容为对头文件的引入:
图 2-5
其中描述了各运行库在计算机中的位置:
图 2-6
声明了其中用到的函数名:
图 2-7
2.4 本章小结
本章介绍了预处理的概念和作用,将hello.c预处理为hello.i,并对预处理结果进行了分析。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
概念:编译器将预处理文本文件hello.i翻译成汇编语言文本文件hello.s,该文件用汇编语言描述了程序的内容
作用:经过词法分析,语法分析,语义分析,和一定的优化来生成可以实现对应程序功能的汇编代码文件,与高级语言相比,汇编语言更接近计算机能够理解的语言,进一步有利于二进制机器语言的生成。
3.2 在Ubuntu下编译的命令
图 3-1
图 3-2
3.3 Hello的编译结果解析
生成的hello.s文件内容如下:
图 3-3
3.3.1变量的处理
全局变量:源文件中声明了全局变量sleepsecs,在汇编语言中它存放在了.data区域
图 3-4
局部变量:源文件中声明了局部变量i(int类型),局部变量不在数据段表现,观察汇编文件的代码段:
图 3-5
可发现源文件中的变量i作为循环条件存在,而汇编代码中参与循环判断的是-4(%rbp),故i的值存放在该位置
图 3-6
3.3.2赋值、算数操作及条件判断与跳转
赋值操作:
该步对应了源代码中i = 0的操作
算数操作:
该步对应源代码中i++
条件判断与跳转:
该步对应了源代码中判断i<10,带汇编语言中表现为判断i是否小于等于9,若是则跳转到循环内部(.L4)
3.3.3函数调用
汇编语言中用call指令实现函数调用的操作
3.3.4数组、指针
该步对应了源代码中对argv[]数组的操作,汇编代码中对数组的实现表示为对数组头加上偏移量的形式实现
3.4 本章小结
本章将预处理的文本文件编译为汇编语言表示的.s文件,用以后续生成过程,并对程序中出现的汇编代码进行了解析,使其与源代码中的命令对应
(第3章2分)
第4章 汇编
4.1 汇编的概念与作用
概念:把汇编语言翻译成机器语言的过程,形成可重定位目标文件。
作用:把汇编语言一一对应地翻译成机器可以理解并直接执行的机器指令。
4.2 在Ubuntu下汇编的命令
图 4-1
图 4-8
4.3 可重定位目标elf格式
ELF头:以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。
节头:指明各个节的名称、类型、起始地址和偏移量
重定位节:
重定位节记录了程序的偏移信息,使程序可以在链接后正确生成可执行文件
4.4 Hello.o的结果解析
图 4-9
对比反汇编结果与hello.s可知二者不同:
- hello.s中分支跳转语句通过跳转到目标节.L3/.L4等来表示,而反汇编文件则通过直接跳转到对应目标地址来实现分支跳转
- hello.s中函数调用通过call对应函数名实现,而反汇编文件通过call对应函数所在地址来实现
- hello.s中可以通过直接访问数据段来访问全局变量,而反汇编文件中数据段位置不确定,故需要借助重定位信息进行访问
4.5 本章小结
本章将汇编语言文件汇编为可重定位目标文件hello.c,并查看了elf头,分析了elf头中各节的作用,并通过反汇编了解了机器语言的底层实现
(第4章1分)
第5章 链接
5.1 链接的概念与作用
概念:链接器把多个可重定位文件链接成一个完整的可执行文件。
作用:链接可以在编译、汇编、加载和运行时执行。链接方便了模块化编程。
5.2 在Ubuntu下链接的命令
图 5-1
图 5-2
图 5-3
5.3 可执行目标文件hello的格式
elf头:
节头:
程序头:在hello_o.elf中没有这部分内容
重定位节:
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明
init:0x00401000
.text:0x004010d0
5.5 链接的重定位过程分析
hello.asm中多出的段包括:
.interp:保存ld.so的路径
.note.ABI-tag
.note.gnu.build-i:编译信息表
.gnu.hash:gnu的扩展符号hash表
.dynsym:动态符号表
.dynstr:动态符号表中的符号名称
.gnu.version:符号版本
.gnu.version_r:符号引用版本
.rela.dyn:动态重定位表
.rela.plt:.plt节的重定位条目
.init:程序初始化
.plt:动态链接表
.fini:程序终止时需要的执行的指令
.eh_frame:程序执行错误时的指令
.dynamic:存放被ld.so使用的动态链接信息
.got:存放程序中变量全局偏移量
.got.plt:存放程序中函数的全局偏移量
.data:初始化过的全局变量或者声明过的函数
5.6 hello的执行流程
加载程序:
ld-2.27.so!_dl_start
ld-2.27.so!_dl_init
hello!_start
call main:
libc-2.27.so!__libc_start_main
hello!main
hello!puts@pl
终止:
exit
5.7 Hello的动态链接分析
由elf文件可知.got.plt的地址
图 5-8
使用edb查看运行前GOTPLT表的内容
图 5-9
运行dl_start和dl_init后GOTPLT表内容发生变化
图 5-10
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。
5.8 本章小结
本章通过链接成功生成了可执行的目标文件,并介绍了动态链接库等内容
(第5章1分)
第6章 hello进程管理
6.1 进程的概念与作用
概念:进程的经典定义就是一个执行中的程序的实例。系统中的每个程序都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量、以及打开文件描述符的集合。
作用:进程给应用程序提供的关键抽象有两种:
a) 一个独立的逻辑控制流,提供一个假象,程序独占地使用处理器。
b) 一个私有的地址空间,提供一个假象,程序在独占地使用系统内存。
6.2 简述壳Shell-bash的作用与处理流程
shell是用户与系统交互作用界面。Shell是一个命令解释程序,也是一种程序设计语言。
1.读入命令行、注册相应的信号处理程序、初始化进程组。
2. 通过paraseline函数解释命令行,如果是内置命令则直接执行,否则阻塞信号后创建相应子进程,在子进程中解除阻塞,将子进程单独设置为一个进程组,在新的进程组中执行子进程。父进程中增加作业后解除阻塞。如果是前台作业则等待其变为非前台程序,如果是后台程序则打印作业信息。
6.3 Hello的fork进程创建过程
int fork(void)函数:
子进程返回0,父进程返回子进程的PID
新创建的子进程几乎但不完全与父进程相同:
子进程得到与父进程虚拟地址空间相同的(但是独立的)一份副本。
子进程获得与父进程任何打开文件描述符相同的副本。
子进程有不同于父进程的PID
fork()调用一次,返回两次。
在shell中如果fork返回值小于0,说明fork时出现了一些问题这时需要进行处理。否则就成功创建了子进程。
6.4 Hello的execve过程
调用fork函数之后,子进程将会调用execve函数,来运行hello程序,如果成功调用则不再返回,若未成功调用则返回-1。
完整的加载运行hello程序需要以下几个步骤
首先加载器会删除当前子进程虚拟地址端。,然后创建一组新的代码、数据、堆端,并初始化为0。
接着映射私有区域和共享区域,将新的代码和数据段初始化为可执行文件中的内容
最后设置程序计数器,使其指向代码区的入口,下一次调度这个进程时,将直接从入口点开始执行
6.5 Hello的进程执行
Linux 系统中的每个程序都运行在一个进程上下文中,有自己的虚拟地址空间。当shell 运行一个程序时,父shell 进程生成一个子进程,它是父进程的一个复制。子进程通过execve 系统调用启动加载器。加载器删除子进程现有的虚拟内存段,并创建一组新的代码、数据、堆和栈段。新的栈和堆段被初始化为零。通过将虚拟地址空间中的页映射到可执行文件的页大小的片(chunk), 新的代码和数据段袚初始化为可执行文件的内容。最后,加载器跳转到_start地址,它最终会调用应用程序的main 函数。
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。
6.6 hello的异常与信号处理
1. 可能出现的异常
中断:来自I/O设备的信号。比如输入CTRL -C或者CTRL-Z
陷阱:有意的异常,是执行一条指令的结果,调用后也会返回到下一条指令,用来调用内核的服务进行操作。
故障是由错误情况引起的,它可能能够被故障处理程序修正。如果修正成功,则将控制返回到引起故障的指令,否则将终止程序。
终止是不可恢复的致命错误造成的结果,通常是一些硬件的错误,处理程序会将控制返回给一个abort例程,该例程会终止这个应用程序。
2. 可能产生的信号:SIGINT,SIGSTP,SIGCONT,SIGWINCH等
3.异常处理:
3.1:运行过程中按CTRL-C,给进程发送SIGINT信号,程序将被终止回收
图 6-1
3.2:运行过程中按CTRL-Z,给进程发送SIGSTP信号,程序将被挂起,用fg命令,可以让程序回到前台继续运行。
图 6-2
3.3:挂起中输入pstree时
图 6-3
6.7本章小结
本章完成了程序从运行到结束的运行过程,体现了hello的进程管理
(第6章1分)
第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:由程序产生的与段相关的偏移地址部分。例如,你在进行C语言指针编程中,可以读取指针变量本身值(&操作),实际上这个值就是逻辑地址,它是相对于你当前进程数据段的地址,不和绝对物理地址相干。
线性地址:地址空间中的整数是连续的。
虚拟地址:在一个带虚拟内存的系统中,CPU从一个有N=2"个地址的地址空间中生成虚拟地址,这个地址空间称为虚拟地址空间。
物理地址:计算机系统的主存被组织成一个个由M个连续的字节大小的单元组成的数组。每字节都有一个唯一的物理地址。
在hello加载进内存时会分配虚拟内存,总是从0x00401000开始
7.2 Intel逻辑地址到线性地址的变换-段式管理
每个段的首地址就会被储存在各自的段描述符里面,所以的段描述符都将会位于段全局描述符表中(每个段的全局描述符表一个局部称为gdgdt和另一个局部的的段描述符表一个局部称为ldldt),通过段选择符我们可以快速寻找到某个段的段全局描述符。逻辑上段地址的偏移量结构就是段选择符+偏移量。
段选择符的索引位组成和定义如下,分别指的是索引位,ti,rpl,当索引位ti=0时,段描述符表在rpgdt中,ti=1时,段描述符表在rpldt中。而索引位index就类似一个数组,每个元素内都存放一个段的描述符,索引位首地址就是我们在查找段描述符时再这个元素数组当中的索引。一个段描述符的首地址是指含有8个元素的字节,我们通常可以在查找到段描述符之后获取段的首地址,再把它与线性逻辑地址的偏移量进行相加就可以得到段所需要的一个线性逻辑地址。
在分段保护模式下,分段有两种机制:段的选择符在段的描述符表->分段索引->目标段的段描述符条目->目标段的描述符基地址+偏移量=转换为线性段的基地址。由于现代的macosx86系统内核使用的描述符是基本扁平的逻辑模型,即目标段的逻辑地址=线性段的描述符=转换为线性段的基地址,等价于描述符转换为线性地址时关闭了偏移量和分段的功能。这样逻辑段的基地址与转换为线性段的基地址就合二为一了。
7.3 Hello的线性地址到物理地址的变换-页式管理
系统将每个段分割为被称为虚拟页(VP)的大小固定的块来作为进行数据传输的单元,在linux下每个虚拟页大小为4KB,类似地,物理内存也被分割为物理页(PP/页帧),虚拟内存系统中MMU负责地址翻译,MMU使用存放在物理内存中的被称为页表的数据结构将虚拟页到物理页的映射,即虚拟地址到物理地址的映射。
7.4 TLB与四级页表支持下的VA到PA的变换
将VPN分成三段,对于TLBT和TLBI来说,可以在TLB中找到对应的PPN,但是有可能出现缺页的情况,这时候就需要到页表中去找。此时,VPN被分成了更多段(这里是4段)CR3是对应的L1PT的物理地址,然后一步步递进往下寻址,越往下一层每个条目对应的区域越小,寻址越细致,在经过4层寻址之后找到相应的PPN让你和和VPO拼接起来。
7.5 三级Cache支持下的物理内存访问
得到物理地址之后,先将物理地址拆分成CT(标记)+CI(索引)+CO(偏移量),然后在一级cache内部找,如果未能寻找到标记位为有效的字节(miss)的话就去二级和三级cache中寻找对应的字节,找到之后返回结果。
7.6 hello进程fork时的内存映射
shell通过一个调用fork的函数让进程内核自动创建一个新的进程,这个新的进程拥有各自新的数据结构,并且被内核分配了一个唯一的pid。它有着自己独立的虚拟内存空间,并且还拥有自己独立的逻辑控制流,它同样可以拥有当前已经可以打开的各类文件信息和页表的原始数据和样本,为了有效保护进程的私有数据和信息,同时为了节省对内存的消耗,进程的每个数据区域都被内核标记起来作为写时复制。
7.7 hello进程execve时的内存映射
execve函数在当前代码共享进程的上下文中加载并自动运行一个新的代码共享程序,它可能会自动覆盖当前进程的所有虚拟地址和空间,删除当前进程虚拟地址的所有用户虚拟和部分空间中的已存在的代码共享区域和结构,但没有自动创建一个新的代码共享进程。新的运行程序仍然在堆栈中拥有相同的区域pid。之后为新运行程序的用户共享代码、数据、bss和所有堆栈的区域结构创建新的共享区域和结构,新代码共享区域可能是在运行时私有的、写时复制的。它首先映射到一个共享的区域,hello这个程序与当前共享的对象libc.so链接,它可能是首先动态通过链接映射到这个代码共享程序上下文中的,然后再通过映射链接到用户虚拟地址和部分空间区域中的另一个共享代码区域内。为了设置一个新的程序计数器,execve函数要做的最后一件要做的事情就是自动设置当前代码共享进程上下文的一个程序计数器,使之成为指向所有代码共享区域的一个入口。
7.8 缺页故障与缺页中断处理
段错误:首先判断这个缺页的虚拟地址是否合法,遍历所有的合法区域结构,如果对所有的区域结构都无法匹配,返回段错误。
非法访问:查看地址的权限,判断进程是否有读写权限。
若上述情况都不符合则为正常缺页,选择一个页面换入新的页面并更新到页表。
7.9动态存储分配管理
分配器通过维护虚拟内存(堆)来实现动态存储分配管理,存在两种维护方式:
隐式空闲链表:分配器检测一个已分配块何时不再被程序所使用,那么就释放这个块
显式空闲链表:每次声明内存空间都保证至少分配size_t大小的内存,双字对齐,每次必须从空闲块中分配空间,在申请空间时将空闲的空间碎片合并,以尽量减少浪费。
7.10本章小结
本章概括了进程的存储管理,介绍了了虚拟地址、物理地址、线性地址、逻辑地址的概念以及进程fork和execve的内存映射。描述了系统应对缺页异常的方法和malloc的内存分配管理机制。
(第7章 2分)
第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件
设备管理:unix io接口
8.2 简述Unix IO接口及其函数
打开:open(),打开或创建目标文件。
关闭:close(),关闭文件。
读取:read(),从当前文件位置读取字节到内存中。
写入:write(),从内存复制字节到当前文件位置。
更改文件位置:lseek(),将文件位置更改为目标位置
8.3 printf的实现分析
printf函数中调用了sprintf函数和write函数
sprintf函数将所有的参数内容格式化为字符串并存入buf,然后返回格式化数组长度。
write函数将buf中的元素写到终端。
vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)
8.4 getchar的实现分析
当buf为空时,getchar调用read函数,否则直接读取buf中的首元素。
read函数把整个缓冲区读到buf中,返回缓冲区长度。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
8.5本章小结
本章概括了Linux系统的IO管理,对IO相关函数进行了介绍和分析
(第8章1分)
结论
1.预处理源文件hello.c,将外部库合并并生成预处理文件hello.i
2.将hello.i编译为汇编语言文件hello.s
3.将汇编文件hello.s汇编为可重定位目标文件hello.o
4.将hello.o与动态链接库链接形成最终的hello可执行目标文件
5.在shell中输入命令运行目标文件hello
6.shell调用fork创建子进程
7.shell调用execve,为程序分配物理内存,映射虚拟内存,执行main函数
8.main函数执行程序命令,执行申请动态内存等操作
9.收到信号或运行完毕,程序终止,进程结束,内核回收该子进程
本次大作业是对本学习计算机系统学习内容的一次总结和回顾,运用到了课程各章节介绍的原理和方法,通过这门课我了解到计算机的底层实现原理非常重要,我们在编程中也应该更多考虑底层原理与问题,这样可以更深入地理解计算机系统
(结论0分,缺失 -1分,根据内容酌情加分)
附件
1.hello.c: C语言源文件。
2.hello.i:预处理文本文件。
3.hello.s:汇编代码文件。
4.hello.o:可重定位目标文件。
5.hello:可执行目标文件。
6.hello.asm:由hello.o反汇编生成的汇编代码。
8.hello_o.asm:由hello反汇编生成的汇编代码文本。
7.hello.elf:hello.o的elf信息文本。
9.hello_o.elf:hello的elf信息文本。
(附件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分)