Linux内核初探

内存管理之内存寻址

  • 内存管理是迄今为止Unix内核中最复杂的活动
    1. 虚拟内存: 所有新近的Unix系统都提供一种有用的抽象, 叫作虚拟内存(virtual memory);
      • 虚拟内存可以理解为一种逻辑层;
      • 处于应用程序的内存请求与硬件内存管理单元(Memory Management Unit, MMU)之间;
    2. 虚拟内存有很多用途和优点:
      • 若干个进程可以并发地执行, 每个进程通过虚拟内存就好像自己拥有整块内存一样;
      • 应用程序所需内存大于可用物理内存时也可以运行;
      • 程序只有部分代码装入内存时进程可以照样执行;
      • 允许每个进程访问可用物理内存的子集;
      • 进程可以共享库函数或者程序的一个单独内存映像;
      • 程序是可重定位的, 也就是说, 也就是说可以把程序放在物理内存的任何地方;
      • 程序员可以编写与机器无关的代码, 因为他们不必关心有关物理内存的组织结构;
        • 程序可重定位就可以直接是位置无关码;
    3. 虚拟内存子系统的主要成分是虚拟地址空间(virtual address space)的概念;
      • 进程所使用的一组内存不等同于物理内存地址;
      • 当进程使用一个虚拟地址时, 内核和MMU协同定位其在内存中的实际物理位置;
      • 现在有些CPU包含了能自动把虚拟地址转换为物理地址的硬件电路;
        • 把RAM(随机存取存储器 -- random access memory,RAM内存), 分为长度为4KB或8KB的页框(page frame);
        • 利用一个页表来制定虚拟地址到物理地址之间的对应关系;
        • 一块连续的虚拟地址请求可以通过分配一组非连续的物理地址页框而得到满足;
    4. 随机访问存储器(RAM)即内存的使用
      • Unix操作系统都将RAM分为两部分: 用于存放内核映像(内核代码和内核静态数据结构), RAM的其余部分由虚拟内存来处理;
      • 虚拟内存的管理分为三种情况:
        • 满足内核对缓冲区、描述符及其他动态内核数据结构的请求;
        • 满足进程对一般内存区的请求即对文件内存映射的请求;
        • 借助高速缓存从磁盘及其缓冲设备获得较好的性能;
      • 当可用内存达到临界时, 可以调用页框回收(page-frame-reclaiming)算法释放其他内存
      • 虚拟内存必须要解决的一个问题是内存碎片(因为虚拟内存支持不连续的物理内存, 不连续的物理内存可能造成内存碎片问题)
        • 还有一些内存空间, 但没有一块很大的连续内存, 照样会分配失败;
    5. 内核内存分配器(kernel memory allocator, KMA)
      • 它试图满足系统中所有部分对内存的请求;
        • 内核需要的内存;
        • 用户程序的系统调用, 用来增加用户进程的地址空间;
      • 一个好的KMA应该具有的特点:
        • 必须快(包括中断处理程序调用);
        • 必须把程序的浪费减到最小;
        • 必须努力减轻内存的碎片(fragmentation)问题;
        • 必须能与其他内存管理子系统合作, 以便借用和释放页框;
      • 几种不同的KMA算法:
        • 资源图分配算法(allocator);
        • 2的幂次方空闲链表;
        • McKusick-Karels分配算法
        • 伙伴系统(buddy系统)
        • Mach的区域(zone)分配算法
        • Dynix分配算法
        • solaris的slab分配算法
        • Linux的KMA在伙伴(buddy)系统之上采用slab分配算法
    6. 进程虚拟地址空间处理
      • 进程虚拟地址空间包括进程可以引用的所有虚拟内存地址;
      • 内核通常用一组内存区描述进程虚拟地址空间;
      • 当进程通过exec系统调用开始某个程序时, 内核分配给进程的虚拟地址空间由以下内存区组成:
        • 程序的执行代码
        • 程序的初始化数据
        • 程序的未初始化数据
        • 初始程序栈(即用户态栈)
        • 所需共享库的可执行代码和数据
        • 堆(由程序动态请求的内存)
      • 现代的Unix操作系统都采用了所谓的请求调页(demand paging)的内存分配策略;
        • 有了请求调页, 进程可以在他的页还没有在内存时就开始自行;
        • 当进程访问一个不存在的页时, MMU产生一个异常;
        • 异常处理程序找到受影响的内存区, 分配一个空闲的页, 并用适当的数据把它初始化;
        • 进程调用malloc或brk系统调用动态地请求内存时, 内核仅仅修改进程的堆内存区大小;
        • 只有师徒引用进程的虚拟内存地址而产生异常时, 才给进程分配页框(物理内存);
      • 虚拟内存也是采用了一些有效的策略: 写时复制策略;
        • 当一个新进程被创建时, 内核仅仅把父进程的页框赋给子进程的地址空间, 但是页框标记为只读, 一旦父子进程试图修改页中的内容时, 就会产生一个异常;
        • 异常处理程序再把再把新页框赋给受影响的进程, 并用原来页中的内容初始化新页框;
    7. 高速缓存
      • 物理内存的一大又是就是用作磁盘和其他块设备的高速缓存;
        • 尽可能的延时写磁盘的时间, 因此, 从磁盘读入内存的数据即使任何进程都不再使用它们, 它们也继续留在RAM中;
        • 前提条件: 新进程请求从磁盘中读或写的数据, 就是撤销进程曾拥有的数据;
        • 当一个进程访问齿盘式, 内核首先检查进程请求的数据是否在缓存中, 如果在(缓存命中), 内核就可以为进程请求提供服务而不访问磁盘;
      • sync系统调用把所有脏的缓冲区写入磁盘来轻质磁盘同步;
      • 为了避免数据丢失, 所有的操作系统都会注意周期性地把脏缓冲区写回磁盘;
    8. 设备驱动程序
      • 内核通过设备驱动程序(device driver) 与 I/O设备交互;
      • 设备驱动程序包含在内核中, 由一个或多个设备的数据结构和函数组成;
      • 包含以下优点:
        • 可以把特定设备的代码封装在特定的模块中;
        • 厂商可以在不了解内核源码而只知道接口规范的情况下, 就能增加新的设备;
        • 内核以统一的方式对待所有的设备, 并且通过相同的接口访问这些设备;
        • 可以把设备驱动程序写成模块, 并动态地把它们装进内核而不需要重新启动系统, 不需要时, 也可以动态地卸载模块, 以减少存储在RAM中的内核映像的大小;
      • 每个设备文件都有专门的设备驱动程序, 他们由内核调用以执行对硬件设备的请求操作;
      • Linux对帧缓冲提供一种抽象;
posted @ 2018-09-10 23:03  coding-for-self  阅读(217)  评论(0编辑  收藏  举报