linux从可执行文件到虚拟内存

可执行文件总结,基本上是参照下面这篇文章,而虚拟内存是自己根据深入理解计算机系统自己总结的。

http://blog.csdn.net/fyl027/article/details/78064697

一:可执行文件格式,

1、Linux中的ELF,windows下面的PE,他们都是COFF格式的变种。

2、文件内容

(1)代码段:程序源代码编译的机器指令

(2)数据段:已经初始化了的全局变量和局部静态变量

(3).bss段:未初始化的全局变量和局部静态变量,未初始化化的全局变量和局部静态变量因为没有初始化,在目标文件中分配空间是没有必要的,所以放到了.bss段中。变量名和变量的大小记录在了符号表中,在进行存储器映射的时候,会映射匿名文件,这个后面会说。

(4)其他段,.symtab符号表,debug调试信息,dynamic动态链接信息等等,对于编译和链接,以后再做总结。

在Linux操作系统中有一个命令objdump,这个命令可以查看目标文件或者可执行文件(目标文件和可执行文件的格式类似)。

可执行文件的中是没有地址空间的概念的,这就是个文件,文件格式遵循coff格式的变种,当进行存储器映射的时候才会有地址空间的概念。在可执行文件中并没有堆栈段一说,在从可执行文件映射当虚拟存储器的时候,才会有堆栈段一说,后面会讲。

3、对可执行文件中的代码段进行分析。


objdump加 -s  参数可以将段数据反汇编为汇编语言,下面是上面的程序对应的汇编语言。



对于栈来说,与栈相关的指针有两个,一个是esp一个是ebp,前者是栈顶,也就是当压栈的时候,esp会减,出栈,esp会加,ebp不变。当对栈中变量进行访问的时候,可以通过偏移量加ebp进行寻址(esp也是可以寻址的,但是每次esp的位置会变,所以操作不方便,而ebp是固定的操作方便一些)当调用一个函数的时候(函数调用有约定成俗的方式,c/c++中用的_cdecl而windowsAPI中用的是_stdcall方式,还有其他的方式前两者,区别是谁清理栈,以及是否支持可变参数。前者是支持可变参数的,并且在调用者中通过对栈顶增加来清除栈,后者不支持可变参数,因此是固定的数量,自己就可以调用ret清理栈后面分析的_stdcall),调用函数之前先对当前函数中的变量进行压栈,之后call调用函数,call函数自动将返回地址压人到栈中,在调用函数中,首先将ebp压进入到栈中,在将esp也就是栈顶的指针位置的值赋给了ebp,之后,esp减少,也就是建立局部变量并且进行压栈了,之后可以用ebp指定偏移量寻找操作数。结束的时候,先恢复ebp的值,在用ret清除栈,并且返回之钱保存的返回地址,栈回到调用之前的情形。

二、虚拟内存

虚拟内存是内存管理的一套机制,它使得应用程序因为他拥有连续分配的内存,实际上它是没有的,一部分在物理内存上,一部分在硬盘上。在实现上面是将主存看做是硬盘的缓冲,通过一个页表将硬盘上的虚拟存储器中的内容映射到主存上面。

1、地址空间:在数据总线为n的计算机中,如32位,64位。地址空间的大小为2^n次方,因为cpu会处理虚拟地址,而cpu能处理最大的数就是2^n次方。

2、虚拟存储器,虚拟页,物理页:从概念上讲(也就是原理上应该是这样的),虚拟存储器是放在磁盘上的N个连续字节大小单元组成的数组(数组中的内容下面会说),每个字节有一个虚拟地址,这个虚拟地址是作为到数组的索引。这些虚拟地址是属于地址空间中的,也就是从0到最大值。虚拟内存机制将虚拟存储器和物理存储器划分为相等大小的页面,前者叫做虚拟页后者叫做物理页。物理页是虚拟页面在内存中的缓冲。虚拟页面分为三种,一种是未分配的,一种是未缓冲的,一种是缓冲的。vm将整个地址空间划分为n个虚拟页,并且在页表中,针对每一个虚拟页建立一个页表项。但是有的虚拟页面,当前并没有映射文件,在当前的虚拟存储器中并没有分配空间,所以是虚拟页面有未分配这一项,这个在虚拟存储器映射的时候,后面还会说到。


