что такое дескриптор потока

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

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

1. Потоки и процессы

1.1. Определение потока

Потоком в Windows называется объект ядра, которому операционная система выделяет процессорное время для выполнения приложения. Каждому потоку принадлежат следующие ресурсы:

Все эти ресурсы образуют контекст потока в Windows. Кроме дескриптора каждый поток в Windows также имеет свой идентификатор, который уникален для потоков выполняющихся в системе. Идентификаторы потоков используются служебными программами, которые позволяют пользователям системы отслеживать работу потоков.

В операционных системах Windows различаются потоки двух типов:

Системные потоки выполняют различные сервисы операционной системы и запускаются ядром операционной системы.

Пользовательские потоки служат для решения задач пользователя и запускаются приложением.

В работающем приложении различаются потоки двух типов:

Рабочие потоки выполняют различные фоновые задачи в приложении. Потоки интерфейса пользователя связаны с окнами и выполняют обработку сообщений, поступающих этим окнам. Каждое приложение имеет, по крайней мере, один поток, который называется первичным (primary) или главным (main) потоком. В консольных приложениях это поток, который исполняет функцию main. В приложениях с графическим интерфейсом это поток, который исполняет функцию WinMain.

Создается поток функцией CreateThread, которая имеет следующий прототип:

function CreateThread(
lpThreadAttributes: Pointer; // атрибуты защиты
dwStackSize: DWORD; // размер стека потока в байтах
lpStartAddress: TFNThreadStartRoutine; // адрес функции
lpParameter: Pointer; // адрес параметра
dwCreationFlags: DWORD; // флаги создания потока
var lpThreadId: DWORD // идентификатор потока
): THandle;

При успешном завершении функция CreateThread возвращает дескриптор созданного потока и его идентификатор, который является уникальным для всей системы. В противном случае эта функция возвращает значение nil.

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

Параметр lpThreadAttributes устанавливает атрибуты защиты создаваемого потока. До тех пор пока мы не изучим систему безопасности в Windows, мы будем устанавливать значения этого параметра в nil при вызове почти всех функций ядра Windows. В данном случае это означает, что операционная система сама установит атрибуты защиты потока, используя настройки по умолчанию.

Параметр dwStacksize определяет размер стека, который выделяется потоку при запуске. Если этот параметр равен нулю, то потоку выделяется стек, размер которого по умолчанию равен 1 Мбайт. Это наименьший размер стека, который может быть выделен потоку. Если величина параметра dwStacksize меньше значения, заданного по умолчанию, то все равно потоку выделяется стек размером в 1 Мбайт. Операционная система Windows округляет размер стека до одной страницы памяти, который обычно равен 4 Кбайт.

Параметр lpStartAddress указывает на исполняемую потоком функцию.

Видно, что функции потока может быть передан единственный параметр lpParameter, который является указателем на пустой тип. Это ограничение следует из того, что функция потока вызывается операционной системой, а не прикладной программой. Программы операционной системы являются исполняемыми модулями и поэтому они должны вызывать только функции, сигнатура которых заранее определена. Поэтому для потоков определили самый простой список параметров, который содержит только указатель. Так как функции потоков вызываются операционной системой, то они также получили название функции обратного вызова.

Параметр dwCreationFiags определяет, в каком состоянии будет создан поток. Если значение этого параметра равно 0, то функция потока начинает выполняться сразу после создания потока. Если же значение этого параметра равно CREATE_SUSPENDED, то поток создается в подвешенном состоянии. В дальнейшем этот поток можно запустить вызовом функции ResumeThread.

Параметр lpThreadId является выходным, т. е. его значение устанавливает Windows. Этот параметр должен указывать на переменную, в которую Windows поместит идентификатор потока. Этот идентификатор уникален для всей системы и может в дальнейшем использоваться для ссылок на поток. Идентификатор потока главным образом используется системными функциями и редко функциями приложения. Действителен идентификатор потока только на время существования потока. После завершения потока тот же идентификатор может быть присвоен другому потоку.

