Отложенные вызовы процедур и процедуры обработки прерываний. Архитектура операционной системы UNIX и управление ею

Обработка прерываний таймера

Каждый компьютер имеет аппаратный таймер или системные часы, которые генерируют аппаратное прерывание через фиксированные интервалы времени. Временной интервал между соседними прерываниями называется тиком процессора или просто тиком (CPU tick, clock tick). Как правило, системный таймер поддерживает несколько значений тиков, но в UNIX это значение обычно устанавливается равным 10 миллисекундам, хотя это значение может отличаться для различных версий операционной системы. Большинство систем хранят это значение в константе HZ, которая определена в файле заголовков Например, для тика в 10 миллисекунд значение HZ устанавливается равным 100.

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

1. Обновление статистики использования процессора для текущего процесса

2. Выполнение ряда функций, связанных с планированием процессов, например пересчет приоритетов и проверку истечения временного кванта для процесса

3. Проверка превышения процессорной квоты для данного процесса и отправка этому процессу сигнала SIGXCPU в случае превышения

4. Обновление системного времени (времени дня) и других связанных с ним таймеров

5. Обработка отложенных вызовов

6. Обработка алармов

7. Пробуждение в случае необходимости системных процессов, например диспетчера страниц и свопера

Часть задач не требует выполнения на каждом тике. Большинство систем вводят нотацию главного тика (major tick), который происходит каждые тиков, где зависит от конкретной версии системы. Определенный набор функций выполняется только на главных тиках. Например, производит пересчет приоритетов каждые 4 тика, a SVR4 обрабатывает и производит пробуждение системных процессов раз в секунду МакКузик М. К., Невилл-Нил Дж. В. FreeBSD: архитектура и реализация. -- М.: КУДИЦ-ОБРАЗ, 2006. -- 800 с...

Отложенные вызовы

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

int co_ID = timeout(void (*fn)(), caddr_t arg, long delta)

где fn определяет адрес функции, которую необходимо вызвать, при этом ей будет передан аргумент arg, а сам вызов будет произведен через delta тиков. Ядро производит вызов fn() в системном контексте, таким образом функция отложенного вызова не должна обращаться к адресному пространству текущего процесса (поскольку не имеет к нему отношения), а также не должна переходить в состояние сна.

Отложенные вызовы применяются для выполнения многих функций, например:

1. Выполнение ряда функций планировщика и подсистемы управления памятью.

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

3. Опрос устройств, не поддерживающих прерывания.

Заметим, что функции отложенных вызовов выполняются в системном контексте, а не в контексте прерывания. Вызов этих функций выполняется не обработчиком прерывания таймера, а отдельным обработчиком отложенных вызовов, который запускается после обработки прерывания таймера. При обработке прерывания таймера система проверяет необходимость запуска тех или иных функций отложенного вызова и устанавливает соответствующий флаг для них. В свою очередь обработчик отложенных вызовов проверяет флаги и запускает необходимые в системном контексте МакКузик М. К., Невилл-Нил Дж. В. FreeBSD: архитектура и реализация. -- М.: КУДИЦ-ОБРАЗ, 2006. -- 800 с...

Контекст процесса

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

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

Регистровый контекст состоит из следующих компонент:

1. Счетчика команд, указывающего адрес следующей команды, которую будет выполнять центральный процессор; этот адрес является виртуальным адресом внутри пространства ядра или пространства задачи.

2. Регистра состояния процессора (PS), который указывает аппаратный статус машины по отношению к процессу. Регистр PS, например, обычно содержит подполя, которые указывают, является ли результат последних вычислений нулевым, положительным или отрицательным, переполнен ли регистр с установкой бита переноса и т. д. Операции, влияющие на установку регистра PS, выполняются для отдельного процесса, потому-то в регистре PS и содержится аппаратный статус машины по отношению к процессу. В других имеющих важное значение подполях регистра PS указывается текущий уровень прерывания процессора, а также текущий и предыдущий режимы выполнения процесса (режим ядра/задачи). По значению подполя текущего режима выполнения процесса устанавливается, может ли процесс выполнять привилегированные команды и обращаться к адресному пространству ядра.

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

