Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009

.pdf
Скачиваний:
6265
Добавлен:
13.08.2013
Размер:
31.38 Mб
Скачать

150 Часть II. Приступаем к работе

typedef struct _JOBOBJECT_BASIC_LIMIT_INFORMATION {

LARGE_INTEGER

PerProcessUserTimeLimit;

LARGE_INTEGER

РеrJobUse rTimeLimit;

DWORD

LimitFlags;

DWORD

MinimumWorkingSetSize;

DWORD

MaximumWorkingSetSize;

DWORD

ActiveProcessLimit;

DWORD_PTR

Affinity;

DWORD

PriorityClass;

DWORD

SchedulingClass;

} JOBOBJECT_BASIC_LIMIT_INFORMATION, *PJOBOBJECT_BASIC_LIMIT_INFORMATION;

Все элементы этой структуры кратко описаны в таблице 5-2. Хочу пояснить некоторые вещи, связанные с этой структурой, которые, по-моему довольно туманно изложены в документации Platform SDK. Указывая ограничения для задания, вы устанавливаете те или иные биты в элементе LimitFlags. Например, в StartRestrictedProcess я использовал флаги JOB_OBJECT_LIMIT_PRIORITY_CLASS

и JOB_ OBJECT_LIMIT_JOB_TIME, т. е. определил всего два ограничения.

При выполнении задание ведет учет по нескольким показателям — например, сколько процессорного времени уже использовали его процессы. Всякий раз, когда вы устанавливаете базовые ограничения с помощью флага JOB_OBJECT_LIMIT_JOB_TIME, из общего процессорного времени, израсходованного всеми процессами, вычитается то, которое использовали завершившиеся процессы. Этот показатель сообщает, сколько процессорного времени израсходовали активные на данный момент процессы. А что если вам понадобится изменить ограничения на доступ к подмножеству процессоров, не сбрасывая при этом учетную информацию по процессорному времени? Для этого вы должны ввести новое базовое ограничение флагом JOB_OBJECT_LIMIT_AFFINITY и отказаться от флага JOB_OBJECT_LIMIT_JOBTIME. Но тогда получится, что вы снимаете ограничения на процессорное время.

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

JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME. Этот флаг и

JOB_OBJECT_LIMIT_JOB_TIME являются взаимоисключающими. Флаг

JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME указывает системе«изменить огра-

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

Обсудим также элемент SchedulingClass структуры JOBOBJECT_BASIC_LIMIT_INFORMATION. Представьте, что для двух заданий определен класс приоритета NORMAL_PRIORITY_CLASS, а вы хотите, чтобы процессы одного задания получали больше процессорного времени, чем

Глава 5. Задания.docx 151

процессы другого. Так вот, элемент SchedulingClass позволяет изменять распределение процессорного времени между заданиями с одинаковым классом приоритета, вы можете присвоить ему любое значение в пределах 0-9 (по умолчанию он равен 5). Увеличивая его значение, вы заставляете Windows 2000 выделять потокам в процессах конкретного задания более длительный квант времени, а снижая — напротив, уменьшаете этот квант.

Табл. 5-2. Элементы структуры JOBOBJECT_BASIC_LIMIT_INFORMATION

Элементы

Описание

Примечание

PerProcessUser-

Максимальное время в пользова-

Система автоматически завершает

TimeLimit

тельском режиме, выделяемое каж-

любой процесс, который пытается

 

дому процессу (в порциях)

использовать больше отведенного

 

 

по 100 нс времени. Это ограниче-

 

 

ние вводится флагом

 

 

JOB_OBJECT_LIMIT_PROCESS_T

 

 

IME в LimitFlags

PerJobUserTime-

Максимальное время в пользова-

По умолчанию система автомати-

Limit

тельском режиме для всех процессов

чески завершает все процессы, ко-

 

в данном задании (в порциях по 100

гда заканчивается это время. Дан-

 

нс)

