Прерывания для самых маленьких / Habr
Сегодня мы поговорим о прерываниях процессоров семейства x86 (-64). Подробнее под катом.Прерывания — это как бы сигнал процессору, что надо прервать выполнение (их поэтому и назвали прерываниями) текущего кода и срочно сделать то, что указано в обработчике.
Все адреса обработчиков прерываний хранятся в IDT. Это таблица, в которой хранятся 256 (можно больше или меньше, но большие значения просто игнорируются) ячеек (векторы прерываний) с типом и атрибутами прерывания, одним просто нулевым значением, собственно адресом обработчика прерываний и селектором кода в GDT или LDT, который будет использовать данный вектор прерываний. Теперь немного о типе и атрибутах.
Тип прерывания и атрибуты занимают 8 бит. Первые 4 бита занимают тип:
- 0b0101: 32-битный гейт задачи, при появлении такого прерывания происходит хардверное переключение задачи (да-да, есть и такое, но его уже давно не используют)
- 0b0110: 16-битный гейт прерывания
- 0b0111: 16-битный гейт trap’a (я не знаю, как это перевести на русский язык, извините)
- 0b1110: 32-битный гейт прерывания
- 0b1111: 32-битный гейт trap’a
Далее идут атрибуты. Первым атрибутом является 1 бит, который задан в 0 для гейтов прерывания и в 1 для остальных. Далее идет уровень привилегий дескриптора — 2 бита, задающие минимальный уровень привилегий для вызова прерываний, и 1 бит, заданный в 0 для неиспользуемых прерываний.
Теперь о том, как процессор вызывает обработчики.
Допустим, что вы вызвали инструкцию
int 0
в ассемблере. Это даст сигнал процессору, что надо вызвать прерывание 0, если это возможно. Вот последовательность действий, которые происходят при этом.- Поиск вектора №0 в IDT.
- Сравнение уровня привилегий дескриптора и текущего уровня привилегий процессора.
- Если текущий уровень привилегий процессора меньше уровня привилегий дескриптора, то просто вызвать генеральную ошибку защиты и не вызывать прерывание.
- Происходит сохранение адреса возвращения, регистра (E)FLAGS и другой информации.
- Происходит переход на адрес, указанный в векторе №0 IDT.
- После выполнения обработчика инструкция
iret
возвращает управление прерванному коду.
Еще есть прерывания, которые генерируются самим процессором при определенных обстоятельствах — исключения. Вот их список с краткими описаниями:
- Деление на ноль. Генерируется при, собственно, делении на ноль.
- Отладочное исключение. Генерироваться само не может, используется для, собственно, отладки.
- Немаскируемое прерывание. Генерируется при ошибках ОЗУ и невосстановимых ошибках «железа». Их невозможно замаскировать с помощью PIC (Programmable Interrupt Controller — программируемый контроллер прерываний), так как оно идет сразу в процессор, минуя PIC, но можно просто отключить.
- Переполнение. Генерируется инструкцией
INTO
, если в (E)FLAGS включен бит переполнения. - Выход за пределы. Генерируется при ошибке инструкции BOUND.
- Недопустимый опкод. Генерируется при попытке выполнения недопустимого кода операции.
- Устройство недоступно. Сейчас не используется, генерировался при попытке использования операций с плавающей точкой на процессорах без FPU.
- Double fault. Сложно перевести название. Ошибка невосстановима, происходит при невозможности вызвать обработчик исключения.
- Переполнение сегмента сопроцессора. Больше не используется.
- Недопустимый TSS. Сегмент состояния задачи задан неправильно.
- Сегмент отсутствует. Возникает при попытке загрузки сегмента с битом Present == 0.
- Ошибка сегмента стека. Возникает при попытке загрузки сегмента с битом Present == 0 или переполнении стека.
- Общая ошибка защиты. Генерируется в очень большом числе случаев, среди них есть ошибка сегмента, попытка выполнения инструкции без необходимых прав, запись туда, куда не надо, попытка доступа к нулевому дескриптору GDT и многое другое.
- Ошибка страницы. Происходит при чтении или записи в несуществующую страницу памяти, попытке доступа к данным без необходимых прав или другом.
- Ошибка с плавающей точкой. Происходит при выполнении инструкции FWAIT или WAIT с битом №5 в CR0 == 0.
- Ошибка при проверке на выравнивание. Происходит только в третьем кольце привилегий процессора, если эта ошибка, конечно, включена.
- Ошибка при проверке машины. Генерируется процессором при обнаружении «железных» ошибок.
- Исключение с плавающей точкой SIMD. Генерируется при ошибках с 128-битными числами с плавающей точкой.
- Ошибка виртуализации.
- Ошибка безопасности.
- Тройная ошибка. По сути исключением не является, это даже не прерывание. Происходит при невозможности вызвать Double Fault. Вызывает немедленную перезагрузку компьютера.
Существует особый тип прерываний — IRQ (Interrupt ReQuest), или же аппаратные прерывания, но я буду их для краткости называть просто IRQ. Технически они почти не отличаются от любых других прерываний, но генерируются не процессором или самим кодом, а устройствами, подключенными к компьютеру. К примеру, IRQ №0 генерируется PIT (таймер с программируемым интервалом), IRQ 1 генерируется при нажатии клавиши на клавиатуре, а IRQ 12 — при действии с PS/2-мышью.
Еще есть так называемые программные прерывания. Их, как понятно из названия, программа должна вызывать сама — никто их за нее не вызывает. Таковыми являются, например, системные вызовы в некоторых системах. В Linux, например, они висят на векторе 0x80. Во многих хобби-ОС они тоже висят на векторе 0x80. Теперь немного отсебятины — я думаю, что сисвызовы сделаны в виде прерываний из-за того, что 1) их так очень легко вызывать, 2) их можно вызвать из любого кода, работающего в ОС — IDT-то одна на всю систему.
Информация взята с OSDev Wiki.
habr.com
Программные прерывания — Мегаобучалка
Хотя большинство прерываний генерируется аппаратно, ядро Windows тоже может генерировать прерывания — только они являются программными. Этот вид прерываний служит для решения многих задач, в том числе:
· инициации диспетчеризации потоков;
· обработки прерываний, не критичных по времени;
· обработки событий таймеров;
· асинхронного выполнения какой-либо процедуры в контексте конкретного потока;
· поддержки асинхронного ввода-вывода. Эти задачи подробно рассматриваются ниже.
Прерывания представляют собой механизм, позволяющий координировать параллельное функционирование отдельных устройств вычислительной системы и реагировать на особые состояния, возникающие при работе процессора.
Переключение по прерыванию отличается от переключения, которое происходит по команде безусловного или условного перехода, предусмотренной программистом в потоке команд приложения. Переход по команде происходит в заранее определенных программистом точках программы. Прерывание же происходит в произвольной точке потока команд программы, которую программист не может прогнозировать. Сходство прерываний с процедурами состоит в том, что в обоих случаях выполняется некоторая подпрограмма, обрабатывающая специальную ситуацию, а затем продолжается выполнение основной ветви программы.
В зависимости от источника прерывания делятся на три больших класса: внешние, внутренние и программные.
Внешние (аппаратные) прерывания могут формироваться в результате действий пользователя или аппаратных устройств, например, контроллеров внешних устройств ввода-вывода, датчиков. Данный класс прерываний является асинхронным по отношению к потоку инструкций прерываемой программы. Аппаратура процессора работает так, что асинхронные прерывания возникают между выполнением двух соседних инструкций, при этом система после обработки прерывания продолжает выполнение процесса, уже начиная со следующей инструкции.
Внутренние прерывания, называемые также исключениями (exeption), происходят синхронно выполнению программы при появлении аварийной ситуации в ходе исполнения некоторой инструкции программы. Примерами исключений являются деление на ноль, ошибки защиты памяти, обращения по несуществующему адресу и т. п. Исключения возникают непосредственно в ходе выполнения тактов команды («внутри» выполнения).
Программные прерывания отличаются от предыдущих двух классов тем, что они по своей сути не являются «истинными» прерываниями. Программное прерывание возникает при выполнении особой команды процессора, выполнение которой имитирует прерывание, то есть переход на новую последовательность инструкций.
Прерываниям приписывается приоритет, с помощью которого они ранжируются по степени важности и срочности.
Прерывания обычно обрабатываются модулями операционной системы, так как действия, выполняемые по прерыванию, относятся к управлению разделяемыми ресурсами ВС. Процедуры, вызываемые по прерываниям, обычно называют обработчиками прерываний или процедурами обслуживания прерываний (Interrupt Service Routine, ISK). Аппаратные прерывания обрабатываются драйверами соответствующих внешних устройств, исключения – специальными модулями ядра, а программные прерывания – процедурами ОС, обслуживающими системные вызовы. Кроме этих модулей в операционной системе может находиться так называемый диспетчер прерываний, который координирует работу отдельных обработчиков прерываний.
В начале прерывания использовались в основном для управления процессором устройствами ввода-вывода. Затем прерывания стали использовать для организации внутренней работы ЭВМ. В соответствии с этим существуют следующие типы прерываний:
1. Аппаратные прерывания – прерывания от устройств компьютера.
2. Программные прерывания – прерывания, которые вырабатывают процессы, находящиеся на стадии выполнения.
3. Логические прерывания – Эти прерывания вырабатывает сам процессор, когда встречается с каким-либо необходимым условием:
1. деление на 0
2. переполнение регистров микропроцессора
3. пошаговое выполнение программ
4. режим контрольных точек.
Каждое прерывание имеет два параметра:
1. Номер прерывания
2. Вектор прерывания.
Вектор прерывания– это адрес ячейки памяти, где хранится программа – обработчик прерывания.
Прерывания обозначаются — IRQ.
Переход от выполнения одного потока к другому осуществляется в результате планирования и диспетчеризации. Работа по определению того, в какой момент необходимо прервать выполнение текущего активного потока и какому потоку предоставить возможность выполняться, называется планированием.
Диспетчеризациязаключается в реализации найденного в результате планирования (динамического или статистического) решения, то есть в переключении процессора с одного потока на другой. Прежде чем прервать выполнение потока, ОС запоминает его контекст, с тем, чтобы впоследствии использовать эту информацию для последующего возобновления выполнения данного потока. Контекст отражает, во-первых, состояние аппаратуры компьютера в момент прерывания потока: значение счетчика команд, содержимое регистров общего назначения, режим работы процессора, флаги, маски прерываний и другие параметры. Во-вторых, контекст включает параметры операционной среды, а именно ссылки на открытые файлы, данные о незавершенных операциях ввода-вывода и т. д.
Диспетчеризация сводится к следующему:
· сохранение контекста текущего потока, который требуется сменить;
· загрузка контекста нового потока, выбранного в результате планирования;
· запуск нового потока на выполнение.
Поскольку операция переключения контекстов существенно влияет на производительность вычислительной системы, программные модули ОС выполняют диспетчеризацию потоков совместно с аппаратными средствами процессора.
Уровни запросов программных прерываний
Хотя контроллеры прерываний различают уровни приоритетов прерываний, Windows использует свою схему приоритетов прерываний, известную под названием уровни запросов прерываний (interrupt request levels, IRQL). Внутри ядра IRQL представляются в виде номеров 0-31 в системах x86 и 0-15 в системах x64 и IA64, причем больший номер соответствует прерыванию с более высоким приоритетом. Ядро определяет стандартный набор IRQL для программных прерываний, a HAL увязывает IRQL с номерами аппаратных прерываний. IRQL, определенные для архитектуры x86, показаны на рис. 3–3, а аналогичные сведения для архитектур x64 и IA64 — на рис. 3–4.
ПРИМЕЧАНИЕ Уровень SYNCH_LEVEL, используемый многопроцессорными версиями ядра для защиты доступа к индивидуальным для каждого процессора блокам PRCB (processor control blocks), не показан на этих схемах, так как его значение варьируется в разных версиях Windows. Описание SYNCH_LEVEL и его возможных значений.
Рис. 3–4. Уровни запросов прерываний (IRQL) в системах x64 и IA64
Прерывания обслуживаются в порядке их приоритета, и прерывания с более высоким приоритетом вытесняют обработку прерываний с меньшим приоритетом. При возникновении прерывания с высоким приоритетом процессор сохраняет информацию о состоянии прерванного потока и активизирует сопоставленный с данным прерыванием диспетчер ловушки. Последний повышает IRQL и вызывает процедуру обслуживания прерывания (ISR). После выполнения ISR диспетчер прерывания понижает IRQL процессора до исходного уровня и загружает сохраненные ранее данные о состоянии машины. Прерванный поток возобновляется с той точки, где он был прерван. Когда ядро понижает IRQL, могут «материализоваться» ранее замаскированные прерывания с более низким приоритетом. Тогда вышеописанный процесс повторяется ядром для обработки и этих прерываний.
Уровни приоритетов IRQL имеют совершенно иной смысл, чем приоритеты в схеме планирования потоков. Приоритет в этой схеме является атрибутом потока, тогда как IRQL — атрибутом источника прерывания, например клавиатуры или мыши. Кроме того, IRQL каждого процессора меняется во время выполнения команд операционной системы.
Значение IRQL определяет, какие прерывания может получать данный процессор. IRQL также используется для синхронизации доступа к структурам данных режима ядра (о синхронизации мы поговорим позже). При выполнении поток режима ядра повышает или понижает IRQL процессора либо напрямую (вызовом соответственно KeRaiseIrql или KeLowerIrqL), либо — что бывает гораздо чаще — опосредованно (через функции, которые обращаются к синхронизирующим объектам ядра). Как показано на рис.3–5, прерывания от источника с IRQL, превышающим текущий уровень, прерывают работу процессора, а прерывания от источников, IRQL которых меньше или равен текущему уровню, маскируются до тех пор, пока выполняемый поток не понизит IRQL.
Поскольку доступ к PIC — операция довольно медленная, в HAL, использующих PIC, реализован механизм оптимизации «отложенный IRQL» (lazy IRQL), который избегает обращений к PIC Когда IRQL повышается, HAL — вместо того чтобы изменять маску прерывания — просто отмечает новый IRQL. Если вслед за этим возникает прерывание с более низким приоритетом, HAL устанавливает маску прерывания в соответствии с первым и откладывает обработку прерывания с более низким приоритетом до понижения IRQL. Таким образом, если при повышенном IRQL не возникнет прерываний с более низким приоритетом, HAL не потребуется обращаться к PIC.
Поток режима ядра повышает и понижает IRQL процессора, на котором он выполняется, в зависимости от того, что именно делает этот поток. Например, обработчик ловушки (или сам процессор) при прерывании повышает IRQL процессора до IRQL источника прерывания. B результате все прерывания с более низким или равным IRQL маскируются (только на этом процессоре), что не дает прерыванию с таким же или более низким IRQL помешать процессору обработать текущее прерывание. Замаскированные прерывания либо обрабатываются другим процессором, либо откладываются до понижения IRQL. Поэтому все системные компоненты, в том числе ядро и драйверы устройств, пытаются удерживать IRQL на уровне passive («пассивный»), иногда называемом низким уровнем. Если бы IRQL долго оставался неоправданно высоким, драйверы устройств не смогли бы оперативно реагировать на аппаратные прерывания.
megaobuchalka.ru
Прерывания в конвейеризированных процессорах / Habr
Наверняка вы знаете, что такое прерывания. Возможно, даже интересовались устройством процессора. Почти наверняка вы нигде не видели внятный рассказ про то, как именно процессор обнаруживает прерывание, переходит к обработчику и, самое главное, возвращается из него именно туда, куда положено.Я писал эту статью год. Изначально она была рассчитана на хардварщиков. Понимание того, что я ее никогда не закончу, а также жажда славы и желание, чтобы ее прочло больше десяти человек, заставило меня адаптировать ее для относительно широкой аудитории, повыкидывав схемы, куски кода на Верилоге и километры временных диаграмм.
Если когда-нибудь вы задумывались над тем, что значат слова «the processor supports precise aborts» в даташите, прошу под кат.
Немного терминологии: процессор, процессы и прерывания
Чтобы не пытаться объять необъятное, я не буду рассматривать:
- Процессоры с экзотическими архитектурами (стековыми, потоковыми, асинхронными и так далее), потому что их доля на рынке весьма мала, а в качестве примера логичнее использовать распространенную архитектуру. RISC я выбрал исключительно по религиозным соображениям
- Многоядерные процессоры, потому что каждое процессорное ядро обрабатывает свои прерывания независимо от других ядер
- Суперскалярные, многопоточные и VLIW процессоры, потому что с точки зрения организации прерываний они похожи на скалярные процессоры (хотя, разумеется, гораздо сложнее).
Таким образом, под процессорами я буду понимать только одноядерные однопоточные скалярные RISC-процессоры. Предполагаю, что читатель хотя бы в общих чертах знаком с их устройством.
Итак, процессор — это устройство, выполняющее последовательность команд (программу) для решения некоторой задачи. Для каждой команды, в свою очередь, процессор должен выполнить последовательность операций, называемую циклом команды (instruction cycle) и состоящую из следующих этапов:
- Выборка команды из памяти
- Декодирование команды
- Исполнение команды
- Запись результатов в регистры и/или память
Процессор с последовательным выполнением команд начинает выполнение очередного цикла команды только после того, как будет закончен предыдущий, то есть в каждый момент времени выполняется только одна команда.
Процессор с параллельным выполнением команд может выполнять несколько команд одновременно. Например, процессор с четырехстадийным конвейером команд может одновременно записывать результаты первой команды, испонять вторую, декодировать третью и выбирать из памяти четвертую.
Процесс — это выполняющаяся программа. Процесс должен давать одинаковые результаты вне зависимости от того, выполняется ли он на процессоре с последовательным или параллельным выполнением команд. Состояние процесса определяется содержимым:
- счетчика команд процессора (program counter, он же instruction pointer)
- регистров процессора (общего назначения, статусных, флагов и так далее)
- оперативной памяти
В системах реального времени необходимо также учитывать влияние кэш-памяти, буферов ассоциативной трансляции MMU (translation lookaside buffer, TLB) и таблиц динамического предсказания переходов.
Каждая выполненная команда каким-то образом обновляет состояние процесса:
- арифметические и логические команды обновляют содержимое регистров и счетчика команд
- команды перехода обновляют содержимое счетчика команд и таблицы динамического предсказания переходов
- команды загрузки обновляют содержимое регистров, счетчика команд и кэш-памяти (при промахе кэша; если потребуется замещение линии кэша — то еще и оперативной памяти)
- команды сохранения обновляют содержимое оперативной памяти (или кэш-памяти) и счетчика команд
Прерывание — это событие, при наступлении которого процессор должен приостановить выполнение текущего процесса, сохранить его состояние и начать выполнять другой процесс, называемый обработчиком прерывания (interrupt handler). После завершения обработчика прерывания состояние прерванного процесса должно быть восстановлено, а в случае фатального прерывания (например, из-за отказа аппаратуры) процессор должен быть перезагружен или остановлен.
В зависимости от источника прерывания оно может быть:
- Внутренним, если вызвано выполнением команды в процессоре:
- Программным (software interrupt), если вызвано специальной командой
- Исключением (exception, fault, abort – это все оно), если вызвано ошибкой при выполнении команды
- Внешним, если вызвано произошедшим снаружи процессора событием
Команду, которая выполнялась в момент появления любого из вышеперечисленных прерываний, для краткости буду называть прерываемой командой.
Сохранение и восстановление состояния процесса может быть реализовано аппаратно, программно или программно-аппаратно. В дальнейшем я буду рассматривать простейший программно-аппаратный вариант, при котором:
- процессор сохраняет счетчик команд в специальный регистр адреса возврата (РАВ), одновременно записывая вектор прерывания в счетчик команд, запуская таким образом обработчик прерывания
- все прочие элементы состояния процесса сохраняются обработчиком прерывания при необходимости (например, прежде чем использовать регистры, он должен сохранить их содержимое в стек)
- перед завершением обработчика прерывания он должен восстановить все элементы состояния процесса, которые изменял (например, восстановить содержимое регистров, сохраненное в стек)
- обработчик прерывания завершается командой возврата из прерывания, которая записывает содержимое РАВ обратно в счетчик команд, то есть возвращает управление прерванному процессу
После возврата управления прерванному процессу он должен иметь возможность продолжить работу так, как будто его и не прерывали. Это требование тривиально, однако для большинства современных процессоров его довольно сложно выполнить. Настолько сложно, что иногда от него отказываются. Прерывания, которые гарантируют выполнение этого требования, называют точными (precise), а прочие — неточными (imprecise).
Точные и неточные прерывания
Формально прерывание называется точным, если выполнены все перечисленные ниже условия:
- все команды, предшествующие прерываемой, были полностью выполнены и корректно сохранили состояние процесса
- все команды, следующие за прерываемой, не были выполнены и ни коим образом не изменили состояние процесса
- прерываемая команда, в зависимости от типа прерывания, либо была полностью выполнена, либо не была выполнена вовсе
Первые два условия точности не нуждаются в комментариях. Третье условие обусловлено следующим:
- Команда, выполнявшаяся в момент прихода внешнего прерывания, должна обновить состояние процесса перед тем, как оно будет сохранено. То же самое касается команды, вызвавшей программное прерывание. В обоих случаях РАВ будет указывать на команду, которая, не случись прерывания, должна была быть выполнена следующей. Она и будет выполнена сразу после возврата из обработчика прерывания
- Команда, вызвавшая исключение — «плохая» команда. Ее результаты, скорее всего, некорректны, поэтому она не должна обновлять состояние процесса. Вместо этого в РАВ сохраняется ее адрес, после чего вызывается обработчик прерывания, который попытается исправить ошибку. После возврата из обработчика эта команда будет выполнена повторно. Если она снова вызовет такое же исключение, значит ошибка неисправима и процессор сгенерирует фатальное прерывание
Очевидно, что внешние прерывания должны быть точными всегда. Кому нужен процессор, который не может корректно восстановить процесс после обработки прерывания от таймера?
Программные прерывания и исключения могут быть точными или неточными. В некоторых случаях без точных исключений просто не обойтись — например, если в процессоре есть MMU (тогда, если случается промах TLB, управление передается соответствующему обработчику исключения, который программно добавляет нужную страницу в TLB, после чего должна быть возможность заново выполнить команду, вызвавшую промах).
В микроконтроллерах исключения могут быть неточными. Например, если команда сохранения вызвала исключение из-за ошибки памяти, то вместо того, чтобы пытаться как-то исправить ошибку и повторно выполнить эту команду, можно просто перезагрузить микроконтроллер и начать выполнять программу заново (то есть сделать то же самое, что делает сторожевой таймер, когда программа зависла).
В большинстве учебников по архитектуре компьютеров (включая классику типа Patterson&Hennessy и Hennessy&Patterson) точные прерывания обходятся стороной. Кроме того, неточные прерывания не представляют никакого интереса. По-моему, это отличные причины продолжить рассказ именно про точные прерывания.
Точные прерывания в процессорах с последовательным выполнением команд
Для процессоров с последовательным выполнением команд реализация точных прерываний довольно проста, поэтому представляется логичным начать с нее. Поскольку в каждый момент времени выполняется только одна команда, то в момент обнаружения прерывания все команды, предшествующие прерываемой, уже выполнены, а последующие даже не начаты.
Таким образом, для реализации точных прерываний в таких процессорах достаточно убедиться, что прерываемая команда никогда не обновляет состояние процесса до тех пор, пока не станет ясно, вызвала она исключение или нет.
Место, где процессор должен определить, позволить ли команде обновить состояние процесса или нет, называется точкой фиксации результатов (commit point). Если процессор сохраняет результаты команды, то есть команда не вызвала исключение, то говорят, что эта команда зафиксирована (на сленге — закоммичена).
Чтобы понять, где же должна быть расположена точка фиксации результатов, полезно вспомнить этапы цикла команды:
- Выборка команды из памяти
- Декодирование команды
- Исполнение команды
- Запись результатов в регистры и/или память
По определению, она должна находиться до записи результатов, но к этому моменту уже должно быть известно, вызвала команда исключение или нет. Исключение может произойти на любом из четырех этапов, например:
- ошибка памяти при выборке команды
- неизвестный код операции при декодировании
- деление на ноль при исполнении
- ошибка памяти при записи результатов
Очевидно, что реализация точных прерываний невозможна до тех пор, пока не решена проблема записи результатов в память:
- нельзя фиксировать команду и разрешать ей записывать результаты в память до тех пор, пока не станет ясно, что команда не вызвала исключение
- нельзя узнать, что исключение не вызвано, не записав результаты в память (для этого нужно получить подтверждение от контроллера памяти, что запись произведена успешно)
Как можно догадаться, эту проблему довольно сложно решить, поэтому во многих процессорах для простоты реализованы «почти точные» прерывания, то есть точными сделаны все прерывания, кроме исключений, вызванных ошибками памяти при записи результатов. В этом случае точка фиксации результатов находится между третьим и четвертым этапами цикла команды.
Важно! Нужно помнить, что счетчик команд тоже должен обновляться строго после точки фиксации результатов. При этом он изменяется вне зависимости от того, зафиксирована команда или нет — в него записывается либо адрес следующей команды, либо вектор прерывания, либо РАВ.
Точные прерывания в процессорах с параллельным выполнением команд
На сегодняшний день процессоров с последовательным выполнением команд почти не осталось (могу вспомнить разве что аналоги интеловского 8051) — их вытеснили процессоры с параллельным выполнением команд, обеспечивающие при прочих равных более высокую производительность. Простейший процессор с параллельным выполнением команд — процессор с конвейером команд (instruction pipeline).
Несмотря на многочисленные преимущества, конвейер команд значительно усложняет реализацию точных прерываний, чем много десятков лет печалит разработчиков.
В процессоре с последовательным выполнением команд этапы цикла команды зависят друг от друга. Простейший пример — счетчик команд. Вначале он используется на этапе выборки (как адрес в памяти, откуда должна быть прочитана команда), затем на этапе исполнения (для вычисления его следующего значения), и потом, если команда зафиксирована, он обновляется на этапе записи результатов. Это приводит к тому, что нельзя выбрать следующую команду до тех пор, пока предыдущая не завершит последний этап и не обновит счетчик команд. То же самое относится и ко всем прочим сигналам внутри процессора.
Процессор с конвейером команд можно получить из процессора с последовательным выполнением команд, если сделать так, чтобы каждый этап цикла команды был независим от предыдущих и последующих этапов.
Для этого результаты каждого этапа, кроме последнего, сохраняются во вспомогательных элементах памяти (регистрах), расположенных между этапами:
- Результат выборки — закодированная команда — сохраняется в регистре, расположенном между этапами выборки и декодирования
- Результат декодирования — тип операции, значения операндов, адрес результата — сохраняются в регистрах между этапами декодирования и исполнения
- Результаты исполнения — новое значение счетчика команд для условного перехода, вычисленный в АЛУ результат арифметической операции и так далее — сохраняются в регистрах между этапами исполнения и записи результатов
- На последнем этапе результаты и так записываются в регистры и/или память, поэтому никакие вспомогательные регистры не нужны.
Вот так работает получившийся конвейер:
Такт СК Выборка Декодирование Исполнение Запись_результатов 1 0x00 Команда1 - - - 2 0x04 Команда2 Команда1 - - 3 0x08 Команда3 Команда2 Команда1 - 4 0x0C Команда4 Команда3 Команда2 Команда1 5 0x10 Команда5 Команда4 Команда3 Команда2
Обратите внимание на столбец СК («счетчик команд»). Его значение меняется каждый такт и определяет адрес в памяти, откуда выбирается команда.
Внимательный читатель уже заметил небольшую неувязочку — для обеспечения точности прерываний первая команда не имеет права изменить счетчик команд раньше четвертого такта. Чтобы это исправить, мы должны перенести счетчик команд за точку фиксации результата (предположим, что она находится между третьим и четвертым этапами):
Такт Выборка Декодирование Исполнение Запись_результатов СК 1 Команда1 - - - 0х00 2 - Команда1 - - 0х00 3 - - Команда1 - 0х00 4 Команда2 - - Команда1 0х04 5 - Команда2 - - 0х04
Производительность процессора немного упала, не так ли? На самом деле, решение лежит на поверхности – нам нужно два счетчика команд! Один должен находиться в начале конвейера и указывать, откуда читать команды, второй – в конце, и указывать на ту команду, которая должна быть зафиксирована следующей.
Первый называется «спекулятивным», второй – «архитектурным». Чаще всего спекулятивный счетчик команд не существует сам по себе, а встроен в предсказатель переходов. Выглядит это вот так:
Такт ССК Выборка Декодирование Исполнение Запись_результатов АСК 1 0x00 Команда1 - - - 0х00 2 0x04 Команда2 Команда1 - - 0х00 3 0x08 Команда3 Команда2 Команда1 - 0х00 4 0x0C Команда4 Команда3 Команда2 Команда1 0х04 5 0x10 Команда5 Команда4 Команда3 Команда2 0х08
Дальше происходит вот что. Команда, перемещаясь между этапами, тащит за собой адрес, из которого она была выбрана (то есть ее ССК). Перед точкой фиксации результата процессор смотрит, не пришло ли внешнее прерывание, не вызвала ли команда исключение, а также сравнивает ее адрес с АСК:
- Если пришло внешнее прерывание, команда коммитится, но адрес следующей команды записывается не в АСК, а в РАВ. В АСК записывается адрес вектора прерывания.
- Если возникло исключение, команда не коммитится, вместо этого в АСК записывается адрес вектора соответсвующего исключения, а адрес команды записывается в РАВ.
- Если адрес команды не равен АСК, она тоже не коммитится (об этом позже). Если адрес равен АСК и исключения не произошло – процессор фиксирует команду и обновляет АСК (записывает адрес перехода в случае команды ветвления или просто инкрементирует в случае другой команды)
Почему адрес команды может быть не равен АСК? Возьмем мой любимый пример: процессор только что включили, и он выбирает первую команду из таблицы прерываний, которая является ни чем иным как командой перехода в далекую даль (по адресу 0х1234):
Такт ССК Выборка Декодирование Исполнение Запись_результатов АСК 1 0x00 jump 0x1234 - - - 0х00 2 0x04 Команда2 jump 0x1234 - - 0х00 3 0x08 Команда3 Команда2 jump 0x1234 - 0х00 4 0x0C Команда4 Команда3 Команда2 jump 0x1234 0х1234 *** Для Команды2 на четвертом такте ее адрес (0х04) не равен АСК, потому что переход был предсказан неверно*** 5 0x1234 Команда666 - - - 0х1234 6 0x1238 Команда667 Команда666 - - 0х1234 7 0x1240 Команда668 Команда667 Команда666 - 0х1234 8 0x1244 Команда669 Команда668 Команда667 Команда666 0х1238
На этом все. Разумеется, показаный четырехстадийный конвейер прост до невозможности. На самом деле, некоторые команды могут исполняться более одного такта, и даже простой микроконтроллер умеет завершать их не в том порядке, в котором он запустил их на выполнение, при этом обеспечивая точность прерываний. Однако общий принцип организации прерываний, смею вас заверить, остается тем же.
Желающим усугубить взрыв мозга рекомендую ознакомиться с Implementation of precise interrupts in pipelined processors. Да-да, ваш новейший Интел Кор Ай Семь работает именно так, как описано в этой статье двадцатипятилетней давности. Добро пожаловать в восьмидесятые!
habr.com
Программные прерывания — это… Что такое Программные прерывания?
- Программные прерывания
Программное прерывание — синхронное прерывание, которое может осуществить программа путем выполнения команды INT. BIOS, хранящиеся в ПЗУ, и прикладные программы IBM PC используют другие прерывания, с большими или меньшими номерами. Это распределение номеров прерываний условно и никаким образом не закреплено аппаратно. Синхронные прерывания происходят не в результате внешних событий, а при выполнении команд процессора. Например, синхронное прерывание происходит при попытке выполнить деления на ноль.
См. также
Wikimedia Foundation. 2010.
- Шведское имя
- Сутупово
Смотреть что такое «Программные прерывания» в других словарях:
Прерывания — Прерывание (англ. interrupt) сигнал, сообщающий процессору о наступлении какого либо события. При этом выполнение текущей последовательности команд приостанавливается, и управление передаётся обработчику прерывания, который выполняет работу по… … Википедия
Вектор прерывания — Прерывание (англ. interrupt) сигнал, сообщающий процессору о наступлении какого либо события. При этом выполнение текущей последовательности команд приостанавливается, и управление передаётся обработчику прерывания, который выполняет работу по… … Википедия
Обработчик прерывания — Эту статью следует викифицировать. Пожалуйста, оформите её согласно правилам оформления статей. Обработчик прерываний (или процедура обслуживания прерываний) специальная процедура, вызываемая по прерыванию для выполнения его обработки.… … Википедия
Прерываний система — в ЦВМ, аппаратные и программные средства, обеспечивающие временное прекращение выполнения последовательности команд для перехода к выполнению др. последовательности команд или для возвращения к ранее прерванной программе. П. с. позволяет… … Большая советская энциклопедия
Прерывание — (англ. interrupt) сигнал, сообщающий процессору о наступлении какого либо события. При этом выполнение текущей последовательности команд приостанавливается и управление передаётся обработчику прерывания, который реагирует на событие и… … Википедия
Таблица векторов прерываний — (англ. Interrupt Descriptor Table, IDT) используется в x86 архитектуре и служит для определения корректного ответа на прерывания и исключения. В микропроцессорах 8086/80186 таблица векторов прерываний расположена в первом килобайте памяти… … Википедия
STI — В системе команд x86 совместимых процессоров, инструкция STI сокращение от «Set Interrupt Enable Flag». Она устанавливает флаг interrupt flag (IF) в регистре EFLAGS, что разрешает процессору обрабатывать асинхронные прерывания от внешних… … Википедия
CLI (x86) — У этого термина существуют и другие значения, см. CLI. В системе команд x86 совместимых процессоров инструкция CLI сокращение от «Clear Interrupt Enable Flag». Она сбрасывает interrupt flag (IF) в регистре EFLAGS. Когда этот флаг сброшен… … Википедия
Расширители DOS — Расширитель DOS (также дос экстендер от англ. DOS extender) технология, позволяющая программам для операционных систем семейства защищённом режиме процессора. Существуют 16 и 32 битные расширители DOS: 16 битные расширители предназначены для… … Википедия
Расширитель DOS — Эта статья или раздел нуждается в переработке. Пожалуйста, улучшите статью в соответствии с правилами написания статей. Расширитель … Википедия
dic.academic.ru
Существует несколько видов экстраординарных ситуаций, в которых поведение процессора регламентируется особыми правилами. Такие ситуации возникают, когда требуется срочная реакция на внешние воздействия, когда при выполнении программы происходит какая-либо ошибка или существует вероятность получения неточного результата вычислений. В общем случае все эти ситуации разделяются по принципу их обнаружения, источника возникновения и реакции на них на следующие три категории:
Прерывания и особые ситуацииПрерывания и особые ситуации (Interrupts and Exceptions) — это специальные средства, обеспечивающие быструю реакцию процессора на внешние воздействия и прочие неожиданные ситуации. При поступлении прерывания или генерации особой ситуации выполнение программы прерывается, а управление передается специальной процедуре — обработчику прерывания или особой ситуации. В большинстве случаев, когда обработка прерывания или особой ситуации заканчивается, управление может быть возвращено в прерванную программу, которая продолжит свое выполнение с той самой точки, в которой она была остановлена. Процессор производит автоматическое сохранение/восстановление контекста и состояния для обеспечения этой возможности. Все прерывания и особые ситуации имеют уникальные идентификационные номера. Эти номера называются векторами прерываний и лежат в пределах от 0 до 255. Векторы от 0 до 31 отведены для особых ситуаций и немаскируемого прерывания, причем некоторые из них зарезервированы и не должны использоваться программами. Векторы от 32 до 255 свободны для любого использования пользовательскими программами и внешними устройствами. Существует два источника поступления прерываний и три типа особых ситуаций. Кроме того, различают внутренние (программные) и внешние (аппаратные) источники генерации прерываний и особых ситуаций.
Внешние или аппаратные прерывания (External or Hardware interrupts):
Программные прерывания и особые ситуации (Software interrupts and Exceptions):
Таблица 3.1. Типы прерываний и особых ситуаций
Типы особых ситуаций и особенности их обработкиОсобые ситуации, генерируемые процессором подразделяются на три типа — ошибки, ловушки и сбои. В зависимости от типа особой ситуации различается реакция процессора на ее возникновение.
Правила функционирования механизма обработки прерываний и особых ситуаций существенно зависят от режима работы процессора и текущих установок некоторых флагов в регистре CR4. Сам этот механизм включает следующие элементы:
Таблица 6.55. Способы обработки процессором прерываний и особых ситуаций в различных режимах работы
|
www.club155.ru
INT n — Вызов процедуры прерывания
Команда INT n обеспечивает программную генерацию прерывания и передачу управления в соответствующий обработчик. Непосредственный операнд команды может принимать значения от 0 до 255, что задает вектор (индекс) вызываемого прерывания по таблице дескрипторов прерываний (IDT) или по таблице векторов прерываний (IVT). При этом первые 32 вектора прерываний (от 0 до 31) зарезервированы Intel для системного использования. Большинство из них используется для обработки генерируемых процессором особых ситуаций. Команда INT n предназначена для программной генерации прерываний с номерами от 32 до 255, но технически возможно вызвать и прерывания с меньшими (любыми) векторами. В этом случае процессор ведет себя не так, как это описано для соответствующего внешнего прерывания или особой ситуации, а так, как это предусмотрено именно для генерируемых программно прерываний пользователя (то есть не записываются никакие коды ошибок, не соблюдаются приоритеты и т.п.).
Команда INT n в общем ведет себя как дальний вызов командой CALL, за исключением того, что регистр флагов помещается в стек перед адресом возврата. Процедуры обработки прерываний завершаются командой IRET/IRETD, которая выбирает из стека флаги и адрес возврата. В деталях же реакция процессора на поступление программного прерывания определяется его текущим режимом работы (см. Прерывания и особые ситуации: Реальный режим, Защищенный режим, Режим V86).
В режиме реальной адресации таблица векторов прерываний (IVT), которую иногда называют просто таблицей прерываний, является массивом 4-байтных дальних указателей (см. рис. 3.1). Конечным результатом исполнения команды INT n в этом случае и является переход (передача управления) по заданному адресу. Процессор автоматически выбирает из памяти соответствующий указатель, затем сохраняет в стеке текущие (16-битные) значения FLAGS, CS, IP (см. рис. 3.2.), очищает биты IF, TF, AC регистра FLAGS и передает управление по считанному адресу. Линейный базовый адрес таблицы прерываний определяется содержимым регистра IDTR. Первоначальное значение IDTR при сбросе процессора или переходе в режим реальной адресации является нулевым.
Рис. 3.1. Таблица векторов прерываний в реальном режиме работы
Рис. 3.2. Стек после вызова прерывания или особой ситуации в реальном режиме работы
В защищенном режиме таблица дескрипторов прерываний (IDT) состоит из множества 8-байтных дескрипторов (см. рис. 3.3). Дескрипторы в таблице могут иметь разные типы. Допустимы: шлюз прерывания, шлюз ловушки или шлюз задачи. При поступлении команды прерывания процессор сохраняет текущие значения EFLAGS, CS и EIP в стеке. Когда передача управления в обработчик связана с изменением уровня привилегий на более высокий (меньшее числовое значение), это сопровождается переключением стека. Новые значения для SS и ESP берутся из TSS текущей задачи в соответствии с уровнем привилегий обработчика. Переключение стека производится до сохранения указанных выше регистров, причем в новом стеке в первую очередь сохраняются значения SS и ESP, указывающие на старый стек (см. рис. 6.10.).
Передавая управление в обработчик, процессор проверяет уровень привилегий DPL соответствующего шлюза с тем, чтобы предотвратить генерацию программных прерываний, если CPL > DPL. Если обработчик прерывания защищенного режима будет расположен в согласованном кодовом сегменте, то ему при получении управления будет доступен только сегмент стека и те сегменты, привилегии которых позволяют это. Если же обработчик будет размещен в несогласованном кодовом сегменте с нулевым уровнем привилегий, то независимо от CPL прерванной программы ему будут доступны все сегменты.
При передаче управления через шлюз прерывания или шлюз ловушки процессор сбрасывает флаги EFLAGS.TF, EFLAGS.RF и EFLAGS.NT, чтобы отключить трассировку прерываний и некоторые особенности обработки вложенных задач. Кроме того, при использовании шлюза прерывания сбрасывается также и флаг разрешения прерываний EFLAGS.IF.
В защищенном режиме (как в и режиме реальной адресации) линейный базовый адрес таблицы IDT определяется содержимым регистра IDTR (см. рис. 3.3).
Рис. 3.3. Таблица дескрипторов прерываний (IDT) в защищенном режиме работы
Рис. 6.10. Стек после вызова программного прерывания в защищенном режиме
При поступлении программного прерывания в режиме V86 процессор автоматически переключается в защищенный режим с уровнем привилегий CPL = 0 (в связи с этим происходит замена указателя стека SS:ESP на стек нулевого уровня привилегий из сегмента состояния задачи TSS) и обращается к соответствующему дескриптору прерывания в таблице IDT. Если в качестве такого дескриптора используется 32-битный шлюз ловушки или шлюз прерывания, то он должен указывать на несогласованный кодовый сегмент с нулевым уровнем привилегий (уровень привилегий DPL соответствующего шлюза должен быть установлен в 3, а CPL процедуры обработки прерывания должен быть равен 0, чтобы обеспечить обработку прерывания защищенного режима на нулевом уровне привилегий), в противном случае генерируется ошибка общей защиты (#GP) с селектором этого сегмента в коде ошибки.
После переключения на нулевой уровень привилегий и загрузки новых значений указателя стека SS:ESP процессор сохраняет в стеке текущие значения сегментных регистров GS, FS, DS, ES, а затем очищает эти регистры, помещая в них нулевые селекторы. Далее действия процессора идентичны процедуре обработки прерываний защищенного режима — он сохраняет в стеке старые значения SS, ESP, EFLAGS, CS и EIP, сбрасывает флаги VM, NT, TF, RF и IF (последний сбрасывается, только если используется шлюз прерывания) регистра флагов EFLAGS и передает управление в обработчик прерывания (см. рис. 6.11.).
Если при вызове программных прерываний командой INT n текущий уровень привилегий ввода/вывода EFLAGS.IOPL меньше текущего уровня привилегий задачи (то есть EFLAGS.IOPL < 3), то генерируется ошибка общей защиты (#GP) и управление передается в обработчик этой ошибки. Такое поведение называется IOPL-чувствительностью и в режиме V86 характерно также для команд CLI, STI, POPF/POPFD, PUSHF/PUSHFD, IRET/IRETD.
При возврате из прерывания по завершении работы программы обработчика защищенного режима процессор получает команду IRETD. Последовательно загружая сохраненные в стеке значения, он считывает и значение флага EFLAGS.VM. При обнаружении единицы в этом флаге производится обратное переключение в режим V86 и восстановление из стека сегментных регистров ES, DS, FS, GS, после чего управление передается в прерванную программу. Если команда IRETD будет получена на уровне привилегий, отличном от нуля, то процессор не сможет установить флаг EFLAGS.VM – и соответственно – не переключится в режим V86 — возврат будет произведен некорректно.
Рис. 6.11. Стек после вызова прерывания в режиме V86
В процессорах, начиная с Pentium, реализуются специальные расширения системы обработки прерываний в защищенном режиме и режиме V86. Это так называемые виртуальные прерывания защищенного режима (включаются при CR4.PVI = 1) и режим EV86 (включается при CR4.VME = 1). В защищенном режиме активация виртуальных прерываний никак не влияет на порядок обработки программных прерываний и особых ситуаций, генерируемых командой INT n (равно как и командами INTO, INT 3, INT01, BOUND). А вот в режиме EV86 включается совершенно другой механизм обработки программных прерываний, который подразумевает наличие четырех разных режимов, определяемых текущими установками флагов и управляющих параметров процессора и конкретной задачи V86:
Указанные режимы действуют только для команд генерации программных прерываний INT n, имеющих двухбайтный код операции (CD ib). Однобайтные команды INTO, INT 3, INT01, а также команда BOUND относятся к командам генерации программных особых ситуаций и на них не распространяется действие расширений режима EV86.
Таблица 6.54. показывает, какое действие из нижней части таблицы происходит в случае выполнения условий из верхней части таблицы. Каждый символ «+» в нижней части указывает на то, что выполняется соответствующая процедура, как это определено в подпункте Операция, в противном случае имеет место ошибка общей защиты #GP(0).
Таблица 6.54. Возможные варианты обработки прерываний
(согласно подпункта Операция)
www.club155.ru
Процедуры и прерывания. Использование стека при вызове процедур и прерываний
Часть 1. Первоначальные сведения об ассемблере и разработке программ для процессора 8086
Глава 3. Загрузка и выполнение программ в DOS
Использование DOS сегментной организации памяти для загрузки и выполнения программ
При загрузке программы в оперативную память DOS инициализирует как минимум три сегментных регистра: CS, DS и SS. При этом совокупности байтов, представляющих команды процессора (код программы), и данные, помещаются из файла на диске в оперативную память, а адреса этих сегментов записываются в регистры CS и DS соответственно. Сегмент стека либо выделяется в области, указанной в программе, либо совпадает (если он явно в программе не описан) с самым первым сегментом программы. Адрес сегмента стека помещается в регистр SS. Программа может иметь несколько кодовых сегментов и сегментов данных и в процессе выполнения специальными командами выполнять переключения между ними.
Для того чтобы адресовать одновременно два сегмента данных, например при выполнении операции пересылки из одной области памяти в другую, можно использовать регистр дополнительного сегмента данных ES. Кодовый сегмент и сегмент стека всегда определяются содержимым своих регистров (CS и SS), и поэтому в каждый момент выполнения программы всегда используется какой-то один кодовый сегмент и сегмент стека. Причем если переключение кодового сегмента – довольно простая операция, то переключать сегмент стека можно только при условии четкого представления логики работы программы со стеком, иначе это может привести к зависанию системы.
Все сегменты могут использовать различные области памяти, а могут частично или полностью совпадать (рис. 3.1).
Кодовый сегмент должен обязательно описываться в программе, все остальные могут отсутствовать. В этом случае DOS при загрузке программы в оперативную память инициализирует регистры DS и ES значением адреса префикса программного сегмента PSP (Program Segment Prefics) – специальной области оперативной памяти размером 256 (100h) байт. PSP может использоваться в программе для определения имен файлов и параметров из командной строки, введенной при запуске программы на выполнение, объема доступной памяти, переменных окружения системы и т.д. Регистр SS при этом инициализируется значением сегмента, находящегося сразу за PSP, т.е. первого сегмента программы. При этом необходимо учитывать, что стек «растет вниз», иными словами при помещении в стек каких-либо значений содержимое регистра SP, указывающего на вершину стека, уменьшается на 2, а при считывании из стека – на столько же увеличивается (цифра 2 связана с тем, что стек работает со словами, а не с байтами). Таким образом, при помещении в стек каких-либо значений они могут затереть PSP и программы, находящиеся в младших адресах памяти (например, DOS), что может привести к непредсказуемым последствиям. Поэтому рекомендуется всегда явно описывать сегмент стека в тексте программы, задавая ему размер, достаточный для нормальной работы.
В примере программы HELLO из главы 1 в тексте программы явно описаны два сегмента – кода с именем CODE и данных с именем DATA. Директива ASSUME связывает имена этих сегментов, которые в общем случае могут быть произвольными, с сегментными регистрами CS и DS соответственно (рис.3.2).
Как видно из рисунка, сегмент стека в данном случае установлен на конец PSP, что при его интенсивном использовании могло бы привести к неожиданным результатам (именно по этой причине компоновщик выдал предупреждение Warning: No stack).
После инициализации в регистре IP находится смещение первой команды программы относительно начала кодового сегмента, адрес которого помещен в CS. Процессор, считывая эту команду, начинает выполнение программы, постоянно изменяя содержимое регистра IP и при необходимости CS для получения кодов очередных команд до тех пор, пока не встретит команду завершения программы.
DS после загрузки программы установлен на начало PSP, поэтому для его использования в первых двух командах программы выполняется загрузка DS значением сегмента данных программы.
Рис.3.2. Состояние сегментных регистров после загрузки программы HELLO.
Процедуры и прерывания. Использование стека при вызове процедур и прерываний
Все современные программы разрабатываются по модульному принципу – программа обычно состоит из нескольких небольших частей, называемых подпрограммами или процедурами, и одной главной программы, которая вызывает эти процедуры на выполнение, передавая им управление процессором. После завершения работы процедуры возвращают управление главной программе и выполнение продолжается с команды, следующей за командой вызова процедуры.
Достоинством такого метода является возможность разработки программ значительного объема небольшими функционально законченными частями. Кроме того, эти процедуры можно использовать в других программах, не прибегая к переписыванию частей программного кода. В довершение ко всему, так как размер сегмента не может превышать 64К, то при разработке программ с объемом кода более 64К, просто не обойтись без модульного принципа.
Ассемблер поддерживает применение процедур. Процедуры в программах на ассемблере могут быть двух типов – ближнего (near) и дальнего (far).
Процедуры ближнего типа должны находиться в том же сегменте, что и вызывающая процедура. Дальний тип процедуры означает, что к ней в принципе можно обращаться и из любого другого кодового сегмента.
При вызове процедуры в стеке сохраняется адрес возврата в вызывающую процедуру:
• при вызове ближней процедуры – слово, содержащее смещение точки возврата относительно текущего кодового сегмента;
• при вызове дальней процедуры – слово, содержащее адрес сегмента, в котором расположена точка возврата, и слово, содержащее смещение точки возврата в этом сегменте.
На рис. 3.3 приведен текст программы HELLO с изменениями, реализующими модульный принцип разработки программ. Как видно из текста программы, теперь она оформлена в виде двух процедур – главной процедуры с именем Main, имеющей тип far, и процедуры вывода сообщения на экран WriteMsg типа near.
AStack SEGMENT STACK
DW 12 DUP(?)
AStack ENDS
AData. SEGMENT
Hello DB ‘Здравствуйте !$’
AData ENDS
ACode SEGMENT
ASSUME CS: ACode, DS: AData, SS: AStack
WriteMsg PROC NEAR
mov AH, 9
int 21h
ret
WriteMsg ENDP
Main PROC FAR
push DS
sub AX, AX
ush AX
mov AX, AData
mov DS, AX
mov DX, OFFSET Hello
call WriteMsg
ret
Main ENDP
ACode ENDS
END Main
Рис. 3.3. Программа HELLO, построенная по модульному принципу.
Появление трех новых операторов в главной процедуре (push DS – помещение в стек содержимого регистра DS; ub АХ,АХ – вычитание содержимого регистра АХ из содержимого регистра АХ, т.е. обнуление AX; push АХ – помещение в стек регистра АХ, т.е. 0) объясняется именно оформлением программы в виде процедур. В конце каждой процедуры находится команда ret, выполнение которой приводит к тому, что из стека восстанавливается адрес точки возврата (одно или два слова, в зависимости от типа процедуры) и по этому адресу осуществляется передача управления.
При вызове процедуры WriteMsg оператором call WriteMsg в стек помещается слово (так как WriteMsg имеет ближний тип), являющееся смещением следующей команды относительно текущего сегмента (в данном случае это команда ret процедуры Main), после чего указатель стека SP автоматически уменьшается на 2, а управление передается первому оператору процедуры WriteMsg (mov АН,9). При выполнении в процедуре WriteMsg команды ret все операции повторяются в обратном порядке: из стека восстанавливается слово, являющееся смещением точки возврат,] относительно кодового сегмента, и по этому адресу выполняется передача управления. При этом содержимое SP увеличивается на 2.
В процедуре же Main выполнение команды ret приводит к восстановлению из стека двух слов, адресующих точку передачи управления. Поскольку в начале процедуры Main в стек были помещены значения регистра DS в момент инициализации программы И 0, а начальное значение регистра DS устанавливается в DOS равным адресу PSP, эти два слова, восстанавливаемые из стека, есть не что иное, как адрес начала PSP, куда и передается управление программой. Первые два байта PSP, в свою очередь, представляют команды, принуждающие программу возвратиться в DOS, поэтому выполнение программы завершается. При помещении в стек в начале процедуры Main DS и 0 содержимое указателя стека уменьшается на 4, при выполнении командь1 ret – восстанавливается до исходного состояния.
Другим видом процедур являются прерывания DOS и BIOS. Прерывания – это обычные процедуры, написанные разработчиками операционной системы (прерывания DOS) или разработчиками аппаратных средств компьютера (прерывания BIOS). Поэтому к этим процедурам можно обращаться точно так жй, как и к другим процедурам. Отличает их лишь форма вызова: вместо команды типа
call ProcNeme
используются команда типа
int Number,
где Number – номер прерывания, например, int 10, int OAh, int lOlOb и т. п.
Прерывания могут быть аппаратными и программными. Аппаратные прерывания возникают от какого-нибудь устройства компьютера, например от клавиатуры, таймера; дисковода или какого-нибудь нестандартного устройства, подключенного к компьютеру через плату расширения. При возникновении аппаратного прерывания процессор прекращает выполнение текущей программы, сохраняет в стеке текущие значения регистров CS и IP, а также флагового регистра и переходит к выполнению программы обработки прерывания путем помещения в CS: IP адреса этой процедуры» Программы обработки аппаратных прерываний заканчиваются командой IRET, которая всегда выталкивает из стека три слова – старое значение CS, IP и регистра флагов, что приводит к продолжению выполнения прерванной программы с того места, в котором возникло аппаратное прерывание.
Механизм вызова программных прерываний аналогичен вызову аппаратных прерываний, за исключением того, что они вызываются в нужных местах программы с помощью команды INT. При вызове прерывания процессор ищет его адрес, используя номер прерывания, в таблице векторов прерываний (вектор – адрес программы обработки прерывания). Эта таблица начинается по адресу 0000:0000, т.е. с самых младших адресов оперативной памяти, и имеет длину 1024 байта. Учитывая, что векторы прерываний хранятся в этой таблице как 16-битовое смещение и 16-битовый сегмент, и каждый вектор, таким образом, имеет длину 4 байта, размер таблицы достаточен для размещения в ней 256 векторов. Чтобы получить адрес необходимого вектора прерывания, достаточно умножить его номер на 4. Например, вектор прерывания 9 (прерывание, аппаратно вызываемое при нажатии клавиши на клавиатуре) находится по адресу 0000:0024 (24h = 36 = 9 х 4).
В программе HELLO, приведенной на рис.3.3, в процедуре WriteMsg осуществляется вызов прерывания int 21h. Это прерывание называется «Вызов функции DOS» и в качестве параметра получает номер необходимой функции DOS в регистре АН (в данном случае 9 – вывод на дисплей строки, адрес первого символа которой должен находиться в DS:DX).
megaobuchalka.ru