
- •WINDOWS
- •Джеффри Рихтер
- •ЧАCTЬ I МАТЕРИАЛЫ ДЛЯ ОБЯЗАТЕЛЬНОГО ЧТЕНИЯ
- •ГЛАВА 1. Обработка ошибок
- •Вы тоже можете это сделать
- •Программа-пример ErrorShow
- •ГЛАВА 2 Unicode
- •Наборы символов
- •Одно- и двухбайтовые наборы символов
- •Unicode: набор широких символов
- •Почему Unicode?
- •Windows 2000 и Unicode
- •Windows 98 и Unicode
- •Windows CE и Unicode
- •В чью пользу счет?
- •Unicode и СОМ
- •Как писать программу с использованием Unicode
- •Unicode и библиотека С
- •Типы данных, определенные в Windows для Unicode
- •Unicode- и ANSI-функции в Windows
- •Строковые функции Windows
- •Ресурсы
- •Текстовые файлы
- •Перекодировка строк из Unicode в ANSI и обратно
- •ГЛАВА 3 Объекты ядра
- •Что такое объект ядра
- •Учет пользователей объектов ядра
- •Защита
- •Таблица описателей объектов ядра
- •Создание объекта ядра
- •Закрытие объекта ядра
- •Совместное использование объектов ядра несколькими процессами
- •Наследование описателя объекта
- •Изменение флагов описателя
- •Именованные объекты
- •Пространства имен Terminal Server
- •Дублирование описателей объектов
- •ЧАСТЬ II НАЧИНАЕМ РАБОТАТЬ
- •ГЛАВА 4 Процессы
- •Ваше первое Windows-приложение
- •Описатель экземпляра процесса
- •Описатель предыдущего экземпляра процесса
- •Командная строка процесса
- •Переменные окружения
- •Привязка к процессорам
- •Режим обработки ошибок
- •Текущие диск и каталог для процесса
- •Текущие каталоги для процесса
- •Определение версии системы
- •Функция CreateProcess
- •Параметры pszApplicationName и pszCommandLine
- •Параметры psaProcess, psaThread и blnheritHandles
- •Параметр fdwCreate
- •Параметр pvEnvironment
- •Параметр pszCurDir
- •Параметр psiStartlnfo
- •Параметр ppiProclnfo
- •Завершение процесса
- •Возврат управления входной функцией первичного потока
- •Функция ExitProcess
- •Функция TerminateProcess
- •Когда все потоки процесса уходят
- •Что происходит при завершении процесса
- •Дочерние процессы
- •Запуск обособленных дочерних процессов
- •Перечисление процессов, выполняемых в системе
- •Программа-пример Processlnfo
- •ГЛАВА 5 Задания
- •Определение ограничений, налагаемых на процессы в задании
- •Включение процесса в задание
- •Завершение всех процессов в задании
- •Получение статистической информации о задании
- •Уведомления заданий
- •Программа-пример JobLab
- •ГЛАВА 6 Базовые сведения о потоках
- •В каких случаях потоки создаются
- •И в каких случаях потоки не создаются
- •Ваша первая функция потока
- •Функция CreateThread
- •Параметр psa
- •Параметр cbStack
- •Параметры pfnStartAddr и pvParam
- •Параметр fdwCreate
- •Параметр pdwThreadlD
- •Завершение потока
- •Возврат управления функцией потока
- •Функция ExitThread
- •Функция TerminateThread
- •Если завершается процесс
- •Что происходит при завершении потока
- •Кое-что о внутреннем устройстве потока
- •Некоторые соображения по библиотеке С/С++
- •Ой, вместо _beginthreadex я по ошибке вызвал CreateThread
- •Библиотечные функции, которые лучше не вызывать
- •Как узнать о себе
- •Преобразование псевдоописателя в настоящий описатель
- •ГЛАВА 7 Планирование потоков, приоритет и привязка к процессорам
- •Приостановка и возобновление потоков
- •Приостановка и возобновление процессов
- •Функция Sleep
- •Переключение потоков
- •Определение периодов выполнения потока
- •Структура CONTEXT
- •Приоритеты потоков
- •Абстрагирование приоритетов
- •Программирование приоритетов
- •Динамическое изменение уровня приоритета потока
- •Подстройка планировщика для активного процесса
- •Программа-пример Scheduling Lab
- •Привязка потоков к процессорам
- •ГЛАВА 8 Синхронизация потоков в пользовательском режиме
- •Кэш-линии
- •Более сложные методы синхронизации потоков
- •Худшее, что можно сделать
- •Критические секции
- •Критические секции: важное дополнение
- •Критические секции и спин-блокировка
- •Критические секции и обработка ошибок
- •Несколько полезных приемов
- •Не занимайте критические секции надолго
- •ГЛАВА 9 Синхронизация потоков с использованием объектов ядра
- •Wait-функции
- •Побочные эффекты успешного ожидания
- •События
- •Программа-пример Handshake
- •Ожидаемые таймеры
- •Ожидаемые таймеры и АРС-очередь
- •И еще кое-что о таймерах
- •Семафоры
- •Мьютексы
- •Отказ от объекта-мьютекса
- •Мьютексы и критические секции
- •Программа-пример Queue
- •Сводная таблица объектов, используемых для синхронизации потоков
- •Другие функции, применяемые в синхронизации потоков
- •Асинхронный ввод-вывод на устройствах
- •Функция WaitForlnputldle
- •Функция MsgWaitForMultipleObjects(Ex)
- •Функция WaitForDebugEvent
- •Функция SignalObjectAndWait
- •ГЛАВА 10 Полезные средства для синхронизации потоков
- •Реализация критической секции: объект-оптекс
- •Программа-пример Optex
- •Создание инверсных семафоров и типов данных, безопасных в многопоточной среде
- •Программа-пример lnterlockedType
- •Синхронизация в сценарии "один писатель/группа читателей"
- •Программа-пример SWMRG
- •Реализация функции WaitForMultipleExpressions
- •Программа-пример WaitForMultExp
- •ГЛАВА 11 Пулы потоков
- •Сценарий 1: асинхронный вызов функций
- •Сценарий 2: вызов функций через определенные интервалы времени
- •Программа-пример TimedMsgBox
- •Сценарий 3: вызов функций при освобождении отдельных объектов ядра
- •Сценарий 4; вызов функций по завершении запросов на асинхронный ввод-вывод
- •ГЛАВА 12 Волокна
- •Работа с волокнами
- •Программа-пример Counter
- •ЧАСТЬ III УПРАВЛЕНИЕ ПАМЯТЬЮ
- •Виртуальное адресное пространство процесса
- •Как адресное пространство разбивается на разделы
- •Увеличение раздела для кода и данных пользовательского режима до 3 Гб на процессорах x86 (только Windows 2000)
- •Закрытый раздел размером 64 Кб (только Windows 2000)
- •Раздел для общих MMF (только Windows 98)
- •Регионы в адресном пространстве
- •Передача региону физической памяти
- •Физическая память и страничный файл
- •Физическая память в страничном файле не хранится
- •Атрибуты защиты
- •Защита типа «копирование при записи»
- •Специальные флаги атрибутов защиты
- •Подводя итоги
- •Блоки внутри регионов
- •Особенности адресного пространства в Windows 98
- •Выравнивание данных
- •ГЛАВА 14 Исследование виртуальной памяти
- •Системная информация
- •Программа-пример Syslnfo
- •Статус виртуальной памяти
- •Программа-пример VMStat
- •Определение состояния адресного пространства
- •Функция VMQuery
- •Программа-пример VMMap
- •ГЛАВА 15 Использование виртуальной памяти в приложениях
- •Резервирование региона в адресном пространстве
- •Передача памяти зарезервированному региону
- •Резервирование региона с одновременной передачей физической памяти
- •В какой момент региону передают физическую память
- •Возврат физической памяти и освобождение региона
- •В какой момент физическую память возвращают системе
- •Программа-пример VMAIloc
- •Изменение атрибутов защиты
- •Сброс содержимого физической памяти
- •Программа-пример MemReset
- •Механизм Address Windowing Extensions (только Windows 2000)
- •Программа-пример AWE
- •ГЛАВА 16 Стек потока
- •Стек потока в Windows 98
- •Функция из библиотеки С/С++ для контроля стека
- •Программа-пример Summation
- •ГЛАВА 17 Проецируемые в память файлы
- •Проецирование в память EXE- и DLL-файлов
- •Статические данные не разделяются несколькими экземплярами EXE или DLL
- •Программа-пример Applnst
- •Файлы данных, проецируемые в память
- •Метод 1: один файл, один буфер
- •Метод 2: два файла, один буфер
- •Метод 3: один файл, два буфера
- •Метод 4: один файл и никаких буферов
- •Использование проецируемых в память файлов
- •Этап1: создание или открытие объекта ядра «файл»
- •Этап 2: создание объекта ядра «проекция файла»
- •Этап 3: проецирование файловых данных на адресное пространство процесса
- •Этап 4: отключение файла данных от адресного пространства процесса
- •Этапы 5 и 6: закрытие объектов «проекция файла» и «файл»
- •Программа-пример FileRev
- •Обработка больших файлов
- •Проецируемые файлы и когерентность
- •Базовый адрес файла, проецируемого в память
- •Особенности проецирования файлов на разных платформах
- •Совместный доступ процессов к данным через механизм проецирования
- •Файлы, проецируемые на физическую память из страничного файла
- •Программа-пример MMFShare
- •Частичная передача физической памяти проецируемым файлам
- •Программа-пример MMFSparse
- •ГЛАВА 18 Динамически распределяемая память
- •Стандартная куча процесса
- •Дополнительные кучи в процессе
- •Защита компонентов
- •Более эффективное управление памятью
- •Локальный доступ
- •Исключение издержек, связанных с синхронизацией потоков
- •Быстрое освобождение всей памяти в куче
- •Создание дополнительной кучи
- •Выделение блока памяти из кучи
- •Изменение размера блока
- •Определение размера блока
- •Освобождение блока
- •Уничтожение кучи
- •Использование куч в программах на С++
- •Другие функции управления кучами
- •ЧАСТЬ IV ДИНАМИЧЕСКИ ПОДКЛЮЧАЕМЫЕ БИБЛИОТЕКИ
- •ГЛАВА 19 DLL: основы
- •DLL и адресное пространство процесса
- •Общая картина
- •Создание DLL-модуля
- •Что такое экспорт
- •Создание DLL для использования с другими средствами разработки (отличными от Visual C++)
- •Создание ЕХЕ-модуля
- •Что такое импорт
- •Выполнение ЕХЕ-модуля
- •ГЛАВА 20 DLL: более сложные методы программирования
- •Явная загрузка DLL и связывание идентификаторов
- •Явная загрузка DLL
- •Явная выгрузка DLL
- •Явное подключение экспортируемого идентификатора
- •Функция входа/выхода
- •Уведомление DLL_PROCESS_ATTACH
- •Уведомление DLL_PROCESS_DETACH
- •Уведомление DLL_THREAD_ATTACH
- •Уведомление DLL_THREAD_DETACH
- •Как система упорядочивает вызовы DIIMain
- •Функция DllMain и библиотека С/С++
- •Отложенная загрузка DLL
- •Программа-пример DelayLoadApp
- •Переадресация вызовов функций
- •Известные DLL
- •Перенаправление DLL
- •Модификация базовых адресов модулей
- •Связывание модулей
- •ГЛАВА 21 Локальная память потока
- •Динамическая локальная память потока
- •Использование динамической TLS
- •Статическая локальная память потока
- •Пример внедрения DLL
- •Внедрение DLL c использованием реестра
- •Внедрение DLL с помощью ловушек
- •Утилита для сохранения позиций элементов на рабочем столе
- •Внедрение DLL с помощью удаленных потоков
- •Программа-пример lnjLib
- •Библиотека lmgWalk.dll
- •Внедрение троянской DLL
- •Внедрение DLL как отладчика
- •Внедрение кода в среде Windows 98 через проецируемый в память файл
- •Внедрение кода через функцию CreateProcess
- •Перехват API-вызовов: пример
- •Перехват API-вызовов подменой кода
- •Перехват API-вызовов с использованием раздела импорта
- •Программа-пример LastMsgBoxlnfo
- •ЧАСТЬ V СТРУКТУРНАЯ ОБРАБОТКА ИСКЛЮЧЕНИЙ
- •ГЛАВА 23 Обработчики завершения
- •Примеры использования обработчиков завершения
- •Funcenstein1
- •Funcenstein2
- •Funcenstein3
- •Funcfurter1
- •Проверьте себя: FuncaDoodleDoo
- •Funcenstein4
- •Funcarama1
- •Funcarama2
- •Funcarama3
- •Funcarama4: последний рубеж
- •И еще о блоке finally
- •Funcfurter2
- •Программа-пример SEHTerm
- •ГЛАВА 24 Фильтры и обработчики исключений
- •Примеры использования фильтров и обработчиков исключений
- •Funcmeister1
- •Funcmeister2
- •EXCEPTION_EXECUTE_HANDLER
- •Некоторые полезные примеры
- •Глобальная раскрутка
- •Остановка глобальной раскрутки
- •EXCEPTION_CONTINUE_EXECUTION
- •Будьте осторожны с EXCEPTION_CONTINUE_EXECUTION
- •EXCEPTION_CONTINUE_SEARCH
- •Функция GetExceptionCode
- •Функция GetExceptionlnformation
- •Программные исключения
- •ГЛАВА 25 Необработанные исключения и исключения С++
- •Отладка по запросу
- •Отключение вывода сообщений об исключении
- •Принудительное завершение процесса
- •Создание оболочки вокруг функции потока
- •Создание оболочки вокруг всех функций потоков
- •Автоматический вызов отладчика
- •Явный вызов функции UnhandledExceptionFilter
- •Функция UnhandledExceptionFilter изнутри
- •Исключения и отладчик
- •Программа-пример Spreadsheet
- •Исключения С++ и структурные исключения
- •Перехват структурных исключений в С++
- •ЧАСТЬ VI ОПЕРАЦИИ С ОКНАМИ
- •ГЛАВА 26 Оконные сообщения
- •Очередь сообщений потока
- •Посылка асинхронных сообщений в очередь потока
- •Посылка синхронных сообщений окну
- •Пробуждение потока
- •Флаги состояния очереди
- •Алгоритм выборки сообщений из очереди потока
- •Пробуждение потока с использованием объектов ядра или флагов состояния очереди
- •Передача данных через сообщения
- •Программа-пример CopyData
- •ГЛАВА 27 Модель аппаратного ввода и локальное состояние ввода
- •Поток необработанного ввода
- •Локальное состояние ввода
- •Ввод с клавиатуры и фокус
- •Управление курсором мыши
- •Подключение к очередям виртуального ввода и переменным локального состояния ввода
- •Программа-пример LISLab
- •Программа-пример LISWatch
должен всегда загружаться именно по этому адресу, а иначе приложения, исполь зующие данный модуль, потребуют больше памяти и будут инициализироваться мед леннее. Поскольку такая ситуация крайне нежелательна, моя утилита сообщает о слу чаях, когда модуль загружен не по предпочтительному базовому адресу. Впрочем, на эти темы мы поговорим в главе 20 (в разделе "Модификация базовых адресов модулей").
ГЛАВА 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;
//получить описатель этого объекта больше нельзя
Определение ограничений, налагаемых на процессы в задании
Создав задание, Вы обычно строите "песочницу" (набор ограничений) для включае мых в него процессов. Ограничения бывают нескольких видов:
базовые и расширенные базовые ограничения — не дают процессам в задании монопольно захватывать системные ресурсы;
базовые ограничения по пользовательскому интерфейсу (UI) — блокируют возможность его изменения; ограничения, связанные с защитой, — перекрывают процессам в задании дос туп к
защищенным ресурсам (файлам, подразделам реестра и т. д,).
Ограничения на задание вводятся вызовом:
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_INFOR