ное значение можно изменять в

 

 

процессе выполнения задания. Это

 

 

ограничение вводится флагом

 

 

JOB_OBJECT_LIMIT_JOB_TIME в

 

 

LimitFlags

LimitFlags

Виды ограничений для задания

См. раздел после таблицы

Minimum Working

Верхний и нижний предел рабочего

Обычно рабочий набор процесса

SetSize и Maximum

набора для каждого процесса (а не

может расширяться за стандартный

Working SetSize

для всех процессов в задании)

предел; указав MaximumWorking-

 

 

SetSize, вы введете жесткое ограни-

 

 

чение. Когда размер рабочего на-

 

 

бора какого-либо процесса достиг-

 

 

нет заданного предела, процесс

 

 

начнет сбрасывать свои страницы

 

 

на диск. Вызовы функции SetPro-

 

 

cessWorkingSetSize этим процессом

 

 

будут игнорироваться, если только

 

 

он не обращается к ней для того,

 

 

чтобы очистить свой рабочий на-

 

 

бор. Это ограничение вводится

 

 

флагом

 

 

JOB_OBJECT_LIMIT_WORKINGS

 

 

ET в LimitFlags

152 Часть II. Приступаем к работе

Табл. 5-2. (окончание)

Элементы

Описание

 

 

Примечание

ActiveProcessLimit

Максимальное

количество про-

Любая попытка обойти такое ограничение

 

цессов, одновременно

выпол-

приведет к завершению в задании нового

 

няемых

 

 

 

процесса с ошибкой «not enough quota»

 

 

 

 

 

(«превышение квоты»). Это ограничение

 

 

 

 

 

вводится флагом JОВ_ОВJЕСТ_LIMIT_

 

 

 

 

 

ACTIVE_PROCESS в LimitFlags

Affinity

Подмножество

процессоров,

на

Для индивидуальных процессов это огра-

 

которых можно выполнять про-

ничение можно еще больше детализиро-

 

цессы этого задания

 

 

вать. Вводится флагом JOB_OBJECT_

 

 

 

 

 

LIMIT_AFFINITY в LimitFlags

PriorityClass

Класс приоритета для всех про-

Вызванная процессом функция SetPriori-

 

цессов в задании

 

 

tyClass сообщает об успехе даже в том

 

 

 

 

 

случае, если на самом деле она не выпол-

 

 

 

 

 

нила свою задачу, а GetPriorityClass воз-

 

 

 

 

 

вращает класс приоритета, каковой и пы-

 

 

 

 

 

тался установить процесс, хотя в реально-

 

 

 

 

 

сти его класс может быть совсем другим.

 

 

 

 

 

Кроме того, SetThreadPriority не может

 

 

 

 

 

поднять приоритет потоков выше normal,

 

 

 

 

 

но позволяет понижать его. Это ограни-

 

 

 

 

 

чение вводится флагом JOB_OBJECT_

 

 

 

 

 

LIMIT_PRIORITY_CLASS в LimitFlags

SchedulingClass

Относительная

продолжитель-

Этот элемент может принимать значения

 

ность кванта времени,

выделяе-

от 0 до 9; по умолчанию устанавливается

 

мого всем потокам в задании

 

5. Подробнее о его назначении см. ниже.

 

 

 

 

 

Это ограничение вводится флагом

 

 

 

 

 

JOB_OBJECT_LIMIT_SCHEDULING_

 

 

 

 

 

CLASS в LimitFlags

Допустим, у меня есть два задания с обычным (normal) классом приоритета: в каждом задании — по одному процессу, а в каждом процессе — по одному потоку (тоже с обычным приоритетом). В нормальной ситуации эти два потока обрабатывались бы процессором по принципу карусели и получали бы равные кванты процессорного времени. Но если я запишу в элемент SchedulingClass для первого задания значение 3, система будет выделять его потокам более короткий квант процессорного времени, чем потокам второго задания.

