背景:这个是在centos7.6的环境上复现的,但该问题其实在很多内核版本上都有,如何做好对linux一些缓存的监控和管制,始终是云计算方向的热点,但这些热点属于细分场景,很难合入到linux主基线,随着ebpf的逐步稳固,对通用linux内核编程,观测,可能会有新的播种。本文将分享咱们是怎么排查并解决这个问题的。
一、故障景象
oppo云内核团队发现集群的snmpd的cpu耗费冲高,
snmpd简直长时间占用一个核,perf发现热点如下:
+ 92.00% 3.96% [kernel] [k] __d_lookup - 48.95% 48.95% [kernel] [k] _raw_spin_lock 20.95% 0x70692f74656e2f73 __fopen_internal __GI___libc_open system_call sys_open do_sys_open do_filp_open path_openat link_path_walk + lookup_fast - 45.71% 44.58% [kernel] [k] proc_sys_compare - 5.48% 0x70692f74656e2f73 __fopen_internal __GI___libc_open system_call sys_open do_sys_open do_filp_open path_openat + 1.13% proc_sys_compare
简直都耗费在内核态 __d_lookup的调用中,而后strace看到的耗费为:
open("/proc/sys/net/ipv4/neigh/kube-ipvs0/retrans_time_ms", O_RDONLY) = 8 <0.000024>------v4的比拟快open("/proc/sys/net/ipv6/neigh/ens7f0_58/retrans_time_ms", O_RDONLY) = 8 <0.456366>-------v6很慢
进一步手工操作,发现进入ipv6的门路很慢:
time cd /proc/sys/net
real 0m0.000s
user 0m0.000s
sys 0m0.000s
time cd /proc/sys/net/ipv6
real 0m2.454s
user 0m0.000s
sys 0m0.509s
time cd /proc/sys/net/ipv4
real 0m0.000s
user 0m0.000s
sys 0m0.000s
能够看到,进入ipv6的门路的工夫耗费远远大于ipv4的门路。
二、故障景象剖析
咱们须要看一下,为什么perf的热点显示为__d_lookup中proc_sys_compare耗费较多,它的流程是怎么样的
proc_sys_compare只有一个调用门路,那就是d_compare回调,从调用链看:
__d_lookup--->if (parent->d_op->d_compare(parent, dentry, tlen, tname, name))struct dentry *__d_lookup(const struct dentry *parent, const struct qstr *name){..... hlist_bl_for_each_entry_rcu(dentry, node, b, d_hash) { if (dentry->d_name.hash != hash) continue; spin_lock(&dentry->d_lock); if (dentry->d_parent != parent) goto next; if (d_unhashed(dentry)) goto next; /* * It is safe to compare names since d_move() cannot * change the qstr (protected by d_lock). */ if (parent->d_flags & DCACHE_OP_COMPARE) { int tlen = dentry->d_name.len; const char *tname = dentry->d_name.name; if (parent->d_op->d_compare(parent, dentry, tlen, tname, name)) goto next;//caq:返回1则是不雷同 } else { if (dentry->d_name.len != len) goto next; if (dentry_cmp(dentry, str, len)) goto next; } ....next: spin_unlock(&dentry->d_lock);//caq:再次进入链表循环 } .....}
集群同物理条件的机器,snmp流程应该一样,所以很天然就狐疑,是不是hlist_bl_for_each_entry_rcu
循环次数过多,导致了parent->d_op->d_compare不停地比拟抵触链,
进入ipv6的时候,是否比拟次数很多,因为遍历list的过程中必定会遇到了比拟多的cache miss,当遍历了
太多的链表元素,则有可能触发这种状况,上面须要验证下:
static inline long hlist_count(const struct dentry *parent, const struct qstr *name){ long count = 0; unsigned int hash = name->hash; struct hlist_bl_head *b = d_hash(parent, hash); struct hlist_bl_node *node; struct dentry *dentry; rcu_read_lock(); hlist_bl_for_each_entry_rcu(dentry, node, b, d_hash) { count++; } rcu_read_unlock(); if(count >COUNT_THRES) { printk("hlist_bl_head=%p,count=%ld,name=%s,hash=%u\n",b,count,name,name->hash); } return count;}
kprobe的后果如下:
[20327461.948219] hlist_bl_head=ffffb0d7029ae3b0 count = 799259,name=ipv6/neigh/ens7f1_46/base_reachable_time_ms,hash=913731689[20327462.190378] hlist_bl_head=ffffb0d7029ae3b0 count = 799259,name=ipv6/neigh/ens7f0_51/retrans_time_ms,hash=913731689[20327462.432954] hlist_bl_head=ffffb0d7029ae3b0 count = 799259,name=ipv6/conf/ens7f0_51/forwarding,hash=913731689[20327462.675609] hlist_bl_head=ffffb0d7029ae3b0 count = 799259,name=ipv6/neigh/ens7f0_51/base_reachable_time_ms,hash=913731689
从抵触链的长度看,的确进入了dcache的hash表中外面一条比拟长的抵触链,该链的dentry个数为799259个,
而且都指向ipv6这个dentry。
理解dcache原理的同学必定晓得,位于抵触链中的元素必定hash值是一样的,而dcache的hash值是用的parent
的dentry加上那么的hash值造成最终的hash值:
static inline struct hlist_bl_head *d_hash(const struct dentry *parent, unsigned int hash){ hash += (unsigned long) parent / L1_CACHE_BYTES; hash = hash + (hash >> D_HASHBITS); return dentry_hashtable + (hash & D_HASHMASK);}高版本的内核是:static inline struct hlist_bl_head *d_hash(unsigned int hash){ return dentry_hashtable + (hash >> d_hash_shift);}
外表上看,高版本的内核的dentry->dname.hash值的计算变动了,其实是
hash寄存在dentry->d_name.hash的时候,曾经加了helper,具体能够参考
如下补丁:
commit 8387ff2577eb9ed245df9a39947f66976c6bcd02Author: Linus Torvalds <torvalds@linux-foundation.org>Date: Fri Jun 10 07:51:30 2016 -0700 vfs: make the string hashes salt the hash We always mixed in the parent pointer into the dentry name hash, but we did it late at lookup time. It turns out that we can simplify that lookup-time action by salting the hash with the parent pointer early instead of late.
问题剖析到这里,有两个疑难如下:
- 抵触链尽管长,那也可能咱们的dentry在抵触链后面啊
不肯定每次都比拟到那么远; - proc下的dentry,按情理都是常见和固定的文件名,
为什么会这么长的抵触链呢?
要解决这两个疑难,有必要,对抵触链外面的dentry进一步剖析。
咱们依据下面kprobe打印的hash头,能够进一步剖析其中的dentry如下:
crash> list dentry.d_hash -H 0xffff8a29269dc608 -s dentry.d_sbffff89edf533d080 d_sb = 0xffff89db7fd3c800ffff8a276fd1e3c0 d_sb = 0xffff89db7fd3c800ffff8a2925bdaa80 d_sb = 0xffff89db7fd3c800ffff89edf5382a80 d_sb = 0xffff89db7fd3c800.....
因为链表十分长,咱们把对应的剖析打印到文件,发现所有的这条抵触链中所有的dentry
都是属于同一个super_block,也就是 0xffff89db7fd3c800,
crash> list super_block.s_list -H super_blocks -s super_block.s_id,s_nr_dentry_unused >/home/caq/super_block.txt# grep ffff89db7fd3c800 super_block.txt -A 2 ffff89db7fd3c800 s_id = "proc\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
0xffff89db7fd3c800 是 proc 文件系统,他为什么会创立这么多ipv6的dentry呢?
持续应用命令看一下dentry对应的d_inode的状况:
...ffff89edf5375b00 d_inode = 0xffff8a291f11cfb0ffff89edf06cb740 d_inode = 0xffff89edec668d10ffff8a29218fa780 d_inode = 0xffff89edf0f75240ffff89edf0f955c0 d_inode = 0xffff89edef9c7b40ffff8a2769e70780 d_inode = 0xffff8a291c1c9750ffff8a2921969080 d_inode = 0xffff89edf332e1a0ffff89edf5324b40 d_inode = 0xffff89edf2934800...
咱们发现,这些同名的,d_name.name均为 ipv6 的dentry,他的inode是不一样的,阐明这些proc
下的文件不存在硬链接,所以这个是失常的。
咱们持续剖析ipv6门路的造成。
/proc/sys/net/ipv6门路的造成,简略地说分为了如下几个步骤:
start_kernel-->proc_root_init()//caq:注册proc fs因为proc是linux零碎默认挂载的,所以查找 kern_mount_data 函数pid_ns_prepare_proc-->kern_mount_data(&proc_fs_type, ns);//caq:挂载proc fsproc_sys_init-->proc_mkdir("sys", NULL);//caq:proc目录下创立sys目录net_sysctl_init-->register_sysctl("net", empty);//caq:在/proc/sys下创立net对于init_net:ipv6_sysctl_register-->register_net_sysctl(&init_net, "net/ipv6", ipv6_rotable);对于其余net_namespace,个别是零碎调用触发创立ipv6_sysctl_net_init-->register_net_sysctl(net, "net/ipv6", ipv6_table);//创立ipv6
有了这些根底,接下来,咱们盯着最初一个,ipv6的创立流程。
ipv6_sysctl_net_init 函数
ipv6_sysctl_register-->register_pernet_subsys(&ipv6_sysctl_net_ops)-->
register_pernet_operations-->__register_pernet_operations-->
ops_init-->ipv6_sysctl_net_init
常见的调用栈如下:
:Fri Mar 5 11:18:24 2021,runc:[1:CHILD],tid=125338.path=net/ipv6 0xffffffffb9ac66f0 : __register_sysctl_table+0x0/0x620 [kernel] 0xffffffffb9f4f7d2 : register_net_sysctl+0x12/0x20 [kernel] 0xffffffffb9f324c3 : ipv6_sysctl_net_init+0xc3/0x150 [kernel] 0xffffffffb9e2fe14 : ops_init+0x44/0x150 [kernel] 0xffffffffb9e2ffc3 : setup_net+0xa3/0x160 [kernel] 0xffffffffb9e30765 : copy_net_ns+0xb5/0x180 [kernel] 0xffffffffb98c8089 : create_new_namespaces+0xf9/0x180 [kernel] 0xffffffffb98c82ca : unshare_nsproxy_namespaces+0x5a/0xc0 [kernel] 0xffffffffb9897d83 : sys_unshare+0x173/0x2e0 [kernel] 0xffffffffb9f76ddb : system_call_fastpath+0x22/0x27 [kernel]
在dcache中,咱们/proc/sys/下的各个net_namespace中的dentry都是一起hash的,
那怎么保障一个net_namespace
内的dentry隔离呢?咱们来看对应的__register_sysctl_table函数:
struct ctl_table_header *register_net_sysctl(struct net *net, const char *path, struct ctl_table *table){ return __register_sysctl_table(&net->sysctls, path, table);}struct ctl_table_header *__register_sysctl_table( struct ctl_table_set *set, const char *path, struct ctl_table *table){ ..... for (entry = table; entry->procname; entry++) nr_entries++;//caq:先计算该table下有多少个项 header = kzalloc(sizeof(struct ctl_table_header) + sizeof(struct ctl_node)*nr_entries, GFP_KERNEL);.... node = (struct ctl_node *)(header + 1); init_header(header, root, set, node, table);.... /* Find the directory for the ctl_table */ for (name = path; name; name = nextname) {....//caq:遍历查找到对应的门路 } spin_lock(&sysctl_lock); if (insert_header(dir, header))//caq:插入到治理构造中去 goto fail_put_dir_locked;....}
具体代码不开展,每个sys下的dentry通过 ctl_table_set 来辨别是否可见
而后在查找的时候,比拟如下:
static int proc_sys_compare(const struct dentry *parent, const struct dentry *dentry, unsigned int len, const char *str, const struct qstr *name){.... return !head || !sysctl_is_seen(head);}static int sysctl_is_seen(struct ctl_table_header *p){ struct ctl_table_set *set = p->set;//获取对应的set int res; spin_lock(&sysctl_lock); if (p->unregistering) res = 0; else if (!set->is_seen) res = 1; else res = set->is_seen(set); spin_unlock(&sysctl_lock); return res;}//不是同一个 ctl_table_set 则不可见static int is_seen(struct ctl_table_set *set){ return ¤t->nsproxy->net_ns->sysctls == set;}
由以上代码能够看出,以后去查找的过程,如果它归属的net_ns的set
和dentry 中归属的set不统一,则会返回失败,而snmpd归属的
set其实是init_net的sysctls,而通过查看抵触链中的各个后面绝大多数dentry
的sysctls,都不是归属于init_net的,所以后面都比拟失败。
那么,为什么归属于init_net的/proc/sys/net的这个dentry会在抵触链的开端呢?
那个是因为上面的代码导致的:
static inline void hlist_bl_add_head_rcu(struct hlist_bl_node *n, struct hlist_bl_head *h){ struct hlist_bl_node *first; /* don't need hlist_bl_first_rcu because we're under lock */ first = hlist_bl_first(h); n->next = first;//caq:每次前面增加的时候,是加在链表头 if (first) first->pprev = &n->next; n->pprev = &h->first; /* need _rcu because we can have concurrent lock free readers */ hlist_bl_set_first_rcu(h, n);}
曾经晓得了snmp对抵触链表比拟须要遍历到很后的地位的起因,接下来,须要弄
明确,为什么会有这么多dentry。依据打点,咱们发现了,如果docker不停地
创立pause容器并销毁,这些net下的ipv6的dentry就会累积,
累积的起因,一个是dentry在没有触发内存缓和的状况下,不会主动销毁,
能缓存则缓存,另一个则是咱们没有对抵触链的长度进行限度。
那么问题又来了,为什么ipv4的dentry就没有累积呢?既然ipv6和ipv4的父parent
都是一样的,那么查看一下这个父parent有多少个子dentry呢?
而后看 hash表外面的dentry,d_parent很多都指向 0xffff8a0a7739fd40 这个dentry。crash> dentry.d_subdirs 0xffff8a0a7739fd40 ----查看这个父dentry有多少child d_subdirs = { next = 0xffff8a07a3c6f710, prev = 0xffff8a0a7739fe90 }crash> list 0xffff8a07a3c6f710 |wc -l1598540----------竟然有159万个child
159万个子目录,去掉后面抵触链较长的799259个,还有差不多79万个,那既然进入ipv4门路很快,
阐明在net目录下,应该还有其余的dentry有很多子dentry,会不会是一个共性问题?
而后查看集群其余机器,也发现类型景象,截取的打印如下:
count=158505,d_name=net,d_len=3,name=ipv6/conf/all/disable_ipv6,hash=913731689,len=4hlist_bl_head=ffffbd9d5a7a6cc0,count=158507 count=158507,d_name=net,d_len=3,name=core/somaxconn,hash=1701998435,len=4hlist_bl_head=ffffbd9d429a7498,count=158506
能够看到,ffffbd9d429a7498有着和ffffbd9d5a7a6cc0简直一样长度的抵触链。
先剖析ipv6 链,core链的剖析其实是一样的,挑取抵触链的数据分析如下:
crash> dentry.d_parent,d_name.name,d_lockref.count,d_inode,d_subdirs ffff9b867904f500 d_parent = 0xffff9b9377368240 d_name.name = 0xffff9b867904f538 "ipv6"-----这个是一个ipv6的dentry d_lockref.count = 1 d_inode = 0xffff9bba4a5e14c0 d_subdirs = { next = 0xffff9b867904f950, prev = 0xffff9b867904f950 }d_child偏移0x90,则0xffff9b867904f950减去0x90为 0xffff9b867904f8c0crash> dentry 0xffff9b867904f8c0struct dentry {...... d_parent = 0xffff9b867904f500, d_name = { { { hash = 1718513507, len = 4 }, hash_len = 18898382691 }, name = 0xffff9b867904f8f8 "conf"------名称为conf }, d_inode = 0xffff9bba4a5e61a0, d_iname = "conf\000bles_names\000\060\000.2\000\000pvs.(*Han", d_lockref = {...... count = 1----------------援用计数为1,阐明还有人援用...... }, ...... d_subdirs = { next = 0xffff9b867904fb90, prev = 0xffff9b867904fb90 }, ......}既然援用计数为1,则持续往下挖:crash> dentry.d_parent,d_lockref.count,d_name.name,d_subdirs 0xffff9b867904fb00 d_parent = 0xffff9b867904f8c0 d_lockref.count = 1 d_name.name = 0xffff9b867904fb38 "all" d_subdirs = { next = 0xffff9b867904ef90, prev = 0xffff9b867904ef90 } 再往下:crash> dentry.d_parent,d_lockref.count,d_name.name,d_subdirs,d_flags,d_inode -x 0xffff9b867904ef00 d_parent = 0xffff9b867904fb00 d_lockref.count = 0x0-----------------------------挖到援用计数为0为止 d_name.name = 0xffff9b867904ef38 "disable_ipv6" d_subdirs = { next = 0xffff9b867904efa0, --------为空 prev = 0xffff9b867904efa0 } d_flags = 0x40800ce-------------上面重点剖析这个 d_inode = 0xffff9bba4a5e4fb0
能够看到,ipv6的dentry门路为ipv6/conf/all/disable_ipv6,和probe看到的一样,
针对 d_flags ,剖析如下:
#define DCACHE_FILE_TYPE 0x04000000 /* Other file type */#define DCACHE_LRU_LIST 0x80000--------这个示意在lru下面#define DCACHE_REFERENCED 0x0040 /* Recently used, don't discard. */#define DCACHE_RCUACCESS 0x0080 /* Entry has ever been RCU-visible */#define DCACHE_OP_COMPARE 0x0002#define DCACHE_OP_REVALIDATE 0x0004#define DCACHE_OP_DELETE 0x0008
咱们看到,disable_ipv6的援用计数为0,然而它是有 DCACHE_LRU_LIST 标记的,
依据如下函数:
static void dentry_lru_add(struct dentry *dentry){ if (unlikely(!(dentry->d_flags & DCACHE_LRU_LIST))) { spin_lock(&dcache_lru_lock); dentry->d_flags |= DCACHE_LRU_LIST;//有这个标记阐明在lru上 list_add(&dentry->d_lru, &dentry->d_sb->s_dentry_lru); dentry->d_sb->s_nr_dentry_unused++;//caq:放在s_dentry_lru是闲暇的 dentry_stat.nr_unused++; spin_unlock(&dcache_lru_lock); }}
到此,阐明它是能够开释的,因为是线上业务,咱们不敢应用
echo 2 >/proc/sys/vm/drop_caches
而后编写一个模块去开释,模块的主代码如下,参考 shrink_slab:
spin_lock(orig_sb_lock); list_for_each_entry(sb, orig_super_blocks, s_list) { if (memcmp(&(sb->s_id[0]),"proc",strlen("proc"))||\ memcmp(sb->s_type->name,"proc",strlen("proc"))||\ hlist_unhashed(&sb->s_instances)||\ (sb->s_nr_dentry_unused < NR_DENTRY_UNUSED_LEN) ) continue; sb->s_count++; spin_unlock(orig_sb_lock); printk("find proc sb=%p\n",sb); shrinker = &sb->s_shrink; count = shrinker_one(shrinker,&shrink,1000,1000); printk("shrinker_one count =%lu,sb=%p\n",count,sb); spin_lock(orig_sb_lock);//caq:再次持锁 if (sb_proc) __put_super(sb_proc); sb_proc = sb; } if(sb_proc){ __put_super(sb_proc); spin_unlock(orig_sb_lock); } else{ spin_unlock(orig_sb_lock); printk("can't find the special sb\n"); }
就发现的确两条抵触链都被开释了。
比方某个节点在开释前:
[3435957.357026] hlist_bl_head=ffffbd9d5a7a6cc0,count=34686[3435957.357029] count=34686,d_name=net,d_len=3,name=core/somaxconn,hash=1701998435,len=4[3435957.457039] IPVS: Creating netns size=2048 id=873057[3435957.477742] hlist_bl_head=ffffbd9d429a7498,count=34686[3435957.477745] count=34686,d_name=net,d_len=3,name=ipv6/conf/all/disable_ipv6,hash=913731689,len=4[3435957.549173] hlist_bl_head=ffffbd9d5a7a6cc0,count=34687[3435957.549176] count=34687,d_name=net,d_len=3,name=core/somaxconn,hash=1701998435,len=4[3435957.667889] hlist_bl_head=ffffbd9d429a7498,count=34687[3435957.667892] count=34687,d_name=net,d_len=3,name=ipv6/conf/all/disable_ipv6,hash=913731689,len=4[3435958.720110] find proc sb=ffff9b647fdd4000-----------------------开始开释[3435959.150764] shrinker_one count =259800,sb=ffff9b647fdd4000------开释完结
独自开释后:
[3436042.407051] hlist_bl_head=ffffbd9d466aed58,count=101[3436042.407055] count=101,d_name=net,d_len=3,name=core/somaxconn,hash=1701998435,len=4[3436042.501220] IPVS: Creating netns size=2048 id=873159[3436042.591180] hlist_bl_head=ffffbd9d466aed58,count=102[3436042.591183] count=102,d_name=net,d_len=3,name=core/somaxconn,hash=1701998435,len=4[3436042.685008] hlist_bl_head=ffffbd9d4e8af728,count=101[3436042.685011] count=101,d_name=net,d_len=3,name=ipv6/conf/all/disable_ipv6,hash=913731689,len=4[3436043.957221] IPVS: Creating netns size=2048 id=873160[3436044.043860] hlist_bl_head=ffffbd9d466aed58,count=103[3436044.043863] count=103,d_name=net,d_len=3,name=core/somaxconn,hash=1701998435,len=4[3436044.137400] hlist_bl_head=ffffbd9d4e8af728,count=102[3436044.137403] count=102,d_name=net,d_len=3,name=ipv6/conf/all/disable_ipv6,hash=913731689,len=4[3436044.138384] IPVS: Creating netns size=2048 id=873161[3436044.226954] hlist_bl_head=ffffbd9d466aed58,count=104[3436044.226956] count=104,d_name=net,d_len=3,name=core/somaxconn,hash=1701998435,len=4[3436044.321947] hlist_bl_head=ffffbd9d4e8af728,count=103
下面能够看出两个细节:
1、开释前,hlist也是在增长的,开释后,hlist还是在增长。
2、开释后,net的dentry变了,所以hashlist的地位变动了。
综上所述,咱们遍历热点慢,是因为snmpd所要查找init_net的ctl_table_set
和dcache中的其余dentry 归属的 ctl_table_set 不统一导致,而链表的长度则
是因为有人在销毁net_namespace的时候,还在拜访ipv6/conf/all/disable_ipv6 以及
core/somaxconn 导致的,这两个dentry 都被放在了归属的super_block的 s_dentry_lru
上。
最初一个疑难,是什么调用拜访了这些dentry呢?触发的机制如下:
pid=16564,task=exe,par_pid=366883,task=dockerd,count=1958,d_name=net,d_len=3,name=ipv6/conf/all/disable_ipv6,hash=913731689,len=4,hlist_bl_head=ffffbd9d429a7498hlist_bl_head=ffffbd9d5a7a6cc0,count=1960pid=16635,task=runc:[2:INIT],par_pid=16587,task=runc,count=1960,d_name=net,d_len=3,name=core/somaxconn,hash=1701998435,len=4,hlist_bl_head=ffffbd9d5a7a6cc0hlist_bl_head=ffffbd9d429a7498,count=1959
能够看到,其实就是 dockerd和runc 触发了这个问题,k8调用docker不停创立pause容器,
cni的网络参数填写不对,导致创立的net_namespace 很快被销毁,尽管销毁时调用了
unregister_net_sysctl_table,但同时 runc 和exe 拜访了
该net_namespace下的两个dentry,导致这两个dentry被缓存在了 super_block的
s_dentry_lru链表上。再因为整体内存比拟短缺,所以始终会增长。
留神到对应的门路就是:ipv6/conf/all/disable_ipv6以及 core/somaxconn,ipv4门路下的dentry因为没有
过后在拜访的,所以ctl_table可能过后就清理掉。
而晦气的snmpd因为始终要拜访对应的链,
cpu就冲高了,应用手工 drop_caches 之后,立即复原,留神,线上的机器不能应用
drop_caches ,这个会导致sys 冲高,影响一些时延敏感型的业务。
三、故障复现
1、内存空余的状况下,没有触发slab的内存回收,k8调用docker创立不同net_namespace
的pause容器,但因为cni的参数不对,会立即销毁刚创立的net_namespace,如果你在dmesg
中频繁地看到如下日志:
IPVS: Creating netns size=2048 id=866615
则有必要关注一下 dentry的缓存状况。
四、故障躲避或解决
可能的解决方案是:
1、通过rcu的形式,读取 dentry_hashtable 的各个抵触链,大于肯定水平,抛出告警。
2、通过一个proc参数,设置缓存的dentry的个数。
3、全局能够关注 /proc/sys/fs/dentry-state
4、部分的,能够针对super_block,读取s_nr_dentry_unused,超过肯定数量,则告警,
示例代码能够参考shrink_slab函数的实现。
5、留神与 negative-dentry-limit 的区别。
6、内核中应用hash桶的中央很多,咱们该怎么监控hash桶抵触链的长度呢?做成模块
扫描,或者找中央保留一个链表长度。
五、作者简介
Anqing OPPO高级后端工程师
目前在oppo混合云负责linux内核及容器,虚拟机等虚拟化方面的工作。
获取更多精彩内容:关注[OPPO互联网技术]公众号