在linux中,Buffer是指緩沖區,是一個用于存儲速度不同步的設備或優先級不同的設備之間傳輸數據的區域,是系統兩端處理速度平衡(從長時間尺度上看)時使用的;它的引入是為了減小短期內突發I/O的影響,起到流量整形的作用。
本教程操作環境:linux7.3系統、Dell G3電腦。
buffer和cache是什么
1、 Buffer(緩沖區)是系統兩端處理速度平衡(從長時間尺度上看)時使用的。它的引入是為了減小短期內突發I/O的影響,起到流量整形的作用。
Buffer是一個用于存儲速度不同步的設備或優先級不同的設備之間傳輸數據的區域。通過緩沖區,可以使進程之間的相互等待變少,從而使從速度慢的設備讀入數據時,速度快的設備的操作進程不發生間斷。
比如:我們用X雷下載一部電影,不可能下載一點就寫一點兒磁盤,這么干的話真的毀硬盤。反之先寫到buffer緩沖區,攢多了再一次性寫入磁盤,減少I/O,既有效率,又對硬盤友好。
2、 Cache(緩存)則是兩端處理速度不匹配時的一種折衷策略。因為CPU和memory之間的速度差異越來越大,所以人們充分利用數據的局部性特征,通過使用存儲系統分級(memory hierarchy)的策略來減小這種差異帶來的影響。
理解Linux的Cache和Buffer
cache指的是Linux中的page cache,buffer指的是buffer cache,也即cat ?/proc/meminfo中顯示的cache和buffer。
我們知道,Linux下頻繁存取文件或單個大文件時物理內存會很快被用光,當程序結束后內存不會被正常釋放而是一直作為cahce占著內存。因此系統經常會因為這點導致OOM產生,尤其在等大壓力場景下概率較高,此時,第一時間查看cache和buffer內存是非常高的。此類問題目前尚未有一個很好的解決方案,以往遇到大多會做規避處理,因此本案嘗試給出一個分析和解決的思路。
解決該問題的關鍵是理解什么是cache和buffer,什么時候消耗在哪里以及如何控制cache和buffer,所以本問主要圍繞這幾點展開。整個討論過程盡量先從內核源碼分析入手,然后提煉APP相關接口并進行實際操作驗證,最后總結給出應用程序的編程建議。
可以通過free或者cat /proc/meminfo查看到系統的buffer和cache情況。
free命令的全解析
1. Cache和Buffer分析
從cat /proc/meminfo入手,先看看該接口的實現:
static?int?meminfo_proc_show(struct?seq_file?*m,?void?*v)?? {?? ……?? cached?=?global_page_state(NR_FILE_PAGES)?-?? ?total_swapcache_pages()?-?i.bufferram;?? if?(cached?<p>其中,內核中以頁框為單位,通過宏K轉化成以KB為單位輸出。這些值是通過si_meminfo來獲取的:</p><pre class="brush:js;toolbar:false;">void?si_meminfo(struct?sysinfo?*val)? {? ?val->totalram?=?totalram_pages;? ?val->sharedram?=?0;? ?val->freeram?=?global_page_state(NR_FREE_PAGES);? ?val->bufferram?=?nr_blockdev_pages();? ?val->totalhigh?=?totalhigh_pages;? ?val->freehigh?=?nr_free_highpages();? ?val->mem_unit?=?PAGE_SIZE;? }
其中bufferram來自于nr_blockdev_pages(),該函數計算塊設備使用的頁框數,遍歷所有塊設備,將使用的頁框數相加。而不包含普通文件使用的頁框數。
long?nr_blockdev_pages(void)?? {?? ?struct?block_device?*bdev;?? ?long?ret?=?0;?? ?spin_lock(&bdev_lock);?? ?list_for_each_entry(bdev,?&all_bdevs,?bd_list)?{?? ?ret?+=?bdev->bd_inode->i_mapping->nrpages;?? ?}?? ?spin_unlock(&bdev_lock);?? ?return?ret;?? }
從以上得出meminfo中cache和buffer的來源:
- Buffer就是塊設備占用的頁框數量;
- Cache的大小為內核總的page cache減去swap cache和塊設備占用的頁框數量,實際上cache即為普通文件的占用的page ?cache。
通過內核代碼分析(這里略過復雜的內核代碼分析),雖然兩者在實現上差別不是很大,都是通過address_space對象進行管理的,但是page ?cache是對文件數據的緩存而buffer ?cache是對塊設備數據的緩存。對于每個塊設備都會分配一個def_blk_ops的文件操作方法,這是設備的操作方法,在每個塊設備的inode(bdev偽文件系統的inode)下面會存在一個radix ?tree,這個radix tree下面將會放置緩存數據的page頁。這個page的數量將會在cat ?/proc/meminfobuffer一欄中顯示。也就是在沒有文件系統的情況下,采用dd等工具直接對塊設備進行操作的數據會緩存到buffer ?cache中。如果塊設備做了文件系統,那么文件系統中的文件都有一個inode,這個inode會分配ext3_ops之類的操作方法,這些方法是文件系統的方法,在這個inode下面同樣存在一個radix ?tree,這里也會緩存文件的page頁,緩存頁的數量在cat /proc/meminfo的cache一欄進行統計。此時對文件操作,那么數據大多會緩存到page ?cache,不多的是文件系統文件的元數據會緩存到buffer cache。
這里,我們使用cp命令拷貝一個50MB的文件操作,內存會發生什么變化:
[root?nfs_dir]?#?ll?-h?file_50MB.bin? -rw-rw-r--?1?4104?4106?50.0M?Feb?24?2016?file_50MB.bin? [root?nfs_dir]?#?cat?/proc/meminfo? MemTotal:?90532?kB? MemFree:?65696?kB? Buffers:?0?kB? Cached:?8148?kB? ……? [root@test?nfs_dir]?#?cp?file_50MB.bin?/? [root@test?nfs_dir]?#?cat?/proc/meminfo? MemTotal:?90532?kB? MemFree:?13012?kB? Buffers:?0?kB? Cached:?60488?kB
可以看到cp命令前后,MemFree從65696 kB減少為13012 kB,Cached從8148 kB增大為60488 ?kB,而Buffers卻不變。那么過一段時間,Linux會自動釋放掉所用的cache內存嗎?一個小時后查看proc/meminfo顯示cache仍然沒有變化。
接著,我們看下使用dd命令對塊設備寫操作前后的內存變化:
[0225_19:10:44:10s][root@test?nfs_dir]?#?cat?/proc/meminfo? [0225_19:10:44:10s]MemTotal:?90532?kB?? [0225_19:10:44:10s]MemFree:?58988?kB?? [0225_19:10:44:10s]Buffers:?0?kB?? [0225_19:10:44:10s]Cached:?4144?kB?? ......?......?? [0225_19:11:13:11s][root@test?nfs_dir]?#?dd?if=/dev/zero?of=/dev/h_sda?bs=10M?count=2000?&?? [0225_19:11:17:11s][root@test?nfs_dir]?#?cat?/proc/meminfo?? [0225_19:11:17:11s]MemTotal:?90532?kB?? [0225_19:11:17:11s]MemFree:?11852?kB?? [0225_19:11:17:11s]Buffers:?36224?kB?? [0225_19:11:17:11s]Cached:?4148?kB?? ......?......?? [0225_19:11:21:11s][root@test?nfs_dir]?#?cat?/proc/meminfo?? [0225_19:11:21:11s]MemTotal:?90532?kB?? [0225_19:11:21:11s]MemFree:?11356?kB?? [0225_19:11:21:11s]Buffers:?36732?kB?? [0225_19:11:21:11s]Cached:?4148kB?? ......?......?? [0225_19:11:41:11s][root@test?nfs_dir]?#?cat?/proc/meminfo?? [0225_19:11:41:11s]MemTotal:?90532?kB?? [0225_19:11:41:11s]MemFree:?11864?kB?? [0225_19:11:41:11s]Buffers:?36264?kB?? [0225_19:11:41:11s]Cached:?4148?kB?? …..?……
裸寫塊設備前Buffs為0,裸寫硬盤過程中每隔一段時間查看內存信息發現Buffers一直在增加,空閑內存越來越少,而Cached數量一直保持不變。
總結:
通過代碼分析及實際操作,我們理解了buffer cache和page cache都會占用內存,但也看到了兩者的差別。page ?cache針對文件的cache,buffer是針對塊設備數據的cache。Linux在可用內存充裕的情況下,不會主動釋放page cache和buffer ?cache。
2. 使用posix_fadvise控制Cache
在Linux中文件的讀寫一般是通過buffer io方式,以便充分利用到page cache。
Buffer ?IO的特點是讀的時候,先檢查頁緩存里面是否有需要的數據,如果沒有就從設備讀取,返回給用戶的同時,加到緩存一份;寫的時候,直接寫到緩存去,再由后臺的進程定期刷到磁盤去。這樣的機制看起來非常的好,實際也能提高文件讀寫的效率。
但是當系統的IO比較密集時,就會出問題。當系統寫的很多,超過了內存的某個上限時,后臺的回寫線程就會出來回收頁面,但是一旦回收的速度小于寫入的速度,就會觸發OOM。最關鍵的是整個過程由內核參與,用戶不好控制。
那么到底如何才能有效的控制cache呢?
目前主要由兩種方法來規避風險:
- 走direct io;
- 走buffer io,但是定期清除無用page cache;
這里當然討論的是第二種方式,即在buffer io方式下如何有效控制page cache。
在程序中只要知道文件的句柄,就能用:
int?posix_fadvise(int?fd,?off_t?offset,?off_t?len,?int?advice);
POSIX_FADV_DONTNEED (該文件在接下來不會再被訪問),但是曾有開發人員反饋懷疑該接口的有效性。那么該接口確實有效嗎?首先,我們查看mm/fadvise.c內核代碼來看posix_fadvise是如何實現的:
/*?? ?*?POSIX_FADV_WILLNEED?could?set?PG_Referenced,?and?POSIX_FADV_NOREUSE?could?? ?*?deactivate?the?pages?and?clear?PG_Referenced.?? ?*/?? SYSCALL_DEFINE4(fadvise64_64,?int,?fd,?loff_t,?offset,?loff_t,?len,?int,?advice)?? {?? ?…?…?…?…?? ?/*?=>?將指定范圍內的數據從page?cache中換出?*/?? ?case?POSIX_FADV_DONTNEED:?? ?/*?=>?如果后備設備不忙的話,先調用__filemap_fdatawrite_range把臟頁面刷掉?*/?? ?if?(!bdi_write_congested(mapping->backing_dev_info))?? ?/*?=>?WB_SYNC_NONE:?不是同步等待頁面刷新完成,只是提交了?*/?? ?/*?=>?而fsync和fdatasync是用WB_SYNC_ALL參數等到完成才返回的?*/?? ?__filemap_fdatawrite_range(mapping,?offset,?endbyte,?? ?WB_SYNC_NONE);?? ?? ? ?/*?First?and?last?FULL?page!?*/?? ?start_index?=?(offset+(PAGE_CACHE_SIZE-1))?>>?PAGE_CACHE_SHIFT;?? ?end_index?=?(endbyte?>>?PAGE_CACHE_SHIFT);?? ?? ? ?/*?=>?接下來清除頁面緩存?*/? ?if?(end_index?>=?start_index)?{?? ?unsigned?long?count?=?invalidate_mapping_pages(mapping,?? ?start_index,?end_index);? ?? ? ?/*?? ?*?If?fewer?pages?were?invalidated?than?expected?then?? ?*?it?is?possible?that?some?of?the?pages?were?on? ?*?a?per-cpu?pagevec?for?a?remote?CPU.?Drain?all?? ?*?pagevecs?and?try?again.?? ?*/?? ?if?(count?<p>我們可以看到如果后臺系統不忙的話,會先調用__filemap_fdatawrite_range把臟頁面刷掉,刷頁面用的參數是是 ?WB_SYNC_NONE,也就是說不是同步等待頁面刷新完成,提交完寫臟頁后立即返回了。</p><p>然后再調invalidate_mapping_pages清除頁面,回收內存:</p><pre class="brush:js;toolbar:false;">/*?=>?清除緩存頁(除了臟頁、上鎖的、正在回寫的或映射在頁表中的)*/?? unsigned?long?invalidate_mapping_pages(struct?address_space?*mapping,?? ?pgoff_t?start,?pgoff_t?end)?? {?? ?struct?pagevec?pvec;?? ?pgoff_t?index?=?start;?? ?unsigned?long?ret;?? ?unsigned?long?count?=?0;?? ?int?i;?? ?? ? ?/*?? ?*?Note:?this?function?may?get?called?on?a?shmem/tmpfs?mapping:?? ?*?pagevec_lookup()?might?then?return?0?prematurely?(because?it?? ?*?got?a?gangful?of?swap?entries);?but?it's?hardly?worth?worrying?? ?*?about?-?it?can?rarely?have?anything?to?free?from?such?a?mapping?? ?*?(most?pages?are?dirty),?and?already?skips?over?any?difficulties.?? ?*/?? ?? ? ?pagevec_init(&pvec,?0);?? ?while?(index?index?*/?? ?index?=?page->index;?? ?if?(index?>?end)?? ?break;?? ?? ? ?if?(!trylock_page(page))?? ?continue;?? ?WARN_ON(page->index?!=?index);?? ?/*?=>?無效一個文件的緩存?*/?? ?ret?=?invalidate_inode_page(page);?? ?unlock_page(page);?? ?/*?? ?*?Invalidation?is?a?hint?that?the?page?is?no?longer?? ?*?of?interest?and?try?to?speed?up?its?reclaim.?? ?*/?? ?if?(!ret)?? ?deactivate_page(page);?? ?count?+=?ret;?? ?}?? ?pagevec_release(&pvec);?? ?mem_cgroup_uncharge_end();?? ?cond_resched();?? ?index++;?? ?}?? ?return?count;?? }? ??? ? /*?? ?*?Safely?invalidate?one?page?from?its?pagecache?mapping.?? ?*?It?only?drops?clean,?unused?pages.?The?page?must?be?locked.?? ?*?? ?*?Returns?1?if?the?page?is?successfully?invalidated,?otherwise?0.?? ?*/?? /*?=>?無效一個文件的緩存?*/?? int?invalidate_inode_page(struct?page?*page)?? {?? ?struct?address_space?*mapping?=?page_mapping(page);?? ?if?(!mapping)?? ?return?0;?? ?/*?=>?若當前頁是臟頁或正在寫回的頁,直接返回?*/?? ?if?(PageDirty(page)?||?PageWriteback(page))?? ?return?0;?? ?/*?=>?若已經被映射到頁表了,則直接返回?*/?? ?if?(page_mapped(page))?? ?return?0;?? ?/*?=>?如果滿足了以上條件就調用invalidate_complete_page繼續?*/?? ?return?invalidate_complete_page(mapping,?page);?? }?? 從上面的代碼可以看到清除相關的頁面要滿足二個條件:?1.?不臟且沒在回寫;?2.?未被使用。如果滿足了這二個條件就調用invalidate_complete_page繼續:?? /*?=>?無效一個完整的頁?*/?? static?int?? invalidate_complete_page(struct?address_space?*mapping,?struct?page?*page)?? {?? ?int?ret;?? ?? ? ?if?(page->mapping?!=?mapping)?? ?return?0;?? ?? ? ?if?(page_has_private(page)?&&?!try_to_release_page(page,?0))?? ?return?0;?? ?? ? ?/*?=>?若滿足以上更多條件,則從地址空間中解除該頁?*/?? ?ret?=?remove_mapping(mapping,?page);? ??? ? ?return?ret;?? }?? ?? ? /*?? ?*?Attempt?to?detach?a?locked?page?from?its?->mapping.?If?it?is?dirty?or?if?? ?*?someone?else?has?a?ref?on?the?page,?abort?and?return?0.?If?it?was?? ?*?successfully?detached,?return?1.?Assumes?the?caller?has?a?single?ref?on?? ?*?this?page.?? ?*/?? /*?=>?從地址空間中解除該頁?*/?? int?remove_mapping(struct?address_space?*mapping,?struct?page?*page)? {?? ?if?(__remove_mapping(mapping,?page))?{?? ?/*?? ?*?Unfreezing?the?refcount?with?1?rather?than?2?effectively?? ?*?drops?the?pagecache?ref?for?us?without?requiring?another?? ?*?atomic?operation.?? ?*/?? ?page_unfreeze_refs(page,?1);?? ?return?1;?? ?}?? ?return?0;?? }? ??? ? /*?? ?*?Same?as?remove_mapping,?but?if?the?page?is?removed?from?the?mapping,?it?? ?*?gets?returned?with?a?refcount?of?0.?? ?*/?? /*?=>?從地址空間中解除該頁?*/?? static?int?__remove_mapping(struct?address_space?*mapping,?struct?page?*page)?? {?? ?BUG_ON(!PageLocked(page));?? ?BUG_ON(mapping?!=?page_mapping(page));?? ?? ? ?spin_lock_irq(&mapping->tree_lock);?? ?/*?? ?*?The?non?racy?check?for?a?busy?page.?? ?*?? ?*?Must?be?careful?with?the?order?of?the?tests.?When?someone?has?? ?*?a?ref?to?the?page,?it?may?be?possible?that?they?dirty?it?then?? ?*?drop?the?reference.?So?if?PageDirty?is?tested?before?page_count? ?*?here,?then?the?following?race?may?occur:? ?*?? ?*?get_user_pages(&page);?? ?*?[user?mapping?goes?away]?? ?*?write_to(page);?? ?*?!PageDirty(page)?[good]?? ?*?SetPageDirty(page);? ?*?put_page(page);? ?*?!page_count(page)?[good,?discard?it]?? ?*?? ?*?[oops,?our?write_to?data?is?lost]?? ?*?? ?*?Reversing?the?order?of?the?tests?ensures?such?a?situation?cannot?? ?*?escape?unnoticed.?The?smp_rmb?is?needed?to?ensure?the?page->flags?? ?*?load?is?not?satisfied?before?that?of?page->_count.?? ?*?? ?*?Note?that?if?SetPageDirty?is?always?performed?via?set_page_dirty,?? ?*?and?thus?under?tree_lock,?then?this?ordering?is?not?required.? ?*/?? ?if?(!page_freeze_refs(page,?2))?? ?goto?cannot_free;?? ?/*?note:?atomic_cmpxchg?in?page_freeze_refs?provides?the?smp_rmb?*/?? ?if?(unlikely(PageDirty(page)))?{?? ?page_unfreeze_refs(page,?2);?? ?goto?cannot_free;? ?}? ?? ?if?(PageSwapCache(page))?{?? ?swp_entry_t?swap?=?{?.val?=?page_private(page)?};? ?__delete_from_swap_cache(page);?? ?spin_unlock_irq(&mapping->tree_lock);?? ?swapcache_free(swap,?page);?? ?}?else?{?? ?void?(*freepage)(struct?page?*);?? ?? ? ?freepage?=?mapping->a_ops->freepage;?? ?? ??/*?=>?從頁緩存中刪除和釋放該頁?*/? ??__delete_from_page_cache(page);? ??spin_unlock_irq(&mapping->tree_lock);? ??mem_cgroup_uncharge_cache_page(page);? ??? ??if?(freepage?!=?NULL)? ??freepage(page);?? ?}?? ?? ??return?1;?? ?? ? cannot_free:?? ?spin_unlock_irq(&mapping->tree_lock);?? ?return?0;?? }?? ?? ?/*? ?*?Delete?a?page?from?the?page?cache?and?free?it.?Caller?has?to?make?? ?*?sure?the?page?is?locked?and?that?nobody?else?uses?it?-?or?that?usage?? ?*?is?safe.?The?caller?must?hold?the?mapping's?tree_lock.?? ?*/?? /*?=>?從頁緩存中刪除和釋放該頁?*/?? void?__delete_from_page_cache(struct?page?*page)?? {?? ?struct?address_space?*mapping?=?page->mapping;?? ??? ?trace_mm_filemap_delete_from_page_cache(page);?? ?/*?? ?*?if?we're?uptodate,?flush?out?into?the?cleancache,?otherwise?? ?*?invalidate?any?existing?cleancache?entries.?We?can't?leave?? ?*?stale?data?around?in?the?cleancache?once?our?page?is?gone?? ?*/?? ?if?(PageUptodate(page)?&&?PageMappedToDisk(page))?? ?cleancache_put_page(page);?? ?else?? ?cleancache_invalidate_page(mapping,?page);?? ?? ? ?radix_tree_delete(&mapping->page_tree,?page->index);?? ?/*?=>?解除與之綁定的地址空間結構?*/?? ?page->mapping?=?NULL;?? ?/*?Leave?page->index?set:?truncation?lookup?relies?upon?it?*/?? ?/*?=>?減少地址空間中的頁計數?*/? ?mapping->nrpages--;? ?__dec_zone_page_state(page,?NR_FILE_PAGES);?? ?if?(PageSwapBacked(page))?? ?__dec_zone_page_state(page,?NR_SHMEM);?? ?BUG_ON(page_mapped(page));? ??? ? ?/*?? ?*?Some?filesystems?seem?to?re-dirty?the?page?even?after?? ?*?the?VM?has?canceled?the?dirty?bit?(eg?ext3?journaling).?? ?*?? ?*?Fix?it?up?by?doing?a?final?dirty?accounting?check?after?? ?*?having?removed?the?page?entirely.?? ?*/?? ?if?(PageDirty(page)?&&?mapping_cap_account_dirty(mapping))?{?? ?dec_zone_page_state(page,?NR_FILE_DIRTY);?? ?dec_bdi_stat(mapping->backing_dev_info,?BDI_RECLAIMABLE);? ?}? }
看到這里我們就明白了:為什么使用了posix_fadvise后相關的內存沒有被釋放出來:頁面還臟是最關鍵的因素。
但是我們如何保證頁面全部不臟呢?fdatasync或者fsync都是選擇,或者Linux下新系統調用sync_file_range都是可用的,這幾個都是使用WB_SYNC_ALL模式強制要求回寫完畢才返回的。所以應該這樣做:
fdatasync(fd);? posix_fadvise(fd,?0,?0,?POSIX_FADV_DONTNEED);
總結:
使用posix_fadvise可以有效的清除page cache,作用范圍為文件級。下面給出應用程序編程建議:
- 用于測試I/O的效率時,可以用posix_fadvise來消除cache的影響;
- 當確認訪問的文件在接下來一段時間不再被訪問時,很有必要調用posix_fadvise來避免占用不必要的可用內存空間。
- 若當前系統內存十分緊張時,且在讀寫一個很大的文件時,為避免OOM風險,可以分段邊讀寫邊清cache,但也直接導致性能的下降,畢竟空間和時間是一對矛盾體。
3. 使用vmtouch控制Cache
vmtouch是一個可移植的文件系統cahce診斷和控制工具。近來該工具被廣泛使用,最典型的例子是:移動應用Instagram(照片墻)后臺服務端使用了vmtouch管理控制page ?cache。了解vmtouch原理及使用可以為我們后續后端設備所用。
快速安裝指南:
$?git?clone?https://github.com/hoytech/vmtouch.git? $?cd?vmtouch? $?make? $?sudo?make?install
vmtouch用途:
- 查看一個文件(或者目錄)哪些部分在內存中;
- 把文件調入內存;
- 把文件清除出內存,即釋放page cache;
- 把文件鎖住在內存中而不被換出到磁盤上;
- ……
vmtouch實現:
其核心分別是兩個系統調用,mincore和posix_fadvise。兩者具體使用方法使用man幫助都有詳細的說明。posix_fadvise已在上文提到,用法在此不作說明。簡單說下mincore:
NAME? ?mincore?-?determine?whether?pages?are?resident?in?memory? ? ?? SYNOPSIS? ?#include?<unistd.h>? ?#include?<sys>? ?? ? ?int?mincore(void?*addr,?size_t?length,?unsigned?char?*vec);? ? ?? ?Feature?Test?Macro?Requirements?for?glibc?(see?feature_test_macros(7)):? ? ?? ?mincore():?_BSD_SOURCE?||?_SVID_SOURCE</sys></unistd.h>
mincore需要調用者傳入文件的地址(通常由mmap()返回),它會把文件在內存中的情況寫在vec中。
vmtouch工具用法:
Usage:vmtouch [OPTIONS] … FILES OR DIRECTORIES …
Options:
- -t touch pages into memory
- -e evict pages from memory
- -l lock pages in physical memory with mlock(2)
- -L lock pages in physical memory with mlockall(2)
- -d daemon mode
- -m
max file size to touch - -p
use the specified portion instead of the entire file - -f follow symbolic links
- -h also count hardlinked copies
- -w wait until all pages are locked (only useful together with -d)
- -v verbose
- -q quiet
用法舉例:
例1、 獲取當前/mnt/usb目錄下cache占用量
[root@test?nfs_dir]?#?mkdir?/mnt/usb?&&?mount?/dev/msc?/mnt/usb/?? [root@test?usb]?#?vmtouch?.?? ?Files:?57?? ?Directories:?2?? ?Resident?Pages:?0/278786?0/1G?0%?? ?Elapsed:?0.023126?seconds
例2、 當前test.bin文件的cache占用量?
[root@test?usb]?#?vmtouch?-v?test.bin?? test.bin?? [?]?0/25600?? ?? ? ?Files:?1?? ?Directories:?0?? ?Resident?Pages:?0/25600?0/100M?0%?? ?Elapsed:?0.001867?seconds
這時使用tail命令將部分文件讀取到內存中:
[root@test usb] # busybox_v400 tail -n 10 test.bin > /dev/null
現在再來看一下:
[root@test?usb]?#?vmtouch?-v?test.bin?? test.bin?? [?o]?240/25600?? ?? ? ?Files:?1?? ?Directories:?0?? ?Resident?Pages:?240/25600?960K/100M?0.938%?? ?Elapsed:?0.002019?seconds
可知目前文件test.bin的最后240個page駐留在內存中。
例3、 最后使用-t選項將剩下的test.bin文件全部讀入內存:
[root@test?usb]?#?vmtouch?-vt?test.bin?? test.bin?? [OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO]?25600/25600?? ?? ? ?Files:?1?? ?Directories:?0?? ?Touched?Pages:?25600?(100M)? ?Elapsed:?39.049?seconds
例4、 再把test.bin占用的cachae全部釋放:
[root@test?usb]?#?vmtouch?-ev?test.bin?? Evicting?test.bin?? ?? ? ?Files:?1?? ?Directories:?0?? ?Evicted?Pages:?25600?(100M)?? ?Elapsed:?0.01461?seconds
這時候再來看下是否真的被釋放了:
[root@test?usb]?#?vmtouch?-v?test.bin?? test.bin?? [?]?0/25600?? ?? ? ?Files:?1?? ?Directories:?0?? ?Resident?Pages:?0/25600?0/100M?0%?? ?Elapsed:?0.001867?seconds
以上通過代碼分析及實際操作總結了vmtouch工具的使用,建議APP組后續集成或借鑒vmtouch工具并靈活應用到后端設備中,必能達到有效管理和控制page ?cache的目的。
4. 使用BLKFLSBUF清Buffer
通過走讀塊設備驅動IOCTL命令實現,發現該命令能有效的清除整個塊設備所占用的buffer。
int?blkdev_ioctl(struct?block_device?*bdev,?fmode_t?mode,?unsigned?cmd,?? ?unsigned?long?arg)?? {?? ?struct?gendisk?*disk?=?bdev->bd_disk;?? ?struct?backing_dev_info?*bdi;?? ?loff_t?size;?? ?int?ret,?n;?? ?? ? ?switch(cmd)?{?? ?case?BLKFLSBUF:?? ?if?(!capable(CAP_SYS_ADMIN))?? ?return?-EACCES;?? ?? ? ?ret?=?__blkdev_driver_ioctl(bdev,?mode,?cmd,?arg);?? ?if?(!is_unrecognized_ioctl(ret))?? ?return?ret;?? ?? ? ?fsync_bdev(bdev);?? ?invalidate_bdev(bdev);? ?return?0;?? case?……:?? …………?? }?? ?? ? /*?Invalidate?clean?unused?buffers?and?pagecache.?*/?? void?invalidate_bdev(struct?block_device?*bdev)?? {?? ?struct?address_space?*mapping?=?bdev->bd_inode->i_mapping;?? ?? ? ?if?(mapping->nrpages?==?0)?? ?return;?? ?? ? ?invalidate_bh_lrus();?? ?lru_add_drain_all();?/*?make?sure?all?lru?add?caches?are?flushed?*/?? ?invalidate_mapping_pages(mapping,?0,?-1);?? ?/*?99%?of?the?time,?we?don't?need?to?flush?the?cleancache?on?the?bdev.?? ?*?But,?for?the?strange?corners,?lets?be?cautious?? ?*/?? ?cleancache_invalidate_inode(mapping);?? }?? EXPORT_SYMBOL(invalidate_bdev);
光代碼不夠,現在讓我們看下對/dev/h_sda這個塊設備執行BLKFLSBUF的IOCTL命令前后的實際內存變化:
[0225_19:10:25:10s][root@test?nfs_dir]?#?cat?/proc/meminfo?? [0225_19:10:25:10s]MemTotal:?90532?kB?? [0225_19:10:25:10s]MemFree:?12296?kB?? [0225_19:10:25:10s]Buffers:?46076?kB?? [0225_19:10:25:10s]Cached:?4136?kB?? …………?? [0225_19:10:42:10s][root@test?nfs_dir]?#?/mnt/nfs_dir/a.out?? [0225_19:10:42:10s]ioctl?cmd?BLKFLSBUF?ok!?? [0225_19:10:44:10s][root@test?nfs_dir]?#?cat?/proc/meminfo?? [0225_19:10:44:10s]MemTotal:?90532?kB?? [0225_19:10:44:10s]MemFree:?58988?kB?? [0225_19:10:44:10s]Buffers:?0?kB?? …………?? [0225_19:10:44:10s]Cached:?4144?kB
執行的效果如代碼中看到的,Buffers已被全部清除了,MemFree一下增長了約46MB,可以知道原先的Buffer已被回收并轉化為可用的內存。整個過程Cache幾乎沒有變化,僅增加的8K ?cache內存可以推斷用于a.out本身及其他庫文件的加載。
上述a.out的示例如下:
#include?<stdio.h>?? #include?<fcntl.h>?? #include?<errno.h>? #include?<sys>? #define?BLKFLSBUF?_IO(0x12,?97)? int?main(int?argc,?char*?argv[])?? {?? ?int?fd?=?-1;?? ?fd?=?open("/dev/h_sda",?O_RDWR);? ?if?(fd?<p>綜上,使用塊設備命令BLKFLSBUF能有效的清除塊設備上的所有buffer,且清除后的buffer能立即被釋放變為可用內存。</p> <p>利用這一點,聯系后端業務場景,給出應用程序編程建議:</p> <ul> <li>每次關閉一個塊設備文件描述符前,必須要調用BLKFLSBUF命令,確保buffer中的臟數據及時刷入塊設備,避免意外斷電導致數據丟失,同時也起到及時釋放回收buffer的目的。</li> <li>當操作一個較大的塊設備時,必要時可以調用BLKFLSBUF命令。怎樣算較大的塊設備?一般理解為當前Linux系統可用的物理內存小于操作的塊設備大小。</li> </ul> <p><strong>5. 使用drop_caches控制Cache和Buffer</strong></p> <p>/proc是一個虛擬文件系統,我們可以通過對它的讀寫操作作為與kernel實體間進行通信的一種手段.也就是說可以通過修改/proc中的文件來對當前kernel的行為做出調整。關于Cache和Buffer的控制,我們可以通過echo ?1 > /proc/sys/vm/drop_caches進行操作。</p> <p>首先來看下內核源碼實現:</p> <pre class="brush:js;toolbar:false;">int?drop_caches_sysctl_handler(ctl_table?*table,?int?write,?? ?void?__user?*buffer,?size_t?*length,?loff_t?*ppos)?? {?? ?int?ret;?? ?? ? ?ret?=?proc_dointvec_minmax(table,?write,?buffer,?length,?ppos);?? ?if?(ret)?? ?return?ret;?? ?if?(write)?{?? ?/*?=>?echo?1?>?/proc/sys/vm/drop_caches?清理頁緩存?*/?? ?if?(sysctl_drop_caches?&?1)?? ?/*?=>?遍歷所有的超級塊,清理所有的緩存?*/?? ?iterate_supers(drop_pagecache_sb,?NULL);?? ?if?(sysctl_drop_caches?&?2)?? ?drop_slab();?? ?}?? ?return?0;?? }?? ?? ? /**?? ?*?iterate_supers?-?call?function?for?all?active?superblocks?? ?*?@f:?function?to?call?? ?*?@arg:?argument?to?pass?to?it?? ?*?? ?*?Scans?the?superblock?list?and?calls?given?function,?passing?it?? ?*?locked?superblock?and?given?argument.?? ?*/?? void?iterate_supers(void?(*f)(struct?super_block?*,?void?*),?void?*arg)?? {?? ?struct?super_block?*sb,?*p?=?NULL;?? ?? ? ?spin_lock(&sb_lock);?? ?list_for_each_entry(sb,?&super_blocks,?s_list)?{?? ?if?(hlist_unhashed(&sb->s_instances))?? ?continue;?? ?sb->s_count++;?? ?spin_unlock(&sb_lock);?? ?? ? ?down_read(&sb->s_umount);? ?if?(sb->s_root?&&?(sb->s_flags?&?MS_BORN))?? ?f(sb,?arg);?? ?up_read(&sb->s_umount);? ?? ? ?spin_lock(&sb_lock);?? ?if?(p)? ?__put_super(p);?? ?p?=?sb;?? ?}?? ?if?(p)?? ?__put_super(p);?? ?spin_unlock(&sb_lock);? }? ?? ? /*?=>?清理文件系統(包括bdev偽文件系統)的頁緩存?*/?? static?void?drop_pagecache_sb(struct?super_block?*sb,?void?*unused)?? {?? ?struct?inode?*inode,?*toput_inode?=?NULL;?? ?? ? ?spin_lock(&inode_sb_list_lock);?? ?/*?=>?遍歷所有的inode?*/?? ?list_for_each_entry(inode,?&sb->s_inodes,?i_sb_list)?{?? ?spin_lock(&inode->i_lock);?? ?/*? ?*?=>?若當前狀態為(I_FREEING|I_WILL_FREE|I_NEW)?或?? ?*?=>?若沒有緩存頁?? ?*?=>?則跳過?? ?*/?? ?if?((inode->i_state?&?(I_FREEING|I_WILL_FREE|I_NEW))?||?? ?(inode->i_mapping->nrpages?==?0))?{? ?spin_unlock(&inode->i_lock);?? ?continue;?? ?}?? ?__iget(inode);?? ?spin_unlock(&inode->i_lock);?? ?spin_unlock(&inode_sb_list_lock);?? ?/*?=>?清除緩存頁(除了臟頁、上鎖的、正在回寫的或映射在頁表中的)*/?? ?invalidate_mapping_pages(inode->i_mapping,?0,?-1);?? ?iput(toput_inode);?? ?toput_inode?=?inode;?? ?spin_lock(&inode_sb_list_lock);?? ?}? ?spin_unlock(&inode_sb_list_lock);?? ?iput(toput_inode);?? }
綜上,echo 1 > ?/proc/sys/vm/drop_caches會清除所有inode的緩存頁,這里的inode包括VFS的inode、所有文件系統inode(也包括bdev偽文件系統塊設備的inode的緩存頁)。所以該命令執行后,就會將整個系統的page ?cache和buffer cache全部清除,當然前提是這些cache都是非臟的、沒有正被使用的。
接下來看下實際效果:
[root@test?usb]?#?cat?/proc/meminfo? MemTotal:?90516?kB? MemFree:?12396?kB? Buffers:?96?kB? Cached:?60756?kB? [root@test?usb]?#?busybox_v400?sync? [root@test?usb]?#?busybox_v400?sync? [root@test?usb]?#?busybox_v400?sync? [root@test?usb]?#?echo?1?>?/proc/sys/vm/drop_caches? [root@test?usb]?#?cat?/proc/meminfo? MemTotal:?90516?kB? MemFree:?68820?kB? Buffers:?12?kB? Cached:?4464?kB
可以看到Buffers和Cached都降了下來,在drop_caches前建議執行sync命令,以確保數據的完整性。sync ?命令會將所有未寫的系統緩沖區寫到磁盤中,包含已修改的 i-node、已延遲的塊 I/O 和讀寫映射文件等。
上面的設置雖然簡單但是比較粗暴,使cache的作用基本無法發揮,尤其在系統壓力比較大時進行drop ?cache處理容易產生問題。因為drop_cache是全局在清內存,清的過程會加頁面鎖,導致有些進程等頁面鎖時超時,導致問題發生。因此,需要根據系統的狀況進行適當的調節尋找最佳的方案。
6. 經驗總結
以上分別討論了Cache和Buffer分別從哪里來?什么時候消耗在哪里?如何分別控制Cache和Buffer這三個問題。最后還介紹了vmtouch工具的使用。
要深入理解Linux的Cache和Buffer牽涉大量內核核心機制(VFS、內存管理、塊設備驅動、頁高速緩存、文件訪問、頁框回寫),需要制定計劃在后續工作中不斷理解和消化。
相關推薦:《Linux視頻教程》