一、內存管理學什麼
Linux內存管理的三個大點:
- 虛擬內存(體現對內存的需求)
- 內存映射(虛擬內存映射物理內存)
- 物理內存(頁面的供應)
二、知識點
- 進程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:
- 先分配虛擬內存,等到實際用到物理內存的時候,纔會去分配物理頁(也就是頁表地址映射發現沒PTE的時候),用戶進程通過malloc來分配虛擬內存,如果需要的內存小於128kb,則走brk,大於128kb,走mmap(內核通過vmalloc來分配內核頁表的虛擬內存)
- 進程的虛擬地址空間結構
- 會分配一個vma_struct
- 程序中的邏輯地址經過計算得到線性地址,線性地址從CPU中流出,經過MMU做映射,此時頁表中的PTE有兩種狀態(在CSAPP第九章有介紹,已分配和未分配兩種狀態——PTE值是否爲0,其中已分配又分爲已緩存和未緩存兩種子狀態——PTE值不爲0,但是PTE最後一個bit位存在位P,P=0表示內存頁交換到swap不在內存,P=1表示頁就在內存中)
- 已分配物理內存:這種情況有兩種狀態,看最低位P存在位的值
- 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流程)
- P=1:page在內存中,頁表中該頁表項存放的是PTE,高20bit就是頁號,低位12bit補0即爲物理頁起始地址
- 未分配物理內存,此時會走page fault,入口do_page_fault -> do_no_page
- 看vm_area_struct -> vm_ops中是否有填充或提供nopage()函數(mmap映射的時候,會分配vm_area_struct)
- 沒有nopage()函數,則走do_anoymous_page,分配一個匿名頁,填充頁表pte
- 有nopage()函數,走do_fault,不同的設備驅動會提供不同的nopage()函數,這時候會從磁盤讀取數據到物理頁,然後填充頁表pte(在做mmap的時候,不同設備驅動中的nopage()函數會關聯到vm_area_struct -> vm_ops中)
- 看vm_area_struct -> vm_ops中是否有填充或提供nopage()函數(mmap映射的時候,會分配vm_area_struct)
- 已分配物理內存:這種情況有兩種狀態,看最低位P存在位的值
- 如果內存不夠,此時需要回收內存,一般由kswap內核線程來操作,但是到了water_mark.min,會同步做direct reclaim
- 到了watermark_low,喚醒kswap內核線程,回收內存,直到watermark_high
- 對於file-backed page,如果頁dirt,則數據寫回磁盤後釋放頁面,如果clean直接釋放;
- 對於anno_page,走 swap流程(swap_entry結構在《深入理解Linux虛擬內存》中有講解24bit offset + 6bit type + 1bit present+ 1bit protnone)
- 如果到了watermark_min,觸發direct reclaim
- 如果走了回收內存流程,還是沒有足夠的內存,那麼走oom流程
- 根據panic_on_oom的配置,是走系統崩潰,還是殺進程
- 如果殺進程,根據oom_kill_allocating_task配置,是選一個大內存運行時間短的進程殺,還是直接殺觸發oom的進程
- 如果選擇殺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章節
後記:本文只是休息時寫的流水賬,更多詳情見有道筆記