Новый обработчик прерываний


MS-DOS для программиста

4.3. Изменение таблицы векторов прерываний

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

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

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

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

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

Кроме того, операция изменения вектора прерывания должна быть непрерывной в том смысле, что во время изменения не должно произойти прерывание. Если вы, например, запишете новое значение смещения, а сегментный адрес обновить не успеете, то по какому адресу будет передано управление в случае прерывания и что при этом произойдет? Об этом можно только догадываться.

Функции MS-DOS для работы с таблицей прерываний

Для облегчения работы по замене векторов прерываний MS-DOS предоставляет в ваше распоряжение специальные функции, предназначенные для чтения элемента таблицы векторов прерывания и для записи в нее нового адреса. Если вы будете использовать эти функции, MS-DOS гарантирует, что операция по замене вектора будет выполнена правильно. Вам не надо заботиться о непрерывности процесса замены вектора прерывания.

Для чтения вектора используйте функцию 35h прерывания INT 21h . Перед ее вызовом регистр AL должен содержать номер вектора в таблице. После выполнения функции в регистрах ES:BX будет искомый адрес обработчика прерывания.

Для вектора с номером, находящимся в регистре AL, функция 25h прерывания INT 21h устанавливает новый обработчик прерывания. Адрес обработчика прерываний следует передать через регистры DS:DX.

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

Пользователям языка С доступны функции _dos_getvect и _dos_setvect . Первая функция получает адрес из таблицы векторов прерываний, вторая устанавливает новый адрес. Обе эти функции обращаются к описанным выше функциям 35h и 25h прерывания INT 21h .

Какие требования предъявляются к программе обработки прерывания?

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

Цепочки обработчиков прерываний

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

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

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

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

Для описания функции, выполняющей роль обработчика прерывания, следует использовать ключевое слово interrupt.

Такая функция завершается командой возврата из обработки прерывания IRET. Для нее автоматически генерируются команды сохранения регистров на входе и их восстановления при выходе из обработчика прерывания. Пример использования ключевого слова interrupt для определения функции обработки прерывания:

Функция обработки прерывания должна быть дальней функцией, так как таблица векторов прерываний содержит полные адреса в формате .

Ключевое слово interrupt используется также для описания переменных, предназначенных для хранения векторов прерываний:

Для установки своего обработчика прерываний используйте функцию _dos_setvec . Эта функция имеет два параметра — номер прерывания и указатель на новую функцию обработки прерывания.

В этом примере для прерывания с номером 16h (программное прерывание, предназначенное для чтения данных из клавиатуры) устанавливается новый обработчик прерывания my_key_intr.

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

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

Программа BEEPER

Программа BEEPER (листинг 4.1) — простой пример, который иллюстрирует применение всех трех перечисленных выше функций, предназначенных для работы с прерываниями.

Листинг 4.1. Файл beeper\beeper.cpp

Эта программа встраивает собственный обработчик прерывания таймера, который будет вызываться примерно 18,2 раза в секунду. Встраиваемый обработчик прерывания подсчитывает прерывания таймера и, если значение соответствующего счетчика кратно 20, громкоговоритель компьютера издает звуковой сигнал.

В конце работы новая программа обработки прерывания таймера вызывает старый обработчик с помощью функции _chain_intr .

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

Маскирование прерываний. Обработка прерываний в мультипрограммной системе

Читайте также:

  1. II. Игра в системе трансляции деятельности и обучения
  2. III. Последующая обработка.
  3. IV. ОБРАБОТКА РЕЗУЛЬТАТОВ ГРУППОВОГО И ИНДИВИДУАЛЬНОГО ОБСЛЕДОВАНИЯ, ЗАПОЛНЕНИЕ ХАРАКТЕРИСТИКИ, ПОСТРОЕНИЕ ИНДИВИДУАЛЬНОГО ПРОФИЛЯ ГОТОВНОСТИ
  4. Автоматизированной системе управления
  5. Административная реформа в системе и структуре федеральных органов государственного экологического контроля 1 страница
  6. Административная реформа в системе и структуре федеральных органов государственного экологического контроля 2 страница
  7. Административное право в правовой системе
  8. Административное судопроизводство в системе судебной власти
  9. Азия в ХХI веке – перспективы эволюции и положения в системе международных отношений
  10. Анодно-механическая обработка
  11. Анодно-механическая обработка.
  12. АРТПЕДАГОГИКА В СИСТЕМЕ МНОГОЛЕТНЕЙ СПОРТИВНОЙ ПОДГОТОВКИ

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

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

Упорядоченное обслуживание запросов прерываний наряду со схемами приоритетной обработки может выполняться механизмом маскирования запросов. Программное управление специальными регистрами маски – маскирование сигналов прерывания независимо от уровня приоритета – позволяет реализовать различные дисциплины обслуживания:

§ с относительными приоритетами, т.е. обслуживание не прерывается даже при поступлении запросов с более высокими приоритетами. Только после окончания обслуживания данного запроса обслуживается новый запрос с наивысшим приоритетом. Для организации такой дисциплины необходимо в программе обслуживания данного запроса наложить маски (запрет) на все остальные сигналы прерываний, или просто отключить систему прерываний;

§ с абсолютными приоритетами – всегда обслуживается прерывание с наивысшим приоритетом. Для реализации этого режима необходимо на время обработки прерывания замаскировать все запросы с более низким приоритетом. При этом возможно многоуровневое прерывание, т.е. прерывание программ обработки прерываний. Число уровней прерывания в этом режиме изменяется и зависит от приоритета запроса. Если процессор работает по такой схеме, то в одном из своих внутренних регистров он поддерживает переменную, фиксирующую уровень приоритета обслуживаемого в данный момент прерывания.

§ по принципу стека (по дисциплине LCFSlast come first served)– запросы с более низким приоритетом могут прерывать обработку прерывания с более высоким приоритетом. Для реализации такой дисциплины необходимо не накладывать маски ни на один сигнал прерывания и не выключать систему прерываний.

Отметим, что для правильной реализации последних двух дисциплин нужно обеспечить полное маскирование системы прерываний при выполнении шагов 1–4 и 6–7, чтобы не потерять запрос и правильно его обслужить. При этом многоуровневое прерывание должно происходить на этапе собственно обработки прерывания, а не на этапе перехода с одного процесса на другой.

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

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

На первом этапе при появлении запроса на прерывание идентификация сигнала выполняется специальным системным программным модулем, который называется супервизором (или диспетчером) прерываний. Он ненадолго запрещает все прерывания, сохраняет контекст прерываемого процесса и выясняет причину прерывания.

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

Замечание. Обработчик прерываний (независимо от его приоритета) всегда имеет приоритет более высокий, чем поток, выполняемый в обычной последовательности, определяемой планировщиком потоков.

После выполнения программы обработки прерывания управление снова передается супервизору (третий этап), на этот раз уже на тот модуль, который занимается диспетчеризацией задач. И уже диспетчер задач в соответствии с принятым режимом распределения процессорного времени восстановит контекст той задачи, которой решено будет выделить процессор.

Контрольные вопросы

1. Что такое прерывание? Когда оно было введено в действие и с какой целью?

2. Из каких этапов состоит механизм обработки прерывания?

3. Можно ли запретить все прерывания на время работы обработчика прерываний? Почему?

4. К какому типу прерываний относятся прерывания клавиатуры? Прерывания таймера? Возникновение деления на ноль в ходе выполнения программы? Обращение к запрещённой странице памяти, занятой кодом ОС?

5. Приведите примеры аппаратных прерываний.

6. Могут ли в ходе нормальной работы программы возникать исключительные ситуации? Почему?

7. Какие существуют способы реализации механизма аппаратных прерываний? В чём их различие?

8. Что такое маскирование прерываний и для чего оно может применяться?

9. Каким образом можно реализовывать различные дисциплины обслуживания прерываний с помощью маскирования?

10. В чём различия между дисциплиной обслуживания прерываний с относительными приоритетами и с абсолютными?

11. Где содержится информация об уровне приоритета текущего обработчика прерываний?

12. Каковы функции супервизора прерываний?

13. Чем отличается реализация механизма прерывания в мультипрограммной системе от однопрограммной системы?

14. Когда происходит возврат к выполнявшейся до поступления сигнала прерывания программе в мультипрограммной системе?

Дата добавления: 2014-12-16 ; Просмотров: 4608 ; Нарушение авторских прав? ;

Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет

Новый обработчик прерываний

Программы обработки прерываний (или попросту обработчики прерываний) относятся к важнейшим программным средствам персональных компьютеров. Запросы на обработку прерываний могут иметь различную природу. Прежде всего, различают аппаратные прерывания от периферийных устройств или других компонентов системы и программные прерывания, вызываемые командой int, которая используется, в частности, для программного обращения к функциям DOS и BIOS. Сигналы, возбуждающие аппаратные прерывания, могут инициироваться цепями самого процессора, например, при попытке выполнения операции деления на ноль (такие прерывания называются внутренними, или отказами), а могут приходить из периферийного оборудования (внешние прерывания). Внешние аппаратные прерывания вызываются, например, сигналами микросхемы таймера, сигналами от принтера или контроллера диска, нажатием или отпусканием клавиши. Таким образом, можно говорить о прерываниях трех типов: внутренних, внешних и программных. Независимо от источника, действия процессора по обслуживанию поступившего прерывания всегда выполняются одинаково, как для аппаратных, так и для программных прерываний. Эти действия обычно называют процедурой прерывания. Подчеркнем, что здесь идет речь лишь о реакции самого процессора на сигнал прерывания, а не об алгоритмах обработки прерывания, предусматриваемых пользователем в обработчике прерываний.
Объекты вычислительной системы, принимающие участие в процедуре прерывания, и их взаимодействие показаны на рис. 25.1.
Самое начало оперативной памяти от адреса 0000h до 03FFh отводится под векторы прерываний — четырехбайтовые области, в которых хранятся адреса программ обработки прерываний (ПОП). В два старших байта каждого вектора записывается сегментный адрес ПОП, в два младших — относительный адрес точки входа в ПОП в сегменте. Векторы, как и соответствующие им прерывания, имеют номера, причем вектор с номером 0 располагается, начиная с адреса 0, вектор 1 — с адреса 4, вектор 2 — с адреса 8 и т.д. Вектор с номером N занимает, таким образом, байты памяти от N*4 до N*4+3. Всего в выделенной под векторы области памяти помещается 256 векторов.

рис. 4.1. Процедура прерывания

Получив сигнал на выполнение процедуры прерывания с определенным номером, процессор сохраняет в стеке выполняемой программы текущее содержимое трех регистров процессора: регистра флагов, CS и IP. Два последних числа образуют полный адрес возврата в прерванную программу. Далее процессор загружает CS и IP из соответствующего вектора прерываний, осуществляя тем самым переход на ПОП.
Программа обработки прерывания обычно заканчивается командой возврата из прерывания iret (interrupt return, возврат из прерывания), выполняющей обратные действия — загрузку IP, CS и регистра флагов из стека, что приводит к возврату в основную программу в ту самую точку, где она была прервана.
Большая часть векторов прерываний предназначена для выполнения определенных действий и автоматически заполняется адресами системных программ при загрузке системы;
часть векторов зарезервирована для будущих применений, а часть (конкретно с номерами 60h. 66h) свободна и может использоваться в прикладных программах.
Для того чтобы прикладной обработчик получал управление в результате прерывания, его адрес следует поместить в соответствующий вектор прерывания. Хотя содержимое вектора прерываний можно изменить простой командой mov, однако предпочтительнее использовать специально предусмотренную функцию DOS 25h. При вызове функции 25h в регистр AL помещается номер модифицируемого вектора, а в регистры DS:DX — полный двухсловный адрес нового обработчика.
Рассмотрим методику использования в прикладной программе прерывания пользователя.

