深入理解守护进程:守护进程干嘛的?

一、前言

在计算机科学中,守护进程(Daemon)是一类在后台运行的系统服务进程,而不是在交互式用户界面下运行的进程。它们通常在系统启动时启动,并持续运行以提供某种服务或执行特定任务。守护进程独立于任何终端会话,通常不接受用户输入,而是通过系统的事件驱动或定时器来执行任务。

Linux系统有很多守护进程,大多数服务都是用守护进程实现的。例如常见的常见的守护进程包括系统日志进程syslogdweb服务器httpd、邮件服务器sendmail和数据库服务器mysqld等。

二、进程与终端的关系

要想了解守护进程的作用,我们首先要搞懂进程与终端是什么关系。在计算机中,终端是用户与计算机系统交互的主要界面,而进程是计算机系统中正在运行的程序的实例。

当用户在终端中启动一个程序时,该程序会作为一个进程开始运行。在这个过程中,终端会为该进程分配一个唯一的进程ID,并将该进程与终端会话相关联。此外,在程序执行过程中,终端还可以向进程输入数据或从进程输出数据,以实现与进程的交互。

当用户关闭终端时,所有与该终端相关联的进程都会被终止。这种情况下,进程与终端的关系就被中断了。不过,有些进程需要在后台持续运行,这时需要将进程与终端分离,使其独立于终端会话,从而实现在后台持续运行的目的。

终端为进程提供了用户交互和输入输出的接口,同时进程也可以通过终端来获取用户输入和向用户输出信息。但有时为了实现进程在后台持续运行的目的,也需要将进程与终端分离。

守护进程能够突破这种限制,它从开始运行,直到整个系统关闭才会退出。如果想让某个进程不会因为用户或终端的变化而受到影响,就必须把这个进程变成一个守护进程。正是因为我们有需要后台持续运行,不用用户交互的需求,所以才有守护进程的出现,下面我们就来详细了解一下守护进程。

三、守护进程是什么?

守护进程(Daemon)是在计算机系统中以后台方式运行的特殊进程。守护进程通常在系统引导时启动,并一直在后台运行,直到系统关闭或显式终止。与一般的用户进程不同,守护进程不依赖于特定的终端或用户会话,它独立于终端控制并在系统级别执行特定的任务。

守护进程常用于执行系统服务任务,如网络服务、打印服务、日志记录等。它们能够以长期运行的方式提供服务,而不受用户登录、注销或关闭终端的影响。守护进程通常在系统启动时自动启动,并以系统管理员的权限运行。

通过在后台运行,守护进程能够周期性地检查和响应某些事件或请求,以满足系统的各种需求。它们能够管理系统资源、提供持久性的服务,并且在需要时可以被其他进程调用或进行交互。

可以把守护进程是一类在后台运行、独立于终端的特殊进程,用于执行系统级别的任务和服务。它们为系统提供稳定性、持久性和自动化功能,是Linux系统中广泛使用的重要组成部分。

四、如何查看守护进程

通过运行命令ps axj,可以查看系统中的所有进程。其中,参数a指定要列出所有用户的进程,而不仅仅是当前用户的进程;参数x包括列出所有无控制终端的进程,而不仅仅是有控制终端的进程;参数j则能够提供与作业控制相关的信息。

这个命令的输出包含了许多字段,包括PID、PPID、PGID、SID、TTY、STAT、TIME、COMMAND等。其中,PID是进程的唯一标识符;PPID是父进程的PID;PGID是进程所属的进程组ID;SID是进程所属的会话ID;TTY是进程所使用的终端设备;STAT是进程状态;TIME是进程运行的时间;COMMAND是进程正在执行的命令。
在这里插入图片描述

由于参数j还提供了作业控制相关的信息,因此此命令的输出还包括作业ID(JID)、进程对应的作业名(JOBNAME)以及作业的状态(STATE)等信息。这些信息对于理解进程和作业之间的关系以及系统运行情况非常有用。

  • 凡是TPGID一栏写着 -1 的都是没有控制终端的进程,也就是守护进程;
  • 在COMMAND一列用[ ]括起来的名字表示内核线程

守护进程的特点

  1. 在Linux中,每个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端被称为这些进程的控制终端;

  2. 当控制终端被关闭的时候,相应的进程都会自动关闭。但是守护进程却能突破这种限制,它脱离于终端并且在后台运行,(脱离终端的目的是为了避免进程在运行的过程中的信息在任何终端中显示并且进程也不会被任何终端所产生的终端信息所打断),它从被执行的时候开始运转,直到整个系统关闭才退出(当然可以认为是杀死相应的守护进程);

  3. 如果想让某个进程不因为用户或中断或其他变化而影响,那么就必须把这个进程变成一个守护进程。

五、进程,会话,控制终端之间的关系

在Linux系统中,进程是执行任务的基本单位。每个进程都有一个唯一的进程ID(PID),并可以包含多个线程。多个进程可以归属于同一个进程组,一个进程组中可以包含多个进程。

每个进程组都有一个进程组ID(PGID),由进程组中的某个进程(通常是第一个进程)的PID表示。进程组的主要作用是统一管理进程,方便对整个组进行操作和控制。

进程组可以归属于一个会话(Session),每个会话都有一个唯一的会话ID(SID)。每个会话可以包含多个进程组,一般情况下,一个会话只有一个控制终端,该终端与会话中的所有进程组和进程建立关联。

