Исключения
Многие процессоры используют механизм, родственный прерываниям, для
обработки не только внешних, но и внутренних событий: мы с вами уже сталкивались
с исключительными ситуациями (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=(pO-pl) ;
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) ;
}
При программировании на ассемблере это может быть реализовано простой
подменой адреса возврата в стеке. Многие языки высокого уровня (ЯВУ) реализуют
те или иные средства для обработки исключений. Уровень этих средств различен
в разных языках, начиная от пары функций setjmp
и longjmp в С [Керниган-Ритчи 2000] (пример
6.3) и заканчивая операторами try/catch и
throw C++ [Страуструп 1999] и Java [Вебер 1999].
Пример 6.3. Исходный текст функций set jmp/ longjmp.
/ setjmp. s (emx+gcc) — Copyright (c) 1990-1996 by Eberhard
Mattes
# include <emx/asm386.h>
.globl _setjmp, _longjmp
.text ALIGN
# define J_EBX 0
# define J_ESI 4
# define J_EDI 8
#define J_ESP 12
#define J_EBP 16
# define J_EIP 20
# define J_XCP 24
/ Слова со смещениями 28.. 44 зарезервированы
/ int setjmp (jmp_buf here)
_setjmp:
PROFILE__NOFRAME
movl l*4(%esp), %edx /* here */
raovl %ebx, J_EBX(%edx)
movl %esi, J_ESI(%edx)
movl ledi, J_EDI(%edx)
movl %ebp, J_EBP(%edx)
movl %esp, J_ESP(%edx)
movl 0*4(%esp), %eax /* Адрес возврата */
movl %eax, J_EIP(%edx)
cmpb $0, __osmode /* OS/2? */
je If /* No -> skip */
fs
movl 0, leax /* handler Обработчик исключений */
movl %eax, J_XCP(%edx) 1: xorl %eax, leax
EPILOGUE(setjmp)
ALIGN
/ void longjmp (jmp_buf there, int n)
_longjmp:
PROFILE_NOFRAME
cmpb $0, __osmode /* OS/2? */
je 2f /* No -> skip */
movl 1*4(%esp), %eax /* there */
pushl J_XCP(%eax)
call ___unwind2 /* восстановить обработчики сигналов */
addl $4, %esp 2: movl l*4(%esp), ledx /* there */
movl 2*4(%esp), leax /* n */
testl %eax, leax
jne 3f
incl %eax
3: movl J_EBX(%edx), %ebx
movl J_ESI(ledx), lesi
raovl J_EDI(%edx), %edi
movl J EBP(%edx), %ebp
J_ESP(%edx) ,
J_EIP(%edx>, %edx
%edx, 0*4(%espj /* адрес возврата */
EPILOGUE(longjmp) /* well, ... */
Исключения в ЯВУ часто позволяют избежать использования нелюбимого структурными
программистами оператора goto. В объектно-ориентированных
(ОО) языках этот механизм играет еще более важную роль: в большининстве
таких языков — это единственный способ сообщить о неудаче при исполнении
конструктора объекта.
Важно подчеркнуть, впрочем, что исключения в смысле ЯВУ и аппаратные исключения
процессора — разные вещи. В многозадачной ОС пользовательская программа
не имеет непосредственного доступа к обработке прерываний и исключений.
ОС предоставляет сервис, позволяющий программисту регистрировать обработчики
для тех или иных событий, как соответствующих аппаратным исключениям,
так и порождаемых самой операционной системой, но вызов таких обработчиков
всегда осуществляется в два этапа: сначала исполняется обработчик, зарегистрированный
ядром (пример 6.4), а он, если сочтет нужным и возможным, переключается
в пользовательский контекст и вызывает обработчик, зарегистрированный
пользователем. Среда исполнения ЯВУ, в свою очередь, может реализовать
и свои обработчики между сервисом операционной системы и средствами, доступными
программисту.
Пример 6.4. Обработчик арифметических исключений
в ядре Linux I
/*
* Iinux/arch/i386/traps.c *
* Copyright (С) 1991, 1992 Linus Torvalds *
* Поддержка Pentium III FXSR, SSE
* Gareth Hughes <gareth@valinux.com>, May 2000 */
void die(const char * str, struct pt_regs * regs, long err) I
console_verbose(); spin_lock_irq(&die_lock); Printk("%s: %041x\n",
str, err & Oxffff}; show_registers(regs);
sPin_unlock_irq(&die_lock);
do_exit (SIGSEGV) ;
static inline void die_if_kernel (const char * str, struct pt_regs * regs
long err)
{
if ( ! (regs->eflags & VM_MASK) && ! (3 & regs->xcs)
) die (str, regs, err);
static inline unsigned long get_cr2 (void) { unsigned long address;
/* получить адрес */
_ asm _ ("movl %%cr2, %0" : "=r" (address));
return address;
static void inline do_trap(int trapnr, int signr, char *str, int vm86,
struct pt_regs * regs, long error_code, siginfo_t *info) { if (vm86 &&
regs->eflags & VM_MASK)
goto vm86_trap; if ( ! (regs->xcs & 3) ) goto kernel_trap;
trap_signal: {
struct task_struct *tsk = current; tsk->thread. error_code = error_code;
tsk->thread. trap_no = trapnr; if (info)
force_sig_info (signr, info, tsk) ; else
force_sig (signr, tsk) ; return;
kernel_trap:
unsigned long fixup = search_exception_table(regs->eip); if (fixup)
regs->eip = fixup; else
die(str, regs, error_code); return;
vm86_trap: {
int ret = handle_vm86_trap((struct kernel_vm86_regs *) regs, er-ror_code,
trapnr);
if (ret) goto trap_signal; return;
fldefine DO_ERROR(trapnr, signr, str, name) \
asmlinkage void do_tt#name(struct pt_regs * regs, long error_code) \ {
\ do_trap(trapnr, signr, str, 0, regs, error_code, NULL); \
Idefine DO_ERROR_INFO(trapnr, signr, str, name, sicode, siaddr) \ asmlinkage
void do_t#name(struct pt_regs * regs, long error_code) \ { \
siginfo_t info; \
info.si_signo = signr; \
info.si_errno =0; \
info.si_code = sicode; \
info.si_addr = (void *)siaddr; \
do^trap(trapnr, signr, str, 0, regs, error_code, Sinfo); \ }
ttdefine DO_VM86_ERROR(trapnr, signr, str, name) \
asmlinkage void do_##name(struct pt_regs * regs, long error_code) \ (
\
do_trap(trapnr, signr, str, 1, regs, error_code, NULL); \ }
ttdefine DO_VM86__ERROR_INFO(trapnr, signr, str, name, sicode, siaddr)
\
I
asmlinkage void do_##name(struct pt_regs * regs, long error_code) \ {
\
siginfo_t info; \
info.si_signo = signr; \
info.si_errno =0; \
info.si_code = sicode; \
info.si_addr = (void *)siaddr; \
do_trap(trapnr, signr, str, 1, regs, error_code, sinfo); \
' "
DO_VM86_ERROR_INFO( 0, SIGFPE, "divide error", divide_error,
FPE_INTDIV, regs->eip)
DO_VM86_ERROR( 3, SIGTRAP, "int3", int3)
DO_VM86_ERROR( 4, SIGSEGV, "overflow", overflow)
DO_VM86_ERROR( 5, SIGSEGV, "bounds", bounds)
DO_ERROR_INFO( 6, SIGILL, "invalid operand", invalid_op, ILL_ILLOPN,
regs->eip)
DO_VM86_ERROR( 7, SIGSEGV, "device not available", device_not_available)
DO_ERROR( 8, SIGSEGV, "double fault", double_fault)
DO_ERROR( 9, SIGFPE, "coprocessor segment overrun", coproces-sor_segment_overrun)
DO_ERROR(10, SIGSEGV, "invalid TSS", invalidJTSSl
DO_ERROR(11, SIGBUS, "segment not present", segment_not_present)
DO_ERROR(12, SIGBUS, "stack segment", stack_segment)
DO_ERROR_INFO(17, SIGBUS, "alignment check", alignment_check,
BUS_ADRALN,
get_cr2 () )
|