Linux/uClinux 下daemon进程的编写


作者:lingyun 来源:凌云物网智科实验室 时间:2014-01-13

Author:   Guo Wenxue<Email: guowenxue@gmail.com   QQ:281143292>

Date:  Mon Oct 24 09:25:34 UTC 2011

转载请注明链接:  http://hi.baidu.com/kkernel/blog/item/b1a55c1ea7d37d034134172f.html

 

守护进程最重要的特性是后台运行;其次,守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工作目录以及文件创建掩模等。这些环境通常是守护进程从执行它的父进程(特别是shell)中继承下来的;最后,守护进程的启动方式有其特殊之处——它可以在Linux系统启动时从启动脚本/etc/rc.d中启动,可以由作业规划进程crond启动,还可以由用户终端(通常是shell)执行。

总之,除开这些特殊性以外,守护进程与普通进程基本上没有什么区别,因此,编写守护进程实际上是把一个普通进程按照上述的守护进程的特性改造成为守护进程。

 

其实,在标准的Linux系统环境下的库函数中已经提供了daemon()函数,如下:

 

[guowenxue@centos6 mobiled]$ man daemon

DAEMON(3)                  Linux Programmer’s Manual                 DAEMON(3)

 

NAME

daemon – run in the background

 

SYNOPSIS

#include <unistd.h>

int daemon(int nochdir, int noclose);

 

 

下面我们将针对Linux平台和uClinux平台来讲解守护编程的编写细节:

 

标准Linux下的守护进程的编写:

void daemonize_linux(int nochdir, int noclose)

{

int retval, fd;

int i;

 

在创建daemon进程时,父进程在子进程退出之前退出,这时子进程会被Init进程(pid=1)领养,所以这里如果进程的parent pid是1的话,说明他已经在后台运行了。

/* already a daemon */

if (1 == getppid())

return;

 

/* fork error */

retval = fork();

if (retval < 0)

_exit(0);

 

为避免挂起控制终端将Daemon放入后台执行。方法是在进程中调用fork使父进程终止, 让Daemon在子进程中后台执行。如果这时父进程在子进程前退出,那么init进程将领养子进程。

/* parent process exit */

if (retval > 0)

_exit(0);

 

这里主要是脱离控制终端,登录会话和进程组 。必要先介绍一下Linux中的进程与控制终端,登录会话和进程组之间的关系:进程属于 一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。 控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们 ,使之不受它们的影响。方法是在这里调用setsid()使进程成为会话组长:

/* obtain a new process session group */

setsid();

 

if (!noclose)

{

进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源, 造成进程所在的文件系统无法卸下以及引起无法预料的错误。按如下方法关闭它们:

/* close all descriptors */

for (i = getdtablesize(); i >= 0; –i)

close(i);

 

因为子进程在后台运行,他们不需要标准输入,标准输出,标准出错.这时我们可以将他们全部重定向到/dev/null文件下去

 

fd = open(“dev/null”, O_RDWR);  /* Redirect Standard input [0] to /dev/null */

dup(fd);   /* Redirect Standard output [1] to /dev/null */

dup(fd);   /* Redirect Standard error [2] to /dev/null */

 

}

进程从创建它的父进程那里继承了文件创建掩码。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩码掩模清除:

umask(0);

 

进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录
改变到根目录 。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如/tmp

if (!nochdir)

chdir(“/”);

return;

}

 

uCLinux下的守护进程编写:

 

/*由于uClinux不支持fork(),只支持vfork()的特性,同时在一些库中也没有提供像标准linux系统中能提供的daemon()函数,所以我们必须自己来实现uclinux操作系统下的daemon()函数:

这里我们主要是通过,第一次vfork()让父进程结束,同时让子进程再次运行一次代码来实现。所以这里的argc和argv参数主要使用在子进程中再次以同样的参数来运行自己。

*/

void daemon_ucLinux (int noclose, int argc, char **argv)

{

int         iRet = 0;

int         fd = -1;

 

5, 父进程是init进程,说明已在后台运行,这是孙子进程加载运行程序了

if (1 == getppid ()) /* already a daemon*/

{

setsid (); /* set new process group */

 

if (!noclose)

{

/*Close all the file description.*/

for (iRet = getdtablesize (); 0 <= iRet; –iRet)

{

fd = iRet;

close(fd);

}

 

fd = open(“/dev/null”, O_RDWR); /*Redirect Standard input [0] to /dev/null */

dup (fd); /*Redirect Standard output [1] to /dev/null */

dup (fd); /*Redirect Standard error [2] to /dev/null */

}

 

umask (027); /* set newly created file mode mask */

chdir(“/”); // change running directory

 

return ;

}

else  /*1,  第一次启动该程序的时候,系统将运行到这里*/

{

iRet = vfork ();

 

/*2, 通过vfrok()创建子进程,由于vfork()会导致父进程阻塞直到子进程推出或调用exec()函数,所以这里父进程并没有结束*/

if (0 > iRet) _exit (1); /* fork error */

if (0 < iRet) _exit (0); /* parent exists */

 

3, 再次调用vfork()产生孙子进程,再让子进程结束,这时上面的父进程就会退出,释放终端,子进程则阻塞在vfork()上,程序跑到后台了。

/*Child process(daemon) continue and fork again.*/

iRet = vfork ();

if (0 > iRet) _exit (1); /* fork error */

if (0 < iRet) _exit (0); /* parent exists */

 

4, 孙子进程调用execv()函数重新装载程序运行,子进程将不再阻塞在vfork()上,这时子进程也退出了。孙子进程被init进程领养,他的Parent PID将变成1

/* Parent exit, so child process parent pid should be init process(pid is 1) */

execv (argv[0], argv); /*Run the pograme again.*/

_exit (1); /*If execv() returned, then something error happend.*/

}

}

在线咨询
微信号
13554373241
联系方式
135-5437-3241
邮箱
guowenxue@aliyun.com
返回顶部