Локализация ошибки не может быть
Таблица 6
1 | Локализация ошибки не может быть определена (система не знает, где произошла ошибка). |
2 | Ошибка произошла в блочном устройстве (диск или магнитная лента). |
3 | Ошибка связана с сетью. |
4 | Ошибка произошла в символьном устройстве, например, в принтере. |
5 | Ошибка связана с оперативной памятью. |
Программы, составленные на языке Си, обращаются к прерываниям 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 прерывания INT21h.
Если Ваша программа, составленная на языке Си, вызывает функции DOS неявным образом (через функции стандартной библиотеки транслятора, такие как fprintf, puts и т.д.), то можно воспользоваться средствами обработки ошибок, входящими в состав стандартной библиотеки.
Когда при обращении к функциям DOS средствами стандартной библиотеки транслятора Си возникает ошибка, то в глобальную переменную errno записывается код ошибки.
Возможны следующие коды ошибок (они описаны в файле errno.h и stdlib.h):
Таблица 6
(0) 1 | drv_num | номер устройства (0 соответствует устройству А:, 1 - В: и т.д.) |
(+1) 1 | drv_numd | дополнительный номер устройства внутри драйвера |
(+2) 2 | sec_size | размер сектора в байтах |
(+4) 1 | clu_size | число, на единицу меньшее количества секторов в кластере |
(+5) 1 | clu_base | если содержимое этого поля не равно нулю, то для получения общего числа секторов в кластере надо возвести 2 в степень clu_base и получившееся число прибавить к clu_size |
(+6) 2 | boot_siz | количество зарезервированных секторов (boot-сектора, начало корневого каталога) |
(+8) 1 | fat_num | количество копий FAT |
(+9) 2 | max_dir | максимальное число дескрипторов файлов в корневом каталоге (т.е. максимальное число файлов, которое может содержать корневой каталог на этом устройстве) |
(+11) 2 | data_sec | номер первого сектора данных на диске (номер сектора, соответствующего кластеру номер 2) |
(+13) 2 | hi_clust | максимальное количество кластеров (равно увеличенному на 1 количеству кластеров данных) |
(+15) 1 | fat_size | количество секторов, занимаемых одной копией FAT |
(+16) 1 | reserv1 | зарезервироано |
(+17) 2 | root_sec | номер первого сектора корневого каталога |
(+19) 4 | drv_addr | FAR-адрес заголовка драйвера, обслуживающего данное устройство |
(+23) 1 | media | байт описания среды носителя данных |
(+24) 1 | acc_flag | флаг доступа, 0 означает, что к устройству был доступ |
(+25) 4 | next | адрес следующего блока DDCB, для последнего блока в поле смещения находится число FFFF |
(+29) 2 | reserv2 | зарезервироано |
(+31) 2 | built | число FFFF в этом поле означает, что блок DDCB был построен |
Еще раз уместно заметить, что формат этого блока не описан в документации по MS-DOS, поэтому он может отличаться в различных версиях операционных систем.
Приведем тексты программ для получения адресов первого и последующих блоков DDCB: /** *.Name get_fddcb * *.Title Получить адрес первого DDCB * *.Descr Функция возвращает адрес первого блока DDCB * *.Params DDCB far *get_fddcb(CVT far *cvt) * * cvt - адрес векторной таблицы связи * *.Return Указатель на первый блок DDCB **/ #include <stdlib.h> #include <stdio.h> #include "sysp.h" DDCB far *get_fddcb(CVT far *cvt) { DDCB far * ddcb; ddcb = cvt->dev_cb; return(ddcb); } /** *.Name get_nddcb * *.Title Получить адрес следующего DDCB * *.Descr Функция возвращает адрес следующего блока DDCB * или 0, если это последний блок в цепочке * *.Params DDCB far *get_nddcb(DDCB far *ddcb) * * ddcb - адрес предыдущего DDCB * *.Return Указатель на следующий блок DDCB * или 0, если это последний блок в цепочке **/ #include <dos.h> #include "sysp.h" DDCB far *get_nddcb(DDCB far *ddcb) { DDCB far *ddcb_n; ddcb_n = ddcb->next; if(FP_OFF(ddcb_n) == 0xffff) return((DDCB far *)0); return(ddcb_n); }
С помощью приведенной ниже программы можно просмотреть содержимое всех блоков DDCB. Так как при большом количестве дисков выводится очень много информации, следует использовать средство переназначения стандартного устройства вывода DOS: show_ddc > drives.lst
Эта программа проверена для версии MS/DOS 4.01. #include <dos.h> #include <stdio.h> #include <stdlib.h> #include "sysp.h" void main(void); void main(void) { CVT far *cvt; DDCB far *ddcb; printf("\nБлоки управления дисковыми устройствами (DDCB)" "\nCopyright (C)Frolov A., 1990\n" "\n"); cvt=get_mcvt(); ddcb=get_fddcb(cvt); for(;;) { if(ddcb == (DDCB far *)0) break; printf("Адрес DDCB: %Fp\n" "Номер устройства: %d\n" "Дополнительный номер: %d\n" "Размер сектора: %d\n" "Размер кластера в секторах: %d\n" "База размера кластера: %d\n" "Зарезервировано секторов: %d\n" "Число копий FAT: %d\n" "Макс. файлов в корневом каталоге : %d\n" "Первый кластер данных: %d\n" "Всего кластеров: %d\n" "Размер FAT в секторах: %d\n" "Первый сектор корневого каталога: %d\n" "Поле reserv1: %01X\n" "Адрес драйвера: %Fp\n" "Байт описателя среды носителя: %01X\n" "Флаг доступа: %01X\n" "Адрес следующего DDCB: %Fp\n" "Поле reserv2: %04X\n" "Блок построен: %04X\n" "-------------------------------------\n\n", ddcb, ddcb->drv_num, ddcb->drv_numd, ddcb->sec_size, ddcb->clu_size, ddcb->clu_base, ddcb->boot_siz, ddcb->fat_num, ddcb->max_dir, ddcb->data_sec, ddcb->hi_clust, ddcb->fat_size, ddcb->root_sec, ddcb->reserv1, ddcb->drv_addr, ddcb->media, ddcb->acc_flag, ddcb->next, ddcb->reserv2, ddcb->built); ddcb=get_nddcb(ddcb); } exit(0); }
Приведенный выше способ получения доступа к блокам DDCB больше всего подходит для просмотра блоков управления всеми дисковыми устройствами. Если вам требуется получить DDCB для какого-нибудь конкретного устройства, можно воспользоваться недокументированной функцией 32H прерывания INT21H (со всеми ограничениями, связанными с использованием недокументированных возможностей).
Функция 32H получает в регистре DL номер устройства (0 - текущий диск, 1 - А: и т.д.) и возвращает в регистровой паре DS:BX адрес соответствующего DDCB. Если номер устройства был задан неправильно, регистр AL после выполнения функции будет содержать значение FF.
Если требуется получить адрес DDCB флоппи-диска, необходимо установить диск в приемный карман дисковода.
Приведем текст программы, возвращающей указатель на DDCB диска с заданным номером: /** *.Name get_ddcb * *.Title Получить адрес DDCB заданного диска * *.Descr Функция возвращает адрес блока управления * устройством DOS DDCB * *.Params DDCB far *get_ddcb(int device_number) * * device_number - номер диска, для которого * требуется получить DDCB * Номер задается так: * 0 - текущий диск, 1 - В и т.д. * *.Return Указатель на DDCB заданного диска **/ #include <dos.h> #include <stdio.h> #include "sysp.h" DDCB far *get_ddcb(unsigned char device_number) { union REGS inregs, outregs; struct SREGS segregs; inregs.h.ah = 0x32; inregs.h.al = 0; inregs.h.dl = device_number; intdosx( &inregs, &outregs, &segregs ); if(outregs.h.al == 0xff) return(DDCB far *)0; return((DDCB far*)FP_MAKE(segregs.ds,outregs.x.bx)); }
Программа, приведенная ниже, выводит адреса всех DDCB. Можете запустить ее (она есть на дискете, прилагающейся к книге) и посмотреть, что получится. Не забудьте вставить флоппи-диски во все дисководы. #include <dos.h> #include <stdio.h> #include <stdlib.h> #include "sysp.h" void main(void); void main(void) { DDCB far *ddcb; unsigned char dr; for(dr=1;;dr++) { ddcb=get_ddcb(dr); if(ddcb == (DDCB far *)0) break; printf("%Fp\n",ddcb); } exit(0); }
Таблица 6
(0) 2 | seg_env | сегментный адрес среды, которая создается родительской программой для запускаемой программы. Если в этом поле находится 0, то для запускаемой программы копируется среда родительской программы |
(+2) 4 | cmd | FAR-адрес строки параметров для запускаемой программы. |
(+6) 4 | fcb1 | адрес блока FCB, который будет помещен в PSP со смещением 5Ch |
(+10) 4 | fcb2 | адрес блока FCB, который будет помещен в PSP со смещением 6Ch. |
(+14) 4 | ss_sp | это поле будет содержать значение SS:SP после возврата |
(+18) 4 | entry_p | адрес точки входа в загруженную программу (CS:IP) |
Таблица 6
Код | Описание |
0 | Нарушение защиты от записи. Была предпринята попытка записи информации на защищенное от записи устройство. |
1 | Неизвестное устройство. |
2 | Устройство не готово. |
3 Этим примером мы завершим обзор TSR-программ. В следующей главе будет описан другой вид резидентных программ - драйверы. Использование драйвера - более предпочтительный, чем TSR-программы способ организовать обслуживание нестандартной аппаратуры. |
Таблица 6
Код | Описание |
0 | Нарушение защиты от записи. Была предпринята попытка записи информации на защищенное от записи устройство. |
1 | Неизвестное устройство. |
2 | Устройство не готово. |
3 | Неизвестная команда. Затребованная команда не поддерживается драйвером. |
4 | Ошибка CRC. При выполнении команды обнаружена ошибка циклического кода проверки. |
5 | Неправильная длина запроса. Поле длины в заголовке запроса содержит неверное значение. |
6 | Ошибка при поиске дорожки (дорожка не найдена). |
7 | Неизвестный носитель данных. |
8 | Сектор не найден. |
9 | Нет бумаги в принтере. |
0Ah | Ошибка записи. |
0Bh | Ошибка чтения. |
0Ch | Общая ошибка. |
0Dh | Зарезервировано. |
0Eh | Зарезервировано. |
0Fh | Неразрешенная замена диска (только для DOS версии 3.0 и более поздних версий). |
- получив управление от операционной системы, программа прерывания сохраняет содержимое всех регистров процессора и считывает номер команды из заголовка запроса;
- при необходимости программа считывает дополнительную информацию из области запроса;
- затребованная команда выполняется (если она поддерживается драйвером);
- если драйвер считывает какие-либо данные от обслуживаемого физического устройства для передачи их DOS, то сами данные или их адреса программа прерывания записывает в область запроса;
- программа прерывания устанавливает слово состояния устройства в соответствии с результатами выполнения команды (если драйвер не поддерживает затребованную команду, в слове состояния устройства устанавливаются биты 15 и в биты 0-7 записывается код ошибки 3 - неизвестная команда);
- восстанавливается содержимое регистров процессора, и управление возвращается операционной системе с помощью команды возврата из дальней процедуры.
Приведем фрагмент исходного текста программы прерывания, который выполняет описанные выше функции: interrupt_proc: push es ;сохраняем регистры push ds push ax push bx push cx push dx push si push di push bp ; Устанавливаем ES:BX на заголовок запроса mov ax,cs:req_seg mov es,ax mov bx,cs:req_off ; Загружаем в регистр AL код команды из заголовка ; запроса и умножаем его на 2 для получения индекса ; в таблице адресов команд mov al,es:[bx]+2 shl al,1 sub ah,ah ;обнуляем AH lea di,functions ;DI содержит смещение таблицы ; команд add di,ax ;добавляем смещение jmp word ptr [di] ;переходим по адресу, взятому ; из таблицы functions LABEL WORD ;это таблица функций dw initialize dw check_media dw make_bpb dw ioctl_in dw input_data dw nondestruct_in dw input_status dw clear_input dw output_data dw output_verify dw output_status dw clear_output dw ioctl_out dw Device_open dw Device_close dw Removable_media ;---выход из драйвера, если функция не поддерживается check_media: make_bpb: ioctl_in: nondestruct_in: input_status: clear_input: output_verify: output_status: clear_output: ioctl_out: Removable_media: ; Если функция не поддерживается драйвером, устанавливаем ; в единицу биты 15 (ошибка), 8 (выполнение команды ; завершено). В биты 0-7 записываем код ошибки 3 - ; неизвестная команда. or es:word ptr [bx]+3,8103h jmp quit ;======================================================= ; Это пример обработчика команды: Device_open: ; . . . . . . . . . . ; Некоторые действия для открытия устройства. ; . . . . . . . . . . jmp quit ;======================================================= ;---выходим, модифицируя байт состояния status в заголовке ; запроса quit: or es:word ptr [bx]+3,100h ;устанавливаем бит 8 ;(выполнение команды ;завершено) pop bp ;восстанавливаем регистры pop di pop si pop dx pop cx pop bx pop ax pop ds pop es ret
В следующем разделе мы подробно рассмотрим все команды, коды которых могут передаваться драйверу через заголовок запроса. Для каждой команды будет приведен формат области запроса.