2009年3月9日 星期一

Linux Kernel 記憶體管理機制之美

Standard
Linux Kernel 的穩定,有一部份可以歸功於它優良的記憶體管理機制,而探討該機制,有助於瞭解記憶體是如何被 Kernel 所使用,對開發 Linux Driver 的人來說,日後更有許多益處。最重要的是,Linux Kernel 之美是由此開始,優美的設計相當令人著迷。

Linux Kernel 的記憶體管理機制,主要由兩大部份組成:
  • Buddy System
  • Slab Allocator
Buddy System(buddy allocator)

如一般的 Operating System Design,Linux Kernel 一樣是以 Page 為記憶體管理的單位,因此設計了一層針對 page(或稱分頁)的管理機制,該機制在 Linux Kernel 裡被稱為『Buddy System (buddy allocator,簡稱 buddy)』,buddy 是 Kernel 最底層的記憶體管理機制,日後所有的記憶體配置,最後都要經過 buddy 才能取得或釋放記憶體。

可以藉由觀察當前 /proc/buddyinfo ,了解目前的記憶體使用情況,當然,是以 page 為單位:
$ cat /proc/buddyinfo
Node 0, zone DMA 76 71 66 50 33 17 5 1 1 1 0
Node 0, zone Normal 22301 6425 45 0 1 1 1 1 1 1 0
Node 0, zone HighMem 97 13 6 10 3 0 2 0 0 0 0


如果沒有什麼意外,/proc/buddyinfo 列會出了三個 zone ,分別是 DMA、Normal、HighMem,這三種 zone 的分類,是以 Physical Memory 位置的範圍而區分,在 IA-32 架構上 為:
  • DMA (16MB以下)
  • Normal (16MB~896MB)
  • HighMem (896MB以上)
不過,由於 Buddy System 是一個很抽象且基礎的機制,對一般的 Driver 開發來說,並不是非常好用,而且,對習慣 C 語言的開發者來說,是相當不合情理的存在。體會一下這樣奇怪的習慣:透過 buddy 的 __get_free_pages() 和 free_pages() 等 functions 取得的記憶體,是以 page 為單位來計算(在 IA-32 上大小為 4096 bytes),而不是使用以 1 byte 為單位來配置。

但是,並非不能直接使用 buddy 提供的 functions 做 Linux Driver 的開發,而是有相當的困難性(如果你想全程使用,請先讓小弟尊稱您一下牛人 :P)。一個最簡單可能發生的例子,就是不當使用 page (一個單位數量)的記憶體,易造成記憶體的浪費和 fragmentation。所以,對一般 Driver 的開發者,除非必要,其實不應該要負責處理這樣低階的記憶體問題。

Slab Allocator

有鑑於此,Linux Kernel 就在 buddy 之上,更進一步設計了『智慧型』機制 -『Slab Allocator(簡稱 Slab)』,來確保記憶體分頁的配置效率和完整性,也使其他開發者有更好的 API 和機制可以操作記憶體。

Slab 算是 Kernel 裡最早的一種演算法,事實上,除了 Slab 之外,Linux Kernel 已經實作出數種演算法,以優化記憶體的配置機制。前面提到,Kernel 的底層是以 page 為記憶體配置的單位,因此,哪怕只是需要 1 Byte 的空間,都會佔用一個 page,所以良好的 Slab 演算法,才能盡可能避免記憶體浪費和破碎的問題。截至目前為止,Linux Kernel 所支援的演算法,除 Slab 外另有:
  • SLOB Allocator
  • SLUB Allocator
此外,雖演算法不同,習慣上,在 Linux Kernel 裡還是統稱為『Slab Allocator』,沿用舊有的稱呼以表示該層級的記憶體管理機制。

* 註:Linux Kernel 2.6.23 之後已改用 SLUB 當做預設演算法。

Slab Allocator 實作了一個 cache 的架構(這裡的 cache 並非意指快取),對系統程式下的記憶體需求群組化,以達到管理的目的。一般的程式可以藉由向 Slab 註冊 cache,以得到記憶體的配置。

要知道目前系統上有那些 cache 在使用,可以查看 /proc/slabinfo,亦或者可用指令工具 slabtop 查看。
$ slabtop
Active / Total Objects (% used) : 858912 / 913334 (94.0%)
Active / Total Slabs (% used) : 34472 / 34472 (100.0%)
Active / Total Caches (% used) : 91 / 151 (60.3%)
Active / Total Size (% used) : 124788.54K / 129749.01K (96.2%)
Minimum / Average / Maximum Object : 0.01K / 0.14K / 4096.00K

OBJS ACTIVE USE OBJ SIZE SLABS OBJ/SLAB CACHE SIZE NAME
599140 599140 100% 0.13K 20660 29 82640K dentry
174267 134508 77% 0.05K 2601 67 10404K buffer_head
62448 62337 99% 0.48K 7806 8 31224K ext3_inode_cache
20527 13331 64% 0.28K 1579 13 6316K radix_tree_node
9706 7895 81% 0.08K 211 46 844K vm_area_struct
7980 7779 97% 0.04K 95 84 380K sysfs_dir_cache
7345 7100 96% 0.03K 65 113 260K size-32
6372 5440 85% 0.06K 108 59 432K size-64
5040 4521 89% 0.19K 252 20 1008K filp
4318 3873 89% 0.01K 17 254 68K anon_vma
2576 2546 98% 0.04K 28 92 112K Acpi-Operand
1859 1756 94% 0.02K 11 169 44K Acpi-Namespace
945 924 97% 0.43K 105 9 420K shmem_inode_cache
780 610 78% 0.12K 26 30 104K size-96
744 688 92% 0.50K 93 8 372K size-512
737 710 96% 0.35K 67 11 268K proc_inode_cache
648 130 20% 0.05K 9 72 36K journal_head
572 285 49% 0.33K 52 11 208K inode_cache
493 476 96% 0.13K 17 29 68K idr_layer_cache
470 443 94% 0.38K 47 10 188K sock_inode_cache
405 405 100% 0.44K 45 9 180K UNIX
384 357 92% 1.00K 96 4 384K size-1024
280 180 64% 0.19K 14 20 56K skbuff_head_cache
254 2 0% 0.01K 1 254 4K revoke_table
240 240 100% 0.12K 8 30 32K size-128
234 192 82% 0.05K 3 78 12K task_delay_info
203 121 59% 0.02K 1 203 4K biovec-1
203 3 1% 0.02K 1 203 4K fasync_cache
203 4 1% 0.02K 1 203 4K revoke_record
184 138 75% 0.04K 2 92 8K inotify_watch_cache
180 180 100% 0.19K 9 20 36K size-192


* 註:kmalloc() 所取得的記憶體,會以『size-*』的名稱存在。

當然不止如此,Linux 的記憶體管理還包括例外的特殊管道,如 vmalloc() 等,此外,Kernel Stack 也是一個很重要的機制,可惜短短篇幅無法一下說明完整,日後有時間再行補上。