手机虚拟空间软件_虚拟空间哪个好用

内存管理模块是操作系统的心脏;它对应用程序和系统管理非常重要 。在这篇文章中,我将着眼于实际的内存问题,但也不避讳其中的技术内幕 。由于不少概念是通用的,所以文中大部分例子取自32位x86平台的Linux和Windows系统 。本系列第一篇文章讲述应用程序的内存布局 。
在多任务操作系统中的每一个进程都运行在一个属于它自己的内存池子中 。这个池子就是虚拟地址空间(virtual address space),在32位模式下它总是一个4GB的内存地址块 。这些虚拟地址通过页表(page table)映射到物理内存,页表由操作系统维护并被处理器引用 。每一个进程拥有一套属于它自己的页表,但是还有一个隐情 。只要虚拟地址被使能,那么它就会作用于这台机器上运行的所有软件,包括内核本身 。因此一部分虚拟地址必须保留给内核使用:
这并不意味着内核使用了那么多的物理内存,仅表示它可支配这么大的地址空间,可根据内核需要,将其映射到物理内存 。内核空间在页表中拥有较高的特权级(ring 2或以下),因此只要用户态的程序试图访问这些页,就会导致一个页错误(page fault) 。在Linux中,内核空间是持续存在的,并且在所有进程中都映射到同样的物理内存 。内核代码和数据总是可寻址的,随时准备处理中断和系统调用 。与此相反,用户模式地址空间的映射随进程切换的发生而不断变化:
蓝色区域表示映射到物理内存的虚拟地址,而白色区域表示未映射的部分 。在上面的例子中,Firefox使用了相当多的虚拟地址空间,因为它是传说中的吃内存大户 。地址空间中的各个条带对应于不同的内存段(memory segment),如:堆、栈之类的 。记住,这些段只是简单的内存地址范围,与Intel处理器的段没有关系 。不管怎样,下面是一个Linux进程的标准的内存段布局:
当计算机开心、安全、可爱、正常的运转时,几乎每一个进程的各个段的起始虚拟地址都与上图完全一致,这也给远程发掘程序安全漏洞打开了方便之门 。一个发掘过程往往需要引用绝对内存地址:栈地址,库函数地址等 。远程攻击者必须依赖地址空间布局的一致性,摸索着选择这些地址 。如果让他们猜个正着,有人就会被整了 。因此,地址空间的随机排布方式逐渐流行起来 。Linux通过对栈、内存映射段、堆的起始地址加上随机的偏移量来打乱布局 。不幸的是,32位地址空间相当紧凑,给随机化所留下的空当不大,削弱了这种技巧的效果 。
进程地址空间中最顶部的段是栈,大多数编程语言将之用于存储局部变量和函数参数 。调用一个方法或函数会将一个新的栈桢(stack frame)压入栈中 。栈桢在函数返回时被清理 。也许是因为数据严格的遵从LIFO的顺序,这个简单的设计意味着不必使用复杂的数据结构来追踪栈的内容,只需要一个简单的指针指向栈的顶端即可 。因此压栈(pushing)和退栈(popping)过程非常迅速、准确 。另外,持续的重用栈空间有助于使活跃的栈内存保持在CPU缓存中,从而加速访问 。进程中的每一个线程都有属于自己的栈 。
通过不断向栈中压入的数据,超出其容量就有会耗尽栈所对应的内存区域 。这将触发一个页故障(page fault),并被Linux的expand_stack()处理,它会调用acct_stack_growth()来检查是否还有合适的地方用于栈的增长 。如果栈的大小低于RLIMIT_STACK(通常是8MB),那么一般情况下栈会被加长,程序继续愉快的运行,感觉不到发生了什么事情 。这是一种将栈扩展至所需大小的常规机制 。然而,如果达到了最大的栈空间大小,就会栈溢出(stack overflow),程序收到一个段错误(Segmentation Fault) 。当映射了的栈区域扩展到所需的大小后,它就不会再收缩回去,即使栈不那么满了 。这就好比联邦预算,它总是在增长的 。
动态栈增长是唯一一种访问未映射内存区域(图中白色区域)而被允许的情形 。其它任何对未映射内存区域的访问都会触发页故障,从而导致段错误 。一些被映射的区域是只读的,因此企图写这些区域也会导致段错误 。
在栈的下方,是我们的内存映射段 。此处,内核将文件的内容直接映射到内存 。任何应用程序都可以通过Linux的mmap()系统调用(实现)或Windows的CreateFileMapping() / MapViewOfFile()请求这种映射 。内存映射是一种方便高效的文件I/O方式,所以它被用于加载动态库 。创建一个不对应于任何文件的匿名内存映射也是可能的,此方法用于存放程序的数据 。在Linux中,如果你通过malloc()请求一大块内存,C运行库将会创建这样一个匿名映射而不是使用堆内存 。'大块'意味着比MMAP_THRESHOLD还大,缺省是128KB,可以通过mallopt()调整 。