我们经常使用的各种APP,像淘宝、京东等购物APP,在晚上的时候,程序员不可能一直守在计算机前操作,但晚上有人想买一件物品的时候也是得提供服务的,因此就有了守护进程,它的作用十分强大,不受用户登录和注销的影响,是一个非常有用的进程。Linux大多数服务器就是守护进程实现的,例如:ftp服务器、ssh服务器、Web服务器等。
守护进程又称精灵进程(Daemon),是运行在后台的一种特殊进程。其本质上是孤儿进程。自成会话,自成进程组。一般情况下,与终端无关(TTY=?)。一般以d结尾,表示Daemon。
创建守护进程
1.setsid函数
该函数可以创建一个新的Session,并成为Session Leader。调用成功返回新创建的Session的id,出错返回-1。
注意一点,调用这个函数之前,当前进程不允许是进程组的组长,否则该函数返回-1。(我们可以先fork再调用该函数,子进程不可能是该组的第一个进程,在子进程中调用setsid就不会出错了)。
成功调用该函数的结果是:
1.创建一个新的会话(Session),当前进程成为会话首进程(Session Leader),当前进程ID就是会话ID。
2.创建一个新的进程组,当前进程成为进程组组长进程,当前进程ID就是进程组的ID。
3.如果当前进程原本有一个控制终端,则它失去这个控制终端,成为一个没有控制终端的进程。(失去控制终端指的是原来的控制终端仍然是打开的,也可以进行读写操作,但只是一个普通的打开文件,不是控制终端)。
创建守护进程的一般步骤:
1.在父进程中执行fork并exit退出。
2.在子进程中调用setsid函数创建新会话。
3.在子进程中调用chdir函数,让根目录”/”成为子进程的工作目录。
4.在子进程中调用umask函数,设置进程的umask为0。
5.在子进程中关闭任何不需要的文件描述符。
说明几点:
1.在后台运行
为避免挂起控制终端,将Daemon放入后台执行,方法是在进程中调用fork使父进程终止,
2.脱离控制终端,登录会话和进程组
首先说一下会话和进程组之间的关系:进程属于一个进程组,该进程组的ID就是组长进程的ID,登录会话可以包含多个进程组。这些进程组共享一个控制终端,控制终端通常是创建进程的登录终端。
调用setsid()函数,使进程成为会话组长。函数调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
3.禁止进程重新打开控制终端
到目前为止,进程已经成为无终端的会话组长,但它可以重新打开一个控制终端,可以通过使进程不再成为会话组长来禁止进程重新打开控制终端。
4.关闭打开的文件描述符
进程从创建它二等父进程那里继承了打开的文件描述符。若不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸载以及出现错误。
5.改变当前工作目录
进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。对于需要核心转储,写运行日志的进程将工作目录改变到特定目录。
6.重设文件创建掩码
进程从创建它的父进程那里继承了文件创建掩码,它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩码清楚:umask(0)。
7.处理SIGCHID信号
处理SIGCHID信号不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在Linux下可以简单的将SIGCHID信号的操作设为SIG_IGN。这样,内核再子进程结束时不会产生僵尸进程。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0);\
void creat_daemon()
{
pid_t pid;
pid = fork();
struct sigaction sa;
if(pid == -1){
ERR_EXIT("fork error");
}else if(pid > 0){//终止父进程
exit(EXIT_SUCCESS);
}
setsid();//创建一个新会话
sa.sa_handler = SIG_IGN;//忽略SIGCHID信号
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if(sigaction(SIGCHLD, &sa, NULL)<0){//注册子进程退出忽略信号
return;
}
chdir("/");
int i;
for(i=0;i<3;++i){
close(1);
open("/dev/null",O_RDWR);
dup(0);
dup(0);
}
umask(0);
return;
}
int main()
{
time_t t;
int fd;
creat_daemon();
while(1){
fd = open("daemon.log",O_WRONLY|O_CREAT|O_APPEND,0644);
if(fd == -1){
ERR_EXIT("open error");
}
t = time(0);
char* buf = asctime(localtime(&t));
write(fd, buf, strlen(buf));
close(fd);
sleep(60);
}
return 0;
}
运行结果:
以root用户执行,成功后在/目录下创建了daemon.log文件,cat查看后每隔一分钟写入一次。
只能以root用户执行执行,原因是我们创建守护进程时,已经将当前目录切换/目录,所以当我之后创建daemon.log文件其其实在/目录下,肯定不可以,因为普通用户没有权限。
2.daemon函数创建守护进程
参数:
nochdir:等于0当前目录更改至”/”
noclose:等于0将标准输入、标准输出、标准错误重定向至”/dev/null”
返回值:成功返回0,失败返回-1