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


и более поздних версий обработка



Таблица 2



1Неправильный код функции
2Файл не найден
3Путь не найден
4Слишком много открытых файлов
5Доступ запрещен
6Неправильный идентификатор файла
7Разрушен блок управления памятью
8Недостаточно памяти
9Неправильный адрес блока памяти
10Неправильная среда
11Неправильный формат
12Неправильный код доступа
13Неправильные данные
14Зарезервировано
15Ошибка при указании дисковода
16Невозможно удалить текущий каталог
17Другое устройство
18Больше нет подходящих файлов
Для DOS версии 3.0 и более поздних версий обработка ошибок значительно расширена. Введена функция 59h прерывания INT21h, предназначенная для получения дополнительной информации об ошибках.
При вызове этой функции регистр BX должен содержать индикатор уровня анализа ошибок, который должен быть равен 0. Кроме расширенного кода ошибки, возвращаемого в регистре AX, программа может получить класс ошибки (регистр BH), код предполагаемых действий (регистр BL), локализацию ошибки, т.е. место, где произошла ошибка (регистр CH).
К сожалению, эта функция разрушает содержимое регистров CL, DX, SI, DI, BP, DS, ES. Программа, использующая функцию 59h, должна позаботиться о сохранении содержимого этих регистров.
Расширенный код ошибки, возвращаемый в регистре AX, может принимать значения, указанные в приводимой ниже таблице. Коды от 1 до 18 эквивалентны представленным выше и второй раз не приводятся.
Расширенные коды ошибок:


Таблица 2

Диапазон адресовСодержимое
0000:0000Векторы прерываний
0000:0400Область данных BIOS
0000:0500 Область данных DOS
xxxx:0000 Область программ DOS ( расширение BIOS, обработчики прерываний DOS, буфера, области данных, загружаемые драйверы устройств)
xxxx:0000Резидентная порция COMMAND.COM
xxxx:0000TSR-программы (остающиеся резидентными после запуска)
xxxx:0000Выполняющиеся прикладные программы типа COM или EXE
xxxx:0000Транзитная порция COMMAND.COM
A000:0000 Память EGA, используемая некоторыми видеорежимами
B000:0000Память монохромного дисплейного адаптера
B800:0000Память видеоадаптера CGA
C800:0000Внешнее ПЗУ
F600:0000ПЗУ интерпретатора BASIC
FE00:0000ПЗУ BIOS
Первый килобайт памяти занимает таблица векторов прерываний. Она содержит 256 4-х байтных элемента - дальние адреса обработчиков прерываний. Подробно формат и использование этой таблицы будет обсуждаться в главе, посвященной прерываниям.
Адреса 0000:0400 - 0000:04FF (или от 0040:0000 до 0050:0000) занимает область данных BIOS. Это внутренние переменные BIOS. К ним можно обращаться для получения различной информации, но необходимо только помнить, что формат этой области может быть различным для различных версий BIOS. Мы будем при необходимости ссылаться на отдельные поля этой области.
Начиная с адреса 0000:0500 (или с адреса 0050:0000, что одно и то же) следует область данных DOS. Здесь DOS хранит свои внутренние таблицы и переменные. Формат этой области (и ее размер) зависит от версии операционной системы.
Далее следует большая область памяти, используемая DOS. Здесь располагаются:
система ввода-вывода DOS (содержимое файла IO.SYS);
обработчики прерываний DOS, в частности, обработчик прерывания INT21H (эти обработчики входят в состав файла MSDOS.SYS);
внутренние буфера DOS и области данных;
загружаемые драйверы (описанные в файле CONFIG.SYS).
После драйверов располагается резидентная порция COMMAND.COM (командный интерпретатор). Она, в частности, обрабатывает прерывания INT 22H, INT 23H и INT 24H.
Следующая область памяти занимается программами, остающимися резидентными после запуска (TSR-программы).
После резидентных программ находится выполняющаяся в настоящий момент программа (COM или EXE). Она может занимать всю оставшуюся память до адреса A000:0000 или только часть этой памяти (для EXE-программ). Для EXE-программ можно при редактировании указать требуемый объем памяти.
Нижнюю часть 640-килобайтного адресного пространства (до адреса A000:0000) занимает транзитная часть COMMAND.COM. Она может перекрываться выполняющейся программой. Если программа перекроет транзитную часть COMMAND.COM, то после завершения выполнения программы эта часть командного интерпретатора будет загружена заново.
Область адресов от A000:0000 до C800:0000 используется дисплейными адаптерами. Каждый адаптер использует эту часть памяти по-своему.
Далее и до конца мегабайтной границы идет область ПЗУ. Там расположено ПЗУ BIOS, ПЗУ интерпретатора BASIC, расширение BIOS (например EGA BIOS - для дисплейных адаптеров EGA).
Диапазон адресов свыше мегабайта используется для машин класса не ниже AT. Это так называемая расширенная память (Extended Memory). Она обслуживается BIOS и используется операционной системой MS-DOS для организации "электронного" диска, кэш-памяти для дисков. Некоторые прикладные программы (например, отладчик Microsoft CodeView) хранят в этой области свои данные. Полностью управлять расширенной памятью способны операционные системы типа OS/2 и UNIX в защищенном режиме работы (процессоры Intel 80286, 80386, 80486), а также оболочка Microsoft Windows версий 3.0 и 3.1.
С расширенной памятью не следует путать дополнительную память (Expanded Memory). Эта память с помощью специальной аппаратуры и драйверов отображается в область адресного пространства, лежащую до границы 1 мегабайт. MS-DOS может использовать эту память аналогично расширенной памяти.
Зона памяти, начиная с области программ DOS и до видеопамяти дисплейных адаптеров, разбита на блоки. Перед каждым блоком находится блок управления памятью - Memory Control Block (MCB).
Сегментный адрес первого блока MCB находится в векторной таблице связи, в поле mcb_seg (смещение блока равно 0). Внутри блока MCB содержится длина описываемого данным MCB блока памяти. Следующий MCB начинается сразу за предыдущим. Таким образом, все блоки управления памятью связаны в список.
Блоки MCB бывают двух типов - M и Z. M-блоки ('middle') - это промежуточные блоки. Блок типа Z является последним блоком в списке и может быть только один.
Приведем формат блока MCB (описание этого блока отсутствует в документации по MS-DOS версии 4.01, поэтому его формат может измениться в последующих версиях операционной системы):