При создании потока его базовый приоритет устанавливается как сумма приоритета процесса, в контексте которого этот поток выполняется, и уровня приоритета потока THREAD_PRIORITY_NORMAL.

В листинге 1.1 приведен пример программы, которая использует функцию CreateThread для создания потока и демонстрирует способ передачи параметров исполняемой потоком функции.

Листинг 1.1. Создание потока функцией CreateThread

Отметим, что в этой программе используется функция WaitForSingleObject, которая ждет завершения потока Add.

Поток завершается вызовом функции ExitThread, которая имеет следующий прототип:

procedure ExitThread(
dwExitCode: DWORD //код завершения потока
); stdcall;

Эта функция может вызываться как явно, так и неявно при возврате значения из функции потока. При выполнении этой функции система посылает динамическим библиотекам, которые загружены процессом, сообщение DLL_THREAD_DETACH, которое говорит о том, что поток завершает свою работу.

Один поток может завершить другой поток, вызвав функцию TerminateThread, которая имеет следующий прототип:

function TerminateThread(
hThread: THandle; //дескриптор потока
dwExitCode: DWORD; //код завершения потока
): BOOL; stdcall;

В случае успешного завершения функция TerminateThread возвращает ненулевое значение, в противном случае — FALSE. Функция TerminateThread завершает поток, но не освобождает все ресурсы, принадлежащие этому потоку. Это происходит потому, что при выполнении этой функции система не посылает динамическим библиотекам, загруженным процессом, сообщение о том, что поток завершает свою работу. В результате динамическая библиотека не освобождает ресурсы, которые были захвачены для работы с этим потоком. Поэтому эта функция должна вызываться только в аварийных ситуациях при зависании потока.

В листинге 1.2 приведена программа, которая демонстрирует работу функции TerminateThread.

Каждый созданный поток имеет счетчик приостановок, максимальное значение которого равно MAXIMUM_SUSPEND_COUNT. Счетчик приостановок показывает, сколько раз исполнение потока было приостановлено. Поток может исполняться только при условии, что значение счетчика приостановок равно нулю. В противном случае поток не исполняется или, как говорят, находится в подвешенном состоянии. Исполнение каждого потока может быть приостановлено вызовом функции SuspendThread, которая имеет следующий прототип:

function SuspendThread(
hThread: THandle //дескриптор потока
): DWORD; stdcall;

Отметим, что поток может приостановить также и сам себя. Для этого он должен передать функции SuspendThread свой псевдодескриптор, который можно получить при помощи функции GetCurrentThread.

Для возобновления исполнения потока используется функция ResumeThread, которая имеет следующий прототип:

function ResumeThread(
hThread: THandle //дескриптор потока
): DWORD; stdcall;

Поток может задержать свое исполнение вызовом функции Sleep, которая имеет следующий прототип:

procedure Sleep(
dwMilliseconds: DWORD //миллисекунды
); stdcall;

Единственный параметр функции Sleep определяет количество миллисекунд, на которые поток, вызвавший эту функцию, приостанавливает свое исполнение. Если значение этого параметра равно 0, то выполнение потока просто прерывается, а затем возобновляется при условии, что нет других потоков, ждущих выделения процессорного времени. Если же значение этого параметра равно INFINITE, тo поток приостанавливает свое исполнение навсегда, что приводит к блокированию работы приложения.

В листинге 1.3 приведена программа, которая демонстрирует работу функций SuspendThread, ResumeThread и Sleep.

Иногда потоку требуется знать свой дескриптор, чтобы изменить какие-то свои характеристики. Например, поток может изменить свой приоритет. Для этих целей в Win32 API существует функция GetcurrentThread, которая имеет следующий прототип:

function GetCurrentThread: THandle; stdcall;

и возвращает псевдодескриптор текущего потока. Псевдодескриптор текущего потока отличается от настоящего дескриптора потока тем, что он может использоваться только самим текущим потоком и, следовательно, может наследоваться другими процессами. Псевдодескриптор потока не нужно закрывать после его использования. Из псевдодескриптора потока можно получить настоящий дескриптор потока, для этого псевдодескриптор нужно продублировать, вызвав функцию DuplicateHandle.

