лекции / Shchupak_Yu._Win32_API_Razrabotka_prilozheniy_dlya_Windows
.pdfПроцессы и потоки |
443 |
|
|
то система получает адрес структуры данных объекта hObject и уменьшает в ней счетчик количества пользователей. Как только счетчик обнулится, ядро удаляет объект из памяти.
Если же дескриптор неверен, то функция CloseHandle возвращает значение FALSE, а функция GetLastError — код ERROR_INVALID_HANDLE.
Перед самым возвратом управления функция CloseHandle удаляет соответству" ющую запись из таблицы дескрипторов. После этого дескриптор hObject считает" ся недоступным для данного процесса, и его нельзя более использовать. Но если счетчик пользователей этого объекта не обнулен, то объект остается в памяти. Это означает, что объект используется другими процессами. Когда и остальные процессы завершат свою работу с этим объектом, тоже вызвав функцию CloseHandle, он будет разрушен.
Ранее указывалось, что объекты ядра используются только в рамках процес" са, их создавшего. Но все же иногда возникает необходимость в совместном ис" пользовании объектов ядра несколькими процессами, например, в следующих ситуациях:
объект ядра «проекция файла» позволяет двум процессам, исполняемым на одной машине, совместно использовать одни и те же блоки памяти;
почтовые ящики и именованные каналы дают возможность программам обме" ниваться данными с процессами, исполняемыми на других машинах в сети;
мьютексы, семафоры и события позволяют синхронизировать потоки, испол" няемые в разных процессах, чтобы одно приложение могло уведомить другое приложение об окончании той или иной операции.
Win32 API предоставляет три механизма, позволяющие процессам исполь" зовать одни и те же объекты ядра: а) наследование дескриптора объекта в дочер" нем процессе; б) именованные объекты; в) дублирование дескрипторов объек" тов. Второй из указанных механизмов основан на использовании совпадающих имен для разделяемых объектов. Именно он будет применен в программных примерах, рассматриваемых ниже в разделе «Обмен данными между процес" сами».
Процессы и потоки
При запуске приложения операционная система Windows создает процесс. Про цесс (process) — это совокупность ресурсов, необходимых для выполнения про" граммы. Процесс владеет виртуальным адресным пространством, выполняемым кодом, данными, дескрипторами необходимых объектов и иными ресурсами. Од" нако сам по себе процесс не выполняется. Вместо этого он запускает единствен" ный поток, который часто называют первичным потоком (primary thread). Если процесс имеет только один первичный поток, то, фактически, понятия «процесс» и «поток» совпадают. Первичный поток может создавать другие потоки, те, в свою очередь, новые потоки и т. д.
Поток (thread) — это основная выполняемая единица, для которой операци" онная система выделяет процессорное время. Каждый поток работает со своим контекстом. Контекст потока (thread context) — это структура, содержащая зна"
444 |
Глава 9. Многозадачность |
|
|
чения всех регистров процессора. Кроме того, поток имеет доступ ко всем ресур" сам своего процесса, включая память, открытые файлы и другие ресурсы.
Обычно приложение содержит только один процесс, поэтому термины «про" грамма» и «процесс» часто используются как синонимы. В то же время любой поток процесса может создать дочерний процесс, выполняющийся одновременно с родительским процессом.
Планирование потоков
Чтобы все потоки работали, операционная система выделяет каждому из них оп" ределенное процессорное время. Тем самым создается иллюзия одновременного выполнения потоков. Разумеется, для многопроцессорных систем возможен ис" тинный параллелизм.
Каждый поток может находиться в одном из трех состояний, показанных на рис. 9.1.
Рис. 9.1. Диаграмма состояний потока
В однопроцессорной системе в любой момент времени только один поток мо" жет находиться в состоянии выполнения. Все остальные потоки находятся либо в состоянии готовности, либо в состоянии блокировки.
C самого начала поток попадает в очередь готовых к выполнению потоков. Через какое"то время операционная система выделяет в его распоряжение цент" ральный процессор, и поток переходит в состояние выполнения. В системе Windows реализована система вытесняющего планирования на основе приоритетов. Это означает, что освободившийся процессор продолжает обслуживать тот поток из очереди, который обладает наибольшим приоритетом. О правилах назначения приоритетов мы поговорим чуть позже.
Выбранный для выполнения поток работает в течение некоторого периода, называемого квантом. Windows оперирует квантом потока не как отрезком вре" мени, а как целым числом. Обычно поток стартует со значением кванта, равным 6 — для Windows 2000 Professional или 36 — для Windows 2000 Server.
Каждый раз, когда возникает прерывание от системного таймера, из кванта выполняющегося потока вычитается фиксированное значение 3, и так продолжается до тех пор, пока значение кванта не достигнет нуля. Поэтому под управлением Windows 2000 Professional поток будет выполняться в течение двух интервалов системного таймера, а под управлением Windows 2000 Server — в течение 12 интер" валов.
Интервал (или период) системного таймера обычно равен 10 мс или около 15 мс, в зависимости от аппаратной платформы. Точное значение этого интервала
446 |
Глава 9. Многозадачность |
|
|
Процессам, запускаемым пользователем, присваивается класс Normal. Это са" мые многочисленные процессы в системе. Как правило, они являются интерак" тивными, то есть требуют постоянного взаимодействия с пользователем, как, на" пример, графические или текстовые редакторы. Процессы класса Normal делятся на процессы переднего плана (foreground) и фоновые (background). Для процесса,
скоторым пользователь в данный момент работает, то есть для процесса передне" го плана, уровень приоритета поднимается на две единицы. Это повышает ком" фортабельность общения пользователя с прикладной программой.
Создавать процессы, относящиеся к классу High, следует с большой осторож" ностью. Если поток с классом приоритета High занимает процессор достаточно долго, то в это время другие потоки вообще не получат доступа к процессору. Обыч" но с классом High работают некоторые системные процессы, которые большую часть времени ожидают какого"либо события, например, winlogon.exe. Если в вашем приложении какая"то подзадача требует быстрой реакции на некоторое событие, то вы можете повышать класс приоритета процесса до значения High именно на тот период, когда решается эта подзадача, а затем возвращать его к зна" чению Normal. Для изменения класса приоритета процесса во время работы при" ложения может применяться функция SetPrioriryClass.
Практически никогда вы не должны использовать класс приоритета Realtime, поскольку в этом случае ваше приложение будет прерывать системные потоки, управляющие мышью, клавиатурой и дисковыми операциями. Система будет фактически парализована. Только в особых случаях, когда программа взаимодей" ствует непосредственно с аппаратурой или решаются короткие подзадачи, для которых нужно гарантировать отсутствие прерываний, класс приоритета Realtime может быть кратковременно использован.
По умолчанию создаваемый поток получает базовый приоритет в соответствии
склассом своего процесса. После создания потока его приоритет может изме" няться как операционной системой, так и приложением с помощью функции SetThreadPriority. В табл. 9.2 приведены относительные приоритеты потоков.
Таблица 9.2. Относительные приоритеты потоков
Относительный |
Флаг в функции |
Описание |
приоритет |
SetThreadPriority |
|
|
|
|
Idle |
THREAD_PRIORIRY_IDLE |
Для процессов класса Realtime прио- |
|
|
ритет потока равен 16, для процес- |
|
|
сов остальных классов равен 1 |
Lowest |
THREAD_PRIORIRY_LOWEST |
Приоритет потока меньше базового |
|
|
приоритета на 2 |
Below normal |
THREAD_PRIORIRY_BELOW_NORMAL |
Приоритет потока меньше базового |
|
|
приоритета на 1 |
Normal |
THREAD_PRIORIRY_NORMAL |
Приоритет потока равен базовому |
|
|
приоритету |
Above normal |
THREAD_PRIORIRY_ABOVE_NORMAL |
Приоритет потока больше базового |
|
|
приоритета на 1 |
Highest |
THREAD_PRIORIRY_HIGHEST |
Приоритет потока больше базового |
|
|
приоритета на 2 |
Time critical |
THREAD_PRIORIRY_TIME_CRITICAL |
Для процессов класса Realtime прио- |
|
|
ритет потока равен 31, для процес- |
|
|
сов остальных классов равен 15 |
|
|
|
Управление процессами |
447 |
|
|
Управление процессами
Самый распространенный способ начала процесса — это осуществить запуск при" ложения в Проводнике (Explorer), либо в меню Пуск (Start), либо набрав название программы в командной строке. Кроме того, Win32 API содержит несколько функ" ций, которые можно использовать для создания и управления процессами.
Использование функции CreateProcess
Функция CreateProcess создает новый процесс и его первичный поток. Она имеет следующий прототип:
BOOL CreateProcess(
LPCTSTR lpApplicationName, // имя исполняемого файла LPTSTR lpCommandLine, // командная строка
LPSECURITY_ATTRIBUTES processAttributes, // атрибуты доступа к процессу LPSECURITY_ATTRIBUTES threadAttributes, // атрибуты доступа к потоку BOOL bInheritHandles, // флаг наследования дескрипторов
DWORD dwCreationFlags, // флаги создания и флаги класса приоритета LPVOID lpEnvironment, // указатель на параметры настройки окружения LPCTSTR lpCurrentDirectory, // путь к текущему каталогу LPSTARTUPINFO lpStartupInfo, // указатель на структуру STARTUPINFO LPPROCESS_INFORMATION lpProcessInformation // указатель на структуру
// PROCESS_INFORMATION
);
Имя исполняемого файла можно задать в первом или втором параметре. Параметр lpCommandLine позволяет указать полную командную строку, исполь"
зуемую функцией CreateProcess при создании нового процесса. Разбирая эту строку, функция полагает, что первый компонент в ней представляет собой имя исполняе" мого файла. Если в этом имени расширение не указано, она считает его .exe. Далее функция приступает к поиску заданного файла и делает это в следующем порядке:
1.Текущий каталог вызывающего процесса.
2.Системный каталог Windows.
3.Основной каталог Windows.
4.Каталоги, перечисленные в переменной окружения PATH.
Конечно, если в имени файла указан полный путь доступа, то система сразу обращается туда и не просматривает эти каталоги.
Чаще всего параметру lpApplicationName передается значение NULL, а имя ис" полняемого файла содержится в параметре lpCommandLine.
Перед вызовом функции CreateProcess вы должны определить две структурные переменные типа STARTUPINFO и PROCESS_INFORMATION:
STARTUPINFO si; PROCESS_INFORMATION pi;
Адреса этих структур передаются в двух последних параметрах функции
CreateProcess.
Элементы структуры STARTUPINFO задают атрибуты отображения нового про" цесса. Надо сказать, что большинство приложений порождает процессы с атрибу" тами по умолчанию. Но и в этом случае вы должны инициализировать все поля структуры STARTUPINFO хотя бы нулевыми значениями, а в поле cb занести размер этой структуры.
448 |
Глава 9. Многозадачность |
|
|
Если запуск нового процесса осуществлен успешно, то функция CreateProcess возвращает значение TRUE и помимо этого заполняет поля структуры PROCESS_ INFORMATION, которая определена следующим образом:
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess; |
// дескриптор нового процесса |
|
HANDLE hThread; |
// дескриптор первичного потока нового процесса |
|
DWORD |
dwProcessId; // идентификатор нового процесса |
|
DWORD |
dwThreadId; |
// идентификатор первичного потока нового процесса |
} PROCESS_INFORMATION;
Описание остальных параметров функции CreateProcess можно найти в спра" вочных материалах MSDN. Для этих параметров часто используются значения по умолчанию.
Когда поток в приложении вызывает функцию CreateProcess, система создает объект ядра «процесс» с начальным значением счетчика его пользователей, рав" ным единице. Затем система создает для нового процесса виртуальное адресное пространство1 и загружает в него код и данные как для исполняемого файла, так и для любых DLL, если они используются в работе. Далее система формирует объект ядра «поток» со счетчиком, равным единице, для первичного потока ново" го процесса.
Первичный поток начинает с исполнения стартового кода из библиотеки C/C++, который в конечном счете вызывает функцию WinMain, wWinMain, main или wmain2 в запускаемой программе.
Завершение процесса
Процесс можно завершить четырьмя способами:
Входная функция первичного потока, например WinMain, возвращает управле" ние (предпочтительный способ).
Один из потоков процесса вызывает функцию ExitProcess.
Поток другого процесса вызывает функцию TerminateProcess (нежелательный способ).
Все потоки процесса завершаются по своей воле. Но это случается очень редко.
Рекомендуется проектировать приложение так, чтобы его процесс завершался только после возврата управления функцией первичного потока. Это единствен" ный способ, который гарантирует корректную очистку всех ресурсов, принадле" жащих первичному потоку. При таком завершении любые объекты C++, создан" ные данным потоком, уничтожаются соответствующими деструкторами. Система освобождает память, которую занимал стек потока, и устанавливает код заверше" ния процесса, который и возвращает входная функция.
Вы можете также завершить процесс, вызвав функцию ExitProcess. В справоч" ных материалах MSDN этот способ указан как рекомендуемый. В то же время Дж. Рихтер указывает [5], что возможны ситуации, когда при данном способе
1Более подробно о виртуальном адресном пространстве говорится в разделе «Виртуальная память. Адресное пространство процесса».
2С символа «w» начинаются имена Unicode"версий входных функций первичного потока. Функции main и wmain используются в консольных приложениях.
Управление процессами |
449 |
|
|
завершения процесса не для всех объектов C++ будут вызваны деструкторы. Ко" нечно, операционная система и в этом случае корректно очистит все ресурсы, выде" ленные процессу. Но при этом весьма вероятна утечка памяти или других ресурсов.
Вызов функции TerminateProcess тоже завершает процесс. Главное отличие этой функции от ExitProcess заключается в том, что ее может вызвать любой поток и за" вершить при этом любой процесс. Пользуйтесь функцией TerminateProcess лишь в крайнем случае, когда иным способом завершить процесс не удается. Процесс не получает абсолютно никаких уведомлений, что он завершается, и приложение не может выполнить очистку ресурсов или предотвратить свое неожиданное за" вершение. При этом теряются все данные, которые программа не успела перепи" сать из памяти на диск. Но операционная система и в этом случае освобождает все принадлежавшие процессу ресурсы.
Четвертая ситуация может возникнуть, если все потоки вызвали ExitThread или они были закрыты другими потоками, вызвавшими функцию TerminateThread. Об" наружив, что в процессе нет исполняющихся потоков, операционная система не" медленно завершает его. Код завершения процесса приравнивается к коду завер" шения последнего потока.
В случае завершения процесса системой выполняются следующие действия.
Выполнение всех потоков в процессе прекращается.
Все User" и GDI"объекты, созданные процессом, уничтожаются, а объекты ядра закрываются, если их не использует другой процесс.
Объект ядра «процесс» переходит в свободное, или незанятое (signaled), со" стояние.
Счетчик пользователей объекта ядра «процесс» уменьшается на единицу.
Запуск обособленных дочерних процессов
В большинстве случаев приложение создает другие процессы как обособленные (detached processes). Это значит, что после создания и запуска нового процесса родительскому процессу нет нужды взаимодействовать с ним или ждать, пока он закончит работу. Именно так и действует Explorer. Это приложение запускает для пользователя новые процессы, а после этого более не следит за их судьбой.
Приведенное ниже приложение CreateMyProcess демонстрирует, как можно выз" вать из вашей программы стандартное приложение Windows Калькулятор (calc.exe).
Создавая приложение CreateMyProcess, добавьте к нему ресурс меню с иден" тификатором IDR_MENU1. Меню должно содержать один пункт с именем Create process и идентификатором IDM_CREATE_PROCESS. Скопируйте также в папку про" екта CreateMyProcess файлы KWnd.h и KWnd.cpp из листинга 1.2 и добавьте их в состав проекта.
Листинг 9.1. Проект CreateMyProcess
//////////////////////////////////////////////////////////////////////
// CreateMyProcess.cpp #include <windows.h> #include "resource.h" #include "KWnd.h"
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //====================================================================
450 Глава 9. Многозадачность
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
KWnd mainWnd("CreateMyProcess", hInstance, nCmdShow, WndProc, MAKEINTRESOURCE(IDR_MENU1), 100, 100, 400, 300);
while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg);
}
return (msg.wParam);
}
//==================================================================== LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HDC hDC; PAINTSTRUCT ps;
STARTUPINFO si;
static PROCESS_INFORMATION pi; BOOL success;
switch (uMsg)
{
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDM_CREATE_PROCESS: ZeroMemory( &si, sizeof(si) ); si.cb = sizeof(si);
success = CreateProcess( NULL, "calc.exe", NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
if (!success)
MessageBox(hWnd, "Error of CreateProcess", NULL, MB_OK); break;
default:
break;
}
case WM_PAINT:
hDC = BeginPaint(hWnd, &ps); EndPaint(hWnd, &ps);
break;
case WM_DESTROY: CloseHandle(pi.hProcess); CloseHandle(pi.hThread); PostQuitMessage(0); break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
//////////////////////////////////////////////////////////////////////
