[Назад] [Содеожание] [Дальше]

MS-DOS для программиста

© Александр Фролов, Григорий Фролов
Том 4, М.: Диалог-МИФИ, 1993, 253 стр.

3. Файловая система DOS

Теперь, после того как мы познакомились с логической структурой диска, можно приступить к изучению одной из самых развитых систем MS-DOS - файловой системы .

Сервис файловой системы доступен программе через прерывание INT 21h . Многочисленные функции этого прерывания, относящиеся к файловой системе, можно разбить на группы:

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

Функции второй группы выполняют все необходимые операции с каталогами - создание, переименование, уничтожение каталогов, изменение текущего каталога.

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

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

Второй класс использует идентификаторы файла (file handle). Этот класс функций впервые появился в MS-DOS версии 2.0.

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

Первые пять идентификаторов зарезервированы операционной системой:

Идентификатор Описание
0 Стандартное устройство ввода (клавиатура)
1 Стандартное устройство вывода (консоль)
2 Стандартное устройство для вывода сообщений об ошибках (консоль)
3 Стандартное устройство последовательного ввода/вывода, обычно асинхронный адаптер COM1
4 Стандартное печатающее устройство (обычно параллельный порт LPT1)

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

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

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

3.1. Получение справочной информации

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

Текущий диск и текущий каталог

В любой момент времени программа может определить текущий диск или текущий каталог, а также сменить текущий диск или текущий каталог. Для этого она должна использовать специальные функции прерывания INT 21h .

Для того чтобы узнать номер текущего диска, программа может воспользоваться функцией 19h:

На входе: AH 19h
На выходе: AL Номер текущего устройства НГМД или НМД (0 - А:, 1 - В:, и т. д.).

Для установки текущего диска можно использовать функцию 0Eh, которая имеет следующие параметры вызова:

На входе: AH 0Eh
DL Номер устройства НГМД или НМД (0 - А:, 1 - В:, и т. д.)
На выходе: AL Общее количество дисковых устройств в системе. Эта величина соответствует параметру LASTDRIVE из файла CONFIG.SYS

Для того чтобы узнать текущий каталог, вы можете воспользоваться функцией 47h:

На входе: AH 47h
DL Номер устройства НГМД или НМД (0 - текущий, 1 - А:, 2 - В:, и т. д.)
DS:SI Адрес буфера для записи пути текущего каталога
На выходе: AX Код ошибки, если установлен флаг переноса CF

Буфер должен иметь размер не менее 64 байт. Функция 47h возвращает текущий каталог в формате ASCIIZ (то есть строку, закрытую двоичным нулем, например: "path\dirname",0) без символа, обозначающего диск. Если текущим является корневой каталог, регистровая пара DS:SI будет указывать на нулевую строку (состоящую из одного двоичного нуля).

Функция 3Bh предназначена для установки текущего каталога:

На входе: AH 3Bh
DL Номер устройства НГМД или НМД (0 - текущий, 1 - А:, 2 - В:, и т. д.)
DS:DX Адрес буфера, содержащего путь к каталогу, который должен стать текущим
На выходе: AX Код ошибки, если установлен флаг переноса CF

Буфер может иметь максимальный размер 64 байт. Он должен содержать путь в формате ASCIIZ. Строка не должна содержать символ, обозначающий диск. Если текущим должен стать корневой каталог, строка должна состоять только из одного двоичного нуля.

Определение размера кластера и сектора

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

Информация о таблице размещения файлов FAT для текущего диска может быть получена с помощью функции 1Bh прерывания INT 21h , имеющего следующие параметры вызова:

На входе: AH 1Bh
На выходе: DS:BX Адрес первого байта FAT . Это байт идентификации среды носителя данных, соответствует байту media в блоке параметров BIOS
DX Общее количество кластеров на диске
AL Количество секторов в одном кластере
CX Количество байт в одном секторе

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

Для старых версий MS-DOS регистровая пара DS:BX указывала на FAT , считанный в память. Более поздние версии операционной системы могут содержать по этому адресу только часть таблицы размещения файлов .

Для получения аналогичной информации не о текущем, а о любом диске, используйте функцию 1Ch. Эта функция полностью аналогична предыдущей, за исключением того, что в регистре DL должен быть указан номер НГМД или НМД: 0 - текущий, 1 - А:, 2 - В: и т. д.

Определение размера свободного пространства

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

На входе: AH 36h
DL Номер устройства НГМД или НМД (0 - текущий, 1 - А:, 2 - В:, и т. д.)
На выходе: AX Количество секторов в кластере или 0FFFFh, если был задан неправильный номер устройства
BX Количество свободных кластеров на диске
CX Количество байт в одном секторе
DX Общее количество кластеров на диске

Эта функция возвращает в регистре AX число 0FFFFh, если вы неправильно указали номер устройства.

Блок управления устройством DDCB

При обсуждении векторной таблицы связи в предыдущем томе "Библиотеки системного программиста" мы рассказывали о блоках управления устройствами DDCB . Поле dev_cb векторной таблицы связи содержит дальний адрес цепочки этих блоков.

Для получения адреса блока DDCB можно воспользоваться недокументированной функцией 32h:

На входе: AH 32h
DL Номер устройства НГМД или НМД (0 - текущий, 1 - А:, 2 - В:, и т. д.)
На выходе: AL 0, если был задан правильный номер устройства;
0FFh, если был задан неправильный номер устройства
DS:BX Адрес блока DDCB

Для получения адреса блока DDCB текущего диска можно также воспользоваться недокументированной функцией 1Fh, которая имеет формат, аналогичный функции 32h, за исключением того, что для нее не надо задавать номер устройства в регистре DL.

Флаг прерывания

С помощью функции 33h программа может проверить или установить флаг прерывания при помощи комбинации клавиш <Ctrl+Break> и, кроме того, узнать номер диска, с которого выполнялась загрузка операционной системы:

На входе: AH 33h
AL Код операции:
0 - Проверить текущее состояние флага прерывания при помощи комбинации клавиш <Ctrl+Break> ;
1 - Установить флаг прерывания при помощи комбинации клавиш <Ctrl+Break>;
5 - Определить номер диска, который был использован для загрузки операционной системы
DL Значение флага прерывания при помощи комбинации клавиш <Ctrl+Break> операции с кодом 1:0 - запретить прерывание,1 - разрешить прерывание
На выходе: DL Текущее состояние флага прерывания при помощи комбинации клавиш <Ctrl+Break> для операции с кодом 0; Номер диска, использованного для загрузки операционной системы для операции 5 (1 - А:, 2 - В:, и т. д.)

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

Состояние флага прерывания при помощи комбинации клавиш <Ctrl+Break> влияет на возможность прервать выполнение программы. Если прерывание запрещено, MS-DOS проверяет эту комбинацию клавиш только при вызове функций стандартного ввода/вывода на консоль, принтер и последовательный порт. Если же прерывание разрешено, указанная комбинация клавиш проверяется и при вызове других функций MS-DOS. Если пользователь нажал комбинацию клавиш <Ctrl+Break>, операционная система выполняет прерывание INT 23h , которое завершает работу текущей программы.

Адрес области DTA

Функция 2Fh возвращает в регистровой паре ES:BX адрес текущей области DTA (Disk Transfer Area ), которая используется при поиске файлов в каталогах. Этот адрес необходим резидентным программам, о чем мы говорили в предыдущем томе "Библиотеки системного программиста".

