Linux 作业管理

进程组/会话

简要概念:

  • 进程组:N(N>=1)个进程的集合,通常在同一作业中关联起来(通过管道)。进程组的ID(PGID)即为进程组组长的PID。进程必定且只能属于一个进程组,只有进程组中一个进程存在,进程组就存在,与组长进程终止与否无关。进程组的概念提出主要是为了进程管理与信号分发
  • 会话:N(N>=1)个进程组的集合,创建会话的进程叫会话首进程。会话ID即为会话首进程PID
  • 控制终端:如果会话有控制终端,建立与控制终端连接的会话首进程叫控制进程(通常就是Shell进程),当前与终端交互的进程组为前台进程组,其余进程组成为后台进程组
  • 无论合适输入终端的退出键,都会将退出信号发送到前台进程组的所有进程
  • 如果控制终端断开连接,则将挂掉信号(SIGHUP)发送至控制进程(会话首进程),SIGHUP信号默认将导致控制进程终止

例如,打开Bash,输入:
proc1 | proc2 &
proc3 | proc4 | proc5

进程关系如下图所示:

作业控制信号

  • SIGCHLD: 子进程终止
  • SIGTTIN: 后台进程组成员读控制终端
  • SIGTTOU: 后台进程组写控制终端
  • SIGCONT: 如果进程已停止,则使其继续运行(fg & bg)
  • SIGSTOP: 进程停止信号,不能被捕获或忽略
  • SIGTSTP: 交互式停止信号(Ctrl+Z)
  • SIGINT: 中断信号(Ctrl+C)
  • SIGQUIT: 退出信号(Ctrl+)

SIGCHILD信号在子进程终止或停止时向父进程发送,系统默认将忽略该信号,如果父进程希望知晓子进程状态变更,应捕获该信号。

对于SIGTTIN和SIGTTOU信号,在后台作业尝试读取控制终端时,终端驱动程序知道它是个后台作业,于是将向改进程发送SIGTTIN信号,该信号默认将导致进程被挂起(停止):

▶ /usr/local/opt/coreutils/libexec/gnubin/cat > file &                                                                                                                   
[1] 44978
[1]  + 44978 suspended (tty input)  /usr/local/opt/coreutils/libexec/gnubin/cat > file
▶ fg                                                                                                                                                                     
[1]  + 44978 continued  /usr/local/opt/coreutils/libexec/gnubin/cat > file
Hello World! // 重新获得终端 读取输入
[Ctrl+D] // 键入EOF

当后台作业尝试写终端时,默认情况下,后台作业的输出将成功输出到控制终端,但我们可以通过stty命令禁止后台作业向控制终端写,此时终端驱动程序向进程发送SIGTTOU信号:

▶ /usr/local/opt/coreutils/libexec/gnubin/cat file &
[1] 46166
Hello World!
[1]  + 46166 done       /usr/local/opt/coreutils/libexec/gnubin/cat file
▶ stty tostop  // 禁止后台作业向控制终端写
▶ /usr/local/opt/coreutils/libexec/gnubin/cat file & 
[1] 46290
[1]  + 46290 suspended (tty output)  /usr/local/opt/coreutils/libexec/gnubin/cat file
▶ fg
[1]  + 46290 continued  /usr/local/opt/coreutils/libexec/gnubin/cat file
Hello World!

注意,在MacOS X上,自带的cat程序有BUG,不是interrupt-safe的,在MacOS X上,尝试恢复cat程序的执行将得到cat: stdin: Interrupted system call错误,这篇文章和APUE 9.8节均提到了这个问题,因此我使用的是brew安装的GNU版本cat命令,安装方案参见这里

关于SIGTSTP和SIGSTOP的区别,前者通常由键盘产生,可被捕获,当通过Ctrl+Z将前台作业放入后台时,前台作业收到该信号,意思是”从哪儿来到哪儿去”。而SIGSTOP通常由kill产生,不可被捕获或忽略,意思是”在那里待着别动”。两者均可由SIGCONT信号恢复运行。

对于键盘输入产生的信号,控制进程将信号发送至前台进程组的所有进程。

作业控制信号间有某些交互,当对一个进程产生四种停止信号(SIGTSTP,SIGSTOP,SIGTTIN,SIGTTOU)中的一种时,对该进程的任意未决SIGCONT信号将被丢弃,同样,当产生SIGCONT信号时,对同一进程的任意停止信号将被丢弃。

作业管理

  • & 将作业放入后台执行,如果没有进行重定向,数据流仍然会输出到前台
  • Ctrl+C 强制中断前台当前作业执行
  • Ctrl+Z 将作业挂到后台
  • jobs -l 查看所有作业,作业ID和其PID
  • fg %作业ID 将后台作业拿到前台来处理
  • bg %作业ID 将后台作业由挂起变为执行
  • kill -signal %作业ID 向指定作业的所有进程发送信号

作业管理的后台不是系统后台,因此,上述的任务管理依旧与终端有关,当远程连接的终端断开连接时,SIGHUP信号默认将导致改会话上所有的任务都会被中断。

脱机管理

  • nohup: nohup CMD & 将任务放在后台执行,并忽略SIGHUP挂掉信号,但是在人机交互上比较麻烦
  • screen: 一个可以在多个进程之间多路复用一个物理终端的窗口管理器,在远端服务器上运行screen,开启一个新会话并执行任务,在终端断开后,任务继续执行,下次登录再attach上screen会话即可,Linux发行版自带
  • tmux: 功能类似于screen,但在分屏切换,配置方面更强大,完全可作为本地终端使用