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的子进程,并且ps是cat的子进程。它看起来是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它已经结束了。
现在让我们在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
|
ps和cat1这两个进程都在新的进程组(5799)中,并且这次是在前台进程组中。能在本例和Bourne Shell的例子中找到一些不同之处。Bourne Shell在管道符中先创建了最后一个进程,而最后一个进程是第一个进程的父进程。这里Bourne-again Shell是两个进程的父进程。如果我们在管道中执行:
ps -o pid,ppid,pgrp,session,tpgid,comm | cat1 &
|
结果是一样的,只是ps和cat1在同一个后台进程组中:
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有所不同。