MATION, инициализирую ее и вызываю функцию 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_OB JECT_LIMIT_AFFINITY и отказаться от флага JOB_OBJECT_LIMIT_JOB_TIME. Но тогда получится, что Вы снимаете ограничения на процессорное время.
Вы хотели другого: ограничить доступ к подмножеству процессоров, сохранив существующее ограничение на процессорное время, и не вычитать время, израсхо дованное завершенными процессами, из общего времени. Чтобы решить эту пробле му,
используйте специальный флаг JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME. Этот флaг и JOB_OBJECT_LIMIT_JOB_TIME являются взаимоисключающими. Флаг JOB_OB JECT_LIMIT_PRESERVE_JOB_TIME указывает системе изменить ограничения, не вычи тая процессорное время, использованное уже завершенными процессами.
Обсудим также элемент SchedulingCtoss структуры JOBOBJECT_BASIC_LIMIT_INFOR MATION. Представьте, что для двух заданий определен класс приоритета 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_NOG PFAULTERRORBOX для каждого из процессов в задании Процесс, в котором возник нет необрабатываемое им исключение, немедленно завершается бсз уведомления пользователя. Этот флаг полезен в сервисных и других пакетных заданиях. В его от сутствие один из процессов в задании мог бы вызвать исключение и не завершиться, впустую расходуя системные ресурсы.
Помимо базовых ограничений, Вы можстс устанавливать расширенные, для чего применяется структура 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 и EXPLO RER, но никаких упоминаний о созданных ими окнах нет. Уверяю Вас, эти потоки наверняка создали какие-нибудь окна — просто Spy++ лишена возможности их ви деть. В правой секции (Windows 3) утилита Spy++ должна показывать иерархию окон на рабочем столе, но там нет ничего, кроме одного элемента — 00000000. (Это не настоящий элемент, но Spy++ была обязана поместить сюда хоть что-нибудь.)

