原文发表于“网络安全和信息化”2018年第3期,转载到博客。

进程管理是Linux系统运维人员应掌握的一项基本技能,Linux作为典型的多用户操作系统,允许多个用户同时从不同的终端进行登录,用户在相应终端上所启动的进程与各自的终端之间存在紧密的联系。本文以CentOS7系统为例,通过两个具体的实例介绍了如何对Linux系统中的进程和终端进行管理。

1. 什么是Linux系统的终端

通常我们将一套键盘鼠标以及显示器这样的输入输出设备称为一个终端,直接连接在电脑主机上的称为物理终端。在操作安装有Windows系统的电脑时,通常是只有一套物理终端并且只有一个操作界面。而Linux系统支持虚拟终端,在操作安装有Linux系统的计算机时,用户虽然面对的也是一套物理终端设备,但却可以通过虚拟终端打开多个互不干扰、独立工作的界面。
Linux中提供的虚拟终端默认有6个,其中第1个是图形界面,第2到6个则是字符界面。可以通过Ctrl+Alt+F(1~6)组合键在不同的虚拟终端之间进行切换,比如安装有X Window的Linux系统启动之后默认就是进入了第1个虚拟终端中的图形界面,此时按下组合键Ctrl+Alt+F2就进入到了第2个虚拟终端,这就是一个字符界面了。
虚拟终端的缩写为tty,在字符界面下执行“tty”命令就可以显示用户当前所在的终端编号。

[root@localhost ~]# tty
/dev/tty2

由于我们通常都是通过网络远程对Linux服务器进行管理,这种通过远程登录方式所打开的终端称为伪终端(pts)。比如我们通过Xshell远程登录到Linux系统,然后同样执行tty命令,发现显示的结果为“/dev/pts/0”,表示这是系统启动的第一个伪终端(伪终端的编号从0开始)。

[root@localhost ~]# tty
/dev/pts/0

另外,如果Linux系统安装了X Window,那么在图形界面中,右键单击桌面空白处然后选择“在终端中打开”,此时所打开的也是一个pts伪终端。但由于在生产环境中很少有人会这样操作,所以这种情况可以忽略不计。
因而可以总结一下,对于Linux系统而言,终端主要分为两类:由用户在本地所打开的终端称为虚拟终端tty,由用户在远程所打开的终端称为伪终端pts。由于绝大多数情况下我们都是远程对Linux服务器进行管理,因而用户所使用的终端主要是伪终端pts。每个终端都有相应的编号,执行tty命令就可以查看到用户当前所在的终端编号。

2. 进程中的终端信息

Linux系统中的进程与启动进程的终端之间是紧密关联的,比如我们直接执行不带任何选项的ps命令,将只显示当前用户在当前终端所启动的进程。

[root@localhost ~]# ps
  PID   TTY     TIME    CMD
 5290   pts/0    00:00:00   bash
 5309   pts/0    00:00:00   ps

可以看到,当前用户只启动了2个进程,分别是“bash”和“ps”。其中“ps”就是刚才执行的ps命令所产生的进程,而“bash”则是当前终端所对应的终端进程,它也是ps进程的父进程。
如果希望查看系统中所有的进程,就需要为ps命令加上相应的选项,比如常用的选项组合“aux”,其中的选项“a”就表示显示与当前终端有关的所有进程,选项“x”表示显示与当前终端无关的所有进程,因而两个选项结合起来就表示显示系统中的所有进程了。
比如分屏查看当前系统中所有进程的详细信息。

[root@localhost ~]# ps aux | more
USER  PID  %CPU  %MEM   VSZ   RSS  TTY  STAT   START   TIME  COMMAND
root    1    0.0     0.4   193628  4636  ?     Ss   00:14   0:06 /usr/lib/systemd/systemd --switched-root --system --deserialize 21
root    2    0.0     0.0      0     0    ?     S    00:14   0:00   [kthreadd]
root    3    0.0     0.0      0     0    ?     S    00:14   0:07   [ksoftirqd/0]
……

在详细的进程信息中可以看到,很多进程的TTY字段显示为“?”,这表示该进程不是由用户在某个终端启动的,而是由系统内核所启动。
对于那些由用户执行命令所启动的进程都是与终端相关的,当把终端关闭,该终端中的所有进程也都会自动关闭。这是Linux系统一个非常重要的特点,下面的两个实例都是对这个特点的具体应用。

3. 进程的后台启动

进程就是运行中的程序,我们只要在Shell命令行下输入并执行某条命令,就会启动一个相应的进程。Linux系统中的进程有前台进程和后台进程之分,通常情况下我们执行命令所产生的进程都是前台进程,前台进程的一个重要特点是会占据当前终端,当进程没有结束的时候,用户不能在当前终端中再进行其它的操作。
比如我们执行“nc -l -p 8000”命令侦听TCP8000端口(CentOS7中默认没有安装nc,如果已经配置好yum源,可以执行“yum install nc”命令安装),这条命令执行之后将会一直处于运行状态,如果用户不按Ctrl+c键强制中止,该进程将一直占据当前终端。
如果在要执行的命令后面加上一个“&”符号,此时进程将转到后台运行,其执行结果不在屏幕上显示,该进程也不会占据当前终端,用户仍可以继续执行其它的操作。
比如,在后台执行nc命令侦听TCP8000端口。

