摘 要
计算机系统是由硬件和系统软件组成的,他们共同工作来运行应用程序。虽然系统的具体实现方式随着时间不断变化,但是系统内在的概念却没有改变。所有计算机系统都有相似的硬件和软件,它们又执行着相似的功能。
我们通过跟踪hello程序的生命周期来回顾我们对计算机系统的学习——从它被程序员创建开始,到系统上运行,输出简单的消息然后终止。我们将沿着这个程序的生命周期,探讨计算机系统中一些概念和原理。
关键词:预处理;编译;汇编;链接;进程管理;异常和信号;存储器体系结构,IO管理。
(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)
目 录
第1章 概述 - 4 -
1.1 HELLO简介 - 4 -
1.2 环境与工具 - 4 -
1.3 中间结果 - 4 -
1.4 本章小结 - 4 -
第2章 预处理 - 6 -
2.1 预处理的概念与作用 - 6 -
2.2在UBUNTU下预处理的命令 - 7 -
2.3 HELLO的预处理结果解析 - 7 -
2.4 本章小结 - 7 -
第3章 编译 - 8 -
3.1 编译的概念与作用 - 8 -
3.2 在UBUNTU下编译的命令 - 8 -
3.3 HELLO的编译结果解析 - 8 -
3.4 本章小结 - 12 -
第4章 汇编 - 13 -
4.1 汇编的概念与作用 - 13 -
4.2 在UBUNTU下汇编的命令 - 13 -
4.3 可重定位目标ELF格式 - 14 -
4.4 HELLO.O的结果解析 - 17 -
4.5 本章小结 - 4 -
第5章 链接 - 5 -
5.1 链接的概念与作用 - 5 -
5.2 在UBUNTU下链接的命令 - 5 -
5.3 可执行目标文件HELLO的格式 - 6 -
5.4 HELLO的虚拟地址空间 - 7 -
5.5 链接的重定位过程分析 - 7 -
5.6 HELLO的执行流程 - 8 -
5.7 HELLO的动态链接分析 - 9 -
5.8 本章小结 - 11 -
第6章 HELLO进程管理 - 12 -
6.1 进程的概念与作用 - 12 -
6.2 简述壳SHELL-BASH的作用与处理流程 - 12 -
6.3 HELLO的FORK进程创建过程 - 12 -
6.4 HELLO的EXECVE过程 - 13 -
6.5 HELLO的进程执行 - 14 -
6.6 HELLO的异常与信号处理 - 16 -
6.7本章小结 - 20 -
第7章 HELLO的存储管理 - 21 -
7.1 HELLO的存储器地址空间 - 21 -
7.2 INTEL逻辑地址到线性地址的变换-段式管理 - 21 -
7.3 HELLO的线性地址到物理地址的变换-页式管理 - 21 -
7.4 TLB与四级页表支持下的VA到PA的变换 - 26 -
7.5 三级CACHE支持下的物理内存访问 - 26 -
7.6 HELLO进程FORK时的内存映射 - 27 -
7.7 HELLO进程EXECVE时的内存映射 - 27 -
7.8 缺页故障与缺页中断处理 - 27 -
7.9动态存储分配管理 - 28 -
7.10本章小结 - 31 -
第8章 HELLO的IO管理 - 32 -
8.1 LINUX的IO设备管理方法 - 32 -
8.2 简述UNIX IO接口及其函数 - 32 -
8.3 PRINTF的实现分析 - 32 -
8.4 GETCHAR的实现分析 - 34 -
8.5本章小结 - 34 -
结论 - 34 -
附件 - 35 -
参考文献 - 36 -
第1章 概述
1.1 Hello简介
根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P:编译器驱动程序(compiler driver)代表用户在需要时调用语言预处理器cpp预处理,编译器cc1编译,汇编器as汇编和链接器ld之后成为可执行程序hello,在shell中键入启动命令后,shell fork产生一个新的子进程,并在该子进程中调用execve,加载并执行hello程序 。
020:在shell中键入启动命令后,shell fork产生一个新的子进程,并在该子进程中调用execve,加载并执行hello程序 ,为其映射虚拟内存,把进程的上下文中的程序计数器指向代码区域的入口点。CPU为运行的hello进程分配时间片,执行逻辑控制流。当hello进程运行结束后,shell父进程负责回收hello进程,内核删除相关数据结构。
1.2 环境与工具
硬件环境:X64 CPU ,1.80GHz,16G RAM
软件环境:Windows 10 64位 ,Vmware 14 ,Ubuntu 16.04 LTS 64 位
开发工具:gcc,gdb,edb,readelf,objdump
1.3 中间结果
hello.c :hello源代码
hello.i :预处理后的文本文件
hello.s :hello.i编译后的汇编文件
hello.o :hello.s汇编后的可重定位目标文件
hello :链接后的可执行文件
hello.o_d-r:hello.o的反汇编代码(objdump -d -r hello.o得到)
hello.o_dxs:hello.o的反汇编代码(objdump -dxs hello.o得到)
hello.o_elf:hello.o的Readelf结果(readelf -a hello.o 得到)
hello_dxs:hello的反汇编代码(objdump -dxs hello得到)
hello_elf:hello.o的Readelf结果(readelf -a hello 得到)
1.4 本章小结
本章对hello进行了简单的介绍,分析了其P2P和020的过程。列出了本次任务的环境和工具,解释了过程中出现的中间产物及其作用。
(第1章0.5分)
第2章 预处理
2.1 预处理的概念与作用
预处理是指进行编译的第一遍扫描之前所做的工作,由预处理器cpp负责完成。C语言提供了多种预处理功能,包括宏定义,文件包含,条件编译。
2.2在Ubuntu下预处理的命令
gcc -E -o hello.i hello.c
图 1 预处理
2.3 Hello的预处理结果解析
经过预处理,29行的hello.c生成了3119行的hello.i,hello.c中的#include
2.4 本章小结
本章我们探讨了预处理过程,了解了预处理提供的三大功能:宏定义,文件包含和条件编译。在这个阶段,我们将hello.c通过预处理转换成了hello.i文件。
hello.c的预处理阶段中做的工作就是处理
#include
#include
#include
将三个头文件插入hello.c文件中生成hello.i文件。
(第2章0.5分)
第3章 编译
3.1 编译的概念与作用
这个阶段编译器主要做词法分析、语法分析、语义分析等,在检查无错误后后,编译器(ccl)把代码翻译成汇编语言。编译器将ASCII文本文件hello.i 翻译成ASCII文本文件hello.s,hello.s中的汇编语言语句以一种文本格式描述了低级机器语言指令
3.2 在Ubuntu下编译的命令
gcc -S hello.i -o hello.s
图 2 编译
3.3 Hello的编译结果解析
图 3 Hello的编译结果解析
3.3.1 数据
(1)整型变量:
(2)常量:常量以立即数的形式出现,如图4。
图 4 常量的出现形式
(3)字符串:argv[1]和argv[2]是指向从命令行读入的字符串。
printf函数的标准字符串存储在.rodate段中,如图5
图 5 字符串的出现形式
3.3.2 赋值
(1) 对全局变量sleepsecs的赋值,因为sleepsec是int类型,将2.5强制类型转换之后赋初值为2:
图 6 对全局变量sleepsecs的赋值
(2) 对局部变量i的赋值:使用movl语句,对应于C程序中i=0;
图 7 对局部变量i的赋值
3.3.3 类型转换
int sleepsecs=2.5;中2.5是float类型,sleepsecs是int类型,2.5隐式转换成int类型的2,sleepsecs被赋值为2,说明在赋值时发生了隐式类型转换。
3.3.4 算数操作
实现了i++.
3.3.5 关系操作
argc存放在-20(%rbp)的位置,通过如下汇编代码实现if(argc!=3)
3.3.6 数组/指针/结构操作
指针数组:char *argv[]:argv[0]指向输入程序的路径和名称,argv[1]和argv[2]分别指向另外两个用户输入的字符串。
当main开始执行时,用户栈的组织结构如图8所示:
图 8 用户栈
将argv[0]移入%rax
将argv[1]移入%rax和%rsi
3.3.7 控制转移
1.if(argc!=3)
{
printf(“Usage: Hello 学号 姓名!\n”);
exit(1);
}
if(argc!=3) 的转移控制由如下汇编代码实现
cmpl语句根据两个操作数之差来设置条件码,他们只设置条件码而不代编任何其他的寄存器。cmpl语句比较 -20(%rbp)(之前拷贝了一份argc在-20(%rbp)中)和3,设置条件码。je判断ZF标志位,如果cmpl操作使得ZF标志位为0,则跳到.L2中,否则顺序执行下一条语句。
2. for(i=0;i<10;i++)的转移控制由如下汇编代码实现
首先初始化控制变量i为0,然后跳转到.L3处的比较代码,使用 cmpl 进行比较,如果 i<=9,则跳入.L4 循环体执行,否则说明循环结束,顺序执行 for 之后的指令。
3.3.8 函数操作
1.main函数:
参数传递:传入参数argc和argv,分别用寄存器%rdi和%rsi存储。
函数调用:被系统启动函数调用。
函数返回:设置%eax为0并且返回,对应return 0 。
2.printf函数:
参数传递:调用printf之前传入要被打印字符串的首地址
if循环体中的printf函数对应call puts,调用时传入了字符串参数首地址;
for循环中调用printf时分别传入了 argv[1]和argc[2]的地址。
函数调用:在if体中可能被调用,在for循环中被调用
函数返回:返回一个int值,表示被打印的字符数。
3.exit函数:
参数传递:传入的参数为1
函数调用:if判断条件满足后被调用
4.sleep函数:
参数传递:传入参数sleepsecs,传递控制call sleep
函数调用:for循环下被调用
函数返回:返回一个int值,表示剩余的睡眠时间
5.getchar
函数调用:在main中被调用