2011-01-11 4 views
5

Я пытаюсь создать новый процесс из моего проекта на C++, используя fork-exec. Я использую fork-exec для создания двунаправленного канала для дочернего процесса. Но я боюсь, что мои ресурсы в разветвленном процессе не будут освобождены должным образом, так как exec-call полностью захватит мой процесс и не назовет каких-либо деструкторов.Освобождение ресурсов C++ и fork-exec?

Я попытался обойти это, выбросив исключение и вызвав execl из блока catch в конце main, но это решение не уничтожает ни одного синглтона.

Есть ли разумный способ достичь этого безопасно? (Надеюсь, избегая atExit хаков)

Пример: Следующий код выводит:

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

Несмотря на то, раздвоенный процесс также имеет копию синглтона, которая должна быть уничтожена, прежде чем я называю 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

С какими ресурсами вы владеете? В основном файловые дескрипторы выживают exec(), которые вы можете отметить close-on-exec, чтобы ядро ​​их закрыло. http://pubs.opengroup.org/onlinepubs/009695399/functions/exec.html –

+1

Кстати, если деструкторы были вызваны в разветвленном дочернем, а также в родительском, это приведет к вызову конструкторов один раз (в родительском) и деструкторы дважды (как в родительском, так и в дочернем). –

+1

Я считаю, что здесь опасно приближается к неопределенному поведению, но класс Resources представляет несколько одноэлементных классов, которые я использую для обертывания C-библиотек в RAII-объекты. И если fork действительно копирует все состояние процесса, я должен, вероятно, вызвать RAII-деструкторы, прежде чем я вызову exec(). Разумеется, это было бы безумным, если бы ресурсы были внешними по отношению к программе (например, соединение с базой данных). Но поскольку они являются библиотеками, я считаю, что они должны быть выпущены как в родительском, так и в дочернем процессе. [Если это помогает, я в настоящее время обертываю ncurses, nscapi и SDL в singleletons] – Phog

ответ

3

Есть отличные шансы, что вы не необходимости называть любые деструкторов между fork и exec. Да, fork делает копию всего вашего состояния процесса, включая объекты, у которых есть деструкторы, и exec стирает все это состояние. Но действительно ли это имеет значение? Может ли наблюдатель извне вашей программы - другой, несвязанный процесс, запущенный на том же компьютере, - сказать, что деструкторы не были запущены в дочернем? Если нет возможности сказать, нет необходимости запускать их.

Даже если внешний наблюдатель может сказать, может быть активно ошибочно для запуска деструкторов у ребенка. Обычный пример для этого: представьте, что вы написали что-то stdout перед вызовом fork, но он получил буферизацию в библиотеке и, тем не менее, до сих пор не был доставлен в операционную систему. В этом случае вы не должны позвонить fclose или fflush по телефону stdout у ребенка, или выход будет происходить дважды! (Это также объясняет, почему вы почти наверняка должны вызвать _exit вместо exit если exec терпит неудачу.)

Сказав все это, есть два случая, когда вы, возможно, потребуется сделать некоторые очистки работы в ребенке. Один из них - это дескрипторы файлов (не путать их с файлами stdio или iostream), которые не должны быть открыты после exec. Правильный способ борьбы с ними - установить на них флаг FD_CLOEXEC после того, как они будут открыты (некоторые операционные системы позволяют это делать только в open, но это не универсально) и/или цикл от 3 до некоторое большое количество звонков close (неfclose) у ребенка. (FreeBSD имеет closefrom, но, насколько я знаю, никто не делает этого, что является позором, потому что это очень удобно.)

Другой случай - системно-глобальные блокировки потоков, которые - это колючие и плохо стандартизированные area - may завершение работы как родительским, так и дочерним, а затем унаследовано через exec в процесс, который не знает, что он содержит блокировку. Это то, что должно быть pthread_atfork, но я прочитал, что на практике он не работает надежно. Единственный совет, который я могу предложить, - «не держите никаких замков, когда звоните fork», извините.

+0

Спасибо! Это забило все недоразумения, которые у меня были. (... Тем более, что это позволяет мне игнорировать нереализованные ресурсы в процессе ребенка с хорошей совестью =) – Phog

+0

Хорошее объяснение; под Linux можно эмулировать closefrom(), но в любом случае это очень уродливый взлом, и я бы не рекомендовал его. – MarkR

Смежные вопросы