Пример 4.1. Обработка прерываний пользователя

Процедура new_65h, вызываемая с помощью программного прерывания (для которого выбран вектор 65h), выполняет простую операцию — очищает экран, накладывая на него окно с заданным атрибутом.
В основной программе, прежде всего заполняется вектор прерывания 65h. Поскольку функция заполнения вектора 25h требует, чтобы адрес прикладного обработчика содержался в парс регистров DS:DX, a DS у нас указывает на сегмент данных, перед вызовом DOS в DS следует занести сегментный адрес того сегмента, в котором находится обработчик, т.е., в нашем случае, общего сегмента команд. Этот адрес извлекается из CS.
Далее в бесконечном цикле выполняется вызов нашего обработчика, позиционирование курсора с помощью функции 02h BIOS и вывод на чистый экран строки символов (функцией 0Ah BIOS). Эта функция не позволяет задавать атрибуты выводимых символов. Символы приобретают атрибут тех позиций, куда они выводятся, т.е., в нашем случае, атрибут окна. После вывода на экран строки выполняется изменение кода символов и номера строки экрана, куда эти символы выводятся.
Функция DOS 08h (ввод символа без эха), включенная в цикл, выполняет две задачи. Bo-первых, она останавливает выполнение программы и позволяет изучить содержимое экрана в каждом шаге цикла. Для того, чтобы продолжить выполнение программы, достаточно нажать на любую клавишу. Во-вторых, эта функция, будучи чувствительна к вводу с клавиатуры сочетания /C, позволяет завершить программу, которая в противном случае выполнялась бы вечно.

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

Структура обработчика прерываний и его взаимодействие с остальными компонентами программного комплекса определяются рядом факторов, из которых важнейшими являются следующие:
• прерывания, инициализирующие обработчик, могут быть аппаратными (от периферийных устройств) или программными (команда int).
• обработчик может входить в состав прикладной программы или представлять собой самостоятельную единицу. В последнем случае он относится к специальному классу резидентных программ;
• вектор обрабатываемого прерывания может быть свободным и использоваться системой или какой-либо резидентной прикладной программой;
• если вектор уже используется системой, т.е. в составе DOS имеется системный или прикладной разработчик прерываний с данным номером, то новый обработчик может полностью заменять уже загруженный (превращая его тем самым фактически в бесполезную программу) или “сцепляться” с ним;
• в случае сцепления с загруженным ранее обработчиком новый обработчик может выполнять свои функции до уже имеющегося в системе или после него.
В настоящем разделе будут рассмотрены общие вопросы обработки прерываний на примере простого обработчика прерывания 1Ch.
Для того чтобы прикладные программы могли использовать сигналы таймера, не нарушая при этом работу системных часов, в программу BIOS, обслуживающую аппаратные прерывания от таймера, поступающие через вектор 08, включен вызов int 1Ch, передающий управление на программу-заглушку BIOS, которая содержит единственную команду iret (рис. 46.1.). пользователь может записать в вектор 1Ch адрес прикладного обработчика сигналов таймера и использовать в своей программе средства реального времени. Естественно, перед завершением программы следует восстановить старое значение вектора 1Ch.

Рис. 4.2 Прикладная обработка прерываний от таймера

При рассмотрении методики включения в программу процедур-подпрограмм мы отмечали, что порядок расположения процедур в программе не влияет на ход ее выполнения. Важно лишь так скомпоновать текст программы, чтобы подпрограммы никогда не активизировались “сами по себе”, иначе, чем в результате выполнения команды call в вызывающей программе. Это правило относится и к обработчикам прерываний, включаемым в состав программы. Текст обработчика можно расположить в любом месте программы, обеспечив лишь невозможность случайного перехода на его строки не в результате прерывания, а по ходу выполнения основной программы. Обычно обработчики располагаются либо в начале, либо в конце текста программы. В примере 46.1 текст обработчика идет вслед за текстом основной программы.
Другое замечание относится к оформлению программы обработчика. Так же, как и в случае подпрограмм, обработчик прерывания может образовывать процедуру (что наглядно), но может начинаться просто с метки. Важно лишь, чтобы последней выполняемой командой обработчика была команда iret.

Пример 4.2. Обработчик прерываний от таймера.

В рассмотренном примере используются макрокоманды из файла mac.mac. Для того, чтобы сделать доступными эти макрокомнда в нашей программе, используем директиву include. Файл mac.mac имеет следующее содержание:

Главная процедура начинается, как обычно, с инициализации сегментного регистра DS. Перед тем, устанавливать собственный обработчик какого-либо прерывания, следует сохранить его исходный (системный) вектор, чтобы перед завершением программы вернуть систему в исходное состояние. Для получения содержимого вектора 1Ch используется функция DOS 35h, которая возвращает содержимое указанного вектора в регистрах ES:BX. Для сокращения объема исходного текста программы и номер функции, и номер требуемого вектора заносятся в регистры АН и AL одной командой (предложение 7). Исходный вектор сохраняется в двухсловной ячейке old_1ch, объявленной директивой dd (define double, определить двойное слово) в сегменте данных программы. Однако команды пересылки не могут работать с двойными словами, поэтому сохраняемый вектор засылается из регистров ES:BX в память пословно, сначала в младшую половину ячейки old_1ch (предложение 9), затем в старшую, естественно, равен old_lch+2 (предложение 10). Поскольку ячейка old_1ch объявлена с помощью директивы dd, для обращения к ее составляющим (словам) необходимо включать в команду описатели word ptr (word pointer, указатель на слово), которые как бы отменяют на время трансляции команды первоначальное описание ячейки.
Сохранив вектор, мы можем приступить к заполнению его адресом нашего обработчика. Для этого используется функция DOS 25h, которая, как уже отмечалось, требует указания в регистре AL номера заполняемого вектора, а в регистрах DS:DX полного адреса обработчика, который и будет записан в указанный нами вектор. Однако регистр DS настроен на сегмент данных программы. Кстати, если бы это было не так, мы не могли бы выполнить предложения 9 и 10, так как поля данных программы адресуются через регистр DS. Поэтому на время выполнения функции 25h нам придется изменить содержимое DS, настроив его на тот сегмент, в котором находится процедура обработчика, т.е. на сегмент команд. Это и выполняется в предложениях 13. 15. Содержимое DS сохраняется в стеке, а затем в него через стек заносится содержимое регистра CS, который, очевидно, указывает на сегмент команд. После возврата из DOS в программу исходное содержимое DS восстанавливается (предложение 17).
Начиная с этого момента, прерывания от таймера, приводящие к выполнению в системной программе BIOS команды int 1Ch, будут активизировать 18,2 раз в секунду программу нашего обработчика. При этом вся наша программа должна находиться в памяти и что-то делать, так как если она завершена, то она и обработчик уйдут из памяти. Для задержки программы в ней предусмотрен многократный, в цикле вывод на экран строки текста (предложения 18. 21).
Перед завершением программы необходимо с помощью той же функции 25h восстановить исходное содержимое вектора 1Ch. Для загрузки регистров DS:DX требуемым адресом в примере 46.1 используется удобная команда Ids (load pointer using DS, загрузка указателя с использованием DS). В качестве операндов для этой команды указывается один из регистров общего назначения и двухсловное поле памяти с искомым адресом. Следует иметь в виду, что после выполнения этой команды старое содержимое регистра DS теряется. В нашем примере оно больше не нужно, так как, выполнив восстановление вектора, программа завершается (предложение 25). Вообще же перед выполнением команды Ids исходное содержимое регистра DS следует сохранить в стеке.
Рассмотрим теперь программу обработчика прерывания от таймера. Программа начинается с сохранения в стеке регистров, которые будут использоваться в обработчике. Это чрезвычайно важное действие, так как переход на программу обработчика осуществляется по команде int1ch из системной программы обработки прерываний от таймера. При выполнении процедуры прерывания процессор настраивает должным образом только регистры CS и IP. Содержимое всех остальных регистров (в том числе сегментных) отражает состояние системной программы, и если оно будет изменено, то после возврата из нашего обработчика в вызвавшую его системную программу она перестанет функционировать. В нашем обработчике используются лишь регистры АХ и ES, которые и сохраняются в стеке (предложения 28-29).
Далее регистр ES настраивается на адрес видеобуфера (предложения 30-31), а в регистр АХ помещается код ASCII выводимого на экран символа вместе с его атрибутом (предложение 32). В последнем предложении используется важная возможность замены сегмента. Как уже отмечалось, при обращении к памяти по умолчанию используется сегментный регистр DS, т.е. предполагается, что адресуемая ячейка находится в том сегменте, на который в настоящий момент указывает DS, в нашем же случае в момент выполнения этой команды DS почти, наверное указывает на какой-то сегмент программы BIOS. Для адресации к нашим данным можно сохранить содержимое DS и настроить его на наши данные. Однако можно поступить проще, именно, ввести в команду префикс замены сегмента CS: и тем самым указать транслятору, чтобы он в данной команде использовал адресацию через регистр CS. Но и данные в этом случае следует разместить не в сегменте данных, к которому нет доступа, а в сегменте команд. У нас так и сделано. Ячейки sym1 и sym2, к которым обращается обработчик, расположены в конце процедуры new_1ch в пределах сегмента команд (предложения 39-40).
Вывод одного и того же символа в одно и то же место экрана приведет к тому, что мы не будем знать, работает ли наш обработчик. В нашем примере предусмотрена периодическая смена атрибута символа, что делает символ мерцающим. Для этого при каждом проходе программы обработчика ячейки sym1 и sym2 взаимно обмениваются своим содержимым. В результате на экран выводится то один, то другой код. Для обмена содержимого регистров или ячеек предусмотрена специальная команда xchg (exchange, обмен). Поскольку в микропроцессорах 80х86 запрещены команды с адресацией к памяти в обоих операндах, обмен приходится осуществлять через регистр АХ.
После восстановления сохраненных в стеке регистров работа обработчика завершается командой iret, которая передает управление назад в вызвавшую наш обработчик программу BIOS. Когда эта программа дойдет до своего завершения, она выполнит команду iret и управление вернется в нашу программу в ту (неопределенную) точку, в которой она была прервана сигналом таймера.
Кроме того при необходимости работать с реальным временем можно использовать различные функции – одна из них AH=00h. Формат её использования имеет следующий вид:
INT 1Ah АН = 00h — Считать значение счетчика времени.
Ввод: АН = 00h
Вывод: CX:DX=значение счетчика, AL=байт переполнения счетчика.
Приведём пример программы, использующей эту функцию. Программа выводит на экран прямоугольник, который меняет цвет с периодичностью 3 секунды, выход по F10. Задержка реализована в макрокоманде. Пример 4.3

Общий алгоритм работы макроопределения задержки следующий: результат работы функции 00h возвращается в регистрах СX:DX. В данной программе работаем по регистру DX – младшие значения времени. Значение в регистре DX изменяется 18.2 раза в секунду, поскольку именно с такой периодичностью таймер вызывает аппаратное переывание. Сохраняем в регистре bx значение на единицу больше, чем в dx, затем сравниваем эти значения(строка 20). Всё это происходит в цикле. Как только значение dx измениться(прошло 18.2 сек) происходит выход из цикла. Данный цикл заключён во внешний цикл zd(строка 9). И поскольку внешний цикл cikl(строка 11) выполняется 18.2 раза в сек., то если внешний выполнить 18 раз, то общая задержка примерно равна 1 сек. Параметром, задающим количество повторений внешнего цикла, является параметр макрокоманды time. Т.е. команда Dely 18 – задержка примерно в 1 сек. для данного макроопределения.

