4. Использование функций IOCTL

В главе предыдущего тома "Библиотеки системного программиста", посвященной драйверам, мы рассказывали о функции управления вводом/выводом IOCTL - функции 44h прерывания INT 21h . Эта функция предоставляет широкие возможности по управлению устройствами посредством обмена управляющей информацией с драйверами устройств.

В этой главе мы вновь вернемся к функциям IOCTL для того чтобы рассказать об их использовании при работе с дисковой системой компьютера.

Мы покажем, как использовать функцию 44h прерывания INT 21h для извлечения разнообразной информации об открытых файлах по их идентификатору, для определения момента достижения конца файла, для получения информации о НГМД и НМД и для выполнения таких низкоуровневых операций, как форматирование дорожки диска, чтение или запись секторов диска и т. д.

Напомним, при вызове функции 44h регистр AL содержит код выполняемой подфункции. Для подфункции 0Dh (Generic IO Control) в регистре CL должен находиться код выполняемой операции.

4.1. Получение различной информации

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

Состояние открытого файла и конфигурация устройства

С помощью подфункции 00h, описанной в предыдущем томе, можно получить информацию об открытом файле или о конфигурации устройства по его идентификатору.

Функция возвращает в регистре DX слово конфигурации устройства, которое имеет следующий формат:

Бит Значение
0 1 - Это устройство является стандартным устройством ввода
1 1 - Стандартное устройство вывода
2 1 - NUL-устройство
3 1 - Часы
4 1 - Специальное устройство
5 1 - двоичный режим работы;
0 - режим ASCII
6 0 - при чтении достигнут конец файла
7 1 - это слово информации относится к устройству (идентификатор относится к устройству);
0 - слово информации относится к файлу
8-10 Зарезервировано
11 1 - Устройство поддерживает команды открытия/закрытия
12 1 - Сетевое устройство
13 1 - Устройство поддерживает вывод до состояния занятости
14 1 - Устройство может обрабатывать управляющие строки IOCTL , посылаемые подфункциями 2, 3, 4, 5 функции 44h. Подфункция 1 функции 44 h может только прочитать, но не установить этот бит
15 Зарезервировано

Если при вызове этой подфункции регистр BX содержит идентификатор файла, формат информации, получаемой в регистре DX, будет следующий:

Бит Значение
0-5 Номер дискового устройства (0 - А:, 1 - В: и т. д.)
6 0 - была запись в выходной файл
7 1 - это слово информации относится к устройству (так как данный идентификатор относится к устройству);
0 - слово информации относится к файлу
8-11 Зарезервировано
12 Сетевое устройство
13-14 Зарезервировано
15 1 - файл является удаленным (при работе в сети)

Определение момента достижения конца файла

Подфункция 06h функции 44h прерывания INT 21h поможет вам определить момент достижения конца файла или готовность устройства посимвольной обработки.

Для проверки условия "Конец файла" или готовности устройства можно использовать следующую функцию:

/**
*  heof
*
*  Проверить условие "Конец файла"
*
*  Функция позволяет проверить факт достижения
*  конца файла или готовность устройства
*
*  int heof(int handle);
*
*  handle - идентификатор файла или устройства,
*           для которого необходимо получить
*           информацию о состоянии
*
* Возвращаемо значение;
*  0      - конец файла не достигнут (для файла),
*           устройство готово (для устройства);
*
*  1      - достигнут конец файла (для файла),
*           устройство не готово (для устройства);
*
*  -1     - произошла ошибка.
**/
int heof(int handle) 
{
  union REGS reg;

  // Заполняем регистровые структуры для вызова
  // прерывания DOS INT 21h. Код используемой
  // подфункции - 06h
  reg.x.ax = 0x4406;
  reg.x.bx = handle;

  // Вызываем прерывание
  intdos(&reg, &reg);

  // Проверяем флаг переноса
  if(reg.x.cflag == 0) 
  {
    // Если флаг переноса сброшен в 0, ошибок нет.
    if(reg.h.al == 0) return(1);
    else return(0);
  }

  // Если флаг переноса установлен в 1, возвращаем
  // признак ошибки
  else return(-1);
}

Вы можете использовать эту функцию аналогично функции eof().

Расположение открытого файла или устройства

Подфункция 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.

Эта подфункция не предназначена для работы с сетевыми устройствами.

4.2. Общее управление устройством GENERIC IOCTL

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

При вызове функции вы должны загрузить регистры процессора следующим образом:

Регистр Содержание
AH 44h
AL 0Dh
BL Номер устройства НМД или НГМД (0 - текущий диск, 1 - А: и т. д.)
CH Код категории устройства: 08h - дисковое устройство
CL Операция:
40h - установить параметры устройства;
60h - получить параметры устройства;
41h - записать дорожку;
61h - прочитать дорожку;
42h - форматировать дорожку;
62h - проверить дорожку
DS:DX Указатель на блок параметров