控制终端是用户与系统进行交互的界面,例如终端窗口、TTY等。当用户打开一个终端并登录时,终端会成为一个会话的控制终端,此时会话中的所有进程都与该终端建立关联。

进程、进程组、会话和控制终端之间存在着以下关系:每个进程都属于一个进程组,进程组又可以属于一个会话,会话与控制终端建立关联。这种关系可以使得用户通过控制终端对会话中的多个进程进行操作和控制。

六、创建守护进程

创建守护进程的过程可以分为以下几步:

  1. 创建子进程:使用fork()函数创建一个子进程,父进程退出,使子进程成为孤儿进程,接下来将子进程设置为守护进程。

  2. 创建新会话:该子进程调用setsid()函数创建一个新的会话,使子进程成为新会话的会话领头进程。

  3. 更改工作目录:为了避免守护进程与其他程序在同一目录下运行造成混淆,需要将当前工作目录切换到根目录下。

  4. 关闭文件描述符:子进程继承了父进程打开的文件描述符,而这些文件描述符可能会影响守护进程的正常运行,因此需要关闭这些文件描述符,只保留标准输入、输出和错误输出文件描述符。

  5. 锁定文件:为了避免多个守护进程同时运行相同的任务,需要锁定某个文件,以保证只有一个守护进程运行某个任务。

用C语言表达出上面的过程如下:

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

void create_daemon() {
    // 创建子进程
    pid_t pid = fork();
    
    if (pid < 0) {
        perror("fork error");
        exit(1);
    } else if (pid > 0) {
        // 父进程退出,使子进程成为孤儿进程
        exit(0);
    }
    
    // 创建新会话
    if (setsid() < 0) {
        perror("setsid error");
        exit(1);
    }
    
    // 更改工作目录
    if (chdir("/") < 0) {
        perror("chdir error");
        exit(1);
    }
    
    // 关闭文件描述符
    int fd = open("/dev/null", O_RDWR);
    if (fd < 0) {
        perror("open error");
        exit(1);
    }
    
    if (dup2(fd, STDIN_FILENO) < 0) {
        perror("dup2 stdin error");
        exit(1);
    }
    
    if (dup2(fd, STDOUT_FILENO) < 0) {
        perror("dup2 stdout error");
        exit(1);
    }
    
    if (dup2(fd, STDERR_FILENO) < 0) {
        perror("dup2 stderr error");
        exit(1);
    }
    
    if (fd > STDERR_FILENO) {
        if (close(fd) < 0) {
            perror("close error");
            exit(1);
        }
    }
    
    // 锁定文件
    int lockfd = open("/var/run/daemon.lock", O_RDWR|O_CREAT, 0644);
    if (lockfd < 0) {
        perror("open lock file error");
        exit(1);
    }
    
    if (flock(lockfd, LOCK_EX|LOCK_NB) < 0) {
        printf("Another daemon is already running.\n");
        exit(1);
    }
    
    // 守护进程的具体逻辑
    // ...
}

int main() {
    create_daemon();
    
    // 守护进程的具体逻辑
    // ...
    
    return 0;
}

上面代码中,首先使用fork()函数创建一个子进程,父进程退出,使子进程成为孤儿进程。然后调用setsid()函数创建一个新的会话,使子进程成为新会话的会话领头进程。接着使用chdir()函数将当前工作目录切换到根目录下。然后打开/dev/null设备文件,将标准输入、输出和错误输出重定向到该文件描述符,并关闭其他文件描述符。最后,打开一个用于锁定的文件,在获取锁之前检查是否有其他守护进程正在运行相同的任务。

以上就是使用C语言创建一个守护进程的简单框架,当然实际的守护进程的编写还会有很多的处理来保证守护进程的创建能够成功。

七、如何杀死守护进程

要想杀死一个守护进程其实和杀死一个普通的进程步骤差不多,首先使用 ps axj | grep 守护进程名字,找到相应的守护进程,得到进程PID之后可以使用以下方式将其杀死:

  1. 使用kill命令:可以使用kill命令向进程发送信号来终止它。首先,使用ps命令或其他类似的命令找到守护进程的进程ID(PID)。然后,使用kill命令加上PID,发送SIGTERM信号(默认为15)给守护进程。例如,如果守护进程的PID是12345,可以使用以下命令终止它:
kill 12345
  1. 使用killall命令:如果你知道守护进程的名称,也可以使用killall命令终止它。这个命令会发送SIGTERM信号给所有与给定名称匹配的进程。例如,如果守护进程的名称是mydaemon,可以使用以下命令终止它:
killall mydaemon
  1. 使用pkill命令:pkill命令与killall命令类似,可以根据进程的名称终止进程。但是,pkill命令更灵活,支持使用更多的选项和模式匹配。例如,可以使用以下命令终止名称中包含"daemon"的所有进程:
pkill -f daemon

但是需要注意的是在终止守护进程之前,请确保你有合适的权限,并确保没有其他重要的进程也使用相同的名称。此外,如果守护进程在终止之前需要做一些清理工作,你可能需要发送其他信号,比如SIGINT(2)或SIGKILL(9),具体取决于守护进程的实现。

关于linux系统创建守护进程的操作步骤就给大家讲解到这里了,在这方面有需要的,不妨按上述操作一下。

👇点击下方公众号卡片获取资料👇
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值