Флаг проверки записи

Функция 54h позволяет программе узнать текущее состояние флага проверки записи информации на диск. В регистре AL эта функция возвращает текущее состояние флага.

Если содержимое регистра равно 1, после записи сектора операционная система считывает его для проверки. Разумеется, такая проверка снижает скорость работы программы. Если после вызова функции регистр AL содержит 0, проверка записи не выполняется.

Для установки флага проверки записи можно использовать функцию 2Eh. Перед вызовом функции в регистр AL необходимо занести новое значение флага проверки: 0 - проверка не нужна; 1 - должна выполняться проверка записанной информации.

Функции библиотеки Borland C++

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

_dos_getdiskfree

Функция _dos_getdiskfree использует функцию 36h для получения информации о диске. Файл dos.h содержит такое описание этой функции:

unsigned _dos_getdiskfree (unsigned drive,
  struct diskfree_t *diskspace);

Параметр drive задает номер используемого устройства: 0 - текущий, 1 - А:, и т. д.

Информация возвращается в структуре diskfree_t, которая определена также в файле dos.h:

struct diskfree_t
{
        unsigned total_clusters;
        unsigned avail_clusters;
        unsigned sectors_per_cluster;
        unsigned bytes_per_sector;
};

Поля этой структуры описаны ниже:

Поле Описание
unsigned total_clusters Общее количество кластеров на диске
unsigned avail_clusters Количество свободных кластеров
unsigned sectors_per_cluster Количество секторов, занимаемых одним кластером
unsigned bytes_per_sector Размер сектора в байтах

_dos_getdrive и _dos_setdrive

Для получения номера текущего диска и для установки номера текущего диска можно использовать, соответственно, функции _dos_getdrive и _dos_setdrive .

Функция _dos_getdrive имеет следующий прототип:

void _dos_getdrive (unsigned *drive);

Она пользуется функцией 19h для получения номера текущего диска, который записывается по адресу, задаваемому параметром drive. Значение 1 соответствует диску А:, 2 - В:, и т. д.

Функция _dos_setdrive предназначена для установки текущего диска и может быть использована для определения общего числа дисков в системе:

void _dos_setdrive (unsigned drive, unsigned *drivecount);

Параметр drive определяет текущий диск (1 - А:, и т. д.). В переменную, адрес которой передается через второй параметр, функция записывает общее количество логических дисков, установленных в системе. Функция _dos_setdrive использует функцию 0Eh прерывания INT 21h .

Программа DISKINF2

Для иллюстрации способов использования функций _dos_getdrive , _dos_setdrive и _dos_getdiskfree мы составили программу DISKINF2 (листинг 3.1).


Листинг 3.1. Файл diskinf2\diskinf2.cpp


#include <dos.h>
#include <bios.h>
#include <conio.h>
#include <stdio.h>

void main(void)
{
  struct diskfree_t dinfo;
  unsigned drive, drivecount;

  printf("\n"
    "\nОпределение параметров текущего логического диска"
    "\n  (C)Фролов А., 1995\n");

  // Определяем номер текущего диска
  _dos_getdrive (&drive);

  // Выводим на экран обозначение текущего диска
  printf("\nТекущий диск:                        %c:\n",
    'A' + drive - 1);

  // Вызываем функцию установки текущего диска.
  // Мы не изменяем текущий диск, вызов этой функции
  // нужен нам для определения количества установленных
  // в системе логических дисков
  _dos_setdrive (drive, &drivecount);

  // Получаем характеристики текущего диска
  _dos_getdiskfree (drive, &dinfo);

  printf("\nОбщее количество кластеров на диске: %u"
    "\nКоличество свободных кластеров:      %u"
    "\nКоличество секторов в кластере:      %u"
    "\nКоличество байт   в секторе:         %u"
    "\nРазмер свободного пространства в байтах: %ld"
    "\n",
    dinfo.total_clusters, dinfo.avail_clusters,
    dinfo.sectors_per_cluster, dinfo.bytes_per_sector,
    (long)dinfo.avail_clusters *
    dinfo.sectors_per_cluster *
    dinfo.bytes_per_sector );

    printf("\nКоличество логических дисков:        %d"
      "\n", drivecount);
}

3.2. Работа с каталогами

После форматирования логический диск содержит корневой каталог. Если диск форматируется как системный, в этом каталоге могут находится дескрипторы файлов операционной системы io.sys , msdos.sys , command.com .

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

Создание каталога

Для создания каталога используйте функцию 39h прерывания INT 21h :

На входе: AH 39h
DS:DX Адрес строки в формате ASCIIZ, содержащей путь создаваемого каталога
На выходе: AL Код ошибки, если был установлен флаг переноса CF

Строка, адрес которой передается в регистрах DS:DX, может содержать полный путь, состоящий из имени диска и имени каталога, в котором должен быть создан каталог, или она может состоять только из одного имени каталога. В последнем случае каталог создается в текущем каталоге на текущем диске.

Размер строки с именем каталога не должен превышать по длине 64 байта.

Удаление каталога

Удалить существующий каталог можно с помощью функции 3Ah:

На входе: AH 3Ah
DS:DX Адрес строки в формате ASCIIZ, содержащей путь к каталогу
На выходе: AL Код ошибки, если был установлен флаг переноса CF

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

Переименование каталога

Для изменения имени каталогов и файлов предназначена функция 56h:

На входе: AH 56h
DS:DX Адрес строки в формате ASCIIZ, содержащей старое имя каталога или файла
ES:DI Адрес строки в формате ASCIIZ, содержащей новое имя каталога или файла
На выходе: AL Код ошибки, если был установлен флаг переноса CF

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

Эта функция может перемещать файл из одного каталога в другой, если вы укажите разные пути. Каталоги перемещать нельзя, их можно только переименовывать.

Функции библиотеки Borland C++

Стандартная библиотека Borland C++ содержат несколько функций, предназначенных для работы с каталогами.

getcwd

Функция getcwd предназначена для определения текущего каталога. Прототип этой функции описан в файле direct.h:

char *getcwd (char *path, int n);

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

Если в качестве первого параметра указать NULL, функция динамически закажет буфер длиной n байт из области кучи и вернет его адрес. Эту память впоследствии необходимо будет освободить при помощи функции free.

Функция getcwd всегда возвращает указатель на буфер, содержащий текущий каталог.

mkdir , rmdir , chdir

Для создания и удаления каталогов, изменения текущего каталога имеются функции mkdir , rmdir , chdir .

Все эти функции имеют один параметр - путь каталога, который имеет тип (char *). В случае успешного выполнения операции функции возвращают 0, при ошибке - 1.

rename

Для переименования каталогов (и файлов) предназначена функция rename :

int rename (char *oldname, char *newname);

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

Функция может возвращать один из приведенных ниже кодов ошибки:

Код ошибки Описание
ENOENT Нет такого файла или каталога
EACCES Нет прав доступа
EXDEV Другой диск

Код ошибки EXDEV возвращается в том случае, когда программа указывает разные диски для старого и нового имен файлов или каталогов.

Важное замечание: если вы задаете полный путь в программе, составленной на С или С++, повторяйте символ '\' два раза в строке пути. Это нужно для того, чтобы избежать конфликта с форматом представления констант в языке С. Например:

ret_code = rename ("c:\\games","c:\\games_new");

Программа DIRCTL

