HITICS-2019大作业报告

**

HITICS-2019大作业报告

**

摘 要

摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息。摘要应包括本论文的目的、主要内容、方法、成果及其理论与实际意义。摘要中不宜使用公式、结构式、图表和非公知公用的符号与术语,不标注引用文献编号,同时避免将摘要写成目录式的内容介绍。

关键词:预处理,编译,汇编,链接,进程管理,存储管理,I/O管理
本文通过介绍hello.c这个源代码如何经过预处理,编译,汇编和链接形成一个可执行文件的,以及这个可执行文件如何经过linux中的shell的命令行来运行它,它的运行所需要的进程的思想,虚拟内存的思想,高速缓存的思想,以及相应的数据结构来阐释计算机系统的原理以及其执行机制。Hello,一个简单而又复杂的一生,揭开了程序底层运行的机制。

(摘要0分,缺失-1分,根据内容精彩称都酌情加分0-1分)

目录

第1章 概述 - 4 -
1.1 HELLO简介 - 4 -
1.2 环境与工具 - 4 -
1.3 中间结果 - 4 -
1.4 本章小结 - 4 -
第2章 预处理 - 5 -
2.1 预处理的概念与作用 - 5 -
2.2在UBUNTU下预处理的命令 - 5 -
2.3 HELLO的预处理结果解析 - 5 -
2.4 本章小结 - 5 -
第3章 编译 - 6 -
3.1 编译的概念与作用 - 6 -
3.2 在UBUNTU下编译的命令 - 6 -
3.3 HELLO的编译结果解析 - 6 -
3.4 本章小结 - 6 -
第4章 汇编 - 7 -
4.1 汇编的概念与作用 - 7 -
4.2 在UBUNTU下汇编的命令 - 7 -
4.3 可重定位目标ELF格式 - 7 -
4.4 HELLO.O的结果解析 - 7 -
4.5 本章小结 - 7 -
第5章 链接 - 8 -
5.1 链接的概念与作用 - 8 -
5.2 在UBUNTU下链接的命令 - 8 -
5.3 可执行目标文件HELLO的格式 - 8 -
5.4 HELLO的虚拟地址空间 - 8 -
5.5 链接的重定位过程分析 - 8 -
5.6 HELLO的执行流程 - 8 -
5.7 HELLO的动态链接分析 - 8 -
5.8 本章小结 - 9 -
第6章 HELLO进程管理 - 10 -
6.1 进程的概念与作用 - 10 -
6.2 简述壳SHELL-BASH的作用与处理流程 - 10 -
6.3 HELLO的FORK进程创建过程 - 10 -
6.4 HELLO的EXECVE过程 - 10 -
6.5 HELLO的进程执行 - 10 -
6.6 HELLO的异常与信号处理 - 10 -
6.7本章小结 - 10 -
第7章 HELLO的存储管理 - 11 -
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 -
7.9动态存储分配管理 - 11 -
7.10本章小结 - 12 -
第8章 HELLO的IO管理 - 13 -
8.1 LINUX的IO设备管理方法 - 13 -
8.2 简述UNIX IO接口及其函数 - 13 -
8.3 PRINTF的实现分析 - 13 -
8.4 GETCHAR的实现分析 - 13 -
8.5本章小结 - 13 -
结论 - 14 -
附件 - 15 -
参考文献 - 16 -

第1章 概述

1.1 Hello简介

根据Hello的自白,利用计算机系统的术语,简述Hello的P2P,020的整个过程。
P2P:在编写完一个hello.c的一个高级语言程序后,经过预处理器(cpp)将头文件中的内容插入到文本中得到hello.i的ascii码文件。经过编译器(cc1)将文件编译成由源代码翻译的汇编代码的hello.s的ascii码文件。经过汇编器(as)将hello.s翻译成机器语言指令(可重定位目标程序)的二进制文件。经过链接器(ld)将多个可重定位目标文件合并,得到可执行目标文件(hello)。(其中可重定位目标文件可能是共享库中的文件)。在命令行中执行此文件,shell判定这个不是内置命令,所以就为其fork一个子进程,于是hello就变成了一个process。至此,P2P结束。
图1-1-1 编译系统

020:在子进程中shell为其execve调用加载器,删除子进程现有的虚拟内存段并创建一组新的代码段,数据段,堆和栈。其中堆和栈都初始化为0。加载器跳转到_start地址,最终会调用应用程序的main函数(加载过程中没有任何从磁盘到内存的数据复制,直至cpu引用一个被映射的虚拟页才进行复制)。当进程执行完后,shell父进程负责回收hello进程,然后内核删除hello相应的数据结构。至此,020结束。

1.2 环境与工具

列出你为编写本论文,折腾Hello的整个过程中,使用的软硬件环境,以及开发与调试工具。
硬件环境:Intel Core i7-6700HQ x86 CPU, 16G RAM, 128GSSD+1TB HDD
软件环境:Ubuntu 18.04.1 LTS
开发与调试工具:vim, gcc, as, ld, edb, readelf, HexEdit

1.3 中间结果