Пишем обработчики прерываний

Для начала выясним, какие прерывания и с какой целью наш вирус будет перехватывать.

Во-первых, необходимо перехватить прерывание Int 21h. Дело в том, что наш вирус является резидентным, и должен заражать файлы при тех или иных событиях в вычислительной системе. Очень многие вирусы активизируются, например, при смене текущего диска или каталога. Этот метод является весьма удачным, и мы реализуем именно его. Но для этого нужно знать, когда именно выполняются смена каталога или диска. Единственный способ узнать о таком событии — это перехватить прерывание Int 21h на себя, и при каждом его вызове проверять, какая именно функция вызывается. Так мы и сделаем.

Во-вторых, нам не обойтись без перехвата Int 13h (см п. 2.13).

Илон Маск рекомендует:  Что такое код asp deletebackup

В-третьих, поскольку наш вирус будет пользоваться функциями DOS, которые работают с диском в резидентном режиме, необходимо знать, когда можно безопасно обращаться к этим функциям. Для этого следует перехватить прерывание Int 28h, которое всегда вызывается только при выполнении DOS реентерабельной секции своего кода. Иными словами, при возникновении прерывания Int 28h можно смело пользоваться любыми функциями DOS.

Далее, для проверки наличия вирусного кода в памяти наш вирус будет использовать так называемое мультиплексное прерывание — Int 2fh, и поэтому мы должны перехватить и его (см п. 2.7).

И, наконец, мы должны написать обработчик критической ошибки. Она возникает,например, если мы попытаемся записать информацию на вынутую из дисковода дискету. Наш вирус должен перехватить прерывание по критической ошибке (Int 24h) и выполнить его обработку.

Обработчик Int 13h

Как мы уже выяснили, этот обработчик должен записывать в ячейку » tg_13h » значение » 1 «, если в данный момент выполняется прерывание Int 13h, или значение » 0 » — в противном случае.

К сожалению, в MS DOS отсутствует какое — либо средство, позволяющее узнать, когда именно активно прерывание Int 13h. И поэтому единственный способ решения этой задачи — установка на Int 13h так называемого » фильтра «, который отслеживал бы все вызовы вышеуказанного прерывания.

Самое простое решение — это перехватить Int 13h на себя, а в самом обработчике вызвать системный обработчик как дальнюю процедуру. Конечно, перед этим нужно зап??сат?? в » tg_13h» единицу — это будет индикатором выполнения Int 13h в данный момент. Когда системный обработчик выполнится, управление вновь получит » фильтр «. Поскольку Int 13h уже выполнилось, можно сбросить в «0» переменную tg_13h.

; _______________________________________________ ;| | ;| Напишем новые обработчики INT 13h, INT 21h, | ;| INT 24h и INT 2fh. .. | ;|_______________________________________________| to_new_13h equ $-vir new_13h: jmp cs:start_13h tg_13h db 0 ax_13h dw 0 cs_13h dw 0 ip_13h dw 0 start_13h: mov cs:tg_13h — 110h,1 pushf db 9ah ;Код команды old_13h dw 0 ; » CALL «. .. old_13h_2 dw 0 mov cs:ax_13h — 110h,ax;Поместим новый pop ax ;флаг на место mov cs:ip_13h — 110h,ax;старого ( CF ) pop ax mov cs:cs_13h — 110h,ax pop ax pushf mov ax,cs:cs_13h — 110h push ax mov ax,cs:ip_13h — 110h push ax mov ax,cs:ax_13h — 110h mov cs:tg_13h — 110h,0 iret

Здесь константа » to_new_13h » показывает смещение от начала вирусного кода до начала обработчика.

Хотелось бы обратить ваше внимание на одну особенность. Она состоит в том, что прерывания Int 21h и Int 13h возвращают в регистре AX код ошибки, а бит CF регистра флагов используется как индикатор этой ошибки.

Пусть, например, при получении фильтром управления бит CF имел значение FLAG 1, а регистры CS и IP имели значения CS 1 и IP 1.Тогда команда «pushf» занесет значение FLAG 1 в стек. Команда «call» поместит в стек значения CS 1 и IP 1,после чего управление получит системный обработчик. Этот обработчик занесет в стек значение FLAG 2, и при своем завершении выполнит команду «iret». Команда «iret» снимет с вершины стека значения IP 1,CS 1 и FLAG2. Теперь уже наш фильтр сбросит в » 0 » переменную «tg_13h»,и командой » iret » передаст управление прерванной программе. Но дело в том, что эта команда извлечет из стека значения IP и CS, которые имели место в момент вызова прерывания Int 13h, а также регистр флагов FLAG 1. Таким образом, из стека будет извлечен FLAG 1 вместо FLAG 2! Чтобы этого не произошло, мы должны поместить в стек FLAG 2 вместо FLAG 1. Именно для этого предназначены команды, записанные после ячейки » old_13h_2 «. Работа этих команд особых пояснений не требует. Мы просто «добираемся» до нужной ячейки в стеке, последовательно считывая предшествующие. Можно, конечно, написать более эффективный фрагмент,зато выбранный нами метод достаточно прост.

Обработчик Int 21h

Рассмотрим теперь создание обработчика прерывания Int 21h. Как мы договорились, он должен помещать «единицу» в ячейку » tg_infect «, если DOS выполняет смену текущего каталога или диска (см п. 2.5). Поэтому напишем » фильтр «, который будет проверять, какая именно функция DOS вызвана в тот или иной момент:

;————————————————- to_new_21h equ $-vir new_21h: jmp cs:start_21h tg_infect db 0 start_21h: pushf push di push es xor di,di ;Перехват mov es,di ;INT 24h в рези- mov di,90h ;дентном режиме mov word ptr es:[di],to_new_24h mov es:[di+2],cs cmp ah,03bh ;Активизировать ;вирус ? jne cs:new_cmp_1 mov cs:tg_infect-110h,1;Да — взводим ;триггер. .. new_cmp_1: cmp ah,00eh jne cs:to_jump mov cs:tg_infect — 110h,1 to_jump: pop es pop di popf db 0eah ;Переход на ста- old_21h dw 0 ;рый обработчик old_21h_2 dw 0 ;INT 21h. ..

Поскольку при вызове функции DOS в регистре AH задается ее номер, достаточно просто проанализировать его и » выловить » нужные значения.Наш вирус будет реагировать на смену текущего каталога (AH=03Bh), и смену текущего диска (AH=0Eh). Эти числа и пытается обнаружить » фильтр «.

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

Помимо решения своей конкретной задачи, написанный нами обработчик используется для перехвата прерывания Int 24h.Делается это прямым обращением к таблице векторов прерываний. Так же перехватывает прерывания и секция инициализации при установке вируса в память. Правда, вы можете спросить, зачем потребовалась такая сложная методика перехвата, и почему бы не выполнить его в секции инициализации? Дело в том, что такой прием будет «работать» только в MS DOS. WINDOWS 95, например, постоянно восстанавливает вектор Int 24h, что делает бессмысленным изменение вектора » только один раз «. Трудно сказать, зачем в WINDOWS 95 принято восстанавливать вектор. Вероятно, это сделано для надежности работы системы. При создании резидентного EXE-вируса мы поговорим еще об одной » странности » этой популярной операционной системы, которая помешает нам сделать вирусную программу «невидимой» для антивирусных средств.

Обработчик Int 24h

Этот обработчик должен устанавливать собственную реакцию на критическую ошибку. Вызывается он очень редко, поэтому просто сделаем так,чтобы при появлении ошибки не происходило » зависание «. Для этого достаточно вернуть управление прерванной программе, поместив предварительно в регистр AL код «3»:

;————————————————- to_new_24h equ $ — vir new_24h: mov al,3 ;Вернем програм- iret ;ме управление

Обработчик Int 2Fh

Напишем обработчик Int 2Fh. Мы договорились использовать это прерывание для проверки наличия вируса в памяти. Напомним, что секция инициализации для решения указанной задачи вызывает Int 2Fh c такими параметрами:

AX = 0F000h BX = 01997h.

Если вирус уже инсталлирован в память, его обработчик должен вернуть AL = 0FFh, это значение и анализирует секция инициализации при запуске зараженшой программы. Исходя из всего сказанного, можно написать такой фрагмент:

;————————————————- to_new_2fh equ $ — vir new_2fh: pushf cmp ax,0f000h jne cs:not_our cmp bx,1997h jne cs:not_our mov al,0ffh popf iret not_our: popf db 0eah old_2fh dw 0 old_2fh_2 dw 0

Если вызывается прерывание Int 2Fh с параметрами, отличными от AX = 0F000h и BX = 01997h, вирусный обработчик просто возвращает управление системному. В противном случае управление передается прерванной программе, причем в этом случае AL будет равно 0FFh.


Обработчик Int 28h

Строго говоря, мы его уже написали (см. п. 2.5, п. 2.6 и т.д.). Именно он занимается поиском и заражением файлов,пользуясь для этого функциями DOS. Но так как эти функции используются тогда, когда активно прерывание Int 28h, ничего страшного произойти не должно.

Область данных вируса

Теперь мы можем привести все данные, с которыми работает наш вирус:

Последнее изменение этой страницы: 2020-07-16; Нарушение авторского права страницы

ТЕМА 2.2: ОБРАБОТКА ПРЕРЫВАНИЙ

В архитектуре ПЭВМ базовая система ввода/вывода (БСВВ) занимает особое место. Ее можно рассматривать, с одной стороны, как составляющую часть аппаратных средств, с другой стороны, это система является одним из программных модулей ОС.

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

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

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

При прерывании ОС сохраняет состояние процессора –значения регистров и значение счетчика команд (program counter – PC)– адреса прерванной команды.Обработчик прерывания в ОС определяет по содержимому сегмента объектного кода, какого вида прерывание возникло и какие действия по его обработке следует предпринять. Среди возможных видов прерываний, кроме фиксации различных ошибок, имеются также прерывания по таймеру– периодические прерывания через определенный квант времени, предназначенные для опроса устройств (polling)– действий операционной системы по периодической проверке состояния всех портов и внешних устройств, которое может меняться с течением времени: например, к USB-порту была подключена флэшка; принтер закончил печать и освободился, и т.д. ОС выполняет реконфигурацию системы и корректирует системные таблицы, хранящие информацию об устройства

Механизм прерывания обеспечивается соответствующими аппаратно-программными средствами компьютера.

Любая особая ситуация, вызывающая прерывание, сопровождается сигналом, называемым запросом прерывания (ЗП). Запросы прерываний от внешних устройств поступают в процессор по специальным линиям, а запросы, возникающие в процессе выполнения программы, поступают непосредственно изнутри микропроцессора. Механизмы обработки прерываний обоих типов схожи. Рассмотрим функционирование компьютера при появлении сигнала запроса прерывания, опираясь в основном на обработку аппаратных прерываний (рис. 13).

Рис. 13. Выполнение прерывания в компьютере:

tр — время реакции процессора на запрос прерывания;

tс — время сохранения состояния прерываемой программы и вызова обработчика прерывания;

tв — время восстановления прерванной программы.

