検索

Linux では、バッファとは、速度が同期していないデバイス間や優先度の異なるデバイス間で送信されるデータを格納する領域であり、システムの両端の処理速度のバランスのことです (長期的な規模を見るときに使用されます); 短期的なバースト I/O の影響を軽減し、トラフィック シェーピングの役割を果たすために導入されます。

Linuxバッファとは何ですか

#このチュートリアルの動作環境: linux7.3 システム、Dell G3 コンピューター。

バッファとキャッシュとは

1. バッファ(buffer)は、システムの両端の処理速度のバランスをとる場合(長期的な観点から)に使用されます。 )。これは、短期的にバースト I/O の影響を軽減し、トラフィック シェーピングの役割を果たすために導入されています。

バッファは、速度が同期していないデバイス間、または優先度が異なるデバイス間で転送されるデータを保存するために使用される領域です。バッファを介してプロセス間の待ち時間を減らすことができるため、遅いデバイスからデータを読み出すときに、高速なデバイスの動作プロセスが中断されなくなります。

例: X Thunder を使用して映画をダウンロードする場合、少しダウンロードしてからディスクに少し書き込むことは不可能であり、これを実行すると、ハードディスクが本当に破壊されてしまいます。逆に、最初にバッファに書き込んでからディスクに一度に書き込むと I/O が削減され、効率的でハードディスクにも優しい方法です。

2. キャッシュは、両端の処理速度が一致しない場合の妥協策です。 CPU とメモリの速度差はますます大きくなっているため、データの局所的な特性を最大限に活用し、ストレージ システムの階層 (メモリ階層) 戦略を使用してこの差の影響を軽減しています。

Linux キャッシュとバッファについて

cache は Linux のページ キャッシュを指し、buffer は cat /proc/meminfo キャッシュに表示されるバッファ キャッシュを指します。そしてバッファー。

Linux では、ファイルや 1 つの大きなファイルに頻繁にアクセスすると、物理メモリがすぐに使い果たされてしまい、プログラムが終了してもメモリは正常に解放されず、常にメモリを占有します。キャッシュ。したがって、特に負荷の高いシナリオでは、これが原因でシステムで OOM が発生することが多く、このとき、最初にキャッシュとバッファー メモリをチェックする負荷が非常に高くなります。現在、この種の問題に対する良い解決策はなく、過去に遭遇した問題のほとんどは回避さ​​れていたため、このケースでは分析と解決策のアイデアを提供することを試みます。

この問題を解決する鍵は、キャッシュとバッファとは何か、それらがいつどこで消費されるか、キャッシュとバッファをどのように制御するかを理解することです。そのため、この質問は主にこれらの点に焦点を当てています。議論のプロセス全体は、可能な限りカーネル ソース コードの分析から開始し、次に APP 関連のインターフェイスを改良して実用的な検証を実施し、最後にアプリケーション プログラミングの提案をまとめて提供する必要があります。

システムのバッファとキャッシュのステータスは、free または cat /proc/meminfo を通じて確認できます。

Linuxバッファとは何ですか

無料コマンドの完全な分析

1. キャッシュとバッファの分析

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; 
}

バッファラムは 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 のキャッシュとバッファのソースを結論付けることができます:

  • バッファはブロック デバイスによって占有されるページ フレームの数です;
  • キャッシュのサイズは、カーネルの総数からページ キャッシュからスワップ キャッシュとブロック デバイスによって占有されるページ フレームの数を引いたものです。実際には、キャッシュは通常のファイルによって占有されるページ キャッシュです。

