Системное программирование в Windows

КОМПЬЮТЕРНЫЕ КУРСЫ "ПОИСК"

[Главная страница] [Контакты]

2. Процессы в Windows


2.1. Определение процесса

Приложение, или, как часто его называют, программа, – это статическая последовательность команд. При запуске программы в ОС Win32 создается отдельный процесс (process), или, как его иногда называют, задача (task). Процесс состоит из:

  • исполняемой программы с ее кодом и данными;
  • виртуального адресного пространства (address space), представляющего собой набор адресов виртуальной памяти, доступных для процесса;
  • ресурсов, выделяемых процессу;
  • как минимум одного потока, именуемого потоком управления (thread of execution), или главным потоком процесса. Именно поток управления и требует от операционной системы времени на обслуживание процесса.

В момент запуска задачи ОС создает дескриптор – специальную структуру, описывающую стартующий процесс. Дескриптор процесса содержит информацию, позволяющую Win32 управлять процессом. В частности в дескрипторе хранится:

  • идентификатор процесса (PID – process identificator);
  • информация о ресурсах, которыми владеет или собирается завладеть процесс;
  • данные о приоритете процесса;
  • контекст задачи, представляющий собой специальную защищенную область памяти, в которую записываются значения регистров процессора в момент приостановки задачи и откуда считываются данные в момент возобновления выполнения задачи.

За один и тот же интервал времени, называемый квантом времени, один процессор реально может выполнять не более одной задачи. Вместе с тем многозадачная (multitasking) ОС вполне способна одновременно обслуживать несколько процессов. Это достигается благодаря переключению процессора компьютера с исполнения одной задачи на другую. Такое переключение называется переключением контекста (context switching). При этом важно помнить, что время выделяется не процессу, а принадлежащим ему потокам. И чем выше производительность процессора, тем более правдоподобна иллюзия одновременного выполнении нескольких программ.

2.2. Создание процесса

Новый процесс в 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.

Если имя файла не содержит расширения, то предполагается расширение .ехе. Но если имя кончается символом точки или если файл задан вместе с путем, то расширение .ехе к имени автоматически не добавляется.

Если путь к файлу не задан, файл ищется в каталогах в следующей последовательности:

  1. Каталог, из которого запускается приложение.
  2. Текущий каталог родительского процесса.
  3. Системный каталог Windows, возвращаемый функцией GetSystemDirectory.
  4. В Windows NT каталог SYSTEM.
  5. Каталог Windows, возвращаемый функцией GetWindowsDirectory.
  6. Каталоги, перечисленные в переменной окружения PATH.

Если функция успешно выполнена, она возвращает значение true. Если произошла ошибка — возвращается false. Тогда информацию об ошибке можно получить, вызвав функцию GetLastError.

Функция CreateProcess пришла на смену прежним функциям WinExec и LoadModule, которые теперь реализуются посредством вызова CreateProcess. Функция CreateProcess возвращается, не ожидая окончания инициализации порождаемого процесса. Но в ряде случаев родительский процесс должен взаимодействовать с порожденным. Такое взаимодействие возможно только после того, как закончена инициализация порожденного процесса. Приостановить выполнение до окончания инициализации дочернего процесса можно функцией WaitForInputIdle. В некоторых случаях выполнение родительского процесса должно быть приостановлено до завершения порожденного процесса. Это необходимо, например, если родительский процесс должен использовать какие-то результаты, полученные порожденным процессом. Для ожидания завершения порожденного процесса можно использовать функцию WaitForSingleObject. Порожденный процесс остается в памяти системы, пока не завершатся все его потоки (нити), и пока все его дескрипторы не закроются вызовом CloseHandle. Если эти дескрипторы не нужны, лучше всего закрыть их сразу после инициализации процесса. Чтобы досрочно прекратить выполнение дочернего процесса лучше всего использовать функцию ExitProcess.

Назначение параметров

lpApplicationName

Указатель на строку, содержащую имя выполняемого модуля: или с полным путем, или только имя (тогда файл должен находиться в текущем каталоге). Если lpApplicationName = nil, имя модуля должно задаваться первым элементом строки lpCommandLine (подробнее см. выше в описании функции).

lpCommandLine

Указатель на строку, содержащую командную строку выполняемого файла. Если lpCommandLine = nil, то в качестве командной строки выступает lpApplicationName (под робнее см. выше в описании функции).

lpProcessAttributes

lpThreadAttributes

Указатели на структуры типа 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.

Значения параметра dwCreationFlags

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 могут быть наследуемыми или ненаследуемыми. Свойство наследования объекта означает, что если наследуемый объект создан или открыт в некотором процессе, то к этому объекту будут также иметь доступ все процессы, которые создаются этим процессом, т. е. являются его потомками. Свойство наследования объекта определяется его дескриптором, который также может быть наследуемым или ненаследуемым. Для того чтобы объект стал наследуемым, необходимо сделать наследуемым его дескриптор и наоборот.

Свойство наследования не поддерживается для объектов, использование которых несколькими процессами нарушило бы изолированность памяти процесса от других процессов. Поэтому не могут наследоваться следующие дескрипторы:

  • дескриптор виртуальной памяти, который возвращает любая из функций LocalAlloc, GlobalAlloc, HeapCreate или HeapAlloc;
  • дескриптор динамической библиотеки, который возвращает функция LoadLibrary.

Однако для того чтобы дочерний процесс имел доступ к наследуемому объекту в родительском процессе, недостаточно просто сделать дескриптор этого объекта наследуемым. Кроме этого, нужно, во-первых, установить значение параметра 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. Для установки приоритета процесса в этом параметре нужно установить один из следующих флагов.

  • IDLE_PRIORITY_CLASS — класс фоновых процессов;
  • BELOW_NORMAL_PRIORITY_CLASS — класс процессов ниже нормальных;
  • NORMAL_PRIORITY_CLASS — класс нормальных процессов;
  • ABOVE_NORMAL_PRIORITY_CLASS — класс процессов выше нормальных;
  • HIGH_PRIORITY_CLASS — класс высокоприоритетных процессов;
  • REAL_TIME_PRIORITY_CLASS — класс процессов реального времени.

Рассмотрим правила, используемые для назначения приоритетов процессам в 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. Для каждого базового приоритета существует очередь потоков. При диспетчеризации потоков квант процессорного времени выделяется потоку, который стоит первым в очереди с наивысшим базовым приоритетом. Базовый приоритет потока определяется как сумма приоритета процесса и уровня приоритета потока, который может принимать одно из следующих значений, которые разобьем на две группы. Первая состоит из:

  • THREAD_PRIORITY_LOWEST — низший приоритет;
  • THREAD_PRIORITY_BELOW_NORMAL — приоритет ниже нормального;
  • THREAD_PRIORITY_NORMAL — нормальный приоритет;
  • THREAD_PRIORITY_ABOVE_NORMAL — приоритет выше нормального;
  • THREAD_PRIORITY_HIGHEST — высший приоритет.

Вторая:

  • THREAD_PRIORITY_IDLE — приоритет фонового потока;
  • THREAD_PRIORITY_TIME_CRITICAL — приоритет потока реального времени.

 

 

 

 

 

 

 

 

Используемая литература:

  1. Александр Побегайло "Системное программироввние в Windows".

  2. А.Я. Архангельский "Delphi 2006".