操作系统------守护进程

守护进程的概念    

        守护进程也被称为精灵进程。它具有如下特点:

(1)它是运行在后台的一种特殊进程。

(2)它独立于控制终端,所以不能与用户进行直接交互。

(3)它周期性的执行某种任务或等待处理某些发生的事件。

(4)它不受用户登录注销的影响,一直在运行着。其它进程都是在用户登录或程序运行后才创建。

        基于以上的这些特点,守护进程有如下用途:

(1)Linux中的大多数服务器都是通过守护进程来实现的。如ssh服务器用于远程连接,ftp服务器。

(2)守护进程可以完成很多系统任务,如作业规划进程crond用于设置定时任务等。

        可以通过ps axj命令查看系统中的守护进程。守护进程通常是以d结尾的名字,表示Daemon。

创建守护进程

        创建步骤如下:

1. 首先,守护进程运行在后台。而我们编写的程序在运行起来会在前台执行,所以,在程序中要用fork创建一个子进程,创建成功后,父进程退出,则子进程就被提到后台继续运行了。(这里也可以是程序直接加&以运行在后台,不过fork的目的主要是为了第二步)。

2. 要使进程转化为守护进程,最重要的一步是要使该进程脱离原来的控制终端。此时,需要调用如下函数:

#include<unistd.h>
pid_t setsid(void);

        注意:在调用该函数时有个前提条件。就是当前进程不允许是进程组的组长进程。否则函数调用失败。所以就需要fork一个子进程。因为程序运行起来后,父进程必然会成为进程组的而第一个进程,即成为进程组的组长进程,而子进程必然不是组长进程,所以,在子进程中调用setsid函数即可成功。而fork创建子进程在第一步中已经实现。

        该函数的作用是:

(1)使调用该函数的进程脱离原会话的控制。再新创建一个会话,并使进程称为会话的Leader,当前进程的id就是该会话的id;

(2)使调用该函数的进程拜托原进程组的控制。再新创建一个进程组,并使该进程成为进程组的组长,该进程的id即为进程组的组id;

(3)摆脱控制终端。如果该进程原本有一个控制终端,则它将失去该控制终端,成为一个没有控制终端的进程。失去控制终端是指,原控制终端仍然是打开的,仍然可以读写,但只是一个打开的文件而不是控制终端了。

        守护进程还具有如下特点:它没有控制终端,自成会话,自成进程组,是一个孤儿进程。

        该函数调用成功返回新创建的会话id,即子进程的id,出错返回-1。

3. 守护进程创建好后,称为一个新会话的话首进程,而话首进程是可以重新申请连接控制终端的,所以,这里需要再次fork使父进程退出,子进程继承父进程的新会话,但又不是话首进程,从而实现不能再次申请控制终端的目的。注意,这一步不是必须的。

4. 守护进程创建好后,会继承父进程的文件屏蔽字。它可能会改变守护进程创建的文件的相关权限,所以要将掩码清零。 

5. 守护进程创建好后,会继承父进程的当前工作目录。 而当守护进程用于特殊的任务时,会在某个特定的工作目录下执行,所以,这里需要修改守护进程的当前工作目录,一般修改为根目录/,也可以修改为其他目录。

6. 守护进程创建好后,会继承父进程的文件描述表。但是守护进程已经脱离了控制终端,所以也就不需要使用标准输入,标准输出,标准错误了。因此将其关闭,或重定向到/dev/null即可。其中,写入/dev/null的内容相当于直接丢弃了。

7. 守护进程创建好后,可能会在后序的操作中创建子进程。子进程退出如果不回收会成为僵尸进程,造成内存泄漏。如果回收,就会造成守护进程的负担。将子进程退出时发送的SIGCHLD信号设置为SIG_IGN,使子进程自动回收资源即可。

        根据上述步骤,编写代码如下:

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

void mydaemon()
{
    pid_t pid = fork();
    if(pid < 0)//创建子进程失败
    {
        perror("fork error");
        exit(1);
    }
    else if(pid > 0)
    {
        exit(0);//使父进程退出
    }

    //子进程调用setsid函数,创建新会话,新进程组,摆脱原控制终端
    if(setsid() < 0)
    {
        perror("setsid error");//setsid函数调用失败
        exit(2);
    }

    //为防止子进程再次申请连接控制终端,再次fork使父进程退出,子进程不再是话首进程
    if(pid = fork() > 0)
    {
        exit(0);
    }
    else if(pid < 0)
    {                                                                                                                                 
        printf("fork again error\n");
        return;
    }

    //将子进程的掩码清空
    umask(0);

    //改变从父进程处继承的当前工作目录为根目录
    if(chdir("/") < 0)
    {
        printf("chdir error\n");
        return;
    }

    //将从父进程处继承的不再需要的文件关闭或重定向至/etc/null
    close(0);
    int fd0;
    if(fd0 = open("/dev/null",O_RDWR) < 0)
    {
        printf("open error\n");
        return;
    }
    dup2(fd0,1);
    dup2(fd0,2);

    //将子进程的SIGCHLD处理信号设置为SIG_IGN
    struct sigaction sa;
    sa.sa_handler = SIG_IGN;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    if(sigaction(SIGCHLD,&sa,NULL) < 0)
    {
        printf("sigaction error\n");
        return;
    }

}

int main()
{
    mydaemon();//创建守护进程

    //在守护进程打开一个文件
    int fd = open("mytext.log",O_CREAT|O_RDWR,0644);
    if(fd < 0)
    {   
        printf("open error\n");
        exit(1);                                                                                                        
    }   

    //使守护进程1s中向上述打开的文件写一句hello
    while(1)
    {
        write(fd,"hello\n",strlen("hello\n"));
        sleep(1);
    }
    return 0;
}                

        运行该程序,结果如下:


        发现在根目录下并没有生成mytext.log文件。这是因为,在根目录下创建文件需要root权限,所以切换至root用户,再次运行,结果如下:


        打开mytext.log文件便可发现1s向其中写一条hello。

        不将标准输出重定向至dev/null时,错误信息会打印出来。再次切换回普通用户运行:


调用daemon创建守护进程

        上述是由我们自己编写代码创建守护进程。而系统也提供了创建守护进程的接口函数:

#include<unistd.h>
int daemon(int nochdir,int noclose);

        函数功能:创建一个守护进程

        参数:

nochdir:为0表示将当前工作目录改为根目录

noclose:为0表示将关闭标准输入,标准输出,标准错误。

        返回值:成功返回0,失败返回-1

        可以将上述的mydaemon函数替换为daemon函数进行测试。

上文中有关进程组,会话的相关知识见:操作系统------进程组/作业/会话/作业控制

有关信号函数sigaction函数的相关知识见:信号相关的知识

猜你喜欢

转载自blog.csdn.net/sandmm112/article/details/80213232