nohup

nohup = no hang up 作用在当用户从终端页面退出时,程序任务仍可以保持运行

那为何 nohup 可以实现这个功能呢

Ps:这篇可能会比较硬,写(chao)的时候也是头疼的很,感谢山川 dalao 的解惑文档

signal

在讲之前,我们需要对 Linux 的信号处理体系有一点了解

Signal 是在一种软件体系下对中断的模拟,所以也被称为软中断。

Signal 是进程间通信机制中唯一的异步通信机制。

与其他进程间通信方式(管道 pipeline、共享内存等)相比,信号所能传递的信息比较粗糙,只是一个整数。 但正是由于传递的信息量少,信号也便于使用和管理,可以用于系统管理相关的任务,例如通知进程终结、中止或者恢复等。

信号可分为可靠、不可靠,实时、非实时。其主要区别是 Unix 与后来改进版 Linux 信号的差别。

进程对信号的响应可分为忽略、捕捉、缺省三种。

发送信号的主要函数有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及 abort()

每种信号用一个整型常量宏表示,以 SIG 开头,比如 SIGCHLD、SIGINT 等,它们在系统头文件中定义。

内核中针对每一个进程都有一张表来保存信号。

在进程task_struct结构体中有一个未决信号的成员变量struct sigpending pending

每个信号在进程中注册都会把信号值加入到进程的未决信号集。

当内核需要将信号传递给某个进程时,就在该进程对应的表中写入信号,这样就生成了信号。

  • 非实时信号发送给进程时,如果该信息已经在进程中注册过,不会再次注册,故信号会丢失;
  • 实时信号发送给进程时,不管该信号是否在进程中注册过,都会再次注册。故信号不会丢失;

信号在从内核态切换至用户态时,进行处理。

当该进程由用户态陷入内核态,再次切换到用户态之前,会查看表中的信号。如果有信号,进程就会首先执行信号对应的操作,此时叫做执行信号。

从生成信号到将信号传递给对应进程这段时间,信号处于等待状态。

我们可以编写代码,让进程阻塞 block 某些信号,也就是让这些信号始终处于等待的状态,直到进程取消阻塞 unblock 或者忽略信号。

processes

在远程主机上,有两个相关的进程:

  1. ssh 守护进程(sshd)的一个实例,它将远程程序的输入和输出中继到本地终端;
  2. 进程组,用于维护各种 bash 命令(bash 也是一个进程);
  • 每个进程属于一个进程组
    • 同组中的进程作为一个命令管道的一部分,例如cat file | sort | grep 'word'将放置在运行cat(1),sort(1), grep(1)的进程组里
  • bash 也是一个进程,也属于一个进程组
  • 进程组是会话的一部分, 会话由一个或多个进程组组成
  • 在会话中,最多有一个进程组,称为前台进程组,可能还有许多后台进程组
  • bash 将进程从后台移动到前台,从前台移动到后台通过 tcsetpgrp(3)
  • 发送到进程组的信号将传递到该组中的每个进程。

some concept

  1. tty: 终端设备,Teletypes, 电传打字机
  2. pty: 伪终端,虚拟终端
  3. pts/ptmx: pts=pseudo-terminal slave, ptmx=pseudo-terminal master, 对 master 的操作会反映到 slave 上
  4. /dev/ttySn: 串行端口终端。计算机把每个串行端口都看作是一个字符设备
  • 例如,echo test > /dev/ttyS1 会把单词”test”发送到连接在 ttyS1(COM2)端口的设备上
  1. /dev/pty: 伪终端是成对的逻辑终端设备,例如/dev/ptyp3 和/dev/ttyp3
  • 例如,telnet 连接 PC,则 telnet 程序会连接到设备 ptyp2(m2)上。此时一个 getty 程序就应该运行在对应的 ttyp2(s2)端口上
  • 当 telnet 从远端获取了一个字符时,该字符就会通过 m2、s2 传递给 getty 程序,而 getty 程序就会通过 s2、m2 和 telnet 程序往网络上返回”login:”字符串信息
  1. /dev/tty: 控制终端,当前进程的控制终端的设备特殊文件,可以使用命令”ps –ax”来查看进程与哪个控制终端相连
  • shell 中,/dev/tty 就是使用的终端,设备号(5,0).使用命令”tty”可以查看它具体对应哪个实际终端设备
  1. /dev/ttyn, /dev/console: 控制台终端