В листинге 1.4 приведен пример программы, которая вызывает функцию GetCurrentThread, а затем выводит на консоль полученный псевдодескриптор.

function GetLastError: DWORD; stdcall;

Эта функция возвращает код последней ошибки, установленной в потоке. Установить код последней ошибки в потоке можно при помощи функции SetLastError, имеющей следующий прототип:

procedure SetLastError(
dwErrCode: DWORD //код ошибки
); stdcall;

Чтобы получить сообщение, соответствующее коду последней ошибки, необходимо использовать функцию FormatMessage, которая имеет следующий прототип:

function FormatMessage(
dwFlags: DWORD; // режимы форматирования
lpSource: Pointer; // источник сообщения
dwMessageId: DWORD; // идентификатор сообщения
dwLanguageId: DWORD; // идентификатор языка
lpBuffer: PChar; // буфер для сообщения
nSize: DWORD; // максимальный размер буфера для сообщения
Arguments: Pointer // список значений для вставки в сообщение
): DWORD; stdcall;

В листинге 1.5 приведен пример программы, которая вызывает функцию FormatMessage

Исходный код скачать. Выполнен на Delphi XE.

Используемая литература: Александр Побегайло «Системное программироввние в Windows»

Источник

Что такое дескриптор потока

Цель работы: Получение практических навыков при использовании Win32 API для исследования процессов

Методические указания к выполнению лабораторной работы

Процесс – это исполняемый экземпляр приложения и набор ресурсов, которые выделяются данному исполняемому приложению. Ресурсы включают в себя следующее:

Поток – это внутренняя составляющая процесса, которой операционная система выделяет процессорное время. Каждый процесс должен иметь, по крайней мере, один поток. Поток включает:

Состояние регистров, содержимое стека и области памяти называют контекстом потока (thread’s context).

Основное назначение потоков – дать процессу возможность поддерживать несколько ветвей управления, то есть выполнять больше задач одновременно.

Дескрипторы и идентификаторы процессов

Дескриптор процесса определяется функцией CreateProcess (подробнее см. [1]), идентификатор текущего процесса – функцией GetCurrentProcessId. Чтобы определить идентификатор другого процесса, необходимо получить список всех процессов и выбрать из него тот процесс, характеристики которого требуются или воспользоваться или воспользовать методикой получения идентификатора процесса от окна ([1], с. 42).

Между дескриптором и идентификатором процесса (или потока) существуют следующие основные различия:

Идентификация процесса возможна различными способами с использованием следующих объектов:

Имена файлов и дескрипторы модулей

Каждый модуль (DLL, OCX, DRV и т.д.), загруженный в пространство процесса, имеет свой дескриптор (module handle), называемый также логическим номером экземпляра (instance handle). В 16-разрядной Windows данными терминами обозначались разные объекты, в 32-разрядной системе это один и тот же объект.

Дескриптором исполняемого модуля является начальный или базовый адрес данного исполняемого модуля в адресном пространстве процесса. Это объясняет, почему такой дескриптор имеет смысл только в рамках процесса, включающего данный модуль.

Перейти от имени файла модуля к дескриптору модуля и наоборот не составляет особого труда, по крайней мере, в пределах одного процесса. Функция GetModuleFileName принимает дескриптор модуля, чтобы вернуть полное имя (имя и путь) исполняемого файла.

Функция GetСurrentProcess возвращает псевдодескриптор текущего процесса.

Псевдодескриптор (pseudohandle) представляет собой упрощенный вариант дескриптора. По определению, псевдодескриптор – это зависимое от процесса число, которое служит идентификатором процесса и может использоваться в вызовах тех API-функций, которым требуемся дескриптор процесса.

Хотя назначение псевдодескрипторов и обычных дескрипторов почти одно и то же, у них все же есть некоторые существенные различия. Псевдодескрипторы не могут наследоваться порожденными процессами, как настоящие дескрипторы (real handler). К тому же псевдодескрипторы ссылаются только на текущий процесс, а настоящие дескрипторы могут ссылаться и на внешний (foreign).