После появления сигнала запроса прерывания ЭВМ переходит к выполнению программы — обработчика прерывания. Обработчик выполняет те действия, которые необходимы в связи с возникшей особой ситуацией. Например, такой ситуацией может быть нажатие клавиши на клавиатуре компьютера. Тогда обработчик должен передать код нажатой клавиши из контроллера клавиатуры в процессор и, возможно, проанализировать этот код. По окончании работы обработчика управление передается прерванной программе.

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

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

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

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

Логические, или процессорные, прерывания возникают при различных нестандартных ситуациях в работе основного микропроцессора – делении на нуль, переполнении регистров, появлении «точки останова» и др.

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

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

В аппаратных компонентах машины в самой ДОС и в прикладных программах могут вырабатываться прерывания, которые нужно обслуживать. На БСВВ возлагается задача обслуживания прерываний нижнего уровня – тех, которые требуют непосредственного управления аппаратными компонентами. Этим прерываниям присвоены номера с 0 по 31 (шестнадцатеричные номера 0 – 1F). Другие прерывания – с номерами 32 – 63 (шестнадцатеричные номера 20 – 3F) – относятся к более высокому уровню, и их обслуживание возлагается на другие модули ОС.

В табл. 3 приведен общий перечень прерываний, обслуживаемых БСВВ. В реальных программах на языке ассемблера и в технической литературе по ОС прерывания идентифицируются шестнадцатеричиыми кодами. Из анализа табл. 3 видно, что обслуживаемые БСВВ прерывания соответствуют базовым операциям по управлению внешними устройствами – дисплеем, клавиатурой, НГМД, принтером, коммуникационными каналами. При этом подпрограммы, входящие в БСВВ, выполняют операции нижнего уровня. Так, например, обслуживание НГМД включает возможность начальной установки магнитных головок, проверки текущего статуса устройства, прямого чтения и записи заданных секторов диска, верификации прочитанных или записанных данных и, наконец, форматирования (начальной разметки) дисков.

Таблица 3. Прерывания, обслуживаемые БСВВ

Деся- тичный номер Шестнадца- теричный номер Обслуживаемая ситуация или выполняемая функция
Деление на ноль
Перевод микропроцессора в пошаговый режим
Падение напряжения питания
Появление точки останова в последовательности команд
Переполнение регистров арифметического устройства
Печать графической копии экрана
Зарезервировано
Зарезервировано
Сигнал от счетчика времени – таймера
Сигнал от нажатия клавиши на клавиатуре
А Зарезервировано
Деся- тичный номер Шестнадца- теричный номер Обслуживаемая ситуация или выполняемая функция
В Зарезервировано
С Зарезервировано
D Зарезервировано
Е Сигнал об окончании обмена с НМД
F Зарезервировано для обслуживания принтера
Управление дисплеем
Запрос списка подсоединенного оборудования
Запрос размера физической памяти
Управление НМД
Управление коммуникационным адаптером
Управление магнитофоном и другими устройствами
Управление клавиатурой
Управление принтером
Обращение к встроенному в ПЗУ бейсику
Перезапуск системы
Запрос/установка текущего времени и даты
1D Адрес таблицы параметров инициализации дисплея.
1E Адрес таблицы параметров НГМД
1F Адрес таблицы символов с кодами 128-255

Некоторые из указанных в табл. 3 прерываний обеспечивают доступ к нескольким взаимосвязанным функциям. Каждая функция идентифицируется своим шестнадцатеричным номером (кодом) и обеспечивает выполнение некоторой частной операции.

Так, например, прерывание 19 (управление НГМД и НМД) открывает доступ к 18 функциям с кодами 0-17):

0 — начальная установка (сброс диска),

1 — выдача текущего статуса диска,

2 — чтение группы (блока) секторов с одной дорожки,

3 — запись группы секторов на одну дорожку,

4 — верификация после чтения или записи,

5 — форматирование дорожки (запись меток секторов),

8 — выдача текущих параметров накопителя,

9 — инициализация таблицы параметров фиксированного диска,

А — «длинное» чтение,

В — «длинная» запись,

С — поиск нужной дорожки,

D — начальная установка диска,

10 — проверка готовности диска,

11 — калибровка диска,

14 — диагностика контроллера,

15 — выдача типа накопителя,

16 — изменение статуса диска,

17 — установка типа накопителя.

Глубина прерывания — максимальное число программ, которые могут прерывать друг друга. Глубина прерывания обычно совпадает с числом уровней приоритетов, распознаваемых системой прерываний. Работа системы прерываний при различной глубине прерываний (n) представлена на рис. 10. Здесь предполагается, что с увеличением номера запроса прерывания увеличивается его приоритет.

Рис. 14. Работа системы прерываний при различной глубине прерываний

При поступлении запроса прерывания компьютер выполняет следующую последовательность действий:

1) определение наиболее приоритетного незамаскированного запроса на прерывание (если одновременно поступило несколько запросов);

2) определение типа выбранного запроса;

3)сохранение текущего состояния счетчика команд и регистра флагов;

4) определение адреса обработчика прерывания по типу прерывания и передача управления первой команде этого обработчика;

5) выполнение программы — обработчика прерывания;

6)восстановление сохраненных значений счетчика команд и регистра флагов прерванной программы;

7) продолжение выполнения прерванной программы.

Этапы 1-4 выполняются аппаратными средствами ЭВМ автоматически при появлении запроса прерывания. Этап 6 также выполняется аппаратно по команде возврата из обработчика прерывания.

Переход к соответствующему обработчику прерывания осуществляется (в реальном режиме работы микропроцессора) посредством таблицы векторов прерываний. Эта таблица располагается в самых младших адресах оперативной памяти, имеет объем 1 Кбайт и содержит значения сегментного регистра команд (CS) и указателя команд (IP) для 256 обработчиков прерываний.

Вектор прерывания – ячейка памяти, содержащая адрес обработчика прерывания.

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

Главные функции механизма прерывания:

1.Распознавание или классификация прерывания.

2. Передача управления обработчику прерывания.

3. Корректное возвращение к прерванной программе

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

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

Вложенные прерывания.

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

Новый обработчик прерываний

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

Почему же прерывания так важны? Например, мы могли бы обойтись без прерываний, в бесконечном цикле опрашивая возникновение интересующих нас событий. Такое функционирование программы называется опросом (polling). Но опрос имеет много недостатков и неудобств — к примеру, программа будет тратить на циклы ресурс ядра, который мог быть потрачен на выполнение других действий. Это одна из причин, почему у микроконтроллера есть много источников прерывания, которые могут использоваться при необходимости. Вместо проверки событий на возникновение микроконтроллер имеет возможность прервать нормальный поток программы при возникновении события и переключиться на действия обработчика события прерывания (ISR, interrupt service routine), затем вернуться обратно и продолжить выполнение основной программы.

[Векторы прерывания]

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

Каждый вектор прерывания AVR занимает в памяти 2 байта (1 слово кода инструкций AVR), и представляет из себя команду rjmp относительный_адрес. Вот так, например, выглядит на языке ассемблера полностью заполненная таблица прерываний микроконтроллера ATmega16:

Такая полностью заполненная векторами таблица никогда не применяется. На практике обычно используется только 1..4 прерывания, в этом случае не используемые адреса векторов могут остаться не инициализированными. Обратите внимание, что в левом столбце метками обозначены шестнадцатеричные адреса инструкций, соответствующие байтовые адреса будут в 2 раза больше (потому что инструкция rjmp занимает 2 байта).

В этой статье на примере обработчика прерывания таймера 1 для ATmega16 рассказывается, как организовать обработчик прерывания в проекте GCC. Показаны два варианта реализации — на языке C и ассемблера. В примерах алгоритм работы таймера отличается, но это не важно для рассмотрения методов организации обработчика прерывания.

[Обработчик прерывания на C]

Это самый простой вариант. В данном примере используется следующий алгоритм — основная программа настраивает таймер 1 и запускает обработчик прерывания. Этот обработчик срабатывает раз в секунду и заботится о себе сам, подстраивая величину счетчика TCNT1 (чтобы прерывания происходили точно раз в секунду). Обработчик раз в секунду также декрементирует счетчик времени timer, который устанавливается и отслеживается в основной программе. Прерывание таймера разрешено постоянно (разрешается при старте программы). Таким образом, основная программа может получить любую задержку времени в интервале от 1 до 255 секунд.

Процесс по шагам:

1. Настраиваем таймер и разрешаем прерывание для него. Этот код должен вызываться однократно, при старте программы. Для этого можно написать отдельную процедуру, например:

2. В любом из модулей проекта (можно в общем, где функция main, а можно в отдельном, например timer.c) пишем код обработчика прерывания таймера. Вот пример такого кода:

3. В нужном месте разрешаем прерывания программы. Это делается также однократно, после того как сделаны все приготовления:

[Обработчик прерывания на ASM]

Этот вариант не многим сложнее, просто организован по-другому. Я его сделал на основе отдельного файла, который содержит только код на языке ассемблера. Алгоритм тут тоже другой — обработчик прерывания срабатывает раз в секунду и сам себя запрещает. Основная программа отслеживает это событие и меняет секундные счетчики (выполняет все действия, которые нужно выполнять раз в секунду), и нова разрешает прерывание. Такой алгоритм позволяет ускорить обработку прерывания, что может быть критично для некоторых задач (например, только так можно организовать точный отсчет времени при использовании библиотеки V-USB). Процесс по шагам:

1. Настраиваем таймер. Это может делать код на C. Все точно так же, как и с обработчиком прерывания на C (см. шаг 1).

2. Готовим файл с нашим кодом обработчика на языке ассемблера. Вот пример кода:

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

3. В нужном месте разрешаем прерывания программы. Это делается также однократно, после того как сделаны все приготовления:

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

[Общие замечания]

Можно заметить, что в обоих примерах использовалась именованная константа TIMER1_OVF_vect, которая задает адрес вектора прерывания таймера 1. Имена констант можно узнать во включаемом файле процессора. Для ATmega16, например, это будет файл c:\WinAVR-20080610\avr\include\avr\iom16.h. Чтобы имена стали доступны, не нужно добавлять именно этот файл в проект директивой #include, достаточно добавить #include и задать макроопределение, задающее тип процессора (например, MCU = atmega16. Это можно сделать либо в Makefile, либо в свойствах проекта).

При использовании одновременно нескольких прерываний в AVR важно помнить, что прерывания имеют фиксированный, ненастраиваемый приоритет. Чем меньше адрес вектора прерывания, тем приоритет у прерывания выше. Этот приоритет срабатывает, если при выходе из прерывания имеется несколько необработанных флагов прерывания. Прерывание с более высоким приоритетом НЕ может временно приостановить уже работающий обработчик прерывания с меньшим приоритетом, чтобы немедленно выполнить свой код. Работает система примерно так:

— когда общие прерывания разрешены (установлен бит I в регистре SREG, этот бит называют Global Interrupt Enable), то может быть вызвано любое разрешенное прерывание с любым приоритетом. Бит Global Interrupt Enable может быть принудительно сброшен или установлен командами CLI или SEI соответственно.
— для того, чтобы прерывание могло сработать и вызвать свой обработчик, кроме установки бита Global Interrupt Enable необходимо также установить бит разрешения соответствующего прерывания. Для таймеров-счетчиков это биты регистра TIMSK, для интерфейса SPI — бит SPIE в регистре SPCR, и т. д.
— когда срабатывает любое прерывание, то сразу очищается флаг I (Global Interrupt Enable), и автоматически запрещаются все прерывания, пока не произойдет выход из обработчика прерывания. Если во время работы обработчика прерывания возникали условия, при которых должны были сработать другие прерывания, то эти другие прерывания не вызываются, а просто запоминаются соответствующие им флаги (прерывания «откладываются» на будущее). При выходе из обработчика прерывания запомненные флаги прерывания запустят нужный обработчик прерывания в соответствии с назначенным ему приоритетом (если на данный момент имеется несколько отложенных прерываний).
— разработчик может в обработчике прерывания вызвать команду SEI (которая установит флаг Global Interrupt Enable), тем самым разрешив выполнение других прерываний во время работы этого обработчика прерывания. Тогда, если произойдет новое другое прерывание до завершения текущего обработчика (в котором уже была вызвана команда SEI), текущий обработчик прерывания будет приостановлен, адрес возврата в него будет сохранен в стеке и будет вызвано новое прерывание. Таким способом можно обеспечить некое подобие соблюдения приоритета — в низкоприоритетных обработчиках прерывания должна первой стоять команда SEI, а в высокоприоритетных обработчиках команда SEI должна отсутствовать, что обеспечит выполнение этого обработчика полностью.

