关于Linux共享内存的实验 [一]
上文采用的“删文件”和“杀进程”的方法主要是为了快速演示实验现象,但这种做法不利于通过调试手段进一步探究其内在的逻辑。为此,在原程序的基础上,引入一个信号的处理,并通过相关的API接口来实现对“共享内存”释放过程的控制。
struct
触发"Ctrl+C"后,正在运行的程序将收到SIGINT信号,执行注册的信号处理函数。第一步我们先暂时只使用munmap()和close()。
static
而后启动程序,并打开crash工具,获取代表共享内存文件的相关VFS结构的内存地址:
根据"file"的地址,可以查看记录其引用计数的"f_count"的值:
只open()了一次文件,为什么引用计数是2呢?在crash工具中,有一个查找被打开文件的进程使用信息的"fuser"命令,通过它就可以找到答案。原来啊,是因为调用mmap()也会给这个"file"的引用计数加1。
再来看看记录"inode"引用计数的"i_count",和追踪inode被link数目的"i_nlink",两者的值都是1:
从VFS的代码中可以发现,这两个值,是在创建一个inode的初始化阶段就确立的:
int
用类似的方法,查到记录"dentry"引用计数的"d_lockref.count"的值为2。这里为什么使用"d_lockref"间接获取,而不是像"file"和"inode"的引用计数那样,用一个"d_count"之类的变量?原因请参考这篇文章的介绍。至于为什么现在的值是2,等到之后追踪了它的释放过程,你就自然会明白。
接下来发送信号,看看调用munmap()和close()后,这几个引用计数的值有何变化。结果是"f_count"变为0,"d_lockref.count"降为1,而"i_count"和"i_nlink"依然为1,保持不变。
猜想"f_count"的变化是因为munmap()和close()引起的吧?为了验证,我们使用systemtap工具来打印一段back trace。修改"/usr/share/systemtap/example/memory/hw_watch_addr.stp"这个现成的脚本:
%
当被监控的内存地址处的内容发生变化时,将被systemtap截获,这种调测手段被称为"watchpoint",通常译作“数据断点”。内核调试是不能像进程调试那样打"breakpoint"(程序断点)的,所以"watchpoint"的作用显得尤为重要。
此处笔者只关注“写”时的触发,所以用的"write",如果想同时监测读和写,那么可以使用"rw"代替,关于systemtap具体的语法规则请参考这篇文档。
加上"-v"是为了查看stap的执行过程,当出现"Pass 5: starting run",说明已经准备就绪,这时就可以重复上面的实验过程,获取"f_count"变更时的函数调用路径。嗯,结果和猜想的一致。
那不调用munmap()和close(),只调用shm_unlink()呢?结果是"f_count"的值保持为2,"d_lockref.count"降为1,"i_nlink"变为0,而"i_count"依然为1。
如法炮制,追踪下"i_nlink"的内存变化:
结合代码可以知道,这里用了一个回调:
dir
unlink之后,已经看不到"/dev/shm/shm1"这个文件了,但是进程还在使用它(相比于上文使用的"lsof"之外的另一种查看方法),这也是为什么其"dentry"和"inode"的引用计数都是1。
最后,同时使用munmap(), close()和shm_unlink(),则"d_lockref.count"变为-128,其余的观测值都归为0:
来追一下"d_lockref.count"的变化:
第一次变化是munmap()和close()引发的,unlink()在查找dentry的过程中,会暂时地将该dentry的引用计数加1,而后减1,最终调用lockref_mark_dead()将其值置为-128。
void
再来追一下"i_count"的变化:
结合"d_lockref.count"打印结果中第三段的vfs_unlink()路径可知,"i_count"值的变化是发生在"d_lockref.count"的值从1变为-128的过程中的。从代码的实现也可以印证这一点:
void
小结
在VFS的体系里,"file"和"dentry"是多对一的关系,而"dentry"和"inode"也是多对一的关系:
杀掉进程或者使用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
原创文章,转载请注明出处。