¿Por qué fork () debe haber sido diseñado para devolver un descriptor de archivo?

16

En su página web sobre el truco del self-pipe , Dan Bernstein explica una condición de carrera select()e indica, ofrece una solución y concluye que

Por supuesto, lo correcto sería fork()devolver un descriptor de archivo, no un ID de proceso.

¿Qué quiere decir con esto? ¿Tiene algo que ver select()con los procesos secundarios para manejar sus cambios de estado en lugar de tener que usar un controlador de señal para recibir notificaciones de esos cambios de estado?

Lassi
fuente
¿Se mezcla ese artículo de entrada y salida, o no lo estoy leyendo correctamente?
ctrl-alt-delor
Puede solicitar que se envíen señales a través de tuberías. Eso es lo que hago.
ctrl-alt-delor
@ ctrl-alt-delor, sí, parece que usa "entrada / salida de tubería" un poco extraño, pero creo que está claro dónde está escribiendo y dónde está leyendo desde una tubería. Ese texto es de 2003, y no estoy seguro de que signalfdalgo así fuera en ese entonces.
ilkkachu
55
Dan sabe de qué está hablando, aunque puede ser un poco deliberadamente provocador. Si estuviera siendo deliberadamente provocativo, diría que, por supuesto, lo correcto sería deshacerse de SIGCHLD.
Steve Summit
1
@mosvy Estoy exagerando un poco, pero cada programa y cada programador que he visto que ha intentado usar SIGCHLD ha tenido problemas. Es una condición de carrera que espera suceder. Cuando todo lo que teníamos era bloquear wait(), había cosas que no podías hacer, así que alguien inventó SIGCHLD, pero fue un mal trabajo. En mi experiencia, y ahora que existen, aspersión agradable, sin bloqueo wait3(), wait4()y / o waitpid()llamadas en lugares clave (tal vez su bucle de eventos principal) es una alternativa muy superior.
Steve Summit

Respuestas:

14

El problema se describe allí en su fuente, select()debe ser interrumpido por señales como SIGCHLD, pero en algunos casos no funciona tan bien. Entonces, la solución es hacer que la señal escriba en una tubería, que luego es observada select(). Mirar los descriptores de archivos es para qué select()sirve, de modo que se soluciona el problema.

La solución alternativa esencialmente convierte el evento de señal en un evento de descriptor de archivo. Si fork()acaba de devolver un fd en primer lugar, la solución no sería necesaria, ya que presumiblemente ese fd podría usarse directamente con select().

Entonces sí, su descripción en el último párrafo me parece correcta.


Otra razón por la que un fd (o algún otro tipo de identificador de kernel) sería mejor que un número de identificación de proceso simple, es que los PID pueden reutilizarse después de que el proceso falle. Eso puede ser un problema en algunos casos cuando se envían señales a los procesos, puede que no sea posible saber con certeza que el proceso es el que usted cree que es, y no otro que reutilice el mismo PID. (Aunque creo que esto no debería ser un problema al enviar señales a un proceso secundario, ya que el padre tiene que ejecutarse wait()en el niño para que se libere su PID).

ilkkachu
fuente
Dicho esto, no recuerdo exactamente los casos que he leído sobre la reutilización de PID como un problema, por lo que si alguien quiere elaborar o aclarar sobre eso, o incluso editar lo anterior, no dude en hacerlo.
ilkkachu
2
Como ya dijo, no hay excusa para que un padre encuentre que su propio hijo pid ha sido reutilizado. Tiene el control total de esa situación porque es quien llama wait().
Joshua
Estos se denominan procesos zombie : "un proceso que ha completado la ejecución pero que aún tiene una entrada en la tabla de procesos: es un proceso en el" Estado finalizado ". Esto ocurre para los procesos secundarios, donde la entrada aún es necesaria para permitir que el padre proceso para leer el estado de salida de su hijo "
Lassi
66
Vale la pena mencionar que ahora Linux puede devolver un descriptor de archivo (pidfd) clone, que es la llamada real del sistema que fork invoca en Linux. La bandera para habilitar esto se llama CLONE_PIDFD- Ver por ejemplo lwn.net/Articles/784831 .
KJ Tsanaktsidis
1
@Lie Ryan, Re " Tener fork () devuelve un identificador global en lugar de un identificador local es conceptualmente más correcto porque ", Windows utiliza identificadores de proceso. El código pid y el código de salida permanecen hasta que todos los controladores del proceso estén cerrados (en lugar de esperar a que el padre coseche), evitando las condiciones de carrera comunes en los sistemas Unix. Cuando los identificadores mantienen vivo el proceso, tiene mucho más sentido que sean identificadores locales en lugar de globales.
ikegami
9

Es solo una reflexión sobre las líneas de "sería genial si Unix fuera diseñado de manera diferente a como es".

El problema con los PID es que viven en un espacio de nombres global donde podrían reutilizarse para otro proceso, y sería bueno si fork()devolviera en el padre algún tipo de identificador que se garantice que siempre se refiera al proceso hijo, y que podría pasar a otros procesos mediante herencia o sockets unix / SCM_RIGHTS[1].

Vea también la discusión aquí para un esfuerzo reciente para "arreglar" eso en Linux, incluida la adición de un indicador clone()que hará que devuelva un pid-fd en lugar de un PID.

Pero incluso entonces, eso no eliminaría la necesidad de ese hack de auto-tubería [2] o mejores interfaces, ya que las señales que notifican a un proceso padre sobre el estado de un hijo no son las únicas que le gustaría manejar en el bucle principal Del programa. Desafortunadamente, cosas como epoll(7) + signalfd(2)en Linux o kqueue(2)en BSD no son estándar: la única interfaz estándar (pero no compatible con sistemas más antiguos) es muy inferior pselect(2).

[1] Evitar que el PID se vuelva a waitpid()reciclar para cuando la llamada al sistema haya regresado y se haya utilizado su valor de retorno probablemente podría lograrse en los sistemas más nuevos mediante el uso waitid(.., WNOWAIT).

[2] No comentaría sobre la afirmación de DJ Bernstein de que lo inventó (perdón por la apófisis ;-)).

Mosvy
fuente
8

Bernstein no da mucho contexto para este comentario de "Cosa correcta", pero me arriesgaré a adivinar: hacer que fork (2) devuelva un PID es inconsistente con los descriptores de archivo abierto (2), creat (2), etc. El resto del sistema Unix podría haber realizado la manipulación del proceso con un descriptor de archivo que representa un proceso, en lugar de un PID. Existe una llamada al sistema signalfd (2) , que permite una interacción algo mejor entre las señales y los descriptores de archivo, y muestra que un proceso que representa un descriptor de archivo podría funcionar.

Bruce Ediger
fuente
signalfd (2) se ve increíble, ¡gracias por mencionarlo! Lástima que sea solo para Linux.
Lassi
1
También ha habido discusiones sobre pidfd_openLinux, ver por ejemplo lwn.net/Articles/789023
dhag el