在最近的一次街头交易会上,我被单人乐队迷住了。 是的,我很容易被逗乐,但仍然给我留下了深刻的印象。 真正的独奏交响曲结合了口琴,班卓琴,和a鼓-分别在嘴,膝,膝盖和脚上–真正的独奏交响乐使齐柏林飞船(Led Zeppelin)的经典作品《通往天堂的阶梯》表现出色,并生动诠释了贝多芬的第五交响曲。 相比之下,我很幸运能拍拍头并串联揉肚子。 (或者是拍拍我的肚子然后擦我的头?)
幸运的是,UNIX®操作系统比笨拙的专栏作家更像单人乐队。 UNIX在一次兼顾许多任务的同时,还特别协调了对系统有限资源(内存,设备和CPU)的访问。 换而言之,UNIX可以轻松地同时走路和嚼口香糖。
本月,让我们比平常更深入地研究一下UNIX如何同时完成许多工作。 在摸索过程中,我们还瞥了一眼外壳的内部结构,以了解如何实现作业控制命令,例如Control-C(终止)和Control-Z(挂起)。 大灯亮! 到蝙蝠洞!
真正的多任务处理程序
在UNIX(以及大多数现代操作系统,包括Microsoft®Windows®,Mac OS X,FreeBSD和Linux®)上,每个计算任务都由一个进程表示。 UNIX似乎在同一时间运行许多任务,因为每个进程以(概念上)循环方式获得一小部分CPU时间。
流程就是一个容器,它捆绑了一个正在运行的应用程序,其环境变量,应用程序输入和输出的状态以及流程的状态,包括其优先级和累积的资源使用情况。 图1描绘了一个过程。
图1. UNIX流程的概念模型
如果有帮助,您可以将流程视为具有边界,资源和国内生产总值的自己的主权国家。
每个过程也都有一个所有者 。 您启动的任务(例如,shell和命令)通常归您所有。 系统服务可能由特殊用户或超级用户root拥有。 例如,为了增强安全性,Apache HTTP Server通常由名为www的专用用户拥有,该用户可以访问Web服务器所需的文件,但不能访问其他文件。
进程的所有权可能会更改,但在其他方面严格排他。 一个过程在任何给定时间只能有一个所有者。
最后(简化本介绍),每个进程都具有特权 。 通常,进程的特权与其所有者的特权相对应。 (例如,如果您无法从命令行外壳程序访问特定文件,则从外壳程序启动的程序会继承相同的限制。)此继承规则的例外,在该例外情况下,进程可能会获得比其所有者更大的特权,是启用了特殊setuid或setgid位的应用程序,如ls所示。
可以使用chmod u+s
设置setuid位。 setuid权限如下所示:
$ ls -l /usr/bin/top
-rwsr-xr-x 1 root wheel 83088 Mar 20 2005 top
可以使用chmod g+s
设置setgid位:
$ ls -l /usr/bin/top
-r-xr-sr-x 1 root tty 19388 Mar 20 2005 /usr/bin/wall
setuid进程(例如launching top )将以拥有该文件的用户的特权运行。 因此,当您运行top时,您的特权将提升为root的特权。 同样,setgid进程将以与文件的组所有者关联的特权运行。
例如,在Mac OS X上, wall实用程序(即“全部写入”的缩写,因为它会将消息写入每个物理或虚拟终端设备)是setgid tty (如上所示)。 登录并分配了要键入的终端设备后(终端成为Shell的标准输入),您将成为设备的所有者,而tty成为组所有者。 由于wall以tty组的特权运行,因此它可以打开并写入每个终端。
盘点
与所有其他系统资源一样,您的UNIX系统具有有限的进程池。 (实际上,系统几乎永远不会耗尽进程。)每个新任务(例如启动vi或运行xclock)都会立即从池中分配一个进程。 在UNIX系统上,可以使用ps
命令查看一个或多个进程。
例如,如果要查看自己拥有的所有进程,请输入ps -w --user username
:
$ ps -w --user mstreicher
您可以使用ps -a -w -x
查看整个进程列表。 ( ps
命令的格式和特定标志因UNIX风格而异,请参见UNIX风格。有关详细信息,请参见系统的联机文档。) -a
选择在tty设备上运行的所有进程; -x
进一步选择与tty无关的所有进程,这些进程通常包括所有永久性系统服务,例如Apache HTTP服务器, cron作业调度程序等; -w
显示宽格式,对于查看与每个进程关联的应用程序的命令行或完整路径名很有用。
ps
具有大量功能,有些ps
版本甚至允许您自定义输出。 例如,这是一个有用的自定义过程清单:
$ ps --user mstreicher -o pid,uname,command,state,stime,time
PID USER COMMAND S STIME TIME
14138 mstreic sshd: mstreicher S 09:57 00:00:00
14139 mstreic -bash S 09:57 00:00:00
14937 mstreic ps --user mstrei R 10:23 00:00:00
-o
根据命名列的顺序格式化输出。 pid
, uname
和command
分别是进程ID,用户名和命令。 state
反映了进程状态,例如正在Hibernate( S
)或正在运行( R
)。 (稍后将详细介绍进程状态。) stime
显示命令何时启动,而time
显示进程已消耗多少CPU时间。
爸爸,流程从何而来?
在UNIX上,某些进程从系统启动到关闭运行,但是随着任务的开始和完成,大多数进程来来往往。 有时,一个过程可能会过早地死亡,甚至导致可怕的死亡(例如由于崩溃)。 新流程从何而来?
每个新的UNIX进程都是现有进程的衍生 。 此外,每个新进程(我们称其为“子”进程)都是其“父”进程的克隆,至少是暂时的,直到子进程继续独立执行。 (如果每个新流程都是现有流程的后代,那将产生如下困惑:“第一个流程来自何处?”请参见下面的边栏以获取答案。)
图1-4详细说明了生成过程,嗯:
- 在图2和3中 ,进程A正在运行一个由蓝色框表示的程序。 它运行编号为10、11、12等的指令。 进程A具有自己的数据,程序的副本,打开的文件集以及环境变量的集合,它们最初是在进程A出现时捕获的。
图2.流程A运行代码
- 在UNIX中,使用
fork()
系统调用(之所以命名是因为它是对操作系统帮助的调用或请求),用于产生新进程。 当程序A在指令13中执行fork()
时,系统会立即创建一个精确的进程A的克隆,名为进程Z。Z具有与A相同的环境变量,相同的内存内容,相同的程序状态以及相同的文件打开。 紧接在进程A生成进程Z之后的进程A和Z的状态如图3所示。图3.进程A产生其自身的克隆
- 在开始时,进程Z在进程A停止的同一位置开始执行。 也就是说,在开始之后,进程Z从指令14开始执行。进程A在同一条指令处继续执行。
- 通常,指令14中的编程逻辑会测试当前进程是子进程还是父进程-也就是说,进程Z中的指令14和进程A中的指令14分别确定其进程是后代还是后代。 为了进行区分,
fork()
系统调用在后代中返回0,但将进程Z的进程ID返回到后代。 - 经过之前的测试,流程A和流程Z分开,分别采用单独的代码路径,就好像它们都走在了道路上,并且各自采用了不同的分支。 考虑到两个旅行者在路上叉的隐喻, 催生新过程的过程通常称为分叉 。 因此,系统调用名为
fork()
。
派生之后,进程A可能会继续运行相同的应用程序。 但是,进程Z可能会立即选择转换为另一个应用程序。 更改进程中正在运行的程序的后一种操作称为执行 ,但您可以将其视为轮回:尽管进程ID保持不变,但进程中的指令完全被新程序中的指令代替。 图4显示了一段时间后进程Z的状态。
图4.流程Z现在独立于其祖先流程A
分叉
您可以从自己的专用命令行舒适地体验分叉。 首先,打开一个新的xterm 。 (您现在可能已经意识到xterm是它自己的进程,并且在xterm中,shell是xterm产生的单独的进程)。 接下来,键入:
ps -o pid,ppid,uname,command,state,stime,time
您应该会看到以下内容:
PID PPID USER COMMAND S STIME TIME
16351 16350 mstreic -bash S 11:23 00:00:00
16364 16351 mstreic ps -o pid,ppid,u R 11:24 00:00:00
根据此列表中的PPID
字段, ps
命令是bash shell的子级。 ( -bash
的连字符表示该shell实例是登录shell。)要运行ps
,bash会创建一个新进程; 新进程将通过执行重新进行自我转换,从而成为ps
的新实例。
这是另一个尝试的实验。 类型:
sleep 10 & sleep 10 & sleep 10 & ps -o pid,ppid,uname,command,state,stime,time
您应该会看到以下内容:
$ sleep 10 & sleep 10 & sleep 10 & ps -o pid,ppid,uname,command,state,stime,time
PID PPID USER COMMAND S STIME TIME
16351 16350 mstreic -bash S 11:23 00:00:00
16843 16351 mstreic sleep 10 S 11:42 00:00:00
16844 16351 mstreic sleep 10 S 11:42 00:00:00
16845 16351 mstreic sleep 10 S 11:42 00:00:00
16846 16351 mstreic ps -o pid,ppid,u R 11:42 00:00:00
命令行产生了四个新进程。 打字符号( &
)每个后sleep
命令运行每个这些命令的背景下 ,或在与外壳平行。 ps
是另一个生成的进程,但是它在前台运行,从而阻止Shell在终止之前运行另一个命令。 同样,所有四个过程都是外壳的生成,如PPID的值所示。 这三个sleep
命令标记为S
,因为它们在睡眠时没有一个进程在消耗资源。
为了方便起见,shell会跟踪其产生的所有后台进程。 输入jobs
以查看列表:
$ sleep 10 & sleep 10 & sleep 10 &
[1] 16843
[2] 16844
[3] 16845
$ jobs
[1] Running sleep 10 &
[2] Running sleep 10 &
[3] Running sleep 10 &
为了方便起见,这里将三个作业标记为1、2和3。 数字16843、16844和16845是每个相应进程的进程ID。 因此,后台任务1是进程ID 16843。
您可以使用这些标签从命令行操作后台作业。 例如,要终止命令,请键入kill % N
,其中N
是命令的标签。 要将命令从后台移动到前景,请输入fg % N
:
$ sleep 10 & sleep 10 & sleep 10 &
[7] 17741
[8] 17742
[9] 17743
$ kill %7
$ jobs
[7] Terminated sleep 10
[8]- Running sleep 10 &
[9]+ Running sleep 10 &
$ fg %8
sleep 10
从命令行同时和异步运行多个命令是处理自己的任务集的好方法。 一项长期运行的工作(例如,数字运算或大型汇编)非常适合放在后台。 要捕获每个后台命令的输出,请考虑使用重定向运算符>
, >&
, >>
和>>&
将输出重定向到文件。 每当后台命令完成时,shell都会在下一个提示之前打印警报消息:
$ whoami
mstreicher
[8]- Done sleep 10
[9]+ Done sleep 10
$
通往天空中的巨大Craft.io池
有些进程可以永久存在(例如init),而有些进程可以将它们自己化身为新的形式(例如您的shell)。 最终,大多数流程都死于自然原因-一个程序运行完成。
此外,您可以将流程放置在一种悬浮的动画中,等待重新进行动画处理。 如前面的示例所示,您可以使用kill
提前终止进程。
如果命令在前台运行,并且您想挂起它,请按Control-Z :
$ sleep 10
(Press Control-Z)
[1]+ Stopped sleep 10
$ ps
PID PPID USER COMMAND S STIME TIME
18195 16351 mstreic sleep 10 T 12:44 00:00:00
为了方便起见,shell暂停了该命令并为其分配了标签。 您可以像以前一样使用此标签来终止作业或将其返回到前台。 您还可以使用bg
命令在后台恢复该过程:
bg %1
[1]+ sleep 10 &
如果命令在前台运行,并且您想终止它,请按Control-C :
$ sleep 10
(Press Control-C
$ jobs
$
您的外壳使挂起和终止进程变得容易,但是外壳的无辜底面下有一些伏都教徒在工作。 在内部,您的Shell使用UNIX 信号来影响进程的状态。 信号是一个事件,用于警告过程。 操作系统产生许多信号,但是您可以将信号从一个进程发送到另一个进程,甚至可以发出一个进程信号。
UNIX包含各种信号,其中大多数具有特殊目的。 例如,如果您将信号SIGSTOP
发送到进程,则该进程将挂起。 (有关信号的完整列表,请键入man 7 signal
或键入kill -L
)。 您可以使用kill
命令发送信号:
$ sleep 20 &
[1] 19988
$ kill -SIGSTOP 19988
$ jobs
[1]+ Stopped sleep 20
最初, sleep
命令在后台启动,进程ID为19988。发送SIGSTOP
,进程更改状态,变为挂起或停止。 发送另一个信号SIGCONT
可使该过程重新生效,并在中断的地方继续进行。
换句话说,每次按Control-Z时,shell都会将SIGSTOP
发送到前台进程。 bg
命令发送SIGCONT
。 然后Control-C发送SIGTERM
,它要求进程立即终止。
某些信号可能会被进程阻止,并且可以将应用程序设计为显式“捕获”信号并以特殊方式对每个事件做出React。 例如,按需启动其他网络服务的系统服务xinetd在收到SIGHUP
后重新读取其配置文件。 在Linux上,发送信号到init可以更改系统运行级别,甚至可以启动系统关闭。 (这是一个问题: kill %1
和kill 1
什么区别?)
一个进程甚至可以发出信号。 想象一下,您在编写游戏,想给用户五秒钟的响应时间。 您的代码可以设置一个五秒钟的计时器,然后继续,例如重绘屏幕。 当计时器用尽时, SIGALRM
将发送回您的进程。 zz! 时间到!
(这是问题的答案: kill %1
杀死标记为1的后台作业。kill kill 1
终止init,这是向操作系统发出的信号,它应该关闭整个计算机。)
在特殊情况下,还有其他信号从操作系统传输到进程。 内存冲突会刺激SIGSEGV
,立即终止进程,同时留下核心转储。 不能阻止或捕获一个特殊信号SIGKILL
,它会立即杀死一个进程。
与UNIX中的许多其他资源一样,您只能用信号通知您拥有的进程。 这样可以防止您终止重要的系统服务和其他用户的进程。 超级用户root可以发出任何进程信号。
更加神秘的魔法
UNIX有许多活动的组成部分。 它具有系统服务,设备,内存管理器等。 幸运的是,这些复杂的设计大多数都不会被看到,或者可以通过用户界面(例如外壳和窗口工具)方便使用。 更好的是,如果您想ps
,可以随时使用专用工具,例如top
, ps
和kill
。
现在您知道流程的工作原理,您可以成为自己的一人乐队。 只需一个请求: Freebird!
翻译自: https://www.ibm.com/developerworks/aix/library/au-speakingunix8/index.html