Приведем исходный текст небольшой программы DIRCTL (листинг 3.2), использующий перечисленные выше функции.


Листинг 3.2. Файл dirctl\dirctl.cpp


#include <direct.h>
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>

void main(void)
{
  // Константа MAXPATH задает максимальный
  // размер пути для каталога
  char current_dir[MAXPATH];
  char test_dir[] = "TEST_DIR";

  // Запоминаем текущий каталог
  getcwd (current_dir, MAXPATH);

  // Пытаемся создать в текущем каталоге новый каталог
  if(!mkdir (test_dir))
  {
    // Если удалось создать каталог, делаем его текущим
    chdir (test_dir);
    printf("\nКаталог создан, для удаления нажмите"
      "\n клавишу <Enter>, для сохранения - '0'");
    if(getche() != '0')
    {
      // Для удаления только что созданного каталога
      // возвращаемся в каталог более высокого уровня
      chdir (current_dir);
      rmdir (test_dir);
    }
  }

  // Если каталог с таким именем уже существует или
  // произошла другая ошибка при создании каталога,
  // выводим сообщение о невозможности создания каталога.
  else
    printf("\nНе могу создать каталог!");
}

3.3. Поиск в каталогах

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

Это функции 4Eh и 4Fh.

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

Вначале вызывается функция 4Eh для поиска в каталоге файла, соответствующего образцу. В образце можно использовать символы "?" и "*", которые означают, соответственно, один любой символ и любое количество любых символов. Информация о найденном файле располагается в специальной области, распределенной каждой работающей программе - области DTA .

Затем для поиска остальных файлов, удовлетворяющих заданному ранее образцу, в цикле вызывается функция 4Fh. Условие завершения цикла - отсутствие в каталоге указанных файлов.

Функция 4Eh вызывается следующим образом:

На входе: AH 4Eh
CX Атрибуты файла, которые будут использованы при поиске. Будут найдены файлы, имеющие атрибут, заданный в регистре CX
DS:DX Адрес строки в формате ASCIIZ, содержащей путь каталога или файла
На выходе: AL Код ошибки, если был установлен флаг переноса CF

Функция 4Fh имеет следующие параметры вызова:

На входе: AH 4Fh
На выходе: AL Код ошибки, если был установлен флаг переноса CF

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

Для работы с областью DTA в составе MS-DOS имеются две функции. Это функция 2Fh, позволяющая получить адрес области DTA (она возвращает этот адрес в регистрах ES:BX), и функция 1Ah, предназначенная для установки своей области DTA (адрес новой области DTA должен быть указан в регистрах DS:DX).

Напомним, что по умолчанию область DTA занимает 128 байт в префиксе сегмента программы PSP со смещением 80h.

В случае успешного поиска функции 4Eh и 4Fh помещают в DTA информацию о найденных файлах в следующем формате:

Смещение Размер Содержимое
0 20 Зарезервировано
21 1 Атрибуты найденного файла
22 2 Поле времени последнего обновления файла
24 2 Поле даты последнего обновления файла
26 4 Длина файла
30 13 Имя файла и расширение в формате ASCIIZ

Номер начального кластера, распределенного файлу или каталогу, невозможно получить с помощью функций 4Eh и 4Fh.

Стандартная библиотека Borland C++ содержат две функции, предназначенные для сканирования каталогов. Это _dos_findfirst и _dos_findnext .

Приведем прототипы этих функций, описанные в файле dos.h:

int _dos_findfirst (char *pattern, struct find_t *found,
  unsigned attr);
int _dos_findnext (struct find_t *found);

В этих функциях параметр pattern определяет образец для поиска файлов, параметр attr (атрибуты файла) используется в качестве дополнительного критерия поиска. Параметр found представляет собой указатель на структуру, в которую будет записываться информация о найденных файлах. Эта структура определена в файле dos.h:

struct find_t
{
  char reserved[21]; // зарезервировано для DOS
  char attrib;       // атрибуты файла
  unsigned wr_time;  // время изменения файла
  unsigned wr_date;  // дата изменения файла
  long size;         // размер файла в байтах
  char name[13];     // имя файла и расширение
};

Программа DIRLIST

Приведем текст программы просмотра содержимого каталога DIRLIST (листинг 3.3). Программа принимает из командной строки параметр - образец для показа файлов. Если вы укажете параметр *.*, будет выведена информация обо всех файлах. Можно задавать полный путь: c:\*.*.


Листинг 3.3. Файл dirlist\dirlist.cpp


#include <stdlib.h>
#include <stdio.h>
#include <dos.h>

void print_info(struct find_t *find);
char *time_conv(unsigned time, char *char_buf);
char *date_conv(unsigned date, char *char_buf);

#pragma argsused
int main(int argc, char *argv[])
{
  struct find_t find;

  // Находим первый файл, удовлетворяющий критериям поиска.
  // В качестве критерия используем образец, полученный
  // из командной строки. Для поиска используем
  // файлы с любыми атрибутами
  if(!_dos_findfirst (argv[1], 0xffff, &find))
  {
    printf("\n"
     "\nИмя файла    Аттр. Дата        Время     Размер"
     "\n------------ ----- ----------  --------  ------");

    // Выводим информацию о первом найленном файле на экран
    print_info(&find);
  }
  else
  {
    printf("Задайте образец для поиска файлов !");
    return(-1);
  }

  // Выводим информацию об остальных найденных файлах
  while(!_dos_findnext (&find)) print_info( &find );
  return(0);
}

// Функция для вывода информации о найденных файлах
void print_info(struct find_t *pfind)
{
  char timebuf[10], datebuf[12];

  // Преобразуем формат даты и времени
  // последнего изменения файла
  date_conv(pfind->wr_date, datebuf);
  time_conv(pfind->wr_time, timebuf);

  // Выводим содержимое дескриптора файла
  printf("\n%-12s",pfind->name);
  printf(" %02X    %8s  %8s %8ld ",
    pfind->attrib, datebuf, timebuf, pfind->size);
}

// Функция преобразования формата времени
char *time_conv(unsigned t, char *buf)
{
  int h, m;

  h = (t >> 11) & 0x1f;
  m = (t >> 5) & 0x3f;
  sprintf(buf, "%2.2d:%02.2d:%02.2d",
    h, m, (t & 0x1f) * 2);
  return buf;
}

// Функция преобразования формата даты
char *date_conv(unsigned d, char *buf)
{
  sprintf(buf, "%2.2d.%02.2d.%04.2d",
    d & 0x1f,(d >> 5) & 0x0f, (d >> 9) + 1980);
  return buf;
}

3.4. Работа с файлами

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

Создание файлов

Для создания файла предназначена функция 3Ch прерывания INT 21h. С помощью этой функции может быть создан файл как в текущем, так и в любом другом каталоге. Если файл с указанным именем уже существует, он обрезается до нулевой длины. Будьте осторожны при использовании этой функции - она может уничтожить файл.

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

Приведем параметры вызова функции создания файла:

На входе: AH 3Ch
CX Атрибуты создаваемого файла:
00h - обычный файл;
01h - только читаемый файл;
02h - скрытый файл;
04h - системный файл
DS:DX Адрес строки, содержащей путь к файлу
На выходе: AX Код ошибки, если был установлен флаг переноса CF;
Идентификатор файла, если флаг переноса CF сброшен

При выполнении этой функции возможно возникновение следующих ошибок:

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

