使用 Screen 管理 Linux 远程会话
通过 SSH 或者 telent 远程登录到 Linux 服务器执行一些长时间运行的任务,比如系统备份、ftp 传输等等。因为他们执行的时间太长了。必须等待它执行完毕,在此期间可不能关掉窗口或者断开连接,否则这个任务就会被杀掉,一切半途而废了。
本文分析了这个问题的原因以及解决方法。
为什么关掉窗口 / 断开连接会使得正在运行的程序死掉?
元凶:SIGHUP 信号
在 Linux/Unix 中,有这样几个概念:
进程组(process group):一个或多个进程的集合,每一个进程组有唯一一个进程组 ID,即组长进程的 ID。
会话(session):一个或多个进程组的集合,开始于用户登录,终止与用户退出,此期间所有进程都属于这个会话。一个会话一般包含一个会话首进程、一个前台进程组和一个后台进程组。
守护进程(daemon):Linux 大多数服务都是通过守护进程实现的,完成许多系统任务如 0 号进程为调度进程,是内核一部分;1 号进程为 init 进程,负责内核启动后启动 Linux 系统。守护进程不因为用户、终端或者其他的变化而受到影响。
当终端接口检测到网络连接断开,将挂断信号(SIGHUP)发送给控制进程(会话期首进程)。而挂断信号默认的动作是终止程序。如果会话期首进程终止,则该信号发送到该会话期前台进程组。
也就是说:ssh 打开以后,bash 等都是他的子程序,一旦 ssh 关闭,系统将所有前台进程杀掉。(后台进程和守护进程不会被关闭!!!)
测试案例
测试例一
打开两个 SSH 终端窗口,在其中一个运行了一个循环打印的 python 脚本。执行命令如下:
[root@localhost ~]# python test.py
test.py
内容如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18while True:
print "hehe"
```
另外一个终端用`pstree -p`查看当前的进程树。显示如下
```
[root@localhost ~]# pstree -p
(省略)
├─sshd(958)─┬─sshd(1282)───bash(1286)───pstree(1436)
│ └─sshd(1410)───bash(1414)───python(1433)
```
可以看到2个bash进程代表了2个终端,pstree是当前进程正在运行的程序,而python进程则是另外一个终端正在运行的程序。
关掉启动python的终端,在刚刚执行pstree的终端上查找pid为1433的进程(也就是原来的python进程),发现没有这个pid的进程,说明python随着终端的关闭而终止了,此时输入`pstree -p`变为了下面这样:
```
[root@localhost ~]# pstree -p
(省略)
├─sshd(958)──sshd(1282)───bash(1286)───pstree(1436)
测试例二
步骤同例一,只是在执行 python 脚本时将其放到后台执行,执行命令如下:
[root@localhost ~]# python test.py &
这样在关闭执行 python 的中断后,python 进程并没有被中断,通过 pstree -p
查看到进程数类似于下面的情况:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43(省略)
├─python(1493)
├─sshd(958)───sshd(1282)───bash(1286)───pstree(1497)
```
因为python执行的是个后台进程,而SIGHup信号只会发送给前台进程组,当父进程结束后,其原来子进程中的后台进程会成为孤儿进程被init进程收养。详见[孤儿进程和僵尸进程][1]
注:网上一些资料显示执行某些复杂程序的时候,只加`&`也会终止,但是博主还没遇到过这种情况,因为我不会这样去执行一个执行时间较长的程序。
同样,nohup命令可以达到这个目的,值得注意的是nohup命令只是使得程序忽略SIGHUP信号,还需要使用标记&把它放在后台运行。这种情况能够保证程序不会被终止。
`nohup <command> [argument…] &`
## 使用screen管理远程会话
虽然nohup和后台进程很容易使用,但还是比较“简陋”的,对于简单的命令能够应付过来,对于复杂的需要人机交互的任务就麻烦了。
其实我们可以使用一个更为强大的实用程序screen。
简单来说,Screen是一个可以在多个进程之间多路复用一个**物理终端**的窗口管理器。Screen中有会话的概念,用户可以在一个screen会话中创建多个screen窗口。
### 创建新的会话
在screen中创建一个新的会话有2种方式
**1.直接在命令行键入screen命令**
`[root@localhost ~]# screen`
Screen将创建一个执行shell的全屏窗口。你可以执行任意shell程序,就像在ssh窗口中那样。在该窗口中键入exit退出该窗口,如果这是该screen会话的唯一窗口,该screen会话退出,否则screen自动切换到前一个窗口。
也可通过`screen -S name` 来为启动的session取名字。
**2.Screen命令后跟你要执行的程序**
[root@localhost ~]# python test.py
Screen创建一个执行python test.py的单窗口会话,终止进程将退出该窗口/会话。
### 进入已创建会话
即使关闭了启动所有终端,在screen会话中启动的进程也不会终止,再次连接时可通过`screen -ls`查看已经启动的screen会话(detached状态),用`screen -r name`恢复指定会话,也可在会话中通过exit退出screen会话。
```
[root@localhost ~]# screen -ls
There is a screen on:
1518.lc (Detached)
1 Socket in /var/run/screen/S-root.
重新连接会话
[root@localhost ~]# screen -r lc或screen -r 1518
退出当前screen会话
[root@localhost ~]#exit
screen 的一些常用参数如下所示
- 分享操作
screen -x name
进入一个还在连接着(attached)的 screen,然后所有操作能够被另外所有正在连着的 screen 看到
- 分屏
- 创建一个新的窗口:ctrl+a+S (注意是大写的 s), 此时新的窗口还没启动 bash
- 启动新窗口的 bash:ctrl+a+c
- 切换窗口:ctrl+a+tab
- 关掉当前窗口:ctrl+a+X(注意是大写的 x)