4. Регистров общего назначения, в которых содержится информация, сгенерированная процессом во время его выполнения.

Системный контекст процесса имеет «статическую часть» (первые три элемента в нижеследующем списке) и «динамическую часть» (последние два элемента). На протяжении всего времени выполнения процесс постоянно располагает одной статической частью системного контекста, но может иметь переменное число динамических частей. Динамическую часть системного контекста можно представить в виде стека, элементами которого являются контекстные уровни, которые помещаются в стек ядром или выталкиваются из стека при наступлении различных событий. Системный контекст включает в себя следующие компоненты:

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

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

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

4. Стек ядра, в котором хранятся записи процедур ядра, если процесс выполняется в режиме ядра. Несмотря на то, что все процессы пользуются одними и теми же программами ядра, каждый из них имеет свою собственную копию стека ядра для хранения индивидуальных обращений к функциям ядра. Пусть, например, один процесс вызывает функцию creat и приостанавливается в ожидании назначения нового индекса, а другой процесс вызывает функцию read и приостанавливается в ожидании завершения передачи данных с диска в память. Оба процесса обращаются к функциям ядра и у каждого из них имеется в наличии отдельный стек, в котором хранится последовательность выполненных обращений. Ядро должно иметь возможность восстанавливать содержимое стека ядра и положение указателя вершины стека для того, чтобы возобновлять выполнение процесса в режиме ядра. В различных системах стек ядра часто располагается в пространстве процесса, однако этот стек является логически-независимым и, таким образом, может помещаться в самостоятельной области памяти. Когда процесс выполняется в режиме задачи, соответствующий ему стек ядра пуст.

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

Ядро помещает контекстный уровень в стек при возникновении прерывания, при обращении к системной функции или при переключении контекста процесса. Контекстный уровень выталкивается из стека после завершения обработки прерывания, при возврате процесса в режим задачи после выполнения системной функции, или при переключении контекста. Таким образом, переключение контекста влечет за собой как помещение контекстного уровня в стек, так и извлечение уровня из стека: ядро помещает в стек контекстный уровень старого процесса, а извлекает из стека контекстный уровень нового процесса. Информация, необходимая для восстановления текущего контекстного уровня, хранится в записи таблицы процессов Робачевский А. М. Операционная система UNIX. -- СПб.: БХВ- Петербург, 2002. -- 528 с. .

Распространенной проблемой операционной системы Windows любой редакции является загрузка ресурсов компьютера «внутренними» процессами. Одним из таких процессов является системное прерывание, которое может серьезно нагружать ресурсы компьютера, что будет отображаться в «Диспетчере задач». Наиболее часто приходится сталкиваться с ситуацией, когда системное прерывание грузит процессор, из-за чего компьютер серьезно теряет в производительности. В рамках данной статьи мы рассмотрим, почему это происходит, а также можно ли отключить системные прерывания в Windows.

Системные прерывания: что это за процесс

Процесс «Системные прерывания» по умолчанию в операционной системе Windows запущен постоянно, но при обычной работе он не должен нагружать компоненты системы более чем на 5%. Если данный процесс более серьезно воздействует на ресурсы компьютера, это говорит о наличии аппаратной проблемы, а именно о нарушении в работе одного из компонентов компьютера.

Когда «Системные прерывания» грузят процессор, это может сигнализировать о неполадках в работе видеокарты, материнской платы, оперативной памяти или другого элемента системного блока. Центральный процессор старается дополнить недостающую мощность, возникшую из-за неправильной работы компонента, при помощи собственных ресурсов, о чем свидетельствует процесс «Системные прерывания». Чаще всего проблема неправильной работы компонентов компьютера связана с полной или частичной несовместимостью запущенной программы (или игры) с драйверами компонентов компьютера.