Для того чтобы при создании файла случайно не уничтожить содержимое уже существующего файла с таким же именем, программа может использовать функцию 5Bh. Эта функция проверяет заданный путь на предмет наличия указанного файла. Если такой файл уже существует, функция возвращает программе признак ошибки:

На входе: AH 5Bh
CX Атрибуты создаваемого файла:
00h - обычный файл;
01h - только читаемый файл;
02h - скрытый файл;
04h - системный файл
DS:DX Адрес строки, содержащей путь к файлу
На выходе: AX Код ошибки, если был установлен флаг переноса CF;
Идентификатор файла, если флаг переноса CF сброшен

Если вам требуется временный файл, вы можете создать его с помощью функции 5Ah:

На входе: AH 5Ah
CX Атрибуты создаваемого файла:
00h - обычный файл;
01h - только читаемый файл;
02h - скрытый файл;
04h - системный файл
DS:DX Адрес блока памяти, в который функция запишет путь созданного временного файла. Размер этого блока памяти должен быть по крайней мере 13 байт
На выходе: AX Код ошибки, если был установлен флаг переноса CF;
Идентификатор файла, если флаг переноса CF сброшен

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

Открытие файла

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

На входе: AH 3Dh
AL Требуемый режим доступа:

Бит 7: флаг наследования

0 - идентификатор файла наследуется порожденным процессом
1 - идентификатор файла не наследуется порожденным процессом

Биты 4...6: режим разделения

000 - режим совместимости
001 - запрещение всех видов доступа
010 - запрещение записи
011 - запрещение чтения
100 - разрешение всех видов доступа

Бит 3:0 - зарезервировано

Биты 0...2: вид доступа

000 - чтение
001 - запись
010 - чтение и запись
DS:DX Адрес строки, содержащей путь к файлу
На выходе: AX Код ошибки, если установлен флаг переноса CF;
Идентификатор файла, если флаг переноса CF сброшен

С помощью функции 3Dh можно открыть любой файл (но не каталог). Если требуется вид доступа "запись", открываемый файл не должен иметь атрибут "Только читаемый".

Для использования битов 4...7 (управляющих доступом к файлу другими программами в сети) должна быть запущена программа share.exe .

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

Функция 6Ch обладает расширенными возможностями по созданию и открытию файлов:

На входе: AH 6Ch
AL 00h
BX Байт флагов расширенного режима открытия файла
CX Атрибуты создаваемого файла, используется только при создании файлов
DX Выполняемая функция, если файл существует или не существует:

Биты 0-3 регистра DX задают действие, если файл существует:

0000h - если файл существует, вернуть признак ошибки;
0001h - если файл существует, открыть его;
0002h - если файл существует, заместить и открыть его.

Биты 4-7 регистра DX задают действие, если файл не существует:

0000h - если файл не существует, вернуть признак ошибки;
0001h - если файл не существует, создать и открыть его
DS:SI Адрес строки, содержащей путь к файлу
На выходе: AX Код ошибки, если флаг переноса CF установлен;
Идентификатор файла, если флаг переноса CF сброшен
CX Код выполненных действий:
0 - файл был открыт;
1 - файл был создан и открыт;
2 - файл был замещен и открыт

Регистр BX на входе задает флаги расширенного режима открытия файла в следующем формате:

Биты Назначение
0...2 Режим доступа при чтении или записи
3 Зарезервировано, должно быть равно 0
4...6 Режим разделения
7 Флаг наследования
8...12 Зарезервировано, должно быть равно 0
13 0 - Режим обычного использования обработчика критических ошибок INT 24h (обработчик критических ошибок будет описан позже)
1 - Блокировка обработчика критических ошибок INT 24h. Для того, чтобы узнать причину ошибки, программа должна использовать функцию 59h прерывания INT 21h
14 Управление буферизацией:
0 - Использование стандартной для MS-DOS буферизации;
1 - Отмена буферизации. Использование этого режима замедлит работу с диском, однако вероятность потери информации при аварии в питающей сети уменьшится

Описанная выше функция является как бы комбинацией функций 3Dh и 3Ch (открытие и создание файла). Она удобна, но при ее использовании программа должна убедиться в том, что версия MS-DOS не ниже, чем 4.0.

Удаление файла

Удалить файл можно при помощи функции 41h прерывания INT 21h :

На входе: AH 41h
DS:DX Адрес строки в формате ASCIIZ, содержащей путь удаляемого файла
На выходе: AL Код ошибки, если установлен флаг переноса CF

С помощью этой функции нельзя удалить файл, имеющий атрибут "Только читаемый".

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

Переименование или перемещение файла

Программа может изменить имя файла или переместить его в другой каталог того же самого диска, воспользовавшись функцией 56h прерывания INT 21h :

На входе: AH 56h
DS:DX Адрес строки в формате ASCIIZ, содержащей старое имя
ES:DI Адрес строки в формате ASCIIZ, содержащей новое имя или новый путь к файлу
На выходе: AL Код ошибки, если установлен флаг переноса CF

С помощью этой функции можно переименовать (но не переместить) не только файл, но и каталог.

Функции библиотеки Borland C++

Стандартная библиотека Borland C++ содержит функции для работы с файлами. Эти функции можно разделить на две группы - функции низкого уровня и функции ввода/вывода потоком. Вторая группа функций использует буферизацию и будет рассмотрена в разделе, посвященном буферизованному вводу/выводу.

Функции низкого уровня отображаются на описанные выше функции прерывания INT 21h (а также на функции этого же прерывания, предназначенные для чтения или записи, позиционирования и т. д.).

creat

Для создания файла можно использовать функцию creat :

int creat (char *filename, int mode);

Эта функция и ее параметры описаны в файлах io.h, sys\types.h, sys\stat.h, errno.h.

Первый параметр определяет путь создаваемого файла и его имя. Если файл с указанным именем существует, и не имеет атрибут "Только читаемый", функция сбрасывает длину файла до нуля. Содержимое файла при этом уничтожается.

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

Параметр Операции, разрешенные для создаваемого файла
S_IWRITE Запись
S_IREAD Чтение
S_IREAD | S_IWRITE Чтение и запись

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

После создания файла функция creat открывает новый файл, возвращая идентификатор файла или код ошибки.

open

Мощная функция open предназначена как для открытия существующих файлов, так и для создания новых:

int open (char *filename, int oflag [, int pmode]);

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

Параметр oflag может являться результатом логической операции ИЛИ над следующими константами, определенными в файле fcntl.h:

Константа Описание
O_APPEND При записи в файл информация будет добавляться в конец файла
O_BINARY Файл открывается для работы в двоичном режиме (игнорируются управляющие символы, такие как конец строки)
O_CREAT Создается новый файл и открывается для записи. Эта константа игнорируется, если указанный в первом параметре файл уже существует
O_EXCL Используется вместе с O_CREAT . Если указанный в первом параметре файл существует, функция возвратит признак ошибки
O_RDONLY Файл открывается только для чтения, попытка записи в файл приведет к тому, что функция записи вернет признак ошибки
O_RDWR Файл открывается как для чтения, так и для записи
O_TEXT Файл открывается в текстовом режиме
O_TRUNC Существующий файл открывается и обрезается до нулевой длины (если для этого файла разрешена операция записи)
O_WRONLY Файл открывается только для записи (в MS-DOS для файла, открытого с признаком O_WRONLY , разрешено выполнение операции чтения)

close

