李守中

Linux 后台运行程序

Table of Contents

1 前台任务与后台任务

前台任务 ( foreground job ) 会独占命令行窗口,只有运行完了或者手动中止,才能执行其他命令。

变成守护进程的第一步,就是把它改成 后台任务 ( background job )。

mv a ../b & 只要在命令的尾部加上符号 & ,启动的进程就会成为后台任务。

让正在运行的前台任务变为后台任务,参考这个操作流程:

  1. mv a ../b 任务启动。
  2. Ctrl + z 任务挂起。
  3. jobs 查看所有任务。
  4. bg %1 把标号为 1 的任务放入后台。
  5. disown -h %1 把标号为 1 的任务交给系统后台。

后台任务有两个特点:

  1. 继承当前 session ( 对话 ) 的标准输出 ( stdout ) 和标准错误 ( stderr )。因此,后台任务的所有输出依然会同步地在命令行下显示。
  2. 不再继承当前 session 的标准输入 ( stdin )。你无法向这个任务输入指令了。如果它试图读取标准输入,就会暂停执行 ( halt )。

后台任务与前台任务的本质区别只有一个: 是否继承标准输入。

所以,执行后台任务的同时,用户还可以输入其他命令。

2 SIGHUP 信号

Linux 系统有这样一个设计:

  1. 用户准备退出 session;
  2. 系统向该 session 发出 SIGHUP 信号;
  3. session 将 SIGHUP 信号发给所有子进程;
  4. 子进程收到 SIGHUP 信号后,自动退出。

因为前台任务收到了 SIGHUP 信号,所以会随着 session 的退出而退出。

后台任务是否也会收到 SIGHUP 信号由 Shell 的 huponexit 参数决定。

执行 shopt | grep huponexit 可以查看 huponexit 参数的值。

大多数 Linux 系统,这个参数默认关闭 ( off )。因此,session 退出的时候,不会把 SIGHUP 信号发给后台任务。所以,一般来说,后台任务不会随着 session 一起退出。

3 disown 命令

通过后台任务启动守护进程并不保险,因为有的系统的 huponexit 参数可能是打开的 ( on )。

更保险的方法是使用 disown 命令将指定任务从后台任务列表 ( jobs 命令的返回结果 ) 之中移除。

一个后台任务只要不在这个列表之中,session 就肯定不会向它发出 SIGHUP 信号。

mv a ../b &
disown

执行上面的命令以后,server.js进程就被移出了后台任务列表。执行 jobs 命令验证,输出结果里面,不会有这个进程。

disown 的用法:

  1. disown -r 移出所有正在执行的后台任务;
  2. disown -a 移出所有后台任务;
  3. disown -h 不移出后台任务,但是让它们不会收到 SIGHUP 信号;
  4. disown %2 disown -h %2 根据jobId,移出指定的后台任务。

4 标准 I/O

使用 disown 命令还有一个问题: 退出 session 后,如果后台进程与标准 I/O 有交互,它还是会挂。

var http = require('http');

http.createServer(function(req, res) {
    console.log('server starts...'); // 加入此行
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World');
}).listen(5000);

启动上面的脚本,然后再执行 disown 命令。

node server.js &
disown

接着,退出 session,访问 5000 端口,会发现连不上。

因为后台任务的标准 I/O 继承自当前 session,disown 命令并没有改变这一点。

一旦后台任务读写标准 I/O,就会发现它已经不存在了,所以就报错终止执行。

为了解决这个问题,需要对"后台任务"的标准 I/O 进行重定向。

node server.js > stdout.txt 2> stderr.txt < /dev/null &
disown

上面这样执行,基本上就没有问题了。

5 nohup 命令

还有比 disown 更方便的命令,就是 nohup 命令。

nohup node server.js &

nohup 命令对 server.js 进程做了三件事。

  1. 阻止 SIGHUP 信号发到这个进程。
  2. 关闭标准输入。该进程不再能够接收任何输入,即使运行在前台。
  3. 重定向标准输出和标准错误到文件 nohup.out。

也就是说,nohup 命令实际上将子进程与它所在的 session 分离了。

注意,nohup 命令不会自动把进程变为后台任务,所以必须加上 & 符号。

nohup 命令:

  1. 无论是否将 nohup 命令的输出重定向到终端,输出都将附加到当前目录的 nohup.out 文件中。
  2. 如果当前目录的 nohup.out 文件不可写,输出重定向到 ~/nohup.out 文件中。
  3. 如果没有文件能创建或打开以用于追加,那么 Command 参数指定的命令不可调用。

5.1 nohup 和 & 的区别

使用 nohup 运行程序:

  1. 无论是否将 nohup 命令的输出重定向到终端,输出都将附加到当前目录的 nohup.out 文件中。
  2. 使用 Ctrl + C 发送 SIGINT 信号,程序关闭
  3. 关闭 Shell Session 发送 SIGHUP 信号,程序免疫

使用 & 运行程序:

  1. 程序转入后台运行
  2. 结果会输出到终端
  3. 使用 Ctrl + C 发送 SIGINT 信号,程序不会停止
  4. 关闭 Shell session 发送 SIGHUP 信号,程序关闭

5.2 nohup 和 & 使用实例

一般两个一起组合使用不会受 Ctrl + C 和 Shell 关闭的影响:

nohup <command> & 最简单的后台运行。

nohup python main.py & 输出默认重定向到当前目录下 nohup.out 文件。

nohup python main.py >> main.log 2>&1 & 自定义输出文件 ( 标准输出和错误输出合并到 main.log )。

nohup python main.py &> main.log & 与上一个例子相同作用的简写方法。

nohup python main.py &> /dev/null & 不记录输出信息。

nohup python main.py &> /dev/null & echo $! > pidfile.txt 不记录输出信息并将程序的进程号写入 pidfile.txt 文件中,方便后续杀死进程。

使用 nohup 时,程序会自动将输出写入 nohup.out 文件中。如果程序不断向控制台输出,文件就会不停的变大。

如果不需要输出,可以用 /dev/null 解决这个问题。它相当于一个黑洞,任何输出到这个文件的东西都将消失。

nohup <command> >/dev/null 2>log & 只保留输出错误信息。

nohup <command> >/dev/null 2>&1 & 所有信息都不要。

2>&1 将错误信息重定向到标准输出。这使用到了 Linux 的重定向,其中 0 1 2 分别是标准输入、标准输出、标准错误输出,用来指定需要重定向的标准输入输出。默认情况下是标出输出,也就是 1

jobs -l 查看任务,返回任务编号和进程号。

bg %<jobnumber> 将一个在后台暂停的命令,变成在后台继续执行。如果后台中有多个命令,可以用 bg %<jobnumber> 将选中的命令调出。

fg %<jobnumber> 将后台中的命令调至前台继续运行。如果后台中有多个命令,可以用 fg %<jobnumber> ( 是命令编号,不是进程号 ) 将选中的命令调出。



Last Update: 2023-05-18 Thu 08:28

Contact: [email protected]     Generated by: Emacs 27.1 (Org mode 9.3)

若正文中无特殊说明,本站内容遵循: 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议