Windows предоставляет возможность получения настоящего дескриптора по псевдодескриптору при помощи API-функции DuplicateHandle.

Перечисление процессов в Windows 9x. Моментальные снимки

Благодаря многозадачной природе среды Win32 такие объекты, как процессы, потоки, модули и т.п., постоянно создаются, разрушаются и модифицируются. И поскольку состояние компьютера непрерывно изменяется, системная информация, которая, возможно, будет иметь значение в данный момент, через секунду уже никого не заинтересует. Например, предположим, что вы хотите написать программу для регистрации всех модулей, загруженных в систему. Поскольку операционная система в любое время может прервать выполнение потока, отрабатывающего программу, чтобы предоставить какие-то кванты времени другому потоку в системе, модули теоретически могут создаваться и разрушаться даже в момент выборки информации о них.

В этой динамической среде имело бы смысл на мгновение заморозить систему, чтобы получить такую системную информацию. В ТооlНе1р32 не предусмотрено средств замораживания системы, но есть функция, с помощью которой можно сделать «снимок» системы в заданный момент времени – CreateToolhelp32Snapshot().

Задание для выполнения лабораторной работы

Составить следующую программу, которая:

Видео выполнения работы

что такое дескриптор потока. Смотреть фото что такое дескриптор потока. Смотреть картинку что такое дескриптор потока. Картинка про что такое дескриптор потока. Фото что такое дескриптор потока что такое дескриптор потока. Смотреть фото что такое дескриптор потока. Смотреть картинку что такое дескриптор потока. Картинка про что такое дескриптор потока. Фото что такое дескриптор потока что такое дескриптор потока. Смотреть фото что такое дескриптор потока. Смотреть картинку что такое дескриптор потока. Картинка про что такое дескриптор потока. Фото что такое дескриптор потока

Ход выполнения лабораторной работы

Программа выведет полную информацию о занятой и свободной физической и виртуальной памяти, составит карту процессов, создаст виртуальную память для каждого процесса.

Вывод информации о потоках выбранного процесса

Вывод информации о модулях текущего процесса

Вывод информации о модулях всех процессов

Источник

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

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

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

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 (под робнее см. выше в описании функции).

lpThreadAttributes

Указатели на структуры типа PSequrityAttributes, определяющие наследование дескриптора в дочернем процессе. Если эти параметры равны nil, наследование невозможно.

bInheritHandles

Определяет, наследуют ли новые процессы дескрипторы родительских. Если true — наследуют с тем же уровнем доступа, что и в родительском процессе.

dwCreationFlags

Определяет флаги, задающие характеристики создаваемого процесса.

lpEnvironment

lpCurrentDirectory

Указывает на строку, определяющую текущий каталог и диск дочернего процесса. Это используется в приложениях — оболочках, выполняющих различные приложения с различными рабочими каталогами. Если параметр равен nil, текущий каталог совпадает с родительским.

lpStartupInfo

Указывет на структуру типа TStartupInfo, определяющую основное окно дочернего процесса.

lpProcessInformation

Указывет на структуру TProcessInformation, из которой родительское приложение может получать информацию о выполнении нового процесса.

Коротко просуммировать информацию, приведенную в таблице, можно следующим образом. Параметры lpProcessAttributes, lpThreadAttributes, lpEnvironment, blnHeritHandles определяют наследование дочерним процессом свойств родительского процесса. Если не вдаваться в подробности наследования, то можно первые три из этих параметров задавать равными nil, а последний — false. Параметр lpCurrentDirectory указывает на строку, определяющую текущий каталог и диск дочернего процесса. Это используется в приложениях-оболочках, выполняющих различные приложения с различными рабочими каталогами. Если параметр равен nil, текущий каталог совпадает с родительским.

Указанный в таблице параметр dwCreationFlags определяет флаги, задающие характеристики создаваемого процесса. Указанные ниже флаги, управляющие созданием процесса, могут задаваться в любых комбинациях (кроме специально оговоренных) с помощью операции оr.

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

