Linux守护进程

本文介绍了Linux守护进程,它们是独立于终端的长期运行进程。守护进程的特点包括没有控制终端,通常自成会话和进程组。创建用户层守护进程涉及调用umask、fork、setsid等函数,其中setsid用于切断与控制终端的联系。通过fork两次的方法可以确保守护进程不成为会话首进程,避免打开控制终端。文章还探讨了fork一次与fork两次的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先,我们在Linux的shell下面运行一条指令ps -ajx,其中 ps是查看当前系统进程状态的指令,-a显示由其它用户所拥有的进程的状态,-x显示没有控制终端的进程的状态,-j显示与作业相关的信息。
(守护进程1)

我们在图中看到,图中所列举的这些都是与控制终端无关的进程,在Linux中我们称这些进程为守护进程,也叫做精灵进程,今天我们就来谈一谈Linux下面的守护进程把。


什么是守护进程?

守护进程也被称为精灵进程,是运行在后台的一种特殊的进程,它独立于终端并且周期性的执行某种任务。他们的生期很长,在系统引导装入时启动,仅在系统关闭时才停止(注意这里是停止,而不是终止)。


守护进程的特征

如上图,我们发现所有的守护进程首先他们是没有控制终端的,即他们独立于终端(他们的终端名被设置为?)。其中内核守护进程以控制终端的方式启动,用户层守护进程缺少控制终端是因为他们调用了setsid函数的结果;后面我们将会提到。许多用户层的守护进程都是进程组的组长进程,以及会话的首进程,即他们自成进程组,自成会话。,最后所有用户层守护进程的父进程都是init进程。

怎样创建一个用户层的守护进程

创建一个用户层守护进程的步骤如下:
1. 首先,调用umask函数设置文件权限,这是因为守护进程可能会创建某些文件,所以我们要设定特定的权限。
2. 调用fork函数,然后让父进程被终止;这样做的原因是因为下面我们要调用setsid函数创建会话,因为setsid函数不能被组长进程调用,而如果我们只创建一个进程,然后再让这个进程创建子进程,那么父进程一定是组长进程,让父进程终止的原因是因为有一条规则叫做孤儿进程的领养机制,这样我们就能保证子进程的父进程为1号进程了。
3. 调用setsid函数创建一个新的会话;下面我们先来看看setsid函数把:

       #include <unistd.h>

       pid_t setsid(void);

这个函数的功能主要有下面几点:

  • 调用该函数的当前进程成为所创建会话的首进程,此外,当前进程时会话中的唯一进程。
  • 该进程创建了一个新的进程组,并成为组长进程。
  • 调用完该函数后,该进程就没有控制终端了,因为setsid函数会切断当前进程与其控制终端的联系,使其独立于终端。
  • 还有一点是,如果调用进程已经是一个进程组的组长,那么该函数会出错。

    在了解了这个函数后,我们发现,这个函数是创建守护进程的关键的步骤,因为守护进程没有控制终端,守护进程自成会话和进程组。

    1. 因为守护进程的工作目录大部分都在跟目录,所以我们通过chdir函数,将守护进程的工作目录设置为根目录。
    2. 下面我们关闭文件描述符,因为在守护进程中这些文件描述符是多余的,如果不关闭让守护进程占着的话是一种对资源的浪费。
    3. 忽略SIGCHLD信号,守护进程默认也是这样做的。

下面,基于这些步骤,我们来编写自己的守护进程把:

/*************************************************************************
    > File Name: mydemon.c
    > Author: LZH
    > Mail: 597995302@qq.com 
    > Created Time: Fri 24 Feb 2017 11:33:38 PM PST
 ************************************************************************/

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

#define PATH "/"          //创建的守护进程的工作目录
void mydemond()
{
  umask(0);
  pid_t pid=fork(); 
  if(pid > 0){
      exit(0);                    //这里让父进程终止,然后子进程调用setsid函数创建会话
  }
  pid_t sid=setsid();
  if(sid==-1){
      perror("setsid...");
      exit(1);
  }

  if(chdir(PATH)!=0)
  {
    perror("chdir..."); 
    exit(1);    
  }
  close(0);
  close(1);
  close(2);
  signal(SIGCHLD,SIG_IGN);
}

int main()
{
    mydemond();
    while(1){
        sleep(1);
    }
    return 0;
}


----------

下面我们来运行一下守护进程,查看结果:
(守护进程2)

下面我们在注销掉当前终端,看看守护进程的状态。
(守护进程3)


创建守护进程fork两次

下面我们来换一种思路,我们在创建守护进程时fork两次来看看会有什么结果!

/*************************************************************************
    > File Name: mydemon.c
    > Author: LZH
    > Mail: 597995302@qq.com 
    > Created Time: Fri 24 Feb 2017 11:33:38 PM PST
 ************************************************************************/

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