カーネル コード分析 (複雑なカーネル コード分析はここでは省略します) によると、この 2 つの実装の違いはそれほど大きくありません。どちらも address_space オブジェクトを通じて管理されますが、ページ キャッシュはファイル用です。データのキャッシュとバッファ キャッシュは、ブロック デバイス データのキャッシュです。各ブロックデバイスには def_blk_ops というファイル操作メソッドが割り当てられます これはデバイスの操作メソッドです 各ブロックデバイスの i ノード (bdev 擬似ファイルシステムの i ノード) の下に基数ツリーが存在します この基数ツリーの下にキャッシュされたデータのページが配置されます。このページの番号は cat /proc/meminfobuffer 列に表示されます。つまり、ファイルシステムがない場合、ddなどのツールを使ってブロックデバイス上で直接操作したデータはバッファキャッシュにキャッシュされることになります。ブロック デバイスがファイル システムとして使用される場合、ファイル システム内の各ファイルには i ノードがあります。この i ノードには ext3_ops などの操作メソッドが割り当てられます。これらのメソッドはファイル システム メソッドです。この i ノードの下には基数ツリーもあります。ここでも ファイルのページがキャッシュされ、キャッシュされたページ数は cat /proc/meminfo のキャッシュ列でカウントされます。この時点でファイルを操作すると、ほとんどのデータがページ キャッシュにキャッシュされ、ファイル システム ファイルの少量のメタデータがバッファ キャッシュにキャッシュされます。

ここでは、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&#39;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&#39;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&#39;re uptodate, flush out into the cleancache, otherwise  
 * invalidate any existing cleancache entries. We can&#39;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
  • -mmax file size to touch
  • -puse 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&#39;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是全局在清内存,清的过程会加页面锁,导致有些进程等页面锁时超时,导致问题发生。因此,需要根据系统的状况进行适当的调节寻找最佳的方案。

6. 经验总结

以上分别讨论了Cache和Buffer分别从哪里来?什么时候消耗在哪里?如何分别控制Cache和Buffer这三个问题。最后还介绍了vmtouch工具的使用。

要深入理解Linux的Cache和Buffer牵涉大量内核核心机制(VFS、内存管理、块设备驱动、页高速缓存、文件访问、页框回写),需要制定计划在后续工作中不断理解和消化。

相关推荐:《Linux视频教程

以上がLinuxバッファとは何ですかの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
Linuxオペレーティングシステムの5つのコアコンポーネントLinuxオペレーティングシステムの5つのコアコンポーネントMay 08, 2025 am 12:08 AM

Linuxオペレーティングシステムの5つのコアコンポーネントは次のとおりです。1。Kernel、2。Systemライブラリ、3。SystemTools、4。SystemServices、5。FileSystem。これらのコンポーネントは、システムの安定した効率的な動作を確保するために連携し、一緒に強力で柔軟なオペレーティングシステムを形成します。

Linuxの5つの重要な要素:説明Linuxの5つの重要な要素:説明May 07, 2025 am 12:14 AM

Linuxの5つのコア要素は、1。Kernel、2。CommandLineインターフェイス、3。ファイルシステム、4。パッケージ管理、5。コミュニティおよびオープンソースです。一緒に、これらの要素はLinuxの性質と機能を定義します。

Linuxオペレーション:セキュリティとユーザー管理Linuxオペレーション:セキュリティとユーザー管理May 06, 2025 am 12:04 AM

Linuxユーザー管理とセキュリティは、次の手順を通じて達成できます。1。sudouseradd-m-gdevelopers-s/bin/bashjohnなどのコマンドを使用して、ユーザーとグループを作成します。 2. bulkly forループとchpasswdコマンドを使用して、ユーザーを作成し、パスワードポリシーを設定します。 3.一般的なエラー、ホームディレクトリ、シェル設定を確認して修正します。 4.強力な暗号化ポリシー、定期的な監査、最低限の権限の原則などのベストプラクティスを実装します。 5.パフォーマンスを最適化し、SUDOを使用し、PAMモジュールの構成を調整します。これらの方法により、ユーザーを効果的に管理でき、システムセキュリティを改善できます。

Linux操作:ファイルシステム、プロセスなどLinux操作:ファイルシステム、プロセスなどMay 05, 2025 am 12:16 AM

Linuxファイルシステムとプロセス管理のコア操作には、ファイルシステム管理とプロセス制御が含まれます。 1)ファイルシステム操作には、MKDIR、RMDIR、CP、MVなどのコマンドを使用したファイルまたはディレクトリの作成、削除、コピー、および移動が含まれます。 2)プロセス管理には、./my_script.sh&、top、killなどのコマンドを使用して、プロセスの開始、監視、殺害が含まれます。

