【随笔】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
    • /proc/sys/vm/min_free_kbytes:调整回收内存的阈值,可以通过/proc/zoneinfo查看high、low、min的阈值,min_free_kbytes主要是用来设置min阈值,通过min阈值来设置low和hight
    • /proc/sys/vm/swappniess:调整内存回收时回收匿名页的积极程度,或者说调整内存回收时回收匿名页和file-backed page的倾向
  • 页内型:anno page和file-backed page
  • 交换区:swap_info_struct、swap cache、swap_entry
  • 内核栈:void * stack&thread_info、32bit和64bit系统上的差异
  • 缺页异常:do_page_fault(do_falut, do_anonymous_page, do_swap_page)+pte+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内存,内核栈kernel stacks详情见下文3.2),其中的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()函数,如果nopage()函数此时指向磁盘驱动,那么这时候会从磁盘读取数据到物理页,然后填充页表pte(在做mmap的时候,不同设备驱动中的nopage()函数会关联到vm_area_struct -> vm_ops中)
  3. 如果内存不够,此时需要回收内存,一般由kswapd内核线程来操作,但是到了water_mark.min,会同步做direct reclaim
    1. 到了watermark_low,唤醒kswapd内核线程,回收内存,直到watermark_high;
    2. 对于inactive file-backed page,如果页dirt,则数据写回磁盘后释放页面,如果clean直接释放;
    3. 对于inactive anno_page,走 swap流程(swap_entry结构在《深入理解Linux虚拟内存》中有讲解24bit offset + 6bit type + 1bit present+ 1bit protnone)
    4. 如果到了watermark_min,触发direct reclaim
    5. 【随笔】linux内存管理概览和线程内核栈_第1张图片
    6. 【随笔】linux内存管理概览和线程内核栈_第2张图片
  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内核文档中kernel stack(https://www.kernel.org/doc/html/latest/x86/kernel-stacks.html?highlight=thread_size)的描述:

x86_64 page size (PAGE_SIZE) is 4K.

Like all other architectures, x86_64 has a kernel stack for every active thread. These thread stacks are THREAD_SIZE (2*PAGE_SIZE) big. These stacks contain useful data as long as a thread is alive or a zombie. While the thread is in user space the kernel stack is empty except for the thread_info structure at the bottom.

默认页是4K,内核栈的大小由THREAD_SIZE定义,默认是两个页的大小2*PAGE_SIZE,即8K。

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)

【随笔】linux内存管理概览和线程内核栈_第3张图片

【随笔】linux内存管理概览和线程内核栈_第4张图片

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

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

 

四、缺页中断

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

【随笔】linux内存管理概览和线程内核栈_第5张图片

 

【随笔】linux内存管理概览和线程内核栈_第6张图片

pidstat -r可以看到系统每秒发生的的主要和次要错误。

minflt/s
Total number of minor faults the task has made per second, those which have not required loading a memory page from disk.

majflt/s
Total number of major faults the task has made per second, those which have required loading a memory page from disk.

【随笔】linux内存管理概览和线程内核栈_第7张图片

 

【随笔】linux内存管理概览和线程内核栈_第8张图片

 

【随笔】linux内存管理概览和线程内核栈_第9张图片

 

【随笔】linux内存管理概览和线程内核栈_第10张图片

 

【随笔】linux内存管理概览和线程内核栈_第11张图片

 

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

你可能感兴趣的:(linux)