linux下守护进程与信号与系统日志


—守护进程,信号,系统日志—

守护进程

背景知识:

​ 用来查看守护进程命令 ps axj (守护进程一般 PID,PGID,SID 相同)
1.文件描述符
​ 文件描述符:用来标识一个文件。当你打开一个存在的文件或者创建一个新文件,操作系统都会返回这个文件描述符。后续对这个文件的操作的一些函数,都会用到这个文件描述符作为参数。
​ Linux中三个特殊的文件描述符,数字分别为0,1,2:
​ 0:标准输入[键盘],对应的符号常量叫 STDIN_FILENO
​ 1:标准输出[屏幕],对应的符号常量叫 STDOUT_FILENO
​ 2:标准错误[屏幕],对应的符号常量叫STDERR_FILENO

  1. 输入输出重定向
    输出重定向:标准输出文件描述符不在指向屏幕,可以重新指向一个文件。

    输入重定向:在命令行中用 < 即可,例如 cat < file ,从文件里面读取内容

  2. 空设备
    /dev/null : 是一个特殊的设备文件,它丢弃一切写入其中的数据(像黑洞一样)例如:cat file > /dev/null 输出重定向文件到 空洞(无任何输出)。

    我们一般把守护进程的标准输入、标准输出和标准错误重定向到空设备(黑洞),从而确保守护进程不从键盘接收任何东西,也不把输出结果打印到屏幕。

守护进程的定义

​ 守护进程,也就是通常说的 Daemon 进程(精灵进程),是 Linux 中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。

守护进程的作用

1.守护进程是一个生存周期较长的进程,通常独立于控制终端并且周期性的执行某种任务或者等待处理某些待发生的事件

2.大多数服务都是通过守护进程实现的

3.关闭终端,相应的进程都会被关闭,而守护进程却能够突破这种限制

守护进程编写步骤

0.屏蔽一些控制终端操作的信号(见下文第二部分信号内容)

​ 这是为了防止守护进行在没有运行起来前,控制终端受到干扰退出或挂起。

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

​ 子进程变成孤儿进程,然后由1号init进程收养

​ 2.子进程创建新会话

​ 调用setsid创建新的会话,摆脱原会话,原进程组,原终端的控制,自己成为新会话的组长

​ 3.将当前目录改为根目录

​ 正在运行的进程文件系统不能卸载,如果目录要回退,则此时进程不能做到,为了避免这种麻烦,以根目录为当前目录

​ 4.重设文件权限掩码

​ 子进程的文件权限掩码是复制的父进程的,不重新设置的话,会给子进程使用文件带来诸多麻烦

​ 5.关闭不需要的文件描述符

​ 子进程的文件描述符也是从父进程复制来的,那些不需要的文件描述符永远不会被守护进程使用,会白白的浪费系统资源,还可能导致文件系统无法结束

信号(sigaction)

信号的定义

信号是 Linux 进程间通信的最古老的方式。信号是软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式 。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件

信号的作用

使用 kill 命令杀死程序,让程序“优雅”地退出。
通知进程发生了异步事件,进程之间可以互相通过系统调用kill()发送软中断信号。

常用的信号:
SIG_INT; Ctrl+c按键终止程序运行
SIG_TERM; Kill命令默认发送的信号,终止信号
SIG_QUIT; Ctrl+\按键终止程序运行

sigaction信号特点

​ 它不像signal()函数有可重入的设计的缺陷,sigaction函数有阻塞的功能,比如SIGINT信号来了,进入信号处理函数,默认情况下,在信号处理函数未完成之前,如果又来了一个SIGINT信号,其将被阻塞,只有信号处理函数处理完毕,才会对后来的SIGINT再进行处理,同时后续无论来多少个SIGINT,仅处理一个SIGINT,sigaction会对后续SIGINT进行排队合并处理。

sigaction()函数原型

头文件:
    #include <signal.h>   
原型:    
    int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

其中:
    signum 为捕获的信号类型
    act 为指定新的信号处理方式  指针类型传地址&
    oldact :输出先前信号处理方式 不用则NULL
    