Linux操作:シェルスクリプトと自動化Linux操作:シェルスクリプトと自動化May 04, 2025 am 12:15 AM

シェルスクリプトは、Linuxシステムでコマンドを自動実行するための強力なツールです。 1)シェルスクリプトは、通訳を介して行ごとにコマンドを実行して、可変置換と条件付き判断を処理します。 2)基本的な使用法には、TARコマンドを使用してディレクトリをバックアップするなど、バックアップ操作が含まれます。 3)高度な使用には、サービスを管理するための機能とケースステートメントの使用が含まれます。 4)デバッグスキルには、セットXを使用してデバッグモードを有効にし、コマンドが故障したときにセットEを終了することが含まれます。 5)サブシェル、使用、最適化ループを避けるために、パフォーマンスの最適化をお勧めします。

Linux操作:コア機能の理解Linux操作:コア機能の理解May 03, 2025 am 12:09 AM

Linuxは、UNIXベースのマルチユーザーのマルチタスクオペレーティングシステムで、シンプルさ、モジュール性、オープン性を強調しています。そのコア機能には、ファイルシステム:ツリー構造で整理され、Ext4、XFS、BTRFSなどの複数のファイルシステムをサポートし、DF-Tを使用してファイルシステムタイプを表示します。プロセス管理:PSコマンドを使用してプロセスを表示し、優先度の設定と信号処理を含むPIDを使用してプロセスを管理します。ネットワーク構成:IPアドレスの柔軟な設定とネットワークサービスの管理、およびsudoipaddraddを使用してIPを構成します。これらの機能は、基本的なコマンドと高度なスクリプト自動化を通じて実際の操作に適用され、効率を向上させ、エラーを削減します。

Linux:メンテナンスモードの入力と終了Linux:メンテナンスモードの入力と終了May 02, 2025 am 12:01 AM

Linuxメンテナンスモードに入る方法には次のものがあります。1。grub構成ファイルを編集し、「シングル」または「1」パラメーターを追加し、Grub構成を更新します。 2。GRUBメニューでスタートアップパラメーターを編集し、「シングル」または「1」を追加します。終了メンテナンスモードでは、システムを再起動する必要があります。これらの手順を使用すると、必要に応じてメンテナンスモードをすばやく入力して安全に終了し、システムの安定性とセキュリティを確保できます。

Linuxの理解:定義されたコアコンポーネントLinuxの理解:定義されたコアコンポーネントMay 01, 2025 am 12:19 AM

Linuxのコアコ​​ンポーネントには、カーネル、シェル、ファイルシステム、プロセス管理、メモリ管理が含まれます。 1)カーネル管理システムリソース、2)シェルはユーザーインタラクションインターフェイスを提供します。3)ファイルシステムは複数の形式をサポートします。4)プロセス管理は、フォークなどのシステムコールを通じて実装され、5)メモリ管理は仮想メモリテクノロジーを使用します。

See all articles

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

SecLists

SecLists

SecLists は、セキュリティ テスターの究極の相棒です。これは、セキュリティ評価中に頻繁に使用されるさまざまな種類のリストを 1 か所にまとめたものです。 SecLists は、セキュリティ テスターが必要とする可能性のあるすべてのリストを便利に提供することで、セキュリティ テストをより効率的かつ生産的にするのに役立ちます。リストの種類には、ユーザー名、パスワード、URL、ファジング ペイロード、機密データ パターン、Web シェルなどが含まれます。テスターはこのリポジトリを新しいテスト マシンにプルするだけで、必要なあらゆる種類のリストにアクセスできるようになります。

SublimeText3 英語版

SublimeText3 英語版

推奨: Win バージョン、コードプロンプトをサポート!

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser は、オンライン試験を安全に受験するための安全なブラウザ環境です。このソフトウェアは、あらゆるコンピュータを安全なワークステーションに変えます。あらゆるユーティリティへのアクセスを制御し、学生が無許可のリソースを使用するのを防ぎます。

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

AtomエディタMac版ダウンロード

AtomエディタMac版ダウンロード

最も人気のあるオープンソースエディター