【Linux编程】守护进程(daemon)详解与创建

一、概述

 

Daemon(守护进程)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。它不需要用户输入就能运行而且提供某种服务,不是对整个系统就是对某个用户程序提供服务。Linux系统的大多数服务器就是通过守护进程实现的。常见的守护进程包括系统日志进程syslogd、 web服务器httpd、邮件服务器sendmail和数据库服务器mysqld等。

守护进程一般在系统启动时开始运行,除非强行终止,否则直到系统关机都保持运行。守护进程经常以超级用户(root)权限运行,因为它们要使用特殊的端口(1-1024)或访问某些特殊的资源。

守护进程的父进程是init进程,因为它真正的父进程在fork出子进程后就先于子进程exit退出了,所以它是一个由init继承的孤儿进程。守护进程是非交互式程序,没有控制终端,所以任何输出,无论是向标准输出设备stdout还是标准出错设备stderr的输出都需要特殊处理。

守护进程的名称通常以d结尾,比如sshd、xinetd、crond等。

 

二、守护进程的创建

首先我们需要理解一些基本概念:

 

  • 进程组(process group): 一个或多个进程的集合,每个进程都有一个进程组ID,这个ID就是进程组长的进程ID
  • 会话期(session): 一个或多个进程组的集合,每个会话有唯一一个会话首进程(session leader),会话ID为会话首进程ID
  • 控制终端(controlling terminal) :每一个会话可以有一个单独的控制终端,与控制终端连接的会话首进程就是控制进程(controlling process)。 这时候,与当前终端交互的就是前台进程组,其他的都是后台进程组。

 

 

创建守护进程的过程中会用到一个关键函数:setsid(),这个函数用于创建一个新的会话期。

给出 setsid() 的 Linux 描述

 

#include 

pid_t setsid(void);

DESCRIPTION 
       setsid()  creates a new session if the calling process is not a process 
       group leader.  The calling process is the leader of  the  new  session, 
       the  process group leader of the new process group, and has no control- 
       ling tty.  The process group ID and session ID of the  calling  process 
       are set to the PID of the calling process.  The calling process will be 
       the only process in this new process group and in this new session.

RETURN VALUE 
       On success, the (new) session ID of the calling  process  is  returned. 
       On  error,  (pid_t) -1  is  returned,  and errno is set to indicate the 
       error.

 

 

进程调用 setsid()函数会:

首先请注意:只有当该进程不是一个进程组长时,才会成功创建一个新的会话期。

(1)摆脱原会话的控制。该进程变成新会话期的首进程

(2)摆脱原进程组。成为一个新进程组的组长

(3)摆脱终端控制。如果在调用 setsid() 前,该进程有控制终端,那么与该终端的联系被解除。 如果该进程是一个进程组的组长,

此函数返回错误。

 

创建守护进程的的一般步骤:

1、fork()创建子进程,父进程exit()退出

这是创建守护进程的第一步。由于守护进程是脱离控制终端的,因此,完成第一步后就会在Shell终端里造成程序已经运行完毕的假象。之后的所有工作都在子进程中完成,而用户在Shell终端里则可以执行其他命令,从而在形式上做到了与控制终端的脱离,在后台工作。

 

2、在子进程中调用 setsid() 函数创建新的会话

在调用了 fork() 函数后,子进程全盘拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但会话期、进程组、控制终端等并没有改变,因此,这还不是真正意义上的独立开来,而 setsid() 函数能够使进程完全独立出来。

 

3、再次 fork() 一个子进程并让父进程退出。

现在,进程已经成为无终端的会话组长,但它可以重新申请打开一个控制终端,可以通过 fork() 一个子进程,该子进程不是会话首进程,该进程将不能重新打开控制终端。退出父进程。

 

4、在子进程中调用 chdir() 函数,让根目录 ”/” 成为子进程的工作目录

这一步也是必要的步骤。使用fork创建的子进程继承了父进程的当前工作目录。由于在进程运行中,当前目录所在的文件系统(如“/mnt/usb”)是不能卸载的,这对以后的使用会造成诸多的麻烦(比如系统由于某种原因要进入单用户模式)。因此,通常的做法是让"/"作为守护进程的当前工作目录,这样就可以避免上述的问题,当然,如有特殊需要,也可以把当前工作目录换成其他的路径,如/tmp。改变工作目录的常见函数是chdir。

 

5、在子进程中调用 umask() 函数,设置进程的文件权限掩码为0

文件权限掩码是指屏蔽掉文件权限中的对应位。比如,有个文件权限掩码是050,它就屏蔽了文件组拥有者的可读与可执行权限。由于使用fork函数新建的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带来了诸多的麻烦。因此,把文件权限掩码设置为0,可以大大增强该守护进程的灵活性。设置文件权限掩码的函数是umask。在这里,通常的使用方法为umask(0)。

 