Как отключить системные прерывания

Как было отмечено выше, системные прерывания являются не более чем указателем, что со стороны Windows идет дополнительное обращение к ресурсам центрального процессора. Отключить системные прерывания, чтобы повысить производительность компьютера, не получится, и нужно искать проблему в работе компонентов PC. Для этого удобно использовать приложение DPC Latency Checker, которое можно загрузить бесплатно в интернете с сайта разработчиков. Программа позволяет определить неисправные компоненты компьютера.

Чтобы провести диагностику системы приложением DPC Latency Checker, запустите его и подождите. Некоторое время уйдет на проверку компьютера, после чего пользователь увидит на графике, если имеются проблемы в работе компонентов системы. Также приложение укажет на возможные ошибки и посоветует их поискать, отключая устройства.

Для этого перейдите в «Диспетчер устройств», нажав правой кнопкой мыши на «Пуск» и выбрав соответствующий пункт, и начните по одному отключать устройства. После каждого отключения проверяйте в «Диспетчере задач» и приложении DPC Latency Checker, устранена ли проблемы с загрузкой процессора системными прерываниями. Если проблема сохранилась, включайте устройство обратно и переходите к следующему.

Важно: В процессе отключения компонентов в «Диспетчере устройств», не отключайте «Компьютер», «Процессор» и «Системные устройства», иначе это приведет к экстренной перезагрузке компьютера.

Когда будет найдено устройство, при отключении которого нагрузка на процессор снизится до нормального состояния, обновите драйвера для этого компонента с официального сайта разработчиков.

Обратите внимание: Если были предприняты попытки отключить все компоненты системы, но процесс «Системные прерывания» продолжает нагружать систему, попробуйте обновить драйвера для процессора.

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

Стоит отметить, что отключать системные прерывания через «Диспетчер задач» не следует, это приведет к сбою системы, но не решит проблему.

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

  • запуск диспетчеризации потоков;
  • обработка не критичных по времени прерываний;
  • обработка истечения времени таймера;
  • асинхронное выполнение процедуры в контексте конкретного потока;
  • поддержка асинхронных операций ввода-вывода.

Эти задачи рассматриваются в следующих разделах.

Диспетчеризируемые прерывания или прерывания отложенного вызова процедуры (DeferredProcedureCall, DPC). Когда выполнение потока больше не может продолжаться, возможно, из-за того, что он был завершен или из-за того, что он добровольно вошел в состояние ожидания, ядро напрямую вызывает диспетчер для немедленного переключения контекста.

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

Ядро всегда поднимает IRQL процессора до уровня DPC/dispatch или выше, когда ему нужно синхронизировать доступ к общим структурам ядра. Тем самым блокируются дополнительные программные прерывания и диспетчеризация потоков. Когда ядро обнаруживает необходимость диспетчеризации, оно запрашивает прерывание уровня DPC/dispatch, но процессор задерживает прерывание, поскольку IRQL находится на этом или на более высоком уровне.

Когда ядро завершает свою текущую работу, процессор видит, что оно собирается поставить IRQL ниже уровня DPC/dispatch, и проверяет наличие каких-либо отложенных прерываний диспетчеризации. Если такие прерывания имеются, IRQL понижается до уровня DPC/dispatch, и происходит обработка прерываний диспетчеризации. Активизация диспетчера потоков с помощью программного прерывания является способом, позволяющим отложить диспетчеризацию, пока для нее не сложатся нужные обстоятельства. Но Windows использует программные прерывания для того, чтобы отложить и другие типы обработки.

Кроме диспетчеризации потоков ядро обрабатывает на этом уровне IRQL и отложенные вызовы процедур (DPC). DPC является функцией, выполняющей ту системную задачу, которая менее критична по времени, чем текущая задача.

Функции называются отложенными (deferred), потому что они не требуют немедленного выполнения.

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

