您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“linux buffer指的是什么”,內容詳細,步驟清晰,細節處理妥當,希望這篇“linux buffer指的是什么”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
在linux中,Buffer是指緩沖區,是一個用于存儲速度不同步的設備或優先級不同的設備之間傳輸數據的區域,是系統兩端處理速度平衡(從長時間尺度上看)時使用的;它的引入是為了減小短期內突發I/O的影響,起到流量整形的作用。
1、 Buffer(緩沖區)是系統兩端處理速度平衡(從長時間尺度上看)時使用的。它的引入是為了減小短期內突發I/O的影響,起到流量整形的作用。
Buffer是一個用于存儲速度不同步的設備或優先級不同的設備之間傳輸數據的區域。通過緩沖區,可以使進程之間的相互等待變少,從而使從速度慢的設備讀入數據時,速度快的設備的操作進程不發生間斷。
比如:我們用X雷下載一部電影,不可能下載一點就寫一點兒磁盤,這么干的話真的毀硬盤。反之先寫到buffer緩沖區,攢多了再一次性寫入磁盤,減少I/O,既有效率,又對硬盤友好。
2、 Cache(緩存)則是兩端處理速度不匹配時的一種折衷策略。因為CPU和memory之間的速度差異越來越大,所以人們充分利用數據的局部性特征,通過使用存儲系統分級(memory hierarchy)的策略來減小這種差異帶來的影響。
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
-p
-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指的是什么”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。