Таблица 2

(0) 2 int20h двоичный код команды int 20h ( программы могут использовать эту команду для завершения своей работы)
(+2) 2mem_top нижняя граница доступной памяти в системе в параграфах
(+4) 1reserv1 зарезервировано
(+5) 5call_dsp команда вызова FAR CALL диспетчера MS-DOS
(+10) 4term_adr адрес завершения (Terminate Address)
(+14) 4cbrk_adr адрес обработчика Ctrl-Break
(+18) 4crit_err адрес обработчика критической ошибки
(+22) 2parn_psp сегмент PSP программы, запустившей данную программу (программы-родителя)
(+24) 20file_tab таблица открытых файлов, если здесь находятся байты 0FFH, то таблица не используется
(+44) 2env_seg сегмент блока памяти, содержащего переменные среды
(+46) 4ss_sp адрес стека SS:SP программы
(+50) 2max_open максимальное число открытых файлов
(+52) 4file_tba адрес таблицы открытых файлов
(+56) 24reserv2 зарезервировано
(+80) 3disp диспетчер функций DOS
(+83) 9reserv3 зарезервировано
(+92) 16fcb1 форматируется как стандартный FCB, если первый аргумент командной строки содержит правильное имя файла
(+108) 20fcb2 заполняется для второго аргумента командной строки аналогично fcb1
(+128) 1p_size число значащих символов в неформатированной области параметров, либо буфер обмена с диском DTA, назначенный по умолчанию
(+129) 127 parm неформатированная область параметров, заполняется при запуске программы из командной строки
#pragma pack(1) typedef struct _PSP_ { unsigned char int20h[2]; unsigned mem_top; unsigned char reserv1; unsigned char call_dsp[5]; void far *term_adr; void far *cbrk_adr; void far *crit_err; unsigned parn_psp; unsigned char file_tab[20]; unsigned env_seg; void far *ss_sp; unsigned max_open; void far *file_tba; unsigned char reserv2[24]; unsigned char disp[3]; unsigned char reserv3[9]; unsigned char fcb1[16]; unsigned char fcb2[20]; unsigned char p_size; unsigned char parm[127]; } PSP; #pragma pack() Программы могут получить из PSP такую информацию, как параметры командной строки при запуске, размер доступной памяти, найти сегмент области переменных среды и т.д.
Как программе узнать адрес своего PSP? Очень просто сделать это для программ, написанных на языке ассемблера: при запуске программы этот адрес передается ей через регистры DS и ES. То есть этот адрес равен DS:0000 или ES:0000 (для COM-программ на PSP указывают также регистры CS и SS).
Для программ, составленных на языке Си, доступна глобальная переменная _psp типа unsigned. Эта переменная содержит сегментный адрес PSP.
В качестве примера приведем текст программы на языке ассемблера, которая выводит на экран передаваемые ей через PSP параметры запуска: .MODEL tiny DOSSEG .STACK 100h .DATA parm_msg DB "Укажите параметры", 13, 10, "$" .CODE .STARTUP mov cl,ds:80h ; количество символов ; в командной строке cmp cl,0 je ask_parm ; нет параметров - просим ; задать параметры mov si,81h ; со смещением 81h ; начинается область ; параметров cld get_parm: lods BYTE PTR es:[si] ; загружаем в al ; очередной ; символ строки ; параметров mov ah,2 ; выводим его на экран mov dl,al int 21h loop get_parm jmp end_progr ask_parm: mov ah, 9h mov dx, OFFSET parm_msg int 21h end_progr: .EXIT 0 END
Приведенная ниже программа, составленная на языке Си, определяет адрес своего PSP, затем показывает содержимое некоторых полей из PSP: #include <stdio.h> #include <stdlib.h> #include <dos.h> #include "sysp.h" void main(void); void main(void) { PSP far *psp_ptr; psp_ptr = FP_MAKE(_psp,0); // Конструируем указатель // на PSP printf("PSP расположено по адресу: %Fp\n" "Доступно памяти, байт: %ld\n" "PSP родительской программы: %Fp\n" "\n", psp_ptr, (long)(psp_ptr->mem_top)*16L, FP_MAKE(psp_ptr->parn_psp,0)); exit(0); }
Используя поле parn_psp, можно определить адрес PSP родительской программы, то есть программы, запустившей Вашу программу.
Немного о назначении полей term_adr, cbrk_adr, crit_err.
Поле term_adr содержит значение, полученное из таблицы векторов прерываний для вектора 22h. Это адрес программы, которая получает управление, когда текущая программа завершает свою работу. Это может быть, например, COMMAND.COM. Программа может создать свою собственную подпрограмму, которая будет получать управление при завершении работы основной программы. Она может записать свой собственный адрес в вектор 22h, затем запустить другую программу. В таком случае в запущенной программе это поле в ее PSP будет содержать адрес родительской программы. Когда основная программа завершает свою работу, DOS восстанавливает адрес программы завершения в векторе 22h из поля term_adr PSP.
Поле cbrk_adr содержит адрес программы обработки прерывания по нажатию Ctrl-Break из вектора 23h таблицы векторов прерываний. Так как программа может устанавливать свою собственную программу обработки прерывания по Ctrl-Break, DOS при завершении работы программы восстанавливает оригинальное значение из поля cbrk_adr.
Аналогично поле crit_err предназначено для восстановления содержимого вектора 24h - адреса обработчика критических ошибок.
Способы переназначения векторов будут приведены в разделе, посвященном прерываниям.
Конечно, программы, составленные на языке Си, не обязательно должны использовать PSP для доступа к параметрам командной строки и переменным среды. Для этого есть параметры функции main и набор функций типа getenv, putenv и т.п., предназначенных для работы со средой. Но ведь PSP содержит и другую информацию!