Ядро использует DPC-вызовы для обслуживания истечений времени таймера (и освобождения потоков, которые ожидают истечения времени таймеров) и для перепланирования времени использования процессора после истечения времени, выделенного потоку (кванта потока). Драйверы устройств используют DPC-вызовы для обработки прерываний. Для обеспечения своевременного обслуживания программных прерываний Windows совместно с драйверами устройств старается сохранять IRQL ниже IRQL-уровней устройств. Одним из способов достижения этой цели является выполнение ISR-процедурами драйверов устройств минимально необходимой работы для оповещения своих устройств, сохранения временного состояния прерывания и задержки передачи данных или обработки других, менее критичных по времени прерываний для выполнения в DPC-вызовах на IRQL-уровне DPC/dispatch.

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

Они называются DPC-очередями. Для запроса DPC системный код вызывает ядро для инициализации DPC-объекта, а затем помещает этот объект в DPC-очередь.

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

Ядро обрабатывает DPC-вызовы, когда IRQL-уровень процессора готов понизиться с IRQL-уровня DPC/dispatch или более высокого уровня до более низкого IRQL-уровня (APCили passive). Windows обеспечивает пребывание IRQL на уровне DPC/dispatch и извлекает DPC-объекты из очереди текущего процессора до тех пор, пока она не будет исчерпана (то есть ядро «расходует» очередь), вызывая по очереди каждую DPC-функцию. Ядро даст возможность IRQL-уровню упасть ниже уровня DPC/dispatch и позволить продолжить обычное выполнение потока только когда очередь истощится. Обработка DPC изображена на рисунке DPC-приоритеты могут повлиять на поведение системы и другим образом.

Обычно ядро инициирует расход DPC-очереди прерыванием DPC/dispatch-уровня. Ядро генерирует такое прерывание только в том случае, когда DPC-вызов направлен на текущий процессор (тот, на котором выполняется ISR-процедура) и DPC имеет приоритет выше низкого (low). Если DPC имеет низкий приоритет, ядро запрашивает прерывание только в том случае, когда количество невыполненных запросов DPC процессора превышает пороговое значение или если количество DPC-вызовов, выполнение которых запрашивается на процессоре в данном окне времени, невелико.

Доставка DPC-вызова

Если DPC-вызов нацелен на центральный процессор, отличающийся от того, на котором запущена ISR-процедура, и приоритет DPC высокий (high) или выше среднего (medium-high), ядро немедленно сигнализирует целевому центральному процессору (посылая ему диспетчерское IPI) о необходимости расхода его DPC-очереди, но только если целевой процессор находится в режиме ожидания. Если приоритет средний или низкий, то для выдачи ядром прерывания DPC/dispatch количество DPC-вызовов, находящихся в очереди на целевом процессоре, должно превысить определенный порог. Системный поток простоя (idle) также опустошает DPC-очередь того процессора, на котором он запущен. Несмотря на ту гибкость, которую придают системе целевые назначения DPC-вызовов и уровни приоритета, драйверам устройств редко требуется изменять поведение по умолчанию своих DPC-объектов. Ситуации, инициирующие опустошение DPC-очереди, сведены в таблице. Если посмотреть на правила генерации, то, фактически, получается, что приоритеты выше среднего и высокий равны друг другу. Разница проявляется при их вставке в список, когда прерывания высокого уровня находятся впереди, а прерывания уровня выше среднего сзади.

Поскольку потоки пользовательского режима выполняются при низком IRQL, высоки шансы на то, что DPC-вызов прервет выполнение обычного пользовательского потока. DPC-процедуры выполняются независимо от того, какой поток запущен, стало быть, когда запускается DPC-процедура, она не может выстроить предположение насчет того, чье адресное пространство, какого именно процесса в данный момент отображается. DPC-процедуры могут вызывать функции ядра, но они не могут вызывать системные службы, генерировать ошибки отсутствия страницы или же создавать или ожидать объекты диспетчеризации. Тем не менее они могут обращаться к невыгружаемым адресам системной памяти, поскольку системное адресное пространство отображено всегда, независимо от того, что из себя представляет текущий процесс.