Глава 5. Задания.docx 153

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

И последнее ограничение, которое заслуживает отдельного упоминания, свя-

зано с флагом JOB_OBJECT_LIMIT_DIE_ONJUNHANDLED_ EXCEPTION. Он отключает для всех процессов в задании вывод диалогового окна с сообщением о необработанном исключении. Система реагирует на этот флаг вызовом SetErrorMode с флагом SEM_NOGPFAULTERRORBOX для каждого из процессов в задании. Процесс, в котором возникнет необрабатываемое им исключение, немедленно завершается без уведомления пользователя. Этот флаг полезен в сервисных и других пакетных заданиях. В его отсутствие один из процессов в задании мог бы вызвать исключение и не завершиться, впустую расходуя системные ресурсы.

Помимо базовых ограничений, вы можете устанавливать расширенные, для чего применяется структура JOBOBJECT_EXTENDED_LIMIT_ INFORMATION:

typedef struct _JOBOBJECT_EXTENDED_LIMIT_INFORMATION { JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitlnformation; IO_COUNTERS IoInfo;

SIZE_T ProcessMemoryLimit; SIZE_T JobMemoryLimit; SIZE_T PeakProcessMemoryUsed; SIZE_T PeakJobMemoryUsed;

} JOBOBJECT_EXTENDED_LIMIT_INFORMATION, *PJOBOBJECT_EXTENDED_LIMIT_INFORMATION;

Как видите, она включает структуру JOBOBJECT_BASIC_LIMIT_ INFORMATION, являясь фактически ее надстройкой. Это несколько странная структура, потому что в ней есть элементы, не имеющие никакого отношения к определению ограничений для задания. Во-первых, элемент IoInfo зарезервирован, и вы ни в коем случае не должны обращаться к нему. О том, как узнать значение счетчика ввода-вывода, я расскажу позже. Кроме того, элементы PeakProcessMemoryUsed и PeakJobMemoryUsed предназначены только для чтения и сообщают о максимальном объеме памяти, переданной соответственно одному из процессов или всем процессам в задании.

Остальные два элемента, ProcessMemoryLimit и JobMemoryLimit, ограничива-

ют соответственно объем переданной памяти, который может быть использован одним из процессов или всеми процессами в задании. Чтобы задать любое из этих ограничений, укажите в элементе LimitFlags флаг JOB_OBJECT_LIMIT_JOB_ MEMORY или JOB_OBJECT_LIMIT_PROCESS_MEMORY.

А теперь вернемся к прочим ограничениям, которые можно налагать на зада-

ния. Структура JOBOBJECT_BASIC_UI_RESTRICTIONS выглядит так:

154 Часть II. Приступаем к работе

typedef struct _JOBOBJECT_BASIC_UI_RESTRICTIONS { DWORD UIRestrictionsClass;

}JOBOBJECT_BASIC_UI_RESTRICTIONS, *PJOBOBJECT_BASIC_UI_RESTRICTIONS;

Вэтой структуре всего один элемент, UIRestrictionsClass, который содержит набор битовых флагов, кратко описанных в таблице 5-3.

Табл. 5-3. Битовые флаги базовых ограничений по пользовательскому интерфейсу для объекта-задания

Флаг

Описание

JOB_OBJECT_UILIMIT_

Запрещает выдачу команд из процессов на выход из сис-

EXITWINDOWS

темы, завершение ее работы, перезагрузку или выключе-

 

ние компьютера через функцию ExitWindowsEx

JOB_OBJECT_UILIMIT_

Запрещает процессам чтение из буфера обмена

READCLIPBOARD

 

JOB_OBJECT_UILIMIT_

Запрещает процессам стирание буфера обмена

WRITECLIPBOARD

 

JOB_OBJECT_UILIMIT_

Запрещает процессам изменение системных параметров

SYSTEMPARAMETERS

через SystemParametersInfo

JOB_OBJECT_UILIMIT_