Новый процесс не наследует режим ошибок родительского процесса. В нем устанавливается режим по умолчанию вызовом SetErrorMode. Обычно используется в многопоточных процессах.

Создается новое консольное приложение. Этот флаг не может использоваться совместно с DETACHED_PROCESS. Это говорит системе о том, что для запускаемого процесса должна быть создана новая консоль. Если этот параметр будет равен 0, то новая консоль для запускаемого процесса не создается и весь консольный вывод нового процесса будет направляться в консоль родительского процесса.

Новый процесс является корневым для новой группы процессов: всех процессов, которые будут наследовать создаваемому. Идентификатор новой группы — тот, который возвращается параметром lpProcessInformation. Группы процессов используются функцией GenerateConsoleCtrlEvent для посылки сигналов Ctrl-С или Ctrl-Break группе консольных процессов.

Используется только в Windows NT для создания 16-битных процессов. Его установка приводит к использованию для процесса отдельной VDM. В этом случае отказ в данном процессе не приведет к гибели других выполняемых процессов.

Используется только в Windows NT для создания 16-битных процессов. Если ключ DefaultSeparateVDM в разделе Windows файла WIN.INI установлен в true, то этот флаг приводит к изменению ключа и все новые процессы запускаются в общей VDM.

Основной поток (нить) нового приложения создается в состоящей ожидания и не выполняется, пока не будет вызвана функция ResumeThread.

При установке этого флага блок окружения, на который указывает lpEnvironment, использует символы Unicode. В отсутствие флага используются символы ANSI.

При установке этого флага родительский процесс воспринимается как отладчик дочернего процесса. Система информирует отладчик обо всех событиях в отлаживаемом процессе. В этом режиме функция WaitForDebugEvent может использоваться только для потока, созданного функцией CreateProcess.

Если этот флаг отсутствует, и родительский процесс отлаживается, новые процессы тоже отлаживаются тем же отладчиком.

Новый консольный процесс не имеет доступа к консоли родительского. Он может позднее вызвать функцию AllocConsole для создания новой консоли. Этот флаг не может использоваться совместно с флагом CREATE_NEW_CONSOLE.

Параметр dwCreationFlags может также контролировать класс приоритета нового процесса. Если ни один из описанных ниже флагов приоритета не установлен, по умолчанию используется NORMAL_PRIORITY_CLASS, если только родительский процесс не имеет класс IDLE_PRIORITY_CLASS. В последнем случае для дочерних процессов по умолчанию принимается класс IDLE_PRIORITY_CLASS.

Приоритет может задаваться одним из следующих флагов:

Указывает на процесс как на критическую задачу, которая должна выполняться немедленно.

Все потоки процесса выполняются только во время простоя системы. Пример — хранители экрана. Все наследники такого процесса будут иметь тот же класс приоритета. Класс фоновых процессов. Обычно эти процессы следят за состоянием системы.

Нормальный приоритет процесса. Это обычные пользовательские процессы. Приоритет обычных пользовательских процессов может также устанавливаться флагами BELOW_NORMAL_PRIORITY_CLASS или ABOVE_NORMAL_PRIORITY_CLASS, которые соответственно немного повышают или понижают приоритет пользовательского процесса.

Высокий приоритет, превышающий приоритеты других процессов, включая приоритеты процессов операционной системы. Приоритет таких процессов устанавливается флагом REAL_TIME_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;

HIGH_PRIORITY_CLASS
Поля обозначают следующее:

Возвращает дескриптор созданного процесса. Используется во всех функциях, осуществляющих операции с объектом процесса.

Возвращает дескриптор первого потока (нити) созданного процесса. Используется во всех функциях, осуществляющих операции с объектом потока.

Возвращает глобальный идентификатор процесса. Значение доступно с момента создания процесса и до момента его завершения.

Возвращает глобальный идентификатор потока. Значение доступно с момента создания потока и до момента его завершения.

Параметр lpStartupInfo функции CreateProcess указывает на запись типа TStartupInfo, соответствующего типу структуры STARTUPINFO API Windows. Запись определяет свойства главного окна создаваемого процесса. Для процессов с графическим интерфейсом пользователя (GUI) эта информация относится к первому окну, создаваемому функцией CreateWindow и отображаемому функцией ShowWindow. Для консольных приложений эта информация относится к создаваемому консольному окну.

