摘要:学习Linux的进程管理,包括:如何前后前后台进程,找出运行的程序,杀死进程,让进程在你离开一天以后还保持运行。
本文帮助你学习Linux进程管理的基本知识:
需要有一个能够工作的Linux系统,在上面实验脚本,本文的截图是从Ubuntu上实验获取的。
如果你停下来思考一下,很容易发现除了终端程序之外,还有很多其他的程序在运行。实际上,如果你使用图形桌面环境的话,你可能已经打开了多个终端仿真窗口、浏览器程序、游戏、电子表格程序等等。以前的例子中,我们都是先输入命令,然后等待命令执行完成,等待的过程中不能做其他的事情。在本文中,你将会在终端窗口中同时做更多的事情。
当你在终端窗口运行一个命令的时候,你是在前台运行它的。很多命令运行的很快,然而假设你想在一个图形桌面上显示数字时钟的情况。当然了,现在很多的图形环境头已经提供了这么一个功能,我们忽略这个事实,现在只是为了做实验。
如果你已经安装了X Window系统,也就很可能已经附带安装了xclock或者xeyes程序。如果没有安装这些程序,就按装名字类似xorg-x11-apps或者x11-apps的软件包。我们在例子中使用xclock。可以使用如下命令:
xclock -d -update 1
其中的-update 1控制每秒更新一次,默认情况下是每分钟更新一次。运行结果如下图所示。不幸的是,此时终端窗口失去了焦点,没有了提示符。所以,非常需要夺回控制权。庆幸的是,bash有一个挂起键(Ctl+z),按下这对组合键,终端窗口将会重新出现提示符,如下所示。
$ xclock -d -update 1 ^Z [1]+ stopped xclock -d -update 1 ian@attic4:~$ fg %1 xclock -d -update 1 ^Z [1]+ Stopped xclock -d -update 1 ian@attic4:~$ fg %?clo xclock -d -update 1 ^Z [1]+ Stopped xclock -d -update 1 ian@attic4:~$ bg [1]+ xclock -d -update 1 &
时钟还显示在桌面上,只是它已经停止了运行,这就是挂起操作。实际上,如果你拖动其他的窗口经过时钟的一部分,这部分都不会重新绘制。注意到终端输出的消息,其中的1是job号。你可以通过运行fg %1来重新启动这个时钟程序。你也可以使用全部或者部分程序的名字,如fg %xclock或者fg %?clo。如果你没有给fg提供任何参数,那么它将会重新启动最后挂起的那个进程,在本例中,就是xclok。重启进程后,fg也把这个job带回了前台,你也就重新失去了bash的提示符。你需要做的是把job放入后台,这就是bg的作用,它使用和fg一样的参数。bg和fg一样,也是恢复xclock的运行,不同的是bg没有把xclock带入前台,而是放入后台执行,这样终端窗口依旧有提示符,同时时钟还在正常运行。
当你把xclock放入后台运行后,输出的消息并没有Stopped,而是在末尾加一个&。实际上,把一进程放入后台执行,并不需要先把它挂起。只要在运行它的命令行后加上&即可。现在让我们后台运行一个模拟时钟。
ian@attic4:~$ xclock -bg wheat -update 1& [2] 4320
注意现在的输出包含了一个job编号以及进程ID。关于PID我们稍后再说。现在我们使用jobs命令来看看有什么job在运行。使用-l选项来列出PID,正如你看到的,job 2确实PID为4320。注意[2]+中的+号,它表示2号job是当前job,当使用fg不带参数运行时,这个当前job就会被送到前台。
ian@attic4:~$ jobs -l [1]- 3878 Running xclock -d -update 1 & [2]+ 4320 Running xclock -bg wheat -update 1 &在我们解决与后台job有关的问题之前,先来构建一个穷人的数字时钟。我们使用sleep命令来造就一个2秒的延迟,使用date命令来打印当前的时间。我们使用while构造的循环来包围这两个命令。然后在后台运行这个组合体。
ian@attic4:~$ (while sleep 2; do date;done)& [2] 4856 ian@attic4:~$ Tue Jan 19 09:23:30 EST 2010 Tue Jan 19 09:23:32 EST 2010 Tue Jan 19 09:23:34 EST 2010 fTue Jan 19 09:23:36 EST 2010 Tue Jan 19 09:23:38 EST 2010 gTue Jan 19 09:23:40 EST 2010 ( while sleep 2; do date; done ) Tue Jan 19 09:23:42 EST 2010 Tue Jan 19 09:23:44 EST 2010 Tue Jan 19 09:23:46 EST 2010 ^C这个Job的PID是4856。每隔2秒钟,date命令输出当前时间。当我们输入fg命令后,这个时钟就进入了前台,它依然没两秒输出一次,不过由于在前台运行,我们再无法在bash输入命令,只能通过Ctrl+c来停止这个程序了。
前面例子中,date命令的输出与fg命令的输入混在了一起。这导致了一个有趣的问题。如果一个后台进程需要从标准输入获取数据,将会发生什么?(译者注:因为bash也从stdin获取数据)。
我们用来启动后台进程的终端进程成为控制终端。除非重定向,否则后台进程的stdout和stderr都定向到控制终端。同样,后台进程也期望从控制终端获得输入。但是控制终端无法把你键入的字符重定向到后台进程的stdin。这种情况下,bash就会挂起进程,这样也就不再运行。你可以会把它放入前台然后提供给它一些输入数据。
ian@attic4:~$ (date; cat - > bginput.txt;date)& [2] 5070 ian@attic4:~$ Tue Jan 19 10:33:13 EST 2010 [2]+ Stopped ( date; cat - > bginput.txt; date ) ian@attic4:~$ ian@attic4:~$ fg ( date; cat - > bginput.txt; date ) some text more text Tue Jan 19 10:33:31 EST 2010 ian@attic4:~$ cat bginput.txt some text more text
实践中,你可能想把后台进程的标准IO流重新定向到文件。即便这样还存在另外一个问题:如果控制终端关闭了,或者用户登出系统了,将会发生什么?答案是不同的shell结果不同。如果shell发送一个SIGHUP信号,那么后台的程序可能会关闭。现在我们考虑一个解决这个问题的方法。
nohup命令用来启动一个程序,从而导致这个程序会忽略掉hangup信号,并且把stdout和stderr重定向到一个文件。这个文件默认是nohup.out或者$HOME/nohup.out。如果这个文件不可写,那么程序就不会运行。如果你想输出到其他地方,就重定向stdout,stderr。
nohup不能执行一个管线或者命令列表。解决方式是把管线和列表写入一个文件,然后用bash来执行它。如下所示。
ian@attic4:~$ echo "while sleep 30; do date;done">pmc.sh ian@attic4:~$ nohup sh pmc.sh& [2] 5485 ian@attic4:~$ nohup: ignoring input and appending output to `nohup.out' ian@attic4:~$ nohup bash pmc.sh& [3] 5487 ian@attic4:~$ nohup: ignoring input and appending output to `nohup.out'
老版本的nohup不会把状态信息写到控制终端上,所以可能不会看到错误的状态。
ian@attic4:~$ nohup . pmc.sh >mynohup.out 2>&1 & [4] 5853 ian@attic4:~$ [4]+ Exit 126 nohup . pmc.sh > mynohup.out 2>&1
前面我們使用了jobs命令來查看job的ID。
另外有一个命令ps用来显示进程状态信息。请记住,ps是process status的缩写。ps可以接受0或者多个PID作为参数,从而显示这些进程的状态信息。如果我们使用jobs命令的-p选项,输出就只剩下了进程ID。我们可以使用这个结果作为ps的参数。
ian@attic4:~$ jobs -p 3878 5485 5487 ian@attic4:~$ ps $(jobs -p) PID TTY STAT TIME COMMAND 3878 pts/1 S 0:06 xclock -d -update 1 5485 pts/1 S 0:00 sh pmc.sh 5487 pts/1 S 0:00 bash pmc.sh
如果没有给ps提供选项,只有控制终端是当前终端的进程才会被显示出来。许多其他的选项,比如-f(ull), -j(obs), -l(ong)用来控制输出的信息量。如果你没有提供参数,那么--forest选项将会显示出进程的树形关系,如下。
ian@attic4:~$ ps -f UID PID PPID C STIME TTY TIME CMD ian 2643 2093 0 Jan18 pts/1 00:00:00 bash ian 3878 2643 0 09:17 pts/1 00:00:06 xclock -d -update 1 ian 5485 2643 0 15:00 pts/1 00:00:00 sh pmc.sh ian 5487 2643 0 15:01 pts/1 00:00:00 bash pmc.sh ian 6635 5485 0 15:41 pts/1 00:00:00 sleep 30 ian 6645 5487 0 15:42 pts/1 00:00:00 sleep 30 ian 6647 2643 0 15:42 pts/1 00:00:00 ps -f ian@attic4:~$ ps -j --forest PID PGID SID TTY TIME CMD 2643 2643 2643 pts/1 00:00:00 bash 3878 3878 2643 pts/1 00:00:06 \_ xclock 5485 5485 2643 pts/1 00:00:00 \_ sh 6657 5485 2643 pts/1 00:00:00 | \_ sleep 5487 5487 2643 pts/1 00:00:00 \_ bash 6651 5487 2643 pts/1 00:00:00 | \_ sleep 6658 6658 2643 pts/1 00:00:00 \_ ps
free用来查看系统的内存使用情况。默认的大小单位是KB,选项-b表示字节,-k表示KB,-m表示MB,-g表示GB。-t显示一个汇总行,-s则指定刷新显示的间隔,单位为秒,可以是小数。(译者注:-/+这行表示把缓存作用的内存空间排除后)
[root@centos192 work]# free total used free shared buffers cached Mem: 3924872 3216928 707944 0 521896 1744736 -/+ buffers/cache: 950296 2974576 Swap: 7574520 0 7574520 [root@centos192 work]# free -mt total used free shared buffers cached Mem: 3832 3141 691 0 509 1703 -/+ buffers/cache: 928 2904 Swap: 7396 0 7396 Total: 11229 3141 8088
uptime命令在一行中显示出如下信息:当前系统时间、自开机系统运行时间、登陆用户数、最近1,5,15分钟内的CPU平均负载。
[root@centos192 work]# uptime 16:16:51 up 99 days, 7:06, 1 user, load average: 0.00, 0.00, 0.00
前面我们使用ps命令只列出了控制终端是当前终端的进程,注意前面例子中的SID列。-a选项用来显示所有拥有控制终端的进程,-x显示所有没有控制终端的进程,-e则显示所有进程。
ian@attic4:~$ ps -af # 显示所有拥有控制终端的进程的全部信息 UID PID PPID C STIME TTY TIME CMD ian 3878 2643 0 09:17 pts/1 00:00:06 xclock -d -update 1 ian 5485 2643 0 15:00 pts/1 00:00:00 sh pmc.sh ian 5487 2643 0 15:01 pts/1 00:00:00 bash pmc.sh ian 7192 5485 0 16:00 pts/1 00:00:00 sleep 30 ian 7201 5487 0 16:00 pts/1 00:00:00 sleep 30 ian 7202 2095 0 16:00 pts/0 00:00:00 ps -af
注意,TTY这一列表示的就是进程的控制终端。ps还有很多其他的选项,很多是控制显示哪些信息以及如何显示。也有一些选项用来选择特定的进程,如-u选择特定用户的进程,-C选择特定命令的进程。
#用来显示命令getty的进程,并显示用户,PID,控制终端,和命令行状态列 ian@attic4:~$ ps -C getty -o user,pid,tty,time,comm USER PID TT TIME COMMAND root 1192 tty4 00:00:00 getty root 1196 tty5 00:00:00 getty root 1209 tty2 00:00:00 getty root 1219 tty3 00:00:00 getty root 1229 tty6 00:00:00 getty root 1731 tty1 00:00:00 getty有时候,你需要对数据结果进行某种排序。这可以通过--sort选项来指定。默认是按照升序排列。
ian@attic4:~$ ps -aj --sort -sid,+comm # 按照sid降序,comm升序排列所有拥有控制终端的进程 PID PGID SID TTY TIME CMD 5487 5487 2643 pts/1 00:00:00 bash 9434 9434 2643 pts/1 00:00:00 ps 5485 5485 2643 pts/1 00:00:00 sh 9430 5485 2643 pts/1 00:00:00 sleep 9433 5487 2643 pts/1 00:00:00 sleep 3878 3878 2643 pts/1 00:00:10 xclock 8019 8019 2095 pts/0 00:00:00 man 8033 8019 2095 pts/0 00:00:00 pager ian@attic4:~$ ps -aj --sort sid,comm # 按照sid升序,comm升序排列 PID PGID SID TTY TIME CMD 8019 8019 2095 pts/0 00:00:00 man 8033 8019 2095 pts/0 00:00:00 pager 5487 5487 2643 pts/1 00:00:00 bash 9435 9435 2643 pts/1 00:00:00 ps 5485 5485 2643 pts/1 00:00:00 sh 9430 5485 2643 pts/1 00:00:00 sleep 9433 5487 2643 pts/1 00:00:00 sleep 3878 3878 2643 pts/1 00:00:10 xclock更多的ps选项,参考man手册页以及ps --help。
如果需要动态的显示进程信息,要使用top命令。它用来实时显示最新的进程信息,而且还提供了有用的汇总。使用q命令可以退出。
top - 16:07:22 up 18:29, 5 users, load average: 0.03, 0.02, 0.00 Tasks: 170 total, 1 running, 169 sleeping, 0 stopped, 0 zombie Cpu(s): 2.1%us, 0.5%sy, 0.0%ni, 97.4%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Mem: 4057976k total, 1543616k used, 2514360k free, 194648k buffers Swap: 10241428k total, 0k used, 10241428k free, 613000k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 6820 ian 20 0 506m 78m 26m S 1 2.0 0:23.97 firefox 1381 root 20 0 634m 40m 18m S 1 1.0 2:06.74 Xorg 2093 ian 20 0 212m 15m 10m S 1 0.4 0:13.53 gnome-terminal 6925 ian 20 0 1118m 298m 19m S 1 7.5 1:07.04 java 6855 ian 20 0 73416 11m 8808 S 1 0.3 0:05.01 npviewer.bin 7351 ian 20 0 19132 1364 980 R 0 0.0 0:00.07 top 1 root 20 0 19584 1888 1196 S 0 0.0 0:00.74 init 2 root 15 -5 0 0 0 S 0 0.0 0:00.01 kthreaddtop命令本身提供了一些子命令:
top - 16:21:48 up 18:43, 5 users, load average: 0.16, 0.06, 0.01 Tasks: 170 total, 3 running, 167 sleeping, 0 stopped, 0 zombie Cpu(s): 2.1%us, 0.8%sy, 0.0%ni, 96.6%id, 0.0%wa, 0.0%hi, 0.5%si, 0.0%st Mem: 4057976k total, 1588940k used, 2469036k free, 195412k buffers Swap: 10241428k total, 0k used, 10241428k free, 613056k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 6925 ian 20 0 1171m 338m 21m S 0 8.5 1:44.10 java 1381 root 20 0 634m 40m 18m S 0 1.0 2:13.63 Xorg 6820 ian 20 0 506m 83m 26m S 3 2.1 0:51.28 firefox 2004 ian 20 0 436m 23m 15m S 0 0.6 0:01.55 nautilus 2031 ian 20 0 419m 13m 10m S 0 0.3 0:00.11 evolution-alarm 2118 ian 20 0 372m 10m 7856 S 0 0.3 0:00.06 evolution-data- 2122 ian 20 0 344m 13m 10m S 0 0.3 0:00.10 evolution-excha 2001 ian 20 0 331m 22m 14m S 0 0.6 0:13.61 gnome-panel 1971 ian 20 0 299m 9.9m 7244 S 0 0.3 0:05.00 gnome-settings- 1989 ian 20 0 288m 15m 11m S 0 0.4 0:11.95 metacity 1954 ian 20 0 265m 5460 3412 S 0 0.1 0:00.28 pulseaudio
现在让我们来看看Linux中的信号。这是进程间异步通信的机制。我们已经提到过SIGHUP这个信号,我们也使用了Ctrl-c和Ctrl-z来发送信号。通用的发送信号的命令是kill。
kill用来向指定的job或者进程发送信号。下面显示了使用SIGSTP和SIGCONT信号来停止和重启一个后台Job。使用SIGSTP的作用等同于先使用fg把Job带入前台然后使用Ctrl-z来暂停它。SIGCONT信号则等同于bg命令。
ian@attic4:~$ kill -s SIGTSTP %1 [1]+ Stopped xclock -d -update 1 ian@attic4:~$ jobs -l [1]+ 3878 Stopped xclock -d -update 1 [2] 5485 Running nohup sh pmc.sh & [3]- 5487 Running nohup bash pmc.sh & ian@attic4:~$ kill -s SIGCONT 3878 ian@attic4:~$ jobs -l [1] 3878 Running xclock -d -update 1 & [2]- 5485 Running nohup sh pmc.sh & [3]+ 5487 Running nohup bash pmc.sh &
我们使用了%1来指定job,然后使用了PID来重启它。
还有很多其他的信号,可以使用kill-l来查看它们。有些信号用来报告一些错误:非法操作符、浮点异常、非法访问内存等。信号不仅有名字,还有一个对应的数字值,如SIGSTP对应的值是20。可以使用-s 名字或者-数字。比如kill -20与kill -s SIGSTP是一样的。在使用数字前,一定要先确认在你的系统上信号和名字的对应情况,因为这并不是POSIX一致要求的。
你已经知道了Ctrl-c会终止一个进程。实际上,它是向进程发送了SIGINT信号。如果不带任何信号名,kill会发送SIGTERM信号。很多情况下,这两个信号是等价的。
你也知道了,nohup命令会让一个进程对SIGHUP失效。通常情况下,一个进程可以实现一个信号处理程序来捕获信号。因为信号处理程序能够知道接收到的是什么信号,所以可以做出忽略SIGINT信号,而在接受到SIGERM信号时终止当前进程。下面例子中,我们想job2所在的进程发送了SIGTERM信号,然后这个Job终止了。
ian@attic4:~$ kill -s SIGTERM %2 ian@attic4:~$ [2]- Terminated nohup sh pmc.sh ian@attic4:~$ jobs -l [1]- 3878 Running xclock -d -update 1 & [3]+ 5487 Running nohup bash pmc.sh &
信号处理程序让进程有了很大的灵活性。一个进程在自己工作的时候,亦可以被信号打断去做一些特殊的事情。信号除了可以用来提醒进程关闭资源外,还用来通知一些服务进程重新读取配置文件初始化。当你改变网络配置后,可能需要发送信号给inetd进程;当你增加一个打印机后,可能需要发送信号给lpd进程。
有些信号是无法被捕获到的,比如一些硬件异常。SIGKILL,这个信号是你最可能使用的,它就不能被信号处理程序捕获,用来无条件的强制结束一个进程。这个信号通常是在其他方法无法结束进程的时候最后的一种方式。
我们看到了,nohup命令可以让一个命令在用户登出后继续运行。现在我们先登出然后再登入来看看。重新登录以后,使用jobs和ps来查看继续运行的穷人钟表进程。
ian@attic4:~$ jobs -l ian@attic4:~$ ps -a PID TTY TIME CMD 10995 pts/0 00:00:00 ps我们没有发现我们的job,这可不是我们期望的。但是,一切并没有丢失。只是这个进程失去了控制终端了,所以看不到。我们可以先找到bash命令对应的SID,然后看看。
ian@attic4:~$ ps -C bash -C sh -o pid,sid,tname,cmd PID SID TTY CMD 5487 2643 ? bash pmc.sh 7050 7050 pts/3 -bash 10851 10851 pts/0 bash ian@attic4:~$ ps -js 2643 PID PGID SID TTY TIME CMD 5487 5487 2643 ? 00:00:00 bash 11197 5487 2643 ? 00:00:00 sleep
注意,pmc.sh依旧在运行,只是它的控制终端显示为?。考虑到你已经学习了如何结束一个进程,你应该能够使用kill来结束这个pmc了。