【Linux系统】任务管理与守护进程 {进程组;作业;会话;守护进程的概念及工作原理;守护进程的创建;注意事项}

一、任务管理

1.1 进程组

进程组(Process Group)是Linux操作系统中多个进程的集合,这些进程通常因为执行同一任务或共享相同目的而被组织在一起。进程组允许系统对这些进程进行统一的管理和操作,尤其是在信号处理和作业控制方面。

每个进程组都有一个唯一的标识符,称为进程组ID(Process Group ID,简称PGID)。当一个新进程被创建时,它默认继承其父进程的进程组ID,除非特别指定加入另一个进程组。进程组ID可以通过系统调用(如setpgid())进行更改。

进程组在多个方面起着重要作用:

  1. 作业控制:在shell中,作业控制允许用户暂停、恢复或终止进程组。这对于管理在后台运行的程序特别有用。
  2. 信号处理:信号可以发送给单个进程、进程组或所有进程。通过将信号发送给进程组,可以同时影响多个进程的行为。例如:pkill -<signum> -g -<PGID>kill -<signum> -<PGID>向指定进程组中的所有进程发信号
  3. 终端管理:当终端接收到某些信号(如中断信号SIGINT)时,这些信号会被发送到与之关联的前台进程组的所有进程。这允许用户通过简单的操作(如按Ctrl+C)来中断前台作业。
  4. 系统资源分配:在某些情况下,系统可能会根据进程组来分配资源或应用限制。尽管这种情况不如信号处理和作业控制那样普遍,但进程组提供了一种组织进程的灵活方式。

总之,进程组是Linux系统中用于组织和控制进程的一个重要概念。它们允许系统对一组相关的进程进行统一的管理和操作,从而提高系统的效率和响应能力。

  • 父子,兄弟进程属于同一个进程组,父进程是组长进程,PGID和父进程的PID相同。
  • 在命令行中使用管道级联的多个进程是兄弟关系,父进程都是bash,可以使用匿名管道进行通信,这些使用管道级联的进程同属于一个进程组,进程组id是第一个进程的pid(组长)。
  • bash单独属于一个进程组,其pgid, 就是bash进程的pid。
  • 需要注意的是,只要在某个进程组中有一个进程存在,则该进程组就存在,这与其组长进程是否终止无关。父进程结束与其PID相同的PGID进程组仍然存在。

1.2 作业

在Linux中,作业(Job) 是shell(如bash、zsh等)用于管理和控制进程组的一个概念。当你在shell中启动一个或多个进程时,这些进程可以被视为一个作业。作业允许用户将多个进程视为一个单元进行管理和控制,比如挂起(暂停)、恢复或终止整个作业。

作业通常与后台进程和前台进程相关联。当你在shell中直接输入一个命令并回车时,该命令通常会在前台执行,这意味着它会占用终端,直到它完成执行。然而,你可以通过在命令的末尾添加&符号来将其放入后台执行,这样shell会立即返回命令提示符,而命令则会在后台继续执行。后台执行的命令(及其相关的进程)被视为一个作业。

前台作业和后台作业

在Linux系统中,前台作业和后台作业是作业管理的重要概念,它们主要区别在于与终端的交互方式以及执行方式。

前台作业

前台作业是指通过终端启动,并且在启动后一直占据该终端的作业。用户可以直接与前台作业进行交互,如输入数据或接收输出。

特点

  • 需要用户持续关注,因为输出会直接显示在终端上。
  • 用户可以通过键盘输入来控制前台作业的执行,如中断(Ctrl+C)或暂停(Ctrl+Z)。
  • 前台作业在执行时会阻塞终端,直到作业完成或用户将其放入后台。
  • 只允许有一个前台作业

后台作业

后台作业是指启动时与终端无关,或者通过终端启动后转入后台运行的作业。后台作业释放了终端,允许用户继续在终端中执行其他命令或操作。

特点

  • 不需要用户持续关注,因为输出不会直接显示在终端上(除非特别指定)。
  • 用户可以通过jobs命令查看后台作业的状态,使用fg命令将后台作业调回前台,或使用bg命令让后台作业继续在后台执行。
  • 后台作业虽然与启动它的终端相关联,但如果终端关闭,后台作业通常会随之终止(除非使用了nohupscreen等工具)。
  • 可以有多个后台作业

如何将前台作业转为后台作业

  1. 使用Ctrl+Z组合键

    正在运行中的前台作业可以使用Ctrl+Z组合键将其调至后台并停止运行。此时,作业处于暂停状态,可以通过bg命令继续在后台执行。

  2. 在命令后添加&字符

    对于尚未启动的作业,可以直接在命令后面添加&字符,使作业在后台启动并执行。

  3. 使用nohup命令

    nohup命令可以将程序以忽略挂起信号的方式运行起来,即使终端关闭,作业也会继续执行。使用nohup时,通常会将输出重定向到文件中,以避免干扰终端。

作业控制

