前段时间工作时,测试反馈了这样的一个问题:用-o loop选项mount 500个squashfs文件系统,然后umount -d卸载,对比执行前后的环境,发现系统内存减少了50M左右
最开始怀疑少了的内存是cache占用的,echo 3 > /proc/sys/vm/drop_caches 清cache之后,free内存并没有明显的变化
后面查看到/dev目录下有很多loopx文件,但是我们umount命令加了-d参数,按理解应该会释放loop设备才是:
man umount:
-d, --detach-loop
When the unmounted device was a loop device, also free this loop device.
并且这些loop设备在umount之后,使用losetup -d 也删除不掉,怀疑是busybox不支持-d参数,阅读了一下busybox的losetup的源码
int losetup_main(int argc UNUSED_PARAM, char **argv)
{
……/* -d LOOPDEV */if (opt == OPT_d && argv[0]) {
if (del_loop(argv[0]))
bb_simple_perror_msg_and_die(argv[0]);
return EXIT_SUCCESS;
}……
}
int FAST_FUNC del_loop(const char *device)
{
int fd, rc;
fd = open(device, O_RDONLY);
if (fd < 0)
return 1;
rc = ioctl(fd, LOOP_CLR_FD, 0);
close(fd);
return rc;
}
是通过ioctl下发LOOP_CLR_FD命令字去删除loop设备的,转到内核代码:
lo_ioctl:
case LOOP_CLR_FD:
/* loop_clr_fd would have unlocked lo_ctl_mutex on success */
err = loop_clr_fd(lo);
if (!err)
goto out_unlocked;
break;
阅读loop_clr_fd代码,都是清除struct loop_device *lo的一些成员,gendisk没有删除,甚至连lo都没有free,loop_clr_fd是起不到深处loop设备的作用的。所以不是busybox不支持-d参数,而是-d参数的实现根本就不回去删除gendisk设备和释放lo占用的内存
因为在loop_clr_fd中没有free lo,所以严重怀疑少了的内存还在被loop占用的:
/dev目录下还有个loop-control设备,查看该设备的ioctl实现:
static long loop_control_ioctl(struct file *file, unsigned int cmd, unsigned long parm)
{
struct loop_device *lo;
int ret = -ENOSYS;
mutex_lock(&loop_index_mutex);
switch (cmd) {
case LOOP_CTL_ADD:
……
case LOOP_CTL_REMOVE:
ret = loop_lookup(&lo, parm);
if (ret < 0)
break;
mutex_lock(&lo->lo_ctl_mutex);
if (lo->lo_state != Lo_unbound) {
ret = -EBUSY;
mutex_unlock(&lo->lo_ctl_mutex);
break;
}
if (lo->lo_refcnt > 0) {
ret = -EBUSY;
mutex_unlock(&lo->lo_ctl_mutex);
break;
}
lo->lo_disk->private_data = NULL;
mutex_unlock(&lo->lo_ctl_mutex);
idr_remove(&loop_index_idr, lo->lo_number);
loop_remove(lo);
break;
case LOOP_CTL_GET_FREE:
…………
}
mutex_unlock(&loop_index_mutex);
return ret;
}
LOOP_CTL_REMOVE命令字的实现调用了loop_remove,在里面删除了loop设备的gendisk和释放了lo指针
于是写了个简单的小程序:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#define LOOP_CTL_REMOVE0x4C81
int main()
{
int fd = 0;
int ret = 0;
int i = 0;
fd = open("/dev/loop-control", O_RDWR);
if (fd < 0) {
printf("open dev loop-control failed \n");
return 1;
}
for(i=0; i<500; i++) {
ret = ioctl(fd, LOOP_CTL_REMOVE, i)
if (ret < 0)
printf("remove loop%d failed \n", i);
}
return 0;
}
在跑完测试后的环境运行,果然/dev目录下的loop设备都被清楚了,并且内存也没有释放回来了。
附:测试用例代码
#!/bin/bash
image=/tmp/squashfs.img
mount_dir=/tmp/mount_dir
image_dir=/tmo/image_dir
mkdir -p $mount_dir $image_dir
mem_before=`cat /proc/meminfo | grep MemFree | awk '{print $2}'`
for i in `seq 500`
do
mkdir $mount_dir/mount$i
cp $image $image_dir/test$i.img
mount -t squashfs -o loop $image_dir/test$i.img $mount_dir/mount$i
done
for j in `seq 500 0 -1`
do
umount -d /dev/loop$j
done
mem_after=`cat /proc/meminfo | grep MemFree | awk '{print $2}'`
mem_diff=`expr mem_before - mem_after`
if [ $memdiff -gt 32000 ]; then
echo "memory leak, test failed!"
exit 1
fi
echo "test success"
exit 0