hello的一生

第1章 概述
1.1 Hello简介
P2P:hello从c语言程序到进程,主要经历了预处理,编译,汇编,链接阶段生成可执行文件,通过shell调用fork()函数新建子进程。
O2O:shell执行可执行文件时通过内存映射分配内存,当hello进程结束,执行回收。
1.2 环境与工具
硬件环境:Intel I5 6300U x64 CPU,8G RAM,256G SSD
软件环境:Windows 10 64位,Vmware workstation 15,Ubuntu 18.01 LTS
开发与调试工具:gcc,readelf,objdump,gdb,edb,sublime
1.3 中间结果
hello.i 经过预处理得到的文件
hello.s 经过编译得到的汇编程序文件
hello.o 经过汇编得到的可重定位目标文件
hello.out 经过链接得到的可执行目标文件
1.4 本章小结
本章介绍了本次大作业的Hello简介,以及实验环境以及工具,同时描述了大作业完成过程中的中间结果。
(第1章0.5分)

第2章 预处理
2.1 预处理的概念与作用
预处理是程序在编译之前的操作,预处理主要有三个方面的内容: 1.宏定义,2.文件包含,3.条件编译。预处理主要处理以#开头的语句。
2.2在Ubuntu下预处理的命令
gcc –E hello.c –o hello.i

图1-1
2.3 Hello的预处理结果解析

图1-2
Hello.s文件如图1-2所示,我们可以看到源程序中的以#开头的语句的语句都已被预处理程序处理,替换为了相应的代码,由此可知预处理阶段主要处理以#开头的语句
2.4 本章小结

本章描述了预处理阶段,预处理是在程序编译之前的阶段,主要处理以#开头的语句即1.宏定义,2.文件包含,3.条件编译。

(第2章0.5分)

第3章 编译
3.1 编译的概念与作用
编译是将.i文件翻译为.s文件的过程,即将高级语言程序转换为汇编语言的过程。汇编将高级语言程序转换为更加底层的汇编语言程序。
3.2 在Ubuntu下编译的命令
gcc –S hello.i –o hello.s

图2-1
3.3 Hello的编译结果解析
如图2-2为hello.s的内容

图2-2

3.3.1常量

源程序中包含了字符串常量数据:Hello 1160100402 杨林峰!\n以及Hello %s %s\n,这些字符串存储在.rodata节中,如图3-1所示

图3-1
3.3.2 全局变量
源程序中包含一个sleepsecs的整型变量如图3-2所示:

图3-2
与其对应的汇编程序如图3-3所示:

图3-3
首先由于sleepsecs为已初始化的全局变量,所以其存储与.data节中。再观察发现sleepsecs为整型,但用一个浮点数对其赋值可知其中发生了强制类型转换。这个类型转换的过程是隐式的。
3.3.3 局部变量
源程序中包含一个i的非静态局部变量,由于为局部变量,当其运行时,存储于栈中,如图3-4所示:

图3-4
由上图可知,i存放在-4(%rbp)中
3.3.4 赋值
源程序中含有i = 0这一赋值操作,其对应于汇编程序如图3-5所示:

图3-5
C语言的赋值对应于汇编语言中的MOV指令
3.3.5算术操作
源程序中含有i++这一算术运算,其对应于汇编程序如图3-6所示:

图3-6
C语言中的加法运算对应于汇编语言中的add指令
3.3.6关系操作
源程序中含有argc!=3这一关系操作,其对应于汇编程序如图3-7所示:

图3-7
关系操作对应于汇编语言中的cmp指令
3.3.7数组操作
源程序中还有一个指针数组: char *argv[]; 同时在源程序中获得了argv[1]和argv[2]的值,其对应的汇编程序如图3-8所示:

图3-8
C语言中的数组标识符为指向数组开头的指针,假设指针的值为x,数组元素i的值将被存放于x+i*L处,其中L为数组类型的大小。因此汇编程序中,可以直接通过内存应用指令访问数组,如图3-8所示。
3.3.8控制转移
源程序中包含了如图3-9所示的语句,发生了控制转移:

图3-9
其对应于汇编程序中如图3-10所示:

图3-10
汇编语言中通过jump指令改变指令的执行顺序实现控制转移。
3.3.9函数调用
函数调用主要通过栈来实现,如图3-11所示:

图3-11
1.参数传递:
64位程序中,参数通过寄存器和栈进行传递,如图3-12所示,整型参数传递所用的寄存器:

图3-12
当参数数目超过6个时,超出数目的参数必须通过栈来传递。
2.函数调用
函数调用主要通过call和ret指令实现,如图3-13所示:

图3-13
3.返回值
对于整型返回值函数,其返回值一般保存在%rax中,先将返回值传送至%rax中,然后进行ret指令操作,如图3-14所示:

图3-14
3.4 本章小结
本章主要介绍了编译操作,编译是将高级语言程序翻译为更底层的汇编语言的程序,本章从源程序的各种操作和数据具体说明了C语言程序到汇编程序转换的过程。

(第3章2分)

第4章 汇编
4.1 汇编的概念与作用

汇编将.s文件转换为.o文件,即汇编将汇编程序翻译为机器语言的过程。汇编过程将汇编程序翻译为机器能直接识别的机器指令。
4.2 在Ubuntu下汇编的命令
gcc –c hello.s –o hello.o

图4-1
4.3 可重定位目标elf格式

图4-2
一个典型的ELF可重定位目标文件如图4-2所示

图4-3
图4-3显示了ELF头,ELF头主要说明了系统信息以及帮助链接器语法分析和解释目标文件的信息。

图4-4
图4-4显示了ELF文件的节头的内容,描述了各个节的类型,地址等信息。

图4-5
图4-5显示了可重定位节的内容,重定位节包含了重定位的信息,其中包括offset需要被修改的应用的节偏移,symbol标识被修改引用应该指向的符号,type重定位类型,以及addend偏移调整。

图4-6
图4-6显示了.symtab节的内容,其存放在程序中定义和引用的函数和全局变量的信息。
4.4 Hello.o的结果解析
使用objdump -d -r hello.o得到图4-7所示:

图4-7
由图4-7可知,机器语言由0和1构成,其进行反汇编后的内容于汇编程序内容大致一致。
但在汇编语言中跳转指令的目的地通常由标号指明,如.L2等,在机器语言中这些标号都被替换为具体的地址。
同时在调用指令call中,汇编程序中的函数符号也被替换为以相对寻址实现的具体的地址。
4.5 本章小结
本章介绍了汇编过程,汇编是将汇编语言转换为机器语言的过程。机器语言由二进制0/1组成,本章说明了可重定位目标文件ELF以及通过反汇编说明了机器语言与汇编语言的不同。
(第4章1分)

第5章 链接
5.1 链接的概念与作用
链接是将各种代码和数据片段收集并组合成一个单一文件的过程。链接由连接器自动执行。链接主要包含两个步骤:符号解析和重定位。
5.2 在Ubuntu下链接的命令
ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/7.3.0/crtbegin.o hello.o -lc /usr/lib/gcc/x86_64-linux-gnu/7.3.0/crtend.o /usr/lib/x86_64-linux-gnu/crtn.o -z relro -o hello.out

图5-1
5.3 可执行目标文件hello的格式
一个典型的ELF可执行目标文件如图5-2所示:

图5-2

图5-3
如图5-3为ELF头,ELF头描述文件的总体格式。

图5-4
如图5-4显示了节头的内容,节头说明了节的信息,其包括类型,地址,节的地址偏移,节大小等。
5.4 hello的虚拟地址空间
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。

图5-5
可执行文件运行时的内存映像如图5-5所示。

图5-6
由图5-6可知,代码段从0x400000开始。
对应于5.3比如.interp开始于0x400200如图5-7所示:

图5-7
由图5-4可知.interp的偏移为0x200,符合图5-7
5.5 链接的重定位过程分析
(以下格式自行编排,编辑时删除)
objdump -d -r hello.out 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。

图5-8

