Московский государственный технический университет им. Н.Э. Баумана
Опубликован: 28.06.2006 | Доступ: свободный | Студентов: 12459 / 340 | Оценка: 4.54 / 3.83 | Длительность: 22:03:00
ISBN: 978-5-9556-0055-0
Лекция 12:

Общие подходы к реализации приложений с параллельным выполнением операций

< Лекция 11 || Лекция 12: 1234 || Лекция 13 >

Асинхронные вызовы процедур

Для реализации асинхронного ввода-вывода в операционной системе предусмотрен специальный механизм, основанный на так называемых асинхронных вызовах процедур (Аsynchronous Procedure Call, APC). Это один из базовых механизмов, необходимый для нормального функционирования операционной системы.

Практика показала, что такой механизм был бы эффективен и для реализации самих приложений. Более того, для реализации асинхронного ввода-вывода с поддержкой функции завершения система уже обязана была предоставить этот механизм. Для реализации этого механизма операционная система ведет списки процедур, которые она должна вызывать в контексте данного потока, с тем ограничением, что прерывать работу занятого потока в произвольный момент времени система не должна. Поэтому для обслуживания накопившихся в очереди процедур необходимо перевести поток в специальное состояние ожидания оповещения (alertable waiting) - для этого Win32 API предусматривает специальный набор функций: например, SleepEx, WaitForSingleObjectEx и др.

Здесь и во многих примерах далее, чтобы не загромождать код примера, опущена проверка ошибок:

VOID CALLBACK ApcProc( ULONG_PTR dwData )
{
  /* ... */
}
int main( void )
{
  QueueUserAPC( ApcProc, GetCurrentThread(), 0 ); 
  /* ... */
  SleepEx( 1000, TRUE );
  return 0;
}

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

Процессы, потоки и объекты ядра

Обсуждение реализации мультипрограммирования в операционной системе неизбежно приводит к обсуждению вопросов безопасности.

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

Для того чтобы ядро операционной системы могло контролировать доступ к тем или иным объектам, сами объекты должны управляться ядром системы. Это приводит к понятию объектов ядра (kernel objects), которые создаются по запросу процессов ядром системы, управляются ядром, и доступ к которым также контролируется ядром системы.

Объекты ядра

Объекты ядра представлены в качестве некоторых структур и типов данных, управляемых ядром операционной системы и размещенных в памяти, принадлежащей ядру. Пользовательские процессы, как правило,

не имеют возможности доступа к этим объектам напрямую. Для того чтобы пользовательский процесс мог оперировать такими объектами, введено понятие описатель (handle) объекта ядра. Так, например, объектами ядра являются файлы, процессы, потоки, события, почтовые ящики и многое другое.

Все созданные описатели объектов ядра должны удаляться с помощью функции

BOOL CloseHandle( HANDLE hKernelObject )

которая уменьшает счетчик использования объекта и уничтожает его, когда на объект никто больше не ссылается.

Доступ к защищаемым объектам в Windows задается так называемыми дескрипторами безопасности (Security Descriptor). Дескриптор содержит информацию о владельце объекта и первичной группе пользователей и два списка управления доступом (ACL, Access Control List): один список задает разрешения доступа, другой - необходимость аудита при доступе к объекту. Список содержит записи, указывающие права выполнения действий, и запреты, назначенные конкретным пользователям и группам. При доступе к защищаемым объектам для начала проверяются запреты - если для данного пользователя и группы имеется запрет доступа, то дальнейшая проверка не выполняется и попытка доступа отклоняется. Если запретов нет, то проверяются права доступа - при отсутствии разрешений доступ отклоняется. Запрет обладает более высоким "приоритетом", чем наличие разрешений - это позволяет разрешить доступ, к примеру, целой группе пользователей и выборочно запретить некоторым ее членам.

Объект, осуществляющий доступ (выполняющийся поток), обладает так называемым маркером доступа (access token). Маркер идентифицирует пользователя, от имени которого предпринимается попытка доступа, а также его привилегии и умолчания (например, стандартный ACL объектов, создаваемых этим пользователем). В Windows маркерами доступа обладают как потоки, так и процессы. С процессом связан так называемый первичный маркер доступа, который используется при создании потоков, а вот в дальнейшем поток может работать от имени какого-либо иного пользователя, используя собственный маркер воплощения (impersonation).

Процессы и потоки в Windows являются с одной стороны "представителями" пользователя, выступающими от его имени, а с другой стороны - защищаемыми объектами, при доступе к которым выполняется проверка прав, то есть они обладают одновременно и маркерами доступа, и дескрипторами безопасности.

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

Для создания большинства объектов ядра используются функции, начинающиеся на слово "Create" и возвращающие описатель созданного объекта, например функции CreateFile, CreateProcess, CreateEvent и т.д. Многие объекты при их создании могут получить собственное имя или остаться неименованными.

Любой процесс или поток может ссылаться на объекты ядра, созданные другим процессом или потоком. Для этого предусмотрено три механизма:

  • Объекты могут быть унаследованы дочерним процессом при его создании. В этом случае объекты ядра должны быть "наследуемыми", и родительский процесс должен принять меры к тому, чтобы потомок мог узнать их описатели. Возможность передавать описатель потомкам по наследованию явно указывается в большинстве функций, так или иначе создающих объекты ядра (обычно такие функции содержат аргумент " BOOL bInheritHandle ", который указывает возможность наследования).
  • Объект может иметь собственное уникальное имя - тогда можно получить описатель этого объекта по его имени. Для разных типов объектов Win32 API предоставляет набор функций, начинающийся на Open... например, OpenMutex, OpenEvent и т.д.
  • Процесс-владелец объекта может передать его описатель любому другому процессу. Для этого процесс-владелец объекта должен получить специальный описатель объекта для "экспорта" в указанный процесс. В Win32 API для этого предназначена функция DuplicateHandle, создающая для объекта, заданного описателем в контексте данного процесса, новый описатель, корректный в контексте нового процесса:
    BOOL DuplicateHandle(
      HANDLE hFromProcess, HANDLE hSourceHandle,
      HANDLE hToProcess, LPHANDLE lpResultHandle,
      DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwOptions
    );

Существенно проследить, чтобы все создаваемые описатели закрывались вызовом функции CloseHandle, включая описатели, созданные функцией DuplicateHandle. Хорошая практика при разработке приложений - проводить мониторинг выделяемых описателей и количества объектов в процессе (например, с помощью таких стандартных средств как менеджер задач или оснастка "производительность" панели управления).

< Лекция 11 || Лекция 12: 1234 || Лекция 13 >
Анастасия Булинкова
Анастасия Булинкова
Рабочим названием платформы .NET было