Для того, чтобы закрыть файл, открытый функциями creat или open , нужно использовать функцию close :

int close (int handle);

В качестве параметра функции передается идентификатор файла, полученный при открытии или создании файла. Функция возвращает 0 при успешном закрытии файла или -1 при ошибке.

Коды ошибок

Код ошибки для этой и других функций стандартной библиотеки Borland C++ записывается в глобальную переменную errno.

3.5. Чтение и запись файлов

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

Запись данных в файл

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

На входе: AH 40h
BX Идентификатор открытого файла
CX Количество записываемых байт
DS:DX Адрес блока памяти, содержащего записываемые данные
На выходе: AX Код ошибки, если был установлен флаг переноса CF;
Количество действительно записанных байт, если флаг переноса CF сброшен

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

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

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

Следует учитывать, что количество действительно записанных байт может не совпадать с заданным в регистре CX при вызове функции 40h. Такая ситуация возможна, например, при записи в файл, открытый в текстовом режиме, байта 1Ah. Этот байт означает конец текстового файла. Другая возможная причина - отсутствие свободного места на диске.

Если функция вызывается с содержимым регистра CX, равным 0, файл будет обрезан или расширен до текущего положения файлового указателя.

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

Функция 40h может выполнять запись не только в файл, но и в символьное устройство, предварительно открытое функцией 3Dh. Об этом мы говорили в разделах книги, посвященных драйверам.

Чтение данных из файла

Для чтения данных из файла (или символьного устройства) предназначена функция 3Fh прерывания INT 21h :

На входе: AH 3Fh
BX Идентификатор открытого файла
CX Количество читаемых байт
DS:DX Адрес блока памяти, в который будут записаны прочитанные данные
На выходе: AX Код ошибки, если установлен флаг переноса CF;
Количество действительно прочитанных байт, если флаг переноса CF сброшен

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

Функции библиотеки Borland C++

Если ваша программа составлена на языке программирования С или C++, для записи и чтения данных она может воспользоваться функциями 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 Кбайт - вы можете получить признак ошибки, хотя передача данных выполнилась правильно. Большие массивы данных можно записывать по частям.

Программа FCOPY

В качестве примера мы приведем программу копирования файлов FCOPY (листинг 3.4), которая пользуется описанными выше функциями.


Листинг 3.4. Файл fcopy\fcopy.cpp


#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>

int main(int argc, char *argv[])
{
  int source, taget, i;
  char *buffer;
  int count;

  if(argc == 3)
  {
    // Открываем исходный файл
    if((source = open (argv[1],
      O_BINARY  | O_RDONLY )) == - 1)
    {
      printf("\nОшибка: %d", errno);
      return(-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);
      return(-1);
    }

    // Будем читать и писать за один раз 10000 байт
    count = 10000;

    // Заказываем буфер для передачи данных
    if((buffer = (char *)malloc(count)) == NULL)
    {
      printf("\nМало памяти");
      return(-1);
    }

    // Копируем исходный файл
    while(!eof(source))
    {
      // Читаем count байт в буфер buffer
      if((count = read (source, buffer, count)) == -1)
      {
        printf("\nОшибка при чтении: %d", errno);
        return(-1);
      }

      // Выполняем запись count байт
      // из буфера в выходной файл
      if((count = write (taget, buffer, count)) == - 1)
      {
        printf("\nОшибка при записи: %d", errno);
        return(-1);
      }
    }

    // Закрываем входной и выходной файлы
    close (source);
    close (taget);

    // Освобождаем память, заказанную под буфер
    free(buffer);
  }

  // Если при запуске программы не были указаны
  // пути для входного или выходного файла,
  // выводим сообщение об ошибке
  else
    printf("\nЗадайте пути для файлов!\n");
  return 0;
}

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

int eof(int handle);

Для файла с идентификатором handle эта функция возвращает одно из трех значений:

Значение Описание
1 Достигнут конец файла
0 Конец файла не достигнут
-1 Ошибка, например, неправильно указан идентификатор файла

Программа, которая читает файл с помощью функции 3Fh прерывания INT 21h , может определить момент достижения конца файла анализируя код ошибки, передаваемый в регистре AX.

3.6. Позиционирование

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

Установка файлового указателя

Установить файловый указатель в нужную вам позицию можно с помощью функции 42h прерывания INT 21h :

На входе: AH 42h
AL Метод кодирования смещения:
00h - абсолютное смещение от начала файла;01h - смещение от текущей позиции;02h - смещение от конца файла
BX Идентификатор открытого файла
CX Старший байт смещения
DX Младший байт смещения
На выходе: AX Код ошибки, если установлен флаг переноса CF;Младший байт текущей позиции, если флаг переноса CF сброшен
DX Старший байт текущей позиции

Функция 42h позволяет указывать новое значение указателя либо как абсолютное смещение от начала файла, либо как смещение от текущей позиции, либо как смещение от конца файла. В последних двух случаях используется смещение со знаком. Для указания смещения или абсолютной позиции программа должна задать в регистрах CX, DX соответствующее 32-битное значение.

Что произойдет, если при использовании методов кодирования 01h или 02h попытаться установить указатель позиции до начала файла?

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

Определение размера файла

Если использовать метод кодирования 02h и при этом задать нулевое смещение, функция установит указатель на конец файла. Это обстоятельство может быть использовано для определения размера файла в байтах.

Функции библиотеки Borland C++

Стандартная библиотека Borland C++ содержит функции, предназначенные для управления файловым указателем позиции и получения текущего значения этого указателя. Это функции lseek , tell , filelength .

lseek

Функция lseek работает аналогично только что описанной функции 42h. Приведем ее прототип:

long lseek (int handle, long offset, int origin);

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

Значение Описание
SEEK_SET Абсолютное смещение от начала файла
SEEK_CUR Смещение относительно текущей позиции
SEEK_END Смещение относительно конца файла

Функция возвращает величину текущего смещения в байтах относительно начала файла или -1 в случае ошибки. Как и для остальных функций библиотеки, код ошибки находится в глобальной переменной errno.

filelength

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

long filelength (int handle);

Эта функция возвращает размер файла в байтах. Файл задается параметром handle. В случае ошибки функция возвращает значение -1.

tell

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

long tell (int handle);

Эта функция возвращает текущую позицию для файла, определенного параметром handle, или -1 в случае ошибки.

Программа SETPOS

Для демонстрации использования функций позиционирования приведем простую программу SETPOS (листинг 3.5), которая для заданного файла и позиции внутри файла отображает содержимое одного байта. Дополнительно программа определяет размер файла и текущую позицию после чтения байта.


Листинг 3.5. Файл setpos\setpos.cpp


#include <io.h>
#include <stdio.h>
#include <fcntl.h>