其中:     struct sigaction {
           void     (*sa_handler)(int);
           void     (*sa_sigaction)(int, siginfo_t *, void *);
           sigset_t   sa_mask;
           int        sa_flags;
           void     (*sa_restorer)(void);
       };
    sa_handler 捕获到信号后要做的函数行为,为一个函数。
    sa_mask    信号屏蔽集
        可以通过函数sigemptyset/sigaddset等来清空和增加需要屏蔽的信号,上面代码中,对信号SIGINT处理时,如果来信号SIGQUIT,其将被屏蔽,但是如果在处理SIGQUIT,来了SIGINT,则首先处理SIGINT,然后接着处理SIGQUIT。
    sa_flags = 0; 通常为默认行为

syslog 系统日志

由于守护进程是脱离控制终端运行,无法在终端上显示出错信息, 合理的做法是把出错信息写入系统日志当中。

头文件: #include <syslog.h>
配套使用:

   openlog(const char *ident,int option , int facility);
   syslog(int priority, const char *format,..);
   closelog();

其中:
ident: 系统日志名字
option:指定openlog函数和接下来调用的syslog函数的控制标志
LOG_PID :每条日志信息都包含进程号。
LOG_PERROR:将信息写入日志的同时,将信息发送到标准错误输出。
facility:指定记录消息程序的类型,与syslog守护进程配置文件 syslog.conf 中的 facility 一致
priority: 输出级别
LOG_EMERG:系统不可用(断言)
LOG_ALERT:需要立即采取动作
LOG_CRIT:临界状态
LOG_ERR:错误
LOG_WARNING:警告
LOG_NOTICE:正常但需要注意
LOG_INFO:正常信息
LOG_DEBUG:调试信息

代码实例

#include <iostream>
using namespace std;

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

void daemonize_exit(int s)
{

    closelog();
    exit(0);

}


int daemonize()
{
    pid_t pid;
    int fd;
    pid = fork();
    if(pid<0)
    {
        syslog(LOG_WARNING,"pid is failed!");
        return -1;
    }
    //父进程退出
    if(pid > 0)  //parent
    {
        exit(0);
    }
    

    //child
    fd = open("/dev/null",O_RDWR);
    if(fd <0)
    {
        syslog(LOG_ERR,"fd open failed!");
        return -2;
    }
    //将标准输入、标准输出和标准错误重定向到空设备
    dup2(fd,0);
    dup2(fd,1);
    dup2(fd,2);
    if(fd >2)
        close(fd);
    
    //子进程创建新会话    
    setsid();
    
    chdir("/");     //更改目录为根目录 防止设备忙碌
    umask(0);       //重设文件权限掩码
    
    return 0 ;
}


int main()
{
    struct sigaction sa;
    FILE *fp;
    

    sa.sa_handler = daemonize_exit;
    
    //阻塞前先清空屏蔽集,然后添加需要屏蔽(阻塞)的信号
    sigemptyset(&sa.sa_mask);
    sigaddset(&sa.sa_mask,SIGTERM);
    sigaddset(&sa.sa_mask,SIGQUIT);
    sigaddset(&sa.sa_mask,SIGINT);
    sa.sa_flags = 0;
    
    //响应SIGINT的时候要阻塞住SIGTERM和SIGQUIT,防止可重入
    sigaction(SIGINT,&sa,NULL);
    //响应SIGQUIT的时候要阻塞住SIGINT和SIGTERM,防止可重入
    sigaction(SIGQUIT,&sa,NULL);
    //同理
    sigaction(SIGTERM,&sa,NULL);

    //打开syslog日志
    openlog("mydaemon",LOG_PID,0);
    
    if(daemonize() != 0 )
    {
        syslog(LOG_ERR,"fd or pid wrong!");
        exit(1);        
    }
    
    //下面则是守护进程需要做的事 且必须做事 后台才会显示守护进程
    // ............
    fp = fopen("/tmp/out","w");
    for(int i = 0 ; ;++i)
    {
        fprintf(fp,"HELLOOOooooooooooooo");
        fflush(fp);
        sleep(1);
    }
    
    exit(0);

}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 351134995@qq.com

×

喜欢就点赞,疼爱就打赏