Hello.i 预处理之后的ascii码程序
Hello.s 编译之后的汇编程序
Hello.o 汇编之后的可重定位目标程序(二进制程序)
Hello.elf hello.o的ELF格式
Helloexe.elf hello的ELF格式
Hello 可执行目标程序
Hello.s1 反汇编后输出的程序
Helloobj.txt hello可执行程序的反汇编代码
Temp.c 存放临时数据

1.4 本章小结

本章总体介绍了P2P和020的过程,通过预处理,编译,汇编,链接,fork的过程的总体概括,介绍了一个代码如何转化成一个进程。通过execve,创建虚拟地址空间,执行目标文件,回收进程介绍了如何将一个文件执行并且回收的机制。
(第1章0.5分)

第2章 预处理

2.1 预处理的概念与作用

概念:预处理器(cpp)根据以字符#开头的命令,修改原式的C程序将头文件的内容直接插入到程序文本中,得到i文件。
作用:将include中的所有头文件内容直接插入到程序文本中
用实际值代替define中的字符串
根据#if后面的条件决定需要编译的代码

2.2在Ubuntu下预处理的命令

gcc –E –o hello.i hello.c

图2-2-1 预处理的linux指令
图2-2-2 生成hello.i的展示
图2-2-3 hello.i内容的部分展示

2.3 Hello的预处理结果解析

因为hello.c的头文件中可能包含其他头文件,因此系统会递归式的寻址和展开。直到所有的符号和函数均已被解释。其次所有的宏定义均已被解释成实际的数值,因此hello.i中不包含任何的宏定义。
删除所有的注释。添加行号和文件标识。

2.4 本章小结

源代码起初的预处理将:
1.所有的头文件的内容插入到文本中。
2.所有的宏定义转化为具体的值。
生成一个ascii码的文本文件。完备了程序需要的函数和解释了宏的必要步骤。
(第2章0.5分)

第3章 编译

3.1 编译的概念与作用

概念:将文本文件hello.i翻译成文本文件hello.s(是一个汇编语言程序)
作用:将属于高级语言的源代码翻译成低级语言的汇编代码,生成汇编代码的ascii码文件。
注意:这儿的编译是指从 .i 到 .s 即预处理后的文件到生成汇编语言程序

3.2 在Ubuntu下编译的命令

gcc –S hello.i –o hello.s
(以下格式自行编排,编辑时删除)
应截图,展示编译过程!
图3-2-1 编译的命令行表示

如何改变文本的样式

图3-2-2 生成文件hello.s展示
图3-2-3 hello.s汇编代码部分展示

3.3 Hello的编译结果解析

3.3.1

汇编伪指令:
.file 显示源文件的名称
.text 声明代码段的起始点
.global 声明一个全局变量sleepsecs,是一个宏
.data 声明数据段的起始点
.align 声明所有数据都以4字节对齐
.type 指明数据的类型或函数的类型
.size 声明一个定义的宏的具体数值大小
.long 声明一个long型的变量
.section 声明只读数据代码段的起始点
.string 声明一个字符串变量

3.3.2

整数:
1.sleepsecs:在源文件中定义的一个宏,被赋值成4。因为是全局变量,所以存储在数据段。编译器在代码段声明该变量。并且在数据段初始化sleepsecs的内容,声明4字节对齐,大小为4。声明其值为2。
图3-3-1 sleepsecs的定义及初始化
2.汇编代码中的整数:
其中包括立即数,以及地址的偏移量都需要整数的帮助。

图3-3-2 汇编代码中的立即数和偏移量
3.main函数的参数argc。
用来指明有多少个参数传给main。

字符串:

1.没有参数要输入的输出字符串:hello \345…… 字符串中的汉字被编码成了UTF-8格式了。一个汉字占3个字节,一个\代表一个字节。
2.有参数输入的输出字符串:有两个%s说明要我们输入两个字符串来构成这个printf的输出字符串。
	图3-3-3 代码段中的字符串定义

算数操作:

1.减法运算:这里将%rsp中寄存器的值减去立即数32(即将栈的顶部下降4个字节)。
2.加法运算:这里将%rax的中的值+8。以及将内存中地址为%rbp-4的4字节数据+1。
图3-3-4 代码中的减法运算和加法运算

赋值运算:

图中所示,分别指将%edi寄存器中的4字节数值赋值给内存中地址为%rbp-20的位置,以及将寄存器%rsi中的8字节的数值赋值给内存中地址为%rbp-32的位置。(即将这两个值放到新拓展的栈中)。
3-3-5 代码中的部分赋值运算

关系操作:

图中所示,是将立即数3和内存中地址为%rbp-20的位置的数值进行比较。(即判断给main传入的参数是不是3个)。如果相等的话就跳到代码中的L2处,如果不等的话就输出后就exit(1)。
将9和内存中的计数器进行比较,如果小于等于9就跳转到L4,这样就能够完成10次循环。
图3-3-6 代码中的比较(cmpl)部分

数组:

