溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

linux buffer指的是什么

發(fā)布時(shí)間:2023-04-25 10:02:14 來源:億速云 閱讀:162 作者:iii 欄目:建站服務(wù)器

本文小編為大家詳細(xì)介紹“l(fā)inux buffer指的是什么”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“l(fā)inux buffer指的是什么”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識吧。

在linux中,Buffer是指緩沖區(qū),是一個(gè)用于存儲速度不同步的設(shè)備或優(yōu)先級不同的設(shè)備之間傳輸數(shù)據(jù)的區(qū)域,是系統(tǒng)兩端處理速度平衡(從長時(shí)間尺度上看)時(shí)使用的;它的引入是為了減小短期內(nèi)突發(fā)I/O的影響,起到流量整形的作用。

buffer和cache是什么

1、 Buffer(緩沖區(qū))是系統(tǒng)兩端處理速度平衡(從長時(shí)間尺度上看)時(shí)使用的。它的引入是為了減小短期內(nèi)突發(fā)I/O的影響,起到流量整形的作用。

Buffer是一個(gè)用于存儲速度不同步的設(shè)備或優(yōu)先級不同的設(shè)備之間傳輸數(shù)據(jù)的區(qū)域。通過緩沖區(qū),可以使進(jìn)程之間的相互等待變少,從而使從速度慢的設(shè)備讀入數(shù)據(jù)時(shí),速度快的設(shè)備的操作進(jìn)程不發(fā)生間斷。

比如:我們用X雷下載一部電影,不可能下載一點(diǎn)就寫一點(diǎn)兒磁盤,這么干的話真的毀硬盤。反之先寫到buffer緩沖區(qū),攢多了再一次性寫入磁盤,減少I/O,既有效率,又對硬盤友好。

2、 Cache(緩存)則是兩端處理速度不匹配時(shí)的一種折衷策略。因?yàn)镃PU和memory之間的速度差異越來越大,所以人們充分利用數(shù)據(jù)的局部性特征,通過使用存儲系統(tǒng)分級(memory hierarchy)的策略來減小這種差異帶來的影響。

理解Linux的Cache和Buffer

cache指的是Linux中的page cache,buffer指的是buffer cache,也即cat  /proc/meminfo中顯示的cache和buffer。

我們知道,Linux下頻繁存取文件或單個(gè)大文件時(shí)物理內(nèi)存會很快被用光,當(dāng)程序結(jié)束后內(nèi)存不會被正常釋放而是一直作為cahce占著內(nèi)存。因此系統(tǒng)經(jīng)常會因?yàn)檫@點(diǎn)導(dǎo)致OOM產(chǎn)生,尤其在等大壓力場景下概率較高,此時(shí),第一時(shí)間查看cache和buffer內(nèi)存是非常高的。此類問題目前尚未有一個(gè)很好的解決方案,以往遇到大多會做規(guī)避處理,因此本案嘗試給出一個(gè)分析和解決的思路。

解決該問題的關(guān)鍵是理解什么是cache和buffer,什么時(shí)候消耗在哪里以及如何控制cache和buffer,所以本問主要圍繞這幾點(diǎn)展開。整個(gè)討論過程盡量先從內(nèi)核源碼分析入手,然后提煉APP相關(guān)接口并進(jìn)行實(shí)際操作驗(yàn)證,最后總結(jié)給出應(yīng)用程序的編程建議。

可以通過free或者cat /proc/meminfo查看到系統(tǒng)的buffer和cache情況。

linux buffer指的是什么

free命令的全解析

1. Cache和Buffer分析

從cat /proc/meminfo入手,先看看該接口的實(shí)現(xiàn):

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 < 0)  
 cached = 0;  
……  
 seq_printf(m,  
 "MemTotal: %8lu kB\n"  
 "MemFree: %8lu kB\n"  
 "Buffers: %8lu kB\n"  
 "Cached: %8lu kB\n"  
 ……  
 ,  
 K(i.totalram),  
 K(i.freeram),  
 K(i.bufferram),  
 K(cached),  
 ……  
 );  
……  
}

