linux下如何自定义或编写一个守护进程

491 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第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 <stdio.h>  
    #include <stdlib.h>  
    #include <string.h>  
    #include <unistd.h>  
    #include <sys/wait.h>  
    #include <sys/types.h>  
    #include <fcntl.h>  
      
    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 1;
        }  
          
    	return 0;  
    }
    
    • 注意:因为守护进程关闭了所有文件描述符,包括默认的标准输入输出和出错,所以在终端上看不到其输出。
    • 运行该程序后,可以通过执行:cat /var/log/daemon.log查看进程运行情况;也可以通过ps -axjf | grep 程序名查看。
    • 关闭当前会话(终端),再次重复执行上步命令,查看进程是否还在运行!

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

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

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

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

    image-20221203214435510

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

    image-20221203214533220