vulnerability seccion race ejemplo critica condiciones condicion competencia carrera c fork

seccion - Evitar una condición de carrera de horquilla()/SIGCHLD



race condition vulnerability (4)

Además de los "niños" existentes, agregue una nueva estructura de datos "muertes tempranas". Esto mantendrá limpio el contenido de los niños.

// main program excerpt for (;;) { if ( is_time_to_make_babies ) { pid = fork(); if (pid == -1) { /* fail */ } else if (pid == 0) { /* child stuff */ print "child started" exit } else { /* parent stuff */ print "parent forked new child ", pid if (!earlyDeaths.contains(pid)) { children.add(pid); } else { earlyDeaths.remove(pid); } } } } // SIGCHLD handler sigchld_handler(signo) { while ( (pid = wait(status, WNOHANG)) > 0 ) { print "parent caught SIGCHLD from ", pid if (children.contains(pid)) { children.remove(pid); } else { earlyDeaths.add(pid); } } }

EDITAR: esto se puede simplificar si su proceso tiene un solo hilo: earlyDeaths no tiene que ser un contenedor, solo tiene que contener un pid.

Considere el siguiente pseudocódigo fork() / SIGCHLD .

// main program excerpt for (;;) { if ( is_time_to_make_babies ) { pid = fork(); if (pid == -1) { /* fail */ } else if (pid == 0) { /* child stuff */ print "child started" exit } else { /* parent stuff */ print "parent forked new child ", pid children.add(pid); } } } // SIGCHLD handler sigchld_handler(signo) { while ( (pid = wait(status, WNOHANG)) > 0 ) { print "parent caught SIGCHLD from ", pid children.remove(pid); } }

En el ejemplo anterior, hay una condición de carrera. Es posible que " /* child stuff */ " termine antes de que comience " /* parent stuff */ ", lo que puede dar como resultado que se agregue un pid de un niño a la lista de elementos secundarios después de salir y que nunca se elimine. Cuando llegue el momento de que la aplicación se cierre, el padre esperará sin parar para que el niño ya terminado termine.

Una solución que se me ocurre para contrarrestar esto es tener dos listas: started_children y finished_children . Añadiría a started_children en el mismo lugar que estoy agregando a los children ahora. Pero en el controlador de señal, en lugar de eliminar de los children , agregaría a finished_children . Cuando la aplicación se cierra, el padre simplemente puede esperar hasta que la diferencia entre started_children y finished_children sea ​​cero.

Otra posible solución en la que puedo pensar es usar la memoria compartida, por ejemplo, compartir la lista de hijos de los padres y dejar que los hijos se .add y se .remove sí mismos. Pero no sé mucho sobre esto.

EDITAR: Otra posible solución, que fue lo primero que me vino a la mente, es simplemente agregar un sleep(1) al comienzo de /* child stuff */ pero eso huele raro para mí, y es por eso que lo dejé fuera. Tampoco estoy seguro de que sea una solución al 100%.

Entonces, ¿cómo corregirías esta condición de carrera? Y si hay un patrón recomendado bien establecido para esto, ¡házmelo saber!

Gracias.


La solución más simple sería bloquear la señal SIGCHLD antes de fork() con sigprocmask() y desbloquearla en el código padre después de haber procesado el pid.

Si el niño murió, se llamará al controlador de señal para SIGCHLD después de desbloquear la señal. Es un concepto de sección crítica: en su caso, la sección crítica comienza antes de fork() y finaliza después de children.add() .


Si no puede usar un fragmento crítico, tal vez un contador simple pueda hacer este trabajo. +1 cuando agrega, -1 cuando se elimina, no importa cuál suceda primero, eventualmente puede obtener cero cuando todo está hecho.


Tal vez un algoritmo optimista? Pruebe children.remove (pid), y si falla, continúe con vida.

¿O comprueba que pid está en los niños antes de tratar de eliminarlo?