main函数的第二个参数就是一个字符串数组char *argv[]。根据argc,一共有三个字符串。执行输入的命令行时,main是第一个参数。
Argv指向已经分配好的,一片存放着字符指针的连续区域,起始地址为argv。其中图示中的%rax+16即是argv[1]的地址。%rax+24即是argv[2]的地址。然后将第一个参数传到寄存器%rdx中,将第二个参数传到%rax中。然后将sleepsecs这个变量传递给%edi,调用sleep函数。这样靠着每次都+1并且与9比较循环10次。
图3-3-7 求取argv数组中字符串的值

控制转移:

这里如果内存地址为%rbp-4的值小于等于9的话,就跳转到.L4,满足了循环10次的条件。
图3-3-8 条件跳转

函数操作:

函数调用是一个过程。需要进行传递控制,传递数据,分配和释放内存。
在执行call之前,需要先将call的下一条指令的地址压栈。然后将部分参数放到传参寄存器(%rdi, %rsi, %rdx, %rcx, %r8, %r9)中,然后将控制传递给被调用的函数,执行被调用函数的代码。如果正常返回的话,就将%rax赋值为0,如果不正常返回就为其他值。在一个函数开始的时候,会先设置寄存器%rbp为当前的栈的地址,所以地址为%rbp-8为返回地址。在被调用函数完成运行后,会将被调用函数的所有压栈的数据都弹出,然后将call后面的返回地址弹出到%rip中。将控制传递给调用函数。就这样一个函数调用完成。
1.main函数:main函数被__libc_start_main函数调用。是程序的主函数,即在主程序中最先执行main函数,其中传入的参数1.有计算有多少参数传到此函数的argc。2.一个或多个字符串数组的指针。3.以及环境参量。
2.puts函数:首先将hello, 学号,姓名的字符串地址传递给传参寄存器%rdi中,然后调用puts函数,将其输出。
3.exit函数:将立即数1传递给传参寄存器%rdi中,然后调用exit函数。说明此程序是以非正常的形式结束的。
4.printf函数:将基本的字符串赋值给%rdi寄存器,argv[1]赋值给%rsi寄存器,argv[2]赋值给%rdx寄存器。然后调用printf函数。这个是循环函数中的10次输出的一个。
5.sleep函数:首先将事先定义好的宏sleepsecs传递给寄存器%rdi,然后调用sleep函数。将程序拖延sleepsecs这么长的时间。
6.getchar函数:调用getchar后,等待从键盘输入一个字符,然后结束调用。

图3-3-9 hello程序中的几个函数
图3-3-9 hello程序中的几个函数
图3-3-9 hello程序中的几个函数
图3-3-9 hello程序中的几个函数
图3-3-9 hello程序中的几个函数

类型转换:

原本的sleepsecs是long的类型,后来被类型转换成了int型。

图3-3-10 sleepsecs类型的强制转换
此部分是重点,说明编译器是怎么处理C语言的各个数据类型以及各类操作的。应分3.3.1~ 3.3.x等按照类型和操作进行分析,只要hello.s中出现的属于大作业PPT中P4给出的参考C数据与操作,都应解析。

3.4 本章小结

本章说明了编译器的工作机制,说明其是如何将C类型的各种数据类型和各种操作转化成低级语言指令的。
这样就能让不同的高级语言转化成通用的低级语言程序。
编译器将hello.i转化成hello.s这样一个更为低级的汇编语言。

(第3章2分)

第4章 汇编

4.1 汇编的概念与作用

概念:汇编器(as)将低级汇编语言程序转化成机器语言指令(即二进制指令)。并把这些指令打包成可重定位目标程序,并将结果保存在hello.o中。Hello.o是一个二进制文件。
作用:将低级汇编语言的ascii的hello.s程序转化成计算机认识的机器语言指令,并且打包成可重定位目标程序。

注意:这儿的汇编是指从 .s 到 .o 即编译后的文件到生成机器语言二进制程序的过程。

4.2 在Ubuntu下汇编的命令

Gcc –c –o hello.o hello.s
应截图,展示汇编过程!

图4-2-1 将.s文件转化成.o文件的命令展示

4.3 可重定位目标elf格式

用命令行 readelf –a hello.o来显示hello.o的所有基本信息
1.ELF头:ELF头以一个16字节的序列开始,magic描述了生成该文件的字的大小和字节顺序。ELF头剩下的部分帮助链接器语法分析和解释目标文件的信息。包括ELF头的大小,机器类型,目标文件的类型,机器类型,字节头部表的文件偏移,以及节头部表的条目大小和数量。
ELF64说明这个是64位ELF的格式。2’s complement和little endian说明这个文件是2进制小端法的文件。REL说明这个文件是可重定位目标文件。1152为从ELF头到段头部表的字节偏移。64说明了这个头部大小为64字节。下一个64说明段头部表的大小也是64字节。13位段头部表的数组的数量为13个,然后其索引一共到达12个。

