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

       

Уведомления заданий


Итак, базовые сведения об объектах-заданиях я изложил. Единственное, что осталось рассмотреть, — уведомления. Допустим, Вам нужно знать, когда завершаются все процессы в задании или заканчивается все отпущенное им процессорное время. Либо выяснить, когда в задании порождается или уничтожается очередной процесс. Если такие уведомления Вас не интересуют (а во многих приложениях они и не нужны), работать с заданиями будет очень легко — не сложнее, чем я уже рассказывал. Но если они все же понадобятся, Вам придется копнуть чуть поглубже.

Информацию о том, все ли выделенное процессорное время исчерпано, получить нетрудно. Объекты-задания не переходят в свободное состояние до тех пор, пока их процессы нс израсходуют отведенное процессорное время. Как только оно заканчивается, система уничтожает всс процессы в задании и переводит его объект в свободное состояние (signaled scate). Это событие легко перехватить с помощью WaitForSingleObject (или похожей функции). Кстати, потом Вы можете вернуть объект-задание в состояние "занято" (nonsignaled state), вызвав SetInformationJobObject и выделив ему дополншельное процессорное время.

Когда я только начинал разбираться с заданиями, мне казалось, что объект-задание должен переходить в свободное состояние после завершения всех его процессов. В конце концов, прекращая свою работу, объекты процессов и потоков освобождаются, то же самое вроде бы должно происходить и с заданиями. Нo Microsoft предпочла сделать по-другому объект-задание переходит в свободное состояние после того, как исчерпает выделенное ему время. Поскольку большинство заданий начинает свою работу с одним процессом, который существует, пока не завершатся все eго дочерние процессы, Вам нужно просто следить за описателем родительского процесса — он освободится, как только завершится все задание. Моя функция StartRestrictedProcess как раз и демонстрирует данный прием.

Но это были лишь простейшие уведомления — более "продвинутые", например о создании или разрушении процесса, получать гораздо сложнее.
В частности, Вам придется создать объект ядра "порт завершения ввода-вывода" и связать с ним объект или объекты "задание". После этого нужно будет перевести один или больше потоков в режим ожидания порта завершения.

Создав порт завершения ввода-вывода. Вы сопоставляете с ним задание, вызывая SetInformationJobObject следующим образом:

JOBOBJECT_ASSOCIATE_COMPLETION_PORT joacp;

joacp.CompletionKey = 1;
// любое значение, уникально идентифицирующее это задание

joacp.CompletionPort = hIOCP;
// описатель порта завершения, принимающего уведомления

SetInformationJobObject(hJob, JobObjectAssociateCompletionPortInforrration, &jоаср, sizeof(joacp))

После выполнения этого кода система начнет отслеживать задание и при возникновении событий передавать их порту завершения. (Кстати, Вы можете вызывать QueryInformationJobQbjectw получать ключ завершения и описатель порта, но вряд ли это Вам когда-нибудь понадобится.) Потоки следят за портом завершения ввода-вывода, вызывая GetQueuedCompletionStatus.

BOOL GetQueuedCompletionStatus( HANDLE hIOCP, PDWORD pNumBytesTransferred, PULONG_PTR pCorripletionKey, POVERLAPPED *pOverlapped, DWORD dwMilliseconds);



Когда эта функция возвращает уведомление о событии задания, *pCompletionKey содержит значение ключа завершения, заданное при вызове SetInformationJobObject для связывания задания с портом завершения. По нему Вы узнаете, в каком из заданий возникло событие. Значение в *pNumBytesTransferred указывет какое именно событие произошло (таблица 5-4). В зависимости от конкретного события в *pOverlapped может возвращаться идентификатор процесса.

Событие Описание
JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO В задании нет работающих процессов
JOB_OBJECT_MSG_END_OF_PROCESS_TIME Процессорное время, выделенное процессу, исчерпано, процесс завершается, и сообщается его идентификатор
JOB_OBJECT_ MSG_ACTIVE_ROCESS_LIMIT Была попытка превысить ограничение на число активных процессов в задании
JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT Была попытки превысить ограничение на объем памяти, которая может быть передана процессу, сообщается идентификатор процесса
JOB_OBJECT_MSG_JOB_ MEMORY_LIMIT Была попытка превысить ограничение на объем памяти, которая может быть передана заданию; сообщается идентификатор процесса
JOB_OBJECT_MSG_NEW_ PROCESS В задание добавлен процесс; сообщается идентификатор процесса
JOB_OBJECT_MSG_EXIT_ PROCESS Процесс завершен, сообщается идентификатор процесса
JOB_OBJECT_MSG_ABNOKMAL._EXIT_PROCESS Процесс завершен из за необработанного им исключения; сообщается идентификатор процесса
JOB_OBJECT_MSG_END_ OFJOR_TIME Процессорное время, выделенное заданию, исчерпано, процессы не завершаются, и Вы можете либо возобновить их работу, задав новый лимит по времени, либо самостоятельно завершить процессы, вызвав TerminateJobObject
<


Таблица 5-4. Уведомления о событиях задания, посылаемые системой связанному с этим заданием порту завершения

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

// создаем структуру JOBOBJECT_END_OF_JOB_TIME_JNFORMATION
// и инициализируем ее единственный элемент

JOBOBJECT_END_OF_JOB_TIME_INFORMATION joeojti;
joeojti.EndOfJobTimeAction = J0B_OBJECT_POST_AT_END_OF_JOB;

// сообщаем заданию, что ену нужно делать по истечении его времени
SetInformationJobObject(hJob, JobObjectEndOfJobTimeInformation, &joeojti, sizeof(joeojti));

Вы можете указать и другое значение, JOB__OBJECT_TERMINATE_AT_END_OF_JOB, но оно задается по умолчанию, еще при создании задания.


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