共計 20976 個字符,預計需要花費 53 分鐘才能閱讀完成。
本文丸趣 TV 小編為大家詳細介紹“linux buffer 指的是什么”,內容詳細,步驟清晰,細節處理妥當,希望這篇“linux buffer 指的是什么”文章能幫助大家解決疑惑,下面跟著丸趣 TV 小編的思路慢慢深入,一起來學習新知識吧。
在 linux 中,Buffer 是指緩沖區,是一個用于存儲速度不同步的設備或優先級不同的設備之間傳輸數據的區域,是系統兩端處理速度平衡(從長時間尺度上看)時使用的;它的引入是為了減小短期內突發 I / O 的影響,起到流量整形的作用。
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 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),
……
);
……
}
其中,內核中以頁框為單位,通過宏 K 轉化成以 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(),該函數計算塊設備使用的頁框數,遍歷所有塊設備,將使用的頁框數相加。而不包含普通文件使用的頁框數。
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 (end_index - start_index + 1)) {
lru_add_drain_all();
invalidate_mapping_pages(mapping, start_index,
end_index);
}
}
break;
… … … …
}
我們可以看到如果后臺系統不忙的話,會先調用__filemap_fdatawrite_range 把臟頁面刷掉,刷頁面用的參數是是 WB_SYNC_NONE,也就是說不是同步等待頁面刷新完成,提交完寫臟頁后立即返回了。
然后再調 invalidate_mapping_pages 清除頁面,回收內存:
/* = 清除緩存頁(除了臟頁、上鎖的、正在回寫的或映射在頁表中的)*/
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);
/* = 無效一個文件的緩存 */
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/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 需要調用者傳入文件的地址 (通常由 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/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;
}
綜上,使用塊設備命令 BLKFLSBUF 能有效的清除塊設備上的所有 buffer,且清除后的 buffer 能立即被釋放變為可用內存。
利用這一點,聯系后端業務場景,給出應用程序編程建議:
每次關閉一個塊設備文件描述符前,必須要調用 BLKFLSBUF 命令,確保 buffer 中的臟數據及時刷入塊設備,避免意外斷電導致數據丟失,同時也起到及時釋放回收 buffer 的目的。
當操作一個較大的塊設備時,必要時可以調用 BLKFLSBUF 命令。怎樣算較大的塊設備? 一般理解為當前 Linux 系統可用的物理內存小于操作的塊設備大小。
5. 使用 drop_caches 控制 Cache 和 Buffer
/proc 是一個虛擬文件系統, 我們可以通過對它的讀寫操作作為與 kernel 實體間進行通信的一種手段. 也就是說可以通過修改 /proc 中的文件來對當前 kernel 的行為做出調整。關于 Cache 和 Buffer 的控制,我們可以通過 echo 1 /proc/sys/vm/drop_caches 進行操作。
首先來看下內核源碼實現:
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 是全局在清內存,清的過程會加頁面鎖,導致有些進程等頁面鎖時超時,導致問題發生。因此,需要根據系統的狀況進行適當的調節尋找最佳的方案。
讀到這里,這篇“linux buffer 指的是什么”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注丸趣 TV 行業資訊頻道。
向 AI 問一下細節
丸趣 TV 網 – 提供最優質的資源集合!