Опасная функция ExitProcess.
Как вы думаете, какая их двух функций опаснее: ExitProcess или TerminateProcess? Ответ, конечно, зависит от определения того, что считать более безопасным. Однако если задать этот вопрос нескольким людям, большинство автоматически укажет на TerminateProcess. Почему? Потому что TerminateProcess в отличие от ExitProcess не делает попыток освободить занятые ресурсы. К примеру, программа не сможет записать несохранённые данные на диск, тем самым нарушив их целостность.
В реальности функция ExitProcess ни чем не безопаснее, чем TerminateProcess, а может и ещё хуже, так как внушает чувство ложной безопасности. Рассмотрим алгоритм работы ExitProcess. Помимо закрытия всех открытых описателей (handles) объектов и установки состояния объектов завершаемого процесса и принадлежащих ему потоков в состояние «signaled», ExitProcess делает две важные вещи:
- Завершает все потоки процесса, кроме потока, вызвавшего ExitProcess;
- Оповещает все загруженные DLL, вызывая DllMain c кодом DLL_PROCESS_DETACH.
В случае если один из потоков выполнял код, защищённый критической секцией, в момент своего завершения, то критическая секция может остаться захваченной навсегда. Если один из обработчиков DLL_PROCESS_DETACH попытается захватить эту секцию, то он будет заблокирован на ней навсегда. (Это пример из статьи в MSDN, посвященной ExitProcess).
Подобная ситуация возникает чаще, чем может показаться. Фактически, любые глобальные данные (всяческие списки, таблицы и т.п.), используемые из нескольких потоков, защищаются критическими секциями или чем-то подобным. Даже если вы уверены, что ваше приложение не использует критические секции во время завершения программы, остаются системные функции, которые также могут быть источником этой проблемы.
И наконец в общем случае вы не можете быть уверены какие DLL загружены в ваш процесс, так как:
- Набор библиотек может отличаться на разных версиях операционной системы;
- Набор библиотек может отличаться в зависимости он версий сторонних библиотек, которые использует ваше приложение;
- Другие приложения могут внедрять свой код в ваш процесс, например с помощью SetWindowsHookEx или CreateRemoteThread. В этом случае, кстати, вероятность того, что внедряемая DLL синхронизирует доступ к глобальным данным с помощью критических секций стремится к 100%.
Т.е. это означает, что вызывая ExitProcess, вы выполняете заранее неизвестный код, обладающий неизвестным поведением.
Единственный способ сделать вызов ExitProcess безопасным – подготовиться к вызову этой функции должным образом: корректно завершить все потоки и вызвать ExitProcess из единственного оставшегося потока. Если в процессе подготовки что-то пошло не так, то имеет смысл вызвать TerminateProcess вместо ExitProcess, так как хуже все равно не будет – состояние программы уже не определено. Следует также помнить, что CRT функции exit, _exit и прочие, а также выход из функции main в конце концов приводят в вызову ExitProcess. Т.е. они подвержены тем же самым проблемам, что и ExitProcess.
Не догнал что имеется ввиду под критической секцией. Если EnterCriticalSection, то это user mode синхропримитив и должен корректно отваливаться при завершении процесса. Или имеется ввиду CreateMutex? Тогда это кернел косяк однако, обработать синхропримитивы зависшие на умерших процессах не должно быть так сложно.
Я вот только одного не понимаю: а как тогда не рушатся приложения, которые завершили поток функции main, но у которых продолжают работать порожденные потоки? Ситуация, возможно, не очень типичная, но я вроде никаких глюков в таких случаях не встречал….
Идеально конечно же через PostThreadMessage нотифицировать потокам о завершении, ждать пока хендлы потоков засигналятся и только тогда завершать процесс.
Я имею ввиду CriticalSection и все остальные примитивы кроме Mutex.
Они, видимо, не пользуются стандартной C runtime library. Или завершают основной поток минуя runtime library.
>>Они, видимо, не пользуются стандартной C runtime library. Или завершают основной поток минуя runtime library.
Может я чего-то не понимаю, но мне кажется в первом предложении ошибка, должно быть “какая из 2-х функций опаснее”…
Точно. Поправил.
Хе-хе. Всё оказалось даже интереснее. Специально для этого случая в EnterCriticalSection добавили проверку. Расскажу об этом подробнее отдельным постом.
http://blogs.msdn.com/oldnewthing/archive/2007/05/03/2383346.aspx
Тут вроде товарищ Чен написал, что после вызова ExitProcess все вызовы EnterCriticalSection срабатывают.