Операционная система MS-DOS© Александр Фролов, Григорий ФроловТом 1, книги 1-2, М.: Диалог-МИФИ, 1991. 1.6. Обработка ошибокКогда программа обращается к DOS для выполнения какой-либо операции, она должна вызвать соответствующее прерывание, загрузив перед вызовом прерывания все необходимые операнды в регистры процессора. Если выполнение операции невозможно по каким-то причинам (неправильные операнды, устройство неработоспособно, запрашиваемая операция не поддерживается текущей версией DOS и т.д.), то для большинства функций DOS устанавливается признак ошибки - флаг переноса CARRY. Для DOS версии 2.0 и более поздних версий регистр AX при этом содержит код ошибки. Приведем коды ошибок, возвращаемые программе через регистр AX:
Для DOS версии 3.0 и более поздних версий обработка ошибок значительно расширена. Введена функция 59h прерывания INT 21h, предназначенная для получения дополнительной информации об ошибках. При вызове этой функции регистр BX должен содержать индикатор уровня анализа ошибок, который должен быть равен 0. Кроме расширенного кода ошибки, возвращаемого в регистре AX, программа может получить класс ошибки (регистр BH), код предполагаемых действий (регистр BL), локализацию ошибки, т.е. место, где произошла ошибка (регистр CH). К сожалению, эта функция разрушает содержимое регистров CL, DX, SI, DI, BP, DS, ES. Программа, использующая функцию 59h, должна позаботиться о сохранении содержимого этих регистров. Расширенный код ошибки, возвращаемый в регистре AX, может принимать значения, указанные в приводимой ниже таблице. Коды от 1 до 18 эквивалентны представленным выше и второй раз не приводятся. Расширенные коды ошибок:
Класс ошибки, передаваемый в регистре BH, содержит информацию, которая поможет вам обработать данную ошибку:
Код предполагаемых действий, передаваемый через регистр BL, поможет Вашей программе правильно обработать ошибку и избежать зависания системы. Приведем таблицу кодов предполагаемых действий:
Сведения о локализации ошибки передаются в регистре CH. Приведем таблицу кодов локализации:
Если Ваша программа составлена на языке ассемблера, то после обращения к DOS через прерывание следует проверить состояние флага переноса: int 21h jc error Программы, составленные на языке Си, обращаются к прерываниям DOS обычно с помощью таких функций, как intdos, int86, intdosx и т.д. Для передачи параметров используются структуры REGS, WORDREGS, BYTEREGS, SREGS. Они описаны в файле dos.h, для использования этих структур программа должна содержать строку: include <dos.h> Значение флага переноса записывается в переменную cflag, определенную в структуре WORDREGS. Эта структура входит в объединение REGS: union REGS { struct WORDREGS x; struct BYTEREGS h; } struct WORDREGS { unsigned int ax; unsigned int bx; unsigned int cx; unsigned int dx; unsigned int si; unsigned int di; unsigned int cflag; } struct BYTEREGS { unsigned char al, ah; unsigned char bl, bh; unsigned char cl, ch; unsigned char dl, dh; } Проверка переменной cflag может быть выполнена, например, таким образом: union REGS inregs, outregs; intdos(&inregs, &outregs); if( outregs.x.cflaf != 0 ) error(); Код ошибки при этом содержится в переменной outregs.x.ax. Приведем пример программы, которая стирает каталог с именем DIR в текущем каталоге и, в случае ошибки, выводит расширенную информацию об ошибке, класс ошибки, код предполагаемых действий и код локализации ошибки: #include <dos.h> #include <stdio.h> union REGS inregs, outregs; struct SREGS segregs; void main(void); void main(void) { char _far *dir_name = "DIR"; // Стираем каталог с именем DIR. Для этого вызываем // функцию 0x3A прерывания INT 21h. inregs.h.ah = 0x3a; segregs.ds = FP_SEG(dir_name); inregs.x.dx = FP_OFF(dir_name); intdosx(&inregs, &outregs, &segregs); // Если после выполнения прерывания установлен // флаг переноса, выводим сообщение об ошибке. if(outregs.x.cflag != 0) { printf( "Ошибка при удалении каталога: %d", outregs.x.ax); // Получаем расширенную информацию об ошибке // с помощью функции 0x59 прерывания INT 21h. inregs.h.ah = 0x59; inregs.x.bx = 0; // Сохраняем регистры в стеке, т.к. их содержимое // изменится _asm { push ds push es push si push di } intdosx(&inregs, &outregs, &segregs); _asm { pop di pop si pop es pop ds } // Выводим расширенную информацию об ошибке. printf("\nРасширенный код ошибки: %d" "\nКласс ошибки: %d" "\nПредполагаемые действия: %d" "\nЛокализация ошибки: %d", outregs.x.ax, outregs.h.bh, outregs.h.bl, outregs.h.ch); } } При составлении программ обработки ошибок следует учитывать, что для DOS версии 1.0 при некоторых ошибках функции DOS возвращают в регистре AX значение 0FFh. Начиная с версии DOS 2.0, при ошибке устанавливается флаг переноса, код ошибки записывается в регистр AX. Однако для более полной диагностики причины ошибки следует использовать функцию 59h прерывания INT 21h. Если Ваша программа, составленная на языке Си, вызывает функции DOS неявным образом (через функции стандартной библиотеки транслятора, такие как fprintf, puts и т.д.), то можно воспользоваться средствами обработки ошибок, входящими в состав стандартной библиотеки. Когда при обращении к функциям DOS средствами стандартной библиотеки транслятора Си возникает ошибка, то в глобальную переменную errno записывается код ошибки. Возможны следующие коды ошибок (они описаны в файле errno.h и stdlib.h):
Из приведенного выше списка кодов ошибок видно, что средствами стандартной библиотеки транслятора обрабатываются не только ошибки, возникающие при обращении к функциям DOS, но и ошибки, появляющиеся при работе с математическим функциями. Для диагностической выдачи сообщения об ошибке можно использовать функции perror и strerror. Первая функция выводит в stderr соответствующее сообщение об ошибке, вторая только формирует строку сообщения. Функции perror и strerror имеют операнд - указатель на строку. Эта строка добавляется в начало стандартного сообщения об ошибке. Если к стандартному сообщению ничего добавлять не надо, операнд должен иметь значение NULL. Следует заметить, что значение переменной errno отражает последнюю ошибку. Успешный вызов функции не приводит к автоматическому сбросу переменной errno. Поэтому функции perror и strerror необходимо вызывать сразу после того, как вызываемая функция возвратит признак ошибки. Приведем пример программы, обрабатывающей ошибки с использованием переменной errno: #include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> void main(int argc, char *argv[]) { FILE *stream; // Открываем файл только для чтения stream = fopen(argv[1], "r"); // Если произошла ошибка, выводим сообщение if( (stream == NULL) || (ferror(stream)) ) { perror("Не могу открыть файл"); exit(errno); } // Пытаемся произвести запись в файл, который // открыт только для чтения. Это приведет к ошибке. fprintf(stream, "Пишем в файл\n"); if( (stream == NULL) || (ferror(stream)) ) { // Выводим сообщение об ошибке двумя способами - // с помощью функции perror и strerror perror("Запись в защищенный файл"); printf("Запись в защищенный файл: %s\n", strerror(errno)); exit(errno); } exit( 0 ); } DOS имеет еще одно средство для обработки ошибок - обработчик критических ошибок (Critical Error Handler). Этот модуль вызывается DOS, когда она получает сообщение об ошибке от драйвера устройства. Модуль выдает на экран хорошо известное вам сообщение: Abort, Retry, Ignore? Это сообщение обычно появляется тогда, когда вы забываете вставить дискету или начинаете печатать при отключенном принтере. Прикладные программы могут подключать свой модуль обработки критических ошибок вместо стандартного. Мы научимся обрабатывать критические ошибки в книге 3. Там же будет приведен соответствующий пример. |