图4-3-1 hello.o的ELF头
2.段头部表:传达了各个段距离段头部表的偏移量,还有每个节的大小和类型。因为还没有进行重定位,所以这个段头部表并不能显示完整的信息。
图4-3-2 hello.o的段头部表
3.重定位节(rela.text)
这个节中包含当hello.o文件要与其他文件进行链接的时候,哪些代码需要被重定位。分别对.data,puts,exit,.rodata,printf,sleepsces,sleep,getchar进行重定位声明。
Offset:需要重定位的代码在.text或.data中的偏移位置,8个字节。
Info:包含symblo和type两个部分。分别是在symtab中的偏移量和重定位的类型。
Addend:计算重定位的辅助信息。
Name:重定位到目标的名称。
重定位公式:
Refaddr = ADDR(s)+r.offset
*refptr = ADDR(r.symbol)+r.addend-refaddr完成重定位。
图4-3-3 hello.o的重定位节
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
4…rela.eh_frame:eh_frame的重定位信息
5.符号表:存放程序中定义和引用的函数和全局变量的信息。也有一些静态变量。
图4-3-4 hello.o的rela.eh_frame和符号表

4.4 Hello.o的结果解析

1.全局变量的访问:在反汇编文件中,访问全局变量的格式是0+%rip,这是因为rodata的数据地址也是在运行时确定,所以访问也需要重定位,所以在转化成机器语言后,将操作数全设置成0并设置为重定位条目。而在.s文件中,全局变量时靠段名称+%rip来引用。
2.函数的引用:在.s文件中,函数是直接用函数的名称进行调用的,而在反汇编文件中,call的目标地址是下一条指令的地址。因为文件中调用的函数都是共享库的函数,要通过动态链接器才能确定函数放置的地址。在转化成机器语言的时候,因为这些函数的地址是不确定的,所以直接设置成0,添加重定位条目,然后在加载或运行时进行动态链接。
3.分支转移:.s文件中的跳转是使用的短名称,而.o文件中是用确定的地址进行跳转的。
objdump -d -r hello.o 分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。

4.5 本章小结

本章说明了从hello.s转化成二进制可重定位目标文件hello.o的过程。以及作用和为后面链接做得重定位的准备。
通过将.s和.o的代码的对比,发现两者的不同在于对不确定地址的引用。.s文件直接用名称引用,而.o文件要用精确的地址进行引用,不确定的地址暂时填为0,并添加到重定位条目中,等待静态和动态链接器的调节。

(第4章1分)

第5章 链接

5.1 链接的概念与作用

概念:由于hello.o调用了很多动态库函数(例如printf, puts, getchar等)必须将这些函数的预编译好的目标文件以某种方式合并到我们的hello.o中,链接器将其合并成hello文件(可执行目标文件)。
作用:将可重定位目标文件hello.o和其他需要的库函数合并成一个可执行目标文件hello,可以被加载到内存,由系统执行。
注意:这儿的链接是指从 hello.o 到hello生成过程。

5.2 在Ubuntu下链接的命令

Ld –o hello –dynamic-linker /lib/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o hello.o /usr/lib/x86_64-linux-gnu/lib.so /usr/lib/x86-64-linux-gnu/crtn.o
使用ld的链接命令,应截图,展示汇编过程! 注意不只连接hello.o文件
图5-2-1 用ld生成hello的命令行和效果
图5-2-1 用ld生成hello的命令行和效果

5.3 可执行目标文件hello的格式

用readelf –a hello 命令行查看hello的全部信息。
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
在可执行文件中,段头部表对所有的节都进行了声明,包括偏移量offset和大小size。Address是程序被载入到虚拟内存的起始地址。这个可执行目标文件包括了24个文件。

图5-3-1 readelf效果预览
5-3-2 段头部表的部分预览

5.4 hello的虚拟地址空间

使用edb打开hello程序后,可以通过data dump来看加载到虚拟地址的hello程序。在0x400000~0x401000内存段,程序载入到主存中。
再次通过readelf查看hello程序的段头部表,程序头表在执行时被使用,告诉链接器运行时加载的内容并提供动态链接的信息,8个段每个都给出的他的虚拟地址和物理地址。
该程序共包含8个段:
1.PHDR:保存程序头表。
2.INTERP:指定在程序加载到内存后,必须调用的解释器。
3.LOAD:表示需要从二进制文件映射到虚拟地址空间的段。其中保存了常量数据,程序的目标代码等。
4.DYNAMIC:保存了由动态链接器保存的信息。
5.NOTE:保存辅助信息。
6.GNU_STACK:权限标志,标志栈是否可执行。
7.GNU_RELRO:指定在重定位结束之后内存区域是否设置为只读。
通过data dump查看虚拟地址段0x600000x602000,在0fff区域与0x400000存放的程序相同。

图5-4-1 hello程序的头部表
使用edb加载hello,查看本进程的虚拟地址空间各段信息,并与5.3对照分析说明。

5.5 链接的重定位过程分析

