嵌入式开发之linux---程序稳健性守护进程daemon 仅登录用户可见

(1)、为什么需要daemon守护进程

(2)、什么是守护进程daemon

(3)、守护进程daemon和一般进程有什么区别

(4)、linux下的daemon 实现

(5)、fork、wait,nohup

(6)、killall app 和kill -9 123 的区别

(7)、守护进程的实现方案

----------author:midu

-----------------dateTime:20210612

----------------------qq:1327706646

 

(1)、为什么需要daemon守护进程

总的来说就是维护进程的稳定性,提高程序的鲁棒性。

(2)、什么是守护进程daemon

Daemon程序是一直运行的服务端程序,又称为 守护进程。通常在系统后台运行,没有控制终端不与前台交互,Daemon程序一般作为 系统服务使用。Daemon是长时间运行的进程,通常在系统启动后就运行,在系统关闭时才结束。一般说Daemon程序在后台运行,是因为它没有控制终端,无法和前台的用户交互。Daemon程序一般都作为服务程序使用,等待客户端程序与它通信。我们也把运行的Daemon程序称作 守护进程。

(3)、守护进程daemon和一般进程有什么区别

1.因为daemon进程独立于终端,故使用ps axj命令查看进程时,其终端名(TTY)显示为?,终端前台进程组ID(TPGID)显示为-1

2.守护进程与用&结尾的后台运行程序有什么区别呢?
最大的区别有几点:
1)守护进程已经完全脱离终端控制台了,而后台程序并未完全脱离终端,在终端未关闭前还是会往终端输出结果
2)守护进程在关闭终端控制台时不会受影响,而后台程序会随用户退出而停止,需要在以nohug xxx & 格式运行才能避免影响
3)守护进程的会话组和当前目录,文件描述符都是独立的。后台运行只是终端进行了一次fork,让程序在后台执行,这些都没改变。

3.daemon函数存在的原因是因为控制终端由于某些原因(如断开终端链接)会发送一些信号的原因。而接收进程处理这些信号缺省动作会让进程退出。这些信号会由于终端上敲一些特殊按键而产生。

(4)、linux下的daemon 实现

编写Daemon程序有一些基本的规则,以避免不必要的麻烦。
  1、fork父进程退出,首先是程序运行后调用fork,并让 父进程退出。子进程获得一个新的进程ID,但继承了 父进程的 进程组ID。
  2、stsid跟终端脱离,调用setsid创建一个新的session,使自己成为新session和新 进程组的leader,并使进程没有控制终端(tty)。
  3、umask关掉0,1,2文件描述符,改变当前工作目录至根目录,以免影响可加载文件系统。或者也可以改变到某些特定的目录。
  4、chdir,设置文件创建mask为0,避免创建文件时权限的影响。
  5、信号处理,关闭不需要的打开 文件描述符。因为Daemon程序在后台执行,不需要于 终端交互,通常就关闭STDIN、STDOUT和STDERR。其它根据实际情况处理。

  这里只有1和 2是必须的。3一般都会做;5大部分不做。
  注意:另一个问题是Daemon程序不能和终端交互,也就无法使用printf方法输出信息了。我们可以使用syslog机制来实现信息的输出,方便程序的调试。在使用syslog前需要首先启动syslogd程序,关于syslogd程序的使用请参考它的man page,或相关文档,我们就不在这里讨论了。

一种不带信号检测的:

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