Для примера рассмотрим программу, которая выводит на консоль свое имя и параметры. Эта программа приведена в листинге 2.1.

Листинг 2.1. Программа, которая выводит на консоль свое имя и параметры

Скомпилируем эту программу. Полученный ехе-файл сохраним на диске С: и назовем ConsoleProcess1.exe. Наша задача состоит в запуске этого файла как нового процесса. Как это сделать показано в листинге 2.2, где приведена программа, запускающая созданный ехе-файл как консольный процесс с новой консолью.

Листинг 2.2. Программа процесса, который создает процесс с новой консолью

Отметим в этой программе два момента. Во-первых, перед запуском консольного процесса 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. Программа процесса, который создает процесс с новой консолью

Отличие этой программы от программы, приведенной в листинге 2.2, состоит в том, что мы передаем системе имя нового процесса и его парамметры через командную строку. В этом случае имя нового процесса может и не содержать полный путь к ехе-файлу, а только имя самого ехе-файла.

Для иллюстрации сказанного запустим приложение Notepad.exe (Блокнот), используя командную строку. Программа, запускающая Блокнот из командной строки, приведена в листинге 2.4.

Листинг 2.4. Запуск приложения Notepad

2.3. Завершение процессов

Процесс может завершить свою работу вызовом функции ExitProcess, которая имеет следующий прототип:

procedure ExitProcess(
uExitCode: UINT //код возврата из процесса
); stdcall;

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

В листинге 2.5 приведен пример программы, которая завершает свою работу вызовом функции ExitProcess.

Листинг 2.5. Завершение процесса функцией ExitProcess

Один процесс может быть завершен другим при помощи вызова функции TerminateProcess, которая имеет следующий прототип!

function TerminateProcess(
hProcess: THandle; // дескриптор процесса
uExitCode: UINT // код возврата
): BOOL; stdcall;

Если функция TerminateProcess выполнилась успешно, то она возвращает True. В противном случае возвращаемое значение равно FALSE. Функция TerminateProcess завершает работу процесса, но нe ocвoбождает все ресурсы, принадлежащие этому процессу. Это происходит потому, что при выполнении этой функции система не посылает динамическим библиотекам, загруженным процессом, сообщение о том, что библиотеку необходимо отсоединить от процесса. Поэтому эта функция должна вызываться только в аварийных ситуациях при зависании процесса.

Приведем программу, которая демонстрирует работу функции TerminateProcess. Для этого сначала создадим бесконечный процесс-счетчик, который назовем ConsoleProcess.exe, и расположим на диске С: (листинг 4.6).

Листинг 2.6. Программа бесконечного процесса

Теперь рассмотрим программу, которая создает этот бесконечный процесс-счетчик, а потом завершает его по требованию пользователя, используя для этого функцию TerminateProcess. Эта программа приведена в листинге 2.7.

Листинг 2.7. Завершение процесса функцией TeminateProcess

2.4. Завершение процессов

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

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

Однако для того чтобы дочерний процесс имел доступ к наследуемому объекту в родительском процессе, недостаточно просто сделать дескриптор этого объекта наследуемым. Кроме этого, нужно, во-первых, установить значение параметра bInheritHandles функции CreateProcess в True и, во-вторых, передать сам дескриптор дочернему процессу, который создается функцией CreateProcess. Наследуемый дескриптор передается системой дочернему процессу неявно и поэтому он скрыт от программ, которые выполняются в дочернем процессе. То есть программа дочернего процесса должна явно знать этот дескриптор и передать его этой программе должна программа родительского процесса. Одним из способов передачи дескрипторов дочернему процессу является использование командной строки, которая позволяет передавать дескрипторы как параметры.

Для пояснения сказанного приведем пример двух процессов, в которых используются наследуемые дескрипторы. В листинге 2.8 приведена программа дочернего процесса. Этот процесс получает дескриптор потока от родительского процесса и, по требованию пользователя, прекращает выполнение этого потока в родительском процессе.

