Исключения
Многие процессоры используют механизм, родственный прерываниям, для обработки не только внешних, но и внутренних событий: мы с вами уже сталкивались с исключительными ситуациями (exception) отсутствия страницы и ошибки доступа в процессорах с виртуальной памятью, а также некоторыми другими – ошибкой шины при доступе к невыровненным словам, заполнению и очистке регистрового окна у SPARC и т. д.
Большинство современных процессоров предоставляют исключения при неизвестном коде операции, делении на ноль, арифметическом переполнении или, например, выходе значения операнда за допустимый диапазон в таких операциях, как вычисление логарифма, квадратного корня или арксинуса.
Исключительные ситуации обрабатываются аналогично внешним прерываниям: исполнение программы останавливается, и управление передается на процедуру-обработчик, адрес которой определяется природой исключения.
Отличие состоит в том, что прерывания обрабатываются после завершения текущей команды, а возврат из обработчика приводит к исполнению команды, следующей за прерванной. Исключение же приводит к прекращению исполнения текущей команды (если в процессе исполнения команды мы уже успели создать какие-то побочные эффекты, они отменяются), и сохраненный счетчик команд указывает на прерванную инструкцию. Возврат из обработчика, таким образом, приводит к попытке повторного исполнения операции, вызвавшей исключение.
Благодаря этому, например, обработчик страничного отказа может подкачать с диска содержимое страницы, вызвавшей отказ, перенастроить таблицу дескрипторов и повторно исполнить операцию, которая породила отказ. Обработчик исключения по неопределенному коду операции может использоваться для эмуляции расширений системы команд.
Например, при наличии арифметического сопроцессора операции с плавающей точкой исполняются им, а при отсутствии – пакетом эмулирующих подпрограмм. Благодаря этому может обеспечиваться полная бинарная совместимость между старшими (имеющими сопроцессор) и младшими (не имеющими его) моделями одного семейства компьютеров.
Исключения, возникающие при исполнении привилегированных команд в пользовательском режиме, могут использоваться системой виртуальных машин. Работающее в виртуальной машине ядро ОС считает, что исполняется в системном режиме. На самом же деле оно работает в пользовательском режиме, а привилегированные команды (переключения режима процессора, настройка диспетчера памяти, команды ввода/вывода) приводят к вызову СВМ.
При грамотной реализации обработчиков таких исключений их обработка произойдет полностью прозрачно для породившей эти исключения программы. Конечно, "подкачка" страницы с диска или программная эмуляция плавающего умножения займет гораздо больше времени, чем простое обращение к памяти или аппаратно реализованное умножение, но, наверное, потребитель вычислительной системы знал, что делал, когда устанавливал недостаточное количество памяти или приобретал машину без сопроцессора.
Многие другие исключения, такие, как деление на ноль, обычно бессмЬ1с ленно обрабатывать повторной попыткой деления на какое-то другое число В этом случае целесообразно возвратить управление не на команду, вызвавшую исключение, а в какую-то другую точку. Вопрос, впрочем, в том, куда именно следует возвращаться. Понятно, что код, который может восстановиться в случае деления на ноль, сильно зависит от контекста, в котором произошла ошибка (пример 6.2).
Пример 6.2. Обработка исключения Floating underflow (антипереполнение при операциях с плавающей точкой)
#tinclude <setjmp.h> static jmp_buf fpe_retry; void fpe_handler (int sig) { 4> __fpreset (); longjmp (fpe__retry, -1); int compare_pgms (Image * imgO, Image * img1) { int xsize=256, ysize=256; int i, j, pO, pi, pd; double avg, avgsq, scale, smooth; scale= (double) xsize* (double) ysize; avg = 0.0; avgsq = 0.0; /* Подавить возможные антипереполнения */ signal (SIGFPE, fpe_handler); for(i=0; i<ysize; i smooth = (double) (imgO › picture [i*xsize] -imgl › picture [i*xsize]); for(j=0; j<xsize; j++) { pO=imgO › picture [ j+i*xsize]; pl=imgl › picture [ j+i*xsize]; pd=(p0-p1); if (setjmp (fpe_retry) == 0) { smooth = smooth* (1. 0-SMOOTH_FACTOR) + (double) pd*SMOOTH_FACTOR; vq += smooth; avgsq += smooth*smooth; eise smooth=0. 0; if (Setjmp(fpe_retry) == 0) Aspersion = avgsq/scale-avg*avg/ (scale*scale); else dispersion = 0.0; signal (SIGFPE, SIGJDFL); }