UNIX 进程--多任务原理

来源:互联网 发布:上传文件夹到linux 编辑:程序博客网 时间:2024/04/25 23:59

了解 UNIX 的多任务原理

级别: 中级        Martin Streicher(martin.streicher@linux-mag.com), 主编,Linux Magazine

2007 年 5 月 16 日

在 UNIX® 系统中,每个系统和最终用户任务都包含在某个进程中。系统总是不断地创建新的进程,当任务结束或意外发生时,进程会终止。在本文中,您将了解如何控制进程和使用一些命令来查看您的系统。

在最近的街头游乐 会上,有一个单人乐队让我很是着迷。的确,这让我很开心,还给我留下了深刻印象。这个单人乐队的唯一成员利用嘴、大腿、膝盖和脚分别控制口琴、五弦琴、钹 和脚鼓,生动地演奏了齐柏林飞船乐队的《天堂的阶梯》,他演奏的贝多芬《第五交响曲》也颇为动人。和他相比,我能一边拍脑袋一边摸肚子就觉得很不错了。 (或者是一边拍肚子一边摸脑袋。)

对您来说,幸运的是,UNIX® 操作系统更像是那个单人乐队,而不是像我这个笨手笨脚的专栏作家。UNIX 特别擅长同时处理多个任务,并安排它们访问系统中的有限资源(内存、设备和 CPU)。打个比方,UNIX 可以一边散步,一边嚼口香糖。

这个月我们研究的内容要比平常更深入一些,我们会看看 UNIX 是如何同时做这么多事的。这次我们还会探索 shell 的内部,了解工作控制命令,如 Ctrl+C(终止)和 Ctrl+Z(挂起)是怎样实现的。

一个真正的多任务系统

在 UNIX(以及大多数现代操作系统,包括 Microsoft® Windows®、Mac OS X、FreeBSD 和 Linux®)中,每个计算任务都是由一个进程表示的。UNIX 似乎能同时运行很多任务,这是因为每个进程都会轮流(从概念上来讲)分到一小片 CPU 时间。

一个进程就像一个容器,它与某个正在运行的应用程序、环境变量、应用程序的输入和输出,以及进程的状态(包括其优先级和累计资源使用情况)捆绑在一起。图 1 显示了一个进程。


图 1. UNIX 进程的概念化模型
UNIX 进程的表示形式

为了便于理解,您可以把一个进程想像成一个独立的国家,有边界、资源,还有国民生产总值。

每个进程还有一个所有者。一般来说,您启动的任务(如您的 shell 和命令)的所有者就是您。系统服务的所有者可能是特殊用户或超级用户 root。例如,为了增强安全性,Apache HTTP Server 的所有者一般是一个名为www 的专用用户,该用户能提供 Web 服务器所需的的文件访问权限,但不包含其他权限。

进程的所有权可能会改变,但必须严格保持其独占性。一个进程在任何时候都只能有一个所有者。

最后,每个进程都具有权限。一般来说,进程的权限与其所有者的权限是相称的。(例如,如果您无法在命令行 Shell 中访问某个特定文件,则您从 Shell 中启动的程序也会继承同样的限制。)这一继承规则有一个例外情况,即应用程序启用了特殊的setuidsetgid 位,如 ls 显示的那样,在此情况下,某个进程可能会获得比其所有者更高的权限。

setuid 位可以使用 chmod u+s 进行设置。setuid 的权限如下所示:

$ ls -l /usr/bin/top
-rwsr-xr-x 1 root wheel 83088 Mar 20 2005 top

setgid 位可以使用 chmod g+s 设置:

$ ls -l /usr/bin/top
-r-xr-sr-x 1 root tty 19388 Mar 20 2005 /usr/bin/wall

一个 setuid 进程(如启动 top)是用拥有该文件的用户权限运行的。因此,当您运行 top 时,您的权限会被提升,与root 的权限等同。类似地,一个 setgid 进程是用与文件的组所有者相关联的权限运行的。

