unix下的守护进程

JustMe2007 2008-06-26 01:02:41
linux下守护进程用daemon这个函数,但是好像unix下没有,那unix下如何实现守护进程,听说是用两次fork。具体怎么实现呢,提供一点代码参考参考
int main(int argc,char* argv[])
{
return 1;
}
就以上程序,该加那些代码让他运行起来成为守护进程?
...全文
734 12 打赏 收藏 转发到动态 举报
写回复
用AI写文章
12 条回复
切换为时间正序
请发表友善的回复…
发表回复
fuqd273 2008-06-30
  • 打赏
  • 举报
回复
mark
威廉-丁 2008-06-30
  • 打赏
  • 举报
回复
lz怎么不接贴!
威廉-丁 2008-06-29
  • 打赏
  • 举报
回复
daemonize()函数创建守护进程
   在对main()函数进行介绍的时候我就提到过,一般服务器程序在接收客户机连接请求之前,都要创建一个守护进程。守护进程是linux/Unix编程 中一个非常重要的概念,因为在创建一个守护进程的时候,我们要接触到子进程、进程组、会晤期、信号机制以及文件、目录、控制终端等多个概念,因此详细地讨 论一下守护进程,对初学者学习进程间关系是非常有帮助的。下面就是例程中的daemonize()函数:
-----------------------------------------------------------------
/****************************************************************
function:   daemonize
description: detach the server process from the current context, creating a pristine, predictable        environment in which it will execute.
arguments:  servfd file descriptor in use by server.
return value: none.
calls:    none.
globals:   none.
****************************************************************/
void daemonize (servfd)
int servfd;
{
int childpid, fd, fdtablesize;
/* ignore terminal I/O, stop signals */
signal(SIGTTOU,SIG_IGN);
signal(SIGTTIN,SIG_IGN);
signal(SIGTSTP,SIG_IGN);
/* fork to put us in the background (whether or not the user
specified '&' on the command line */
if ((childpid = fork()) < 0) {
fputs("failed to fork first child\r\n",stderr);
exit(1);
}
else if (childpid > 0)
exit(0); /* terminate parent, continue in child */
/* dissociate from process group */
if (setpgrp(0,getpid())<0) {
fputs("failed to become process group leader\r\n",stderr);
exit(1);
}
/* lose controlling terminal */
if ((fd = open("/dev/tty",O_RDWR)) >= 0) {
ioctl(fd,TIOCNOTTY,NULL);
close(fd);
}
/* close any open file descriptors */
for (fd = 0, fdtablesize = getdtablesize(); fd < fdtablesize; fd++)
if (fd != servfd)
close(fd);
/* set working directory to allow filesystems to be unmounted */
chdir("/");
/* clear the inherited umask */
umask(0);
/* setup zombie prevention */
signal(SIGCLD,(Sigfunc *)reap_status);
}
-----------------------------------------------------------------

此函数的作用就是创建一个守护进程。在Linux系统中,如果要将一个普通进程转换成为守护进程,必须要执行下面的步骤:
1. 调用函数fork()创建子进程,然后父进程终止,保留子进程继续运行。之所以要让父进程终止是因为,当一个进程是以前台进程方式由shell启动时,在 父进程终止之后子进程自动转为后台进程。另外,我们在下一步要创建一个新的会晤期,这就要求创建会晤期的进程不是一个进程组的组长进程。当父进程终止,子 进程运行,这就保证了进程组的组ID与子进程的进程ID不会相等。
函数fork()的定义为:
-----------------------------------------------------------------
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
-----------------------------------------------------------------
该函数被调用一次,但是返回两次,这两次返回的区别是子进程的返回值为"0",而父进程的返回值为子进程的ID。如果出错则返回"-1"。

