Конечно, неизбежное "сваливание" программы при возникновении любой ошибки – ситуация довольно неприятная. Было бы неплохо, если при ошибке выполнялась бы какая-то собственная процедура, а затем выполнение программы продолжалось. К счастью, в программировании нет ничего невозможного.
В самых младших адресах оперативной памяти операционная система хранит векторы прерываний (interrupt vectors). Вектор прерывания представляет собой указатель на адрес в памяти, начиная с которого расположена программа обработки этого прерывания. Самое важное – вектор прерывания можно изменить и заставить компьютер при возникновении ошибки выполнять собственный программный код. Каждое прерывание (и его вектор) имеет свой номер от 0 до 255. Например, нажатие клавиши вызывает прерывание с номером 9, движение мыши – с номером 13 и т.д.
В программе надо будет сначала запомнить указатель на старый обработчик прерывания, затем заменить старый вектор новым, а в конце работы программы обязательно все вернуть на место. Если этого не сделать, возникнет катастрофическая ситуация: ваша программа перехватила прерывание клавиатуры, при любом нажатии клавиши выполняется кусочек кода вашей программы. Затем программа прекратила работу, ее больше нет в памяти, а вектор прерывания по-прежнему указывает на то место, где раньше располагалась программа. В итоге первое же нажатие клавиши вполне может привести к зависанию компьютера.
Кстати, ряд прерываний происходит достаточно часто. Пользователь нажимает на клавиши, двигает мышь… Прерывание таймера компьютера происходит каждую 1/18 с. Соответственно программа-обработчик обязана быть небольшой, чтобы не тормозить работу машины.
Помимо этого, обработчик прерывания должен обеспечивать выполнение очень важного условия. Обработчик вклинивается в поток команд процессора. Чтобы ничего не нарушить, после окончания работы обработчика все регистры процессора должны находиться в том же состоянии, что и в момент начала его работы. Тогда компьютер "не заметит" прерывания и будет работать дальше, как ни в чем не бывало (Рис. 15.4).
Рис. 19.4. Функции обработчика прерывания.
Как этого достичь? Очевидно, в начале работы обработчик должен где-то запомнить содержимое регистров процессора, а при окончании - восстановить его.
При входе в обработчик прерывания, он сохраняет регистры процессора в особой структуре данных, называемой стеком (stack). Стек работает по принципу "первым вошел – последним вышел" (Рис. 15.5).
Рис. 19.5. Стек.
Хорошим аналогом стека является магазин автомата Калашникова (программистская шутка гласит, что АКМ – лучшее средство для превращения стека в очередь).
При извлечении значений из стека нужно помнить о том, что теперь они будут идти в обратном порядке. Если мы занесли в стек сначала а, потом b, потом c, то первым извлечется с, затем b и, наконец, а.