其中,內(nèi)核中以頁框?yàn)閱挝?,通過宏K轉(zhuǎn)化成以KB為單位輸出。這些值是通過si_meminfo來獲取的:

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(),該函數(shù)計(jì)算塊設(shè)備使用的頁框數(shù),遍歷所有塊設(shè)備,將使用的頁框數(shù)相加。而不包含普通文件使用的頁框數(shù)。

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就是塊設(shè)備占用的頁框數(shù)量;

  • Cache的大小為內(nèi)核總的page cache減去swap cache和塊設(shè)備占用的頁框數(shù)量,實(shí)際上cache即為普通文件的占用的page  cache。

通過內(nèi)核代碼分析(這里略過復(fù)雜的內(nèi)核代碼分析),雖然兩者在實(shí)現(xiàn)上差別不是很大,都是通過address_space對象進(jìn)行管理的,但是page  cache是對文件數(shù)據(jù)的緩存而buffer  cache是對塊設(shè)備數(shù)據(jù)的緩存。對于每個(gè)塊設(shè)備都會分配一個(gè)def_blk_ops的文件操作方法,這是設(shè)備的操作方法,在每個(gè)塊設(shè)備的inode(bdev偽文件系統(tǒng)的inode)下面會存在一個(gè)radix  tree,這個(gè)radix tree下面將會放置緩存數(shù)據(jù)的page頁。這個(gè)page的數(shù)量將會在cat  /proc/meminfobuffer一欄中顯示。也就是在沒有文件系統(tǒng)的情況下,采用dd等工具直接對塊設(shè)備進(jìn)行操作的數(shù)據(jù)會緩存到buffer  cache中。如果塊設(shè)備做了文件系統(tǒng),那么文件系統(tǒng)中的文件都有一個(gè)inode,這個(gè)inode會分配ext3_ops之類的操作方法,這些方法是文件系統(tǒng)的方法,在這個(gè)inode下面同樣存在一個(gè)radix  tree,這里也會緩存文件的page頁,緩存頁的數(shù)量在cat /proc/meminfo的cache一欄進(jìn)行統(tǒng)計(jì)。此時(shí)對文件操作,那么數(shù)據(jù)大多會緩存到page  cache,不多的是文件系統(tǒng)文件的元數(shù)據(jù)會緩存到buffer cache。

這里,我們使用cp命令拷貝一個(gè)50MB的文件操作,內(nèi)存會發(fā)生什么變化:

[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卻不變。那么過一段時(shí)間,Linux會自動釋放掉所用的cache內(nèi)存嗎?一個(gè)小時(shí)后查看proc/meminfo顯示cache仍然沒有變化。

接著,我們看下使用dd命令對塊設(shè)備寫操作前后的內(nèi)存變化:

[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  
….. ……

裸寫塊設(shè)備前Buffs為0,裸寫硬盤過程中每隔一段時(shí)間查看內(nèi)存信息發(fā)現(xiàn)Buffers一直在增加,空閑內(nèi)存越來越少,而Cached數(shù)量一直保持不變。

總結(jié):

通過代碼分析及實(shí)際操作,我們理解了buffer cache和page cache都會占用內(nèi)存,但也看到了兩者的差別。page  cache針對文件的cache,buffer是針對塊設(shè)備數(shù)據(jù)的cache。Linux在可用內(nèi)存充裕的情況下,不會主動釋放page cache和buffer  cache。

2. 使用posix_fadvise控制Cache

在Linux中文件的讀寫一般是通過buffer io方式,以便充分利用到page cache。

Buffer  IO的特點(diǎn)是讀的時(shí)候,先檢查頁緩存里面是否有需要的數(shù)據(jù),如果沒有就從設(shè)備讀取,返回給用戶的同時(shí),加到緩存一份;寫的時(shí)候,直接寫到緩存去,再由后臺的進(jìn)程定期刷到磁盤去。這樣的機(jī)制看起來非常的好,實(shí)際也能提高文件讀寫的效率。

但是當(dāng)系統(tǒng)的IO比較密集時(shí),就會出問題。當(dāng)系統(tǒng)寫的很多,超過了內(nèi)存的某個(gè)上限時(shí),后臺的回寫線程就會出來回收頁面,但是一旦回收的速度小于寫入的速度,就會觸發(fā)OOM。最關(guān)鍵的是整個(gè)過程由內(nèi)核參與,用戶不好控制。

那么到底如何才能有效的控制cache呢?

目前主要由兩種方法來規(guī)避風(fēng)險(xiǎn):

  • 走direct io;

  • 走buffer io,但是定期清除無用page cache;

這里當(dāng)然討論的是第二種方式,即在buffer io方式下如何有效控制page cache。

在程序中只要知道文件的句柄,就能用:

int posix_fadvise(int fd, off_t offset, off_t len, int advice);

POSIX_FADV_DONTNEED (該文件在接下來不會再被訪問),但是曾有開發(fā)人員反饋懷疑該接口的有效性。那么該接口確實(shí)有效嗎?首先,我們查看mm/fadvise.c內(nèi)核代碼來看posix_fadvise是如何實(shí)現(xiàn)的:

/*  
 * 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)  
{  
 … … … …  
 /* => 將指定范圍內(nèi)的數(shù)據(jù)從page cache中換出 */  
 case POSIX_FADV_DONTNEED:  
 /* => 如果后備設(shè)備不忙的話,先調(diào)用__filemap_fdatawrite_range把臟頁面刷掉 */  
 if (!bdi_write_congested(mapping->backing_dev_info))  
 /* => WB_SYNC_NONE: 不是同步等待頁面刷新完成,只是提交了 */  
 /* => 而fsync和fdatasync是用WB_SYNC_ALL參數(shù)等到完成才返回的 */  
 __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 < (end_index - start_index + 1)) {  
 lru_add_drain_all();  
 invalidate_mapping_pages(mapping, start_index,  
 end_index);  
 }  
 }  
 break;  