int main(void)
{
  int handle;
  long position, length;
  char buffer[2], fname[80];

  // Запрашиваем имя файла, с которым будем работать
  printf("Введите имя файла: ");
  gets(fname);

  // Открываем файл
  handle = open (fname, O_BINARY  | O_RDONLY );

  // Если такого файла нет, выводим сообщение об ошибке
  // и завершаем работу программы
  if(handle == -1)
  {
    printf("\nНет такого файла!");
    return(-1);
  }

  // Определяем и выводим на экран
  // размер файла в байтах
  length = filelength (handle);

  printf("\nДлина файла %s составляет %ld байт\n",
    fname, length);

  // Запрашиваем позицию для чтения и отображения байта
  do
  {
    printf("Введите позицию: ");
    scanf ("%ld", &position);
  } while(position > length);

  // Устанавливаем заданную позицию
  lseek (handle, position, SEEK_SET );

  // Читаем один байт в буфер, начиная с установленной
  // позиции
  if(read (handle, buffer, 1) == -1)
  {
    // Для вывода сообщения об ошибке используем
    // функцию perror(), которая добавляет к сообщению,
    // заданному в параметре, расшифрованное
    // системное сообщение об ошибке.
    // Код ошибки функция perror() берет
    // из переменной errno
    perror("Ошибка при чтении");
    return(-1);
  }

  // Выводим считанный байт на экран
  printf( "Смещение: %ld; байт: %02.2x ('%c')\n",
    position, (unsigned char)*buffer, *buffer);

  // Определяем текущую позицию и выводим ее
  // на экран
  position = tell (handle);
  printf("\nТекущая позиция в файле: %ld\n", position);

  // Закрываем файл
  close (handle);
  return 0;
}

3.7. Изменение дескриптора файла

Напомним: атрибуты файла, время и дата его последней модификации, а также размер файла хранятся в дескрипторе файла. Дескриптор файла находится в каталоге.

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

Атрибуты файла

Для работы с полем атрибутов файла предназначена функция 43h прерывания INT 21h :

На входе: AH 43h
AL Выполняемая операция: 00h - чтение атрибутов файла;01h - установка новых атрибутов файла
CX Новые атрибуты файла, если AL = 01h:Биты регистра CX:5 - бит архивации; 4 - каталог;3 - метка диска;2 - системный файл; 1 - скрытый файл; 0 - только читаемый файл
DS:DX Путь к файлу в формате строки ASCIIZ
На выходе: AX Код ошибки, если установлен флаг переноса CF
CX Если не было ошибки, этот регистр содержит атрибуты файла

При изменении атрибутов файла допустимо указывать комбинации битов в регистре CX.

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

Время и дата изменения файла

Для работы с полями времени и даты последней модификации файла предназначена функция 57h прерывания INT 21h :

На входе: AH 57h
AL Выполняемая операция:
00h - чтение даты и времени;01h - установка даты и времени
BX Идентификатор открытого файла
CX Время
DX Дата
На выходе: AX Код ошибки, если установлен флаг переноса CF
CX Если не было ошибки, этот регистр содержит время последнего изменения файла
DX Если не было ошибки, этот регистр содержит дату последнего изменения файла

Для того чтобы изменить время или дату последней модификации файла с помощью этой функции, файл предварительно должен быть открыт. Формат времени и даты для этой функции такой же, как и используемый в дескрипторе каталога (рис. 2.4 и 2.5).

Функции библиотеки Borland C++

Стандартная библиотека Borland C++ содержит функции для чтения и изменения атрибутов файлов, а также времени и даты их последней модификации.

_dos_getfileattr

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

unsigned _dos_getfileattr (char *path, unsigned *attrib);

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

В случае успешного завершения функция возвращает 0. При ошибке она возвращает код ошибки, полученный от операционной системы и устанавливает глобальную переменную errno в значение ENOENT, что означает отсутствие файла, указанного в параметре path.

_dos_setfileattr

Для изменения атрибутов файла можно использовать функцию _dos_setfileattr :

unsigned _dos_setfileattr (char *path, unsigned attrib);

Параметр attrib может принимать следующие значения:

Значение Описание
_A_ARCH Установка бита архивации
_A_HIDDEN Скрытый файл
_A_NORMAL Обычный файл
_A_RDONLY Только читаемый файл
_A_SUBDIR Каталог
_A_SYSTEM Системный файл
_A_VOLID Метка диска

_dos_getftime

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

unsigned _dos_getftime (int handle, unsigned *date,
  unsigned *time);

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

_dos_setftime

Если вам надо изменить время или дату последней модификации файла, используйте функцию _dos_setftime :

unsigned _dos_setftime (int handle, unsigned date,
  unsigned time);

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

Программа READONLY

Приведем программу READONLY (листинг 3.6), изменяющую на противоположное значение бита файла атрибутов "Только читаемый" для файла, имя которого передается программе в качестве параметра.


Листинг 3.6. Файл readonly\readonly.cpp


#include <dos.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

#pragma argsused
void main(int argc, char *argv[])
{
  unsigned fattr;

  _dos_getfileattr (argv[1], &fattr);
  _dos_setfileattr (argv[1], fattr ^ _A_RDONLY);
}

Программа сначала считывает байт атрибутов, затем инвертирует соответствующий бит и устанавливает новое значение байта атрибутов.

3.8. Буферизация

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

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

Операционная система MS-DOS может создать несколько буферов. Их количество зависит от оператора BUFFERS из файла config.sys . Этот оператор позволяет определить от 2 до 99 буферов. Если файл config.sys не содержит оператора BUFFERS, по умолчанию используются два буфера.

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

Еще один способ организовать буферизацию данных для жестких дисков и устройств CD-ROM - использовать драйвер smartdrv.exe . Этот драйвер позволяет создать для диска кеш-память в расширенной памяти.

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

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

При закрытии файла все буферы, связанные с ним, сбрасываются на диск. Если вам надо сбросить буферы, не закрывая файл, это можно сделать с помощью функции 68h прерывания INT 21h :

На входе: AH 68h
BX Идентификатор открытого файла
На выходе: AX Код ошибки, если установлен флаг переноса CF;
0, если операция выполнена успешно

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

Обратите также внимание на функцию расширенного открытия файлов 6Ch. Эта функция позволяет при открытии файла отменить буферизацию.

3.9. Потоки ввода и вывода

Стандартная библиотека Borland C++ содержит многочисленные функции, использующие собственный механизм буферизации при работе с файлами. Их часто называют функциями потокового ввода/вывода . Такую буферизацию не следует путать с буферизацией, выполняемой операционной системой. Имена всех этих функций начинаются на f - fopen , fclose , fprintf и т. д.

Функции потокового ввода/вывода хорошо описаны во многих учебных пособиях по языку программирования С, поэтому мы приведем лишь краткий обзор, делая акцент на особенностях их применения.

Существуют потоки , соответствующие стандартным устройствам ввода, вывода, вывода сообщений об ошибках, стандартному устройству последовательного ввода/вывода и стандартному устройство печати:

Поток Описание
stdin Стандартное устройство ввода
stdout Стандартное устройство вывода
stderr Стандартное устройство для вывода сообщений об ошибках
stdaux Стандартное последовательное устройство ввода/вывода
stdprn Стандартное печатающее устройство

Для использования этих потоков не требуются выполнять процедуру открытия и закрытия.

Для работы со стандартными устройствами ввода/вывода в библиотеках трансляторов языка программирования С имеется соответствующий набор функций, которые должны быть вам хорошо известны - printf, scanf , putchar и т. д. Мы не будем их описывать, так как объем книги ограничен.

Открытие и закрытие потоков

При использовании функций потокового ввода/вывода файлы открываются функцией fopen , а закрываются функцией fclose . Эти функции не только открывают и закрывают файлы (получают и освобождают их идентификаторы), но и, соответственно, создают и уничтожают переменную типа FILE , описанную в файле stdio.h и связанную с данным файлом.

fopen

Для организации потокового ввода/вывода вначале необходимо при помощи функции fopen открыть файл. Функция fopen имеет следующий прототип:

