Windows для профессионалов

       

Задания


Глава 5 - Задания

Гpynny процессов зачастую нужно рассматривать как единую сущность. Например, когда Вы командуете Microsoft Developer Studio собрать проект, он порождает процесс Ct.exe, а тот в свою очередь может создать другие процессы (скажем, для дополнительных проходов компилятора). Но, если Вы пожелаете прервать сборку, Developer Studio должен каким-то образом завершить C1.exe и все его дочерние процессы. Решение этой простой (и распространенной) проблемы в Windows было весьма затруднительно, поскольку она не отслеживает родственные связи между процессами. В частности, выполнение дочерних процессов продолжается даже после завершения родительского.

При разработке сервера тоже бывает полезно группировать процессы. Допустим, клиентская программа просит сервер выполнить приложение (которое создает ряд дочерних процессов) и сообщить результаты. Поскольку к серверу может обратиться сразу несколько клиентов, было бы неплохо, если бы он умел как-то ограничивать ресурсы, выделяемые каждому клиенту, и тем самым не давал бы одному клиенту монопольно использовать все серверные ресурсы. Под ограничения могли бы подпадать такие ресурсы, как процессорное время, выделяемое на обработку клиентского запроса, и размеры рабочего набора (working set). Кроме того, у клиентской программы не должно быть возможности завершить работу сервера и т.д.

В Wmdows 2000 введен новый объект ядра — задание job). Он позволяет группировать процессы и помещать их в нечто вроде песочницы, которая определенным образом ограничивает их действия. Относитесь к этому объекту как к контейнеру процессов. Кстати, очень полезно создавать задание и с одним процессом — это позволяет налагать на процесс ограничения, которые иначе указать нельзя.

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

WININDOWS 98
Windows 98 не поддерживает задания.

void StartRestictedProcess() {
// создаем объект ядра "задание" HANDLE hjob = CreateJobObject(NULL, NULL);


// вводим oграничения для процессов в задании
// сначала определяем некоторые базовые ограничения
JOBOBJECT_BASIC_LIMIT_INFORMATION jobli = { 0 };
// процесс всегда выполняется с классом приоритета idle
jobli.PriontyClass = IDLE_PRIORITY_CLASS;
// задание не может использовать более одной секунды процессорного времени
jobli.PerJobUserTimeLimit.QuadPart = 10000000;
// 1 секунда, выраженная в 100-наносекундных интервалах
// два ограничения, которые я налагаю на задание (процесс)
jobli.LimitFlags = JOB_OBJECT_LIMIT_PRIORITY_CLASS | JOB_OBJECT_LIMIT_JOB_TIME;
SetInforrnationJobObject(hjob, JobOb]ectBasicLimitInformation, &jobli, sizeof(jobli));


// теперь вводим некоторые ограничения по пользовательскому интерфейсу
JOBOBJECT_BASIC_UI_RESTRICTIONS jobuir;
jobuir.UTRestrictionsClass = JOB_OBJECT_UILIMIT_NONE;
// "замысловатый" нуль
// процесс не имеет права останавливать систему
jobuir.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_EXITWINDOWS;
// процесс не имеет права обращаться к USER-объектам в системе (например, к другим окнам)
jobuir.UIRestrictionsClass |= JOB_OBJECT_UILIMIT_HANDLES;
SetInrormationJobObject(hjob, JobObjectBasicUIRestrictions, &jobuir, sizeof(jobuir));
// Порождаем процесс, который будет размещен в задании.
// ПРИМЕЧАНИЕ: процесс нужно сначала создать и только потом поместить
// в задание А это значит, что поток процесса должен быть создан
// и тут же приостановлен, чтобы он не смог выполнить какой-нибудь код
// еще до введения ограничений,
STARTUPTNFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
CreatePiocess(NULL, "CMD", NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
// Включаем процесс в задание
// ПРИМЕЧАНИЕ, дочерние процессы, порождаемые этим процессом,
// автоматически становятся частью того же задания.
AssignProcessToJobObject(hjob, pi hProcess);
// теперь потоки дочерних процессов могут выполнять код
ResumeThread(pi.hThread);
CloseHandle(pi.hThread);
// ждем, когда процесс завершится или будет исчерпан


// лимит процессорного времени, указанный для задания
HANDLE h[2];
h[0] = pi.hProcess;
h[1] = hjob;
DWORD dw = WaitForMultipleObjects(2, h, FALSE, INFINITE);
switch (dw - WAIT_OBJECT_0){
case 0: // процесс завершился.,
break;
case 1:
// лимит процессорного времени исчерпан
break;
}
// проводим очистку
CloseHandle(pi hProcess), CloseHandle(hjob);
}
Рис. 5-1. Функция StartRestrictedProcess
А теперь я объясню, как работает StartRestrictedProcess. Сначала я создаю новый объект ядра "задание", вызывая:
HANDLE CreateJobObject( PSECURITY_ATTRIBUTES psa, PCTSTR pszName);
Как и любая функция, создающая объекты ядра, CreateJobObject принимает в первом параметре информацию о защите и сообщает системе, должна ли она вернуть наследуемый описатель. Параметр pszName позволяет присвоить заданию имя, что бы к нему могли обращаться другие процессы через функцию OpenJobObject.
HANDLE OpenJobObject( DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName);
Закончив работу с объектом-заданием, закройте сго описатель, вызвав, как всегда, CloseHandle. Именно так я и делаю в конце своей функции StartRestrictedProcess. Имейте в виду, что закрытие объекта-задания не приводит к автоматическому завершению всех его процессов. На самом деле этот объект просто помечается как подлежащий разрушению, и система уничтожает его только после завершения всех включенных в него процессов.
Заметьте, что после закрытия описателя объект-задание становится недоступным для процессов, даже несмотря на то, что объект все еще существует. Этот факт иллюстрирует следующий код:
// создаем именованный объект-задание
HANDlF hjob = CreateJobObject(NULL, TEXT("Jeff"));
// включаем в него наш процесс
AssignProcessToJobObject(hjob, GetCurrentProcess());
// закрытие обьекта-задания не убивает ни наш процесс, ни само задание,
// но присвоенное ему имя ('Jeff') моментально удаляется
CloseHandle(hjob);
// пробуем открыть существующее задание
hjob = OpenJobObject(JOB_OBJECT_ALL_ACCESS, FALSE, TEXT("Jeff"));
// OpenJobOb]ect терпит неудачу и возвращает NULL, поскольку имя ('Jeff")
// уже не указывает на объект-задание после вызова CloseHandle; // получить описатель этого объекта больше нельзя

Содержание раздела