Por que o bash não gera um subshell para comandos simples?

5

Considere os dois seguintes comandos bash. Um cria um subnível e o outro não.

Nenhum subshell

$ bash -c "sleep 10000 "

saída pstree:

bash,100648
  └─sleep,103509 10000

Com subshell

$ bash -c "sleep 10000; sleep 99999 "


bash,100648
  └─bash,103577 -c sleep 10000; sleep 99999
      └─sleep,103578 10000

Isso é algum tipo de otimização? Bash olha o comando e pula a criação do processo para comandos simples. Parece que o comando simples é gerado pelo bash do terminal pai.

Versão do Bash que estou usando é

$ bash --version
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)

No entanto, acho que o mesmo pode ser observado no bash 3.X também.

Mais alguns exemplos. Com builtins, o subshell é gerado. Builtins não são executados no bash pai.

$ bash -c "read"

bash,100648
  └─bash,104336 -c read

O mesmo comportamento com sleep pode ser reproduzido com top e com o redirecionamento de saída.

$ bash -c "top -b "
bash,100648
  └─top,104392 -b

e

$ bash -c "top -b > /dev/null "
bash,100648
  └─bash,104420 -c top -b > /dev/null
      └─top,104421 -b
    
por Hakan Baba 28.10.2017 / 10:22

2 respostas

3

A saída de pstree é enganosa.

bash -c "sleep 10000 "

cria um processo filho bash. Mas esse processo não cria outro processo filho. Como não há mais nada a fazer depois de executar sleep , o shell faz um execve() direto para sleep sem forjar primeiro.

Como isso é tão rápido, você só verá o resultado depois do execve in pstree .

Mas no

bash -c "sleep 10000; sleep 99999 "

caso o novo bash forks duas vezes, uma vez para cada comando. Ele poderia fazer um execve para o último comando, também, em vez de forjar primeiro. Eu não sei porque isso não acontece.

Este e o caso de redirecionamento provavelmente são apenas problemas com a detecção quando uma bifurcação é necessária.

    
por 28.10.2017 / 17:27
1

No primeiro exemplo, o shell com PID 100648 é seu shell interativo, enquanto sleep é de fato o processo que substituiu bash -c '' process. O execve() syscall gera um novo programa que retém o PID do processo original que chamado, daí porque você não vê o original bash -c '' lá. A razão simples de porque os comandos simples substituem o processo shell é porque ele economiza recursos, conforme explicado na resposta de Stephane .

Isso também é evidente em um teste simples em um terminal:

$ echo $$
10250
$ bash -c 'sleep 60'

E em outro:

$ pstree -p 10250
bash(10250)───sleep(21031)

Por que isso acontece dessa forma, é porque há apenas um comando simples sem canos e bash executa execve direta para comandos simples .

Exatamente por esse motivo, bash reconhece que você tem várias instruções de comando no segundo exemplo, portanto, para cada comando sleep que você tem em "sleep 10000; sleep 99999 " . No primeiro caso, execve() pode apenas substituir o processo pai e quando as novas saídas - não há problema. Mas aqui o shell não pode sair após o primeiro comando ser concluído. Portanto, haverá fork e execve para sleep 10000 , que é o que você vê na saída de pstree e, posteriormente, um novo fork e execve para sleep 99999

Outro teste que você pode realizar para acreditar que o comando simples em bash -c '' substitui o processo shell é este:

$ bash -c 'grep "^Pid:" /proc/self/status /proc/$$/status'
/proc/self/status:Pid:  23946
/proc/23946/status:Pid: 23946

Note que esse comportamento é específico do bash. Para /bin/dash em distros baseadas no Debian (e também evidente em strace , também):

$ sh -c 'grep "^Pid:" /proc/self/status /proc/$$/status'
/proc/self/status:Pid:  24188
/proc/24187/status:Pid: 24187
    
por 03.09.2018 / 10:26