zoukankan      html  css  js  c++  java
  • Linux内核之内存管理

    Linux内核之内存管理

    • Linux利用的是分段+分页单元把逻辑地址转换为物理地址;
      • RAM的某些部分永久地分配给内核, 并用来存放内核代码以及静态内核数据结构;
      • RAM的其余部分称动态内存(dynamic memory); 整个系统的性能取决于如何有效的管理动态内存;
      • 尽力优化对动态内存的使用, 尽量做到需要时使用, 不需要时释放;
    • 内核如何给自己分配动态内存: 页框管理和内存区管理对连续内存去处理的两种不同的技术;
      • 非连续区的管理是处理不连续内存去的一种技术;
      • 内存管理必须知道的几个主题技术: 内存管理区, 内核映射, 伙伴系统, slab高速缓存和内存池;

    页框管理

    • 不同的页框大小(连续物理地址的大小): 4KB或4MB;
    • 由分页异常引发的缺页异常很容易得到解释, 或是由于请求的页存在但不允许进程对其访问, 或者是由于请求的页不存在;
    • 内存分配器必须找到一个4KB的空闲页框, 并将其分配给进程;
    • 虽然页框都是磁盘大小的倍数, 在绝大多数时候, 当主存和磁盘之间传输小块数据时更高效;

    页描述符

    1. 内核必须记录每个页框当前的状态;
      • 内核必须区分那些页框包含的是属于进程的页, 那些页框包含的是内核代码或内核数据;
      • 内核还必须能够确定动态内存中的页框是否空闲;
      • 如果动态内存中的页框不包含有用的数据, 那么该页框就是空闲的;
      • 包含用户态进程的数据, 某个软件高速缓存的数据, 动态分配的内核数据结构, 设备驱动程序缓冲区的数据, 内核模块的代码等等都是可用数据;

    非一致内存访问(NUMA)

    1. 我们习惯上认为计算机内存是一种均匀、共享的资源, 在忽略硬件高速缓存作用下, 我们希望不管内存单元处于何处, 也不管CPU处于何处, CPU对内存单元的访问都要相同的时间;
    2. 非一致访问内存(Non-Uniform Memory Access, NUMA);
      • 系统的物理内存被划分为几个节点(node), 每个节点中的物理内存又可以分为几个管理区(Zone);

    内存管理区

    1. 一个理想的计算机体系结构中, 一个页框就是一个内存存储单元, 可用于任何事情: 存放内核数据和用户数据、 缓冲区磁盘数据等等;
      • 但实际的计算机体系结构有硬件的制约:
        • ISA总线的直接内存存取(DMA)处理器有一个严格的限制: 只能对RAM的前16M寻址;
        • 在具有大容量RAM的32为计算机中, CPU不能直接访问所有物理内存, 因为线性地址空间太小;
      • 物理内存划分为3个管理区(zone):
        • ZONE_DMA 包含低于16MB的内存页框;
        • ZONE_NORMAL 包含高于16MB且低于896MB的内存页框
        • ZONE_HIGHMEM 包含从896MB开始高于896MB的内存页框
    2. 当内核调用一个内存分配函数时, 必须指明请求页框所在的管理区, 使用zonelist数据结构管理描述符指针数组;

    保留的页框池

    1. 有两种不同的方法可以用来满足内存分配请求:
      • 如果有足够的空闲内存可用, 请求就会被立刻满足;
      • 否则, 必须回收一些内存, 并且将发出请求的内核控制路径阻塞, 直到内存被释放;
    2. 内核为原子内存分配请求保留了一个页框池, 只有在内存不足的时候才使用;
      • 保留池的大小 = (16 X 直接映射内存)的开方;

    保留的页框池

    1. 被称作分区页框器(zoned page frame allocator)的内核子系统, 处理对连续页框组的内存分配请求;
      • 管理区分配器 ----> 每CPU页框高速缓存 ----> 伙伴系统;
      • 管理区分配器 ----> 伙伴系统;
    2. 管理区分配器主要是用来接收动态内存分配和释放请求;
      • 在请求分配的情况下, 该部分搜索一个能满足所请求的一组梁旭页框内存的管理区;
      • 在每个管理区内, 页框被名为"伙伴系统"的部分来处理;
      • 为达到更好的性能, 一小部分页框保留在高速缓存中, 用于快速地满足对每个也宽的分配请求;
    3. 请求和释放页框:
      • alloc_pages
      • alloc_page
      • __get_free_pages
      • __get_free_page
      • get_zeroed_page
      • __get_dma_pages
      • 释放页框:
        • __free_pages
        • free_pages
        • __free_page
        • free_page

    高端内存页框的内核映射

    1. 高端内存的始端对应的线性地址存放在high_memory变量中, 被设置为896MB;
    2. 返回页框线性地址的页分配器函数不适用于高端内存, 即不适用于ZONW_HIGHMEM内存管理区内的页框;
      • 64位平台不存在这个问题, 因为可使用的线性地址空间远大于能安装的RAM大小;
      • 64位系统上, ZONE_HIGHMEM管理区总是空的;
      • 高端内存映射是为了打破4GB内存访问的限制, 而专门设计的一门技术;

    永久内核映射

    1. 永久内核映射允许内核建立高端页框到内核地址空间的长期映射;
      • 使用的是主内核页表中的一个专门的页表, 其地址存放在pkmap_page_table变量中;
    2. kmap函数可以用来建立永久内核映射;
    3. 在kmap_high函数中调用kmap_lock自旋锁, 以保护页表免受多处理器系统上的并发访问;

    临时内核映射

    1. 临时内核映射比永久内核映射的实现要简单;
      • 它们可以用在中断处理程序和可延迟函数的内部, 因为他们从不阻塞当前进程;
    2. 每个CPU都有它自己的包含13个串口的集合, 它们用enum_km_type数据结构表示;
      • 该数据结构中定义每个符号, 比如KM_BOUNCE_READ, KM_USRE0或KM_PTE0, 并标识了窗口的线性地址;
    3. 内核必须确保同一窗口永不会被两个不同的控制路径同时使用;
    4. 调用kmap_atomic函数建立零食内核映射;

    伙伴系统算法

    1. 内核应该为分配一组连续的页框而建立一种健壮、 高效的分配策略;
      • 著名的内存问题 -- 外碎片(external fragmentation);
      • 频繁地请求和释放不同大小的义序连续页框, 必然导致在已分配也矿上的块内分散了许多小块的空闲页框;
      • 问题是: 即使有足够的空闲页框可以满足请求, 但要分配一个大块的连续页框就无法满足;
    2. 从本质上说, 避免外碎片的方法有两种:
      • 利用分页单元把一组非连续的空闲页框映射到连续的线性地址空间;
      • 开发一种合适的技术来记录现存的空闲连续页框块的情况, 以尽量避免为满足对小块的请求二分割大的空闲块;
    3. Linux内核选择的是第二种方案: 记录空闲连续页框的情况, 避免对小块的请求分割大的空闲块;
      • 连续的页框是必要的, 仅仅是连续的线性地址有时候无法满足需求;
      • 给DMA处理器分配缓冲区的内存请求, DMA忽略分页单元直接访问地址总线, 所请求的缓冲区必须是在连续的页框中;
      • 连续的页框在保持内核页表不变方面起着不可忽视的作用;
        • 频繁的修改页表会导致平均访问内存次数的增加, 因为这会使CPU频繁地刷新转换后援缓冲器(TLB)的内容;
      • 内核通过4MB的页可以访问大块连续的物理内存, 这样减少了转换后援缓冲区(TLB)的失效率;
    4. Linux采用Buddy system(伙伴系统)算法来解决外碎片问题;
      • 把所有的空闲页框分为11个块链表, 每个块的大小为: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 和 1024个连续的页框;
      • 对1024个页框的最大请求对应着4MB大小的连续RAM块, 每个块的第一个页框的物理地址是该快大小的整数倍;
      • 算法举例:
        • 假设请求一个256个页框, 算法先在256个页框的链表中检查是否有一个空闲块, 如果存在这样的块, 直接返回即可;
        • 如果没有这样的块, 算法会查找下一个更大的页块, 也就是在512个页框的链表中找一个空闲块;
          • 如果存在这样的块, 内核就把512的页框分成两等分, 一半用作满足请求, 另一半插入到256个页框的链表中;
          • 如果没有512的块, 继续查找更大的内存块(1024);
      • 内核试图把大小为b的一对空闲伙伴合并为一个大小为2b的单独块;
        • 两个块具有相同的大小, 记作b;
        • 他们的物理地址是连续的;
        • 第一块的第一个页框的物理地址是2b2^12的倍数;
      • buddy算法是迭代的, 如果成功合并所释放的块, 它会试图合并2b的块, 以再次试图形成更大的块;
    5. 三种不同的伙伴系统:
      • 第一种处理适合ISA DMA的页框;
      • 第二种处理"常规"页框;
      • 第三种处理高端内存页框

    管理区分配器

    1. 管理区分配器是内核页框分配器的前端;
      • 必须分配一个包含足够多空闲页框的内存区, 使能满足内存请求;
    2. 管理区分配器必须满足几个目标:
      • 它应当保护保留的页框池;
      • 当内存不足且允许组设当前进程时, 它应当触发页框回收算法; 一旦某些压矿被释放, 管理区分配器将再次尝试分配;
      • 如果可能, 他应该保存小而珍贵的ZONE_DMA内存管理区;
    3. buddy系统使用页框作为基本的内存区, 适用于对大块内存的请求;
    4. 小块内存的访问, 在同一页框中如何分配小内存区 --- slab分配器;
      • 所存放数据的类型可以影响内存区的分配方式;
      • slab分配器把内存区看做是对象, 由构造函数和析构函数来进行管理;
      • slab 不会丢弃已经分配的对象, 而是释放但把他们保存在内存中, 以后要请求对象时, 直接从内存中重新初始化即可;
      • 内核倾向于反复请求同一类型的内存区;
        • 内核把时间浪费在反复分配和回收那些包含同一内存区的页框上, slab分配器把那些页框保存在高速缓存中并很快地重新使用它们;
      • 对内存的请求可以根据它们发生的频率来分类, 对于预期频率请求一个特定大小的内存区而言, 可以通过创建一组具有适当大小的专用对象来高效地处理, 由此来避免内存碎片;
      • 借助处理器硬件高速缓存而导致较好的性能;
      • buddy系统函数每次调用会弄脏硬件高速缓存, 会增加对内存的平均访问时间;
    5. slab分配器把对象分组放进高速缓存, 每个高速缓存都是同种类型对象的一种储备;
      • 高速缓存的主存区被划分为多个slab, 每个slab由一个或多个连续的页框组成, 这些页框既包含已分配的对象, 也包含空闲的对象;
    6. slab分配器通过一种叫做slab着色的策略, 尽量降低高速缓存的不愉快的行为, 把颜色的不同随机数分配给slab;
      • slab的长度 = (num*osize)+dsize+free;
    7. slab分配器利用空闲魏永的字节free来对slab着色, 着色其本质是将内存分配器把对象展开在不同的线性地址中;
      • 着色的本质导致把slab中的一些空闲区域从末尾一道开始;
      • 当free足够大时, 着色才起作用;

    通用对象

    • 如果对存储区的请求不频繁, 就用一组普通高速缓存来处理, 普通高速缓存中的对象具有几何分布的大小, 范围为32~131072字节;
      • 调用kmalloc可以得到这种对象;

    内存池

    • 内存池允许一个内核成分, 仅在内存不足的紧急情况下分配一些动态内存来使用, 比如块设备子系统;
      • 保留的页框池, 只能用于满足中断处理程序或内部临界区发出的原子内存分配请求;
      • 内存池是动态内存的设备, 只能被特定的内核成分(即池的拥有者)使用;
    • 内存池常常叠加在slab分配器上, 它被用来保存slab对象的储备;

    非连续内存区管理

    • 把内存区映射到一组连续的页框是最好的选择, 这样会充分利用高速缓存并获得较低的平均访问时间;
    • 通过连续的线性地址来访问非连续的页框, 优点是避免了外碎片, 缺点是必须打乱内核页表;
      • 非连续内存区的大小必须是4096的倍数;
      • 为活动的交换区分配数据结构;
      • 为模块分配空间;
      • 给某些I/O驱动程序分配缓冲区;
    • 调用vmalloc函数给内核分配一个非连续内存区, 分配为页框大小的整数倍;
  • 相关阅读:
    轻重搭配
    EF的优缺点
    使用bootstrap-select有时显示“Nothing selected”
    IIS发布 HTTP 错误 500.21
    js添加的元素无法触发click事件
    sql server查看表是否死锁
    sql server把一个库表的某个字段更新到另一张表的相同字段
    SQLSERVER排查CPU占用高的情况
    SQL server中如何按照某一字段中的分割符将记录拆成多条
    LINQ to Entities does not recognize the method 'System.DateTime AddDays(Double)' method, and this method cannot be translated into a store expression.
  • 原文地址:https://www.cnblogs.com/longjiang-uestc/p/9632905.html
Copyright © 2011-2022 走看看