#define PATH "/"
void mydemond()
{
  umask(0);
  pid_t pid=fork();
  if(pid > 0){
      exit(0);
  }
  else{
      //fork once child
     pid_t sid=setsid();
     if(sid==-1){
      perror("setsid...");
      exit(1);
      }
      pid_t newpid=fork();
      if(newpid > 0){
          //fork twice father
          exit(0);
      }

  if(chdir(PATH)!=0)
  {
    perror("chdir..."); 
    exit(1);    
  }
  close(0);
  close(1);
  close(2);
  signal(SIGCHLD,SIG_IGN);
  }
}

int main()
{
    mydemond();
    while(1){
        sleep(1);
    }
    return 0;
}

下面我们运行后来看看fork两次的守护进程的状态。
(守护进程4)

此时,守护进程已经不是自成会话和进程组了,那我们为什么要这样做呢?


fork一次与fork两次的区别

前面我们fork一次,是为了保证调用setsid函数的进程不是组长进程(因为setsid函数不允许组长进程调用),这时,我们创建出来的守护进程虽然独立于控制终端,但是由于它自成进程组,自成会话。所以作为一个会话首进程,它拥有打开一个控制终端的权利,如果他打开了一个控制终端,那么它就和守护进程独立于控制终端相互矛盾了,所以就有了fork两次。
fork两次保证了第二次fork出来的孙子进程不是会话的首进程,所以这时我们在终止掉儿子进程,此时会话首进程被终止,所以这个会话中的其他进程就没有打开控制终端的权利了。


下面我们来看看fork两次的步骤

这里写图片描述

### Linux 守护进程的创建与管理 #### 什么是守护进程守护进程是一种特殊的后台进程,在系统启动时自动运行,并在系统关闭时终止。它们不依赖于任何特定用户的交互操作,主要用于提供服务功能,例如 SSH 服务、网络管理服务等[^1]。 #### 守护进程的特点 - **长期运行**:守护进程通常会在整个系统生命周期内持续运行。 - **无控制终端**:这些进程脱离了父进程以及控制终端的影响,因此即使用户退出登录也不会影响其正常工作。 - **独立性强**:作为后台任务执行,不会干扰前台其他应用程序的操作[^3]。 #### 创建守护进程的主要步骤 以下是构建一个标准守护进程的一般过程: 1. **fork() 子进程并让父进程退出** 这一步是为了使新生成的子进程不再受原始 shell 的信号影响。当调用了 `exit(0)` 后,原父进程结束掉自己,从而使得当前正在运行的新 fork 出来的子进程成为孤儿状态,由 init (PID=1) 接管[^4]。 2. **设置新的会话 ID 和组长身份** 调用 `setsid()` 方法来建立一个新的 session 并将其设为主领头者(session leader),这样可以确保该进程没有关联到任何一个 tty 设备上[^5]。 3. **改变当前的工作目录至根路径 ("/") 或指定位置** 防止占用挂载点导致无法卸载磁盘分区等问题发生。通过更改 chdir("/") 来解决这个问题。 4. **重置文件模式掩码 umask** 使用函数 `umask(0)` 将默认权限屏蔽字清零以便能够自由定义所创建资源的具体访问属性。 5. **关闭不需要的标准输入/输出流描述符** 关闭 stdin, stdout 及 stderr 文件句柄以避免不必要的 I/O 行为干扰实际业务逻辑处理。 6. **重新打开日志记录设备或其他必要的管道连接** 如果需要的话,则应该在此阶段初始化 syslog 或类似的机制用于跟踪调试信息或者错误报告用途。 7. **最后再次 fork 新一代子线程再杀死旧版实例** 此举进一步增强了安全性,因为此时已经完全摆脱了所有可能存在的外部环境变量绑定关系。 8. **完成上述准备工作之后就可以正式进入主循环体开始履行职责啦!** 下面给出一段简单的 Python 实现代码示例展示如何按照以上原则编写自己的 daemon service: ```python import os import sys import time def create_daemon(): try: pid = os.fork() if pid > 0: # Parent process exits after creating the child. sys.exit(0) except OSError as e: print(f"Fork failed: {e}", file=sys.stderr) sys.exit(1) # Decouple from parent environment. os.chdir("/") os.setsid() os.umask(0) # Second fork to ensure that we are not a session leader. try: pid = os.fork() if pid > 0: sys.exit(0) except OSError as e: print(f"Second fork failed: {e}", file=sys.stderr) sys.exit(1) if __name__ == "__main__": create_daemon() while True: with open("/tmp/daemon.log", "a+") as f: f.write("Daemon is running...\n") time.sleep(10) ``` 此脚本展示了基本框架结构,具体应用还需根据需求调整细节部分。 #### 管理守护进程的方法 对于已有的守护进程可以通过 systemctl 命令来进行启停控管活动: - 启动某个服务:`systemctl start <service_name>` - 停止某个服务:`systemctl stop <service_name>` - 查看服务的状态:`systemctl status <service_name>` 另外还可以利用 crontab 计划定时任务配合 nohup 工具达到相同效果。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值