3、页表:vm管理机制需要判断一个虚拟页是否被映射到了物理页中,如果是还要确定放到了哪个物理页面中。另外如果虚拟页没有被映射到物理页中,但是目前需要访问此虚拟页面,那么就要从物理页面中选择一个牺牲掉,并将虚拟页面映射到上面。这些操作是通过软硬件结合的。有操作系统软件,MMU中的地址翻译硬件和一个存放在物理存储器中叫做页表的数据结构。页表是右页表项(PTE)组成的数组,地址空间中的每一个虚拟页对应PTE中的一项。PTE内容包括有效位(此虚拟页面是否存在在物理页面中),地址段,如果有效位置一,那么地址段中的内容就是物理页面的起始地址,如果没有置一,那么地址段中的内容就是虚拟页面在磁盘中的起始位置,如果是未分配的。那么为NULL。另外在页表中还有关于此虚拟页面是否可以被访问,是否可读,可修改。如下图所示。当cpu产生某个虚拟地址,如果此虚拟地址所在的虚拟页对应的页表项中有效位为1,那么说明此次访问命中,如果页表项中的有效位为0并且磁盘地址为NULL,那么操作系统此时会在虚拟存储器中分配虚拟页,并且将此页映射到物理页中,并且更新页表中对应页表项。如果有效位为0但是磁盘地址不为NULL,那么操作系统会将磁盘中的虚拟页映射到物理页中,并且更新页表对应的页表项。后面两种情况叫做分配页和却页,这两种情况在将新的虚拟页映射到物理页中的时候,如果物理内存不够都是先要牺牲一个物理页的,此时在Linux中是根据LRU最近未使用页面置换算法。下面举个LRU的例子,例子是百度百科中LRU的例子。



4、地址翻译


cpu中有个寄存器叫做页表基址寄存器它指向页表的起始地址。cpu将虚拟地址发送给MMU,MMU将虚拟地址解析为VPN和VPO,虚拟页号VPN作为偏移量,页表基址寄存器中的地址作为起始地址,起始地址加偏移量在页表中找到相应的页表项。如果有效位有效并且访问符合保护位,那么就从页表项中取出物理页号,返回给MMU,因为虚拟页面和物理页面的大小是一样的,所以虚拟地址中的偏移量作为物理地址中的偏移量,根据这两个规则MMU构造出物理地址,把它传给高速缓存/内存,高速缓存/内存将数据返回给处理器。上述命中的情形完全 由硬件进行,当不命中的时候,也就是当发现请求的虚拟地址的所在页面没有在物理页面中进行映射,那么此时操作系统,根据LRU,牺牲掉一个物理页面,并且将请求的虚拟页面映射到这个物理页面上面,并且更新页表。上述处理过程叫做缺页处理。当发生缺页的时候,MMU会触发中断,将控制权转移到操作系统,操作系统调用缺页处理程序。 缺页处理程序处理完之后,操作系统将控制权返回给原来的进程,原来的进程会再次执行引起缺页的指令,这样就不会发生缺页了。

TLB:每次进行地址转换的时候,MMU都要查看内存中或者缓冲在高速缓存中的页表,这样速度会很慢的,所以现代硬件系统会在MMU中包含一个小的页表缓冲,每次通过VPN去TLB中查找物理页号。下面就是一个TLB,它有4组条目,所以就取VPN的前两位作为组索引,后面的位数作为标记位来区分同一组中的不同页表项缓冲。当发生命中的时候,TLB将物理页面的物理页号PPN返回。否则,就要到页表中去查找PPN,如果页表中可以发生命中,那么从高级缓冲或者内存中(页表是存在高级缓冲或者内存中的)返回PPN。如果当前请求的TLB组中并没有虚拟地址所在页面的标志位,那么就会覆盖掉这组中的某一块。如果页表中也没有发生命中,那么也是会先发生缺页中断的。



多级页表:当页面的大小一定,页面数量就是地址空间的大小除以页面大小。这个大小也是页表中的数组的大小,页表中的页表项也会占用几个字节,所以一般页表所占用的内存是有几M的,例如对于4KB大小的页面,页表项为4字节,那么页表大小为4M。当采用多级页表的时候,可以对页表进行压缩。