图5-9
objdump -d -r hello.out 如图5-8所示,对比objdump –d –r hello.o如图5-9所示发现可重定位目标文件由.text开始且只含有.text,而可执行目标文件在.text前面增加了.init和.plt,同时在.text后增加了.fini。同时在.text中未链接前只含有main函数且main函数从地址0开始,链接后.text中包含其他函数且main函数地址不再是0,链接进行了重定位,所有的重定位条目都以确定为运行时的地址。
5.6 hello的执行流程
0x00007f2676c6dea0 <ld-2.27.so!_dl_start>
0x00007f5ba9f39630 <ld-2.27.so!_dl_init>
0x00007f6d3fd52ab0 <libc-2.27.so!__libc_start_main>
0x00000000004004b0 hello!puts@plt
0x00000000004004e0 hello!exit@plt
5.7 Hello的动态链接分析
GOT是一个数组,其中每个条目是8字节地址,和PLT联合使用时,GOT[0]和GOT[1]包含动态链接器在解析函数地址时会使用的信息。GOT[2]是动态链接器在ld-linux.so模块中的入口点。其余的每一个条目对应于一个被调用的函数,其地址需要在运行时被解析。每个条目都有一个相匹配的PLT条目。

图5-10
在调用_dl_start前GOT表如图5-10所示

图5-11
调用后GOT表如图5-11所示

5.8 本章小结
本章介绍了链接,链接是将各种代码和数据片段收集并组合成一个单一文件的过程,通过对比链接前后不同,说明了链接的过程
(第5章1分)

第6章 hello进程管理
6.1 进程的概念与作用
进程即为一个执行中程序的实例。进程是对处理器主存和I/O设备的抽象表示,进程提供了一种程序独占处理器和内存的假象。
6.2 简述壳Shell-bash的作用与处理流程
shell是一个交互型应用级程序,代表用户运行其他程序
shell执行一系列的读/求值步骤
读步骤读取用户的命令行,求值步骤解析命令,代表用户运行
流程:
(1)终端进程读取用户由键盘输入的命令行。
(2)分析命令行字符串,获取命令行参数,并构造传递给 execve 的 argv 向量
(3)检查第一个(首个、第 0 个)命令行参数是否是一个内置的 shell 命令,如果不是内部命令,调用 fork( )创建新进程/子进程
(4)在子进程中,用步骤 2 获取的参数,调用 execve( )执行指定程序。
(5)如果用户没要求后台运行(命令末尾没有&号)否则 shell 使用 waitpid(或 wait…) 等待作业终止后返回。
(6)如果用户要求后台运行(如果命令末尾有&号),则 shell 返回;
6.3 Hello的fork进程创建过程
Shell通过fork()函数创建一个子进程,该子进程具有相同但独立的地址空间,父进程与子进程具有相同的用户栈,相同的本地变量值,相同的堆,相同的全局变量值。fork()函数会返回两次,一次是在父进程中,他返回子进程的PID,在子进程中他返回0.
6.4 Hello的execve过程
execve函数加载并运行可执行目标文件hello,且带参数列表argv和环境变量列表envp,execve函数调用一次永不返回。在execve加载了hello之后,他调用启动代码,启动代码设置栈,并将控制传递给新程序的主函数,该主函数由如下形式的原型
int main(int argc,char **argv,char **envp);
6.5 Hello的进程执行
系统中的每个程序都运行在某个进程的上下文中。上下文由程序正确运行所需的状态组成的,这个状态包括存放在内存中的程序的代码和数据,它的栈、通用存储器的内容、程序计数器、环境变量以及打开文件描述符的集合。进程抽象为一个独立的逻辑控制流以及一个私有的地址空间。进程向每个程序提供一种假象,好像他在独占地使用处理器。处理器提供一种机制限制一个应用可以执行的指令以及他可以访问的地址空间范围。当进程运行在用户模式中,进程不允许执行特权指令,进程初始时在用户模式中。进程从用户模式变为内核模式通过中断,故障或者陷入系统调用。当内核代表用户执行系统调用时,可能会发生上下文转换。上下文转换:1.保存当前进程的上下文,2.恢复某个先前被抢占的进程被保存的上下文,3.将控制传递给这个新恢复的进程。
在hello程序中
6.6 hello的异常与信号处理
Ctrl-Z:

图6-1
如图6-1所示,Ctrl-Z停止进程,在Ctrl-Z后运行ps jobs pstree fg kill等命令:

图6-2
如图6-2,运行ps命令,显示了当前运行的进程