2. 保证进程不会获得任何控制终端。通常的做法是调用函数setsid()创建一个新的会晤期。setsid()的详细描述为:
-----------------------------------------------------------------
#include <sys/types.h>
#include <unistd.h>
pid_t setsid(void);
-----------------------------------------------------------------
第一步的操作已经保证调用此函数的进程不是进程组的组长,那么此函数将创建一个新的会晤,其结果是:首先,此进程变成该会晤期的首进程 (session leader,系统默认会晤期的首进程是创建该会晤期的进程)。而且,此进程是该会晤期中的唯一进程。然后,此进程将成为一个新的进程组的组长进程,新进 程组的组ID就是该进程的进程ID。最后,保证此进程没有控制终端,即使在调用setsid()之前此进程拥有控制终端,在创建会晤期后这种联系也将被解 除。如果调用该函数的进程为一个进程组的组长,那么函数将返回出错信息"-1"。
当然我们还有其他的办法让进程无法获得控制终端,就象例程中所做的那样,
-----------------------------------------------------------------
if ((fd = open("/dev/tty",O_RDWR)) >= 0) {
ioctl(fd,TIOCNOTTY,NULL);
close(fd);
}
-----------------------------------------------------------------
其中/dev/tty是一个流设备,也是我们的终端映射。调用close()函数将终端关闭。

3.信号处理。一般是要忽略掉某些信号。这里就涉及到信号的概念了。信号其实相当于软件中断,Linux/Unix下的信号机制提供了一种处理异步事 件的方法,终端用户键入印发中断的键,或是系统异常发出信号,这都会通过信号处理机制终止一个或多个程序的运行。
不同情况下引发的信号是不同的。不过所有的信号都有自己的名字,所有的名字都是以"SIG"开头的,只是后面有所不同,我们可以通过这些名字了解到系统中到底发生了些什么事。

当信号出现时,我们可以要求系统进行以下三种操作:
◇忽略信号。大多数信号都是采取这种方式进行处理的,在例程中我们就可以见到这种用法。但值得注意的是有两个例外,那就是对SIGKILL和SIGSTOP信号不能做忽略处理。
◇捕捉信号。这是一种最为灵活的操作方式。这种处理方式的意思就是,当某种信号发生时,我们可以调用一个函数对这种情况进行相应的处理。最常见的情况 就是,如果捕捉到SIGCHID信号,则表示子进程已经终止,然后可在此信号的捕捉函数中调用waitpid()函数取得该子进程的进程ID以及它的终止 状态。在我们这段例程中,就有这种用法的一个实例。还有就是如果进程创建了临时文件,那么就要为进程终止信号SIGTERM编写一个信号捕捉函数来清除这 些临时文件。
◇执行系统的默认动作。对绝大多数信号而言,系统的默认动作都是终止该进程。
在Linux下,信号有很多种,我在这里就不一一介绍了,如果想详细地对这些信号进行了解,可以查看头文件<sigal.h>,这些信号 都被定义为正整数,也就是它们的信号编号。在对信号进行处理时,必须要用到函数signal(),此函数的详细描述为:
-----------------------------------------------------------------
#include <signal.h>
void (*signal (int signo, void (*func)(int)))(int);
-----------------------------------------------------------------
其中参数signo为信号名,参数func的值根据我们的需要可以是以下几种情况:(1)常数SIG_DFL,表示执行系统的默认动作。(2)常数 SIG_IGN,表示忽略信号。(3)收到信号后需要调用的处理函数的地址,此信号捕捉程序应该有一个整型参数但是没有返回值。signal()函数返回 一个函数指针,而该指针指向的函数应该无返回值(void),这个指针其实指向以前的信号捕捉程序。
下面 回到我们的daemonize()函数上来。这个函数在创建守护进程时忽略了三个信号:
signal(SIGTTOU,SIG_IGN);
signal(SIGTTIN,SIG_IGN);
signal(SIGTSTP,SIG_IGN);
这三个信号的含义分别是:SIGTTOU表示后台进程写控制终端,SIGTTIN表示后台进程读控制终端,SIGTSTP表示终端挂起。

4.关闭不再需要的文件描述符,并为标准输入、标准输出和标准错误输出打开新的文件描述符(也可以继承父进程的标准输入、标准输出和标准错误输出文件 描述符,这个操作是可选的)。在我们这段例程中,因为是代理服务器程序,而且是在执行了listen()函数之后执行这个daemonize()的,所以 要保留已经转换成功的倾听套接字,所以我们可以见到这样的语句:
if (fd != servfd)
close(fd);

5.调用函数chdir("/")将当前工作目录更改为根目录。这是为了保证我们的进程不使用任何目录。否则我们的守护进程将一直占用某个目录,这可能会造成超级用户不能卸载一个文件系统。