FILE  *fopen (char *filename, char *mode);

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

Режим Операция, для выполнения которой открывается файл
"r" Чтение
"w" Запись
"a" Запись, данные будут добавляться в конец файла

К буквам r, w, a справа могут добавляться буквы t и b.

Буква t означает, что файл будет открыт в текстовом режиме, b - в двоичном. Для двоичного режима не выполняется обработка таких символов, как конец строки, конец файла и т. д.

Строка режима открытия файла может дополнительно содержать символ '+'. Этот символ означает, что для файла разрешены операции чтения и записи одновременно.

fclose

Для закрытия файлов, открытых для ввода или вывода потоком, должна использоваться функция fclose :

int fclose (FILE  *stream);

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

fdopen

Если вы открыли файл с помощью функции open , вы можете создать поток для этого файла, используя функцию fdopen :

FILE  *fdopen (int handle, char *mode);

В качестве первого параметра используется идентификатор файла, полученный от функции open . Второй параметр аналогичен параметру mode для функции fopen .

Для того чтобы закрыть поток, созданный функцией fdopen , необходимо использовать функцию fclose , а не close .

fileno

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

int fileno (FILE  *stream);

Функция возвращает идентификатор файла, связанного с данным потоком.

fwrite

Для записи данных в поток предназначена функция fwrite :

size_t fwrite (void *buffer, size_t size,
  size_t count, FILE  *stream);

Эта функция записывает в файл stream блоки информации, каждый из которых имеет длину size байт. Количество блоков - count. Данные для записи расположены по адресу buffer.

Если файл открыт в текстовом режиме, каждый символ возврата каретки CR заменяется на два символа - возврата каретки CR и перевода строки LF.

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

fread

Чтение данных потоком можно выполнить с помощью функции fread :

size_t fread (void *buffer, size_t size,
  size_t count, FILE  *stream);

Эта функция используется аналогично предыдущей. Для распознавания конца файла и обнаружения ошибок после вызова этой функции необходимо использовать функции feof и ferror.

Если при использовании функции fread вы задали значения параметров size или count, равные нулю, функция fread не изменяет содержимое буфера buffer.

fseek

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

ftell

Функция ftell возвращает текущее значение указателя позиции для файла, или -1 при ошибке:

long ftell (FILE  *stream);

Пара функций ftell и fseek позволит вам правильно организовать позиционирование для файлов, открытых в текстовом режиме.

fgetpos , fsetpos

Есть еще одна возможность организовать позиционирование внутри файлов, открытых потоком - вызов пары функций 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

Для записи в поток отдельных байт используется функция fputc :

int fputc (int c, FILE  *stream);

Байт c записывается в поток stream начиная с текущей позиции. После записи текущая позиция увеличивается на единицу. Функция возвращает записанный байт или значение EOF , которое служит признаком ошибки.

fgetc

Для побайтового чтения содержимого файла, открытого потоком, удобно использовать функцию fgetc :

int fgetc (FILE  *stream);

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

При достижении конца файла или в случае ошибок функция fgetc возвращает значение EOF . Однако для проверки на ошибку или конец файла лучше пользоваться специальными функциями ferror и feof. Если вы открыли файл в двоичном режиме, единственный способ определить момент достижения конца файла - использовать функцию feof, так как значение константы EOF может находиться в любом месте двоичного файла.

fputs и fgets

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

Для форматного вывода в файл содержимого переменных удобно использовать функцию fprintf :

int fprintf (FILE  *stream, char *format [,arg]...);

Эта функция аналогична хорошо известной вам функции форматного вывода на экран printf, с которой обычно начинают изучение языка программирования С. Вспомните такую программу:

#include <stdio.h>
main()
{
  printf("Hello, world!");
}

Функция fprintf имеет дополнительно один параметр - stream, который определяет выходной поток.

После завершения работы функция возвращает количество записанных байт или отрицательную величину, если при записи произошла ошибка.

fscanf

Для форматного ввода информации из файла можно использовать функцию fscanf , аналогичную известной вам функции scanf :

int fscanf (FILE  *stream, char *format [,arg]...);

Эта функция читает данные, начиная с текущей позиции в потоке stream, в переменные, определенные аргументами arg. Каждый аргумент должен являться указателем на переменную, соответствующую типу, определенному в строке формата format.

Функция fscanf возвращает количество успешно считанных и преобразованных в указанный формат полей. Те поля, которые были считаны, но не преобразовывались, в возвращаемом функцией значении не учитываются.

При достижении конца файла функция возвращает значение EOF . Если функция возвратила нулевое значение, это означает, что преобразование полей не производилось.

Буферизация потоков

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

setbuf

Функция setbuf позволяет вам заменить системный буфер на свой собственный:

void setbuf (FILE  *stream, char *buffer);

Параметр buffer должен указывать на подготовленный пользователем массив, имеющий размер BUFSIZ байт. Константа BUFSIZ описана в файле stdio.h.

setvbuf

Функция 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.

Для чего может понадобиться изменение размера буфера?

Главным образом - для сокращения времени, необходимого для позиционирования магнитных головок при выполнении операций одновременно над несколькими файлами, например, при копировании файлов, слиянии нескольких файлов в один и т. д.

fflush

При закрытии потока функцией fclose содержимое буфера записывается на диск. Если программе необходимо выполнить запись содержимого буфера на диск без закрытия файла, она может воспользоваться функцией fflush :

int fflush (FILE  *stream);

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

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

Программа BUFCOPY

В качестве примера приведем текст программы BUFCOPY (листинг 3.7), копирующей содержимое текстового файла.

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


Листинг 3.7. Файл bufcopy\ bufcopy.cpp


#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];