1.函数个数:在使用ld命令连接的时候,指定了动态链接器为64为主定义了程序入口函数_start,初始化函数_init,_start调用hello.c中的main函数,lib.so是动态链接共享库,其中定义了hello.c用到的printf,sleep,getchar,exit函数和_start调用的__lib_csu_init和__lib_csu_fini和__lib_start_main。链接器将上述函数加入。
2.函数调用:链接器解析重定位条目时发现对外部函数调用的类型为R_X86_64_PLT32的重定位,此时动态链接库的函数已经加入到PLT中了,.text和.plt的相对距离已经确定,链接器计算相对距离,将对动态链接库函数的调用值改为PLT中相应的函数与下条指令相对地址,指向对应函数。
3…rodata引用:链接器解析重定位条目时发现有几个对.rodata的重定位,rodata和text的相对距离已经确定,因此链接器直接修改call之后的指令的地址与调用数据的相对距离,得到全局变量的引用。
objdump -d -r hello 分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
图5-5-1 hello反汇编文件

5.6 hello的执行流程

程序名称 程序地址
Ld-2.27.so!_dl_start 0x7fce8cc38ea0
Ld-2.27.so!_dl_init 0x7fce8cc47630
Hello!_start 0x400500
Libc-2.27.so!__lib_start_main 0x7fce8c867ab0
-lib-2.27.so!__cxa_atexit 0x7fce8c889430
-libc-2.27.so!__libc_csu_init 0x4005c0
Hello!_init 0x400488
Hello!main 0x400532
Hello!puts@plt 0x4004b0
Hello!exit@plt 0x4004e0
*hello!printf@plt
*hello!sleep@plt
*hello!getchar@plt
Ld-2.27.so!_dl_runtime_resolve_xsave 0x7fce8cc4e680
Ld-2.27.so!_dl_fixup 0x7fce8cc46df0
–ld-2.27.so!_dl_lookup_symbol_x 0x7fce8cc420b0
Libc-2.27.so!exit 0x7fce8c889128
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。

5.7 Hello的动态链接分析

编译器没有办法预测动态共享链接库中PIC函数的运行时地址,所以需要添加重定位记录,等待动态链接器处理,链接器采用延迟绑定的策略,以避免运行时修改调用模块的代码段。动态链接器使用工程链接表PLT和全局偏移量表GOT实现函数的动态链接,GOT中存放函数的目标地址,PLT使用GOT中地址跳转到目标函数。
在dl_init调用之前,对于每一条PIC函数的调用,调用的目标地址都实际指向PLT中的代码逻辑,GOT存放的是PT中函数调用指令的下一条指令地址。
图5-7-1.执行dl_init函数前的GOT
在调用dl_init之后,0x601008和0x601010处的两个8B数据分别发生改变为0x7fd9d3925170和0x7fd9d3913680,其中GOT[1]指向重定位表,用来确定调用的函数地址,GOT[2]指向动态链接器ld-linux.so运行时地址。
图5-7-2 执行dl_init函数后的GOT
在之后的函数调用时,先跳转到PLT执行的.plt中逻辑,第一次访问跳转时GOT为下一条指令,将函数序号压栈,然后跳转到PLT[0],在PLT[0]中将重定位表地址压栈 ,然后访问动态链接器,在动态链接器中使用函数序号和重定位表确定函数运行时地址,重写GOT,再将控制传递给目标函数,如果之后对同样函数调用,第一次访问就直接跳转到目标函数。
分析hello程序的动态链接项目,通过edb调试,分析在dl_init前后,这些项目的内容变化。要截图标识说明。

5.8 本章小结

本章主要介绍了链接使一个可重定位文件链接上其需要的函数之后变成了一个可执行目标文件,此外,还分析了hello的ELF内容的组成,hello的虚拟地址空间,位置无关代码的第一次引用和之后引用的区别。就这样,一个可执行文件hello就诞生了
(第5章1分)

第6章 hello进程管理

6.1 进程的概念与作用

概念:即一个执行中程序的实例。系统中的每个进程都运行在某个进程的上下文中。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中程序的代码,数据,栈,通用寄存器的内容,程序计数器以及环境变量。
作用:进程为用户提供了假象
1.程序是系统当中唯一运行的程序,且独占CPU和内存。
2.处理器是不间断执行程序中的指令。

6.2 简述壳Shell-bash的作用与处理流程

作用:shell是一个用编程语言编写的程序,是linux系统和用户之间沟通所需的必要工具,它提供了一个界面,用户通过这个界面访问操作系统和运行自己的目标文件。
处理流程:
1.从命令行中读取命令。
2.将命令行分成许多参数。
3.判断命令是不是内置命令,如果是内置命令直接调用相关的程序执行。
4.如果不是内置命令,则fork一个子进程并运行,调用execve来引用加载器来完成目标文件。
5.在执行完后,控制权还给shell,等待用户的下一条命令。

6.3 Hello的fork进程创建过程

在命令行中输入./hello。Hello不是一个shell的内置命令,所以调用子进程来完成这个可执行目标文件。Fork函数在父进程返回子进程的pid,在子进程中返回0.新创建的子进程与父进程几乎完全相同,且虚拟地址空间是父进程的一个副本,所以子进程可以读写父进程中打开的任何文件,他们拥有不同的pid。父进程与子进程是并发的进程,内核通过上下文切换来执行他们两个中的其中一个,当父进程完成之后,会等待子进程的完成,将子进程回收,并将其在内核中的上下文删除。

