可执行文件的装载于进程

6.1  进程虚拟地址空间
      每个程序运行起来以后,它将拥有自己独立的虚拟地址空间,这个虚拟地址空间的大小由计算机的硬件平台决定,具体来说是由CPU的位数决定的


    对于windows来说,它的进程虚拟地址空间划分是操作系统占用2GB,那么进程就只剩2GB空间。
    windows有个启动参数可以将操作系统占用的虚拟地址空间减少到1GB,即跟Linux分布一样。方法如下:
       修改windows系统盘根目录下的Boot.ini,加上“/3G”参数


      [boot loader]
timeout=30
default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
[operating systems]
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /3G /noexecute=optin /fastdetect


AWE(Windows):
   操作系统提供一种窗口映射的方式使用那些大于常规的内存空间


   Linux 等Unix类操作系统用mmap()系统调用来实现


6.2   装载的方式
        动态装载:
             程序运行时是有局部性原理的,可以将程序最常用的部分驻留在内存中,而将一些不太常用的数据存放在磁盘上,这就是动态装载的基本原理
         覆盖装入和页映射是两种很典型的动态装载方法


         覆盖管理器需要保证两点:
              1)、树状结构从任何一个模块到树的根模块都叫做调用路径
              2)、禁止跨树间调用,任何一个模块不允许跨过树状结构进行调用


       页映射是虚拟存储机制的一部分




6.4  可执行文件的装载
      进程的建立
         从操作系统的角度看,一个进程最关键的特征是它拥有独立的虚拟地址空间,这使得它有别于其他进程


     创建一个进程,然后装载相应的可执行文件并且执行,在有虚拟存储的情况下:
         1) 创建一个独立的虚拟地址空间
         2) 读取可执行文件头,并且建立虚拟空间与可执行文件的映射关系
         3) 将CPU的指令寄存器设置成可执行文件的入口地址,启动运行


      创建虚拟地址空间
         创建一个虚拟地址空间实际上并不是创建空间而是创建映射函数所需要的相应的数据结构,在Linux下,创建虚拟地址空间实际上只是分配一个页目录就可以了


      读取可执行文件头,并且建立虚拟空间与可执行文件的映射关系
          当操作系统捕获到缺页错误时,它应知道程序当前所需要的页在可执行程序的哪一个位置,这就是虚拟空间与可执行文件之间的映射关系


           由于可执行文件在装载时实际上被映射到虚拟空间,所以可执行文件很多时候也被称为“映射文件”


      Linux 中将进程虚拟空间中的一个段叫做虚拟内存区域(VMA)  windows中将整个叫做虚拟段


      将CPU指令寄存器设置成可执行文件的入口,启动运行
          操作系统通过设置CPU的指令寄存器将控制权转交给进程,由此进程开始执行。从进程的角度而言,操作系统执行了一条跳转指令,直接跳转到可执行文件的入口地址




6.5  进程虚拟内存空间分布
      ELF可执行文件引入了一个概念叫做“Segment”,一个“Segment”包含一个或多个属性类似的“Section”     这样做的好处就是很明显地可以减少页面内存碎片


6.6  堆和栈
      操作系统通过使用VMA来对进程地址的空间进行管理,进程在执行他们的时候还需要用到栈、堆等空间


     匿名虚拟内存区域(VMA): 三个段的文件所在的主设备号和次设备号及文件节点号都是0,则表示它们没有映射到文件中。


    一个进程基本上可以分为如下几种VMA区域:
        代码VMA,数据VMA,堆VMA,栈VMA


6.7  堆得最大申请数量
       使用下面的程序能够检测malloc最大内存申请数量:
        #include <stdio.h>
#include <stdlib.h>


unsigned maximum = 0;


int main(int argc, char *argv[])
{
    unsigned blocksize[] = { 1024*1024,1024,1};
    int i,count;
    for(i = 0;i < 3; i++)
       for(count = 1;;count++)
          {
             void *block = malloc(maximum + blocksize[i]*count);
             if(block)
             {
                 maximum = maximum + blocksize[i]*count;
                 free(block);
             }
             else
             {
                 break;
             }
          }
    printf("maximum malloc size = %u bytes\n",maximum);
}


在Linux机器上结果大概是2.9GB左右,在windows上大概是1.5GB


6.8  进程栈初始化
     进程刚启动的时候,最基本的就是系统环境变量和进程的运行参数
      进程在启动以后,程序的库部分会把堆栈里的初始化信息中的参数信息传递给main()函数,也就是我们熟知的main()函数的两个argc和argv参数,这两个参数分别对应着这里的命令行参数数量和命令行参数字符串的指针数组


6.9  Linux内核装载ELF过程简介
    bash进程会调用fork()系统调用创建一个新的进程,然后新的进程调用execve()系统调用执行指定的ELF文件,原先的bash进程继续返回等待刚才启动的新进程结束


    每种可执行文件的格式的开头几个字节都是很特殊的,特别是开头的4个字节,常常被称为魔数,通过对魔数的判断可以确定文件的格式和类型


    ELF的头4个字节为0x7F、'e’,'l'、'f'         Java的可执行文件的头4个字节为'c','a','f','e'


    shell、perl、python等解释型脚本语言,第一行往往是"#!/bin/sh"、“#!/usr/bin/perl”或“#!/usr/bin/python”这个时候前面2个字节“#”和“!”就构成了魔数,系统解析到这2个字节,就对后面的字符串进行解析,以确定具体的解释程序的路径


   主要步骤是:
     1)、检查ELF可执行文件格式的有效性,比如魔数、程序头表中段(Segment)的数量
     2)、寻找动态链接的“.interp”段,设置动态链接器的路径
     3)、根据ELF可执行文件的程序头表的描述,对ELF文件进行映射,
     4)、初始化ELF进程环境,比如进程启动时EDX寄存器的地址应该是DT_FINI的地址
     5)、将系统调用的返回地址修改成ELF可执行文件的入口地址




6.10  Windows PE的装载
    RVA 表示相对虚拟地址,相对于PE文件的装载基地址的偏移地址,每一个PE文件装载的时候都会有一个装载目标地址,这就是基地址
 
    装载PE可执行文件:
      1)、读取文件的第一个页,该页包含了DOS头,PE头和段表
      2)、检查进程地址空间中,目标地址是否可用,如果不可用,则另外选一个装载地址
      3)、使用段表中提供的信息,将PE文件中所有的段一一映射到地址空间中相应的位置
      4)、如果装载的不是目标位置,则进行Rebasing
      5)、装在所有PE文件所需要的DLL文件
      6)、对PE文件中的所有导入符号进行解析
      7)、根据PE头中指定的参数,建立初始化栈和堆
      8)、建立主线程并且启动进程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值