用以促学——Linux进程后台运行的原理、方法、比较及其实现
前言
相关基础知识
- 操作系统:进程相关概念,调度等
- Linux
应用场景
后台运行不用说了吧,有时候在服务器上面跑个爬虫啥的,都要用的。
一般远程连接服务器的时候,有时候网络不好或者不想一直开着终端的话,后台运行就很有必要了。
问题所在
一般我们连接服务器,都是通过SSH(Secure Shell)
进行连接,本文也着重介绍这种情况。
当我们通过ssh运行一些耗时的任务后,经常会由于网络不稳定、或者电脑关机休眠之类的导致任务中途失败。
如何让命令开始执行后优雅的退出本地终端、关闭ssh并且不会影响其运行呢?
在开始之前,我们需要了解一些概念,当然,你也可以选择跳过它。
linux概念说明
session(会话)
定义
会话是一组进程的集合(并不是进程)
集合内的每个进程的sid(SessionID)
是相同的,代表他们属于同一个集合
会话的创建
会话是由会话中的第一个进程创建的 (领头进程session leader)
其 pid(ProcessID)
等于 sid
这里简单介绍一个函数 setsid
子进程默认从父进程继承了:SessionID、进程组ID和打开的终端。
子进程如果要脱离这些,可通过setsid来实现。
setsid帮助一个进程脱离从父进程继承而来的已打开的终端、隶属进程组和隶属的会话。
setsid很好理解嘛,就是长大了翅膀硬了飞了,脱离父进程。
一个进程当它setsid后,一个新session就被建立了,并且他就是那个session leader
会话中的进程是树状结构。一个session中的进程,一定是session leader或者其子孙进程
就像自立门户一样。
会话的消亡
-
session中的所有进程都结束时消亡
-
session leader退出(或者结束)时,session中的进程会收到SIGHUP信号,默认结束掉进程。
如果该进程对SIGHUP做了处理,不结束,那么会受到SIGCONT信号,让进程正常执行完毕
signal(信号)
SIGHUP (signal hang up)
意为:信号挂起
是一个进程的 **控制终端 **在关闭时,发送给进程的信号。
SIGINT (Signal interrupt)
意为:信号中断
当用户希望中断进程时**(关联Ctrl+C)**,SIGINT信号由其控制终端发送到进程。
与SIGTERM相比,其只能结束前台进程
SIGTERM (Signal terminate)
意为:信号中止
Sigterm信号被发送到一个进程以请求其终止。
这允许该进程友好的终止、释放资源,并在适当的情况下保存状态。
在某种程度上sigint与sigterm几乎相同
与Sigkill信号不同,它可以被阻塞、处理和忽略。
kill
命令默认不带参数时,发送的信号就是SIGTERM,让程序友好的退出。
kill -9
发送的即是SIGKILL,强制杀死,无条件终止。
SIGKILL (Signal kill)
意为:信号杀死
SIGKILL信号被发送到进程以使其立即终止。强制性的
与SIGTERM和SIGINT相反,这个信号不能被捕获或忽略,接收进程在接收到这个信号后不能进行任何清理。
有一些例外在这里不做赘述。
SIGCONT (Signal continue)
意为:信号继续
SIGCONT信号指示操作系统继续(重新启动)先前由SIGSTOP或SIGTSTP信号暂停的进程。
这个信号的一个重要用途是在Unix shell中的作业控制中。
后台运行进程的方法
有些时候,当你开始一个耗时的任务,你的终端会被占用,你无法输入你想输入的下一条指令。
这很好解决,用符号&
即可。我们一般将它附在命令后面可以使进程在后台执行,不会占用前台界面。
但它实际上是在当前会话中开启了一个后台作业。
但此时终端被关闭后,进程还是会退出。这是因为,& 符号只有让进程让出前台终端的功能,无法让进程不受 SIGHUP 信号的影响。
这显然不是我们想要的
如果我们想要让进程脱离终端去运行呢?,接下来我们进行讨论
当通过终端运行命令时,发生了什么?
通常,用户登陆终端时,会创建一个 session ,session 中的领头进程是运行用户登录 shell 的进程。
在这之后,用户新创建的每个进程都会在这个session里。
当终端关闭时,又发生了什么?
当用户注销或者网络断开时,SIGHUP
信号会被发送到会话中的所有子进程。
而 SIGHUP
的默认处理方式是终止收到该信号的进程。所以若程序中没有捕捉该信号,当终端关闭后,会话中获得进程就会退出。
由此可以想到,我们的解决办法有两种方法:
- 让进程忽略
SIGHUP
信号。 - 让进程运行在新的会话里从而成为不属于此终端的子进程。
后台运行的实现
使用nohup指令
顾名思义,nohup指令使进程不受 SIGHUP
信号的影响。即第一种方法
nohup py run.py
当执行上述命令后,进程还会一直占用着终端前台。但当终端被关闭或连接断开,进程还是会继运行。
默认会将输出存至当前文件夹下的 nohup.out
文件(不存在则创建)。
如果同时搭配 &
就可以实现让出前台的同时,使得进程不受终端关闭的影响
使用setsid指令
在前面有简单介绍这个指令。它的作用是让进程转移到一个新的会话运行。即第二种方法
setsid py run.py
它直接创建了一个新的会话来运行,那么原会话的终端的状态就再也不会影响到此进程了。
我们使用pstree -a |grep -C 6 ping来查看一下发生的变化
默认
|-sshd
| `-sshd
| `-sshd
| `-bash
| |-grep -C 6 ping
| |-ping 106.55.250.70 <<-在这里
| `-pstree -a
使用 setsid
|-ping 106.55.250.70 <<-在这里
|-sshd
| `-sshd
| `-sshd
| `-bash
| |-grep -C 6 ping
| `-pstree -a
使用了 setsid 后,ping已与 sshd 同级,即属于 init 进程的子进程了。
但是 setsid 并没有为进程分配一个输出终端,所以进程还是会输出到当前终端上。
比较
nohup | & | setsid | screen | disown -h | |
---|---|---|---|---|---|
用途 | 不挂断地运行命令 | 让出前台 | 创建一个新的会话并运行进程 | 伪终端 | 移出作业列表 |
原理 | 使进程不受 SIGHUP 信号的影响 | 在当前会话中开启了一个后台作业 又称守护进程 | 脱离父进程,开启新会话 | 直接开一个虚拟终端 | 将作业进程转为init的子进程 |
进程位置 | 当前会话 | 当前会话 | 新的会话 | 对应伪终端会话 | 当前会话,关闭后到init |
启动 | nohup py run.py | py run.py & | setsid py run.py | screen创建 | bg %1 && disown %1 |
正在运行 | 当前终端即是 | jobs查看后台正在运行(仅当前终端可用) | ps | 对应伪终端即是 | ps |
如何停止 | Ctrl+C | 使用fg将后台切换到前台后Ctrl+C | kill | 切换到伪终端内进行操作 | kill |
前台终端 | 不让出 | 让出 | 不让出 | 不让出对应伪终端 | 与&相同 |
输出 | 输出重定向,默认到当前目录下 nohup.out 文件 | 使用标准输出时(echo、ls)仍会一直占用前台(例如ping指令) | 当前终端 | 对应伪终端 | 与&相同 |
关闭ssh | SIGHUP 信号,程序免疫 | SIGHUP 信号,程序关闭 | 不受影响 | 不受影响 | 转换为init的子进程 |
Ctrl+C | SIGINT 信号,程序关闭 | 不受影响 | 不受影响 | 在对应伪终端内有效 | 与&相同 |
配合&使用时 查看:jobs 停止:fg+ Ctrl+C Ctrl+C 不能关闭程序。Ctrl+C + jobs 可以关闭。 |
其他
huponexit
一般默认为off,仅针对使用exit命令退出终端的情况,这时不会发送sighup信号。
参考资料
Linux让进程后台运行且连接断开不影响(nohup、setsid、disown、screen)
Linux后台进程管理以及ctrl+z(挂起)、ctrl+c(中断)、ctrl+\(退出)和ctrl+d(EOF)的区别