6.4 Hello的execve过程

当在父进程fork一个子进程后,子进程将调用execve函数,并且把命令行参数传递给execve。然后将子进程原来的虚拟内存内容删除,为execve创建一个新的虚拟内存。Execve调用加载器的操作系统代码来执行hello程序,新的栈和堆被初始化为0.通过将虚拟地址空间中的页映射到可执行文件的页大小的片,新的代码和数据段被初始化为可执行文件中的内容,最后加载器设置PC指向函数_start地址,_start调用主函数main。(加载过程中没有任何数据从磁盘传输到内存,直到CPU引用一个被映射的虚拟页时才会传输)。

6.5 Hello的进程执行

上下文信息:内核为每一个进程维护一个上下文,上下文就是内核重新启动一个被抢占的进程所需的状态(通用目的寄存器,浮点寄存器,程序寄存器,用户栈,状态寄存器,内核栈和各种内核数据结构)
进程时间片:一个进程执行它的控制流的一部分的每一时间段。
用户态和内核态:处理器通常使用一个寄存器提供两种模式的区分,该寄存器描述进程当前的权利,当没有设置模式位时,进程处于用户模式中,不允许执行内核指令。当处于内核状态时,可以执行指令集中的任何命令。
进程的调度(hello):在调用sleep之前,hello被当做一个进程,和其他进程并发地运行。当内核进行上下文切换选择执行hello进程时,内核就会切换成hello的上下文,开始执行hello。如果在执行hello的过程中内核进行上下文切换的话,就保存hello的上下文,恢复原进程被保存的上下文,并将控制传递给原进程。这样就完成了上下文切换。
在调用了sleep之后陷入内核模式,内核被请求主动释放当前进程,并将hello进程从运行队列中移除加入等待序列。定时器开始计时,等待2.5s之后发送一个中断信号,此时进入内核状态处理中断处理,将hello进程从等待队列中移除加入运行队列,成为就绪状态,hello就可以继续执行了。
在调用了getchar函数后,就会在stdin系统实行read,在进行read调用之后陷入内核,内核中的陷阱处理程序请求来自键盘的DMA传输,并且安排在完成传输后中断处理器,再次进入内核状态,内核执行上下文切换,切换到其他进程,当完成输入时,引发一个中断信号,此时内核从其他进程进行上下文切换回hello
图6-5-1 进程的上下文切换
结合进程上下文信息、进程时间片,阐述进程调度的过程,用户态与核心态转换等等。

6.6 hello的异常与信号处理

1.当hello正常执行时,进程完成之后,进程被回收,等待下一个shell的命令。

图6-6-1 正常运行的hello程序
2.如果在hello程序运行时按下ctrl-c,父进程收到SIGINT信号,信号处理函数的逻辑是结束hello,并回收hello进程。
图6-6-2 执行ctrl-c的hello程序
3.如果在hello运行的过程中执行ctrl-z,父进程收到SIGSTP信号,信号处理函数的逻辑是将信息打印到屏幕上,然后将hello进程挂起,通过ps命令可以看到hello进程没有被回收,此时job号为1,调用fg 1使其在前台继续执行,直到程序结束,父进程回收子进程。
图6-6-3 执行ctrl-z的hello程序
图6-6-3 执行ctrl-z的hello程序
4.如果不停乱按的话,乱按的字符会到输入缓存的stdin中,当getchar的时候读出一个回车符结尾的字符串,其他字符串会当做shell命令行输入。
图6-6-4 在执行的情况下不断乱按的情况
hello执行过程中会出现哪几类异常,会产生哪些信号,又怎么处理的。
程序运行过程中可以按键盘,如不停乱按,包括回车,Ctrl-Z,Ctrl-C等,Ctrl-z后可以运行ps jobs pstree fg kill 等命令,请分别给出各命令及运行结截屏,说明异常与信号的处理。

6.7本章小结

程序shell为系统与用户之间搭建了沟通的桥梁,shell为了hello fork并且execve,分配时间片,linux依靠进程的上下文切换有条不紊的并发地执行各种各样的进程(即程序)。并且实验证明了在进程运行的过程中如果遇到意外的情况,系统的意外处理程序会采取各种各样的动作以维持系统的运行。
(第6章1分)

第7章 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:由一个段标识符加上一个指定段内相对地址的偏移量,表示为【段标识符;段内偏移量】。
线性地址:某地址空间中的地址是连续的非负整数时,该地址空间中的地址被成为线性地址。
虚拟地址:CPU在寻址的时候,系统为每个进程都安排了一个虚拟地址空间,其中每个地址空间都对应一个主存的大小,通过CPU的MMU将虚拟地址转化成主存中实际的地址来寻找想要找到的元素。
物理地址:即计算机中主存的大小和索引,每个字节都有唯一的地址。
结合hello说明逻辑地址、线性地址、虚拟地址、物理地址的概念。

7.2 Intel逻辑地址到线性地址的变换-段式管理