Таблица 2

НомерОписание
8IRQ0 - прерывание интервального таймера, возникает 18,2 раза в секунду.
9IRQ1 - прерывание от клавиатуры. Генерируется при нажатии и при отжатии клавиши. Используется для чтения данных с клавиатуры.
AIRQ2 - используется для каскадирования аппаратных прерываний в машинах класса AT.
70IRQ8 - прерывание от часов реального времени.
71IRQ9 - прерывание от контроллера EGA.
72IRQ10 - зарезервировано.
73IRQ11 - зарезервировано.
74IRQ12 - зарезервировано.
75IRQ13 - прерывание от математического сопроцессора.
76IRQ14 - прерывание от контроллера жесткого диска.
77IRQ15 - зарезервировано.
BIRQ3 - прерывание асинхронного порта COM2.
CIRQ4 - прерывание асинхронного порта COM1.
DIRQ5 - прерывание от контроллера жесткого диска для XT.
EIRQ6 - прерывание генерируется контроллером флоппи-диска после завершения операции.
FIRQ7 - прерывание принтера. Генерируется принтером, когда он готов к выполнению очередной операции. Многие адаптеры принтера не используют это прерывание.
Из таблицы видно, что самый высокий приоритет у прерываний от интервального таймера, затем идет прерывание от клавиатуры.
Для управления схемами приоритетов необходимо знать внутреннее устройство контроллера прерываний 8259. Поступающие прерывания запоминаются в регистре запроса на прерывание IRR. Каждый бит из восьми в этом регистре соответствует прерыванию. После проверки на обработку в настоящий момент другого прерывания запрашивается информация из регистра обслуживания ISR. Перед выдачей запроса на прерывание в процессор проверяется содержимое восьмибитового регистра маски прерываний IMR. Если прерывание данного уровня не замаскировано, то выдается запрос на прерывание.
Наиболее интересными с точки зрения программирования контроллера прерываний являются регистры маски прерываний IMR и управляющий регистр прерываний.
В машинах класса XT регистр маски прерываний имеет адрес 21h, управляющий регистр прерываний - 20h. Для машин AT первый контроллер 8259 имеет такие же адреса, что и в машинах XT, регистр маски прерываний второго контроллера имеет адрес A1h, управляющий регистр прерываний - A0h.
Разряды регистра маски прерываний соответствуют номерам IRQ. Для того чтобы замаскировать аппаратное прерывание какого-либо уровня, надо заслать в регистр маски байт, в котором бит, соответствующий этому уровню, установлен в 1. Например, для маскирования прерываний от НГМД в порт 21h надо заслать двоичное число 01000000.
Приведем пример программы, маскирующей прерывание от флоппи-диска: #include <stdio.h> #include <stdlib.h> #include <conio.h> void main(void); void main(void) { outp(0x21,0x40); printf("\nПрерывания от флоппи-диска запрещены.\n"); exit(0); }
Эта программа есть на дискете, прилагающейся к книге. Запустите ее (с жесткого диска) и попробуйте поработать, например, с дисководом А:. У вас ничего не получится!
Чтобы "оживить" флоппи-диски, запустите программу, которая размаскирует все прерывания (в том числе и от флоппи): #include <stdio.h> #include <stdlib.h> #include <conio.h> void main(void); void main(void) { outp(0x21,0); printf("\nПрерывания от флоппи-диска разрешены.\n"); exit(0); }
Заметьте, что мы только что замаскировали прерывание именно от флоппи-диска, все остальные устройства продолжали нормально работать. Если бы мы выдали машинную команду CLI, то отключились бы все аппаратные прерывания. Это привело бы, например, к тому, что клавиатура была бы заблокирована.
Еще одно замечание, касающееся обработки аппаратных прерываний. Если вы полностью заменяете стандартный обработчик аппаратного прерывания, не забудьте в конце программы выдать байт 20h в порт с адресом 20h (A0h для второго контроллера 8259). Эти действия необходимы для очистки регистра обслуживания прерывания ISR. При этом разрешается обработка прерываний с более низким приоритетом чем то, которое только что обрабатывалось.
Если вы обрабатываете прерывание 1Ch, то добавка в конце программы не нужна, так как это прерывание является расширением другого прерывания (прерывания таймера).
Перед тем, как завершить изучение прерываний, зададимся вопросом - можно ли замаскировать немаскируемое прерывание? Оказывается можно!
Конечно, если сигнал прерывания пришел на вход немаскируемого прерывания процессора, ничего сделать нельзя - прерывание произойдет неизбежно. Но в компьютерах XT и AT предусмотрены схемы, блокирующие вход немаскируемого прерывания процессора NMI.
Для XT маскированием немаскируемого прерывания управляет порт с адресом 0A0h. Если записать в него 0, немаскируемое прерывание будет запрещено, если 80h - разрешено.
Аналогично для AT маскированием немаскируемого прерывания управляет бит 7 порта 70h. Запись байта 0ADh в порт 70h запретит немаскируемое прерывание, а байта 2Dh - разрешит прохождение прерывания.
Заметим, что мы не запрещаем немаскируемое прерывание "внутри" процессора - это невозможно по определению, мы "не пускаем" сигнал прерывания на вход NMI.