图6-3
如图6-3,运行jobs命令,显示了当前shell的作业状态信息。

图6-4
如图6-4运行pstree命令,以树状图显示进程间的关系。

图6-5
如图6-5,运行kill命令,发送终止信号

如图6-6,运行fg命令,提示进程终止

图6-6
Ctrl-C

图6-7
如图6-7所示,Ctrl-C终止进程
6.7本章小结
本章主要介绍了进程管理,进程概念的提出提供给程序一个独立的控制流以及私有的地址空间。进程是对处理器主存和I/O设备的抽象表示
(第6章1分)

第7章 hello的存储管理
7.1 hello的存储器地址空间
逻辑地址:是指由hello程序产生的与段相关的偏移地址部分
线性地址:是逻辑地址到物理地址变换之间的中间层。hello程序代码会产生逻辑地址,或者说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。
虚拟地址:有时我们也把逻辑地址称为虚拟地址。因为与虚拟内存空间的概念类似,逻辑地址也是与实际物理内存容量无关的。
物理地址:是指出现CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。如果启用了分页机制,那么线性地址会使用页目录和页表中的项变换成物理地址。如果没有启用分页机制,那么线性地址就直接成为物理地址了。
7.2 Intel逻辑地址到线性地址的变换-段式管理
被选中的段描述符先被送至描述符cache,每次从描述符cache中取32位段基址,与32位段内偏移量(有效地址)相加得到线性地址。

图7-1
7.3 Hello的线性地址到物理地址的变换-页式管理
线性地址空间划分:4GB=1K个子空间 * 1K个页面/子空间 * 4KB/页
页目录项和页表项格式一样,有32位(4B)

图7-2

图7-3
P:1表示页表或页在主存中;P=0表示页表或页不在主存,即缺页,此时需将页故障线性地址保存到CR2。
R/W:0表示页表或页只能读不能写;1表示可读可写。
U/S:0表示用户进程不能访问;1表示允许访问。
PWT:控制页表或页的cache写策略是全写还是回写(Write Back)。
PCD:控制页表或页能否被缓存到cache中。
A:1表示指定页表或页被访问过,初始化时OS将其清0。利用该标志,OS可清楚了解哪些页表或页正在使用,一般选择长期未用的页或近来最少使用的页调出主存。由MMU在进行地址转换时将该位置1。
D:修改位(脏位dirty bit)。页目录项中无意义,只在页表项中有意义。初始化时OS将其清0,由MMU在进行写操作的地址转换时将该位置1。
高20位是页表或页在主存中的首地址对应的页框号,即首地址的高20位。每个页表的起始位置都按4KB对齐。
7.4 TLB与四级页表支持下的VA到PA的变换

图7-4
如图7-4,MMU从虚拟地址中抽取出VPN,并且检查TLB,判断是否TLB命中,若TLB命中将缓存的PPN返回给MMU,否则MMU需要从主存中取出相应的PTE。

图7-5
如图7-5为1-3级页表条目格式,每个条目引用一个 4KB子页表:
P: 子页表在物理内存中 (1)不在 (0).
R/W: 对于所有可访问页,只读或者读写访问权限.
U/S: 对于所有可访问页,用户或超级用户 (内核)模式访问权限.
WT: 子页表的直写或写回缓存策略.
A: 引用位 (由MMU 在读或写时设置,由软件清除).
PS: 页大小为4 KB 或 4 MB (只对第一层PTE定义).
Page table physical base address: 子页表的物理基地址的最高40位 (强制页表 4KB 对齐)
XD: 能/不能从这个PTE可访问的所有页中取指令.

图7-6
如图7-6为第四级页表条目的格式,每个条目引用一个 4KB子页表:
P: 子页表在物理内存中 (1)不在 (0).
R/W: 对于所有可访问页,只读或者读写访问权限.
U/S: 对于所有可访问页,用户或超级用户 (内核)模式访问权限.
WT: 子页表的直写或写回缓存策略.
A:引用位 (由MMU 在读或写时设置,由软件清除).
D: 修改位 (由MMU 在读和写时设置,由软件清除)
Page table physical base address: 子页表的物理基地址的最高40位 (强制页表 4KB 对齐)
XD: 能/不能从这个PTE可访问的所有页中取指令。