Запрещает процессам изменение параметров экрана че-

DISPLAYSETTINGS

рез ChangeDisplaySettings

JOB_OBJECT_UILIMIT_

Предоставляет заданию отдельную глобальную таблицу

GLOBALATOMS

атомарного доступа (global atom table) и разрешает его

 

процессам пользоваться только этой таблицей

JOB_OBJECT_UILIMIT_

Запрещает процессам создание новых рабочих столов

DESKTOP

или переключение между ними через функции Create-

 

Desktop или SwitchDesktop

JOB_OBJECT_UILIMIT_

Запрещает процессам в задании использовать USER-

HANDLES

объекты (например, HWND), созданные внешними по

 

отношению к этому заданию процессами

Последний флаг, JOB_OBJECT_UILIMIT_HANDLES, представляет особый интерес: он запрещает процессам в задании обращаться к USER-объектам, созданным внешними по отношению к этому заданию процессами. Так, запустив утилиту Microsoft Spy++ из задания, вы не обнаружите никаких окон, кроме тех, которые создаст сама Spy++. На рис. 5-1 показано окно Microsoft Spy++ с двумя открытыми дочерними MDI-окнами.

Заметьте, что в левой секции (Threads 1) содержится список потоков в системе. Кажется, что лишь у одного из них, 000006АС SPYXX, есть дочерние окна. А все дело в том, что я запустил Microsoft Spy++ из задания и ограничил ему права на использование описателей USER-объектов. В том же окне сообщается о потоках MSDEV и EXPLORER, но никаких упоминаний о созданных ими окнах нет. Уверяю вас, эти потоки наверняка создали какие-нибудь окна - просто Spy++ лишена возможности их видеть. В правой секции

Глава 5. Задания.docx 155

(Windows 1) утилита Spy++ должна показывать иерархию окон на рабочем столе, но там нет ничего, кроме одного элемента — 00000000. (Это не настоящий элемент, но Spy++ была обязана поместить сюда хоть что-нибудь)

Рис. 5-1. Microsoft Spy++ работает в задании, которому ограничен доступ к описателям USER-объектов

Обратите внимание, что такие ограничения односторонни, т. е. внешние процессы все равно видят USER-объекты, которые созданы процессами, включенными в задание. Например, если запустить Notepad в задании, а Spy++ — вне его, последняя увидит окно Notepad, даже если для задания указан флаг

JOB_OBJECT_UILIMIT_HANDLES. Кроме того, Spy++, запущенная в отдельном задании, все равно увидит это окно Notepad, если только для ее задания не уста-

новлен флаг JOB_OBJECT_UILIMIT_HANDLES.

Ограничение доступа к описателям USER-объектов — вещь изумительная, если вы хотите создать по-настоящему безопасную песочницу, в которой будут «копаться» процессы вашего задания. Однако часто бывает нужно, чтобы процесс в задании взаимодействовал с внешними процессами.

Одно из самых простых решений здесь — использовать оконные сообщения, но, если процессам в задании доступ к описателям пользовательского интерфейса запрещен, ни один из них не сможет послать сообщение (синхронно или асинхронно) окну, созданному внешним процессом. К счастью, теперь есть функция, которая поможет решить эту проблему:

BOOL UserHandleGrantAccess( HANDLE hUserObj,

156 Часть II. Приступаем к работе

HANDLE hJob,

BOOL bGrant);

Параметр hUserObj идентифицирует конкретный USER-объект, доступ к I которому вы хотите предоставить или запретить процессам в задании. Это почти всегда описатель окна, но USER-объектом может быть, например, рабочий стол, программная ловушка, ярлык или меню. Последние два параметра, hjob и fGrant, указывают на задание и вид ограничения. Обратите внимание, что функция не сработает, если ее вызвать из процесса в том задании, на которое указывает hjob, - процесс не имеет права сам себе предоставлять доступ к объекту.