bool start_daemon()
{
    int fd;

    switch (fork()) {
        case -1:
            printf("fork() failed\n");
            return false;

        case 0:
            break;

        default:
            exit(0);
    }
    
    /*
    pid_t setsid(void);
    进程调用setsid()可建立一个新对话期间。
    如果调用此函数的进程不是一个进程组的组长,则此函数创建一个新对话期,结果为:
        1、此进程变成该新对话期的对话期首进程(session leader,对话期首进程是创建该对话期的进程)。
           此进程是该新对话期中的唯一进程。
        2、此进程成为一个新进程组的组长进程。新进程组ID就是调用进程的进程ID。
        3、此进程没有控制终端。如果在调用setsid之前次进程有一个控制终端,那么这种联系也被解除。
    如果调用进程已经是一个进程组的组长,则此函数返回错误。为了保证不处于这种情况,通常先调用fork(),
    然后使其父进程终止,而子进程继续执行。因为子进程继承了父进程的进程组ID,而子进程的进程ID则是新
    分配的,两者不可能相等,所以这就保证了子进程不是一个进程组的组长。
    */
    if (setsid() == -1) {
        printf("setsid() failed\n");
        return false;
    }
    
    switch (fork()) {
        case -1:
            printf("fork() failed\n");
            return false;

        case 0:
            break;

        default:
            exit(0);
    }

    umask(0);
    chdir("/");
    
    long maxfd;
    if ((maxfd = sysconf(_SC_OPEN_MAX)) != -1)
    {
        for (fd = 0; fd < maxfd; fd++)
        {
            close(fd);
        }
    }

    fd = open("/dev/null", O_RDWR);
    if (fd == -1) {
        printf("open(\"/dev/null\") failed\n");
        return false;
    }
    
    /*
    // Standard file descriptors.
    #define STDIN_FILENO    0   // Standard input.
    #define STDOUT_FILENO   1   // Standard output.
    #define STDERR_FILENO   2   // Standard error output.
    */
    
    /*
    int dup2(int oldfd, int newfd);
    dup2()用来复制参数oldfd所指的文件描述符,并将它拷贝至参数newfd后一块返回。
    如果newfd已经打开,则先将其关闭。
    如果oldfd为非法描述符,dup2()返回错误,并且newfd不会被关闭。
    如果oldfd为合法描述符,并且newfd与oldfd相等,则dup2()不做任何事,直接返回newfd。
    */
    if (dup2(fd, STDIN_FILENO) == -1) {
        printf("dup2(STDIN) failed\n");
        return false;
    }

    if (dup2(fd, STDOUT_FILENO) == -1) {
        printf("dup2(STDOUT) failed\n");
        return false;
    }

    if (dup2(fd, STDERR_FILENO) == -1) {
        printf("dup2(STDERR) failed\n");
        return false;
    }

    if (fd > STDERR_FILENO) {
        if (close(fd) == -1) {
            printf("close() failed\n");
            return false;
        }
    }

    return true;
}


int main(int argc, char** argv)
{
    start_daemon();
    
    while (true)
    {
        sleep(100);
    }
    
    return 0;
}
# 可以看到 example_daemon 进程在后台运行,并且其父进程ID为1
$ ps -ef | grep example_daemon


https://segmentfault.com/a/1190000022770900 Linux守护进程(Daemon)介绍与C++实现


一种带信号检测的:
#include <unistd.h> #include <signal.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <sys/stat.h> #include <time.h> #include <stdio.h> static bool flag = true; void create_daemon(); void handler(int); int main() { time_t t; int fd; create_daemon(); struct sigaction act; act.sa_handler = handler; sigemptyset(&act.sa_mask); act.sa_flags = 0; if(sigaction(SIGQUIT, &act, NULL)) { printf("sigaction error.\n"); exit(0); } while(flag) { fd = open("/home/mick/daemon.log", O_WRONLY | O_CREAT | O_APPEND, 0644); if(fd == -1) { printf("open error\n"); } t = time(0); char *buf = asctime(localtime(&t)); write(fd, buf, strlen(buf)); close(fd); sleep(60); } return 0; } void handler(int sig) { printf("I got a signal %d\nI'm quitting.\n", sig); flag = false; } void create_daemon() { pid_t pid;

//忽略I/O信号,STOP信号 signal(SIGTTOU,SIG_IGN); signal(SIGTTIN,SIG_IGN); signal(SIGTTSTP,SIG_IGN); signal(SIGTTUP,SIG_IGN);
pid = fork(); if(pid == -1) { printf("fork error\n"); exit(1); } else if(pid) { exit(0);//结束父进程,是的子进程成为后台进程 } if(-1 == setsid())//建立一个新的进程组,并是的该子进程成为该组的首进程,脱离所有终端 { printf("setsid error\n"); exit(1); } //为什么要两次fork pid = fork();//再次新建一个子进程,退出父进程,保证该进程不是进程组长,同事让该进程无法再打开一个新的终端 if(pid == -1) { printf("fork error\n"); exit(1); } else if(pid) { exit(0); } chdir("/");//改变工作目录,使得进程不与任何文件系统联系 int i; for(i = 0; i < 3; ++i)//关闭所有从父进程继承的不再需要的文件描述符 { close(i); } umask(0);//将文件当时创建屏蔽字设置为0
    signal(SIGCHLD,SIG_IGN); return; }