图7-5
7.5 三级Cache支持下的物理内存访问

图7-6
如图7-6得到物理地址后,将物理地址抽取出CO,CI,CT,检测缓存是否命中。
7.6 hello进程fork时的内存映射
fork函数为新进程创建虚拟内存时需:
1.创建当前进程的的mm_struct, vm_area_struct和页表的原样副本.
2.两个进程中的每个页面都标记为只读
3.两个进程中的每个区域结构(vm_area_struct)都标记为私有的写时复制(COW)
在新进程中返回时,新进程拥有与调用fork进程相同的虚拟内存
随后的写操作通过写时复制机制创建新页面
7.7 hello进程execve时的内存映射
1.删除已存在的用户区域
2.创建新的区域结构
3.共享对象由动态链接映射到本进程共享区域
4.设置PC,指向代码区域的入口点
7.8 缺页故障与缺页中断处理
假设MMU在试图翻译某个虚拟地址A时,触发了一个缺页。这个异常导致控制转移到内核的缺页处理程序,该程序执行以下步骤:

  1. 判断虚拟地址A是否合法,如果A不在某个区域结构定义的区域内,触发段错误
  2. 判断试图运行的内存访问是否合法,例如试图对代码段的只读页面进行写操作,触发保护异常
  3. 由此,该内存地址合法,选择一个牺牲页面,如果该牺牲页面被修改过,将他交换出去,换入新的页面并更新页表。当缺页处理程序返回时,CPU重新启动引起缺页的指令,这条指令将再次发送A到MMU。

图7-7
如图7-7描述了以上过程。
7.9动态存储分配管理

图7-8
在程序运行时程序员使用动态内存分配器 (比如 malloc) 如图7-8获得虚拟内存。如图7-9,动态内存分配器维护者一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块(blocks)的集合来维护,每个块要么是已分配的,要么是空闲的。

图7-9
分配器类型包括:
显式分配器: 要求应用显式地释放任何已分配的快
例如,C语言中的 malloc 和 free
隐式分配器: 应用检测到已分配块不再被程序所使用,就释放这个块
比如Java,ML和Lisp等高级语言中的垃圾收集 (garbage collection)
7.10本章小结
本章描述了存储管理相关的内容,阐述了地址空间,物理地址,虚拟地址,线性地址,逻辑地址的概念。以Intel I7处理器为例,说明了线性地址到物理地址的变换,VA到PA的变换。同时说明了一个进程的地址空间,Linux的缺页处理以及说明fork()与execve()的内存映射。

(第7章 2分)

第8章 hello的IO管理
8.1 Linux的IO设备管理方法
设备的模型化:文件 所有的I/O设备(例如网络、磁盘、终端)都被模型华为文件,所有的输入和输出都被当作对应文件的读和写来执行。
设备管理:unix io接口 这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种笼统且一致的方式来执行。
8.2 简述Unix IO接口及其函数
将设备优雅地映射为文件的方式,允许Linux内核引出一个简单、低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种笼统且一致的方式来执行:
打开和关闭文件:
open()and close()
读写文件:
read() and write()
改变当前的文件位置:
lseek()
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函数:
int vsprintf(char *buf, const char fmt, va_list args)
{
char
p;
char tmp[256];
va_list p_next_arg = args;

for (p=buf;*fmt;fmt++) { 
if (*fmt != '%') { 
*p++ = *fmt; 
continue; 
} 

fmt++; 

switch (*fmt) { 
case 'x': 
itoa(tmp, *((int*)p_next_arg)); 
strcpy(p, tmp); 
p_next_arg += 4; 
p += strlen(tmp); 
break; 
case 's': 
break; 
default: 
break; 
} 
} 

return (p - buf); 

}
vsprintf的作用就是格式化。它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。
write函数:把buf中的i个元素的值写到终端。
printf函数运行过程为:
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
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;
}
从getchar函数体我们看出当n=0时,getchar调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区
8.5本章小结
本章主要内容为IO管理,所有的I/O设备(例如网络、磁盘、终端)都被模型华为文件,所有的输入和输出都被当作对应文件的读和写来执行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值