6、在子进程中关闭任何不需要的文件描述符

同文件权限码一样,用fork函数新建的子进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸下。
在上面的第二步之后,守护进程已经与所属的控制终端失去了联系。因此从终端输入的字符不可能达到守护进程,守护进程中用常规方法(如printf)输出的字符也不可能在终端上显示出来。所以,文件描述符为0、1和2 的3个文件(常说的输入、输出和报错)已经失去了存在的价值,也应被关闭。

 

7、守护进程退出处理

当用户需要外部停止守护进程运行时,往往会使用 kill 命令停止该守护进程。所以,守护进程中需要编码来实现 kill 发出的signal信号处理,达到进程的正常退出。

 

一张简单的图可以完美诠释之前几个步骤:

 

 

 

以下程序是创建一个守护进程,然后利用这个守护进程每隔一分钟向daemon.log文件中写入当前时间,当守护进程收到 SIGQUIT 信号后退出。

 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

static bool flag = true;
void create_daemon();
void handler(int);

int main()
{
	time_t t;
	int fd;
	create_daemon();
	struct sigaction act;
	act.sa_handler = handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	if(sigaction(SIGQUIT, &act, NULL))
	{
		printf("sigaction error.\n");
		exit(0);
	}
	while(flag)
	{
		fd = open("/home/mick/daemon.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
		if(fd == -1)
		{
			printf("open error\n");
		}
		t = time(0);
		char *buf = asctime(localtime(&t));
		write(fd, buf, strlen(buf));
		close(fd);
		sleep(60);
	}
	return 0;
}
void handler(int sig)
{
	printf("I got a signal %d\nI'm quitting.\n", sig);
	flag = false;
}
void create_daemon()
{
	pid_t pid;
	pid = fork();
	
	if(pid == -1)
	{
		printf("fork error\n");
		exit(1);
	}
	else if(pid)
	{
		exit(0);
	}

	if(-1 == setsid())
	{
		printf("setsid error\n");
		exit(1);
	}

	pid = fork();
	if(pid == -1)
	{
		printf("fork error\n");
		exit(1);
	}
	else if(pid)
	{
		exit(0);
	}

	chdir("/");
	int i;
	for(i = 0; i < 3; ++i)
	{
		close(i);
	}
	umask(0);
	return;
}

注意守护进程一般需要在 root 权限下运行。

通过

 

ps -ef | grep 'daemon'


可以看到:

 

root     26454  2025  0 14:20 ?        00:00:00 ./daemon

 

并且产生了 daemon.log,里面是这样的时间标签

 

Thu Dec  8 14:35:11 2016
Thu Dec  8 14:36:11 2016
Thu Dec  8 14:37:11 2016

 

最后我们想退出守护进程,只需给守护进程发送 SIGQUIT 信号即可

 

sudo kill -3 26454 


再次使用 ps 会发现进程已经退出。

 

 

三、利用库函数 daemon()创建守护进程

 

其实我们完全可以利用 daemon() 函数创建守护进程,其函数原型:

 

#include 

int daemon(int nochdir, int noclose);

DESCRIPTION         

       The daemon() function is for programs wishing to detach themselves
       from the controlling terminal and run in the background as system
       daemons.


       If nochdir is zero, daemon() changes the process's current working
       directory to the root directory ("/"); otherwise, the current working
       directory is left unchanged.


       If noclose is zero, daemon() redirects standard input, standard
       output and standard error to /dev/null; otherwise, no changes are
       made to these file descriptors.

RETURN VALUE         

       (This function forks, and if the fork(2) succeeds, the parent calls
       _exit(2), so that further errors are seen by the child only.)  On
       success daemon() returns zero.  If an error occurs, daemon() returns
       -1 and sets errno to any of the errors specified for the fork(2) and
       setsid(2).

 

现在让我们使用 daemon() 函数来再次创建一次守护进程,其实就是用 daemon() 替换掉我们自己的 create_daemon():

 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

static bool flag = true;
void handler(int);

int main()
{
	time_t t;
	int fd;
	if(-1 == daemon(0, 0))
	{
		printf("daemon error\n");
		exit(1);
	}
	struct sigaction act;
	act.sa_handler = handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	if(sigaction(SIGQUIT, &act, NULL))
	{
		printf("sigaction error.\n");
		exit(0);
	}
	while(flag)
	{
		fd = open("/home/mick/daemon.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
		if(fd == -1)
		{
			printf("open error\n");
		}
		t = time(0);
		char *buf = asctime(localtime(&t));
		write(fd, buf, strlen(buf));
		close(fd);
		sleep(60);
	}
	return 0;
}
void handler(int sig)
{
	printf("I got a signal %d\nI'm quitting.\n", sig);
	flag = false;
}


没问题,和之前一样。

 

你可能感兴趣的:(嵌入式)