2. Векторная таблица связи MS-DOS

Вслед за областью данных BIOS в оперативной памяти IBM PC располагается область данных DOS. Здесь располагаются внутренние переменные и структуры DOS. Основные структуры данных организованы в виде дерева. Корнем является векторная таблица связи, которая содержит адреса всех остальных структур: список блоков управления памятью (MCB), список блоков управления устройствами DOS, таблицу файлов, дисковые буфера.

Векторная таблица связи содержит и другую полезную информацию, открывающую доступ практически ко всем внутренним структурам данных операционной системы. Можно, например, получить доступ ко всем резидентным и загружаемым драйверам операционной системы. Можно узнать, какие дисковые устройства установлены в системе и каковы их характеристики. Зная форматы управляющих блоков операционной системы, можно анализировать ошибочные ситуации, возникающие при отладке программного обеспечения, разрабатывать программы, отображающие внутреннее состояние системы и конфигурацию устройств.

Понимание внутренней структуры MS-DOS - едва ли не самое важное для профессионального системного программирования, поэтому это первое, на чем мы подробно остановимся. В последующих главах мы будем постоянно пользоваться этой информацией.

Многочисленные управляющие блоки, которые использует файловая система и BIOS при работе с дисковыми устройствами, будут описаны в книге 3, посвященной файловой системе.

Прикладная программа может пользоваться услугами DOS, вызывая прерывание INT 21H. Это прерывание имеет несколько десятков функций, которые и представляют собой интерфейс между DOS и прикладной программой. С помощью этих функций прикладная программа получает доступ к файловой системе, может обращаться к драйверам устройств, получать и устанавливать системные параметры, работать с дисплеем и клавиатурой и т.д.

Для полного использования возможностей прерываний DOS необходимы сведения о форматах различных управляющих блоков, используемых этими прерываниями.

2.1. Таблица связи управляющих блоков MS-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));
}

Ниже будут подробно описаны отдельные поля векторной таблицы связи и приведены примеры использования информации из этой таблицы.

2.2. Блоки управления памятью в MS-DOS

Первое поле векторной таблицы связи 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);
}

2.3. Список управляющих блоков устройств

Поле 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);
}

2.4. Таблица файлов MS-DOS

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".

2.5. Список загружаемых драйверов устройств

Все драйверы, резидентные или подключенные к операционной системе во время обработки файла 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    

К этой картинке мы еще вернемся при обсуждении процесса загрузки драйверов.

2.6. Другие поля векторной таблицы связи

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