Отсутствие возможности четко настроить приоритет — довольно серьезный недостаток платформы AVR.

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

Функция, которую выполняет ядро в ответ на определенное прерывание, называется обработчиком прерывания (interrupt handler) ил и подпрограммой обслуживания прерывания (interrupt service routine). Каждому устройству, которое генерирует прерывания, соответствует свой обработчик прерывания. Например, одна функция обрабатывает прерывание от системного таймера, а другая — прерывания, сгенерированные клавиатурой. Обработчик прерывания для какого-либо устройства является частью драйвера этого устройства — кода ядра, который управляет устройством.

В операционной системе Linux обработчики прерываний — это обычные функции , написанные на языке программирования С. Он и должны соответствовать определенному прототипу, чтобы ядро могло стандартным образом принимать информацию об обработчике, а в остальном— это обычные функции. Единственное, что отличает обработчики прерываний от других функций ядра, — это то, что они вызываются ядром в ответ на прерывание и выполняются в специальном контексте, именуемом контекстом прерывания (interrupt context), который будет рассмотрен далее.

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

Верхняя и нижняя половины

Ясно, что два указанных требования о том, что обработчик прерывания должен выполняться быстро и, в дополнение к этому, выполнять много работы, являются противоречивыми. В связи с конфликтными требованиями, обработчик прерываний разбивается на две части, или половины. Обработчик прерывания является верхней половиной (top half)— он выполняется сразу после приема прерывания и выполняет работу, критичную к задержкам во времени, такую как отправка подтверждения о получении прерывания или сброс аппаратного устройства. Работа, которую можно выполнить позже, откладывается до выполнения нижней (или основной) половины (bottom half). Нижняя половина обрабатывается позже, в более удобное время, когда все прерывания разрешены. Достаточно часто нижняя половина выполняется сразу же после возврата из обработчика прерывания.

Илон Маск рекомендует:  Что такое код pg_options

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

Рассмотрим пример разделения обработчика прерывания на верхнюю и нижнюю половины на основе старой доброй сетевой платы. Когда сетевой интерфейсный адаптер получает входящие из сети пакеты, он должен уведомить ядро о том, что доступны новые данные. Это необходимо сделать немедленно, чтобы получить оптимальную пропускную способность и время задержки при передаче информации по сети. Поэтому немедленно генерируется прерывание: «Эй, ядро! Есть свежие пакеты!. Ядро отвечает выполнением зарегистрированного обработчика прерывания от сетевого адаптера.

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

Регистрация обработчика прерывания

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

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

/* request_irq: выделить заданную линию прерывания */

