Вслед за областью данных BIOS в оперативной памяти IBM PC располагается область данных DOS. Здесь располагаются внутренние переменные и структуры DOS. Основные структуры данных организованы в виде дерева. Корнем является векторная таблица связи, которая содержит адреса всех остальных структур: список блоков управления памятью (MCB), список блоков управления устройствами DOS, таблицу файлов, дисковые буфера.
Векторная таблица связи содержит и другую полезную информацию, открывающую доступ практически ко всем внутренним структурам данных операционной системы. Можно, например, получить доступ ко всем резидентным и загружаемым драйверам операционной системы. Можно узнать, какие дисковые устройства установлены в системе и каковы их характеристики. Зная форматы управляющих блоков операционной системы, можно анализировать ошибочные ситуации, возникающие при отладке программного обеспечения, разрабатывать программы, отображающие внутреннее состояние системы и конфигурацию устройств.
Понимание внутренней структуры MS-DOS - едва ли не самое важное для профессионального системного программирования, поэтому это первое, на чем мы подробно остановимся. В последующих главах мы будем постоянно пользоваться этой информацией.
Многочисленные управляющие блоки, которые использует файловая система и BIOS при работе с дисковыми устройствами, будут описаны в книге 3, посвященной файловой системе.
Прикладная программа может пользоваться услугами DOS, вызывая прерывание INT 21H. Это прерывание имеет несколько десятков функций, которые и представляют собой интерфейс между DOS и прикладной программой. С помощью этих функций прикладная программа получает доступ к файловой системе, может обращаться к драйверам устройств, получать и устанавливать системные параметры, работать с дисплеем и клавиатурой и т.д.
Для полного использования возможностей прерываний DOS необходимы сведения о форматах различных управляющих блоков, используемых этими прерываниями.
Операционная система MS-DOS, подобно операционным системам для больших ЭВМ серии ЕС, содержит векторную таблицу связи основных управляющих блоков. К сожалению, в руководстве по MS-DOS ничего не говорится об этой таблице. Мы попытаемся в некоторой степени восполнить этот пробел, так как изучение векторной таблицы связи позволит глубже осознать принципы работы операционной системы. Информация из векторной таблицы связи будет полезной для составления программ отображения распределения памяти, вывода списка загруженных драйверов, списка устройств прямого доступа и т.д.
Для получения адреса векторной таблицы связи можно воспользоваться недокументированной внутренней функцией 52h прерывания 21h. Для версий MS-DOS 2.х, 3.х, 4.00, 4.01 и 5.0 после вызова этой функции регистры ES:BX будут содержать искомый адрес. Так как описание этой функции отсутствует в руководстве по MS-DOS, в следующих версиях операционной системы возможно придется искать другой способ получения адреса векторной таблицы связи. Может также измениться формат этой таблицы.
Функции для получения адреса векторной таблицы связи:
;** ;.Name get_cvt ; ;.Title Получить адрес векторной таблицы связи ; ;.Descr Функция возвращает адрес векторной таблицы связи ; в регистрах ES:BX для DOS версий 2.х, 3.х, 4.00, ; 4.01 ; ;.Params Нет ; ;.Return ES - сегмент векторной таблицы связи, ; BX - смещение векторной таблицы связи ;** PUBLIC get_cvt .MODEL tiny .CODE get_cvt proc near mov ax,5200h int 21h ret get_cvt endp end /** *.Name get_cvt * *.Title Получить адрес векторной таблицы связи * *.Descr Функция возвращает адрес векторной таблицы связи * для DOS версий 2.х, 3.х, 4.00, 4.01 * *.Params Нет * *.Return Указатель на векторную таблицу связи **/ #include <dos.h> #include <stdio.h> #include "sysp.h" void far *get_cvt(void) { union REGS inregs, outregs; struct SREGS segregs; inregs.h.ah = 0x52; intdosx( &inregs, &outregs, &segregs ); return(FP_MAKE(segregs.es,outregs.x.bx)); }
В примере на языке Ассемблера процедура get_cvt вызывает функцию 52h прерывания 21h, после чего возвращает адрес векторной таблицы связи в регистрах ES:BX.
Функция get_cvt, составленная на языке Си, также вызывает это прерывание, пользуясь функцией intdosx стандартной библиотеки транслятора. Функция intdosx вызывает прерывание 21h. В качестве параметров ей передаются адреса структур и объединения inregs, outregs и segregs. Объединение inregs должно содержать значения регистров перед вызовом прерывания, в объединение outregs и структуру segregs заносятся значения регистров общего назначения и сегментных регистров после того, как завершится обработка прерывания.
Подробнее об этом сказано в описании стандартной библиотеки.
Макро FP_MAKE описано в файле sysp.h и предназначено для конструирования FAR-указателя из значений сегмента и смещения:
#define FP_MAKE(seg,off) ((void far *) \ ((((unsigned long) (unsigned)(seg)) << 16L) | \ ((unsigned long) (unsigned) (off))))
Следующая программа, составленная на языке Си, выводит на консоль адрес векторной таблицы связи:
#include <stdio.h> #include "sysp.h" void main(void) { void far *cvt; cvt=get_cvt(); printf("Адрес векторной таблицы связи: %Fp\n",cvt); exit(0); }
Спецификация формата вывода %Fp используется для вывода значения указателя типа FAR.
Описание векторной таблицы связи, а также других управляющих блоков будет состоять из строк следующего вида:
(+4) 4 filename_ptr - указатель на имя файла
В этой строке:
(+4) - смещение поля в управляющем поле в байтах; 4 - длина поля в байтах; filename - имя поля.
Вслед за именем идет краткое описание содержимого поля.
Итак, векторная таблица связи MS/DOS:
(-2) 2 |
mcb_seg |
сегмент первого
управляющего блока памяти (MCB) |
(0) 4 |
dev_cb |
указатель на первый блок
управления устройствами DOS (DOS Device Control Block) |
(+4) 4 |
file_tab |
указатель на таблицу
файлов DOS |
(+8) 4 |
clock_dr |
указатель на драйвер CLOCK$,
установленный или резидентный |
(+12) 4 |
con_dr |
указатель на актуальный
драйвер CON, установленный или резидентный |
------------------------- DOS 2.x
------------------------- |
||
(+16) 1 |
num_lgdr |
число логических
драйверов в системе |
(+17) 2 |
max_btbl |
максимальное число
байт/блоков любого блочного устройства |
(+19) 4 |
disk_buf |
указатель на первый
дисковый буфер |
(+23) |
null_dr |
начало драйвера NUL -
первого драйвера в списке драйверов DOS |
--------------- DOS 3.x, 4.x---------------- |
||
(+16) 2 |
max_btbl |
максимальное число байт в
блоке блочного устройства |
(+18) 4 |
disk_buf |
указатель на первый
дисковый буфер |
(+22) 4 |
drv_info |
указатель на массив
информации об устройствах |
(+26) 4 |
fcb_tabl |
указатель на таблицу FCB |
(+30) 2 |
fcb_size |
размер таблицы FCB |
(+32) 1 |
num_bdev |
число блочных устройств |
(+33) 1 |
lastdriv |
значение LASTDRIVE в файле
CONFIG.SYS (по умолчанию равно 5) |
(+34) |
null_dr |
начало драйвера NUL -
первого драйвера в списке драйверов DOS |
Функция get_cvt возвращает адрес поля dev_cb. Для удобства работы с векторной таблицей связи определим тип CVT:
#pragma pack(1) typedef struct _CVT_ { unsigned mcb_seg; void far *dev_cb; void far *file_tab; void far *clock_dr; void far *con_dr; unsigned max_btbl; void far *disk_buf; void far *drv_info; void far *fcb_tabl; unsigned fcb_size; unsigned char num_bdev; unsigned char lastdriv; } CVT; #pragma pack()
Эта структура содержит описание полей векторной таблицы связи для MS-DOS версий 3.х, 4.х и 5.0.
Директива #pragma pack(1) предназначена для выравнивания полей структуры на границу байта. Эта директива необходима потому, что по умолчанию транслятор Microsoft выравнивает поля в структуре на границу 16-ти битового слова. Неправильное выравнивание может привести к тому, что поля структуры не будут располагаться в памяти последовательно.
Заметьте, что функция get_cvt возвращает указатель на поле dev_cb. Модифицируем эту функцию так, чтобы можно было использовать для обращения к полям векторной таблицы связи структуру _CVT_:
/** *.Name get_mcvt * *.Title Получить адрес векторной таблицы связи * *.Descr Функция возвращает адрес векторной таблицы связи * для DOS версий 2.х, 3.х, 4.00, 4.01 * *.Params Нет * *.Return Указатель на векторную таблицу связи **/ #include <dos.h> #include <stdio.h> #include "sysp.h" CVT far *get_mcvt(void) { union REGS inregs, outregs; struct SREGS segregs; inregs.h.ah = 0x52; intdosx( &inregs, &outregs, &segregs ); return((CVT far*)FP_MAKE(segregs.es,outregs.x.bx-2)); }
Ниже будут подробно описаны отдельные поля векторной таблицы связи и приведены примеры использования информации из этой таблицы.
Первое поле векторной таблицы связи mcb_seg содержит сегментный адрес первого блока управления памятью (MCB). Блок MCB всегда начинается на границе параграфа, поэтому полный адрес первого блока будет равен mcb_seg:0.
Для лучшего понимания механизма управления памятью в MS-DOS вспомним карту распределения памяти:
Диапазон адресов |
Содержимое |
0000:0000 |
Векторы прерываний |
0000:0400 |
Область данных BIOS |
0000:0500 |
Область данных DOS |
xxxx:0000 |
Область программ DOS
(расширение BIOS, обработчики прерываний DOS, буфера,
области данных, загружаемые драйверы устройств) |
xxxx:0000 |
Резидентная порция COMMAND.COM |
xxxx:0000 |
TSR-программы (остающиеся
резидентными после запуска) |
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, в частности, обработчик прерывания INT 21H (эти обработчики входят в состав файла 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, поэтому его формат может измениться в последующих версиях операционной системы):
(0) 1 |
type |
тип блока MCB (M или Z) |
(+1) 2 |
owner |
параграф владельца блока (если 0,
то блок описывает сам себя) |
(+3) 2 |
size |
число параграфов в этом блоке
(один параграф имеет размер 16 байт) |
(+5) 11 |
reserve |
зарезервировано |
Для удобства работы с блоком MCB файл sysp.h содержит определение типа MCB:
#pragma pack(1) typedef struct _MCB_ { unsigned char type; unsigned owner; unsigned size; char reserve[11]; } MCB; #pragma pack()
Существует несколько удобных программ для просмотра списка блоков MCB. Вот какую информацию выдает программа MI.COM из пакета PCSHELL при запуске с параметром /A:
Memory Info v5.8 Copyright 1989 Central Point Software, Inc. All rights reserved. Conventional memory. Total: 640k Largest executable program: 485k Type Paragraphs Bytes Owner ---- ---------- ----- ------------- Sys 0BA4-18C5h 53792 0008h < DOS > Free 18C7-18CFh 144 0000h < DOS > Env 18D1-18D2h 32 18D4h JYRKEYB Prog 18D4-1904h 784 18D4h JYRKEYB C:\DOS\JYRKEYB.COM C Prog 1906-1A69h 5696 1906h COMMAND Env 1A6B-1A7Dh 304 1906h COMMAND 1A7F-1A82h 64 1906h COMMAND Free 1A84-1A93h 256 0000h < DOS > Prog 1A95-1DD8h 13376 1A95h MOUSE Env 1DDA-1DEDh 320 1ED8h NS Prog 1DEF-1ED6h 3712 1DEFh SHELLB DOSSHELL Prog 1ED8-21EBh 12608 1ED8h NS f:\norton\NS.EXE Env 21ED-2200h 320 2202h NC Prog 2202-2527h 12896 2202h NC f:\norton\NC.EXESocha 2529-253Ch 320 253Eh COMMAND Prog 253E-26A1h 5696 253Eh COMMAND /a Env 26A3-26B5h 304 253Eh COMMAND Env 26B7-26CAh 320 26CCh MI Prog 26CC-9FFFh 485k 26CCh MI c:\dos\MI.COM /a
Программа сообщает размер оперативной памяти (640 К), максимальный размер выполняемой программы (485 К), затем выдает список блоков памяти с указанием типа, занимаемых параграфов памяти, размера в байтах и владельца блока памяти. Программа MI различает четыре типа блоков памяти:
системный (Sys), его владельцем является MS-DOS;
свободный (Free), обычно тоже принадлежит MS-DOS;
программный (Prog) - его занимает запущенная программа;
среда (Env) - содержит переменные среды MS-DOS.
Откуда MI берет информацию о типе блока памяти и имени программы? Системный блок распознается по занимаемым адресам, программный - по наличию правильного префикса программного сегмента (будет описан ниже). Блок переменных среды находится перед программным блоком и содержит кроме собственно переменных среды еще и полный путь файла запущенной программы. Если блок не системный, программный, или не является блоком среды и если в поле владельца этого блока записан ноль, программа отмечает такой блок памяти как свободный.
Видно, что для каждой запущенной программы создается два блока памяти - блок среды и программный блок. Среда формируется при загрузке операционной системы с помощью команд SET и содержит строки вида:
LIB=D:\C600\LIB
Строки хранятся в формате ASCIIZ, т.е. закрыты двоичным нулем. Вся таблица переменных среды также закрыта двоичным нулем. После переменных среды в блоке памяти, отведенном для среды, содержится путь программного файла в формате ASCIIZ.
Блок памяти типа Prog (программный) независимо от формата загрузочного модуля (COM или EXE) начинается с префикса программного сегмента PSP, за которым следует сама программа.
Таким образом, при загрузке для программы выделяются блоки памяти, располагающиеся в следующей последовательности:
MCB для блока памяти переменных среды;
блок памяти переменных среды;
MCB программного блока памяти;
префикс программного сегмента PSP;
программный модуль.
Приведем фрагмент дампа оперативной памяти, полученные при отладке программы MI.COM с помощью отладчика Microsoft CodeView. Дамп памяти начинается с адреса 6D47:0000.
Первая строка дампа (смещение 000-00F) - это MCB блока памяти переменных среды. Это не последний блок памяти, поэтому MCB имеет тип M. В поле владельца блока памяти находятся нули, следовательно, MCB принадлежит сам себе. Поле длины содержит значение 0014 - это количество параграфов в блоке памяти переменных среды.
Со смещением 010 начинается блок переменных среды. Из дампа видно, что строки определения переменных среды закрыты двоичным нулем. Таблица переменных среды также закрыта нулем (смещение 13B). Со смещением 13C записано слово 0001 (количество слов в последующей строке), после которого расположен полный путь для файла запущенной программы.
000 4D00 0014 0000 0000 0000 0000 0000 0000 M............... 010 434F 4D53 5045 433D 433A 5C43 4F4D 4D41 COMSPEC=C:\COMMA 020 4E44 2E43 4F4D 0054 4D50 3D67 3A5C 7465 ND.COM.TMP=g:\te 030 6D70 0050 4154 483D 673A 5C3B 643A 5C71 mp.PATH=g:\;d:\q 040 6332 5C62 696E 3B65 3A5C 6336 3030 5C62 c2\bin;e:\c600\b 050 696E 623B 653A 5C63 3630 305C 6269 6E3B inb;e:\c600\bin; 060 633A 5C64 6F73 3B63 3A5C 6172 633B 663A c:\dos;c:\arc;f: 070 5C6E 6F72 746F 6E3B 653A 5C77 6F72 643B \norton;e:\word; 080 004C 4942 3D65 3A5C 7163 325C 4C49 4200 .LIB=e:\qc2\LIB. 090 494E 434C 5544 453D 673A 5C69 6E63 6C75 INCLUDE=g:\inclu 0A0 6465 3B65 3A5C 7163 325C 494E 434C 5544 de;e:\qc2\INCLUD 0B0 453B 653A 5C63 746F 6F6C 735C 696E 636C E;e:\ctools\incl 0C0 7564 653B 0048 454C 5046 494C 4553 3D65 ude;.HELPFILES=e 0D0 3A5C 4336 3030 5C48 454C 505C 2A2E 484C :\C600\HELP\*.HL 0E0 503B 0049 4E49 543D 653A 5C43 3630 305C P;.INIT=e:\C600\ 0F0 494E 4954 0044 4D41 4B45 3D67 3A5C 646D INIT.DMAKE=g:\dm 100 616B 653B 0056 4547 413D 653A 5C76 6567 ake;.VEGA=e:\veg 110 613B 004E 4152 4348 454C 503D 663A 5C61 a;.NARCHELP=f:\a 120 7263 5C6E 6172 633B 0048 454C 5050 4154 rc\narc;.HELPPAT 130 483D 643A 5C68 656C 703B 0000 0100 433A H=d:\help;....C: 140 5C44 4F53 5C6D 692E 636F 6D00 6D00 7016 \DOS\mi.com.m.p.
Далее начинается MCB прогаммного блока памяти (смещение 150). Это последний блок памяти, поэтому MCB имеет тип Z. Непосредственно за MCB располагается префикс программного сегмента PSP (смещение 160). Размер PSP - 256 байт, его формат будет описан ниже.
И, наконец, со смещением 260 расположена сама программа MI.COM.
150 5A00 00A3 3216 0000 6D69 0000 0000 0000 Z...2...mi...... 160 CD20 00A0 009A F0FE 1DF0 3D09 B22E 340A . ........=...4. 170 B22E 850E B22E CD26 FFFF FFFF FFFF FFFF .......&........ 180 FFFF FFFF FFFF FFFF FFFF FFFF 486D 92B3 ............Hm.. 190 FD4B 1400 1800 5D6D FFFF FFFF 0000 0000 .K....]m........ 1A0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 1B0 CD21 CB00 0000 0000 0000 0000 0020 2020 .!........... 1C0 2020 2020 2020 2020 0000 0000 0020 2020 ..... 1D0 2020 2020 2020 2020 0000 0000 0000 0000 ........ 1E0 0320 2F61 0038 0F00 2C09 0001 0000 4005 . /a.8..,.....@. 1F0 7808 0000 9000 0100 04B3 0D07 ED2F 14B3 x............/.. 200 7501 4A36 0000 0000 0000 4003 A308 3F00 u.J6......@...?. 210 FD4B FD4B 0100 2ABA 0000 0000 026F 5718 .K.K..*......oW. 220 3800 0500 3800 CF08 DD26 2D09 FD4B FD4B 8...8....&-..K.K 230 E408 0000 4200 A308 3F00 FD4B FD4B E408 ....B...?..K.K.. 240 1304 0000 5718 C000 0500 C000 CE02 CE03 ....W........... 250 5718 8400 0500 8400 CE02 CE03 973A 92B3 W............:.. 260 E9BB 000D 0A4D 656D 6F72 7920 496E 666F .....Memory Info 270 2076 352E 380D 0A43 6F70 7972 6967 6874 v5.8..Copyright 280 2031 3938 3920 4365 6E74 7261 6C20 506F 1989 Central Po 290 696E 7420 536F 6674 7761 7265 2C20 496E int Software, In 2A0 632E 2020 416C 6C20 7269 6768 7473 2072 c. All rights r 2B0 6573 6572 7665 642E 0D0A 0A00 4279 2047 eserved.....By G 2C0 5744 2030 312F 3036 2F38 391A 2D2D 2D2D WD 01/06/89.---- 2D0 5041 5443 4820 4152 4541 2D2D 2D2D 2D2D PATCH AREA------ 2E0 2D2D FF90 5601 4102 0000 0290 5D6D 5C6D --..V.A.....]m\m
Префикс программного сегмента всегда создается при загрузке программы COM или EXE в память и имеет одинаковый формат для COM и EXE файлов:
(0) 2 |
int20h |
двоичный код команды int 20h
(программы могут использовать эту команду для
завершения своей работы) |
(+2) 2 |
mem_top |
нижняя граница доступной памяти
в системе в параграфах |
(+4) 1 |
reserv1 |
зарезервировано |
(+5) 5 |
call_dsp |
команда вызова FAR CALL диспетчера
MS-DOS |
(+10) 4 |
term_adr |
адрес завершения (Terminate Address) |
(+14) 4 |
cbrk_adr |
адрес обработчика Ctrl-Break |
(+18) 4 |
crit_err |
адрес обработчика критической
ошибки |
(+22) 2 |
parn_psp |
сегмент PSP программы, запустившей
данную программу (программы-родителя) |
(+24) 20 |
file_tab |
таблица открытых файлов, если
здесь находятся байты 0FFH, то таблица не
используется |
(+44) 2 |
env_seg |
сегмент блока памяти,
содержащего переменные среды |
(+46) 4 |
ss_sp |
адрес стека SS:SP программы |
(+50) 2 |
max_open |
максимальное число открытых
файлов |
(+52) 4 |
file_tba |
адрес таблицы открытых файлов |
(+56) 24 |
reserv2 |
зарезервировано |
(+80) 3 |
disp |
диспетчер функций DOS |
(+83) 9 |
reserv3 |
зарезервировано |
(+92) 16 |
fcb1 |
форматируется как стандартный FCB,
если первый аргумент командной строки содержит
правильное имя файла |
(+108) 20 |
fcb2 |
заполняется для второго
аргумента командной строки аналогично fcb1 |
(+128) 1 |
p_size |
число значащих символов в
неформатированной области параметров, либо
буфер обмена с диском DTA, назначенный по
умолчанию |
(+129) 127 |
parm |
неформатированная область
параметров, заполняется при запуске программы из
командной строки |
Для обращения к полям PSP файл sysp.h содержит следующее определение:
#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()
Приведем программу, определяющую адрес первого блока MCB:
/** *.Name get_fmcb * *.Title Получить адрес первого MCB * *.Descr Функция возвращает адрес первого блока MCB * *.Params MCB far *get_fmcb(CVT far *cvt) * * cvt - адрес векторной таблицы связи * *.Return Указатель на первый блок MCB **/ #include "sysp.h" MCB far *get_fmcb(CVT far *cvt) { return((MCB far *)FP_MAKE(cvt->mcb_seg,0)); }
В качестве аргумента функции get_fmcb необходимо передать указатель на векторную таблицу связи, полученный с помощью описанной ранее функции get_mcvt.
Для получения адреса следующего MCB можно использовать функцию get_nmcb:
/** *.Name get_nmcb * *.Title Получить адрес следующего MCB * *.Descr Функция возвращает адрес следующего блока MCB * или 0, если это последний блок. В качестве * параметра используется указатель на предыдущий * блок MCB * *.Params MCB far *get_fmcb(MCB far *mcb) * * mcb - адрес предыдущего MCB * *.Return Указатель на следующий блок MCB или 0, если * это последний MCB **/ #include <dos.h> #include "sysp.h" MCB far *get_nmcb(MCB far *mcb) { unsigned seg, off; if(mcb->type == 'M') { seg = FP_SEG(mcb) + mcb->size + 1; off = FP_OFF(mcb); return((MCB far *) FP_MAKE(seg,off)); } else return ((MCB far *)0); }
В завершение описания блоков управления памятью приведем программу, выводящую некоторую информацию об активных MCB. Эта программа определяет адрес векторной таблицы связи, получает из этой таблицы адрес первого MCB и сканирует весь список MCB.
#include <dos.h> #include <stdio.h> #include "sysp.h" void main(void); void main(void) { CVT far *cvt; MCB far *mcb; printf("\nБлоки управления памятью (MCB)" "\nCopyright (C)Frolov A., 1990\n" "\nАдрес MCB Тип Владелец Размер" "\n--------- --- -------- ------" "\n"); cvt=get_mcvt(); mcb=get_fmcb(cvt); for(;;) { if(mcb == (MCB far *)0) break; printf("%Fp %c %04X %04X\n", mcb, mcb->type, mcb->owner, mcb->size); mcb=get_nmcb(mcb); } exit(0); }
Поле dev_cb векторной таблицы связи содержит FAR-адрес цепочки блоков управления устройствами DOS (DOS Device Control Block) - DDCB. Блок DDCB строится операционной системой для каждого дискового устройства и содержит информацию о характеристиках этого устройства и указатель на заголовок драйвера, обслуживающего данное устройство.
Этот блок может быть использован программами, которые выполняют доступ к диску на уровне секторов. Подробнее назначение и использование полей блока DDCB будет описано в разделе, посвященном файловой системе, так как эта информация требуется в основном для организации работы с диском на низком уровне.
Приведем формат блока DDCB для DOS версий 2.х и 3.х:
(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) 2 |
root_sec |
номер первого сектора корневого
каталога |
(+18) 4 |
drv_addr |
FAR-адрес заголовка драйвера,
обслуживающего данное устройство |
(+22) 1 |
media |
байт описания среды носителя
данных |
(+23) 1 |
acc_flag |
флаг доступа, 0 означает, что к
устройству был доступ |
(+24) 4 |
next |
адрес следующего блока DDCB, для
последнего блока в поле смещения находится число
FFFF |
--------------- только для DOS 2.x -----------------
|
||
(+28) 2 |
dir_clu |
номер начального кластера
текущего каталога (0 для корневого каталога) |
(+30) 64 |
dir_path |
строка в формате ASCIIZ, содержащая
путь к текущему каталогу |
----- DOS 3.х ------ |
||
(+28) 2 |
reserv1 |
зарезервировано, обычно равно 0 |
(+30) 2 |
built |
число FFFF в этом поле означает, что
блок DDCB был построен |
Для DOS версии 4.х формат этого блока другой. Кроме того, изменилась его длина:
(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 был построен |
Файл 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 прерывания INT 21H (со всеми ограничениями, связанными с использованием недокументированных возможностей).
Функция 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); }
DOS создает таблицу открытых файлов и помещает ее адрес в поле file_tab векторной таблицы связи. В этой таблице для каждого открытого файла хранится такая информация, как количество файловых чисел (file handle), связанных с данным файлом, режим открытия файла (чтение, запись и т.д.), слово информации об устройстве, указатель на заголовок драйвера, обслуживающего данное устройство, элемент дескриптора файла (дата, время, имя файла, номер начального кластера, распределенного файлу), номер последнего прочитанного кластера и т.д.
Эта информация может пригодиться при организации защиты программы от копирования путем ее привязки к номерам занимаемых программой кластеров. Заметьте, что получить информацию о начальном кластере файла довольно трудно - стандартные средства DOS не предоставляют такой возможности. Приходится работать с диском на уровне секторов, отслеживать FAT, читать напрямую каталог и т.д. Таблица файлов содержит этот номер в явном виде.
Строка файла CONFIG.SYS может содержать оператор FILES=xx. Этот оператор в конечном счете определяет размер таблицы файлов DOS (DFT). Каждая таблица DFT содержит указатель на следущую таблицу и количество управляющих блоков файлов DOS (DFCB). Сами блоки DFCB (по одному для каждого файла) расположены в конце таблицы DFT.
Формат этого блока различается для DOS 3.х и 4.х. Информация по некоторым полям отсутствует. И хотя эти поля отмечены как резервные, на самом деле они используются, но неизвестно как.
Приведем сначала формат таблицы файлов для DOS 3.х:
(0) 4 |
next |
указатель на следующую
таблицу файлов |
(+4) 2 |
file_count |
количество файлов в этой
таблице |
--- Дальше идут блоки DFCB в
количестве file_count штук ---- |
||
(0) 2 |
handl_num |
количество файловых
чисел, связанных с данным файлом (file handle) |
(+2) 1 |
access_mode |
режим доступа к файлу,
заданный при открытии файла |
(+3) 2 |
reserv1 |
зарезервировано |
(+5) 2 |
dev_info |
информация IOCTL, полученная
для устройства, на котором расположен этот файл
(подробно формат и назначение этого поля будут
рассмотрены в главе, посвященной драйверам) |
(+7) 4 |
driver |
указатель на драйвер,
обслуживающий устройство, содержащее файл |
(+11) 2 |
first_clu |
номер первого кластера,
распределенного файлу |
(+13) 2 |
time |
время последнего
изменения файла в упакованном формате |
(+15) 2 |
date |
дата последнего изменения
файла в упакованном формате |
(+17) 4 |
fl_size |
размер файла в байтах |
(+21) 4 |
offset |
текущее смещение внутри
файла в байтах |
(+25) 2 |
reserv2 |
зарезервировано |
(+27) 2 |
last_clu |
номер только что
прочитанного кластера |
(+29) 3 |
reserv3 |
зарезервировано |
(+32) 11 |
filename |
имя файла в формате FCB (имя
выровнено на левую границу поля, дополнено
пробелами до 8 символов, справа к нему прилегает 3
символа расширения без точки) |
(+43) 2 |
reserv4 |
зарезервировано |
(+45) 2 |
ownr_psp |
PSP программы, открывшей
файл |
(+47) 2 |
reserv5 |
зарезервировано |
Операционная система MS-DOS версии 4.х отличается расположением поля last_clu, кроме того изменилась длина DFCB:
(0) 4 |
next |
указатель на следующую таблицу
файлов |
(+4) 2 |
file_count |
количество файлов в этой таблице |
--- Дальше идут блоки DFCB в
количестве file_count штук ---- |
||
(0) 2 |
handl_num |
количество файловых чисел,
связанных с данным файлом (file handle) |
(+2) 1 |
access_mode |
режим доступа к файлу, заданный
при открытии файла |
(+3) 2 |
reserv1 |
зарезервировано |
(+5) 2 |
dev_info |
информация IOCTL, полученная для
устройства, на котором расположен этот файл
(подробно формат и назначение этого поля будут
расмотрены в главе, посвященной драйверам) |
(+7) 4 |
driver |
указатель на драйвер,
обслуживающий устройство, содержащее файл |
(+11) 2 |
first_clu |
номер первого кластера,
распределенного файлу |
(+13) 2 |
time |
время последнего изменения файла
в упакованном формате |
(+15) 2 |
date |
дата последнего изменения файла
в упакованном формате |
(+17) 4 |
fl_size |
размер файла в байтах |
(+21) 4 |
offset |
текущее смещение внутри файла в
байтах |
(+25) 2 |
reserv2 |
зарезервировано |
(+27) 2 |
reserv7 |
зарезервировано |
(+29) 3 |
reserv3 |
зарезервировано |
(+32) 1 |
reserv4 |
зарезервировано |
(+33) 11 |
filename |
имя файла в формате FCB (имя
выравнено на левую границу поля, дополнено
пробелами до 8 символов, справа к нему прилегает 3
символа расширения без точки) |
(+44) 2 |
reserv5 |
зарезервировано |
(+46) 2 |
ownr_psp |
PSP программы, открывшей файл |
(+48) 2 |
reserv6 |
зарезервировано |
(+50) 2 |
last_clu |
номер только что прочитанного
кластера |
(+52) 4 |
reserv8 |
зарезервировано |
Для версии MS/DOS 4.01 файл sysp.h содержит определение структур для работы с таблицами файлов:
typedef struct _DFCB_ { unsigned handl_num; unsigned char access_mode; unsigned reserv1; unsigned dev_info; void far *driver; unsigned first_clu; unsigned time; unsigned date; unsigned long fl_size; unsigned long offset; unsigned reserv2; unsigned reserv7; unsigned reserv3; char reserv4; char filename[11]; char reserv5[6]; unsigned ownr_psp; unsigned reserv6; unsigned last_clu; char reserv8[4]; } DFCB; typedef struct _DFT_ { struct _DFT_ far *next; unsigned file_count; DFCB dfcb; } DFT;
Приведем текст программ, возвращающих указатели на первый и последующий элементы списка таблиц файлов DOS:
/** *.Name get_fdft * *.Title Получить адрес первой DTF * *.Descr Функция возвращает адрес первой таблицы файлов DOS * *.Params DTF far *get_fdtf(CVT far *cvt) * * cvt - адрес векторной таблицы связи * *.Return Указатель на первый блок DDCB **/ #include <stdlib.h> #include <stdio.h> #include "sysp.h" DFT far *get_fdft(CVT far *cvt) { DFT far * dft; dft = cvt->file_tab; return(dft); } /** *.Name get_ndft * *.Title Получить адрес следующей DTF * *.Descr Функция возвращает адрес следующей * таблицы файлов DOS или 0, если это последняя таблица * *.Params DFT far *get_ndft(DFT far *dft) * * dft - адрес предыдущей таблицы DFT * *.Return Указатель на следующую DFT или 0, если последняя **/ #include <dos.h> #include <stdlib.h> #include <stdio.h> #include "sysp.h" DFT far *get_ndft(DFT far *dft) { DFT far * dft_next; dft_next = dft->next; if(FP_OFF(dft_next) == 0xffff) return((DFT far *)0); return(dft_next); }
Для подробной распечатки содержимого таблицы файлов можно использовать следующую программу, которая была проверена в 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; DFT far *dft; unsigned i,j,k; DFCB far *dfcb; FILE *list; printf("Информация об открытых файлах DOS\n" "Copyright Frolov A. (C),1990\n"); // Открываем файл для вывода информации о файлах list=fopen("!dfcb.lst","w+"); fprintf(list,"Информация об открытых файлах DOS\n" "Copyright Frolov A. (C),1990\n\n"); cvt=get_mcvt(); // Адрес векторной таблицы связи dft=get_fdft(cvt); // Адрес начала таблицы файлов for(;;) { if(dft == (DDCB far *)0) break; // Конец таблицы i=dft->file_count; fprintf(list,"Таблица файлов DFT: %Fp, в ней %d файлов\n" "===========================================\n", dft,i); for(j=0;j<i;j++) { // Цикл по файловым // управляющим блокам dfcb=(&(dft->dfcb))+j; // Адрес DFCB файла fprintf(list,"\nDFCB файла: %Fp\n\n",dfcb); fprintf(list,"Имя файла: "); for(k=0;k<11;k++) { fputc(dfcb->filename[k],list); } fprintf(list,"\nКоличество file handles: %d\n" "Режим доступа: %d\n" "Поле reserv1: %04X\n" "Информация об устройстве: %04X\n" "Адрес драйвера: %Fp\n" "Начальный кластер: %d\n" "Время: %04X\n" "Дата: %04X\n" "Размер файла в байтах: %ld\n" "Текущее смещение в файле: %ld\n" "Поле reserv2: %04X\n" "Последний прочитанный кластер: %d\n" "Сегмент PSP владельца файла: %04X\n" "Поле reserv7: %d\n" "-------------------------------\n\n", dfcb->handl_num, dfcb->access_mode, dfcb->reserv1, dfcb->dev_info, dfcb->driver, dfcb->first_clu, dfcb->time, dfcb->date, dfcb->fl_size, dfcb->offset, dfcb->reserv2, dfcb->last_clu, dfcb->ownr_psp, dfcb->reserv7); } dft=get_ndft(dft); } fclose(list); exit(0); }
Описание содержимого таблицы файлов будет записано в файл с именем "!dfcb.lst".
Все драйверы, резидентные или подключенные к операционной системе во время обработки файла CONFIG.SYS, связаны в список. Первый драйвер - это так называемый NUL-драйвер - располагается всегда непосредственно после векторной таблицы связи. Подробно о драйверах будет рассказано во второй книге, а сейчас мы кратко опишем, как проследить цепочку загруженных драйверов и получить некоторые сведения об этих драйверах.
Драйвер - это программа, которая занимает не более одного сегмента (64 килобайта) и имеет в самом начале специальный заголовок. Заголовок драйвера имеет следующий формат:
(0) 4 |
next |
указатель на заголовок
следующего драйвера. Если смещение адреса
следующего драйвера равно FFFF, это последний
драйвер в цепочке |
(+4) 2 |
attrib |
атрибуты драйвера |
(+6) 2 |
strateg |
смещение программы
стратегии драйвера |
(+8) 2 |
interrupt |
смещение программы
обработки прерывания для драйвера |
(+10) 8 |
dev_name |
имя устройства для
символьных устройств или количество
обслуживаемых устройств для блочных устройств. |
Мы приведем программу, которая сканирует список драйверов и выводит на стандартное устройство вывода адрес драйвера, его атрибуты, имя устройства для символьных устройств и количество обслуживаемых устройств для блочных драйверов.
Эта программа написана на языке ассемблера.
; Программа выводит информацию о загруженных драйверах ; ; Mакроопределение печатает символы на экране ; @@out_ch MACRO c1,c2,c3,c4,c5 mov ah,02h IRP chr,<c1,c2,c3,c4,c5> IFB <chr> EXITM ENDIF mov dl,chr int 21h ENDM ENDM .MODEL tiny DOSSEG .STACK 100h .DATA msg DB 13,10,"Device Drivers Information V1.00", 13, 10 DB "Copyright (C)Frolov A.,1990",13,10,13,10 DB "Address Attr Device Name",13,10 DB "------- ---- -----------",13,10 DB "$" bl_msg DB "------> Block Device, Number of Units: ","$" .CODE .STARTUP mov ah, 9h ; Выводим заголовок mov dx, OFFSET msg int 21h mov ah,52h ; Получаем адрес первого int 21h ; драйвера в цепочке add bx,22h ; es:bx - адрес первого драйвера dr_loop: call show_driver_info ; выводим параметры ; драйвера cmp WORD PTR es:[bx+2], 0ffffh ; последний ? jz end_of_driver_list cmp WORD PTR es:[bx], 0ffffh jz end_of_driver_list mov ax,es:[bx] ; получаем адрес следующего mov cx,es:[bx+2] ; драйвера mov bx,ax mov es,cx jmp dr_loop end_of_driver_list: .EXIT 0 show_driver_info proc near ;es:bx - адрес драйвера push es push bx mov ax,es ; выводим адрес драйвера call Print_word @@out_ch ':' mov ax,bx call Print_word @@out_ch ' ',' ' mov ax,es:[bx+4] ; выводим атрибут драйвера call Print_word @@out_ch ' ',' ' test WORD PTR es:[bx+4],8000h ; проверяем, это ; символьный jz is_block ; драйвер или ; блочный mov cx,8 ; для символьного выводим mov si,bx ; имя драйвера pr_name: mov al,BYTE PTR es:[si+10] @@out_ch al inc si loop pr_name jmp nxt is_block: mov ah, 9h ; для блочного драйвера mov dx, OFFSET bl_msg ; выводим количество int 21h ; логических устройств, mov al,BYTE PTR es:[bx+10] ; которые ; обслуживает mov ah,0 ; этот драйвер call Print_word nxt: @@out_ch 13,10 pop bx pop es ret show_driver_info endp ; Вывод на экран содержимого регистра AX Print_word proc near ;-------------------- push ax push bx push dx ; push ax mov cl,8 rol ax,cl call Byte_to_hex mov bx,dx @@out_ch bh @@out_ch bl ; pop ax call Byte_to_hex mov bx,dx @@out_ch bh @@out_ch bl ; pop dx pop bx pop ax ret Print_word endp ; Byte_to_hex proc near ;-------------------- ; al - input byte ; dx - output hex ;-------------------- push ds push cx push bx ; lea bx,tabl mov dx,cs mov ds,dx ; push ax and al,0fh xlat mov dl,al ; pop ax mov cl,4 shr al,cl xlat mov dh,al ; pop bx pop cx pop ds ret ; tabl db '0123456789ABCDEF' Byte_to_hex endp ; END
Если запустить эту программу, она выведет на экран сведения о всех загруженных драйверах:
Device Drivers Information V1.00 Copyright (C)Frolov A.,1990 Address Attr Device Name ------- ---- ----------- 02C1:0048 8004 NUL 112F:0000 8800 RBUSDRIV 10E4:0000 0800 ------> Block Device, Number of Units: 0001 0D86:0000 C800 SMARTAAR 0CC7:0000 A000 XMSXXXX0 0BA5:0000 6842 ------> Block Device, Number of Units: 0003 0070:016E 8013 CON 0070:0180 8000 AUX 0070:0192 A040 PRN 0070:01A4 8008 CLOCK$ 0070:01B6 0842 ------> Block Device, Number of Units: 0003 0070:01CA 8000 COM1 0070:01DC A040 LPT1 0070:01EE A040 LPT2 0070:0200 A040 LPT3 0070:0212 8000 COM2 0070:0224 8000 COM3
К этой картинке мы еще вернемся при обсуждении процесса загрузки драйверов.
Как мы только что увидели, все драйверы связаны в цепочку, которую нетрудно проследить. Для драйверов часов CLOCK$ и консоли CON векторная таблица связи содержит их актуальные адреса, соответственно в полях clock_dr и con_dr. Если вам надо получить адреса актуальных драйверов, то самый быстрый способ - взять эти адреса из векторной таблицы связи.
Поле lastdriv содержит значение команды LASTDRIVE в файле CONFIG.SYS. Его можно использовать для определения максимального количества дисковых устройств в системе. Количество действительно используемых блочных устройств находится в поле num_bdev.
Если CONFIG.SYS содержит команду FCBS=xx, то в поле fcb_tabl находится адрес таблицы FCB, а в поле fcb_size - размер этой таблицы.
В поле max_btbl находится максимальное число байтов, содержащихся в блоке блочного устройства.
Очень интересные сведния находятся в массиве дисковой информации, на который указывает поле drv_info векторной таблицы связи. Количество элементов этого массива равно количеству дисковых устройств в системе.
Для каждого диска в этом массиве можно найти текущий путь доступа в виде строки ASCIIZ, указатель на блок управления устройствами DOS DDCB, номер начального кластера текущего каталога (кстати, если к какому-либо каталогу ни разу не обращались, в этом поле находится -1).
Приведем формат элемента массива и соответствующую структуру в файле sysp.h:
(0) 64 |
path |
текущий путь доступа для диска |
(+64) 2 |
reserv1 |
зарезервировано |
(+66) 2 |
reserv2 |
зарезервировано |
(+68) 1 |
reserv3 |
зарезервировано |
(+69) 4 |
ddcb |
адрес соответствующего DDCB |
(+73) 2 |
cdir_clu |
первый кластер текущего каталога
на диске. 0 соответствует корневому каталогу, -1 -
если к диску еще не обращались |
(+75) 2 |
reserv4 |
зарезервировано |
(+77) 2 |
reserv5 |
зарезервировано |
(+79) 2 |
reserv6 |
зарезервировано |
---------------- для DOS 4.х ----------------- |
||
(+81) 7 |
reserv7 |
зарезервировано |
Приводимое ниже определение типа DINFO соответствует формату MS-DOS 4.х.
#pragma pack(1) typedef struct _DINFO_ { char path[64]; unsigned reserv1; unsigned reserv2; unsigned char reserv3; DDCB far *ddcb; unsigned cdir_clu; unsigned reserv4; unsigned reserv5; unsigned reserv6; unsigned char reserv7[7]; } DINFO; #pragma pack()
Как пример использования этой информации приведем программу, которая выводит содержимое массива на экран:
#include <dos.h> #include <stdio.h> #include <stdlib.h> #include "sysp.h" void main(void); void main(void) { CVT far *cvt; DINFO far *dinfo; unsigned i,j,k; printf("Информация о дисковых устройствах\n" "Copyright Frolov A. (C),1990\n"); cvt=get_mcvt(); // Адрес векторной таблицы связи dinfo=cvt->drv_info; // Адрес таблицы дисковых // устройств i=cvt->num_bdev; // Количество дисковых устройств for(j=0;j<i;j++) { printf("Адрес: %Fp,путь: %Fs\n" "Первый кластер каталога: %d\n\n", dinfo, dinfo->path, dinfo->cdir_clu); dinfo = dinfo+1; } exit(0); }
На этом мы завершаем изучение векторной таблицы связи MS-DOS. Еще раз уместно напомнить, что вся приведенная выше информация отсутствует в документации и может меняться (и действительно меняется!) от версии к версии. Если вы хотите использовать описанные выше средства, вам необходимо динамически определять версию DOS и использовать соответствующие форматы управляющих блоков.