Определение ограничений, налагаемых на процессы в задании
Создав задание, Вы обычно строите "песочницу" (набор ограничений) для включаемых в него процессов. Ограничения бывают нескольких видов:
Ограничения на задание вводятся вызовом:
BOOL SetInformationJobObject( HANDLE hJob, JOBOBJECTINFOCLASS JobObjectInformationClass, PVOID pJobObjectTnformation, DWORD cbJobObjectInformationLength);
Первый параметр определяет нужное Вам задание, второй параметр (перечислимого типа) — вид ограничений, третий — адрес структуры данных, содержащей подробную информацию о задаваемых ограничениях, а четвертый — размер этой структуры (используется для указания версии). Следующая таблица показывает, как устанавливаются ограничения.
Вид ограничений | Значение второго параметра | Структура, указываемая в третьем параметре | |||
Базовые ограничения | JobObjectBasicLimitInformation | JOBOBJECT_BASIC_ LIMIT_INFORMATION | |||
Расширенные базовые ограничения | JobObjectExtendedLimitInformation | JOBOBJECT_EXTENDED_ LIMIT_INFORMATION | |||
Базовые ограничения по ользовательскому интерфейсу | JobObjectBasicUIRestrictions | JOBOBJECT_BASIC UI_RESTRICTIONS | |||
Ограничения, связанные с защитой | JobObjectSecurityLimitInformation | JOBOBJECT_SECURITY_ LIMIT_INFORMATION |
В функции StartRestrictedProcess я устанавливаю для задания лишь несколько базовых ограничений. Для этого я создаю структуру JOB_OBJECT_BASIC_LIMIT_INFORMATION, инициализирую ее и вызываю функцию SetInformationJobObject. Данная структура выглядит так:
typedef struct _JOBOBJECT_BASIC_LIMIT_INFORMATION
{
LARGE_INTEGER PerProcessUserTimeLimit;
LARGE_INTEGER PorJobUserTimeLimit;
DWORD LimitFlags;
DWORD MinimumWorkingSetSize;
DWORD MaximumWorkingSetSize;
DWORD ActiveProcessLimit;
DWORD^PTR Affinity;
DWORD PriorityClass;
DWORD SchedulingClass;
} JOBOBJECT_BASIC_LIMIT_INFORMATION, *PJOBOBJECT_BASIC_LIMIT_INFORMATION;
Все элементы этой структуры кратко описаны в таблице 5-1.
Элементы | Описание | Примечание |
PerProcessUserTtmeLimit | Максимальное время в пользова тельском режиме, выделяемое каждому процессу (в порциях по 100 нс) | Система автоматически завершает любой процесс, который пытается использовать больше обведенного времени. Это ограничение вводится флагом JOB OBJECT LIMIT PROCESS_TIME в LimitFlags |
PerJobUserTimeLimit | Максимальное время в пользова тельском режиме для всех процессов в данном задании (в порциях по 100 нс) | По умолчанию система автомати чески завершает все процессы, когда заканчивается это время Данное значение можно изменять в процес ее выполнения задания. Это ограничение вводится флагом JOB_OBJFCT_LIMIT_JOB_TIME в LimitFlags |
LimitFlags | Виды ограничений для задания | См раздел после таблицы. |
MinimumWorkingSetSize и MaximumWorkingSetSize | Верхний и нижний предел рабочего набора для каждого процесса (а не для всех процессов в задании) |
Обычно рабочий набор процесса может расширяться за стандартный предел; указав MaximumWorkingSetSize, Вы введете жесткое ограничение. Когда размер рабочего набора какого-либо процесса достигнет заданного предела, процесс начнет сбрасывать свои страницы на диск. Вызовы функции SetProcessWorkingSetSize этим процессом будут игнорироваться, если только он не обра щается к ней для того, чтобы очистить свой рабочий набор. Это ограничение вводится флагом JOB_OBJECT_LIMIT_WORKINGSET в LimitFlags. |
ActiveProcessLimit | Максимальное количество процессов, одновременно выполняемых в задании | Любая попьпка обойти такое ограничение приведет к завершению нового процесса с ошибкой "not enough quota" ("превышение квоты") Это ограничение вводится флагом JOB_OBJECT_LIMIT_ACTIVE_ PROCESS в LimitFlags. |
Affinity | Подмножество процессоров, на которых можно выполнять процессы этого задания | Для индивидуальных процессов это ограничение можно еще больше детализировать. Вводится флагом JOB_OBJECT_LIMIT AFFINITY в LimitFlags. |
PriorityClass | Класс приоритета для всех процессов в задании | Вызванная процессом функция SetPriorityClass сообщает об успехе даже в том случае, если на самом деле она не выполнила свою задачу, a GetPriorityClass возвращает класс приоритета, каковой и пытался уста новить процесс, хотя в реальности его класс может быть совсем другим. Кроме того, SetThreadPriority не может поднять приоритет потоков выше normal, но позволяет понижать его. Это ограничение вводится флагом JOB_OBJECT_LIMIT_ PRIORITY_CLASS в LimitFlags. |
SchedulingClass | Относительная продолжительность кванта времени, выделяемого всем потокам в задании | Этот элемент может принимать значения от 0 до 9; по умолчанию устанавливается 5. Подробнее о его назначении см. ниже. Это ограниче ние вводится флагом JOB_OBJECT_ LIMIT_SCHEDULING_CLASS в LimitFlags. |
Таблица 5-1. Элементы структуры JOBOBJECT_BASIC_LIMIT_INFORMATION
Хочу пояснить некоторые вещи, связанные с этой структурой, которые, по-моему довольно туманно изложены в документации Platform SDK. Указывая ограничения для задания, Вы устанавливаете те или иные биты в элементе LimitFlags. Например, в StartRestrictedProcess я использовал флаги JOB_OBJECT_LIMIT_PRIORITY_CLASS и JOB_ OBJECT_LIMIT_JOB_TIME, т. e. определил всего два ограничения.
При выполнении задание ведет учет по нескольким показателям — например, сколько процессорного времени уже использовали его процессы. Всякий раз, когда Вы устанавливаете базовые ограничения с помощью флага JOB_OBJECT_LIMIT_JOB_TIME, из общего процессорного времени, израсходованного всеми процессами, вычитается то, которое использовали завершившиеся процессы. Этот показатель сообщает, сколько процессорного времени израсходовали активные на данный момент процессы, А что если Вам понадобится изменить ограничения на доступ к подмножеству процессоров, не сбрасывая при этом учетную информацию по процессорному времени? Для этого Вы должны ввести новое базовое ограничение флагом JOB_OBJECT_LIMIT_AFFINITY и отказаться от флага JOB_OBJECT_LIMIT_JOB_TIME. Но тогда получится, что Вы снимаете ограничения на процессорное время.
Вы хотели другого: ограничить доступ к подмножеству процессоров, сохранив существующее ограничение на процессорное время, и не вычитать время, израсходованное завершенными процессами, из общего времени. Чтобы решить эту проблему, используйте специальный флаг JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME. Этот флaг и JOB_OBJECT_LIMIT_JOB_TIME являются взаимоисключающими. Флаг JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME указывает системе изменить ограничения, не вычитая процессорное время, использованное уже завершенными процессами.
Обсудим также элемент SchedulingCtoss структуры JOB_OBJECT_BASIC_LIMIT_INFORMATION. Представьте, что для двух заданий определен класс приоритета NORMAL_PRIORITY_CLASS, а Вы хотите, чтобы процессы одного задания получали больше процессорного времени, чем процессы другого.
Так вот, элемент SchedulingClass позволяет изменять распределение процессорного времени между заданиями с одинаковым классом приоритета. Вы можете присвоить ему любое значение в пределах 0-9 (по умолчанию он равен 5). Увеличивая сго значение, Вы заставляете Windows 2000 выделять потокам в процессах конкретного задания более длительный квант времени, а снижая — напротив, уменьшаете этот квант.
Допустим, у меня есть два задания с обычным (normal) классом приоритета: в каждом задании — по одному процессу, а в каждом процессе — по одному потоку (тоже с обычным приоритетом). В нормальной ситуации эти два потока обрабатывались бы процессором по принципу каруссли и получали бы равные кванты процессорного времени. Но если я запишу в элемент SchedulingClass для первого задания значение 3, система будет выделять его потокам более короткий квант процессорного времени, чем потокам второго задания.
Используя SchedulingClass, избегайте слишком больших его значений, иначе Вы замедлите общую реакцию других заданий, процессов и потоков на какие-либо со бытия в системе. Кроме того, учтите, что все сказанное здесь относится только к Windows 2000. В будущих версиях Windows планировщик потоков предполагается существенно изменить, чтобы операционная система могла более гибко планировать потоки в заданиях и процессах.
И последнее ограничение, которое заслуживает отдельного упоминания, связано с флагом JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION. Он отключает для всех процессов в задании вывод диалогового окна с сообщением о необработанном исключении. Система реагирует на этот флаг вызовом SetErrorMode с флагом SEM_NOGPFAULTERRORBOX для каждого из процессов в задании. Процесс, в котором возникнет необрабатываемое им исключение, немедленно завершается без уведомления пользователя. Этот флаг полезен в сервисных и других пакетных заданиях. В его отсутствие один из процессов в задании мог бы вызвать исключение и не завершиться, впустую расходуя системные ресурсы.
Помимо базовых ограничений, Вы можете устанавливать расширенные, для чего применяется структура JOBOBJECT_EXTENDED_LIMIT_INFORMATION:
typedef struct _JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
IO_COUNTERS Iolnfo;
SIZE_T Proces&MemoryLimit;
SIZE_T JobMemoryLimit;
SIZE_T PeakProcessMemoryUsed;
SIZE_T PeakJobMemoryUsed;
} JOBOBJECT_EXTENDED_LIMIT_INFORHATION, *PJOBOBJECT_EXTENDED LIMIT_INFORMATION;
Как видите, она включает структуру JOBOBJECT_BASIC_LIMIT_INFORMATION, являясь фактически ее надстройкой. Это несколько странная структура, потому что в ней есть элементы, не имеющие никакого отношения к определению ограничений для задания. Во-первых, элемент IoInfo зарезервирован, и Вы ни в коем случае не должны обращаться к нему. О том, как узнать значение счетчика ввода-вывода, я расскажу позже. Кроме того, элементы PeakProcessMemoryUsed и PeakJobMemoryUsed предназначены только для чтения и сообщают о максимальном объеме памяти, переданной соответственно одному из процессов или всем процессам в задании.
Остальные два элемента, ProcessMemoryLimit и JobMemoryLimit, ограничивают со ответственно объем переданной памяти, который может быть использован одним из процессов или всеми процессами в задании. Чтобы задать любое из этих ограничений, укажите в элементе LimitFlags флаг JOB_OBJECT_LIMIT_JOB_MEMORY или JOB_OB JECT_LIMIT_PROCESS_MEMORY.
А теперь вернемся к прочим ограничениям, которые можно налагать на задания. Структура JOBOBJECT_BASIC_UI_RESTRICTIONS выглядит так:
typedef struct _JOBOBJECT_BASIC_UI_RESTRICTIONS
{
DWORD UIRestrictionsClass;
} JOBOBJECT_BASIC_UI_RESTRICTIONS, *PJOBOBJECT_BASIC_UI_RESTRICTIONS;
В этой структуре всего один элемент, UIRestrictionsClass, который содержит набор битовых флагов, кратко описанных н таблице 5-2.
Флаг | Описание |
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_UILIM1T_DESKTOP |
Запрещает процессам создание новых рабочих столов или переключение между ними через функции CreateDesktop или SwitchDesktop |
JOB_OBJECT_UILIMIT HANDLES | Запрещает процессам в задании использо вать USER-объекты (например, HWND), созданные внешними по отношению к этому заданию процессами |
Таблица 5-2. Битовые флаги базовых ограничений по пользовательскому интерфейсу дпя объекта-задания
Последний флaг, JOB_OBJECT_UILIMIT_HANDLES, представляет особый интерес: он запрещает процессам в задании обращаться к USER-объектам, созданным внешними по отношению к этому заданию процессами. Так, запустив утилиту Microsoft Spy++ из задания, Вы не обнаружите никаких окон, кроме тех, которые создаст сама Spy++. Ha рис. 5-2 показано окно Microsoft Spy++ с двумя открытыми дочерними MDI-окнами. Заметьте, что в левой секции (Threads 1) содержится список потоков в системе. Кажется, что лишь у одного из них, 000006АС SPYXX, есть дочерние окна. А все дело в том, что я запустил Microsoft Spy++ из задания и ограничил ему права па использование описателей USER-объектов. В том же окне сообщается о потоках MSDEV и EXPLORER, но никаких упоминаний о созданных ими окнах нет. Уверяю Вас, эти потоки наверняка создали какие-нибудь окна — просто Spy++ лишена возможности их видеть. В правой секции (Windows 3) утилита Spy++ должна показывать иерархию окон на рабочем столе, но там нет ничего, кроме одного элемента — 00000000. (Это не настоящий элемент, но Spy++ была обязана поместить сюда хоть что-нибудь.)
Обратите внимание, что такие oграничения односторонни, т e. внешние процессы все равно видят USER-объекты, которые созданы процессами, включенными в задание. Например, если запустить Notepad в задании, a Spy++ — вне его, последняя увидит окно Notepad, даже если для задания указан флаг JOB_OBJECT_UILIMIT_HANDLES. Кроме того, Spy++, запущенная в отдельном задании, все равно увидинт окно Notepad, если только для ее задания не установлен флаг JOB_OBJECT_UILIMIT_HANDLES.
Рис. 5-2. Microsoft Spy++ работает в задании, которому ограничен доступ к описателям USER-объектов
Ограничение доступа к описателям USER-объектов — вещь изумительная, если Вы хотите создать по-настоящему безопасную песочницу, в которой будут "копаться" процессы Вашего задания. Однако часто бывает нужно, чтобы процесс в задании взаимодействовал с внешними процессами.
Одно из самых простых решений здесь — использовать оконные сообщения, но, если процессам в задании доступ к описателям пользовательского интерфейса запрещен, ни один из них не сможет послать сообщение (синхронно или асинхронно) окну, созданному внешним процессом. К счастью, теперь есть функция, которая поможет решить эту проблему:
BOOL UserHandleGrantAccess( HANOIF hUserObj, HANDLE hjob, BOOL fGrant);
Параметр hUserObj идентифицирует конкретный USER-объект, доступ к которому Вы хотите предоставить или запретить процессам в задании. Это почти всегда описатель окна, но 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;
Ее элементы описаны в следующей таблице:
Элемент | Описание |
SecurityLimitFlags | Набор флагов, которые закрывают доступ администратору, запрещают маркер неограниченного доступа, принудительно назначают заданный маркер доступа, блокируют доступ по каким-либо идентификаторам защиты (security ID, SID) и отменяют указанные привилегии |
JobToken | Маркер доступа, связываемый со всеми процессами в задании |
SidsToDisable | Указывает, по каким SID не разрешается доступ |
PrivilegesToDelete | Определяет привилегии, которые снимаются с маркера доступа |
RestrictedSids | Задает набор SID, по которым запрещается доступ к любому защищенному объекту (deny-only SIDs); этот набор добавляется к маркеру доступа |
Естественно, если Вы налагаете ограничения, то потом Вам, наверное, понадобится информация о них. Для этого вызовите:
BOOL QueryInformationJobObject( HANDLE hJob, JOBOBJECTINFOCLASS JobObjectInformationClass. PVOID pvJobObjectInformation, DWORD cbJobObjectInformationLength, PDWORD pdwReturnLength);
В эту функцию, как и в SetInformationJobObject, передается описатель задания, переменная перечислимого типа JOBOJECTINFOCLASS. Она сообщает информацию об ограничениях, адрес и размер структуры данных, инициализируемой функцией. Последний параметр, pdwReturnLength, заполняется самой функцией и указывает, сколько байтов помещено в буфер. Если эти сведения Вас не интересуют (что обычно и бывает), передавайте в этом параметре NULL.
NOTE:
Процесс может получить информацию о своем задании, передав при вызове QuerylnformationJobObject вместо описателя задания значение NULL. Это позволит ему выяснить установленные для него ограничения. Однако аналогичный вызов SetInformationJobOtject даст ошибку, так как процесс не имеет права самостоятельно изменять заданные для него ограничения