目录
6.2简述壳Shell-bash的作用与处理流程 - 10 -
7.2 Intel逻辑地址到线性地址的变换-段式管理 - 11 -
7.3 Hello的线性地址到物理地址的变换-页式管理 - 11 -
7.4 TLB与四级页表支持下的VA到PA的变换 - 11 -
7.7 hello进程execve时的内存映射 - 11 -
1.1 Hello简介
P2P即 From Program to Process,是将源程序hello.c(Program),在OS(比如linux)中进行预处理(cpp处理器),编译(cll),汇编(as)这三个过程,再链接生成可执行文件,得到可执行的hello的过程。
O2O是指在终端中输入“./hello”运行可执行文件,此时shell会为它创建一个新的子进程,为其execve映射虚拟内存,程序完成之后,shell父进程回收子进程恢复原本的hello进程,这就是O2O的过程
1.2 环境与工具
硬件:Intel Core i5,6G RAM 1T HDD
软件:Windows10,Ubuntu 18.04.1
开发与调试工具:gedit、vim、gcc、edb、readelf、hexedit
列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。
hello.c :用C语言编写的hello程序源代码hello.i : hello.c经预处理处理得到的文本文件hello.s :编译hello.i后得到的汇编代码hello.o :汇编hello.s后得到的可重定位目标执行文件hello : hello.o链接后得到的二进制文件
本章中简单的用文字介绍了P2P和O2O的整个过程,并介绍了即将运用的开发工具和所处的开发环境。
(第1章0.5分)
2.1 预处理的概念与作用
概念:在进行编译之前,将hello.c首先翻译成一个由ASCII码构成的中间文件hello.i,预处理会从宏定义(#define),源文件包含(#include),条件编译(#if #ifdef #ifndef #else #elif #endif)等方面进行。主要处理那些用“#”开头的指令。
作用:预处理指令一般被用来使源代码在不同的执行环境中被方便的修改或者编译。
图2-2-1预处理命令行
图2-2-2 hello.i打开图(部分)
图2-2-3 hello.i打开图(部分)
预处理之后的文件达到了3042行,源文件中的代码出现在在文件的末尾,头文件被引入,以“#”起始的行被展开,文件开头的注释“// 大作业的 hello.c 程序”等等内容被删除。
本章中介绍了预处理的基本概念和作用,介绍了hello.c的预处理命令,给出了预处理后文件的部分截图,并且对hello.i进行了简单的分析
(第2章0.5分)
概念:利用编译程序从源语言(C语言)编写的源程序产生目标程序(汇编语言程序)的过程, 编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。
在本文中可以解释成检查hello.i是否有错误,无错误将其翻译成汇编语言,生成hello.s。
作用:在本文中是将源代码转换称为汇编代码
注意:这儿的编译是指从.i到 .s 即预处理后的文件到生成汇编语言程序
图3-2-1编译的命令行
下面的图是按照顺序将hello.s的各部分截图粘贴
图3-3-1
file:源文件名
.text:代码段
.section .rodata:rodata节
.align:对齐方式
.global全局变量
.string:字符串
.type:指定对象类型或函数类型
图3-3-2
①汇编语言中各种数据是通过立即数的形式存储的。比如在上图中可以看出main函数的参数argc在%edi内保存,储存在-20(%rbp)内 。
②
使用了加载有效地址指令leaq计算LC1的段地址%rip+.LC1并传递给%rdi
③
部分比较argc和4的大小,相等就跳到L2继续程序,不相等则执行下一步程序【源代码中if(argc!=4)的部分】。
④
调用printf()函数。
⑤
把返回值设定为1(说明存在异常),并且调用exit()函数退出程序,给exit()函数传入的参数值是1。
图3-3-3
⑥给i赋值为0,并保存在-4(%rbp)内,然后执行L3部分的程序
图3-3-4
⑦
调用printf()函数
⑧
调用atoi()函数
⑨
调用sleep()函数
⑩
i=i+1实现源代码中的i++
图3-3-5
(11)
判断i和7的关系,如果i小于等于7,则执行L4的代码。从而实现了循环功能【源代码中for(i=0;i<8;i++)的部分】
(12)
调用getchar()函数。
(13)
设置返回值为0,退出程序。
图3-3-6
本章主要讲述了编译的作用和概念,hello.s汇编代码的生成方法和具体内容,并且对hello.s中汇编语言是如何表示高级语言的做了简单的分析。
(第3章2分)
概念:as(编译器)把汇编语言代码翻译成和它相对应的机器语言指令,并且这些指令将被打包成为可重定位目标文件,这个过程就是汇编。
作用:把汇编语言转换成机械语言以供计算机识别
注意:这儿的汇编是指从.s到 .o 即编译后的文件到生成机器语言二进制程序的过程。
图4-2-1汇编的指令行
分析hello.o的ELF格式,用readelf等列出其各节的基本信息,特别是重定位项目分析。
。
图4-3-1 elf文件头
图4-3-2 hello.o各节的详细信息
图4-3-3 hello.o的可重定位信息
图4-3-4 hello.o的各节基本信息
由上面的图我们可以看出hello.o文件是可重定位文件,采用补码和小端存储。
ELF头(图4-3-1)包括类别(Class),数据(Data),机器类型(Machine),入口点地址(Start of program headers),程序头起点地址(Start of section headers),本头大小,节头部大小等信息。
(以下格式自行编排,编辑时删除)
objdump -d -r hello.o分析hello.o的反汇编,并请与第3章的 hello.s进行对照分析。
说明机器语言的构成,与汇编语言的映射关系。特别是机器语言中的操作数与汇编语言不一致,特别是分支转移函数调用等。
图4-4 hello.o的反汇编
①机器语言是二进制的机器指令,机器指令由操作码和操作数组成。
②每一条汇编语言都有一条机器语言和它相对应,反之亦然。
③不同之处的分析:
[if !supportLists](1)[endif]hello.s中立即数采用10进制形式,hello.o的反汇编中采用16进制
[if !supportLists](2)[endif]分支转移:hello.o的反汇编中用指令地址代替L1,L2等跳转目标的名称。
[if !supportLists](3)[endif]函数调用:hello.s中调用函数使用函数的名称,hello.o的反汇编中函数调用的目标是一个指令地址。
[if !supportLists](4)[endif]hello.o的反汇编中全局变量和常量已经被存在一个具体的地址里,调用时在具体的地址里存取。
介绍了汇编的基本概念和作用,以及将hello.s汇编成为hello.o的方法。分析了hello.o的ELF格式,查看了各节的信息和hello.o的可重定位信息。
列出了hello.o的反汇编文件内容,分析了机器语言和汇编语言的关系。对比探讨了hello.s与hello.o的区别。
(第4章1分)
(以下格式自行编排,编辑时删除)
概念:将各种代码和数据片段收集并组合成为一个可以在终端下“./文件名”执行的单一的文件的过程
作用:把各个代码和数据片段整合,得到一个可以执行的文件。
注意:这儿的链接是指从hello.o到hello生成过程。
(以下格式自行编排,编辑时删除)
图5-2-1链接指令
图5-2-2链接后的文件夹
图中hello就是链接成的文件
分析hello的ELF格式,用readelf等列出其各段的基本信息,包括各段的起始地址,大小等信息。
图5-3-1 elf文件头信息
图5-3-2 elf节基本信息
由上面的图可以看出hello属于可执行文件,使用补码和小端存储。
使用edb加载hello,再Plugins - SymbolViewer就可以查看本进程的虚拟地址空间的信息。
比如说
和
图纸中红线标出地方对比
我们可以的发现两者显示的init段的起始地址均为0x0000000000401000,是相同的。
再比如
和
对比,发现rodata的起始地址也是相同的。
(以下格式自行编排,编辑时删除)
objdump -d -r hello分析hello与hello.o的不同,说明链接的过程。
结合hello.o的重定位项目,分析hello中对其怎么重定位的。
(以下格式自行编排,编辑时删除)
使用edb执行hello,说明从加载hello到_start,到call main,以及程序终止的所有过程。请列出其调用与跳转的各个子程序名或程序地址。
加载hello之后,首先运行一段程序如下:
然后最后的跳转指令中储存的是_start的地址0x0000000000401090,再运行如下程序:
调用函数到达main函数。
执行main函数,本程序中途会进入exit()函数,运行exit()函数程序终止。
从加载hello到_start:
跳转到*%R12
_start到main调用和跳转的子程序:
程序编译时会采用两种表进行辅助,一个为PLTT表,一个为GOT表,PLT表可以称为内部函数表,GOT表为全局函数表(也可以说是动态函数表这是个人自称),这两个表是相对应的,PLT表中的数据就是GOT表中的一个地址,可以理解为一定是一一对应的。
在dl_init前,初始时每个GOT条目都指向对应的PLT条目的第二条指令。
dl_init后数据应该有改变但是本程序edb时无法执行到那一步
本小节中介绍了hello可执行文件的相关信息,链接生成可执行文件的过程以及它的反汇编语言。
(第5章1分)
概念:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。进程是正在运行的程序的实例。
作用:从理论角度看,是对正在运行的程序过程的抽象;
从实现角度看,是一种数据结构,目的在于清晰地刻画动态系统的内在规律,有效管理和调度进入计算机系统主存储器运行的程序
Shell是用户和操作系统之间完成交互式操作的一个接口程序。
bash是Linux操作系统的默认shell程序。
处理流程:
[if !supportLists](1)[endif]判断收到的是否是内置命令
[if !supportLists](2)[endif]是则解析命令行
[if !supportLists](3)[endif]不是判断是否要打开某个程序
[if !supportLists](4)[endif]搜索路径里没有目标程序则报错
Shell调用fork()函数,父进程创建一个子进程,hello的打开是在子进程内进行,子进程不完全但是几乎是和父进程相同的,子进程得到的是父进程虚拟空间的一个副本,这个副本内包含代码段,数据段,malloc,共享区域,用户栈等等和父进程虚拟空间相同的内容。且这个子进程还得到了父进程中打开文件的副本。子进程和父进程唯一的不同是PID。
shell会调用execve()函数,在创建好的子进程里面加载hello程序,并且允许它进行操作,这个函数把hello的.data节,.bss节等部分的内容都加载到了fork出的子程序的虚拟空间内
Linux系统的每个程序运行时,一定是处于一个进程的上下文当中的,shell运行hello时,父进程生成一个子进程,它几乎与父进程一样,子进程再由shell调用execve把hello的数据内容载入到子进程的虚拟内存当中。新产生的代码段和数据段经过虚拟内存映射成为可执行文件的内容,然后hello就能够被执行了。
异常种类:中断、终止、故障。
可能出现SIGSTP,SIGINT故障
简单介绍了进程的概念和作用,以及Shell-bash和hello进程运行的相关内容,以及hello异常和信号处理的内容。
(第6章1分)
逻辑地址是指在计算机体系结构中是指应用程序角度看到的内存单元(memory cell)、存储单元(storage element)、网络主机(network host)的地址。比如hello.o的反汇编里就给出了这样的一些地址
线性地址:是逻辑地址到物理地址变换之间的中间层。在分段部件中逻辑地址是段中的偏移地址,然后加上基地址就是线性地址。
虚拟地址:程序访问存储器所使用的逻辑地址称为虚拟地址,与实地址模式下的分段地址类似,虚拟地址也可以写为“段:偏移量”的形式,这里的段是指段选择器。
物理地址:在存储器里以字节为单位存储信息,为正确地存放或取得信息,每一个字节单元给以一个唯一的存储器地址,称为物理地址(Physical Address),又叫实际地址或绝对地址。
7.3 Hello的线性地址到物理地址的变换-页式管理
页式管理是一种内存空间存储管理的技术,页式管理分为静态页式管理和动态页式管理。将各进程的虚拟空间划分成若干个长度相等的页(page),页式管理把内存空间按页的大小划分成片或者页面(page frame),然后把页式虚拟地址与内存地址建立一一对应页表,并用相应的硬件地址变换机构,来解决离散地址变换问题。页式管理采用请求调页或预调页技术实现了内外存存储器的统一管理。
cache就是高速缓存,是为了调和CPU得过快访问速度和内存过慢的速度的一个硬件,现代计算机一般都有三级高速缓存,L1、L2、L3,访问速度依次递减。
读取信息的时候一般先访问一级cache,若没有所需数据则向二级cache调用,二级cache没有则向三级cache调用,三级cache向内存调用数据。
在shell给hello创建子进程的时候,内核为这个子进程分配了一个唯一的PID,并且创建了各种数据结构。然后创建当前进程的的mm_struct, vm_area_struct和页表的原样副本。将每个页面都标记为只读且每个区域结构(vm_area_struct)都标记为私有的写时复制(COW)。
execve的过程中,加载并且运行包含在当前可执行目标文件中,属于hello的程序。
通过删除目前已有的子进程用户区数据,映射私有区域,利用hello的代码段,数据段,bss段等等创建新的区域结构,再映射共享区域的内容,最后设置一个指向hello程序的代码的入口处的程序计数器,就可以执行了。
缺页中断就是要访问的页不在主存,需要操作系统将其调入主存后再进行访问。在这个时候,被内存映射的文件实际上成了一个分页交换文件。
产生缺页中断的几种情况:
1、当内存管理单元(MMU)中确实没有创建虚拟物理页映射关系,并且在该虚拟地址之后再没有当前进程的线性区(vma)的时候,可以肯定这是一个编码错误,这将杀掉该进程;
2、当MMU中确实没有创建虚拟页物理页映射关系,并且在该虚拟地址之后存在当前进程的线性区vma的时候,这很可能是缺页中断,并且可能是栈溢出导致的缺页中断;
3、当使用malloc/mmap等希望访问物理空间的库函数/系统调用后,由于linux并未真正给新创建的vma映射物理页,此时若先进行写操作,将和2产生缺页中断的情况一样;若先进行读操作虽然也会产生缺页异常,将被映射给默认的零页,等再进行写操作时,仍会产生缺页中断,这次必须分配1物理页了,进入写时复制的流程;
[if !supportLists]4、[endif]当使用fork等系统调用创建子进程时,子进程不论有无自己的vma,它的vma都有对于物理页的映射,但它们共同映射的这些物理页属性为只读,即linux并未给子进程真正分配物理页,当父子进程任何一方要写相应物理页时,导致缺页中断的写时复制。
当进程执行过程中发生缺页中断时,需要进行页面换入,步骤如下:
<1>首先硬件会陷入内核,在堆栈中保存程序计数器。大多数机器将当前指令的各种状态信息保存在CPU中特殊的寄存器中。
<2>启动一个汇编代码例程保存通用寄存器及其它易失性信息,以免被操作系统破坏。这个例程将操作系统作为一个函数来调用。
(在页面换入换出的过程中可能会发生上下文换行,导致破坏当前程序计数器及通用寄存器中本进程的信息)
<3>当操作系统发现是一个页面中断时,查找出来发生页面中断的虚拟页面(进程地址空间中的页面)。这个虚拟页面的信息通常会保存在一个硬件寄存器中,如果没有的话,操作系统必须检索程序计数器,取出这条指令,用软件分析该指令,通过分析找出发生页面中断的虚拟页面。
<4>检查虚拟地址的有效性及安全保护位。如果发生保护错误,则杀死该进程。
<5>操作系统查找一个空闲的页框(物理内存中的页面),如果没有空闲页框则需要通过页面置换算法找到一个需要换出的页框。
<6>如果找的页框中的内容被修改了,则需要将修改的内容保存到磁盘上,此时会引起一个写磁盘调用,发生上下文切换(在等待磁盘写的过程中让其它进程运行)。
(注:此时需要将页框置为忙状态,以防页框被其它进程抢占掉)
<7>页框干净后,操作系统根据虚拟地址对应磁盘上的位置,将保持在磁盘上的页面内容复制到“干净”的页框中,此时会引起一个读磁盘调用,发生上下文切换。
<8>当磁盘中的页面内容全部装入页框后,向操作系统发送一个中断。操作系统更新内存中的页表项,将虚拟页面映射的页框号更新为写入的页框,并将页框标记为正常状态。
<9>恢复缺页中断发生前的状态,将程序指令器重新指向引起缺页中断的指令。
<10>调度引起页面中断的进程,操作系统返回汇编代码例程。
<11>汇编代码例程恢复现场,将之前保存在通用寄存器中的信息恢复
(以下格式自行编排,编辑时删除)
Printf会调用malloc,请简述动态内存管理的基本方法与策略。
1)首次适应:首次适应策略要求空闲区按其起始地址从小到大排列,当某一用户作业要求装入内存时,存储分配程序从起始地址最小的空间区开始扫描,直到找到满足该作业要求的空闲区为止。
2)循环首次适应:在查找空闲区时,不再每次从链首开始查找,而是从上一次找到的空闲区的下一个空闲区开始查找,直到找到一个能满足要求的空闲区为止,并从中划出一块与请求大小相等的内存空间分给该作业。
3)最佳适应:该策略总是把满足要求,又使最小的空闲区分配给请求作业,即在空闲区表中,按空闲区的大小从小到大排列,建立索引,当用户作业请求内存空间时,从索引表中找到第一个满足该作业的空闲区分给它。
4)最差适应:该策略总是把最大的空闲区分配给请求作业,空闲区表(空闲区链)中的空闲分区要按大小从大到小进行排序,自表头开始查找到第一个满足要求的空闲分区分配给作业。
本小节主要介绍了hello在执行时,有关储存的管理的各项相关内容。
(第7章 2分)
设备的模型化:文件
设备管理:unix io接口
Unix IO接口是连接CPU与外设之间的部件,它完成CPU与外界的信息传送。还包括辅助CPU工作的外围电路,如中断控制器、DMA控制器、定时器、高速CACHE。
它最重要的四个函数,打开open,关闭close,读取read,写入write
从vsprintf生成显示信息,到write系统函数,到陷阱-系统调用 int 0x80或syscall.
字符显示驱动子程序:从ASCII到字模库到显示vram(存储每一个点的RGB颜色信息)。
显示芯片按照刷新频率逐行读取vram,并通过信号线向液晶显示器传输每一个点(RGB分量)。
异步异常-键盘中断的处理:键盘中断处理子程序。接受按键扫描码转成ascii码,保存到系统的键盘缓冲区。
getchar等调用read系统函数,通过系统调用读取按键ascii码,直到接受到回车键才返回。
本小节介绍了Linux的IO设备,接口和接口函数,以及prinf和getchar实现过程
(第8章1分)
Hello.c从被编写出来到执行,经历了下面的这些流程:
[if !supportLists](1) [endif]预处理器将hello.c预处理成为hello.i
[if !supportLists](2) [endif]编译器将hello.i翻译成汇编语言hello.s
[if !supportLists](3) [endif]汇编器将hello.s汇编成可重定位二进制代码hello.o
[if !supportLists](4) [endif]链接器将外部文件和hello.o链接成为可执行二进制文件hello
[if !supportLists](5) [endif]shell进程调用fork为其创建子进程.
[if !supportLists](6) [endif]shell调用execve函数使得hello的数据写入子进程的虚拟内存空间,从而使得hello的代码可以在一个可执行文件中被执行。
如今的电脑程序运行速度主要受限制于读取的速度,未来如果能够实现cache的更准确命中或者提高cache每一级的容量,可以更好的提升电脑速度。
(结论0分,缺失 -1分,根据内容酌情加分)
hello.i预处理之后文本文件
hello.s编译之后的汇编文件
hello.o汇编之后的可重定位目标执行
Hello链接之后的可执行目标文件
(附件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分)
`���k�x�κ