- Interrupts
- Часть 1 — Прерывания, ISR, IRQ,PIC
- Что такое прерывание ?
- Где используются прерывания ?
- Что такое ISR?
- Написание ISR
- PIC — шлюз между IRQ и CPU
- Часть 2 — Exceptions
- Что такое исключение ?
- Что должна делать ISR , обрабатывающая исключение ?
- Exception Interrupt 14 — Page Fault
- Часть 3 — IDT
- Что такое IDT?
- IDT Descriptor Format
- Загрузка IDT с помощью LIDT
- Все в кучу
- Введение в драйверы устройств ввода/вывода
- Обработка ISR и IST для драйверов устройств
Interrupts
Часть 1 — Прерывания, ISR, IRQ,PIC
Что такое прерывание ?
Интел учит нас , что прерывания останавливают ход программы при возникновении внешних событий или ошибок.
Как происходит прерывание ? Оно может быть сгенерировано железом. Его можно также вызвать программно. Например , при нажатии клавиши прерывание поступает в процессор. Процессор останавливает выполняемый код и вызывает функцию , которая будет читать порт 0x60(keyboard’s output port). Эта же функция потом должна вернуть контроль. Программный код может вызвать прерывание путем вызова инструкции ‘int ‘
Разрешение и запрещение прерываний выполняется с помощтю команд ‘cli'(disable) и ‘sti'(enable).
Где используются прерывания ?
Дисководы , звуковые карты , сетевые карты также используют прерывания.
PIT(Programable Interrupt Timer) генерит прерывания со специальным интервалом , что полезно при реализации многозадачности.
Пользовательские программы могут использовать прерывния. Например , в досе с помощью прерываний выводятся сообщения на экран. В биосе есть специальные прерывания для пользовательских программ , с помощью которых можно переключаться между графическими режимами.
Что такое ISR?
ISR(Interrupt Service Routine) — это код , выполняемый в результате прерывания. Каждому прерыванию должна соответствовать своя ISR.
Написание ISR
Во первых , нужно сохранить в стеке содержимое регистров SS, EIP, ESP, CS . Затем в указатель стека положить адрес нужной ISR. После чего будет выполнен ее код вплоть до инструкции ‘iret’, которая восстановит вышеуказанные регистры .
Небольшой кусок на NASM :
IRQ(Interrupt Request) — прерывание , генерируемое железом.
16 IRQ для PC пронумерованы от 0 до 15.
PIC(Programable Interrupt Controller) ловит эти прерывания с помощью 2-х блоков по 8 IRQ каждое. IRQ пересекаются с исключениями , поэтому их номера нужно переназначать.
PIC — шлюз между IRQ и CPU
Когда случается хардварное прерывание , много чего может произойти. Для лучшей совместимости устройств и различных платформ , при генерации прерывания устройство посылает минимум необходимой информации на PIC(Programable Interrupt Controller). PIC вычисляет номер прерывания и сигнализирует процессору об этом. После чего процессор останавливает поток выполняемых инструкций и вызывает соответствующую процедуру прерывания.
Часть 2 — Exceptions
Что такое исключение ?
Исключение — это событие , возникающее тогда , когда выясняется , что в выполняемом коде заложена ошибка. Например , это может быть деление на ноль , попытка доступа к несуществующему сегменту .
Существуют таблица стандартных интеловских исключений.
0 | Divide Error |
1 | Debug Exceptions |
2 | Intel reserved |
3 | Breakpoint |
4 | Overflow |
5 | Bounds Check |
6 | Invalid Opcode |
7 | Coprocessor Not Available |
8 | Double Fault |
9 | Coprocessor Segment Overrun |
10 | Invalid TSS |
11 | Segment Not Present |
12 | Stack Exception |
13 | General Protection Exception(Triple Fault) |
14 | Page Fault |
15 | Intel reserved |
16 | Coprocessor Error |
Что должна делать ISR , обрабатывающая исключение ?
Рассмотрим конкретный пример обратки исключения — деления на ноль. Этот пример со смешанным кодом на си и ассемблере.
C function — interrupt_0:
Фактически на каждое исключение нужно иметь отдельную функцию .
Exception Interrupt 14 — Page Fault
Если вы будете использовать paging , вам понадобится это исключение. Если вы попытаетесь получить доступ к странице , которая промаркирована как not-present, процессор сгенерирует это исключение.
Часть 3 — IDT
Что такое IDT?
IDT(Interrupt Descriptor Table) это массив дескрипторов (каждый длиной 8 байт) , который необходим для того , чтобы привязать прерывания и исключения к нужным ISR. IDT может хранить максимум 256 дескрипторов. Не обязательно инициализировать все 256 дескрипторов. Достаточно только те , которые вы собираетесь использовать. Инструкция LIDT инициализирует этот массив.
IDT Descriptor Format
Дескриптор IDT имеет 8-байтную длину.
31 | 15 | 14 | 12 | 7 | 4 | 0 |
Offset 31-16(word) | Present(1 bit) | DPL(2 bits) | 01110(5 bits) | 000(3 bits) | Not Used | 4 |
Selector31-16(word) | Offset 15-0(word) | 0 |
Несколько слов о бите Present , поле DPL , селекторе и смещении :
Present: 0 = not present, 1 = present. Если Present =0 , будет сгенерировано исключение.
DPL: Descriptor Privilege Level, 00 = ring0, 01 = ring1, 10 = ring2, 11 = ring3
Selector: Кодовый селектор , который будет использовать ISR
Offset: Адрес самой ISR.
Теперь посмотрим на реальный дескриптор. Установим DPL = ring0, present = 1, selector = 0x10, offset= 0x200000.
31 | 15 | 14 | 12 | 7 | 4 | 0 |
0x20 | 1 | 00 | 01110 | 000 | 00000 | 4 |
0x10 | 0x0000 | 0 |
Переведем это на NASM:
Теперь осталось сказать процессору , как воспользоваться всем этим.
Загрузка IDT с помощью LIDT
Мы загружаем IDT с помощью инструкции LIDT . LIDT в качестве параметра имеет указатель на структуру , которая описывает IDT, в которой содержится начальный адрес и число дескрипторов.
31 | 15 | 0 |
Limit 15-0(word) | 4 | |
Base 31-0(double word) | 0 |
Base: адрес самой IDT
Limit: длина IDT в байтах
Теперь рассмотрим пример с IDT , в которой 3 дескриптора :
2 метки — start_of_idt и end_of_idt , дадут нам возможность вычислить IDT pointer.
31 | 15 | 0 |
end_of_idt — start_of_idt — 1 | 4 | |
start_of_idt | 0 |
Переведем указатель на NASM:
И наконец сама загрузка :
Все в кучу
Теперь соберем все вместе и построим IDT , которая будет отлавливать 16 прерываний и передавать их в ISR , которая будет расположена по адресу 0x200000(окромя прерываний 2 и 15 — которые зарезервированы Intel).
Введение в драйверы устройств ввода/вывода
Обработка ISR и IST для драйверов устройств
Большинство периферийных устройств могут генерировать прерывания, чтобы получить обслуживание от операционной системы. Некоторыми примерами устройств, которые могут использовать прерывания, являются платы ПК, встроенные таймеры, устройства аудио-ввода, клавиатуры, сенсорные экраны, последовательные порты, и устройства указатели. Почти любой тип периферийного устройства может использовать прерывания в качестве основного метода инициирования действий ОС по обслуживанию.
Так как эти периферийные устройства могут вызывать или сигнализировать о прерываниях, их драйверы устройств должны обрабатывать прерывания, чтобы обслужить свои устройства. Физические прерывания (IRQ) являются аппаратными линиями связи, по которым устройства могут посылать сигналы прерываний в микропроцессор. Логические прерывания (SYSINTR) являются отображением IRQ, которое определяет OAL.
Когда обрабатывается прерывание, имеет место определенная последовательность событий. Необходимо написать для драйвера устройства запрос на обслуживание прерывания ( ISR ) и поток обслуживания прерывания ( IST ), помня о следующей последовательности событий.
- Когда происходит прерывание, микропроцессор переходит к обработчику исключительных ситуаций ядра.
- Обработчик исключительных ситуаций отключает все прерывания равного или более низкого приоритета микропроцессора, и затем вызывает соответствующий ISR для запроса физического прерывания (IRQ).
- ISR возвращает логическое прерывание в форме идентификатора прерывания обработчику прерываний и, обычно, маскирует прерывание устройства уровня платы.
- Обработчик прерываний снова включает все прерывания в микропроцессоре, за исключением текущего прерывания, которое остается замаскированным на плате, и затем сигнализирует о соответствующем событии IST .
- IST планируется, обрабатывает оборудование, и затем заканчивает обработку прерывания.
- IST вызывает функцию InterruptDone , которая в свою очередь вызывает в OAL функцию OEMInterruptDone .
Функция OEMInterruptDone снова включает текущее прерывание. Драйвер устройства должен выполнить при загрузке следующие действия:
- Регистрирует свой ISR в ядре. Драйвер должен зарегистрировать свой ISR в ядре, если драйвер не использует общую функцию OAL ISR для обработки своего прерывания. Драйвер должен зарегистрировать свой ISR в ядре, чтобы ядро вызывало ISR , когда происходит соответствующее физическое прерывание. Отобразить IRQ в SYSINTR в функции OEMInit из OAL. -или- Драйвер шины, который загружает драйвер, должен отобразить IRQ в SYSINTR , что делается в случае драйвера шины PCI.
- Если драйвер не устанавливает ISR , любые прерывания, сгенерированные устройством, обрабатываются используемым по умолчанию ISR , который устанавливается OAL в OEMInit .
Процесс регистрации обработчика прерываний регистрирует событие, которое ассоциируется с системным прерыванием SYSINTR . После загрузки драйвера устройства драйвер создает поток службы прерываний ( IST ), а затем вызывает InterruptInitialize для регистрации события. IST может затем использовать функцию WaitForSingleObject для ожидания этого события и регистрации его в обработчике прерываний. IST регистрируется для одного или нескольких логических прерываний ( SYSINTR ).
Если для определенного драйвера используется реализация компании Microsoft модельного драйвера устройства (MDD), вам не нужно будет писать код для регистрации прерывания. Слой MDD драйвера регистрирует драйвер для прерываний. Если создается монолитный драйвер, нужно реализовать код для регистрации IST драйвера в обработчике прерываний. Для этого используется функция CreateEvent для создания события и функция InterruptInitialize для соединения события с SYSINTR .
Если драйвер устройства должен остановить обработку прерывания, то драйвер может использовать функцию InterruptDisable . Когда драйвер вызывает эту функцию, обработчик прерываний удаляет ассоциацию между IST и указанным логическим прерыванием. Обработчик прерываний выполняет это, вызывая функцию OEMInterruptDisable для отключения прерывания. Драйвер, если понадобиться, может позже снова зарегистрироваться для прерывания.
Могут возникать ситуации, когда требуется передать информацию между процедурой службы прерываний ( ISR ) и потоком службы прерываний ( IST ). Например, потому что вызов IST всякий раз, когда приходит запрос прерывания (IRQ), является длительным процессом, можно спроектировать ISR с буферизацией данных IRQ перед вызовом IST .
ISR будет возвращать SYSINTR_NOP , пока буфер не будет заполнен, а затем вернет соответствующий идентификатор SYSINTR , когда ISR будет готова для выполнения IST . Во время выполнения IST она может выбирать данные, которые буферизировала ISR .
Для передачи данных между ISR и IST :
- Зарезервируйте физическую память для ISR в файле Config. bib . Config. bib содержит несколько примеров резервирования физической памяти для последовательного и отладочного драйверов.
- Используйте зарезервированную память в вызове ISR . Так как ISR выполняется в режиме ядра, ISR может обращаться к зарезервированной памяти для буферизации данных.
Вызовите функцию MmMapIoSpace в IST для отображения физической памяти в виртуальные адреса.
Функция MMMapIoSpace вызывает функции VirtualAlloc и VirtualCopy для отображения физической памяти в адреса виртуальной памяти, к которой может обращаться IST .
Можно также вызывать функции VirtualAlloc и VirtualCopy непосредственно. Например, можно выделить память вне пространства виртуальной памяти процесса, вызывая VirtualAlloc с параметрами, заданными следующим образом:
- dwSize >= 2 MB
- flAllocationType задается как MEM_RESERVE
- flProtect задается как PAGE_NOACCESS
В Windows Embedded CE устанавливаемая ISR может легко использовать данные совместно с IST , так как память может распределяться динамически, вместо резервирования в файле Config. bib . Поток службы прерываний ( IST ) является потоком, который выполняет большую часть обработки прерываний. ОС пробуждает IST , когда ОС получает прерывание для обработки. Иначе IST находится в спящем режиме. IST для клавиатуры, сенсорного экрана, мыши, и драйвера дисплея должны вызывать функцию SetKMode(TRUE) , чтобы позволить GWES писать в общую кучу памяти.
Чтобы ОС пробудила IST , IST должна ассоциировать объект события с идентификатором прерывания. Используйте функцию CreateEvent для создания объекта события. После обработки прерывания IST должна ждать следующий сигнал прерывания . Этот вызов обычно делается в цикле.
Когда происходит аппаратное прерывание, ядро сигнализирует о событии от имени ISR , и затем IST выполняет необходимые операции В/В на устройстве для сбора данных и их обработки. Когда обработка прерывания завершается, IST должна информировать ядро, чтобы снова включить аппаратное прерывание.
Обычно потоки IST выполняются с приоритетом выше нормального. Они могут задать для себя более высокий приоритет перед регистрацией своего события в ядре, вызывая функцию CeSetThreadPriority . Таблица 9.3 показывает функции, которые обычно использует IST .
Функция | Описание |
---|---|
InterruptInitialize | Соединяет событие с идентификатором прерывания ISR . |
WaitForSingleObject | Возвращает управление, когда указанный объект находится в сигнальном состоянии или когда истекает интервал перерыва. |
InterruptDone | Инструктирует ядро о повторном включении аппаратного прерывания, связанного с этим потоком. |
Следующий список содержит примеры того, что IST может сделать при запуске:
- Создать структуру для хранения значений прерываний.
- Использовать CreateEvent в качестве триггера IST .
- Прочитать значения IRQ и SysIntr из реестра и позволить OAL отобразить IRQ в значение SYSINTR перед загрузкой драйвера.
- Сохранить указатель для созданного потока.
Следующий пример кода драйвера устройства клавиатуры PS/2 из ..\WINCE600\Public\Common\OAK\Drivers\Keybd\Ps2_8042\Ps2keybd.cpp показывает типичную IST .
Следующий образец кода показывает реализацию функции KeybdIstLoop .