本次文章划分为四个部分

1. 守护进程的概念。
2. 认识实现守护进程的相关函数。
3. 创建一个守护进程的基本步骤。

4. 演示实现守护进程代码。

一、守护进程的概念

  • 守护进程就是一个脱离于控制终端、进程组与会话并且在后台运行的进程。
  • 进程组:每个进程除了有一进程ID之外,还属于一个进程组。进程组是一个或多个进程的集合,每一个进程有一个唯一的进程组ID。进程组ID类似于进程ID——它是一个正整数,并可存放再pid_t数据类型中。可用函数getpgrp返回进程的进程组ID。
  • 会话:一个或多个进程组的集合。

总结一下守护进程的特点:1、在后台运行的一个进程。2、守护进程脱离原来的控制终端、进程组与会话。3、不受用户登陆注销影响。4、周期性执行某任务。

二、认识实现守护进程的相关函数

其实我们只需认识一个函数就可以了——setsid()函数。

  • 如果调用setsid的进程不是一个进程组的组长,此函数创建一个新的会话。
  • 此进程变成该对话期的首进程。
  • 此进程变成一个新进程组的组长进程。
  • 此进程没有控制终端,如果在调用setsid前,该进程有控制终端,那么与该终端的联系被解除。 如果该进程是一个进程组的组长,此函数返回错误。

三、创建一个守护进程的基本步骤

  1. 父进程先fork()出一个子进程,然后让父进程终止,让守护进程再子进程中后台执行
    if(pid = fork()) exit(0);
  2. 在子进程中调用setsid()函数。当子进程是进程组组长时调用失败,但是通过上一步已经保证子进程不会是一个进程组组长了,进程成为新的会话会长和新的进程组组长,并与原来的会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
  3. 禁止进程重新打开控制终端。现在进程已经成为无终端的会话会长,但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话会长来进制进程重新打开控制终端。
    if(pid = fork()) exit(0); //结束第一子进程,第二子进程继续(第二子进程不再是会话会长)
  4. 改变当前工作目录。一般需要将工作目录改变到根目录,但这不是必须的步骤。
  5. 重设文件创建掩码。进程从创建它的父进程那里继承了文件创建掩码。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩码清除:umask(0);
  6. 关闭打开的文件描述符。进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源。一般我们关闭都要关闭这三个:STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO。它们分别对应输入、输出、错误。如果还有其它的还需要继续关闭。
  7. 处理相关事件。

四、演示实现守护进程代码

先实现一个简单的守护进程,该守护进程什么任务也不执行

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<signal.h>
#include<sys/time.h>
#include<time.h>
#include<fcntl.h>

int main()
{
	pid_t pid = fork();         //fork()一个子进程
	if(pid < 0)
	{
		perror("fork()");
		exit(1);
	}
	if(pid > 0)                //终止父进程
	{
		exit(1);
	}
	else if(pid == 0)
	{
		setsid();                   //子进程调用setsid()创建新的会话
		if(pid = fork()) exit(1);   //终止子进程
		chdir("/home/notang");     //更改目录,放在根目录下
		umask(0);                  //重设文件创建掩码
                //关闭输入、输出、错误文件描述符
		close(STDIN_FILENO);
		close(STDOUT_FILENO);
		close(STDERR_FILENO);

		while(1);                 //让守护进程一直进行下去
	}

}

执行该代码

Java守护进程启动命令 进程守护程序_linux编程

我们发现./deamon执行代码后,马上又可以在终端上输入命令了,这印证了守护进程的一个特点:脱离终端并且在后台运行。

为了查看该进程是否真的在运行,我们可以执行命令:ps aux 查看进程

Java守护进程启动命令 进程守护程序_Java守护进程启动命令_02

回车

Java守护进程启动命令 进程守护程序_Java守护进程启动命令_03

我们找到./deamon,这就是刚刚创建的守护进程,进程ID2146,TTY:?,进一步证明真的脱离了终端。

为了终止该进程,我们可以使用kill命令:kill -9 2146

数字9代表第九号信号为SIGKILL,可以通过 kill -l 查看各信号对应的号码

Java守护进程启动命令 进程守护程序_#include_04

现在执行kill -9 2146

Java守护进程启动命令 进程守护程序_守护进程_05

再次输入ps aux 命令查看刚才的守护进程还在不在

Java守护进程启动命令 进程守护程序_进程组_06

显然,刚才ID为2146的守护进程已经被杀死了,在这里找不到了。

至此,我们已经实现一个简单的守护进程,并且改进程什么任务也不执行。

上面我提到守护进程的特点之一是周期性的执行某种任务,现在讲解一下怎么让守护进程执行任务。

如果我们想让守护进程执行任务,可以利用信号机制,不会信号处理的可以参考我之前的文章《TCP/IP网络编程]fork函数、僵尸进程以及信号处理》

我这次执行的任务为:获取电脑的实时时间,并把时间写进一个文件里。

获取时间思路:可通过time()函数获取实时时间,再通过ctime函数格式化时间的格式,最后open一个文件并把获取的时间write()进去。

实现一个执行“获取实时时间并把时间写进文件”任务的守护进程

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<signal.h>
#include<sys/time.h>
#include<time.h>
#include<fcntl.h>

//守护进程执行的任务
void dowork(int n)              
{
	time_t curtime;
	time(&curtime);      //获取系统时间

        //格式化时间,返回一个字符串
	char* pt = ctime(&curtime);
        //把格式化的时间写进“temp.txt”文件
	int fd = open("/home/notang/temp.txt",O_CREAT | O_WRONLY | O_APPEND,0664);
	write(fd,pt,strlen(pt)+1);
	close(fd);   //关闭文件
}

int main()
{
	pid_t pid = fork();
	if(pid < 0)
	{
		perror("fork()");
		exit(1);
	}
	if(pid > 0)
	{
		exit(1);
	}
	else if(pid == 0)
	{
		setsid();
                if(pid = fork()) exit(1);
		chdir("/home/notang");
		umask(0);
		close(STDIN_FILENO);
		close(STDOUT_FILENO);
		close(STDERR_FILENO);
        //------------直至该步,完成守护进程的创建---------------

        //利用信号机制实现执行任务,这里笔者用的是SIGALRM信号
		struct sigaction act; 
		act.sa_flags = 0;
		sigemptyset(&act.sa_mask);
		act.sa_handler = dowork;
		sigaction(SIGALRM,&act,NULL);

	//struct itimerval结构体可设置闹钟几秒后开始、间隔响应又是几秒
        //笔者设置了2秒后开始响应信号,每隔一秒再响应一次,也即每隔一秒写一次新的时间进文件
		struct itimerval val;
		val.it_value.tv_sec = 2;
		val.it_value.tv_usec = 0;

		val.it_interval.tv_sec = 1;
		val.it_interval.tv_usec = 0;

        //把设置好的结构体传进setitimer函数
		setitimer(ITIMER_REAL,&val,NULL);
		
		while(1);
	}

}

运行代码,因为笔者在代码中传进文件是把文件放在了 /home/notang 目录下

Java守护进程启动命令 进程守护程序_守护进程_07

现在返回该目录查看是否有创建该文件了。

Java守护进程启动命令 进程守护程序_linux编程_08

确实已经创建成功了,让我们打开看看文件里面的内容

Java守护进程启动命令 进程守护程序_守护进程_09

回车

Java守护进程启动命令 进程守护程序_#include_10

通过图片可以看出确实实现了我们的功能。

至此,我们完成了守护进程的实现,并利用守护进程实现了一个小功能。

希望本文能帮助到大家。