shell使用作业控制来管理前台和后台作业。作业控制允许用户通过shell命令(如fgbgjobskill等)来暂停、恢复、查看或终止作业。例如,fg命令可以将后台作业带到前台,而bg命令可以让暂停的作业在后台继续执行。

每个作业都有一个唯一的作业编号,这个编号在你使用作业控制命令时非常有用。你可以通过jobs命令来查看当前shell会话中所有作业的列表,包括它们的作业编号、状态(运行中、已停止等)和命令名。

  • jobs:查看当前终端中运行的作业列表,加-l选项可以展开作业中的所有进程附带PID
  • fg:将后台作业切换到前台运行。
  • bg:让作业在后台继续运行。
  • kill %jobid:终止指定作业的运行,也可以将指定的信号发送给作业中的所有进程

需要注意的是,作业和进程组之间有着密切的关系。通常,一个作业中的所有进程都属于同一个进程组,而作业控制命令(如fgbg)实际上是通过向该进程组的所有进程发送信号来实现其功能的。因此,进程组是作业控制的基础,而作业是shell提供的一种高级抽象,用于更方便地管理和控制进程组


1.3 会话

Linux会话是指用户在Linux操作系统中与系统进行交互的一段时间或过程。会话是用户登录到系统后启动的进程集合,每个会话都有一个唯一的会话ID(SID)用于标识。

  • 系统会为每一次登录创建一个会话,为登录用户提供服务的进程或进程组,或是用户自己启动的进程或进程组,都同属于一个会话。
  • bash是新会话中的第一个进程,因此会话id就是bash进程的pid,在命令行上启动的所有进程都属于该会话。
  • 当用户退出登录时,当前会话就会被释放,会话中启动的所有进程及服务都会被关闭。因此想要创建守护进程,就必须使其自成一个独立的会话。

会话的组成

  • 进程组:每个会话包含一个或多个进程组,最多只能有一个前台进程组(前台作业),其他的都是后台进程组。
  • 会话首领:每个会话都有一个会话首领(leader),即创建会话的进程。会话首领进程的进程ID(PID)即为该会话的SID。如每次登录到系统后,bash进程就是新会话的首领。
  • 控制终端:一个会话可以有控制终端,也可没有。控制终端是用户与会话进行交互的接口,如输入命令、查看输出等。每个会话最多只能连接一个控制终端。

会话的类型

  • 前台会话:用户正在操作的会话,用户可以通过命令行终端或图形界面与前台会话进行交互。
  • 后台会话:用户在执行某些任务时创建的会话,这些任务在后台运行,用户可以在后台会话执行任务的同时继续进行其他操作。

会话的创建

  • 创建会话:在Linux中,可以通过命令行终端、SSH远程登录等方式来创建会话。此外,还可以使用setsid函数或nohup命令来创建一个新的会话,该会话将脱离控制终端,成为一个独立的会话。

二、守护进程

2.1 概念及工作原理

守护进程是一类在后台运行的特殊进程,用于执行特定的系统任务。它们独立于控制终端,并且周期性地执行某种任务或等待处理某些发生的事件。

守护进程具有以下特点:

  • 后台运行:守护进程在后台运行,不与任何终端或控制台交互。
  • 持续运行:守护进程通常在系统启动时开始运行,并持续运行直到被停止或系统关闭。
  • 无控制终端:守护进程不与任何终端相关联,它们的标准输入、输出和错误通常被重定向到/dev/null或日志文件。
  • 低优先级:守护进程通常具有较低的进程优先级,以避免影响前台进程的性能。

提示:守护进程是一个独立的、无控制终端的、后台会话。

工作原理

  • 启动方式:守护进程通常在系统启动时通过系统的初始化脚本(如Systemd、System V init、Upstart等)自动启动,也可以手动启动。
  • 循环监听:守护进程在一个或多个循环中监听特定的事件,如网络端口、硬件中断或特定的文件。
  • 事件处理:当守护进程检测到事件时,它会执行相应的处理程序来响应事件。
  • 日志记录:守护进程将它们的行为记录在日志文件中,这些日志文件通常位于/var/log/目录下。

常见守护进程

Linux系统中存在许多常见的守护进程,它们负责执行各种关键的系统任务。以下是一些常见的守护进程示例:

  • sshd:提供SSH服务,允许远程登录和管理。
  • nginx或apache2:提供Web服务器功能。
  • cron:定时任务守护进程,执行周期性任务。
  • syslog或systemd-journald:系统日志服务,记录系统日志。
  • ntpd或chronyd:网络时间同步服务。
  • DHCP和DNS服务:如isc-dhcp-server和bind9,提供网络配置和域名解析服务。

2.2 守护进程的创建

