
Este post é basicamente uma continuação de umpergunta anteriormeu.
A partir da resposta a essa pergunta, percebi que não apenas não entendo muito bem todo o conceito de "subcamada", mas, de maneira mais geral, não entendo a relação entre os fork
processos -ing e filhos.
Eu costumava pensar que quando o processo X
executa um fork
, umnovoY
é criado um processo cujo pai é X
, mas de acordo com a resposta a essa pergunta,
[a] subshell não é um processo completamente novo, mas uma bifurcação do processo existente.
A implicação aqui é que uma “bifurcação” não é (ou não resulta em) “um processo completamente novo”.
Agora estou muito confuso, muito confuso, na verdade, para formular uma pergunta coerente que dissipe diretamente minha confusão.
Posso, no entanto, formular uma questão que pode levar indiretamente à iluminação.
Como, de acordo com zshall(1)
, $ZDOTDIR/.zshenv
é originado sempre que uma nova instância é zsh
iniciada, qualquer comando que $ZDOTDIR/.zshenv
resulte na criação de um "processo [zsh] completamente novo" resultaria em uma regressão infinita. Por outro lado, incluir qualquer uma das seguintes linhas em um $ZDOTDIR/.zshenv
arquivo nãonãoresultar em uma regressão infinita:
echo $(date; printenv; echo $$) > /dev/null #1
(date; printenv; echo $$) #2
A única maneira que encontrei de induzir uma regressão infinita pelo mecanismo descrito acima foi incluir uma linha como a seguinte no arquivo $ZDOTDIR/.zshenv
:
$SHELL -c 'date; printenv; echo $$' #3
Minhas perguntas são:
qual a diferença entre os comandos marcados
#1
acima#2
e aquele marcado#3
como contas dessa diferença de comportamento?se os shells que são criados
#1
e#2
são chamados de "subshells", quais são aqueles gerados por#3
chamados?é possível racionalizar (e talvez generalizar) as descobertas empíricas/anedóticas descritas acima em termos da "teoria" (por falta de uma palavra melhor) dos processos Unix?
A motivação para a última pergunta é ser capaz de determinarantes do tempo(ou seja, sem recorrer à experimentação) quais comandos levariam a uma regressão infinita se fossem incluídos em $ZDOTDIR/.zshenv
?
1 A sequência específica de comandos date; printenv; echo $$
que usei nos vários exemplos acima não é muito importante. Acontece que são comandos cuja saída foi potencialmente útil para interpretar os resultados dos meus "experimentos". (Eu queria, no entanto, que essas sequências consistissem em mais de um comando, pelo motivo explicadoaqui.)
Responder1
Visto que, de acordo com zshall(1), $ZDOTDIR/.zshenv é obtido sempre que uma nova instância de zsh é iniciada
Se você se concentrar na palavra “começa” aqui, você terá um tempo melhor. O efeito fork()
é criar outro processoque começa exatamente onde o processo atual já está. Está clonando um processo existente, com a única diferença sendo o valor de retorno de fork
. A documentação usa "starts" para significar entrar no programa desde o início.
Seu exemplo nº 3 é executado $SHELL -c 'date; printenv; echo $$'
, iniciando um processo totalmente novo desde o início. Ele seguirá o comportamento normal de inicialização. Você pode ilustrar isso, por exemplo, trocando outro shell: run bash -c ' ... '
em vez de zsh -c ' ... '
. Não há nada de especial em usar $SHELL
aqui.
Os exemplos nº 1 e nº 2 executam subshells. O fork
próprio shell executa seus comandos dentro do processo filho e, em seguida, continua com sua própria execução quando o filho termina.
A resposta à sua pergunta nº 1 é a acima: o exemplo 3 executa um shell totalmente novo desde o início, enquanto os outros dois executam subshells. O comportamento de inicialização inclui o carregamento de arquivos .zshenv
.
A razão pela qual eles chamam esse comportamento especificamente, o que provavelmente é o que leva à sua confusão, é que esse arquivo (ao contrário de alguns outros) é carregado em shells interativos e não interativos.
Para sua pergunta nº 2:
se os shells criados em #1 e #2 são chamados de "subshells", como são chamados aqueles gerados por #3?
Se você quiser um nome, poderá chamá-lo de "shell filho", mas na verdade não é nada. Não é diferente de qualquer outro processo iniciado a partir do shell, seja o mesmo shell, um shell diferente ou cat
.
Para sua pergunta nº 3:
é possível racionalizar (e talvez generalizar) as descobertas empíricas/anedóticas descritas acima em termos da "teoria" (por falta de uma palavra melhor) dos processos Unix?
fork
faz um novo processo, com um novo PID, que começa a rodar em paralelo exatamente de onde este parou.exec
substitui o código atualmente em execução por um novo programa carregado de algum lugar, em execução desde o início. Quando você gera um novo programa, primeiro você fork
mesmo e depois exec
esse programa no filho. Essa é a teoria fundamental dos processos que se aplica em todos os lugares, dentro e fora das cascas.
Subshells são fork
s, e cada comando não integrado que você executa leva a a fork
e an exec
.
Observe que $$
se expande para o PID do shell paiem qualquer shell compatível com POSIX, então você pode não estar obtendo o resultado esperado de qualquer maneira. Observe também que o zsh otimiza agressivamente a execução do subshell de qualquer maneira e geralmente exec
é o último comando ou não gera o subshell se todos os comandos estiverem seguros sem ele.
Um comando útil para testar suas intuições é:
strace -e trace=process -f $SHELL -c ' ... '
Isso imprimirá no erro padrão todos os eventos relacionados ao processo (e nenhum outro) para o comando ...
executado em um novo shell. Você pode ver o que é executado e o que não é executado em um novo processo e onde exec
ocorrem.
Outro comando possivelmente útil é pstree -h
, que imprimirá e destacará a árvore de processos pais do processo atual. Você pode ver quantas camadas de profundidade você tem na saída.
Responder2
Quando o manual diz que os comandos .zshenv
são "sourced", significa que eles são executados dentro do shell que os executa. Eles não causam uma chamada para fork()
, portanto, não geram um subshell. Seu terceiro exemplo executa explicitamente um subshell, chamando uma chamada para fork()
e, portanto, recorre infinitamente. Acredito que isso deveria (pelo menos parcialmente) responder à sua primeira pergunta.
Não há nada "criado" nos comandos 1 e 2, portanto não há nada que possa ser chamado de alguma coisa - esses comandos são executados no contexto do shell de sourcing.
A generalização é a diferença entre "chamar" uma rotina ou programa shell e "fornecer" uma rotina ou programa shell - sendo o último geralmente aplicável apenas a comandos/scripts shell, não a programas externos. "Sourcing" de um script de shell geralmente é feito via,
. <scriptname>
em oposição a./<scriptname>
ou/full/path/to/script
- observe a sequência "ponto-espaço" no início da diretiva de sourcing. O sourcing também pode ser invocado usandosource <scriptname>
,source
sendo o comando um shell interno.
Responder3
fork
, supondo que tudo corra bem, retorna duas vezes. Um retorno está no processo pai (que possui o ID do processo original) e o outro no novo processo filho (um ID de processo diferente, mas que compartilha muito em comum com o processo pai). Neste ponto, o filho poderia exec(3)
fazer algo que faria com que algum "novo" binário fosse carregado nesse processo, embora o filho não precisasse fazer isso e pudesse executar outro código já carregado através do processo pai (funções zsh, por exemplo) . Conseqüentemente, a fork
pode ou não resultar em um processo "completamente novo", se "completamente novo" significar algo carregado por meio de uma exec(3)
chamada do sistema.
Adivinhar antecipadamente quais comandos causam regressão infinita é complicado; além do caso fork-calling-fork (também conhecido como "forkbomb"), outro caso fácil é por meio de um wrapper de função ingênuo em torno de algum comando
function ssh() {
ssh -o UseRoaming=no "$@"
}
que provavelmente deveria ser escrito como
function ssh() {
=ssh -o UseRoaming=no "$@"
}
ou command ssh ...
para evitar chamadas de função infinitas da ssh
função que chama a ssh
função que chama o ... Isso não envolve de forma alguma fork
, já que as chamadas de função são internas ao processo ZSH, mas acontecerão alegremente até o infinito até que algum limite seja atingido por aquele único Processo ZSH.
strace
, como sempre, é útil para revelar exatamente quais chamadas de sistema estão envolvidas para qualquer comando (em particular aqui fork
e talvez em alguma exec
chamada); shells podem ser depurados -x
ou similares que mostram o que o shell está fazendo internamente (por exemplo, chamadas de função). Para mais leitura, Stevens em "Programação Avançada no Ambiente Unix" tem alguns capítulos relacionados à criação e manipulação de novos processos.