Lection 9_1
.docГлава 7. Управление процессами в среде ОС Windows
Процесс - это исполняемая программа, которая во время выполнения представлена двумя компонентами:
объектом ядра, через который ОС управляет процессом, и в котором хранится статическая информация о процессе;
адресным пространством, в котором содержится код и данные всех EXE и DLL модулей, стеки потоков и другая необходимая информация.
Для того, чтобы процесс мог работать, в нем должен быть минимум один поток, так как:
-
потоки отвечают за исполнение кода, содержащегося в адресном пространстве процесса;
-
один процесс может иметь несколько потоков, которые как бы одновременно исполняют код в адресном пространстве процесса, для этого каждый поток должен располагать собственным набором регистров;
-
если у процесса нет потоков, то система автоматически уничтожает его;
-
каждому потоку система отводит определённое процессорное время (отрезки времени, называемые квантами).
Windows 2000 в полной мере использует возможности машин с несколькими процессорами. Она способна закрепить каждый поток за отдельным процессором, и тогда два потока работают действительно одновременно. Ядра Win 9x/ME работают только с одним процессором, остальные простаивают.
В среде ОС Windows предоставляются возможности работы с процессами. Если нужно запустить новую программу, то нужно создать новый процесс. Для этого используется стандартный системный вызов CreateProcess. Однако пользоваться им не очень удобно. Приходится определять значение множества аргументов. Для этой цели можно использовать менее сложный механизм.
Проще всего использовать WinExec. В случае, если произошла ошибка, функция WinExec возвращает значение, меньшее 32. Если программа успешно запущена, функция возвращает дескриптор новой программы (который не может быть меньше 32). После запуска новой программы функция WinExec немедленно передает управление программе, из которой был осуществлен вызов, то есть она не ждет момента, пока вновь запущенная программа завершит работу,
Другой вариант запуска программ, это использование API функции ShellExecute. Эта функция похожа на функцию WinExec. Она также поддерживает обработку типов файлов, зарегистрированных в операционной системе. Функцию ShellExecute можно использовать, например, для того, чтобы открыть Internet Explorer c необходимой ссылкой:
ShellExecute(Handle, nil, 'http://www.mysite.com', nil, nil, SW_SHOW);
Также эту функцию можно использовать для открытия соответствующего подкаталога для просмотра. Вот пример такого вызова:
ShellExecute(handle, "open", path, NULL, NULL, SW_SHOWNORMAL);
Параметр path - это путь к необходимому подкаталогу. В ответ на вызов этой функции будет открыт проводник Windows.
Функция ShellExecute имеет следующий синтаксис, ее параметры описаны в таблице 7.1:
ShellExecute( HWND hwnd, LPCTSTR lpOperation, LPCTSTR lpFile,
LPCTSTR lpParameters, LPCTSTR lpDirectory,INT nShowCmd );
Таблица 7.1. Параметры функции ShellExecute
Параметр |
Описание |
Hwnd |
Указатель на родительское окно, из которого осуществляется вызов функции. |
LpOperation |
Указатель на строку, которая указывает на операцию, которую нужно выполнить. Допустимы следующие параметры: "open" - функция открывает файл, указанный в параметре lpFile. Файл может быть программой или другим файлом. "print" будет печататься файл, указанный в lpFile. Если файл является программой, то он будет запущен. "explore" функция вызовет окно для навигации по файлам и подкаталогам, начиная с того подкаталога, который указан в параметре lpFile. Этот параметр может быть NULL. В этом случае функция будет открывать файл, указанный в lpFile. |
LpFile |
Указатель на строку, которая указывает на файл, который нужно открыть или напечатать. |
LpParameters |
Этот параметр содержит командную строку, если запускается исполняемый файл. Также этот параметр может быть NULL, если программа не требует параметров для запуска. |
LpDirectory |
Указатель на строку, которая содержит подкаталог по умолчанию для работы программы. |
nShowCmd |
Если параметр lpFile содержит исполняемый файл, то в этом параметре указывается режим отображения окна запущенной программы. SW_HIDE, SW_MAXIMIZE, SW_MINIMIZE, SW_RESTORE, SW_SHOW, SW_SHOWDEFAULT, SW_SHOWMAXIMIZED, SW_SHOWMINIMIZED SW_SHOWMINNOACTIVE SW_SHOWNA SW_SHOWNOACTIVATE SW_SHOWNORMAL |
Вызовы, наподобие ShellExecute и WinExec, удобно использовать для выполнения простейших действий вроде открытия файлов или запуска программ, контроль за действиями которых не имеет значения, или результаты их работы не влияют на вызывавшую программу. Если же есть необходимость создать новый процесс, используя при этом некоторые дополнительные параметры, то необходимо использовать функцию CreateProcess из Windows API, параметры которой приведены в таблице 7.3, а параметры, используемые при создании процесса приведены в таблице 7.4 .
Таблица 7.3 Параметры функции CreateProcess
Аргумент |
Описание |
lpApplicatlonName |
Имя программы (или NULL, если имя программы указано в командной строке) |
lpCommandLine |
Командная строка |
lpProcessAttributes |
Атрибуты безопасности для дескриптора процесса, возвращаемого функцией |
lpThreadAttributes |
Атрибуты безопасности для дескриптора потока, возвращаемого функцией |
bInheritHandlers |
Указывает, наследует ли новый процесс дескрипторы, принадлежащие текущему процессу |
dwCreationFlags |
Параметры создания процесса |
lpEnvironment |
Значения переменных окружения (или NULL, в случае если наследуется текущее окружение) |
lpCurrentDlrectory |
Текущий каталог (или NULL, если используется текущий каталог текущего процесса) |
lpStartupInfo |
Структура STARTUPINFO, содержащая информацию о запуске процесса |
lpProcessInformation |
Возвращаемые функцией дескрипторы и идентификаторы ID процесса и потока |
Таблица 7.4 Параметры, используемые при создании процесса
Флаг |
Значение |
CREATE_DEFAULT_ERROR_MODE |
He наследовать текущий режим сообщений об ошибках |
CREATE_NEW_CONSOLE |
Создать новую консоль |
CREATE_NEW_PROCESS_GROUP |
Создать новую группу процессов |
CREATE_SEPARATE_WOW_VDM |
Запустить 16-битное приложение в его собственном адресном пространстве |
CREATE_SHARED_WOW_VDM |
Запустить 16-битное приложение в адресном пространстве общего доступа |
CREATE_SUSPENDED |
Создать процесс в приостановленном состоянии |
CREATE_UNICODE_ENVIRONMENT |
Блок переменных окружения записан в формате UNICODE |
DEBUG_PROCESS |
Запустить процесс в отладочном режиме |
DEBUG_ONLY_THIS_PROCESS |
Предотвратить отладку процесса текущим отладчиком (используется при отладке родительского процесса) |
DETACHED_PROCESS |
Новый консольный процесс не имеет доступа к консоли родительского консольного процесса |
Аргумент lpStartupInfo — это указатель на структуру STARTUPINFO. Описание структуры приведено ниже.
typedef struct _STARTUPINFO {
DWORD cb;
LPTSTR lpReserved;
LPTSTR lpDesktop;
LPTSTR lpTitle;
DWORD dwX;
DWORD dwY;
DWORD dwXSize;
DWORD dwYSize;
DWORD dwXCountChars;
DWORD dwYCountChars;
DWORD dwFillAttribute;
DWORD dwFlags;
WORD wShowWindow;
WORD cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;
Параметр dwCreationFlags, определяющий какие свойства будет иметь создаваемый процесс. Параметр dwCreationFlags позволяет задать и класс приоритета с одним из возможных значений, указанных в таблице 7.6:
Таблица 7.6 Возможные значения параметра dwCreationFlags для задания приоритета
Значение |
Класс приоритета |
IDLE_PRIORITY_CLASS |
Idle (простаивающий) |
BELOW_NORMAL_PRIORITY_CLASS |
Below normal (ниже обычного) |
NORMAL_PRIORITY_CLASS |
Normal (обычный) |
ABOVE_NORMAL_PRIORITY_CLASS |
Above normal (выше обычного) |
HIGH_PRIORITY_CLASS |
High (высокий) |
REALTIME_PRIORITY_CLASS |
Realtime (реального времени) |
Поля этой структуры содержат заголовок консоли, начальный размер и позицию нового окна и перенаправление стандартных потоков ввода/вывода. Новая программа может проигнорировать все эти параметры в зависимости от того, как она написана. Поле dwFlags этой структуры содержит флаги, установленные в соответствии с тем, какие из остальных полей структуры хотелось бы использовать при запуске новой программы. Часто нет надобности использовать какие-либо из полей структуры STARTUPINFO, но все равно необходимо функции CreateProcess передать корректный указатель на пустую структуру STARTUPINFO. Также обязательно надо передать размер структуры в поле cb и присвоить полю dwFlags значение 0.
Функция CreateProcess записывает в аргумент lpProcessInformation указатель на структуру, содержащую дескрипторы и идентификаторы ID нового процесса и потока. Доступ к этим дескрипторам определяется аргументами lpProcessAttributes и lpThreadAttributes.
Некоторые аргументы функции CreateProcess относятся к консольным приложениям. Другие имеют значение для всех типов программ. Зачастую при обращении к CreateProcess можно не заполнять структуру STARTUPINFO, но в любом случае необходимо передать функции указатель на существующую в памяти структуру, даже если эта структура не заполнена.
Функция CreateProcess возвращает значение типа BOOL. По завершении ее работы полезная для программиста информация размещается функцией в структуре типа PROCESS_INFORMATION, указатель на которую возвращается при помощи параметра lpProcessInformation этой функции. В структуре PROCESS_INFORMATION содержится идентификатор и дескриптор нового процесса, а также идентификатор и дескриптор самого первого потока, принадлежащего новому процессу. Эти сведения могут использоваться для того, чтобы сообщить о новом процессе другим программам, а также для того, чтобы контролировать новый процесс. Структура PROCESS_INFORMATION имеет следующее описание:
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION;
Следует помнить, что создание нового процесса влечёт за собой создание объектов ядра «процесс» и «поток», их счетчики числа пользователей инициализируются единицей. Функция CreateProcess (перед возвратом управления) открывает объекты ядра «процесс» и «поток», заносит их дескрипторы в элементы hProcess и hThread структуры PROCESS_INFORMATION и увеличивает числа пользователей до двух. Это значит, что перед тем как система сможет высвободить из памяти объект «процесс», процесс должен быть завершен (счетчик уменьшен до 1), а родительский процесс обязан вызвать функцию CloseHandle (обнулить счетчик), аналогично должно быть и с потоком. Созданному объекту ядра «процесс» (и «поток») присваивается уникальный идентификатор, ни у каких объектов этого типа не может быть одинаковых идентификаторов в системе. Автоматически присваиваемые идентификаторы могут повторно использоваться системой.
При создании процесса с использованием вызова CreateProcess можно передать новому процессу дескрипторы открытых файлов. Однако это редко бывает полезным за исключением случаев, когда необходимо объединить стандартные потоки ввода/вывода нескольких консольных приложений. В этой ситуации дескрипторы файлов стандартных потоков ввода/вывода обладают заранее определенными значениями. Если новый процесс получает дескриптор открытого файла, он не может определить его значения, если только оно не определено заранее. Обладая дескриптором процесса, можно управлять процессом при помощи функций Windows API. Список этих функций приведен в таблице 7.7.
Таблица 7.7 Функции Windows API для управления процессом
Вызов |
Назначение |
Требуемый уровень доступа |
CreateRemoteThread |
Создает поток в другом процессе |
PROCESS_CREATE_THREAD |
GetExitCodeProcess |
Возвращает код завершения процесса |
PROCESS_QUERY_INFORMATION |
GetGuiRecources |
Определяет, сколько объектов USER или GDI (Graphical Device Interface) используется процессом |
PROCESS_QUERY_INFORMATION |
SetPriorityClass |
Устанавливает базовый приоритет процесса |
PROCESS_QUERY_INFORMATION |
GetPriorityClass |
Возвращает базовый приоритет, процесса |
PROCESS_QUERY_INFORMATION |
SetProcessAffinityMask |
Определяет, какие из процессоров используются процессом в качестве основных |
PROCESS_SET_INFORMATION |
GetProcessAfflnityMask |
Устанавливает, какие из процессоров используются процессом в качестве основных |
PROCESS_QUERY_INFORMATION |
SetProcessPriorityBoost |
Позволяет или запрещает Windows динамически изменять приоритет процесса |
PROCESS_SET_INFORMATION |
GetProcessPriorityBoost |
Возвращает статус изменения приоритета процесса |
PROCESS_GET_INFORMATION |
SetProcessShutDownParameters |
Определяет, в каком порядке система закрывает процессы при завершении работы всей системы |
Необходимо вызвать изнутри процесса |
GetProcessShutDownParameters |
Возвращает статус механизма завершения работы системы |
PROCESS_QUERY_INFORMATION |
SetProcessWorklngSetSize |
Устанавливает минимальный и максимальный допустимый объем физической оперативной памяти, используемый процессом |
PROCESS_SET_INFORMATION |
GetProcessWorklngSetSize |
Возвращает информацию об использовании физической памяти процессом |
PROCESS_GET_INFORMATION |
TerminateProcess |
Корректное завершение работы процесса |
PROCESS_TERMINATE |
ExitProcess |
Немедленное завершение процесса |
Необходимо вызвать изнутри процесса |
GetProcessVersion |
Возвращает версию Windows, в среде которой хотел бы работать процесс |
Использует ID процесса |
GetProcessTimes |
Возвращает степень использования CPU процессом |
PROCESS_GET_INFORMATION |
GetStartUpInfo |
Возвращает структуру STARTUPINFO, переданную процессу при обращении к CreateProcess |
Необходимо вызвать изнутри процесса |
В таблице 7.7 указывается различный уровень доступа для различных функций для работы с процессами. Права доступа определяют точный набор возможностей, которые могут быть предоставлены или запрещены процессу, когда он пытается получить доступ к использованию объекта. Эти права доступа описываются так называемыми масками доступа, которые представляют собой 32-х битные числа. Возможные уровни доступа и их описание приведено в таблице 7.8.
Таблица 7.8. Уровни доступа и их описание
Значение уровня доступа |
Описание |
PROCESS_ALL_ACCESS |
Предоставляет все возможные права доступа для объекта процесса |
PROCESS_CREATE_PROCESS |
Необходимый уровень доступа для создания процесса |
PROCESS_CREATE_THREAD |
Необходимый уровень доступа для создания потока |
PROCESS_DUP_HANDLE |
Необходимый уровень доступа для копирования указателя |
PROCESS_QUERY_INFORMATION |
Необходимый уровень доступа для получения некоторой информации о процессе, такой как класс приритета процесса |
PROCESS_SET_INFORMATION |
Необходимый уровень доступа для установки некоторой информации о процессе, такой как класс приоритета процесса |
PROCESS_TERMINATE |
Необходимый уровень доступа для завершения процесса |
PROCESS_VM_OPERATION |
Необходимый уровень доступа для осуществления операций над адресным пространством процесса |
PROCESS_VM_READ |
Необходимый уровень доступа для чтения памяти внутри процесса |
PROCESS_VM_WRITE |
Необходимый уровень доступа для записи в память внутри процесса |
SYNCHRONIZE |
Необходимый уровень доступа для ожидания завершения процесса |
Дескриптор, возвращаемый функцией CreateProcess имеет значение PROCESS_ALL_ACCESS для уровня прав доступа к объекту процесса.
Необходимо помнить о том, что идентификатор ID-процесса не идентичен с дескриптором процесса. Дескрипторы процессов нельзя просто так передавать из процесса в процесс. Это означает, что если необходимо управлять процессом из другого процесса, то нужно получить дескриптор управляемого процесса. Для идентификации процесса другими процессами служит идентификатор ID этого процесса. Этот идентификатор можно передать из процесса в процесс. Преобразовать идентификатор ID-процесса в его дескриптор можно при помощи функции OpenProcess, но для этого нужно обладать необходимыми привилегиями. Определить идентификатор текущего процесса можно при помощи функции GetCurrentProcessId. Эта функция позволяет узнать идентификатор собственного процесса и передать этот идентификатор другому процессу. Получив идентификатор ID процесса, другой процесс сможет открыть его дескриптор. При обращении к функции OpenProcess необходимо указать требуемый уровень доступа к открываемому процессу.
В примере показано использование функции CreateProcess, а также функции WaitForSingleObject. Функция WaitForSingleObject переводит программу в режим ожидания завершения запущенного процесса. Эта функция будет подробно рассмотрена в следующей главе.
Пример 7.1. Использование функции CreateProcess
FillChar(StartupInfo, SizeOf(StartupInfo), 0);
StartupInfo.cb := SizeOf(StartupInfo);
StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
StartupInfo.wShowWindow := SW_Minimize; {SW_HIDE};
TheCommand := AppPath +“/i/p”;// формируется коммандная строка
if( not CreateProcess( nil, PChar(TheCommand),nil, nil, false,
0, nil, nil, StartupInfo, ProcInfo ))
then Beep; // Запуск процесса не удался
// Ожидаем завершения процесса
WaitForSingleObject(ProcInfo.hProcess, INFINITE);
// Закрываем дескрипторы процесса и потока
CloseHandle(ProcInfo.hProcess);
CloseHandle(ProcInfo.hThread);
Теперь рассмотрим аспекты, связанные с завершением процессов. Алгоритм завершения процесса состоит из таких этапов:
-
Выполнение всех потоков в процессе прекращается.
-
Все объекты USER и GDI, созданные процессом, уничтожаются, а объекты ядра закрываются (если их не использует другой процесс).
-
Код завершения процесса меняется со значения STILL_ACTIVE на код, переданный в ExitProcess или TerminateProcess.
-
Объект ядра «процесс» переходит в свободное состояние, или незанятое (signaled), состояние.
-
Прочие потоки в системе могут приостановить своё выполнение, пока не будет завершён данный процесс.
-
Счётчик объекта ядра «процесс» уменьшается на 1.
При завершении процесса, существуют некоторые особенности, которые будут сейчас рассмотрены. Связанный с завершаемым процессом объект ядра не высвобождается, пока не будут закрыты ссылки на него из других процессов. В момент завершения процесса система автоматически уменьшает счетчик пользователей этого объекта ядра на 1, объект будет разрушен, как только счетчик обнулится. При завершении процесса его код и выделенные ему ресурсы удаляются из памяти, но объект ядра удалённого процесса продолжает оставаться в памяти, пока счётчик числа пользователей не равен нулю. Подобный объект ядра завершённого процесса пригоден только для получения кода завершения функцией:
BOOL GetExitCodeProcess(
HANDLE hProcess, // описатель процесса
PDWORD pdwExitCode // код его завершения
);
Если процесс не завершён, будет возвращено значение STILL_ACTIVE.
Процесс можно завершить следующими способами:
1. Входная функция первичного потока возвращает управление. Наиболее рекомендуемый способ завершения приложения (процесса). Это единственный способ, гарантирующий очистку всех ресурсов, принадлежавших первичному потоку: любые C++ объекты, созданные данным потоком, уничтожаются соответствующими деструкторами, ОС освобождает память, которую занимал стек потока, ОС устанавливает код завершения процесса (поддерживаемый объектом ядра «процесса») – его и возвращает входная функция, счетчик пользователей данного объекта ядра «процесс» уменьшается на 1.
2. Один из потоков процесса вызывает функцию ExitProcess. Процесс завершается, когда один из его потоков вызывает функцию ExitProcess. Эта функция завершает процесс и заносит значение в параметр код завершения процесса. Возвращаемое значение у функции отсутствует, так как результат её действия - завершение процесса. Код, стоящий после вызова этой функции никогда не выполнится. Когда входная функция возвращает управление, оно передаётся стартовому коду из библиотеки C/C++, и тот проводит очистку всех ресурсов, а затем обращается к ExitProcess, передавая ей значение, возвращённое входной функцией. Это приводит к тому, что будет пропущено освобождение ресурсов, выделенных объектам C++.
3. Когда в процессе существует только один поток, можно вызвать функцию ExitThread. Вызов этой функции приведет к закрытию потока и автоматически ОС закроет процесс, поскольку процессы без потоков существовать не могут. В данной ситуации нет гарантии, что все объекты, принадлежавшие данному процессу будут удалены из памяти корректно. Не рекоментдуется использовать этот способ.
4. Поток другого процесса вызывает функцию TerminateProcess. Функция аналогична функции ExitProcess, но её основное отличие заключается в том, что она может быть вызвана из любого потока и завершает любой процесс.
BOOL TerminateProcess(
HANDLE hProcess, // дескриптор процесса
UINT fuExitCode // код завершения программы
);
При использовании этой функции завершаемый процесс не получает никаких уведомлений о своём завершении, и приложение не может выполнить очистку и сохранение данных. Эта функция – асинхронная, то есть она сообщает ОС, что процесс должен быть завершён, но когда именно – решает сама ОС.
5. Все потоки завершаются самостоятельно по своей воле. Такая ситуация может возникнуть, если все потоки вызвали ExitThread или их закрыли вызовом TerminateThread. Обнаружив, что в процессе не исполняется ни один поток, система больше не считает нужным содержать адресное пространство процесса и немедленно завершает его.
В Windows 2000 и более поздних версиях существует такое понятие как рабочий набор. Рабочий набор процесса определяет, какой объем физической памяти диспетчер памяти Windows пытается сохранить за данным процессом. В рабочем наборе указывается минимальное и максимальное количество страниц памяти, которые должны принадлежать данному процессу. Узнать текущий размер можно при помощи вызова GetProcessWorkingSetSize. Если объем памяти, доступный для других приложений, становится неприемлемо маленьким, система может нарушить границы, установленные в рабочем наборе той или иной программы. Обладая необходимыми для этого привилегиями, программа может изменить собственный рабочий набор при помощи функции SetProcessWorkingSetSize. Если обе границы становятся равными 0xFFFFFFFF, то Windows сбрасывает на диск всю память, принадлежащую процессу.