linux内存管理概览

一、内存管理学什么

Linux内存管理的三个大点:

  1. 虚拟内存(体现对内存的需求)
  2. 内存映射(虚拟内存映射物理内存)
  3. 物理内存(页面的供应)

 

二、知识点

  • 进程PCB:task_struct
  • 虚拟内存结构:task_struct -> mm_struct -> vm_area_struct
  • 页表映射:mm_struct -> pgd、分段、分页、多级页表、pte(page table entry)
  • 物理内存结构:mem_map、pglist_data、zone、free_area_struct、page
  • 物理内存管理:buddy、slab、kswap、watermark、LRU、active & inactive page
  • 内核栈:void * stack&thread_info、32bit和64bit系统上的差异
  • 缺页异常:do_page_fault(do_falut, do_anonymous_page, do_swap_page)+pte+swap_entry
  • 页内型:anno page和file-backed page
  • 交换区:swap_info_struct、swap cache、swap_entry
  • OOM:oom-killer和几个选项(panic_on_oom、oom_kill_allocating_task、/proc/pid/oom_score_adj)

 

三、概览

3.1 一般流程

要贯穿Linux整个内存管理的逻辑,起点是进程PCB,即task_struct(创建进程的时候,load_elf_binary会根据可执行文件的ELF格式把程序加载到内存并做好VMA的映射,此时每个进程的内核栈在32bit系统上会默认会分配8KB内存,在64bit系统上会分配16kb内存),其中的mm_struct:

  1. 先分配虚拟内存,等到实际用到物理内存的时候,才会去分配物理页(也就是页表地址映射发现没PTE的时候),用户进程通过malloc来分配虚拟内存,如果需要的内存小于128kb,则走brk,大于128kb,走mmap(内核通过vmalloc来分配内核页表的虚拟内存)
    1. 进程的虚拟地址空间结构
    2. 会分配一个vma_struct
  2. 程序中的逻辑地址经过计算得到线性地址,线性地址从CPU中流出,经过MMU做映射,此时页表中的PTE有两种状态(在CSAPP第九章有介绍,已分配和未分配两种状态——PTE值是否为0,其中已分配又分为已缓存和未缓存两种子状态——PTE值不为0,但是PTE最后一个bit位存在位P,P=0表示内存页交换到swap不在内存,P=1表示页就在内存中)
    1. 已分配物理内存:这种情况有两种状态,看最低位P存在位的值
      1. P=0:page不在内存中,而在swap,所以此时页表中该页表项存放的不是PTE,而是swap_entry(24bit offset + 6bit type + 1bit present+ 1bit protnone),走swap cache+swap_info_struct(一样触发缺页异常,走do_page_falut -> do_swap_page流程)
      2. P=1:page在内存中,页表中该页表项存放的是PTE,高20bit就是页号,低位12bit补0即为物理页起始地址
    2. 未分配物理内存,此时会走page fault,入口do_page_fault -> do_no_page
      1. 看vm_area_struct -> vm_ops中是否有填充或提供nopage()函数(mmap映射的时候,会分配vm_area_struct)
        1. 没有nopage()函数,则走do_anoymous_page,分配一个匿名页,填充页表pte
        2. 有nopage()函数,走do_fault,不同的设备驱动会提供不同的nopage()函数,这时候会从磁盘读取数据到物理页,然后填充页表pte(在做mmap的时候,不同设备驱动中的nopage()函数会关联到vm_area_struct -> vm_ops中)
  3. 如果内存不够,此时需要回收内存,一般由kswap内核线程来操作,但是到了water_mark.min,会同步做direct reclaim
    1. 到了watermark_low,唤醒kswap内核线程,回收内存,直到watermark_high
    2. 对于file-backed page,如果页dirt,则数据写回磁盘后释放页面,如果clean直接释放;
    3. 对于anno_page,走 swap流程(swap_entry结构在《深入理解Linux虚拟内存》中有讲解24bit offset + 6bit type + 1bit present+ 1bit protnone)
    4. 如果到了watermark_min,触发direct reclaim
  4. 如果走了回收内存流程,还是没有足够的内存,那么走oom流程
    1. 根据panic_on_oom的配置,是走系统崩溃,还是杀进程
    2. 如果杀进程,根据oom_kill_allocating_task配置,是选一个大内存运行时间短的进程杀,还是直接杀触发oom的进程
    3. 如果选择杀oom_score进程,可以通过设置/proc/pid/oom_score_adj来配置进程的打分,可以设置-1000~1000,如果设置为-1000,则oom时永远不会选择杀该进程

3.2 内核栈

Linux给每个task都分配了内核栈。在32位系统上arch/x86/include/asm/page_32_types.h,是这样定义的:一个PAGE_SIZE是4K,左移一位就是乘以2,也就是8K。

#define THREAD_SIZE_ORDER 1 
#define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER)

内核栈在64位系统上arch/x86/include/asm/page_64_types.h,是这样定义的:在PAGE_SIZE的基础上左移两位,也即16K,并且要求起始地址必须是8192的整数倍。

#ifdef CONFIG_KASAN
#define KASAN_STACK_ORDER 1
#else
#define KASAN_STACK_ORDER 0
#endif

#define THREAD_SIZE_ORDER	(2 + KASAN_STACK_ORDER)
#define THREAD_SIZE  (PAGE_SIZE << THREAD_SIZE_ORDER)

这段空间的最低位置,是一个thread_info结构。这个结构是对task_struct结构的补充。因为task_struct结构庞大但是通用,不同的体系结构就需要保存不同的东西,所以往往与体系结构有关的,都放在thread_info里面。

32bit系统和64bit系统上,内核栈的差异,参看《趣谈Linux操作系统》专栏。

 

 

四、缺页中断

参看《深入理解Linux虚拟内存管理》4.6章节

 

 

 

 

 

 

 

后记:本文只是休息时写的流水账,更多详情见有道笔记

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章