Por que a subshell criada pelo operador de controle de plano de fundo (&) não é exibida sob o pstree?

5

Eu entendo que quando eu executo exit ele termina meu shell atual porque o comando exit é executado no mesmo shell. Eu também entendo que quando eu executo exit & então o shell original não terminará porque & garante que o comando seja executado no sub-shell resultando que exit terminará este sub-shell e retornará ao shell original. Mas o que eu não entendo é por que os comandos com e sem & parecem exatamente os mesmos em pstree , nesse caso sleep 10 e sleep 10 & . 4669 é o PID de bash sob o qual primeiro sleep 10 e, em seguida, sleep 10 & foram emitidos e a seguinte saída foi obtida de outra instância de shell durante esse tempo:

# version without &
$ pstree 4669
bash(4669)───sleep(6345)

# version with &
$ pstree 4669
bash(4669)───sleep(6364)

A versão com & não deve conter mais um sub-shell gerado (por exemplo, neste caso com o PID 5555), como este?

bash(4669)───bash(5555)───sleep(6364)

PS: O código a seguir foi omitido da saída de pstree para melhor legibilidade:

systemd(1)───slim(1009)───ck-launch-sessi(1370)───openbox(1551)───/usr/bin/termin(4510)───bash(4518)───screen(4667)───screen(4668)───
    
por Wakan Tanka 20.01.2016 / 23:22

1 resposta

7

Até que comecei a responder a essa pergunta, não percebi que usar o & operador de controle para executar um trabalho em segundo plano inicia um subshell. Subshells são criados quando os comandos são colocados entre parênteses ou fazem parte de um pipeline (cada comando em um pipeline é executado em sua própria sub-rede).

A seção Listas de Comandos do manual de Bash ( obrigado jimmij ) afirma:

If a command is terminated by the control operator ‘&’, the shell executes the command asynchronously in a subshell. This is known as executing the command in the background. The shell does not wait for the command to finish, and the return status is 0 (true).

Pelo que entendi, quando você executa sleep 10 & o garfo s do shell para criar um novo filho processo (uma cópia de si mesmo) e, em seguida, imediatamente exec s para substituir esse processo filho pelo código da parte externa comando ( sleep ). Isso é semelhante ao que acontece quando um comando é executado normalmente (em primeiro plano). Veja o artigo do Wikipedia sobre o fork-exec para uma breve descrição deste mecanismo.

Eu não conseguia entender por que o Bash executaria comandos em segundo plano em um subshell, mas faz sentido se você também deseja executar os shell builtins como exit ou echo para serem executados em segundo plano (não apenas comandos externos).

Quando se trata de um shell embutido em execução em segundo plano, o fork acontece (resultando em um subshell) sem uma chamada exec para substituir-se por um comando externo. A execução dos comandos a seguir mostra que quando o comando echo é empacotado em chaves e executado em segundo plano (com o & ), uma subshell é realmente criada:

$ { echo $BASH_SUBSHELL $BASHPID; }
0 21516
$ { echo $BASH_SUBSHELL $BASHPID; } &
[1] 22064
$ 1 22064

No exemplo acima, coloquei o comando echo em chaves para evitar que BASH_SUBSHELL seja expandido pelo shell atual; chaves são usadas para agrupar comandos sem usar um subshell. A segunda versão do comando (terminando com o operador & control) demonstra claramente que terminar o comando com o "e" comercial resultou em uma subshell (com um novo PID) sendo criada para executar o echo builtin. (estou provavelmente simplificando o comportamento do shell aqui. Veja o comentário do mikeserv.)

Eu nunca teria pensado em executar exit & e se eu não tivesse lido sua pergunta, eu esperaria que o shell atual fosse encerrado. Sabendo agora que esses comandos são executados em uma subcamada, sua explicação é de que é a subshell que sai faz sentido.

“Por que a subshell criada pelo operador de controle de plano de fundo (&) não é exibida em pstree”

Como mencionado acima, quando você executa sleep 10 & , o Bash se bifurca para criar o subshell, mas como sleep é um comando externo, ele chama a chamada de sistema exec() que substitui imediatamente o código Bash e os dados no filho. processo com uma cópia em execução do programa sleep . No momento em que você executar pstree , a chamada exec já terá sido concluída e o processo filho agora terá o nome “ sleep ”.

Enquanto estava longe do meu computador, tentei pensar em uma maneira de manter o subshell em execução por tempo suficiente para que o subshell fosse exibido por pstree . Eu imaginei que poderíamos executar o comando através do time builtin:

$ time sleep 11 &
[2] 4502
$ pstree -p 26793
bash(26793)─┬─bash(4502)───sleep(4503)
            └─pstree(4504)

Aqui, o shell Bash (26793) se bifurca para criar um subshell (4502) para executar o comando em segundo plano. Este subshell executa seu próprio comando time builtin que, por sua vez, se bifurca (para criar um novo processo com PID 4503) e executa execs para executar o comando sleep externo.

Usando pipes nomeados , jimmij criou uma maneira inteligente de manter a sub-sela criada para executar exit vivo por tempo suficiente para que seja exibido por pstree :

$ mkfifo file
$ exit <file &
[2] 6413
$ pstree -p 26793
bash(26793)─┬─bash(6413)
            └─pstree(6414)
$ echo > file
$ jobs
[2]-  Done    exit < file

Redirecionar stdin de um pipe nomeado é inteligente, pois faz com que o subshell bloqueie até receber entrada do pipe nomeado. Posteriormente, redirecionar a saída de echo (sem nenhum argumento) grava um caractere de nova linha no canal nomeado que desbloqueia o processo de subshell que, por sua vez, executa o comando exit builtin.

Da mesma forma, para o comando sleep :

$ mkfifo named_pipe
$ sleep 11 < named_pipe &
[1] 6600
$ pstree -p 26793
bash(26793)─┬─bash(6600)
            └─pstree(6603)

Aqui vemos que o subshell criado para executar o comando em segundo plano tem um PID de 6600 . Em seguida, desbloqueamos o processo escrevendo um caractere de nova linha no pipe:

$ echo > named_pipe

A subshell então exec s para executar o comando sleep .

$ pstree -p 26793
bash(26793)─┬─pstree(6607)
            └─sleep(6600)

Após a chamada exec() , podemos ver que o processo filho ( 6600 ) agora está executando o programa sleep .

    
por 20.01.2016 / 23:41