简介
守护进程是一种运行在后台的特殊进程,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
其实,本质上守护进程和普通的进程并没有什么区别,只是我们规定了一种进程的编写规则,将其叫做守护进程,仅此而已。
特点
1. 在后台运行
为了不让其阻塞终端,我们用fork()创建子进程,然后退出父进程,就可以完成在后台运行的目的。
2. 脱离控制终端,创建新的会话组和进程组
简单说一下会话、进程、进程组以及控制终端的关系。多个进程构成进程组,多个进程组构成一个会话,会话可以有一个控制终端
此时我们的会话进程组等都是由父进程继承来的,我们要与之脱离,不受其影响。
方法是调用setsid();成功返回0,失败返回-1。
执行成功后会为当前进程创建会话组并成为该会话组的组长,当然也是新的进程组的组长了。
当调用者本身就是会话组长时会失败。
3. 使其不再是会话组长,用以禁止其打开终端
仍然是通过fork()产生子进程,然后退出父进程来做到这一点
4. 关闭已经打开文件描述符
这里的作用是避免对资源的占用
for(int i=0; i<NOFILE; i++)
close(i);
//NOFILE所属头文件是 <sys/param.h>
5. 更改当前工作目录
原因很简单,比如你讲工作目录设在了/home/aaa下,现在你要删掉aaa,是删不掉的,因为守护进程在运行过程中,是依赖于aaa这个目录的。
通常需要将工作目录设为根,根据特定情况而定。
chdir("/")
6. 重设文件掩码
使文件操作权限不再受父进程影响
直接将其设为0即可。
umask(0);
这里强调一点,umask函数里的参数,我们平时使用的比如0666,第一位的0只是表示0666是一个八进制的数,没有别的含义。与umask命令略有区别
7. 处理信号(非必须)
signal(SIGCHLD, SIG_IGN);
忽略SIGCHLD信号,常用于并发服务器的性能提升。因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设为忽略,内核就会把僵尸子进程转交给init进程去处理,避免了大量僵尸进程对系统资源的占用。
代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <stdlib.h>
#include <fcntl.h>
#include <time.h>
#include <string.h>
#define DIR "/Users/shiyi/Desktop"
#define TMP_TXT "/Users/shiyi/Desktop/tmp.txt"
void init_daemon()
{
//转为后台进程
int pid = fork();
if(pid < 0)
exit(1);
else if(pid != 0)
exit(0);
//开启新的会话组,成为会话组长和进程组长
setsid();
//使其不再是会话组长,不能开启终端
pid = fork();
if(pid < 0)
exit(1);
else if(pid != 0)
exit(0);
//关闭已经打开的文件描述符,避免浪费系统资源
for(int i=0; i<NOFILE; i++)
close(i);
//更改工作目录
chdir(DIR);
//重设文件掩码,使文件操作权限不再受父进程影响
umask(0000);
//重定向输入输出
int fd = open(TMP_TXT, O_CREAT | O_RDWR, 0644);
if(fd < 0)
exit(2);
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
close(fd);
//忽略SIGCHLD信号,避免大量僵尸进程占用系统资源
signal(SIGCHLD, SIG_IGN);
}
int main()
{
//创建守护进程
init_daemon();
time_t t;
char str[30];
for(int i=0; i<10; i++)
{
time(&t);
strcpy(str, ctime(&t));
str[strlen(str)-1] = '\0';
printf("%s : 当前是第%d次循环\n", str, i);
fflush(NULL);
sleep(1);
}
return 0;
}