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
在远程主机上,有两个相关的进程:
- ssh 守护进程(sshd)的一个实例,它将远程程序的输入和输出中继到本地终端;
- 进程组,用于维护各种 bash 命令(bash 也是一个进程);
- 每个进程属于一个进程组
- 同组中的进程作为一个命令管道的一部分,例如
cat file | sort | grep 'word'
将放置在运行cat(1),sort(1), grep(1)
的进程组里
- 同组中的进程作为一个命令管道的一部分,例如
- bash 也是一个进程,也属于一个进程组
- 进程组是会话的一部分, 会话由一个或多个进程组组成
- 在会话中,最多有一个进程组,称为前台进程组,可能还有许多后台进程组
- bash 将进程从后台移动到前台,从前台移动到后台通过 tcsetpgrp(3)
- 发送到进程组的信号将传递到该组中的每个进程。
some concept
tty
: 终端设备,Teletypes, 电传打字机pty
: 伪终端,虚拟终端pts/ptmx
: pts=pseudo-terminal slave, ptmx=pseudo-terminal master, 对 master 的操作会反映到 slave 上/dev/ttySn
: 串行端口终端。计算机把每个串行端口都看作是一个字符设备
- 例如,echo test > /dev/ttyS1 会把单词”test”发送到连接在 ttyS1(COM2)端口的设备上
/dev/pty
: 伪终端是成对的逻辑终端设备,例如/dev/ptyp3 和/dev/ttyp3
- 例如,telnet 连接 PC,则 telnet 程序会连接到设备 ptyp2(m2)上。此时一个 getty 程序就应该运行在对应的 ttyp2(s2)端口上
- 当 telnet 从远端获取了一个字符时,该字符就会通过 m2、s2 传递给 getty 程序,而 getty 程序就会通过 s2、m2 和 telnet 程序往网络上返回”login:”字符串信息
/dev/tty
: 控制终端,当前进程的控制终端的设备特殊文件,可以使用命令”ps –ax”来查看进程与哪个控制终端相连
- shell 中,/dev/tty 就是使用的终端,设备号(5,0).使用命令”tty”可以查看它具体对应哪个实际终端设备
/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/null
把stdout
stderr
重定向到nohup.out
(重定向地址可以更改)
但是如果 ssh 是使用pipe
方式的, 使用 nohup 发现没有效果,stdin
还是pipe
那是因为nohup
里面会对原有的stdin
做判断,只有是istty
才会把stdin stdout stderr
替换掉
nohup command &
setsid
换个角度思考,如果我们的进程不属于接受SIGNHUP
信号的终端的子进程,那么自然也就不会受到 HUP 信号的影响了
setsid
就是利用这一点
setsid command
&
如果把"&"放入“()”内之后,会发现所提交的 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
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)机制