一个段描述符由8个字节组成,分成了GDT和LDT两类,段描述符描述了段的特征。一般系统只定义一个GDT。IA-32中引入了GDTR和LDTR两个寄存器,用来存放当前正在使用的GDT和LDT的首地址。在linux系统中,每个CPU对应一个GDT。一个GDT中有18个段描述和14个未使用的保留项。
段地址的转换过程
1.IA-32首选确定要访问的段,然后决定使用的段寄存器。
2.根据段选择符号的TI字段决定是访问GDT和LDT,他们的首地址则通过GTDR和LDTR来获得。
3.将段选择符的Index字段的值*8,然后加上GDT或LDT的首地址,就能得到当前段描述符的地址。
4.得到段描述符地址后,可以通过段描述符中BASE获得段的首地址。
5.将逻辑地址中32位的偏移地址和段首地址相加就可以得到实际要访问的物理地址。

7.3 Hello的线性地址到物理地址的变换-页式管理

Linux下的虚拟地址VA属于线性地址的一种。
一级页表中的每个PTE映射虚拟地址空间中的4MB的片,由1024个连续页面组成,以此类推还有二级页表,三级页表等。。。最后找到PPN后,将PPN和VPO结合,得到了物理地址。

7.4 TLB与四级页表支持下的VA到PA的变换

CPU想要得到一个VA的数据,先将VA传给内存管理单元(MMU),然后MMU在TLB中寻找相应的VPN,如果匹配上的话,就hit,将得到的PPN和VPO结合就是想要的物理地址,然后再将PA传递给主存的高速缓存中找到相应的地址的数据,传递给CPU就结束了。
如果在TLB中没有找到相应的PPN的话,就会将VA分为4个VPN和1个VPO。每一个级数的VPN都是在相应级数的页表的索引,就这样在每一级中寻找相应的PPN,再将其组合起来,就得到了PA,接下来和HIT后的操作一样。

7.5 三级Cache支持下的物理内存访问

在MMU得到了一个PA之后,将其传递给高速缓存cache中。并且按照cache的大小来将PA进行划分,最右边是CO,中间是CI,左边是CT首先在最高级的cache下寻找有没有匹配到的,如果有,直接将数据返回给CPU。
如果没有命中的话,一次在L2,L3,主存中寻址。直到把相应的数据找到并返回给CPU。

7.6 hello进程fork时的内存映射

当调用fork函数之后,内核为子进程创建一个虚拟内存空间,并分配给它唯一一个PID。其内容和父进程的相同。即映射到相同物理地址上,当子进程想要改数据时,系统就会采用写时复制的机制,这样就保持了每个进程都独立的虚拟地址。

7.7 hello进程execve时的内存映射

当进程执行execve时,此函数在当前程序中加载并运行可执行目标文件hello。用hello的虚拟地址空间代替子进程的虚拟地址空间。然后映射私有区域,为新程序的代码和数据创建堆和栈,所有这些都是私有的,写时复制的。然后开始复制共享区域,即与共享库的链接。最后设置程序计数器PC,使其指向hello代码执行的入口点。
图7-7-1 虚拟内存空间

7.8 缺页故障与缺页中断处理

缺页故障:当MMU在TLB中找不到相应的页表项,或在cache中找不到相应的内存地址对应的数据,就会发生缺页故障。
缺页中断处理:当缺页异常发生时,处理器会调用相应的缺页处理程序。缺页处理程序从相应的磁盘加载,然后再次执行这个取指令的命令,这次就不会发生缺页故障了。
图7-8-1 缺页处理程序的执行

7.9动态存储分配管理

动态内存管理每一个进程的堆,动态内存分配器将其看做不同大小的块来执行,块是分配或者空闲的。一个已分配块保持已分配的状态,直到它被释放。
分配器有两种类型:
1.显式分配器:要求应用显式地释放任何已分配的块。
2.隐式分配器:要求分配器检测一个已分配块何时不再被程序使用,就释放这个块。也叫做垃圾收集器
在C语言中采用的是显式分配器。
其中显式分配有两种方法
1.隐式空闲链表:
将每一块的头部和尾部都设置一个4字节的指明块大小的和块是否分配的状态。其次还有要填充的块以满足对齐要求

图7-9-1 隐式分配链表
2.显式空闲链表:
分配块的组成成分和原来一样,需要在空闲块的空闲处中添加空闲块的前继和后继指针,使首次适配的时间从块总数的线性时间减少到空闲块线性的时间。只不过空闲块要足够大以填充上两个指针。
Printf会调用malloc,请简述动态内存管理的基本方法与策略。

7.10本章小结

本章是系统程序的运行,阐明了CPU如何得到相应地址的数据。其中用到虚拟地址的翻译。在执行可执行文件时,是由shell创建子进程中运行。然后,还些许地解释了动态内存分配器的工作原理和数据结构。
(第7章 2分)

第8章 hello的IO管理

8.1 Linux的IO设备管理方法

所有的I/O设备(如网络,磁盘和终端)都被模型化为文件,而所有的输入输出都被当做相对应的文件的读和写。这种将设备优雅地映射为文件的方式,允许linux内核引出一个简单,低级的应用接口。
设备的模型化:文件
设备管理:unix io接口

