Linux下⽤C语⾔实现守护进程
守护进程(Daemon)是运⾏在后台的⼀种特殊进程。它独⽴于控制终端并且周期性地执⾏某种任务或等待处理某些发⽣的事件。守护进程是⼀种很有⽤的进 程。Linux的⼤多数服务器就是⽤守护进程实现的。⽐如,Internet服务器inetd,Web服务器httpd等。同时,守护进程完成许多系统任 务。⽐如,作业规划进程crond,打印进程lpd等。
守护进程的编程本⾝并不复杂,复杂的是各种版本的Unix的实现机制不尽相同,造成不同Unix环境下守护进程的编程规则并不⼀致。这需要读者注意,照搬 某些书上的规则(特别是BSD4.3和低版本的System V)到Linux会出现错误的。下⾯将全⾯介绍Linux下守护进程的编程要点并给出详细实例。
⼀. 守护进程及其特性
守护进程最重要的特性是后台运⾏。在这⼀点上DOS下的常驻内存程序TSR与之相似。其次,守护进程必须与其运⾏前的环境隔离开来。这些环境包括未关闭的 ⽂件描述符,控制终端,会话和进程组,⼯作⽬录以及⽂件创建掩模等。这些环境通常是守护进程从执⾏它的⽗进程(特别是shell)中继承下来的。最后,守 护进程的启动⽅式有其特殊之处。它可以在Linux系统启动时从启动脚本/etc/rc.d中启动,可以由作业规划进程crond启动,还可以由⽤户终端 (通常是shell)执⾏。
总之,除开这些特殊性以外,守护进程与普通进程基本上没有什么区别。因此,编写守护进程实际上是把⼀个普通进程按照上述的守护进程的特性改造成为守护进程。如果读者对进程有⽐较深⼊的认识就更容易理解和编程了。
⼆. 守护进程的编程要点
前⾯讲过,不同Unix环境下守护进程的编程规则并不⼀致。所幸的是守护进程的编程原则其实都⼀样,区别在于具体的实现细节不同。这个原则就是要满⾜守护 进程的特性。同时,Linux是基于Syetem V的SVR4并遵循Posix标准,实现起来与BSD4相⽐更⽅便。编程要点如下:
1. 在后台运⾏
为避免挂起控制终端将Daemon放⼊后台执⾏。⽅法是在进程中调⽤fork使⽗进程终⽌,让Daemon在⼦进程中后台执⾏。
if(pid=fork())
exit(0);//是⽗进程,结束⽗进程,⼦进程继续
2. 脱离控制终端,登录会话和进程组
有必要先介绍⼀下Linux中的进程与控制终端,登录会话和进程组之间的关系:进程属于⼀个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享⼀个控制终端。这个控制终端通常是创建进程的登录终端。
控制终端,登录会话和进程组通常是从⽗进程继承下来的。我们的⽬的就是要摆脱它们,使之不受它们的影响。⽅法是在第1点的基础上,调⽤setsid()使进程成为会话组长:
setsid();
说明:当进程是会话组长时setsid()调⽤失败。但第⼀点已经保证进程不是会话组长。setsid()调⽤成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
3. 禁⽌进程重新打开控制终端linux下的sleep函数
现在,进程已经成为⽆终端的会话组长。但它可以重新申请打开⼀个控制终端。可以通过使进程不再成为会话组长来禁⽌进程重新打开控制终端:
if(pid=fork())
exit(0);//结束第⼀⼦进程,第⼆⼦进程继续(第⼆⼦进程不再是会话组长)
4. 关闭打开的⽂件描述符
进程从创建它的⽗进程那⾥继承了打开的⽂件描述符。如不关闭,将会浪费系统资源,造成进程所在的⽂件系统⽆法卸下以及引起⽆法预料的错误。按如下⽅法关闭它们:
for(i=0;i 关闭打开的⽂件描述符close(i);>
for(i=0;i< NOFILE;++i)
5. 改变当前⼯作⽬录
进程活动时,其⼯作⽬录所在的⽂件系统不能卸下。⼀般需要将⼯作⽬录改变到根⽬录。对于需要转储核⼼,写运⾏⽇志的进程将⼯作⽬录改变到特定⽬录如/tmpchdir("/")
6. 重设⽂件创建掩模
进程从创建它的⽗进程那⾥继承了⽂件创建掩模。它可能修改守护进程所创建的⽂件的存取位。为防⽌这⼀点,将⽂件创建掩模清除:umask(0);
7. 处理SIGCHLD信号
处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时⽣成⼦进程处理请求。如果⽗进程不等待⼦进程结束,⼦进程将成为 僵⼫进程(zombie)从⽽占⽤系统资源。如果⽗进程等待⼦进程结束,将增加⽗进程的负担,影响服务器进程的并发性能。在Linux下可以简单地将 SIGCHLD信号的操作设为SIG_IGN。
signal(SIGCHLD,SIG_IGN);
这样,内核在⼦进程结束时不会产⽣僵⼫进程。这⼀点与BSD4不同,BSD4下必须显式等待⼦进程结束才能释放僵⼫进程。
三. 守护进程实例
守护进程实例包括两部分:主程序test.c和初始化程序init.c。主程序每隔⼀分钟向/tmp⽬录中的⽇志test.log报告运⾏状态。初始化程序中的init_daemon函数负责⽣成守护进程。读者可以利⽤init_daemon函数⽣成⾃⼰的守护进程。
1. init.c清单
#include < unistd.h >
#include < signal.h >
#include < sys/param.h >
#include < sys/types.h >
#include < sys/stat.h >
void init_daemon(void)
{
int pid;
int i;
if(pid=fork())
exit(0);//是⽗进程,结束⽗进程
else if(pid< 0)
exit(1);//fork失败,退出
/
/是第⼀⼦进程,后台继续执⾏
setsid();//第⼀⼦进程成为新的会话组长和进程组长
//并与控制终端分离
if(pid=fork())
exit(0);//是第⼀⼦进程,结束第⼀⼦进程
else if(pid< 0)
exit(1);//fork失败,退出
//是第⼆⼦进程,继续
//第⼆⼦进程不再是会话组长
for(i=0;i< NOFILE;++i)//关闭打开的⽂件描述符
close(i);
chdir("/tmp");//改变⼯作⽬录到/tmp
umask(0);//重设⽂件创建掩模
return;
}
2. test.c清单
#include < stdio.h >
#include < time.h >
void init_daemon(void);//守护进程初始化函数
main()
{
FILE *fp;
time_t t;
init_daemon();//初始化为Daemon
while(1)//每隔⼀分钟向test.log报告运⾏状态
{
sleep(60);//睡眠⼀分钟
if((fp=fopen("test.log","a")) >=0)
{
t=time(0);
fprintf(fp,"I'm here at %sn",asctime(localtime(&t)) );
fclose(fp);
}
}
}
以上程序在RedHat Linux6.0下编译通过。步骤如下:
1)编译:gcc –g –o test init.c test.c
2)执⾏:./test
3)查看进程:ps –ef
从输出可以发现test守护进程的各种特性满⾜上⾯的要求。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论