程序人生-Hello’s P2P

摘 要
计算机系统是由硬件和系统软件组成的,他们共同工作来运行应用程序。虽然系统的具体实现方式随着时间不断变化,但是系统内在的概念却没有改变。所有计算机系统都有相似的硬件和软件,它们又执行着相似的功能。
我们通过跟踪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语言提供了多种预处理功能,包括宏定义,文件包含,条件编译。

  1. 宏定义
    在C语言源程序中允许一个标识符来表示一个字符串,称为“宏”;被定义为“宏”的标识符称为“宏名”。在编译程序中,对程序所有出现的“宏名”,都用宏定义中的字符取代换,又被称为“宏替代”或“宏代换”。宏定义是有源程序中的宏定义命令完成的。宏代换是由预处理程序自动完成的。在C语言中,宏分为有参数和无参数两种。
    (1)无参数宏
    无参数宏定义的一般形式为:#define 标示字符串
    其中:“#”代表是编译预处理命令,define是宏处理的关键词,标识符是宏名。字符串是宏名所代替的内容,也可以是常数,表达式等。宏定义是用宏名表示一个字符串,在宏展开时又以该字符串取代宏,是一种简单的替换。
    (2)带参数宏
    带参数宏定义中,宏名和形参之间不能有空格出现。在带参宏定义中,形参不分配内存单元,因此,不作为类型主义,宏调用参数的实参有具体的值。去代替形参,形参和实参是两个不同的量。在带参宏中,只是符号代换,不存在值传递。
  2. 文件包含
    文件包含命令行的一般形式是: #incude “文件名”
    文件包含命令功能是指定文件插入该位置取代该命令行,从而把指定文件和当前源文件程序连成一个源文件。
  3. 条件编译
    程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。条件编译指令将决定哪些代码被编译,而哪些不被编译的。可以根据表达式的值或者某个特定的宏是否被定义来确定编译条件。

2.2在Ubuntu下预处理的命令
gcc -E -o hello.i hello.c

图 1 预处理

2.3 Hello的预处理结果解析
经过预处理,29行的hello.c生成了3119行的hello.i,hello.c中的#include命令预处理器读取系统头文件stdio.h的内容,并把它直接插入程序文本中。我们可以在hello.i的末尾部分找到hello.c
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)整型变量:

  1. int sleepsecs=2.5;在C程序中sleepsecs是已初始化的全局变量,存放在.data段。sleepsecs被定义为全局变量globl,在.data段中,sleepsecs被设置为对齐方式是4,类型是object,大小是4字节。
  2. int argc;argc作为局部变量存储在栈或者寄存器中,argc是函数传入的第一个int型参数,存储在%edi中。
    3.int i;i作为局部变量存储在栈或者寄存器中。i存放在-4(%rbp)的位置,movl $0, -4(%rbp)将i初始化为0。

(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 关系操作

  1. argc存放在-20(%rbp)的位置,通过如下汇编代码实现if(argc!=3)

    1. i存放在-4(%rbp)的位置,通过如下汇编代码实现判断for(i=0;i<10;i++)中的i<10

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中被调用

你可能感兴趣的:(程序人生-Hello’s P2P)