Linux內存管理

理解linux內存管理首先得理解內存映射。轉自:http://blog.chinaunix.net/uid-27119491-id-3282175.html

程序用的都是邏輯地址,以下爲objdump反彙編程序的結果,左邊一列都是邏輯地址:

  1. 000000000040053c:
  2. 40053c:      55                      push %rbp
  3. 40053d:     48 89 e5             mov %rsp,%rbp
  4. 400540:     bf 4c 06 40 00    mov $0x40064c,%edi
  5. 400545:     e8 e6 fe ff ff        callq 400430
  6. 40054a:     b8 00 00 00 00   mov $0x0,%eax
  7. 40054f:      c9                       leaveq
  8. 400550:     c3                       retq

vmcore中,我們看到內存地址也是邏輯地址:

  1. crash> bt 1300
  2. PID: 1300 TASK: ffff81024fba1810 CPU: 0 COMMAND: "sshd"
  3. #0 [ffff81026a2e9cd8] schedule         at ffffffff802d9025
  4. #1 [ffff81026a2e9da0] schedule_timeout at ffffffff802d9a6b
  5. #2 [ffff81026a2e9df0] do_select        at ffffffff801935f2
  6. #3 [ffff81026a2e9ed0] sys_select       at ffffffff80193880
  7. #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,該函數中計算線性地址中的pgdpudpmdpte值,然後調用handle_pte_fault,在handle_pte_fault函數中,調用do_anonymous_page等函數完成內存申請工作。

 

pmap命令讀取/proc/PID/maps文件,使用該命令可以查看特定進程的內存映射情況,以下爲當前bash進程的部分堆、棧、文件映射和匿名內存段信息:

  1. linux # pmap $$
  2. 7561: bash
  3. START                          SIZE      RSS     PSS   DIRTY   SWAP    PERM   MAPPING
  4. 0000000000400000   552K    480K   248K       0K        0K      r-xp    /bin/bash
  5. 000000000068f000  1180K  1104K 1104K 1104K        0K      rw-p   [heap]
  6. 00007fd7397b9000      20K       16K     16K     16K        0K      rw-p   [anon]
  7. 00007fd7399c0000        4K         4K       4K       4K        0K       r--p   /lib64/libdl-2.11.1.so
  8. 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結構的地址:

  1. crash> bt
  2. PID: 1300 TASK: ffff81024fba1810 CPU: 0 COMMAND: "sshd"
  3. ……

找到mmactive_mm字段的值:

  1. crash> struct task_struct ffff81024fba1810
  2. struct task_struct {
  3.   ……
  4.   mm = 0xffff810273d6ef80,
  5.   active_mm = 0xffff810273d6ef80,
  6.   ……
  7. }

解析mm地址對應的mm_struct結構,第1個字段就是mmap

  1. crash> struct mm_struct 0xffff810273d6ef80
  2. struct mm_struct {
  3.   mmap = 0xffff810272b20870,
  4.   ……
  5. }

解析mmap地址對應的vm_area_struct結構,就找到了該進程的一段內存映射,繼續解析vm_next,就可以遍歷該進程的所有內存映射:

  1. crash> struct vm_area_struct 0xffff810272b20870
  2. struct vm_area_struct {
  3.   vm_mm = 0xffff810273d6ef80,
  4.   vm_start = 47474870689792,
  5.   vm_end = 47474870800384,
  6.   vm_next = 0xffff810276839b50,
  7.   ……
  8. }


夥伴算法

對內核而言,內存最小的分配單位爲4k,爲解決外部碎片問題,內核提供了夥伴算法(buddy algorithm),夥伴算法用於管理最小單位爲4k的連續的內存塊。

 

使用以下命令,可以將系統中內存相關的信息顯示到/var/log/messagesdmesg中:

  1. echo m > /proc/sysrq-trigger

由該命令打出的信息中,包含了夥伴算法管理的內存塊信息:

  1. 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接口查到:

  1. cat /proc/meminfo
  2. MemTotal:    8110624 kB
  3. MemFree:     6336284 kB
  4. Buffers:        78020 kB
  5. Cached:        1514352 kB
  6. ……

free命令就是讀取/proc/meminfo,進行內存信息顯示的。

 

buffers用於塊設備緩存,cached用於文件緩存。查看內核中的meminfo_proc_show函數,我們就能發現bufferscached的具體計算方法。

 

執行以下dd命令,我們可以看到free命令中buffers值的增長:

  1. 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


發佈了68 篇原創文章 · 獲贊 45 · 訪問量 20萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章