运维中需要对一些关键的服务进程进行守护,例如tomcat进程,mysql之类,这种进程没有自己的守护进程,而我们又不可能去改它们的源代码。
为此我用perl写了一个守护进程,根据传入的命令,启动要守护的进程,若是进程挂了,则重新启动进程。
即使子进程被杀死了,也能自动起来,但是程序有点缺陷:
1. 这个守护进程只是针对那些永远不退出的进程有效。
2. 若是杀死了守护进程,被守护的进程有可能不会退出,还要手动去杀死被守护进程,才能退出。因为我们找到杀死整个进程树的方法。
our $logfile="/tmp/deamon.log"; #输出日志
our $child_pid; #记录子进程的id,以便父进程去杀死子进程
our $parent_pid;#记录父进程的id,以便区分父子进程
our $maxloop=10000; #放置大量产生子进程
our $loop=0;
our $CMD=$ARGV[0];
if(!$CMD){
die("please input a cmdn");
}
#杀死父子进程
sub kill_pid{
logs("catch quit signaln");
if($parent_pid){#判断是不是父进程
#父进程杀死所有的子进程
logs("parent kill childn");
kill(15,$child_pid);
kill(15,-$$);
logs("parent quitn");
exit 0;
}else{
#子进程退出
logs("child quitn");
exit 0;
}
};
$SIG{'INT'} = 'kill_pid'; # 中断退出
$SIG{'TERM'} = 'kill_pid';
$SIG{CHLD} = 'IGNORE'; # 忽略 SIGCHLD 信号,系统会自动回收结束的子进程
#deamon方式
daemonize();
#启动服务
logs("START deamon '$CMD'n");
while(1){
#防止输入的命令不是服务性命令,例如ls,执行很短时间的命令,或者后台执行的命令,这样最多会产生$maxloop个进程,不会把系统崩溃
$loop++;
if($loop>$maxloop){
exit 0;
}
#产生子进程
$child_pid=fork();
if (not defined $child_pid) {
print "cannot forkn";
exit 0;
}
if($child_pid)
{ # child >; 0, so we're the parent
$parent_pid=1;
logs("launching '$CMD'n");
setpgrp(0, 0); #成为进程首领
wait();#等待子进程结束,若是结束则继续循环生成子进程
}else{
$parent_pid=0;
system($CMD);# child handles,子进程应该也是死循环的
logs("system return : $r ");
#执行命令非正常退出
if ($? == -1) {
logs("failed to execute: $! ");
}
elsif ($? & 127) {
logs("child died with signal",($? & 127),",",($? & 128) ? 'with' : 'without',' coredump');
}
else {
logs("child exited with value ",$? >> 8);
#若是$CMD正常退出的话,表示$CMD不是服务性程序
logs("cannot deamon on '$CMD'");
}
#子进程退出
exit 0;
}
}
#记录日志
sub logs{
open(LOGFILE,">>$logfile") or die("cannot open $logfile");
my($sec,$min,$hour,$mday,$mon,$year)=localtime();
print LOGFILE sprintf("%04d-%02d-%02d %02d:%02d:%02d ",($year< 2000?($year+1900):$year),($mon+1),$mday,$hour,$min,$sec);
for my $msg (@_){
print LOGFILE $msg;
}
print LOGFILE "n";
close(LOGFILE);
}
#deamon方式
sub daemonize {
# 使当前进程对自己所写文件拥有完全控制权,避免继承的umask()设置带来困挠。这一步可选
umask(0);
# 关闭0、1、2三个句柄。许多daemon程序用sysconf()获取_SC_OPEN_MAX,并在一个偱环中关闭所有可能打开的文件句柄。目的在于释放不必要的系统资源,它们是有限资源。
close STDIN;
close STDOUT;
close STDERR;
chdir '/' or croak "Can't chdir to /: $!"; #减少管理员卸载(unmount)文件系统时可能遇上的麻烦。这一步可选,也可chdir()到其它目录。
open STDIN, '/dev/null' or croak "Can't read /dev/null: $!";
open STDOUT, '>/dev/null' or croak "Can't write to /dev/null: $!";
open STDERR, '>&STDOUT' or croak "Can't dup stdout: $!";
defined(my $pid = fork) or croak "Can't fork: $!";
exit if $pid;
#创建新的session和process group,成为其leader,并脱离控制终端。
setsid or croak "Can't start a new session: $!";
$SIG{CHLD} = 'IGNORE