例如,在 Mac OS X 中,wall 工具(“write all”的缩写,因其会将某个消息写入所有物理或虚拟终端设备而得名)的 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 版本而有所差异。请参阅系统的联机文档,以查找具体的说明。)-a 是选择 tty 设备上运行的所有进程;-x 则可进一步选择与 tty 无关的所有进程,通常包括所有的永久系统服务,如 Apache HTTP server、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 根据各列名称的顺序对输出进行格式化。pidunamecommand 分别指进程 ID、用户名和命令。state 代表进程的状态,如正在睡眠 (S) 或运行 (R)。(稍后将对进程状态进行更详细的说明。)stime 显示命令的开始时间,time 则显示该进程占用了多少 CPU 时间。

进程从哪里来?

在 UNIX 中,某些进程会从系统启动到关机的时间里一直运行,但大多数进程都会随任务的开始和完成而迅速地出现和消失。有时,某个进程可能会“早夭“,甚至会“暴死”(比如在系统崩溃时)。新的进程是从哪里来的呢?

每个新的 UNIX 进程都是某个现有进程的产物。另外,每个新进程(不妨将其称为“子”进程)是对“父”进程的克隆体(至少有一瞬间是如此),直到“子”进程继续独立执行为止。(如果每个进程都是某个现有进程的后代,那么不免会有一个疑问:“第一个进程是从哪里来的?”请参阅下面的侧栏以寻找答案。)

鸡和蛋

某些争论是经久不息的:生存还是毁灭?可口可乐还是百事可乐?PC 还是 Mac?当然,还有一个古老的悖论,“鸡生蛋,还是蛋生鸡?”

如果每个新的 UNIX 进程都是某个现有的、正在运行的进程的后代,那么第一个进程是从哪里来的?答案是:UNIX 内核在系统启动序列中产生了第一个进程。

第一个进程被恰如其分地称为 init,所有其他系统进程的亲缘关系最终都可以追溯到 init。实际上,init 的进程编号是 1。如果您要查看init 的状态,可键入 ps -l 1

F S UID PID PPID  C PRI  NI ADDR SZ WCHAN  TTY TIME CMD
4 S 0 1 0 0 68 0 - 373 select ? 0:02 init [2]

正如您所看到的,init 的所有者 (UID) 是 0 (root)。和系统中所有其他进程不同的是,init 没有父进程,它的父进程 ID (PPID) 为0

图 1-4 详细说明了进程的产生过程:

  1. 在图 2 和图3 中,进程 A (Process A),正在运行一个由蓝色方框表示的程序。它运行编号为 10,11,12…的指令。进程 A 有属于自己的数据、程序的副本、打开的文件集,以及自己的环境变量集,当进程 A 刚出现时,会对它们进行初次捕捉。

    图 2. 进程 A 运行代码
    在某个进程内运行代码

  2. 在 UNIX 中,fork() 系统调用(之所以有这个名称,是因为它是一个调用或请求,要求操作系统进行协助)被用来产生新的进程。当程序 A (Program A) 执行指令 13 (Instruction 13)fork() 时,系统会立即创建进程 A 的一个精确克隆版本,并将其命名为进程 Z (Process Z)。Z 具有和 A 相同的环境变量、相同的内存内容、相同的程序状态,打开的文件也一样。图 3 显示的是进程 A 生成进程 Z后,进程 A 和 Z 的状态。

    图 3. 进程 A 生成自身的克隆体
    生成的进程的表现形式

  3. 起初,进程 Z 是从进程 A 停止的地方开始执行的。也就是说,此后进程 Z 从指令 14 (Instruction 14) 处开始执行。进程 A 会在同一指令位置继续执行。
  4. 一般来说,指令 14 处的编程逻辑将测试当前的进程是子进程还是父进程,也就是说,进程 Z 和进程 A 中的指令 14 分别判定这两个进程是否为其他进程的后代或祖先。为了以示区别,fork() 系统调用在子进程中返回 0,但返回给父进程的却是进程 Z 的进程 ID。
  5. 在上次测试之后,进程 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 命令后键入 &,在后台运行每一个命令,或与 Shell 并行。ps 是生成的另一个进程,但它是在前台运行的,可以防止 shell 在该进程终止之前运行其他命令。而且,如 PPID 的值所示,所有四个进程都是 Shell 的后代。三个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
$

向遥远的进程池前进

某些进程会一直存活(如 init),而某些进程会以新的形式重生(如您的 shell)。最终大多进程都会因自然原因(即程序运行结束)而消亡。

此外,您还可以将某个进程放在一个挂起的动作序列中,等待被再次激活。正如先前的示例所示,您可以用 kill 提前终止某个进程。