https://www.zhangshengrong.com/p/q0arAP4L1x/ C++编写LINUX守护进程的实现代码

(5)、(fork、wait),nohup,(fork、execl),(sigemptyset,sigaction,struct sigaction act),getpid,getppid

捕获sigchld信号,判断子进程是否为僵尸信号还是释放后的僵尸信号,避免守护进程waitpid阻塞死等,可以让守护进程不断运行。

 

struct sigaction act;

sigemptyset,sigaction;

sigemptyset 函数初始化信号集合set,将set 设置为空.

sigaction函数用来查询和设置信号处理方式,它是用来替换早期的signal函数。sigaction函数原型及说明如下

sigfillset 也初始化信号集合,只是将信号集合设置为所有信号的集合.

sigaddset 将信号signo 加入到信号集合之中,

sigdelset 将信号从信号集合中删除.

sigismember 查询信号是否在信号集合之中.s

igprocmask 是最为关键的一个函数.在使用之前要先设置好信号集合set.

https://blog.csdn.net/isunbin/article/details/84032708 SIGCHLD信号

 

pid:

从参数的名字pid和类型pid_t中就可以看出,这里需要的是一个进程ID。但当pid取不同的值时,在这里有不同的意义。

  pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。

  pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。

  pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。

  pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

 

(6)、killall app 和kill -9 123 的区别

1.如果用户使用killall 命令杀掉守护进程程序,那么监控守护进程和被监控应用进程都会被杀死。这种情况需要隔离守护进程和应用进程程序,不能通过守护进程fork子进程应用程序。

2.所有父进程为init的子进程(包括守护进程)均不能被kill -9 杀掉。

3. 除了wait信号检测外,代码检测还有如下方式:

while( 1 )
{
if( kill(pid_to_be_check, 0) < 0 ) system("重启命令");
sleep(10);
}


其中的pid_to_be_check 就是要检测的进程的PID

 

(7)、守护进程的实现方案

总的来说有两种:

1. 一种是通过shell脚本来检测pid进程号判断是否存在,不存在就调用执行路径命令启动程序,通过脚本,不断的ps -aux | grep "aaaa",然后启动进程

2. 另一种是通过程序内部监控应用程序,然后拉起程序,即开启另外一个监控进程,通过IPC来进程进程间keepalive,一旦发现被监控进程挂了,则重启之。监控进,这里又分为两种:

a. 守护进程直接fork子进程。

b. 守护进程通过心跳或是消息和应用程序进行通信,保持运行状态检测,一旦发现应用程序崩掉异常,守护进程命令fork子进程。

使用fork+execl+domian socket 配合进行父进程和子进程的创建以及程序运行状态监控;
守护进程程序中使用fork函数、fork会按照当前进程的资源复制一个子进程,通过execl函数调用想要运行程序来覆盖子进程;
fork函数会在两个进程中分别返回两个值;父进程中返回大于0值,子进程中返回==0值;错误返回-1
通过判断fork()==0判断当前线程是否为子进程,成立就使用execl函数调用想运行的程序;
通过判断fork()>0判断是否为父进程,成立就运行domain socket和子进程建立socket通信,父进程可以通过捕获sigchld信号+wait函数来是否子进程死后的僵尸进程和释放僵尸进程或者waitpid(x,x,WNOHANG)就不会阻塞死等了,可以让守护进程不断运行;

b方案的心跳通信方式,可以采用内存共享、消息队列等:

两个进程,ipc共享内存,内容为0,一个对内存内容 每秒加1,一个对内存每秒减1,这样基本上维持为1 0 -1,如果发现大于1小于-1,就说明一个程序死了,另一个程序就启动该程序,并把内存清0.这只是一种想法.还有别的方法.这个只供借鉴