创建步骤:

  1. 忽略异常信号:防止服务器异常终止,如SIGPIPE, SIGSTP, SIGCHLD
  2. 创建子进程:使用fork()系统调用创建一个子进程,父进程退出(组长不能创建新会话),子进程继续向后执行。
  3. 创建新会话(核心):使用setsid()系统调用使子进程脱离终端,成为一个新的会话组长。
  4. 关闭文件描述符:关闭不需要的文件描述符,以防止守护进程与终端或其他进程的关联。可以使用close()系统调用来关闭不需要的文件描述符。
  5. 重定向标准输入输出、错误:将标准输入、输出和错误重定向到/dev/null或日志文件中。可以使用dup2()系统调用来重定向文件描述符。
  6. 设置工作目录(可选):将工作目录切换到根目录,以防止守护进程运行时影响其他目录。可以使用chdir()系统调用来切换工作目录。
  7. 执行任务:执行守护进程的主要任务。

提示:一般守护进程的程序名末尾都代d,如sshd。

#include <unistd.h>
#include <signal.h>
#include <cstdlib>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
 
#define DEV "/dev/null"
 
void daemon(const char *currPath = nullptr)
{
    // 1. 让调用进程忽略掉异常的信号
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);
    signal(SIGSTP, SIG_IGN);
 
    // 2. 创建子进程,父进程退出
    if (fork() > 0)
        exit(0);
    // 子进程 -- 守护进程,本质就是孤儿进程的一种!
    
    // 3.创建新会话脱离终端:使用setsid()系统调用使子进程脱离终端,成为一个新的会话组长。
    pid_t n = setsid(); //返回新的SID,也是当前进程的PID,PGID
    assert(n != -1);
 
    // 4. 关闭文件描述符 或 重定向标准输入输出、错误
    int fd = open(DEV, O_RDWR);
    if (fd >= 0)
    {
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
        close(fd);
    }
    else
    {
        close(0);
        close(1);
        close(2);
    }
 
    // 5. 可选:进程执行路径发生更改
    if (currPath)
        chdir(currPath);
}

/dev/null

/dev/null是Linux操作系统中的一个特殊文件,它会丢弃所有写入它的数据,并在从中读取时返回文件结束条件EOF。它通常被用作丢弃不需要的输出或测试程序在遇到写入错误时的行为。/dev/null 被形象地称为文件黑洞

setsid函数

函数功能

  • 创建新会话:调用setsid函数的进程会成为新的会话领头进程,并与其父进程的会话组和进程组脱离。
  • 脱离控制终端:由于会话对控制终端的独占性,调用setsid函数的进程会同时与控制终端脱离。

函数原型

#include <unistd.h>  
pid_t setsid(void);

返回值

  • 成功时,setsid返回新的会话ID,即将调用该函数的进程的PID,作为新会话的SID,和新进程组的PGID。
  • 失败时,返回-1,并设置errno以指示错误原因。

使用注意事项

  1. 进程组长的限制:当进程已经是进程组组长时,调用setsid会失败并返回-1。因此,在调用setsid之前,通常需要先调用fork()创建一个新的子进程,并在子进程中调用setsid
  2. 文件描述符和工作目录:调用setsid后,进程可能需要关闭不必要的文件描述符,并更改其工作目录到如/tmp等安全位置,以避免占用不必要的系统资源。
  3. 信号处理:守护进程通常需要处理SIGCHLD信号,以避免产生僵尸进程(zombie process)。

daemon函数

在Linux中,daemon函数并不是C标准库的一部分,而是特定于某些Unix系统和发行版的传统函数,如BSD及其衍生系统。这个函数的主要用途是将一个程序转换成守护进程(daemon)。

daemon函数的典型原型如下:

#include <unistd.h>  
int daemon(int nochdir, int noclose);
  • nochdir参数:当设置为0时,daemon函数会将当前工作目录更改为根目录(“/”),以避免当前目录被卸载。如果设置为非零值,则不会更改工作目录。
  • noclose参数:当设置为0时,daemon函数会关闭所有打开的文件描述符(除了标准输入、输出和错误),并将这些标准I/O流重定向到/dev/null。如果设置为非零值,则不会关闭文件描述符。

提示:daemon函数的工作原理其实就是我们上面讲的守护进程的创建步骤

注意事项

  • 由于daemon函数的行为可能因系统而异,建议查阅特定系统的文档以了解确切的行为和要求。
  • 在某些Linux系统中,可能不提供daemon函数,或者其行为可能略有不同。因此,在某些情况下,可能需要手动编写代码来实现daemon函数的功能。
  • 编写守护进程时,还需要考虑其他因素,如错误处理、日志记录、信号处理等。

总之,daemon函数是Linux中用于创建守护进程的一个有用工具,它简化了将程序转换成守护进程的过程。然而,在使用时需要注意其特定系统的实现细节和限制。


2.3 注意事项

  • 安全性:守护进程可能成为安全漏洞的来源,因此需要确保它们的配置安全,及时应用安全更新。
  • 资源使用:守护进程应设计为轻量级和高效的,以避免过度消耗系统资源。
  • 错误处理:守护进程应能够妥善处理错误和异常情况,避免崩溃或产生不稳定的行为。

综上所述,Linux守护进程在系统中扮演着关键角色,为系统提供了许多自动化和无人值守的服务。了解守护进程的工作原理和管理方式对于维护系统的稳定性和安全性至关重要。

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芥末虾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值