int main(int argc, char *argv[])
{
  time_t start, end;
  FILE  *stream_from, *stream_to;

  if(argc < 3)
  {
    printf("Задайте имена файлов!\n");
    return(-1);
  }

  // Открываем файлы и используем для копирования
  // буфер стандартного размера
  if((stream_from = fopen (argv[1], "rt")) == NULL)
  {
    printf("Задайте имя входного файла!\n");
    return(-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)
    return(-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)
    return(-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);

  return(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);
}

3.10. Другие функции для работы с файлами

В задачу данной книги не входит описание всех функций стандартных библиотек трансляторов Borland C++, Microsoft Quick C или аналогичных, предназначенных для работы с дисками и файловой системой. Но мы приведем еще несколько интересных и полезных на наш взгляд функций.

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

3.11. Таблица открытых файлов

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

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

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

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

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

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

3.12. Обработка критических ошибок

Операционная система MS-DOS позволяет программам устанавливать собственный обработчик критических ошибок аппаратуры. Мы уже говорили о том, что вектор 0000h:0090h, соответствующий прерыванию INT 24h , содержит адрес обработчика критических ошибок. Этот обработчик получает управление от операционной системы, когда драйвер какого-либо устройства обнаруживает ошибку аппаратуры.

Обратите внимание на то, что обработчик критических ошибок не вызывается при работе с диском через прерывания INT 25h или INT 26h. Тем более, он не вызывается при работе с диском на уровне прерывания INT 13h .

При запуске программы операционная система MS-DOS копирует адрес обработчика в префикс сегмента программы PSP, а после завершения работы программы - восстанавливает его из PSP.

Стандартный обработчик MS-DOS выводит на экран сообщение:

Abort, Retry, Ignore, Fail?

Если ваша программа должна сама обрабатывать ошибки аппаратуры, она может установить свой собственный обработчик критических ошибок.

Анализ регистров

Когда обработчик получает управление, регистры процессора содержат информацию, необходимую для определения причины и места появления ошибки:

Регистр Содержимое
AH Информация об ошибке.Бит 0: тип операции:
0 - чтение, 1 - запись
Биты 1,2: область диска, где произошла ошибка:
00 - системные файлы;
01 - область FAT ;
10 - область каталога;
11 - область данных.
Бит 3: если равен 1, возможен выход с кодом FAIL
Бит 4: если равен 1, возможен выход с кодом RETRY
Бит 5: если равен 1, возможен выход с кодом IGNORE
Бит 6 зарезервирован, равен 0
Бит 7 тип устройства: 0 - диск; 1 - символьное устройство
AL Номер диска (если бит 7 регистра AH равен 0)
DI Код ошибки (биты 0...7, остальные биты не определены)
BP:SI Адрес заголовка драйвера устройства, в котором произошла ошибка

Обработчик критических ошибок не должен пользоваться функциями MS-DOS с кодами, большими чем 0Ch (из-за того, что функции MS-DOS не реентерабельны).

Программа обработки критических ошибок может вывести на экран сообщение об ошибке и запросить оператора о необходимых действиях. Ей разрешено также получить дополнительную уточняющую информацию об ошибке с помощью функции 59h прерывания INT 21h или узнать версию MS-DOS с помощью функции 30h этого же прерывания.

Дополнительная информация об устройстве, в котором произошла ошибка, может быть получена с использованием адреса заголовка драйвера устройства, который передается операционной системой при вызове обработчика в регистрах BP:SI.

Анализ стека

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

Адрес возврата в DOS для команды IRET

IP

CS

FLAGS

Содержимое регистров программы перед вызовом INT 21h

AX, BX, CX, DX, SI, DI, BP, DS, ES

Адрес возврата в программу, вызвавшую функцию DOS

IP

CS

FLAGS

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

Код действия

После выполнения всех необходимых действий программа обработки критических ошибок должна возвратить в регистре AL код действия, которое должна выполнить операционная система для обработки данной ошибки:

Код Описание
0 Игнорировать ошибку
1 Повторить операцию
2 Завершить задачу аварийно, используя адрес завершения, записанный в векторе прерывания INT 23h
3 Вернуть программе управление с соответствующим кодом ошибки

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

Функции библиотеки Borland C++

Для составления программы обработки критических ошибок вы можете воспользоваться языком ассемблера или функциями стандартной библиотеки Borland C++ с именами _dos_getvect , _dos_setvect , _chain_intr . Однако лучше всего использовать специально предназначенные для этого функции _harderr , _hardresume и _hardretn .

_harderr

Функция _harderr предназначена для установки нового обработчика критических ошибок, она имеет следующий прототип:

void _harderr (void (far *handler)());

Параметр handler - указатель на новую функцию обработки критических ошибок.

_hardresume

Функция _hardresume и описанная ниже функция _hardretn должны быть использованы в обработчике критических ошибок, установленном функцией _harderr .

Функция _hardresume возвращает управление операционной системе, она имеет прототип:

_hardresume (int result);

Параметр result может иметь следующие значения (в соответствии с необходимыми действиями):

Значение Описание
_HARDERR_ABORT Завершить программу аварийно
_HARDERR_FAIL Вернуть код ошибки
_HARDERR_IGNORE Игнорировать ошибку
_HARDERR_RETRY Повторить операцию

Эти параметры описаны в файле dos.h.

_hardretn

Функция _hardretn возвращает управление непосредственно программе, передавая ей код ошибки, определяемый параметром функции error:

void _hardretn (int error);

При этом программа получает код ошибки error после возврата из вызванной ей функции MS-DOS. Если ошибка произошла при выполнении функции с номером, большим чем 38h, дополнительно устанавливается флаг переноса. Если номер функции был меньше указанного значения, в регистр AL записывается величина FFh.

Функция обработки критических ошибок

Функция обработки критических ошибок handler имеет следующие параметры:

void far handler(unsigned deverror, unsigned errcode,
  unsigned far *devhdr);

Первый параметр - код ошибки устройства. Он равен содержимому регистра AX при вызове обработчика прерывания INT 24h . Аналогично, параметр errcode соответствует содержимому регистра DI - код ошибки. Третий параметр devhdr - это указатель на заголовок драйвера устройства (передаваемый в регистрах BP:SI).

Программа CRITERR

Для демонстрации использования функций установки обработчика критических ошибок приведем программу, которая пытается создать каталог на диске А:. Эта программа сама обрабатывает критические ошибки, запрашивая у оператора информацию о необходимых действиях.


Листинг 3.8. Файл criterr\criterr.cpp


// Эту программу можно запускать только из командной
// строки. При запуске из интегрированной среды
// Borland C++ возможен конфликт с используемым в этих
// средах обработчиком критических ошибок

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <direct.h>
#include <string.h>
#include <dos.h>
#include <bios.h>

void far hhandler(unsigned deverr,
  unsigned doserr, unsigned far *hdr);
void _bios_str(char *p);

int main()
{
  // Устанавливаем обработчик критических ошибок
  _harderr (hhandler);

  // Моделируем критическую ошибку.
  // Выполняем попытку создать каталог на диске А:.
  // Если мы "забудем" вставить в дисковод
  // дискету, будет вызван обработчик
  // критической ошибки
  printf("\nВставьте (или не вставляйте) "
    "дискету в дисковод A:"
    "\nи нажмите любую клавишу...\n");
  getch();

  // Создаем каталог
  if(mkdir ("a:\\test_ctl"))
  {
    printf("\nОшибка при создании каталога");
    return(-1);
  }
  else
  {
    // Удаляем только что созданный каталог
    rmdir ("a:test_ctl");
  }
  return 0;
}

// Новый обработчик критических ошибок
#pragma argsused
void far hhandler(unsigned deverr,
  unsigned doserr, unsigned far *hdr)
{
  int ch;
  static char buf[200];

  // Выводим сообщение о критической ошибке
  sprintf(buf,"\n\r"
    "\n\rКод ошибки устройтсва: %04.4X"
    "\n\rКод ошибки DOS:        %d"
    "\n\r\n\r"
    "\n\rВыполняемые действия:"
    "\n\r  0 - повторить"
    "\n\r  1 - отменить"
    "\n\r  2 - завершить"
    "\n\r----> ?",
     deverr, doserr);

  _bios_str(buf);

  // Вводим ответ с клавиатуры
  ch = _bios_keybrd(_KEYBRD_READ) & 0x00ff;
  _bios_str("\n\r");

  switch(ch)
  {
    case '0':  // Пытаемся повторить операцию
    default:
      _hardresume (_HARDERR_RETRY);

    case '2':  // Завершаем работу программы
      _hardresume (_HARDERR_ABORT);

    case '1':  // Возврат в DOS с кодом ошибки
      _hardretn (doserr);
  }
}

// Программа для вывода строки символов на экран
// с помощью функции BIOS 0Eh
void _bios_str(char *ptr)
{
  union REGS inregs, outregs;

  inregs.h.ah = 0x0e;
  for(; *ptr; ptr++)
  {
    inregs.h.al = *ptr;
    int86(0x10, &inregs, &outregs);
  }
}

[Назад] [Содеожание] [Дальше]


Hosted by uCoz