… … … …  
}

我們可以看到如果后臺系統(tǒng)不忙的話,會先調(diào)用__filemap_fdatawrite_range把臟頁面刷掉,刷頁面用的參數(shù)是是  WB_SYNC_NONE,也就是說不是同步等待頁面刷新完成,提交完寫臟頁后立即返回了。

然后再調(diào)invalidate_mapping_pages清除頁面,回收內(nèi)存:

/* => 清除緩存頁(除了臟頁、上鎖的、正在回寫的或映射在頁表中的)*/  
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 <= end && pagevec_lookup(&pvec, mapping, index,  
 min(end - index, (pgoff_t)PAGEVEC_SIZE - 1) + 1)) {  
 mem_cgroup_uncharge_start();  
 for (i = 0; i < pagevec_count(&pvec); i++) {  
 struct page *page = pvec.pages[i];  
  
 
 /* We rely upon deletion not changing page->index */  
 index = page->index;  
 if (index > end)  
 break;  
  
 
 if (!trylock_page(page))  
 continue;  
 WARN_ON(page->index != index);  
 /* => 無效一個(gè)文件的緩存 */  
 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.  
 */  
/* => 無效一個(gè)文件的緩存 */  
int invalidate_inode_page(struct page *page)  
{  
 struct address_space *mapping = page_mapping(page);  
 if (!mapping)  
 return 0;  
 /* => 若當(dāng)前頁是臟頁或正在寫回的頁,直接返回 */  
 if (PageDirty(page) || PageWriteback(page))  
 return 0;  
 /* => 若已經(jīng)被映射到頁表了,則直接返回 */  
 if (page_mapped(page))  
 return 0;  
 /* => 如果滿足了以上條件就調(diào)用invalidate_complete_page繼續(xù) */  
 return invalidate_complete_page(mapping, page);  
}  
從上面的代碼可以看到清除相關(guān)的頁面要滿足二個(gè)條件: 1. 不臟且沒在回寫; 2. 未被使用。如果滿足了這二個(gè)條件就調(diào)用invalidate_complete_page繼續(xù):  
/* => 無效一個(gè)完整的頁 */  
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);  
 /* => 解除與之綁定的地址空間結(jié)構(gòu) */  
 page->mapping = NULL;  
 /* Leave page->index set: truncation lookup relies upon it */  
 /* => 減少地址空間中的頁計(jì)數(shù) */ 
 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后相關(guān)的內(nèi)存沒有被釋放出來:頁面還臟是最關(guān)鍵的因素。

但是我們?nèi)绾伪WC頁面全部不臟呢?fdatasync或者fsync都是選擇,或者Linux下新系統(tǒng)調(diào)用sync_file_range都是可用的,這幾個(gè)都是使用WB_SYNC_ALL模式強(qiáng)制要求回寫完畢才返回的。所以應(yīng)該這樣做:

