理解linux內存管理首先得理解內存映射。轉自:http://blog.chinaunix.net/uid-27119491-id-3282175.html
程序用的都是邏輯地址,以下爲objdump反彙編程序的結果,左邊一列都是邏輯地址:
- 000000000040053c:
- 40053c: 55 push %rbp
- 40053d: 48 89 e5 mov %rsp,%rbp
- 400540: bf 4c 06 40 00 mov $0x40064c,%edi
- 400545: e8 e6 fe ff ff callq 400430
- 40054a: b8 00 00 00 00 mov $0x0,%eax
- 40054f: c9 leaveq
- 400550: c3 retq
在vmcore中,我們看到內存地址也是邏輯地址:
- crash> bt 1300
- PID: 1300 TASK: ffff81024fba1810 CPU: 0 COMMAND: "sshd"
- #0 [ffff81026a2e9cd8] schedule at ffffffff802d9025
- #1 [ffff81026a2e9da0] schedule_timeout at ffffffff802d9a6b
- #2 [ffff81026a2e9df0] do_select at ffffffff801935f2
- #3 [ffff81026a2e9ed0] sys_select at ffffffff80193880
- #4 [ffff81026a2e9f80] system_call at ffffffff8010ad3e
MMU固件完成邏輯地址到物理地址的轉換:
從上圖看到轉換分成兩部分,段式內存管理將邏輯地址轉換成線性地址,線性地址再經過頁式內存管理單元轉換成物理地址。
對於intel x86架構的cpu,段的基址爲0,段式內存管理並不真正起作用,我們看到的邏輯地址可以理解爲就是線性地址。
線性地址到物理地址的轉換關係,由內核中的頁表(page table)維護,下圖說明了線性地址轉換成物理地址的過程:
寄存器cr3中保存了進程的頁基地址,它是物理地址,每個進程的頁基地址都不相同,通過這樣實現進程間物理地址空間隔離。
64位操作系統中,線性地址各段長度在中定義。
以上計算地址的“+”操作由cpu完成,而頁表由內核維護。
進程內存佈局
對於32位操作系統,進程的內存佈局如下:
我們使用c庫的malloc申請內存時,malloc調用brk()或mmap()實現堆(heap)或映射區域(memory mapping segment)的增長。
用malloc申請內存,其實申請到的不是真正的內存,而是虛擬內存,當程序使用這些內存的時候,將出發缺頁異常,內核這時才分配物理內存頁面給用戶。
內核中,由do_page_fault函數處理缺頁異常,do_page_fault調用handle_mm_fault,該函數中計算線性地址中的pgd、pud、pmd和pte值,然後調用handle_pte_fault,在handle_pte_fault函數中,調用do_anonymous_page等函數完成內存申請工作。
pmap命令讀取/proc/PID/maps文件,使用該命令可以查看特定進程的內存映射情況,以下爲當前bash進程的部分堆、棧、文件映射和匿名內存段信息:
- linux # pmap $$
- 7561: bash
- START SIZE RSS PSS DIRTY SWAP PERM MAPPING
- 0000000000400000 552K 480K 248K 0K 0K r-xp /bin/bash
- 000000000068f000 1180K 1104K 1104K 1104K 0K rw-p [heap]
- 00007fd7397b9000 20K 16K 16K 16K 0K rw-p [anon]
- 00007fd7399c0000 4K 4K 4K 4K 0K r--p /lib64/libdl-2.11.1.so
- 00007fff5ec37000 84K 24K 24K 24K 0K rw-p 0000000000000000 00:00 [stack]
以上pmap結果顯示的每個地址段,在內核中由vm_area_struct結構表示,vm_area_struct結構在中定義,該結構包含地址段起始地址、結束地址、權限標誌等信息。
vmcore中,通過以下方法,我們可以找到某個進程的vm_area_struct結構,查看其內存映射信息。
首先獲取到進程task_struct結構的地址:
- crash> bt
- PID: 1300 TASK: ffff81024fba1810 CPU: 0 COMMAND: "sshd"
- ……
找到mm或active_mm字段的值:
- crash> struct task_struct ffff81024fba1810
- struct task_struct {
- ……
- mm = 0xffff810273d6ef80,
- active_mm = 0xffff810273d6ef80,
- ……
- }
解析mm地址對應的mm_struct結構,第1個字段就是mmap:
- crash> struct mm_struct 0xffff810273d6ef80
- struct mm_struct {
- mmap = 0xffff810272b20870,
- ……
- }
解析mmap地址對應的vm_area_struct結構,就找到了該進程的一段內存映射,繼續解析vm_next,就可以遍歷該進程的所有內存映射:
- crash> struct vm_area_struct 0xffff810272b20870
- struct vm_area_struct {
- vm_mm = 0xffff810273d6ef80,
- vm_start = 47474870689792,
- vm_end = 47474870800384,
- vm_next = 0xffff810276839b50,
- ……
- }
夥伴算法
對內核而言,內存最小的分配單位爲4k,爲解決外部碎片問題,內核提供了夥伴算法(buddy algorithm),夥伴算法用於管理最小單位爲4k的連續的內存塊。
使用以下命令,可以將系統中內存相關的信息顯示到/var/log/messages和dmesg中:
- echo m > /proc/sysrq-trigger
由該命令打出的信息中,包含了夥伴算法管理的內存塊信息:
- Jul 20 01:38:39 linux197 kernel: Node 0 Normal: 1362*4kB 1137*8kB 491*16kB 336*32kB 192*64kB 100*128kB 69*256kB 34*512kB 14*1024kB 3*2048kB 795*4096kB = 3370112kB-
以上輸出說明4kB的內存有1362塊,8kB的連續內存有1137塊,以此類推。
夥伴算法分配與回收內存的策略如下:
- 當所需的小塊連續內存不足以分配時,將拆分較大塊的連續內存塊
- 當內存返回給內核時,若相鄰的內存塊爲空閒內存,則與相鄰的內存合併
slab
slab解決內部碎片問題,申請一塊保存task_struct結構的內存,該塊內存小於4k,而分配出去的內存若還是4k,則會造成內存浪費。
slab依據內核中各種數據結構爲單位,進行內存分配。/proc/slabinfo提供了查詢slab信息的接口。
對於系統內存的整體使用情況,我們可以通過/proc/meminfo接口查到:
- cat /proc/meminfo
- MemTotal: 8110624 kB
- MemFree: 6336284 kB
- Buffers: 78020 kB
- Cached: 1514352 kB
- ……
free命令就是讀取/proc/meminfo,進行內存信息顯示的。
buffers用於塊設備緩存,cached用於文件緩存。查看內核中的meminfo_proc_show函數,我們就能發現buffers與cached的具體計算方法。
執行以下dd命令,我們可以看到free命令中buffers值的增長:
- dd if=/dev/sda2 of=/dev/null bs=1000 count=10000000
內存回收
對於linux server,以下兩種情況會觸發內核通過頁幀回收算法(page frame reclaiming algorithm, PFRA)進行內存回收:
- 內存使用緊張
- 內核線程kswapd週期性回收
回收內存的方式大體有兩種:
- 丟棄數據
- 將數據交換出去
下圖說明了內核中回收內存相關的函數調用:
Reference: Chapter 12 - Memory Management, Linux kernel development.3rd.Edition