API ExitProcess
в целом выполняет следующие действия:
- Вход в критическую секцию блокировки загрузчика
- блокировка куча основного процесса (возвращается
GetProcessHeap()
) через HeapLock
(GetProcessHeap())
(хорошо, конечно, через RtlLockHeap
) (это очень важный шаг для избежания тупика) - затем завершить все потоки в процессе, кроме текущего (по вызову
NtTerminateProcess(0, 0)
) - , затем вызвать
LdrShutdownProcess
- внутри этого загрузчика API пройти по списку загруженных модулей и отправить DLL_PROCESS_DETACH
с lpvReserved
ненулевым. - наконец, вызовите
NtTerminateProcess(NtCurrentProcess(), ExitCode )
, который завершает процесс.
Проблема здесь в том, что потоки завершили в произвольном месте.Например, поток может выделять или освобождать память из любой кучи и находиться в критической секции кучи, когда он завершается.В результате, если код во время DLL_PROCESS_DETACH
пытается освободить блок из той же кучи, он блокируется при попытке войти в критический раздел этой кучи (если, конечно, реализация кучи использует его).
Обратите внимание, что это не влияет на кучу основного процесса, потому что мы вызываем HeapLock
для него до того, как завершит все потоки (кроме текущего).Цель этого: мы ждем в этом вызове, пока все другие потоки не выйдут из критической секции кучи процессов, и после того, как мы получим критическую секцию, никакие другие потоки не смогут войти в нее - потому что куча основного процесса заблокирована.
Итак, когда мы завершаем потоки после блокировки основной кучи - мы можем быть уверены, что никакие другие уничтоженные потоки не находятся внутри критической секции главной кучи или структуры кучи в несогласованном состоянии.Благодаря RtlLockHeap
звоните.Но это связано только с основной кучей процессов.Любые другие кучи в процессе не заблокированы.Таким образом, эти могут находиться в несогласованном состоянии в течение DLL_PROCESS_DETACH
или могут быть получены исключительно уже завершенным потоком.
Итак, использование HeapFree
для GetProcessHeap
или высказывание LocalFree
здесь безопасно (однако не задокументировано).
Использование HeapFree
для любых других куч является небезопасным , если во время завершения процесса вызывается DllMain
.
Также, если вы используете другие пользовательские структуры данных несколькими потоками - это может быть в несогласованном состоянии, потому что другие потоки (которые могут его использовать) завершаются в произвольной точке.
Так что это примечание является предупреждениемчто когда параметр lpvReserved равен не-NULL (что означает DllMain вызывается во время завершения процесса), вы должны быть особенно осторожны при очистке ресурсов.В любом случае, после завершения процесса все выделения внутренней памяти будут свободны операционной системой.