sshd

首先得明白 sshd 过程

当 sshd 接收到 shell 命令,会创建一对 pty(master pty, slave pty).(不明白的参考上一节)

sshd 拥有master pty,那么 shell 会把slave pty作为 shell 程序的stdin stdout stdeer

对应的 ssh 命令就是 ssh -t root@ip ls(对主要就是 -t)

如果不使用 pty, 则 sshd 会使用 pipe 作为与 shell 的关联,shell 会把stdin stdout stderr变为pipe

那么为什么 ssh 这边的客户端关了之后,服务器运行的命令会关闭呢?

那是因为如果 ssh 客户端关闭了,sshd可以感知的到,则服务器端会把master pty关闭。

那么操作系统就会向slave pty发送SIGNUP信号,默认的信号处理方式就是终止

如果换成pipe,那么sshd也会关闭pipe的一端。如果此时客户端已经关闭,那么会收到一个SIGNPIPE信号,默认的处理函数是终止函数,所以只要程序不读写stdin stdout stderr那么不会退出

nohup

再说说nohup

nohup的原理就是假装看不见SIGNHUP信号,同时把stdin重定向到/dev/nullstdoutstderr 重定向到nohup.out(重定向地址可以更改)

但是如果 ssh 是使用pipe方式的, 使用 nohup 发现没有效果,stdin还是pipe

那是因为nohup里面会对原有的stdin 做判断,只有是istty 才会把stdin stdout stderr替换掉

nohup command &
1

setsid

换个角度思考,如果我们的进程不属于接受SIGNHUP信号的终端的子进程,那么自然也就不会受到 HUP 信号的影响了

setsid 就是利用这一点

setsid command
1

&

如果把"&"放入“()”内之后,会发现所提交的 job 并不在 jobs 中,那么这样就能躲过SIGNHUP信号

$ (ping wyydsb.xin &)
$ ps -ef |grep wyydsb.xin
  501 18420 18140   0  6:50PM ttys003    0:00.02 watch curl https://wyydsb.xin
  501 18456 18140   0  6:51PM ttys003    0:00.23 watch -n 5 curl https://wyydsb.xin
  501 20519     1   0  8:46PM ttys005    0:00.01 ping wyydsb.xin
  501 20598 20528   0  8:47PM ttys008    0:00.00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn wyydsb.xin
1
2
3
4
5
6

当然 这个 最好别作死去试

因为PID=1的是内核进程

然后启起来 你就发现kill不了 只能 重启了

disown

如果 我们 起命令的时候忘记加 nohup 或者 setsid,然后还想忽略 SIGNHUP,只能通过作业调度和 disown 来解决问题了

  • disown -h jobspec来使某个作业忽略SIGNHUP信号
  • disown -ah 来使所有的作业都忽略SIGNHUP信号
  • disown -rh来使正在运行的作业忽略SIGNHUP信号

screen

如果有大量这种命令需要在稳定的后台里运行,如何避免对每条命令都做这样的操作呢?

  • screen -dmS session name 建立一个处于断开模式下的会话(并指定其会话名)
  • screen -list 列出所有会话
  • screen -r session name 重新连接指定会话
  • 快捷键CTRL-a d 暂时断开当前会话

systemd

当然还可以把 bash 封装成一个服务运行在 Linux 上

参考

. Paramiko . Why does running a background task over ssh fail if a pseudo-tty is allocated? . ssh command unexpectedly continues on other system after ssh terminates . What causes various signals to be sent? . SIGNAL(7) . Linux 技巧:让进程在后台可靠运行的几种方法 . Linux 环境进程间通信 . Linux 信号(signal)机制

You can use this BibTex to reference this blog if you find it useful and want to quote it.