`

深入理解Linux内核--内存(阅读笔记)(原创)

 
阅读更多


深入理解Linux内核--内存(阅读笔记)(原创)

 

 

 

由  王宇 原创并发布

 

 

第二章内存寻址

    1、内存地址

        [1]逻辑地址

            每个逻辑地址都由一个段(segment)和偏移量(offset或displacement)组成,偏移量指明了从段开始的地方到实际地址之间的距离

        [2]线性地址

            是一个32为无符号整数,可以用来表示高达4GB的地址

        [3]物理地址

            用于内存芯片级内存单元寻址。它们与从微处理器的地址引脚发送到内存总线上的电信号相对应。物理地址由32位或36位无符号整数表示

        内存控制单元(MMU)通过一种称为分段单元的硬件电路把一个逻辑地址转换成线性地址;接着,第二个称为分页单元的硬件电路把线性地址转换成一个物理地址。 参见图:2-1 p41



 

    2、硬件中的分段

        实模式和保护模式

        (1)段选择符和段寄存器

            段选择符是一个16位长的字段,称为选择符(SegmentSeletor)   

            为了快速方便地找到段选择符,处理器提供段寄存器,段寄存器的唯一目的是存放段选择符。这些段寄存器称为cs,ss,ds,es,fs和gs

        (2)段描述符

            每个段由一个8字节的段描述符表示,它描述了段的特征。段描述符放在全局描述符表(GlobalDescriptorTable,GDT)或局部描述符表(LocalDescriptorTable,LDT)中。参考表2-1 p43

            Linux中被广泛采用的类型:

                代码段描述符

                数据段描述符

                任务状态段描述符

        (3)快速访问段描述符

            每当一个段选择符被装入段寄存器时,相应的段描述符就由内存装入到对应的非编程CPU寄存器。从那时起,针对哪个段的逻辑地址转换就可以不访问主存中的GDT或LDT,处理器只需直接引用存放段描述符的CPU寄存器即可。仅当段寄存器的内容改变时,才有必要访问GDT或LDT

        (4)分段单元***

            参考图2-5,显示一个逻辑地址是怎样转换成相应的线性地址的


 
    3、Linux中的分段

        分段和分页某种程度上有点多余:分段给一个进程分配不同的线性地址空间,分页可以把同一线性地址空间映射到不同的物理空间。Linux更喜欢分页方式

        Linux进程都使用一对相同的段来对指令和数据寻址。

        四个主要的Linux段

            用户代码段:__USER_CS

            用户数据段:__USER_DS

            内核代码段:__KERNEL_CS

            内核数据段:__KERNEL_DS


        (1)LinuxGDT

        (2)LinuxLDT

    4、硬件中的分页***

        分页单元(pagingunit)把线性地址转换成物理地址。为了效率起见,线性地址被分成以固定长度为单位的组,称为页(page).页内部连续的线性地址被映射到连续的物理地址中。分页单元把所有的RAM分成固定长度的页框(pageframe)(有时叫做物理页)。每一个页框包含一个页,也就是说一个页框的长度与一个页的长度一致。页框是主存的一部分,因此也是一个存储区域。区分一页和一个页框是很重要的,前者只是一个数据块,可以存放在任何页框或磁盘中

        把线性地址映射到物理地址的数据结构称为页表(pagetable).页表存放在主存中,并在启动分页单元之前必须由内核对页表进行适当的初始化

        (1)常规分页:

            32位的线性地址被分成3个域:(参考图:2-7)


 
                目录:

                页表:

                偏移量:

        二级模式的目的在于减少每个进程页表所需RAM的数量


        每个活动进程必须有一个分配给它的页目录。没有必要马上为进程的所有页表都分配RAM

        (2)扩展分页

            80x86微处理器允许页框大小为4MB,内核可以不用中间页表进行地址转换,从而节省内存

        (3)硬件保护方案

            段:读,写,执行;页:读,写

        (4)常规分页举例

        (5)物理地址扩展(PAE)分页机制

        (6)64位系统中的分页


            64位处理器的硬件分页系统都使用了额外的分页级别

        (7)硬件高速缓存

            为了缩小CPU和RAM之间的速度不匹配,引入了硬件高速缓存内存。

            硬件高速缓存基于著名的局部性原理:最近最常用的相邻地址在最近的将来又被用到的可能性极大

        (8)转换后援缓存器(TLB)

            用于加快线性地址的转换

    5、Linux中的分页

        Linux采用了一种同时适用于32位和64位系统的普通分页模型;2.6.10采用三级分页的模型,2.6.11采用了四级分页模型。(参考图:2-12**)


 
        页全局目录:

        页上级目录:

        页中间目录:

        页表:

        页全局目录包含若干页上级目录的地址,页上级目录又依次包含若干页中间目录的地址,而页中间目录又包含若干页表的地址,每一个页表项指向一个页框。线性地址因此被分成五个部分。

        Linux通过使“页上级目录”位和“页中间目录”位全为0,从根本上取消了页上级目录和页中间目录字段。

        Linux的进程处理很大程度上依赖于分页,事实上,线性地址到物理地址的自动转换使下面的设计目标变的可行

        [1]给每个进程分配一块不同的物理地址空间,这确保了可以有效地防止寻址错误

        [2]区别页(即一组数据)和页框(即主存中的物理地址)之不同。这就允许存放在某个页框中的一个页,然后保存到磁盘上,以后重新装入这同一页时又可以被装在不同的页框中。这就是虚拟内存机制的基本要素。

        (1)线性地址字段

        (2)页表处理

            pte_tpmd_tpud_tpgd_t分别描述页表项,页中间目录项,页上级目录和页全局目录项的格式

        (3)物理内存布局

            在初始化阶段,内核必须建立一个物理地址映射来指定那些物理地址范围对内核可用而那些不可用

            内核将页框记为保留的有:在不可用的物理地址范围内的页框;含有内核代码和已初始化的数据结构的页框。保留页框中的页决不能被动态分配或交换到磁盘上

            一般来说,Linux内核安装在RAM中从物理地址:0x00100000开始的地方,页就是说,从第二(第一个被计算机模型保留)个MB开始。所需页框总数依赖于内核的配置方案

        (4)进程页表

            进程的线性地址空间分为两部分:

            从0x00000000到0xbfffffff的线性地址,无论进程运行在用户态还是内核态都可以寻址

            从0x00000000到0xcfffffff的线性地址,只有内核态的进程才能寻址

        (5)内核页表:参考第八章

            (6)临时内核页表

            (7)当RAM小于896MB时的最终内核页表

            (8)当RAM大小在896MB和4096MB之间时的最终内核页表

            (9)当RAM大于4096MB时的最终内核页表

        (10)固定映射的线性地址

        (11)处理硬件高速缓存和TLB


            处理硬件高速缓存

            处理TLB

           

第八章内存管理   

    RAM的某些部分永久地分配给内存,并用来存放内核代码以及静态内核数据结构,RAM的其余部分称为动态内存,这不仅是进程所需的宝贵资源,也是内核本身所需的宝贵资源。整个系统的性能取决于如何有效地管理动态内存。即尽可能做到当需要时分配,不需要时释放。

    1、页框管理  

        (1)页描述符

            内核必须记录每个页框当前的状态。内核还必须能够确定动态内存中的页框是否空闲。页框的状态信息保存在一个类型为page的页描述符中。参考表8-1

        (2)非一致内存访问(NUMA)

            我们习惯上认为计算机内存是一种均匀、共享的资源。我们期望不管内存单元处于何处,也不管CPU出于何处,CPU对内存单元的访问都需要相同的时间。可惜,这种假设在某些体系结构上并不总是成立。Linux2.6支持非一致内存访问(Non-UniformMemoryAccess,NUMA)模型。

            我们只关注80x86体系结构。IBM兼容PC使用一致访问内存(UMA)模型

        (3)内存管理区

            计算机体系结构有硬件的制约,这限制了页框可以使用的方式。Linux内核必须处理80x86体系结构的两种硬件约束:

            [1]ISA总线的直接内存存取(DMA)处理器有一个严格的限制:它们只能对RAM的前16MB寻址

            [2]在具有大容量RAM的现代32位计算机中,CPU不能直接访问所有的物理内存,因为线性地址空间太小

            为了应对这两种限制,划分3个管理区:

                ZONE_DMA:低于16MB的内存页框

                ZONE_NORMAL:高于16MB且低于896MB的内存页框

                ZONE_HIGHMEM:高于等于896MB的内存页框


            每个内存管理区都有自己的描述符:参考表8-4

        (4)保留的页框池

            内核为原子内存分配请求保留了一个页框池,只有在内存不足时才使用

        (5)分区页框分配器

            被称作分区页框分配器(zonedpageframeallocator)的内核子系统,处理对连续页框组的内存分配请求

            伙伴系统

            图:8-2


 
        (6)请求和释放页框

            6个函数和宏请求页框:

                alloc_pages(gfp_mask,order);

                alloc_page(gfp_mask);

                __get_free_pages(gfp_mask,order);

                __get_free_page(gfp_mask);

                get_zeroed_page(gfp_mask);

                __get_dma_pages(gft_mask|__GFP_DMA,order);

            4个函数和宏请求页框:

                __free_pages(page,order);

                free_pages(addr,order);

                __free_page(page);

                free_page(addr);


        (7)高端内存页框的内核映射

            896MB边界以上的页框并不映射在内核线性地址空间,因此,内核不能直接访问它们。这就意味着,返回所分配页框线性地址的页分配器函数不适合于高端内存,即不适合用于ZONE_HIGHMEM内存管理区内的页框

            64位硬件平台不存在这个问题,因为可使用的线性地址空间远大于能安装的RAM大小,简言之,这些体系结构的ZONE_HIGHMEM管理区总是空的。

            内核采用三种不同的机制将页框映射到高端内存;分别叫做:永久内核映射、临时内核映射及非连续内存分配。

            [1]永久内核映射

                永久内核映射允许内核建立高端页框到内核地址空间的长期映射。它们使用主内核页表中一个专门的页表,其地址存放在pkmap_page_table变量中。

                建立永久内核映射可能阻塞当前进程;永久内核映射不能用于中断处理程序和可延迟函数。

            [2]临时内核映射

                建立临时内核映射可以用在中断处理程序和可延迟函数的内部,因此它们从不阻塞当前进程。;缺点:是只有很少的临时内核映射可以同时建立起来

        (8)伙伴系统算法

            内存应该为分配一组连续的页框而建立一种健壮、高效的分配策略

            开发一种适当的技术来记录现存的空闲连续页框块得情况,以尽量避免为满足对小块的请求而分割大的空闲块

            Linux采用著名的伙伴系统算法来解决外碎片问题。把所有的空闲页框分组为11个块链表,每个块链表分别包含为1,2,4,8,16,32,64,128,256,512和1024个连续的页框。对1024个页框的最大请求对应着4MB大小的连续RAM块。每个块得第一页框的物理地址是该块大小的整数倍。

            假设要请求一个256个页框的块。算法现在256个页框的链表中检查是否有一个空闲块。如果没有这样的块,算法会查找下一个更大的页框,页就是,在512个页框的链表中找一个空闲块。如果存在这样的块,内核就把256的页框分成两份,一半用作满足请求,另一半插入到256个页框的链表中。如果在512个页框的块链表中页没有找到空闲块,就继续找更大的块--1024个页框的块。如果这样的块存在,内核把1024个页框块的256个页框用作请求,然后从剩余的768个页框中拿512个插入到512个页框链表中,再把最后的256个插入到256个页框的链表中。如果1024个页框的链表还是空的,算法就放弃并发出错信号。以上过程的逆过程就是页框块的释放过程,也是该算法名字的由来。

        (9)数据结构

        (10)分配块

            __rmqueue()

        (11)释放块

            __free_pages_bulk()

        (12)每CPU页框高速缓存

            内核经常请求和释放单个页框。为了提升系统性能,每个内存管理区定义了一个“每CPU”页框高速缓存。所有“每CPU”高速缓存包含一些预先分配的页框,它们被用于满足本地CPU发出的单一内存请求

            每个CPU提供了两个高速缓存:热高速缓存;冷高速缓存

            如果内核或用户态进程在刚分配到页框后就立即向页框写,那么从热高速缓存中获得页框就对系统性能有利;反过来,如果页框将要被DMA操作填充,那么从冷高速缓存中获得页框是方便的

        (13)通过每CPU页框高速缓存分配页框

            buffered_rmqueue()函数在指定的内存管理区中分配页框

        (14)释放页框到每CPU页框高速缓存

            free_hot_page()和free_cold_page()函数   

        (15)管理区分配器

           

        (16)释放一组页框

    2、内存区管理

        内碎片的产生主要是由于请求内存的大小与分配给它的大小不匹配而造成。一种典型的解决方法就是提供按几何分布的内存大小,换句话说,内存区大小取决于2的幂而不取决于所存放的数据大小。这样,不管请求内存的大小是多少,我们都可保证内碎片小于50%。为此内核建立了13个按几何分布的空闲内存区链表,它们的大小从32到131072字节。

        (1)slab分配器

            算法基于下列前提:

                [1]所存放数据的类型可以影响内存区得分配方式。

                    slab分配器概念扩充了这种思想,并把内存区看作对象(object),这些对象由一组数据结构和几个叫做构造或析构的函数组成。前者初始化内存区,后者回收内存区

                    为了避免重复初始化对象,slab分配器并不丢弃已分配的对象,而是释放但把它们保存在内存中。当以后又要请求新的对象时,就可以从内存获取而不用重新初始化。

                [2]内核函数倾向于反复请求同一类型的内存区;slab分配器把那些页框保存在高速缓存中并很快地重新使用它们。   

                [3]在引入的对象大小不是几何分布的情况下,也就是说,数据结构的起始地址不是物理地址值的2的幂次方,事情反倒好办。这可以借助处理器硬件高速缓存而导致较好的性能。

                [4]硬件高速缓存的高性能又是尽可能地限制对伙伴系统分配器调用的另一个理由,因为对伙伴系统函数的每次调用都“弄脏”硬件高速缓存,所以增加了对内存的平均访问时间。

            slab分配器把对象分组放进高速缓存,每个高速缓存都是同种类型对象的一种“储备”   

            包含高速缓存的主存区被划分为多个slab,每个slab由一个或多个连续的页框组成,这些页框中既包含已分配的对象,页包含空闲的对象。 参考图:8-3


 
        (2)高速缓存描述符

            每个高速缓存描述符由kmem_cache_t类型的数据结构 来描述的

        (3)slab描述符

            slab描述符,参考:表8-10

        (4)普通和专用高速缓存

            高速缓存被分为两种类型:普通和专用。普通高速缓存只由slab分配器用于自己的目的,而专用高速缓存由内核的其余部分使用

            在系统初始化期间调用kmem_cache_init()和kmem_cache_sizes_init()来建立普通高速缓存。;专用高速缓存是由kmem_cache_create()函数创建的

        (5)slab分配器与分区页框分配器的接口

            当slab分配器创建新的slab时,它依靠分区页框分配器来获得一组连续的空闲页框。

        (6)给高速缓存分配slab

            一个新创建的高速缓存没有包含任何slab,因此也没有空闲的对象:当以下两个条件都为真时,才给高速缓存分配slab:

            已发出一个分配新对象的请求

            高速缓存不包含任何空闲对象

        (7)从高速缓存中释放slab

            在两种条件下才能撤销slab:

                slab高速缓存中有太多的空闲对象。

                被周期性调用的定时器函数确定是否有完全未使用的slab能被释放

            调用:slab_destory()函数撤销一个slab   

        (8)对象描述符

            每个对象都有类型为kmem_bufctl_t的一个描述符,对象描述符存放在一个数组中,位于相应的slab描述符之后。

        (9)对齐内存中的对象

            slab分配器所管理的对象可以在内存中进行对齐,也就是说,存放它们的内存单元的起始物理地址是一个给定常量的倍数,通常是2的倍数。这个常量就叫对齐因子

            slab分配器所允许的最大对齐因子是4096,即页框大小。这就意味着通过访问对象的物理地址或线性地址就可以对齐对象


        (10)slab着色

            在不同的slab内具有相同偏移量的对象最终很可能映射在同一高速缓存行中。高速缓存的硬件可能因此而花费内存周期在同一高速缓存行与RAM内存单元之间来来往往传送两个对象,而其他的高速缓存行并未充分使用。slab分配器通过一种叫做颜色的不同随机数分配给slab

        (11)空闲slab对象的本地高速缓存

            类似“每CPU页框高速缓存”

        (12)分配slab对象

            通过调用kmem_cache_alloc() 函数可以获得新对象。

        (13)释放slab对象

            kmem_cache_free() 函数释放一个曾经由slab分配器分配给某个内核函数的对象

        (14)通用对象

            正如“伙伴系统算法”一节中所描述的那样,如果对存储区的请求不频繁,就用一组普通高速缓存来处理,普通高速缓存中的对象具有几何分布的大小,范围为32-131072字节。调用kmalloc()函数就可以得到这种类型的对象。

        (15)内存池

            内存池是Linux2.6的一个新特性。基本上讲,一个内存池允许一个内核成分,如块设备子系统,仅在内存不足的紧急情况下分配一些动态内存来使用。

            实际上这些页框只能用于满足中断处理程序或内部临界区发出的原子内存分配请求。而内存池是动态内存的设备,只能被特定的内核成分使用。

            创建一个内存池就像手头存放一些灌装食物作为储备,当没有新鲜食物时就使用开罐器。

            一个内存池常常叠加在slab分配器之上--也就是说,它被用来保存slab对象的储备

    3、非连续内存区管理

        把内存区映射到一组连续的页框是最好的选择,这样会充分利用高速缓存并获得较低的平均访问时间。不过,如果对内存区的请求不是很频繁,那么通过连续的线性地址来访问非连续的页框这样一种分配模式就会很有意义。这种模式的主要优点是避免了外碎片,而缺点是必须打乱内核页表。显然非连续内存区得大小必须是4096的倍数。

        Linux在几个方面使用非连续内存区,例如:为活动的交换区分配数据结构,为模块分配空间,或者给某个I/O驱动程序分配缓冲区。此外非连续内存区还提供了另一种使用高端内存页框的方法

        (1)非连续内存区的线性地址

            图8-7:从PAGE_OFFSET开始的线性地址区间

        (2)非连续内存区的描述符

            每个非连续内存区都对应着一个类型为vm_struct的描述符

            表8-13:

        (3)分配非连续内存区

            vmalloc() 函数给内核分配一个非连续内存区

        (4)释放非连续内存区

            vfree()函 数释放vmalloc()或vmalloc_32()创建的非连续内存区,而vunmap()函数释放vmap()创建的内存区

           

第九章进程地址空间

    内核中的函数以相当直接了当的方式获得动态内存,这是通过调用以下几个函数中的一个达到的:__get_free_pages()或alloc_pages()从分区框分配其中获得页框,kmem_cache_alloc()或kmalloc()使用slab分配器为专用或通用对象分配块,而vmalloc()或vmalloc_32()获得一块非连续的内存区。如果所请求的内存区得以满足,这些函数都返回一个页描述符地址或线性地址,即所分配动态内存区的起始地址。

    使用这些简单方法是基于以下两个原因

        内核是操作系统中优先级最高的成分

        内核信任自己


    当给用户态进程分配内存时,情况完全不同:

        进程对动态内存的请求被认为是不紧迫的。例如,当进程的可执行文件被装入时,进程并不一定立即对所有的代码页进行访问。类似地,当进程调用malloc()以获得请求的动态内存时,也并不意味着进程很快就会访问所有所获得的内存。因此一般来说,内核总是尽量推迟给用户态进程分配动态内存   

        由于用户进程是不可信任的。因此,内核必须能够随时准备捕捉用户进程引起的所有寻址错误。

        内核使用一种新的资源成功实现了对进程动态内存的推迟分配。当用户态进程请求动态内存时,并没有获得请求的页框,而仅仅获得对一个新的线性地址区间的使用权,而这一线性地址区间就成为进程地址空间的一部分。这一区间叫做“线性区”

    1、进程的地址空间

        进程的地址空间由允许进程使用的全部线性地址组成。每个进程所看到的线性地址集合是不同的,一个进程所使用的地址与另外一个进程所使用的地址之间没有什么关系。内核可以通过增加或删除某些线性地址区间来动态地修改进程的地址空间。

        起始地址和线性区的长度都必须是4096的倍数

        进程获得新线性区得一些典型情况:

        [1]当用户在控制台输入一条命令时,shell进程创建一个新的进程去执行这个命令。结果是,一个全新的地址空间分配给了新进程

        [2]正在运行的进程有可能决定装入一个完全不同的程序。在这种情况下,进程标识符仍然保持不变,可是再装入这个程序以前所使用的线性区却被释放,并有一组新的线性区被分配给这个进程。

        [3]正在运行的进程可能对一个文件执行“内存映射”,在这种情况下,内核给这个进程分配一个新的线性区来映射这个文件

        [4]进程可能持续向它的用户态堆栈增加数据,直到映射这个堆栈的线性区用完为止。在这种情况下,内核也许会决定扩展这个线性区的大小。

        [5]进程可能创建一个IPC共享线性区来与其他合作进程共享数据。在这种情况下,内核给这个进程分配一个新的线性区以实现这个方案

        [6]进程可能通过调用类似malloc()这样的函数扩展自己的动态区。结果是,内核可能决定扩展给这个堆所分配的线性区。
  

    2、内存描述符

        与进程地址空间有关的全部信息都包含在一个叫做内存描述符:mm_struct 参考:表9-2:       

    3、线性区

        Linux通过类型为vm_aree_struct的对象实现线性区

        每个线性区描述符表示一个线性地址区

        当一个新的线性地址区间加入到进程的地址空间时,内核检查一个已经存在的线性区是否可以扩大。如果不能,就创建一个新的线性区。类似地,如果从进程的地址空间删除一个线性地址区间,内核就要调整受影响的线性区大小。这些情况下,调整大小迫使一个线性区被分成两个更小的部分 。参考图9-1


 
        (1)线性区数据结构

            参考图:9-2与进程地址空间相关的描述符


 
            内核频繁执行的一个操作就是查找包含指定线性地址的线性区。由于链表是经过排序的,因此,只要在指定线性地址之后找到一个线性区,搜索就可以结束。


 
            为了提高效率,Linux2.6把内存描述符存放在叫做红-黑树 的数据结构中:

                红-黑树必须满足下列4条规则:

                [1]每个节点必须或为黑或为红

                [2]树的根必须为黑

                [3]红节点的孩子必须为黑

                [4]从一个节点到后代叶子节点的每个路径都包含相同数量的黑节点。当统计黑节点个数时,空指针也算作黑节点


            这4条规则确保具有n个内部节点的任何红-黑树其高度最多为2*log(n+1),在红-黑树中搜索一个元素因此而变的非常高效,因为其操作的执行时间与树大小的对数成线性比例。换句话说,双倍的线性区个数只多增加一次循环   

            在红-黑树中插入和删除一个元素也是高效的,因为算法能很快地遍历树以确定插入元素的位置或删除元素的位置。

            为了存放进程的线性区,Linux既使用了链表,也使用了红-黑树

        (2)线性区访问权限

            页与线性区之间的关系:每个线性区都由一组号码连续的页所构成.

            页相关的标志:

                在每个页表项中存放的几个标志,如:Read/Write、Present或User、Supervisor

                存放在每个页描述符flags字段中的一组标志

            第一种标志由80x86硬件用来检查能否执行所请求的寻址类型;第二种标志由Linux用于许多不同的目的。第三种标志,即与线性区得页相关的那些标志,存放在vm_area_struct描述符的vm_flags字段中。参考表:9-5线性区标志   

            线性区描述符所包含的页访问权限可以任意组合

        (3)线性区的处理

            [1]查找给定地址的最邻近区:find_vma()

            [2]查找一个与给定的地址区间相重叠的线性区:find_vma_intersection()

            [3]查找一个空闲的地址区间:get_unmapped_area()

            [4]向内存描述符链表中插入一个线性区:insert_vm_struct()


        (4)分配线性地址区间:do_mmap()

        (5)释放线性地址区间:


            do_munmap()

            split_vma()

            unmap_region()

           

    4、缺页异常处理程序

        Linux缺页异常处理程序必须区分一下两种情况:由编程错误所引起的异常,及由引用属于进程地址空间但还尚未分配物理页框的页所引起的异常。参考图9-4;图9-5

        (1)处理地址空间以外的错误地址

        (2)处理地址空间内的错误地址

        (3)请求调页

            "请求调页"指的是一种动态内存分配技术,它把页框的分配推迟到不能在推迟为止,也就是说,一直推迟到进程要访问的页不在RAM中时为止,由此引起一个缺页异常

            请求调页技术背后的动机是:进程开始运行的时候并不访问其地址空间中的全部地址;事实上,有一部分地址也许永远不被进程使用。此外,程序的局部性原理(参见第二章中的“硬件高速缓存”)保证了在程序执行的每个阶段,真正引用的进程页只有一小部分,因此临时用不着的页所在的页框可以由其他程序来使用。请求调页技术更好地利用空闲内存,从总体上能使系统有更大的吞吐量

            为这一切优点付出的代价是系统额外的开销:由请求调页所引发的每个“缺页”异常必须由内核处理,这将浪费CPU的时钟周期。幸运的是,局部性原理保证了一旦进程开始在一组页上运行,在接下来相当长的一段时间内它会一直停留在这些页上而不去访问其他的页,这样我们就可以认为“缺页”异常是一种稀有事件。

        (4)写时复制

            内核原样复制父进程的整个地址空间并把复制的那一份分配给子进程。这样做耗时的原因:

                为子进程的页表分配页框

                为子进程的页分配页框

                初始化子进程的页表

                把父进程的页复制到子进程相应的页中

            这种创建地址空间的方法涉及许多内存访问,消耗许多CPU周期,并且完全破坏了高速缓存中的内容。因为许多子进程通过装入一个新的程序开始它们的执行,这样就完全丢弃了所继承的地址空间

            Unix内核采用一种更为有效的方法,称之为写时复制:父进程和子进程共享页框而不是复制页框。然而只要页框被共享,它们就不能被修改。无论父进程还是子进程何时试图写一个共享的页框,就产生一个异常,这时内核就把这个页复制到一个新的页框中并标记为可写。原来的页框仍然是写保护的:当其他进程试图写入时,内核检查写进程是否是这个页框的唯一属主,如果是,就把这个页框标记为对这个进程是可写的。

        (5)处理非连续内存区访问

   
    5、创建和删除进程的地址空间


        (1)创建进程的地址空间

            当创建一个新的进程时内核调用copy_mm() 函数,这个函数通过建立新进程的所有页表和内存描述符来创建进程的地址空间

            写时复制方法,传统的进程继承父进程的地址空间,只要页是只读的,就依然共享它们。当其中的一个进程试图对某个页进行写时,这个页就被复制一份。一段时间之后,所创建的进程通常获得与父进程不一样的完全属于自己的地址空间

        (2)删除进程的地址空间

            内核调用exit_mm() 函数释放进程的地址空间

   

    6、堆的管理

        每个Unix进程都拥有一个特殊的线性区,这个线性区就是所谓的堆(heap),堆用于满足进程的动态内存请求。

            API:

            malloc(size):请求size个字节的动态内存。如果分配成功,就返回所分配内存单元第一个字节的线性地址

            calloc(n,size):请求含有n个大小为size的元素的一个数组,如果分配成功,就把数组元素初始化为0,并返回第一个元素的线性地址

            realloc(ptr,size):改变由前面的malloc()或calloc()分配的内存区字段的大小

            free(addr):释放

            brk(addr):直接修改堆的大小

   

第十五章页高速缓存

第十七章回收页框


    1、读写文件

    2、内存映射

    3、直接I/O传递

    4、异步I/O

  • 大小: 15.1 KB
  • 大小: 39.4 KB
  • 大小: 39.4 KB
  • 大小: 38.9 KB
  • 大小: 22.2 KB
  • 大小: 59.7 KB
  • 大小: 31.9 KB
  • 大小: 32.5 KB
  • 大小: 27.7 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics