本次文章划分为四个部分
1. 守护进程的概念。
2. 认识实现守护进程的相关函数。
3. 创建一个守护进程的基本步骤。
4. 演示实现守护进程代码。
一、守护进程的概念
- 守护进程就是一个脱离于控制终端、进程组与会话并且在后台运行的进程。
- 进程组:每个进程除了有一进程ID之外,还属于一个进程组。进程组是一个或多个进程的集合,每一个进程有一个唯一的进程组ID。进程组ID类似于进程ID——它是一个正整数,并可存放再pid_t数据类型中。可用函数getpgrp返回进程的进程组ID。
- 会话:一个或多个进程组的集合。
总结一下守护进程的特点:1、在后台运行的一个进程。2、守护进程脱离原来的控制终端、进程组与会话。3、不受用户登陆注销影响。4、周期性执行某任务。
二、认识实现守护进程的相关函数
其实我们只需认识一个函数就可以了——setsid()函数。
- 如果调用setsid的进程不是一个进程组的组长,此函数创建一个新的会话。
- 此进程变成该对话期的首进程。
- 此进程变成一个新进程组的组长进程。
- 此进程没有控制终端,如果在调用setsid前,该进程有控制终端,那么与该终端的联系被解除。 如果该进程是一个进程组的组长,此函数返回错误。
三、创建一个守护进程的基本步骤
-
父进程先fork()出一个子进程,然后让父进程终止,让守护进程再子进程中后台执行。
if(pid = fork()) exit(0);
-
在子进程中调用setsid()函数。当子进程是进程组组长时调用失败,但是通过上一步已经保证子进程不会是一个进程组组长了,进程成为新的会话会长和新的进程组组长,并与原来的会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
-
禁止进程重新打开控制终端。现在进程已经成为无终端的会话会长,但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话会长来进制进程重新打开控制终端。
if(pid = fork()) exit(0); //结束第一子进程,第二子进程继续(第二子进程不再是会话会长) -
改变当前工作目录。一般需要将工作目录改变到根目录,但这不是必须的步骤。
-
重设文件创建掩码。进程从创建它的父进程那里继承了文件创建掩码。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩码清除:umask(0);
-
关闭打开的文件描述符。进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源。一般我们关闭都要关闭这三个:STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO。它们分别对应输入、输出、错误。如果还有其它的还需要继续关闭。
-
处理相关事件。
四、演示实现守护进程代码
先实现一个简单的守护进程,该守护进程什么任务也不执行
#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); //让守护进程一直进行下去
}
}
执行该代码
我们发现./deamon执行代码后,马上又可以在终端上输入命令了,这印证了守护进程的一个特点:脱离终端并且在后台运行。
为了查看该进程是否真的在运行,我们可以执行命令:ps aux 查看进程
回车
我们找到./deamon,这就是刚刚创建的守护进程,进程ID2146,TTY:?,进一步证明真的脱离了终端。
为了终止该进程,我们可以使用kill命令:kill -9 2146
数字9代表第九号信号为SIGKILL,可以通过 kill -l 查看各信号对应的号码
现在执行kill -9 2146
再次输入ps aux 命令查看刚才的守护进程还在不在
显然,刚才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 目录下
现在返回该目录查看是否有创建该文件了。
确实已经创建成功了,让我们打开看看文件里面的内容
回车
通过图片可以看出确实实现了我们的功能。
至此,我们完成了守护进程的实现,并利用守护进程实现了一个小功能。
希望本文能帮助到大家。