2012-03-13 15 views
9

Sto provando a scrivere un wrapper che eseguirà uno script come leader di sessione. Sono confuso dal comportamento del comando linux setsid. Considerate questo script, chiamato test.sh:comando 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 

L'uscita varia a seconda se è eseguito o di provenienza:

$ 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 

Quindi, mi sembra che setsid restituisce immediatamente quando lo script è di provenienza, ma aspetta il suo bambino quando viene eseguito lo script. Perché la presenza di un terminale di controllo non ha nulla a che fare con setsid? Grazie!

Modifica: per chiarimenti, ho aggiunto pid/ppid/sid a tutti i comandi pertinenti.

risposta

17

The source code of the setsid utility è in realtà molto semplice. Noterai che è solo fork() s se vede che l'ID del processo e l'ID del gruppo di processo sono uguali (cioè, se vede che è un leader del gruppo di processi) — e che non è mai wait() s per il suo processo figlio: se fork() s, quindi il processo genitore torna immediatamente. Se nonfork() che fa, allora si dà l'apparenza di wait() ing per un bambino, ma in realtà ciò che accade è solo che è il bambino, ed è Bash che è wait() ing (proprio come fa sempre). (Naturalmente, quando lo fa davvero fork(), Bash non può fare il wait() per il bambino che crea, perché i processi wait() per i loro figli, non i loro nipoti.)

Così il comportamento che si sta vedendo è una diretta conseguenza di un comportamento diverso:

  • quando si esegue . ./test.sh o source ./test.sh o roba del genere — o per quella materia, quando si esegue appena setsid direttamente dal Bash prompt — Bash avvierà setsid con un nuovo ID del gruppo di processi per gli scopi job control, quindi setsid avrà lo stesso ID di processo del relativo ID del gruppo di processi (ovvero, è un leader del gruppo di processi), quindi sarà fork() e non sarà wait().
  • quando si esegue ./test.sh o bash test.sh o roba del genere e si lancia setsid, setsid sarà parte dello stesso gruppo di processi dello script che è in esecuzione, quindi il suo processo-ID e di processo-group-ID sarà diverso, così ha vinto 't fork(), quindi darà l'aspetto di aspettare (senza effettivamente wait() ing).
+4

Hai ragione. Mi chiedo se valga la pena di proporre che 'setsid' prenda una bandiera in più, diciamo' -w', sulla cui presenza dovrebbe aspettare il suo bambino se ce n'è uno. Così com'è, sento che il suo comportamento è incoerente: ritorna immediatamente se e solo se è gestito da un capogruppo (e si biforca). Inoltre, come dici tu, solo 'setsid' può aspettare il suo bambino, il bash di invocazione non può aspettare un nipotino. –

+1

Sì; e, in generale, è un po 'strano "fork" senza "wait'ing". Non credo che il mio professore di Sistemi operativi universitari avrebbe approvato. :-P – ruakh

1

Il comportamento che osservo è quello che mi aspetto, anche se diverso dal tuo. Puoi usare set -x per assicurarti di vedere le cose nel modo giusto?

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

Quando si esegue ./test.sh 1, genitore dello script - la shell interattiva - è il leader di sessione, in modo $$ != $SID e il condizionale è vero.

Quando si esegue . test.sh 1, la shell interattiva sta eseguendo lo script in-process ed è il proprio leader di sessione, quindi $$ == $SID e il condizionale è falso, quindi non esegue mai lo script secondario interno.

+0

Hai ragione; ma la mia preoccupazione è ciò che accade quando la shell che lancia lo script (sia execute che source) non è _non_ un leader di sessione. Prova ad aggiungere un altro livello di 'bash' come ho fatto nel mio esempio originale. (Quello che sto cercando di ottenere è di eseguire lo script come leader di sessione garantito.Se presumo che sia il caso, non ha senso avere lo script in primo luogo.) –

0

Non vedo alcun problema con il tuo script così com'è. Ho aggiunto dichiarazioni in più nel codice per vedere che cosa sta accadendo:

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

Il caso che La riguardano è:

./test.sh 1 

E credimi eseguire questo script modificato e vedrete esattamente cosa sta succedendo. Se la shell che non è un leader di sessione esegue lo script, va semplicemente al blocco else. Mi sto perdendo qualcosa?

Ora capisco cosa intendi: quando fai lo ./test.sh 1 con il tuo script così com'è, il genitore aspetta che il bambino sia completato. il bambino blocca il genitore. Ma se si avvia il bambino in background, si noterà che il genitore si completa prima del bambino. Quindi, solo fare questo cambiamento nello script:

 setsid bash test.sh & 
+1

Il motivo per cui voglio questo pezzo di codice è quello di incorporalo in uno script più complesso, che a sua volta può essere eseguito a mano, o estratto a mano, o addirittura eseguito da un motore di lavoro SGE per quel che ne so. Ciò di cui ho bisogno è un comportamento coerente di genitori e figli. Idealmente: _if_ 'setsid' è necessario (ad es., Non sono già il leader di sessione) _then_ Vorrei che il genitore aspettasse il bambino. La _if_ parte è vera in entrambi gli scenari nel mio esempio: parent 'PID'! = Parent' SID'. Quello che mi infastidisce è che la parte _then_ non viene conservata quando lo script è originario: il genitore esce prima di un figlio. –

Problemi correlati