2012-03-13 186 views
9

我正在尝试编写一个将执行脚本作为会话负责人的包装器。 我对linux命令setsid的行为感到困惑。考虑这个剧本,叫test.shlinux命令setsid

#!/bin/bash 
SID=$(ps -p $$ --no-headers -o sid) 
if [ $# -ge 1 -a $$ -ne $SID ] ; then 
    setsid bash test.sh 
    echo pid=$$ ppid=$PPID sid=$SID parent 
else 
    sleep 2 
    echo pid=$$ ppid=$PPID sid=$SID child 
    sleep 2 
fi 

输出的不同取决于它是否被执行或来源:

$ bash 
$ SID=$(ps -p $$ --no-headers -o sid) 
$ echo pid=$$ ppid=$PPID sid=$SID 
pid=9213 ppid=9104 sid= 9104 
$ ./test.sh 1 ; sleep 5 
pid=9326 ppid=9324 sid= 9326 child 
pid=9324 ppid=9213 sid= 9104 parent 
$ . ./test.sh 1 ; sleep 5 
pid=9213 ppid=9104 sid= 9104 parent 
pid=9336 ppid=1 sid= 9336 child 
$ echo $BASH_VERSION 
4.2.8(1)-release 
$ exit 
exit 

所以,在我看来,立即setsid返回时脚本来源,但它在脚本执行时等待它的孩子。 为什么控制tty的存在与setsid有什么关系?谢谢!

编辑:为了说明起见,我添加了pid/ppid/sid报告给所有相关的命令。

回答

17

The source code of the setsid utility实际上非常简单。如果它看到它的进程ID和进程组ID相同(即,如果它看到它是一个进程组的领导者),并且它从未为其子进程wait() s,那么您将注意到它只有fork():如果它fork() s,那么父进程立即返回。如果它不是fork(),那么它为wait()给出一个孩子的外观,但真正发生的只是它孩子,而它的Bash是wait() ing(就像它总是那样)。 (当然,当它确实为fork(),Bash不能为它创建的子项wait(),因为子进程wait(),而不是他们的孙辈。)

所以,你看到的行为是不同的行为的直接后果:

当您运行
  • . ./test.shsource ./test.sh或诸如此类的东西—或与此有关,当你刚刚从运行setsid直接Bash提示— Bash将启动setsid,并带有用于job control目的的新进程组标识,因此setsid将具有与其进程组ID相同的进程标识(即它是进程组标识),因此它将会fork()并不会wait()
  • 当您运行./test.shbash test.sh或诸如此类的东西,它推出setsidsetsid将是相同的进程组为正在运行它的脚本的一部分,所以它的进程ID和进程组ID会有所不同,因此赢得了't fork(),所以它会显示等待(实际上没有wait() ing)。
+4

你说得对。我想知道是否值得提议'setsid'带上一个额外的标志,例如'-w',如果有的话,它应该等到它的孩子出现在那儿。事实上,我觉得它的行为是不一致的:当且仅当它由组长领导(并且分叉)时它立即返回。另外,就像你说的,只有'setsid'可以等待它的孩子,调用的'bash'不能等待一个孙子。 – 2012-03-19 13:14:04

+1

是的;一般来说,没有“等待”的“fork”有点奇怪。我不认为我的大学操作系统教授会批准。 :-P – ruakh 2012-03-19 13:21:52

1

我观察到的行为是我期望的,尽管与您的不同。你可以使用set -x来确保你看到的东西正确吗?

 
$ ./test.sh 1 
child 
parent 
$ . test.sh 1 
child 
$ uname -r 
3.1.10 
$ echo $BASH_VERSION 
4.2.20(1)-release 

当运行./test.sh 1,脚本的母公司 - 交互式shell - 是会议的领导者,所以$$ != $SID和条件是真实的。

运行. test.sh 1时,交互式shell正在执行脚本进程中,并且是它自己的会话引导程序,所以$$ == $SID和条件为false,因此从不执行内部子脚本。

+0

你是对的;但我担心的是当启动脚本的shell(执行或源代码)不是会话负责人时发生的情况。尝试像我在原始示例中那样添加另一个“bash”级别。 (我试图实现的是将脚本作为保证会话负责人运行,如果我认为是这种情况,那么将脚本放在第一位毫无意义。) – 2012-03-13 03:54:03

0

我觉得你的脚本没有任何问题。我添加了额外的语句在你的代码,看看发生了什么:

#!/bin/bash 

    ps -H -o pid,ppid,sid,cmd 

    echo '$$' is $$ 

    SID=`ps -p $$ --no-headers -o sid` 

    if [ $# -ge 1 -a $$ -ne $SID ] ; then 
     setsid bash test.sh 
     echo pid=$$ ppid=$PPID sid=$SID parent 
    else 
     sleep 2 
     echo pid=$$ ppid=$PPID sid=$SID child 
     sleep 2 
    fi 

你关注的是这样的:

./test.sh 1 

相信我运行这个修改后的脚本,你会看到究竟发生了什么。如果不是会话负责人的shell运行脚本,那么它只是去else块。我错过了什么吗?

我现在明白你的意思了:当你用脚本做./test.sh 1时,父母等待孩子完成。孩子阻止父母。但是如果你在后台启动孩子,那么你会发现父母在孩子之前完成。因此,只需在脚本中进行以下更改:

 setsid bash test.sh & 
+1

我想要这段代码的原因是将它整合到一个更复杂的脚本中,而这个脚本又可以手动执行,也可以手工执行,或者甚至可以由我知道的SGE作业引擎运行。我需要的是父母和孩子的_consistent_行为。理想情况下:_if_'setsid'是必要的(即,我还不是会议的领导者),然后_我希望父母等待孩子。在我的例子中,_if_部分在这两种情况下都是正确的:父'PID'!=父'SID'。让我感到困惑的是,当脚本来源时,_then_部分不成立:parent在孩子之前退出。 – 2012-03-13 16:33:38