В разделе книги, посвященным драйверам, мы рассказывали о функции управления вводом/выводом IOCTL - функции 44h прерывания INT 21h. Эта функция предоставляет широкие возможности по управлению устройствами ввода/вывода посредством обмена управляющей информацией с драйверами устройств.
В этом разделе мы вновь вернемся к функциям IOCTL для того, чтобы рассказать об их использовании при работе с дисковой подсистемой.
Мы покажем, как использовать функцию 44h прерывания INT 21h для извлечения разнообразной информации об открытых файлах по их файловому индексу, для определения момента достижения конца файла, для получения информации об дисководах и для выполнения таких низкоуровневых операций, как форматирование дорожки диска, чтение/запись секторов диска и т.д.
Напомним, при вызове функции 44h регистр AL содержит код выполняемой подфункции. Для подфункции 0Dh (Generic IO Control) в регистре CL должен находиться код выполняемой операции.
С помощью подфункции 00h можно получить информацию об открытом файле или устройстве по файловому индексу.
Для удобства работы с этой подфункцией мы подготовили следующую программу:
/** *.Name get_devi * *.Title Получить информацию об устройстве * *.Descr Функция получает информацию о файле * или об устройстве по его файловому индексу. * * *.Params int get_devi(int handle, int *info); * * handle - файловый индекс для * которого необходимо получить * информацию * * info - указатель на слово, в которое * должна быть записана информация * *.Return 0 - если нет ошибок; * Код ошибки - если произошла ошибка. **/ #include <stdio.h> #include <dos.h> #include "sysp.h" int get_devi(int handle, int *info) { union REGS reg; // Заполняем регистровые структуры для вызова // прерывания DOS INT 21h. Код используемой // подфункции - 00h. reg.x.ax = 0x4400; reg.x.bx = handle; // Вызываем прерывание intdos(®, ®); // Проверяем флаг переноса if(reg.x.cflag == 0) { // Если флаг переноса сброшен в 0, ошибок нет. // Записываем информацию по адресу *info *info = reg.x.dx; return(0); } // Если флаг переноса установлен в 1, возвращаем // код ошибки else return(reg.x.ax); }
Приведенная выше функция принимает в качестве первого параметра файловый индекс открытого файла или устройства и адрес слова, в который она запишет информацию об устройстве или файле, связанном с этим файловым индексом.
Вы можете использовать эту функцию так, как это сделано в следующем примере:
#include <dos.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> void main(int argc, char *argv[]); void main(int argc, char *argv[]) { int handle; int info; int rc; // Открываем файл или устройство с заданным именем handle = open(argv[1], O_RDONLY); // Если открыть невозможно, выводим сообщение // и завершаем работу программы if(handle == -1) { printf("Не могу открыть файл!\n"); exit(-1); } // Получаем информацию для файлового индекса handle rc = get_devi(handle, &info); if(rc != 0) { printf("Ошибка с кодом %d\n", rc); exit(-1); } printf("\nСлово информации об устройстве: %04X",info); close(handle); }
В качестве параметра при запуске этой программы попробуйте задавать имена файлов или имена устройств, например - CON, AUX, и т.д.
Мы уже описывали формат слова, содержащего информацию об устройстве или файле, для удобства приведем его еще раз.
Для устройства:
Бит |
Значение |
0 |
Это устройство является стандартным
устройством ввода. |
1 |
Стандартное устройство вывода. |
2 |
NUL-устройство. |
3 |
Часы. |
4 |
Специальное устройство. |
5 |
1 - двоичный режим работы; |
6 |
0 - при чтении достигнут конец файла. |
7 |
1 - это слово информации относится к
устройству (данный handle относится к устройству); |
8-10 |
Зарезервировано. |
11 |
1 - Устройство поддерживает команды
открытия/закрытия. |
12 |
Сетевое устройство (только для DOS версии
3.0 и более поздних версий). |
13 |
Устройство поддерживает вывод до
состояния занятости. |
14 |
Устройство может обрабатывать
управляющие строки IOCTL, посылаемые
подфункциями 2, 3, 4, 5 функции 44h. Этот бит
может быть только прочитан, его установка
подфункцией 1 функции 44h не производится. |
15 |
Зарезервировано. |
Для файла:
Бит |
Значение |
0-5 |
Номер дисковода (0-А:, 1-В: и т.д.). |
6 |
0 - Была запись в выходной файл. |
7 |
1 - это слово информации относится к
устройству (данный handle относится к устройству); |
8-11 |
Зарезервировано. |
12 |
Сетевое устройство (только для DOS версии
3.0 и более поздних версий). |
13-14 |
Зарезервировано. |
15 |
1 - Данный файл является удаленным
при работе в сети (только для DOS версии 3.0 и более
поздних версий). |
Подфункция 06h функции 44h прерывания INT 21h поможет вам определить момент достижения конца файла или готовность устройства посимвольной обработки.
Для проверки условия "Конец файла" или готовности устройства можно использовать следующую функцию:
/** *.Name heof * *.Title Проверить условие "Конец файла" * *.Descr Функция позволяет проверить факт достижения * конца файла или готовность устройства * *.Params int heof(int handle); * * handle - файловый индекс для * которого необходимо получить * информацию о состоянии * *.Return 0 - конец файла не достигнут (для файла), * устройство готово (для устройства); * * 1 - достигнут конец файла (для файла), * устройство не готово (для устройства); * * -1 - произошла ошибка. **/ #include <stdio.h> #include <dos.h> #include "sysp.h" int heof(int handle) { union REGS reg; // Заполняем регистровые структуры для вызова // прерывания DOS INT 21h. Код используемой // подфункции - 06h. reg.x.ax = 0x4406; reg.x.bx = handle; // Вызываем прерывание intdos(®, ®); // Проверяем флаг переноса if(reg.x.cflag == 0) { // Если флаг переноса сброшен в 0, ошибок нет. if(reg.h.al == 0) return(1); else return(0); } // Если флаг переноса установлен в 1, возвращаем // признак ошибки else return(-1); }
Вы можете использовать эту функцию аналогично функции eof(). В приведенном ниже примере программы, копирующей файлы, мы так и поступили:
#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, target, i; char *buffer; unsigned count; if(argc == 3) { // Открываем исходный копируемый файл if((source = open(argv[1], O_BINARY | O_RDONLY)) == - 1) { printf("\nОшибка при открытии исходного файла: %d", errno); exit(-1); } // Открываем выходной файл. При необходимости создаем // новый. Если файл уже существует, выводим на экран // запрос на перезапись содержимого существующего файла target = 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')) target = open(argv[2], O_BINARY | O_WRONLY | O_CREAT | O_TRUNC, S_IREAD | S_IWRITE); } // Если выходной файл открыть невозможно, выводим // сообщение об ошибке и завершаем работу программы if(target == -1){ printf("\nОшибка при открытии выходного файла: %d", errno); exit(-1); } // Будем читать и писать за один раз 10000 байтов count = 10000; // Заказываем буфер для передачи данных if((buffer = (char *)malloc(count)) == NULL) { printf("\nНедостаточно оперативной памяти"); exit(-1); } // Копируем исходный файл while(!heof(source)) { // Читаем count байтов в буфер buffer if((count = read(source, buffer, count)) == -1) { printf("\nОшибка при чтении: %d", errno); exit(-1); } // Выполняем запись count байтов из буфера в выходной файл if((count = write(target, buffer, count)) == - 1) { printf("\nОшибка при записи: %d", errno); exit(-1); } } // Закрываем входной и выходной файлы close(source); close(target); // Освобождаем память, заказанную под буфер free(buffer); } // Если при запуске программы не были указаны // пути для входного или выходного файла, // выводим сообщение об ошибке else printf("\n" "Задайте пути для исходного" " и результирующего файлов!\n"); }
Подфункция 0Ah функции 44h прерывания INT 21h поможет программе, работающей в сети, определить расположение открытого файла или устройства - на рабочей станции или на сервере.
Перед вызовом запишите в регистр BX файловый индекс проверяемого файла или устройства.
После возврата из подфункции регистр DX содержит слово атрибутов для файла или устройства. Если самый старший бит в этом слове равен 0, то файл или устройство является локальным и расположено на рабочей станции. Если же этот бит равен 1, то файл или устройство удаленное и находится на сервере (подключено к серверу, если проверяется устройство).
Обычно программы составляют таким образом, чтобы их работа не зависела от расположения. Но если такая информация вам когда-либо понадобится, вы можете воспользоваться подфункцйией 0Ah.
Аналогично для проверки расположения дисковода можно использовать подфункцию 09h.
Перед вызовом запишите в регистр BL код дисковода (0-текущий дисковод, 1 - А:, 2 - В:, и т.д.). Двенадцатый бит регистра DX после вызова этой функции покажет вам расположение дисковода: 0 - локальное, 1 - удаленное.
Для проверки возможности замены носителя данных в дисководе вы можете воспользоваться подфункцией 08h. Используя эту подфункцию, вы сможете отличить НГМД от НМД. Это может вам понадобиться, например, для операции форматирования, так как форматирование НГМД и НМД выполняется по-разному.
Перед вызовом подфункции 08h запишите код устройства в регистр BL (0 -текущий дисковод, 1 - А:, 2 - В:, и т.д.). Если носитель данных сменный, то после выполнения подфункции регистр AL будет содержать 0, в противном случае - 1.
Эта подфункция не предназначена для работы с сетевыми устройствами.
Подфункция 0Dh функции 44h прерывания INT 21h обеспечивает механизм взаимодействия между прикладным программным обеспечением и драйверами блочных устройств. Эта подфункция позволяет программам читать и изменять параметры устройств, предоставляет возможность выполнять аппаратно-независимое чтение, запись, форматирование и проверку дорожек диска.
Эта подфункция была уже нами описана в разделе, посвященном драйверам.
Для удобства приведем формат вызова этй подфункции еще раз:
Вызов:
Регистр |
Содержание |
AH |
44h |
AL |
0Dh |
BL |
Номер дисковода (0 - текущий дисковод,
1 - дисковод А: и т.д.) |
CH |
Код категории устройства: 08h -
дисковое устройство |
CL |
Операция: |
DS:DX |
Указатель на блок параметров. |
Возврат без ошибки:
Регистр |
Содержание |
CF |
0 |
Возврат с ошибкой:
Регистр |
Содержание |
CF |
1 |
AX |
Код ошибки |
Формат блока параметров зависит от выполняемой операции:
CL = 40h/60h (получить/установить параметры устройства)
Смещение |
Размер |
Содержимое поля |
(0) |
1 |
Специальные функции: |
(+1) |
1 |
Тип устройства, возвращаемый драйвером: |
(+2) |
2 |
Атрибуты устройства, возвращаемые
драйвером. В этом поле используются только два
младших бита. |
(+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, говорит о
том, что все сектора на этой дорожке имеют
одинаковый размер |
Таблица разметки дорожки начинается с двухбайтового слова, содержащего общее количество секторов на дорожке. Затем для каждого сектора в таблице находится по два двухбайтовых слова, содержащих номер сектора (1, 2 и т.д.) и размер сектора. То есть для каждого сектора в таблице содержится два слова.
Если в поле "специальные функции" бит 2 установлен в 1, размеры всех секторов должны быть одинаковыми.
CL = 41h/61h (записать/прочитать дорожку)
Смещение |
Размер |
Содержимое поля |
(0) |
1 |
Специальные функции (это поле всегда
содержит 0) |
(+1) |
2 |
Номер головки |
(+3) |
2 |
Номер дорожки |
(+5) |
2 |
Номер начального сектора (нумерация
секторов, в отличие от нумерации головок и
дорожек начинается с 0) |
(+7) |
2 |
Общее количество секторов на дорожке,
уменьшенное на единицу |
(+9) |
4 |
FAR-указатель на буфер обмена с
диском, в который помещается считываемая
информация или откуда берется записываемая
информация |
CL = 42h/62h (форматировать/проверить дорожку)
Смещение |
Размер |
Содержимое поля |
(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); }