8.2 简述Unix IO接口及其函数

输入输出都以统一地方式来执行:
1.打开文件:一个应用程序通过要求内核打开相应的文件,来宣告它想访问一个I/O设备。内核返回一个小的非负整数。它在后续对此文件的所有操作中标识这个文件。内核记录有关这个打开文件的所有信息。应用程序只需记住这个描述符。
2.linux shell创建的每个进程开始时都由三个打开的文件:标准输入,标准输出和标准错误。
3.改变当前的文件位置。对于每个打开的文件,内核都保持着一个文件位置k,初始为0.这个文件位置时从文件开头起始的字节偏移量。应用程序能够通过执行seek操作。
4.读写文件。一个读操作就是从文件复制字节到内存,从当前文件位置k开始,然后将k开始,然后将k增加到k+n。给定一个大小为m字节的文件。
5.关闭文件。
Unix I/O函数:
1.进程通过调用open函数来打开一个以存在的文件或者创建一个新文件
2.进程通过调用close函数关闭一个打开的文件。
3.应用程序通过分别调用read和write函数来执行输入和输出的。

8.3 printf的实现分析

从printf生成显式信息,到write系统函数。
printf的实现:
fmt是一个指针,这个指针指向第一个const参数(const char fmt)中的第一个元素。
由于栈是从高地址向低地址方向增长的,可知(char)(&fmt) + 4) 表示的是第一个参数的地址。
由vsprint的功能为把指定的匹配的参数格式化,并返回字符串长度。
从寄存器中通过总线复制到显卡的显存中,此时字符以ascii码形式存储。字符显示驱动子进程将ascii码在自模库找到点阵信息存储到vram中,最后显示芯片按照刷新频率逐行读取vram,并通过信号线向显示器传输每一点。
https://www.cnblogs.com/pianist/p/3315801.html
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。

8.4 getchar的实现分析

异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。Getchar进行封装,大体逻辑是读取字符串的第一个字符然后返回。

8.5本章小结

本章主要讲述了linux的IO设备的管理方法,unixi/o接口及其函数,以及printf函数实现的分析和getchar函数的实现。特别地,在printf函数的底层实现的分析过程中,将原来只是简单的打印函数一层层地展开,清晰了底层的工作过程。
(第8章1分)

结论

用计算机系统的语言,逐条总结hello所经历的过程。
你对计算机系统的设计与实现的深切感悟,你的创新理念,如新的设计与实现方法。
1.编写程序。
2.源文件的预处理:将hello.c中头文件的内容都插入到文本中,将所有的宏都解释成具体的值。
3.编译:编译器将hello.i转化成汇编语言的ascii码文件hello.s。
4.汇编:汇编器将hello.s转化成二进制的机器代码,生成hello.o(可重定位目标文件)。
5.链接:对hello.o中引用的外部函数,全局变量等进行符号解析,重定位并且生成hello的可执行目标文件。
6.运行:在命令行中输入hello 1182910227 liufengzhe
7.创建子进程:通过fork创建。
8.执行:通过execxe调用加载器,建立虚拟内存映射,设置当前进程的上下文中的程序计数器,使之指向程序入口处。
9.访存:CPU上的MMU根据页表将CPU生成的虚拟地址翻译成物理地址,并用物理地址在主存的高速缓存中找相应的数据。
10.动态内存申请:printf调用malloc进行动态内存分配,在堆中申请所需的内存。
11.接收信号:中途接收ctrl+z挂起,ctrl+c终止。
12.程序返回后,内核向父进程发送SIGCHLD信号,终止的hello被父进程回收,内核将其上下文删除。

本次大作业贯通了整本深入了解计算机系统的内容,一个简单到不能再简单的hello程序竟然如此复杂。要进行预编译,编译,汇编,链接才能成为一个可执行目标文件。再通过shell来执行这个目标程序。其中包括创造子进程,执行,访问内存数据,通过malloc和free动态分配内存,以及信号的机制和作用,最后再将程序返回删除,完美得实现了P2P和020的过程。就这样,一个简单而复杂的hello程序就完成了。其中虚拟内存的思想使得许多进程能够共享主存这个空间,这是十分神奇的,解决的紧缺资源的问题。一个简单的hello程序所包含的知识竟是一本书的容量,因此对发明计算机和创造计算机进程,虚拟内存思想的人我感到十分的敬畏。他们是真正的天才。
(结论0分,缺少 -1分,根据内容酌情加分)

附件

列出所有的中间产物的文件名,并予以说明起作用。
Hello.i 预处理之后的ascii码程序
Hello.s 编译之后的汇编程序
Hello.o 汇编之后的可重定位目标程序(二进制程序)
Hello.elf hello.o的ELF格式
Helloexe.elf hello的ELF格式
Hello 可执行目标程序
Hello.s1 反汇编后输出的程序
Helloobj.txt hello可执行程序的反汇编代码
Temp.c 存放临时数据
(附件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分)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值