fdatasync(fd); 
posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);

總結(jié):

使用posix_fadvise可以有效的清除page cache,作用范圍為文件級。下面給出應(yīng)用程序編程建議:

  • 用于測試I/O的效率時(shí),可以用posix_fadvise來消除cache的影響;

  • 當(dāng)確認(rèn)訪問的文件在接下來一段時(shí)間不再被訪問時(shí),很有必要調(diào)用posix_fadvise來避免占用不必要的可用內(nèi)存空間。

  • 若當(dāng)前系統(tǒng)內(nèi)存十分緊張時(shí),且在讀寫一個(gè)很大的文件時(shí),為避免OOM風(fēng)險(xiǎn),可以分段邊讀寫邊清cache,但也直接導(dǎo)致性能的下降,畢竟空間和時(shí)間是一對矛盾體。

3. 使用vmtouch控制Cache

vmtouch是一個(gè)可移植的文件系統(tǒng)cahce診斷和控制工具。近來該工具被廣泛使用,最典型的例子是:移動應(yīng)用Instagram(照片墻)后臺服務(wù)端使用了vmtouch管理控制page  cache。了解vmtouch原理及使用可以為我們后續(xù)后端設(shè)備所用。

快速安裝指南:

$ git clone https://github.com/hoytech/vmtouch.git 
$ cd vmtouch 
$ make 
$ sudo make install

vmtouch用途:

  • 查看一個(gè)文件(或者目錄)哪些部分在內(nèi)存中;

  • 把文件調(diào)入內(nèi)存;

  • 把文件清除出內(nèi)存,即釋放page cache;

  • 把文件鎖住在內(nèi)存中而不被換出到磁盤上;

  • ……

vmtouch實(shí)現(xiàn):

其核心分別是兩個(gè)系統(tǒng)調(diào)用,mincore和posix_fadvise。兩者具體使用方法使用man幫助都有詳細(xì)的說明。posix_fadvise已在上文提到,用法在此不作說明。簡單說下mincore:

NAME 
 mincore - determine whether pages are resident in memory 
 
  
SYNOPSIS 
 #include <unistd.h> 
 #include <sys/mman.h> 
  
 
 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

mincore需要調(diào)用者傳入文件的地址(通常由mmap()返回),它會把文件在內(nèi)存中的情況寫在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、 獲取當(dāng)前/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、 當(dāng)前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

這時(shí)使用tail命令將部分文件讀取到內(nèi)存中:

[root@test usb] # busybox_v400 tail -n 10 test.bin > /dev/null

現(xiàn)在再來看一下:

