2011-01-11 15 views
5

Estoy tratando de generar un nuevo proceso de mi proyecto C++ - usando fork-exec. Estoy utilizando fork-exec para crear un conducto bidireccional para el proceso secundario. Pero me temo que mis recursos en el proceso bifurcado no se liberarán correctamente, ya que la llamada ejecutiva asumirá completamente mi proceso y no va a llamar a ningún destructor.Liberando recursos de C++ y fork-exec?

Intenté eludir esto lanzando una excepción y llamando a execl desde un bloque catch al final de main, pero esta solución no destruye ningún singletons.

¿Hay alguna manera sensata de lograr esto de forma segura? (Esperemos evitando cualquier hacks atexit)

Ex: Las siguientes salidas de código:

We are the child, gogo! 
Parent proc, do nothing 
Destroying object 

A pesar de que el proceso de horquilla también tiene una copia del singleton que debe ser destruido antes de que llame execl.

#include <iostream> 
#include <unistd.h> 

using namespace std; 

class Resources 
{ 
public: 
    ~Resources() { cout<<"Destroying object\n"; } 
}; 

Resources& getRes() 
{ 
    static Resources r1; 
    return r1; 
} 

void makeChild(const string &command) 
{ 
    int pid = fork(); 
    switch(pid) 
    { 
    case -1: 
     cout<<"Big error! Wtf!\n"; 
     return; 
    case 0: 
     cout<<"Parent proc, do nothing\n"; 
     return; 
    } 
    cout<<"We are the child, gogo!\n"; 
    throw command; 
} 

int main(int argc, char* argv[]) 
{ 
    try 
    { 
     Resources& ref = getRes(); 
     makeChild("child"); 
    } 
    catch(const string &command) 
    { 
     execl(command.c_str(), ""); 
    } 
    return 0; 
} 
+1

¿De qué recursos estás hablando? La mayoría de las descripciones de archivos sobreviven a exec(), que puede marcar close-on-exec para que el kernel las cierre por usted. http://pubs.opengroup.org/onlinepubs/009695399/functions/exec.html –

+1

Por cierto, si se llamaran a los destructores tanto en el hijo bifurcado como en el padre, terminaría llamando a los constructores una vez (en el padre) y destructores dos veces (tanto en el padre como en el hijo). –

+1

Creo que me estoy acercando peligrosamente al comportamiento indefinido aquí, pero la clase Resources representa varias clases singleton que utilizo para envolver bibliotecas C en objetos RAII. Y si fork realmente copia todo el estado del proceso, probablemente debería llamar a RAII-destructores antes de llamar a exec(). Esto, por supuesto, sería una locura si los recursos fueran externos al programa (como una conexión de base de datos). Pero dado que son bibliotecas, creo que deberían publicarse tanto en el proceso principal como en el secundario. [Si me sirve, estoy actualizando ncurses, nscapi y SDL en singletons] – Phog

Respuesta

3

hay excelentes probabilidades de que no necesidad de llamar a cualquier destructores entre fork y exec. Sí, fork hace una copia de todo el estado del proceso, incluidos los objetos que tienen destructores, y exec borra todo ese estado. Pero, ¿realmente importa? ¿Puede un observador externo a su programa (otro proceso no relacionado que se ejecuta en la misma computadora) decir que los destructores no se han ejecutado en el niño? Si no hay forma de saberlo, no hay necesidad de ejecutarlos.

Incluso si un observador externo sabe, puede ser activamente incorrecto para ejecutar destructores en el elemento secundario. El ejemplo habitual para esto es: imagina que escribiste algo en stdout antes de llamar al fork, pero se almacenó en la biblioteca y por lo tanto no se ha entregado al sistema operativo. En ese caso, usted no debe llamar alfclose o fflush en stdout en el niño, ¡o la salida ocurrirá dos veces! (Esta es la razón por la que casi con toda seguridad debe llamar _exit en lugar de exit si el exec falla.)

Habiendo dicho todo esto, hay dos casos comunes donde es posible que tenga que hacer algún trabajo de limpieza en el niño. Uno es descriptores de archivos (no confundirlos con stdio FILE u objetos iostream) que no deberían abrirse después del exec. La forma correcta de tratar con esto es establecer el indicador FD_CLOEXEC tan pronto como sea posible después de que estén abierto (algunos sistemas operativos le permiten hacer esto en open sí mismo, pero eso no es universal) y/o pasar del 3 al algunos números grandes que llaman close (nofclose) en el niño. (FreeBSD tiene closefrom, pero hasta donde yo sé, nadie más lo hace, lo cual es una lástima porque es realmente muy útil.)

El otro caso es el sistema de bloqueos de hilos globales, que - este es un espinoso y poco estandarizado área - puede encerrar tanto por el padre como el hijo, y luego heredado a través de exec en un proceso que no tiene idea de que tiene un bloqueo. Esto es lo que se supone que es pthread_atfork, pero he leído que en la práctica no funciona de manera confiable. El único consejo que puedo ofrecer es "no sostenga ningún candado cuando llame al fork", discúlpelo.

+0

¡Gracias! Esto resolvió todos los malentendidos que tuve. (... Especialmente porque me permite ignorar recursos inéditos en el proceso del niño con buena conciencia =) – Phog

+0

Buena explicación; en Linux es posible emular closefrom(), pero en cualquier caso, es un hack muy feo y no lo recomendaría. – MarkR

Cuestiones relacionadas