Cuando se inicia un proceso desde un shell, ¿por qué el shell se bifurca antes de ejecutar el proceso?
Por ejemplo, cuando el usuario ingresa grep blabla foo
, ¿por qué el shell no puede invocar exec()
grep sin un shell hijo?
Además, cuando un shell se bifurca dentro de un emulador de terminal GUI, ¿inicia otro emulador de terminal? (como pts/13
comenzar pts/14
)
fuente
exec grep blabla foo
. Por supuesto, en este caso particular, no será muy útil (ya que la ventana de su terminal se cerrará tan pronto como termine el grep), pero puede ser útil ocasionalmente (por ejemplo, si está iniciando otro shell, tal vez a través de ssh / sudo / screen, y no tengo la intención de volver al original, o si el proceso de shell en el que está ejecutando esto es un sub-shell que nunca debe ejecutar más de un comando de todos modos).bash -c 'grep foo bar'
y llamar a exec hay una forma de optimización que bash hace por usted automáticamenteSegún el
pts
, compruébelo usted mismo: en un shell, ejecutepara conocer su ID de proceso (PID), tengo por ejemplo
Luego ejecuta por ejemplo
sleep 60
y luego, en otra terminalEntonces no, en general tiene el mismo tty asociado al proceso. (Tenga en cuenta que esto es
sleep
porque tiene su shell como padre).fuente
TL; DR : porque este es el método óptimo para crear nuevos procesos y mantener el control en el shell interactivo
fork () es necesario para procesos y tuberías
Para responder a la parte específica de esta pregunta, si
grep blabla foo
se llamaraexec()
directamente a través de parent, parent aprovecharía para existir, y su PID con todos los recursos sería asumido porgrep blabla foo
.Sin embargo, hablemos en general sobre
exec()
yfork()
. La razón clave de este comportamiento es porquefork()/exec()
es el método estándar para crear un nuevo proceso en Unix / Linux, y esto no es algo específico de bash; Este método ha estado en funcionamiento desde el principio e influenciado por este mismo método de los sistemas operativos ya existentes de la época. Parafraseando un poco la respuesta de Ricitos de Oro en una pregunta relacionada,fork()
crear un proceso nuevo es más fácil ya que el núcleo tiene menos trabajo que hacer en lo que respecta a la asignación de recursos y muchas de las propiedades (como descriptores de archivos, entorno, etc.). ser heredado del proceso padre (en este caso debash
).En segundo lugar, en lo que respecta a los shells interactivos, no puede ejecutar un comando externo sin bifurcación. Para iniciar un ejecutable que vive en el disco (por ejemplo
/bin/df -h
), debe llamar a una de lasexec()
funciones familiares, comoexecve()
, que reemplazará al padre con el nuevo proceso, se hará cargo de su PID y los descriptores de archivo existentes, etc. Para el shell interactivo, desea que el control regrese al usuario y permita que el shell interactivo principal continúe. Por lo tanto, la mejor manera es crear un subproceso víafork()
y dejar que ese proceso se haga cargo de víaexecve()
. Entonces, el shell interactivo PID 1156 generaría un hijo a travésfork()
de PID 1157, luego llamaríaexecve("/bin/df",["df","-h"],&environment)
, lo que hace que se/bin/df -h
ejecute con PID 1157. Ahora el shell solo tiene que esperar a que el proceso salga y devolverle el control.En caso de que tenga que crear una tubería entre dos o más comandos, por ejemplo
df | grep
, necesita una forma de crear dos descriptores de archivo (que es el final de la tubería de lectura y escritura que proviene depipe()
syscall), de alguna manera deje que dos procesos nuevos los hereden. Esto se hace bifurcando un nuevo proceso y luego copiando el extremo de escritura de la tubería a través de ladup2()
llamada en sustdout
también conocido como fd 1 (por lo tanto, si el final de escritura es fd 4, lo hacemosdup2(4,1)
). Cuando ocurre elexec()
engendro,df
el proceso hijo no pensará en nadastdout
y le escribirá sin darse cuenta (a menos que verifique activamente) que su salida realmente se convierte en una tubería. El mismo proceso ocurregrep
, excepto nosotrosfork()
, tomamos el extremo de lectura de la tubería con fd 3 ydup(3,0)
antes de desovargrep
conexec()
. Todo este tiempo el proceso padre sigue ahí, esperando recuperar el control una vez que se complete la canalización.En el caso de los comandos integrados, generalmente Shell no
fork()
, con la excepción delsource
comando. Se requieren subcapasfork()
.En resumen, este es un mecanismo necesario y útil.
Desventajas de bifurcación y optimizaciones
Ahora, esto es diferente para shells no interactivos , como
bash -c '<simple command>'
. A pesar defork()/exec()
ser un método óptimo en el que tiene que procesar muchos comandos, es un desperdicio de recursos cuando tiene un solo comando. Para citar a Stéphane Chazelas de esta publicación :Por lo tanto, muchos shells (no solo
bash
) se usanexec()
para permitir que esobash -c ''
sea asumido por ese simple comando simple. Y exactamente por las razones indicadas anteriormente, es mejor minimizar las canalizaciones en los scripts de shell. A menudo puedes ver a los principiantes hacer algo como esto:Por supuesto, esto tendrá
fork()
3 procesos. Este es un ejemplo simple, pero considere un archivo grande, en el rango de Gigabytes. Sería mucho más eficiente con un proceso:El desperdicio de recursos en realidad puede ser una forma de ataque de denegación de servicio, y en particular las bombas de horquilla se crean a través de funciones de shell que se llaman a sí mismas en la tubería, que bifurca múltiples copias de sí mismos. Hoy en día, esto se mitiga limitando el número máximo de procesos en cgroups en systemd , que Ubuntu también usa desde la versión 15.04.
Por supuesto, eso no significa que el tenedor sea malo. Todavía es un mecanismo útil como se discutió anteriormente, pero en caso de que pueda salirse con la suya con menos procesos y consecutivamente menos recursos y, por lo tanto, un mejor rendimiento, debe evitarlo
fork()
si es posible.Ver también
fuente
Para cada comando (ejemplo: grep) que emita en el indicador de bash, realmente tiene la intención de comenzar un nuevo proceso y luego volver al indicador de bash después de la ejecución.
Si el proceso de shell (bash) llama a exec () para ejecutar grep, el proceso de shell será reemplazado por grep. Grep funcionará bien, pero después de la ejecución, el control no puede volver al shell porque el proceso bash ya está reemplazado.
Por esta razón, bash llama a fork (), que no reemplaza el proceso actual.
fuente