a. 方案代码实现:

1). 首先使用fork系统调用,创建子进程

2). 在子进程中使用execv函数,执行需要自动重启的程序

3). 在父进程中执行wait函数等待子进程的结束,然后重新创建一个新的子进程

supervisor.c

/** 
 * 
 * supervisor 
 * 
 * date: 2016-08-10 
 * 
 */ 
 
#include <stdio.h> 
#include <unistd.h> 
#include <errno.h> 
#include <string.h> 
#include <sys/types.h> 
#include <sys/wait.h> 
#include <stdlib.h> 
#include <time.h> 
 
#define LOG_FILE "/var/log/supervisor.log" 
 
void s_log(char *text) { 
  time_t   t; 
  struct tm *tm; 
  char *log_file; 
  FILE *fp_log; 
  char date[128]; 
   
  log_file = LOG_FILE; 
  fp_log = fopen(log_file, "a+"); 
  if (NULL == fp_log) { 
    fprintf(stderr, "Could not open logfile '%s' for writing\n", log_file); 
  } 
   
  time(&t); 
  tm = localtime(&t); 
  strftime(date, 127, "%Y-%m-%d %H:%M:%S", tm); 
   
  /* write the message to stdout and/or logfile */   
  fprintf(fp_log, "[%s] %s\n", date, text); 
  fflush(fp_log); 
  fclose(fp_log); 
}  
 
int main(int argc, char **argv) { 
  int ret, i, status; 
  char *child_argv[100] = {0}; 
  pid_t pid; 
  if (argc < 2) { 
    fprintf(stderr, "Usage:%s <exe_path> <args...>", argv[0]); 
    return -1; 
  } 
   
  for (i = 1; i < argc; ++i) { 
    child_argv[i-1] = (char *)malloc(strlen(argv[i])+1); 
    strncpy(child_argv[i-1], argv[i], strlen(argv[i])); 
    //child_argv[i-1][strlen(argv[i])] = '0'; 
  } 
   
  while(1) { 
    pid = fork();  
    if (pid == -1) { 
      fprintf(stderr, "fork() error.errno:%d error:%s", errno, strerror(errno)); 
      break; 
    } 
    if (pid == 0) { 
      s_log(child_argv[0]); 
      ret = execv(child_argv[0], (char **)child_argv); 
      s_log("execv return"); 
      if (ret < 0) { 
        fprintf(stderr, "execv ret:%d errno:%d error:%s", ret, errno, strerror(errno)); 
        continue; 
      } 
      s_log("exit child process"); 
      exit(0); 
    } 
    if (pid > 0) { 
      pid = wait(&status); 
      fprintf(stdout, "Child process id: %d\n", pid); 
      //fprintf(stdout, "wait return"); 
      s_log("Wait child process return"); 
    } 
  } 
   
  return 0; 
} 

应用进程
demo.c

/* 
* 
* demo  
* 
*/ 
#include <stdio.h> 
#include <unistd.h> 
#include <errno.h> 
#include <string.h> 
#include <sys/types.h> 
#include <sys/wait.h> 
#include <stdlib.h> 
#include <time.h> 
 
#define LOG_FILE "/var/log/demo.log" 
 
void demo_log(int num) { 
  time_t   t; 
  struct tm *tm; 
  char *log_file; 
  FILE *fp_log; 
  char date[128]; 
   
  log_file = LOG_FILE; 
  fp_log = fopen(log_file, "a+"); 
  if (NULL == fp_log) { 
    fprintf(stderr, "Could not open logfile '%s' for writing\n", log_file); 
  } 
   
  time(&t); 
  tm = localtime(&t); 
  strftime(date,127,"%Y-%m-%d %H:%M:%S",tm); 
   
  /* write the message to stdout and/or logfile */   
  fprintf(fp_log, "[%s] num = %d\n", date, num); 
  fflush(fp_log); 
  fclose(fp_log); 
}  
 
int main(int argc, char **argv[]) { 
  int num = 0; 
   
  while(1) { 
    sleep(10); 
    num++; 
    demo_log(num); 
  } 
} 
执行如下测试命令 ./supervisor ./demo
测试结果:

c1. execv(progname, arg) 执行成功后,其后的代码不会执行;只有当执行错误时,才会返回 -1。原来调用execv进程的代码段会被progname应用程序的代码段替换。

c2. 当kill掉子进程时,父进程wait函数会接收到子进程退出的信号,进而循环再启动子进程,此过程实时性非常高。

c3. 当kill掉父进程时,子进程会被init进程接管,如果此时再kill掉子进程,则子进程会退出。

c4. 当同时kill掉父子进程,则父子进程都会退出。



b. 方案代码实现:

通过msg和内存共享通信的心跳方式实现进程间通信。

附录:

一般的守护进程列表:

linux守护进程列表
  amd:自动安装NFS( 网络文件系统)守侯进程
  apmd:高级电源治理
  Arpwatch:记录日志并构建一个在LAN接口上看到的以太网地址和ip地址对数据库
  Autofs:自动安装治理进程automount,与NFS相关,依靠于NIS
  Bootparamd:引导参数服务器,为LAN上的 无盘工作站提供引导所需的相关信息
  crond:linux下的计划任务
  Dhcpd:启动一个DHCP(动态IP地址分配)服务器
  Gated: 网关路由守候进程,使用动态的OSPF 路由选择协议
  Httpd:WEB服务器
  Inetd:支持多种 网络服务的核心守候程序
  Innd:Usenet新闻服务器
  Linuxconf:答应使用本地WEB服务器作为 用户接口来配置机器
  Lpd: 打印服务器
  Mars-nwe:mars-nwe文件和用于Novell的 打印服务器
  Mcserv:Midnight命令 文件服务器
  named:DNS服务器
  netfs:安装NFS、Samba和NetWare 网络文件系统
  network:激活已配置网络接口的 脚本程序
  nfs:打开NFS服务
  nscd:nscd(Name Switch Cache daemon)服务器,用于NIS的一个支持服务,它高速缓存用户口令和组成成员关系
  portmap:RPC portmap治理器,与inetd类似,它治理基于RPC服务的连接
  postgresql:一种SQL 数据库服务器
  routed:路由守候进程,使用动态RIP 路由选择协议
  rstatd:一个为LAN上的其它机器收集和提供系统信息的守候程序
  ruserd: 远程用户定位服务,这是一个基于RPC的服务,它提供关于当前记录到LAN上一个机器日志中的用户信息
  rwalld:激活rpc.rwall服务进程,这是一项基于RPC的服务,答应用户给每个注册到LAN机器上的其他终端写消息
   rwhod:激活rwhod服务进程,它支持LAN的rwho和ruptime服务
  sendmail: 邮件服务器sendmail
  smb:Samba文件共享/打印服务
  snmpd:本地简单网络治理候进程
  squid:激活代理服务器squid
  syslog:一个让系统引导时起动syslog和klogd 系统日志守候进程的脚本
  xfs:X Window字型服务器,为本地和远程X服务器提供字型集
  xntpd:网络时间服务器
  ypbind:为NIS(网络信息系统)客户机激活ypbind服务进程
  yppasswdd:NIS口令服务器
  ypserv:NIS主服务器
  gpm:管鼠标的
  identd:AUTH服务,在提供用户信息方面与finger类似

 

https://cloud.tencent.com/developer/article/1722336  详解Linux监控重要进程的实现方法

https://segmentfault.com/a/1190000022770900 Linux守护进程(Daemon)介绍与C++实现

https://blog.csdn.net/yun6853992/article/details/120420210 linux实现守护进程demo

https://www.zhangshengrong.com/p/q0arAP4L1x/ C++编写LINUX守护进程的实现代码

https://blog.csdn.net/str999_cn/article/details/78686923 用C语言在Linux系统下创建守护进程(Daemon)

https://www.cnblogs.com/my-way/p/5729120.html signal函数、sigaction函数及信号集(sigemptyset,sigaddset)操作函数

https://blog.51cto.com/u_13097817/2050887 不可靠信号

https://blog.csdn.net/u012736748/article/details/75040897 sigemptyset、sigaddset、sigprocmask的用法 信号未决,信号阻塞 信号的捕捉

