Перейти к основному содержимому

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

Разработчику Архитектору

Процедуры в Ассемблере

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

Вызов процедуры происходит с помощью специальной инструкции CALL. Процессор сохраняет адрес возврата в стек, переключает указатель команд (Instruction Pointer) на начало тела процедуры и начинает выполнение инструкций внутри неё. После завершения работы процедура использует инструкцию RET для извлечения адреса возврата из вершины стека и передачи управления обратно в исходную точку.


Типы процедур

Процедуры в ассемблере классифицируют по способу передачи параметров и объёму используемой памяти. Существуют процедуры, принимающие параметры через регистры, через стек или через глобальные переменные. Параметры, передаваемые через стек, требуют строгого соблюдения порядка их размещения, так как стек работает по принципу LIFO (Last In First Out). Параметры, передаваемые через регистры, позволяют ускорить доступ к данным, но ограничивают количество одновременно обрабатываемых аргументов количеством свободных регистров процессора.

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


Организация памяти процедуры

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

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

; Пример простой процедуры на x86
; Передача параметров через стек
; Возврат значения в регистре AX

my_procedure PROC
push bp ; Сохраняем базовый указатель стека
mov bp, sp ; Устанавливаем текущий базовый указатель

; Доступ к параметрам через BP
; Параметр 1: [BP+4]
; Параметр 2: [BP+6]

; Логика процедуры
mov ax, [bp+4] ; Загружаем первый параметр
add ax, [bp+6] ; Добавляем второй параметр

; Восстановление базового указателя
pop bp ; Восстанавливаем старый BP
ret ; Возврат с очисткой стека (если используется RETN)
my_procedure ENDP

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


Локальные переменные

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

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

; Выделение места под локальные переменные
push bp
mov bp, sp
sub sp, 8 ; Выделяем 8 байт под две целочисленные переменные

; Использование локальных переменных
mov word ptr [bp-2], 10 ; Первая переменная = 10
mov word ptr [bp-4], 20 ; Вторая переменная = 20

; Восстановление стека
add sp, 8
pop bp
ret

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


Стек вызовов

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

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

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


Возврат значения

Процедура может возвращать результат вычислений несколькими способами. Наиболее распространенный метод — использование регистра аккумулятора (например, AX в 16-битной архитектуре или EAX в 32-битной). Значение помещается в регистр перед выполнением инструкции RET. Вызывающая сторона считывает результат из этого регистра.

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

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


Прерывания в Ассемблере

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

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


Система векторов прерываний

Система векторов прерываний хранит адреса обработчиков для всех возможных типов прерываний. В архитектуре x86 таблица векторов прерываний (IDT) содержит 256 записей, каждая из которых соответствует определенному типу прерывания. Номер прерывания используется как индекс для поиска адреса обработчика в таблице.

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

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


Аппаратные прерывания

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

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

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


Программные прерывания

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

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

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


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

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

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

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


Маскирование прерываний

Маскирование прерываний позволяет временно отключать определенные типы прерываний. Процессор использует специальный флаг в регистре флагов (IF — Interrupt Flag) для разрешения или запрета внешних прерываний. Установка флага разрешает прерывания, сброс — запрещает.

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

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


Исключения

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

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

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


Векторизация прерываний

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

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

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


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).