Таблица 2

БитНазначение
01 - драйвер обслуживает стандартное устройство ввода; 0 - этот драйвер не обслуживает стандартное устройство ввода
11 - драйвер обслуживает стандартное устройство вывода; 0 - драйвер не обслуживает стандартное устройство вывода
21 - это драйвер стандартного устройства NUL; 0 - драйвер не обслуживает устройство NUL
31 - драйвер обслуживает часы
4Зарезервировано, бит должен быть равен 0
5Зарезервировано, бит должен быть равен 0
61 - разрешено использование драйвером функций GENERIC IOCTL (для версий DOS, более поздних, чем 3.2); 0 - функции GENERIC IOCTL не поддерживаются
7-10Эти биты зарезервированы и должны быть равны 0
111 - поддерживаются функции открытия/закрытия устройства (OPEN/CLOSE) для символьных устройств; 0 - функции OPEN/CLOSE для символьных устройств не поддерживаются
12Зарезервировано, бит должен быть равен 0
131 - для символьных устройств поддерживается функция вывода до получения состояния занятости устройства; 0 - функция вывода до состояния занятости не поддерживается
141 - поддерживаются функции IOCTL; 0 - функции IOCTL не поддерживаются
151 - символьное устройство; 0 - блочное устройство
Для драйверов блочных устройств формат слова атрибутов другой:



Таблица 2

БитНазначение
01 - драйвер обслуживает стандартное устройство ввода; 0 - этот драйвер не обслуживает стандартное устройство ввода
11 - драйвер обслуживает стандартное устройство вывода; 0 - драйвер не обслуживает стандартное устройство вывода
21 - это драйвер стандартного устройства NUL; 0 - драйвер не обслуживает устройство NUL
31 - драйвер обслуживает часы
4Зарезервировано, бит должен быть равен 0
5Зарезервировано, бит должен быть равен 0
61 - разрешено использование драйвером функций GENERIC IOCTL (для версий DOS, более поздних, чем 3.2); 0 - функции GENERIC IOCTL не поддерживаются
7-10Эти биты зарезервированы и должны быть равны 0
111 - поддерживаются функции открытия/закрытия устройства (OPEN/CLOSE) для символьных устройств; 0 - функции OPEN/CLOSE для символьных устройств не поддерживаются
12Зарезервировано, бит должен быть равен 0
131 - для символьных устройств поддерживается функция вывода до получения состояния занятости устройства; 0 - функция вывода до состояния занятости не поддерживается
141 - поддерживаются функции IOCTL; 0 - функции IOCTL не поддерживаются
151 - символьное устройство; 0 - блочное устройство
Для драйверов блочных устройств формат слова атрибутов другой:

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