DOS не разочаровала пользователей компьютеров
Версия 5.0 операционной системы MS- DOS не разочаровала пользователей компьютеров и программистов. Преимущества столь значительны и заметны, что уже не может быть оправдания для приверженцев версии 3.30 или даже 4.01. Перечислим только основные нововведения.
Возможность загрузки ядра DOS в так называемую верхнюю память. Эта память расположена за пределами первого мегабайта оперативной памяти, что позволяет выделить для программ пользователя до 630 (!) килобайт свободного пространства.
При работе MS-DOS версии 5.0 на компьютере, оснащенном процессором 80386 или 80486
возможна выгрузка драйверов и резидентных программ в расширенную память. Для этого можно использовать драйвер EMM386.
Новая диалоговая оболочка DOSSHELL
обеспечивает переключение задач. Это еще не мультизадачность, так как все запущенные программы, кроме одной, приостанавливают свою работу и выгружаются на диск. В любой момент времени работает только одна программа, остальные ждут своей очереди на диске. Возможности переключателя задач будут рассмотрены в разделе "Переключатель задач".
Многие функции и прерывания MS-DOS, которые были недокументированы в предыдущих версиях, получили "признание", отныне программисты могут использовать их на законных основаниях. Особенно это относится к функциям, обеспечивающим работу резидентных программ.
Появились новые функции и новые возможности для программистов. Мы кратко рассмотрим наиболее полезные, на наш взгляд, новые функции MS-DOS в следующих разделах этой главы.
Сразу отметим, что функция 30h прерывания INT21h, возвращающая версию MS-DOS, может ввести вас в заблуждение. Возможно, вы будете сильно удивлены, когда в среде MS-DOS версии 5.0 эта функция сообщит о том, что у вас MS-DOS версии 3.30 или даже 1.0. Как это может быть?
В состав MS-DOS версии 5.0 входит драйвер SETVER.EXE.
Этот драйвер поддерживает список имен программ, в котором каждому имени поставлен в соответствие номер версии MS-DOS, возвращаемый функцией 30h.
При запуске программы драйвер SETVER.EXE сверяет ее имя со списком (который, кстати, вы можете редактировать) и модифицирует соответствующим образом обработчик функции 30h.
Для чего потребовалось вводить программы в заблуждение относительно используемой версии MS-DOS?
Дело в том, что некоторые программы (а также драйверы) рассчитаны на работу только в среде конкретной версии MS-DOS, например, 4.00 или 4.01. На самом деле эти программы будут правильно работать и в MS-DOS версии 5.0. Но проверив версию, такие программы часто завершают свое выполнение с сообщением об ошибке в номере версии.
Вы можете получить список таких программ, просто запустив SETVER.EXE как обычную программу.
Как же получить "правильный" номер версии MS-DOS?
Для этого необходимо использовать функцию 3306h
прерывания INT 21h.
Приведем формат этой функции.
Регистры на входе:
Регистр | Содержимое |
AX | 3306h |
Регистры на выходе:
Регистр | Содержимое |
BH | Верхний (major) номер версии. |
BL | Нижний (minor) номер версии. |
DL | Биты 0..2 содержат номер изменения (revision), остальные биты равны 0. |
DH | Флаги: Бит 3 - DOS находится в ПЗУ; Бит 4 - DOS загружен в старшие адреса памяти (область HMA) |
Функция 30h в версии MS-DOS также претерпела некоторые изменения. Если при вызове этой функции в регистр AL записать значение 00h, все будет аналогично версиям 2.0-4.0. Если в AL
записать 01h, после возврата регистр BH
будет содержать флаги, по которым можно судить о расположении MS-DOS в ПЗУ или в старших адресах памяти.
Приведем полный формат вызова функции 30h
прерывания INT 21h.
Регистры на входе:
Регистр | Содержимое |
AH | 30h |
AL | 00h - получить код ОЕМ (код фирмы-изготовителя DOS) и версию DOS; 01h - получить флаги расположения DOS. |
Регистры на выходе:
Регистр | Содержимое |
AL | Верхний (major) номер версии. |
AH | Нижний (minor) номер версии. |
BX:CL | 24-битовый серийный номер конкретного экземпляра MS-DOS |
BH | Если при вызове функции AL был равен 0: Код фирмы-изготовителя: 00h - IBM; 05h - Zenith; 16h - DEC; 23h - Olivetti; 29h - Toshiba; 4Dh - Hewlett Packard; 99h - архитектура STARLITE; FFh - Microsoft, Phoenix. Если при вызове функции AL был равен 1: 08h - DOS расположен в ПЗУ; 10h - DOS загружен в старшие адреса памяти (область HMA). |
Изменения в векторной таблице связи
Формат векторной таблицы связи для MS-DOS версии 5.0 практически идентичен используемому версией 4.0. В этой таблице добавился указатель на список программ, для которых драйвер SETVER.EXE
выполняет подстановку значения версии MS-DOS.
Приведем формат векторной таблицы связи для MS-DOS версии 5.0:
(-02h) 2 | mcb_seg | сегмент первого управляющего блока памяти (MCB) |
(00h) 4 | dev_cb | указатель на первый блок управления устройствами DOS (DOS Device Control Block) |
(+04h) 4 | file_tab | указатель на таблицу файлов DOS |
(+08h) 4 | clock_dr | указатель на драйвер CLOCK$, установленный или резидентный |
(+0Ch) 4 | con_dr | указатель на актуальный драйвер CON, установленный или резидентный |
(+10h) 2 | max_btbl | максимальное число байт в блоке блочного устройства |
(+12h) 4 | disk_buf | указатель на структуру, описывающую дисковые буфера |
(+16h) 4 | drv_info | укзатель на массив информации об устройствах |
(+1Ah) 4 | fcb_tabl | указатель на таблицу FCB |
(+1Eh) 2 | fcb_size | размер таблицы FCB, для версии MS-DOS 5.0 в этом поле находится значение 00h |
(+20h) 1 | num_bdev | число блочных устройств |
(+21h) 1 | lastdriv | значение LASTDRIVE в файле CONFIG.SYS (по умолчанию равно 5) |
(+22h) 18 | null_dr | заголовок драйвера NUL - первого драйвера в списке драйверов DOS |
(+34h) 1 | joined | количество устройств, подсоединенных при помощи команды JOIN |
(+35h) 2 | special_prg | смещение внутри сегмента кода IBMDOS списка специальных программ, для MS-DOS 5.0 в этом поле находится 0000h |
(+ 37h) 4 | setver_list | указатель на список программ, для которых драйвер SETVER.EXE выполняет подстановку значения версии MS-DOS |
(+3Bh) 4 | internal_1 | используется MS-DOS |
(+3Fh) 2 | buffers_x | значение x в команде BUFFERS x,y |
(+41h) 2 | buffers_y | значение y в команде BUFFERS x,y |
(+43h) 1 | boot_drive | номер дискового устройства, использованного для загрузки MS-DOS (1 - A:) |
(+44h) 1 | cpu_type | 01h, если используется процессор 80386 или 80486, 00h для 8086 - 80286. |
(+45h) 2 | extended_mem | размер установленной расширенной памяти в килобайтах |
Управление памятью
В MS-DOS версии 5.0 стал документирован блок управления памятью MCB. Приведем формат этого блока.
(00h) 1 | Тип блока: 4Dh для всех блоков, кроме последнего. Для последнего блока используется значение 5Ah. |
(01h) 2 | Сегментный адрес блока PSP
программы-владельца данного MCB или значение 0008h для MCB, принадлежащего MS-DOS. Для свободного блока MCB в этом поле находится значение 0000h. |
(03h) 2 | Размер блока MCB в параграфах. |
(05h) 3 | Не используются. |
(08h) 8 | ASCII-имя программы-владельца блока MCB, если это блок PSP. |
Самый первый блок MCB является сегментом данных MS-DOS. Он делится на подсегменты, в которых расположены драйверы, системные данные, буфера и т.д.
Приведем формат управляющего блока подсегмента для первого блока MCB. Этот управляющий блок расположен непосредственно перед соотвестсвующим ему подсегментом.
(00h) 1 | Тип подсегмента:
"D" - драйвер устройства; "E" - расширение драйвера устройства; "I" - драйвер инсталлируемой файловой системы IFS; "F" - область памяти для управляющего блока оператора "FILES="; "X" - область памяти для управляющего блока оператора "FCBS="; "C" - рабочая область для буферов EMS, если используется оператор "BUFFERS /X"; "B" - рабочая область для буферов, если используется оператор "BUFFERS"; "L" - область памяти для массива структур текущих каталогов; "S" - сегмент памяти для оператора "STACKS="; "T" - сегмент памяти для оператора "INSTALL=". | |
(01h) 2 | Параграф, начиная с которого располагается данный подсегмент. Обычно это следующий за управляющим блоком параграф. | |
(03h) 2 | Размер подсегмента в параграфах. | |
(05h) 3 | Не используются. | |
(08h) 8 | Для подсегментов "I" и "D"
в этом поле находится имя файла, из которого был загружен драйвер. |
Кроме рассмотренных выше, в MS-DOS версии 5.0 появился новый тип управляющего блока памяти - UMCB
- управляющий блок для верхней области памяти UMB (Upper Memory Block):
(00h) 1 | Тип блока: 4Dh для всех блоков, кроме последнего. Для последнего блока используется значение 5Ah. |
(01h) 2 | Первый доступный в верхней памяти UMB параграф для самого первого блока UMCB, 000Ah - если управляющий блок расположен в конце UMB. |
(03h) 2 | Размер блока UMB в параграфах. |
(05h) 3 | Не используются. |
(08h) 8 | Тип блока: "UMB" для начального блока, "SM" - для конечного. |
Начиная с версии 3.0 MS-DOS содержит функцию 58h
прерывания INT21h, позволяющую определять и изменять стратегию выделения памяти запущенным программам. В версии 5.0 добавились новые возможности, в частности, возможность выделения блоков верхней памяти, используемых только в MS-DOS версии 5.0.
Регистры на входе:
Регистр | Содержимое |
AH | 58h |
AL | Подфункция: 00h - определить используемую стратегию; 01h - установить стратегию; 02h - получить состояние списка блоков верхней памяти UMB; 03h - установить состояние списка блоков верхней памяти UMB. |
BL | Подфункция 01h - новая стратегия: 00h - первым заполняется первый свободный участок в области младших адресов памяти; 01h - аналогично, но используется участок памяти, наилучшим образом подходящий для запроса; 02h - аналогично, но используется последний свободный участок памяти; 40h ,41h 42h - аналогично 00h, 01h, 02h, но используются старшие адреса памяти; 80h, 81h, 82h - аналогично 00h, 01h, 02h, но сначала делается попытка выделить старшие адреса памяти, а в случае неудачи - младшие. Подфункция 03h - новое состояние списка блоков верхней памяти: BX = 0000h - удалить блоки UMB из списка блоков памяти DOS; BX = 0001h - добавить блоки UMB в список блоков памяти DOS. |
BH | Подфункция 01h - новая стратегия: Равен 00h. |
Регистры на выходе:
Регистр | Содержимое |
AX | Подфункция 00h - определить стратегию: AX = код используемой стратегии (см. выше). Подфункция 02h - определитьсостояние списка блоков верхней памяти: AL = 00h - блоки UMB удалены из списка блоков памяти DOS; AL = 01h - блоки UMB входят в список блоков памяти DOS. |
В версии 5.0 MS- DOS появились функция для непосредственного выделения программам блоков старшей памяти HMA. Это функция 4A02h:
Регистры на входе:
Регистр | Содержимое |
AX | 4A02h |
BX | Размер выделяемого блока памяти в байтах (после возврата содержимое этого регистра будет уничтожено). |
Регистры на выходе:
Регистр | Содержимое |
ES:DI | Адрес выделенного блока памяти или FFFF:FFFF в случае неудачи. |
Прежде чем пользоваться только что описанной функцией, имеет смысл определить размер свободной старшей памяти при помощи функции 4A01h:
Регистры на входе:
Регистр | Содержимое |
AX | 4A01h |
Регистры на выходе:
Регистр | Содержимое |
BX | Размер доступной области старшей памяти в байтах или 0000h, если DOS не использует область HMA. |
ES:DI | Адрес начала доступной области старшей памяти или FFFF:FFFF, если DOS не использует область HMA. |
Управление программами
В версии 5.0 MS-DOS появились некоторые новые функции, имеющие отношение к управлению программами. Например, функция 4B05h прерывания INT 21h предназначена для подготовки программы к выполнению. Эта функция может использоваться программами, перехватывающими функцию 4B00h (обычный запуск программы), например, для установки версии MS-DOS.
Регистры на входе:
Регистр | Содержимое |
AX | 4B05h |
DS:DX | Адрес управляющей структуры (см. ниже) |
Регистры на выходе:
Регистр | Содержимое |
AX | 00h, если сброшен флаг CF и, соответственно, функция выполнилась без ошибок; код ошибки, если установлен флаг CF. |
Перед вызовом функции необходимо подготовить управляющую структуру:
(00h) 2 | Зарезервировано и равно 00h. | |
(02h) 2 | Флаги типа программы:
Бит 0 - программа типа .EXE; Бит 1 - оверлей. | |
(04h) 4 | Указатель на ASCII-имя файла с программой. | |
(08h) 2 | PSP сегмента новой программы. | |
(0Ah) 4 | Стартовый адрес CS:IP новой программы. | |
(0Eh) 4 | Размер программы с учетом PSP. |
После возврата из этой функции до завершения запущенной программы нельзя вызывать прерывания DOS, BIOS или другие программные прерывания. Если MS-DOS работает в верхней области памяти HMA, после возврата из этой функции адресная линия A20
выключается .
Некоторые недокументированные функции управления программами были включены в документацию MS-DOS версии 5.0:
Функция | Назначение |
50h | Установить PSP для текущей программы. |
51h | Получить PSP текущей программы. |
Резидентные программы
К сожалению, версия 5.0 операционной системы MS-DOS не порадовала разработчиков какими-либо новыми возможностями в плане создания резидентных программ. Это, в частности, говорит о неперспективности данного класса программ. Очевидно, что при наличии переключателя программ и таких средств, как Microsoft Windows, обеспечивающих одновременное (или псевдо-одновременное) выполнение программ, актуальность резидентных программ падает.
Но так как нельзя игнорировать большое количество уже созданных (и достаточно удобных в использовании) резидентных программ, все недокументированные ранее возможности, без которых невозможно создать правильно работающую резидентную программу, стали документированными.
Взяв на себя обязательства поддерживать эти возможности, фирма Microsoft гарантирует, что и в следующих версиях MS-DOS вы по-прежнему сможете использовать свои старые добрые резидентные программы.
Драйверы
Что касается драйверов устройств, то версия 5.0 операционной системы содержит расширения для работы с командами общего ввода/вывода - GENERIC IOCTL.
В заголовке драйвера в слове атрибутов определен зарезервированный ранее бит 7. Если этот бит установлен в 1, драйвер поддерживает новую команду с кодом 19h. Эта команда позволяет операционной системе определить, спосбен ли данный драйвер работать с командами общего ввода/вывода (GENERIC IOCTL).
Для проверки возможности использования GENERIC IOCTL программа может вызвать одну из двух новых функций - 4410h или 4411h. Первая функция определяет поддержку GENERIC IOCTL для устройства, заданного своим индексом (handle), вторая - для устройства, заданного своим адресом (номером).
Функция 4410h:
Регистры на входе:
Регистр | Содержимое | |
AX | 4410h | |
BX | Индекс (handle) устройства. | |
CH | Код категории:
00h неизвестное устройство 01h COMn: 03h CON 05h LPTn: | |
CL | Код функции, для которого необходимо выполнить проверку поддержки:
45h установить счетчик повторов 4Ah выбрать кодовую страницу 4Ch начать подготовку кодовой страницы 4Dh завершить подготовку кодовой страницы 5Fh установить информацию для отображения 65h получить счетчик повторов 6Ah запросить выбранную кодовую страницу 6Bh запросить подготовленный список 7Fh получить информацию для отображения |
Регистры на выходе:
Регистр | Содержимое | |
AX | 0000h, флаг CF сброшен - данная функция GENERIC IOCTL поддерживается драйвером.
xx01h, флаг CF установлен в 1 - функция не поддерживается. |
Функция 4411h:
Регистры на входе:
Регистр | Содержимое |
AX | 4410h |
BL | Номер устройства. |
CH | Код категории (08h - диск) |
CL | Код функции, для которого необходимо выполнить проверку поддержки. |
Регистры на выходе:
Регистр | Содержимое | |
AX | 0000h, флаг CF сброшен - данная функция GENERIC IOCTL поддерживается драйвером.
xx01h, флаг CF установлен в 1 - функция не поддерживается. |
Переключатель задач
Переключатель задач, входящий в диалоговую оболочку DOSSHELL - одно из значительных усовершенствований MS-DOS версии 5.0. Этото переключатель позволяет запустить на выполнение одновременно несколько программ, хотя в действительности работать будет только одна - та, которая была запущена последней. Неактивные программы выгружаются на диск.
Нажимая клавиши <ALT-TAB>, можно выбирать для выполнения одну из запущенных программ.
Обратим ваше внимание на одну потенциальную опасность, связанную с использованием переключателя задач.
Предположим, что одна из запущенных вами программ устанавливает собственный обработчик какого-либо аппаратного прерывания. Например, вы запустили телекоммуникационную программу и работаете через модем с удаленным компьютером. Не исключено, что ваша программа устанавливает собственный обработчик аппаратного прерывания от асинхронного порта, к которому подключен модем.
Так как передача данных через модем - дело не быстрое, у вас может появиться необходимость выполнить на компьютере какую-либо другую работу, например, отредактировать и распечатать текст. Нет проблем, вы переключаетесь на программу редактирования текстов. Но... компьютер почему-то "зависает".
А дело оказывается в том, что выгрузив телекоммуникационную программу на диск, переключатель задач "оторвал" обработчик аппаратного прерывания асинхронного порта. Прерывание пришло, а обработчика-то и нет!
Для пользователя в данной ситуации есть только один выход - не использовать переключатель программ совместно с телекоммуникационными программами.
Однако программист имеет возможность учесть в своих разработках присутствие переключателя задач. Программа, определив наличие переключателя, может либо отказаться от работы, либо запретить переключение, если оно затребовано в неподходящий момент времени, либо выполнить перед переключением некоторые "замораживающие" действия (например, замаскировать аппаратное прерывание). Когда программа вновь получит управление, она может "разморозиться".
Последний способ взаимодействия с переключателем самый удобный. Он возможен благодаря тому, что переключатель задач имеет механизм, позволяющий известить работающую программу о том, что затребовано переключение.
Альтернативный сброс дисковода (НМД)
На входе: | AH = 0Dh |
DL = Адрес дисковода (0, 1, ..., 80h, 81h, ...) | |
На выходе: | AH = Состояние дисковода после завершения последней операции |
CF = 1, если произошла ошибка, 0, если ошибки нет |
|
Примечание: | PC, XT, AT, PS/2 |
Вы можете использовать эту функцию для сброса контроллера вместо функции с кодом 00h. В отличие от функции сброса дисковой подсистемы с кодом 00h эта функция не влияет на контроллер НГМД, она сбрасывает только контроллер накопителя на жестком магнитном диске.
Атрибуты драйвера
Символьное устройство
Бит | Назначение |
0 | 1 - драйвер обслуживает стандартное устройство ввода; 0 - этот драйвер не обслуживает стандартное устройство ввода |
1 | 1 - драйвер обслуживает стандартное устройство вывода; 0 - драйвер не обслуживает стандартное устройство вывода |
2 | 1 - это драйвер стандартного устройства NUL; 0 - драйвер не обслуживает устройство NUL |
3 | 1 - драйвер обслуживает часы |
4 | Зарезервировано, бит должен быть равен 0 |
5 | Зарезервировано, бит должен быть равен 0 |
6 | 1 - разрешено использование драйвером функций GENERIC IOCTL (для версий DOS, более поздних, чем 3.2); 0 - функции GENERIC IOCTL не поддерживаются |
7-10 | Эти биты зарезервированы и должны быть равны 0 |
11 | 1 - поддерживаются функции открытия/закрытия устройства (OPEN/CLOSE) для символьных устройств; 0 - функции OPEN/CLOSE для символьных устройств не поддерживаются |
12 | Зарезервировано, бит должен быть равен 0 |
13 | 1 - для символьных устройств поддерживается функция вывода до получения состояния занятости устройства; 0 - функция вывода до состояния занятости не поддерживается |
14 | 1 - поддерживаются функции IOCTL; 0 - функции IOCTL не поддерживаются |
15 | 1 - символьное устройство; 0 - блочное устройство |
Блочное устройство
Бит | Назначение |
0 | Зарезервировано, бит должен быть равен 0 |
1 | 1 - драйвер поддерживает 32-битовую адресацию сектора (для версий DOS, начиная с 4.00 и более поздних); если установлен этот бит, поле номера сектора всех запросов является двойным словом, добавляемым в конец заголовка запроса, старое поле номера сектора должно содержать -1); 0 - используется 16-битовая адресация сектора |
2-5 | Эти биты зарезервированы и должны быть равны 0 |
6 | 1 - поддерживаются логические устройства (используется блочными драйверами для управления "виртуальными" флоппи-дисками, создаваемые драйвером DRIVER.SYS
в DOS версии 3.2 и более поздних версиях); 0 - логические устройства для блочных драйверов не поддерживаются; |
7-10 | Эти биты зарезервированы и должны быть равны 0 |
11 | 1 - единица в этом бите означает, что драйвер поддерживает функцию проверки замены носителя данных в устройстве (например, замены дискеты), используется для DOS версий 3.00 и более поздних; 0 - для блочных устройств функция проверки замены носителя данных не поддерживается |
12 | Зарезервировано, бит должен быть равен 0 |
13 | 1 - драйвер не использует стандартное IBM-устройство, и необходимо выдать запрос на построение блока параметров BIOSBIOS
BPB; 0 - используется IBM-устройство |
14 | 1 - поддерживаются функции IOCTL; 0 - функции IOCTL не поддерживаются |
15 | 1 - символьное устройство; 0 - блочное устройство |
Байт атрибутов файла
0 | Файл предназначен только для чтения, в этот файл нельзя писать и его нельзя стирать. |
1 | Скрытый файл, этот файл не будет появляться в списке файлов, создаваемом командой операционной системы DIR. |
2 | Системный файл. Этот бит обычно установлен в файлах, являющихся составной частью операционной системы. |
3 | Данный дескриптор описывает метку диска. Для этого дескриптора поля имени файла и расширения имени файла должны рассматриваться как одно поле длиной 11 байт. Это поле содержит метку диска. |
4 | Дескриптор описывает файл, являющийся подкаталогом данного каталога. |
5 | Флаг архивации. Если этот бит установлен в 1, это означает, что данный файл не был выгружен утилитой архивации (например, программой BACKUP). |
6-7 | Зарезервированы. |
Байт-описатель среды media
FFh - 2 стороны, 8 секторов на дорожке; FEh - 1 сторона, 8 секторов на дорожке; FDh - 2 стороны, 9 секторов на дорожке; FCh - 1 сторона, 9 секторов на дорожке; F9h - 2 стороны, 15 секторов на дорожке; F8h - жесткий диск.
Блок BPB
(0) 2 | sect_siz | Количество байтов в одном секторе диска. |
(+2) 1 | clustsiz | Количество секторов в одном кластере. |
(+3) 2 | res_sect | Количество зарезервированных секторов. |
(+5) 1 | fat_cnt | Количество таблиц FAT. |
(+6) 2 | root_siz | Максимальное количество файловых дескрипторов, содержащихся в корневом каталоге диска. |
(+8) 2 | tot_sect | Общее количество секторов на носителе данных (в разделе DOS). |
(+10) 1 | media | Байт-описатель среды носителя данных. |
(+11) 2 | fat_size | Количество секторов, занимаемых одной копией FAT. |
Блок управления памятью MCB
(0) 1 | type | тип блока MCB (M или Z) |
(+1) 2 | owner | параграф владельца блока (если 0, то блок описывает сам себя) |
(+3) 2 | size | число параграфов в этом блоке (один параграф имеет размер 16 байт) |
(+5) 11 | reserve | зарезервировано |
Буферизация ввода/вывода
Ввод/вывод для дисков в операционной системе MS-DOS буферизован. Это означает, что данные не сразу записываются на диск, а накапливаются в специальном массиве (буфере). По мере заполнения буфер сбрасывается на диск. При чтении информация заполняет весь входной буфер, независимо от количества байтов, которые программа читает из файла. В дальнейшем, если программе потребуются данные, которые уже были считаны с диска и записаны во входной буфер, она получит данные непосредственно из этого буфера. Обращения к диску при этом не будет.
Буферизация сокращает затраты времени на ввод/вывод, особенно в тех случаях, когда программе периодически требуется одни и те же участки файлов. При копировании файлов буферизация сокращает время на перемещение головок от исходного файла к результирующему и обратно, причем эффект получается тем больше, чем больше размер используемого буфера.
Операционная система MS-DOS имеет несколько буферов. Их количество зависит от оператора BUFFERS, находящегося в файле CONFIG.SYS. Этот оператор позволяет определить от 2 до 99 буферов. Если файл CONFIG.SYS
не содержит оператора BUFFERS, по умолчанию используются два буфера.
При увеличении количества буферов увеличивается вероятность того, что нужная часть файла уже считана и находится в оперативной памяти. Однако необходимо учитывать, что для хранения буферов расходуется основная оперативная память. Кроме того, с ростом количества буферов увеличивается время, необходимое операционной системе на анализ состояния буферов, что может привести к снижению производительности. Значительное снижение скорости работы наступает при количестве буферов порядка 50.
Обычно для машин класса AT с диском размером 20-40 мегабайтов рекомендуется использовать 32 буфера, однако для каждого конкретного случая может потребоваться подбор этого параметра для оптимизации производительности системы.
Если ваша программа интенсивно использует обращение к каталогам файловой системы, вы можете использовать утилиту MS-DOS FASTOPEN, которая запоминает в оперативной памяти расположение на диске файлов и каталогов, уменьшая интенсивность обращения к диску. Например, при использовании следующей команды в оперативной памяти будет храниться информация о расположении максимально о 100 файлах и каталогах:
FASTOPEN c:=100
В операционной системе MS-DOS версии 4. 0 вы можете указать для утилиты FASTOPEN опцию /X. Эта опция вызывает размещение информации о файлах и каталогах в дополнительной (expanded) памяти. Для этой версии операционной системы вызов утилиты FASTOPEN
лучше всего выполнять через оператор INSTALL
файла CONFIG.SYS:
INSTALL=C:\DOS\FASTOPEN d: =(n,m) ... [/X]
В приведенной выше строке используются следующие обозначения:
d: | обозначение диска; |
n | количество файлов или каталогов, для которых необходимо запомнить расположение, может иметь значение от 1 до 999, по умолчанию используется 34; |
m | количество буферов для фрагментированных файлов, может иметь значение от 1 до 999, по умолчанию используется 34; |
/X | задает расположение буферов в дополнительной памяти. |
device=c:\dos\smartdrv.sys 530
Кэш-память особенно эффективна при работе с базами данных, когда вам периодически требуется одна и та же информация. Если создать кэш-память достаточно большого размера, можно значительно сократить количество обращений к диску.
Если вы используете кэш-память для диска, не следует задавать оператор BUFFERS в файле CONFIG.SYS или пользоваться утилитой FASTOPEN, так как это приведет к многократной буферизации и вызовет излишние пересылки данных в оперативной памяти.
Буферизация данных имеет и свои недостатки. Если в результате аварии в питающей сети или по какой-то другой причине компьютер выключился, то информация, хранящаяся в буферах и не записанная на диск, будет потеряна.
При закрытии файла все буфера, связанные с ним, сбрасываются на диск. Если вам надо сбросить буфера, не закрывая файл, это можно сделать с помощью функции 68h прерывания INT21h:
На входе: | AH = 68h |
BX = файловый индекс открытого файла | |
На выходе: | AX = Код ошибки, если был установлен в 1 флаг переноса CF; 0, если операция выполнена успешно. |
Обратите также внимание на функцию расширенного открытия файлов 6Ch, входящую в состав MS-DOS версии 4.0. Эта функция позволяет при открытии файла отменить буферизацию.
Стандартные библиотеки трансляторов Microsoft QC 2.5 и C 6.0 содержат многочисленные функции, использующие собственный механизм буферизации при работе с файлами. Их часто называют функциями потокового ввода/вывода. Такую буферизацию не следует путать с буферизацией, выполняемой операционной системой. Имена всех этих функций начинаются на f - fopen(), fclose(), fprintf() и т.д.
Функции потокового ввода/вывода хорошо описаны во многих учебных пособиях по языку программирования Си, поэтому мы приведем лишь краткий обзор этих функций, делая акцент на особенностях их применения.
При использовании функций потокового ввода/вывода файлы открываются функцией fopen(), закрываются функцией fclose(). Эти функции не только открывают и закрывают файлы (получают и освобождают их файловый индекс), но и, соответственно, создают и уничтожают структуру типа FILE, описанную в файле stdio.h и связанную с данным файлом:
extern FILE {
char *_ptr; // положение текущего символа int _cnt; // количество оставшихся байтов char *_base; // адрес буфера char _flag; // флаг управления доступом char _file; // файловый индекс
} _NEAR _CDECL _iob[];
Для организации потокового ввода/вывода вначале необходимо при помощи функции fopen()
открыть файл. Функция fopen() имеет следующий прототип:
FILE *fopen(char *filename, char *mode);
Первый параметр указывает на строку, содержащую путь открываемого файла, второй - на строку режима открытия файла. Возможны следующие режимы:
"r" | файл открывается для чтения; |
"w" | файл открывается для записи; |
"a" | файл открывается для записи, данные будут добавляться в конец файла. |
К буквам r, w, a справа могут добавляться буквы t и b.
Буква t означает, что файл будет открыт в текстовом режиме, b - в двоичном. Для двоичного режима не производится обработка таких символов, как конец строки, конец файла и т.д.
Строка режима открытия файла может дополнительно содержать символ '+'. Этот символ означает, что для файла разрешены операции чтения и записи одновременно.
Для закрытия файлов, открытых для ввода/вывода потоком, должна использоваться функция fclose():
int fclose(FILE *stream);
При закрытии файла освобождаются и сбрасываются на диск все буфера, распределенные этому файлу.
Если вы открыли файл с помощью низкоуровневой функции open(), вы можете создать поток для этого файла, используя функцию fdopen():
FILE *fdopen(int handle, char *mode);
В качестве первого параметра используется файловый индекс, полученный от функции open(),
второй параметр аналогичен парметру mode для функции fopen().
Для того чтобы закрыть поток, созданный функцией fdopen(), необходимо использовать функцию fclose(), а не close().
Для открытого потока вы можете узнать файловый индекс с помощью функции fileno():
int fileno(FILE *stream);
Функция возвращает файловый индекс, связанный с данным потоком.
Существуют потоки, соответствующие стандартным устройствам ввода, вывода, вывода сообщений об ошибках, стандартное устройство последовательного ввода/вывода и стандартное устройство печати. Для использования этих потоков не требуются процедуры открытия и закрытия. Для работы с потоками, соответствующими стандартным устройствам ввода/вывода, в функциях необходимо указывать:
stdin | стандартное устройство ввода; |
stdout | стандартное устройство вывода; |
stderr | стандартное устройство для вывода сообщений об ошибках; |
stdaux | стандартное последовательное устройство ввода/вывода; |
stdprn | стандартное печатающее устройство. |
Приведем обзор функций, предназначенных для потокового ввода/вывода.
Для записи данных в поток предназначена функция fwrite():
size_t fwrite(void *buffer, size_t size, size_t count, FILE *stream);
Эта функция записывает в файл stream блоки информации, каждый из которых имеет длину size
байтов. Количество блоков - count. Данные для записи расположены по адресу buffer.
Если файл открыт в текстовом режиме, каждый символ возврата каретки CR заменяется на два символа - возврата каретки CR и перевода строки LF.
Функция возвращает количество действительно записанных блоков информации, без учета замены символа CR в текстовом режиме.
Чтение данных потоком можно выполнить с помощью функции fread():
size_t fread(void *buffer, size_t size, size_t count, FILE *stream);
Эта функция используется аналогично предыдущей. Для распознавания конца файла и обнаружения ошибок после вызова этой функции необходимо использовать функции feof() и ferror().
Если при использовании функции fread() вы задали значения параметров size или count, равные нулю, функция fread() не изменяет содержимое буфера buffer.
Для позиционирования внутри файла, открытого потоком с помощью функции fopen(), предназначена функция fseek():
int fseek(FILE *stream, long offset, int origin);
В этой функции параметр offset задает новое содержимое указателя текущей позиции в файле stream, а параметр origin - определяет способ задания новой позиции. Этот оператор может иметь значения, аналогичные используемым в функции
lseek():
SEEK_SET | Абсолютное смещение от начала файла |
SEEK_CUR | Смещение относительно текущей позиции |
SEEK_END | Смещение относительно конца файла |
Функция fseek() позволяет вам установить указатель за конец файла, однако при попытке установаит указатель до начала файла функция возвратит признак ошибки - ненулевое значение.
При использовании функции fseek() для позиционирования внутри файлов, открытых в текстовом режиме, необходимо учитывать особенность обработки текстовых файлов - автоматическую замену символа возврата каретки CR
на пару символов: возврат каретки CR и перевод строки LF. Для текстовых файлов функция fseek()
будет правильно работать только в следующих двух случаях:
поиск со смещением offset, равным нулю, при любом значении параметра origin;
поиск выполняется относительно начала файла, причем в качестве смещения offset используется значение, полученное специальной функцией ftell().
Функция ftell() возвращает текущее значение указателя позиции для файла, или -1 при ошибке:
long ftell(FILE *stream);
Пара функций ftell() - fseek() позволит вам правильно организовать позиционирование для файлов, открытых в текстовом режиме.
Есть еще одна возможность организовать позиционирование внутри файлов, открытых потоком - использовать пару функций fgetpos() - fsetpos():
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, fpos_t *pos);
Эти две функции используют для запоминания и установки позиции переменную с типом fpos_t,
определенным в файле stdio.h. Функция fgetpos()
записывает в эту переменную текущую позицию в потоке stream. Содержимое переменной затем может быть использовано для установки позиции в потоке с помощью функции fsetpos().
Обе эти функции возвращают нулевое значение в случае успешного завершения работы, или ненулевое - при ошибке.
Среди функций потокового ввода/вывода можно выделить группу функций форматного ввода/вывода. Это такие функции, как fputc(), fgetc(), fputs(), fgets(), fprintf(), fscanf().
Функции форматного ввода/вывода сильно облегчают запись/чтение таких элементов данных, как отдельные байты, текстовые строки, числа в различных форматах.
Для записи в поток отдельных байтов используется функция fputc():
int fputc(int c, FILE *stream);
Байт c записывается в файл stream начиная с текущей позиции. После записи текущая позиция увеличивается на единицу. Функция возвращает записываемый байт или значение EOF, которое служит признаком ошибки.
Для побайтового чтения содержимого файла, открытого потоком, удобно использовать функцию
fgetc():
int fgetc(FILE *stream);
Эта функция возвращает считанный из файла и преобразованный к типу int байт из потока stream. После чтения байта текущая позиция в файле увеличивается на единицу.
При достижении конца файла или в случае ошибок функция fgetc() возвращает значение EOF. Однако для проверки на ошибку или конец файла лучше пользоваться специальными функциями
ferror() и feof(). Если вы открыли файл в двоичном режиме, единственный способ определить момент достижения конца файла - использовать функцию feof(), так как значение константы EOF может находиться в любом месте двоичного файла.
Для работы со строками предназначены функции fputs()
и fgets().
Функция fputs() предназначена для вывода строки в файл, открытый потоком:
int fputs(char *string, FILE *stream);
Первый параметр определяет выводимую строку, второй - файл, в который эта строка выводится. Двоичный ноль, закрывающий строку, в выходной файл не копируется. После вывода строки содержимое текущего указателя позиции увеличивается на количество записанных байтов.
Для ввода строк из текстового файла удобна функция fgets():
int fgets(char *string, int n, FILE *stream);
Функция читает байты из потока stream и записывает их в буфер string до тех пор, пока не произойдет одно из двух событий - будет прочитан символ новой строки '\n' или количество прочитанных символов не будет равно n-1.
После того, как байты будут прочитаны в буфер, в конец образованной из этих байтов строки функция записывает двоичный ноль. Если был прочитан символ новой строки '\n', он тоже записывается в буфер.
Для анализа достижения конца файла или ошибок необходимо использовать функции feof() и ferror().
Для форматного вывода в файл содержимого переменных удобно использовать функцию fprintf():
int fprintf(FILE *stream, char *format [,arg]...);
Эта функция аналогична хорошо известной вам функции форматного вывода на экран printf(), с которой обычно начинают изучение языка программирования Си. Вспомните такую программу:
#include <stdio.h> main() { printf("Hello, world!"); }
Функция fprintf() имеет дополнительно один параметр - stream, который определяет выходной файл.
После завершения работы функция возвращает количество записанных байтов или отрицательную величину, если при записи произошла ошибка.
Для форматного ввода информации из файла можно использовать функцию fscanf(), аналогичную известной вам функции scanf():
int fscanf(FILE *stream, char *format [,arg]...);
Эта функция читает данные, начиная с текущей позиции в потоке stream, в переменные, определенные аргументами arg. Каждый аргумент должен являться указателем на переменную, соответствующую типу, определенному в строке формата format.
Функция fscanf() возвращает количество успешно считанных и преобразованных в указанный формат полей. Те поля, которые были считаны, но не преобразовывались, в возвращаемом функцией значении не учитываются.
При достижении конца файла функция возвращает значение EOF. Если функция возвратила нулевое значение, это означает, что преобразование полей не производилось.
Рассмотрим теперь функции, управляющие буферизацией для потокового ввода/вывода.
Функция setbuf() позволяет вам заменить системный буфер на свой собственный:
void setbuf(FILE *stream, char *buffer);
Параметр buffer должен указывать на подготовленный пользователем массив, имеющий размер BUFSIZ байтов. Константа BUFSIZ описана в файле stdio.h.
Функция setvbuf() позволяет программе не только указать свой буфер, но и задать его размер:
int setvbuf(FILE *stream, char *buffer, int mode, size_t size);
Параметр stream должен указывать на открытый поток, причем для этого потока до вызова функции setvbuf()
нельзя выполнять операции чтения/записи.
Параметр buffer должен указыват на подготовленный программой буфер размером size
байтов. Этот буфер будет использоваться для работы с потоком stream.
Параметр mode может иметь значения _IOFBF, _IOLBF, _IONBF. Если mode равно _IOFBF или _IOLBF, параметр size указывает размер буфера. Если параметр mode равен _IONBF, буферизация не используется, парметры buffer и size
игнорируются.
Параметры _IOFBF и _IOLBF эквивалентны.
Если в качестве адреса буфера buffer задать значение NULL, функция автоматически закажет буфер размером size.
Функция setvbuf() возвращает ноль при успешном завершении и ненулевую величину, если указан неправильный парметр mode или неправильный размер буфера size.
Для чего может понадобиться изменение размера буфера?
Главным образом - для сокращения времени, необходимого для позиционирования магнитных головок при выполнении операций одновременно над несколькими файлами, например, при копировании файлов, слиянии нескольких файлов в один и т.д.
При закрытии потока функцией fclose()
содержимое буфера записывается на диск. Если программе необходимо выполнить запись содержимого буфера на диск без закрытия файла, она может воспользоваться функцией fflush():
int fflush(FILE *stream);
Эта функция возвращает ноль при успешной записи буфера на диск, а так же в тех случаях, когда поток либо совсем не имеет буферов, либо открыт только для чтения. При ошибке возвращается значение EOF.
Если поток открыт только для чтения, функция
fflush() очищает содержимое буфера, связанного с этим потоком.
В качестве примера приведем текст программы, копирующей содержимое текстового файла. Программа копирует этот файл три раза. В первый раз одна использует буфер стандартного размера, затем увеличивает размер буфера в десять раз, и, наконец, копирует файл без использования механизма буферизации. Каждый раз программа измеряет продолжительность копирования файла с помощью функции clock(), входящей в состав стандартных библиотек трансляторов Microsoft QC 2.5 и C 6.0.
#include <stdio.h> #include <stdlib.h> #include <time.h>
void filecpy(FILE *stream_from, FILE *stream_to);
// Буфера для файлов
char buf1[BUFSIZ * 10]; char buf2[BUFSIZ * 10];
void main(int argc, char *argv[]) {
time_t start, end; FILE *stream_from, *stream_to;
if(argc < 3) { printf("Задайте имена файлов!\n"); exit(-1); }
// Открываем файлы и используем для копирования // буфер стандартного размера
if((stream_from = fopen(argv[1], "rt")) == NULL) { printf("Задайте имя входного файла!\n"); exit(-1); } stream_to = fopen(argv[2], "wt+");
// Определяем время начала копирования
start = clock();
// Выполняем копирование файла
filecpy(stream_from,stream_to);
// Определяем время завершения копирования
end = clock();
// Выводим время копирования при использовании // буферов стандартного размера
printf("Время копирования: %5.1f Размер буфера, байтов: %d\n", ((float)end - start) / CLK_TCK, BUFSIZ);
// Задаем свой буфер большего размера
if((stream_from = fopen(argv[1], "rt")) == NULL) exit(-1); stream_to = fopen(argv[2], "wt+");
// Устанавливаем буфера как для входного, // так и для выходного файлов
setvbuf(stream_from, buf1, _IOFBF, sizeof(buf1)); setvbuf(stream_to, buf2, _IOFBF, sizeof(buf2));
// Копируем файл и измеряем продолжительность // копирования
start = clock(); filecpy(stream_from,stream_to); end = clock();
printf("Время копирования: %5.1f Размер буфера: %d\n", ((float)end - start) / CLK_TCK, BUFSIZ * 10);
// Копируем без использования буферизации
if((stream_from = fopen(argv[1], "rt")) == NULL) exit(-1); stream_to = fopen(argv[2], "wt+");
setvbuf(stream_from, NULL, _IONBF, 0); setvbuf(stream_to, NULL, _IONBF, 0);
start = clock(); filecpy(stream_from,stream_to); end = clock();
printf("Время копирования: %5.1f Буферизация не используется\n", ((float)end - start) / CLK_TCK);
exit(0); }
// Функция для копирования файлов
void filecpy(FILE *stream_from, FILE *stream_to) {
char linebuf[256];
// Цикл копирования. Условие выхода из цикла - // конец входного файла
while(!feof(stream_from)) {
// Читаем в буфер linebuf одну строку
if(fgets(linebuf, 255, stream_from) == NULL) break;
// Записываем содержимое буфера linebuf // в выходной файл
if(fputs(linebuf, stream_to) != 0) break; }
// Закрываем входной и выходной файлы
fclose(stream_from); fclose(stream_to);
}
Чтение буфера сектора (НМД)
На входе: | AH = 0Eh |
AL = Количество секторов, которые нужно прочитать | |
CH = Номер дорожки | |
CL = Номер сектора | |
DH = Номер головки | |
ES:BX = Адрес буфера для данных | |
DL = Адрес дисковода (0, 1, ..., 80h, 81h, ...) | |
На выходе: | AH = Состояние дисковода после завершения последней операции |
CF = 1, если произошла ошибка, 0, если ошибки нет |
|
Примечание: | PC, XT |
Контроллеры НМД в компьютерах PC и XT содержат внутренний буфер данных. С помощью функции 0Eh программа может прочитать содержимое этого буфера в оперативную память. Чтения данных с диска при этом не происходит. В основном функция чтения буфера используется для диагностики дискового контроллера.
Чтение сектора
На входе: | AH = 02h |
AL = Количество секторов, которые нужно прочитать | |
CH = Номер дорожки | |
CL = Номер сектора | |
DH = Номер головки | |
DL = Адрес дисковода (0, 1, ...,80h, 81h, ...) | |
ES:BX = Адрес буфера для данных | |
На выходе: | AH = Состояние дисковода после завершения последней операции |
CF = 1, если произошла ошибка, 0, если ошибки нет |
|
Примечание: | PC, XT, AT, PS/2 |
Эта функция позволяет прочитать один или несколько секторов диска в буфер, находящийся в оперативной памяти. Вам надо задать для начального сектора номера дорожки, головки и номер самого сектора.
Для НМД номер дорожки и сектора задаются следующим образом: биты регистра CX 5...0 задают номер сектора, а биты 15...6 - номер дорожки.
Перед чтением необходимо подготовить таблицу параметров дискеты или диска (для операций с НМД).
Чтение секторов длинное (НМД)
На входе: | AH = 0Ah |
AL = Количество секторов, которые нужно прочитать | |
CH = Номер дорожки | |
CL = Номер сектора | |
DH = Номер головки | |
DL = Адрес дисковода (0, 1, ..., 80h, 81h, ...) | |
ES:BX = Адрес буфера для данных | |
На выходе: | AH = Состояние дисковода после завершения последней операции |
CF = 1, если произошла ошибка, 0, если ошибки нет |
|
Примечание: | PC, XT, AT, PS/2 |
Функция "Чтение секторов длинное" отличается от обычной функции чтения (код 02h) тем, что она дополнительно считывает в буфер данных 4 байта кода коррекции ошибки (ECC).
Чтение/запись файлов
После того, как вы открыли файл, можно выполнять над ним операции чтения/записи. Для записи данных в файл предназначена функция 40h прерывания INT 21h. В качестве параметров для этой функции необходимо задать файловый индекс, полученный при открытии существующего файла или создании нового, адрес буфера, содержащего записываемые данные и количество записываемых байтов:
На входе: | AH = 40h |
BX = файловый индекс открытого файла | |
CX = количество записываемых байтов | |
DS:DX = Адрес буфера, содержащего записываемые данные | |
На выходе: | AX = Код ошибки, если был установлен в 1
флаг переноса CF; Количество действительно записанных байтов, если флаг переноса CF сброшен в 0. |
При записи данные попадают в то место внутри файла, которое определяется содержимым так называемого файлового указателя позиции. При создании нового файла этот указатель сбрасывается в 0, что соответствует началу файла. При открытии файла с помощью функции 3Dh
указатель также устанавливается на начало файла. Операция записи в файл с помощью функции 40h
продвигает указатель вперед к концу файла на количество записываемых байтов.
По мере увеличения размера файла ему будут распределяться все новые и новые кластеры из числа отмеченных как свободные.
Если вам необходимо перезаписать содержимое файла, а не дописывать данные в конец, необходимо воспользоваться функцией позиционирования. Эта функция позволяет управлять содержимым файлового указателя позиции, она будет описана в следующем разделе.
Следует учитывать, что количество действительно записанных байтов может не совпадать с заданным в регистре CX при вызове функции 40h. Такая ситуация возможна, например, при записи в файл, открытый в текстовом режиме, байта Ctrl-Z (1Ah). Этот байт означает конец текстового файла. Другая возможная причина - отсутствие свободного места на диске.
Если функция вызывается с содержимым регистра CX, равным 0, файл будет обрезан или расширен до текущего положения файлового указателя.
Разумеется, что если программа, выполняющая запись в файл, работает в сети, она должна иметь соответствующие права доступа к каталогу и файлу.
Функция 40h может выполнять запись не только в файл, но и в устройство посимвольной обработки, предварительно открытое функцией 3Dh. Об этом мы говорили в разделах книги, посвященных драйверам.
Для чтения данных из файла (или устройства посимвольной обработки) предназначена функция 3Fh
прерывания INT 21h:
На входе: | AH = 3Fh |
BX = файловый индекс открытого файла | |
CX = количество читаемых байтов | |
DS:DX = Адрес буфера для данных | |
На выходе: | AX = Код ошибки, если был установлен в 1 флаг переноса CF; Количество действительно прочитанных байтов, если флаг переноса CF сброшен в 0. |
Если ваша программа составлена на языке программирования С, для записи и чтения данных она может воспользоваться функциями write() и
read():
int write(int handle, void *buffer, unsigned count);
int read(int handle, void *buffer, unsigned count);
Эти функции работают аналогично функциям 40h
и 3Fh прерывания INT 21h. Параметр handle
определяет файл, для которого необходимо выполнить операцию записи или чтения. Параметр buffer
- указатель на буфер, который содержит данные для записи или в который необходимо поместить прочитанные данные. Количество записываемых/читаемых байтов определяется третьим параметром - count.
После выполнения операции функция возвращает количество действительно записанных или прочитанных данных или -1 при ошибке. Будьте внимательны, если вы записываете или читаете больше 32К байтов - вы можете получить признак ошибки, хотя передача данных выполнилась правильно. Большие массивы данных можно записывать по частям.
В качестве примера мы приведем программу копирования файлов, которая пользуется описанными выше функциями ввода/вывода:
#include <io.h> #include <conio.h> #include <stdio.h> #include <fcntl.h> #include <sys\types.h> #include <sys\stat.h> #include <malloc.h> #include <errno.h>
void main(int, char *[]); void main(int argc, char *argv[]) {
int source, taget, i; char *buffer; unsigned count;
if(argc == 3) {
// Открываем исходный копируемый файл
if((source = open(argv[1], O_BINARY | O_RDONLY)) == - 1) {
printf("\nОшибка при открытии исходного файла: %d", errno); exit(-1);
}
// Открываем выходной файл. При необходимости создаем // новый. Если файл уже существует, выводим на экран // запрос на перезапись содержимого существующего файла
taget = open(argv[2], O_BINARY | O_WRONLY | O_CREAT | O_EXCL, S_IREAD | S_IWRITE); if(errno == EEXIST) {
printf("\nФайл существует. Перезаписать? (Y,N)\n");
// Ожидаем ответ оператора и анализируем его
i = getch(); if((i == 'y') (i == 'Y')) taget = open(argv[2], O_BINARY | O_WRONLY | O_CREAT | O_TRUNC, S_IREAD | S_IWRITE);
}
// Если выходной файл открыть невозможно, выводим // сообщение об ошибке и завершаем работу программы
if(taget == -1){ printf("\nОшибка при открытии выходного файла: %d", errno); exit(-1); }
// Будем читать и писать за один раз 10000 байтов
count = 10000;
// Заказываем буфер для передачи данных
if((buffer = (char *)malloc(count)) == NULL) { printf("\nНедостаточно оперативной памяти"); exit(-1); }
// Копируем исходный файл
while(!eof(source)) {
// Читаем count байтов в буфер buffer
if((count = read(source, buffer, count)) == -1) { printf("\nОшибка при чтении: %d", errno); exit(-1); }
// Выполняем запись count байтов из буфера в выходной файл
if((count = write(taget, buffer, count)) == - 1) { printf("\nОшибка при записи: %d", errno); exit(-1); } }
// Закрываем входной и выходной файлы
close(source); close(taget);
// Освобождаем память, заказанную под буфер
free(buffer); }
// Если при запуске программы не были указаны // пути для входного или выходного файла, // выводим сообщение об ошибке
else printf("\n" "Задайте пути для исходного" " и результирующего файлов!\n"); }
В приведенной программе для определения конца исходного файла использована функция eof():
int eof(int handle);
Для файла с файловым индексом handle эта функция возвращает одно из трех значений:
1 | достигнут конец файла; |
0 | конец файла не достигнут; |
-1 | ошибка, например, неправильно указан handle. |
Дисководы и контроллеры
Первые персональные компьютеры фирмы IBM - IBM PC не имели жесткого диска ("винчестера", или, по отечественной терминологии, накопителя на жестком магнитном диске - НМД). Они были оборудованы двумя флоппи-дисками (накопителями на гибком магнитном диске - НГМД), которые и представляли собой дисковую подсистему. Отечественная персональная профессиональная ЭВМ (ППЭВМ) ЕС-1840 и некоторые модели ЕС-1841 также не имеют НМД. В таких компьютерах установлены, как правило, два дисковода для флоппи-дисков (дискет). Эти дисководы подключены к контроллеру - специальному устройству, находящемуся в корпусе персонального компьютера и выполняющему функции управления дисководами. Контроллер обычно выполнен в виде платы и вставлен в разъем общей шины, который находится на материнской плате (Motherboard) компьютера:
В оригинальном компьютере IBMPC и в отечественных ЕС1840/1841 используются флоппи-диски диаметром 5 дюймов.
Компьютер IBM XT и его ближайший отечественный аналог ЕС1841 может иметь один или два НГМД для дискет диаметром 5 дюймов и, как правило, один НМД емкостью 20 мегабайтов. Все дисководы подключаются к одному общему контроллеру, как это показано на рисунке:
Машины IBM AT и машины более высокого класса могут содержать несколько дисковых контроллеров, два флоппи-диска с различным диаметром (3 и 5 дюймов) и несколько жестких дисков. Впрочем, иногда обходятся одним флоппи-диском диаметром 5 или 3 дюйма. Если вы решили установить несколько дисковых контроллеров, необходимо позаботиться о том, чтобы эти контроллеры имели различные адреса на шине ввода/вывода компьютера. Это достигается правильной установкой соответствующих перемычек или переключателей, находящихся на плате контроллера, о чем подробно рассказано в документации на контроллер.
На рисунке показано, как могут быть подключены к машине AT несколько различных дисководов:
Покупая новый дисковод или контроллер, необходимо помнить о совместимости. Существует много типов контроллеров и дисководов, отличающихся используемым интерфейсом, способами записи информации и другими характеристиками. Эта книга не содержит конкретных рекомендаций по подбору дисководов и контроллеров, однако мы приведем несколько простых советов, которые помогут вам избежать некоторых неприятностей.
Покупайте дисковод вместе с тем контроллером, который для него предназначен. В этом случае совместимость дисковода и контроллера гарантируется.
Если вы покупаете дисковод для замены испорченного, выбирайте тот же тип и ту же фирму-изготовитель. Не надейтесь, что если дисковод имеет такую же емкость в мегабайтах, что и использовавшийся раньше, то он сможет работать в вашей системе.
Если дисковод должен работать в сети на сервере, убедитесь в том, что сетевая операционная система поддерживает данный тип дисковода и контроллера. Необходимая информация по этому вопросу находится в документации по сетевой операционной системе.
Не всегда удается заменить пятидюймовый НГМД на трехдюймовый. Убедитесь в том, что ваш контроллер может работать с трехдюймовым НГМД. Например, контроллер ППЭВМ ЕС-1841 не работает с такими дисководами.
Проверьте, достаточна ли мощность блока питания, встроенного в компьютер, для подключения новых дисководов, при необходимости используйте блок расширения.
Относительно недавно появились диски, расположенные непосредственно на плате контроллера, или, другими словами, совмещенные с контроллером (Hardcard). Эти диски имеют емкость несколько десятков мегабайтов, и их можно установить несколько штук.
Другая разновидность жестких дисков - сменные диски (Cartridge). В этих устройствах используется сменный магнитный носитель, иногда герметизированный и со встроенными элементами механического привода и встроенными магнитными головками. Емкость таких дисков составляет несколько десятков мегабайтов. Для сменных дисков используются специальные контроллеры и специальное программное обеспечение.
Пожалуй, самая интересная разновидность современных дисковых накопителей - оптические, или лазерные.
В настоящее время существует три типа оптических дисковых накопителей - CD ROM, WORM и стираемые диски.
Диски CD ROM (Compact-Disk, Read-Only Memory) - это диски, которые по своему формату и технологии записи информации напоминают компакт-диски для записи звука. Они имеют диаметр 120 миллиметров и могут содержать порядка 600 мегабайтов информации. Эта информация записывается один раз и впоследствии может только читаться, как из постоянного запоминающего устройства.
WORM-диски (Write Once, Read Many) предназначены для однократной записи и многократного считывания данных. Эти диски наилучшим образом подходят для архивного хранения информации, например, содержимого обширных баз данных.
Стираемые диски могут многократно использоваться для записи и чтения информации. Это самые дорогостоящие дисковые накопители.
Основной недостаток лазерных накопителей - относительно невысокое быстродействие по сравнению с традиционными накопителями на жестких дисках. Однако этот недостаток постепенно преодолевается и, по-видимому, оптические накопители скоро получат широкое распространение, особенно для работы с большими архивами и базами данных.
Для работы с оптическими накопителями используются специальные контроллеры и специальное программное обеспечение.
Другие функции для работы с файлами
В задачу данной книги не входит описание всех функций стандартных библиотек трансляторов Microsoft QC 2.5 и C 6.0, предназначенных для работы с дисками и файловой системой. Но мы приведем еще несколько интересных и полезных на наш взгляд функций.
Как мы уже отметили, программа может использовать два режима ввода/вывода для файлов - текстовый и двоичный. Для переключения этого режима для открытого файла можно использовать функцию setmode():
int setmode(int handle, int mode);
Первый параметр - файловый индекс. Второй параметр может принимать два значения:
O_TEXT | установить текстовый режим; |
O_BINARY | установить двоичный режим. |
Функция setmode() должна вызываться перед началом ввода/вывода в открытый файл.
Мы рассказывали о позиционировании внутри файла. Если вам нужно просто установить указатель позиции на начало файла, открытого для потокового ввода/вывода, вы можете воспользоваться функцией rewind():
void rewind(FILE *stream);
Если вам нужно переназначить ввод/вывод для стандартных устройств (потоков stdin, stdout, stderr), вы можете использовать функцию freopen():
FILE *freopen(char *filename, char *mode, FILE *stream);
Функция freopen() закрывает файл, с которым был связан поток stream, и переназначает этот поток файлу, определенному параметром filename. Параметр mode задается так же, как и для функции
fopen().
Можно переназначить файловый индекс для файла, открытого функцией open(). Для этого можно воспользоваться одной из двух функций - dup()
или dup2():
int dup(int handle);
int dup2(int handle1, int handle2);
Первая функция связывает с открытым файлом еще один файловый индекс. Этот индекс она возвращает при успешном завершении. В случае ошибки она возвращает значение -1.
Новый файловый индекс может быть использован для любых операций над файлом.
Функция dup2() переназначает файловый индекс handle2, связывая его с тем же файлом, которому соответствует файловый индекс handle1. Если во время вызова функции dup2() с файловым индексом handle2
связан какой-либо открытый файл, этот файл закрывается. В случае успешного завершения функция dup2() возвращает нулевое значение. Если произошла ошибка, возвращается значение -1.
Файловая система DOS
3.1.
3.2.
3.3.
3.4.
3.5.
3.6.
3.7.
3.8.
3.9.
3.10.
Теперь, после того, как мы познакомились с логической структурой диска в MS-DOS, можно приступить к изучению одной из самых развитых подсистем операционной системы - файловой системы.
Сервис файловой системы доступен программе через прерывание MS-DOS INT21h. Многочисленные функции этого прерывания, относящиеся к файловой системе, можно разбить на группы:
получение справочной информации;
работа с каталогами;
работа с файлами.
Функции первой группы позволяют программе получить разностороннюю информацию, касающуюся текущего состояния дисковой подсистемы - текущие используемые диск и каталог, размер свободного места на диске, параметры логического диска и т.д.
Функции второй группы выполняют все необходимые операции с каталогами - создание, переименование, уничтожение каталогов, изменение текущего каталога и т.д.
Третья группа функций позволяет программе выполнять практически любые операции над файлами - создание, удаление, чтение/запись, переименование, копирование, пересылка и т.п.
Заметим, что существует два класса функций для работы с файлами. Первый класс использует управляющие блоки файлов FCB. Эти функции исплоьзовались в MS-DOS версий 1.х и имеют в настоящее время чисто исторический интерес. Вам они скорее всего никогда не будут нужны, за исключением одного случая - если вам надо составить программу, способную работать под управлением MS-DOS версии 1.0 или 1.1. В этой книге мы не будем упоминать функции, предназначенные для работы с файлами через FCB. При необходимости вы сможете найти информацию об этих функциях в руководстве по операционной системе MS-DOS.
Второй класс использует файловые индексы (handle). Этот класс функций впервые появился в MS-DOS версии 2.0. Эти функции аналогичны используемым в операционной системе UNIX.
Смысл файлового индекса очень прост. Для того чтобы начать работу с файлом, программа должна вызывать определенную функцию DOS, "открывающую" этот файл. Процесс открытия файла заключается в присвоении этому файлу определенного числа (индекса) и выполнении некоторых других инициализирующих действий. Для выполнения каких-либо операций с файлом программа, вызывая соответствующую функцию MS-DOS, должна указать индекс этого файла.
Первые пять файловых индексов зарезервированы операционной системой:
0 | Стандартное устройство ввода (клавиатура) |
1 | Стандартное устройство вывода (экран) |
2 | Стандартное устройство для вывода сообщений об ошибках (экран) |
3 | Стандартное устройство последовательного ввода/вывода, обычно это асинхронный адаптер COM1. |
4 | Стандартное печатающее устройство (обычно первый принтерный порт LPT1) |
Одно из преимуществ второго класса файловых функций - возможность одновременной работы с файлами, находящимися в разных каталогах.
Состав функций MS-DOS, предназначенных для работы с файловой системой, достаточно разнообразен и функционально полон. Только в очень редких случаях, связанных в основном с организацией защиты информации от несанкционированного доступа, вам может потребоваться доступ к диску на более низком уровне. Если ваша программа использует для работы с файлами только документированные функции операционной системы, ее работа не будет зависеть от аппаратных средств компьютера, от используемой для создания разделов диска утилиты.
Описание функций MS-DOS, предназначенных для работы с файловой системой, мы начнем с функций получения справочной информации.
Файлы и каталоги
Вы, конечно, знаете, что файловая система DOS имеет древовидную структуру. В корневом каталоге располагаются 32-байтовые элементы, которые содержат информацию о файлах и других каталогах. Для чтения корневого каталога необходимо определить его расположение и размер.
Корневой каталог находится сразу за последней копией FAT. Количество секторов, занимаемых одной копией FAT, находится в блоке параметров BIOS в BOOT-секторе в поле fatsize, количество копий FAT - в поле fatcnt блока BPB. Следовательно, перед корневым каталогом находится один BOOT-сектор и (fatcnt_*_fatsize)
секторов таблицы размещения файлов FAT.
Размер корневого каталога можно определить исходя из значения поля rootsize. В этом поле при форматировании диска записывается максимальное количество файлов и каталогов, которые могут находиться в корневом каталоге. Для каждого элемента в каталоге отводится 32 байта, поэтому корневой каталог имеет длину (32_*_rootsize) байтов.
Корневой каталог занимает непрерывную область фиксированного размера. Размер корневого каталога задается при форматировании и определяет максимальное количество файлов и каталогов, которые могут быть описаны в корневом каталоге. Для определения количества секторов, занимаемых корневым каталогом, можно воспользоваться следующей формулой:
RootSecs = sectsize_/_(32_*_rootsize)
В этой формуле sectsize - размер сектора в байтах, он может быть получен из соответствующего поля BOOT-сектора.
После корневого каталога на логическом диске находится область файлов и подкаталогов корневого каталога. На рисунке изображены все области логического диска. Такую структуру имеют логические диски, расположенные в разделах жестких дисков, а также дискеты.
Области логического диска Номер начального сектора на логическом диске
++ ¦ ¦ 0 ¦ BOOT-сектор и ¦ ¦ зарезервированные ¦ ¦ сектора ¦ ¦ ¦ +¦ ¦ ¦ ressecs - количество резервных ¦ Первая копия FAT ¦ секторов ¦ ¦ +¦ ¦ ¦ ressecs+fatsize ¦ Вторая копия FAT ¦ ¦ ¦ +¦ ¦ ¦ ressecs+(fatsize*fatcnt) ¦ Корневой каталог ¦ ¦ ¦ +¦ ¦ ¦ ressecs+(fatsize*fatcnt)+ ¦ Область данных ¦ sectsize_/_(32*rootsize) ¦ ¦ ++
Область данных разбита на кластеры, причем нумерация кластеров начинается с числа 2. Кластеру с номером 2 соответствуют первые сектора области данных. Теперь мы можем привести формулу, которая позволит нам связать номер кластера с номерами секторов, занимаемых им на логическом диске:
SectNu = DataStart + ((ClustNu-2) * clustsize)
В этой формуле:
SectNu - номер первого сектора, распределенного кластеру с номером ClustNu;
DataStart = ressecs+(fatsize*fatcnt)+(sectsize/(32*rootsize));
ClustNu - номер кластера, для которого необходимо определить номер первого сектора;
clustsize - количество секторов, занимаемых кластером, находится в блоке параметров BIOS.
Этой формулой мы воспользуемся для чтения корневого каталога.
Как мы уже говорили, любой каталог содержит 32-байтовые элементы - дескрипторы, описывающие файлы и другие каталоги. Приведем формат дескриптора:
Смещение | Размер | Содержимое |
(+0) | 8 | Имя файла или каталога, выравненное на левую границу и дополненное пробелами. |
(+8) | 3 | Расширение имени файла, выравненное на левую границу и дополненное пробелами. |
(+11) | 1 | Атрибуты файла. |
(+12) | 10 | Зарезервировано. |
(+22) | 2 | Время создания файла или время его последней модификации. |
(+24) | 2 | Дата создания файла или дата его последней модификации. |
(+26) | 2 | Номер первого кластера, распределенного файлу. |
(+28) | 4 | Размер файла в байтах. |
0 | Файл предназначен только для чтения, в этот файл нельзя писать и его нельзя стирать. |
1 | Скрытый файл, этот файл не будет появляться в списке файлов, создаваемом командой операционной системы DIR. |
2 | Системный файл. Этот бит обычно установлен в файлах, являющихся составной частью операционной системы. |
3 | Данный дескриптор описывает метку диска. Для этого дескриптора поля имени файла и расширения имени файла должны рассматриваться как одно поле длиной 11 байтов. Это поле содержит метку диска. |
4 | Дескриптор описывает файл, являющийся подкаталогом данного каталога. |
5 | Флаг архивации. Если этот бит установлен в 1, то это означает, что данный файл не был выгружен утилитой архивации (например, программой BACKUP). |
6-7 | Зарезервированы. |
Обычно файлы имеют следующие комбинации битов в байте атрибутов:
0 | Обычные файлы (тексты программ, загрузочные модули, пакетные файлы). |
7 | Только читаемые, скрытые, системные файлы. Такая комбинация битов байта атрибутов используется для файлов операционной системы IO.SYS, MSDOS.SYS. |
8 | Метка тома. Дескриптор метки тома может находиться только в корневом каталоге логического диска. |
10h | Дескриптор, описывающий каталог. |
20h | Обычный файл, который не был выгружен утилитами BACKUP или XCOPY. |
". "
Этот дескриптор указывает на содержащий его каталог. Т.е. каталог имеет ссылку сам на себя.
Второй специальный дескриптор содержит в поле имени строку:
".. "
Этот дескриптор указывает на каталог более высокого уровня.
Если в поле номера первого занимаемого кластера дескриптора с именем ".. " находится нулевое значение, это означает, что данный каталог содержится в корневом каталоге.
Таким образом, в древовидной структуре каталогов имеются ссылки как в прямом, так и в обратном направлении. Эти ссылки можно использовать для проверки сохранности структуры каталогов файловой системы.
При удалении файла первый байт его имени заменяется на байт E5h (символ 'х'). Все кластеры, распределенные файлу, отмечаются в FAT
как свободные. Если вы только что удалили файл, его еще можно восстановить, так как в дескрипторе сохранились все поля, кроме первого байта имени файла. Но если на диск записать новые файлы, то содержимое кластеров удаленного файла будет изменено и восстановление станет невозможным.
Остановимся подробнее на полях времени и даты создания или последней модификации файла. DOS обновляет содержимое этих полей после любой операции, изменяющей содержимое файла - создания файла, перезаписи содержимого файла, добавления данных в файл или обновления содержимого файла. После обновления файла DOS устанавливает бит архивации 5 байта атрибутов в 1.
Формат поля времени показан на рисунке:
15 11 10 5 4 0 ++ ¦ Часы (0...23) ¦ Минуты (0...59) ¦ Секунды/2 (0...29) ¦ ++
Старшие пять битов содержат значение часа модификации файла, шесть битов с номерами 5-10 содержат значение минут модификации файла, и, наконец, в младших 5 битах хранится значение секунд, деленное на 2. Для того, чтобы время обновления файла уместилось в шестнадцати битах, пришлось пойти на снижение точности времени до двух секунд.
Формат даты обновления файла напоминает формат времени:
15 9 8 5 4 0 ++ ¦ Год (0...119) ¦ Месяц (1...12) ¦ День (1...31) ¦ ++
Для того, чтобы получить значение года обновления файла, необходимо прибавить к величине, хранимой в старших семи битах, значение 1980. Поля месяца и дня каких-либо особенностей не имеют, они полностью соответствуют календарной дате.
Поле длины в дескрипторе содержит точную длину файла в байтах. Для каталогов в поле длины записано нулевое значение. Вы не можете работать с каталогом, как с обычным файлом средствами DOS. Единственный способ прочитать каталог как файл - использовать FAT для определения цепочки занимаемых каталогом кластеров и прочитать сектора, соответствующие этим кластерам при помощи прерывания DOS INT 25h.
Для удобства работы с каталогами файл sysp.h
содержит следующие определения типов:
#pragma pack(1)
/* Время последнего обновления файла */
typedef struct _FTIME_ { unsigned sec : 5, min : 6, hour : 5; } FTIME;
/* Дата последнего обновления файла */
typedef struct _FDATE_ { unsigned day : 5, month : 4, year : 7; } FDATE;
/* Дескриптор файла в каталоге */
typedef struct _FITEM_ { char name[8]; char ext[3]; char attr; char reserved[10]; FTIME time; FDATE date; unsigned cluster_nu; unsigned long size; } FITEM; #pragma pack()
Приведем исходный текст программы, которая читает BOOT-сектор выбранного диска, определяет формат FAT, вычисляет размер и расположение FAT и корневого каталога. Затем программа читает FAT и корневой каталог в динамически получаемые буфера и выводит на экран содержимое корневого каталога.
#include <stdio.h> #include <malloc.h> #include <dos.h> #include "sysp.h"
void main(void); void main(void) {
BOOT _far *boot_rec; int i,j, k, status, fat_sectors; long total_sectors; int fat, root_begin, root_sectors; char drive; unsigned _far *fat_buffer; FITEM _far *root_buffer, _far *rptr;
union REGS reg; struct SREGS segreg;
printf("\n" "\nЧтение корневого каталога логического диска" "\n (C)Фролов А., 1991" "\n");
// Заказываем буфер для чтения BOOT-записи. // Адрес буфера присваиваем FAR-указателю.
boot_rec = _fmalloc(sizeof(*boot_rec));
// Запрашиваем диск, для которого необходимо // выполнить чтение загрузочной записи.
printf("\n" "\nВведите обозначение диска, для просмотра" "\nкорневого каталога (A, B, ...):");
drive = getche();
// Вычисляем номер дисковода
drive = toupper(drive) - 'A';
// Читаем загрузочную запись в буфер
status = getboot((BOOT _far*)boot_rec, drive);
// Если произошла ошибка (например, неправильно указано // обозначение диска), завершаем работу программы
if(status) { printf("\nОшибка при чтении BOOT-сектора"); exit(-1); }
// Определяем формат таблицы FAT
total_sectors = boot_rec->bpb.totsecs;
// Если мы работаем с расширенным разделом диска, // общее количество секторов на диска берем из // расширенного PBP
if(total_sectors == 0) total_sectors = boot_rec->bpb.drvsecs;
// Формат FAT определяем исходя из общего // количества секторов на логическом диске
if(total_sectors > 20791) { printf("\nFAT имеет 16-битовый формат"); fat=16; } else { printf("\nFAT имеет 12-битовый формат"); fat=12; }
// Определяем количество секторов, занимаемых FAT
fat_sectors = boot_rec->bpb.fatsize;
// Заказываем буфер для FAT
fat_buffer = _fmalloc(fat_sectors * boot_rec->bpb.sectsize);
// Вычисляем номер первого сектора FAT
j = boot_rec->bpb.ressecs;
// Читаем FAT в буфер fat_buffer
// Заполняем регистровые структуры для вызова // прерывания DOS INT 25h
reg.x.ax = drive; reg.x.bx = FP_OFF(fat_buffer); segreg.ds = FP_SEG(fat_buffer); reg.x.cx = fat_sectors; reg.x.dx = j; int86x(0x25, ®, ®, &segreg);
// Извлекаем из стека оставшееся там после // вызова прерывания слово
_asm pop ax
// Вычисляем номер первого сектора корневого каталога
root_begin = j + fat_sectors * boot_rec->bpb.fatcnt;
// Вычисляем длину корневого каталога
root_sectors = (boot_rec->bpb.rootsize * 32) / boot_rec->bpb.sectsize;
// Заказываем буфер для корневого каталога
root_buffer = _fmalloc(root_sectors * boot_rec->bpb.sectsize);
// Читаем корневой каталог в буфер root_buffer
reg.x.ax = drive; reg.x.bx = FP_OFF(root_buffer); segreg.ds = FP_SEG(root_buffer); reg.x.cx = root_sectors; reg.x.dx = root_begin; int86x(0x25, ®, ®, &segreg); _asm pop ax
// Показываем содержимое корневого каталога
printf("\n" "\nИмя файла Аттр. Дата Время Кластер Размер" "\n------------ ----- ---------- -------- ------- ------");
for(rptr = root_buffer;;rptr++) { printf("\n");
// Признак конца каталога - нулевой байт в начале // имени файла
if(rptr->name[0] == 0) break;
// Выводим содержимое дескриптора файла
for(i=0; i<8; i++) printf("%c",rptr->name[i]); printf("."); for(i=0; i<3; i++) printf("%c",rptr->ext[i]); printf(" %02X %02d-%02d-%02d %02d:%02d:%02d ", rptr->attr, rptr->date.day, rptr->date.month, rptr->date.year + 1980, rptr->time.hour, rptr->time.min, rptr->time.sec * 2); printf(" %-5d %lu", rptr->cluster_nu, rptr->size);
}
// Освобождаем буфера
_ffree(root_buffer); _ffree(boot_rec); _ffree(fat_buffer); }
Запустив программу два раза для диска С: и RAM-диска G: мы получили на экране следующую картину:
Чтение корневого каталога логического диска (C)Фролов А., 1991
Введите обозначение диска, для просмотра корневого каталога (A, B, ...):c FAT имеет 12-битовый формат
Имя файла Аттр. Дата Время Кластер Размер ------------ ----- ---------- -------- ------- ------ IO .SYS 07 26-11-1988 00:55:26 2 33337 MSDOS .SYS 07 26-11-1988 00:56:18 11 37376 DOS . 10 22-09-1990 00:50:34 21 0 ARC . 10 22-09-1990 00:58:08 22 0 SYSPRG . 10 03-10-1990 12:09:10 23 0 COMMAND .COM 20 30-11-1988 00:00:04 25 37557 SSTOR .SYS 20 03-04-1989 12:00:00 354 17884 DUMM1004.COM 20 17-06-1990 12:59:24 35 1004 AUTOEXEC.B21 20 20-12-1990 11:21:02 359 677 AUTOEXEC.C60 20 28-07-1990 09:17:26 360 241 хYSLOG . 20 15-01-1991 19:42:48 441 510 SD .INI 20 08-10-1990 10:05:52 362 2497 NULLFILE. 20 17-02-1991 13:59:28 0 0 CLSCREEN.SYS 20 06-06-1990 20:58:36 363 157 AUTOEXEC.BAT 20 18-10-1990 16:14:14 364 677 FRECOVER.IDX 27 08-10-1990 10:07:16 504 29 FRECOVER.DAT 21 08-10-1990 10:07:16 467 18432 CONFIG .SYS 20 02-02-1991 21:19:34 332 390 х . 20 04-02-1991 21:34:34 361 254
Чтение корневого каталога логического диска (C)Фролов А., 1991
Введите обозначение диска, для просмотра корневого каталога (A, B, ...):g FAT имеет 12-битовый формат
Имя файла Аттр. Дата Время Кластер Размер ------------ ----- ---------- -------- ------- ------ MS-RAMDR.IVE 08 03-01-1990 00:00:00 0 0 TEMP . 10 17-02-1991 13:59:22 2 0 INCLUDE . 10 17-02-1991 13:59:26 3 0 BOOK3 .DOC 20 17-02-1991 15:27:38 18 181248
Заметьте, что приведенная выше программа предоставляет вам параметр, который невозможно получить с помощью команды операционной системы DIR
- номер первого кластера, распределенного файлу. Операционная система MS-DOS не дает программам иной возможности определить номер первого кластера файла, чем чтение каталога по секторам.
Для исследования подкаталогов корневого каталога и для демонстрации основных приемов работы с таблицей размещения файлов FAT
предназначена следующая программа. Вы можете использовать ее для исследования структуры каталогов диска.
#include <stdio.h> #include <malloc.h> #include <dos.h> #include "sysp.h"
void main(void); void main(void) {
BOOT _far *boot_rec; int i,j, k, status, fat_sectors; long total_sectors; int ffat, root_begin, root_sectors; char drive; unsigned _far *fat_buffer; FITEM _far *root_buffer, _far *rptr; char cbuf[128]; char _far *clust_buffer; int cur_clust;
union REGS reg; struct SREGS segreg;
printf("\n" "\nЧтение каталогов логического диска" "\n (C)Фролов А., 1991" "\n");
// Заказываем буфер для чтения BOOT-записи. // Адрес буфера присваиваем FAR-указателю.
boot_rec = _fmalloc(sizeof(*boot_rec));
// Запрашиваем диск, для которого необходимо // выполнить чтение загрузочной записи.
printf("\n" "\nВведите обозначение диска (A, B, ...):");
drive = getche();
// Вычисляем номер дисковода
drive = toupper(drive) - 'A';
// Читаем загрузочную запись в буфер
status = getboot((BOOT _far*)boot_rec, drive);
// Если произошла ошибка (например, неправильно указано // обозначение диска), завершааем работу программы
if(status) { printf("\nОшибка при чтении BOOT-сектора"); exit(-1); }
// Определяем формат таблицы FAT
total_sectors = boot_rec->bpb.totsecs;
// Если мы работаем с расширенным разделом диска, // общее количество секторов на диска берем из // расширенного PBP
if(total_sectors == 0) total_sectors = boot_rec->bpb.drvsecs;
// Формат FAT определяем исходя из общего // количества секторов на логическом диске
if(total_sectors > 20791) { printf("\nFAT имеет 16-битовый формат"); ffat=16; } else { printf("\nFAT имеет 12-битовый формат"); ffat=12; }
// Определяем количество секторов, занимаемых FAT
fat_sectors = boot_rec->bpb.fatsize;
// Заказываем буфер для FAT
fat_buffer = _fmalloc(fat_sectors * boot_rec->bpb.sectsize);
// Вычисляем номер первого сектора FAT
j = boot_rec->bpb.ressecs;
// Читаем FAT в буфер fat_buffer
// Заполняем регистровые структуры для вызова // прерывания DOS INT 25h
reg.x.ax = drive; reg.x.bx = FP_OFF(fat_buffer); segreg.ds = FP_SEG(fat_buffer); reg.x.cx = fat_sectors; reg.x.dx = j; int86x(0x25, ®, ®, &segreg);
// Извлекаем из стека оставшееся там после // вызова прерывания слово
_asm pop ax
// Вычисляем номер первого сектора корневого каталога
root_begin = j + fat_sectors * boot_rec->bpb.fatcnt;
// Вычисляем длину корневого каталога
root_sectors = (boot_rec->bpb.rootsize * 32) / boot_rec->bpb.sectsize;
// Заказываем буфер для корневого каталога
root_buffer = _fmalloc(root_sectors * boot_rec->bpb.sectsize);
// Читаем корневой каталог в буфер root_buffer
reg.x.ax = drive; reg.x.bx = FP_OFF(root_buffer); segreg.ds = FP_SEG(root_buffer); reg.x.cx = root_sectors; reg.x.dx = root_begin; int86x(0x25, ®, ®, &segreg); _asm pop ax
// Показываем содержимое корневого каталога
printf("\n" "\nИмя файла Аттр. Дата Время Кластер Размер" "\n------------ ----- ---------- -------- ------- ------");
for(rptr = root_buffer;;rptr++) { printf("\n");
// Признак конца каталога - нулевой байт в начале // имени файла
if(rptr->name[0] == 0) break;
// Выводим содержимое дескриптора файла
for(i=0; i<8; i++) printf("%c",rptr->name[i]); printf("."); for(i=0; i<3; i++) printf("%c",rptr->ext[i]); printf(" %02X %02d-%02d-%02d %02d:%02d:%02d ", rptr->attr, rptr->date.day, rptr->date.month, rptr->date.year + 1980, rptr->time.hour, rptr->time.min, rptr->time.sec * 2); printf(" %-5d %lu", rptr->cluster_nu, rptr->size);
}
// Получаем буфер для чтения кластеров каталога
clust_buffer = _fmalloc(boot_rec->bpb.clustsize * boot_rec->bpb.sectsize);
printf("\nНомер первого кластера каталога:"); gets(cbuf); cur_clust = atoi(cbuf);
// Переменная k используется в качестве флага. // При первом просмотре каталога ее значение равно 0, // затем эта переменная устанавливается в 1.
k=0;
for(;;) {
// Сохраняем номер кластера каталога
j=cur_clust;
// Вычисляем номер следующего кластера, распределенного // каталогу
cur_clust = fat(fat_buffer, ffat, cur_clust); printf("%d ",cur_clust);
// Читаем кластер в буфер clust_buffer
reg.x.ax = drive; reg.x.bx = FP_OFF(clust_buffer); segreg.ds = FP_SEG(clust_buffer); reg.x.cx = boot_rec->bpb.clustsize; reg.x.dx = root_begin + root_sectors + ((j-2)*boot_rec->bpb.clustsize); int86x(0x25, ®, ®, &segreg); _asm pop ax
// Показываем содержимое каталога
rptr = (FITEM _far *)clust_buffer;
// Первый дескриптор в каталоге указывает на // этот же каталог. В поле имени первого дескриптора // находится строка ". ". Этот факт можно использовать // для проверки каталога. Если вы по ошибке указали // номер кластера, не принадлежащего каталогу, // программа завершит работу с сообщением об ошибке.
if(k == 0) { k=1; if(strncmp(rptr->name,". ",8) != 0) { printf("\nЭто не каталог !"); exit(-1); } }
printf("\n" "\nИмя файла Аттр. Дата Время Кластер Размер" "\n------------ ----- ---------- -------- ------- ------");
for(;;rptr++) { printf("\n");
// Признак конца каталога - нулевой байт в начале // имени файла
if(rptr->name[0] == 0) break;
// Выводим содержимое дескриптора файла
for(i=0; i<8; i++) printf("%c",rptr->name[i]); printf("."); for(i=0; i<3; i++) printf("%c",rptr->ext[i]); printf(" %02X %02d-%02d-%02d %02d:%02d:%02d ", rptr->attr, rptr->date.day, rptr->date.month, rptr->date.year + 1980, rptr->time.hour, rptr->time.min, rptr->time.sec * 2); printf(" %-5d %lu", rptr->cluster_nu, rptr->size);
}
// Если этот кластер - последний из распределенных каталогу, // завершаем работу программы
if((cur_clust == 0xfff) (cur_clust == 0xffff)) break;
}
// Освобождаем буфера
_ffree(root_buffer); _ffree(boot_rec); _ffree(fat_buffer); _ffree(clust_buffer); }
Эта программа обращается к таблице размещения файлов при помощи функции fat():
/** *.Name fat * *.Title Выбрать элемент из FAT * *.Descr Функция выбирает элемент с заданным номером из таблицы * размещения файлов FAT. Формат FAT передается * функции как параметр. * *.Params int fat(b_fat, t_fat, idx); * * char _far *b_fat - буфер, содержащий FAT * * int t_fat - формат FAT, может быть * равен 12 или 16 * * int idx - номер элемента FAT, который * должен быть выбран * *.Return Содержимое ячейки FAT с указанным номером **/
#include <stdio.h> #include <stdlib.h> #include "sysp.h"
int fat(char _far *b_fat, int t_fat, int idx) {
div_t clust_nu ; int cluster;
if(t_fat == 12) {
/* FAT - 12 */
clust_nu = div(idx * 3, 2);
if( clust_nu.rem != 0 )
cluster = (*((int*)(b_fat + clust_nu.quot)) >> 4) & 0xfff;
else
cluster = *((int*)(b_fat + clust_nu.quot)) & 0xfff; }
else if(t_fat == 16) {
/* FAT - 16 */
cluster = *((int*)(b_fat + idx * 2)); }
else { printf("*FAT()* FAT format error\n"); exit(-100); }
return(cluster); }
В качестве примера приведем результат работы программы для диска E:
Чтение каталогов логического диска (C)Фролов А., 1991
Введите обозначение диска (A, B, ...):e FAT имеет 12-битовый формат
Имя файла Аттр. Дата Время Кластер Размер ------------ ----- ---------- -------- ------- ------ C600 . 10 22-09-1990 01:22:14 2 0 SOLO . 10 22-09-1990 11:15:42 6 0 QC25 . 10 07-10-1990 22:53:48 7 0 SYSPRG . 10 03-10-1990 09:19:08 12 0 WORD . 10 02-02-1991 14:02:14 15 0 SD .INI 20 17-02-1991 15:36:52 799 2497 FRECOVER.IDX 27 17-02-1991 15:42:10 2551 29 FRECOVER.DAT 21 17-02-1991 15:42:10 1958 21504 х . 20 17-02-1991 16:37:30 1973 347
Номер первого кластера каталога:3 4095
Имя файла Аттр. Дата Время Кластер Размер ------------ ----- ---------- -------- ------- ------ . . 10 22-09-1990 01:22:24 3 0 .. . 10 22-09-1990 01:22:24 2 0 UTILS .HLP 20 08-02-1990 00:09:42 800 162023 QH .HLP 20 29-01-1990 19:32:04 840 20763 CV .HLP 20 07-02-1990 21:33:32 846 239863
Обратите внимание на выделенные элементы каталога. Это ссылки соответственно на сам каталог и на каталог более высокого уровня.
Формат блока 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 числа секторов в кластере |
(+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 числа секторов в кластере |
(+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 был построен |
Формат блока DDCB для DOS версии 4.х
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 был построен
Формат блока DDCB для версий 2.х и 3.х
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 был построен
переход типа NEAR на программу
Смещение Размер Содержимое (+0) 3 Команда JMP xxxx - переход типа NEAR на программу начальной загрузки (+3) 8 Название фирмы-производителя операционной системы и версия, например: "IBM 4.0" (+11) 25 Extended BPB - расширенный блок параметров BIOS (+36) 1 Физический номер дисковода (0 -флоппи, 80h - жесткий диск) (+37) 1 Зарезервировано (+38) 1 Символ ')' - признак расширенной загрузочной записи DOS 4.0 (+39) 4 Серийный номер диска (Volume Serial Number), создается во время форматирования диска (+43) 11 Метка диска (Volume Label) (+54) 8 Зарезервировано, обычно содержит запись типа 'FAT12 ', которая идентифицирует формат таблицы размещения файлов FAT
res_sect Количество зарезервированных секторов.
(0) 2 sect_siz Количество байтов в одном секторе диска. (+2) 1 clustsiz Количество секторов в одном кластере. (+3) 2 res_sect Количество зарезервированных секторов. (+5) 1 fat_cnt Количество таблиц FAT. (+6) 2 root_siz Максимальное количество дескрипторов файлов, содержащихся в корневом каталоге диска. (+8) 2 tot_sect Общее количество секторов на носителе данных (в разделе DOS). (+10) 1 media Байт-описатель среды носителя данных. (+11) 2 fat_size Количество секторов, занимаемых одной копией FAT.
Формат даты обновления файла
15 9 8 5 4 0 ++ ¦ Год (0...119) ¦ Месяц (1...12) ¦ День (1...31) ¦ ++
Формат дескриптора файла
Смещение Размер Содержимое (+0) 8 Имя файла или каталога, выравненное на левую границу и дополненное пробелами. (+8) 3 Расширение имени файла, выравненное на левую границу и дополненное пробелами. (+11) 1 Атрибуты файла. (+12) 10 Зарезервировано. (+22) 2 Время создания файла или время его последней модификации. (+24) 2 Дата создания файла или дата его последней модификации. (+26) 2 Номер первого кластера, распределенного файлу. (+28) 4 Размер файла в байтах.
Формат элемента массива дисковой информации
(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 | зарезервировано |
Формат первого сектора жесткого диска
Смещение Размер Содержимое (+0) 1BEh Загрузочная запись - программа, которая загружается и выполняется во время начальной загрузки операционной системы (+1BEh) 10H Элемент таблицы разделов диска (+1CEh) 10H Элемент таблицы разделов диска (+1DEh) 10H Элемент таблицы разделов диска (+1EEh) 10H Элемент таблицы разделов диска (+1FEh) 2 Признак таблицы разделов - 55AAh
Формат поля времени
15 11 10 5 4 0 ++ ¦ Часы (0...23) ¦ Минуты (0...59) ¦ Секунды/2 (0...29) ¦ ++
Формат таблицы файлов DFT
для 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 | зарезервировано |
для DOS 4.х:
(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 | зарезервировано |
переход типа NEAR на программу
Смещение Размер Содержимое (+0) 3 Команда JMP xxxx - переход типа NEAR на программу начальной загрузки (+3) 8 Название фирмы-производителя операционной системы и версия, например: "IBM 4.0" (+11) 13 BPB - блок параметров BIOS (+24) 2 Количество секторов на дорожке (+26) 2 Количество головок (поверхностей диска) (+28) 2 Количество скрытых секторов, эти сектора могут использоваться для схемы разбиения физического диска на разделы
Форматирование диска (ESDI НМД)
На входе: | AH = 1Ah |
AL = Количество элементов в таблице дефектов | |
DL = Адрес дисковода (80h, 81h, ...) | |
CL = Режим форматирования | |
ES:BX = Адрес таблицы дефектов | |
На выходе: | AH = Состояние дисковода после завершения последней операции |
CF = 1, если произошла ошибка, 0, если ошибки нет |
|
Примечание: | PS/2 |
Эта функция форматирования жесткого диска предназначена для НМД, используемого совместно с контроллером ESDI. Она поддерживает таблицу дефектных дорожек и имеет несколько режимов форматирования в зависимости от содержимого регистра CL при вызове:
Бит 0 | игнорировать первичную таблицу дефектов; |
Бит 1 | игнорировать вторичную таблицу дефектов; |
Бит 2 | обновить вторичную таблицу дефектов; |
Бит 3 | выполнить анализ поверхности; |
Бит 4 | генерация периодических прерываний; |
Биты 5-7 | зарезервированы, должны быть равны 0. |
Если при форматировании затребована функция генерации периодических прерываний, то после форматирования каждой дорожки вызывается прерывание INT 5h с регистром AH=0Fh. Это прерывание можно использовать для индикации хода процесса либо для завершения процесса форматирования по требованию оператора или программы.
При установке бита 2 регистра CL содержимое вторичной таблицы дефектов обновляется, в нее заносятся результаты тестирования диска. Для углубленного анализа поверхности диска сначала необходимо выполнить форматирование диска с битом 3, сброшенным в 0. Затем следует выполнить анализ поверхности диска, вызвав эту функцию с битом 3, установленным в 1.
Форматирование диска (НМД)
На входе: | AH = 07h |
AL = Фактор чередования (только для XT) | |
CH = Номер дорожки | |
CL = Номер сектора | |
DH = Номер головки | |
DL = Адрес дисковода (0, 1, ..., 80h, 81h, ...) | |
ES:BX = Адрес буфера формата | |
На выходе: | AH = Состояние дисковода после завершения последней операции |
CF = 1, если произошла ошибка, 0, если ошибки нет |
|
Примечание: | PC, XT |
Функция форматирования с кодом 7 предназначена для форматирования целого диска начиная с определенной дорожки. Буфер формата подготавливается аналогично функции 5.
Форматирование дорожки
На входе: | AH = 05h |
AL = Количество секторов, которые нужно создать на дорожке, или Фактор чередования для НМД XT |
|
CH = Номер дорожки | |
CL = Номер сектора | |
DH = Номер головки | |
DL = Адрес дисковода (0, 1, ..., 80h, 81h, ...) | |
ES:BX = Адрес буфера формата, используется для НГМД и НМД машин XT | |
На выходе: | AH = Состояние дисковода после завершения последней операции |
CF = 1, если произошла ошибка, 0, если ошибки нет |
|
Примечание: | PC, XT, AT, PS/2 |
Функция форматирования предназначена для начального формирования структуры дорожки диска, она разрушает все имеющиеся на дорожке данные. С помощью функции 05 вы можете за один раз отформатировать только одну дорожку с указанным номером.
Для этой функции необходимо задать два интересных параметра, на которых мы остановимся подробнее - фактор чередования и буфер формата.
Что такое фактор чередования (Interleave)?
Этот фактор определяет последовательность расположения секторов на дорожке. Сектора могут располагаться в порядке своих номеров, через один, через два и т.д. Способ размещения секторов определяется значением фактора чередования.
Фактор 1 означает последовательное расположение секторов на дорожке в порядке их номеров, т.е. чередование отсутствует. Фактор 2 задает расположение секторов через один, 3 - через два и т.д. На рисунке показано использование фактора чередования при форматировании дорожки:
Все утилиты, предназначенные для подготовки жесткого диска к работе, требуют задания величины фактора чередования при выполнении низкоуровневого форматирования.
Для чего может понадобиться несмежное расположение секторов с последовательными номерами на дорожке диска?
При последовательном расположении секторов может получиться так, что процессор не будет успевать обрабатывать смежные сектора за один проход дорожки. Например, программа считывает последовательно второй и третий сектор. В момент времени, когда второй сектор уже считан, при быстром вращении диска к моменту начала чтения третьего сектора головки могут оказаться в середине третьего сектора и диск совершит еще один оборот, прежде чем головки окажутся в начале третьего сектора. Поэтому если программа последовательно обращается к смежным секторам, может получиться так, что при чтении каждого сектора диск будет совершать один оборот.
Если же сектора будут расположены через один или через два, количество оборотов диска, нужных для обработки последовательности смежных секторов, будет значительно меньше.
Для подбора оптимального фактора чередования можно использовать специальные программы или делать это методом проб и ошибок, задавая каждый раз новое значение фактора и проверяя быстродействие диска.
Займемся теперь буфером формата.
Перед вызовом функции форматирования регистры ES:BX должны содержать полный адрес буфера формата. Для дискет перед форматированием этот буфер должен представлять из себя заполненный массив четырехбайтовых элементов - номера дорожки, головки, сектора и кода размера сектора. Код размера сектора может иметь следующие значения:
0 | 128 байтов на сектор |
1 | 256 байтов на сектор |
2 | 512 байтов на сектор |
3 | 1024 байтов на сектор |
Для жесткого диска буфер формата должен представлять из себя массив размером 512 байтов. В начале этого массива для каждого сектора на дорожке необходимо подготовить двухбайтовые элементы. Первый байт содержит признак - хороший это сектор (00) или плохой (80h). Второй байт - это номер сектора.
Задавая последовательность номеров в буфере формата соответствующим образом, программа определяет фактор чередования.
Приведем пример подготовленного буфера формата для форматирования дорожки на 17 секторов с фактором чередования, равным 2:
db 00h,01h,00h,0ah,00h,02h,00h,0bh,00h,03h,00h,0ch db 00h,04h,00h,0dh,00h,05h,00h,0eh,00h,06h,00h,0fh db 00h,07h,00h,10h,00h,08h,00h,11h,00h,09h
Отметим, что буфер формата используется только для машин AT. Машины XT при форматировании НМД не используют буфер формата, вместо этого значение фактора чередования указывается при вызове функции форматирования в регистре AL.
При форматировании флоппи-дисков с помощью этой функции таблица параметров дискеты должна содержать правильное значение количества секторов на дорожке и другие параметры.
Форматирование дорожки (НМД)
На входе: | AH = 06h |
AL = Фактор чередования | |
CH = Номер дорожки | |
CL = Номер сектора | |
DH = Номер головки | |
DL = Адрес дисковода (0, 1, ..., 80h, 81h, ...) | |
ES:BX = Адрес буфера формата | |
На выходе: | AH = Состояние дисковода после завершения последней операции |
CF = 1, если произошла ошибка, 0, если ошибки нет |
|
Примечание: | PC, XT |
Функция форматирования дорожки с кодом 6 предназначена только для НМД и устанавливает флаг плохого сектора. Буфер формата подготавливается аналогично функции 5.
Форматы запросов для различных команд
0 - Инициализация
(0) 13 | header | Заголовок запроса. |
(+13) 1 | n_units | Количество устройств, обслуживаемых драйвером. Это поле заполняется только блочным драйвером. |
(+14) 4 | end_addr | Конечный FAR-адрес резидентной части кода драйвера. В это поле драйвер записывает адрес байта памяти, следующего за той частью кода драйвера, которая должна стать резидентной. |
(+18) 4 | parm | FAR-адрес строки параметров инициализации драйвера из файла CONFIG.SYS. Эта строка содержит все, что находится в строке файла после команды 'DEVICE=', она заканчивается символами перевода строки и возврата каретки 0Ah, 0Dh. При возврате драйвер блочного устройства должен записать в это поле адрес массива указателей на блоки параметров BIOSBIOS
(BPB), по одному указателю на каждое устройство, обслуживаемое драйвером. |
(+22) 1 | drive | Номер устройства. Для версии DOS 3.0 и более поздних версий в это поле при загрузке драйвера операционная система заносит номер, назначенный устройству, обслуживаемому драйвером. Например, для устройства А:это 0, для B: - 1 и т.д. |
1 - Проверка замены носителя
(0) 13 | header | Заголовок запроса. |
(+13) 1 | media | В этом поле драйверу передается байт-описатель среды носителя данных, с которым DOS работала раньше. |
(+14) 1 | reply | В это поле драйвер должен поместить ответ о факте замены среды: 1 - диск не заменялся; 0 - неизвестно; -1 - диск был заменен. |
(+15) 4 | vol_id | Указатель на предыдущую метку тома (если установлен бит 11 слова атрибута устройства и диск был заменен) |
2 - Построить блок BPB.
(0) 13 | header | Заголовок запроса. |
(+13) 1 | media | В этом поле драйверу передается байт-описатель среды носителя данных, с которым DOS работала раньше. |
(+14) 4 | buf_adr | Адрес буфера обмена. Содержимое этого буфера при вызове драйвера зависит от утановки бита 13 слова атрибутов устройства (IBM-формат). Если этот бит равен 0 (устройство формата IBM), буфер содержит первый сектор первой копии FAT. В противном случае указатель установлен на буфер свободного сектора. |
(+18) 4 | bpb_adr | Указатель на новый BPB, записывается в это поле драйвером. |
3, 4, 8, 9, 0Ch - Чтение/Запись.
(0) 13 | header | Заголовок запроса. |
(+13) 1 | media | В этом поле драйверу передается байт-описатель среды носителя данных. |
(+14) 4 | buf_adr | Адрес буфера для передачи данных. |
(+18) 2 | count | Количество передаваемых байтов для символьных устройств или секторов для блочных устройств. |
(+20) 2 | sector | Номер начального сектора, если драйвер использует 16-битовую адресацию секторов или -1 для 32-битовой адресации. Это поле не используется символьными драйверами. |
(+22) 4 | vol_id | Указатель на метку тома в формате ASCIIZ. Возвращается блочным драйвером, если он выставляет ошибку 15 (неправильная смена диска). Это поле должно содержать ссылку на метку требуемого диска. |
(+26) 4 | sect32 | Номер начального сектора, если содержимое поля sector равно -1. Первым идет старшее слово номера сектора. Если обнаружена ошибка с номером 15, в это поле записывается указатель на метку тома. |
(0) 13 | header | Заголовок запроса. |
(+13) 1 | byte | В это поле драйвер записывает извлеченный из буфера байт, который будет считан по следующей команде ввода. |
Для команд проверки состояния запрос состоит только из заголовка, область переменного формата отсутствует.
7 - Сброс буфера устройства ввода.
11 - Сброс буфера устройства вывода.
Запрос состоит только из заголовка.
0Dh, 0Eh - Открыть/Закрыть устройство.
Запрос для этих команд состоит только из заголовка.
15 - Проверка сменяемости диска.
Запрос состоит только из заголовка.
19 - Общее управление вводом/выводом (GENERIC_IOCTL).
(0) 13 | header | Заголовок запроса. |
(+13) 1 | funct | Это поле содержит код функции команды общего IOCTL. |
(+14) 1 | subfunc | Код подфункции для функции funct. |
(+15) 2 | si_reg | Значение регистра SI при вызове функции 44h прерывания 21h. Эта функция DOS предназначена для управления вводом/выводом. |
(+17) 2 | di_reg | Значение, передаваемое при вызове функции 44h прерывания 21h через регистр DI. |
(+19) 4 | buf | Указатель на буфер данных, содержащий управляющую информацию для устройства или предназначенный для приема информации от устройства. |
23 - Получить активное логическое устройство.
24 - Установить активное логическое устройство.
(0) 13 | header | Заголовок запроса. |
(+13) 1 | unit | Код логического устройства, которое должно стать активным при использовании команды 24, или код активного устройства, помещаемый драйвером по команде 23. |
(+14) 1 | cmd | Код команды. |
(+15) 4 | status | Слово состояния. |
(+19) 4 | reserved | Зарезервировано. |
Функции BIOS для работы с дисками
Наилучшим и самым безопасным способом работы с дисками на физическом уровне является использование функций BIOS. Эти функции учитывают все особенности аппаратуры и предоставляют достаточно широкий набор средств доступа к дискам на физическом уровне.
Вся дисковая подсистема обслуживается прерыванием BIOS INT13h. Это прерывание выполняет множество функций. Для вызова определенной функции программа должна занести ее код в регистр AH, другие регистры, как правило, должны содержать параметры - номера используемых дисководов, цилиндров, головок, адреса таблиц параметров дискеты и жесткого диска и т.д.
Библиотека транслятора Microsoft QC 2.5 содержит специальную функцию _bios_disk(), сильно упрощающую работу с дисковыми функциями BIOS. В примерах программ, приведенных в книге, мы продемонстрируем как непосредственный вызов прерывания INT 13h, так и использование функции _bios_disk().
Мы приведем краткую таблицу функций прерывания INT 13h, после чего займемся детальным описанием этих функций. В примечании к описанию функций мы будем указывать типы компьютеров, на которых данная функция работоспособна.
00h | Сброс дисковой подсистемы |
01h | Получить состояние дисковой подсистемы |
02h | Чтение сектора |
03h | Запись сектора |
04h | Проверка сектора |
05h | Форматирование дорожки |
06h | Форматирование дорожки (НМД) |
07h | Форматирование диска (НМД) |
08h | Получить текущие параметры дисковода (НМД) |
09h | Инициализация таблиц параметров жесткого диска |
0Ah | Чтение длинное (НМД) |
0Bh | Запись длинная (НМД) |
0Ch | Поиск цилиндра (НМД) |
0Dh | Альтернативный сброс дисковода (НМД) |
0Eh | Чтение буфера сектора (НМД) |
0Fh | Запись буфера сектора (НМД) |
10h | Проверка готовности дисковода (НМД) |
11h | Рекалибровка дисковода (НМД) |
12h | Проверка памяти контроллера (НМД) |
13h | Проверка дисковода (НМД) |
14h | Проверка контроллера (НМД) |
15h | Получить тип дисковода |
16h | Проверка замены диска |
17h | Установка типа дискеты |
18h | Установка среды носителя данных для форматирования |
19h | Парковка головок (НМД) |
1Ah | Форматирование диска (ESDI НМД) |
Функция _bios_disk()
Стандартная библиотека трансляторов Microsoft QC .01, QC .5, C .0 содержит специальную функцию, облегчающую работу с диском на уровне BIOS - _bios_disk(). Эта функция требует использования файла bios.h
и описана следующим образом:
unsigned _bios_disk(unsigned funct, struct diskinfo_t *diskinfo);
Параметр funct задает выполняемую функцию, параметр diskinfo - это указатель на структуру, описывающую необходимые параметры, такие как номер дорожки, номер головки и т.д.:
struct diskinfo_t { unsigned drive; // Номер дисковода unsigned head; // Номер головки unsigned track; // Номер дорожки unsigned sector; // Номер первого сектора unsigned nsectors; // Количество читаемых, // записываемых // или сравниваемых секторов void far *buffer; // Адрес буфера в памяти };
Перед использованием функции _bios_disk() программа должна заполнить поля структуры diskinfo и вызвать _bios_disk() с соответствующим параметром funct.
Файл bios.h содержит константы для следующих значений параметра funct:
_DISK_FORMAT | Форматирование дорожки, описанной параметром diskinfo функции _bios_disk(). Для этой функции программа должна задать в структуре diskinfo номер дисковода, для которого выполняется форматирование, номера головки и форматируемой дорожки. Указатель buffer программа должна установить на подготовленный буфер формата, описанный выше. Необходимо выполнить все подготовительные действия, связанные с настройкой контроллера НГМД и таблицы параметров дискеты. |
_DISK_READ | Чтение одного или нескольких секторов диска. Эта функция аналогична функции 2 прерывания INT 13h. Если при чтении секторов произошла ошибка, ее код будет возвращен функцией _bios_disk() в старшем байте. При успешном завершении операции функция возвращает 0. |
_DISK_WRITE | Запись одного или нескольких секторов на диск. Функция аналогична предыдущей, за исключением того, что данные из буфера записываются на диск. |
_DISK_RESET | Сброс контроллера НГМД. Для этой функции не надо заполнять структуру diskinfo, ее содержимое игнорируется. Сброс контроллера выполняют после того, как произошла ошибка при выполнении другой операции, например, чтения или записи. После сброса можно попробовать повторить выполнение операции. |
_DISK_STATUS | Получение состояния НГМД после выполнения последней операции. Старший байт возвращаемого функцией bios_disk() значения содержит байт состояния. |
_DISK_VERIFY | Проверка диска. С помощью этой функции можно убедиться в том, что указанные сектора существуют и могут быть прочитаны в память. Дополнительно выполняется циклический избыточный тест (CRC). Функция проверки диска использует все поля структуры diskinfo. При ошибке старшие 8 битов возвращаемого функцией значения содержат байт состояния. |
Приведем пример программы, читающей первый сектор нулевой дорожки (нулевая головка) диска А:. В случае ошибки программа пытается прочесть сектор три раза:
#include <stdio.h> #include <conio.h> #include <bios.h> #include <dos.h> #include <stdlib.h>
char _far diskbuf[512];
void main(void);
void main(void) {
unsigned status = 0, i; struct diskinfo_t di;
di.drive = 0; di.head = 0; di.track = 0; di.sector = 1; di.nsectors = 1; di.buffer = diskbuf;
for(i = 0; i < 3; i++) { status = _bios_disk(_DISK_READ, &di) >> 8; if( !status ) break; }
}
Последний пример, который мы приведем перед тем, как закончить с работой диска на физическом уровне, это форматирование дорожки. Сейчас мы будем использовать стандартное форматирование. Как отформатировать дорожку нестандартным образом, вы узнаете в разделе, посвященном защите информации от несанкционированного копирования. Там же будет приведен соответствующий пример.
Приведенная ниже программа форматирует 20-ю дорожку дискеты, установленной в дисковод А:.
#include <stdio.h> #include <conio.h> #include <dos.h> #include <stdlib.h> #include <bios.h> #include "sysp.h"
// Номер форматируемой дорожки
#define TRK 20
// Код размера сектора - 512 байт
#define SEC_SIZE 2
union REGS inregs, outregs; char _far diskbuf[512];
void main(void); void main(void) {
struct diskinfo_t di; unsigned status; unsigned char old_sec_size, old_fill_char, old_eot; int i, j; DPT _far *dpt_ptr;
// Получаем адрес таблицы параметров дискеты
dpt_ptr = get_dpt();
// Сохраняем старые значения из таблицы параметров
old_sec_size = dpt_ptr->sec_size; old_fill_char = dpt_ptr->fill_char; old_eot = dpt_ptr->eot;
// Устанавливаем в таблице параметров дискеты // код размера сектора, символ заполнения при // форматировании, количество секторов на дорожке
dpt_ptr->sec_size = SEC_SIZE; dpt_ptr->fill_char = 0xf8; dpt_ptr->eot = 15;
// Устанавливаем тип диска
inregs.h.ah = 0x17; inregs.h.al = 3; inregs.h.dl = 0; int86(0x13, &inregs, &outregs);
// Устанавливаем среду для форматирования
inregs.h.ah = 0x18; inregs.h.ch = TRK; inregs.h.cl = dpt_ptr->eot; inregs.h.dl = 0; int86(0x13, &inregs, &outregs);
// Подготавливаем параметры для функции форматирования
di.drive = 0; di.head = 0; di.track = TRK; di.sector = 1; di.nsectors = 15; di.buffer = diskbuf;
// Подготавливаем буфер формата для 15-ти секторов
for(i=0, j=1; j<16; i += 4, j++) { diskbuf[i] = TRK; diskbuf[i+1] = 0; diskbuf[i+2] = j; diskbuf[i+3] = SEC_SIZE; }
// Вызываем функцию форматирования дорожки
status = _bios_disk(_DISK_FORMAT, &di) >> 8; printf("\nФорматирование завершилось с кодом: %d",status);
// Восстанавливаем старые значения в // таблице параметров дискеты
dpt_ptr->sec_size = old_sec_size; dpt_ptr->fill_char = old_fill_char; dpt_ptr->eot = old_eot;
}
H Общее управление вводом/выводом GENERIC IOCTL
Вызов:
Регистр | |
AH | 44h |
AL | 0Dh |
BL | Номер дисковода (0 - текущий дисковод, 1 - дисковод А: и т.д.) |
CH | Код категории устройства: 08h - дисковое устройство |
CL | Операция: 40h - установить параметры устройства; 60h - получить параметры устройства; 41h - записать дорожку на логическом устройстве; 61h - прочитать дорожку на логическом устройстве; 42h - форматировать дорожку на логическом устройстве; 62h - проверить дорожку на логическом устройстве |
DS:DX | Указатель на блок параметров. |
Возврат без ошибки:
Регистр | |
CF | 0 |
Возврат с ошибкой:
Регистр | |
CF | 1 |
AX | Код ошибки |
Формат блока параметров зависит от выполняемой операции:
CL = 40h/60h (получить/установить параметры устройства)
Смещение | Размер | Содержимое поля |
(0) | 1 | Специальные функции: |
(+1) | 1 | Тип устройства, возвращаемый драйвером: 0 - 320/360 К флоппи-диск (5"); 1 - 1,2 М флоппи-диск (5"); 2 - 720 К флоппи-диск (3"); 3 - 8" флоппи-диск нормальной плотности; 4 - 8" флоппи-диск двойной плотности; 5 - жесткий диск; 6 - накопитель на магнитной ленте; 7 - 1,44 М флоппи-диск (3") и прочие дисковые устройства. |
(+2) | 2 | Атрибуты устройства, возвращаемые драйвером. В этом поле используются только два младших бита. Бит 0 - признак заменяемости среды носителя данных (0 - заменяемая, 1 - не заменяемая), бит 1 - признак наличия аппаратного контроля замены дискеты (1 - контроль выполняется, 0 - контроль не выполняется). Остальные биты зарезервированы и должны содержать 0. |
(+4) | 2 | Максимальное количество цилиндров на физическом устройстве. Это поле устанавливается драйвером. |
(+6) | 1 | Тип среды носителя данных. Используется для устройств, поддерживающих несколько типов носителей данных, например, для флоппи-дисковода на 1.2М значение этого поля, равное 0, соответствует дискете на 1.2М, а 1 - 360К. |
(+7) | 31 | BPB для устройства. Если бит 0 поля специальных функций сброшен, то в этом поле находится новый BPB для устройства. Если бит 0 установлен, драйвер устройства возвращает BPB
для всех последующих запросов на построение BPB. |
(+38) | ? | Таблица разметки дорожки, имеет переменную длину. |
Биты специальных функций
Бит 0: |
В операции 60h значение этого бита, равное 1, используется для извлечения текущего BPB, как если бы он был получен по команде драйвера с кодом 2 (построить BPB). Значение этого бита, равное 0, говорит о том, что надо извлечь BPB, используемый по умолчанию. Для операции с кодом 40h значение бита, равное 1, используется для извлечения текущего BPB, значение 0 приводит к использованию BPB, подготовленного в данном блоке параметров; |
Бит 1: |
Значение этого бита, равное 1 - это указание игнорировать все поля в блоке параметров, кроме поля описания физической структуры дорожки на данном устройстве; |
Бит 2: |
Значение этого бита, равное 1, говорит о том, что все сектора на этой дорожке имеют одинаковый размер |
Если в поле "специальные функции" бит 2
установлен в 1, размеры всех секторов должны быть одинаковыми.
CL = 41h/61h (записать/прочитать дорожку)
Смещение | Размер | Содержимое поля |
(0) | 1 | Специальные функции (это поле всегда содержит 0) |
(+1) | 2 | Номер головки |
(+3) | 2 | Номер дорожки |
(+5) | 2 | Номер начального сектора (нумерация секторов, в отличие от нумерации головок и дорожек начинается с 0) |
(+7) | 2 | Общее количество секторов на дорожке, уменьшенное на единицу |
(+9) | 4 | FAR-указатель на буфер обмена с диском, в который помещается считываемая информация или откуда берется записываемая информация |
Смещение | Размер | Содержимое поля |
(0) | 1 | Специальные функции. Для этой операции определен только бит 0. Перед вызовом команды значение бита, равное 0, требуется для форматирования дорожки. Если этот бит установлен в 1, то проверяется возможность использования заданного формата дорожки. Если после выполнения команды значение бита 0 равно 0, то поддерживается заданный формат дорожки и заполенную таблицу разметки дорожки можно использовать. Если значение бита 0 равно 1, то затребованный формат дорожки не поддерживается. |
(+1) | 2 | Номер головки для форматирования/проверки |
(+3) | 2 | Номер дорожки для форматирования/проверки |
Перед началом выполнения операции программа должна получить и созранить текущие параметры устройства. Для получения текущих параметров устройства необходимо выполнить операцию с кодом 60h. Затем программа должна установить новые параметры устройства, которые будут использованы в операциях чтения/записи, проверки или форматирования. Для установки параметров программа должна выполнить операцию с кодом 40h.
После выполнения операции программа должна восстановить первоначальные параметры устройства, выполнив операцию с кодом 40h.
Приведем пример программы, иллюстрирующей применение функции общего управления вводом/выводом для блочных устройств.
Эта программа выполняет стандартное форматирование двадцатой дорожки диска А:.
Для работы с блоками параметров файл sysp.h
содержит определения специальных типов данных, которые будут использованы в программе форматирования:
#pragma pack(1)
/* Формат дорожки для GENERIC IOCTL */
typedef struct _TRK_LY_ { unsigned no; unsigned size; } TRK_LY;
/* Параметры устройства для GENERIC IOCTL */
typedef struct _DPB_ {
char spec; char devtype; unsigned devattr; unsigned numofcyl; char media_type;
EBPB bpb; char reserved[6];
unsigned trkcnt; TRK_LY trk[100];
} DPB;
/* Параметры для форматирования функцией GENERIC IOCTL */
typedef struct _DPB_FORMAT_ {
char spec; unsigned head; unsigned track;
} DPB_FORMAT;
#pragma pack()
Программа форматирования читает текущие параметры для диска А:, формирует структуру дорожки и устанавливает параметры для выполнения операции форматирования. Затем программа проверяет возможность использования указанной структуры дорожки и выполняет форматирование.
#include <dos.h> #include <stdio.h> #include <malloc.h> #include <errno.h> #include "sysp.h"
void main(void); void main(void) {
union REGS reg; struct SREGS segreg; DPB _far *dbp; DPB_FORMAT _far *dbp_f;
int sectors, i;
printf("\nПрограмма уничтожит содержимое" "\n20-й дорожки диска А:." "\nЖелаете продолжить? (Y,N)\n");
// Ожидаем ответ оператора и анализируем его
i = getch(); if((i != 'y') && (i != 'Y')) exit(-1);
// Заказываем память для блока параметров устройства
dbp = _fmalloc(sizeof(DPB));
// Заказываем память для блока параметров устройства, // который будет использован для форматирования
dbp_f = _fmalloc(sizeof(DPB_FORMAT));
if(dbp == NULL dbp_f == NULL) { printf("\nМало оперативной памяти!"); exit(-1); }
// Получаем текущие параметры диска А:
dbp->spec = 0;
// Вызываем подфункцию 0Dh для выполнения // операции чтения текущих параметров диска А:
reg.x.ax = 0x440d; reg.h.bl = 1; reg.x.cx = 0x0860; reg.x.dx = FP_OFF(dbp); segreg.ds = FP_SEG(dbp);
intdosx(®, ®, &segreg);
// Проверяем флаг переноса
if(reg.x.cflag != 0) { printf("\nОшибка: %d",reg.x.ax); exit(-1); }
// Заполняем блок параметров для форматирования. // Байт специальных функций содержит значение, // равное 5. Это означает, что: // - используется текущий блок параметров BIOS BPB; // - используются все поля в блоке параметров устройства; // - все сектора на дорожке имеют одинаковый размер
dbp->spec = 5;
// Считываем из BPB количество секторов на дорожке
sectors = dbp->bpb.seccnt;
// Подготавливаем таблицу, описывающую формат дорожки
// Записываем количество секторов на дорожке
dbp->trkcnt = sectors;
// Для каждого сектора на дорожке в таблицу // записываем его номер и размер. // Заметьте, что записывается размер сектора // в байтах, а не код размера, как это делается // при форматировании с помощью функции 05h прерывания INT13h
for(i = 0; i < sectors; i++) { dbp->trk[i].no = i+1; dbp->trk[i].size = 512; }
// Устанавливаем новые параметры для диска А:
reg.x.ax = 0x440d; reg.h.bl = 1; reg.x.cx = 0x0840; reg.x.dx = FP_OFF(dbp); segreg.ds = FP_SEG(dbp);
intdosx(®, ®, &segreg);
// Проверяем флаг переноса
if(reg.x.cflag != 0) { printf("\nОшибка: %d",reg.x.ax); exit(-1); }
// Подготавливаем блок параметров устройства, // который будет использован при вызове // операции проверки возможности форматирования // дорожки
// В поле специальных функций записываем 1, // это означает, что будет выполняться проверка // возможности использования указанного формата дорожки
dbp_f->spec = 1; dbp_f->head = 0; dbp_f->track = 20;
reg.x.ax = 0x440d; reg.h.bl = 1; reg.x.cx = 0x0842; reg.x.dx = FP_OFF(dbp_f); segreg.ds = FP_SEG(dbp_f);
intdosx(®, ®, &segreg);
// Проверяем флаг переноса
if(reg.x.cflag != 0) { printf("\nОшибка: %d",reg.x.ax); exit(-1); }
// Если указанный формат дорожки поддерживается, // поле специальных функций будет содержать 0. // Проверяем это.
if(dbp_f->spec != 0) { printf("\nФормат дорожки не поддерживается!"); exit(-1); }
// Заполняем блок параметров для выполнения // операции форматирования
dbp_f->spec = 0; dbp_f->head = 0; dbp_f->track = 20;
// Форматируем дорожку с номером 20, головка 0
reg.x.ax = 0x440d; reg.h.bl = 1; reg.x.cx = 0x0842; reg.x.dx = FP_OFF(dbp_f); segreg.ds = FP_SEG(dbp_f);
intdosx(®, ®, &segreg);
// Проверяем флаг переноса
if(reg.x.cflag != 0) { printf("\nОшибка: %d",reg.x.ax); exit(-1); }
// Освобождаем буфера
_ffree(dbp); _ffree(dbp_f);
exit(0); }
Теперь приведем программу, копирующую содержимое двух первых секторов нулевой дорожки (головка 0) в первые два сектора двадцатой дорожки.
Эта программа использует тип данных, используемый в операциях чтения/записи:
#pragma pack(1)
/* Параметры для чтения/записи функцией GENERIC IOCTL */
typedef struct _DPB_WR_ {
char spec; unsigned head; unsigned track; unsigned sector; unsigned sectcnt; void _far *buffer;
} DPB_WR;
#pragma pack()
Программа пользуется текущими параметрами диска А:, поэтому операции чтения текущих параметров и записи новых параметров не используются.
Обратите внимание на то, что эта и предыдущая программа разрушают содержимое двадцатой дорожки дискеты, поэтому для экспериментов с этими программами надо подготовить чистую отформатированную дискету.
#include <dos.h> #include <stdio.h> #include <malloc.h> #include <errno.h> #include "sysp.h"
void main(void); void main(void) {
union REGS reg; struct SREGS segreg; DPB_WR _far *dbp_wr; char buf[2000];
int sectors, i;
printf("\nПрограмма уничтожит содержимое" "\n20-й дорожки диска А:." "\nЖелаете продолжить? (Y,N)\n");
// Ожидаем ответ оператора и анализируем его
i = getch(); if((i != 'y') && (i != 'Y')) exit(-1);
// Заказываем память для блока параметров устройства, // который будет использован для чтения/записи
dbp_wr = malloc(sizeof(DPB_WR));
if(dbp_wr == NULL) { printf("\nМало оперативной памяти!"); exit(-1); }
// Заполняем блок параметров для выполнения // операции чтения. // Мы будем читать первые два сектора // на нулевой дорожке, головка 0.
dbp_wr->spec = 0; dbp_wr->head = 0; dbp_wr->track = 0; dbp_wr->sector = 0; dbp_wr->sectcnt = 2; dbp_wr->buffer = buf;
// Выполняем операцию чтения дорожки
reg.x.ax = 0x440d; reg.h.bl = 1; reg.x.cx = 0x0861; reg.x.dx = FP_OFF(dbp_wr); segreg.ds = FP_SEG(dbp_wr);
intdosx(®, ®, &segreg);
// Проверяем флаг переноса
if(reg.x.cflag != 0) { printf("\nОшибка: %d",reg.x.ax); exit(-1); }
// Заполняем блок параметров для выполнения // операции записи. // Только что прочитанные два сектора нулевой // дорожки будут записаны на 20-ю дорожку.
dbp_wr->spec = 0; dbp_wr->head = 0; dbp_wr->track = 20; dbp_wr->sector = 0; dbp_wr->sectcnt = 2; dbp_wr->buffer = buf;
// Выполняем операцию записи
reg.x.ax = 0x440d; reg.h.bl = 1; reg.x.cx = 0x0841; reg.x.dx = FP_OFF(dbp_wr); segreg.ds = FP_SEG(dbp_wr);
intdosx(®, ®, &segreg);
// Проверяем флаг переноса
if(reg.x.cflag != 0) { printf("\nОшибка: %d",reg.x.ax); exit(-1); }
// Освобождаем буфер
free(dbp_wr);
exit(0); }
Характеристики дисководов
Прежде чем начать работу с дисками на физическом уровне, необходимо выяснить конфигурацию дисковой подсистемы - сколько дисководов и какого типа подключено к компьютеру, сколько дорожек и головок имеется на каждом из дисководов и т.п. Способ, которым определяется конфигурация дисковой подсистемы, зависит от модели компьютера (PC, XT, AT), поэтому вначале займемся определением типа персонального компьютера.
ПЗУ BIOS BIOSсодержит по адресу FFFF:FFFE
байт, значение которого можно использовать для идентификации типа компьютера:
FF | оригинальный IBM PC |
FE | XT, Portable PC |
FD | PCjr |
FC | AT |
FB | XT с памятью 640 К на материнской плате |
F9 | Convertible PC |
Для компьютеров IBM PC и IBM XT конфигурация дисковой подсистемы определяется установкой переключателей на материнской плате, в частности, переключателями устанавливается количество подключенных к системе НГМД.
Машины IBM AT (и машины более высокого класса) имеют на материнской плате КМОП-память с малым энергопотреблением и питающуюся от аккумулятора (КМОП - это технология изготовления микросхем - КОМПЛЕМЕНТАРНАЯ пара МЕТАЛЛ-ОКИСЕЛ-ПОЛУПРОВОДНИК). В КМОП-памяти хранится информация о конфигурации дисковой подсистемы, при инициализации BIOS считывает эту информацию и записывает ее в свою внутреннюю область данных.
Для определения модели компьютера мы предлагаем следующую функцию:
/** *.Name pc_model * *.Title Определить модель компьютера * *.Descr Функция возвращает байт, идентифицирующий * модель персонального компьютера * *.Params Нет * *.Return Код модели персонального компьютера: * * 0xff - оригинальный PC; * 0xfe - XT, Portable PC; * 0xfd - PCjr; * 0xfc - AT; * 0xfb - XT с памятью 640К; * 0xf9 - Convertible PC. **/
#include <stdio.h> #include <dos.h> #include "sysp.h"
char unsigned pc_model(void) {
char unsigned _far *modptr;
modptr = FP_MAKE(0xf000,0xfffe);
return *modptr; }
Проанализировав значение, возвращаемое этой функцией, можно сделать предварительное заключение о конфигурации дисковой подсистемы компьютера. Если мы получили значения 0xff, 0xfd, 0xf9,
то наш компьютер не имеет НМД - это одна из разновидностей IBM PC. Значения 0xfe, 0xfb могут соответствовать IBM XT и совместимым с ним машинам. Такие машины могут быть оборудованы НМД. И, наконец, значение 0xfc соответствует IBM AT. Для этой машины конфигурация дисковой подсистемы должна определяться исходя из содержимого КМОП-памяти.
Следует заметить, что новые модели компьютеров могут иметь другие, не перечисленные выше, коды идентификации.
Прерывание BIOS INT11h возвращает в регистре AX
байт конфигурации системы, который можно использовать для определения количества НГМД и наличия НМД. Самый младший бит байта конфигурации - бит 0 - содержит признак наличия в системе НМД. Если этот бит установлен в 1, то НМД присутствует в системе, иначе дисковая подсистема состоит только из накопителей на гибких магнитных дисках.
Биты 7 и 6 содержат информацию о количестве флоппи-дисков:
Содержимое битов 7 и 6 | Количество установленных флоппи-дисков |
00 | 1 |
01 | 2 |
10 | 3 |
11 | 4 |
КМОП-память не адресуема непосредственно из программы, как обычная оперативная память. Для работы с ней необходимо использовать команды ввода/вывода в порты с адресами 70h и 71h. Перед началом операции чтения/записи в порт 70h
надо записать адрес для КМОП-памяти (0...3Fh). Затем из порта 71h можно прочитать содержимое требуемой ячейки КМОП-памяти или записать в этот порт байт, который будет записан в КМОП-память.
Приведем фрагмент программы, составленной на языке ассемблера, который считывает байт из КМОП-памяти с адресом 12h:
mov al,12h out 70h,al ; задаем адрес в КМОП-памяти jmp $+2 ; небольшая задержка in al,71h ; записываем в AL считанное значение
Запись в КМОП-память выполняется аналогично.
При анализе конфигурации дисковой подсистемы для нас представляют наибольший интерес ячейки КМОП-памяти со следующими адресами:
14h - байт конфигурации
Биты 7, 6 этого байта имеют такое же значение, что и в младшем байте слова конфигурации, возвращаемого прерыванием BIOS INT 11h - они содержат информацию о количестве установленных дисководов для флоппи-дисков.
Значение бита 0, равное нулю, говорит о том, что система не содержит НГМД.
10h - тип используемых флоппи-дисков
Младшая и старшая тетрады этого байта описывают соответственно второй и первый НГМД:
0000 | дисковод не установлен; |
0001 | дисковод на 360К; |
0010 | дисковод на 1,2М. |
0011 | дисковод на 720К. |
0100 | дисковод на 1.44М. |
Этот байт разделен на две тетрады аналогично байту, описывающему НГМД. Однако в тетраде можно закодировать только 16 различных значений, а типов НМД значительно больше. Поэтому тип 15
используется специальным образом - если тип НМД в младшей тетраде (диск C:) равен 15, то правильное значение типа находится в КМОП-памяти по адресу 19h. Аналогично для диска D: этот тип можно взять из байта по адресу 1Ah (если старшая тетрада байта с адресом 12h равна 15).
Если в вашем компьютере установлены диски с интерфейсом ESDI или SCSI или другим специализированным интерфейсом, то как правило, для работы с ними используется специальный "дисковый" BIOS. При этом в КМОП-памяти в ячейке 12h для типа диска может быть указано нулевое значение, несмотря на то, что диск установлен. Прерывание BIOS INT 11h скажет вам, что в системе имеется НМД.
Если используется "дисковый" BIOS, то он сам инициализирует таблицу параметров диска и выполняет обработку дискового прерывания INT 13h. Так как MS-DOS для работы использует именно это прерывание, то не возникает никаких проблем, связанных с отсутствием типа диска в КМОП-памяти. Другие операционные системы, такие как XENIX и OS/2, могут использовать для работы с диском собственные драйверы. При установке они могут запрашивать информацию о типе установленного диска.
Если ваша машина содержит дисковый BIOS, то не исключено, что у вас будут проблемы при установке операционных систем XENIX и OS/2. В этом случае необходимо убедиться в том, что устанавливаемая операционная система содержит драйверы для работы с вашим типом диска.
Теперь мы готовы к тому, чтобы определить конфигурацию дисковой подсистемы - количество и типы используемых дисководов.
Приведем функцию, которая заполнит структуру типа DISK_CONFIG, описанную в файле sysp.h,
информацией о конфигурации дисковой подсистемы.
Структура DISK_CONFIG содержит поля:
n_floppy | количество установленных в системе НГМД. |
n_hard | количество установленных жестких НМД. |
t_floppy1 | тип первого НГМД. |
t_floppy2 | тип второго НГМД. |
t_hard1 | тип первого НМД. |
t_hard2 | тип второго НМД. |
#include <stdio.h> #include <dos.h> #include "sysp.h"
void disk_cfg(DISK_CONFIG* cfg) {
char unsigned _far *modptr; char unsigned pc_type; char cfg_byte; int cfg_word;
union REGS inregs, outregs;
// Определяем тип компьютера
modptr = FP_MAKE(0xf000,0xfffe); pc_type = *modptr;
// В зависимости от типа компьютера выбираем // способ определения конфигурации дисковой // подсистемы
switch (pc_type) {
case 0xfc:
// Для IBM AT считываем конфигурацию дисковой // подсистемы из КМОП-памяти
// Считываем байт конфигурации
outp(0x70, 0x14); cfg_byte = inp(0x71);
// Определяем количество установленных флоппи-дисков
if((cfg_byte & 1) == 0) {
// Если младший бит байта конфигурации равен 0, // флоппи-диски не установлены
cfg->n_floppy = 0; cfg->t_floppy1 = 0; cfg->t_floppy2 = 0;
} else {
// Определяем количество установленных // флоппи-дисков
cfg->n_floppy = ((cfg_byte >> 6) & 3) + 1;
// Определяем типы флоппи-дисков
outp(0x70, 0x10); cfg_byte = inp(0x71);
cfg->t_floppy2 = cfg_byte & 0xf; cfg->t_floppy1 = (cfg_byte >> 4) & 0xf;
}
// Определяем конфигурацию жестких дисков
outp(0x70, 0x12); cfg_byte = inp(0x71);
if(cfg_byte == 0) {
// Если обе тетрады равны нулю, система // не содержит жестких дисков
cfg->n_hard = 0; cfg->t_hard1 = 0; cfg->t_hard2 = 0; } else {
// Определяем тип первого диска - диска C:
if((cfg_byte & 0xf) != 0xf) cfg->t_hard1 = cfg_byte & 0xf;
else { outp(0x70, 0x19); cfg->t_hard1 = inp(0x71); }
// Определяем тип второго диска - диска D:
if((cfg_byte & 0xf0) != 0xf0) cfg->t_hard2 = (cfg_byte >> 4) & 0xf;
else { outp(0x70, 0x1a); cfg->t_hard2 = inp(0x71); }
}
// Вычисляем количество установленных // в системе жестких дисков
cfg->n_hard = 0; if(cfg->t_hard1 != 0) cfg->n_hard++; if(cfg->t_hard2 != 0) cfg->n_hard++;
// Для некоторых совместимых с IBM AT машин невозможно // определить тип диска, так как в КМОП-памяти для // типа диска установлено значение 0, несмотря на то, // что диск установлен (например машина Bondwell, // модель В-300). В таких случаях можно определить // наличие жесткого диска, используя слово // конфигурации, возвращаемое прерыванием INT 11h.
if(cfg->n_hard == 0) {
int86(0x11, &inregs, &outregs); cfg_word = outregs.x.ax;
// Определяем наличие жесткого диска
if((cfg_word & 1) != 0) {
cfg->n_hard = 1;
// Считаем, что тип используемого жесткого // диска неопределен
cfg->t_hard1 = 0; cfg->t_hard2 = 0;
} }
break;
default:
// Для остальных типов компьютеров вызываем // прерывание INT 11h, используем возвращаемый // этим прерыванием байт конфигурации
int86(0x11, &inregs, &outregs); cfg_word = outregs.x.ax;
// Определяем количество установленных // флоппи-дисков
cfg->n_floppy = ((cfg_word >> 6) & 3) + 1;
// Считаем, что тип используемого флоппи-диска // неопределен
cfg->t_floppy1 = 0; cfg->t_floppy2 = 0;
// Определяем наличие жесткого диска
if((cfg_word & 1) != 0) {
cfg->n_hard = 1;
// Считаем, что тип используемого жесткого // диска неопределен
cfg->t_hard1 = 0; cfg->t_hard2 = 0;
}
break;
}
}
Пользуясь приведенной выше функцией мы всегда сможем определить количество дисководов для флоппи-дисков и жестких дисков, но не всегда сможем определить их тип. Это само по себе не страшно, так как для работы с дисками на физическом уровне нам надо знать не столько тип диска, сколько другие его характеристики, такие как количество головок, секторов и др. Эти характеристики можно определить из таблиц параметров для дискет и жестких дисков, заполняемых модулями BIOS процессе инициализации системы.
Приведем сокращенную таблицу параметров для стандартных типов жестких дисков, возвращаемых функцией disk_cfg. Информация, которая содержится в этой таблице, используется BIOS процессе инициализации, когда модули BIOS анализируют содержимое КМОП-памяти.
Тип | Количество цилиндров | Количество головок | Емкость диска в байтах |
1 | 306 | 4 | 10.653.696 |
2 | 615 | 4 | 21.411.840 |
3 | 615 | 6 | 32.117.760 |
4 | 940 | 8 | 65.454.080 |
5 | 940 | 6 | 49.090.560 |
6 | 615 | 4 | 21.411.840 |
7 | 462 | 8 | 32.169.984 |
8 | 733 | 5 | 31.900.160 |
9 | 900 | 15 | 117.504.000 |
10 | 820 | 3 | 21.411.840 |
11 | 855 | 5 | 37.209.600 |
12 | 855 | 7 | 52.093.440 |
13 | 306 | 8 | 21.307.392 |
14 | 733 | 7 | 44.660.224 |
15 | 0 | 0 | 0 |
16 | 612 | 4 | 21.307.392 |
17 | 977 | 5 | 42.519.040 |
18 | 977 | 7 | 59.526.656 |
19 | 1024 | 7 | 62.390.272 |
20 | 733 | 5 | 31.900.160 |
21 | 733 | 7 | 44.660.224 |
22 | 733 | 5 | 31.900.160 |
23 | 306 | 4 | 10.653.696 |
24 | 977 | 5 | 42.519.040 |
25 | 1024 | 9 | 80.216.064 |
26 | 1224 | 7 | 74.575.872 |
27 | 1224 | 11 | 117.190.656 |
28 | 1224 | 15 | 159.805.440 |
29 | 1024 | 8 | 71.303.168 |
30 | 1024 | 11 | 98.041.856 |
31 | 918 | 11 | 87.892.992 |
32 | 925 | 9 | 72.460.800 |
33 | 1024 | 10 | 89.128.960 |
34 | 1024 | 12 | 106.954.752 |
35 | 1024 | 13 | 115.867.648 |
36 | 1024 | 14 | 124.780.544 |
37 | 1024 | 2 | 17.825.792 |
38 | 1024 | 16 | 142.606.336 |
39 | 918 | 15 | 119.854.080 |
40 | 820 | 6 | 42.823.680 |
секторов.
Стандартная машина IBM XT комплектуется обычно НМД с типом 1, тип 2 используется стандартной IBM AT. Остальные типы НМД поддерживаются не всеми версиями BIOS, например, типы 16...23 поддерживаются BIOS только тех версий, которые были изготовлены не позднее 15/11/85.
Наиболее широко распространены флоппи-диски емкостью 360К, 1.2М, 720К, 1.44М. Их параметры приведены в следующей таблице:
Тип | Емкость, Кбайтов | Диаметр, дюймы | Количество секторов на одну дорожку | Количество цилиндров |
1 | 360 | 5 | 9 | 40 |
2 | 1200 | 5 | 15 | 80 |
3 | 720 | 3 | 9 | 40 |
4 | 1440 | 3 | 18 | 80 |
Анализируя содержимое КМОП-памяти в машинах AT или установку переключателей конфигурации на материнской плате в машинах PC и XT, BIOS процессе инициализации создает таблицу параметров дискеты DPT (Diskette Parameter Table), а также одну или две таблицы параметров жесткого диска HDPT (Hard Disk Parameter Table). Если имеется специальный дисковый BIOS, то он сам создает таблицы HDPT.
Таблица параметров дискеты DPT имеет длину 10 байт, ее адрес располагается в области данных BIOS по адресу 0000:0078, что соответствует вектору прерывания INT 1Eh. Таблица содержит параметры, важные для работы дисковода:
(0) 1 | srt_hut | Биты 0...3 - SRT (Step Rate Time) - задержка для переключения головок, лежит в пределах 1-16 мс и задается с интервалом 1 мс (0Fh - 1mc, 0Eh - 2 mc, 0Dh - 3 mc, ...); биты 4...7 - задержка разгрузки головки, лежит в пределах 16-240 мс и задается с интервалом 16 мс (1 - 16 mc, 2 - 32 mc, ..., 0Fh - 240 mc). |
(+1) 1 | dma_hlt | Бит 0 - значение этого бита, равное 1, говорит о том, что используется прямой доступ к памяти (DMA); биты 2...7 - время загрузки головок HLT - интервал между сигналом загрузки головок и началом операции чтение/запись, лежит в пределах 2-254 мс и задается с интервалом 2 мс (1 - 2 mc, 2 - 4 mc, ..., 0FFh - 254 mc). |
(+2) 1 | motor_w | Задержка перед выключением двигателя. |
(+3) 1 | sec_size | Код размера сектора в байтах (0 - 128 байтов, 1 - 256, 2 - 512, 3 - 1024). |
(+4) 1 | eot | Номер последнего сектора на дорожке |
(+5) 1 | gap_rw | Длина межсекторного промежутка для чтения/записи. |
(+6) 1 | dtl | Максимальная длина передаваемых данных, используется когда не задана длина сектора. |
(+7) 1 | gap_f | Длина межсекторного промежутка для операции форматирования. |
(+8) 1 | fill_char | Байт-заполнитель для форматирования (обычно используется F6h). |
(+9) 1 | hst | Время установки головки в миллисекундах. |
(+10) 1 | mot_start | Время запуска двигателя в 1/8 долях секунды. |
Все времена в таблице зависят от частоты тактового генератора контроллера НГМД, приведенные значения соответствуют частоте 8 МГц.
Для удобства работы с таблицей параметров дискеты файл sysp.h содержит определение типа DPT:
#pragma pack(1)
typedef struct _DPT_ { unsigned char srt_hut; unsigned char dma_hlt; unsigned char motor_w; unsigned char sec_size; unsigned char eot; unsigned char gap_rw; unsigned char dtl; unsigned char gap_f; unsigned char fill_char; unsigned char hst; unsigned char mot_start; } DPT;
#pragma pack()
Адреса таблиц параметров жестких дисков HDPT
расположены по адресам, соответствующим векторам прерываний INT 41h (для первого физического диска) и INT 46h (для второго физического диска). Эти таблицы имеют следующий формат:
(0) 2 | max_cyl | Максимальное количество цилиндров на диске. |
(+2) 1 | max_head | Максимальное количество магнитных головок. |
(+3) 2 | srwcc | Начальный цилиндр для предварительной записи (Starting reduced-write current cylinder). |
(+5) 2 | swpc | Начальный цилиндр для предварительной компенсации при записи (Starting write precompensation cylinder). |
(+7) 1 | max_ecc | Максимальная длина блока коррекции ошибок ECC (Maximum ECC data burst length). |
(+8) 1 | dstopt | Опции устройства: бит 7 - запрет восстановления; бит 6 - запрет восстановления по блоку коррекции ошибок ECC (Error Correction Code); биты 2-0 - опции устройства. |
(+9) 1 | st_del | Стандартная величина задержки. |
(+10) 1 | fm_del | Величина задержки для форматирования диска. |
(+11) 1 | chk_del | Величина задержки для проверки диска. |
(+12) 4 | reserve | Зарезервировано. |
#pragma pack(1)
typedef struct _HDPT_ { unsigned max_cyl; unsigned char max_head; unsigned srwcc; unsigned swpc; unsigned char max_ecc; unsigned char dstopt; unsigned char st_del; unsigned char fm_del; unsigned char chk_del; char reserve[4]; } HDPT;
#pragma pack()
Наиболее полезная информация, которую можно извлечь из таблицы параметров дискеты - это код размера сектора. Если вам когда-либо понадобится работать с нестандартным размером сектора (512 байтов), вам не обойтись без этой таблицы.
Таблица параметров жесткого диска содержит такие важнейшие значения, как максимальное количество цилиндров и максимальное количество головок. Если вам не удалось определить тип диска, то таблица HDPT - единственное надежное место, откуда можно получить информацию о цилиндрах и головках.
Для удобства использования таблиц параметров дискет и дисков мы подготовили следующие функции:
/** *.Name get_dpt * *.Title Вычислить адрес таблицы параметров дискеты * *.Descr Функция возвращает указатель на таблицу * параметров дискеты * *.Params Нет * *.Return Указатель на таблицу параметров дискеты DPT **/
#include <stdio.h> #include <dos.h> #include "sysp.h"
DPT _far *get_dpt(void) { void _far * _far *ptr;
ptr = (void _far * _far *)FP_MAKE(0x0,0x78); return(*ptr); } /** *.Name get_hdp1 * *.Title Вычислить адрес таблицы параметров диска 1 * *.Descr Функция возвращает указатель на таблицу * параметров диска 1 * *.Params Нет * *.Return Указатель на таблицу параметров диска 1 HDPT **/
#include <stdio.h> #include <dos.h> #include "sysp.h"
HDPT _far *get_hdp1(void) { void _far * _far *ptr;
ptr = (void _far * _far *)FP_MAKE(0x0,0x104); return(*ptr);
} /** *.Name get_hdp2 * *.Title Вычислить адрес таблицы параметров диска 2 * *.Descr Функция возвращает указатель на таблицу * параметров диска 2 * *.Params Нет * *.Return Указатель на таблицу параметров диска 2 HDPT **/
#include <stdio.h> #include <dos.h> #include "sysp.h"
HDPT _far *get_hdp2(void) { void _far * _far *ptr;
ptr = (void _far * _far *)FP_MAKE(0x0,0x118); return(*ptr);
}
В качестве примера приведем программу, которая определяет конфигурацию дисковой подсистемы и отображает основные характеристики используемых дисководов. Программа обращается к таблицам параметров НГМД и НМД:
#include <stdio.h> #include <dos.h> #include "sysp.h"
void main(void); void main(void) {
DISK_CONFIG cfg; DPT _far *dpt_ptr; HDPT _far *hdpt1_ptr; HDPT _far *hdpt2_ptr;
printf("\n" "\nКонфигурация дисковой подсистемы" "\n (C)Фролов А., 1991" "\n");
// Определяем конфигурацию дисковой подсистемы
disk_cfg(&cfg);
printf("\nУстановлено:" "\n Флоппи-дисков: %d" "\n Дисков: %d", cfg.n_floppy, cfg.n_hard);
printf("\nТипы флоппи-дисков: A: - %d, B: - %d" "\nТипы дисков: C: - %d, D: - %d", cfg.t_floppy1, cfg.t_floppy2, cfg.t_hard1, cfg.t_hard2);
// Получаем адрес таблицы параметров дискеты
dpt_ptr = get_dpt();
printf("\n" "\nКод размера сектора дискеты: %d" "\ nЗаполняющий символ для форматирования дискеты: %2.2X", dpt_ptr->sec_size, dpt_ptr->fill_char);
// Получаем адреса первой и второй таблицы // параметров жесткого диска
hdpt1_ptr = get_hdp1(); hdpt2_ptr = get_hdp2();
printf("\n" "\nПараметры первого диска:" "\n Количество цилиндров: %d" "\n Количество головок: %d" "\n" "\nПараметры второго диска:" "\n Количество цилиндров: %d" "\n Количество головок: %d", hdpt1_ptr->max_cyl, hdpt1_ptr->max_head, hdpt2_ptr->max_cyl, hdpt2_ptr->max_head);
}
Идентификатор BIOS
F000:FFF5 (8) - дата изготовления BIOS; F000:FFFC (2) - не используется; F000:FFFE (1) - код типа компьютера.
Инициализация контроллера НМД
На входе: | AH = 09h |
DL = Адрес дисковода (80h, 81h, ...) | |
На выходе: | AH = Состояние дисковода после завершения последней операции |
CF = 1, если произошла ошибка, 0, если ошибки нет |
|
Примечание: | PC, XT, AT, PS/2 |
Функцию инициализации контроллера НМД применяют после модификации таблиц параметров жесткого диска. BIOS BIOSузнает о внесенных в таблицы изменениях и инициализирует соответствующим образом контроллер НМД.
Использование функций BIOS
Только что мы привели функции BIOS для работы с диском на физическом уровне. Когда и как ими пользоваться?
Доступ к диску на физическом уровне может потребоваться для чтения отдельных секторов диска, расположенных в фиксированных (или известных) местах диска - таблицы разделов диска, каталогов и т.п. С помощью функций BIOS можно выполнить низкоуровневое форматирование диска, как стандартное, так и использующее нестандартный формат дорожки.
В любом случае при записи информации в сектора следует внимательно анализировать работу программы - ошибки могут привести к разрушению логической структуры диска. В результате этого могут оказаться потеряны каталоги и файлы. Все "опасные" эксперименты лучше проводить на дискетах, и только когда вы уверены в безошибочной работе программы, можно "допустить" ее к жесткому диску.
Если вы используете дисковод с высокой плотностью записи (например, 1.2М) для работы с дискетами, использующими двойную плотность записи (360 К), перед началом работы вам надо правильно установить скорость передачи данных через контроллер НГМД. Лучше всего это сделать функцией 17h прерывания INT3h, указав тип диска.
Не следует забывать о задержке, необходимой для разгона двигателя НГМД до рабочей скорости. Некоторые функции BIOS могут вернуть признак ошибки, если двигатель не набрал нужной скорости. Если вы получили признак ошибки, вначале следует три раза повторить вызов функции, сбрасывая каждый раз перед этим контроллер НГМД функцией 0 прерывания INT 13h. Если и после этого ошибка не исчезла, следует провести ее углубленный анализ.
Приведем примеры использования функций прерывания INT 13h для работы с НГМД.
Первый пример - программа, составленная на языке ассемблера. Она читает самый первый сектор диска, расположенный на нулевой дорожке, нулевой стороне (нулевая головка). Этот сектор имеет номер 1.
.MODEL tiny
.STACK 100h
.DATA
; Буфер, в который будет прочитан сектор диска
buf db 512 dup(?)
.CODE .STARTUP
mov ch,00h ; Номер дорожки mov cl,01h ; Номер сектора
mov dh,00h ; Номер головки (стороны диска) mov dl,00h ; Номер дисковода - дисковод А:
; Готовим адрес буфера в ES:BX
mov ax,cs mov es,ax
mov bx,OFFSET buf
; Готовим код функции
mov ah,02h ; Код функции - чтение сектора mov al,01h ; Количество читаемых секторов - 1
; Вызываем прерывание
int 13h
.EXIT 0
END
Следующая программа - пример использования аппаратной поддержки проверки замены дискеты. Эта поддержка реализована в машинах класса AT, PS/2.
Сначала программа устанавливает тип дискеты. Это нужно для правильного выбора скорости передачи данных контроллером НГМД. При установке типа дискеты сбрасывается флаг замены дискеты.
Далее после чтения состояния НГМД программа делает паузу, во время которой вы можете заменить дискету или просто открыть и закрыть дверцу дисковода. Выполнив (или не выполнив) действия по замене дискеты, нажмите на любую клавишу. Программа выведет на экран новое состояние флага замены дискеты.
Попробуйте запустить эту программу без дискеты, обратите внимание на состояние порта 0x3F7.
Главное, что вы можете взять из приведенной ниже программы - это техника работы с флагом замены дискеты. Используя аппаратную поддержку проверки замены дискеты, ваша программа сможет более полно контролировать действия оператора по установке и замене дискет.
Текст программы:
#include <stdio.h> #include <conio.h> #include <dos.h> #include <stdlib.h>
union REGS inregs, outregs;
void main(void); void main(void) {
// Устанавливаем тип диска и сбрасываем // флаг замены дискеты
inregs.h.ah = 0x17; inregs.h.al = 3; inregs.h.dl = 0; int86(0x13, &inregs, &outregs);
// Определяем тип диска и наличие аппаратной // поддержки проверки замены дискеты
inregs.h.ah = 0x15; inregs.h.dl = 0; int86(0x13, &inregs, &outregs); printf("\nТип диска А: %d",outregs.h.ah);
// Определяем состояние флага замены дискеты
inregs.h.ah = 0x16; inregs.h.dl = 0; int86(0x13, &inregs, &outregs); printf("\nСостояние флага замены дискеты: %d",outregs.h.ah);
// Выводим состояние порта 0x3F7. // Бит 7 этого порта отображает состояние // флага замены дискеты
printf("\nПорт 0x3F7: %02.2x",inp(0x3f7));
// Сбрасываем контроллер НГМД
inregs.h.ah = 0; inregs.h.dl = 0; int86(0x13, &inregs, &outregs);
// Делаем паузу, во время которой можно // заменить дискету. Запуская программу несколько // раз, попробуйте во время ожидания нажатия на клавишу // открыть и затем закрыть дверцу дисковода - это // приведет к установке флага замена дискеты
printf("\nЗамените дискету и нажмите на любую клавишу"); getch();
// Определяем заново состояние флага замены дискеты
inregs.h.ah = 0x16; inregs.h.dl = 0; int86(0x13, &inregs, &outregs); printf("\nСостояние флага замены дискеты: %d",outregs.h.ah);
// Выводим состояние порта 0x3F7.
printf("\nПорт 0x3F7: %02.2x",inp(0x3f7));
}
Еще один пример - нестандартное форматирование дорожки флоппи-диска - мы приведем в следующем разделе.
Использование функций IOCTL
4.1.
4.2.
В разделе книги, посвященным драйверам, мы рассказывали о функции управления вводом/выводом IOCTL - функции 44h прерывания INT21h. Эта функция предоставляет широкие возможности по управлению устройствами ввода/вывода посредством обмена управляющей информацией с драйверами устройств.
В этом разделе мы вновь вернемся к функциям IOCTL
для того, чтобы рассказать об их использовании при работе с дисковой подсистемой.
Мы покажем, как использовать функцию 44h
прерывания INT 21h для извлечения разнообразной информации об открытых файлах по их файловому индексу, для определения момента достижения конца файла, для получения информации об дисководах и для выполнения таких низкоуровневых операций, как форматирование дорожки диска, чтение/запись секторов диска и т.д.
Напомним, при вызове функции 44h регистр AL
содержит код выполняемой подфункции. Для подфункции 0Dh (Generic IO Control) в регистре CL
должен находиться код выполняемой операции.
Изменение атрибутов, времени и даты файлов
Напомним: атрибуты файла, время и дата его последней модификации, а также размер файла хранятся в дескрипторе файла. Дескриптор файла находится в каталоге.
Операционная система предоставляет вам все необходимые средства для изменения всех полей дескриптора файла, кроме номера начального кластера. Для изменения этого номера вам придется работать с каталогом через таблицу размещения файлов FAT. Вам придется сначала считать каталог по кластерам с помощью прерывания INT 25h, модифицировать нужные поля и записать каталог обратно на диск при помощи прерывания INT 26h.
Для работы с полем атрибутов файла предназначена функция 43h прерывания INT 21h:
На входе: | AH = 43h |
AL = выполняемая операция: 00h чтение атрибутов файла 01h установка новых атрибутов файла |
|
CX = новые атрибуты файла, если AL = 01h: Биты: 5 - бит архивации 4 - каталог 3 - метка диска 2 - системный файл 1 - скрытый файл 0 - только читаемый файл |
|
DS:DX = путь файла в формате ASCIIZ | |
На выходе: | AX = Код ошибки, если был установлен в 1
флаг переноса CF |
CX = Если не было ошибки, этот регистр содержит атрибуты файла |
При изменении атрибутов файла допустимо указывать комбинации битов в регистре CX.
Если ваша программа работает в сети, она должна иметь соответствующие права доступа к каталогу, содержащему файл, для которого программа собирается изменять байт атрибутов.
Для работы с полями времени и даты последней модификации файла предназначена функция 57h
прерывания INT 21h:
На входе: | AH = 57h |
AL = выполняемая операция: 00h чтение даты и времени 01h установка даты и времени |
|
BX = файловый индекс открытого файла | |
CX = время | |
DX = дата | |
На выходе: | AX = Код ошибки, если был установлен в 1
флаг переноса CF |
CX = Если не было ошибки, этот регистр содержит время последнего изменения файла | |
DX = Если не было ошибки, этот регистр содержит дату последнего изменения файла |
Для того, чтобы изменить время или дату последней модификации файла с помощью этой функции, файл предварительно должен быть открыт. Формат времени и даты для этой функции такой же, как и используемый в дескрипторе каталога. Приведем эти форматы еще раз для удобства.
Формат поля времени:
15 11 10 5 4 0 ++ ¦ Часы (0...23) ¦ Минуты (0...59) ¦ Секунды/2 (0...29) ¦ ++
Формат поля даты:
15 9 8 5 4 0 ++ ¦ Год (0...119) ¦ Месяц (1...12) ¦ День (1...31) ¦ ++
Стандартные библиотеки трансляторов Microsoft QC 2.5 и C 6.0 содержат функции для чтения и изменения атрибутов файлов и времени/даты их последней модификации.
Для определения атрибутов файла можно использовать функцию _dos_getfileattr():
unsigned _dos_getfileattr(char *path, unsigned *attrib);
Эта функция получает атрибуты файла, заданного первым аргументом, и записывает байт атрибутов в младший байт по адресу, указанному вторым параметром.
В случае успешного завершения функция возвращает 0, в противном случае она возвращает код ошибки, полученный от операционной системы и устанавливает глобальную переменную errno в значение ENOENT, что означает отсутствие указанного в параметре path файла.
Для изменения атрибутов файла можно использовать функцию _dos_setfileattr():
unsigned _dos_setfileattr(char *path, unsigned attrib);
Параметр attrib может принимать следующие значения:
_A_ARCH | установка бита архивации |
_A_HIDDEN | файл скрытый |
_A_NORMAL | обычный файл |
_A_RDONLY | только читаемый файл |
_A_SUBDIR | каталог |
_A_SYSTEM | системный файл |
_A_VOLID | метка диска |
unsigned _dos_getftime(int handle, unsigned *date, unsigned *time);
Перед использованием этой функции программа должна открыть файл. Дата и время записываются по адресу, указываемому, соответственно, вторым и третьим параметрами.
Если вам надо изменить время или дату последней модификации файла, используйте функцию
_dos_setftime():
unsigned _dos_setftime(int handle, unsigned date, unsigned time);
Параметры этой функции аналогичны используемым в функции _dos_getftime(), за исключением того, что в качестве второго и третьего параметра применяются не указатели, а непосредственные значения даты и времени.
Приведем программу, изменяющую при запуске значение бита файла атрибутов "Только читаемый" для файла, имя которого передается программе в качестве параметра:
#include <dos.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h>
void main(int argc, char *argv[]); void main(int argc, char *argv[]) {
unsigned fattr;
_dos_getfileattr(argv[1], &fattr);
_dos_setfileattr(argv[1], fattr ^ _A_RDONLY); }
Программа сначала считывает байт атрибутов, затем инвертирует соответствующий бит и устанавливает новое значение байта атрибутов.
Элементы таблицы разделов диска
Смещение Размер Содержимое (+0) 1 Признак активного раздела: 0 - раздел не активный; 80h - раздел активный. (+1) 1 Номер головки для начального сектора раздела. (+2) 2 Номер сектора и цилиндра для начального сектора раздела в формате функции чтения сектора INT_13h. (+4) 1 Код системы: 0 - неизвестная система; 1, 4 - DOS; 5 - расширенный раздел DOS. (+5) 1 Номер головки для последнего сектора раздела. (+6) 2 Номер сектора и цилиндра для последнего сектора раздела в формате функции чтения сектора INT_13h. (+8) 4 Относительный номер сектора начала раздела. (+12) 4 Размер раздела в секторах.
Как программа может определить формат FAT?
Для DOS версии 3.0 16-битовый формат используется, если размер диска превышает 4086 кластеров. Это число получилось исходя из того, что в 12 разрядах может быть представлено максимальное число 4096, кроме того, значения, большие 0ff6, зарезервированы.
Для DOS версии 3.2 16-битовый формат FAT используется в том случае, когда размер диска превышает 20790 секторов (именно секторов, а не кластеров). Фактически это означает, что 16-битовый формат используется только для дисков, имеющих размер более 10 мегабайтов.
Сектор загрузочной записи (BOOT-сектор) диска, отформатированного в DOS версии 4.0 в поле со смещением 36h содержит восьмибайтовую строку, идентифицирующую формат FAT. Она имеет вид "FAT12 " или "FAT16 ". Вы можете использовать это поле для определения формата FAT. В структуре BOOT, описанной в файле sysp.h, это поле называетcя fat_format.
Если разделы на жестком диске создавались утилитой DOS FDISK, формат FAT можно определить, анализируя содержимое поля sys
главной загрузочной записи (Master Boot Record). Если это поле содержит значение 1, используется 12-битовый формат, если 4 - 16-битовый. Однако диск, подготовленный программами диск-менеджеров, может иметь нестандартный для DOS формат таблицы разделов диска (Partition Table), и поле sys может содержать другие величины, отличные от 1 и 4.
Код ошибки при работе с диском на уровне BIOS
00h | Успешное завершение операции |
01h | Неправильная команда |
02h | Не найдена адресная метка |
03h | Попытка записи на диск, защищенный от записи |
04h | Сектор не найден |
05h | Ошибка при сбросе (НМД) |
06h | Произошла замена дискеты |
07h | Неправильные параметры дисковода (НМД) |
08h | Переполнение канала ПДП (НГМД) |
09h | Переход за границу 64К при работе с ПДП |
0Ah | Обнаружен плохой сектор (НМД) |
0Bh | Обнаружена плохая дорожка (НМД) |
0Ch | Неправильный номер дорожки |
0Dh | Неправильный номер сектора при форматировании (НМД) |
0Eh | Обнаружена адресная метка управляющих данных (НМД) |
0Fh | Ошибка ПДП (НМД) |
10h | Обнаружена ошибка в CRC/ECC |
11h | Данные скорректированы с использованием ECC (НМД) |
20h | Сбой контроллера |
40h | Сбой при поиске дорожки |
80h | Таймаут - программа не успевает обрабатывать данные |
AAh | Дисковод не готов (НМД) |
BBh | Неизвестная ошибка (НМД) |
CCh | Сбой при записи (НМД) |
E0h | Ошибка регистра состояния (НМД) |
FFh | Ошибка операции считывания (НМД) |
Коды идентификации типа компьютера
FF | оригинальный IBM PC |
FE | XT, Portable PC |
FD | PCjr |
FC | AT |
FB | XT с памятью 640 К на материнской плате |
F9 | Convertible PC |
Коды ошибок
Код | Описание |
0 | Нарушение защиты от записи. Была предпринята попытка записи информации на защищенное от записи устройство. |
1 | Неизвестное устройство. |
2 | Устройство не готово. |
3 | Неизвестная команда. Затребованная команда не поддерживается драйвером. |
4 | Ошибка CRC. При выполнении команды обнаружена ошибка циклического кода проверки. |
5 | Неправильная длина запроса. Поле длины в заголовке запроса содержит неверное значение. |
6 | Ошибка при поиске дорожки (дорожка не найдена). |
7 | Неизвестный носитель данных. |
8 | Сектор не найден. |
9 | Нет бумаги в принтере. |
0Ah | Ошибка записи. |
0Bh | Ошибка чтения. |
0Ch | Общая ошибка. |
0Dh | Зарезервировано. |
0Eh | Зарезервировано. |
0Fh | Неразрешенная замена диска (только для DOS версии 3.0 и более поздних версий). |
Команды драйвера
0 | Инициализация |
1 | Проверка смены носителя данных |
2 | Построить блок BPB |
3 | Чтение IOCTL |
4 | Чтение |
5 | Неразрушающее чтение |
6 | Проверка состояния ввода |
7 | Сброс буферов ввода |
8 | Запись |
9 | Запись с проверкой |
10 | Проверка состояния вывода |
11 | Сброс буферов вывода |
12 | Запись IOCTL |
13 | Открыть устройство |
14 | Закрыть устройство |
15 | Проверка возможности смены носителя данных |
16 | Зарезервировано |
17 | Зарезервировано |
18 | Зарезервировано |
19 | Общее управление вводом/выводом (GENERIC IOCTL) |
20 | Получить текущее логическое устройство |
21 | Установить логическое устройство |
Команды контроллера НГМД
Команда Байты команды
Чтение ++ данных ¦MT ¦MFM¦SK ¦ 0 ¦ 0 ¦ 1 ¦ 1 ¦ 0 ¦ ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Чтение удаленных ++ данных ¦MT ¦MFM¦SK ¦ 0 ¦ 1 ¦ 1 ¦ 0 ¦ 0 ¦ ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Запись ++ данных ¦MT ¦MFM¦ 0 ¦ 0 ¦ 0 ¦ 1 ¦ 0 ¦ 0 ¦ ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Запись удаленных ++ данных ¦MT ¦MFM¦ 0 ¦ 0 ¦ 1 ¦ 0 ¦ 0 ¦ 1 ¦ ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Чтение данных ++ с дорожки ¦MT ¦MFM¦SK ¦ 0 ¦ 0 ¦ 0 ¦ 1 ¦ 0 ¦ ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Сканирование до ++ "равно" ¦MT ¦MFM¦SK ¦ 1 ¦ 0 ¦ 0 ¦ 0 ¦ 1 ¦ ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Сканирование до ++ "меньше" или ¦MT ¦MFM¦SK ¦ 1 ¦ 1 ¦ 0 ¦ 0 ¦ 1 ¦ "равно" ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Сканирование до ++ "больше" или ¦MT ¦MFM¦SK ¦ 1 ¦ 1 ¦ 1 ¦ 0 ¦ 1 ¦ "равно" ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Форматирование ++ дорожки ¦ 0 ¦MFM¦ 0 ¦ 0 ¦ 1 ¦ 1 ¦ 0 ¦ 1 ¦ ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Считывание ++ индексных ¦ 0 ¦MFM¦ 0 ¦ 0 ¦ 1 ¦ 0 ¦ 1 ¦ 1 ¦ данных ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Инициализация ++ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 1 ¦ 1 ¦ 1 ¦ ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 1 ¦ 0 ¦ ++
Чтение состояния ++ прерывания ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 1 ¦ 0 ¦ 0 ¦ 0 ¦ ++
Определить ++ параметры ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 1 ¦ 1 ¦ ++
Чтение ++ состояния ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 1 ¦ 0 ¦ 0 ¦ накопителя ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Поиск ++ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 1 ¦ 1 ¦ 1 ¦ 1 ¦ ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Байты параметров, которые должны следовать за командами и байты результата, которые процессор должен считать после выполнения команды:
Команда Байты Байты параметров результата Чтение данных C, H, R, N, EOT, ST0, ST1, ST2, EOT, GPL, DTL C, H, R, N Чтение удаленных данных
Запись данных
Запись удаленных данных
Чтение данных с дорожки
Сканирование до "равно"
Сканирование до "меньше" или "равно"
Сканирование до "больше" или "равно"
Форматирование N, SC, GPL, D ST0, ST1, ST2, дорожки C, H, R, N
Чтение индексных отсутствуют ST0, ST1, ST2, данных C, H, R, N
Инициализация отсутствуют отсутствуют
Чтение состояния отсутствуют ST0, PCN прерывания
Определить 1 байт: отсутствуют параметры мл. тетрада - HUT ст. тетрада - SRT 2 байт: бит 0 - ND биты 1-7 - HLT
Чтение состояния отсутствуют ST3 накопителя
Поиск C отсутствуют
предназначен для обработки до восьми
Программируемый контроллер прерываний 8259 (отечественный аналог - КР1810ВН59А) предназначен для обработки до восьми приоритетных уровней прерываний. Возможно каскадирование микросхем, при этом общее число уровней прерываний будет достигать 64.
Контроллер 8259 имеет несколько режимов работы, которые устанавливаются программным путем. В персональных компьютерах XT и AT за первоначальную установку режимов работы микросхем 8259 отвечает BIOSBIOS. У программиста скорее всего не возникнет потребность перепрограммировать контроллер - это небезопасно, так как неправильное программирование контроллера приведет к нарушению логики работы всей системы.
Однако часто возникает необходимость изменения текущего режима работы (запрет или разрешение прерываний определенного или всех уровней, обработка конца прерывания) или опроса состояния внутренних регистров контроллера. Для этого необходимо ознакомиться со справочными данными на микросхему 8259, где детально описано как первоначальное прогрммирование контроллера, так и управление им во время работы.
Каждому приоритетному уровню прерывания микросхема ставит в соответствие определенный, задаваемый программно номер прерывания. В разделе книги, посвященном особенностям обработки аппаратных прерываний, приводится такое соответствие для машин типа XT и AT.
Если контроллеры 8259 каскадированы, то ведомой микросхеме присваивается код (выдачей в микросхему соответствующего командного слова). Этот код равен номеру входа IRQ ведущей микросхемы, с которым соединен выход запроса прерывания INT ведомой микросхемы. Внутри микросхемы приоритет зависит от номера IRQ и задается программно. Для компьютеров XT и AT самым высоким приоритетом внутри группы, обслуживаемой каждым контроллером, является вход IRQ0. Однако возможно программное изменение приоритетов в рамках так называемого приоритетного кольца. При этом дно приоритетного кольца имеет самый низкий приоритет.
Приведем возможные варианты задания приоритетов:
Вход | Уровни приоритета |
IRQ0 | 7 6 5 4 3 2 1 0 |
IRQ1 | 0 7 6 5 4 3 2 1 |
IRQ2 | 1 0 7 6 5 4 3 2 |
IRQ3 | 2 1 0 7 6 5 4 3 |
IRQ4 | 3 2 1 0 7 6 5 4 |
IRQ5 | 4 3 2 1 0 7 6 5 |
IRQ6 | 5 4 3 2 1 0 7 6 |
IRQ7 | 6 5 4 3 2 1 0 7 |
Наиболее высокий приоритет у входа IRQ с обозначением 0 приоритетного кольца, наиболее низкий - с обозначением 7.
Для обработки прерываний контроллер имеет несколько внутренних регистров. Это регистр запросов прерываний IRR, регистр обслуживания прерываний ISR, регистр маски прерываний IMR. В регистре IRR хранятся запросы на обслуживание прерываний от аппаратуры. После выработки сигнала прерывания центральному процессору соответствующий разряд регистра ISR
устанавливается в единичное состояние, что блокирует обслуживание всех запросов с равным или более низким приоритетом. Устранить эту блокировку можно либо сбросом соответствующего бита в ISR, либо командой специального маскирования.
Имеется два типа команд, посылемых программой в контроллер 8259 - команды инициализации и команды операции. Возможны следующие операции:
индивидуальное маскирование запросов прерывания;
специальное маскирование обслуженных запросов;
установка статуса уровней приоритета (по установке исходного состояния, по обслуженному запросу, по указанию);
операции конца прерывания (обычный конец прерывания, специальный конец прерывания, автоматический конец прерывания);
чтение регистров IRR, ISR, IMR.
Мы не будем подробно описывать команды инициализации контроллера 8259, так как программистам они скорее всего не понадобятся. Желающих разобраться во всех тонкостях задания начального режима работы контроллера прерываний мы отсылаем к справочной литературе по микросхеме 8259 или ее отечественному аналогу.
Рассмотрим команды операций. Существуют три типа команд операций:
Маскирование запросов прерывания.
Команды обработки конца прерывания.
Опрос регистров и специальное маскирование.
Байты команды маскирования запросов прерывания выводятся соответственно в порты 21h
и A1h для первого и второго контроллера 8259 компьютера AT. Команды операций второго и третьего типа используют порты с адресами 20h
и A0h.
Маскирование запросов прерываний мы уже описывали в главе, посвященной прерываниям. Для маскирования какого-либо уровня прерывания надо записать в регистр маски IMR по адресу 21h
или A1h единицу в соответствующий разряд регистра.
Команды обработки конца прерывания приведем в виде таблицы:
Биты байта команды D7 D6 D5 D4 D3 D2 D1 D0 |
Описание |
0 0 1 0 0 0 0 0 | Обычный конец прерывания. |
0 1 1 0 0 B2 B1 B0 | Специальный конец прерывания, B0...B2 - двоично-десятичный код сбрасываемого разряда в регистре обслуживания прерывания ISR. |
1 0 1 0 0 X X X | Циклический сдвиг уровней приоритета с обычным концом прерывания. Дно приоритетного кольца устанавливается по обслуженному запросу. |
1 1 1 0 0 B2 B1 B0 | Циклический сдвиг уровней приоритета со специальным концом прерывания, B0...B2 - двоично-десятичный код дна приоритетного кольца. |
1 0 0 0 0 X X X | Разрешение вращения уровней приоритета. |
0 0 0 0 0 X X X | Сброс разрешения вращения уровней приоритета. |
1 1 0 0 0 B2 B1 B0 | Циклический сдвиг уровней приоритета без завершения прерывания, B0...B2 - двоично-десятичный код дна приоритетного клоьца. |
Биты байта команды D7 D6 D5 D4 D3 D2 D1 D0 |
Описание |
0 0 0 0 1 1 X X | Установка режима опроса. |
0 0 0 0 1 0 1 1 | Разрешение чтения регистра ISR. |
0 0 0 0 1 0 1 0 | Разрешение чтения регистра IRR. |
0 1 1 0 1 0 0 0 | Разрешение триггера специального маскирования. |
0 1 0 0 1 0 0 0 | Сброс триггера специального маскирования. |
Команда специального конца прерывания устанавливает в нулевое состояние тот разряд ISR, номер которого указан в разрядах B0...B2
команды.
Команда циклического сдвига уровней приоритета с обычным концом прерывания устаналивает в ноль разряд ISR, соответствующий последнему обслуженному запросу, и этому же номеру запроса присваивается низший уровень приоритета.
Аналогично работает команда циклического сдвига уровней приоритета со специальным концом прерывания, только низший уровень приоритета присваивается тому входу IRQ, номер которого указан в разрядах B0...B2 команды.
Команда циклического сдвига уровней приоритета устанавливает статус уровней приоритета без выполнения операции конца прерывания. Разряды B0...B2 указывают дно приоритетного кольца.
После выполнения команд разрешения чтения регистров ISR или IRR при выполнении команды ввода из порта 20h и A0h считывается соответственно содержимое регистров ISR и IRR. Для получения содержимого регистра IMR
необходимо выполнить чтение портов с адресами соответственно 21h и A1h.
Команда разрешения триггера специального маскирования блокирует действие тех разрядов ISR, которые замаскированы командой типа 1 (маскирования индивидуальных приоритетных уровней запроса прерывания). Специальное маскирование используется для обслуживания такого запроса, который блокируется старшим или равным по уровню приоритета обслуженным запросом, хранящимся в ISR, не сбрасывая последний.
Чтение регистров ISR и IRR может испльзоваться резидентными программами при проверке возможности своей активизации - можно проверить, не выполняется ли в настоящий момент обработка какого-нибудь прерывания, которая может конфликтовать с действиями резидентной программы.
Логическая структура диска в DOS
2.1.
2.2.
2.3.
2.4.
2.5.
До сих пор при работе с дисками мы не обращались за помощью к DOS, выполняя все дисковые операции либо на уровне команд ввода/вывода, либо на уровне функций BIOS. Операционная система предоставляет намного более удобные средства для работы с диском, чем обращение к отдельным секторам по их номеру, номерам дорожки и головки. К сожалению, некоторые операции можно выполнить только с помощью функций BIOS или даже только с помощью непосредственного программирования контроллера дисковода.
Если возможности DOS по обслуживанию диска вас устраивают, то лучше пользоваться именно функциями DOS - вам не придется заботиться о многих мелочах и вы будете застрахованы от некоторых ошибок. Есть и другие причины предпочтительного использования функций DOS - ваша программа будет меньше зависеть от типа и конфигурации конкретной машины, так как дисковые драйверы DOS скроют от вас многие детали и особенности конкретной системы.
Мы будем изучать возможности DOS по управлению дисковой подсистемой от простых функций ко все более сложным, постепенно вводя все необходимые определения.
MS-DOS версии 5.0
7.6.1.
7.6.2.
7.6.3.
7.6.4.
7.6.5.
7.6.6.
7.6.7.