当某个命令在前台运行时,如果您希望将它挂起,请按 Ctrl + 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 &

当某个命令在前台运行时,如果您想终止它,请按 Ctrl + C

$ sleep 10
(Press Control-C
$ jobs
$

您的 Shell 能使进程的挂起和终止变得更容易,但在 Shell 单纯的外表下,却隐藏着复杂的一面。在内部,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,重新激活进程,该进程将从上次停止的地方继续执行。

也就是说,每次您按 Ctrl + Z 时,您的 shell 将向前台发送SIGSTOP 信号。bg 命令发送 SIGCONT。而 Ctrl + C 则会发送SIGTERM,要求立即终止进程。

一些信号可以被某个进程阻塞,应用程序可以通过设计,显式地“捕捉 (catch)”信号,并以一种特殊的方式对每个事件作出反应。例如,系统服务xinetd 会按需要启动其他网络服务,它在收到 SIGHUP 时会重新读取它的配置文件。在 Linux 中,向 init 发送信号,可能会改变系统的运行级别,甚至会导致系统关闭。.(这里有一个问题:kill %1kill 1 有什么区别?

进程甚至可以给自己发送信号。想像一下,您正在编写一个游戏,想留给用户五秒钟时间作出反应。您的代码可以设置一个五秒钟的定时器,接下来继续进行重绘屏幕等操作。当定时器的时间耗尽后,将有一个SIGALRM 信号被送回您的进程。呯!时间到!

(这里提供了问题的答案:kill %1 会终止标签为 1 的后台工作。kill 1 会终止 init,当必须关闭计算机时,将向操作系统发送这个信号。)

在特殊情况下,操作系统还可以将一些其他信号传送给进程。内存违例会引发 SIGSEGV 信号,立即终止进程,并留下一个内核转储。有一个特殊的信号SIGKILL 是无法被阻塞或捕捉的,它会立即终止某个进程。

和 UNIX 中许多其他资源一样,您只能向您拥有的进程发送信号。这可以防止您终止重要的系统服务和其他用户的进程。超级用户 root 可以向任何进程发送信号。

更多魔法揭密

UNIX 有许多可活动的部分。它有系统服务、设备、内存管理器等等。好在这些复杂的花样大都被隐藏起来,不会被看到,或可以通过用户界面(如 shell 或窗口工具)很方便地使用。更妙的是,如果您想深入探究,随时都可以使用top, pskill 等专用工具。

现在您已经知道了进程的工作原理,可以组成自己的单人乐队了。只有一个要求:成为一只自由自在的飞鸟!

共享本文……

digg请 Digg 这个故事del.icio.us发布到 del.icio.usSlashdotSlashdot 一下!

参考资料

学习
  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文

  • 对话 UNIX:查看本系列中的其他部分。

  • AIX and UNIX:AIX and UNIX developerWorks 专区提供了大量与_ AIX 系统管理的所有方面相关的信息,您可以利用它们来扩展自己的 UNIX 技能。

  • New to AIX and UNIX:访问 New to AIX and UNIX页面可了解更多关于_ AIX 和 UNIX 的内容。

  • AIX 5L™ Wiki:AIX 相关技术信息的协作环境。

  • 查看由 Martin Striecher 编写的其他文章和教程:
    • Across developerWorks and IBM

  • 按主题搜索“AIX and UNIX”库:
    • 系统管理
    • 应用程序开发
    • 性能
    • 移植
    • 安全性
    • 提示
    • 工具和实用程序
    • Java™ 技术
    • Linux
    • 开放源代码

  • Safari 书店:访问此电子参考资料库可查找特定的技术资源。


获得产品和技术
  • IBM 试用软件:从 developerWorks 可直接下载这些试用软件,您可以利用它们开发您的下一个项目。


讨论
  • 参与 developerWorks Blog,从而加入到 developerWorks 社区中来。

  • 参与“AIX and UNIX”论坛:
    • AIX 5L——技术论坛
    • AIX for Developers 论坛
    • 集群系统管理
    • IBM Support Assistant
    • 性能工具——技术
    • 虚拟化——技术
    • 更多“AIX and UNIX”论坛

  • zsh:在 zsh wiki 协作、讨论和分享您的 zsh 专业知识。
 
0 0
原创粉丝点击