Boroda_2
.docВажно то, что исключения могут вызываться программно, а перехватываются они аппаратно или на микропрограммном уровне. Помимо перехвата исключения, есть и другой способ определения факта переполнения. Для этого нужно иметь 1-разрядный регистр, который будет устанавливаться всякий раз, когда происходит переполнение. В этом случае программисту, который хотел бы проверять результат на переполнение, после каждой арифметической команды пришлось бы включать в программу команду перехода по переполнению, что очень неудобно. То есть по сравнению с явной программной проверкой перехват исключений экономит время и память.
Перехват исключений можно реализовать не только аппаратно, но и с помощью микропрограммы путем той же явной проверки. В этом случае при обнаружении факта переполнения адрес обработчика исключений загружается в счетчик команд. Проверка на уровне микропрограммы требует меньше времени, чем проверка на уровне программы, поскольку может выполняться одновременно с каким-либо другим действием. Кроме того, такая проверка экономит память, поскольку ее можно реализовать только в одном месте, например в основном цикле микропрограммы независимо от того, сколько арифметических команд имеется в основной программе.
К наиболее распространенным условиям, которые могут вызывать исключения, относятся: переполнение и исчезновение значащих разрядов при выполнении операций с плавающей точкой, переполнение при выполнении операций с целыми числами, нарушения защиты, неопределяемый код операции, переполнение стека, запуск несуществующего устройства ввода-вывода, вызов слова с нечетным адресом, деление на 0.
Прерывания
Прерывания — это изменения в потоке управления, вызванные не самой программой, а чем-либо другим. Обычно прерывания связаны с процессом ввода- вывода. Например, программа может дать команду диску начать передачу информации и инициировать прерывание, как только передача данных завершится. Как и в случае исключений, при прерываниях работа программы останавливается, и управление передается программе обработки прерываний (Interrupt Service Routine, ISR), или обработчику прерываний, который выполняет определенные действия. После завершения этих действий обработчик прерываний передает управление прерванной программе. Она должна заново начать прерванный процесс в том же самом состоянии, в котором находилась в момент прерывания. Это значит, что прежнее состояние всех внутренних регистров (то есть состояние, которое было до прерывания) должно быть восстановлено.
Различие между исключениями и прерываниями заключается в том, что исключения синхронны по отношению к программе, а прерывания асинхронны. Если многократно перезапускать программу с одними и теми же входными данными, исключения каждый раз будут происходить в одних и тех же местах программы, а прерывания — нет (в нашем примере с диском прерывание произойдет только тогда, когда диск завершит передачу данных, а не когда этого потребует программа). Причина воспроизводимости исключений и невоспроизводимости прерываний состоит в том, что первые вызываются программой непосредственно, а вторые — опосредованно.
Чтобы понять, как происходят прерывания, рассмотрим обычный пример: компьютеру нужно вывести на терминал строку символов. Программа сначала собирает в буфер все символы, предназначенные для вывода на экран, инициализирует глобальную переменную ptr, которая должна указывать на начало буфера, и делает вторую глобальную переменную count равной числу символов, выводимых на экран. Затем программа проверяет, готов ли терминал, и, если готов, выводит на экран первый символ (например, используя регистры, показанные на рис. 5.20). Начав процесс ввода-вывода, центральный процессор освобождается и может запустить другую программу или сделать что-либо еще.
Через некоторое время символ отображается на экране. После этого может быть инициировано прерывание. Ниже перечислены основные шаги (в упрощенной форме).
Действия аппаратного обеспечения:
-
Контроллер устройства нагружает линию прерывания на системной шине.
-
Когда центральный процессор оказывается готовым к обработке прерывания, он выставляет на шине символ подтверждения прерывания.
-
Когда контроллер устройства обнаруживает, что сигнал прерывания подтвержден, он помещает небольшое целое число на информационные линии, чтобы «представиться» (то есть, показать, что за устройство является источником прерывания). Это число называется вектором прерываний1.
-
Центральный процессор считывает вектор прерываний с шины и временно его сохраняет.
-
Центральный процессор помещает в стек счетчик команд и слово состояния программы.
-
Центральный процессор определяет местонахождение нового счетчика команд, используя вектор прерывания в качестве индекса в таблице в нижней части памяти. Если, например, размер счетчика команд составляет 4 байта, тогда вектор прерываний п соответствует адресу 4п. Новый счетчик команд указывает на начало программы обработки прерываний для устройства, ставшего источником прерывания. Часто помимо этого загружается или изменяется слово состояния программы (например, чтобы блокировать дальнейшие прерывания).
Дальнейшие действия выполняются программно:
-
Программа обработки прерываний сохраняет все нужные ей регистры таким образом, чтобы их можно было восстановить позднее. Их можно сохранить в стеке или в системной таблице.
-
Каждый вектор прерываний используется всеми устройствами данного типа совместно, поэтому в данный момент еще не известно, какой терминал вызвал прерывание. Номер терминала можно узнать, считав значение какого-нибудь регистра.
-
После этого можно считывать любую другую информацию прерывания, например коды состояния.
-
Если происходит ошибка ввода-вывода, на этом этапе ее нужно обработать.
-
Глобальные переменные ptr и count обновляются. Первая увеличивается на 1, чтобы показывать на следующий байт, а вторая уменьшается на 1, чтобы указать, что осталось вывести на 1 байт меньше. Если count все еще больше 0, значит, еще не все символы выведены на экран. Тот символ, на который в данный момент указывает ptr, копируется в выходной буферный регистр.
-
В случае необходимости выдается специальный код, который сообщает устройству или контроллеру прерывания, что прерывание обработано.
-
Восстанавливаются все сохраненные регистры.
-
Выполняется команда выхода из прерывания, возвращающая центральный процессор в то состояние, в котором он находился до прерывания. После этого компьютер продолжает работу с того места, в котором ее приостановил.
С прерываниями связано важное понятие прозрачности. Когда происходит прерывание, могут производиться разнообразные действия и запускаться разного рода программы, но когда все заканчивается, компьютер должен вернуться точно в то же состояние, в котором он находился до прерывания. Программа обработки прерываний, обладающая таким свойством, называется прозрачной.
Если компьютер имеет только одно устройство ввода-вывода, тогда прерывания работают именно так, как мы только что описали. Однако большой компьютер может содержать много устройств ввода-вывода, причем несколько устройств могут работать одновременно, возможно, у разных пользователей. Существует некоторая вероятность, что во время работы обработчика прерывания другое устройство ввода-вывода тоже попытается вызвать свое прерывание.
Здесь существует два подхода. Первый — для всех программ обработки прерываний сначала (даже до сохранения регистров) предотвратить последующие прерывания — в этом случае прерывания будут происходить строго поочередно. Однако это может привести к проблемам с устройствами, которые не могут долго простаивать. Например, на линию связи, поддерживающую скорость передачи 9600 бит в секунду, символы поступают каждые 1042 микросекунды. Если первый символ окажется необработанным, когда поступит второй, данные будут потеряны.
Если компьютер имеет подобные устройства ввода-вывода, то лучше всего приписать каждому устройству определенный приоритет, высокий — для более критичных, низкий — для менее критичных устройств. Центральный процессор тоже должен иметь приоритеты, которые определяются по одному из полей слова состояния программы. Если устройство с приоритетом п вызывает прерывание, программа обработки прерывания тоже должна работать с приоритетом п.
Если программа обработки прерывания выполняется с приоритетом п, любая попытка обработать прерывание от другого устройства с более низким приоритетом будет игнорироваться, пока программа обработки прерывания не завершится и центральный процессор не начнет выполнение программы более низкого приоритета. В то же время прерывания от устройств с более высоким приоритетом должны обрабатываться без задержек.
Поскольку сами программы обработки прерываний могут прерываться, единственно возможный способ точно управлять ситуацией — сделать так, чтобы все
прерывания были прозрачными. Рассмотрим простой пример с несколькими прерываниями. Пусть компьютер имеет три устройства ввода-вывода: принтер, диск и линию RS232 с приоритетами 2, 4 и 5 соответственно. Изначально (t = О, где t — время) работает пользовательская программа. При t = 10 принтер неожиданно инициирует прерывание. Запускается программа обработки прерывания (ISR) от принтера, как показано на рис. 5.28.
При t = 15 прерывания требует линия RS232. Так как линия RS232 имеет более высокий приоритет (5), чем принтер (2), инициируется обработка этого прерывания. Состояние машины, при котором работает ISR принтера, сохраняется в стеке, и начинается выполнение программы обработки прерывания линии RS232.
Немного позже, при t = 20, диск завершает свою работу и сигнализирует об этом прерыванием. Однако его приоритет (4) ниже, чем приоритет работающей в данный момент программы обработки прерываний (5), поэтому центральный процессор не подтверждает прием сигнала прерывания, и диск вынужден ждать. При £ = 25 заканчивает работать ISR линии RS232, и машина возвращается в то состояние, в котором она находилась до прерывания от линии RS232, то есть в состояние, соответствующее работе ISR принтера с приоритетом 2. Как только центральный процессор переключается на приоритет 2, еще до выполнения первой команды, диск с приоритетом 4 совершает прерывание, и запускается ISR диска. После ее завершения снова продолжается программа обработки прерываний от принтера. Наконец, при t = 40 все программы обработки прерываний завершаются, и выполнение пользовательской программы начинается с того места, на котором она прервалась.
Со времен процессора 8088 все процессоры Intel имеют два уровня (приоритета) прерываний: маскируемые и немаскируемые прерывания. Немаскируемые прерывания обычно используются только для сообщения об очень серьезных ситуациях, например об ошибках четности в памяти. Для всех устройств ввода-вывода имеется единственное маскируемое прерывание.
Когда устройство ввода-вывода требует прерывания, центральный процессор использует вектор прерывания при индексировании таблицы из 256 элементов, чтобы найти адрес программы обработки прерываний. Элементы таблицы представляют собой 8-байтные дескрипторы сегмента. Таблица может начинаться в любом месте памяти. Глобальный регистр указывает на ее начало.
При наличии только одного уровня прерываний у центрального процессора нет возможности сделать так, чтобы высокоприоритетное устройство прерывало работу среднеприоритетной программы обработки прерываний, пока этому мешает низкоприоритетное устройство. Для решения проблемы центральные процессоры Intel обычно используют внешний контроллер прерываний (например, 8259А). При первом прерывании (например, с приоритетом п) работа процессора приостанавливается. Если после этого происходит еще одно прерывание с более высоким приоритетом, контроллер прерывания инициирует прерывание во второй раз. Если же второе прерывание обладает более низким приоритетом, оно не инициируется до окончания первого. Чтобы эта система работала, контроллер прерываний должен каким-либо образом узнавать о завершении текущей программы обработки прерываний. Поэтому когда полностью завершается обработка текущего прерывания, центральный процессор должен посылать контроллеру прерываний специальную команду.
Ханойская башня
Теперь, когда мы изучили уровень архитектуры набора команд трех машин, нам нужно все обобщить. Давайте подробно рассмотрим все тот же пример решения задачи «Ханойская башня»). В листинге 5.6 приведена версия этой программы на языке Java. В следующих подразделах мы предложим программы на ассемблере Pentium 4 и UltraSPARC III.
Однако чтобы избежать проблем с вводом-выводом Java, для машин Pentium 4 и UltraSPARC III мы будем транслировать версию программы не на Java, а на С. Единственное различие — это замена Java-onepaTopa print!п стандартным оператором языка С:
printf("Переместить диск с 2d на %й\п". i, j)
Синтаксис строки в операторе printf не важен (строка печатается буквально за исключением символов *d, означающих, что следующее целое число будет представлено в десятичной системе счисления). Здесь важно только то, что процедура вызывается с тремя параметрами: форматирующей строкой и двумя целыми числами.
Мы использовали язык С для Pentium 4 и UltraSPARC III, поскольку библиотека ввода-вывода Java для этих машин недоступна, в отличие от библиотеки С ввода-вывода. Разница минимальна — всего один оператор вывода строки на экран.
Решение задачи «Ханойская башня» на ассемблере Pentium 4
В листинге 5.7 приведен возможный вариант программы на языке С для компьютера Pentium 4 после трансляции. Регистр EBP используется в качестве указателя фрейма. Первые два слова требуются для сборки, поэтому первый параметр п (или N, поскольку регистр символов в макроассемблере не важен) находится в ячейке EBP + 8, а за ним следуют параметры i и j в ячейках EBP + 12 и EBP + 16 соответственно. Локальная переменная к находится в EBP + 20.
Листинг
5.7
.586
.MODEL
FLAT PUBLIC _towers EXTERN _printf:NEAR .CODE towers:
; компилируется для Pentium
L1:
PUSH EBP |
сохраняет EBP (указатель фрейма) |
MOV EBP, ESP |
устанавливает новый указатель фрейма над ESP |
CMP[EBP+8],1 |
if (п—1) |
JNE LI |
переход, если п не равно 1 |
MOV EAX, [EBP+16] |
printf("...", i. j); |
PUSH EAX |
сохранение параметров i, j и формата |
MOV ЕАХ, [EBP+12] |
строка помещается в стек |
PUSH EAX |
в обратном порядке (требование языка С) |
PUSH OFFSET FLAT:format |
OFFSET FLAT - это адрес формата |
CALL printf |
вызов процедуры printf |
ADD ESP, 12 |
удаление параметров из стека |
JMP Done |
завершение |
MOV EAX,6 |
начало вычисления k=6-i-j |
SUB EAX, [EBP+12] |
EAX=6-i |
SUB EAX, [EBP+16] |
EAX=6-i-j |
MOV [EBP+20], EAX |
k=EAX |
PUSH EAX |
начало процедуры towers(n-1. i. k) |
MOV EAX, [EBP+12] |
EAX=i |
PUSH EAX |
помещает в стек i |
MOV EAX, [EBP+8] |
EAX=n |
DEC EAX |
EAX=n-l |
PUSH EAX |
помещает в стек n-1 |
CALL towers |
вызов процедуры towers(n-l, i, 6-i-j) |
ADD ESP, 12 |
удаляет параметры из стека |
MOV EAX, [EBP+16] |
начало процедуры towers (1, i. j) |
PUSH EAX |
помещает в стек j |
MOV EAX, [EBP+12] |
EAX=i |
PUSH EAX |
помещает в стек i |
PUSH 1 |
помещает в стек 1 |
CALL towers |
вызывает процедуру towersd, i, j) |
ADD ESP, 12 |
удаляет параметры из стека |
MOV EAX, [EBP+12] |
начало процедуры towers(n-l, 6-i-j, i) |
PUSH EAX |
помещает в стек i |
MOV EAX, [EBP+20] |
EAX=k |
PUSH EAX |
помещает в стек к |
MOV EAX, [EBP+8] |
ЕАХ = п |
DEC EAX |
ЕАХ=п-1 |
PUSH EAX |
помещает в стек п-1 |
CALL towers |
вызов процедуры towers(n-l, 6-i-j, i) |
ADD ESP, 12 |
корректировка указателя стека |
Done: LEAVE ; подготовка к выходу
RET 0 ; возврат к вызывающей программе
.DATA
format DB "Переместить диск с %6 на %6\п" ; форматирующая строка END
Процедура начинается с создания нового фрейма в конце старого. Для этого значение регистра ESP копируется в указатель фрейма ЕВР. Затем п сравнивается с 1, и если n > 1, совершается переход к оператору else. Тогда код then помещает в стек три значения: адрес форматирующей строки, i и j, а затем вызывает сам себя.
Параметры помещаются в стек в обратном порядке, поскольку это требование языка С. Необходимо поместить указатель на форматирующую строку в вершину стека. Процедура printf имеет переменное число параметров, и если параметры будут помещаться в стек в прямом порядке, то процедура не сможет узнать, в каком месте стека находится форматирующая строка.
После вызова процедуры к регистру ESP прибавляется 12, чтобы удалить параметры из стека. На самом деле они не удаляются из памяти, но изменение регистра ESP делает их недоступными через обычные операции со стеком.
Выполнение части el se начинается с метки L1. Здесь сначала вычисляется выражение 6 — i — j, а полученное значение сохраняется в переменной к. Сохранение значения в переменной к избавляет от необходимости вычислять это выражение во второй раз.
Затем процедура вызывает сама себя три раза, каждый раз с новыми параметрами. После каждого вызова стек освобождается.
Рекурсивные процедуры иногда приводят людей в замешательство. Но на самом деле они совсем не сложные. Просто параметры помещаются в стек, после чего процедура вызывает сама себя.
Решение задачи «Ханойская башня» на ассемблере UltraSPARC III
А теперь рассмотрим ту же программу на ассемблере UltraSPARC III (листинг 5.8). Поскольку программа для UltraSPARC III совершенно нечитабельна даже после длительной практики, мы решили определить несколько символов, чтобы прояснить дело. Чтобы такая программа работала, ее перед ассемблированием нужно пропустить через программу под названием срр (препроцессор С). Здесь мы используем строчные буквы, поскольку ассемблер UltraSPARC III этого требует (это на тот случай, если читатели захотят набрать эту программу и запустить).
Листинг 5.8. Решение задачи «Ханойская башня» для UltraSPARC III
Idefine Scratch Ш /* в срр комментарии записываются как в С*/
! if(n— 1)
.ргос
04 .global
towers towers: save £sp,-112, £sp cmp N, 1 bne Else
sethi
Xhi(format),
! i. j)
or ParamO, Xlo(format). ParamO ! ParamO - адрес форматирующей строки
2
(к)
(J)
(J)
mov I, Paraml call printf mov J, Param2 b Done nop
Else: mov 6, К
sub K,J,K sub K,I,K
add N, -1, Scratch mov Scratch, ParamO mov I, Paraml call towers mov K, Param2
mov 1, ParamO mov I, Paraml call towers mov J, Param2
mov Scratch, ParamO mov K, Paraml call towers mov J. Param2
Paraml = i
вызов printf ДО установки параметра 2 (j) пустая операция для установки параметра 2 завершение
вставляет пустую операцию
начало вычисления к = 6
k=6-j
k=6-i-j
-
к)
начало процедуры towers(п-1,
Scratch = п-1 параметр 1 = i
вызов towers ДО установки параметра пустая операция после вызова процедуры для установки параметра 2
начало процедуры towersd, i, j) параметр 1 = i
вызов towers ДО установки параметра 2 параметр 2 = j
начало процедуры towers(n-1, k, j) параметр 1 = к
вызов towers ДО установки параметра 2 параметр 2 = j
Done: ret ! выход из процедуры
restore ! вставка пустой команды после ret
! для восстановления окон format: .asciz "Переместить диск с %6 на %6\п"
По алгоритму версия UltraSPARC III идентична версии Pentium 4. В обоих случаях сначала проверяется п, и, если n > 1, совершается переход к else. Основные сложности версии UltraSPARC III связаны с некоторыми свойствами архитектуры команд.
Сначала код UltraSPARC III должен передать адрес форматирующей строки в printf, но машина не может просто переместить адрес в регистр, который содержит выходной параметр, поскольку нельзя поместить 32-разрядную константу в регистр одной командой. Для этого требуется выполнить две команды: sethi и ог.
После вызова не нужно делать подстройку стека, поскольку регистровое окно корректируется командой restore в конце процедуры. Возможность помещать выходные параметры в регистры, а не обращаться к памяти, дает значительный выигрыш в производительности.
А теперь рассмотрим команду пор, которая следует за Done. Это пустая операция, которая всегда будет выполняться, даже если она следует за командой условного перехода. Сложность состоит в том, что процессор UltraSPARC III очень конвейеризирован, и к тому моменту, когда аппаратно обнаруживается команда перехода, следующая команда уже практически заканчивает обрабатываться. Добро пожаловать в удивительный мир RISC-программирования!
Эта особенность распространяется и на вызовы процедур. Рассмотрим первый вызов процедуры towers в части el se. Процедура помещает п - 1 в %о0, a i — в %ol, но совершает вызов процедуры towers до того, как поместит последний параметр в нужное место. На компьютере Pentium 4 вы сначала передаете параметры, а затем вызываете процедуру. А здесь вы сначала передаете часть параметров, затем вызываете процедуру, и только после этого передаете последний параметр. К тому моменту, когда машина осознает, что она имеет дело с командой cal 1, следующую команду все равно приходится выполнять (из-за конвейеризации). А почему бы в этом случае не использовать пустую операцию, чтобы передать последний параметр? Даже если этот параметр потребуется самой первой команде вызванной процедуры, он уже будет на своем месте.
Наконец, рассмотрим часть команды Done. Здесь после команды ret тоже вставляется пустая операция. Эта пустая операция используется для команды restore, которая увеличивает на 1 значение CWP, чтобы вернуть регистровое окно в прежнее состояние.
Архитектура IA-64 и процессор Itanium 2
Неизбежно, и очень скоро, наступит момент, когда создать на основе архитектуры команд IA-32 и линейки процессоров Pentium 4 что-то более совершенное, чем существующие модели, будет уже невозможно. Пока что новые модели совершенствуются только благодаря новым технологиям производства, которые позволяют уменьшать размеры транзисторов, а значит, повышать тактовые частоты. В то же время, повышать скорость работы становится все труднее, и виной тому — ограничения, свойственные архитектуре команд IA-32.