[root@localhost ~]# nc -l -p 8000 &
[1] 79878

当然也可以直接执行命令启动一个前台进程,然后再通过按下Ctrl+z组合键,将该进程转入到后台。只不过通过这种方式转入到后台的进程将处于停止状态,需要再通过执行bg命令使其在后台继续执行。

[root@localhost ~]# nc -l -p 8000
^Z
[1]+  已停止               nc -l -p 8000
[root@localhost ~]# jobs -l
[1]+ 102964 停止                  nc -l -p 8000
[root@localhost ~]# bg 1
[1]+ nc -l -p 8000 &
[root@localhost ~]# jobs -l
[1]+ 102964 运行中               nc -l -p 8000 &

无论是通过在命令后面加“&”符号在后台执行的进程,还是通过Ctrl+z组合键而转往后台的进程,它们都与当前终端相关。如果将当前终端关闭,那么这些在后台运行的进程也将全部关闭。这样如果我们希望能够通过nc命令在系统中始终监听TCP8000端口,这就很难实现了。
因而如果希望某些进程能够始终在后台运行,那么可以通过nohup命令解除其与当前终端之间的关系。比如我们希望无论当前终端是否关闭,始终都在后台执行nc命令监听本机的TCP6000端口。那么可以执行下面的命令:

[root@localhost ~]# nohup nc -l -p 6000 &
[2] 103240
[root@localhost ~]# nohup: 忽略输入并把输出追加到"nohup.out"

命令执行之后,将当前终端关闭,然后再次打开一个新的终端,执行“ps aux | grep nc”命令查找nc命令所产生的进程,可以看到由“nc -l -p 6000”命令产生的进程所对应的终端已经变为了“?”,即由系统内核启动,而不再与任何终端关联。

[root@localhost ~]# ps aux | grep nc
……
root   103240  0.0  0.1  43512   1808  ?        S    15:55   0:00  nc -l -p 6000
root   103304  0.0  0.0  112668   968  pts/0     R+   15:57   0:00  grep --color=auto nc

这样这条命令所产生的进程就成为了系统的后台进程,如果管理员不用kill命令强制终止的话,这个进程将一直运行下去。

4. 踢出系统中的可疑用户

下面再通过一个实例进一步说明进程和终端之间的关系。
由于Linux是一个多用户的操作系统,作为管理员就需要随时了解当前正在有哪些用户在登录系统。通过执行who命令可以查看当前正在登录系统的用户以及其相关信息。

[root@localhost ~]# who
root     :0           2017-10-14 15:20 (:0)
root     pts/0        2017-12-28 15:57 (192.168.80.1)
root     tty2         2017-12-28 16:11

从who命令的执行结果可以发现,有3个用户正在以root用户的身份登录系统。其中第一行信息没有显示终端,表明root用户是在本地登录,由于是本地登录,因而未显示IP地址。第二行信息表明root用户在远程的伪终端pts/0上登录,并显示了登录IP。第三行信息表明root用户在本地的虚拟终端tty2上登录,同样不显示IP地址。
下面我们在系统中创建一个名为hacker的账号,并利用该账号在某个终端上远程登录。
创建账号并设置密码:

[root@localhost ~]# useradd hacker
[root@localhost ~]# echo "123" | passwd --stdin hacker
更改用户 hacker 的密码 。
passwd:所有的身份验证令牌已经成功更新。

然后在另外一台Kali系统中(IP地址192.168.80.20)利用hacker账号远程登录Linux服务器(IP地址192.168.80.146):
root@kali:~# ssh hacker@192.168.80.146
登录成功之后,在Linux服务器中执行who命令查看当前登录用户。可以发现有一个可疑用户正在IP地址为192.168.80.20的客户端上远程登录,其所在终端编号为pts/1。

[root@localhost ~]# who
root     :0           2017-10-14 15:20 (:0)
hacker   pts/1        2017-12-28 16:19 (192.168.80.20)
root     pts/0        2017-12-28 15:57 (192.168.80.1)
root     tty2         2017-12-28 16:11

下面我们将这个可疑用户踢出系统。
首先需要根据可疑用户所在终端查找出该终端所对应的进程,可以执行“ps aux | grep pts/1”命令。从命令的执行结果可以看到PID为103707的进程对应的终端为pts/1,而且该进程是由bash启动的,这正是我们所要找的终端进程。

[root@localhost ~]# ps aux | grep pts/1
hacker  103706  0.0  0.2 142916  2224  ?       S    16:19   0:00  sshd: hacker@pts/1
hacker  103707  0.0  0.2 116168  2740  pts/1    Ss+  16:19   0:00  -bash
root    103801  0.0  0.0 112664   972  pts/0    S+   16:25   0:00  grep --color=auto pts/1

然后通过kill命令强制终止该进程。
[root@localhost ~]# kill -9 103707
再次执行who命令,发现可疑用户已经被踢出,而且该用户启动的所有进程也将自动关闭(通过nohup命令转为系统后台进程的除外)。

[root@localhost ~]# who
root     :0           2017-10-14 15:20 (:0)
root     pts/0        2017-12-28 15:57 (192.168.80.1)
root     tty2         2017-12-28 16:11