Если функция выполнилась без ошибки, флаг переноса CF сброшен. В противном случае этот флаг установлен, при этом регистр AX содержит код ошибки.

Через регистры DS:DX функции необходимо передать адрес подготовленного блока параметров, формат которого зависит от выполняемой операции. Опишем этот формат для наиболее важных операций.

Смещение Размер Содержимое поля
0 1 Специальные функции:Бит 0:Если установить этот бит в операции с кодом 60h, можно извлечь текущий BPB . Результат аналогичен выполнению команды драйвера с кодом 2 (построить BPB). Если этот бит сброшен, надо извлечь BPB, используемый по умолчанию.Бит 1:Если этот бит установлен, функция игнорирует все поля в блоке параметров, кроме поля описания физической структуры дорожки на данном устройстве.Бит 2:Если этот бит установлен, все секторы на дорожке имеют одинаковый размер
1 1 Тип устройства, возвращаемый драйвером:

0 - НГМД емкостью 320 или 360 Кбайт с диаметром 5,25";
1 - НГМД емкостью 1,2 Мбайт и диаметром 5,25";
2 - НГМД емкостью 720 Кбайт и диаметром 3,5";
3 - НГМД диаметром 8" нормальной плотности;
4 - НГМД диаметром 8" двойной плотности;
5 - жесткий диск;
6 - накопитель на магнитной ленте;
7 - НГМД емкостью 1,44 Мбайт и диаметром 3,5", а также прочие дисковые устройства
2 2 Атрибуты устройства, возвращаемые драйвером. В этом поле используются только два младших бита.
Бит 0 - признак возможности замены среды носителя данных (0 - заменяемая, 1 - не заменяемая).Бит 1 - признак наличия аппаратного контроля замены дискеты (1 - контроль выполняется, 0 - контроль не выполняется). Остальные биты зарезервированы и должны содержать 0
4 2 Максимальное количество дорожек на физическом устройстве. Это поле устанавливается драйвером
6 1 Тип среды носителя данных. Используется для устройств, поддерживающих несколько типов носителей данных. Например, если в НГМД высокой плотности установлена дискета высокой плотности, тип равен 0, а если установлена дискета двойной плотности - 1
7 31 BPB для устройства. Если бит 0 поля специальных функций сброшен, то в этом поле находится новый BPB для устройства. Если бит 0 установлен, драйвер устройства возвращает BPB для всех последующих запросов на построение BPB
38 ? Таблица разметки дорожки, имеет переменную длину

Таблица разметки дорожки начинается с двухбайтового слова, содержащего общее количество секторов на дорожке. Затем для каждого сектора в таблице находится по два двухбайтовых слова, содержащих номер сектора (1, 2 и т. д.) и размер сектора. То есть для каждого сектора в таблице содержится два слова.

Если в поле "специальные функции" бит 2 установлен в 1, размеры всех секторов должны быть одинаковыми.

Смещение Размер Содержимое поля
0 1 Специальные функции (это поле всегда содержит 0)
1 2 Номер головки
3 2 Номер дорожки
5 2 Номер начального сектора (нумерация секторов, в отличие от нумерации головок и дорожек начинается с 0)
7 2 Общее количество секторов на дорожке, уменьшенное на единицу
9 4 Дальний указатель на буфер обмена с диском, в который помещается считываемая информация или откуда берется записываемая информация
Смещение Размер Содержимое поля
0 1 Специальные функции. Для этой операции определен только бит 0. Перед вызовом команды значение, равное 0, требуется для форматирования дорожки. Если этот бит установлен в 1, то проверяется возможность использования заданного формата дорожки. Если после выполнения команды значение бита равно 0, то заданный формат дорожки и заполненную таблицу разметки дорожки можно использовать. Если значение бита 0 равно 1, то указанный формат дорожки не поддерживается
1 2 Номер головки для форматирования или проверки
3 2 Номер дорожки для форматирования или проверки

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

После выполнения операции программа должна восстановить первоначальные параметры устройства, выполнив операцию с кодом 40h.

4.3. Примеры использования функций GENERIC IOCTL

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

Программа FMTIOCTL

Приведем пример программы FMTIOCTL (листинг 4.1), иллюстрирующей применение функций общего управления GENERIC IOCTL . Эта программа выполняет стандартное форматирование двадцатой дорожки диска А:.

Листинг 4.1. Файл fmtioctl\fmtioctl.cpp

#include <dos.h>
#include <stdio.h>
#include <conio.h>
#include <malloc.h>
#include <errno.h>

typedef struct _EBPB_
{
  unsigned sectsize;
  char clustsize;
  unsigned ressecs;
  char fatcnt;
  unsigned rootsize;
  unsigned totsecs;
  char media;
  unsigned fatsize;
  unsigned seccnt;
  unsigned headcnt;
  unsigned hiddensec_low;
  unsigned hiddensec_hi;
  unsigned long drvsecs;
} EBPB;

