linux0.11内存管理(1)

来自专栏 linux0.114 人赞了文章

1、物理内存布局

  • 假设物理内存的大小为16MB,内存虚拟盘的大小为2MB。内存布局在函数main()中初始化。

  • 物理页的用途:

    • 系统使用(前6MB)

      1. 保存内核镜像(内核代码&数据)
      2. 磁碟数据缓冲区
      3. 作为内存虚拟盘

      这三类物理页的用途固定,即低6MB内存不做内存分配(即,mem_map中相应的位初始化为100、或不被mem_map管理)。

    • 进程使用(后10MB)

      1. 保存进程的管理结构(进程描述符、进程页表)
      2. 进程线性地址空间映射的物理页(代码段&数据段&栈&堆&参数)

      (引用于《Linux内核完全剖析基于0.12内核》)

      第2种情况中的物理页是linux0.11内存管理的对象。

2、物理内存管理数据结构

linux0.11内存管理相关的数据结构只有一个:mem_map[15*256]。

  • mem_map[15*256]:在mem_init()中初始化。

    • linux0.11将16MB内存划分为4KB大小的物理页进行管理,共4*1024个物理页。
    • mem_map数组只管理1MB以上的物理页,即只管理高15MB,且数组的每一项对应一页。所以一共(15*1024*1024)/(4*1024)=3840项。
    • mem_map数组用于标记物理页是否空闲或被占用。
      • 初始化时,将6MB~16MB范围内的物理页对应的数组项清0(空闲);
      • 将1MB~6MB范围内的物理页对应的数组项设置为100(被占用)。
      • 若物理页被申请,则赋值为1;若被共享,则加1。

    1MB~6MB内存用于buffer和ramdisk,只能被内核访问,因此数组mem_map的项数可以只包含6MB~16MB内存,即(10*1024*1024)/(4*1024)=2560项。

3、申请和释放物理内存

申请和释放的最小单位是4KB。

  • 申请:

    • get_free_page():返回物理页的物理起始地址。
  • 释放:

    • free_page(addr):参数addr是要释放的物理页的物理起始地址。

4、使用物理页:

这里指的物理页是被映射到进程线性地址空间的物理页(高10MB)。

  • 物理页使用的条件

    • 进程不能直接使用物理页,只有将线性页映射到物理页后,进程在访问线性页时,最终访问到物理页。

    • 线性地址空间就像OS给进程开的一张空头支票,进程可以将代码段、数据段、栈段、堆段、运行参数「载入」到相应的地方。

    • 在创建进程时,OS没有为进程线性地址空间中的线性页映射物理页(除了进程运行参数),即OS没有将进程运行的程序载入到内存。根据父、子进程运行的程序是否相同,分为两种情况:

      • 相同,则将父进程的页表项的值复制给子进程对应的页表项,即父子进程共享物理页。copy_page_tables
      • 不同,将子进程的页目录表项清0,并释放所有的页表,即不为子进程分配物理页。
    • OS实现空头支票的方法是,将线性地址空间中的线性页映射到具体的物理页。(通过缺页中断实现do_no_page)

      • 例子:cpu读指令是从进程的代码段所在的线性页中「读取」,若线性页已经被映射到了物理页,则通过地址转换从物理页中获取指令;若未映射,则OS会将指令从磁碟载入到物理页后(一次载入4KB),将代码所在的线性页映射到该物理页,然后再次访问线性页。
    • 物理页本身无属性,物理页的属性是由被映射到的线性页的属性决定的。(属性:可写,用户态可访问、、、)

  • 建立和销毁映射:

    • 建立映射:(两种情况)

      • 建立多页:copy_page_tables(from, to, size)
        • 函数copy_page_tables仅仅在创建进程时被调用。
        • 将父进程的线性页和物理页的映射关系,拷贝给子进程。即,父子进程共享物理页。
      • 建立一页:put_page(page, address),将线性页(address)映射到物理页(page)。
    • 销毁映射:free_page_tables(from, size)

      • 将进程所有页(目录)表项的内容赋值为0;
      • 调用函数free_page,释放占用的物理页(包括页表)。
      • 在两个地方用到该函数:
        • 终止进程时;
        • 子进程另起炉灶时。

5、节省内存的方法

物理页的使用者是进程,分配者是OS,OS对有限的物理内存的分配使用相当苛刻。linux0.11节省内存的方法有两个:

(1)按需分配内存

如前面所说,OS仅仅给进程开了一个空头支票(OS可以给每个进程提供64MB的物理内存),只在进程需要物理页的时候,为其分配物理页。这个过程是由缺页中断实现的。do_no_page

  • 为线性页申请一页物理页,初始化物理页后,将线性页映射到物理页。put_page

在linux0.11中,当内存使用完后,若申请物理页时,会终止掉当前进程。

(2)内存共享

  • 内存共享的地方:

    • fork进程时,调用copy_page_tables,父、子进程共享物理页。
    • 产生缺页中断时,首先看是否有能共享的物理页。 (在do_no_page中调用share_page)
      • 进程A共享进程B使用的物理页的条件:
        • A和B有且运行相同的程序。share_page(address)
        • 共享的只有代码段和数据段中的内容。do_no_page(share_page仅仅被do_no_page调用)
        • B中物理页存在,且未被修改。try_to_share
  • OS将共享物理页的两个进程中对应的线性页设置为只读,将共享物理页的引用次数加1。

  • 进程可以对共享物理页进行读操作,但是若其中一个进程写共享物理页时,产生一个防写中断。un_wp_page

    • 若线性页映射的物理页此时未被共享(物理页的引用次数为1,即mem_map中对应项的值为1),则直接将该线性页设置为可写。
    • 若被共享,则为当前进程的线性页申请一页物理页(将共享页的内容拷贝到该物理页),将该线性页设置为可写,将共享物理页的引用次数减1。

    这儿好像潜藏著一次无谓的防写中断。

问题

  1. 代码段可以被写吗?
  2. 通过段寄存器,可以访问其他进程的数据段吗?
  3. 交换空间的实现。
  4. malloc的运用。

推荐阅读:

相关文章