可执行文件的装载与进程

可执行文件的装载与进程

进程虚拟地址空间

程序是一个静态的概念,是一些预先编译好的指令和数据集合的一个文件

进程是一个动态的概念,是程序运行时的一个过程

C语言指针大小的位数与虚拟空间的位数相同

#include<stdio.h>

int main()
{
    int a = 10;
    int* p = &a;
    printf("%ld\n",sizeof(p));//8 可得出64位平台下指针位64位,即8字节
    return 0;
}

程序在运行时处于操作系统的监督下,操作系统为了达到监控程序运行等一系列目的,进程的虚拟空间都在操作系统的掌握之中。进程只能使用操作系统分配给进程的地址,如果访问未经允许的空间,操作系统回捕获到这些访问,将进程的这种访问当作非法操作,强制结束进程。Windows下的"进程因非法操作需要关闭"和Linux下的"Segmentation fault"大部分都是因为进程访问了未经允许的地址

在32位平台下,一个进程分配了4GB的虚拟内存

image-20211122110541479

进程最多可以使用3GB的虚拟空间,即整个进程在执行的时候,所有的代码、数据包括通过申请的虚拟空间之和不得超过3GB。但进程不能完全使用这3GB的虚拟空间,其中有一部分时预留给其他用途的

Windows系统的进程虚拟空间划分操作系统占2GB,可修改Windows系统盘跟目录下的Boot.ini,加上"/3G"参数即可将操作系统占用的虚拟地址空间减少到1GB

image-20211122111026260

如果程序只能运行在32位系统下,但是需要占用的内存空间超过3GB,怎么办

PAE

32位的CPU下,程序使用的空间能不能超过4GB

如果空间指的是虚拟地址空间,那么不能。因为32位的CPU只能使用32位的指针,其最大的寻址范围是0到4GB

如果空间指的是计算机的内存空间,那么可以。Intel采用36位的物理地址,可以访问高达64GB的物理内存

原先的32位地址线只能访问最多4GB的物理内存,但扩展至36位地址线后,Intel修改了页映射的方式,使得新的映射方式可以访问到更多的物理内存,Intel将这个地址扩展方式叫做PAE

操作系统提供一个窗口映射的方法,将额外的内存映射到进程地址空间中。应用程序可以根据需要来选择申请和映射,比如一个应用程序中0x100000000x20000000这一端256MB的虚拟地址空间用来做窗口,程序可以从高于4GB的物理空间中申请多个大小为256MB的物理空间,编号为A、B、C等,然后根据需要将这个窗口映射到不同的物理空间块,用到A是将0x100000000x20000000映射到A,用到B、C再映射过去

在Windows下这种访问内存的操作方式叫做AWE

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

装载的方式

程序执行时所需要的指令和数据必须在内存中才可以正常运行,但大多数情况下程序所需要的内存数量大于物理内存数量

程序运行时是有局部性原理,可以将程序最常使用的部分驻留在内存中,将一些不太常用的数据存放在磁盘中,这就是动态装入的基本原理

覆盖装入

覆盖装入将挖掘内存潜力的任务交给了程序员,程序员在编写程序的时候必须手工将程序分割为若干块,然后编写一个覆盖管理器(Ovetlay Manager)来管理模块合适应该驻留在内存何时应该被替换掉

image-20211122131132652

覆盖管理器一般很小,从数十字节到数百字节不等,一般常驻在内存中

一般程序员需要手工将模块按照调用依赖关系组织成树状结构

image-20211122131317183

覆盖管理器必须保证两点

  • 树状结构中从任何一个模块到树的根模块都叫调用路径。当该模块被调用是,整个调用路径上的模块都必须在内存中
  • 禁止跨树间调用。任意一个模块不允许跨过树状结构进行调用,因为覆盖管理器不能保证跨树间的模块能够存在于内存中。可能两个子模块都需要依赖于某个模块,比如模块E和C都需要另外一个模块G,那么最方便的做法就是将模块G并入main模块中

由于跨模块间的调用都需要经过覆盖管理器,以确保所有被调用到的模块都能够正确地驻留在内存中,一旦模块没有在内存中,需要从磁盘或其他存储器读取相应的模块,覆盖装入的速度比较慢。时间换空间

页映射

image-20211122132503313

image-20211122132523837

装载管理器就是现代操作系统的存储管理器

目前几乎所有的主流操作系统都是按照页映射的方式装载可执行文件

从操作系统角度看可执行文件的装载

进程的建立

创建虚拟地址空间

一个虚拟空间有一组页映射函数将虚拟空间中的各个页映射至相应的物理空间,创建一个虚拟空间实际上并不是创建空间而是创建映射函数所需要的相应的数据结构。在i386的Linux下,创建虚拟地址空间实际上只是分配一个页目录,不是指页映射关系,映射关系等后面程序发生页错误时再进行设置

