下一页 上一页 目录

7. Linux 内存管理

7.1 概述

Linux 使用分段 + 分页,这简化了表示法。

Linux 仅使用 4 个段

                               __
   4 GB--->|                |    |
           |     Kernel     |    |  Kernel Space (Code + Data/Stack)
           |                |  __|
   3 GB--->|----------------|  __
           |                |    |
           |                |    |
   2 GB--->|                |    |
           |     Tasks      |    |  User Space (Code + Data/Stack)
           |                |    |
   1 GB--->|                |    |
           |                |    |
           |________________|  __| 
 0x00000000
          Kernel/User Linear addresses
 

7.2 特定 i386 实现

同样,Linux 使用 3 级分页来实现分页,但在 i386 架构中,实际上只使用了 2 级

 
   ------------------------------------------------------------------
   L    I    N    E    A    R         A    D    D    R    E    S    S
   ------------------------------------------------------------------
        \___/                 \___/                     \_____/ 
 
     PD offset              PF offset                 Frame offset 
     [10 bits]              [10 bits]                 [12 bits]       
          |                     |                          |
          |                     |     -----------          |        
          |                     |     |  Value  |----------|---------
          |     |         |     |     |---------|   /|\    |        |
          |     |         |     |     |         |    |     |        |
          |     |         |     |     |         |    | Frame offset |
          |     |         |     |     |         |   \|/             |
          |     |         |     |     |---------|<------            |
          |     |         |     |     |         |      |            |
          |     |         |     |     |         |      | x 4096     |
          |     |         |  PF offset|_________|-------            |
          |     |         |       /|\ |         |                   |
      PD offset |_________|-----   |  |         |          _________|
            /|\ |         |    |   |  |         |          | 
             |  |         |    |  \|/ |         |         \|/
 _____       |  |         |    ------>|_________|   PHYSICAL ADDRESS 
|     |     \|/ |         |    x 4096 |         |
| CR3 |-------->|         |           |         |
|_____|         | ....... |           | ....... |
                |         |           |         |    
 
               Page Directory          Page File

                       Linux i386 Paging
 


7.3 内存映射

Linux 仅使用分页来管理访问控制,因此不同的任务将具有相同的段地址,但 CR3(用于存储页目录地址的寄存器)不同,指向不同的页表项。

在用户模式下,任务无法突破 3 GB 限制 (0 x C0 00 00 00),因此只有前 768 个页目录项是有意义的(768*4MB = 3GB)。

当任务进入内核模式(通过系统调用或 IRQ)时,其他 256 个页目录项变得重要,它们指向与所有其他任务(与内核相同)相同的页面文件。

请注意,内核(且仅内核)线性空间等于内核物理空间,因此

 
            ________________ _____                    
           |Other KernelData|___  |  |                |
           |----------------|   | |__|                |
           |     Kernel     |\  |____|   Real Other   |
  3 GB --->|----------------| \      |   Kernel Data  |
           |                |\ \     |                |
           |              __|_\_\____|__   Real       |
           |      Tasks     |  \ \   |     Tasks      |
           |              __|___\_\__|__   Space      |
           |                |    \ \ |                |
           |                |     \ \|----------------|
           |                |      \ |Real KernelSpace|
           |________________|       \|________________|
      
           Logical Addresses          Physical Addresses
 

线性内核空间对应于物理内核空间向下平移 3 GB(实际上,页表类似于 { "00000000", "00000001" },因此它们不进行虚拟化,它们仅报告从线性地址获取的物理地址)。

请注意,内核空间和用户空间之间不会发生“地址冲突”,因为我们可以使用页表来管理物理地址。

7.4 底层内存分配

启动初始化

我们从 kmem_cache_init 开始(由 start_kernel [init/main.c] 在启动时启动)。

|kmem_cache_init
   |kmem_cache_estimate

kmem_cache_init [mm/slab.c]

kmem_cache_estimate

现在我们继续 mem_init(也由 start_kernel[init/main.c] 启动)

|mem_init
   |free_all_bootmem
      |free_all_bootmem_core

mem_init [arch/i386/mm/init.c]

free_all_bootmem [mm/bootmem.c]

free_all_bootmem_core

运行时分配

在 Linux 下,当我们想要分配内存时,例如在“写时复制”机制期间(参见第 10 章),我们调用

|copy_mm 
   |allocate_mm = kmem_cache_alloc
      |__kmem_cache_alloc
         |kmem_cache_alloc_one
            |alloc_new_slab
               |kmem_cache_grow
                  |kmem_getpages
                     |__get_free_pages
                        |alloc_pages
                           |alloc_pages_pgdat
                              |__alloc_pages
                                 |rmqueue   
                                 |reclaim_pages

函数可以在以下位置找到

TODO:理解 Zones(区域)

7.5 交换

概述

交换由 kswapd 守护进程(内核线程)管理。

kswapd

与其他内核线程一样,kswapd 也有一个主循环,等待唤醒。

|kswapd
   |// initialization routines
   |for (;;) { // Main loop
      |do_try_to_free_pages
      |recalculate_vm_stats
      |refill_inactive_scan
      |run_task_queue
      |interruptible_sleep_on_timeout // we sleep for a new swap request
   |}

我们何时需要交换?

当我们必须访问物理内存中不存在的页面时,才需要交换。

Linux 使用 ''kswapd'' 内核线程来执行此目的。当任务收到缺页异常时,我们执行以下操作

 
 | Page Fault Exception
 | cause by all these conditions: 
 |   a-) User page 
 |   b-) Read or write access 
 |   c-) Page not present
 |
 |
 -----------> |do_page_fault
                 |handle_mm_fault
                    |pte_alloc 
                       |pte_alloc_one
                          |__get_free_page = __get_free_pages
                             |alloc_pages
                                |alloc_pages_pgdat
                                   |__alloc_pages
                                      |wakeup_kswapd // We wake up kernel thread kswapd
   
                   Page Fault ICA
 


下一页 上一页 目录