И последний вид ограничений, устанавливаемых для задания, относится к защите. (Введя в действие такие ограничения, вы не сможете их отменить.) Струк-

тура JOBOBJECT_SECURITY_LIMIT_INFORMATION выглядит так:

typedef struct _JOBOBJECT_SECURITY_LIMIT_INFORMATION { DWORD SecurityLimitFlags;

HANDLE JobToken; PTOKEN_GROUPS SidsToDisable;

PTOKEN_PRIVILEGES PrivilegesToDelete; PTOKEN_GROUPS RestrictedSids;

} JOBOBJECT_SECURITY_LIMIT_INFORMATION, *PJOBOBJECT_SECURITY_LIMIT_INFORMATION;

Ее элементы описаны в следующей таблице.

Табл. 5-4. Элементы структуры JOBOBJECT_SECURITY_LIMIT_INFORMATION

Элемент

Описание

SecurityLimitFlags

Набор флагов, которые закрывают доступ администратору,

 

запрещают маркер неограниченного доступа, принудительно

 

назначают заданный маркер доступа, блокируют доступ по

 

каким-либо идентификаторам защиты (security ID, SID) и от-

 

меняют указанные привилегии

JobToken

Маркер доступа, связываемый со всеми процессами в задании

SidsToDisable

Указывает, по каким SID не разрешается доступ

PrivilegesToDelete

Определяет привилегии, которые снимаются с маркера досту-

 

па

RestrictedSids

Задает набор SID, по которым запрещается доступ к любому

 

защищенному объекту (deny-only SIDs); этот набор добавля-

 

ется к маркеру доступа

Естественно, если вы налагаете ограничения, то потом вам, наверное, понадобится информация о них. Для этого вызовите:

BOOL QueryInformationJobObject(

HANDLE hJob,

JOBOBJECTINFOCLASS JobObjectInfоrmationClass,

Глава 5. Задания.docx 157

PVOID pvJobObjectInformation,

DWORD cbJobObjectIlnformationSize,

PDWORD pdwReturnSize);

В эту функцию, как и в SetInformationJobObject, передается описатель задания, переменная перечислимого типа JOBOBJECTINFOCLASS. Она сообщает информацию об ограничениях, адрес и размер структуры данных, инициализируемой функцией. Последний параметр, pdwReturnLength, заполняется самой функцией и указывает, сколько байтов помещено в буфер. Если эти сведения вас не интересуют (что обычно и бывает), передавайте в этом параметре NULL.

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

Включение процесса в задание

О'кей, с ограничениями на этом закончим. Вернемся к StartRestrictedProcess. Установив ограничения для задания, я вызываю CreateProcess и создаю процесс, который помещаю в это задание. Я использую здесь флаг CREATE_SUSPENDED, и он приводит к тому, что процесс порождается, но код пока не выполняет. Поскольку StartRestrictedProcess вызывается из процесса, внешнего по отношению к заданию, его дочерний процесс тоже не входит в это задание. Если бы я разрешил дочернему процессу немедленно начать выполнение кода, он проигнорировал бы мою песочницу со всеми ее ограничениями. Поэтому сразу после создания дочернего процесса и перед началом его работы я должен явно включить этот процесс в только что сформированное задание, вызвав:

BOOL AssignProcessToJobObject( HANDLE hJob, HANDLE hProcess);

Эта функция заставляет систему рассматривать процесс, идентифицируемый параметром hProcess, как часть существующего задания, на которое указывает hJob. Обратите внимание, что AssignProcessToJobObject позволяет включить в задание только тот процесс, который еще не относится ни к одному заданию. Как только процесс стал частью какого-нибудь задания, его нельзя переместить в другое задание или отпустить на волю. Кроме того, когда процесс, включенный в задание, порождает новый процесс, последний автоматически помещается в то же задание. Однако этот порядок можно изменить.

158 Часть II. Приступаем к работе

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

