这篇文章主机频道详细介绍了“linux缓冲区是什么意思”。内容详细,步骤清晰,细节处理得当。希望这篇文章《linux缓冲区是什么意思》能帮你解决疑惑。让我们按照主机频道的思路,一起学习新知识。
在linux中,Buffer指的是缓冲区,用于存储速度不同步的设备之间或者优先级不同的设备之间的数据,在系统两端处理速度均衡时使用(从长时间尺度来看);它的引入是为了减少短时间内突发I/O的影响,起到流量整形的作用。
什么是缓冲区和缓存?1.缓冲区(Buffer)在系统两端平衡处理速度时使用(从长时间尺度来看)。它的引入是为了减少短时间内突发I/O的影响,起到流量整形的作用。
缓冲区是一个区域,用于存储异步速度设备或不同优先级设备之间传输的数据。通过缓冲区,可以减少进程间的相互等待,使得从慢速设备读取数据时,快速设备的操作过程不会被中断。
比如我们用x光下载一部电影,下载完就不可能写一点盘,真的会破坏硬盘。相反,先写到缓冲区,保存多了再一次性写到磁盘,减少I/O,高效又对硬盘友好。
2.缓存是两端处理速度不匹配时的一种折中策略。由于CPU和内存的速度差异越来越大,人们充分利用数据的局部特性,通过使用内存层次化的策略来减少这种差异的影响。
了解Linux Cache和Buffercache在Linux中是指页面缓存,buffer是指缓冲区缓存,也就是cat /proc/meminfo中显示的Cache和buffer。
我们知道,在Linux下文件或单个大文件被频繁访问时,物理内存会被很快用完,程序结束后内存不会被正常释放,而会一直作为cahce占用内存。因此,系统往往会因此而导致OOM,尤其是在高压场景下。这时候第一时间检查缓存和缓冲内存是很高的。这类问题目前没有很好的解决方法,大部分都会逃避过去,所以本案例试图给出一个分析和解决方法。
解决这个问题的关键是了解什么是cache和buffer,什么时候在什么地方消耗,如何控制,所以这个问题主要围绕这几点。整个讨论过程尽可能从内核源代码的分析入手,然后提炼APP的相关接口,并在实践中进行验证。最后,总结了应用程序的编程建议。
可以通过free或者cat /proc/meminfo查看系统的缓冲区和缓存。
自由指挥的全面分析
1.缓存和缓冲区分析
从cat /proc/meminfo开始,我们来看看这个接口的实现:
静态int meminfo _ proc _ show(struct seq _ file * m,void *v)
{
……
cache = global _ page _ state(NR _ FILE _ PAGES)-
total _ swap cache _ pages()-I . buffer ram;
if(缓存的& lt0)
缓存= 0;
……
seq_printf(m,
"内存总计:% 8lu kB \ n & quot
"MemFree:% 8lu kB \ n & quot;
"缓冲区:%8lu kB
"已缓存:% 8lu kB \ n & quot
……
,
K(i.totalram),
K(i.freeram),
K(i.bufferram),
k(缓存),
……
);
……
}其中,在内核中,以页框为单位,通过宏K将输出转换为KB..这些值通过si_meminfo获得:
void si _ meminfo(struct sysinfo * val)
{
val->;totalram = totalram _ pages
val->;shared ram = 0;
val->;freeram =全局_页面_状态(NR _ FREE _ PAGES);
val->;buffer ram = NR _ block dev _ pages();
val->;totalhigh = totalhigh _ pages
val->;free high = NR _ free _ high pages();
val->;mem _ unit = PAGE _ SIZE
}其中bufferram来自nr_blockdev_pages(),此函数计算块设备使用的页帧数,遍历所有块设备,并合计使用的页帧数。不包括普通文件使用的页面帧数。
long nr_blockdev_pages(void)
{
结构块_设备* bdev
long ret = 0;
自旋锁。bdev _ lock);
每个条目列表(bdev & amp;all _ bdevs,bd_list) {
ret+= bdev-& gt;BD _ inode-& gt;I _ mapping-& gt;nrpages
}
自旋解锁(& ampbdev _ lock);
返回ret
}从上面我们可以得到meminfo中缓存和缓冲区的来源:
Buffer是块设备占用的页帧数;
缓存的大小是内核的总页面缓存减去交换缓存和块设备占用的页面帧数。其实缓存就是普通文件占用的页面缓存。
通过内核代码分析(这里忽略复杂的内核代码分析),虽然两者在实现上没有太大区别,都是用address_space对象管理,但是页面缓存是文件数据的缓存,缓冲区缓存是块设备数据的缓存。每个块设备都会被分配一个def_blk_ops的文件操作方法,这是设备的操作方法。在每个块设备的inode (inode(bdev伪文件系统)下,会有一个radix树,缓存数据的page page会放在这个radix树下。该页面的编号将显示在cat /proc/meminfobuffer列中。即在没有文件系统的情况下,使用dd等工具直接在块设备上操作的数据会缓存在buffer cache中。如果块设备有文件系统,那么文件系统中的所有文件都有一个inode,这个inode会分配ext3_ops之类的操作方法,这就是文件系统方法。这个inode下还有一个radix树,文件的页面页面也会缓存在这里。缓存的页数在cat /proc/meminfo的cache列中计算。此时如果操作文件,大部分数据会缓存在页面缓存中,不多的是文件系统文件的元数据会缓存在缓冲区缓存中。
这里,我们使用cp命令复制一个50MB的文件。记忆会怎么样?
[根nfs _目录] # ll -h file_50MB.bin
-rw-rw-r-1 4104 4106 50.0m 2016年2月24日file_50MB.bin
[根nfs目录] # cat /proc/meminfo
内存总量:90532 kB
内存空闲:65696 kB
缓冲区:0 kB
缓存:8148 kB
……
[root @ test NFS _ dir]# CP file _ 50mb . bin/
[root @ test NFS _ dir]# cat/proc/meminfo
内存总量:90532 kB
内存空闲:13012 kB
缓冲区:0 kB
Cached: 60488 kB可以看到cp命令前后,MemFree从65696 kB下降到13012 kB,Cached从8148 kB上升到60488 kB,而Buffers保持不变。那么过一段时间后,Linux会自动释放已使用的缓存内存吗?一个小时后,查看proc/meminfo显示缓存没有变化。
然后,我们来看看用dd命令写入块设备前后的内存变化:
[0225 _ 19:10:44:10s][root @ test NFS _ dir]# cat/proc/meminfo
[0225 _ 19:10:44:10s]内存总计:90532 kB
[0225 _ 19:10:44:10s]内存可用空间:58988 kB
[0225 _ 19:10:44:10s]缓冲区:0 kB
[0225 _ 19:10:44:10s]缓存:4144 kB
............
[0225 _ 19:11:13:11s][root @ test NFS _ dir]# DD if =/dev/zero of =/dev/h _ sda bs = 10M count = 2000 & amp;
[0225 _ 19:11:17:11s][root @ test NFS _ dir]# cat/proc/meminfo
[0225 _ 19:11:17:11s]内存总计:90532 kB
[0225 _ 19:11:17:11s]内存可用空间:11852 kB
[0225 _ 19:11:17:11s]缓冲:36224 kB
[0225 _ 19:11:17:11s]缓存:4148 kB
............
[0225 _ 19:11:21:11s][root @ test NFS _ dir]# cat/proc/meminfo
[0225 _ 19:11:21:11s]内存总计:90532 kB
[0225 _ 19:11:21:11s]内存可用空间:11356 kB
[0225 _ 19:11:21:11s]缓冲:36732 kB
[0225 _ 19:11:21:11s]缓存:4148kB
............
[0225 _ 19:11:41:11s][root @ test NFS _ dir]# cat/proc/meminfo
[0225 _ 19:11:41:11s]内存总计:90532 kB
[0225 _ 19:11:41:11s]内存可用空间:11864 kB
[0225 _ 19:11:41:11s]缓冲:36264 kB
[0225 _ 19:11:41:11s]缓存:4148 kB
.................................................................................................................................................................................
总结:
通过代码分析和实际操作,我们明白缓冲区缓存和页面缓存都会占用内存,但也看到了两者的区别。页面缓存用于文件缓存,缓冲区用于块设备数据缓存。当有足够的可用内存时,Linux不会主动释放页面缓存和缓冲区缓存。
2.使用posix_fadvise来控制缓存。
在Linux中,文件一般是通过缓冲io读写的,这样可以充分利用页面缓存。
Buffer IO的特点是在读取时,先检查页面缓存中是否有需要的数据,如果没有,则从设备中读取,返回给用户,并添加到缓存中;写的时候直接写到缓存,然后后台进程会定时刷到磁盘。这个机制看起来很好,实际上可以提高文件读写的效率。
但是当系统的IO密集时,就会出现问题。当系统写的很多,超过内存的某个极限时,后台回写线程就会出来回收页面,但是一旦回收速度小于写入速度,就会触发OOM。最重要的是整个过程都是内核参与,用户不好控制。
那么如何才能有效控制缓存呢?
目前,规避风险主要有两种方式:
直接去io;
取缓冲io,但定期清理无用的页面缓存;
当然,这里讨论的是第二种方式,即如何在buffer io模式下有效控制页面缓存。
只要知道程序中文件的句柄,就可以使用:
int posix_fadvise(int fd,off_t offset,off_t len,int advice);POSIX_FADV_DONTNEED(这个文件不会再被访问),但是有开发者反馈怀疑这个接口的有效性。那么接口真的有效吗?首先,让我们看看mm/fadvise.c内核代码,看看posix_fadvise是如何实现的:
/*
* POSIX _ FADV _威尔需要可以设置PG_Referenced,而POSIX _ FADV _诺里斯可以
*停用页面并清除PG_Referenced。
*/
SYSCALL_DEFINE4(fadvise64_64,int,fd,loff_t,offset,loff_t,len,int,advice)
{
… … … …
/* = & gt;将指定范围内的数据交换出页面缓存*/
案例POSIX _ FADV _东尼德:
/* = & gt;如果备份设备不忙,首先调用__filemap_fdatawrite_range清除脏页*/
如果(!bdi_write_congested(映射-& gt;backing_dev_info))
/* = & gt;WB_SYNC_NONE:不是等待页面同步刷新,而是提交*/
/* = & gt;而fsync和fdatasync只有在用WB_SYNC_ALL参数*/完成后才返回
__filemap_fdatawrite_range(映射,偏移量,endbyte,
WB _ SYNC _ NONE);
/*第一页和最后一整页!*/
start _ index =(offset+(PAGE _ CACHE _ SIZE-1))& gt;& gtPAGE _ CACHE _ SHIFT
end _ index =(end byte & gt;& gtPAGE _ CACHE _ SHIFT);
/* = & gt;接下来清除页面缓存*/
if(end _ index & gt;=开始索引){
无符号长整型计数= invalidate _ mapping _ pages(mapping,
start_index,end _ index);
/*
*如果无效页面比预期的少,则
*某些页面可能是打开的
*远程cpu的每cpu pagevec。排干所有
* pagevecs并重试。
*/
if(count & lt;(结束索引-开始索引+ 1)) {
LRU _ add _ drain _ all();
invalidate_mapping_pages(映射,开始索引,
end _ index);
}
}
打破;
… … … …
}我们可以看到,如果后台系统不忙,它会先调用__filemap_fdatawrite_range来刷掉脏页,而用来刷页的参数是WB_SYNC_NONE,也就是说,它不会等待页面同步刷新,而是在提交脏页后立即返回。
然后调整invalid _ mapping _ pages以清除页面并回收内存:
/* = & gt;清除缓存页(脏页、锁定页、被写回或映射到页表中的页除外)*/
无符号长整型invalidate _ mapping _ pages(struct address _ space * mapping,
pgoff_t开始,pgoff_t结束)
{
struct pagevec pvec
pgoff _ t index = start
无符号长整型ret
无符号长计数= 0;
int I;
/*
*注意:可能会在shmem/tmpfs映射中调用此函数:
* pagevec_lookup()可能会过早返回0(因为它
*得到了大量的交换条目);但是它& # 39;这几乎不值得担心
* about——很少有东西可以从这样的映射中解放出来
*(大部分页面很脏),已经跳过任何难点。
*/
page vec _ init(& amp;pvec,0);
while(index & lt;= end & amp& amppage vec _ lookup(& amp;pvec,映射,索引,
min(end - index,(pgoff_t)PAGEVEC_SIZE - 1) + 1)) {
mem _ cgroup _ uncharge _ start();
for(I = 0;我& ltpagevec _ count(& amp;pvec);i++) {
struct page * page = pvec . pages[I];
/*我们依靠删除而不是换页-& gt;索引*/
index = page-& gt;指数;
if(index & gt;结束)
打破;
如果(!trylock_page(page))
继续;
WARN_ON(第-& gt;指数!= index);
/* = & gt;文件的缓存无效*/
ret = invalidate _ inode _ page(page);
解锁_页面(page);
/*
*无效是一种提示,表明该页面不再是
*的利息,并试图加快其收回。
*/
如果(!ret)
deactivate_page(页面);
count+= ret;
}
page vec _ release(& amp;pvec);
mem _ cgroup _ uncharge _ end();
cond _ resched();
index++;
}
返回计数;
}
/*
*从页面缓存映射中安全地使一个页面无效。
*它只删除干净的、未使用的页面。页面必须被锁定。
*
*如果页面成功失效,则返回1,否则返回0。
*/
/* = & gt;文件的缓存无效*/
int invalidate_inode_page(结构页面*页面)
{
struct地址_空间*映射=页面_映射(page);
如果(!映射)
返回0;
/* = & gt;如果当前页是脏页或被写回的页,则直接返回*/
if(page dirty(page)| | page write back(page))
返回0;
/* = & gt;如果它已经映射到页表,它将返回*/
if (page_mapped(page))
返回0;
/* = & gt;调用invalid _ complete _ page继续*/
return invalidate_complete_page(映射,页面);
}
从上面的代码中我们可以看出,清除相关页面有两个条件:1。不脏不回写;2.没用过。如果满足这两个条件,调用invalid _ complete _ page继续:
/* = & gt;无效的完整页面*/
静态int
invalidate_complete_page(结构地址空间*映射,结构页面*页面)
{
int ret
如果(第->映射!=映射)
返回0;
if(page _ has _ private(page)& amp;& amp!try_to_release_page(第0页))
返回0;
/* = & gt;如果满足以上条件,页面*/
ret = remove_mapping(mapping,page);
返回ret
}
/*
*试图将锁定的页面从其->;映射。如果它是脏的或者如果
*其他人在页面上有ref,中止并返回0。如果是的话
*成功分离,返回1。假设调用方有一个打开的引用
*本页。
*/
/* = & gt;从地址空间释放页面*/
int remove_mapping(结构地址空间*映射,结构页面*页面)
{
if (__remove_mapping(mapping,page)) {
/*
*用1而不是2有效地解冻refcount
*为我们删除pagecache引用,而不需要另一个
*原子操作。
*/
page_unfreeze_refs(第1页);
返回1;
}
返回0;
}
/*
*与remove_mapping相同,但如果从映射中删除页面,它
*返回的refcount为0。
*/
/* = & gt;从地址空间释放页面*/
static int __remove_mapping(结构地址空间*映射,结构页面*页面)
{
BUG_ON(!page locked(page));
BUG_ON(映射!= page _ mapping(page));
spin _ lock _ IRQ(amp;映射-& gt;tree _ lock);
/*
*对繁忙页面的非racy检查。
*
*必须注意测试的顺序。当有人
*一提到这一页,就有可能被他们弄脏了
*删除引用。因此,如果在page_count之前测试PageDirty
*在这里,可能会发生以下竞争:
*
*获取用户页面(& amp页面);
*[用户映射消失]
* write_to(第页);
* !page dirty(page)[好]
* SetPageDirty(页面);
* put_page(页面);
* !page _ count(page)[好,丢弃]
*
*[糟糕,我们的写入数据丢失]
*
*颠倒测试顺序可确保这种情况不会发生
*神不知鬼不觉地逃走。需要smp_rmb来确保页面-& gt;旗帜
*页面加载前未满足加载-& gt;_count。
*
*注意,如果SetPageDirty总是通过set_page_dirty执行,
*因此在tree_lock下,不需要这种排序。
*/
如果(!page_freeze_refs(第2页)
goto cannot _ free
/*注意:page_freeze_refs中的atomic_cmpxchg提供了smp_rmb */
if(不太可能(PageDirty(page))) {
page_unfreeze_refs(第2页);
goto cannot _ free
}
if (PageSwapCache(page)) {
swp_entry_t swap = {。val = page _ private(page)};
_ _ delete _ from _ swap _ cache(page);
spin_unlock_irq。映射-& gt;tree _ lock);
swapcache_free(swap,page);
}否则{
void(* freepage)(struct page *);
free page = mapping-& gt;a _ ops-& gt;freepage
/* = & gt;从页面缓存中删除并释放页面*/
_ _ delete _ from _ page _ cache(page);
spin_unlock_irq。映射-& gt;tree _ lock);
mem _ cgroup _ un charge _ cache _ page(page);
如果(freepage!=空)
freepage(页面);
}
返回1;
无法释放:
spin_unlock_irq。映射-& gt;tree _ lock);
返回0;
}
/*
*从页面缓存中删除一个页面并释放它。呼叫者必须做出
*确保该页面被锁定,并且没有其他人使用它-或该用法
*是安全的。调用者必须持有映射& # 39;s树_锁。
*/
/* = & gt;从页面缓存中删除并释放页面*/
void _ _ delete _ from _ page _ cache(struct page * page)
{
结构地址空间*映射=页面-& gt;映射;
trace _ mm _ filemap _ delete _ from _ page _ cache(page);
/*
*如果我们& # 39;刷新到cleancache中,否则
*使任何现有的cleancache条目无效。我们可以& # 39;不要离开
*一旦我们的页面消失,cleancache中就会有过时的数据
*/
if(page up date(page)& amp;& ampPageMappedToDisk(page))
clean cache _ put _ page(page);
其他
cleancache_invalidate_page(映射,页面);
基数_树_删除(& amp映射-& gt;page_tree,page-& gt;指数);
/* = & gt;解除其地址空间结构的绑定*/
page-& gt;映射= NULL
/*离开页面-& gt;索引集:截断查找依赖于它*/
/* = & gt;减少地址空间中的页数*/
映射-& gt;nrpages-;
__dec_zone_page_state(page,NR _ FILE _ PAGES);
if (PageSwapBacked(page))
__dec_zone_page_state(page,NR _ SHMEM);
BUG _ ON(page _ mapped(page));
/*
*一些文件系统似乎在之后会重新弄脏页面
*虚拟机已取消脏位(如ext3日志)。
*
*在以下情况后,通过进行最终的不良会计检查来解决问题
*已完全移除页面。
*/
if(page dirty(page)& amp;& ampmapping _ cap _ account _ dirty(mapping)){
dec_zone_page_state(page,NR _ FILE _ DIRTY);
dec_bdi_stat(映射-& gt;backing_dev_info,BDI _可回收);
}
}看到这里,我们就能理解为什么使用posix_fadvise后相关内存没有被释放了:脏页是最关键的因素。
但是怎么能保证所有的页面都不脏呢?Fdatasync或fsync都是选项,或者Linux下新的系统调用sync_file_range可用,这些都是WB_SYNC_ALL模式强制回写后返回的。所以你应该这么做:
fdatasync(FD);
posix_fadvise(fd,0,0,posix _ fadvise东尼);总结:
使用posix_fadvise可以有效清除页面缓存,其作用范围是文件级。以下是对应用程序编程的一些建议:
在测试I/O效率时,posix_fadvise可以用来消除缓存的影响。
在确认被访问的文件暂时不会被访问时,需要调用posix_fadvise来避免占用不必要的可用内存空间。
如果当前系统内存非常紧张,在读写大文件的时候,为了避免OOM风险,我们可以在分段读写的同时清空缓存,但这也直接导致了性能的下降。毕竟空间和时间是一对矛盾。
3.使用vmtouch控制缓存。
Vmtouch是一个可移植的文件系统缓存诊断和控制工具。最近,这个工具被广泛使用。最典型的例子就是移动应用Instagram(照片墙)后台服务器使用vmtouch来管理和控制页面缓存。了解了vmtouch的原理和使用方法,可以为我们后续的后端设备所用。
快速安装指南:
$ git克隆https://github.com/hoytech/vmtouch.git
$ cd vmtouch
$ make
$ sudo make installvmtouch使用:
查看文件(或目录)的哪些部分在内存中;
将文件转移到内存中;
清除内存中的文件,即释放页面缓存;
将文件锁定在内存中,而不将其换出到磁盘;
……
Vmtouch实现:
它的核心是两个系统调用,mincore和posix_fadvise。这两种方法的具体用法将通过使用手册帮助进行详细解释。上面已经提到了Posix_fadvise,这里不解释它的用法。简而言之,mincore:
名字
mincore -确定页面是否驻留在内存中
摘要
# include & ltunistd.h & gt
# include & ltsys/mman . h & gt;
int mincore(void *addr,size_t length,unsigned char * vec);
glibc的功能测试宏要求(参见feature_test_macros(7)):
mincore():_ BSD _ source | | _ svid _ source mincore需要调用者传入的文件的地址(通常由mmap()返回),它会将文件以vec的形式写入内存。
vmtouch工具的使用:
用法:VM touch[选项]...文件或目录...
选项:
-t将页面存入内存
-e从内存中逐出页面
-l使用mlock(2)锁定物理内存中的页面
-L使用mlockall(2)锁定物理内存中的页面
-d守护程序模式
-m
要触摸的最大文件大小-p
使用指定的部分而不是整个文件-f跟随符号链接
-h也计算硬链接副本
-w等待所有页面被锁定(仅与-d一起使用)
-v详细
安静
用法示例:
示例1:获取当前/mnt/usb目录中的缓存使用情况。
[root @ test NFS _ dir]# mkdir/mnt/USB & amp;& amp挂载/开发/msc /mnt/usb/
[root@test usb] # vmtouch。
文件:57
目录:2
常驻页数:0/278786 0/1G 0%
耗时:0.023126秒例2。当前test.bin文件的缓存使用情况如何?
[root @ test USB]# VM touch-v test . bin
测试. bin
[ ] 0/25600
文件:1
目录:0
常驻页面:0/25600 0/100M 0%
耗时:0.001867秒此时,使用tail命令将一些文件读入内存:
[root @ test USB]# busybox _ v 400 tail-n 10 test . bin & gt;/dev/null现在我们再来看一下:
[root @ test USB]# VM touch-v test . bin
测试. bin
[ o] 240/25600
文件:1
目录:0
常驻页面:240/25600 960K/100M 0.938%
Elapsed: 0.002019秒表示当前文件test.bin的最后240页驻留在内存中。
例3。最后,使用-t选项将所有剩余的test.bin文件读入内存:
[root @ test USB]# VM touch-vt test . bin
测试. bin
[哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦哦25600/25600
文件:1
目录:0
触摸页数:25600(100米)
耗时:39.049秒例4。释放所有被test.bin占用的缓存:
[root @ test USB]# VM touch-ev test . bin
驱逐test.bin
文件:1
目录:0
被逐出的页面:25600 (100M)
耗时:0.01461秒我们来看看是不是真的发布了:
[root @ test USB]# VM touch-v test . bin
测试. bin
[ ] 0/25600
文件:1
目录:0
常驻页面:0/25600 0/100M 0%
用时:0.001867秒以上通过代码分析和实际操作总结了vmtouch工具的使用方法。建议APP组集成或借鉴vmtouch工具,灵活应用于后端设备,实现对页面缓存的有效管理和控制。
4.使用BLKFLSBUF清除缓冲区
通过块设备驱动程序IOCTL命令的实现,发现该命令可以有效地清除整个块设备占用的缓冲区。
int blk dev _ ioctl(struct block _ device * bdev,fmode_t模式,无符号cmd,
无符号长参数)
{
struct gendisk * disk = bdev-& gt;bd _ disk
struct backing _ dev _ info * bdi
loff_t大小;
int ret,n;
开关(cmd) {
案例BLKFLSBUF:
如果(!capable(CAP_SYS_ADMIN))
return-EACCES;
ret = __blkdev_driver_ioctl(bdev,mode,cmd,arg);
如果(!is_unrecognized_ioctl(ret))
返回ret
fsync _ bdev(bdev);
invalidate _ bdev(bdev);
返回0;
案例…:
…………
}
/*使清理未使用的缓冲区和页面缓存无效。*/
void invalidate_bdev(结构块_设备*bdev)
{
结构地址空间*映射= bdev-& gt;BD _ inode-& gt;i _ mapping
if(映射-& gt;nrpages == 0)
返回;
invalidate _ BH _ lrus();
LRU _ add _ drain _ all();/*确保刷新所有lru添加缓存*/
invalidate _ mapping _ pages(mapping,0,-1);
/* 99%的时候,我们不会& # 39;不需要刷新bdev上的cleancache。
*但是,对于奇怪的弯道,我们要小心
*/
cleancache_invalidate_inode(映射);
}
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]内存总计:90532 kB
[0225 _ 19:10:25:10s]内存可用空间:12296 kB
[0225 _ 19:10:25:10s]缓冲区:46076 kB
[0225 _ 19:10:25:10s]缓存:4136 kB
…………
[0225 _ 19:10:42:10s][root @ test NFS _ dir]#/mnt/NFS _ dir/a . out
[0225 _ 19:10:42:10s]ioctl cmd BLKFLSBUF正常!
[0225 _ 19:10:44:10s][root @ test NFS _ dir]# cat/proc/meminfo
[0225 _ 19:10:44:10s]内存总计:90532 kB
[0225 _ 19:10:44:10s]内存可用空间:58988 kB
[0225 _ 19:10:44:10s]缓冲区:0 kB
…………
[0225 _ 19:10:44:10s]Cached:4144 KB执行效果从代码中可以看出,缓冲区已经全部清空,MemFree增加了大约46MB,所以我们可以知道原来的缓冲区已经被回收,转化为可用内存。整个过程中缓存几乎没有变化,只能推断增加的8K缓存内存用于加载a.out本身和其他库文件。
上述a.out的示例如下:
# include & ltstdio.h & gt
# include & ltfcntl.h & gt
# include & lt错误号& gt
# include & ltsys/ioctl . h & gt;
#定义BLKFLSBUF _IO(0x12,97)
int main(int argc,char* argv[])
{
int FD =-1;
FD = open(& quot;/dev/h _ sda & quot;,O _ RDWR);
if(FD & lt;0)
{
return-1;
}
if (ioctl(fd,BLKFLSBUF,0))
{
printf(& quot;ioctl cmd BLKFLSBUF失败,错误号:% d \ n & quot,errno);
}
关闭(FD);
printf(& quot;ioctl cmd BLKFLSBUF正常!\ n & quot);
返回0;
综上所述,使用块设备命令BLKFLSBUF可以有效地清除块设备上的所有缓冲区,被清除的缓冲区可以立即被释放,成为可用内存。
利用这一点,联系后端业务场景,给出应用编程建议:
每次关闭一个块设备文件描述符之前,必须调用BLKFLSBUF命令,保证缓冲区中的脏数据及时刷入块设备,避免意外断电造成的数据丢失,并及时释放恢复的缓冲区。
操作大型块设备时,如果需要,可以调用BLKFLSBUF命令。一件比较大的设备怎么算?一般理解,当前Linux系统的可用物理内存小于操作块设备的大小。
5.使用drop_Caches控制缓存和缓冲区。
/proc是一个虚拟文件系统,我们可以通过读写它来与内核实体进行通信。也就是说,我们可以通过修改/proc中的文件来调整当前的内核行为。关于缓存和缓冲区的控制,我们可以用echo 1 >;/proc/sys/VM/drop _ cache。
首先看内核源代码实现:
int drop _ caches _ sysctl _ handler(CTL _ table * table,int write,
void _ _用户*缓冲区,size _ t *长度,loff_t *ppos)
{
int ret
ret = proc_dointvec_minmax(表、写、缓冲、长度、ppos);
如果(返回)
返回ret
如果(写){
/* = & gt;echo 1 & gt/proc/sys/VM/drop _ cache清除页面缓存*/
if(sysctl _ drop _ cache & amp;1)
/* = & gt;遍历所有超级块并清除所有缓存*/
iterate _ supers(drop _ page cache _ sb,NULL);
if(sysctl _ drop _ cache & amp;2)
drop _ slab();
}
返回0;
}
/**
* iterate_supers -调用所有活动超级块的函数
* @f:要调用的函数
* @arg:要传递给它的参数
*
*扫描超级块列表并调用给定的函数,传递它
*锁定的超级块和给定的参数。
*/
void iterate _ supers(void(* f)(struct super _ block *,void *),void *arg)
{
struct super_block *sb,* p = NULL
自旋锁。sb _ lock);
每个条目的列表(sb & amp;超级块,列表){
if(hlist _ unhashed(& amp;sb-& gt;s_instances))
继续;
sb-& gt;s _ count++;
自旋解锁(& ampsb _ lock);
down _ read(& amp;sb-& gt;s _ umount);
如果(s B- & gt;s _ root & amp& amp(s B- & gt;旗帜& ampMS_BORN))
f(sb,arg);
up _ read(& amp;sb-& gt;s _ umount);
自旋锁。sb _ lock);
如果(p)
_ _ put _ super(p);
p = sb
}
如果(p)
_ _ put _ super(p);
自旋解锁(& ampsb _ lock);
}
/* = & gt;清理文件系统(包括bdev伪文件系统)的页面缓存)*/
静态void drop _ page cache _ sb(struct super _ block * sb,void *unused)
{
struct inode *inode,* toput _ inode = NULL
自旋锁。inode _ sb _ list _ lock);
/* = & gt;遍历所有信息节点*/
每个条目的列表(索引节点& ampsb-& gt;s_inodes,i_sb_list) {
自旋锁。信息节点-& gt;I _ lock);
/*
* = & gt如果当前状态是(I _ FREEING | I _ WILL _ FREE | I _ NEW)或
* = & gt如果没有缓存页面
* = & gt跳跃
*/
if((inode-& gt;i _ state & amp(I _ FREEING | I _ WILL _ FREE | I _ NEW))| |
(信息节点->;I _ mapping-& gt;nrpages == 0)) {
自旋解锁(& amp信息节点-& gt;I _ lock);
继续;
}
_ _ iget(inode);
自旋解锁(& amp信息节点-& gt;I _ lock);
自旋解锁(& ampinode _ sb _ list _ lock);
/* = & gt;清除缓存页(脏页、锁定页、被写回或映射到页表中的页除外)*/
invalidate _ mapping _ pages(inode-& gt;i_mapping,0,-1);
iput(toput _ inode);
toput _ inode = inode
自旋锁。inode _ sb _ list _ lock);
}
自旋解锁(& ampinode _ sb _ list _ lock);
iput(toput _ inode);
}综上所述,echo 1 > /proc/sys/vm/drop_caches将清除所有inode的缓存页面,包括VFS的inode和所有文件系统inode(包括bdev伪文件系统块设备的inode的缓存页面)。因此,在执行该命令之后,整个系统的所有页面缓存和缓冲区缓存都将被清除,当然,前提是这些缓存没有被占用。
接下来看实际效果:
[root @ test USB]# cat/proc/meminfo
内存总量:90516 kB
内存空闲:12396 kB
缓冲区:96 kB
缓存:60756 kB
[root @ test USB]# busybox _ v 400 sync
[root @ test USB]# busybox _ v 400 sync
[root @ test USB]# busybox _ v 400 sync
[root @ test USB]# echo 1 & gt;/proc/sys/VM/drop _ cache
[root @ test USB]# cat/proc/meminfo
内存总量:90516 kB
内存空闲:68820 kB
缓冲区:12 kB
缓存:4464 kB,可以看到Buffers和Cached都掉了。建议在drop _ caches之前执行sync命令,以确保数据的完整性。sync命令会将所有未写入的系统缓冲区写入磁盘,包括修改的I节点、延迟的块I/O和读写映射文件。
以上设置虽然简单,但是比较粗糙,使得缓存的作用基本发挥不出来,尤其是系统压力大的时候,处理掉缓存容易出问题。因为drop_cache是全局清空内存,所以在清空的过程中会添加页锁,这会导致一些进程在等待页锁时超时,导致问题的产生。因此,需要根据系统的情况进行适当的调整,找到最佳的解决方案。
评论前必须登录!
注册