Правила генерации DPC-прерывания.

Приоритет DPC DPC-вызов нацеливается на процессор, выполняющий
ISR -процедуру
DPC-вызов нацеливается
на другой процессор
Низкий (Low) Длина DPC-очереди превышает максимальную длину DPC-очереди или уровень DPC-запросов ниже минимального
уровня DPC-запросов
Средний
(Medium)
Всегда Длина DPC-очереди превышает максимальную длину DPC-очереди или система находится в простое
Выше среднего
(Medium-High)
Всегда
Высокий (High) Всегда Целевой процессор простаивает

DPC-вызовы предоставляются в первую очередь драйверам устройств, но также используются и ядром. Чаще всего ядро использует DPC для обработки истечения кванта времени. При каждом такте системных часов возникает прерывание на IRQL-уровне clock. Обработчик прерывания от часов (запускаемый на IRQL-уровне clock) обновляет системное время и затем уменьшает показании счетчика, отслеживающего продолжительность работы текущего потока.

Когда счетчик доходит до нуля, квант времени потока истекает, и ядру, возможно, нужно будет перепланировать время процессора, то есть выполнить задачу с более низким приоритетом, которая должна выполняться на IRQL-уровне DPC/dispatch.

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

Так как DPC-вызовы выполняются независимо от того, какой из потоков выполняется в данное время на системе (что во многом похоже на прерывания), они являются основной причиной того, что система, на которой они выполняются, становится невосприимчивой к рабочей нагрузке клиентских систем или рабочей станции, потому что даже потоки с самым высоким уровнем приоритета будут прерваны ожидающим DPC-вызовом.

Некоторые DPC-вызовы выполняются настолько долго, что пользователи могут заметить отставание видео или звука или даже ощутить ненормальное замедление реакции мыши или клавиатуры, поэтому для драйверов с продолжительными DPC-вызовами Windows поддерживает потоковые DPC-вызовы.

Потоковые DPC-вызовы, как следует из их названия, предназначены для выполнения DPC-процедуры на пассивном (passive) уровне в потоке с приоритетом реального времени (priority 31). Это позволяет DPC-вызову воспользоваться приоритетом над большинством потоков пользовательского режима (поскольку большинство потоков приложений не запускается в диапазонах приоритетов реального времени). Но это позволяет другим прерываниям, непотоковым DPC-вызовам, APC-вызовам и потокам с более высоким приоритетом реализовать свой приоритет перед данной процедурой.

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

Для разрешения такого типа проблем, программный код, предназначенный для работы в режиме ядра, должен быть сконструирован таким образом, чтобы избегать продолжительной работы при повышенных уровнях IRQL. Одним из самых важных компонентов этой стратегии являются Deferred Procedure Calls (DPC) — отложенные процедурные вызовы.

Функционирование DPC

Схема применения отложенных процедурных вызовов позволяет построить процесс выполнения таким образом, что задача может быть запланирована кодом, работающим на высоком уровне IRQL, но она еще не выполняется . Такая отсрочка выполнения применима, если производится обслуживание прерывания в драйвере, и при этом нет никаких причин блокировать выполнение другого программного кода более низкого уровня IRQL. Иными словами — когда обработка данной ситуации может быть безболезненно перенесена на более позднее время.

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

Для начала, ограничимся рассмотрением более простого случая работы с DPC процедурами, предназначенными для использования совместно с процедурами обработки прерываний. Данный тип DPC процедур получил в литературе специальное название DpcForIsr.