int request_irq(unsigned int irq,


irqreturn_t (*handler)(int, void *, struct pt_regs *), unsigned long irqflags,

const char * devname,

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

Второй параметр, handler , — это указатель на функцию обработчика прерывания, которая обслуживает данное прерывание. Эта функция вызывается, когда в операционную систему приходит прерывание. Следует обратить внимание на специфический прототип функции-обработчика. Она принимает три параметра и возвращает значение типа irqreturn_t . Ниже в этой главе мы более подробно обсудим эту функцию.

Третий параметр, irqflags , может быть равным нулю или содержать битовую маску с одним или несколькими следующими флагами.

• SA_INTERRUPT. Этот флаг указывает, что данный обработчик прерывания — это быстрыйобработчикпрерывания.Историческитаксложилось,чтооперационная система Linux различает быстрые и медленные обработчики прерываний. Предполагается, что быстрые обработчики выполняются быстро, но потенциально очень часто, поэтому поведение обработчика прерывания изменяется, чтобы обеспечить максимально возможную скорость выполнения. Сегодня существует только одно отличие: при выполнении быстрого обработчика прерываний запрещаются все прерывания на локальном процессоре. Это позволяет быстрому обработчику завершится быстро, и другие прерывания никак этому не пометают. По умолчанию (если этот флаг не установлен) разрешены все прерывания, кроме тех, которые маскированы на всех процессорах и обработчики которых в данный момент выполняются. Для всех прерываний, кроме прерываний таймера, нет необходимости устанавливать этот флаг.

• SA_SAMPLE_RANDOM. Этот флаг указывает, что прерывания, сгенерированные данным устройством, должны вносить вклад в пул энтропии ядра. Пул энтропии ядра обеспечивает генерацию истинно случайных чисел на основе различных случайных событий. Если этот флаг указан, то моменты времени, когда приходят прерывания, будут введены в пул энтропии. Этот флаг нельзя устанавливать, если устройство генерирует прерывания в предсказуемые моменты времени (как, например, системный таймер) или на устройство может повлиять внешний злоумышленник (как, например, сетевое устройство). С другой стороны, большинство устройств генерируют прерывания в непредсказуемые моменты времени и поэтому являются хорошим источником энтропии. Для более подробного описания пула энтропии ядра см.. приложение Б, «Генератор случайных чисел ядра».

• SA_SHIRQ. Этот флаг указывает, что номер прерывания может совместно использоваться несколькими обработчиками прерываний (shared). Каждый обработчик, который регистрируется на одну и ту же линию прерывания, должен указывать этот флаг. В противном случае для каждой линии может существовать только один обработчик прерывания. Более подробная информация о совместно используемых обработчиках прерываний приведена в следующем разделе.

Четвертый параметр, devname, — это ASCII-строка, которая описывает, какое устройство связано с прерыванием. Например, для прерывания клавиатуры персонального компьютера это значение равно «keyboard». Текстовые имена устройств применяются для взаимодействия с пользователями с помощью интерфейсов

/proc/ir q и /proc/interrupts , которые вскоре будут рассмотрены.

Пятый параметр, devid , в основном, применяется для совместно используемых линий запросов на прерывания. Когда обработчик прерывания освобождается (описано ниже), параметр dev_id обеспечивает уникальный идентификатор (cookie), который позволяет удалять только необходимый обработчик линии прерывания. Без этого параметра было бы невозможно ядру определить, какой обработчик данной линии прерывания следует удалить. Если линия запроса на прерывание не является совместно используемой, то можно в качестве этого параметра указывать NULL, если же номер прерывания является совместно используемым, то необходимо указывать уникальный идентификатор (cookie) (если устройство не подключено к тине ISA, то, скорее всего, оно поддерживает совместно используемые номера прерываний).

Этот параметр также передается обработчику прерывания при каждом вызове. Обычная практика — это передача указателя на структуру устройства (контекст устройства), так как этот параметр является уникальным, и, кроме того, в обработчике прерывания может быть полезным иметь указатель на эту структуру.

В случае успеха функция request_ir q () возвращает нуль. Возврат ненулевого значения указывает на то, что произошла ошибка и указанный обработчик прерывания не был зарегистрирован. Наиболее часто встречающийся код ошибки — это значение -EBUSY, что указывает на то, что данная линия запроса на прерывание уже занята (и или при текущем вызове, или при первом вызове не был указан флаг SA_SHIRQ).

Следует обратить внимание, что функция request_ir q () может переходить в состояние ожидания (sleep) и, соответственно, не может вызываться из контекста прерывания, или в других ситуациях, когда код не может блокироваться. Распространенной ошибкой является мнение, что функцию request_irq( ) можно безопасно вызывать в случаях, когда нельзя переходить в состояние ожидания. Это происходит отчасти от того, что действительно сразу непонятно, почему функция request_irq( ) должна чего-то ожидать. Дело в том. что при регистрации происходит добавление информации о линии прерывания в каталоге /proc/irq . Функция proc_mkdir () используется для создания новых элементов на файловой системе procfs . Эта функция вызывает функцию proc_creat e () для создания новых элементов файловой системы procfs , которая в свою очередь вызывает функцию kmalloc () для выделения памяти. Как будет показано в главе 11, «Управление памятью», функция kmalloc () может переходить в состояние ожидания. Вот так вот!

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

if (request_irq(irqn, my_interrupt, SA_SHIRQ, «my_device», dev))<

printk(KERN_ERR «my_device: cannot register IRQ %d\n», irqn);

В этом примере параметр irq n — это запрошенный номер линии запроса на прерывание, параметр my_interrup t — это обработчик этой линии прерывания, линия запроса на прерывание может быть совместно используемой, имя устройства — «my_device», dev — значение параметра dev_id. В случае ошибки код печатает сообщение, что произошла ошибка, и возвращается из выполняющейся функции. Если функция регистрации возвращает нулевое значение, то обработчик прерывания инсталлирован успешно. С этого момента обработчик прерывания будет вызываться в ответ на приходящие прерывания. Важно произвести инициализацию оборудования и регистрацию обработчика прерывания в правильной последовательности, чтобы предотвратить возможность вызова обработчика до того момента, пока оборудование не инициализировано.

Освобождение обработчика прерывания

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

void free_irq(unsigned int irq, void *dev_id)

Если указанная линия не является совместно используемой, то эта функция удаляет обработчик и запрещает линию прерывания. Если линия запроса на прерывание является совместно используемой, то удаляется обработчик, соответствующий параметру dev_id. Линия запроса на прерывание также запрещается, когда удаляется последний обработчик. Теперь понятно, почему важно передавать уникальное значение параметра dev_id. При использовании совместно используемых прерываний требуется уникальный идентификатор для того, чтобы отличать друг от друга различные обработчики, связанные с одним номером прерывания, и позволить функции free_irq( ) удалять правильный обработчик. В любом случае, если параметр devoid не равен значению NULL, то он должен соответствовать тому обработчику, который удаляется.

Вызов функции free_irq( ) должен производиться из контекста процесса.

Таблица 6.1 . Список функций управления регистрацией прерываний

request_ir q () Зарегистрировать заданный обработчик прерывания для заданной линии прерывания

f ree_ir q () Освободить указанный обработчик прерывания. Если с линией прерывания больше не связан ни один обработчик, то запретить указанную линию прерывания

Источник: Лав, Роберт. Разработка ядра Linux, 2-е издание. : Пер. с англ. — М. : ООО «И.Д. Вильяме» 2006. — 448 с. : ил. — Парал. тит. англ.

Основы разработки прикладных виртуальных драйверов

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

Выходы сигналов прерываний из установки подсоединяются к свободным уровням прерываний, а в программное обеспечение включаются обработчики прерываний, соответствующих этим уровням векторов. Обычно используются уровни 3 или 5 ведущего контроллера (векторы 0Bh и 0Dh реального режима) или уровни 9. 13 и 15 ведомого контроллера (векторы 71h. 75h и 77h).

При работе в операционной системе MS-DOS контроллеры прерываний программируются так, что ведущему контроллеру назначается базовый вектор 8, а ведомому — 70h. Однако такая схема не может использоваться в защищенном режиме, поскольку первые 18 векторов заняты исключениями и не могут использоваться аппаратными прерываниями. Поэтому при загрузке Windows контроллеры прерываний программируются иначе: ведущему контроллеру назначается базовый вектор 50h, ведомому — 58h. Эти векторы отвечают последним 16 дескрипторам таблицы дескрипторов прерываний IDT, содержащей в целом 60h дескрипторов.

В дескрипторах IDT, соответствующих аппаратным прерываниям, хранятся адреса системных обработчиков, которые принадлежат виртуальному драйверу контроллера прерываний (виртуальному контроллеру) VPICD. Эти адреса назначаются системой, не могут быть изменены пользователем и вообще не видны из приложения. Если программист включил в 16-разрядное приложение Windows собственный обработчик какого-либо аппаратного прерывания, этот обработчик устанавливается с помощью функций Си getvect() и setvect(), которые преобразуются компилятором в вызовы функций 35h и 25h DOS. При вызове этих функций следует указывать номера векторов, которые использовались в DOS (8 — для таймера, 70h — для часов реального времени и т.д.). Вызовы указанных функций в защищенном режиме перехватываются VPICD, который записывает адреса прикладных обработчиков не в IDT, а в отдельную таблицу векторов защищенного режима, которая создается в каждой виртуальной машине (если их несколько). По приходе аппаратного прерывания процессор вызывает его обработчик в соответствии с содержимым IDT. Этот обработчик, входящий в состав VPICD и обслуживающий прерывание по правилам Windows, в конце концов передает управление по адресу, который хранится в таблице векторов защищенного режима.

Соответствие номеров векторов, номеров уровней прерываний и устройств, характерное для системы Windows, показано в табл. 1.

На старых машинах типа XT с одним контроллером прерываний уровень IRQ2 был свободен и мог использоваться при подключении к компьютеру нестандартной аппаратуры, работающей в режиме прерываний. Этому уровню соответствует вектор 0Ah, который обычно и назначался прикладным обработчикам аппаратных прерываний (разумеется, нестандартное устройство можно было подключить и к любому другому свободному уровню).

В современных машинах, где используются два контроллера прерываний, второй (ведомый) контроллер подключается как раз ко входу 2 первого (ведущего). Приход аппаратного прерывания на любой вход ведомого контроллера приводит к возникновению сигнала на входе 2 ведущего контроллера, который, однако, сопровождается посылкой в процессор не вектора 0Ah (уровень IRQ2), а вектора, который соответствует в ведомом контроллере пришедшему прерыванию (например, 70h для уровня IRQ8).

Линия IRQ2 внешней магистрали и соответствующий контакт разъема расширения подключаются теперь не ко входу 2 ведущего контроллера (этот вход занят), а ко входу 1 ведомого контроллера, которому соответствуют уровень IRQ9 и вектор 71h реального режима. Для того чтобы обеспечить программную совместимость старых и новых машин, в систему MS-DOS включен обработчик прерывания 71h, в котором вызывается программное прерывание 0Ah. Таким образом, хотя внешнее устройство, подключенное к линии IRQ2, фактически работает на уровне IRQ9, его могут обслуживать обработчики прерываний, вызываемые через вектор 0Ah.

В системе Windows перенаправление вектора 71h не реализуется. Поэтому внешние устройства, подключаемые к линии IRQ2 (а фактически к в уровню IRQ9, то есть к входу 1 ведомого контроллера прерываний), необходимо программировать через вектор 71h, соответствующий этому уровню. Устройства же, подключаемые к другим свободным уровням, например IRQ3 или IRQ5, как и раньше, программируются через векторы 0Bh или 0Dh. При этом, хотя на первый взгляд вектор 0Dh совпадает по номеру с вектором исключения общей защиты, никаких конфликтов не возникает, так как обработчик исключения общей защиты вызывается через аппаратный вектор 0Dh таблицы IDT, а «вектор 0Dh», используемый в программе, в аппаратном плане соответствует дескриптору IDT с номером 55h, а в программном — ячейке с номером 0Dh в таблице векторов защищенного режима, не имеющей никакого отношения к обработке исключений.

Общие правила обработки прерываний от нестандартной аппаратуры в системах DOS и Windows одинаковы. В основной программе выполняются сохранение исходного содержимого используемого вектора и занесение в вектор полного адреса нового обработчика. Далее необходимо размаскировать используемый уровень прерываний в контроллере прерываний (порт 21h в ведущем контроллере или A1h — в ведомом) и, возможно, разрешить прерывания в подключенной к компьютеру аппаратуре, если в регистре управления интерфейсной платы, осуществляющей связь с нестандартной аппаратурой, имеется бит управления прерываниями. Выполнив эти инициализирующие действия, основная программа может заниматься чем угодно. Сигнал прерывания из аппаратуры передаст управление установленному обработчику, который должен перед своим завершением послать в контроллер прерываний команду EOI, чтобы снять блокировку в контроллере прерываний. Перед завершением всей программы необходимо в общем случае восстановить в используемом векторе его исходное содержимое, замаскировать используемый уровень в контроллере прерываний и запретить прерывания в регистре управления аппаратурой.

Для изучения базовых, архитектурных вопросов обработки прерываний можно воспользоваться простыми обработчиками прерываний от системного таймера, клавиатуры или мыши. Эти периферийные устройства имеются на любом компьютере и везде работают одинаково. Однако обсуждение особенностей обработки прерываний от нестандартной аппаратуры в системе Windows требует привязки к конкретной измерительной или управляющей аппаратуре, которая, разумеется, отсутствует на компьютере пользователя. Примеры, которые мы приводим ниже отлаживались на специально разработанном макете, оформленном в виде интерфейсной платы и представляющем собой программируемый таймер-счетчик внешних событий. Этот макет используется в лабораторном студенческом практикуме по курсам, посвященным программированию аппаратуры физического эксперимента. В частности, с помощью этой интерфейсной платы демонстрируется методика разработки драйверов Windows для управления нестандартной аппаратурой. Хотя разработанный нами макет является сугубо специфическим устройством и имеется только в лаборатории автора, в нем используются общепринятые методы построения микропроцессорной аппаратуры, и в этом плане макет вместе с компьютером представляет собой типичную программно-управляемую измерительную установку. Читателю не составит труда трансформировать описываемые ниже примеры применительно к собственным потребностям.

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

Плата предназначена для построения на базе персонального компьютера автоматизированного устройства для счета событий. Плата включает микросхему 3-канального таймера КР580ВИ53, 16-битовый счетчик для счета поступающих событий и ряд логических узлов. Счетчик может считать внешние события или импульсы от встроенного в плату генератора. Последний режим используется для проверки работоспособности платы и отладки разрабатываемого программного обеспечения.

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

где f — частота импульсов, подаваемых на вход канала 0. Канал 0 работает в режиме 3 пересчета импульсов; канал 1 работает в режиме 0 генерации единственного импульса с длительностью Т. На плате установлен кварцевый генератор с частотой 1 МГц, поэтому

Соотношение констант С0 и С1 в принципе не имеет значения, однако в силу некоторых особенностей микросхемы таймера (в котором, строго говоря, не предусмотрена возможность последовательного соединения каналов) он дает погрешность приблизительно в ½ периода канала 0, и для ее уменьшения желательно выбирать С0 как можно меньше (однако запрещается значение С0 = 3).

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

f2 = 10 6 : C2 Гц.

В состав электронной схемы платы входят четыре флага — S0, S1, S2 и S3, служащие для управления устройством и индикации его состояния. Флаги S0 и S2 являются управляющими, S1 и S3 — индикаторными. Флаг S0 переключает вход счетчика (счет внешних сигналов или импульсов от встроенного генератора), флаг S2 разрешает счет, а также установку флага S3 по завершении временного интервала. Индикаторный флаг S3 указывает на завершение установленного временного интервала, а S1 — на переполнение счетчика. Установка любого из этих флагов приводит к возбуждению сигнала прерывания на линии IRQ5.

Все четыре флага входят в состав 4-битового регистра с адресом 30Ah, где они занимают биты 4, 5, 6 и 7 соответственно и могут быть прочитаны командой in ввода из порта. Однако выполнить запись в любой из флагов через порт 30Ah нельзя — для установки и сброса флагов предусмотрены специальные операции. Так, для установки флага S2 разрешения счета следует выполнить чтение из порта 30Bh, а для сброса флага S1 переполнения счетчика — запись (чего угодно) в порт 309h. Полный состав операций, реализованных в рассматриваемой схеме, также приведен на рис. 1.

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

  • общий сброс (чтение или запись через порт 30Ch);
  • засылка управляющих слов в регистр команд (порт 303h) для задания режима каналов 0, 1 и 2;
  • засылка констант счета в буферные регистры каналов 0, 1 и 2 (порты 300h, 301h и 302h);
  • установка флага S2 для разрешения счета (чтение из порта 30Bh);
  • ожидание установки флага S3, то есть окончания заданного временного интервала;
  • после установки флага S3 считывание содержимого счетчика платы (в два этапа: сначала один байт счетчика, затем другой; чтение младшего байта осуществляется через порт 308h, чтение старшего — через порт 309h).

При использовании режима прерываний на этапе инициализации следует выполнить первые четыре из перечисленных выше операций; флаг разрешения-запрещения прерываний в схеме не предусмотрен. Поскольку прерывание возбуждается как при завершении временного интервала, так и при переполнении счетчика, в обработчике прерываний следует проверять состояние флагов S1 и S3 и определять причину прерывания.

Рассмотрим сначала обычное 16-разрядное приложение Windows, выполняющее управление платой таймера-счетчика в режиме прерываний без драйвера. Чтобы продемонстрировать проблемы, возникающие при обработке прерываний в приложениях Windows, нам придется сделать эту программу более типичной (ввести в нее главное окно приложения с меню) и, следовательно, значительно более сложной по сравнению с рассмотренными выше.

Для того чтобы в главном окне приложения появилась стандартная полоска с пунктами меню, необходимо в тексте программы заказать вывод окна с меню, а состав меню описать в специальном файле ресурсов. Этот файл является текстовым файлом и имеет стандартное расширение .RC. Различные ресурсы описываются здесь в специальных форматах, понятных для компилятора ресурсов, входящего в состав любой интегрированной среды разработки приложений Windows (IDE), например пакета Borland C++. Кроме меню в файле ресурсов можно описывать состав диалоговых окон, таблицы символьных строк и другие объекты, выводимые в окно приложения. Для того чтобы IDE выполнила обработку файла ресурсов, его вместе с основным, программным файлом необходимо включить в состав проекта, создаваемого в рамках IDE.

Файл ресурсов для приложения Windows, управляющего платой таймера-счетчика в режиме прерываний:

Состав, или сценарий, меню описывается в файле ресурсов с помощью предусмотренных для этого ключевых слов. Сценарий меню начинается с ключевого слова MENU, за которым следует перечень пунктов меню, заключенный в фигурные скобки. Слово MENU предваряется произвольным именем (у нас это имя Main), которое выступает как идентификатор меню и далее будет использовано в тексте программы.

Каждый пункт перечня пунктов меню начинается с ключевого слова POPUP, за которым следует название пункта меню. В нашем примере все меню состоит из единственного пункта «Режим».

Вслед за предложением POPUP следует перечень подпунктов этого пункта меню, который появится на экране при его выборе (щелчком левой клавиши мыши). Перечень заключается в фигурные скобки. Каждый подпункт начинается ключевым словом MENUITEM, за ним указываются название данного подпункта и его идентификатор. Идентификаторы обычно имеют символическую форму. У нас это обозначения MI_START, MI_EXIT и MI_READ, которым в начале файла ресурсов присваиваются (оператором препроцессора #define) произвольные значения: 100, 101 и 102.

Вид меню, соответствующего приведенному выше файлу ресурсов, показан на рис. 2.

Теперь перейдем к рассмотрению основного файла приложения.

Исходный текст приложения Windows, управляющего платой таймера-счетчика в режиме прерываний:

В типичном приложении Windows главная функция WinMain() должна выполнить по меньшей мере три важных процедуры:

  • зарегистрировать в системе Windows класс главного окна. Если помимо главного окна планируется выводить на экран внутренние, порожденные окна, то их классы тоже необходимо зарегистрировать, поскольку Windows выводит на экран и обслуживает только зарегистрированные окна;
  • создать главное окно и показать его на экране. Порожденные окна также необходимо создать, хотя это можно сделать и позже, причем не обязательно в функции WinMain();
  • организовать цикл обработки сообщений, поступающих в приложение. Вся дальнейшая жизнь приложения будет состоять фактически из бесконечного выполнения этого цикла и из обработки поступающих в приложение сообщений. Запущенное приложение Windows обычно функционирует до тех пор, пока пользователь не подаст команду его завершения с помощью системного меню или посредством ввода + . Эти действия приводят к завершению главной функции и удалению приложения из списка действующих задач.

Первое из перечисленных действий выполняется в нашем примере вызовом прикладной функции Register(), второе — вызовом функции (тоже прикладной) Create(), третье — циклом, включающим две функции Windows: GetMessage() и DispatchMessage().

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

Сообщения Windows являются, пожалуй, самой важной концепцией этой системы. Каждый раз, когда происходит какое-то событие, затрагивающее интересы программы (например, пользователь выбирает пункт меню или нажимает на кнопку в окне диалога), Windows посылает приложению сообщение об этом событии. Задача функции WinMain() заключается в приеме этого сообщения и передаче его второму важнейшему компоненту любого приложения Windows — функции главного окна, или, проще говоря, оконной функции (оконной процедуре). В отличие от главной функции WinMain(), имя которой изменять нельзя, имя оконной функции может быть любым. В наших примерах оконная функция главного окна именуется WndProc.

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

В функцию Register() вынесены действия по регистрации класса окна. В ней объявляется и заполняется структура типа WNDCLASS, в которой указываются, в частности, имя меню из файла ресурсов и имя оконной функции, а затем вызывается функция Windows RegisterClass(), которая и выполняет регистрацию данного класса.

Зарегистрировав класс окна, можно приступить к его созданию и показу. Эта процедура вынесена у нас в подпрограмму Create(), в которой последовательно вызываются две функции Windows: CreateWindow() — для создания главного окна и ShowWindow() — для его вывода на экран.

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

В этом бесконечном (если его не разорвать изнутри) цикле вызывается функция Windows GetMessage() и, в случае если она возвращает ненулевое значение, вызывается функция DispatchMessage().

Функция GetMessage() анализирует очередь сообщений приложения. Если в очереди обнаруживается сообщение, GetMessage() изымает его из очереди и передает в структурную переменную msg типа MSG, объявленную в начале функции WinMain(). После этого вызывается функция DispatchMessage(), вызывающая оконную функцию для того окна, которому предназначено очередное сообщение. После того как оконная функция обработает сообщение, возврат из нее приводит к возврату из функции DispatchMessage() на продолжение цикла while. При отсутствии сообщений в очереди приложения функция GetMessage() передает управление системе Windows, которая последовательно опрашивает очереди сообщений других запущенных приложений (если, конечно, они имеются). После опроса всех приложений управление возвращается в функцию GetMessage(), которая опять анализирует состояние очереди нашего приложения.

Оконная функция главного окна состоит из стольких фрагментов, сколько предполагается обрабатывать сообщений. Конкретное содержимое каждого фрагмента определяет программист. Все сообщения, не нуждающиеся в прикладной обработке, должны поступать в функцию обработки сообщений по умолчанию DefWindowProc() и обрабатываться системой Windows. Эти действия можно описать в программе с помощью конструкции switch-case:

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

Заметного упрощения структуры программы можно добиться, используя макрос HANDLE_MSG, определенный в файле windowsx.h. Этот макрос, в зависимости от его параметров, преобразуется в один из многочисленных макросов вида HANDLE_WM_COMMAND, HANDLE_WM_DESTROY и т.д., которые расширяются в конструкции вида:

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

В нашей программе обрабатываются всего два сообщения: WM_COMMAND от пунктов меню и WM_DESTROY, которое создается системой, если пользователь завершает приложение, например, введя команду + . Соответственно в программу введены две функции обработки этих сообщений: OnCommand() и OnDestroy().

Илон Маск рекомендует:  Преобразование XML в массив

Для обеспечения работы системы прерываний в начале программы указан прототип функции isr() с описателем interrupt, которая будет служить в качестве прикладного обработчика прерывания, а в области глобальных переменных объявлен указатель old_isr на функцию типа «interrupt», где будет храниться адрес исходного обработчика.

При выборе пункта «Пуск» (идентификатор MI_START) с помощью функции C getvector() читается и сохраняется в переменной old_isr исходное содержимое вектора 13, после чего функцией setvector() в вектор 13 заносится адрес нашего обработчика isr. Как было показано в начале статьи, функции getvector() и setvector() работают не с истинной таблицей дескрипторов прерываний IDT, а со специальной таблицей векторов защищенного режима, входящей в состав виртуальной машины.

Процедура инициализации платы вынесена в отдельную функцию InitCard(). В ней прежде всего выполняется общий сброс всех узлов платы, далее посредством посылки кодов 36h, 70h и B6h в порт регистра команд 303h задаются режимы всех трех каналов таймера и, наконец, во все три канала последовательно засылаются константы С0, С1 и С2. Для простоты значения констант определены прямо в тексте программы; в реальной программе их следовало бы вводить с клавиатуры. Для выделения младших и старших половин констант (вспомним, что константы размером в одно слово засылаются в порты таймера в два приема, по байтам) используются удобные макросы Windows: LOBYTE и HIBYTE.

Нам осталось размаскировать уровень 5 (соответствующий вектору 13) контроллера прерываний. Текущая маска читается в переменную mask, оператором & в ней обнуляется бит 5 (нулю в бите 5 соответствует число DFh), и новое значение маски отправляется в порт 21h. Еще раз отметим, что VMM перехватывает обращение к запрещенному порту 21h, в результате чего наше данное поступает не в аппаратный порт контроллера прерываний, а в его программную копию — виртуальную маску прерываний, входящую в состав виртуальной машины. «Заведует» этой маской, как и всей системой прерываний, виртуальный контроллер прерываний VPICD.

Последним предложением функции InitCard() в схеме счетчика-таймера устанавливается флаг S2, открывающий вход счетчика и одновременно разрешающий установку флага S3 завершения временного интервала.

Обработчик прерываний в реальной установке может выполнять различные функции. Наверное, самое естественное — вывести на экран сообщение об окончании измерений. Однако сделать это не так-то просто. Дело в том, что возможности обработчика прерываний ограничены. В нем можно выполнять вычислительные действия, а также чтение и запись ячеек памяти, но нельзя, например, вызвать функцию Windows MessageBox() для вывода на экран сообщения. Чтобы не усложнять рассматриваемый пример, в функции isr() мы просто читаем содержимое счетчика платы и заносим его в глобальную переменную data, давая тем самым программе возможность работать с этими данными. Чтение числа накопленных событий приходится выполнять в два приема. Старшая половина данных из порта 309h читается в байтовую переменную half, переносится в переменную data с преобразованием в слово и сдвигается в этой переменной влево на 8 бит, то есть переносится в старший байт. Далее младшая половина данных читается в ту же переменную half и складывается со старшей в переменной data.

Последнее (обязательное!) действие в обработчике прерываний — снятие блокировки в контроллере прерываний командой EOI. И эта операция в действительности выполняется не в физическом контроллере прерываний, а в виртуальном.

При выборе пункта меню «Чтение» выполняются относительно простая операция: преобразование с помощью функции Windows wsprintf() числа в переменной data в символьную форму и вывод его в окно сообщения функцией MessageBox() (результат этих действий был показан на рис. 3 части 4 этой статьи).

Алгоритм обработки прерываний

Механизм прерываний

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

Отключите систему прерываний – и «жизнь» в операционной системе немедленно остановится. Периодические прерывания от таймера вызывают смену процессов в мультипрограммной операционной системе, а прерывания от устройств ввода-вывода управляют потоками данных, которыми вычислительная система обменивается с внешним миром.

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

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

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

Прерывание возникает либо в зависимости от внешних по отношению к процессу событий, либо при появлении непредвиденных аварийных ситуаций в процессе выполнения данной программы.

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

Идея прерывания оказалась настолько полезной, что сейчас прерывания применяются в компьютере для множества целей.

Прерывания представляют собой механизм, позволяющий координировать параллельное функционирование отдельных устройств вычислительной системы и реагировать на особые состояния, возникающие в работе процессора, то есть прерывание – это принудительная передача управления от выполняемой программы к системе (а через неё — к соответствующей программе обработки прерывания), происходящая при возникновении определённого события.

Итак, главные функции механизма прерываний – это:

q распознавание или классификация прерываний;

q передача управления соответствующему обработчику прерываний;

q корректное возвращение к прерванной программе.

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

Алгоритм обработки прерываний

Механизм обработки прерываний независимо от архитектуры вычислительной системы подразумевает выполнение некоторой последовательности шагов.

1. Установление факта прерывания (приём сигнала запроса на прерывание) и идентификация прерывания.

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

3. Управление аппаратно передается на подпрограмму обработки прерывания.

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

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

6. Восстановление информации, относящейся к прерванному процессу (этап, обратный шагу 4).

7. Возврат на прерванную программу.


Шаги 1-3 реализуются аппаратно, шаги 4-7 – программно.

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

Рис. 1. Обработка прерывания

Типы прерываний

В зависимости от источника прерывания делятся на три вида:

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

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

q прерывания от таймера;

q прерывания от внешних устройств (прерывания по вводу-выводу);

q прерывания по нарушению питания;

q прерывания с пульта оператора вычислительной системы;

q прерывания от другого процессора или другой вычислительной системы.

Внутренние прерывания вызываются событиями, которые связаны с работой процессора и являются синхронными с его операциями. Могут быть вызваны при обнаружении ошибки при выполнении программы. Это похоже на то, как если бы вы при чтении какого-либо предложения встретили «РБНСС ЗМЙ ЮДЙХМ ЯСМ ЖЯК», т.е. полнейшую бессмыслицу. Аналогичная ситуация может возникнуть и в компьютере. Процессор может встретить команды, не имеющие для него никакого смысла, или данные, которые нельзя обработать. Например, нулевой делитель, ошибки защиты памяти, обращение по несуществующему адресу, попытка выполнить привилегированную инструкцию в пользовательском режиме и т.п.

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

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

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

На рис. 2 изображен обычный порядок (приоритеты) обработки прерываний в зависимости от типа прерываний. Учет приоритета может быть встроен в технические средства, а также определяться операционной системой.

Рис. 2. Распределение прерываний по уровням приоритета

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

Управление ходом выполнения задач со стороны операционной системы заключается в организации реакций на прерывания, в организации обмена информацией (данными и программами), в предоставлении необходимых ресурсов, в динамике выполнения задачи и в организации сервиса. Причины прерываний определяет операционная система (модуль, который называют супервизором прерываний), она же и выполняет действия, необходимые при данном прерывании и в данной ситуации. Супервизор прерываний (первые две секции на рис.1) прежде всего сохраняет в дескрипторе текущей задачи рабочие регистры процессора, определяющие контекст прерываемого вычислительного процесса. Далее он определяет ту подпрограмму, которая должна выполнить действия, связанные с обслуживанием настоящего (текущего) запроса на прерывание. Перед тем, как передать управление на эту подпрограмму, супервизор прерываний устанавливает необходимый режим обработки прерывания. После выполнения подпрограммы обработки прерывания управление вновь передается ядру операционной системы. На этот раз уже на тот модуль, который занимается диспетчеризацией задач. И уже диспетчер задач, в свою очередь, восстановит контекст той задачи, которой будет решено выделить процессор. Рассмотренную схему иллюстрирует рис. 3.

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

Рис. 3. Обработка прерывания при участии супервизоров ОС

Механизм прерываний

Механизм прерываний поддерживается аппаратными средствами компьютера и программными средствами ОС.

Существуют два основных способа выполнения прерывания:

1. векторный (vectored), когда в процессор передается номер вызываемой процедуры обработки прерывания;

2. опрашиваемый (polled), когда процессор вынужден последовательно опрашивать потенциальные источники запроса прерывания.

Устройствам, которые используют векторные прерывания, назначается вектор прерываний.

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

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

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

Для некоторых аппаратных платформ могут сочетаться оба типа прерываний.

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

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

1. При возникновении сигнала (для аппаратных прерываний) или условия (для внутренних прерываний) прерывание происходит первичное аппаратное распознавание типа прерывания. Если прерывания данного типа в настоящий момент запрещены (приоритетной схемой или механизмом маскирования), то процессор продолжает поддерживать естественный ход выполнения команд. Иначе происходит автоматический вызов процедуры обработки прерывания, адрес которой находится в специальной таблице ОС, размещаемой либо в регистрах процессора, либо в определенном месте ОП.

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

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

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

| следующая лекция ==>
Регулирование отношений в области телекоммуникаций. | Отрицательное влияние человека на природную среду.

Дата добавления: 2020-10-18 ; просмотров: 840 | Нарушение авторских прав

Реализация обработчика

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

Единственной особенностью является то, что обработчик работает во время прерывания и, следовательно, испытывает некоторые ограничения в том, что может делать. Эти ограничения являются такими же, какие мы видели для таймеров ядра. Обработчик не может передавать данные в или из пространства пользователя, так как не выполняется в контексте процесса. Обработчики также не могут делать ничего, что могло бы заснуть, например, вызвать wait_event , выделять память ни с чем другим, кроме GFP_ATOMIC , или блокировать семафор. Наконец, обработчик не может вызвать schedule .

Ролью обработчика прерываний является предоставление обратной связи устройству о приёме прерывания и прочитать или записать данные в зависимости от смысла обслуживаемого прерывания. Первый шаг обычно состоит в очистке бита на интерфейсной плате; большинство аппаратных устройств не будет генерировать другие прерывания, пока их бит «ожидание прерывания» не очищен. В зависимости от того, как работает ваше оборудование, этот шаг может выполняться последним, а не первым, здесь нет всеобъемлющего правила. Некоторые устройства не требуют этого шага, так как они не имеют бита «ожидание прерывания»; такие устройства составляют меньшинство, хотя параллельный порт является одним из них. По этой причине short ничего не делает для очистки такого бита.

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

В случае примера захвата кадров, процесс мог бы получать последовательность изображений постоянно читая устройство; перед чтением каждого фрейма вызов read блокируется, а обработчик прерывания пробуждает процесс при поступлении каждого нового кадра. Это предполагает, что захват прерывает процессор для сигнализации об успешном появлении каждого нового кадра.

Программист должен быть внимательным, чтобы написать процедуру, которая выполняется в минимальное количество времени, независимо от того, быстрый это или медленный обработчик. Если должно быть выполнено долгое вычисление, лучшим подходом является использование микрозадачи (tasklet) или очереди задач (workqueue) для выполнения вычислений в безопасное время (в разделе «Верхние и нижние половины» мы увидим, как работа может быть отложена таким образом).

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

irqreturn_t short_interrupt(int irq, void *dev_id, struct pt_regs *regs)

struct timeval tv;

/* Пишем 16 байтовый отчёт. Подразумеваем, что PAGE_SIZE кратна 16 */

written = sprintf((char *)short_head,»%08u.%06u\n»,

(int)(tv.tv_sec % 100000000), (int)(tv.tv_usec));

wake_up_interruptible(&short_queue); /* пробудить любой процесс чтения */

Этот код, хотя и прост, показывает типичную работу обработчика прерываний. Он, в свою очередь, вызывает short_incr_bp , которая определена так:

static inline void short_incr_bp(volatile unsigned long *index, int delta)

unsigned long new = *index + delta;

barrier( ); /* Не оптимизировать эти две строки вместе */

*index = (new >= (short_buffer + PAGE_SIZE)) ? short_buffer : new;

Эта функция была написана тщательно, чтобы обернуть указатель в круговом буфере никогда не получая неправильные значения. Чтобы заблокировать оптимизацию компилятором двух разных строчек функции, здесь используется вызов barrier . Без барьера компилятор мог бы принять решение оптимизировать, удалив переменную new , и выполнить присвоение непосредственно *index . Такая оптимизация может привести к неправильному значение индекса в течение короткого периода, когда он оборачивается. Приняв меры для предотвращения получения некорректного значения другими потоками, мы можем безопасно, без блокирования, манипулировать указателем кругового буфера.

Файлом устройства, используемого для чтения заполненного во время прерывания буфера, является /dev/shortint . Это специальный файл устройства не был рассмотрен в Главе 9 вместе с /dev/shortprint , поскольку его использование является специфичным для обработки прерываний. Внутренняя организация /dev/shortint специально предназначена для генерации прерывания и отчётности. Запись в устройство генерирует одно прерывание для каждого байта; чтение устройства даёт время, когда было сообщено о каждом прерывании.

Если соединить вместе контакты 9 и 10 разъёма параллельного порта, вы сможете генерировать прерывания, устанавливая самый старший бит байта данных параллельного порта. Это может быть достигнуто записью бинарных данных в /dev/short0 или записью чего-нибудь в /dev/shortint . (* Устройство shortint выполняет свою задачу поочередно записывая в параллельный порт 0x00 и 0xFF.)

Следующий код реализует read и write для /dev/shortint :

ssize_t short_i_read (struct file *filp, char __user *buf, size_t count, loff_t *f_pos)

while (short_head == short_tail) <

if (short_head == short_tail)

if (signal_pending (current)) /* сигнал поступил */

return -ERESTARTSYS; /* предложить уровню файловой системы обработать его */

/* count0 — число читаемых байтов данных */

count0 = short_head — short_tail;

count0 = short_head + PAGE_SIZE — short_tail;

if (copy_to_user(buf, (char *)short_tail, count))

ssize_t short_i_write (struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)

int written = 0, odd = *f_pos & 1;

unsigned long port = short_base; /* вывод в регистр-защёлку параллельных данных */

iowrite8(0xff * ((++written + odd) & 1), address);

outb(0xff * ((++written + odd) & 1), port);

Другой специальный файл устройства, /dev/shortprint , использует параллельный порт для управления принтером; вы можете использовать его, если хотите избежать соединения контактов 9 и 10 разъёма D-25. Реализация write в shortprint использует круговой буфер для хранения данных, которые будут печататься, а реализация read аналогична только что показанной (так что вы можете читать время, которое забирает ваш принтер, чтоб съесть каждый символ).

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

Аргументы обработчика и возвращаемое значение

Обработчику прерывания передаются три аргумента: irq , dev_id и regs , хотя short и игнорирует их. Давайте посмотрим на роль каждого из них.

Номер прерывания ( int irq ) полезен как информация, которую вы можете напечатать в журнале сообщений, если таковой имеется. Второй аргумент, void *dev_id , является видом данных клиента; аргумент void * передаётся в request_irq и этот же указатель затем передаётся обратно как аргумент для обработчика, когда происходит прерывание. Обычно передаётся указатель на вашу структуру данных устройства в dev_id , так что драйверу, который управляет несколькими экземплярами одинаковых устройств, не требуется какого-либо дополнительного кода в обработчике прерывания, чтобы выяснить, какое устройство отвечает за текущее событие прерывания.

Типичное использование аргумента в обработчике прерывания выглядит следующим образом:

static irqreturn_t sample_interrupt(int irq, void *dev_id, struct pt_regs *regs)

struct sample_dev *dev = dev_id;

/* теперь `dev’ указывает на правильный объект оборудования */

Типичный код open , связанный с этим обработчиком, выглядит следующим образом:

static void sample_open(struct inode *inode, struct file *filp)

struct sample_dev *dev = hwinfo + MINOR(inode->i_rdev);

0 /* flags */, «sample», dev /* dev_id */);

Последний аргумент, struct pt_regs *regs , используется редко. Он содержит снимок контекста процессора перед тем, как процессор вошёл в код прерывания. Регистры могут быть использованы для мониторинга и отладки; для обычных задач драйвера устройства они обычно не требуются.

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

где handled отличен от нуля, если вы были в состоянии обработать прерывание. Возвращаемое значение используется ядром для выявления и пресечения ложных прерываний. Если ваше устройство не даёт вам способа узнать, действительно ли прервало оно, вы должны вернуть IRQ_HANDLED .

Разрешение и запрет прерываний

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

Запрет одного прерывания

void disable_irq(int irq);

void disable_irq_nosync(int irq);

void enable_irq(int irq);

Вызов любой из этих функций может обновить маску указанного irq в программируемом контроллере прерываний (programmable interrupt controller, PIC), таким образом, запрещая или разрешая указанное прерывание на всех процессорах. Вызовы этих функций могут быть вложенными, если disable_irq вызвана два раза подряд, требуется дважды вызвать enable_irq для действительного разрешения прерывания заново. Можно вызывать эти функции из обработчика прерывания, но разрешение вашего собственного прерывания при его обработке обычно нехорошая практика.

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

Но зачем же отключать прерывание? Придерживаясь параллельного порта, давайте посмотрим на сетевой интерфейс plip . Устройство plip использует простой параллельный порт для передачи данных. Так как из разъёма параллельного порта могут быть прочитаны только пять бит, они интерпретируются как четыре бита данных и тактовый/настроечный сигнал. Когда первые четыре бита пакета переданы инициатором (интерфейсом отправки пакета), для прерывания процессора в случае принимающего интерфейса поднимается тактовая линия. Затем для обработки вновь прибывших данных вызывается обработчик plip .

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

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

Запрет всех прерываний

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

void local_irq_save(unsigned long flags);

Вызов local_irq_save запрещает доставку прерывания на текущий процессор после сохранения текущего состояния прерывания в flags . Обратите внимание, что flags передаётся непосредственно, не по указателю; так происходит потому что local_irq_save на самом деле реализована как макрос, а не как функция. local_irq_disable выключает местную доставку прерывания без сохранения состояния; вы должны использовать эту версию только если вы знаете, что прерывания уже не были запрещены где-то ещё.

Включение прерываний снова осуществляется с помощью:

void local_irq_restore(unsigned long flags);

Первая версия восстанавливает то состояние, которое было сохранено во flags функцией local_irq_save , а local_irq_enable разрешает прерывания безоговорочно. В отличие от disable_irq , local_irq_disable не отслеживает множественные вызовы. Если в цепочке вызова запретить прерывания может потребоваться более чем одной функции, должна быть использована local_irq_save .

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

Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL