守护进程

我们经常使用的各种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

猜你喜欢

转载自blog.csdn.net/zwe7616175/article/details/79982554