[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個(gè)page駐留在內(nèi)存中。

例3、 最后使用-t選項(xiàng)將剩下的test.bin文件全部讀入內(nèi)存:

[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

這時(shí)候再來看下是否真的被釋放了:

[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

以上通過代碼分析及實(shí)際操作總結(jié)了vmtouch工具的使用,建議APP組后續(xù)集成或借鑒vmtouch工具并靈活應(yīng)用到后端設(shè)備中,必能達(dá)到有效管理和控制page  cache的目的。

4. 使用BLKFLSBUF清Buffer

通過走讀塊設(shè)備驅(qū)動IOCTL命令實(shí)現(xiàn),發(fā)現(xiàn)該命令能有效的清除整個(gè)塊設(shè)備所占用的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);

光代碼不夠,現(xiàn)在讓我們看下對/dev/h_sda這個(gè)塊設(shè)備執(zhí)行BLKFLSBUF的IOCTL命令前后的實(shí)際內(nèi)存變化:

[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

執(zhí)行的效果如代碼中看到的,Buffers已被全部清除了,MemFree一下增長了約46MB,可以知道原先的Buffer已被回收并轉(zhuǎn)化為可用的內(nèi)存。整個(gè)過程Cache幾乎沒有變化,僅增加的8K  cache內(nèi)存可以推斷用于a.out本身及其他庫文件的加載。

上述a.out的示例如下:

#include <stdio.h>  
#include <fcntl.h>  
#include <errno.h> 
#include <sys/ioctl.h> 
#define BLKFLSBUF _IO(0x12, 97) 
int main(int argc, char* argv[])  
{  
 int fd = -1;  
 fd = open("/dev/h_sda", O_RDWR); 
 if (fd < 0) 
 { 
 return -1; 
 }  
 if (ioctl(fd, BLKFLSBUF, 0))  
 {  
 printf("ioctl cmd BLKFLSBUF failed, errno:%d\n", errno);  
 } 
 close(fd); 
printf("ioctl cmd BLKFLSBUF ok!\n"); 
 return 0;  
}

綜上,使用塊設(shè)備命令BLKFLSBUF能有效的清除塊設(shè)備上的所有buffer,且清除后的buffer能立即被釋放變?yōu)榭捎脙?nèi)存。

利用這一點(diǎn),聯(lián)系后端業(yè)務(wù)場景,給出應(yīng)用程序編程建議:

  • 每次關(guān)閉一個(gè)塊設(shè)備文件描述符前,必須要調(diào)用BLKFLSBUF命令,確保buffer中的臟數(shù)據(jù)及時(shí)刷入塊設(shè)備,避免意外斷電導(dǎo)致數(shù)據(jù)丟失,同時(shí)也起到及時(shí)釋放回收buffer的目的。

  • 當(dāng)操作一個(gè)較大的塊設(shè)備時(shí),必要時(shí)可以調(diào)用BLKFLSBUF命令。怎樣算較大的塊設(shè)備?一般理解為當(dāng)前Linux系統(tǒng)可用的物理內(nèi)存小于操作的塊設(shè)備大小。

5. 使用drop_caches控制Cache和Buffer

/proc是一個(gè)虛擬文件系統(tǒng),我們可以通過對它的讀寫操作作為與kernel實(shí)體間進(jìn)行通信的一種手段.也就是說可以通過修改/proc中的文件來對當(dāng)前kernel的行為做出調(diào)整。關(guān)于Cache和Buffer的控制,我們可以通過echo  1 > /proc/sys/vm/drop_caches進(jìn)行操作。

首先來看下內(nèi)核源碼實(shí)現(xiàn):

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); 
} 
  
 
/* => 清理文件系統(tǒng)(包括bdev偽文件系統(tǒng))的頁緩存 */  
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);  
 /* 
 * => 若當(dāng)前狀態(tài)為(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、所有文件系統(tǒng)inode(也包括bdev偽文件系統(tǒng)塊設(shè)備的inode的緩存頁)。所以該命令執(zhí)行后,就會將整個(gè)系統(tǒng)的page  cache和buffer cache全部清除,當(dāng)然前提是這些cache都是非臟的、沒有正被使用的。

接下來看下實(shí)際效果:

[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前建議執(zhí)行sync命令,以確保數(shù)據(jù)的完整性。sync  命令會將所有未寫的系統(tǒng)緩沖區(qū)寫到磁盤中,包含已修改的 i-node、已延遲的塊 I/O 和讀寫映射文件等。

上面的設(shè)置雖然簡單但是比較粗暴,使cache的作用基本無法發(fā)揮,尤其在系統(tǒng)壓力比較大時(shí)進(jìn)行drop  cache處理容易產(chǎn)生問題。因?yàn)閐rop_cache是全局在清內(nèi)存,清的過程會加頁面鎖,導(dǎo)致有些進(jìn)程等頁面鎖時(shí)超時(shí),導(dǎo)致問題發(fā)生。因此,需要根據(jù)系統(tǒng)的狀況進(jìn)行適當(dāng)?shù)恼{(diào)節(jié)尋找最佳的方案。

讀到這里,這篇“l(fā)inux buffer指的是什么”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識點(diǎn)還需要大家自己動手實(shí)踐使用過才能領(lǐng)會,如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI