КОМПЬЮТЕРНЫЕ КУРСЫ "ПОИСК"
2.1. Определение процесса
Приложение, или, как часто его называют, программа, – это статическая последовательность команд. При запуске программы в ОС Win32 создается отдельный процесс (process), или, как его иногда называют, задача (task). Процесс состоит из:
В момент запуска задачи ОС создает дескриптор – специальную структуру, описывающую стартующий процесс. Дескриптор процесса содержит информацию, позволяющую Win32 управлять процессом. В частности в дескрипторе хранится:
За один и тот же интервал времени, называемый квантом времени, один процессор реально может выполнять не более одной задачи. Вместе с тем многозадачная (multitasking) ОС вполне способна одновременно обслуживать несколько процессов. Это достигается благодаря переключению процессора компьютера с исполнения одной задачи на другую. Такое переключение называется переключением контекста (context switching). При этом важно помнить, что время выделяется не процессу, а принадлежащим ему потокам. И чем выше производительность процессора, тем более правдоподобна иллюзия одновременного выполнении нескольких программ.
Новый процесс в Windows создается вызовом функции CreateProcess, которая имеет следующий прототип:
function CreateProcess(
lpApplicationName: PChar; //имя исполняемого модуля
lpCommandLine: PChar;
// командная строка
lpProcessAttributes,
// защита процесса
lpThreadAttributes: PSecurityAttributes;
// защита потока
bInheritHandles: BOOL;
// признак наследования дескриптора
dwCreationFlags: DWORD;
// флаги создания процесса
lpEnvironment: Pointer;
// блок новой среды окружения
lpCurrentDirectory: PChar; // текущий каталог
const
lpStartupInfo: TStartupInfo; // вид главного окна
var lpProcessInformation: TProcessInformation
// информация о процессе
): BOOL; stdcall;
Функция CreateProcess порождает новый дочерний процесс и его первый поток (нить). В рамках этого процесса выполняется указанный файл lpApplicationName с командной строкой lpCommandLine. Впрочем, параметр lpApplicationName может быть равен nil (пустой строке), а имя выполняемого модуля в этом случае должно быть первым элементом командной строки, задаваемой параметром lpCommandLine. Сам выполняемый модуль может быть любого вида: 32-разрядным приложением Windows, приложением MS-DOS, OS/2 и т.п. Однако, если из приложения Windows создается процесс MS-DOS, то параметр lpApplicationName должен быть равен nil (пустой строке), а имя файла и его командная строка включаются в lpCommandLine. Так что, как правило, чтобы не ошибиться, проще всегда задавать lpApplicationName = nil и помещать всю информацию в lpCommandLine.
Если имя файла не содержит расширения, то предполагается расширение .ехе. Но если имя кончается символом точки или если файл задан вместе с путем, то расширение .ехе к имени автоматически не добавляется.
Если путь к файлу не задан, файл ищется в каталогах в следующей последовательности:
Если функция успешно выполнена, она возвращает значение true. Если произошла ошибка — возвращается false. Тогда информацию об ошибке можно получить, вызвав функцию GetLastError.
Функция CreateProcess пришла на смену прежним функциям WinExec и LoadModule, которые теперь реализуются посредством вызова CreateProcess. Функция CreateProcess возвращается, не ожидая окончания инициализации порождаемого процесса. Но в ряде случаев родительский процесс должен взаимодействовать с порожденным. Такое взаимодействие возможно только после того, как закончена инициализация порожденного процесса. Приостановить выполнение до окончания инициализации дочернего процесса можно функцией WaitForInputIdle. В некоторых случаях выполнение родительского процесса должно быть приостановлено до завершения порожденного процесса. Это необходимо, например, если родительский процесс должен использовать какие-то результаты, полученные порожденным процессом. Для ожидания завершения порожденного процесса можно использовать функцию WaitForSingleObject. Порожденный процесс остается в памяти системы, пока не завершатся все его потоки (нити), и пока все его дескрипторы не закроются вызовом CloseHandle. Если эти дескрипторы не нужны, лучше всего закрыть их сразу после инициализации процесса. Чтобы досрочно прекратить выполнение дочернего процесса лучше всего использовать функцию ExitProcess.
lpApplicationName |
Указатель на строку, содержащую имя выполняемого модуля: или с полным путем, или только имя (тогда файл должен находиться в текущем каталоге). Если lpApplicationName = nil, имя модуля должно задаваться первым элементом строки lpCommandLine (подробнее см. выше в описании функции). |
lpCommandLine |
Указатель на строку, содержащую командную строку выполняемого файла. Если lpCommandLine = nil, то в качестве командной строки выступает lpApplicationName (под робнее см. выше в описании функции). |
lpProcessAttributes |
Указатели на структуры типа PSequrityAttributes, определяющие наследование дескриптора в дочернем процессе. Если эти параметры равны nil, наследование невозможно. |
bInheritHandles |
Определяет, наследуют ли новые процессы дескрипторы родительских. Если true — наследуют с тем же уровнем доступа, что и в родительском процессе. |
dwCreationFlags |
Определяет флаги, задающие характеристики создаваемого процесса. |
lpEnvironment |
Указывает на блок окружения нового процесса. Если параметр равен nil, используется окружение родительского процесса. Блок окружения состоит из оканчивающихся нулевым символом строк вида: <имя>=<значение>. Если задан блок окружения, то информация о текущем каталоге в окружение нового процесса автоматически не передается. Блок может состоять из символов UNICODE или ANSI (см. далее в описании флагов). Блок ANSI должен завершаться двумя нулевыми символами (один для строки, другой для блока). Блок UNICODE должен завершаться четырьмя нулевыми, символами. |
lpCurrentDirectory |
Указывает на строку, определяющую текущий каталог и диск дочернего процесса. Это используется в приложениях — оболочках, выполняющих различные приложения с различными рабочими каталогами. Если параметр равен nil, текущий каталог совпадает с родительским. |
lpStartupInfo |
Указывет на структуру типа TStartupInfo, определяющую основное окно дочернего процесса. |
lpProcessInformation |
Указывет на структуру TProcessInformation, из которой родительское приложение может получать информацию о выполнении нового процесса. |
Коротко просуммировать информацию, приведенную в таблице, можно следующим образом. Параметры lpProcessAttributes, lpThreadAttributes, lpEnvironment, blnHeritHandles определяют наследование дочерним процессом свойств родительского процесса. Если не вдаваться в подробности наследования, то можно первые три из этих параметров задавать равными nil, а последний — false. Параметр lpCurrentDirectory указывает на строку, определяющую текущий каталог и диск дочернего процесса. Это используется в приложениях-оболочках, выполняющих различные приложения с различными рабочими каталогами. Если параметр равен nil, текущий каталог совпадает с родительским.
Указанный в таблице параметр dwCreationFlags определяет флаги, задающие характеристики создаваемого процесса. Указанные ниже флаги, управляющие созданием процесса, могут задаваться в любых комбинациях (кроме специально оговоренных) с помощью операции оr.
CREATE_DEFAULT_ERROR_MODE |
Новый процесс не наследует режим ошибок родительского процесса. В нем устанавливается режим по умолчанию вызовом SetErrorMode. Обычно используется в многопоточных процессах. |
CREATE_NEW_CONSOLE |
Создается новое консольное приложение. Этот флаг не может использоваться совместно с DETACHED_PROCESS. Это говорит системе о том, что для запускаемого процесса должна быть создана новая консоль. Если этот параметр будет равен 0, то новая консоль для запускаемого процесса не создается и весь консольный вывод нового процесса будет направляться в консоль родительского процесса. |
CREATE_NEW_PROCESS_GROUP |
Новый процесс является корневым для новой группы процессов: всех процессов, которые будут наследовать создаваемому. Идентификатор новой группы — тот, который возвращается параметром lpProcessInformation. Группы процессов используются функцией GenerateConsoleCtrlEvent для посылки сигналов Ctrl-С или Ctrl-Break группе консольных процессов. |
CREATE_SEPARATE_WOW_VDM |
Используется только в Windows NT для создания 16-битных процессов. Его установка приводит к использованию для процесса отдельной VDM. В этом случае отказ в данном процессе не приведет к гибели других выполняемых процессов. |
CREATE_SHARED_WOW_VDM |
Используется только в Windows NT для создания 16-битных процессов. Если ключ DefaultSeparateVDM в разделе Windows файла WIN.INI установлен в true, то этот флаг приводит к изменению ключа и все новые процессы запускаются в общей VDM. |
CREATE_SUSPENDED |
Основной поток (нить) нового приложения создается в состоящей ожидания и не выполняется, пока не будет вызвана функция ResumeThread. |
CREATE_UNICODE_ENVIRONMENT |
При установке этого флага блок окружения, на который указывает lpEnvironment, использует символы Unicode. В отсутствие флага используются символы ANSI. |
DEBUG_PROCESS |
При установке этого флага родительский процесс воспринимается как отладчик дочернего процесса. Система информирует отладчик обо всех событиях в отлаживаемом процессе. В этом режиме функция WaitForDebugEvent может использоваться только для потока, созданного функцией CreateProcess. |
DEBUG_ONLY_THIS_PROCESS |
Если этот флаг отсутствует, и родительский процесс отлаживается, новые процессы тоже отлаживаются тем же отладчиком. |
DETACHED_PROCESS |
Новый консольный процесс не имеет доступа к консоли родительского. Он может позднее вызвать функцию AllocConsole для создания новой консоли. Этот флаг не может использоваться совместно с флагом CREATE_NEW_CONSOLE. |
Параметр dwCreationFlags может также контролировать класс приоритета нового процесса. Если ни один из описанных ниже флагов приоритета не установлен, по умолчанию используется NORMAL_PRIORITY_CLASS, если только родительский процесс не имеет класс IDLE_PRIORITY_CLASS. В последнем случае для дочерних процессов по умолчанию принимается класс IDLE_PRIORITY_CLASS.
HIGH_PRIORITY_CLASS |
Указывает на процесс как на критическую задачу, которая должна выполняться немедленно. |
IDLE_PRIORITY_CLASS |
Все потоки процесса выполняются только во время простоя системы. Пример — хранители экрана. Все наследники такого процесса будут иметь тот же класс приоритета. Класс фоновых процессов. Обычно эти процессы следят за состоянием системы. |
NORMAL_PRIORITY_CLASS |
Нормальный приоритет процесса. Это обычные пользовательские процессы. Приоритет обычных пользовательских процессов может также устанавливаться флагами BELOW_NORMAL_PRIORITY_CLASS или ABOVE_NORMAL_PRIORITY_CLASS, которые соответственно немного повышают или понижают приоритет пользовательского процесса. |
REAL_TIME_PRIORITY_CLASS |
Высокий приоритет, превышающий приоритеты других процессов, включая приоритеты процессов операционной системы. Приоритет таких процессов устанавливается флагом REAL_TIME_PRIORITY_CLASS. Работа таких процессов обычно происходит в масштабе реального времени и связана с реакцией на внешние события. Эти процессы должны работать непосредственно с аппаратурой компьютера. |
BELOW_NORMAL_PRIORITY_CLASS |
Класс процессов ниже нормальных. |
ABOVE_NORMAL_PRIORITY_CLASS |
Класс процессов выше нормальных. |
Рассмотрим правила, используемые для назначения приоритетов процессам в Windows. Предполагается, что операционная система Windows различает четыре типа процессов в соответствии с их приоритетами: фоновые процессы, процессы с нормальным приоритетом, процессы с высоким приоритетом и процессы реального времени. Рассмотрим подробнее каждый из этих типов процессов.
Фоновые процессы выполняют свою работу, когда нет активных пользовательских процессов. Обычно эти процессы следят за состоянием системы. Приоритет таких процессов устанавливается флагом IDLE_PRIORITY_CLASS.
Процессы с нормальным приоритетом — это обычные пользовательские процессы. Приоритет таких процессов устанавливается флагом NORMAL_PRIORITY_CLASS. Этот приоритет также назначается пользовательским процессам по умолчанию. В Windows 2000 приоритет обычных пользовательских процессов может также устанавливаться флагами BELOW_NORMAL_PRIORITY_CLASS или ABOVE_NORMAL_PRIORITY_CLASS, которые соответственно немного повышают или понижают приоритет пользовательского процесса.
Процессы с высоким приоритетом это такие пользовательские процессы, от которых требуется более быстрая реакция на некоторые события, чем от обычных пользовательских процессов. Приоритет таких процессов устанавливается флагом HIGH_PRIORITY_CLASS. Эти процессы должны содержать небольшой программный код и выполняться очень быстро, чтобы не замедлять работу системы. Обычно такие приоритеты имеют другие системы, работающие на платформе операционных систем Windows.
К последнему типу процессов относятся процессы реального времени. Приоритет таких процессов устанавливается флагом REAL_TIME_PRIORITY_CLASS. Работа таких процессов обычно происходит в масштабе реального времени и связана с реакцией на внешние события. Эти процессы должны работать непосредственно с аппаратурой компьютера.
Теперь остановимся на параметре lpProcessInformation, который указывает на запись типа TProcessInformation, соответствующего типу структуры PROCESS_INFORMATION API Windows. Из этой записи приложение может получать информацию о выполнении нового процесса.
Объявление типа TProcessInformation:
PProcessInformation = ^TProcessInformation;
_PROCESS_INFORMATION = record
hProcess: THandle;
hThread: THandle;
dwProcessId: DWORD;
dwThreadId: DWORD;
end;
{$EXTERNALSYM_PROCESS_INFORMATION}
TProcessInformation = _PROCESS_INFORMATION;
PROCESS_INFORMATION = _PROCESS_INFORMATION;
{$EXTERNALSYM PROCESS_INFORMATION}
hProcess |
Возвращает дескриптор созданного процесса. Используется во всех функциях, осуществляющих операции с объектом процесса. |
hThread |
Возвращает дескриптор первого потока (нити) созданного процесса. Используется во всех функциях, осуществляющих операции с объектом потока. |
dwProcessId |
Возвращает глобальный идентификатор процесса. Значение доступно с момента создания процесса и до момента его завершения. |
dwThreadId |
Возвращает глобальный идентификатор потока. Значение доступно с момента создания потока и до момента его завершения. |
Параметр lpStartupInfo функции CreateProcess указывает на запись типа TStartupInfo, соответствующего типу структуры STARTUPINFO API Windows. Запись определяет свойства главного окна создаваемого процесса. Для процессов с графическим интерфейсом пользователя (GUI) эта информация относится к первому окну, создаваемому функцией CreateWindow и отображаемому функцией ShowWindow. Для консольных приложений эта информация относится к создаваемому консольному окну.
Для примера рассмотрим программу, которая выводит на консоль свое имя и параметры. Эта программа приведена в листинге 2.1.
Листинг 2.1. Программа, которая выводит на консоль свое имя и параметры
program ConsoleProcess1; {$APPTYPE CONSOLE} uses SysUtils, Windows; var i: Integer; begin Writeln('I am created.'); Writeln('My name is: ', ParamStr(0)); for i := 1 to ParamCount do Writeln('My ', i, ' parameter = ', ParamStr(i)); Writeln('Press any key to finish.'); Readln; end.
Скомпилируем эту программу. Полученный ехе-файл сохраним на диске С: и назовем ConsoleProcess1.exe. Наша задача состоит в запуске этого файла как нового процесса. Как это сделать показано в листинге 2.2, где приведена программа, запускающая созданный ехе-файл как консольный процесс с новой консолью.
Листинг 2.2. Программа процесса, который создает процесс с новой консолью
program ConsoleProcess2; {$APPTYPE CONSOLE} uses SysUtils, Windows; var lpApplicationName: string = 'ConsoleProcess1.exe'; si: STARTUPINFO; piApp: PROCESS_INFORMATION; begin ZeroMemory(@si, SizeOf(STARTUPINFO)); si.cb := SizeOf(STARTUPINFO); //создаем новый консольный процесс if not CreateProcess(PChar(lpApplicationName), nil, nil, nil, False, CREATE_NEW_CONSOLE, nil, nil, si, piApp) then begin Writeln('The new process is not created.'); Writeln('Check a name of the process.'); Writeln('Press any key to finish'); Readln; Exit; end; Writeln('The new process is created.'); //ждем завершения созданного процесса WaitForSingleObject(piApp.hProcess, INFINITE); //закрываем дексрипторы этого процесса в текущем процессе CloseHandle(piApp.hThread); CloseHandle(piApp.hProcess); end.
Отметим в этой программе два момента. Во-первых, перед запуском консольного процесса ConsoleProcess1.exe все поля структуры si типа STURTUPINFO должны заполняться нулями. Это делается при помощи вызова функции ZeroMemory, которая предназначена для этой цели и имеет следующий прототип:
procedure ZeroMemory(
Destination: Pointer; // адрес блока памяти
Length: DWORD
// длина блока памяти
); inline;
В этом случае вид главного окна запускаемого приложения определяется по умолчанию самой операционной системой Windows. Во-вторых, в параметре dwCreationFlags устанавливается флаг CREATE_NEW_CONSOLE. Это говорит системе о том, что для запускаемого процесса должна быть создана новая консоль. Если этот параметр будет равен nil, тo новая консоль для запускаемого процесса не создается и весь консольный вывод нового процесса будет направляться в консоль родительского процесса.
Структура piApp типа PROCESS_INFORMATION содержит идентификаторы и дескрипторы нового создаваемого процесса и его главного потока. Мы не используем эти дескрипторы в нашей программе и поэтому закрываем их. Значение false параметра bInheritHandle говорит о том, что эти дескрипторы не являются наследуемыми.
Теперь запустим наш новый консольный процесс другим способом, используя второй параметр функции CreateProcess. Это можно сделать при помощи программы, приведенной в листинге 2.3.
Листинг 2.3. Программа процесса, который создает процесс с новой консолью
program ConsoleProcess3; {$APPTYPE CONSOLE} uses SysUtils, Windows, Dialogs; var lpszCommandLine: AnsiString = 'ConsoleProcess1.exe p1 p2 p3'; si: TStartupInfoA; piCom: TProcessInformation; begin ZeroMemory(@si, SizeOf(TStartupInfoA)); // FillChar(si, Sizeof(si),#0); si.cb := SizeOf(TStartupInfoA); si.dwFlags := STARTF_USESHOWWINDOW; si.wShowWindow := SW_SHOWNORMAL; //создаем новый консольный процесс if not CreateProcessA(nil, PAnsiChar(lpszCommandLine), nil, nil, False, CREATE_NEW_CONSOLE or HIGH_PRIORITY_CLASS, nil, nil, si, piCom) then begin ShowMessage(IntToStr(GetLastError)); Exit; end; //Закрываем дескрипторы этого процесса CloseHandle(piCom.hThread); CloseHandle(piCom.hProcess); Writeln('The new process is created.'); Writeln('Press any key to finish.'); Readln; end.
Отличие этой программы от программы, приведенной в листинге 2.2, состоит в том, что мы передаем системе имя нового процесса и его парамметры через командную строку. В этом случае имя нового процесса может и не содержать полный путь к ехе-файлу, а только имя самого ехе-файла.
Для иллюстрации сказанного запустим приложение Notepad.exe (Блокнот), используя командную строку. Программа, запускающая Блокнот из командной строки, приведена в листинге 2.4.
Листинг 2.4. Запуск приложения Notepad
//запуск приложения Notepad program RunNotepad; {$APPTYPE CONSOLE} uses SysUtils, Windows; var si: STARTUPINFOA; pi: PROCESS_INFORMATION; begin // заполняем значения структуры STARTUPINFO по умолчанию ZeroMemory(@si, SizeOf(STARTUPINFOA)); si.cb := SizeOf(STARTUPINFOA); // запускаем процесс Notepad if not CreateProcessA(nil, // имя не задаем 'Notepad.exe', // имя программы nil, // атрибуты защиты процесса устанавливаем по умолчанию nil, // атрибуты защиты первичного потока по умолчанию False, // дескрипторы текущего процесса не наследуются 0, // по умолчанию NORMAL_PRIORITY_CLASS nil, // используем среду окружения вызывающего процесса nil, // текущий диск и каталог, как и в вызывающем процессе si, // вид главного окна - по умолчанию pi) then // информация о новом процессе begin Writeln('The new process is not created. Check a name of the process.'); Exit; end; Sleep(1000); //немного подождем и закончим работу //закрываем дексрипторы этого процесса в текущем процессе CloseHandle(pi.hThread); CloseHandle(pi.hProcess); end.
2.3. Завершение процессов
Процесс может завершить свою работу вызовом функции ExitProcess, которая имеет следующий прототип:
procedure ExitProcess(
uExitCode: UINT //код возврата из процесса
); stdcall;
При вызове функции ExitProcess завершаются все потоки процесса с кодом возврата, который является параметром этой функции. При выполнении этой функции система посылает динамическим библиотекам, которые загружены процессом, сообщение DLL_PROCESS_DETACH, которое говорит о том, что динамическую библиотеку необходимо отсоединить от процесса.
В листинге 2.5 приведен пример программы, которая завершает свою работу вызовом функции ExitProcess.
Листинг 2.5. Завершение процесса функцией ExitProcess
program ExitProcess2; {$APPTYPE CONSOLE} uses SysUtils, Windows; var count: Integer = 0; c: Char; hThread: HWND; IDThread: DWORD; procedure thread; stdcall; begin while True do begin count := count + 1; Sleep(100); end; end; begin hThread := CreateThread(NIL, 0, @thread, nil, 0, IDThread); if hThread = 0 then Halt(GetLastError); while True do begin Writeln('Input ''y'' to display the count or any char to exit: '); Readln(c); if c = 'y' then Writeln('count = ', count) else ExitProcess(1); end; end.
Один процесс может быть завершен другим при помощи вызова функции TerminateProcess, которая имеет следующий прототип!
function TerminateProcess(
hProcess: THandle; // дескриптор процесса
uExitCode: UINT
// код возврата
): BOOL; stdcall;
Если функция TerminateProcess выполнилась успешно, то она возвращает True. В противном случае возвращаемое значение равно FALSE. Функция TerminateProcess завершает работу процесса, но нe ocвoбождает все ресурсы, принадлежащие этому процессу. Это происходит потому, что при выполнении этой функции система не посылает динамическим библиотекам, загруженным процессом, сообщение о том, что библиотеку необходимо отсоединить от процесса. Поэтому эта функция должна вызываться только в аварийных ситуациях при зависании процесса.
Приведем программу, которая демонстрирует работу функции TerminateProcess. Для этого сначала создадим бесконечный процесс-счетчик, который назовем ConsoleProcess.exe, и расположим на диске С: (листинг 4.6).
Листинг 2.6. Программа бесконечного процесса
//программа бесконечного процесса program ConsoleProcess; {$APPTYPE CONSOLE} uses SysUtils, Windows; var count: Integer = 0; begin while True do begin count := count + 1; Sleep(1000); Writeln('count = ', count); end; end.
Теперь рассмотрим программу, которая создает этот бесконечный процесс-счетчик, а потом завершает его по требованию пользователя, используя для этого функцию TerminateProcess. Эта программа приведена в листинге 2.7.
Листинг 2.7. Завершение процесса функцией TeminateProcess
//завершение процесса функцией TerminateProcess program TerminateProcesss; {$APPTYPE CONSOLE} uses SysUtils, Windows; var lpApplicationName: AnsiString = 'ConsoleProcess.exe'; si: STARTUPINFOA; pi: PROCESS_INFORMATION; c: Char; b: Boolean = True; begin ZeroMemory(@si, SizeOf(STARTUPINFOA)); si.cb := SizeOf(STARTUPINFOA); //создаем новый консольный процесс if not CreateProcessA(PAnsiChar(lpApplicationName), nil, nil, nil, False, CREATE_NEW_CONSOLE, nil, nil, si, pi) then begin Writeln('The new process is not created.'); Writeln('Check a name of the process.'); Writeln('Press any key to finish'); Readln; Exit; end; Writeln('The new process is created.'); while b do begin Write('Input ''t'' to terminate the new console process: '); Readln(c); if c = 't' then begin // завершаем новый процесс TerminateProcess(pi.hProcess, 1); Break; end; end; //закрываем дексрипторы этого процесса в текущем процессе CloseHandle(pi.hThread); CloseHandle(pi.hProcess); end.
2.4. Завершение процессов
Большинство объектов категории Kernel могут быть наследуемыми или ненаследуемыми. Свойство наследования объекта означает, что если наследуемый объект создан или открыт в некотором процессе, то к этому объекту будут также иметь доступ все процессы, которые создаются этим процессом, т. е. являются его потомками. Свойство наследования объекта определяется его дескриптором, который также может быть наследуемым или ненаследуемым. Для того чтобы объект стал наследуемым, необходимо сделать наследуемым его дескриптор и наоборот.
Свойство наследования не поддерживается для объектов, использование которых несколькими процессами нарушило бы изолированность памяти процесса от других процессов. Поэтому не могут наследоваться следующие дескрипторы:
Однако для того чтобы дочерний процесс имел доступ к наследуемому объекту в родительском процессе, недостаточно просто сделать дескриптор этого объекта наследуемым. Кроме этого, нужно, во-первых, установить значение параметра bInheritHandles функции CreateProcess в True и, во-вторых, передать сам дескриптор дочернему процессу, который создается функцией CreateProcess. Наследуемый дескриптор передается системой дочернему процессу неявно и поэтому он скрыт от программ, которые выполняются в дочернем процессе. То есть программа дочернего процесса должна явно знать этот дескриптор и передать его этой программе должна программа родительского процесса. Одним из способов передачи дескрипторов дочернему процессу является использование командной строки, которая позволяет передавать дескрипторы как параметры.
Для пояснения сказанного приведем пример двух процессов, в которых используются наследуемые дескрипторы. В листинге 2.8 приведена программа дочернего процесса. Этот процесс получает дескриптор потока от родительского процесса и, по требованию пользователя, прекращает выполнение этого потока в родительском процессе.
Листинг 2.8. Завершение потока в родительском процессе, используя дескриптор потока, который передается через командную строку
//Завершение потока в родительском процессе, используя //дескриптор потока, который передается через командную строку program ConsoleProcess; {$APPTYPE CONSOLE} uses SysUtils, Windows; var c: Char; hThread: THandle; begin // преобразуем символьное представление дескриптора в число hThread := StrToInt64(ParamStr(1)); //ждем команды о завершении потока while True do begin Writeln('Input ''t'' to terminate the thread: '); Readln(c); if c = 't' then begin Writeln('t'); Break; end; end; // завершаем поток TerminateThread(hThread, 0); // закрываем дескриптор потока CloseHandle(hThread); Writeln('Press any key to exit.'); Readln; end.
В листинге 2.9 приведена программа родительского процесса, который создает дочерний процесс и передает ему через командную строку наследуемый дескриптор потока. Этот поток и должен быть завершен дочерним процессом, программа которого приведена в листинге 2.8.
Листинг 2.9. Процесс, который передает наследуемый дескриптор потока дочернему процессу через командную строку
//процесс, который передает наследуемый дескриптор потока //дочернему процессу через командную строку program CreateProcesss; {$APPTYPE CONSOLE} uses SysUtils, Windows; var count: Integer = 0; lpszComLine: AnsiString; //для командной строки si: STARTUPINFOA; pi: PROCESS_INFORMATION; sa: SECURITY_ATTRIBUTES; hThread: THandle; IDThread: DWORD; procedure thread; begin while True do begin count := count + 1; Sleep(500); Writeln('count = ', count); end; end; begin Writeln('Press any key to start the count-thread.'); Readln; // устанавливаем атрибутч защиты потока sa.nLength := SizeOf(SECURITY_ATTRIBUTES); sa.lpSecurityDescriptor := nil; //защита по умолчанию sa.bInheritHandle := True; // дескриптор потока наследуемый // запускаем поток счетчик hThread := CreateThread(@sa, 0, @thread, nil, 0, IDThread); if hThread = 0 then Halt(GetLastError); // запускаем атрибуты нового процесса ZeroMemory(@si, SizeOf(STARTUPINFOA)); si.cb := SizeOf(STARTUPINFOA); // формируем командную строку lpszComLine := 'ConsoleProcess.exe ' + AnsiString(IntToStr(hThread)); // запускаем консольный процесс if not CreateProcessA( nil, // имя процесса PAnsiChar (lpszComLine), //адрес командной строки nil, //атрибуты защиты процесса по умолчанию nil, //атрибуты защиты первичного потока по умолчанию true, //наследуемые дескрипторы текущего процесса наследуются новым процессом CREATE_NEW_CONSOLE, // новая консоль nil, //используем среду окружения процесса предка nil, //текущий диск и каталог, как и в процессе-предке si, // вид главного окна - по умолчанию pi //здесь будут дексрипторы и идентификаторы //нового процесса и его первичного потока ) then begin Writeln('The new process is not created.'); Writeln('Press any key to finish.'); Readln; Halt(GetLastError); end; // закрываем дескрипторы нового процесса CloseHandle(pi.hProcess); CloseHandle(pi.hThread); // ждем закрытия потока-счетчика WaitForSingleObject(hThread, INFINITE); Writeln('Press any key to exit'); Readln; // закрываем дескриптор потока CloseHandle(hThread); end.
В программе из листинга 2.9 особенно нужно обратить внимание на два момента: значение параметра bInheritHandle функции CreateProcess и использование структуры sa типа SECURITY_ATTRIBUTES, адрес которой является первым параметром функции CreateThread. Если значение параметра bInheritHandle равно true, то наследуемые дескрипторы родительского процесса передаются дочернему процессу. Поле bInheritHandle структуры sa имеет тип bool. Если значение этого поля установлено в true, то дескриптор создаваемого потока является наследуемым, в противном случае — ненаследуемым.
Теперь рассмотрим следующую ситуацию. Предположим, что дескриптор созданного объекта является ненаследуемым, а нам необходимо сделать его наследуемым. Для решения этой проблемы в операционной системе Windows можно использовать функцию SetHandlelnformation, которая используется для изменения свойств дескрипторов и имеет следующий прототип:
function SetHandleInformation(
hObject: THandle; // дескриптор объекта
dwMask: DWORD;
// флаги, которые изменяем
dwFlags: DWORD
// новые значения флагов
): BOOL; stdcall;
В случае успешного завершения эта функция возвращает true, в противном случае — false.
Для иллюстрации работы функции SetHandlelnformation изменим программу, приведенную в листинге 4.9. Модифицированная программа приведена в листинге 2.10.
Листинг 2.10. Изменение свойства наследования дескриптора
//изменение свойства наследования дескриптора program CreateProcesss; {$APPTYPE CONSOLE} uses SysUtils, Windows; var count: Integer = 0; lpszComLine: AnsiString = 'ConsoleProcess '; //имя нового процесса с пробелом si: STARTUPINFOA; pi: PROCESS_INFORMATION; hThread: THandle; IDThread: THandle; procedure thread; begin while True do begin count := count + 1; Sleep(500); Writeln('count = ', count); end; end; begin Writeln('Press any key to start the count-thread.'); Writeln('After terminating the thread press any key to exit'); Readln; // запускаем поток-счетчик hThread := CreateThread(nil, 0, @thread, nil, 0, IDThread); if hThread = 0 then Halt(GetLastError); // делаем дескриптор потока наследуемым if not SetHandleInformation( hThread, // дескриптор потока HANDLE_FLAG_INHERIT, // изменяем наследование дескриптора HANDLE_FLAG_INHERIT // делаем дескриптор наследуемым ) then begin Writeln('The inheritance is not changed.'); Writeln('Press any key to finish.'); Readln; Halt(GetLastError); end; // запускаем атрибуты нового процесса ZeroMemory(@si, SizeOf(STARTUPINFOA)); si.cb := SizeOf(STARTUPINFOA); // создаем командную строку lpszComLine := 'ConsoleProcess.exe ' + AnsiString(IntToStr(hThread)); // запускаем консольный процесс if not CreateProcessA( nil, // имя процесса PAnsiChar(lpszComLine), //адрес командной строки nil, //атрибуты защиты процесса по умолчанию nil, //атрибуты защиты первичного потока по умолчанию true, //наследуемые дескрипторы текущего процесса наследуются новым процессом CREATE_NEW_CONSOLE, // новая консоль nil, //используем среду окружения процесса предка nil, //текущий диск и каталог, как и в процессе-предке si, // вид главного окна - по умолчанию pi //здесь будут дексрипторы и идентификаторы //нового процесса и его первичного потока ) then begin Writeln('The new process is not created.'); Writeln('Press any key to finish.'); Readln; Halt(GetLastError); end; // закрываем дескрипторы нового процесса CloseHandle(pi.hProcess); CloseHandle(pi.hThread); Readln; // закрываем дескриптор потока CloseHandle(hThread); end.
Для определения свойств дескриптора используется функция GetHandlelnformation, которая имеет следующий прототип!
function GetHandleInformation(
hObject: THandle;
// дескриптор объекта
var lpdwFlags: DWORD // свойство дескриптора
): BOOL; stdcall;
Эта функция также в случае успешного завершения возвращает True. В противном случае возвращаемое значение равно False.
В завершение этого раздела разберем несколько подробнее, зачем нужно наследование объектов, тогда как любые процессы, включая дочерние, могут получить доступ к объекту по его имени. Проблема как раз и состоит в именовании объектов. Во-первых, затрачивается время на поиск имени созданного объекта. Но это не столь важно, важнее то, что, во-вторых, объекты должны иметь уникальные имена. Это требуется для того, чтобы не допустить ошибки при создании объекта по причине присутствия другого объекта с таким же именем. То есть нельзя допустить, чтобы совершенно разные приложения непреднамеренно создавали никак не связанные между собой объекты с одинаковыми именами. Кроме того, при неуникальном именовании объектов также возможны проблемы при одновременной работе двух экземпляров одного приложения. Эта проблема уже гораздо сложнее, и для ее решения используются специальные программы, которые могут генерировать уникальные имена. Такие имена обычно называются GUID — глобальными универсальными идентификаторами. Поэтому, как видим, проще и быстрее создавать анонимные наследуемые объекты и передавать их дескрипторы дочерним процессам, чем заниматься уникальным именованием объектов.
2.5. Дублирование дескрипторов
Дублирование дескрипторов необходимо для решения следующей задачи. Иногда при передаче дескриптора из одного процесса в другой необходимо изменить не только свойство наследования дескриптора, но и другие свойства этого дескриптора, которые управляют доступом к объекту. Для решения этой проблемы предназначена функция DuplicateHandle, которая имеет следующий прототип:
function DuplicateHandle(
hSourceProcessHandle,
// дескриптор процесса источника
hSourceHandle,
// исходный дескриптор
hTargetProcessHandle: THandle; // дескриптор процесса приемника
lpTargetHandle: PHandle;
// дубликат исходного дескриптора
dwDesiredAccess: DWORD;
// флаги доступа к объекту
bInheritHandle: BOOL;
// наследование дескриптора
dwOptions: DWORD
// дополнительные необязательные флаги
): BOOL; stdcall;
Если функция DuplicateHandle завершается успешно, то она возвращает True. В противном случае эта функция возвращает значение False.
Параметра — dwOptions, в котором может быть установлена комбинация флагов DUPLICATE_CLOSE_SOURCE и DUPLICATE_SAME_ACCESS. Если установлен флаг DUPLICATE_CLOSE_SOURCE, тo при любом своем завершении функция DuplicateHandle закрывает исходный дескриптор. Если установлен флаг DUPLICATE_SAME_ACCESS, тo режимы доступа к объекту через дублированный дескриптор совпадают с режимами доступа к объекту через исходный дескриптор. Совместное использование этих флагов обеспечивает выполнение двух указанных действий.
Теперь перейдем к параметру dwDesiredAccess, который определяет возможные режимы доступа к объекту через дубликат исходного дескриптора, используя определенную комбинацию флагов. Значения этих флагов отличаются для объектов разных типов и будут описаны далее, в процессе работы с объектами. Если доступ к объекту не изменяется, что определяется значением последнего параметра dwOptions, то система игнорирует значение параметра dwDesiredAccess.
Параметр blnheritHandle функции DuplicateHandle устанавливает свойство наследования нового дескриптора. Если значение этого параметра равно true, то создаваемый дубликат исходного дескриптора является наследуемым, в случае false — ненаследуемым.
В листинге 2.11 приведен пример программы, которая использует функцию DuplicateHandle для разрешения дочернему процессу завершить поток в родительском процессе. Эта программа является другим решением задачи, решаемой программой, приведенной в листинге 2.10.
Листинг 2.11. Создание наследуемого дескриптора функцией DuplicateHandle
//создание наследуемого дескриптора функцией DuplicateHandle program CreateProcesss; {$APPTYPE CONSOLE} uses SysUtils, Windows; var count: Integer = 0; lpszComLine: AnsiString = 'ConsoleProcess '; //имя нового процесса с пробелом si: STARTUPINFOA; pi: PROCESS_INFORMATION; hThread, hInheritThread: THandle; IDThread: DWORD; procedure thread; begin while True do begin count := count + 1; Sleep(500); Writeln('count = ', count); end; end; begin Writeln('Press any key to start the count-thread.'); Writeln('After terminating the thread press any key to exit'); Readln; // запускаем поток-счетчик hThread := CreateThread(nil, 0, @thread, nil, 0, IDThread); if hThread = 0 then Halt(GetLastError); // создаем наследуемый дубликат дескриптора потока if not DuplicateHandle( GetCurrentProcess, // дескриптор текущего процесса hThread, // исходный дескриптор потока GetCurrentProcess, // дескриптор текущего процесса @hInheritThread, // новый дескриптор потока 0, // этот параметр игнорируется True, // новый дескриптор наследуемый DUPLICATE_SAME_ACCESS // доступ не изменяем ) then begin Writeln('The handle is not duplicated.'); Writeln('Press any key to finish.'); Readln; Halt(GetLastError); end; // запускаем атрибуты нового процесса ZeroMemory(@si, SizeOf(STARTUPINFOA)); si.cb := SizeOf(STARTUPINFOA); // создаем командную строку lpszComLine := 'ConsoleProcess.exe ' + AnsiString(IntToStr(hInheritThread)); // запускаем консольный процесс if not CreateProcessA( nil, // имя процесса PAnsiChar(lpszComLine), //адрес командной строки nil, //атрибуты защиты процесса по умолчанию nil, //атрибуты защиты первичного потока по умолчанию true, //наследуемые дескрипторы текущего процесса наследуются новым процессом CREATE_NEW_CONSOLE, // новая консоль nil, //используем среду окружения процесса предка nil, //текущий диск и каталог, как и в процессе-предке si, // вид главного окна - по умолчанию pi //здесь будут дексрипторы и идентификаторы //нового процесса и его первичного потока ) then begin Writeln('The new process is not created.'); Writeln('Press any key to finish.'); Readln; Halt(GetLastError); end; // закрываем дескрипторы нового процесса CloseHandle(pi.hProcess); CloseHandle(pi.hThread); Readln; // закрываем дескриптор потока CloseHandle(hThread); end.
Теперь приведем, в листинге 2.12, пример программы, которая дублирует дескриптор, но при этом изменяет доступ к нему. Разрешим дочернему процессу прекращать выполнение потока в родительском процессе. В этом случае остальные функции над потоками, такие как, например, SuspendThread и ResumeThread, будут недоступны для выполнения в дочернем процессе. Обратите внимание на изменение значений параметров функции DuplicateHandle.
Листинг 2.12. Создание наследуемого дескриптора функцией DuplicateHandle с изменением доступа к объекту
//создание наследуемого дескриптора функцией DuplicateHandle с изменением // доступа к объекту program CreateProcesss; {$APPTYPE CONSOLE} uses SysUtils, Windows; var count: Integer = 0; lpszComLine: AnsiString = 'ConsoleProcess '; //имя нового процесса с пробелом si: STARTUPINFOA; pi: PROCESS_INFORMATION; hThread, hInheritThread: THandle; IDThread: DWORD; const THREAD_TERMINATE = $0001; procedure thread; begin while True do begin count := count + 1; Sleep(500); Writeln('count = ', count); end; end; begin Writeln('Press any key to start the count-thread.'); Writeln('After terminating the thread press any key to exit'); Readln; // запускаем поток-счетчик hThread := CreateThread(nil, 0, @thread, nil, 0, IDThread); if hThread = 0 then Halt(GetLastError); // создаем наследуемый дубликат дескриптора потока if not DuplicateHandle( GetCurrentProcess, // дескриптор текущего процесса hThread, // исходный дескриптор потока GetCurrentProcess, // дескриптор текущего процесса @hInheritThread, // новый дескриптор потока THREAD_TERMINATE, // этот параметр игнорируется True, // новый дескриптор наследуемый 0 // доступ не изменяем ) then begin Writeln('The handle is not duplicated.'); Writeln('Press any key to finish.'); Readln; Halt(GetLastError); end; // запускаем атрибуты нового процесса ZeroMemory(@si, SizeOf(STARTUPINFOA)); si.cb := SizeOf(STARTUPINFOA); // создаем командную строку lpszComLine := 'ConsoleProcess.exe ' + AnsiString(IntToStr(hInheritThread)); // запускаем консольный процесс if not CreateProcessA( nil, // имя процесса PAnsiChar(lpszComLine), //адрес командной строки nil, //атрибуты защиты процесса по умолчанию nil, //атрибуты защиты первичного потока по умолчанию true, //наследуемые дескрипторы текущего процесса наследуются новым процессом CREATE_NEW_CONSOLE, // новая консоль nil, //используем среду окружения процесса предка nil, //текущий диск и каталог, как и в процессе-предке si, // вид главного окна - по умолчанию pi //здесь будут дексрипторы и идентификаторы //нового процесса и его первичного потока ) then begin Writeln('The new process is not created.'); Writeln('Press any key to finish.'); Readln; Halt(GetLastError); end; // закрываем дескрипторы нового процесса CloseHandle(pi.hProcess); CloseHandle(pi.hThread); Readln; // закрываем дескриптор потока CloseHandle(hThread); end.
2.6. Псевдодескрипторы процессов
Иногда процессу требуется знать свой дескриптор, чтобы изменить какие-то свои характеристики. Например, процесс может изменить свой приоритет. Для этих целей в Win32 API существует функция GetCurrentProcess, которая имеет следующий прототип:
function GetCurrentProcess: THandle; stdcall;
и возвращает псевдодескриптор текущего процесса. Псевдодескриптор текущего процесса отличается от настоящего дескриптора процесса тем, что он может использоваться только текущим процессом и не может наследоваться другими процессами. Псевдодескриптор процесса не нужно закрывать после его использования. Из псевдодескриптора процесса можно получить настоящий дескриптор процесса: для этого псевдодексриптор нужно продублировать, вызвав функцию DuplicateHandle.
В листинге 2.13 приведен пример программы, которая получает псевдодескриптор процесса посредством вызова функции GetCurrentProcess, а затем выводит полученный псевдодескриптор на консоль.
Листинг 2.13. Получение псевдодескриптора процесса
// получение псевдодескриптора процесса program GetCurrentProcesss; {$APPTYPE CONSOLE} uses SysUtils, Windows; var hProcess: THandle; begin // получсаем псевдодескриптор текущего процесса hProcess := GetCurrentProcess; // выводим псевдодескриптор на консоль Writeln(hProcess); Readln; end.
2.7. Обслуживание потоков
Операционные системы Windows распределяют процессорное время между потоками в соответствии с их приоритетами. По истечении кванта времени исполнение текущего потока прерывается, его контекст запоминается и процессорное время передается потоку с высшим приоритетом. Часто говорят, что поток с высшим приоритетом вытесняет поток с низшим приоритетом. Такое обслуживание потоков в Windows называется вытесняющая многозадачность (preempting multitasking). Величина кванта времени, выделяемого потоку, зависит от типа операционной системы Windows, типа процессора и приблизительно равна 20 мс.
Приоритеты потоков в Windows определяются относительно приоритета процесса, в контексте которого они исполняются, и изменяются от 0 (низший приоритет) до 31 (высший приоритет). Приоритет процессов устанавливается при их создании функцией CreateProcess, используя параметр dwCreationFlags. Для установки приоритета процесса в этом параметре нужно установить один из следующих флагов.
Рассмотрим правила, используемые для назначения приоритетов процессам в Windows. Предполагается, что операционная система Windows различает четыре типа процессов в соответствии с их приоритетами: фоновые процессы, процессы с нормальным приоритетом, процессы с высоким приоритетом и процессы реального времени. Рассмотрим подробнее каждый из этих типов процессов.
Фоновые процессы выполняют свою работу, когда нет активных пользовательских процессов. Обычно эти процессы следят за состоянием системы. Приоритет таких процессов устанавливается флагом IDLE_PRIORITY_CLASS.
Процессы с нормальным приоритетом — это обычные пользовательские процессы. Приоритет таких процессов устанавливается флагом NORMAL_PRIORITY_CLASS. Этот приоритет также назначается пользовательским процессам по умолчанию.
Процессы с высоким приоритетом это такие пользовательские процессы, от которых требуется более быстрая реакция на некоторые события, чем от обычных пользовательских процессов. Приоритет таких процессов устанавливается флагом HIGH_PRIORITY_CLASS. Эти процессы должны содержать небольшой программный код и выполняться очень быстро, чтобы не замедлять работу системы. Обычно такие приоритеты имеют другие системы, работающие на платформе операционных систем Windows.
К последнему типу процессов относятся процессы реального времени. Приоритет таких процессов устанавливается флагом REAL_TIME_PRIORITY_CLASS. Работа таких процессов обычно происходит в масштабе реального времени и связана с реакцией на внешние события. Эти процессы должны работать непосредственно с аппаратурой компьютера.
Приоритет процесса можно изменить при помощи функции SetPriorityClass, которая имеет следующий прототип:
function SetPriorityClass(
hProcess: THandle;
// дескриптор процесса
dwPriorityClass: DWORD // приоритет
): BOOL; stdcall;
При успешном завершении функция SetPriorityClass возвращает true, в противном случае значение — false. Параметр dwPriorityClass этой функции должен быть равен одному из флагов, которые приведены выше.
Узнать приоритет процесса можно посредством вызова функции GetPriorityClass, которая имеет следующий прототип:
function GetPriorityClass(
hProcess: THandle // дескриптор процесса
): DWORD; stdcall;
При успешном завершении эта функция возвращает флаг установленного приоритета процесса, в противном случае возвращаемое значение равно нулю.
В листинге 2.14 приведена программа, которая демонстрирует работу функций SetPriorityClass И GetPriorityClass.
Листинг 2.14.
program SetGetPriority; {$APPTYPE CONSOLE} uses SysUtils, Windows; var hProcess: THandle; dwPriority: DWORD; begin // получаем псевдодескриптор текущего потока hProcess := GetCurrentProcess; // узнаем приоритет текущего процесса dwPriority := GetPriorityClass(hProcess); Writeln('The priority of the process = ', dwPriority); // устанавливаем фоновый приоритет текущего процесса if not SetPriorityClass(hProcess, IDLE_PRIORITY_CLASS) then begin Writeln('Set priority class failed.'); Writeln('Press any key to exit.'); Readln; Halt(GetLastError); end; dwPriority := GetPriorityClass(hProcess); Writeln('The priority of the process = ', dwPriority); // устанавливаем высокий приоритет текущего процесса if not SetPriorityClass(hProcess, HIGH_PRIORITY_CLASS) then begin Writeln('Set priority class failed.'); Writeln('Press any key to exit.'); Readln; Halt(GetLastError); end; dwPriority := GetPriorityClass(hProcess); Writeln('The priority of the process = ', dwPriority); Writeln('Press any key to exit.'); Readln; end.
Отметим в связи с этой программой, что числовые значения флагов не соответствуют числовым значениям приоритетов процессов. Так, например, числовое значение флага IDLE_PRIORITY_CLASS больше чем числовое значение флага NORMAL_PRIORITY_CLASS. Но система считает, что приоритет нормального процесса выше, чем приоритет фонового процесса.
Теперь перейдем к приоритетам потоков, задание которых в Windows довольно запутанное. Приоритет потока, который учитывается системой при выделении потокам процессорного времени, называется базовым (base) или основным приоритетом потока. Всего существует 32 базовых приоритета — от 0 до 31. Для каждого базового приоритета существует очередь потоков. При диспетчеризации потоков квант процессорного времени выделяется потоку, который стоит первым в очереди с наивысшим базовым приоритетом. Базовый приоритет потока определяется как сумма приоритета процесса и уровня приоритета потока, который может принимать одно из следующих значений, которые разобьем на две группы. Первая состоит из:
Вторая:
Используемая литература:
Александр Побегайло "Системное программироввние в Windows".
А.Я. Архангельский "Delphi 2006".