https://blog.csdn.net/shaoye_csdn1/article/details/94599271 daemon 进程为什么要fork两次,与后台运行程序区别?

https://blog.csdn.net/csdn2798694115/article/details/80029322 什么是linux的daemon进程?和一般进程有什么区别?怎么判断一个进程是否为daemon进程?

https://gitlab.com/firefly-linux/app/ipc-daemon daemon

https://blog.csdn.net/xuleilx/article/details/8258798 守护进程

https://blog.csdn.net/fall221/article/details/45420197 redis的daemon实现

https://www.cnblogs.com/klb561/p/8724469.html 软件开发模式对比(瀑布、迭代、螺旋、敏捷)

https://www.cnblogs.com/klb561/p/8660285.html 计算机网络基础知识

https://www.cnblogs.com/klb561/p/9135642.html dbus通信与接口介绍 

http://blog.chinaunix.net/uid-27658317-id-5050645.html OpenWRT路由固件在VMware、vShpere虚拟机中运行操作

https://blog.csdn.net/pp13164892/article/details/123294879 crud 增删改查开发

https://www.cnblogs.com/ransn/p/5244656.html Modelsim初级使用教程###########################################

https://www.cnblogs.com/liujinggang/p/9462706.html 软件与Verilog基本格式规范说明

https://www.cnblogs.com/liujinggang/p/9463589.html Verilog实现流水灯及与C语言的对比

https://www.cnblogs.com/liujinggang/p/9656358.html IIC总线的原理与Verilog实现

https://www.cnblogs.com/liujinggang/p/9535366.html uart串口原理及Verilog实现

https://www.cnblogs.com/ransn/p/11452197.html

https://www.cnblogs.com/liujinggang/p/9609739.html spi总线原理及实现

https://www.cnblogs.com/liujinggang/p/9462706.html Verilog的基本实现。

  1. #include<unistd.h>
  2. #include<signal.h>
  3. #include<stdlib.h>
  4. #include<string.h>
  5. #include<fcntl.h>
  6. #include<sys/stat.h>
  7. #include<time.h>
  8. #include<stdio.h>
  9.  
  10. staticbool flag =true;
  11. void create_daemon();
  12. void handler(int);
  13.  
  14. int main()
  15. {
  16. time_t t;
  17. int fd;
  18. create_daemon();
  19. struct sigaction act;
  20. act.sa_handler = handler;
  21. sigemptyset(&act.sa_mask);
  22. act.sa_flags =0;
  23. if(sigaction(SIGQUIT,&act, NULL))
  24. {
  25. printf("sigaction error.\n");
  26. exit(0);
  27. }
  28. while(flag)
  29. {
  30. fd = open("/home/mick/daemon.log",
  31. O_WRONLY | O_CREAT | O_APPEND,0644);
  32. if(fd ==-1)
  33. {
  34. printf("open error\n");
  35. }
  36. t = time(0);
  37. char*buf = asctime(localtime(&t));
  38. write(fd, buf, strlen(buf));
  39. close(fd);
  40. sleep(60);
  41. }
  42. return0;
  43. }
  44. void handler(int sig)
  45. {
  46. printf("I got a signal %d\nI'm quitting.\n", sig);
  47. flag =false;
  48. }
  49. void create_daemon()
  50. {
  51. pid_t pid;
  52. pid = fork();
  53.  
  54. if(pid ==-1)
  55. {
  56. printf("fork error\n");
  57. exit(1);
  58. }
  59. elseif(pid)
  60. {
  61. exit(0);
  62. }
  63.  
  64. if(-1== setsid())
  65. {
  66. printf("setsid error\n");
  67. exit(1);
  68. }
  69.  
  70. pid = fork();
  71. if(pid ==-1)
  72. {
  73. printf("fork error\n");
  74. exit(1);
  75. }
  76. elseif(pid)
  77. {
  78. exit(0);
  79. }
  80.  
  81. chdir("/");
  82. int i;
  83. for(i =0; i <3;++i)
  84. {
  85. close(i);
  86. }
  87. umask(0);
  88. return;
  89. }
posted @ 2023-02-16 16:30  阿风小子  阅读(225)  评论(0编辑  收藏  举报