当发现磁盘占用比较多的时候,可以通过下面的命令,查看各个挂载路径的占用情况:
$ df -h
udev 3.9G 0 3.9G 0% /dev
tmpfs 784M 2.0M 782M 1% /run
/dev/sda11 19G 6.5G 12G 37% /
tmpfs 3.9G 91M 3.8G 3% /dev/shm
tmpfs 5.0M 4.0K 5.0M 1% /run/lock
tmpfs 3.9G 0 3.9G 0% /sys/fs/cgroup
/dev/sda12 9.4G 37M 8.8G 1% /tmp
/dev/sda14 6.4G 168M 5.9G 3% /boot
/dev/sda10 57G 2.0G 52G 4% /home
/dev/sda1 256M 33M 224M 13% /boot/efi
tmpfs 784M 16K 784M 1% /run/user/121
tmpfs 784M 44K 784M 1% /run/user/1000
当然我这里并没有哪个挂载路径的磁盘占用率比较高,这里假设home占用比较高,然后可以通过:
$ cd /home
$ du -h --max-depth=1
1.9G ./shouwang
16K ./lost+found
1.9G .
这样可以逐层知道哪些目录有了不该有的大文件。
当然你也可以使用find直接找出大文件,比如查找当前目录下大于800M的文件:
$ find . -type f -size +800M
find的用法可以参考《find命令高级用法》。
如果找到了该文件,并且确认是无用文件,那么就可以删除了。
但是如果仍然有程序打开了该文件,那么即便你删除了文件,其占用的磁盘空间也并不会释放,因为仍然它的"文件引用"不是0,文件并不会被删除。
所以你需要看一下,是否还有程序打开该文件,举个例子:
$ lsof conf.json
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
less 6750 shouwang 4r REG 8,10 233 3411160 conf.json
从上面的结果,可以看到,是less程序打开了conf.json文件,并且它的进程id是6750。
找到进程之后,根据实际情况决定是否需要停止程序,然后删除大文件。
现实常常可能不如意,比如虽然可以通过df命令看到某些挂载路径磁盘占用率比较高,但是始终找不到大文件,那么你就要考虑,是不是大文件看似被删除了,但是还有程序打开。要找到这样的文件,其实也很简单,前面已经介绍过了:
lsof |grep deleted
lsof能看到被打开的文件,而如果文件被删除了(比如使用rm命令),但是仍然有程序打开,则会是deleted状态,举个例子:
$ touch test.txt
$ less test.txt
创建一个文件test.txt,并随意输入一些内容,然后使用less命令打,随后在另一个终端,删除该文件:
$ rm test.txt
$ lsof |grep test.txt |grep deleted
less 6989 shouwang 4r REG 8,10 134 3541262 /home/shouwang/workspaces/shell/testdeleted/test.txt (deleted)
可以看到打开该文件的进程id为6989,我们看一下这个程序打开的文件:
$ ls -al /proc/6989/fd
dr-x------ 2 shouwang shouwang 0 10月 6 10:57 .
dr-xr-xr-x 9 shouwang shouwang 0 10月 6 10:56 ..
lrwx------ 1 shouwang shouwang 64 10月 6 10:57 0 -> /dev/pts/1
lrwx------ 1 shouwang shouwang 64 10月 6 10:57 1 -> /dev/pts/1
lrwx------ 1 shouwang shouwang 64 10月 6 10:57 2 -> /dev/pts/1
lr-x------ 1 shouwang shouwang 64 10月 6 10:57 3 -> /dev/tty
lr-x------ 1 shouwang shouwang 64 10月 6 10:57 4 -> '/home/shouwang/workspaces/shell/testdeleted/test.txt (deleted)'
$ du -h
从上面也可以看到,文件描述符4的文件为test.txt,但是deleted状态。
停止这个进程,你会发现所占用的磁盘空间会被释放。
通常在终端启动一个程序后,文件描述符0,1,2通常对应标准输入,标准输出,标准错误。从前面的例子中也能窥见一二,它打开的是/dev/pts/1,其实就是当前终端。
回到开始的问题,之前例子中daemonize的参考实现如下:
#include
#include
#include
#include
#include
#include
#include
/*实现仅供参考,可根据实际情况调整*/
int daemonize()
{
/*清除文件权限掩码*/
umask(0);
/*父进程退出*/
pid_t pid;
if((pid=fork()) < 0)
{
/*for 出错*/
perror("fork error");
return -1;
}
else if(0 != pid)/*父进程*/
{
printf("father exit\n");
exit(0);
}
/*子进程,成为组长进程,并且摆脱终端*/
setsid();
/*修改工作目录*/
if(chdir("/") < 0)
{
perror("change dir failed");
return -1;
}
struct rlimit rl;
/*先获取文件描述符最大值*/
if(getrlimit(RLIMIT_NOFILE,&rl) < 0)
{
perror("get file decription failed");
return -1;
}
/*如果无限制,则设置为1024*/
if(rl.rlim_max == RLIM_INFINITY)
rl.rlim_max = 1024;
/*为了使得终端有输出,保留了文件描述符0,1,2;实际上父进程可能没有打开2以上的文件描述符*/
int i;
for(i = 3;i < rl.rlim_max;i++)
close(i);
return 0;
}
int main(void)
{
if(0 == daemonize())
{
while(1)
{
printf("daemonize ok\n");
sleep(2);
}
}
else
{
printf("daemonize failed\n");
sleep(1);
}
return 0;
}
这里注意到,daemonize函数最后关闭了2以上的文件描述符。
在其中一个终端运行上面的例子:
$ gcc -o daemon daemon.c #编译
$ ./daemon #运行
$ ls -al /proc/`pidof daemon`/fd #查看打开的文件
dr-x------ 2 shouwang shouwang 0 10月 6 11:26 .
dr-xr-xr-x 9 shouwang shouwang 0 10月 6 11:26 ..
lrwx------ 1 shouwang shouwang 64 10月 6 11:26 0 -> /dev/pts/4
lrwx------ 1 shouwang shouwang 64 10月 6 11:26 1 -> /dev/pts/4
lrwx------ 1 shouwang shouwang 64 10月 6 11:26 2 -> /dev/pts/4
可以看到0,1,2打开的是程序所在的终端,这时关闭该终端,在另外一个终端执行:
$ ls -al /proc/`pidof daemon`/fd
lrwx------ 1 shouwang shouwang 64 10月 6 11:26 0 -> '/dev/pts/4 (deleted)'
lrwx------ 1 shouwang shouwang 64 10月 6 11:26 1 -> '/dev/pts/4 (deleted)'
lrwx------ 1 shouwang shouwang 64 10月 6 11:26 2 -> '/dev/pts/4 (deleted)'
发现0,1,2都是deleted状态了,因为关闭前面启动程序的终端后,也相当于删除了它标准输入输出和标准错误指向的文件。
实际上,到这里,都没有任何问题,程序中的printf打印最多无法打印出来而已。
但是,如果程序不是终端启动的呢?或者说没有终端的环境,比如crontab启动,at命令启动:
$ at now <<< “./daemon"
at命令表示当前时间执行daemon程序。
再看看它打开的文件:
$ ls -l /proc/`pidof daemon`/fd
lr-x------ 1 shouwang shouwang 64 10月 6 11:42 0 -> '/var/spool/cron/atjobs/a00001019765fe (deleted)'
lrwx------ 1 shouwang shouwang 64 10月 6 11:42 1 -> '/var/spool/cron/atspool/a00001019765fe (deleted)'
lrwx------ 1 shouwang shouwang 64 10月 6 11:42 2 -> '/var/spool/cron/atspool/a00001019765fe (deleted)'
看见没有,你会发现它打开了一些奇怪的文件。
很明显,我们自己写的程序中并没有打开这样的文件,但是从文件名可以推断,它看能是cron程序打开的。那么怎么会变成daemon程序打开了呢?
这要从fork说起,fork出来的子进程会继承父进程的文件描述符,我们的daemon实现已经将2以上的描述符关闭了,但是并没有关闭0,1,2,而由于daemon程序自己实际上没有打开任何文件,0,1,2是空着的,实际上就变成了打开的是父进程曾经打开的文件。
但是由于printf持续向标准输出打印信息,即不断向描述符1打开的文件写入内容,而该文件又是deleted状态,最终可能会导致磁盘空间占用不断增大,但是又找不到实际的大文件。
为了验证我们的想法,可以看下前面的文件内容到底是什么:
$ tail -5 /proc/`pidof daemon`/fd/1
daemonize ok
daemonize ok
daemonize ok
daemonize ok
daemonize ok
看到了吗,这既是我们程序的打印!竟然打印到一个毫无相关的文件中了。
从上面的例子可以看到,要想实现一个线上可用的daemon程序,还必须重定向标准输入,标准输出和标准错误,比例:
/* redirect stdin, stdout, and stderr to /dev/null */
open("/dev/null", O_RDONLY);
open("/dev/null", O_RDWR);
open("/dev/null", O_RDWR);
如果我们不关心这些输入输出,则重定向到/dev/null,相当于丢弃该内容。
是否要重定向标准输入输出,完全取决于你的实际应用场景,比如某些情况你可能就是需要将标准输出指向父进程的文件,则可以不需要重定向。当然了,至于实现,更推荐的做法是调用daemon函数:
#include
int daemon(int nochdir, int noclose);