粉丝不过W 

守护进程概述

   守护进程,一般为 Daemon进程,常在系统引导载入时启动,在系统关闭时终止。许多后台服务都是通过守护进程实现的

        如,要使某个进程脱离控制终端,那就必须把该进程转换为守护进程

  编写守护进程

    编写守护进程要遵循一个特定的流程,创建一个简单的守护进程分 5 步

        1 创建子进程,父进程退出

            当父进程退出 就会在 shell 终端里造成一种程序已经运行完毕的假象,之后的工作都在子进程中完成,而用户在 shell 终端里就可以执行其他的命令,从而实现了 与控制终端的脱离

           由于父进程已经先于 子进程 退出,会造成子进程没有父进程,这样就变成了一个孤儿进程

            在 Linux 中,每当系统发现一个孤儿进程,就会自动由 1 号进程(也就是 init 进程)收养它,这样,原先的子进程就会变成 init 进程的子进程

    关键代码:

pid = fork();
if(pid > 0)
{
    exit(0);    //父进程退出
}

  2 在子进程中创建新会话

     进程组:

         进程组是一个或多个进程的集合,进程组由进程组 ID 来惟一标识

         除了进程号(PID)之外,进程组 ID 也是一个进程的必备属性

         每个进程组都有一个组长进程,而组长进程的进程号 = 进程组 ID,且该进程 ID 不会因组长进程的退出而受到影响

     会话期:

         会话组是一个或多个进程组的集合,一般 一个会话开始于用户登录,终止于用户退出,此期间的该用户运行的所有进程都属于这个会话期

    关系图:

 

#include <sys/types.h>
#include <unistd.h>

/*
 *创建一个新的会话,并担任该会话组的组长
 *return:
 *成功:进程ID
 *失败:-1
 */
pid_t setsid(void);

        调用 setsid()的3 个作用:

           让进程摆脱原会话的控制

          让进程摆脱原进程组的控制

          让进程摆脱原控制终端的控制

       fork()函数:创建子进程再令父进程退出,子进程全盘复制了父进程的会话期、进程组和控制终端等,虽然父进程退出,但原先的会话期、进程组和控制终端等并没有改变

          setsid()函数:能使进程完全独立出来,从而脱离所有其他进程的控制

       3 改变当前目录为根目录

           fork():创建的子进程继承了父进程的当前工作目录

             通常可以 让“ / ”作为守护进程的当前工作目录,也可以把当前工作目录换成其他的路径,如/tmp

          chdir():改变工作目录

       4 重设文件权限掩码

          文件权限掩码: 指屏蔽掉文件权限中的对应位

          如,有一个文件权限掩码是 050,它就屏蔽了文件组拥有者的可读与可执行权限

         由于fork() 新建的子进程继承了父进程的文件权限掩码,这样 子进程使用文件有许多的麻烦。因此,把文件权限掩码设置为 0,可以大大增强该守护进程的灵活性

        umask() : 设置文件权限掩码

       5 关闭文件描述符

          同文件权限掩码一样, fork():新建的子进程会从父进程那里继承一些已经打开了的文件,这些被打开的文件可能永远不会被守护进程读或写

         方式:

for(i = 0; i < MAXFILE; i++)
{
    close(i);
}

创建守护进程的流程图:

     守护进程的出错处理:

          由于守护进程完全脱离了控制终端,因此,不能像其他普通进程一样将错误信息输出到控制终端来通知程序员, 即使使用 gdb 也无法正常调试

         通用的办法:通过守护进程 syslogd 来维护将程序中的出错信息输入到系统日志文件中(例如:“/var/log/messages”),从而可以直观地看到程序的问题所在

       该机制提供了 3 个 syslog 相关函数:

           openlog()函数:用于打开系统日志服务的一个连接

           syslog()函数: 是用于向日志文件中写入消息

           closelog()函数: 是用于关闭系统日志服务的连接

#include <syslog.h>

/*
 *ident:
 *  通常为程序的名称, 要向每个消息加入的字符串
 *option:
 *  LOG_CONS:如 消息无法送到系统日志服务,则直接输出到系统控制终端
 *  LOG_NDELAY:立即打开系统日志服务的连接。正常情况下,直接发送到第一条消息时才打开连接
 *  LOG_PERROR:将消息也同时送到 stderr 上
 *  LOG_PID:在每条消息中包含进程的 PID
 *Facility:
 *  指定程序发送的消息类型
 *  LOG_AUTHPRIV:安全/授权信息
 *  LOG_CRON:时间守护进程(cron 及 at)
 *  LOG_DAEMON:其他系统守护进程
 *  LOG_KERN:内核信息
 *  LOG_LOCAL[0~7]:保留
 *  LOG_LPR:行打印机子系统
 *  LOG_MAIL:邮件子系统
 *  LOG_NEWS:新闻子系统
 *  LOG_SYSLOG:syslogd 内部所产生的信息
 *  LOG_UUCP:UUCP子系统
 */
void openlog(char *ident, int option, int facility);
#include <syslog.h>

/*
 *priority:
 *  指定消息的重要性
 *  LOG_EMERG:系统无法使用
 *  LOG_ALERT:需要立即采取措施
 *  LOG_CRIT:有重要情况发生
 *  LOG_ERR:有错误发生
 *  LOG_WARNING:有警告发生
 *  LOG_NOTICE:正常情况,但也是重要情况
 *  LOG_INFO:信息消息
 *  LOG_DEBUG:调试信息
 *format:
 *  以字符串指针的形式表示输出的格式,类似 printf 中的格式
 */
void syslog(int priority, char *format,...);
#include <syslog.h>

void closelog(void);

利用 syslog 服务的守护进程实例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <syslog.h>

int main()
{
    pid_t pid,sid;
    int i,fd;
    char *buf = "This is a Daemon\n";

    pid = fork();
    if(pid < 0)
    {
        printf("Error fork\n");
        exit(1);
    }
    else if(pid > 0)
    {
        exit(0);    /*父进程退出*/
    }

    /*打开系统日志服务, openlog*/
    openlog("daemon_syslog",LOG_PID, LOG_DAEMON);
    if((sid = setsid()) < 0)
    {
        syslog(LOG_ERR, "%s\n", "setsid");
        exit(1);
    }
    if((sid = chdir("/")) < 0)
    {
        close(i);
    }
    /*这时创建完守护进程,以下开始正式进入守护进程工作*/
    while(1)
    {
        if((fd = open("/tmp/daemon.log", O_CREAT |
                                         O_WRONLY |
                                         O_APPEND, 0600)) < 0)
        {
            syslog(LOG_ERR,"open");
            exit(1);
        }
        write(fd, buf, strlen(buf)+1);
        close(fd);
        sleep(10);
    }
    closelog();
    exit(0);
}