读取可执行文件头,并且建立虚拟空间和可执行文件的映射关系

由于可执行文件在装载时实际上是被映射的虚拟空间,所以可执行文件又被称为映像文件

image-20211122133927741

这种映射关系只是保存在操作系统内部的一个数据结构。Linux中将进程虚拟空间中的一个段叫做虚拟内存区域VMA,Windows中叫虚拟段

在上例中,操作系统创建进程后回在进程相应的数据结构中设置一个有.text段的VMA,在虚拟空间中的地址为0x08048000~0x08049000,对于ELF文件中偏移为0的.text,属性为只读

将CPU指令寄存器设置成可执行文件入口,启动运行

操作系统通过设置CPU的指令寄存器将控制权转交给进程,由此进程开始执行。可以认为操作系统执行了一条跳转指令,直接跳转到可执行文件的入口地址,即ELF文件头中保存的入口地址

页错误

image-20211122134910655

image-20211122134934840

进程虚拟空间分布

ELF文件链接视图和执行视图

当段的数量增多时,就会产生空间浪费的问题。当ELF文件被映射时,是以系统的页长度作为单位的,那么每个段在映射时的长度时系统页长度的整数倍,如果不是,那么多余部分将占用一个页

对于拥有相同权限的段,合并到一起当作一个段进行映射

image-20211122135606135

一个Segment包含一个或多个属性类似的Section

Segment和Section是从不同角度来划分同一个ELF文件,在ELF中被称为不同的视图

从Section角度看ELF文件就是链接视图

从Segment角度看就是执行视图

当讨论ELF装载是,段专门指Segment,其他情况下指Section

ELF可执行文件中有一个专门的数据结构叫程序头表用于保存Segment信息

是一个结构体数组

image-20211122140039241

堆和栈

VMA除了用于映射可执行文件中的各个Segment,还可以对进程的地址空间进行管理

在Linux下可以通过查看/proc查看进程的虚拟空间分布

image-20211122140656362

进程有5个VMA,只有前两个是映射到可执行文件中的Segment,另外三个段的文件所在设备主设备号和次设备号及文件节点号都是0,表示没有映射到文件中,这种VMA为匿名虚拟内存区域

vdso的地址已经在内核空间了(大于0xC0000000的地址),事实上是一个内核的模块,进程可以通过访问这个VMA来与内核进行一些通信

一个进程基本上可以划分为一下几种VMA区域

image-20211122141010790

堆的最大申请数量

image-20211122141228151

malloc的最大申请数量受到操作系统版本、程序本身大小、用到的动态/共享库数量、大小、程序栈数量、大小等

每一次运行结果可能不同,因为操作相同使用随机地址空间分布技术(出于安全考虑,防止程序受恶意攻击),使得进程的堆空间变小

段地址对齐

将段分开映射,长度不足一个页的部分则占一个页,造成内存碎片,浪费磁盘空间

image-20211122141913378

image-20211122141948832

image-20211122142103122

进程栈初始化

进程刚开始启动的时候必须知道一下进程运行的环境,最基本的就是系统环境变量和进程的运行参数

操作系统在进程启动之前将这些信息提前保存到进程的虚拟空间的栈中

image-20211122142423115

image-20211122142448003

进程启动以后,程序的库部分会把堆栈里的初始化信息中的参数信息传递给main()函数的argc和argv参数,这两个参数分别对应命令行参数数量和命令行参数字符串指针数组

Linux内核装载ELF过程简介

首先在用户层面,bash进程会调用fork()系统调用创建一个新的进程,然后新的进程调用execve系统调用执行指定的ELF文件,原先的bash进行继续发挥等待刚才启动的新进程借宿,然后继续等待用户输入命令

在进入execve()系统调用之后,Linux内核就开始进行真正的装载工作

在内核中,execve()系统调用的相应入口是sys_execve(),sys_execve()进行一系列参数的检查复制之后调用do_execve()读取被执行文件的前128个字节判断文件的格式,之后调用search_binary_handle()去搜索和匹配合适的可执行文件装载处理过程。ELF可执行文件的装载处理过程为load_elf_binary(),主要步骤为

image-20211122143450441

当load_elf_binary()执行完毕,返回到do_execve()在返回到sys_execve(),上面的第五步中已经将系统调用的返回地址改成了被装载的ELF程序的入口地址,所以当sys_execve()系统调用从内核态返回到用户态时,EIP寄存器直接跳转到ELF程序的入口地址,于是新的程序开始执行,ELF可执行文件装载完成

Windows PE的装载

image-20211122143747480

image-20211122143802121

改成了被装载的ELF程序的入口地址,所以当sys_execve()系统调用从内核态返回到用户态时,EIP寄存器直接跳转到ELF程序的入口地址,于是新的程序开始执行,ELF可执行文件装载完成

Windows PE的装载

在这里插入图片描述
在这里插入图片描述

image-20211122143814903

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值