A. 现代操作系统中,一般采用()和()两种存储管理方法。—交换和虚拟内存有何不同
现在操作系统中,一般采用比较欢和两组二交换的轻松管理方式,你交换和虚拟内容存在不同,根据他的不同进行交换和模拟的需要。
B. 阐述操作系统是如何对cpu,内存和磁盘进行管理的
cpu:基于优先级的调度算法
内存:虚拟内存、段页式管理
磁盘:磁盘调度算法、文件系统
C. 常用文件存储设备管理方法有哪些操作系统课后题
率主要是通过内存分配功能实现的,内存分配的基本任务是为每道程序( ) 。使每道程序能在不受干扰 的环境下运行,主要是通过( )功能实现的。 Ⅰ.分配内存;Ⅱ.内存保护;Ⅲ.地址映射;Ⅳ.对换;Ⅴ.内存扩充;Ⅵ.逻辑地址到物理地址的变换;Ⅶ.内 存到外存间交换;Ⅷ.允许用户程序的地址空间大于内存空间。 正确答案: Ⅰ;Ⅱ 4 适合多道程序运行的存储管理中,存储
D. linux中使用了什么内存管理方法,为什么
“事实胜于雄辩”,我们用一个小例子(原形取自《User-Level Memory Management》)来展示上面所讲的各种内存区的差别与位置。
进程的地址空间对应的描述结构是“内存描述符结构”,它表示进程的全部地址空间,——包含了和进程地址空间有关的全部信息,其中当然包含进程的内存区域。
进程内存的分配与回收
创建进程fork()、程序载入execve()、映射文件mmap()、动态内存分配malloc()/brk()等进程相关操作都需要分配内存给进程。不过这时进程申请和获得的还不是实际内存,而是虚拟内存,准确的说是“内存区域”。进程对内存区域的分配最终都会归结到do_mmap()函数上来(brk调用被单独以系统调用实现,不用do_mmap()),
内核使用do_mmap()函数创建一个新的线性地址区间。但是说该函数创建了一个新VMA并不非常准确,因为如果创建的地址区间和一个已经存在的地址区间相邻,并且它们具有相同的访问权限的话,那么两个区间将合并为一个。如果不能合并,那么就确实需要创建一个新的VMA了。但无论哪种情况,do_mmap()函数都会将一个地址区间加入到进程的地址空间中--无论是扩展已存在的内存区域还是创建一个新的区域。
同样,释放一个内存区域应使用函数do_ummap(),它会销毁对应的内存区域。
如何由虚变实!
从上面已经看到进程所能直接操作的地址都为虚拟地址。当进程需要内存时,从内核获得的仅仅是虚拟的内存区域,而不是实际的物理地址,进程并没有获得物理内存(物理页面——页的概念请大家参考硬件基础一章),获得的仅仅是对一个新的线性地址区间的使用权。实际的物理内存只有当进程真的去访问新获取的虚拟地址时,才会由“请求页机制”产生“缺页”异常,从而进入分配实际页面的例程。
该异常是虚拟内存机制赖以存在的基本保证——它会告诉内核去真正为进程分配物理页,并建立对应的页表,这之后虚拟地址才实实在在地映射到了系统的物理内存上。(当然,如果页被换出到磁盘,也会产生缺页异常,不过这时不用再建立页表了)
这种请求页机制把页面的分配推迟到不能再推迟为止,并不急于把所有的事情都一次做完(这种思想有点像设计模式中的代理模式(proxy))。之所以能这么做是利用了内存访问的“局部性原理”,请求页带来的好处是节约了空闲内存,提高了系统的吞吐率。要想更清楚地了解请求页机制,可以看看《深入理解linux内核》一书。
这里我们需要说明在内存区域结构上的nopage操作。当访问的进程虚拟内存并未真正分配页面时,该操作便被调用来分配实际的物理页,并为该页建立页表项。在最后的例子中我们会演示如何使用该方法。
系统物理内存管理
虽然应用程序操作的对象是映射到物理内存之上的虚拟内存,但是处理器直接操作的却是物理内存。所以当应用程序访问一个虚拟地址时,首先必须将虚拟地址转化成物理地址,然后处理器才能解析地址访问请求。地址的转换工作需要通过查询页表才能完成,概括地讲,地址转换需要将虚拟地址分段,使每段虚地址都作为一个索引指向页表,而页表项则指向下一级别的页表或者指向最终的物理页面。
每个进程都有自己的页表。进程描述符的pgd域指向的就是进程的页全局目录。下面我们借用《linux设备驱动程序》中的一幅图大致看看进程地址空间到物理页之间的转换关系。
上面的过程说起来简单,做起来难呀。因为在虚拟地址映射到页之前必须先分配物理页——也就是说必须先从内核中获取空闲页,并建立页表。下面我们介绍一下内核管理物理内存的机制。
物理内存管理(页管理)
Linux内核管理物理内存是通过分页机制实现的,它将整个内存划分成无数个4k(在i386体系结构中)大小的页,从而分配和回收内存的基本单位便是内存页了。利用分页管理有助于灵活分配内存地址,因为分配时不必要求必须有大块的连续内存[3],系统可以东一页、西一页的凑出所需要的内存供进程使用。虽然如此,但是实际上系统使用内存时还是倾向于分配连续的内存块,因为分配连续内存时,页表不需要更改,因此能降低TLB的刷新率(频繁刷新会在很大程度上降低访问速度)。
鉴于上述需求,内核分配物理页面时为了尽量减少不连续情况,采用了“伙伴”关系来管理空闲页面。伙伴关系分配算法大家应该不陌生——几乎所有操作系统方面的书都会提到,我们不去详细说它了,如果不明白可以参看有关资料。这里只需要大家明白Linux中空闲页面的组织和管理利用了伙伴关系,因此空闲页面分配时也需要遵循伙伴关系,最小单位只能是2的幂倍页面大小。内核中分配空闲页面的基本函数是get_free_page/get_free_pages,它们或是分配单页或是分配指定的页面(2、4、8…512页)。
注意:get_free_page是在内核中分配内存,不同于malloc在用户空间中分配,malloc利用堆动态分配,实际上是调用brk()系统调用,该调用的作用是扩大或缩小进程堆空间(它会修改进程的brk域)。如果现有的内存区域不够容纳堆空间,则会以页面大小的倍数为单位,扩张或收缩对应的内存区域,但brk值并非以页面大小为倍数修改,而是按实际请求修改。因此Malloc在用户空间分配内存可以以字节为单位分配,但内核在内部仍然会是以页为单位分配的。
另外,需要提及的是,物理页在系统中由页结构structpage描述,系统中所有的页面都存储在数组mem_map[]中,可以通过该数组找到系统中的每一页(空闲或非空闲)。而其中的空闲页面则可由上述提到的以伙伴关系组织的空闲页链表(free_area[MAX_ORDER])来索引。
内核内存使用
Slab
所谓尺有所长,寸有所短。以页为最小单位分配内存对于内核管理系统中的物理内存来说的确比较方便,但内核自身最常使用的内存却往往是很小(远远小于一页)的内存块——比如存放文件描述符、进程描述符、虚拟内存区域描述符等行为所需的内存都不足一页。这些用来存放描述符的内存相比页面而言,就好比是面包屑与面包。一个整页中可以聚集多个这些小块内存;而且这些小块内存块也和面包屑一样频繁地生成/销毁。
为了满足内核对这种小内存块的需要,Linux系统采用了一种被称为slab分配器的技术。Slab分配器的实现相当复杂,但原理不难,其核心思想就是“存储池[4]”的运用。内存片段(小块内存)被看作对象,当被使用完后,并不直接释放而是被缓存到“存储池”里,留做下次使用,这无疑避免了频繁创建与销毁对象所带来的额外负载。
Slab技术不但避免了内存内部分片(下文将解释)带来的不便(引入Slab分配器的主要目的是为了减少对伙伴系统分配算法的调用次数——频繁分配和回收必然会导致内存碎片——难以找到大块连续的可用内存),而且可以很好地利用硬件缓存提高访问速度。
Slab并非是脱离伙伴关系而独立存在的一种内存分配方式,slab仍然是建立在页面基础之上,换句话说,Slab将页面(来自于伙伴关系管理的空闲页面链表)撕碎成众多小内存块以供分配,slab中的对象分配和销毁使用kmem_cache_alloc与kmem_cache_free。
Kmalloc
Slab分配器不仅仅只用来存放内核专用的结构体,它还被用来处理内核对小块内存的请求。当然鉴于Slab分配器的特点,一般来说内核程序中对小于一页的小块内存的请求才通过Slab分配器提供的接口Kmalloc来完成(虽然它可分配32到131072字节的内存)。从内核内存分配的角度来讲,kmalloc可被看成是get_free_page(s)的一个有效补充,内存分配粒度更灵活了。
有兴趣的话,可以到/proc/slabinfo中找到内核执行现场使用的各种slab信息统计,其中你会看到系统中所有slab的使用信息。从信息中可以看到系统中除了专用结构体使用的slab外,还存在大量为Kmalloc而准备的Slab(其中有些为dma准备的)。
内核非连续内存分配(Vmalloc)
伙伴关系也好、slab技术也好,从内存管理理论角度而言目的基本是一致的,它们都是为了防止“分片”,不过分片又分为外部分片和内部分片之说,所谓内部分片是说系统为了满足一小段内存区(连续)的需要,不得不分配了一大区域连续内存给它,从而造成了空间浪费;外部分片是指系统虽有足够的内存,但却是分散的碎片,无法满足对大块“连续内存”的需求。无论何种分片都是系统有效利用内存的障碍。slab分配器使得一个页面内包含的众多小块内存可独立被分配使用,避免了内部分片,节约了空闲内存。伙伴关系把内存块按大小分组管理,一定程度上减轻了外部分片的危害,因为页框分配不在盲目,而是按照大小依次有序进行,不过伙伴关系只是减轻了外部分片,但并未彻底消除。你自己比划一下多次分配页面后,空闲内存的剩余情况吧。
所以避免外部分片的最终思路还是落到了如何利用不连续的内存块组合成“看起来很大的内存块”——这里的情况很类似于用户空间分配虚拟内存,内存逻辑上连续,其实映射到并不一定连续的物理内存上。Linux内核借用了这个技术,允许内核程序在内核地址空间中分配虚拟地址,同样也利用页表(内核页表)将虚拟地址映射到分散的内存页上。以此完美地解决了内核内存使用中的外部分片问题。内核提供vmalloc函数分配内核虚拟内存,该函数不同于kmalloc,它可以分配较Kmalloc大得多的内存空间(可远大于128K,但必须是页大小的倍数),但相比Kmalloc来说,Vmalloc需要对内核虚拟地址进行重映射,必须更新内核页表,因此分配效率上要低一些(用空间换时间)
与用户进程相似,内核也有一个名为init_mm的mm_strcut结构来描述内核地址空间,其中页表项pdg=swapper_pg_dir包含了系统内核空间(3G-4G)的映射关系。因此vmalloc分配内核虚拟地址必须更新内核页表,而kmalloc或get_free_page由于分配的连续内存,所以不需要更新内核页表。
vmalloc分配的内核虚拟内存与kmalloc/get_free_page分配的内核虚拟内存位于不同的区间,不会重叠。因为内核虚拟空间被分区管理,各司其职。进程空间地址分布从0到3G(其实是到PAGE_OFFSET,在0x86中它等于0xC0000000),从3G到vmalloc_start这段地址是物理内存映射区域(该区域中包含了内核镜像、物理页面表mem_map等等)比如我使用的系统内存是64M(可以用free看到),那么(3G——3G+64M)这片内存就应该映射到物理内存,而vmalloc_start位置应在3G+64M附近(说"附近"因为是在物理内存映射区与vmalloc_start期间还会存在一个8M大小的gap来防止跃界),vmalloc_end的位置接近4G(说"接近"是因为最后位置系统会保留一片128k大小的区域用于专用页面映射,还有可能会有高端内存映射区,这些都是细节,这里我们不做纠缠)。
上图是内存分布的模糊轮廓
由get_free_page或Kmalloc函数所分配的连续内存都陷于物理映射区域,所以它们返回的内核虚拟地址和实际物理地址仅仅是相差一个偏移量(PAGE_OFFSET),你可以很方便的将其转化为物理内存地址,同时内核也提供了virt_to_phys()函数将内核虚拟空间中的物理映射区地址转化为物理地址。要知道,物理内存映射区中的地址与内核页表是有序对应的,系统中的每个物理页面都可以找到它对应的内核虚拟地址(在物理内存映射区中的)。
而vmalloc分配的地址则限于vmalloc_start与vmalloc_end之间。每一块vmalloc分配的内核虚拟内存都对应一个vm_struct结构体(可别和vm_area_struct搞混,那可是进程虚拟内存区域的结构),不同的内核虚拟地址被4k大小的空闲区间隔,以防止越界——见下图)。与进程虚拟地址的特性一样,这些虚拟地址与物理内存没有简单的位移关系,必须通过内核页表才可转换为物理地址或物理页。它们有可能尚未被映射,在发生缺页时才真正分配物理页面。
这里给出一个小程序帮助大家认清上面几种分配函数所对应的区域。
#include<linux/mole.h>
#include<linux/slab.h>
#include<linux/vmalloc.h>
unsignedchar*pagemem;
unsignedchar*kmallocmem;
unsignedchar*vmallocmem;
intinit_mole(void)
{
pagemem = get_free_page(0);
printk("<1>pagemem=%s",pagemem);
kmallocmem = kmalloc(100,0);
printk("<1>kmallocmem=%s",kmallocmem);
vmallocmem = vmalloc(1000000);
printk("<1>vmallocmem=%s",vmallocmem);
}
voidcleanup_mole(void)
{
free_page(pagemem);
kfree(kmallocmem);
vfree(vmallocmem);
}
实例
内存映射(mmap)是Linux操作系统的一个很大特色,它可以将系统内存映射到一个文件(设备)上,以便可以通过访问文件内容来达到访问内存的目的。这样做的最大好处是提高了内存访问速度,并且可以利用文件系统的接口编程(设备在Linux中作为特殊文件处理)访问内存,降低了开发难度。许多设备驱动程序便是利用内存映射功能将用户空间的一段地址关联到设备内存上,无论何时,只要内存在分配的地址范围内进行读写,实际上就是对设备内存的访问。同时对设备文件的访问也等同于对内存区域的访问,也就是说,通过文件操作接口可以访问内存。Linux中的X服务器就是一个利用内存映射达到直接高速访问视频卡内存的例子。
熟悉文件操作的朋友一定会知道file_operations结构中有mmap方法,在用户执行mmap系统调用时,便会调用该方法来通过文件访问内存——不过在调用文件系统mmap方法前,内核还需要处理分配内存区域(vma_struct)、建立页表等工作。对于具体映射细节不作介绍了,需要强调的是,建立页表可以采用remap_page_range方法一次建立起所有映射区的页表,或利用vma_struct的nopage方法在缺页时现场一页一页的建立页表。第一种方法相比第二种方法简单方便、速度快,但是灵活性不高。一次调用所有页表便定型了,不适用于那些需要现场建立页表的场合——比如映射区需要扩展或下面我们例子中的情况。
我们这里的实例希望利用内存映射,将系统内核中的一部分虚拟内存映射到用户空间,以供应用程序读取——你可利用它进行内核空间到用户空间的大规模信息传输。因此我们将试图写一个虚拟字符设备驱动程序,通过它将系统内核空间映射到用户空间——将内核虚拟内存映射到用户虚拟地址。从上一节已经看到Linux内核空间中包含两种虚拟地址:一种是物理和逻辑都连续的物理内存映射虚拟地址;另一种是逻辑连续但非物理连续的vmalloc分配的内存虚拟地址。我们的例子程序将演示把vmalloc分配的内核虚拟地址映射到用户地址空间的全过程。
程序里主要应解决两个问题:
第一是如何将vmalloc分配的内核虚拟内存正确地转化成物理地址?
因为内存映射先要获得被映射的物理地址,然后才能将其映射到要求的用户虚拟地址上。我们已经看到内核物理内存映射区域中的地址可以被内核函数virt_to_phys转换成实际的物理内存地址,但对于vmalloc分配的内核虚拟地址无法直接转化成物理地址,所以我们必须对这部分虚拟内存格外“照顾”——先将其转化成内核物理内存映射区域中的地址,然后在用virt_to_phys变为物理地址。
转化工作需要进行如下步骤:
找到vmalloc虚拟内存对应的页表,并寻找到对应的页表项。
获取页表项对应的页面指针
通过页面得到对应的内核物理内存映射区域地址。
如下图所示:
第二是当访问vmalloc分配区时,如果发现虚拟内存尚未被映射到物理页,则需要处理“缺页异常”。因此需要我们实现内存区域中的nopaga操作,以能返回被映射的物理页面指针,在我们的实例中就是返回上面过程中的内核物理内存映射区域中的地址。由于vmalloc分配的虚拟地址与物理地址的对应关系并非分配时就可确定,必须在缺页现场建立页表,因此这里不能使用remap_page_range方法,只能用vma的nopage方法一页一页的建立。
程序组成
map_driver.c,它是以模块形式加载的虚拟字符驱动程序。该驱动负责将一定长的内核虚拟地址(vmalloc分配的)映射到设备文件上。其中主要的函数有——vaddress_to_kaddress()负责对vmalloc分配的地址进行页表解析,以找到对应的内核物理映射地址(kmalloc分配的地址);map_nopage()负责在进程访问一个当前并不存在的VMA页时,寻找该地址对应的物理页,并返回该页的指针。
test.c它利用上述驱动模块对应的设备文件在用户空间读取读取内核内存。结果可以看到内核虚拟地址的内容(ok!),被显示在了屏幕上。
执行步骤
编译map_driver.c为map_driver.o模块,具体参数见Makefile
加载模块:insmodmap_driver.o
生成对应的设备文件
1在/proc/devices下找到map_driver对应的设备命和设备号:grepmapdrv/proc/devices
2建立设备文件mknodmapfilec 254 0(在我的系统里设备号为254)
利用maptest读取mapfile文件,将取自内核的信息打印到屏幕上。
E. 系统内存怎么管理
一、 进程的虚拟地址空间每个进程都被赋予自己的虚拟地址空间,对于32位进程来说,这个地址空间为4G,因此进程中的地址可以为0X00000000至0XFFFFFFFF之间的任何一个值。其中4G空间中的低区的2G空间留给进程使用,而高区的2G空间则留给系统使用。在Windows2000下,分区情况为:1、 NULL指针分配的分区NULL指针分配的分区占据0x00000000至0x0000FFFF,该分区的设置是为了帮助程序员掌握NULL指针的分配情况。例如当malloc或new分配失败时就返回NULL,该分区的地址是禁止进入的,访问该分区内的地址将发生访问违规现象,同时终止该进程的运行。2、 用户方式分区该分区是进程私有的地址空间,对于应用程序来讲,该分区是维护进程的大部分数据的地方,所有的.exe和dll模块均加载在这个分区,每个进程可以将dll加载到不同的地址中。对于.exe而言,一般加载到0x00400000地址处。进程的栈和堆也是在该分区中开辟的。3、 禁止进入分区该分区是用户方式分区上禁止进入的分区,位于0x7FFF0000至0x7FFFFFFF,访问该分区的任何企图均将导致访问违规,该分区的存在是为了防止用户从用户区访问内核区的代码。如:BYTE buf[70000];DWORD numWritten;WriteProcessMemory(GetCurrentProces(), (PVOID)0x7FFEEE90, buf, sizeof(buf), &numWritten);将有可能成功地将数据写入只能有内核方式代码访问的内存。4、 内核方式分区该分区是存放操作系统代码的地方,用于线程调度、内存管理、文件系统支持、网络支持和所有设备驱动程序的代码全部在这个分区加载驻留在该分区的一切均可被所有近进程共享。二、 虚拟内存的实现本质上虚拟内存就是要让一个程序的代码和数据再没有全部载入内存的情况下运行程序。运行过程中,当执行到尚未载入的内存代码,或者要访问还没有载入到内存的数据时,虚拟内存管理器动态的将这部分代码或数据从硬盘载入到内存中。而且在通常情况下,虚拟内存管理器也会相应的将内存中某些代码或者数据置换到硬盘中,为即将载入的代码或数据腾出地方。Win32中用来辅助实现虚拟内存的硬盘文件称为“调页文件”,调页文件用来存放被虚拟内存管理器置换处内存的数据,当这些数据再次被进程访问时,虚拟内存管理器会先将它们从调页文件中置换进内存,这样进程可以正确访问这些数据。程序代码(包括exe和dll文件)不会被修改,所以他们所在的页被置换处内存时,并不会被写进调页文件中,而是直接抛弃,当再次被需要时,虚拟内存管理器直接从存放他们的exe或dll文件中找到他们并调入内存。三、 虚拟内存的状态近处虚拟地址空间中的页有三种状态:自由(free)、预留(reserved)和提交(committed)。1、自由自由表示此页尚未分配出去,可以用来满足新的内存分配请求。2、预留预留是指从虚拟地址空间中划出一块区域(页的整数倍大小),划出之后的区域中的页不能用来满足新的内存分配请求,只是用来供要求“预留”此段区域的代码以后使用,预留状态的区域并没有分配物理存储,只是增加了一个描述进程虚拟地址空姐使用状态的数据结构,用来记录这块区域已倍预留。 3、提交提交内存时会从调页文件中开辟空间,并修改VAD中的相应项。当然,此时并没有立刻从物理内存中分配空间,只是从磁盘的调页文件中开辟空间。当有代码第一次访问这段提交内存中的某些数据时,系统发现并没有真正的物理内存,抛出缺页异常,虚拟内存管理器处理该异常,此时才会真正的分配物理内存。四、 虚拟内存状态的管理Win32虚拟内存管理器使用一个数据结构(Virtual Address Descriptor, VAD)来记录和维护每个进程4G的虚拟地址空姐的使用及状态,美国进程都有自己的VAD集合,这个集合被组织成一个自平衡二叉树。只有预留或提交的内存块才有VAD,自由的内存块没有VAD,因此不在VAD数据结构中的虚拟地址就是自由的。每块VAD数据结构记录了该块的起始地址以及当前状态。五、 访问虚拟内存时的处理流程对某虚拟内存区域进行了预留并提交之后,就可以对该区域中的数据进行访问了,一下为访问虚拟内存时的处理流程。1、当被访问数据已在物理内存时,虚拟内存管理器只需记那个该数据的虚拟地址映射为物理指针。2、当被访问的数据不在物理内存时,检测此数据是否在调页文件中或者在exe或dll文件中,若存在则进入下一步,否则发生访问违例错误,进程退出。3、判断内存中是否有空闲页,若存在空闲页,则直接将调页文件或exe或dll文件的数据加载到该空闲页中。 虚拟内存管理器维护了一个称为“页帧数据库page-frame database”的数据结构,该数据结构时操作系统全局的,当系统启动时被初始化,用来跟踪和记录物理内存真能干的每个页的状态。4、当内存中不存在空闲页时,根据调页算法,首先选择出某个页作为换出页。5、判断此页自上次调进后是否修改过,若没有被修改过,则将需要访问的数据调入此页,若被修改过,则需要先将此页的内容写到调页文件与此页相对应的备份页中,并随即将此页标记为空闲页。六、 虚拟地址到物理地址的映射当访问的数据已在物理内存中后,需要将虚拟地址转换为物理地址,即“地址映射”,才能真正访问此数据。 每个进程都维护一套自己的层次表结构来实现地址映射。第一层称为“目录表”,实际上是一个内存页,以四个字节为单元分成1024个项,每一项称为“页目录项”。第二层称为“页表”,共有1024个页表,页目录表中的每个目录表项对应这一层中的某个页表,每个页表也占一个内存也,同样被分成1024项,表页的每一项称为“页表项”,每个页表项指向物理内存中的某一个页帧。第三层称为页帧,是真正存数数据的地方。当进行地址映射的时候,对应于层次表中的三个部分,页将32位的地址分成三个部分,最高的十位对应页目录下标,次高的十位对应页表的下标,低位的12位对应字节下标作为页内寻址。
F. linux内核物理内存管理有哪些常用算法 lru slab
采用伙伴算法分配内存时,每次至少分配一个页面。但当请求分配的内存大小为几十个字节或几百个字节时应该如何处理?如何在一个页面中分配小的内存区,小内存区的分配所产生的内碎片又如何解决?
Linux2.0采用的解决办法是建立了13个空闲区链表,它们的大小从32字节到132056字节。从Linux2.2开始,MM的开发者采用了一种叫做slab的分配模式,该模式早在1994年就被开发出来,用于Sun Microsystem Solaris 2.4操作系统中。Slab的提出主要是基于以下考虑:
· 内核对内存区的分配取决于所存放数据的类型。例如,当给用户态进程分配页面时,内核调用get_free_page()函数,并用0填充这个页面。 而给内核的数据结构分配页面时,事情没有这么简单,例如,要对数据结构所在的内存进行初始化、在不用时要收回它们所占用的内存。因此,Slab中引入了对象这个概念,所谓对象就是存放一组数据结构的内存区,其方法就是构造或析构函数,构造函数用于初始化数据结构所在的内存区,而析构函数收回相应的内存区。但为了便于理解,你也可以把对象直接看作内核的数据结构。为了避免重复初始化对象,Slab分配模式并不丢弃已分配的对象,而是释放但把它们依然保留在内存中。当以后又要请求分配同一对象时,就可以从内存获取而不用进行初始化,这是在Solaris 中引入Slab的基本思想。
实际上,Linux中对Slab分配模式有所改进,它对内存区的处理并不需要进行初始化或回收。出于效率的考虑,Linux并不调用对象的构造或析构函数,而是把指向这两个函数的指针都置为空。Linux中引入Slab的主要目的是为了减少对伙伴算法的调用次数。
· 实际上,内核经常反复使用某一内存区。例如,只要内核创建一个新的进程,就要为该进程相关的数据结构(task_struct、打开文件对象等)分配内存区。当进程结束时,收回这些内存区。因为进程的创建和撤销非常频繁,因此,Linux的早期版本把大量的时间花费在反复分配或回收这些内存区上。从Linux2.2开始,把那些频繁使用的页面保存在高速缓存中并重新使用。
· 可以根据对内存区的使用频率来对它分类。对于预期频繁使用的内存区,可以创建一组特定大小的专用缓冲区进行处理,以避免内碎片的产生。对于较少使用的内存区,可以创建一组通用缓冲区(如Linux2.0中所使用的2的幂次方)来处理,即使这种处理模式产生碎片,也对整个系统的性能影响不大。
· 硬件高速缓存的使用,又为尽量减少对伙伴算法的调用提供了另一个理由,因为对伙伴算法的每次调用都会“弄脏”硬件高速缓存,因此,这就增加了对内存的平均访问次数。
Slab分配模式把对象分组放进缓冲区(尽管英文中使用了Cache这个词,但实际上指的是内存中的区域,而不是指硬件高速缓存)。因为缓冲区的组织和管理与硬件高速缓存的命中率密切相关,因此,Slab缓冲区并非由各个对象直接构成,而是由一连串的“大块(Slab)”构成,而每个大块中则包含了若干个同种类型的对象,这些对象或已被分配,或空闲,如图6.12所示。一般而言,对象分两种,一种是大对象,一种是小对象。所谓小对象,是指在一个页面中可以容纳下好几个对象的那种。例如,一个inode结构大约占300多个字节,因此,一个页面中可以容纳8个以上的inode结构,因此,inode结构就为小对象。Linux内核中把小于512字节的对象叫做小对象。
G. 操作系统的内存分配一般有哪几种方式,各有什么优缺点
一、程序载入
在早期的计算机中,要运行一个程序,会把这些程序全都装入内存,程序都是直接运行在内存上的,也就是说程序中访问的内存地址都是实际的物理内存地址。当计算机同时运行多个程序时,必须保证这些程序用到的内存总量要小于计算机实际物理内存的大小。
这样会造成的问题有:
(1):进程地址空间不隔离。由于程序都是直接访问物理内存,所以恶意程序可以随意修改别的进程的内存数据,以达到破坏的目的。有些非恶意的,但是有bug的程序也可能不小心修改了其它程序的内存数据,就会导致其它程序的运行出现异常。
(2):内存使用效率低。有大量的数据在装入装出,导致效率十分低下。
(3):程序运行的地址不确定。因为是随机分配的,所以程序运行的地址是不确定的。
二、虚拟内存
为了解决上述问题,人们想到了一种变通的方法,就是增加一个中间层,利用一种间接的地址访问方法访问物理内存。按照这种方法,程序中访问的内存地址不再是实际的物理内存地址,而是一个虚拟地址,然后由操作系统将这个虚拟地址映射到适当的物理内存地址上。这样,只要操作系统处理好虚拟地址到物理内存地址的映射,就可以保证不同的程序最终访问的内存地址位于不同的区域,彼此没有重叠,就可以达到内存地址空间隔离的效果。人们之所以要创建一个虚拟地址空间,目的是为了解决进程地址空间隔离的问题。但程序要想执行,必须运行在真实的内存上,所以,必须在虚拟地址与物理地址间建立一种映射关系。这样,通过映射机制,当程序访问虚拟地址空间上的某个地址值时,就相当于访问了物理地址空间中的另一个值。人们想到了一种分段(Sagmentation)的方法,它的思想是在虚拟地址空间和物理地址空间之间做一一映射。
这种分段的映射方法虽然解决了上述中的问题一和问题三,但并没能解决问题二,即内存的使用效率问题。在分段的映射方法中,每次换入换出内存的都是整个程序,这样会造成大量的磁盘访问操作,导致效率低下。所以这种映射方法还是稍显粗糙,粒度比较大。实际上,程序的运行有局部性特点,在某个时间段内,程序只是访问程序的一小部分数据,也就是说,程序的大部分数据在一个时间段内都不会被用到。基于这种情况,人们想到了粒度更小的内存分割和映射方法,这种方法就是分页(Paging)。
(三):分页
分页的基本方法是,将地址空间分成许多的页。每页的大小由CPU决定,然后由操作系统选择页的大小。目前Inter系列的CPU支持4KB或4MB的页大小,而PC上目前都选择使用4KB。按这种选择,4GB虚拟地址空间共可以分成1048576个页,512M的物理内存可以分为131072个页。显然虚拟空间的页数要比物理空间的页数多得多。分页方法的核心思想就是当可执行文件执行到第x页时,就为第x页分配一个内存页y,然后再将这个内存页添加到进程虚拟地址空间的映射表中,这个映射表就相当于一个y=f(x)函数。应用程序通过这个映射表就可以访问到x页关联的y页了。
一 页式管理
1 页式管理的基本原理将各进程的虚拟空间划分成若干个长度相等的页(page),页式管理把内存空间按页的大小划分成片或者页面(page frame),然后把页式虚拟地址与内存地址建立一一对应页表,并用相应的硬件地址变换机构,来解决离散地址变换问题。页式管理采用请求调页或预调页技术实现了内外存存储器的统一管理。 它分为
1 静态页式管理。静态分页管理的第一步是为要求内存的作业或进程分配足够的页面。系统通过存储页面表、请求表以及页表来完成内存的分配工作。静态页式管理解决了分区管理时的碎片问题。但是,由于静态页式管理要求进程或作业在执行前全部装入内存,如果可用页面数小于用户要求时,该作业或进程只好等待。而且作业和进程的大小仍受内存可用页面数的限制。
2 动态页式管理。动态页式管理是在静态页式管理的基础上发展起来的。它分为请求页式管理和预调入页式管理。
优点: 没有外碎片,每个内碎片不超过页大小。一个程序不必连续存放。便于改变程序占用空间的大小(主要指随着程序运行而动态生成的数据增多,要求地址空间相应增长,通常由系统调用完成而不是操作系统自动完成)。
缺点:程序全部装入内存。
要求有相应的硬件支持。例如地址变换机构,缺页中断的产生和选择淘汰页面等都要求有相应的硬件支持。这增加了机器成本。增加了系统开销,例如缺页中断处理机,请求调页的算法如选择不当,有可能产生抖动现象。 虽然消除了碎片,但每个作业或进程的最后一页内总有一部分空间得不到利用果页面较大,则这一部分的损失仍然较大。
二 段式管理的基本思想
把程序按内容或过程(函数)关系分成段,每段有自己的名字。一个用户作业或进程所包含的段对应一个二维线形虚拟空间,也就是一个二维虚拟存储器。段式管理程序以段为单位分配内存,然后通过地址影射机构把段式虚拟地址转换为实际内存物理地址。
程序通过分段(segmentation)划分为多个模块,如代码段、数据段、共享段。其优点是: 可以分别编写和编译。 可以针对不同类型的段采取不同的保护。 可以按段为单位来进行共享,包括通过动态链接进行代码共享。
三 段页式管理的实现原理
1 虚地址的构成
一个进程中所包含的具有独立逻辑功能的程序或数据仍被划分为段,并有各自的段号s。这反映相继承了段式管理的特征。其次,对于段s中的程序或数据,则按照一定的大小将其划分为不同的页。和页式系统一样,最后不足一页的部分仍占一页。这反映了段页式管理中的页式特征。从而,段页式管理时的进程的虚拟地址空间中的虚拟地址由三部分组成:即段号s,页号P和页内相对地址d。虚拟空间的最小单位是页而不是段,从而内存可用区也就被划分成为着干个大小相等的页面,且每段所拥有的程序和数据在内存中可以分开存放。分段的大小也不再受内存可用区的限制。
2 段表和页表
为了实现段页式管理,系统必须为每个作业或进程建立一张段表以管理内存分配与释放、缺段处理、存储保护相地址变换等。另外,由于一个段又被划分成了若干页,每个段又必须建立一张页表以把段中的虚页变换成内存中的实际页面。显然,与页式管理时相同,页表中也要有相应的实现缺页中断处理和页面保护等功能的表项。另外,由于在段页式管理中,页表不再是属于进程而是属于某个段,因此,段表中应有专项指出该段所对应页表的页表始址和页表长度。
3 动态地址变换过程
在一般使用段页式存储管理方式的计算机系统中,都在内存中辟出一块固定的区域存放进程的段表和页表。因此,在段页式管理系统中,要对内存中指令或数据进行一次存取的话,至少需要访问三次以上的内存:
第一次是由段表地址寄存器得段表始址后访问段表,由此取出对应段的页表在内存中的地址。
第二次则是访问页表得到所要访问的物理地址。
第三次才能访问真正需要访问的物理单元。
显然,这将使CPU的执行指令速度大大降低。为了提高地址转换速度,设置快速联想寄存器就显得比段式管理或页式管理时更加需要。在快速联想寄存器中,存放当前最常用的段号s、页号p和对应的内存页面与其它控制用栏目。当要访问内存空间某一单元时,可在通过段表、页表进行内存地址查找的同时,根据快速联想寄存器查找其段号和页号。如果所要访问的段或页在快速联想寄存器中,则系统不再访问内存中的段表、页表而直接把快速联想寄存器中的值与页内相对地址d拼接起来得到内存地址。
总之,因为段页式管理是段式管理的页式管理方案结合而成的,所以具有它们二者的优点。但反过来说,由于管理软件的增加,复杂性和开销也就随之增加了。另外,需要的硬件以及占用的内存也有所增加。更重要的是,如果不采用联想寄存器的方式提高CPU的访内速度,将会使得执行速度大大下降