• 如何在Linux下 自定义/编写 一个守护进程


    【摘要】本文主要讲述何为守护进程,以及如何自编或者利用现有程序将其伪装成守护进程。

    1、何为守护进程?

    • 正常情况下,当我们运行一个前台或后台进程时,一旦离开当前会话(终端),那该会话中的所有前后台进程也随即结束,当你重新打开会话时,已经“物是人非,难遇故人”了。
    • 而守护进程就可以不受会话的限制,可在前后台一直运行直至结束的进程。
    • 守护进程的实现有两种方式:自编和利用现有程序伪装,下文将分别讲述。

    2、自编守护进程的一般步骤及代码实例

    通常,当我们写一个守护进程时,需要按照如下步骤来写:

    1. 创建一个子进程,父进程随即退出

      • fork()函数用来创建一个子进程;

      • 在父进程退出后,后续所有步骤在子进程中进行。

    2. 在子进程中创建新会话

      • setsid()函数用来创建一个新的会话;
      • 创建新的会话的目的是使子进程完全从父进程中独立出来,使得即使父进程所在会话(终端)被关闭,也不会影响子进程。
    3. 改变子进程当前工作目录,一般设为根目录

      • chdir()函数用来为子进程重新分配工作目录;
      • 重新制定工作目录的原因是防止原来父进程工作目录含有可卸载的目录(文件系统),从而为今后守护进程的运行产生不必要的麻烦。
      • 工作目录一般设为根目录,当然也可以换成其它安全路径。
    4. 重设文件权限掩码

      • umask()函数用来更改子进程的默认掩码;
      • 文件权限掩码一般设为0,便于在守护进程中指定的文件权限不受掩码干扰,增加灵活性;
        • 通常,系统默认的文件权限为666(rw-rw-rw-),默认的文件权限掩码为022,故最后二者相与,得到的文件真实权限为644(rw-r–r–)。
    5. 关闭文件描述符

      • close()函数用来关闭文件描述符指定的文件;

      • 从父进程继承的打开文件及默认打开的0、1、2在守护进程中一般不会用到,而且又浪费系统资源,所以一般要关闭他们。

      • getdtablesize()函数返回当前打开的文件项数;

    6. 守护进程功能实现(无限循环)

    • 代码实例

      #include   
      #include   
      #include   
      #include   
      #include   
      #include   
      #include   
        
      int main() 
      {    
          pid_t pid;  
          int i, fd, len;  
          char *buf = "守护进程运行中.\n";  
          len = strlen(buf)+1;
          
          pid = fork();	//1.1 创建子进程
          if (pid < 0) {  
          	printf("fork error!");  
          	exit(1);  
          }
          if (pid>0) 		// 1.2父进程退出  
          	exit(0);  
            
          setsid(); 		// 2.在子进程中创建新会话。  
          
          chdir("/"); 	// 3.设置工作目录为根目录  
          
          umask(0); 		// 4.设置权限掩码  
          
          for(i=0; i<getdtablesize(); i++) //5.关闭用不到的文件描述符  
          	close(i);
          
          //6.守护进程功能实现
          while(1) {			// 死循环表征它将一直运行
              fd = open("/var/log/daemon.log",O_CREAT|O_WRONLY|O_APPEND,0600);
          	if(fd < 0) {
                  printf("Open file failed!\n");
                  exit(1);  
          	}  
          	write(fd, buf, len);  // 将buf写到fd中  
          	close(fd);  
          	sleep(10);  
          }  
          printf("error: Never run here!\n");  
      	return 0;  
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 注意:因为守护进程关闭了所有文件描述符,包括默认的标准输入输出和出错,所以在终端上看不到其输出。
      • 运行该程序后,可以通过执行:cat /var/log/daemon.log查看进程运行情况;也可以通过ps -axjf | grep 程序名查看。
      • 关闭当前会话(终端),再次重复执行上步命令,查看进程是否还在运行!

    2.1 守护进程的出错处理

    由于守护进程脱离了控制终端,因此,不能像其它普通进程一样将信息输出到控制终端来通知程序员。所以,只有使用syslog服务,将程序中的出错信息输入到系统日志文件中,例如/var/log/messages

    syslog是Linux的系统日志管理服务,通过守护进程syslogd来维护。该服务在启动时会读配置文件/etc/syslog.conf,该文件决定了不同类别的消息会发往何处。例如,紧急消息发到控制台上直接显示,警告消息则发到记录文件中。

    该机制为程序员提供了3个接口函数,分别为openlog()、syslog()、closelog():

    • openlog()

      • 作用:打开系统日志服务的一个连接
      • 原型:void openlog(char *ident, int option, int facility);
      • 参数:
        • ident:要向每个消息加入的字符串,通常为程序的名称
        • option:
          • LOG_CONS:若消息无法送到系统日志服务,则直接输出到系统控制终端
          • LOG_NDELAY:立即打开系统日志服务的连接。正常情况下,直到发送第一条消息时才打开连接
          • LOG_PERROR:将消息同时送到标准输出上
          • 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:syslog内部所产生的信息
          • LOG_USER:一般使用者等级的信息
          • LOG_UUCP:UUCP子系统
    • syslog()

      • 作用:向日志文件中写消息,可以规定消息的优先级、输出格式等
      • 原型:void syslog(int priority, char *format, ...);
      • 参数:
        • priority:指定信息等级
          • LOG_EMERG:系统无法继续使用
          • LOG_ALERT:需要立即采取措施
          • LOG_CRIT:有重要情况发送
          • LOG_ERR:有错误发生
          • LOG_WARNING:有警告发生
          • LOG_NOTICE:正常情况,但也是重要情况
          • LOG_INFO:信息消息
          • LOG_DEBUG:调试信息
        • format:类似printf中的字符格式
    • closelog()

      • 作用:关闭系统日志服务的连接
      • 原型:void closelog(void);

    重写上节代码

    #include   
    #include   
    #include   
    #include   
    #include   
    #include   
    #include   
      
    int main() 
    {    
        pid_t pid;  
        int i, fd, len;  
        char *buf = "守护进程运行中.\n";  
        len = strlen(buf)+1;
        
        pid = fork();	//1.1 创建子进程
        if (pid < 0) {  
        	printf("fork error!");  
        	exit(1);  
        }
        if (pid>0) 		// 1.2父进程退出  
        	exit(0);  
        
       /*打开系统日志服务连接*/
       openlog("daemon_syslog", LOG_PID, LOG_DAEMON);
        
       if ((sid = setsid()) < 0 ) 		// 2.在子进程中创建新会话。
       {
           syslog(LOG_ERR, "%s\n", "setsid");
           exit(1);
       }
        
       if ((sid = chdir("/")) < 0) 	// 3.设置工作目录为根目录  
       {
           syslog(LOG_ERR, "%s\n", "chdir");
           exit(1);
       }
        
        umask(0); 		// 4.设置权限掩码  
        
        for(i=0; i<getdtablesize(); i++) //5.关闭用不到的文件描述符  
        	close(i);
        
        //6.守护进程功能实现
        while(1) 			// 死循环表征它将一直运行
        {
            if((fd = open("/var/log/daemon.log",\
                          O_CREAT|O_WRONLY|O_APPEND,0600)) < 0)
        	{
                syslog(LOG_ERR, "open");
                exit(1);  
        	}  
        	write(fd, buf, len);  // 将buf写到fd中  
        	close(fd);  
        	sleep(10);  
        }  
        closelog();
        exit(1);  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    3、利用现有程序伪装成守护进程

    • 意思就是希望某一个程序能够在当前会话被关闭后,照样能够运行,方法也很简单,就是利用nohup命令。

    • 例如,我想运行一个命令sleep 1000,正常情况下,该命令在终端执行后,如果关闭该终端,命令随之结束,而不会等到1000秒之后。

    • 通过执行:nohup sleep 1000 &命令后,查看sleep的运行情况如下:

      image-20221203214435510

    • 关闭当前会话窗口,再重新打开,执行ps axjf | grep sleep命令发现它任然在执行中。

      image-20221203214533220

  • 相关阅读:
    KingbaseES插件参考手册(2. 扩展插件概述)
    Java-Swing程序设计
    源码包部署
    计算机毕业设计Java微服务毕业论文管理系统(源码+系统+mysql数据库+Lw文档)
    自制操作系统番外:编程语言中变量是如何存储的
    可以 DIY 装修的商城系统,你也能拥有!
    redis主从同步
    vue 中使用 this 更新数据的一次大坑
    从零学习 InfiniBand-network架构(二) —— 属性与管理器
    java计算机毕业设计仁爱公益网站源码+数据库+系统+lw文档+mybatis+运行部署
  • 原文地址:https://blog.csdn.net/liangzc1124/article/details/128167071