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
同样,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
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" },因此它们不进行虚拟化,它们仅报告从线性地址获取的物理地址)。
请注意,内核空间和用户空间之间不会发生“地址冲突”,因为我们可以使用页表来管理物理地址。
我们从 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(区域)
交换由 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