Операционная система MSDOS


Локализация ошибки не может быть



Таблица 6

1 Локализация ошибки не может быть определена (система не знает, где произошла ошибка).
2Ошибка произошла в блочном устройстве (диск или магнитная лента).
3Ошибка связана с сетью.
4Ошибка произошла в символьном устройстве, например, в принтере.
5Ошибка связана с оперативной памятью.
Если Ваша программа составлена на языке ассемблера, то после обращения к 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 прерывания INT21h.
Если Ваша программа, составленная на языке Си, вызывает функции DOS неявным образом (через функции стандартной библиотеки транслятора, такие как fprintf, puts и т.д.), то можно воспользоваться средствами обработки ошибок, входящими в состав стандартной библиотеки.
Когда при обращении к функциям DOS средствами стандартной библиотеки транслятора Си возникает ошибка, то в глобальную переменную errno записывается код ошибки.
Возможны следующие коды ошибок (они описаны в файле errno.h и stdlib.h):


Таблица 6

(0) 1 drv_num номер устройства (0 соответствует устройству А:, 1 - В: и т.д.)
(+1) 1drv_numd дополнительный номер устройства внутри драйвера
(+2) 2sec_size размер сектора в байтах
(+4) 1clu_size число, на единицу меньшее количества секторов в кластере
(+5) 1clu_base если содержимое этого поля не равно нулю, то для получения общего числа секторов в кластере надо возвести 2 в степень clu_base и получившееся число прибавить к clu_size
(+6) 2boot_siz количество зарезервированных секторов (boot-сектора, начало корневого каталога)
(+8) 1fat_num количество копий FAT
(+9) 2max_dir максимальное число дескрипторов файлов в корневом каталоге (т.е. максимальное число файлов, которое может содержать корневой каталог на этом устройстве)
(+11) 2data_sec номер первого сектора данных на диске (номер сектора, соответствующего кластеру номер 2)
(+13) 2hi_clust максимальное количество кластеров (равно увеличенному на 1 количеству кластеров данных)
(+15) 1fat_size количество секторов, занимаемых одной копией FAT
(+16) 1reserv1 зарезервироано
(+17) 2root_sec номер первого сектора корневого каталога
(+19) 4drv_addr FAR-адрес заголовка драйвера, обслуживающего данное устройство
(+23) 1media байт описания среды носителя данных
(+24) 1acc_flag флаг доступа, 0 означает, что к устройству был доступ
(+25) 4next адрес следующего блока DDCB, для последнего блока в поле смещения находится число FFFF
(+29) 2reserv2 зарезервироано
(+31) 2built число FFFF в этом поле означает, что блок DDCB был построен
Файл sysp.h содержит определение типа DDCB для MS-DOS версии 4.х и 5.0: /* Блок управления устройством DOS */ #pragma pack(1) typedef struct _DDCB_ { unsigned char drv_num; unsigned char drv_numd; unsigned sec_size; unsigned char clu_size; unsigned char clu_base; unsigned boot_siz; unsigned char fat_num; unsigned max_dir; unsigned data_sec; unsigned hi_clust; unsigned char fat_size; char reserv1; unsigned root_sec; void far *drv_addr; unsigned char media; unsigned char acc_flag; struct _DDCB_ far *next; unsigned reserv2; unsigned built; } DDCB; #pragma pack()
Еще раз уместно заметить, что формат этого блока не описан в документации по 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) 2seg_env сегментный адрес среды, которая создается родительской программой для запускаемой программы. Если в этом поле находится 0, то для запускаемой программы копируется среда родительской программы
(+2) 4cmd FAR-адрес строки параметров для запускаемой программы.
(+6) 4fcb1 адрес блока FCB, который будет помещен в PSP со смещением 5Ch
(+10) 4fcb2 адрес блока FCB, который будет помещен в PSP со смещением 6Ch.
(+14) 4ss_sp это поле будет содержать значение SS:SP после возврата
(+18) 4entry_p адрес точки входа в загруженную программу (CS:IP)
Для подфункции 2:



Таблица 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
В следующем разделе мы подробно рассмотрим все команды, коды которых могут передаваться драйверу через заголовок запроса. Для каждой команды будет приведен формат области запроса.

Содержание раздела