Обратите внимание, что такие oграничения односторонни, т e. внешние процес сы все равно видят USER-объекты, которые созданы процессами, включенными в за дание. Например, если запустить Notepad в задании, a Spy++ — внс сго, последняя увидит окно
Notepad, даже если для задания указан флаг JOB_OBJECT_UILIMIT_HAND LES Кроме того, Spy++, запущенная в отдельном задании, все равно увидинт окно Notepad, если только для ее задания не установлен флаг JOB_OBJECT_UILIMIT_HAN DLES.
Рис. 5-2. Microsoft Spy++ работает в задании, которому ограничен доступ к описателям USERобъектов
Ограничение доступа к описателям USER-объектов — вещь изумительная, если Вы хотите создать по-настоящему безопасную песочницу, в которой будут «копаться" процессы Вашего задания. Однако часто бывает нужно, чтобы процесс в задании взаимодействовал с внешними процессами. Одно из самых простых решений здесь — использовать оконные сообщения, но, ссли процессам в задании доступ к описате лям пользовательского интерфейса запрещен, ни один из них не сможет послать со общение (синхронно или асинхронно) окну, созданному внешним процессом К счастью,теперь есть функция, которая поможет решить эту проблему:
BOOL UserHandleGrantAccess( HANOIF hUserObj, HANDLE hjob, BOOL fGrant);
Параметр hUserObj идентифицирует конкретный USER-объект, доступ к которому Вы хотите предоставить или запретить процессам в задании. Это почти всегда опи сатель окна, но USER объектом может быть, например, рабочий стол, программная ловушка, ярлык или меню Последние два параметра, hjob и fGrant, указывают на зада ние и вид ограничения. Обратите внимание, что функция не сработает, если ее выз вать из процесса в том задании, на которое указывает hjob, — процесс нс имеет права сам себе предоставлять доступ к объекту.
И последний вид ограничений, устанавливаемых для задания, относится к защи те. (Введя в действие такие ограничения, Вы не сможете их отменить) Структура
JOBOBJECT_SECURITY_LIMIT_INFORMATION выглядит так.