nohup和setsid命令

-- TOC --

nohup和setsid命令,都可以用来创建不会因为用户logout而退出的进程,但他们背后的机制不一样。

什么是session?

Linux下的进程关系是一颗由父子关系构成的树,但仅有这颗树还不够。

比如当用户logout的时候,kernel需要将此用户启动的所有进程都kill掉(发送SIGHUP信号),为了简化这个事情,用户进程被组织成一个session。

The session's ID is the same as the pid of the process that created the session through the setsid() system call. That process is known as the session leader for that session group. All of that process's descendants are then members of that session unless they specifically remove themselves from it. The setsid() function does not take any arguments and returns the new session ID.

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

每一个session都绑定了一个controlling terminal。每一个terminal在同一时刻,只能是一个session的controlling terminal。

Every session is tied to a terminal from which processes in the session get their input and to which they send their output. That terminal may be the machine's local console, a terminal connected over a serial line, or a pseudo terminal that maps to an X window or across a network

在一个session中,用户可以通过Job Control来控制多个进程的执行,比如暂停,继续,(一个)前台(组),(一个或多个)后台(组)....

什么是进程组?

进程组的存在,也是为了方便进程管理。

进程组就是一系列相互关联的进程集合,系统中的每一个进程都必须属于某一个进程组。每个进程组中都会有一个唯一的 process group ID,简称 PGID。PGID 一般等同于进程组的创建进程的 Process ID,而这个进进程一般也会被称为进程组leader(process group leader)。进程组的存在,方便了系统对多个相关进程执行某些统一的操作,例如,我们可以一次性发送一个信号量给同一进程组中的所有进程。

当在shell命令行使用管道操作的时候,管道左右的命令进程,就属于同一个group。父进程和子进程默认同属一个组。

One of the original design goals of Unix was to construct a set of simple tools that could be used together in complex ways (through mechanisms like pipes)

直接向一个进程组发信号(在pgid前用一个-符号):

$ sudo kill -9 -<pgid>

管道进程组的示例:

$ cat | grep abc &
[1] 250841
[xinlin@fedora test]$ ps -o pid,ppid,pgid,session,comm
    PID    PPID    PGID    SESS COMMAND
 245770  245746  245770  245770 bash
 250840  245770  250840  245770 cat
 250841  245770  250840  245770 grep
 250842  245770  250842  245770 ps

[1]+  Stopped                 cat | grep --color=auto abc
$ kill -9 -250840
$ 
[1]   Killed                  cat | grep --color=auto abc

bash让cat和grep属于同一个进程组,id为250840。

以下是与进程组有关的系统调用:

int setpgid(pid_t pid, pid_t pgid);
pid_t getpgid(pid_t pid);
pid_t getpgrp(void);

进程组与Session是两个不同的概念,没有Session,也有进程组。

Another popular feature added to Unix fairly early was job control. Job control allows users to suspend the current task (known as the foreground task) while they go and do something else on their terminals. When the suspended task is a sequence of processes working together, the system needs to keep track of which processes should be suspended when the user wants to suspend "the" foreground task. Process groups allow the system to keep track of which processes are working together and hence should be managed together via job control.

以上这段解释了session的前台进程组和后台进程组。

孤儿进程组

When the session leader (the shell) exits, the process groups are left in a difficult situation. If they are actively running, they can no longer use stdin or stdout as the terminal has been closed. If they have been suspended, they will probably never run again as the user of that terminal cannot easily restart them, but never running means they will not terminate either.

In this situation, each process group is called an orphaned process group. POSIX defines this as a process group whose parent is also a member of that process group, or is not a member of that group's session. This is another way of saying that a process group is not orphaned as long as a process in that group has a parent in the same session but a different group.

While both definitions are complicated, the concept is pretty simple. If a process group is suspended and there is not a process around that can tell it to continue, the process group is orphaned.

When a process group is orphaned, every process in that process group is sent a SIGHUP, which normally terminates the program. Programs that have chosen not to terminate on SIGHUP are sent a SIGCONT, which resumes any suspended processes. This sequence terminates most processes and makes sure that any processes that are left are able to run (are not suspended).

SIGHUP

The shell exits by default upon receipt of a SIGHUP. Before exiting, an interactive shell resends the SIGHUP to all jobs, running or stopped. Stopped jobs are sent SIGCONT to ensure that they receive the SIGHUP. To prevent the shell from sending the signal to a particular job, it should be removed from the jobs table with the disown builtin (see SHELL BUILTIN COMMANDS below) or marked to not receive SIGHUP using disown -h.

上面这段引用来自man bash,已经把bash resend SIGHUP介绍的很清楚了。

SIGHUP信号对应的默认动作,是Term。这就是关闭Terminal窗口,一般进程都会被终止的原因。

nohup命令

nohup - run a command immune to hangups, with output to a non-tty. Run COMMAND, ignoring hangup signals.

执行命令,并且让命令对SIGHUP信号免疫,用户logout会触发SIGHUP信号。nohup命令默认将进程的输出导入nohup.out文件!

$ nohup <command> [&]

If standard input is a terminal, redirect it from an unreadable file. If standard output is a terminal, append output to 'nohup.out' if possible, '$HOME/nohup.out' otherwise. If standard error is a terminal, redirect it to standard output. To save output to FILE, use nohup COMMAND > FILE.

setsid

setsid - run a program in a new session.

$ setsid ping cs.pynote.net

setsid的原理是,这个接口会让进程开启一个新的sesssion,此进程是session leader。因此可以有关闭shell不会退出的效果。这部分更详细的信息,请参考Contrlling Terminal

disown

disown是bash的一个builtin

孤儿进程

父进程提前与子进程终止后,子进程就成了孤儿进程,其ppid会被设置为1。

1号进程不会像bash那样转发窗口关闭时的SIGHUP信号,因此,下面两种方式,以创建孤儿进程的形式,实现了关闭shell窗口进程不关闭的效果。

  1. 以非source命令的方式启动shell脚本文件中的后台进程(命令后台带&);
  2. 用下面两行python代码,也可以创建一个后台进程,关闭shell窗口后不退出的那种:
$ cat test_background_subprocess.py 
import subprocess
subprocess.Popen('ping www.maixj.net'.split())

不要设置shell=True,否则ping进程还会多一个shell父进程。

如果父进程要终止,一般都要确保其子进程先结束。就像申请了内存,不用时要释放一样。如果父进程不管子进程,子进程要么一直在1号进程下运行,要么自己运行到自然结束。

本文链接:https://cs.pynote.net/sf/linux/shell/202112283/

-- EOF --

-- MORE --