c++ 共享内存_关于Linux共享内存的实验 [二] - 原因

1d4abf99fcfa5cd11bc92e653541307c.png

关于Linux共享内存的实验 [一]

上文采用的“删文件”和“杀进程”的方法主要是为了快速演示实验现象,但这种做法不利于通过调试手段进一步探究其内在的逻辑。为此,在原程序的基础上,引入一个信号的处理,并通过相关的API接口来实现对“共享内存”释放过程的控制。

struct 

触发"Ctrl+C"后,正在运行的程序将收到SIGINT信号,执行注册的信号处理函数。第一步我们先暂时只使用munmap()和close()。

static 

而后启动程序,并打开crash工具,获取代表共享内存文件的相关VFS结构的内存地址:

7237dd2fb71a8ba636cf735b6426007d.png

根据"file"的地址,可以查看记录其引用计数的"f_count"的值:

e47b0319249a04b02bc7019b8792eede.png

只open()了一次文件,为什么引用计数是2呢?在crash工具中,有一个查找被打开文件的进程使用信息的"fuser"命令,通过它就可以找到答案。原来啊,是因为调用mmap()也会给这个"file"的引用计数加1。

f56c8e739fce377c12f543d097a198bf.png

再来看看记录"inode"引用计数的"i_count",和追踪inode被link数目的"i_nlink",两者的值都是1:

4f713111856c72f3036ee9da5127c5df.png

从VFS的代码中可以发现,这两个值,是在创建一个inode的初始化阶段就确立的:

int 

用类似的方法,查到记录"dentry"引用计数的"d_lockref.count"的值为2。这里为什么使用"d_lockref"间接获取,而不是像"file"和"inode"的引用计数那样,用一个"d_count"之类的变量?原因请参考这篇文章的介绍。至于为什么现在的值是2,等到之后追踪了它的释放过程,你就自然会明白。

af6f8291a93e0b87287b06d10a1ec4ed.png

接下来发送信号,看看调用munmap()close()后,这几个引用计数的值有何变化。结果是"f_count"变为0,"d_lockref.count"降为1,而"i_count"和"i_nlink"依然为1,保持不变。

8965e9bc3ad0678d53726a2ab9f5b33f.png

猜想"f_count"的变化是因为munmap()和close()引起的吧?为了验证,我们使用systemtap工具来打印一段back trace。修改"/usr/share/systemtap/example/memory/hw_watch_addr.stp"这个现成的脚本:

%

当被监控的内存地址处的内容发生变化时,将被systemtap截获,这种调测手段被称为"watchpoint",通常译作“数据断点”。内核调试是不能像进程调试那样打"breakpoint"(程序断点)的,所以"watchpoint"的作用显得尤为重要。

此处笔者只关注“写”时的触发,所以用的"write",如果想同时监测读和写,那么可以使用"rw"代替,关于systemtap具体的语法规则请参考这篇文档。

d289b508ecb0c0a9ccefc475516b5c3e.png

加上"-v"是为了查看stap的执行过程,当出现"Pass 5: starting run",说明已经准备就绪,这时就可以重复上面的实验过程,获取"f_count"变更时的函数调用路径。嗯,结果和猜想的一致。

30c259dbf3d90eb02c4bd470bc0557ff.png

那不调用munmap()和close(),只调用shm_unlink()呢?结果是"f_count"的值保持为2,"d_lockref.count"降为1,"i_nlink"变为0,而"i_count"依然为1。

babd5e42c40600552e250041c07dd4a8.png

如法炮制,追踪下"i_nlink"的内存变化:

a07540585a14d99b50e11791ba905645.png

结合代码可以知道,这里用了一个回调:

dir

unlink之后,已经看不到"/dev/shm/shm1"这个文件了,但是进程还在使用它(相比于上文使用的"lsof"之外的另一种查看方法),这也是为什么其"dentry"和"inode"的引用计数都是1。

7c0e59d5a5809e84b41d625c39af1cfc.png

最后,同时使用munmap(), close()和shm_unlink(),则"d_lockref.count"变为-128,其余的观测值都归为0:

8cd9e5fa5686f30666222cb63b0890b6.png

来追一下"d_lockref.count"的变化:

2467243d925a24f44fdc8a69887807c9.png

第一次变化是munmap()和close()引发的,unlink()在查找dentry的过程中,会暂时地将该dentry的引用计数加1,而后减1,最终调用lockref_mark_dead()将其值置为-128。

void 

再来追一下"i_count"的变化:

29e17b9da414da7901f6a73a61c75b63.png

结合"d_lockref.count"打印结果中第三段的vfs_unlink()路径可知,"i_count"值的变化是发生在"d_lockref.count"的值从1变为-128的过程中的。从代码的实现也可以印证这一点:

void 

小结

在VFS的体系里,"file"和"dentry"是多对一的关系,而"dentry"和"inode"也是多对一的关系:

8675702ae06bffeac5eebcb27f08236a.png

杀掉进程或者使用munmap()和close(),"file"不复存在,进程和"dentry"的关系被解除,但"dentry"和"inode"的指向关系依然存在。要想真正释放一段共享内存的空间,需要保证其对应的inode的引用计数和link数都为0。

上文还留了一个问题,为什么使用rm命令移除目录后,"df"和"du"命令的显示结果会出现差异?因为"df"命令是通过查询文件系统的空闲块来计算其占用的存储空间,而"du"命令是通过查询文件系统的目录来统计其大小。因此对于unlink的文件,虽然还在占用空间,但由于其目录已经不存在,所以无法被du识别到,可见du命令的输出结果并不能作为判断文件系统大小的依据。

参考:

Why is space not being freed from disk after deleting a file

原创文章,转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值