赞
踩
存储器映射
虚拟存储器区域 与 一个磁盘上的对象关联起来,用这个关联对象来初始化这个虚拟存储器区域的内容,这个过程叫做存储器映射【存储器强调的是对虚拟存储器区域的初始化】;
虚拟存储器区域与普通的文件映射,比如与可执行文件的映射,这时可执行文件中的程序头已经包含了可执行文件与虚拟空间地址的映射关系,这个程序头中指明了可执行文件的各个段映射到虚拟存储器空间的虚拟地址 以及 读写权限等信息。这就是ELF中的映射信息。虚拟存储器只不过根据块的大小将这些规划好的段(区域)再映射一下而已。
虚拟存储器区域还可以映射到一个全0的匿名文件(使用该虚拟区域时,用这个全0的匿名文件初始化);
其实不论如何映射(映射只是在使用到该分配且未缓存的虚拟区域时,用对应的映射区域进行初始化对应的物理页而已,一旦一个虚拟页被初始化了,他就在一个由内核维护的专门的交换文件(swap file)之间进行换来换去【这里强调的是虚拟页对应的物理页初始化后,换进换出的过程】。这个交换文件就是交换空间(交换区域)。因此交换空间限制了当前运行着的(所有)进程能够分配的虚拟页面的总是。
共享对象
由于很多的进程包含有公共的只读文件区域等(如C库中printf等),如果每个进程都在屋里存储器重包含这样的常用代码,那将是一种浪费。
存储器映射提供了一种机制,用来控制多个进程如何共享对象;
我们可以将一个对象映射到虚拟存取器区域中去,且这个对象要么是共享对象要么是私有对象;
共享对象:多个进程将这个共享对象映射到各自的虚拟空间,且各进程共享同一个共享对象的物理空间,某个进程进程对该共享对象的写操作,其他的进程是可见的,且写操作会反映在磁盘原始文件中;每个对象有唯一的文件名,内核可迅速判定进程1已映射该对象,且进程2的页表条目指向相应的物理页面,关键是即使对象被映射到多个进程的共享区域,物理存储器中也只存放共享对象的一个拷贝私有对象:私有对象使用写时拷贝的方法,物理存储器只保持私有对象的一份拷贝,这个各个进程共享这个对象的一个物理拷贝
Fork函数:
一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。注意,子进程持有的是上述存储空间的“副本”,这意味着父子进程间不共享这些存储空间。
当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给这个新进程创建虚拟存储器,它创建了当前进程的mm_struct、区域结构和页表的原样拷贝。它将两个进程中的每个页面都为标记只读,并将两个进程中的每个区域结构都标记为私有的写时拷贝。
当fork在新进程中返回时,新进程现在的虚拟存储器刚好和调用fork时存在的虚拟存储器相同。当这两个进程中的任一个后来进行写操作时,写时拷贝机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。
execve函数:
execve("a.out",NULL,NULL) ;
execve函数在当前进程中加载并运行包含在可执行目标文件a.out中的程序,用a.out程序有效地替代了当前程序。加载并运行a.out需要以下几个步骤:
删除已存在的用户区域。删除当前进程虚拟地址用户部分中的已存在的区域结构。
映射私有区域。为新程序的文本、数据、bss和栈区域创建新的区域结构。所有这些新的区域都是私有的、写时拷贝的。文本和数据区域被映射为a.out文件中的文本和数据区。bss区域是请求二进制零的,映射到匿名文件,其大小包含在a.out中。栈和堆区域也是请求二进制零的。
映射共享区域。如果a.out程序与共享对象(或目标)链接,比如标准C库libc.so,那么这些对象都是动态链接到这个程序的,然后再映射到用户虚拟地址空间中的共享区域内。
设置程序计数器(PC)。execve做的最后一件事情就是设置当前进程上下文中的程序计数器,使之指向文本区域的入口点。
下一次调度这个进程时,它将从这个入口点开始执行。Linux将根据需要换入代码和数据页面。使用mmap函数的用户级存储器映射:
[cpp] view plaincopy
#include
#include
void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offset) ;
//返回:若成功时则为指向映射区域的指针,若出错则为MAP_FAILED(-1)
mmap函数要求内核创建一个新的虚拟存储器区域是,最好是从地址start开始的一个区域,并将文件描述符fd指定的对象的一个连续的片(chunk)映射到这个新区域。连续的对象片大小为length字节,从距文件开始处偏移量为offset字节的地方开始。start地址仅仅是一个暗示,通常被定义为NULL。
munmap函数删除虚拟存储器的区域:
#include
#include
int munmap( void *start, size_t length);
Munmap函数删除从虚拟地址start开始的,由接下来的length字节组成的区域。接下来对已删除区域的引用会导致段错误。
动态存储器分配
虽可通过低级的mmap、munmap函数来创建与删除虚拟存取器的区域,但是用动态存储器分配器更方便,更易于移植;
动态存储器分配器维护着一个进程的虚拟存储器区域—堆heap; 堆紧接着未初始化的bss区域后面,并向上生长,对每个进程,内核维护着一个变量brk,他指向堆的顶部;
分配器的分类:
显式分配器:要求应用显式地释放任何已分配的块。C程序通过调用malloc函数来分配一个块,并通过调用free函数来释放一个块。
隐式分配器:要求适配器检测一个已分配块何时不再被程序所使用,那么就释放这个块。隐式分配器也叫垃圾收集器(java),而自动释放未使用的已分配的块的过程叫做垃收集。
C语音用malloc free等来从堆中分配虚拟空间;
Linux进程的虚拟存储器
简单来讲,程序的装入到运行的主要包含以下几个步骤:
1:读入可执行文件的头部信息以确定其文件格式及地址空间的大小;
2:以段的形式划分地址空间;
3:将可执行程序读入地址空间中的各个段,建立虚实地址间的映射关系;
4:将bbs段清零;
5:创建堆栈段;
6:建立程序参数、环境变量等程序运行过程中所需的信息;
7:启动运行。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。