Листинг 2.8. Завершение потока в родительском процессе, используя дескриптор потока, который передается через командную строку

В листинге 2.9 приведена программа родительского процесса, который создает дочерний процесс и передает ему через командную строку наследуемый дескриптор потока. Этот поток и должен быть завершен дочерним процессом, программа которого приведена в листинге 2.8.

Листинг 2.9. Процесс, который передает наследуемый дескриптор потока дочернему процессу через командную строку

В программе из листинга 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. Изменение свойства наследования дескриптора

Для определения свойств дескриптора используется функция 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

Теперь приведем, в листинге 2.12, пример программы, которая дублирует дескриптор, но при этом изменяет доступ к нему. Разрешим дочернему процессу прекращать выполнение потока в родительском процессе. В этом случае остальные функции над потоками, такие как, например, SuspendThread и ResumeThread, будут недоступны для выполнения в дочернем процессе. Обратите внимание на изменение значений параметров функции DuplicateHandle.

Листинг 2.12. Создание наследуемого дескриптора функцией DuplicateHandle с изменением доступа к объекту

2.6. Псевдодескрипторы процессов

Иногда процессу требуется знать свой дескриптор, чтобы изменить какие-то свои характеристики. Например, процесс может изменить свой приоритет. Для этих целей в Win32 API существует функция GetCurrentProcess, которая имеет следующий прототип:

function GetCurrentProcess: THandle; stdcall;

и возвращает псевдодескриптор текущего процесса. Псевдодескриптор текущего процесса отличается от настоящего дескриптора процесса тем, что он может использоваться только текущим процессом и не может наследоваться другими процессами. Псевдодескриптор процесса не нужно закрывать после его использования. Из псевдодескриптора процесса можно получить настоящий дескриптор процесса: для этого псевдодексриптор нужно продублировать, вызвав функцию DuplicateHandle.

В листинге 2.13 приведен пример программы, которая получает псевдодескриптор процесса посредством вызова функции GetCurrentProcess, а затем выводит полученный псевдодескриптор на консоль.

Листинг 2.13. Получение псевдодескриптора процесса

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.

Приоритет процесса можно изменить при помощи функции SetPriorityClass, которая имеет следующий прототип:

function SetPriorityClass(
hProcess: THandle; // дескриптор процесса
dwPriorityClass: DWORD // приоритет
): BOOL; stdcall;

При успешном завершении функция SetPriorityClass возвращает true, в противном случае значение — false. Параметр dwPriorityClass этой функции должен быть равен одному из флагов, которые приведены выше.

Узнать приоритет процесса можно посредством вызова функции GetPriorityClass, которая имеет следующий прототип:

function GetPriorityClass(
hProcess: THandle // дескриптор процесса
): DWORD; stdcall;

При успешном завершении эта функция возвращает флаг установленного приоритета процесса, в противном случае возвращаемое значение равно нулю.

В листинге 2.14 приведена программа, которая демонстрирует работу функций SetPriorityClass И GetPriorityClass.

Отметим в связи с этой программой, что числовые значения флагов не соответствуют числовым значениям приоритетов процессов. Так, например, числовое значение флага IDLE_PRIORITY_CLASS больше чем числовое значение флага NORMAL_PRIORITY_CLASS. Но система считает, что приоритет нормального процесса выше, чем приоритет фонового процесса.

Теперь перейдем к приоритетам потоков, задание которых в Windows довольно запутанное. Приоритет потока, который учитывается системой при выделении потокам процессорного времени, называется базовым (base) или основным приоритетом потока. Всего существует 32 базовых приоритета — от 0 до 31. Для каждого базового приоритета существует очередь потоков. При диспетчеризации потоков квант процессорного времени выделяется потоку, который стоит первым в очереди с наивысшим базовым приоритетом. Базовый приоритет потока определяется как сумма приоритета процесса и уровня приоритета потока, который может принимать одно из следующих значений, которые разобьем на две группы. Первая состоит из:

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

hProcess