SHELL执行程序

SHELL执行程序

让我们看一下shell如何执行程序,并且了解一下它和进程组的概念、控制终端和会话之间的关系。要了解这些我们就要使用ps命令。

首先, 在不solaris上运行不支持任务控制的Bourne shell。如果我们执行:

ps -o pid,ppid,pgid,sid,comm

输出:

     PID  PPID  PGID  SID  COMMAND
     949   947   949  949  sh
    1774   949   949  949  ps

ps的父进程是shell,这和我们预计的一样。shell和ps命令都在同一个会话中,并且都在同一个前台进程组(949)中。

如果我们在后台执行这条命令:

ps -o pid,ppid,pgid,sid,comm &

唯一改变的值是进程ID:

        PID  PPID  PGID  SID COMMAND
        949   947   949  949 sh
       1812   949   949  949 ps

这个shell并不知道任务控制,所以后台任务没有进入它自己的进程组中,并且控制终端没有离开后台任务。

现在看一下Bourne shell如何处理管道,当我们执行:

ps -o pid,ppid,pgid,sid,comm | cat

输出:

    PID  PPID  PGID  SID COMMAND
    949   947   949  949 sh
   1823   949   949  949 cat1
   1824  1823   949  949 ps

(cat1只是标准cat程序用了不同名字的一个拷贝,如果有其它拷贝,就会出现cat2)注意cat是shell的子进程,并且pscat的子进程。它看起来是shell fork了一个它自己的拷贝,并且这个拷贝又fork了管道符号之前每一个进程。

如果我们在后台程序中使用管道符号:

ps -o pid,ppid,pgid,sid,comm | cat1 &

只有进程ID改变。因为shell不处理任务控制,后台进程的进程组ID保留了949,做为会话的进程组ID。

如果后台进程尝试从控制终端读取数据会发生什么?例如,我们执行:

cat > temp.foo &

在有任务控制时,会把后台任务放入后台进程组,如果后台进程尝试读取控制终端,它会导致产生一个SIGTTIN信号。在没有任务控制的情况下,如果进程没有重定向自己的标准输入,那么shell自动地重定向后台进程的标准输入到/dev/null。从/dev/null中读取数据,会得到文件结尾标记。这意味着我们的后台cat进程会立即读取一个文件结尾并结束。

上面描述的是通过标准输入的方式,如果后台进程指定打开/dev/tty,并从控制终端中读取,会发生什么?答案是“看情况”,但通常不是我们想要的结果。例如:

crypt < salaries | lpr &

在后台运行它,但crypt程序打开/dev/tty,改变终端特性(关闭反馈(to disable echoing)),从设备读取,并重置终端特性。当我们在后台执行这个管道符号(pipeline)时,来自crypt程序的“Password:”提示会被打印到终端上,但是我们向shell输入任何东西,它都会把输入当做命令的名字来执行。下一行我们的输入会被shell当做密码来使用,并且没有正确加密,再发送一些没用的东西到打印机。这里我们需要两个进程在同一时刻读取同一设备,这个结果依赖系统。像之前描述的任务控制,它善于处理这类问题。

回到我们Bourne shell的例子中,如果我们在管道符中执行三个进程,可以考验这个shell的进程控制:

ps -o pid,ppid,pgid,sid,comm | cat1 | cat2

产生下面的输出:

     PID  PPID  PGID  SID COMMAND
     949   947   949  949 sh
    1988   949   949  949 cat2
    1989  1988   949  949 ps
    1990  1988   949  949 cat1

注意:在管道符中的最后一个进程是shell的子进程,在它之前的进程都是该进程的子进程。下图所示发生了什么。因为在管道符中最后一个进程是登陆shell的子进程,所以在它结束时会通知shell它已经结束了。
figure9-9

现在让我们在linux上使用带任务控制的shell测试一下同样的例子。这显示了这些shell处理后台任务的方法。我们将会使用Bourne-again shell;结果和其它任务控制的shell相同。

ps -o pid,ppid,pgrp,session,tpgid,comm

输出:

     PID  PPID  PGRP  SESS  TPGID COMMAND
    2837  2818  2837  2837   5796 bash
    5796  2837  5796  2837   5796 ps

(前台进程使用了粗字体)我们立刻得到了和Bourne shell不同的结果。Bourne-again shell把前台任务(ps)放到它自己的进程组中(5796)。ps命令是“进程组长”并且是该进程组中的唯一进程。

另外,因为它有控制终端,所以这个进程组是前台进程组。当执行ps命令时我们的登陆shell是后台进程组。注意:两个进程组2837和5796都是同一会话的成员。实际上,在我们的例子里,在这个会话中会话是不会改变的。

在后台执行这个进程:

ps -o pid,ppid,pgrp,session,tpgid,comm &

将会输出:

     PID  PPID  PGRP  SESS  TPGID COMMAND
    2837  2818  2837  2837   2837 bash
    5797  2837  5797  2837   2837 ps

ps命令在它自己的进程组中,但这次进程组(5797)不再是前台进程组了。它是一个后台进程组。TPGID的2837指出前台进程组是我们的登陆SHELL。

在管道符中执行两个进程,如:

ps -o pid,ppid,pgrp,session,tpgid,comm | cat1

将会输出:

     PID  PPID  PGRP  SESS  TPGID COMMAND
    2837  2818  2837  2837   5799 bash
    5799  2837  5799  2837   5799 ps
    5800  2837  5799  2837   5799 cat1

pscat1这两个进程都在新的进程组(5799)中,并且这次是在前台进程组中。能在本例和Bourne Shell的例子中找到一些不同之处。Bourne Shell在管道符中先创建了最后一个进程,而最后一个进程是第一个进程的父进程。这里Bourne-again Shell是两个进程的父进程。如果我们在管道中执行:

ps -o pid,ppid,pgrp,session,tpgid,comm | cat1 &

结果是一样的,只是pscat1在同一个后台进程组中:

     PID  PPID  PGRP  SESS  TPGID COMMAND
    2837  2818  2837  2837   2837 bash
    5801  2837  5801  2837   2837 ps
    5802  2837  5801  2837   2837 cat1

注意:顺序可能会根据不同的shell有所不同。

Comments are closed.