typedef struct _TRK_LY_
{
  unsigned no;
  unsigned size;
} TRK_LY;

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;

typedef struct _DPB_FORMAT_
{
  char spec;
  unsigned head;
  unsigned track;
} DPB_FORMAT;

int 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'))
    return(-1);

  // Заказываем память для блока
  // параметров устройства
  dbp = (DPB far*)farmalloc(sizeof(DPB));

  // Заказываем память для блока параметров
  // устройства, который будет
  // использован для форматирования
  dbp_f = (DPB_FORMAT far*)
    farmalloc(sizeof(DPB_FORMAT));

  if(dbp == NULL || dbp_f == NULL)
  {
    printf("\nМало памяти");
    return(-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(&reg, &reg, &segreg);

  // Проверяем результат выполнения операции
  if(reg.x.cflag != 0)
  {
    printf("\nОшибка: %d",reg.x.ax);
    return(-1);
  }

  // Заполняем блок парметров для форматирования.
  // Байт специальных функций содержит значение,
  // равное 5. Это означает, что:
  //    - используется текущий блок параметров BIOS  BPB ;
  //    - используются все поля в блоке парметров устройства;
  //    - все секторы на дорожке имеют одинаковый размер
  dbp->spec = 5;

  // Считываем из BPB  количество секторов на дорожке
  sectors = dbp->bpb.seccnt;

  // Подготавливаем таблицу, описывающую формат дорожки

  // Записываем количество секторов на дорожке
  dbp->trkcnt = sectors;

  // Для каждого сектора на дорожке в таблицу
  // записываем его номер и размер.
  // Заметьте, что записывается размер сектора
  // в байтах, а не код размера, как это делается
  // при форматировании с помощью
  // функции 05h прерывания INT 13h
  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(&reg, &reg, &segreg);

  // Проверяем результат выполнения операции
  if(reg.x.cflag != 0)
  {
    printf("\nОшибка: %d",reg.x.ax);
    return(-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(&reg, &reg, &segreg);

  // Проверяем результат выполнения операции
  if(reg.x.cflag != 0)
  {
    printf("\nОшибка: %d", reg.x.ax);
    return(-1);
  }

  // Если указанный формат дорожки поддерживается,
  // поле специальных функций будет содержать 0.
  // Проверяем это
  if(dbp_f->spec != 0)
  {
    printf("\nФормат дорожки не поддерживается");
    return(-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(&reg, &reg, &segreg);

  // Проверяем резултат выполнения операции
  if(reg.x.cflag != 0)
  {
    printf("\nОшибка: %d", reg.x.ax);
    return(-1);
  }

  // Освобождаем память
  farfree(dbp);
  farfree(dbp_f);

  return(0);
}

После запуска программа форматирования читает текущие параметры для диска А:, формирует структуру дорожки и устанавливает параметры для выполнения операции форматирования. Затем программа проверяет возможность использования указанной структуры дорожки и выполняет форматирование.

Программа CPYIOCTL

Теперь приведем программу CPYIOCTL (листинг 4.2), копирующую содержимое двух первых секторов нулевой дорожки (головка 0) в первые два сектора двадцатой дорожки.

Листинг 4.2. Файл cpyioctl\cpyioctl.cpp

#include <dos.h>
#include <stdio.h>
#include <conio.h>
#include <malloc.h>
#include <errno.h>

typedef struct _DPB_WR_
{
  char spec;
  unsigned head;
  unsigned track;
  unsigned sector;
  unsigned sectcnt;
  void _far *buffer;
} DPB_WR;

char buf[2000];

int main(void)
{
  union REGS reg;
  struct SREGS segreg;
  DPB_WR far *dbp_wr;
  int sectors, i;

  printf("\nПрограмма уничтожит содержимое"
    "\n20-й дорожки диска А:."
    "\nЖелаете продолжить? (Y,N)\n");

  // Ожидаем ответ и анализируем его
  i = getch();
  if((i != 'y') && (i != 'Y'))
    return(-1);

  // Заказываем память для блока параметров
  // устройства, который будет
  // использован для чтения и записи
  dbp_wr = (DPB_WR far*)farmalloc(sizeof(DPB_WR));

  if(dbp_wr == NULL)
  {
    printf("\nМало памяти");
    return(-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(&reg, &reg, &segreg);

  // Проверяем результат выполнения операции
  if(reg.x.cflag != 0)
  {
    printf("\nОшибка: %d", reg.x.ax);
    return(-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(&reg, &reg, &segreg);

  // Проверяем результат выполнения операции
  if(reg.x.cflag != 0)
  {
    printf("\nОшибка: %d", reg.x.ax);
    return(-1);
  }

  // Освобождаем память
  farfree(dbp_wr);

  return(0);
}

Программа пользуется текущими параметрами диска А:, поэтому операции чтения текущих параметров и записи новых параметров не используются.

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