Объект DPC для использования в процедурах обработки прерываний создается по вызову IoInitializeDpcRequest , выполняемому обычно в стартовых процедурах драйвера. Данный вызов регистрирует предлагаемую драйвером DpcForIsr процедуру и ассоциирует ее с создаваемым объектом — достаточно распространенная методика в Windows. Следует особо отметить, что DPC объект, созданный данным вызовом так и останется в недрах операционной системы, недоступным разработчику драйвера. (Отличие DpcForIsr от других DPC процедур состоит только в том, что работа с последними проходит при помощи вызовов Ke...Dpc , а создаваемые для них DPC объекты доступны разработчику драйвера.)

Если драйвер зарегистрировал свою процедуру DpcForIsr, то во время обработки прерывания ISR процедурой в системную очередь DPC может быть помещен соответствующий DPC объект (фактически, запрос на вызов этой DpcForIsr процедуры позже) — при помощи вызова IoRequestDpc . Процедура DpcForIsr и завершит позже обработку полученного ISR процедурой запроса, что будет выполнено в менее критичных условиях и при низком уровне IRQL.

В общих чертах, функционирование DPC процедур (в данном случае, DpcForIsr) складывается из следующих операций:

  • Когда некоторый фрагмент программного кода, работающий на высоком (аппаратном) уровне IRQL желает запланировать выполнение части своей работы так, чтобы она была выполнена при низком значении IRQL, то он добавляет DPC объект в системную очередь отложенных процедурных вызовов.
  • Рано или поздно, значение IRQL процессора падает ниже DISPATCH_LEVEL, и работа, которая была отложена прерыванием, обслуживается DPC функцией. Диспетчер DPC извлекает каждый DPC объект из очереди и вызывает соответствующую функцию, указатель на которую хранится в этом объекте. Вызов этой функции выполняется в то время, когда процессор работает на уровне DISPATCH_LEVEL.

Особенности механизма DPC

Как правило, работа с отложенными процедурными вызовами не является сложной, поскольку операционные системы Windows 2000/XP/Server 2003 предлагают большой набор системных вызовов, скрывающих большую часть деталей этого процесса. Тем не менее, особо следует выделить два наиболее обманчивых момента в работе с DPC.

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

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

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

Если присмотреться к списку параметров вызовов IoInitializeDpcRequest и IoRequestDpc (предназначенных для работы с DpcForIsr процедурами), то нетрудно заметить, что DPC объект "привязан" к объекту устройства. При размещении этого объекта в DPC очереди в момент работы ISR процедуры также указывается объект устройства. Этим и достигается определенность, вызов какой конкретно DPC процедуры "заказан" ISR процедурой (соотнесение по объекту устройства). Это же говорит о том, что драйвер, который создал несколько объектов устройств (достаточно редкий случай), может эксплуатировать и несколько процедур DpcForIsr — по одной для каждого объекта устройства.

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

Выше было рассмотрено использование DPC процедур для завершения обработки прерываний, то есть DpcForIsr. Однако DPC процедуры можно использовать и в другом ключе, например, совместно с таймерами для организации ожидания. Для этого создастся объект DPC при помощи вызова KeInitializeDPC , который связывает этот объект с DPC процедурой, входящей в состав драйвера. После этого можно выполнять установку времени ожидания в предварительно инициализированном (используя KeInitializeTimer или KeInitializeEx ) объекте таймера. Для установки интервала ожидания используется вызов KeSetTimer , которому в качестве одного из параметров необходимо передать указатель на инициализированный DPC объект. Пo истечении интервала ожидания DPC объект будет внесен в системную DPC очередь, и DPC процедура, ассоциированная с ним, будет вызвана так скоро, насколько этo будет возможно. Процедуры DPC данного, второго, типа обозначены в документации DDK термином "Custom DPC". (Этот вариант использования DPC процедур делает их весьма напоминающими APC вызовы пользовательского режима.)

Для размещения в системной DPC очереди объектов, соответствующих второму типу DPC процедур (не связанных с прерываниями), следует использовать вызов KeInsertQueueDpc . Соответственно, код-инициатор вызова должен работать на уровне IRQL не ниже DISPATCH_LEVEL.

