Linux 作业控制、进程组和会话

作业控制 (Job Control)

作业控制是大多数 shell 提供的一个功能,允许用户在单个终端上同时运行和管理多个命令(作业)。

  • 前台作业 (Foreground Job): 在终端中启动,它会独占终端的输入和输出。在它运行期间,shell 会被挂起,等待该作业完成。

  • 后台作业 (Background Job): 通过在命令后添加 & 符号启动。它不会占用终端,你可以在它运行时继续在 shell 中输入其他命令。

  • Ctrl+Z(SIGTSTP)暂停前台进程Ctrl+C(SIGINT)终止当前前台进程Ctrl+D(EOF)

  • 常用命令: jobs, fg, bg, kill %<job_id>

进程组 (Process Group)

进程组是一个或多个进程的集合。系统中的每个进程都属于一个进程组。

  • 目的: 主要用于作业控制,方便将信号(如 SIGINT, SIGTSTP)发送到一组相关的进程。例如,当你在终端按下 Ctrl+C 时,SIGINT 信号会被发送到当前前台作业的整个进程组。

  • 进程组ID (PGID): 每个进程组都有一个唯一的 ID。

  • 进程组领导 (Process Group Leader): 进程组中第一个创建的进程,其 PID 通常就是该进程组的 PGID。

  • 一个管道(pipeline)中的所有进程(例如 cat file | grep "text" | wc -l)通常属于同一个进程组。

会话 (Session)

会话是一个或多个进程组的集合。它提供了一个更高层次的进程组织方式。

  • 目的: 将一个用户登录到退出期间创建的所有进程组织在一起。

  • 会话ID (SID): 每个会话都有一个唯一的 ID。

  • 会话领导 (Session Leader): 创建该会话的进程。通常,这是用户登录时启动的 shell 进程。

  • 控制终端 (Controlling Terminal): 一个会话通常与一个控制终端相关联。当控制终端断开连接时(例如关闭终端窗口或网络断开),内核会向会话领导发送 SIGHUP 信号,后者通常会将其传播给会话中的所有进程,导致它们终止。

setsid() 系统调用

setsid() 是一个关键的系统调用,用于创建一个新的会话。

  • 功能:

  1. 调用 setsid() 的进程会成为一个新会话的会话领导

  2. 该进程会成为一个新进程组的进程组领导

  3. 该进程会脱离它之前的控制终端。

  • 前提: 调用 setsid() 的进程不能是某个现有进程组的领导者。为了确保这一点,通常的做法是 fork() 一个子进程,然后让父进程退出,子进程再调用 setsid()

  • 用途: 这是创建守护进程(Daemon)的标准方法。通过创建一个没有控制终端的新会hs话,守护进程可以确保自己不会因为终端的关闭而意外终止。

lxc-attach 后台进程导致退出卡住的原因分析

问题场景

  1.  用户在终端中执行 lxc-attach <container_name>

  2.  在 lxc-attach 创建的 shell 中,启动一个后台作业。

  3.  输入 exit 尝试退出 lxc-attach 的 shell。

  4.  此时,终端卡住,用telnet 连接然后kill 掉才可以回到host 主机的shell

原因剖析

这个问题的核心在于进程关系和信号处理

  1.  进程结构:

    - 当你运行 lxc-attach 时,它会在容器内启动一个新的 shell 进程(如 bash)。

    - 这个新的 shell 是 lxc-attach 进程的子进程。

    - 重要的是,这个新 shell 没有成为新的会话领导。它与 lxc-attach 进程、以及你最初的登录 shell 位于同一个会话中,并共享同一个控制终端

  1.  启动后台进程:

    - 当你在 lxc-attach 的 shell 中运行一个后台进程。

    - 这个 后台进程与 lxc-attach 的 shell 属于同一个进程组

  1.  执行 exit:

    - 你输入 exitlxc-attach 的 shell 进程开始退出流程。

    - shell 进程本身会终止。

    - lxc-attach 进程在等待其子进程(即那个 shell)完全终止。

  1.  卡住的根源:

    - shell 进程虽然终止了,但它启动的后台进程仍然在运行。

    - 这个后台进程仍然是前台进程组的一部分(相对于控制终端而言),或者说它仍然与控制终端关联。

    - 控制终端的驱动程序会发现,虽然前台的 shell 退出了,但该进程组里还有其他进程 在运行。此时,终端会处于一种“挂起”或“等待”状态,因为它需要处理这个仍在运行的后台进程的标准输入/输出(即使它不读写)。

    - lxc-attach 进程本身也在等待,因为它可能需要清理与子进程相关的所有资源。只要 这个后台进程还在运行,整个进程链就无法干净地结束。

    - 只有当 这个后台进程结束后,整个进程组才算完全终结,控制终端的锁定状态被解除,lxc-attach 进程也随之退出,你才能回到原来的 shell 提示符。

  1. 解决办法: nohup setsid disown

        

参考链接 Linux session和进程组概述 - Linux程序员 - SegmentFault 思否

Linux TTY/PTS概述 - Linux程序员 - SegmentFault 思否

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计