2011-01-11 14 views
5

Sto provando a generare un nuovo processo dal mio progetto C++ usando fork-exec. Sto usando fork-exec per creare un pipe bidirezionale per il processo figlio. Ma temo che le mie risorse nel processo forked non vengano liberate correttamente, dal momento che la chiamata exec prenderà completamente il mio processo e non chiamerà alcun distruttore.Rilascio di risorse C++ e fork-exec?

Ho tentato di aggirare questo problema lanciando un'eccezione e chiamando execl da un blocco catch alla fine del main, ma questa soluzione non distrugge alcun singletons.

Esiste un modo ragionevole per raggiungere questo obiettivo in sicurezza? (Si spera evitando qualsiasi hack AtExit)

Es: le seguenti uscite codice:

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

Anche se il processo biforcuta ha anche una copia del singleton che deve essere distrutti prima che chiami 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

Di quali risorse stai parlando? Principalmente i descrittori di file sopravvivono a exec(), che è possibile contrassegnare close-on-exec in modo che il kernel li chiuda per te. http://pubs.opengroup.org/onlinepubs/009695399/functions/exec.html –

+1

A proposito, se i distruttori venivano chiamati sia nel bambino biforcuto che nel genitore, finiva per chiamare i costruttori una volta (nel genitore) e distruttori due volte (sia nel genitore che nel bambino). –

+1

In questo caso, mi sto avvicinando pericolosamente al comportamento non definito, ma la classe Resources rappresenta diverse classi singleton che utilizzo per il wrapping di librerie C in oggetti RAII. E se fork copia davvero l'intero stato del processo, allora probabilmente dovrei chiamare i distruttori RAII prima di chiamare exec(). Questo sarebbe ovviamente folle se le risorse fossero esterne al programma (come una connessione al database). Ma dal momento che sono biblioteche, credo che dovrebbero essere rilasciati sia nel processo genitore che in quello secondario. [Se aiuta, sto attualmente inserendo ncurses, nscapi e SDL in singleton] – Phog

risposta

3

ci sono ottime probabilità che non si necessità per chiamare qualsiasi distruttori tra fork e exec. Sì, fork crea una copia dell'intero stato del processo, inclusi gli oggetti che hanno distruttori, e exec cancella tutto quello stato. Ma importa davvero? Può un osservatore esterno al tuo programma - un altro processo non correlato in esecuzione sullo stesso computer - dire che i distruttori non sono stati eseguiti nel bambino? Se non c'è modo di dirlo, non è necessario eseguirli.

Anche se un osservatore esterno può dire, è possibile che attivi in ​​modo errato per eseguire i distruttori nel figlio. Il solito esempio è: immagina di aver scritto qualcosa a stdout prima di chiamare fork, ma è stato memorizzato nella libreria e quindi non è stato ancora consegnato al sistema operativo. In tal caso, l'utente non deve chiamarefclose o fflush su su stdout nel figlio o l'output si verificherà due volte! (Questo è anche il motivo per cui quasi certamente dovrebbe chiamare _exit invece di exit se il exec fallisce.)

Detto questo, ci sono due casi comuni in cui potrebbe essere necessario fare un certo lavoro di pulizia nel bambino. Uno è descrittori di file (non confondere questi con FILE stdio o oggetti iostream) che non dovrebbero essere aperti dopo il exec. Il modo corretto per gestirli è impostare il flag FD_CLOEXEC al più presto dopo che sono aperto (alcuni SO ti permettono di farlo in open stesso, ma non è universale) e/o di passare da 3 a un numero elevato che chiama close (nonfclose) nel bambino. (FreeBSD ha closefrom, ma per quanto ne so, nessun altro lo fa, il che è un peccato perché è davvero abbastanza comodo.)

L'altro caso è un blocco di thread di sistema globale, che - questo è un spinoso e scarsamente standardizzato area - può trattenuto dal padre e dal figlio e quindi ereditato attraverso exec in un processo che non ha idea di contenere un blocco. Questo è ciò che dovrebbe essere pthread_atfork, ma ho letto che in pratica non funziona in modo affidabile. L'unico consiglio che posso offrire è "non tenere serrature quando chiami fork", chiedi scusa.

+0

Grazie! Questo ha martellato tutti i fraintendimenti che ho avuto. (... Soprattutto perché mi permette di ignorare le risorse non pubblicate nel processo figlio con una buona coscienza =) – Phog

+0

Buona spiegazione; sotto Linux è possibile emulare closefrom(), ma in ogni caso, è un trucco molto brutto e non lo consiglierei. – MarkR