
Esta publicación es básicamente una continuación de unapregunta anteriormío.
A partir de la respuesta a esa pregunta, me di cuenta de que no solo no entiendo del todo el concepto completo de "subcapa", sino que, en términos más generales, no entiendo la relación entre fork
-ing y los procesos infantiles.
Solía pensar que cuando el proceso X
ejecuta a fork
, anuevoSe crea un proceso Y
cuyo padre es X
, pero de acuerdo con la respuesta a esa pregunta,
[a] subshell no es un proceso completamente nuevo, sino una bifurcación del proceso existente.
La implicación aquí es que una "bifurcación" no es (o no da como resultado) "un proceso completamente nuevo".
Ahora estoy muy confundido, demasiado confundido, de hecho, para formular una pregunta coherente que disipe directamente mi confusión.
Sin embargo, puedo formular una pregunta que puede conducir indirectamente a la iluminación.
Dado que, según zshall(1)
, $ZDOTDIR/.zshenv
se obtiene cada vez que zsh
se inicia una nueva instancia de, cualquier comando que $ZDOTDIR/.zshenv
dé como resultado la creación de un "proceso [zsh] completamente nuevo" resultaría en una regresión infinita. Por otro lado, incluir cualquiera de las siguientes líneas en un $ZDOTDIR/.zshenv
archivo nonoresulta en una regresión infinita:
echo $(date; printenv; echo $$) > /dev/null #1
(date; printenv; echo $$) #2
La única forma que encontré para inducir una regresión infinita mediante el mecanismo descrito anteriormente fue incluir una línea como la siguiente 1 en el $ZDOTDIR/.zshenv
archivo:
$SHELL -c 'date; printenv; echo $$' #3
Mis preguntas son:
¿Qué diferencia entre los comandos marcados
#1
arriba#2
y el marcado#3
se debe a esta diferencia de comportamiento?Si los shells que se crean
#1
y#2
se llaman "subshells", ¿cómo se llaman aquellos como el generado#3
?¿Es posible racionalizar (y tal vez generalizar) los hallazgos empíricos/anecdóticos descritos anteriormente en términos de la "teoría" (a falta de una palabra mejor) de los procesos Unix?
La motivación para la última pregunta es poder determinarantes de tiempo(es decir, sin recurrir a la experimentación), ¿qué comandos conducirían a una regresión infinita si se incluyeran en $ZDOTDIR/.zshenv
?
1 La secuencia particular de comandos date; printenv; echo $$
que utilicé en los distintos ejemplos anteriores no es demasiado importante. Resultan ser comandos cuyo resultado fue potencialmente útil para interpretar los resultados de mis "experimentos". (Sin embargo, quería que estas secuencias constaran de más de un comando, por el motivo explicadoaquí.)
Respuesta1
Dado que, según zshall(1), $ZDOTDIR/.zshenv se obtiene cada vez que se inicia una nueva instancia de zsh
Si te concentras en la palabra "comienza" aquí, lo pasarás mejor. El efecto de fork()
es crear otro proceso.que comienza exactamente donde ya se encuentra el proceso actual. Está clonando un proceso existente, con la única diferencia de que el valor de retorno es fork
. La documentación utiliza "inicios" para referirse a ingresar al programa desde el principio.
Su ejemplo #3 se ejecuta $SHELL -c 'date; printenv; echo $$'
, iniciando un proceso completamente nuevo desde el principio. Pasará por el comportamiento de inicio normal. Puede ilustrar esto, por ejemplo, intercambiando en otro shell: ejecutar bash -c ' ... '
en lugar de zsh -c ' ... '
. No hay nada especial en usarlo $SHELL
aquí.
Los ejemplos 1 y 2 ejecutan subcapas. El shell fork
se envía a sí mismo y ejecuta sus comandos dentro de ese proceso hijo, luego continúa con su propia ejecución cuando el hijo termina.
La respuesta a su pregunta número 1 es la anterior: el ejemplo 3 ejecuta un shell completamente nuevo desde el principio, mientras que los otros dos ejecutan subshells. El comportamiento de inicio incluye la carga .zshenv
.
La razón por la que señalan este comportamiento específicamente, que es probablemente lo que genera confusión, es que este archivo (a diferencia de otros) se carga tanto en shells interactivos como no interactivos.
A tu pregunta número 2:
Si los shells que se crean en los números 1 y 2 se llaman "subshells", ¿cómo se llaman aquellos como el generado por el número 3?
Si desea un nombre, puede llamarlo "shell secundario", pero en realidad no es nada. No es diferente de cualquier otro proceso que inicie desde el shell, ya sea el mismo shell, uno diferente o cat
.
A tu pregunta número 3:
¿Es posible racionalizar (y tal vez generalizar) los hallazgos empíricos/anecdóticos descritos anteriormente en términos de la "teoría" (a falta de una palabra mejor) de los procesos Unix?
fork
crea un nuevo proceso, con un nuevo PID, que comienza a ejecutarse en paralelo exactamente donde quedó este.exec
reemplaza el código que se está ejecutando actualmente con un nuevo programa cargado desde algún lugar, ejecutándose desde el principio. Cuando genera un nuevo programa, primero usted fork
mismo y luego exec
ese programa en el niño. Ésa es la teoría fundamental de los procesos que se aplica en todas partes, dentro y fuera de las capas.
Los subshells son fork
s, y cada comando no integrado que ejecuta conduce tanto a a fork
como a an exec
.
Tenga en cuenta que $$
se expande al PID del shell principalen cualquier shell compatible con POSIX, por lo que es posible que no obtenga el resultado que espera de todos modos. Tenga en cuenta también que zsh optimiza agresivamente la ejecución del subshell de todos modos y, comúnmente, exec
envía el último comando o no genera el subshell en absoluto si todos los comandos son seguros sin él.
Un comando útil para poner a prueba tus intuiciones es:
strace -e trace=process -f $SHELL -c ' ... '
Eso imprimirá en error estándar todos los eventos relacionados con el proceso (y ningún otro) para el comando ...
que ejecuta en un nuevo shell. Puede ver qué se ejecuta y qué no en un nuevo proceso, y dónde exec
ocurre.
Otro comando posiblemente útil es pstree -h
, que imprimirá y resaltará el árbol de procesos principales del proceso actual. Puede ver cuántas capas de profundidad tiene en la salida.
Respuesta2
Cuando el manual dice que los comandos .zshenv
tienen "fuente", significa que se ejecutan dentro del shell que los ejecuta. No provocan una llamada a fork()
, por lo que no generan una subcapa. Su tercer ejemplo ejecuta explícitamente una subcapa, llamando a una llamada a fork()
y, por lo tanto, recurre infinitamente. Creo que esto debería responder (al menos parcialmente) a su primera pregunta.
No hay nada "creado" en los comandos 1 y 2, por lo que no hay nada que pueda llamarse de ninguna manera: esos comandos se ejecutan dentro del contexto del shell de abastecimiento.
La generalización es la diferencia entre "llamar" a una rutina o programa de shell y "obtener" una rutina o programa de shell; este último generalmente solo se aplica a comandos/scripts de shell, no a programas externos. El "abastecimiento" de un script de shell generalmente se realiza a través
. <scriptname>
de./<scriptname>
o/full/path/to/script
- tenga en cuenta la secuencia de "puntos y espacio" al comienzo de la directiva de abastecimiento. El abastecimiento también se puede invocar usandosource <scriptname>
,source
siendo el comando un shell interno.
Respuesta3
fork
, suponiendo que todo va bien, regresa dos veces. Una devolución está en el proceso principal (que tiene el ID del proceso original) y la otra en el nuevo proceso secundario (un ID de proceso diferente pero que por lo demás tiene mucho en común con el proceso principal). En este punto, el niño podría exec(3)
hacer algo, lo que causaría que se cargara algún binario "nuevo" en ese proceso, aunque el niño no necesita hacerlo, y podría ejecutar otro código ya cargado a través del proceso padre (funciones zsh, por ejemplo). . Por lo tanto, a fork
puede o no resultar en un proceso "completamente nuevo", si se entiende por "completamente nuevo" algo cargado a través de una exec(3)
llamada al sistema.
Adivinar de antemano qué comandos causan una regresión infinita es complicado; Además del caso fork-calling-fork (también conocido como "forkbomb"), otro caso fácil es a través de una función ingenua que envuelve algún comando
function ssh() {
ssh -o UseRoaming=no "$@"
}
que probablemente debería escribirse como
function ssh() {
=ssh -o UseRoaming=no "$@"
}
o command ssh ...
para evitar infinitas llamadas a funciones de la ssh
función que llama a la ssh
función que llama al ... Esto de ninguna manera implica fork
, ya que las llamadas a funciones son internas del proceso ZSH, pero felizmente sucederán hasta el infinito hasta que ese único límite alcance algún límite. Proceso ZSH.
strace
, como siempre, es útil para revelar exactamente qué llamadas al sistema están involucradas para cualquier comando (en particular aquí fork
y quizás alguna exec
llamada); Los shells se pueden depurar con -x
o algo similar que muestre lo que el shell está haciendo internamente (por ejemplo, llamadas a funciones). Para obtener más información, Stevens en "Programación avanzada en el entorno Unix" tiene algunos capítulos relacionados con la creación y el manejo de nuevos procesos.