6.调用函数umask(0)将文件方式创建屏蔽字设置为"0"。这是因为由继承得来的文件创建方式屏蔽字可能会禁止某些许可权。例如我们的守护进程 需要创建一组可读可写的文件,而此守护进程从父进程那里继承来的文件创建方式屏蔽字却有可能屏蔽掉了这两种许可权,则新创建的一组文件其读或写操作就不能 生效。因此要将文件方式创建屏蔽字设置为"0"。
在daemonize()函数的最后,我们可以看到这样的信号捕捉处理语句:
signal(SIGCLD,(Sigfunc *)reap_status);
这不是创建守护进程过程中必须的一步,它的作用是调用我们自定义的reap_status()函数来处理僵死进程。reap_status()在例程中的定义为:
-----------------------------------------------------------------
/****************************************************************
function:    reap_status
description:   handle a SIGCLD signal by reaping the exit status of the perished child, and            discarding it.
arguments:    none.
return value:  none.
calls:      none.
globals:     none.
****************************************************************/
void reap_status()
{
int pid;
union wait status;
while ((pid = wait3(&status,WNOHANG,NULL)) > 0)
; /* loop while there are more dead children */
}
-----------------------------------------------------------------
上面信号捕捉语句的原文为:
signal(SIGCLD, reap_status);
我们刚才说过,signal()函数的第二个参数一定要有有一个整型参数但是没有返回值。而reap_status()是没有参数的,所以原来的语句 在编译时无法通过。所以我在预编译部分加入了对Sigfunc()的类型定义,在这里用做对reap_status进行强制类型转换。而且在BSD系统中 通常都使用SIGCHLD信号来处理子进程终止的有关信息,SIGCLD是System V中定义的一个信号名,如果将SIGCLD信号的处理方式设定为捕捉,那么内核将马上检查系统中是否存在已经终止等待处理的子进程,如果有,则立即调用信 号捕捉处理程序。
一般在信号捕捉处理程序中都要调用wait()、waitpid()、wait3()或是wait4()来返回子进程的终止状态。这些"等待"函数的 区别是,当要求函数"等待"的子进程还没有终止时,wait()将使其调用者阻塞;而在waitpid()的参数中可以设定使调用者不发生阻塞,wait ()函数不被设置为等待哪个具体的子进程,它等待调用者所有子进程中首先终止的那个,而在调用waitpid()时却必须在参数中设定被等待的子进程 ID。而wait3()和wait4()的参数分别比wait()和waitpid()还要多一个"rusage"。例程中的reap_status() 就调用了函数wait3(),这个函数是BSD系统支持的,我们把它和wait4()的定义一起列出来:
-----------------------------------------------------------------
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>
pid_t wait3(int *statloc, int options, struct rusage *rusage);
pid_t wait4(pid_t pid, int *statloc, int options, struct rusage *rusage);
-----------------------------------------------------------------
其中指针statloc如果不为"NULL",那么它将指向返回的子进程终止状态。参数pid是我们指定的被等待的子进程的进程ID。参数 options是我们的控制选择项,一般为WNOHANG或是WUNTRACED。例程中使用了选项WNOHANG,意即如果不能立即返回子进程的终止状 态(譬如由于子进程还未结束),那么等待函数不阻塞,此时返回"0"。      WUNTRACED选项的意思是如果系统支持作业控制,如果要等待的子进程的状态已经暂停,而且其状态自从暂停以来还从未报告过,则返回其状 态。参数rusage如果不为"NULL",则它将指向内核返回的由终止进程及其所有子进程使用的资源摘要,该摘要包括用户CPU时间总量、缺页次数、接 收到信号的次数等。
/*sleep和nanosleep都是使进程睡眠一段时间后被唤醒,但是二者的实现完全不同。
Linux中并没有提供系统调用sleep,sleep是在库函数中实现的,它是通过调用alarm来设定报警时间,调用sigsuspend将进程挂起在信号SIGALARM上,sleep只能精确到秒级上。
nanosleep则是Linux中的系统调用,它是使用定时器来实现的,该调用使调用进程睡眠,并往定时器队列上加入一个time_list型定时器,time_list结构里包括唤醒时间以及唤醒后执行的函数,通过nanosleep加入的定时器的执行函数仅仅完成唤醒当前进程的功能。系统通过一定的机制定时检查这些队列(比如通过系统调用陷入核心后,从核心返回用户态前,要检查当前进程的时间片是否已经耗尽,如果是则调用schedule()函数重新调度,该函数中就会检查定时器队列,另外慢中断返回前也会做此检查),如果定时时间已超过,则执行定时器指定的函数唤醒调用进程。当然,由于系统时间片可能丢失,所以nanosleep精度也不是很高。
alarm也是通过定时器实现的,但是其精度只精确到秒级,另外,它设置的定时器执行函数是在指定时间向当前进程发送SIGALRM信号。
freeskyo 2008-06-28
  • 打赏
  • 举报
回复
不错!
悠悠长风 2008-06-27
  • 打赏
  • 举报
回复
[Quote=引用 7 楼 fierygnu 的回复:]
引用 4 楼 JustMe2007 的回复:
这。。。这么复杂?

标准模板,理解了就简单了。
[/Quote]
说的好!!
fierygnu 2008-06-27
  • 打赏
  • 举报
回复
[Quote=引用 4 楼 JustMe2007 的回复:]
这。。。这么复杂?
[/Quote]
标准模板,理解了就简单了。
cceczjxy 2008-06-27
  • 打赏
  • 举报
回复
所谓的守护进程是指:
1,父进程是1号进程
2,不依赖于任何终端
3,是会话组组长


一般这样就可以了
void init_daemon()
{
pid_t pid;
int i;
if((pid=fork())==-1)
exit(1);
if(pid>0)
exit(0);
setsid();
if((pid=fork())==-1)
exit(1);
if(pid>0)
exit(0);
for(i=0;i<NOFILE;++i)
close(i);
umask(0);
return;
}
chenyx329 2008-06-27
  • 打赏
  • 举报
回复
呵呵,不懂,学习中~~,帮顶
JustMe2007 2008-06-26
  • 打赏
  • 举报
回复
这。。。这么复杂?
FreeElf 2008-06-26
  • 打赏
  • 举报
回复
#include <stdio.h>
#include <syslog.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/resource.h>

void daemonize(const char *cmd)
{
int i,fd0,fd1,fd2;
pid_t pid;
struct rlimit rl;
struct sigaction sa;

umask(0);

if(getrlimit(RLIMIT_NOFILE,&rl) < 0)
{
printf("%s:can't get file limit\n",cmd);
exit(1);
}

if((pid = fork()) < 0)
{
printf("%s:can't fork\n",cmd);
exit(1);
}
else if(pid != 0)
{
exit(0);
}
setsid();

sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if(sigaction(SIGHUP,&sa,NULL) < 0)
{
printf("%s:can't ignore SIGHUP");
exit(1);
}
if((pid = fork()) < 0)
{
printf("%s:can't fork\n",cmd);
exit(1);
}
else if(pid != 0)
{
exit(0);
}

if(chdir("/") < 0)
{
printf("%s:can't change directory to /",cmd);
exit(1);
}

if(rl.rlim_max == RLIM_INFINITY)
{
rl.rlim_max = 1024;
}
for(i = 0;i < rl.rlim_max;++ i)
{
close(i);
}

fd0 = open("/dev/null",O_RDWR);
fd1 = dup(0);
fd2 = dup(0);

openlog(cmd,LOG_CONS,LOG_DAEMON);
if(fd0 != 0 || fd1 != 1 || fd2 != 2)
{
syslog(LOG_ERR,"unexpected file descriptors %d %d %d",
fd0,fd1,fd2);
exit(1);
}
}

int main()
{
daemonize("hello");
sleep(1000);
exit(0);
}

这个是《UNIX环境高级编程》上的daemonize守护进程的代码
看看吧,如果要效果好的话,把sleep的时间跳大一点
运行后,就会看到一个在后台运行的程序,当sleep时间完了后,程序就退出了
不好意思,忘写注释了。。。
sanshao27 2008-06-26
  • 打赏
  • 举报
回复
学习中
huailairen 2008-06-26
  • 打赏
  • 举报
回复
还是找书看吧

23,131

社区成员

发帖
与我相关
我的任务
社区描述
Linux/Unix社区 应用程序开发区
社区管理员
  • 应用程序开发区社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