Windows сама создаёт дополнительные потоки в твоей программе
Ты пишешь однопоточную программу для Windows, открываешь ProcessExplorer и удивляешься: у твоей программы на самом деле несколько потоков.
Посмотрим на потоки в программе notepad.exe. Их количество зависит от версии операционной системы.
Windows Бесять:
TID | CPU | Cycles delta | Start Address | Priority |
---|---|---|---|---|
6200 | combase.dll!RoGetServerActivatableClasses+0x1060 | Normal | ||
6924 | notepad.exe+0x23db0 | Normal | ||
2372 | ntdll.dll!TpReleaseCleanupGroupMembers+0x450 | Normal | ||
2648 | ntdll.dll!TpReleaseCleanupGroupMembers+0x450 | Normal | ||
9512 | ntdll.dll!TpReleaseCleanupGroupMembers+0x450 | Normal | ||
10592 | ntdll.dll!TpReleaseCleanupGroupMembers+0x450 | Normal | ||
12056 | ntdll.dll!TpReleaseCleanupGroupMembers+0x450 | Normal |
Windows XP:
TID | CPU | Cycles delta | Start Address | Priority |
---|---|---|---|---|
1620 | notepad.exe+0x73a5 | Normal | ||
1804 | ntdll.dll!RtlSetLastWin32ErrorAndNtStatusFromNtStatus+0x59 | Normal |
Отложенная загрузка DLL
Теперь некоторые DLL загружаются в адресное пространство процесса не до момента передачи управления в стартовую точку программы, а параллельно с ним. Эти дополнительные потоки загружают DLL, чтобы процесс стартовал быстрее.
Каждый такой поток создаёт событие синхронизации и ждёт его в течении 30 секунд. По истечении этого времени поток завершается. Если основной поток завершается до этого времени, то дополнительные системные потоки продолжают ждать 30 секунд. После чего процесс завершается.
Функция ExitProcess
Функция ExitProcess(ExitCode) завершает процесс.
- Все потоки в процессе, кроме вызывающего, завершают своё выполнение (через TerminateThread), не получая уведомления DLL_THREAD_DETACH.
- Состояние всех завершённых потоков становятся сигнальными.
- Точки входа всех загруженных динамических библиотек вызываются с помощью DLL_PROCESS_DETACH.
- После того как все DLL выполнили код завершения, завершается текущий процесс, включая вызывающий поток (через ExitThread).
- Состояние вызывающего потока становится сигнальным.
- Все открытые дескрипторы объектов ядра закрываются.
- Статус завершения процесса изменяется с STILL_ACTIVE на значение ExitCode.
- Состояние объекта процесса становится сигнальным.
Стандартная стартовая функция точки входа в программу
Обычно функция, с которой начинается выполнение программы, лежит в библиотеке времени выполнения. Стартовая функция из библиотеки времени выполнения:
- инициализирует свои объекты;
- запускает неявную функцию Main из главного модуля программы;
- выполняет очистку своих объектов;
- вызывает ExitProcess.
Как видно, в конце концов вызывается ExitProcess, что завершает все дополнительные потоки и основной.
Нестандартная стартовая функция точки входа в программу
Мы можем указать компоновщику свою стартовую функцию. После того, как функция отработала, в регистр eax помещается значение ExitCode, и происходит возврат внутрь кода, вызвавшего стартовую функцию. А там происходит вызов ExitThread с нашим параметром ExitCode. Текущий поток завершается.
Это означает, что если в процессе есть другие потоки, то они остаются работать. Для принудительного завершения потоков используют функции TerminateThread или ExitProcess, которая внутри себя вызывает TerminateThread для каждого потока.
Это также объясняет, почему программа будет висеть в списке процессов, когда главный поток приложения завершился, а дополнительные системные потоки — ещё нет. Чтобы процесс завершился, необходимо либо дождаться завершения всех системных потоков из ntdll.dll (подождать 30 секунд, пока не сработает таймаут), либо вызывать ExitProcess для принудительного завершения всех потоков.