CreateProcess с новым флагом CREATE_BREAKAWAY_FROM_ JOB. (Если вы сделаете это без флага JOB_OBJECT_BREAKAWAY_OK в LimitFlags, функция CreateProcess завершится с ошибкой.) Такой механизм пригодится на случай, если новый процесс тоже управляет заданиями.

Включая в LimMags структуры JOBOBJECT_BASIC_LIMIT_INFORMATION флаг JOB_OBJECT_SILENT_BREAKAWAY_OK, вы тоже сообщаете системе,

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

Что касается StartRestrictedProcess, тo пocлe вызова AssignProcessToJobObject

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

Завершение всех процессов в задании

Уверен, именно это вы и будете делать чаще всего. В начале главы я упомянул о том, как непросто остановить сборку в Developer Studio, потому что для этого ему должны быть известны все процессы, которые успел создать его самый первый процесс. (Это очень каверзная задача. Как Developer Studio справляется с ней, я объяснял в своей колонке «Вопросы и ответы по Win32» в июньском выпуске Microsoft Systems Journal за 1998 год, см. http://www.microsoft.com/msj/0698/ win320698.aspx.) Подозреваю, что следующие версии Developer Studio будут использовать механизм заданий, и решать задачу, о которой мы с вами говорили, станет гораздо легче.

Чтобы уничтожить все процессы в задании, вы просто вызываете:

BOOL TerminateJobObject(

HANDLE hJob,

UINT uExitCode);

Вызов этой функции похож на вызов TerminateProcess для каждого процесса в задании и присвоение всем кодам завершения одного значения — uExitCode.

Получение статистической информации о задании

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

Глава 5. Задания.docx 159

Например, чтобы выяснить базовые учетные сведения, вызовите ее, передав JobObjectBasicAccountingInformation во втором параметре и адрес структуры JOBOBJECT_BASIC_ACCOUNTING_INFORMATION:

typedef struct _JOBOBJECT_BASIC_ACCOUNTING_INFORMATION { LARGE_INTEGER TotalUserTime;

LARGE_INTEGER TotalKernelTime; LARGE_INTEGER ThisPeriodTotalUserTime; LARGE_INTEGER ThisPeriodTotalKernelTime; DWORD TotalPageFaultCount;

DWORD TotalProcesses;

DWORD ActiveProcesses;

DWORD TotalTerminatedProcesses;

} JOBOBJECT_BASIC_ACCOUNTING_INFORMATION, *PJOBOBJECT_BASIC_ACCOUNTING_INFORMATION;

Элементы этой структуры кратко описаны в таблице 5-5.

Табл. 5-5. Элементы структуры JOBOBJECT_BASIC_ACCOUNTING_ INFORMATION

Элемент

 

 

Описание

 

TotalUserTime

Процессорное время, израсходованное процессами задания в поль-

 

зовательском режиме

 

 

TotalKernelTime

Процессорное время, израсходованное процессами задания в ре-

 

жиме ядра

 

 

ThisPeriodTotalUserTime

То же, что TotalUserTime, но обнуляется, когда базовые ограниче-

 

ния

изменяются

вызовом

SetInformationJobObject,

 

JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME не используется

ThisPeriodTotalKernelTime

То же, что ThisPeriodTotalUserTime, но относится к процессорному

 

времени, израсходованному в режиме ядра

 

TotalPageFaultCount

Общее количество ошибок страниц, вызванных процессами зада-

 

ния

 

 

 

TotalProcesses

Общее число процессов, когда-либо выполнявшихся в этом зада-

 

нии

 

 

 

ActiveProcesses

Текущее количество процессов в задании

 

TotalTerminatedProcesses

Количество процессов, завершенных из-за превышения ими отве-

 

денного лимита процессорного времени

 

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

Вы можете извлечь те же сведения вместе с учетной информацией по вводу-

выводу, передав JobObjectBasicAndloAccountingInformation во втором пара-

Соседние файлы в предмете Программирование на C++