大小適中的增強日志文件系統 (JFS2) inode 緩存對實現 IBM AIX 系統的高性能和穩定性至關重要。通常用戶會通過調優 j2_inodeCacheSize 來控制 inode 緩存的最大內存使用。inode 緩存大小也可通過內存動態重新配置 (DR) 操作進行更改。在 AIX 6.1(6100-04 以上)和 AIX 7.1 中,有一種隱藏的副作用,在執行 j2_inodeCacheSize 調優操作或動態邏輯分區 (DLPAR) 內存操作之後,inode 緩存類的最大堆大小只能下調。在本文中,我們將演示這種副作用是如何導致 inode 緩存耗盡的,還會介紹一些處理這類問題的方法。
inode 是 JFS2 的基礎結構。每個 inode 擁有一個磁盤上 512 字節 的數據結構。當 inode 在內存中工作時,JFS2 跟蹤的不僅僅是磁盤上的字段。核內 inode(包括磁盤上部分和工作部分)目前大約為 1 KB。AIX 內核會緩存所有這些數據來提高性能。
為了預防多個處理器爭用,inode 緩存拆分為多個緩存類。AIX 內核為每個處理器創建了兩個緩存類,還創建了另外一個緩存類,也就是說,在系統初始化時會創建總計 [n(處理器數)* 2 + 1] 個緩存類。iCacheClass 和 iCache 結構是在 /usr/include/j2/j2_inode.h 中定義的:
typedef struct iCacheClass { MUTEXLOCK_T cc_lock; int32 cc_nInode; /* # of inode in cacheList */ CDLL_HEADER(inode) cc_cacheList; /* cacheList header */ struct pile *cc_pile; /* inode pile */ boolean_t pileFull; /* pile is full */ } iCacheClass_t; struct iCache { int32 nInode; /* # of in-memory inode */ int16 nCacheClass; /* # of cacheClass */ struct iCacheClass *cacheTable; int32 nInodePerCacheClass; /* # of inode per cacheClass */ int32 nHashClass; /* # of hashClass - 1 */ int32 nNewHashClass; /* # of hashClass - 1 */ int32 nInodePerHashClass; /* # of inode per hashClass */ struct iHashClass **hashTable; int32 nPagesPerCacheClass; /* # of pile pages per cacheClass */ int32 nMaxInode; /* nInode at initialization time */ };
為每個緩存類提供了一個用於 inode 分配的堆:
struct pile { eye_catch_t pile_eyec; /* 8: pile eye-catcher */ uint32_t flags; /* 4: guarded by pile_lock */ uint16_t obj_size; /* 2: opaque object size */ uint16_t align; /* 2: object align (offset mask) */ uint16_t slab_size; /* 2: alloc slab size in pages */ ...... uint64_t max_total_pages; /* 8: max total pages, ideally */ uint64_t min_total_pages; uint64_t cur_total_pages; /* 8: real world value */ ...... };
可為一個堆配置最大頁數。這個 max_total_pages 字段確定分配多少個 inode 才能將一個堆裝滿,並開始從緩存列表回收 inode。可強制性縮小堆,也可以擴大它。此過程在內存 DR 和 j2_inodeCacheSize 調節期間執行。
調節 j2_inodeCacheSize
inode 緩存大小可通過使用 ioo 命令更改 j2_inodeCacheSize tunable 來調節。在 AIX 6.1 中,該值默認為 400,在 AIX 7.1 中默認為 200。該值沒有明確表明將使用的緩存量,而只是一個縮放比例系數。它可與主要內存的大小結合使用,以確定 inode 緩存的最大內存使用量。目前的公式為:
(inode 緩存內存)=(系統內存)*(j2_inodeCacheSize)/4000
我們可運行以下命令來顯示 j2_inodeCacheSize 的當前值:
#ioo -a |grep j2_inodeCacheSize
j2_inodeCacheSize = 400
可使用 kdb 命令獲取 inode 緩存的詳細信息:
(0)> i2 -c iCache: nInode: 0xB3306 (733958) nMaxInode: 0xB3306 (733958) nCacheClass: 17 nHashClass: 0xFFFF (65535) nNewHashClass: 0xFFFF (65535) cacheTable: 0xF10001003B4FC000 hashTable: 0xF10001003B54B000 Cache table: CLASS LOCK INODES CACHELIST.HEAD PILE FULL 0 0 260 F10001003FD92080 F10001003B502300 0 1 0 273 F10001003D4A2880 F10001003B502600 0 …… 16 0 260 F10001003FF17880 F10001003B503400 0 (0)> dw iCache 12 iCache+000000: 000B3306 00110000 F1000100 3B4FC000 ..3.........;O.. iCache+000010: 0000A8A6 0000FFFF 0000FFFF 00000010 ................ iCache+000020: F1000100 3B54B000 000029D5 000B3306 ....;T....)...3.
輸出行表明 nInodePerCacheClass 為 0xA8A6,nPagesPerCacheClass 為 0x29D5。
通過 kdb 命令可以檢查每個緩存類的堆:
(0)> pile F10001003B502300 name........iCache prev........0xF100010034832800 next........0xF10001003B502600 eyec........0x4C465361 objectsize..0x0400 align.......0x007F slabsize....0x0010 intpri......0x000B flags.......0x00000026 SLAB_PINNED ZEROED PROTECTED pa_slabs....0x0000 paq_next....0x0000000000000000 paq_prev....0x0000000000000000 pa_flags....0x00000000 maxtotalpg..0x00000000000029D5 mintotalpg..0x0000000000000000 curtotalpg..0x0000000000000060
輸出表明 maxtotalpg 為 0x29D5,這等於 nPagesPerCacheClass。
現在,讓我們使用 ioo 命令降低 j2_inodeCacheSize 的當前值:
#ioo -o j2_inodeCacheSize=300 Setting j2_inodeCacheSize to 300 (0)> i2 -c iCache: nInode: 0x86609 (550409) nMaxInode: 0xB3306 (733958) nCacheClass: 17 nHashClass: 0xFFFF (65535) nNewHashClass: 0xFFFF (65535) cacheTable: 0xF10001003B4FC000 hashTable: 0xF10001003B54B000 Cache table: CLASS LOCK INODES CACHELIST.HEAD PILE FULL 0 0 271 F10001003FF2C480 F10001003B502300 0 1 0 282 F10001003D4A5880 F10001003B502600 0 …… 16 0 272 F10001003FF1C480 F10001003B503400 0
輸出表明 nInode 已降低到 0x86609。
(0)> dw iCache 12
iCache+000000: 00086609 00110000 F1000100 3B4FC000 ..f.........;O..
iCache+000010: 00007E79 0000FFFF 0000FFFF 00000010 ..~y............
iCache+000020: F1000100 3B54B000 00001F5F 000B3306 ....;T....._..3.
輸出表明 nPagesPerCacheClass 已降低到 0x1F5F。
(0)> pile F10001003B502300
name........iCache
……
maxtotalpg..0x0000000000001F5F mintotalpg..0x0000000000000000
curtotalpg..0x0000000000000060
輸出表明 maxtotalpg 也已降低到 0x1F5F,這等於 nPagesPerCacheClass。
現在讓我們使用 ioo 命令增加 j2_inodeCacheSize 的當前值:
#ioo -o j2_inodeCacheSize=500Setting j2_inodeCacheSize to 500 (0)> i2 -c iCache: nInode: 0xE0003 (917507) nMaxInode: 0xE0003 (917507) nCacheClass: 17 nHashClass: 0xFFFF (65535) nNewHashClass: 0xFFFF (65535) cacheTable: 0xF10001003B4FC000 hashTable: 0xF10001003B54B000 Cache table: CLASS LOCK INODES CACHELIST.HEAD PILE FULL 0 0 1628 F10001004339D080 F10001003B502300 0 1 0 1640 F100010042B78480 F10001003B502600 0 …… 16 0 1629 F10001004338D080 F10001003B503400 0
輸出表明 nInode 已增加到了 0xE0003。
(0)> dw iCache 12
iCache+000000: 000E0003 00110000 F1000100 3B4FC000 ............;O..
iCache+000010: 0000D2D3 0000FFFF 0000FFFF 00000010 ................
iCache+000020: F1000100 3B54B000 0000344B 000E0003 ....;T....4K....
輸出表明 nPagesPerCacheClass 已增加到了 0x344B。
(0)> pile F10001003B502800
name........iCache
……
maxtotalpg..0x0000000000001F5F mintotalpg..0x0000000000000000
curtotalpg..0x00000000000001B0
輸出表明 maxtotalpg 比 nPagesPerCacheClass 要少得多。
事實上,它仍然將原始值保留為 0x1F5F。
內存 DR 操作
執行內存 DR 操作後,我們可使用相同的方法檢查 maxtotalpg 值。然後我們會發現,從 LPAR 刪除一些內存後,maxtotalpg 降低了,但在向 LPAR 添加一些內存後,它絕不會增加。
跟蹤日志和報告
跟蹤日志可幫助我們進一步理解這一問題。
降低 inodeCacheSize 的跟蹤日志
#ioo -a | grep j2_inodeCacheSize
j2_inodeCacheSize = 500
#trace -anl -C all -T100M -L200M -K vmm -o trace.raw;
ioo -o j2_inodeCacheSize=300; trcstop
Setting j2_inodeCacheSize to 300
#trcrpt -C all -o trc.out trace.raw
4DC ioo 0
2228422 21758033 close
0.122932631 2
pile_config_max: pile= F10001003B502300, pflags=0026, origmax=344B, newmax=1F5F: rc=0000
我們可從跟蹤日志中發現,ioo 命令調用了 pile_config_max() 函數來降低 maxtotalpg 值,
增加 inodeCacheSize 的跟蹤日志
#ioo -a | grep j2_inodeCacheSize
j2_inodeCacheSize = 300
#trace -anl -C all -T100M -L200M -K vmm -o trace.raw;
ioo -o j2_inodeCacheSize=400; trcstop
Setting j2_inodeCacheSize to 400
#trcrpt -C all -o trc.out trace.raw#grep pile_config_max trc.out
從跟蹤日志中我們可以發現,在增加 j2_inodeCacheSize 時,未調用 pile_config_max() 函數,這就是 maxtotalpg 原封未動的原因。
執行內存 DR 操作可獲得類似的跟蹤日志:刪除內存時調用了 pile_config_max() 函數,但增加內存時未調用它。
inode 緩存耗盡
maxtotalpg 只能降低的事實意味著,在偶然降低 j2_inodeCacheSize 或執行 DLPAR 內存刪除後,inode 緩存可能被耗盡,甚至在一次 j2_inodeCacheSize 增加或 DLPAR 內存添加後就被耗盡。
查看本欄目更多精彩內容:http://www.bianceng.cn/OS/unix/
下面的測試演示了 inode 緩存耗盡情況。
# ioo -o j2_inodeCacheSize=50 Setting j2_inodeCacheSize to 50 # ioo -o j2_inodeCacheSize=400 Setting j2_inodeCacheSize to 400 #kdb (0)> i2 -c iCache: nInode: 0xB3306 (733958) nMaxInode: 0xB3306 (733958) nCacheClass: 17 nHashClass: 0xFFFF (65535) nNewHashClass: 0xFFFF (65535) cacheTable: 0xF10001003A70F000 hashTable: 0xF10001003B57D000 Cache table: CLASS LOCK INODES CACHELIST.HEAD PILE FULL 0 0 282 F10001003FAFA080 F10001003B50D300 0 1 0 280 F10001003D4B4480 F10001003B50D600 0 …… 16 0 281 F10001003FAEA080 F10001003B510500 0 (0)> dw iCache 12 iCache+000000: 000B3306 00110000 F1000100 3A70F000 ..3.........:p.. iCache+000010: 0000A8A6 0000FFFF 0000FFFF 00000010 ................ iCache+000020: F1000100 3B57D000 000029D5 000B3306 ....;W....)...3. (0)> pile F10001003B50D300 name........iCache …… maxtotalpg..0x0000000000000539 mintotalpg..0x0000000000000000 curtotalpg..0x0000000000000060
然後,我們編寫了一個程序來打開許多文件(參見 openfile.c)。
#./openfile 1000 100 /home/testdir
以下錯誤消息被顯示:
open 90 failed Resource temporarily unavailable
從 kdb 中可以發現,所有堆都已裝滿,所有緩存列表都是空的,也就是說 inode 緩存已被耗盡:
(0)> i2 -c iCache: nInode: 0xB3306 (733958) nMaxInode: 0xB3306 (733958) nCacheClass: 17 nHashClass: 0xFFFF (65535) nNewHashClass: 0xFFFF (65535) cacheTable: 0xF10001003A70F000 hashTable: 0xF10001003B57D000 Cache table: CLASS LOCK INODES CACHELIST.HEAD PILEFULL 0 0 0 F10001003A70F010 F10001003B50D300 1 1 0 0 F10001003A70F040 F10001003B50D600 1 …… 16 0 0 F10001003A70F310 F10001003B510500 1
curtotalpg 的值與 maxtotalpg 的值幾乎一樣:
(0)> pile F10001003B510300 | grep totalpg
maxtotalpg..0x0000000000000539 mintotalpg..0x0000000000000000
curtotalpg..0x0000000000000530
因為 inode 緩存已被耗盡,所以我們無法打開任何新文件,無法啟動任何新進程。此外,沒有用戶能夠登錄到系統中。而且根據系統配置,緩存耗盡可能導致更嚴重的後果,比如文件系統損壞,甚至是系統故障。
檢測和修復錯誤的 maxtotalpg
可通過一些方法檢測和修復 inode 緩存堆的錯誤 maxtotalpg 值。
使用 kdb
首先,我們可以使用 kdb 並運行 pile 命令來檢查 maxtotalpg 是否等於 nPagesPerCacheClass,就像前面介紹的那樣。如果 maxtotalpg 和 nPagesPerCacheClass 的值之間出現任何差異,那麼我們可以重新啟動系統,這樣 maxtotalpg 就會恢復到默認值。我們還可以在 kdb 中手動將 maxtotalpg 修改為正確值。
還有另外一種更方便的方法。我們可以先將 j2_inodeCacheSize 增加為一個較大的值,然後將它降低到必要的值。類似的,可以先添加更多的內存,然後將內存刪除到需要的內存量。這樣就可以得到正確的 maxtotalpg 值。
編寫一個程序來修復 maxtotalpg
我們還可以編寫一個程序來自動檢查和修復 maxtotalpg 值(參見 pilefix.c)。
首先,使用 kdb 獲取 iCache 的內存地址:
(0)> ns
Symbolic name translation off
(0)> dd iCache
0285C158
然後,通過 /dev/kmem 界面讀取和對比 maxtotalpg 和 nPagesPerCacheClass 的值。如果 maxtotalpg 的值不同於 nPagesPerCacheClass 的值,那麼我們可寫回正確的值:
open("/dev/kmem", O_RDWR, 0); kread((unsigned long long )icachep, (char *)&icache, sizeof(icache)); ccp = (iCacheClass_t *)icache.cacheTable; for (i=0; i<icache.nCacheClass; i++, ccp++){ kread((unsigned long long )ccp, (char *)&cc, sizeof(iCacheClass_t)); kread((unsigned long long )pmaxtp, (char *)&max_total_pages, 8); if(icache.nPagesPerCacheClass != max_total_pages){ max_total_pages = icache.nPagesPerCacheClass; kwrite((unsigned long long)pmaxtp, (char *)&max_total_pages, 8); } }
結束語
在 AIX 6.1(高於 6100-04 的版本)和 AIX 7.1 中,在執行 j2_inodeCacheSize 調優操作或 DLPAR 內存操作後,inode 緩存類的最大堆大小只能降低。IBM 最近提供了一份特許程序分析師報告 (APAR),表明 IV41462 可以避免此問題。如果 AIX 系統沒有應用 IV41462,那麼可以使用本文中介紹的方法來避免 inode 緩存耗盡。