Для очистки системной DPC очереди от Custom DPC процедур, например, если драйвер должен срочно завершить работу, предназначен вызов KeRemoveQueueDpc , который может быть вызван из кода любого уровня IRQL.

Объекты управления включают объекты-примитивы для потоков, прерываний, таймеров, синхронизации, профилирования, а также два специальных объекта для реализации DPC и APC. Объекты DPC (Deferred Procedure Call - отложенный вызов процедуры) используются для уменьшения времени выполнения ISR (Interrupt Service Routines - процедура обслуживания прерываний), которая запускается по прерыванию от устройства. Ограничение времени, затрачиваемого на ISR-процедуры, сокращает шансы утраты прерывания.

Оборудование системы присваивает прерываниям аппаратный уровень приоритета. Процессор также связывает уровень приоритета с выполняемой им работой. Процессор реагирует только на те прерывания, которые имеют более высокий приоритет, чем используемый им в данный момент. Нормальный уровень приоритета (в том числе уровень приоритета всего пользовательского режима) - это 0. Прерывания устройств происходят с уровнем 3 или более высоким, а ISR для прерывания устройства обычно выполняется с тем же уровнем приоритета, что и прерывание (чтобы другие менее важные прерывания не происходили при обработке более важного прерывания).

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

Для уменьшения времени обработки ISR выполняются только критические операции, такие как запись результатов операций ввода-вывода и повторная инициализация устройства. Дальнейшая обработка прерывания откладывается до тех пор, пока уровень приоритета процессора не снизится и не перестанет блокировать обслуживание других прерываний. Объект DPC используется для представления подлежащей выполнению работы, а ISR вызывает уровень ядра для того, чтобы поставить DPC в список DPC конкретного процессора. Если DPC является первым в списке, то ядро регистрирует специальный аппаратный запрос на прерывание процессора с уровнем 2 (на котором NT вызывает уровень DISPATCH). Когда завершается последняя из существующих ISR, уровень прерывания процессора падает ниже 2, и это разблокирует прерывание для обработки DPC. ISR для прерывания DPC обработает каждый из объектов DPC (которые ядро поставило в очередь).

Методика использования программных прерываний для откладывания обработки прерываний является признанным методом уменьшения латентности ISR. UNIX и другие системы начали использовать отложенную обработку в 1970-х годах (для того, чтобы справиться с медленным оборудованием и ограниченным размером буферов последовательных подключений к терминалам). ISR получала от оборудования символы и ставила их в очередь. После того как вся обработка прерываний высшего уровня была закончена, программное прерывание запускало ISR с низким приоритетом для обработки символов (например, для реализации возврата курсора на одну позицию - для этого на терминал посылался управляющий символ для стирания последнего отображенного символа, и курсор перемещался назад).

Аналогичным примером в современной системе Windows может служить клавиатура. После нажатия клавиши клавиатурная ISR читает из регистра код клавиши, а затем опять разрешает клавиатурное прерывание, но не делает обработку клавиши немедленно. Вместо этого она использует DPC для постановки обработки кода клавиши в очередь (до того момента, пока все подлежащие обработке прерывания устройства не будут отработаны).

Поскольку DPC работают на уровне 2, они не мешают выполнению ISR для устройств, но мешают выполняться любым потокам до тех пор, пока все поставленные в очередь DPC не завершатся и уровень приоритета процессора не упадет ниже 2. Драйверы устройств и система не должны слишком долго выполнять ISR или DPC. Поскольку потокам не разрешается выполняться, то ISR и DPC могут сделать систему медлительной и породить сбои при проигрывании музыки (затормаживая те потоки, которые пишут музыку из буфера в звуковое устройство). Другой частый случай использования DPC - это выполнение процедур по прерыванию таймера. Во избежание блокирования потоков события таймера (которые должны выполняться продолжительное время) должны ставить в очередь запросы к пулу рабочих потоков (который ядро поддерживает для фоновой работы).