虚拟存储器内容:分为两部分,一部分是内核虚拟存储器,位于用户栈的上部,这部分中,是映射的内核的代码段和数据段,对于每个进程都是一样的。另外,还有的就是与进程相关的数据结构,页表、task和mm结构,内核栈,这些是在初始化的时候建立的。这个后面会细说。另外还有用户空间。用户空间中包括可执行文件中的.data  .text等等的段。linux将虚拟存储器组成一些区域,一个区域就是已经分配的连续的虚拟片。区域很重要,它使得虚拟地址空间有间隙。例如代码段,数据段,.bss段,栈,共享库段,以及用户栈都是不同的区域,在某一时刻,可能某一区域虽然保留了地址空间中的一部分,但是在虚拟存储器上并没有为其保留空间也就是前面说的未分配页面。拿栈区域来说,它是有一个起始地址的,而这个起始地址对于任何进程来说被分配到的虚拟地址都是确定的,大小可以自己设置。也就是从起始地址和偏移大小都能确定,那么虚拟地址的范围也就是可以确定了。这个虚拟页面是属于未分配的,当压栈的时候也就是减少了esp,那么操作系统就会分配虚拟页面并为虚拟页面分配资源。虚拟页面映射的时候映射的是匿名文件也就是二进制0(下面会说),操作系统将对应的虚拟页面调入到内存中,并建立映射。堆也是一样的情况。


内核中的记录进程的数据结构:task_struct就是LINUX中的PCB,里面记录了,进程的PID号,可执行文件的名字,指向用户栈的指针(包括ESP和EBP,都是虚拟地址),还有程序计数器等等内容。其中有个条目指向了mm_struct这个结构中有一级页表的基址,还有一个是指向vm_area_strcut区域的指针,vm_area_struct区域是一个指针链表。每一个节点中存放着指向虚拟存储器中的某一个域的起始地址以及结束地址,这个区域内所有虚拟页的读写权限,以及这个区域内的页面是否是共享的页面还是私有的,当然还有指向链表中的下一个节点的指针域,vm_area_struct区域决定着是否这个虚拟页面能否响应当前的请求。当一个进程运行的时候,要把task-struct中的相关寄存器恢复。在将mm_struct中的pgd中的页表的基址放到cpu中的相关寄存器中去。当执行缺页中断程序的时候,就会根据这个区域中的内容去判断,造成缺页中断的虚拟地址是否合法,不合法也就是此时的页面是未分配的,或者超出了地址空间范围于是缺页处理程序返回1也就是段错误并且结束进程。这个页面是否可以访问,不可以访问的情况是,对只能读页面进行写操作,运行在用户模式下却去访问了内核空间中的数据,当不能访问的时候,缺页中断也会结束当前进程的。


存储器映射:linux通过将一个虚拟存储中的内容和一个磁盘中的对象关联起来,以初始化虚拟存储中的内容。存储器映射分为两种,一种是普通文件的映射,一种是匿名文件映射

A:普通文件映射:虚拟存储器中的区域可以映射到一个普通磁盘文件的连续的一部分,例如一个可执行目标文件,可执行文件中的.text段就是映射到了虚拟存储器的文本段,.data就是映射到了数据段。

B:匿名文件:一个区域也可以映射到匿名文件,匿名文件本来是不存字磁盘中的,是由操作系统创建的全部为二进制0的文件。典型的例如.bss段,它在可执行文件中并没有对应的段,但是名字和大小记录在了符号表中。当第一次坐落在这个区域中的虚拟页面被引用的时候,内核就在物理空间找到一个牺牲面,之后把这个牺牲面全部填写二进制的0。

书上说,无论哪种情况下,一旦一个虚拟文件被初始化了,他就是在交换文件之间换来换去,这个交换文件也叫做交换空间。交换空间限制着当前运行的继承能够分配的虚拟页面总数。这句话我理解的是,对于应用程序来说,它以为内核为它的所以区域分配了内存空间。实际上是物理内存加上交换空间(磁盘空间)。当交换空间的值越大,那么电脑能“同时”运行的进程越多,但是从磁盘中读取虚拟页面的时间太久,所以效率很低。所以一般我们不要把交换空间设置的太大,当加载不了进程内存的时候就内存爆掉了。


 



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值