2012-03-13 30 views
9

Estoy tratando de escribir un contenedor que ejecutará un script como líder de la sesión. Estoy confundido por el comportamiento del comando de Linux setsid. Considere este script, llamado test.sh:comando de Linux 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 

La salida varía en función de si se ejecuta o de origen:

$ 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 

Por lo tanto, me parece que setsid vuelve inmediatamente cuando el guión es de origen, pero espera a su hijo cuando se ejecuta el script. ¿Por qué la presencia de una tty controladora tiene algo que ver con setsid? ¡Gracias!

Editar: Para aclarar, agregué el informe pid/ppid/sid a todos los comandos relevantes.

Respuesta

17

The source code of the setsid utility es realmente muy sencillo. Notará que solo fork() s si ve que su ID de proceso e Id. De grupo de procesos son iguales (es decir, si ve que es un líder de grupo de proceso) — y que nunca wait() s para su proceso hijo: si fork() s, entonces el proceso principal simplemente regresa inmediatamente. Si no sefork(), entonces se da la apariencia de wait() ing para un niño, pero en realidad lo que sucede es que es el niño, y es Bash que es wait() ing (como siempre lo hace). (Por supuesto, cuando realmente lo hace fork(), Bash no puede wait() para el niño que crea, porque procesa wait() para sus hijos, no sus nietos.)

Así que el comportamiento que se está viendo es una consecuencia directa de un comportamiento diferente:

  • cuando se ejecuta . ./test.sh o source ./test.sh o lo que sea — o para el caso, cuando se acaba de ejecutar directamente desde el setsid Bash prompt — Bash lanzará setsid con un nuevo ID de grupo de proceso para job control, por lo que setsid tendrá el mismo ID de proceso que su ID de grupo de proceso (es decir, es un líder de grupo de proceso), por lo que será fork() y no lo hará wait().
  • cuando se ejecuta ./test.sh o bash test.sh o lo que sea y se pone en marcha setsid, setsid serán parte del mismo grupo de procesos que el script que se está ejecutando, así que su proceso de identificación y proceso de identificación de grupo serán diferentes, por lo que ganó 't fork(), por lo que dará la apariencia de esperar (sin realmente wait() ing).
+4

Tienes razón. Me pregunto si valdría la pena proponer que 'setsid' tome una bandera extra, digamos' -w', en cuya presencia debería esperar a su hijo si hay uno. Tal como están las cosas, creo que su comportamiento es inconsistente: regresa inmediatamente si y solo si es dirigido por un líder de grupo (y se bifurca). Además, como dices, solo 'setsid' podría esperar a su hijo, la invocación' bash' no puede esperar a un nieto. –

+1

Sí; y solo en general, es un poco extraño "tenedor" sin "esperar". No creo que mi profesor de Sistemas Operativos de la universidad lo haya aprobado. :-PAG – ruakh

1

El comportamiento que observo es el que espero, aunque diferente del suyo. ¿Puedes usar set -x para asegurarte de que estás viendo las cosas bien?

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

Cuando se ejecuta ./test.sh 1, el padre del guión - la consola interactiva - es el líder de la sesión, por lo $$ != $SID y el condicional es cierto.

Al ejecutar . test.sh 1, el shell interactivo está ejecutando el script en proceso, y es su propio líder de sesión, por lo que $$ == $SID y el condicional es falso, nunca ejecutando el script secundario interno.

+0

Tienes razón; pero mi preocupación es qué sucede cuando el intérprete de comandos que lanza la secuencia de comandos (ya sea ejecutar o fuente) no es un líder de la sesión. Intenta agregar otro nivel de 'bash' como lo hice en mi ejemplo original. (Lo que intento lograr es ejecutar el guión como líder de sesión garantizado. Si asumo que es así, no tiene sentido tener el guión en primer lugar). –

0

No veo ningún problema con su secuencia de comandos como está. Añadí declaraciones adicionales en el código para ver lo que está sucediendo:

#!/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 

El caso que le concierne es:

./test.sh 1 

Y créeme ejecutar este script modificado y verá exactamente lo que está sucediendo. Si el shell que no es un líder de sesión ejecuta el script, simplemente va al bloque else. ¿Me estoy perdiendo de algo?

Veo ahora lo que quiere decir: Cuando haga ./test.sh 1 con su secuencia de comandos, el padre espera a que el hijo complete. el niño bloquea al padre. Pero si inicia al niño en segundo plano, notará que el padre completa antes del niño. Así que acaba de hacer este cambio en la secuencia de comandos:

 setsid bash test.sh & 
+1

La razón por la que quiero este código es para incorporarlo en una secuencia de comandos más compleja, que a su vez podría ser ejecutada a mano, o a mano, o incluso ejecutar por un motor de trabajo SGE por lo que sé. Lo que necesito es un comportamiento "consistente" de padres e hijos. Idealmente: _if_ 'setsid' es necesario (es decir, ya no soy líder de sesión) _then_ Me gustaría que el padre espere al niño. La parte _if_ es verdadera en ambos escenarios en mi ejemplo: parent 'PID'! = Parent' SID'. Lo que me molesta es que la parte _then_ no se cumple cuando el script se origina: el padre sale antes que el hijo. –

Cuestiones relacionadas