КаталогИндекс раздела
НазадОглавлениеВперед


10. Внешняя память на магнитных дисках

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

10.1. Физический дисковый адрес

Предполагая, что читатель в общих чертах знаком с конструкцией диска и дисковода, мы лишь напомним здесь те аспекты построения аппаратуры, которые играют существенную роль для дальнейшего рассмотрения.
Дискета представляет собой круглую пластину, покрытую с двух сторон магнитным материалом. Информация записывается раздельно на две стороны дискеты. В дисководе к поверхности дискеты прижимаются две (по одной с каждой стороны) головки чтения/записи. Пара головок может перемещаться вдоль радиуса дискеты, занимая на нем ряд фиксированных положений. При вращении дискеты точки ее поверхности, которые могут находиться в контакте с головками, образуют концентрические окружности, именуемые дорожками. Дорожки делятся на секторы. Поскольку обмен информацией с дисками происходит через пря- мой доступ к памяти, сектор - это наименьший объем (блок) информации, передаваемый за одну операцию чтения/записи. Объем сектора - 512 байт.
Жесткий диск представляет собой два или более дисков, посаженных на одну ось. Все головки чтения/записи здесь, как и для дискеты образуют жесткий блок, то есть перемещаются все вместе. Для жесткого диска вполне органичным является понятие цилиндра - совокупности всех дорожек, расположенных на разных поверхностях и равноудаленных от оси вращения. С точки зрения адресации понятия "дорожка" и "цилиндр" являются синонимами.
Сектор является минимальной адресуемой единицей при обращениях к внешней памяти. Адрес сектора на внешней памяти состоит из трех составляющих:

При записи на диск больших объемов информации мы заинтересованы в том, чтобы свести к минимуму затраты по переключению на следующий сектор диска. Отсюда вопрос: какой сектор считать следующим при последовательной записи (чтении) ? Принят такой порядок его определения. Следующим считается сектор, расположенный следующим на той же дорожке, под той же головкой (при записи сектора головки за счет вращения диска установятся над следующим сектором, так что затраты времени на переключения практически нулевые). При заполнении всей дорожки следующим принято считать первый сектор дорожки, расположенный на том же цилиндре под следующей головкой (этот сектор будет находиться под головками, а электрическое переключение на другую головку - процесс быстрый). И только при заполнении всего цилиндра меняется номер дорожки в адресе (этот процесс требует механического перемещения головок и, следовательно, гораздо больших затрат времени).

10.2. Cредства чтения секторов BIOS и DOS.

В BIOS работа с дисками поддерживается прерыванием 0x13. Это весьма "богатое" прерывание, имеющее много функций, определяемых содержимым регистра AH. Ниже приведена краткая сводка функций этого прерывания (номера функций 16-ричные):

0- сброс дисковой подсистемы;
1- состояние дисковой подсистемы;
2- чтение секторов;
3- запись секторов;
4- контроль секторов;
5,6,7- форматирование дорожки;
8- параметры накопителя;
9- инициализация таблиц BIOS для жесткого диска;
A- длинное чтение (жесткий диск);
B- длинная запись (жесткий диск);
C- поиск цилиндра (жесткий диск);
D- альтернативный сброс (жесткий диск);
E- чтение буфера секторов (жесткий диск);
F- запись буфера секторов (жесткий диск);
10- проверка готовности (жесткий диск);
11- перекалибровка дисковода (жесткий диск);
12,13,14- диагностика контроллера (жесткий диск);
15- тип диска;
16- состояние замены (гибкий диск);
17- установка типа;
18- установка типа носителя;
19- парковка головок;
1A,1B,1C- ESDI жесткий диск.
Для целей нашего пособия наиболее интересны функции 2 и 3 - чтение и запись. Формат обращения к этим функция преры- вания 0x13 (содержимое регистров) следующий:
AH- номер функции;
AL- количество секторов, которое нужно прочитать/записать; CX - номер дорожки и сектора (см.ниже); DH - номер головки; DL - физический номер дисковода (0 - дисковод A, 1 - B, 0x80 - 1-й жесткий диск, 0x81 - 2-й жесткий диск);
ES:BX- адрес области оперативной памяти, с которой происходит обмен.
После выполнения прерывания 0x13 флаг переноса устанавливается в 0, если операция прошла без ошибок или в 1 при наличии ошибок, в последнем случае регистр AH содержит код ошибки.
Номер дорожки и сектора задается в регистре CX в следующем упакованном формате:
  ttttttttTTssssss,
где

В DOS для чтения/записи секторов служат прерывания (прерывания, а не функции DOS!) 0x25 (чтение) и 0x26 (запись).
Обычный формат обращения к этим прерываниям следующий:

AL- номер логического диска (0 - A, 1 - B, 2 - C, etc.);
CX- количество секторов, которое нужно прочитать /записать; DX - логический номер сектора;
DS:BX- адрес области оперативной памяти, с которой происходит обмен.
На выходе, как и для прерывания 0x13, устанавливается флаг переноса, а регистр AH содержит код ошибки при наличии таковой.
Заметим, что дисковый адрес задается здесь не физический, а логический - номер сектора относительно начала логического диска (о логических дисках - см. ниже). Нумерация секторов внутри логического диска начинается с 0, и номер логического сектора может быть вычислен как:

logs = ( (t * ns) + h) * nh + s-1;    (10.1)

где

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

В программе примера 10.1, иллюстрирующей применение средств чтения секторов средствами BIOS и DOS наибольший интерес (кроме вызывающих последовательностей для прерываний) представляют функции Daddr_to_Sect и Sect_to_Daddr, осуществляющие перевод физического адреса в логический и наоборот соответственно. Программа запрашивает способ задания адреса, формирует по заданному физическому адресу логический или наоборот, вводит один и тот же сектор дважды (сначала используя прерывание BIOS, а затем - DOS) и выводит на экран содержимое начального участка сектора, при двух вариантах ввода - результаты должны быть одинаковыми. Физический адрес в программе описывается структурой daddr, поля t, h, s которой содержат компоненты физического адреса, а ts - заготовку для регистра CX при обращении к прерыванию 0x13.
Предупредим читателя, что приведенная программа будет правильно работать только с дискетой, отформатированной на 360 Кбайт, так как величины nh, ns, s0 (параметры формулы 10.1) и другие (nt, nls) имеют константные значения, свойственные именно этому формату.


/*==           ПРИМЕР 10.1          ==*/
/*========== Чтение сектора средствами BIOS и DOS ========*/
#include <dos.h>
/* типы и структуры данных */
#define byte unsigned char
#define word unsigned int
/* описание функций */
void Sect_to_Daddr(word sect,struct daddr *d);
word Daddr_to_Sect(struct daddr *d);
word get(word min, word max, char *name);
struct daddr {
 byte h; /* головка */
 word s, /* сектор */
    t, /* дорожка */
    ts; /* сектор, дорожка упакованные */
 } a;  /* физический дисковый адрес */
word ls; /* номер логического сектора */
/* параметры дискеты */
word nh=2,  /* число головок */
   ns=9,  /* число секторов на дорожке */
   nt=40,  /* число дорожек */
   s0=0,  /* номер 1-го сектора */
   nls=720; /* общее число секторов */
byte drive;  /* номер логич.диска */
byte buff13[512]; /* буфер сектора */
byte buff25[512]; /* буфер сектора */
/*==== main ====*/
main() {
 union REGS rr;
 struct SREGS sr;
 byte fx, flag;
 byte mode;
 int i, i0, y;

 for (fx=0; fx==0;) {
  printf("диск (A|B) >"); drive=getche();
  if ((drive!='a')&&(drive!='b')) printf("\7");
  else fx=1;
  }
 drive-='a';
 for (flag=0; flag==0;) {
  printf("\nA - задание физического адреса;\n");
  printf("S - задание номера сектора;\n");
  printf("Q - конец работы.\n");
  printf("--->"); mode=getche(); printf("\n");
  switch(mode) {
   case 'q': flag=1; continue;
   case 'a': a.h=get(0,nh-1,"головка");
    a.t=get(0,nt-1,"дорожка");
    a.s=get(1,ns,"сектор");
    a.ts=(a.t<<8)|a.s|((a.t&0x300)>>2);
    ls=Daddr_to_Sect(&a);
    break;
   case 's': ls=get(0,nls-1,"логич.сектор");
    Sect_to_Daddr(ls,&a);
    break;
   default: printf("\7"); continue;
   }
  /* чтение сектора при помощи прерывания 13. */
  rr.h.ah=2;         /* функция ЧТЕНИЕ */
  rr.h.al=1;         /* секторов 1 */
  rr.h.dl=drive;       /* адрес диска */
  rr.h.dh=a.h; rr.x.cx=a.ts; /* адрес на диске */
  sr.es=FP_SEG(buff13);    /* адрес в ОП */
  rr.x.bx=FP_OFF(buff13);
  int86x(0x13,&rr,&rr,&sr);
  /* Проверка ошибок чтения */
  if (rr.x.cflag) {
   printf("Ошибка при INT 13 : %u\n",rr.h.ah);
   exit(0);
   }
  /* Чтение при помощи прерывания DOS 25 */
  rr.h.al=drive;     /* логич.диск */
  rr.x.cx=1;       /* секторов 1 */
  rr.x.dx=ls;       /* номер сектора */
  sr.ds=FP_SEG(buff25);  /* адрес в ОП */
  rr.x.bx=FP_OFF(buff25);
  int86x(0x25,&rr,&rr,&sr);
  /* Проверка ошибок чтения */
  if (rr.x.cflag) {
   printf("\nОшибка при INT 25 : %u. ",rr.h.ah);
   exit(0);
   }
  /* дамп буферов */
  clrscr();
  printf(" INT 13 : гол=%d дор=%-2d сект=%d   |",
      a.h,a.t,a.s);
  printf("| INT 25 : лог.сект=%d \n",ls);
  printf(" -------------------------------------|");
  printf("|-------------------------------------\n");
  for(i0=0,y=3;y<25; y++,i0+=12) {
   for (i=0; i<12; printf(" %02X",buff13[i0+i++]));
   printf(" || ");
   for (i=0; i<12; printf(" %02X",buff25[i0+i++]));
   printf("\n");
   }
  printf(" Нажмите любую клавишу..."); getch();
  }
}
/*=== формирование физич.дискового адреса из # сектора ===*/
void Sect_to_Daddr(word sect,struct daddr *d) {
/* sect - номер сектора, a - адрес на диске */
int s;
 d->s=sect%ns+1;
 s=sect/ns;
 d->h=s%nh;
 d->t=s/nh;
 d->ts=(d->t<<8)|d->s|((d->t&0x300)>>2);
}
/*=== формирование # сектора из физич.дискового адреса ===*/
word Daddr_to_Sect(struct daddr *d) {
word s;
 s=((d->t*nh)+d->h)*ns+d->s-1+s0;
 return(s);
 }
/*==== ввод какого-либо параметра ====*/
word get(word min, word max, char *name) {
byte fx;
word val;
 for (fx=0; fx==0;) {
  printf("%s >",name); scanf("%d",&val);
  if ((val<min)||(val>max)) printf("\7");
  else fx=1;
  }
 return (val);
}

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

Логический номер сектора, задается в 2-байтном регистре DX, следовательно, не может быть больше, чем 65535, это накладывает ограничение на объем логического диска - не более 32 Мбайт. Поскольку, начиная с версии DOS 4.0, допускается больший объем логических дисков, для работы с такими дисками применяется другой формат обращений к прерываниям 0x25, 0x26, а именно:


  struct parm { /* блок параметров чтения/записи */
    unsigned long sect_num; /* номер логич. сектора */
    unsigned int sect_cnt; /* число секторов */
    void far *ptr;      /* адрес области в ОП, с которой происходит обмен */
    };
В этом случае номер сектора 4-байтный, что практически снимает ограничение на число секторов на логическом диске.

10.3. Логическая структура диска

Некоторые области диска содержат системную информацию, используемую DOS при работе с данным диском. К таким областям относятся:

10.3.1. MBR занимает самый первый сектор жесткого диска (дорожка 0, головка 0, сектор 1). Причины введения MBR в логическую структуру диска следующие. Формат некоторых системных данных и обращений к прерываниям 0x25, 0x26 в ранних версиях DOS не предусматривал возможности работы с жестким диском объемом более 32 Мбайт. Начиная с версии DOS 3.30, это ограничение обходится путем разбиения жесткого диска на два или более логических дисков, объем каждого из которых менее 32 Мбайт. И хотя уже в версии 4.0 снято ограничение на объем логического диска, возможность разбиения диска остается, так как обеспечивает целый ряд дополнительных удобств (например, разделение внешней памяти между пользователями).
Формат MBR следующий (в программе примера 10.2 он описан структурой struct MBR):

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

Элемент таблицы разделов описан в программе примера 10.2 структурой struct Part. Добавим некоторые комментарии к этому описанию.
Поле ActFlag принимает значение 0x80 для активного раздела или 0 - для неактивного.
В физических адресах начала и конца раздела дорожка и сектор задаются в формате регистра CX прерывания 0x13. Раздел, как правило, начинается и заканчивается на границе цилиндра. Если первый сектор цилиндра занимает MBR (или ее продолжение в расширенном разделе DOS), то остальные сектора этой дорожки не используются, и раздел начинается с сектора 1, головки 1 этой дорожки. Неиспользуемые сектора называются скрытыми.
Поле SysCode для MS-DOS может принимать значения: 1 - логический диск объема менее 32 Мбайт, 12-битная FAT; 4 - логический диск объема менее 32 Мбайт, 16-битная FAT; 6 - логический диск объема более 32 Мбайт; 5 - расширенный раздел DOS.
Последнее значение SysCode означает, что сектор, задаваемый адресом начала раздела в свою очередь содержит MBR (без программы загрузки, но с таблицей разделов по смещению 446), в этой таблице в свою очередь может содержаться описатель расширенного раздела и т.д. Системная утилита FDISK, производящая разбиение физического диска на логические, использует только два элемента в каждой таблице разделов, позволяя создать один первичный (соответствующий логическому диску) и один расширенный раздел DOS.
Поле RelSect содержит номер начального сектора (логический адрес) относительно начала раздела внешнего по отношению к данному.

Программист не может прочитать MBR средствами DOS. Для прерывания 0x25 задается логический адрес - номер сектора внутри данного логического диска, а сама MBR не принадлежит никакому логическому диску.

Пример 10.2 демонстрирует разбиение логического диска. Начальный адрес для чтения задается : 0,0,1. При помощи прерывания 0x13 программа считывает сектор по заданному адресу, далее происходит поэлементный анализ таблицы разделов - пока не встретится признак конца таблицы или раздел нулевого размера. Значения полей элемента таблицы выводятся на экран. Манипуляции, описываемые макросами TRK и SECT, опеспечивают распаковку номера дорожки и сектора. Если поле SysCode содержит признак расширенного раздела, то устанавливается новый дисковый адрес, считывается новый сектор и анализируется новая таблица.


/*==          ПРИМЕР 10.2          ==*/
/*=== Чтение и анализ гл.загруз.записи твердого диска ===*/
#include <dos.h>
#define byte unsigned char
#define word unsigned int
#define dword unsigned long
#define SECT(x) x&0x3f
#define TRK(x) (x>>8)|((x<<2)&0x300)
main()
 {
 /* структура элемента раздела */
 struct Part {
  byte ActFlag;           /* описатель*/
        /* физический адрес начала раздела */
   byte Begin_Hd;         /* # головки */
   word Begin_SecTrk;  /* # сектора и дорожки */
  byte SysCode;          /* код системы */
         /* физический адрес конца раздела */
   byte End_Hd;          /* # головки */
   word End_SecTrk;   /* # сектора и дорожки */
  dword RelSec;       /* # сектора начала */
  dword Size;         /* число секторов */
  };
 /* стpуктуpа главной загpузочной записи */
 struct MBR {
  char LoadCode[0x1be];  /* пpогpамма загpузки */
  struct Part rt[4];     /* 4 эл-та pазделов */
  word EndFlag;          /* подпись MBR */
  } mbr;
 int x=10,y;   /* экpанные кооpдинаты */
 byte head=0;   /* номеp головки (0) */
 word Sect_Trk=1; /* номеp доpожки и сектоpа (0,1) */
 int ndrive=0;  /* номеp лог.диска */
 word *EndList;  /* указатель на подпись */
 union REGS rr;
 struct SREGS sr;
 word i;

 clrscr();
 printf("Разделы жесткого диска # 1\n");
 printf("==========================\n\n");
 printf("Лог.диск    \nПризнак   =  \n
 printf("Код системы =  \nНачало: гол.=  \n");
 printf("   дор. =  \n   сект. =  \n");
 printf("Конец : гол.=  \n    дор. =  \n");
 printf("    сект.=  \nНач.сектор =  \n");
 printf("Размер   =  \n");
NEXT:
  /* Чтение при помощи прерывания 13. Только таким путем
    можно прочитать MBR, т.к. она не принадлежит никакому 
    логическому диску. */
  rr.h.ah=2;      /* Чтение */
  rr.h.al=1;      /* Секторов 1 */
  rr.h.dl=0x80;    /* Тв.диск */
  rr.h.dh=head;    /* Головка */
  rr.x.cx=Sect_Trk;  /* Дорожка, сектор */
  sr.es=FP_SEG(&mbr); /* Адрес буфера в ОП */
  rr.x.bx=FP_OFF(&mbr);
  int86x(0x13,&rr,&rr,&sr);
  /* Проверка ошибок чтения */
  if (rr.x.cflag) {
   printf("Ошибка чтения: %x. ",rr.h.ah);
   printf("Нажмите любую клавишу...\n\7");
   getch();
   exit();
   }
 /* В нач.установках EndList указывает на 1-й
   байт 1-го элемента pаздела */
 for (EndList=(word *)&mbr.rt[(i=0)];
   /* пока не встpетилась подпись MBR или pаздел
     нулевого pазмеpа */
   (*EndList!=0xaa55)&&(mbr.rt[i].Size>0L);
   /* пеpеход к след. эл-ту (++i), EndList указывает
     на 1-й байт следующего элемента pаздела */
   EndList=(word *)&mbr.rt[++i]) {
  /* кооpдинаты куpсоpа */
  y=4; x+=7; gotoxy(x,y++);
  if (mbr.rt[i].SysCode==5) printf("Ext  ");
  else printf("%-7c",'C'+ndrive++);
  gotoxy(x,y++); printf("%02xH    ",mbr.rt[i].ActFlag);
  gotoxy(x,y++); printf("%-7d",mbr.rt[i].SysCode);
  gotoxy(x,y++); printf("%-7d",mbr.rt[i].Begin_Hd);
  gotoxy(x,y++);
  printf("%-7u",TRK(mbr.rt[i].Begin_SecTrk));
  gotoxy(x,y++);
  printf("%-7u",SECT(mbr.rt[i].Begin_SecTrk));
  gotoxy(x,y++); printf("%-7d",mbr.rt[i].End_Hd);
  gotoxy(x,y++);
  printf("%-7u",TRK(mbr.rt[i].End_SecTrk));
  gotoxy(x,y++);
  printf("%-7u",SECT(mbr.rt[i].End_SecTrk));
  gotoxy(x,y++); printf("%-7lu",mbr.rt[i].RelSec);
  gotoxy(x,y++); printf("%-7lu",mbr.rt[i].Size);
  if (mbr.rt[i].SysCode==5) {
   /* если код системы 5, pаздел содеpжит свою таблицу
    pазделов; устанавливается ее дисковый адpес,
    и новая таблица считывается в память */
   head=mbr.rt[i].Begin_Hd;
   Sect_Trk=mbr.rt[i].Begin_SecTrk;
   goto NEXT;
   }
  }
 gotoxy(x,y++); printf("\n\nНажмите любую клавишу...");
 getch();
}

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

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

Boot-сектор является самым первым сектором логического диска (номер логического сектора - 0). Для дискеты его физический адрес - дорожка 0, головка 0, сектор 1; для логического диска на жестком диске адрес опеределяется полями Begin_Hd, Begin_SecTrk соответствующего элемента таблицы разделов. При загрузке системы с дискеты именно этот сектор считывается в память программой POST и выполняет дальнейшую загрузку; при загрузки с жесткого диска этот сектор считывается загрузчиком, содержащимся в MBR. Boot-сектор содержит информацию о физических параметрах диска и о размещении на нем системной информации, используемой далее системой при работе с диском, поэтому даже если дискета и несистемная, то есть, загрузка с нее не выполняется, на ней все равно имеется Boot-сектор с данными о носителе.

Формат Boot-сектора различен для версий DOS до 4.0 и от 4.0 и выше. В программном примере 10.3 структурой struct BootRec описан Boot-сектор для DOS 4.0 и выше. Дадим некоторые комментарии к полям этой структуры.
Первые 3 байта сектора содержат команду JMP, обеспечивающую переход на программу загрузки. Следующие 8 байт - символьный идентификатор программного средства, производившего форматизацию дискеты (диска); если дискета форматировалась системной утилитой FORMAT, там записан номер версии операционной системы. Поле SectSize всегда содержит значение 512. Поле ClustSize показывает, сколько секторов содержится в одном кластере - единице распределения дисковой памяти. Поле ResSect содержит число секторов, расположенных до начала FAT, обычно это 1 - перед FAT имеется только Boot-сектор. Число копий FAT (поле FatCnt) всегда 2. Поле RootSize показывает размер Корневого Каталога, чтобы узнать число секторов в корневом каталоге надо содержимое этого поля разделить на 16. Поле TotSecs - общее число секторов на диске для диска, отформатированного в DOS 4.0 или выше, содержит 0, если число секторов больше 65535, в этом случае число секторов может быть выбрано из поля LongTotSecs. Байт Media идентифицирует тип носителя; возможны такие его значения: 0xFF - 2 стороны, 8 секторов на дорожке; 0xFE - 1 сторона, 8 секторов на дорожке; 0xFD - 2 стороны, 9 секторов на дорожке; 0xFC - 1 сторона, 9 секторов на дорожке; 0xF9 - 2 стороны, 15 секторов на дорожке; 0xF8 - жеский диск. Поле FatSize - число секторов в одной копии FAT. Поля TrkSecs, HeadCnt содержат значения, необходимые для преобразования дискового адреса по формуле 10.1. Поля HidnSecL и HidnSecH для DOS 4.0 и выше можно интерпретировать как одно поле: dword HidnSec - это количество скрытых секторов, упоминавшихся выше. Для DOS ниже 4.0 используется 2-байтное число скрытых секторов - HidnSecL, и на этом область данных Boot-сектора кончается. Поле Drive - номер дисковода, на котором диск форматировался. Поле DOS4_flag содержит код 0x29 (символ ')' - правая скобка), если диск форматировался в DOS 4.0 и выше. Серийный номер тома - случайное число, записываемое в поле VolNum при форматизации, может использоваться в дальнейшем для идентификации диска, как и метка тома (поле VolLabel). Наконец, поле FatForm содержит символьную последовательность 'FAT12' или 'FAT16', в зависимости от формата FAT. После этой системной информации в Boot-секторе записана программа начальной загрузки.
Для версий DOS более ранних, чем 4.0 блок системной информации в Boot-секторе имеет меньший размер - до поля HidnSecL включительно. Поля от SectSize по FatSize включительно образуют так называемый Блок Параметров BIOS (BPB - BIOS Parameter Block), он формируется в оперативной памяти драйвером диска и в дальнейшем используется при всех операциях с данным диском. В DOS 4.0 и выше принят Расширенный BPB - от поля SectSize до LongTotSecs включительно.

Опыт общения со студентами подсказывает нам, что будет нелишним напоминание о том, что формат Boot-сектора зависит не от версии DOS, установленной на той ПЭВМ, на которой дискета читается, а от версии на той ПЭВМ, где дискета форматировалась. Поскольку формат ранних версий полностью перекрывается форматом поздних, совместимость по носителям сохраняется.

Программа примера 10.3 демонстрирует чтение Boot-сектора дискеты или логического диска. Boot-сектор считывается средствами DOS как логический сектор 0. Поскольку прерывание 0x25 имеет два варианта применения - в зависимости от объема диска, программа запрашивает объем диска и по-разному готовит исходные данные для чтения сектора. Считанный сектор программа распечатывает - в зависимости от содержимого поля DOS4_flag в формате той или иной версии.


/*==          ПРИМЕР 10.3           ==*/
/*==== Корневой (загрузочный) сектор логического диска ===*/
#include <dos.h>
#define byte unsigned char
#define word unsigned int
#define dword unsigned long
 /*   Структура корневой записи DOS 4.x       */
 struct RootRec {
  byte jmp[3];         /* Переход на загрузку */
  char ident[8];       /* Идентификатор системы */
   /*   Расширенный Блок Параметров BIOS      */
   /* стандартная часть                */
   word SectSize;      /* Размер сектора (байт) */
   byte ClustSize;     /* Размер кластера (сект) */
   word ResSect;        /* Резервных секторов */
   byte FatCnt;          /* Число копий FAT */
   word RootSize;   /* Размер корневого оглавления
        (число элементов оглавления по 32 байта) */
   word TotSecs;       /* Общее число секторов */
   byte Media; /* Тип диска (то же, что 1-й байт FAT */
   word FatSize;       /* Размер FAT (секторов) */
   /* расширение; следующие 3 поля не входят в BPB
     для DOS 3.x, но входят в загрузочную запись   */
   word TrkSecs;        /* Секторов на дорожке */
   word HeadCnt;        /* Число поверхностей */
   word HidnSecL;    /* Число спрятанных секторов
                     (младшая часть) */
   /* эта часть имеется только для DOS 4.x и больше  */
   word HidnSecH;         /* (старшая часть) */
   /* для диска >32 Мбайт используется вместо TotSecs */
   dword LongTotSecs;        /* Число секторов */
   /* конец расширенного BPB             */
  byte Drive;      /* Физический номер дисковода */
  byte reserved1;
  byte DOS4_flag;   /* Код 41 в этом поле - признак
            расширенного формата загр.записи */
  dword VolNum;         /* Серийный номер тома */
  char VolLabel[11];           /* Метка тома */
  char FatForm[8];         /* FAT12 или FAT16 */
  /* Далее следуют программа и данные загрузки */
  } *rt;
 byte buffer[512];
/* Структура параметров для INT 25 при работе с большим
 диском (>32 Мбайт) */
struct{
  dword first_sect;   /* # логического сектора */
  word count;      /* число секторов */
  byte *ptr;       /* адрес в памяти */
  } parm;
 union REGS rr;
 struct SREGS sr;

main()
 {
 char drive;  /* идентификатор дисковода */
 byte sys;   /* признак объем > 32 Мбайт */
ASK1: printf("\nУкажите имя диска >");
 drive=getche();
 if (drive>'b') {
ASK2:printf("\nОбьем лог. диска больше 32 Мбайт? (y/n) >");
  sys=getche();
  switch(sys) {
   case 'y' : sys=1; break;
   case 'n' : sys=0; break;
   default: goto ASK2;
   }
  }
 else sys=0;
 /* Чтение при помощи прерывания DOS 25 */
 rr.h.al=drive-'a'; /* Диск */
 if (!sys) {
  /* диск < 32 Мбайт */
  rr.x.cx=1; /* Секторов 1 */
  rr.x.dx=0; /* Логический сектор 0 */
  sr.ds=FP_SEG(buffer); /* Адрес буфера в ОП */
  rr.x.bx=FP_OFF(buffer);
  }
 else {
  /* диск > 32 Мбайт */
  parm.first_sect=0;/* Логический сектор 0 */
  parm.count=1; /* Секторов 1 */
  parm.ptr=buffer; /* Адрес буфера в ОП */
  sr.ds=FP_SEG(&parm); /* Адрес области параметров */
  rr.x.bx=FP_OFF(&parm);
  rr.x.cx=0xffff;
  }
 int86x(0x25,&rr,&rr,&sr);
 /* Проверка ошибок чтения */
 if (rr.x.cflag) {
  printf("\nОшибка чтения: %x. ",rr.h.ah);
  printf("Нажмите любую клавишу...\n\7"); getch();
  exit();
  }
 rt=(struct RootRec *)buffer;
 /* Вывод результатов */
 clrscr();
 printf("Загрузочная запись дискеты/диска %c:\n",drive-32);
 printf("==================================\n\n");
 printf("Команда JMP -------------------> ");
 for(i=0;i<3;printf("%02x",rt->jmp[i++]));printf("H\n");
 printf("Тип носителя ------------------> %02xH\n",
  rt->Media);
 printf("Система -----------------------> ");
 for(i=0;i<8;printf("%c",rt->ident[i++]));printf("\n");
 if (rt->DOS4_flag==41)
  printf("==== Расширенный блок параметров BIOS ====\n");
 else
  printf("========== Блок параметров BIOS ==========\n");
 printf("Байт в секторе ----------------> %u\n",
  rt->SectSize);
 printf("Секторов в кластере -----------> %u\n",
  rt->ClustSize);
 printf("Секторов на дорожке -----------> %u\n",
  rt->TrkSecs);
 printf("Число головок -----------------> %u\n",
  rt->HeadCnt);
 printf("Резервных секторов ------------> %u\n",
  rt->ResSect);
 printf("Скрытых секторов --------------> ");
 if (!sys && rt->DOS4_flag==41) printf("%u\n",
  rt->HidnSecL);
 else printf("%lu\n",(dword)rt->HidnSecL);
 printf("Всего секторов ----------------> ");
 if (rt->TotSecs>0) printf("%u\n",rt->TotSecs);
 else printf("%lu\n",rt->LongTotSecs);
 printf("Копий FAT ---------------------> %u\n",
  rt->FatCnt);
 printf("Секторов в одной FAT ----------> %u\n",
  rt->FatSize);
 if (rt->DOS4_flag==41) {
  printf("Формат FAT --------------------> ");
  for(i=0;i<8;printf("%c",rt->FatForm[i++]));
  printf("\n");
  }
 printf("Элементов в корневом каталоге -> %u\n",
  rt->RootSize);
 if (rt->DOS4_flag==41) {
  printf("\nНомер дисковода ---------------> %xH\n",
   rt->Drive);
  printf("Серийный номер диска ----------> %08xH\n",
   rt->VolNum);
  printf("Метка тома --------------------> ");
  for(i=0; i<11; printf("%c",rt->VolLabel[i++]));
  printf("\n");
  }
 printf("\nНажмите любую клавишу...\n"); getch();
}

10.3.3.Следом за Boot-сектором на диске расположена FAT. С точки зрения распредения дискового пространства диск разбит на кластеры. Кластер представляет собой группу последовательно расположенных секторов. Если DOS получает запрос на выделение дискового пространства, она выделяет сразу целый кластер. Размер кластера является компромиссом между двумя противоречивыми требованиями: с точки зрения экономии дискового пространства выгоден малый размер кластера, так как для маленького файла (размером даже в один байт) выделяется целый кластер, большая часть которого не используется. Но с другой стороны, при малом размере кластера их на диске получится очень много, и управление ими усложняется. FAT представляет собой "карту" дискового пространства - массив элементов, каждый из которых соответствует одному кластеру диска. Номер элемента соответствует номеру кластера.
Поскольку область диска, содержащая системную информацию, распределена предварительно, она в FAT не отражается. Первые два элемента FAT не используются (первый байт содержит код, совпадающий с полем Media Boot-сектора), нумерация кластеров области данных диска начинается, таким образом, с 2. Размер элемента FAT может быть 12 или 16 бит (см. поле SysCode в элементе раздела жесткого диска, а также поле FatForm в Boot -секторе). Описание содержимого полей далее дается для 12-битного формата FAT, в скобках указываются значения для 16-битного формата. Значения элементов FAT от 2 до 0xFEF (0xFFEF) включительно - информационные значения. Такое значение - номер следующего кластера, распределенного данному файлу. Таким образом, FAT обеспечивает списковую структуру распределения - каждый ее элемент содержит указание на следующий элемент. Поскольку кластеры распределяются файлу по мере его заполнения, файл не обязательно занимает смежные кластеры, FAT может обеспечить связывание в цепочку разнесенных по диску кластеров. Другие значения элементов зарезервированы для системной информации. Значение 0 индицирует свободный кластер. Значения элементов от 0xFF0 до 0xFF6 (от 0xFFF0 до 0xFFF6) - резервные кластеры. Значение 0xFF7 (0xFFF7) - сбойный кластер. Значения от 0xFF8 до 0xFFF (от 0xFFF8 до 0xFFFF) - признак конца цепочки, последнего кластера файла.

Программа примера 10.4 считывает FAT и распечатывает поэлементно ее начальную часть. Для определения начального сектора и размера FAT программа вынуждена вначале считать в память Boot-сектор так же, как это делалось в предыдущем примере. Для диска, отформатированного в DOS 4.0 или выше программа может определить формат FAT по содержимому поля FatForm Boot-сектора, в противном случае программа запрашивает формат у оператора.
Выборка элемента 12-битной FAT ведется по следующему алгоритму: выбирается слово по смещению i*1,5 от начала FAT, где i - номер кластера; если i четное, содержимое элемента FAT составляют младшие 12 бит этого слова, в противном случае - старшие 12 бит.
Для 16-битной FAT слово по смещению i*2 содержит элемент FAT.


/*==          ПРИМЕР 10.4          ==*/
/*========== Чтение Таблицы Размещения Файлов ==========*/
#include <dos.h>
#include <alloc.h>
#define byte unsigned char
#define word unsigned int
#define dword unsigned long
 /*   Структура корневой записи  */
 struct RootRec {
  byte jmp[3], ident[8];
  word SectSize; byte ClustSize;
  word ResSect; byte FatCnt;
  word RootSize, TotSecs; byte Media;
  word FatSize, TrkSecs, HeadCnt, HidnSecL, HidnSecH;
  dword LongTotSecs; byte Drive;
  byte reserved1, DOS4_flag;
  dword VolNum; char VolLabel[11], FatForm[8];
  } *rt;
/* Структура параметров для INT 25 */
struct{
  dword first_sect;
  word count; byte *ptr;
  } parm;
union REGS rr;
struct SREGS sr;
main()
{
 byte *buff;      /* адрес буфера в ОП */
 byte sys;  /* признак диска > 32 Мбайт */
 char drive; /* идентификатор диска */
 byte fat16;      /* признак 16-битной FAT */
 word ss;
 int i,k,m,j;

ASK1:printf("\nУкажите идентификатор диска (A,B...) >");
 drive=getche();
 if (drive>'b') {
ASK2:printf("\nОбьем лог. диска больше 32 Мбайт? (y/n) >");
  sys=getche();
  switch(sys) {
   case 'y': sys=1;break;
   case 'n': sys=0;break;
   default: goto ASK2;
   }
  }
 else sys=0;
 buff=(byte *)malloc(512);
 /* Чтение boot-сектора */
 rr.h.al=drive-'a'; /* Диск */
 if (!sys) { rr.x.cx=1; rr.x.dx=0;
  sr.ds=FP_SEG(buff); rr.x.bx=FP_OFF(buff); }
 else { sr.ds=FP_SEG(&parm); rr.x.bx=FP_OFF(&parm);
   parm.first_sect=0; parm.count=1; parm.ptr=buff;
   rr.x.cx=0xffff; }
 int86x(0x25,&rr,&rr,&sr); readerror();
 rt=(struct RootRec *)buff;
 /* определение формата FAT */
 if (rt->DOS4_flag==41) {
  /* для диска, отформатированного в DOS 4 можно
    определить формат FAT из Boot-сектора */
  if (!memcmp(rt->FatForm,"FAT16",5)) fat16=1;
  else fat16=0;
  }
 else {
ASK3:printf("\nФормат FAT 12-битный? (y/n)");
  fat16=getche();
  switch(fat16) {
   case 'y': fat16=0; break;
   case 'n': fat16=1; break;
   default: goto ASK3;
   }
  }
 /* Выделение памяти под FAT */
 buff=(byte *)realloc(buff,rt->FatSize*512);
 if (buff==NULL) { printf("Нехватка памяти\n"); exit(); }
 /* Чтение FAT */
 rr.h.al=drive-'a';
 if (!sys) { /* маленький диск */
  rr.x.cx=rt->FatSize;
  rr.x.dx=rt->ResSect;
  sr.ds=FP_SEG(buff); rr.x.bx=FP_OFF(buff);
  }
 else { /* большой диск */
  parm.first_sect=rt->ResSect;
  parm.count=rt->FatSize;
  parm.ptr=buff;
  sr.ds=FP_SEG(&parm); rr.x.bx=FP_OFF(&parm);
  rr.x.cx=0xffff;
  }
 int86x(0x25,&rr,&rr,&sr); readerror();
 clrscr();
 /* Форматная распечатка FAT */
 printf("   Диск %c:     FAT - %d бит\n",
  drive-32,fat16*4+12);
 printf("  |"); for(i=0;i<10;printf("%5d ",i++));
 printf("\n____|____________________________________");
 printf("________________________");
 for(j=0,i=0,k=0;i<220;i++) {
  if (!k) printf("\n%3d |",j);
  if (!fat16) {
   m=(i*3)/2; ss=*(word *)(buff+m);
   if(i%2) /* нечетный элемент */ ss>>=4;
   else /* четный элемент */ ss&=0x0fff;
   if (ss>0x0fef) printf(" %03xH ",ss);
   else printf("%5d ",ss);
   }
  else {
   m=i*2; ss=*(word *)(buff+m);
   if (ss>0xffef) printf("%04xH ",ss);
   else printf("%5d ",ss);
   }
  if (++k>=10) { j+=10; k=0; }
  }
 getch(); free(buff);
}
/*==== Проверка ошибок чтения ====*/
readerror() {
 if (rr.x.cflag) {
  printf("\nОшибка чтения: %x. ",rr.h.ah);
  printf("Нажмите любую клавишу...\n\7"); getch();
  exit();
  }
}

10.3.4. Как уже упоминалось, на диске хранятся две идентичные копии FAT, расположенные одна за другой. Следом за FAT размещается Корневой Каталог. Корневой Каталог состоит из Элементов Каталога. В программе примера 10.5 Элемент Каталога описывается структурой struct Dir_Item. Вот некоторые дополнительные комментарии к полям этой структуры.
Поле fname содержит имя файла или подкаталога, причем для файла байты 0-7 поля содержат имя, а 8-10 - расширение, неиспользуемые байты поля заполнены пробелами. Если файл удаляется из каталога, то в соответствующем элементе первый символ имени заменяется на код 0xE5, это оставляет возможность восстановления случайно удаленного файла, если, однако, за это время Элемент Каталога не будет использован для нового файла. Код 0 в первом символе имени означает, что элемент свободен и никогда не использовался. При внесении в каталог нового файла система сначала использует элементы удаленных файлов, и лишь затем - свободные.
Поле атрибутов содержит признаки, характеризующие файл. Распределение разрядов байта атрибутов следующее:

  0 a d l s h r,
где
AL- номер логического диска;
CX=0xFFFF- признак того, что работа производится с логическим диском, объем которого больше 32 Мбайт;
DS:BX- адрес блока параметров в оперативной памяти, ко- торый может быть описан следующей структурой:
r- 1 в этом бите означает, что файл только для чтения;
h- скрытый файл;
s- системный файл;
l- метка тома (может быть только в Корневом Каталоге);
d- подкаталог;
a- 1 в этом бите означает, что копия файла утилитой BACKUP не создавалась.
Поля time и date содержат время и дату последней модификации файла. Формат времени: ЧЧЧЧЧММММММССССС (Часы, Минуты, Секунды); формат даты: ГГГГГГГММММДДДДД (Год, Месяц, День).
Поле cl - номер первого кластера, распределенного файлу, то есть, начало той цепочки, которая продолжается в FAT.
Поле size - размер файла в байтах. В DOS не предусмотрены какие-либо специальные признаки конца файлов. Применяемый в некоторых случаях символ ^Z (код 26) интерпретируется как признак конца конкретными программами, но не DOS. DOS же определяет конец файла по его размеру, получаемому из этого поля Элемента Каталога. Для подкаталога это поле содержит 0.

Программа примера 10.5 считывает и выводит на печать содержимое Корневого Каталога. Как и в предыдущих примерах, программа вынуждена сначала прочитать Boot-сектор. Из Boot-сектора программа узнает:


/*==           ПРИМЕР 10.5          ==*/
/*============= Чтение корневого оглавления ==============*/
#include <alloc.h>
#include <dos.h>
#define byte unsigned char
#define word unsigned int
#define dword unsigned long
/* Структура, описывающая элемент оглавления */
struct Dir_Item {
  char fname[11];   /* имя файла */
  byte attr;      /* атрибут */
  byte reserved[10];
  word time;      /* время */
  word date;      /* дата */
  word cl;       /* номер 1-го кластера */
  dword size;      /* размер файла */
  } *dir;
 /*   Структура корневой записи  */
 struct RootRec {
  byte jmp[3], ident[8]; word SectSize; byte ClustSize;
  word ResSect;  byte FatCnt;
  word RootSize, TotSecs; byte Media;
  word FatSize, TrkSecs, HeadCnt, HidnSecL, HidnSecH;
  dword LongTotSecs;  byte Drive;
  byte reserved1;  byte DOS4_flag;
  dword VolNum; char VolLabel[11], FatForm[8];
  } *rt;
/* Структура параметров для INT 25 */
struct{
  dword first_sect;
  word count; byte *ptr;
  } parm;
 union REGS rr;
 struct SREGS sr;
char *attrs[]={"ТОЛЬКО_ЧТЕНИЕ","СКРЫТЫЙ_ФАЙЛ",
  "СИСТЕМНЫЙ_ФАЙЛ","МЕТКА_ТОМА","ПОДКАТАЛОГ"};
main()
 {
 byte *buff; /* адрес буфера в ОП */
 byte sys,  /* признак диска > 32 Мбайт */
    drive; /* идентификатор диска */
 int i, j, k, m;
 byte ms;   /* маска атрибутов */
 word RootSect,  /* номер сектора */
    RootCnt,   /* число элементов */
    RootSects;  /* число секторов */

ASK1: printf("\nУкажите идентификатор диска (A,B...) >");
 drive=getche();
 if (drive>'b') {
ASK2:printf("\nОбьем лог. диска больше 32 Мбайт? (y/n) >");
  switch(sys=getche()) {
   case 'y' : sys=1; break;
   case 'n' : sys=0; break;
   default: goto ASK2;
   }
  }
 else sys=0;
 buff=(byte *)malloc(512);
 /* Чтение boot-сектора */
 rr.h.al=drive-'a'; /* Диск */
 if (!sys) { rr.x.cx=1; rr.x.dx=0;
  sr.ds=FP_SEG(buff); rr.x.bx=FP_OFF(buff); }
 else { parm.first_sect=0; parm.count=1; parm.ptr=buff;
  sr.ds=FP_SEG(&parm); rr.x.bx=FP_OFF(&parm);
  rr.x.cx=0xffff; }
 int86x(0x25,&rr,&rr,&sr); readerror();
 rt=(struct RootRec *)buff;
 /* Параметры корневого каталога */
 RootCnt=rt->RootSize;
 RootSect=rt->ResSect+rt->FatSize*rt->FatCnt;
 RootSects=rt->RootSize/16;
 /* Выделение памяти под корневой каталог */
 buff=(byte *)realloc(buff,RootSects*512);
 if (buff==NULL) { printf("Нехватка памяти\n"); exit(); }
 dir=(struct Dir_Item *)buff;
 clrscr();
 /* Чтение каталога */
 rr.h.al=drive-'a';
 if (!sys) { /* маленький диск */
  rr.x.cx=RootSects; rr.x.dx=RootSect;
  sr.ds=FP_SEG(buff); rr.x.bx=FP_OFF(buff);
  }
 else { /* большой диск */
  parm.first_sect=RootSect; parm.count=RootSects;
  parm.ptr=buff;
  sr.ds=FP_SEG(&parm); rr.x.bx=FP_OFF(&parm);
  rr.x.cx=0xffff;
  }
 int86x(0x25,&rr,&rr,&sr);
 readerror();
 /* Распечатка оглавления */
 for (j=0, k=0; j<RootCnt; j++) {
  printf("#%3d  ",j);
  if (dir[j].fname[0]!=0) {
   /* Если элемент оглавления непустой */
   for (i=0; i<11; i++) {
    if (i==8) printf(".");
    if ((i==0)&&(dir[j].fname[i]==0xe5)) printf("?");
    else printf("%c",dir[j].fname[i]);
    }
   if (dir[j].fname[0]==0xE5)
    printf(" ******УДАЛЕН****** ");
   printf(" атрибут=%02x (",dir[j].attr);
   if (!(dir[j].attr&0x01f)) printf("ЧТЕНИЕ/ЗАПИСЬ");
   else for(ms=1, m=0; m<5; m++,ms<<=1)
   if (dir[j].attr&ms) printf("%s ",attrs[m]);
   printf(")\n   ");
   printf(" время=%02d/%02d/%02d",
    (dir[j].time>>11)&0x001f,(dir[j].time>>5)&0x003f,
    (dir[j].time&0x001f)*2);
   printf(" дата=%02d/%02d/%04d",
    ((dir[j].date>>9)&0x007f)+1980,
    (dir[j].date>>5)&0x000f,
    dir[j].date&0x001f);
   printf(" нач.кластер=%03x",dir[j].cl);
   printf(" размер=%lu\n",dir[j].size);
   }
  else printf(" свободен\n");
  if (++k>7) { k=0; if (getch()==27) goto EOJ; }
  }
EOJ:free(buff);
}
/* Проверка ошибок чтения */
readerror() {
 if (rr.x.cflag) {
  printf("\nОшибка чтения: %x. ",rr.h.ah);
  printf("Нажмите любую клавишу...\n\7"); getch();
  exit();
  }
}

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

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

Поскольку программный пример 10.6 представляет собой в основном сведение воедино предыдущих примеров, для него будет достаточно самого поверхностного описания.
Задание (полный путь и имя файла) вводится в строку job, указатель jobptr отслеживает в процессе выполнения программы текущее состояние лексического разбора задания. Первые три символа задания интерпретируются как идентификация логического диска, например: "A:\". В дальнейшем для лексического разбора задания применяется функция Get_Name, которая выделяет из задания очередное слово и переназначает jobptr. Пустое (NULL) значение jobptr свидетельствует об исчерпании задания.
Для жесткого диска выполняется по алгоритму, подобному примеру 10.2, выборка элемента таблицы разделов для заданного диска (функция Read_Mbr). Здесь и далее все операции чтения выполняются функцией Read_13, читающей один сектор при помощи прерывания BIOS. Выбранный элемент сохраняется в глобальной структуре part и используется в дальнейшем. Функция Read_Boot считывает Bootсектор логического диска, причем для гибкого диска адрес этого сектора назначается - 0, 0, 1, а для жесткого - выбирается из part. Здесь же (функция Get_First) определяется абсолютный номер начального сектора логического диска и запоминается в переменной First_Sect. Это значение вычисляется из физического адреса начала, получаемого из полей Begin_Hd, Begin_SecTrk элемента таблицы разделов; но можно предложить и другой способ его получения - в процессе перебора таблицы разделов суммировать значения полей RelSect. В дальнейшем дисковые адреса в программе представляются в виде номера логического сектора (в пределах данного логического диска), и только непосредственно перед чтением номер логического сектора преобразуется (функция Sect_to_Daddr) в физический адрес. FAT считывается в память целиком (функция Read_Fat), адрес начала FAT на диске и ее размер определяются из ранее прочитанного Boot-сектора. Далее происходит поиск имени - выделенного из задания слова в каталоге. Поиск, естественно, начинается с корневого каталога. Начальный сектор корневого каталога определяется из Boot -сектора, оттуда же выбираются и исходные данные для определения числа секторов в корневом каталоге. Эти секторы поочередно считываются в память и интерпретируются как массив из 16 элементов оглавления. В каждом секторе ищется выбранное имя (функция Find_Dir). При поиске в подкаталоге выбирается номер начального кластера подкаталога, переводится в номер сектора (функция Clust_to_Sect) и поочередно считываются подряд идущие секторы, входящие в этот кластер. При исчерпании кластера определяется номер следующего кластера, и операции повторяются для следующего кластера. Определение номера следующего кластера выполняет функция Next_Clust, анализирующая FAT. Для последнего кластера (и для корневого каталога) эта функция возвращает нулевое значение. При поиске имени в каталоге учитываются значения поля атрибутов: если искомое имя не является последним словом в задании, то оно должно представлять собой имя подкаталога, в противном случае - имя файла, атрибуты найденного элемента должны соответствовать этому условию. Если имя найдено, то переменной dirnum присваивается значение индекса найденного элемента в массиве элементов каталога, и из этого элемента выбирается номер начального кластера соответствующего подкаталога или файла. Когда найдено имя файла, файл считывается в память посекторно (так же, как подкаталог) прочитанное выводится на экран в текстовом виде. При выводе каждого следующего байта модифицируется счетчик размера файла, программа завершается при исчерпании этого счетчика.


/*==          ПРИМЕР 10.6          ==*/
/*======== Поиск и чтение файла средствами BIOS ========*/
#include <dos.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
/*== типы и структуры данных ==*/
#define byte unsigned char
#define word unsigned int
#define dword unsigned long
#define daddr struct DADDR
struct DADDR { /* физический дисковый адрес */
 byte h; /* головка */
 word s, /* сектор */
    t, /* дорожка */
    ts; /* сектор, дорожка упакованные */
 };
struct PART { /* структура элемента раздела */
 byte Boot, Begin_Hd; word Begin_SecTrk;
 byte SysCode, End_Hd; word End_SecTrk;
 dword RelSec, Size;
 };
struct MBR { /* стpуктуpа главной загpузочной записи */
 char LoadCode[0x1be]; struct PART rt[4]; word EndFlag;
 };
struct BootRec { /* структура корневой записи */
 byte jmp[3], ident[8]; word SectSize;
 byte ClustSize; word ResSect;
 byte FatCnt; word RootSize ,TotSecs;
 byte Media; word FatSize, TrkSecs, HeadCnt;
 word HidnSecL, HidnSecH; dword LongTotSecs;
 byte Drive, reserved1, DOS4_flag;
 dword VolNum; char VolLabel[11], FatForm[8];
 };
struct Dir_Item { /* структура элемента оглавления */
  char fname[11]; byte attr; char reserved[10];
  word time, date, cl; dword size;
  };
/*== описания функций ==*/
void Read_Mbr(void);
void Read_Boot(void);
void Get_First(void);
void Read_Fat(void);
void Read_13(void *mem);
void Sect_to_Daddr(dword sect);
dword Clust_to_Sect(word clust);
word Next_Clust(word clust);
char *Get_Name(char *s, char *d);
int  Find_Name();
void End_of_Job(int n);
/*== переменные ==*/
struct PART part;   /* текущий элемент раздела */
byte buff1[512];    /* буфер MBR и boot */
struct MBR *mbr;    /* указатель на таблицу разделов */
struct BootRec *boot; /* указатель на корневую запись */
byte buff2[512];    /* буфер каталога и текста */
struct Dir_Item *dir; /* указатель на часть каталога */
char *text;      /* указатель на текстовый буфер */
byte *fat;       /* указатель на FAT */
char job[81];     /* строка-задание */
char *jobptr;     /* текущий указатель в job */
char cname[12];    /* текущее имя для поиска */
byte Fdisk;      /* физический номер диска */
daddr caddr;      /* текуший дисковый адрес */
dword sect;      /* текуший номер сектора */
word clust;      /* текуший номер кластера */
byte fat16;      /* признак формата FAT */
dword fsize;      /* размер файла */
int dirnum;      /* номер элемента в оглавлении */
dword FirstSect;    /* абс.сектор начала */
byte rootdir=1;    /* признак корневого каталога
             или подкаталога (1/0) */
word lastsect;     /* последний сектор при чтении */
byte fatalloc=0;    /* признак выделения памяти */
/*-------------------------------------------------*/
main() {
int n;
 /* ввод имени файла */
 printf("Укажите имя файла >"); scanf("%s",job);
 /* перевод в верхний регистр */
 strupr(job);
 /* проверка правильности идентификатора диска */
 if ((!isalpha(job[0]))||(job[1]!=':')||(job[2]!='\\')) {
  printf("%c%c%c -",job[0],job[1],job[2]); End_of_Job(0);
  }
 jobptr=job+3;
 if (job[0]>'A') {
  /* для жесткого диска - физический номер и чтение MBR */
  Fdisk=0x80; Read_Mbr();
  }
 else /* для гибкого диска - физический номер */
  Fdisk=job[0]-'A';
 Read_Boot(); /* чтение boot-сектора */
 Read_Fat(); /* чтение FAT */
 dir=(struct Dir_Item *)buff2;
 do { /* движение по каталогам */
  if (!rootdir)
   clust=dir[dirnum].cl; /* начальный кластер */
  /* выделение следующего элемента из строки-задания */
  jobptr=Get_Name(jobptr,cname);
  do { /* пока не дойдем до последнего кластера */
   if (rootdir) { /* корневой каталог */
    /* нач.сектор корневого огл. и число секторов */
    sect=boot->ResSect+boot->FatSize*boot->FatCnt;
    lastsect=boot->RootSize*32/boot->SectSize+sect;
    }
   else { /* подкаталог */
    sect=Clust_to_Sect(clust);
    lastsect=boot->ClustSize+sect;
    }
   /* посекторное чтение всего корневого каталога
     или одного кластера подкаталога */
   for (; sect<lastsect; sect++) {
    Sect_to_Daddr(sect); Read_13(dir);
    /* поиск имени в прочитанном секторе */
    if ((dirnum=Find_Name())>=0) goto FIND;
    }
   /* до последнего кластера подкаталога */
   } while (clust=Next_Clust(clust));
  /* весь каталог просмотрен, а имя не найдено
    - ошибка */
  printf("%s -",cname);
  if (jobptr==NULL) End_of_Job(4); else End_of_Job(5);
FIND: /* имя найдено */
  rootdir=0;
  } while (jobptr!=NULL);
 /* найдено имя файла */
 /* из оглавления получаем размер и 1-й кластер */
 fsize=dir[dirnum].size; clust=dir[dirnum].cl;
 text=(char *)buff2;
 /* движение по файлу */
 do {
  sect=Clust_to_Sect(clust);
  lastsect=sect+boot->ClustSize;
  /* посекторное чтение кластера */
  for ( ; sect<lastsect; sect++) {
   Sect_to_Daddr(sect); Read_13(text);
   /* распечатка прочитанного кластера */
   for (n=0;n<boot->SectSize;n++) {
    printf("%c",text[n]);
    /* контроль длины файла */
    if (!(--fsize)) goto EOJ;
    }
   }
  } while (clust=Next_Clust(clust));
 /* если кластеры закончились, а длина не исчерпана
   - ошибка */
 printf("\n%s -",job); End_of_Job(6);
EOJ: /* нормальное завершение программы */
 printf("\n"); End_of_Job(7);
}
/*---------------------------------------*/
/* чтение MBR и поиск требуемого раздела */
void Read_Mbr(void) {
 int i;
 char ndrive;
 word *EndList;
 caddr.h=0; caddr.ts=1; ndrive='C';
 mbr=(struct MBR *)buff1;
NEXT: Read_13(buff1);
 for (EndList=(word *)&mbr->rt[(i=0)];
   (*EndList!=0xaa55)&&(mbr->rt[i].Size>0L);
   EndList=(word *)&mbr->rt[++i]) {
  if (mbr->rt[i].SysCode==5) {
   caddr.h=mbr->rt[i].Begin_Hd;
   caddr.ts=mbr->rt[i].Begin_SecTrk;
   goto NEXT;
   }
  if (ndrive==job[0]) {
   movmem(&mbr->rt[i],&part,sizeof(struct PART));
   return;
   }
  else ndrive++;
  }
 /* требуемый раздел не найден */
 printf("%c: -",job[0]); End_of_Job(1);
}
/*---------------------*/
/* чтение boot-сектора */
void Read_Boot(void) {
 if (Fdisk<0x80) { caddr.h=0; caddr.ts=1; }
 else
  { caddr.h=part.Begin_Hd; caddr.ts=part.Begin_SecTrk; }
 Read_13(buff1);
 boot=(struct BootRec *)buff1;__ _.Get_First();
}
/*------------*/
/* чтение FAT */
void Read_Fat(void) {
dword s, ls;
byte *f;
 fat=(byte *)malloc(boot->FatSize*boot->SectSize);
 if (fat==NULL)
  { printf("Размещение FAT -"); End_of_Job(3); }
 fatalloc=1;
 s=boot->ResSect; ls=s+boot->FatSize;
 for (f=fat; s<ls; s++) {
  Sect_to_Daddr(s); Read_13(f);
  f+=boot->SectSize;
  }
 /* установка формата FAT */
 if (Fdisk>=0x80)
  if (part.SysCode==1) fat16=0; else fat16=1;
 else fat16=0;
}
/*--------------------------------------------*/
/* чтение 1 сектора при помощи прерывания 13. */
void Read_13(void *mem) {
/* mem - адрес в ОП */
union REGS rr;
struct SREGS sr;
 rr.h.ah=2; rr.h.al=1; rr.h.dl=Fdisk;
 rr.h.dh=caddr.h; rr.x.cx=caddr.ts;
 sr.es=FP_SEG(mem); rr.x.bx=FP_OFF(mem);
 int86x(0x13,&rr,&rr,&sr);
 /* Проверка ошибок чтения */
 if (rr.x.cflag&1) {
  printf("%u -",rr.h.ah); End_of_Job(2);
  }
}
/*-------------------------------------------------*/
/* определение абс.номера сектора начала лог.диска */
void Get_First(void) {
 word s, t;
 if (Fdisk<0x80) FirstSect=0;
 else {
  /* формирование # сектора из физич. дискового адреса */
  t=(part.Begin_SecTrk>>8)|((part.Begin_SecTrk<<2)&0x300);
  s=part.Begin_SecTrk&0x3f;
  FirstSect=(((dword)t*boot->HeadCnt)+part.Begin_Hd)*
   boot->TrkSecs+s-1;
  }
}
/*--------------------------------------------------------*/
/* формирование физического дискового адреса из # сектора */
void Sect_to_Daddr(dword sect) {
/* sect - номер сектора, caddr - адрес на диске */
dword s;
 if (Fdisk>=0x80) sect+=FirstSect;
 caddr.s=sect%boot->TrkSecs+1;
 s=sect/boot->TrkSecs;
 caddr.h=s%boot->HeadCnt;
 caddr.t=s/boot->HeadCnt;
 caddr.ts=(caddr.t<<8)|caddr.s|((caddr.t&0x300)>>2);
}
/*----------------------------------------------*/
/* вычисление номера сектора по номеру кластера */
dword Clust_to_Sect(word clust) {
/* clust - номер кластера, возвращает номер сектора */
 dword ds, s;
 ds=boot->ResSect+boot->FatSize*boot->FatCnt+
   boot->RootSize*32/boot->SectSize;
 s=ds+(clust-2)*boot->ClustSize;
 return(s);
}
/*------------------------------------*/
/* выборка следующего кластера по FAT */
word Next_Clust(word clust) {
/* clust - номер кластера, возвращает номер след.кластера
  или 0 - если следующего нет */
 word m, s;
 if (rootdir) return(0);
 if (!fat16) {
  m=(clust*3)/2; s=*(word *)(fat+m);
  if(clust%2) /* нечетный элемент */ s>>=4;
  else /* четный элемент */ s=*s1&0x0fff;
  if (s>0x0fef) return(0);
  else return(s);
  }
 else {
  m=clust*2; s=*(word *)(fat+m);
  if (s>0xffef) return(0);
  else return(s);
  }
}
/*-------------------------------------------------*/
/* выделение очередного элемента из строки-задания */
char *Get_Name(char *s, char *d) {
/* s - строка задания, d - выделенный элемент, возвращает
  указатель на новое начало строки задания. */
char *p,*r; int i;
 for(i=0;i<11;d[i++]=' '); d[11]='\0';
 if ((p=strchr(s,'\\'))==NULL) {
  /* последний элемент строки - имя файла */
  /* перезапись имени */
  for(r=s,i=0; (i<8)&&*r&&(*r!='.'); i++,r++) *(d+i)=*r;
  /* перезапись расширения */
  if (*r) for(i=0,r++; (i<3)&&*r; i++,r++) *(d+8+i)=*r;
  return(NULL);
  }
 else {
  /* очередной элемент - имя подкаталога */
  *p='\0';
  for(r=s,i=0; (i<11)&&*r; i++,r++) *(d+i)=*r;
  return(p+1);
  }
}
/*------------------------*/
/* поиск имени в каталоге */
int Find_Name() {__ _.int j;
/* cname - искомое имя, возвращает индекс найденного
  элемента в массиве dir или (-1) */
 for (j=0; j<boot->SectSize/sizeof(struct Dir_Item); j++) {
  if (dir[j].fname[0]=='\0') {
   /* конец использованных элементов каталога */
   printf("%s -",cname);
   if (jobptr==NULL) End_of_Job(4); else End_of_Job(5);
   }
  if ((byte)dir[j].fname[0]!=0xe5) {
   if (memcmp(dir[j].fname,cname,11)==0) {
   /* если имя совпало, то:
     - при поиске файла элемент не должен иметь
      атрибутов "подкаталог" или "метка тома",
     - при поиске подкаталога элемент должен иметь
      атрибут "подкаталог" */
    if (jobptr==NULL) {
     if ( !(dir[j].attr&0x18) ) return(j);
     }
    else if (dir[j].attr&0x10) return(j);
    }
   }
  }
 return(-1);
}
/*------------------------------------*/
/* завершение (при n=0-5 - аварийное) */
void End_of_Job(int n) {
/* n - номер сообщения */
char *msg[] = {
  "неправильный идентификатор диска",
  "лог.диск отсутствует",
  "ошибка чтения",
  "нехватка памяти",
  "подкаталог не найден",
  "файл не найден",
  "непредусмотренный конец файла",
  "======== КОНЕЦ ФАЙЛА ======="
  };
 /* освобождение памяти */
 if (fatalloc) free(fat);
 /* выдача сообщения */
 printf(" %s\nНажмите любую клавишу...\n\7",msg[n]);
 getch();
 /* завершение программы */
 exit(0);
}

10.4. Средства работы с файлами DOS

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

В DOS имеются два совершенно независимых метода работы с файлами: метод Управляющих Блоков Файлов (FCB - File Control Block) и метод файловых дескрипторов (Handle - переводится также как файловые описатель, файловое число, файловый индекс). Первый метод остался "по наследству" от старых версий DOS и далее - от CP/M. Второй метод, впервые реализованный в DOS 2.0, является предпочтительным в новых разработках, поэтому описание начнем с него.

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

0- стандартный файл ввода (обычно - клавиатура);
1- стандартный файл вывода (обычно - экран);
2- стандартный файл вывода сообщений об ошибках (всегда - экран);
3- стандартный файл AUX (асинхронный порт);
4- стандартный файл печати (1-й параллельный принтер).
Используя эти файловые дескрипторы, программист может обращаться к указанным устройствам, как к открытым дисковым файлам.

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


   struct FCB {
    byte drive; /* Логич. номер диска: 0-текущий, 1 - A, 2 - B и т.д. */
    char fname[8]; /* Имя файла (дополн.пробелами) */
    char fext[3]; /* Расширение файла (доп.пробелами) */
    word curblk; /* Текущий номер блока (в блоке 128 записей) */
    word recsize; /* Размер записи (байт) */
    dword fsize; /* Размер файла (байт) */
    word date; /* Дата создания (в формате элемента каталога) */
    word time; /* Время создания (в формате элемента каталога) */
    char reserved1[8];
    byte currec; /* Текущий номер записи в блоке */
    dword randrec; /* Относительный номер записи */
    };
Во всех операциях с файлом идентификатором файла служит адрес FCB.
Программист перед открытием файла должен заполнить поля drive, fname и fext, остальные поля заполняются при открытии файла системой и поддерживаются DOS при работе с файлом. Если при создании файла необходимо задать его атрибуты в каталоге, FCB должен иметь расширенный формат, а именно:

   struct Ext_FCB {
    byte flag;     /* Признак. Его значение 0xFF
      говорит о том, что представлен расширенный FCB */
    char reserved2[5];
    byte attr;     /* Атрибуты файла в каталоге */
    struct FCB fcb;   /* Стандартный FCB */
    };
На первый взгляд может показаться, что обрабатываемый файл должен состоять из записей фиксированной длины, но на самом деле это не так. Поле recsize после открытия файла имеет значение 128, но оно доступно программисту и может быть изменено в любой момент, что позволяет работать с записями переменной, в том числе и динамически изменяющейся длины.
Метод FCB использует так называемую Дисковую Передаточную Область (DTA - Disk Transfer Area). При загрузке программы эта область выделяется программе системой, но программист может организовать собственную DTA и сообщить системе ее адрес. Весь обмен с дисками ведется только через DTA - назначенную системой или установленную программистом. Это, однако, не требует обязательной пересылки данных между DTA и рабочими областями программы, так как программист может в любой момент переназначить адрес DTA на свою рабочую область.

10.4.2. Функции ввода-вывода
Оба метода предоставляют программисту полный набор функций DOS для ввода-вывода. Поскольку эти средства подробно описаны в справочной литературе, мы здесь ограничимся далеко не исчерпывающим их обзором.
При использовании метода дескрипторов адрес ASCIIZ-строки, содержащей полный путь и имя файла, передается функциям DOS, выполняющим создание/открытие файла, в регистрах DS:DX, а в AL - код режима разделения. Эти функции возвращают дескриптор открытого файла в регистре AX. Тем функциям, которые работают с уже открытым файлом, файловый дескриптор передается в регистре BX. Для функций, выполняющих обмен с диском, адрес буфера в памяти задается в DS:DX, а в CX - счетчик байт для обмена, эти функции возвращают в AX число действительно переданных байт. Для всех функций флаг CY служит индикатором ошибки, если он взведен, то AX содержит код ошибки. В операциях чтения/записи обмен начинается с того байта файла, на который указывает текущее значение указателя чтения/записи (при открытии файла это значение устанавливается в 0), после каждой операции значение указателя увеличивается на число прочитанных/записанных байт.
Функции метода дескрипторов следующие:

0x3C- создать файл (если файл уже существует, он создается заново);
0x5B- создать новый файл (если файл уже существует, он не создается заново);
0x5A- создать временный файл с оригинальным именем (этой функции передается ASCIIZ-строка, содержащая только путь, без имени, а функция добавляет в эту строку имя созданного файла);
0x6C- расширенная функция создания/открытия - в DOS 4.0 и выше (особенности обращения к этой функции здесь не рассматриваются);
0x3D- открыть файл;
0x3E- закрыть файл;
0x42- установить указатель чтения/записи; на входе: двойное слово CX:DX - размер перемещения указателя, AL - способ отсчета (0 - от начала, 1 - от текущего, 2 - от конца); на выходе: DX:AX - новая позиция указателя;
0x3F- читать из файла;
0x40- писать в файл;
0x45- дублировать файловый дескриптор - возврашает в AX альтернативный дескриптор для того же файла;
0x46- перенаправить ввод-вывод - после выполнения этой функции все операции, адресованные файлу, дескриптор которого был задан функции 0x46 в регистре CX, будут переадресовываться файлу, дескриптор которого был задан в BX.
При использовании метода FCB адрес FCB файла, с которым оперирует программа передается функциям DOS в регистрах DS:DX. Выходом функций является содержимое регистра AL - нулевое при отсутствии ошибок, ненулевое в противном случае. Функции метода FCB следующие:
0x29- сформировать FCB (DS:SI - адрес ASCIIZ строки, содеpжащей имя файла в общепринятом формате, ES:DI - адрес буфера, в котором система формирует неоткрытый FCB);
0x16- создать файл (если файл уже существует, он создается заново);
0x0F - открыть файл;
0x10 - закрыть файл;
0x14- читать последовательный файл;
0x15- писать в последовательный файл;
0x21- читать запись с произвольным доступом;
0x22- писать запись с произвольным доступом;
0x27- читать несколько записей с произвольным доступом (CX - количество записей);
0x28- писать несколько записей с произвольным доступом;
0x24- установить номер записи для произвольного доступа;
0x1A- установить адрес текущей DTA (задается в DS:DX);
0x2F- получить адрес текущей DTA (получается в ES:BX).
Понятия "последовательный" и "с произвольным доступом" относятся не к файлу, а к способу его обработки - один и тот же файл можно обрабатывать как последовательно, так и с произвольным доступом. При использовании функций последовательной обработки поля FCB, задаюшие номер записи (curblk, currec, randrec) модифицируются системой после каждой операции. При операциях с произвольным доступом программист формирует поля curblk, currec, обращается к функции 0x24, которая формирует поле randrec, а затем вызывает одну из функций произвольного доступа. Программист может, однако, собственными средствами формировать поле randrec перед каждой операцией и пользоваться функциями 0x14/0x15 - чтение/запись будет оперировать именно с той записью, номер которой имеется в поле randrec.

10.4.3. Функции работы с файлами и каталогами
Рассмотренные выше функции работали с файлом "со стороны программы", обеспечивая операции по обмену данными между программой и внешней средой. Теперь рассмотрим функции, работающие с файлом "со стороны операционной системы", которая рассматривает файл как единицу хранения данных на внешней памяти. Поскольку нам уже известна структура каталогов в MS DOS, должно быть ясно, как эти функции выполняют свои задачи. Для некоторых из приведенных функций имеется два варианта - старый, использующий метод FCB, и новый, использующий метод дескрипторов:

0x13- удалить файл (FCB) - задается адрес (DS:DX) неоткрытого FCB;
0x41- удалить файл (дескрипторы) - задается адрес (DS:DX) ASCIIZ-строки, содержащей путь и имя файла;
0x17- переименовать файл (FCB) - задается (DS:DX) блок FCB, в котором поля drive, fname, fext заполнены для старого имени, а со смещением 17 (0x11) от начала записаны новое имя и расширение;
0x56- переименовать (дескрипторы) - задается: DS:DX - адрес ASCIIZ-строки - старое имя, ES:DI - адрес ASCIIZ-строки - новое имя;
0x43- изменить (AL=1) /прочитать (AL=0) атрибуты файла в каталоге (дескрипторы); DS:DX - строка-имя, CX - атрибуты;
0x57- установить (AL=1) /прочитать (AL=0) время последней модификации файла (дескрипторы); BX - дескриптор открытого файла, DX - дата, CX - время;
0x23- получить размер файла (FCB); DS:DX - неоткрытый FCB, результат - в поле randrec; в методе дескрипторов эта операция может быть выполнена функцией 0x42 (CX=0, DX=0, AL= 2);
0x11, 0x12- поиск в текущем каталоге имени по заданному образцу (FCB); DS:DX - адрес неоткрытого FCB, содержащего образец имени, на выходе первый байт DTA содержит идентификатор диска (0-A, 1-B,...), следующие 32 байта - элемент ка-талога. Функция 0x11 ищет первое подходящее имя, все последующие обращения к 0x12 выдают следующие подходящие имена; при неуспешном поиске AL=0xFF;
0x4E, 0x4F- поиск в заданном каталоге по образцу (дескрипторы); DS:DX - строка-образец, CX - атрибуты искомого элемента. При успешном поиске (CY=0) DTA (!) содержит следующую структуру:

    struct FIND {
     byte reserved[21];
     byte attr;    /* атрибут */
     word ftime;   /* время */
     word fdate;   /* дата */
     dword fsize;   /* размер */
     char fname[13]; /* имя.расширение, ASCIIZ-строка */
     };
Функция 0x4E ищет первое подходящее имя (параметры передаются только ей), все последующие обращения к 0x4F (без параметров) выдают следующие подходящие имена;
0x5C- блокировать (AL=0) / разблокировать (AL=1) доступ к области, начинающейся со смещения (двойное слово в CX:DX) размером (двойное слово в SI:DI) файла, дескриптор которого задан в BX;
0x47- получить текущий каталог на диске, заданном в DL, DS:SI указывают на буфер, в котором формируется ASCIIZ-строка текущего пути;
0x3b- изменить текущий каталог, DS:DX указывают на строку, содержащую имя подкаталога;
0x39- создать новый подкаталог, DS:DX указывают на строку, содержащую имя подкаталога;
0x3a- удалить подкаталог, DS:DX указывают на строку, содержащую имя подкаталога;

10.4.4. Функции работы с дисками
Следующие функции оперируют с логическими дисками.

0x1B- информация о FAT текущего диска, на выходе DS:BX указывают на первый байт FAT, DX - число кластеров, AL - число секторов в кластере, CX - размер сектора;
0x1C- то же, но в DL задается логический номер требуемого диска;
0x32- получить информацию о диске (диск задается в DL), на выходе DS:BX содержат адрес Блока Параметров Драйвера (DPB), который будет рассмотрен позже;
0x36- получить информацию о дисковом пространстве (DL - номер диска), на выходе: AX - число секторов в кластере, BX - число доступных кластеров, CX - размер сектора, DX - общее число кластеров;
0x19- получить номер текущего диска (в регистре AL);
0x0E- задать номер текущего диска (в регистре DL).

10.4.5. Программные иллюстрации
Вместо того, чтобы приводить маленькие примеры, каждый из которых иллюстрирует применение какой-то одной функции, приведем "программное изделие" солидного размера, которое включает в себя почти все описанные нами функции метода дескрипторов - как ввода-вывода, так и работы с файлами и каталогами. Поскольку, однако, подробное описание такой большой программы заняло бы слишком много места, ограничимся описанием поверхностным, но надеемся, что оно в сочетании с комментариями в самой программе даст читателю возможность в ней разобраться - пусть даже ценой некоторых интеллектуальных усилий. Программа обеспечивает создание и обслуживание библиотечного набора данных. Библиотека представляет собой файл, организованный разделами. Программа обслуживания библиотеки обеспечивает выход по заданному имени раздела на его начало, а затем обработку раздела как последовательного файла. Принципы организации библиотеки мы заимствуем из операционной системы ОС ЕС. Начальная часть библиотечного файла содержит некоторый форматированный участок, именуемый оглавлением, размер оглавления задается при создании библиотеки, тогда же выделяется и форматируется дисковое пространство для него. Оглавление состоит из элементов, каждый из которых описывает местоположение в библиотеке одного раздела. Для нашей программы формат элемента оглавления описывается структурой struct lib_dir (пример 10.7, файл 10_7.H). Имя элемента здесь не ASCIIZ-строка, а просто последовательность символов, дополняемая до нужной длины пробелами. Условимся, что код 0 в первом байте имени раздела является признаком пустого, свободного элемента. Простоты ради условимся также, что если библиотека рассчитана на хранение M разделов, то в оглавлении записывается M+1 элементов, самый первый (нулевой) элемент используется не по прямому назначению - его поле memb_size содержит число M. Разделы состоят из текстовых строк (ASCIIZ) и занимают непрерывные участки файлового пространства.

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

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

Наша программа состоит из четырех файлов: 10_7.H, 10_7.C, 10_7_A.C и 10_7_H.C. Первый файл включается в остальные файлы препроцессором Турбо-Си, три других файла объединяются в одну программу компоновщиком. Файл 10_7.C содержит главную функцию и вспомогательные функции, поддерживающие вывод данных на экран и прием данных от оператора. Файл 10_7_A.C содержит функции, обеспечивающие организацию операций на логическом уровне. Наконец, функции, сосредоточенные в файле 10_7_H.C, выполняют обращения к функциям метода дескрипторов DOS.
Обратим внимание лишь на некоторые особенности функций последнего файла. При создании файла (функция creat_lib) делается попытка сначала применить функцию DOS 0x5B (создать новый файл). Если эта функция возвращает ошибку (файл уже существует), то выдается запрос на создание его заново, и в этом случае применяется функция DOS 0x3C. При каждом открытии файла (функция open_ lib) считывается в память нулевой элемент оглавления и устанавливается размер оглавления (глобальная переменная memb_max). Получение информации о файле включает в себя обращение к трем функциям DOS - 0x42 (размер файла), 0x57 (дата и время), 0x43 (атрибут).


/*==       ПРИМЕР 10.7 == файл 10_7.H       ==*/
/*================= Работа с библиотекой ================ */
/* элемент оглавления библиотеки */
struct lib_dir {
 char memb_name[8];    /* имя раздела */
 unsigned long memb_addr; /* адрес раздела
     (смещение в байтах от начала файла) */
 unsigned memb_size;   /* размер (число строк) */
 };
/* параметры файла */
struct file_info {
 unsigned char attr; /* атрибуты */
 unsigned date;   /* дата */
 unsigned time;   /* время */
 unsigned long size; /* размер */
 };
/*==       ПРИМЕР 10.7 == файл 10_7.C       ==*/
/*================= Работа с библиотекой ================ */
/*     Главная программа и сервисные функции     */
#include <stdio.h>
#include <10_7.h>
int memb_max;   /* max число разделов */
char libname[80]; /* имя выбранной библиотеки */
char fmemb[9];   /* имя раздела */
int SIZE;     /* размер эл-та оглавления */
/*==== main ====*/
main() {
 char opt;
 int flag;

 init_l();  /* нач.установки */
 for(flag=0; flag==0; ) {
  opt=getopt(); /* меню и выбор опции */
  switch (opt) {
   /* выполнение опций */
   case '0': getlib(); break;
   case '1': if (checkname()) creat_lib(); break;
   case '2': list_lib(); break;
   case '3': query_lib(); break;
   case '4': ren_lib(); break;
   case '5': del_lib(); break;
   case 'A': add_memb(); break;
   case 'B': repl_memb(); break;
   case 'C': find_out_memb(); break;
   case 'D': del_memb(); break;
   case 'E': ren_memb(); break;
   case 'F': look_membs(); break;
   case 27: flag++; break;
   default: printf("\7");
   }
  }
}
/*==== начальные установки ====*/
init_l() {
 libname[0]=0;  /* имя не выбрано */
 SIZE=sizeof(struct lib_dir); /* размер эл-та оглавления */
}
/*==== ввод имени библиотеки ====*/
getlib() {
 printf("\nИмя файла-библиотеки >"); scanf("%s",libname);
 strupr(libname); strcat(libname,".LBR");
 }
/*==== ввод имени раздела ====*/
getmemb() {
 char member[9];
 int i;
 printf("\nИмя раздела >"); scanf("%s",member);
 for(i=0;i<8;fmemb[i++]=' ');
 for(i=0;(i<8)&&member[i];i++) fmemb[i]=member[i];
 }
/*==== проверка имени библиотеки ====*/
checkname() {
 if (libname[0]) return (1);
 prt("Не выбрано имя библиотеки");
 return(0);
 }
/*==== вывод на экран меню и ввод кода операции ====*/
int getopt() {
 char f;
 clrscr();
 printf("===== Операции с библиотекой =====\n");
 printf("0 - выбор имени библиотеки\n");
 printf("1 - создание библиотеки\n");
 printf("2 - список .LBX-файлов\n");
 printf("3 - информация о выбранном файле\n");
 printf("4 - переименование библиотеки\n");
 printf("5 - уничтожение библиотеки\n");
 printf("====== Операции с разделами ======\n");
 printf("A - добавление раздела\n");
 printf("B - замена раздела\n");
 printf("C - поиск раздела\n");
 printf("D - удаление раздела\n");
 printf("E - переименование раздела\n");
 printf("F - оглавление библиотеки\n");
 printf("\nEsc - конец работы\n");
 if (libname[0]) {
  gotoxy(45,1); printf("%s",libname);
  }
 gotoxy(1,18);
 f=getche(); printf("\n"); f=toupper(f);
 return(f);
 }
/*==== пауза ====*/
pressany() {
 printf("\nНажмите любую клавишу...\n");
 getch();
 }
/*==== печать элемента оглавления ====*/
print_head(struct lib_dir *a) {
 int i;
 printf("Раздел ");
 for(i=0;i<8;printf("%c",a->memb_name[i++]));
 printf("  размер - %d  ",a->memb_size);
 printf("адрес - %lxH\n",a->memb_addr);
}
/*==== печать сообщения с паузой ====*/
prt(char *s) {
 printf("%s",s); pressany();
}
/*==== вывод информации о файле ====*/
prt_info(struct file_info *a) {
 if (a->attr!=0xff)
  printf("Атрибут - %02Xh\n",a->attr);
 printf("Дата  - %04d/%02d/%02d\n",
   ((a->date>>9)&0x007f)+1980,(a->date>>5)&0x000f,
   a->date&0x001f);
 printf("Время  - %02d/%02d/%02d\n",
   (a->time>>11)&0x001f,(a->time>>5)&0x003f,
   (a->time&0x001f)*2);
 printf("Размер - %ld\n",a->size);
 printf("Мест в оглавлении - %d\n",memb_max);
 pressany();
}
/*==      ПРИМЕР 10.7 == файл 10_7_A.C      ==*/
/*================= Работа с библиотекой =================*/
/*      Организация операций над библиотекой     */
#include <stdlib.h>
#include <10_7.h>
unsigned long fptr;   /* текущая позиция в файле */
extern char fmemb[9];  /* имя раздела */
extern char libname[80]; /* имя файла-библиотеки */
extern int memb_max;   /* число мест в оглавлении */
static int memb_num;   /* номер эл-та оглавления */
static char strbuf[200]; /* буфер строки */
static struct lib_dir a; /* буфер эл-та оглавления */

/*==== добавление раздела ====*/
add_memb() {
 if (!checkname()) return;
 if (!open_lib()) return;
 /* поиск свободного места в оглавлении */
 if (memb_num=find_free()) {
  getmemb(); /* ввод имени раздела */
  /* поиск раздела с таким именем */
  if (!find_memb(&a)) {
   seek_eof();  /* выход на конец файла */
   /* формирование эл-та оглавления */
   a.memb_addr=fptr; a.memb_size=0;
   memcpy(a.memb_name,fmemb,8);
   puts("вводите>");
   /* построчный ввод и запись в файл текста раздела */
   while(gets(strbuf)!=NULL) {
    write_memb(strbuf); a.memb_size++;
    }
   /* запись эл-та на место свободного */
   seek_dir(memb_num); write_dir(&a);
   prt("Раздел создан");
   }
  else prt("Раздел уже есть в библиотеке");
  }
 else prt("Нет места в оглавлении");
 close_lib();
}
/*==== замена раздела ====*/
repl_memb() {
 if (!checkname()) return;
 if (!open_lib()) return;
 getmemb(); /* ввод имени раздела */
 /* поиск раздела с таким именем */
 if (memb_num=find_memb(&a)) {
  seek_eof();  /* выход на конец файла */
  /* формирование эл-та оглавления */
  a.memb_addr=fptr; a.memb_size=0;
  puts("вводите>");
  /* построчный ввод и запись в файл текста раздела */
  while(gets(strbuf)!=NULL) {
   write_memb(strbuf); a.memb_size++;
   }
  /* запись эл-та на прежнее место */
  seek_dir(memb_num); write_dir(&a);
  prt("Раздел заменен");
  }
 else prt("Раздела нет в библиотеке");
 close_lib();
}
/*==== переименование раздела ====*/
ren_memb() {
 if (!checkname()) return;
 if (!open_lib()) return;
 getmemb(); /* ввод имени раздела */
 /* поиск раздела с таким именем */
 if (memb_num=find_memb(&a)) {
  printf("Новое имя: "); getmemb();
  /* поиск раздела с новым именем */
  if (find_memb(&a))
   prt("Раздел с таким именем уже есть");
  else {
   /* замена имени и запись эл-та на прежнее место */
   memcpy(a.memb_name,fmemb,8);
   seek_dir(memb_num); write_dir(&a);
   prt("Раздел переименован");
   }
  }
 else prt("Раздела нет в библиотеке");
 close_lib();
}
/*==== поиск раздела ====*/
/* функция при успешном поиске возвращает номер элемента
  и элемент оглавления в c, при неуспешном - 0 */
int find_memb(struct lib_dir *c) {
 int i;
 seek_dir(1); /* выход на начало оглавления */
 for(i=0; i<memb_max; i++) {
  /* чтение очередного раздела и сравнение имени */
  read_dir(c);
  if (!memcmp(fmemb,c->memb_name,8)) return(i+1);
  }
 return(0);
}
/*==== поиск свободного элемента оглавления ====*/
int find_free() {
 int i;
 seek_dir(1); /* выход на начало оглавления */
 for(i=0; i<memb_max; i++) {
  /* чтение очередн.раздела,проверка 1-го символа имени */
  read_dir(&a);
  if (a.memb_name[0]==0) return(i+1);
  }
 return(0);
}
/*==== удаление раздела ====*/
del_memb() {
 if (!checkname()) return;
 if (!open_lib()) return;
 getmemb(); /* ввод имени раздела */
 /* поиск раздела с таким именем */
 if (memb_num=find_memb(&a)) {
  /* замена 1-го символа имени на 0
    и запись эл-та на прежнее место */
  a.memb_name[0]=0;
  seek_dir(memb_num); write_dir(&a);
  prt("Раздел удален");
  }
 else prt("Раздел не найден в библиотеке");
 close_lib();
 }
/*==== поиск и распечатка раздела ====*/
find_out_memb() {
 int i;
 if (!checkname()) return;
 if (!open_lib()) return;
 getmemb(); /* ввод имени раздела */
 /* поиск раздела с таким именем */
 if (find_memb(&a)) {
  print_head(&a); /* распечатка эл-та оглавления */
  seek_memb(a.memb_addr); /* выход на начало раздела */
  for (i=0; i<a.memb_size; i++) {
   /* построчное чтение и печать раздела */
   read_memb(strbuf);
   printf("%s\n",strbuf);
   }
  prt("Конец раздела");
  }
 else prt("Раздел не найден в библиотеке");
 close_lib();
}
/*==== просмотр оглавления библиотеки ====*/
look_membs() {
 int i, k;
 if (!checkname()) return;
 if (!open_lib()) return;
 for(k=1,i=0; i<memb_max; i++) {
  /* чтение очередного эл-та огдавления и распечатка
    его, если он не свободен */
  read_dir(&a);
  if (a.memb_name[0]) {
   printf("%3d ",k++); print_head(&a);
   }
  }
 prt("Конец библиотеки");
 close_lib();
}
/*==== запись пустого оглавления ====*/
form_lib() {
 int i;
 /* формирование пустого эл-та оглавления */
 for(i=0;i<8;a.memb_name[i++]=0);
 a.memb_size=memb_max; a.memb_addr=0;
 /* запись memb_max+1 пустых эл-тов */
 for (i=0; i<=memb_max; i++) write_dir(&a);
}
/*==== список библиотек ===*/
list_lib() {
 int i;
 /* поиск первого LBR-файла */
 i=first_f(strbuf);
 while (i) {
  /* распечатка имени найденного и поиск следующего */
  printf("%s\n",strbuf);
  i=next_f(strbuf);
  }
 prt("Конец списка");
}
/*==== справка по библиотеке ===*/
query_lib() {
struct file_info a;
 if (!checkname()) return;
 if (!open_lib()) return;
 lib_info(&a); /* получение информации */
 close_lib();
 prt_info(&a); /* печать информации */
 }
/*==== переименование библиотеки ===*/
ren_lib() {
 if (!checkname()) return;
 strcpy(strbuf,libname);
 printf("Старое имя > %s\n",libname);
 printf("Новое имя : "); getlib();
 if (ren_f(strbuf,libname))
  prt("Библиотека переименована");
 else {
  prt("Переименование не удалось");
  strcpy(libname,strbuf);
  }
 }
/*==== удаление библиотеки ===*/
del_lib() {
 if (!checkname()) return;
 if (del_f()) {
  prt("Библиотека удалена");
  libname[0]=0;
  }
 else prt("Удаление не удалось");
}
/*==       ПРИМЕР 10.7 == файл 10_7_H.C      ==*/
/*================= Работа с библиотекой =================*/
/*    Функции handle-ориентированного ввода-вывода   */
#include <dos.h>
#include <10_7.h>
extern int SIZE;      /* размер эл-та оглавления */
extern int memb_max;    /* число мест в оглавлении */
extern char libname[80];  /* имя файла-библиотеки */
extern unsigned long fptr; /* текущая позиция в файле */
static struct lib_dir a;  /* буфер эл-та */
static int lib;      /* дескриптор файла-библиотеки */
static struct FIND {    /* структура DTA для функций */
 char reserved[30];    /* поиска: 0x4E, 0x4F */
 char fname[13];
 } *dta;
static union REGS rr;
static struct SREGS sr;
/*==== индикация ошибки ввода-вывода ====*/
iferror(char *s) {
 if (rr.x.cflag) {
  printf("Ошибка %s: %x\n",s,rr.x.ax);
  exit();
  }
}
/*==== создание библиотеки ====*/
creat_lib() {
 int f;
 rr.h.ah=0x5b;      /* функция CreatNewFile */
 rr.x.cx=0;       /* атрибут файла */
 sr.ds=FP_SEG(libname); /* адрес строки имени */
 rr.x.dx=FP_OFF(libname);
 intdosx(&rr,&rr,&sr);
 if (rr.x.cflag) {
  if (rr.x.ax==0x50) {
   printf("Файл %s уже существует\n",libname);
   do {
    printf("Создать файл заново (y/n)?");
    if ((f=getche())=='n') {
     prt("\nБиблиотека не создана"); return;
     }
    } while (f!='y');
   rr.h.ah=0x3c;      /* функция CreatFile */
   rr.x.cx=0;        /* атрибут файла */
   sr.ds=FP_SEG(libname);  /* адрес строки имени */
   rr.x.dx=FP_OFF(libname);
   intdosx(&rr,&rr,&sr);
   }
  iferror("создания");
  }
 lib=rr.x.ax;
 printf("\nЧисло разделов MAX >"); scanf("%d",&memb_max);
 form_lib();  /* формирование пустого оглавления */
 close_lib();
 prt("Библиотека создана");
}
/*==== открытие файла ====*/
int open_lib() {
 rr.h.ah=0x3d;       /* функция Open */
 rr.h.al=2;        /* режим доступа Read/Write */
 sr.ds=FP_SEG(libname);  /* адрес строки имени */
 rr.x.dx=FP_OFF(libname);
 intdosx(&rr,&rr,&sr);
 if (rr.x.cflag) {
  prt("Файл не открыт"); return (0);
  }
 lib=rr.x.ax;__ _.fptr=0;
 read_dir(&a);
 memb_max=a.memb_size;
 return (1);
}
/*==== закрытие файла ====*/
close_lib()
{
 rr.h.ah=0x3e;     /* функция Close */
 rr.x.bx=lib;     /* дескриптор */
 intdosx(&rr,&rr,&sr);
 iferror("закрытия");
}
/*==== вывод в файл/ввод из файла ====*/
f_rdwr(unsigned char op, /* код функции DOS */
    char *ms,     /* сообщение на случай ошибки */
    int s,       /* число байт */
    void *a)      /* адрес памяти */
{
 rr.h.ah=op;   /* ф-ция Write (0x40) или Read (0x3F) */
 rr.x.bx=lib;   /* дескриптор */
 rr.x.cx=s;    /* размер блока */
 sr.ds=FP_SEG(a); /* адрес данных */
 rr.x.dx=FP_OFF(a);
 intdosx(&rr,&rr,&sr);
 iferror(ms);
 fptr+=rr.x.ax;
}
/*==== запись элемента в оглавление ====*/
write_dir(struct lib_dir *a) {
 f_rdwr(0x40,"записи",SIZE,a);
 }
/*==== чтение элемента из оглавления ====*/
read_dir(struct lib_dir *a) {
 f_rdwr(0x3f,"чтения",SIZE,a);
 }
/*=== позиционирование указателя на элемент оглавления ===*/
seek_dir(int n) {
unsigned long ptr;
 ptr=n*SIZE;__ _.seek_f(ptr);
}
/*==== позиционирование указателя файла ====*/
seek_f(unsigned long pos)
 {
 rr.h.ah=0x42;    /* функция Seek */
 rr.x.bx=lib;    /* дескриптор */
 rr.x.cx=pos>>16;  /* смещение */
 rr.x.dx=pos&0x0ffff;
 rr.h.al=0;     /* 0 - от начала */
 intdos(&rr,&rr);
 iferror("позиционирования");
 fptr=rr.x.dx; fptr=(fptr<<16)|rr.x.ax;
}
/*==== установка указателя на конец файла ====*/
seek_eof()
 {
 rr.h.ah=0x42;    /* функция Seek */
 rr.x.bx=lib;    /* дескриптор */
 rr.x.cx=0;     /* смещение */
 rr.x.dx=0;
 rr.h.al=2;     /* 2 -от конца */
 intdos(&rr,&rr);
 iferror("позиционирования");
 fptr=rr.x.dx; fptr=(fptr<<16)|rr.x.ax;
}
/*==== установка указателя на начало раздела ====*/
seek_memb(unsigned long ptr) {
 seek_f(ptr);
}
/*==== запись в раздел строки текста ====*/
write_memb(char *s) {
int l;
 l=strlen(s)+1;
 f_rdwr(0x40,"записи",l,s);
}
/*==== чтение из раздела строки текста ====*/
read_memb(char *s) {
unsigned long i;
 i=fptr;
 f_rdwr(0x3f,"чтения",200,s);
 fptr=i+strlen(s)+1;
 seek_f(fptr);
}
/*==== поиск первого файла .LBR ====*/
int first_f(char *c) {
 char findstr[]="*.LBR";
 /* получение адреса DTA */
 rr.h.ah=0x2f;     /* функция GetDta */
 intdosx(&rr,&rr,&sr);
 dta=(struct FIND *)MK_FP(sr.es,rr.x.bx);
 /* поиск */
 rr.h.ah=0x4e;      /* функция FindFirst */
 rr.x.cx=0;        /* атрибут файла */
 sr.ds=FP_SEG(findstr);  /* адрес строки - маски поиска */
 rr.x.dx=FP_OFF(findstr);
 intdosx(&rr,&rr,&sr);
 if (rr.x.cflag) return(0);
 strcpy(c,dta->fname);
 return(1);
}
/*==== поиск следующего файла .LBR ====*/
int next_f(char *c) {
 rr.h.ah=0x4f;     /* функция FindNext */
 intdos(&rr,&rr);
 if (rr.x.cflag) return(0);
 strcpy(c,dta->fname);
 return(1);
}
/*==== переименование файла ====*/
int ren_f(char *old, char *new) {
 rr.h.ah=0x56;    /* функция Rename */
 sr.ds=FP_SEG(old); /* адрес строки старого имени */
 rr.x.dx=FP_OFF(old);
 sr.es=FP_SEG(new); /* адрес строки нового имени */
 rr.x.di=FP_OFF(new);
 intdosx(&rr,&rr,&sr);
 if (rr.x.cflag) return(0);
 return(1);
}
/*==== удаление файла ====*/
int del_f() {
 rr.h.ah=0x41;       /* функция DeleteFile */
 sr.ds=FP_SEG(libname);  /* адрес строки имени */
 rr.x.dx=FP_OFF(libname);
 intdosx(&rr,&rr,&sr);
 if (rr.x.cflag) return(0);
 return(1);
}
/*==== получение информации о файле ====*/
lib_info(struct file_info *a) {
 seek_eof();   /* выход на конец файла */
 a->size=fptr;
 rr.h.ah=0x57;   /* функция Set/QueryFileTime/Date */
 rr.h.al=0;    /* проверить дату и время */
 rr.x.bx=lib;   /* дескриптор */
 intdos(&rr,&rr);
 iferror("функции 0x57");
 a->date=rr.x.dx; a->time=rr.x.cx;
 rr.h.ah=0x43;   /* функция Set/QueryFileAttribute */
 rr.h.al=0;    /* проверить атрибут */
 sr.ds=FP_SEG(libname);
 rr.x.dx=FP_OFF(libname);
 intdosx(&rr,&rr,&sr);
 a->attr=rr.x.cx;
}

Программа, выполняющая те же функции, может быть реализована и с использованием метода FCB. Файл 10_7_F.C содержит функции одноименные с функциями файла 10_7_H.C и при компоновке можно включать его объектный модуль в программу вместо модуля 10_7_H.
Некоторые особенности функций этого файла. При создании файла (creat_lib) проверка существования файла производится попыткой его открыть - если попытка успешна, то файл уже существует. Обмен с файлом ведется двумя способами: при чтении /записи элементов оглавления устанавливается размер записи, равный размеру элемента и обмен ведется записями; при чтении /записи разделов устанавливается размер записи 1, а обмен ведется блоками. Обратите внимание на использование полей currec, curblk, randrec в FCB. Главным является последнее поле, только его содержимое определяет номер записи, которая будет обрабатываться. Программист должен сформировать поля currec и curblk, а функция 0x24 по ним вычислит и заполнит randrec (так сделано во всех функциях позиционирования), но если программист сам вычислит значение для randrec, он может не вызывать 0x24 (так мы делаем в функции fb_ rdwr). Для получения информации о файле в методе FCB имеется только одна специальная функция - 0x23 (размер файла). Но дату и время можно получить из открытого FCB, а атрибут, хотя мы это и не реализовали - как один из результатов поиска по функции 0x11.


/*==     ПРИМЕР 10.7 == файл 10_7_F.C ==       */
/*================= Работа с библиотекой =================*/
/* Функции FCB-ориентированного ввода-вывода */
#include <dos.h>
#include <10_7.h>
static struct { /* FCB для библиотеки */
  unsigned char drive;  /* идентификатор диска */
  char fname[11];    /* имя и расширение */
  unsigned curblk;    /* текущий блок */
  unsigned recsize;   /* размер записи */
  unsigned long fsize;  /* размер файла */
  unsigned date;     /* дата */
  unsigned time;     /* время */
  char reserved[8];
  unsigned char currec; /* текущий номер записи */
  unsigned long randrec; /* относительный номер записи */
  } lib;
extern int SIZE;      /* размер эл-та оглавления */
extern int memb_max;    /* число мест в оглавлении */
extern char libname[80];  /* имя файла-библиотеки */
extern unsigned long fptr; /* текущая позиция в файле */
static struct lib_dir a;  /* буфер эл-та */
static union REGS rr;
static struct SREGS sr;
static struct {
/* формат структуры, возвращаемой при поиске по FCB */
  char drive;
  char fname[8];      /* имя файла */
  unsigned char attr;    /* атрибут */
  char v[10];        /* резерв */
  unsigned time;      /* время */
  unsigned date;      /* дата */
  unsigned cl;       /* номер 1-го кластера */
  unsigned long sz;     /* размер файла */
  } find;
/*==== формирование неоткрытого FCB ====*/
make_fcb(void *n, /* адрес ASCIIZ-строки */
     void *f) /* адрес FCB */
{
 rr.h.ah=0x29;     /* функция ParseFnam */
 sr.ds=FP_SEG(n);   /* адрес ASCIIZ-строки */
 rr.x.si=FP_OFF(n);
 sr.es=FP_SEG(f);   /* адрес FCB */
 rr.x.di=FP_OFF(f);
 intdosx(&rr,&rr,&sr);
 iferror("преобразования имени");
}
/*==== индикация ошибки ввода-вывода ====*/
iferror(char *s) {
 if (rr.h.al) {
  printf("Ошибка %s: %x\n",s,rr.h.al);
  exit();
  }
}
/*==== создание библиотеки ====*/
creat_lib() {
 int f;
 make_fcb(libname,&lib);
 rr.h.ah=0x0f;      /* функция OpenFCB */
 sr.ds=FP_SEG(&lib);   /* адрес FCB */
 rr.x.dx=FP_OFF(&lib);
 intdosx(&rr,&rr,&sr);
 if (!rr.h.al) {
  close_lib();
  printf("Файл %s уже существует\n",libname);
  do {
   printf("Создать файл заново (y/n)?");
   if ((f=getche())=='n') {
    prt("\nБиблиотека не создана"); return;
    }
   } while (f!='y');
  }
 rr.h.ah=0x16;     /* функция CreatFCB */
 sr.ds=FP_SEG(&lib);  /* адрес FCB */
 rr.x.dx=FP_OFF(&lib);
 intdosx(&rr,&rr,&sr);
 iferror("создания");
 printf("\nЧисло разделов MAX >"); scanf("%d",&memb_max);
 form_lib();  /* формирование пустого оглавления */
 close_lib();
 prt("Библиотека создана");
}
/*==== открытие файла ====*/
int open_lib() {
 make_fcb(libname,&lib);
 rr.h.ah=0x0f;      /* функция OpenFCB */
 sr.ds=FP_SEG(&lib);   /* адрес FCB */
 rr.x.dx=FP_OFF(&lib);
 intdosx(&rr,&rr,&sr);
 if (rr.h.al!=0) {
  printf("Файл не открыт\n"); return (0);
  }
 fptr=0;
 seek_dir(0); /* чтение 0-го элемента и */
 read_dir(&a); /* установка memb_max */
 memb_max=a.memb_size;
 return (1);
}
/*==== закрытие файла ====*/
close_lib()
{
 rr.h.ah=0x10;     /* функция CloseFCB */
 sr.ds=FP_SEG(&lib);  /* адрес FCB */
 rr.x.dx=FP_OFF(&lib);
 intdosx(&rr,&rr,&sr);
 iferror("закрытия");
}
/*==== запись элемента в оглавление ====*/
write_dir(void *a) {
 f_rdwr(0x15,"записи",SIZE,a);
}
/*==== чтение элемента из оглавления ====*/
read_dir(void *a) {
 f_rdwr(0x14,"чтения",SIZE,a);
}
/*==== запись в раздел строки текста ====*/
write_memb(char *s) {
 fb_rdwr(0x28,"записи",strlen(s)+1,s);
}
/*==== чтение из раздела строки текста ====*/
read_memb(char *s) {
unsigned long p;
 p=fptr;
 fb_rdwr(0x27,"чтения",200,s);
 fptr=p+strlen(s)+1;
}
/*==== ввод/вывод записи ====*/
f_rdwr(unsigned char op,char *m,int s,void *a) {
/* op - функция DOS, m - сообщение на случай ошибки,
  s - кол.байт; a - адрес */
 /* назначение DTA */
 rr.h.ah=0x1a;     /* функция SetDTA */
 sr.ds=FP_SEG(a);   /* адрес данных */
 rr.x.dx=FP_OFF(a);
 intdosx(&rr,&rr,&sr);
 rr.h.ah=op; /* ф-ция ReadSeqFCB(0x14)/WriteSeqFCB(0x15) */
 lib.recsize=s;   /* размер записи */
 sr.ds=FP_SEG(&lib); /* адрес FCB */
 rr.x.dx=FP_OFF(&lib);
 intdosx(&rr,&rr,&sr);
 iferror(m);
}
/*==== блочный вывод/вывод ====*/
fb_rdwr(unsigned char op,char *m,int s,void *a) {
 rr.h.ah=0x1a;     /* функция SetDTA */
 sr.ds=FP_SEG(a); rr.x.dx=FP_OFF(a);
 intdosx(&rr,&rr,&sr);
 rr.h.ah=op; /* ф-ция ReadRdmBlk(0x27)/WriteRdmBlk(0x28) */
 lib.recsize=1;    /* размер записи = 1 */
 lib.randrec=fptr;  /* номер байта */
 rr.x.cx=s;      /* размер блока */
 sr.ds=FP_SEG(&lib); /* адрес FCB */
 rr.x.dx=FP_OFF(&lib);
 intdosx(&rr,&rr,&sr);
 /* для чтения ситуация EOF не аварийная */
 if (!((op==0x27)&&(rr.h.al==1))) iferror(m);
 fptr+=s;
}
/*=== позиционирование указателя на элемент оглавления ===*/
seek_dir(int n) {
 lib.curblk=n/128; lib.currec=n%128; lib.recsize=SIZE;
 rr.h.ah=0x24;      /* функция SetRdmBlk */
 sr.ds=FP_SEG(&lib);   /* адрес FCB */
 rr.x.dx=FP_OFF(&lib);
 iferror("позиционирования");
}
/*==== установка указателя на конец файла ====*/
seek_eof()
{
 lib.curblk=lib.fsize/128; lib.currec=lib.fsize%128;
 lib.recsize=1;
 rr.h.ah=0x24;     /* функция SetRdmBlk */
 sr.ds=FP_SEG(&lib);  /* адрес FCB */
 rr.x.dx=FP_OFF(&lib);
 fptr=lib.fsize;
}
/*==== установка указателя на начало раздела ====*/
seek_memb(unsigned long ptr) {
 lib.curblk=ptr/128; lib.currec=ptr%128;
 lib.recsize=1;
 rr.h.ah=0x24;      /* функция SetRdmBlk */
 sr.ds=FP_SEG(&lib);   /* адрес FCB */
 rr.x.dx=FP_OFF(&lib);
 iferror("позиционирования");
 fptr=ptr;
}
/*==== поиск первого файла .LBR ====*/
int first_f(char *c) {
 char *s; int i;
 /* подготовка маски для поиска */
 lib.drive=0;
 for(i=0;i<8;lib.fname[i++]='?');
 lib.fname[i++]='L'; lib.fname[i++]='B'; lib.fname[i]='R';
 rr.h.ah=0x1a;     /* функция SetDTA */
 sr.ds=FP_SEG(&find);  /* адрес области результатов */
 rr.x.dx=FP_OFF(&find);
 intdosx(&rr,&rr,&sr);
 rr.h.ah=0x11;      /* функция Find1stFCB */
 sr.ds=FP_SEG(&lib);   /* адрес FCB */
 rr.x.dx=FP_OFF(&lib);
 intdosx(&rr,&rr,&sr);
 if (rr.h.al) return(0);
 /* перевод результата в ASCIIZ-строку */
 for(s=c,i=0; (i<8)&&(find.fname[i]!=' ');
        *(s++)=find.fname[i++]);
 *(s++)='.';
 for(i=8; (i<11)&&(find.fname[i]!=' ');
      *(s++)=find.fname[i++]);
 *s=0;
 return(1);
}
/*==== поиск следующего файла .LBR ====*/
int next_f(char *c) {
 char *s; int i;
 rr.h.ah=0x12;      /* функция FindNxtFCB */
 sr.ds=FP_SEG(&lib);   /* адрес FCB */
 rr.x.dx=FP_OFF(&lib);
 intdosx(&rr,&rr,&sr);
 if (rr.h.al) return(0);
 for(s=c,i=0; (i<8)&&(find.fname[i]!=' ');
   *(s++)=find.fname[i++]);
 *(s++)='.';
 for(i=8; (i<11)&&(find.fname[i]!=' ');
      *(s++)=find.fname[i++]);
 *s=0;
 return(1);
}
/*==== переименование файла ====*/
int ren_f(char *old, char *new) {
 make_fcb(old,&lib);   /* формир. в FCB старого имени */
 make_fcb(new,&lib.fsize); /* формир. в FCB нового имени */
 rr.h.ah=0x17;      /* функция RenameFCB */
 sr.ds=FP_SEG(&lib);    /* адрес FCB */
 rr.x.dx=FP_OFF(&lib);
 intdosx(&rr,&rr,&sr);
 if (rr.h.al) return(0);
 return(1);
}
/*==== удаление файла ====*/
int del_f() {
 make_fcb(libname,&lib);
 rr.h.ah=0x13;      /* функция DeleteFCB */
 sr.ds=FP_SEG(&lib);   /* адрес FCB */
 rr.x.dx=FP_OFF(&lib);
 intdosx(&rr,&rr,&sr);
 if (rr.h.al) return(0);
 return(1);
}
/*==== получение информации о файле ====*/
lib_info(struct file_info *a) {
 close_lib();
 rr.h.ah=0x23;      /* функция FileSizFCB */
 sr.ds=FP_SEG(&lib);   /* адрес FCB */
 rr.x.dx=FP_OFF(&lib);
 lib.recsize=1;
 intdosx(&rr,&rr,&sr);
 a->size=lib.randrec;
 /* дату и время можно выбрать из открытого FCB */
 open_lib();
 a->date=lib.date; a->time=lib.time;
 a->attr=0xff; /* атрибут взять неоткуда */
}

Нельзя оставить без иллюстраций очень интересные функции 0x45, 0x46, используемые обычно для переназначения системных потоков ввода-вывода. В нашем примере 10.8 для файла стандартного вывода sysout (это постоянно открытый файл, его дескриптор 1) создается альтернативный дескриптор, служащий для сохранения ссылки на системный файл CON. Затем происходит переназначение дескриптора sysout_main на пользовательский файл. С этого момента все данные, направляемые в sysout, будут попадать в пользовательский файл. (Для вывода мы пользуемся функцией DOS 0x40, но тот же эффект получится при использовании функции Си printf и т.п.). Вывод же, направляемый по альтернативному дескриптору, полученному ранее для sysout, попадает на экран. В конце программы мы восстанавливаем прежнее назначение sysout по альтернативному дескриптору, но если бы мы этого не сделали, оно бы восстановилось само при завершении программы.


/*==         ПРИМЕР 10.8         ==*/
/*============== Перенаправление вывода =============*/
#include <dos.h>
#include <stdio.h>
union REGS rr;
struct SREGS sr;
main() {
 int userout;
 int sysout_main=1;
 int sysout_alt;
 char *mess[]= { "Перенаправление вывода",
  "Этот текст направляется, казалось бы, на экран,",
  "но попадет в файл SYSOUT.ALT",
  "Переназначение сохранит силу даже",
  "после закрытия файла SYSOUT.ALT",
  "Направление восстановлено",
  "Для вывода на экран можно пользоваться",
  "альтернативным дескриптором для sysout" };
  clrscr();
  /* создается новый файл */
  userout=creat_file("SYSOUT.ALT");
  write_file(sysout_main,mess[0]);
  /* создается альтернативный дескриптор для sysout */
  sysout_alt=dup_handle(sysout_main);
  /* перенаправление из sysout в userout */
  redirect(sysout_main,userout);
  write_file(sysout_main,mess[1]);
  write_file(sysout_main,mess[2]);
  /* этот вывод идет в sysout_alt */
  write_file(sysout_alt,mess[6]);
  write_file(sysout_alt,mess[7]);
  close_file(userout);
  write_file(sysout_main,mess[3]);
  write_file(sysout_main,mess[4]);
  /* восстановл. дескриптора sysout из альтернативного */
  redirect(sysout_main,sysout_alt);
  write_file(sysout_main,mess[5]);
  getch();
 }
/*==== получение альтернативного дескриптора ====*/
int dup_handle(int h)
{
 rr.h.ah=0x45;     /* функция DupHandle */
 rr.x.bx=h;     /* дескриптор */
 intdos(&rr,&rr);
 iferror("дублирования");
 return (rr.x.ax);
}
/*==== переназначение дескрипторов ====*/
redirect(int old, int new)
{
 rr.h.ah=0x46;     /* функция ReDir */
 rr.x.bx=new;     /* дескриптор */
 rr.x.cx=old;
 intdos(&rr,&rr);
 iferror("перенаправления");
}
/*==== индикация ошибки ввода-вывода ====*/
iferror(char *s) { if (rr.x.cflag) {
 printf("Ошибка %s: %x\n",s,rr.x.ax); exit(); } }
/*==== создание ====*/
int creat_file(char *fname) { rr.h.ah=0x3c; rr.x.cx=0;
 sr.ds=FP_SEG(fname); rr.x.dx=FP_OFF(fname);
 intdosx(&rr,&rr,&sr); iferror("создания");
 return(rr.x.ax); }
/*==== закрытие файла ====*/
close_file(int h) { rr.h.ah=0x3e; rr.x.bx=h;
 intdos(&rr,&rr); iferror("закрытия"); }
/*==== запись в файл строки текста с ВК, ПС ====*/
write_file(int h, char *s) {
 char a[3] = { 13, 10, 0 };
 write_filex(h,s); /* запись строки */
 write_filex(h,a); /* перевод строки */
}
/*==== запись в файл ====*/
write_filex(int h,char *s) { rr.h.ah=0x40; rr.x.bx=h;
 rr.x.cx=strlen(s); sr.ds=FP_SEG(s); rr.x.dx=FP_OFF(s);
 intdosx(&rr,&rr,&sr); iferror("записи"); }

Наконец, пример 10.9 иллюстрирует применение дисковых функций DOS при получении информации о дисковом пространстве.


/*==         ПРИМЕР 10.9         ==*/
/*=========== Получение информации о диске ==========*/
#include <dos.h>
#include <ctype.h>
#define byte unsigned char
#define word unsigned int
union REGS rr;
struct SREGS sr;
/* полученная информация о диске */
struct di {
 byte error;   /* признак ошибки */
 byte media;   /* тип носителя */
 word tot_clust; /* общее число кластеров */
 word free_clust; /* число свободных кластеров */
 word clust_size; /* секторов в кластере */
 word sect_size; /* байт в секторе */
 } info[3];
byte cdrive; /* текущий диск */
byte drive;  /* анализируемый диск */
int n;
main() {
 do {
 printf("\nУкажите идентификатор диска >");
 drive=getche();
 } while (!isalpha(drive));
 drive=toupper(drive)-'A';
 /*== 1-й способ ==*/ n=0;
 /* получить номер текущего диска */
 rr.h.ah=0x19;
 intdos(&rr,&rr);
 cdrive=rr.h.al;
 /* задать номер текущего диска */
 rr.h.ah=0x0e;
 rr.h.dl=drive;
 intdos(&rr,&rr);
 /* проверить результат */
 rr.h.ah=0x19;
 intdos(&rr,&rr);
 if (rr.h.al!=drive) {
  printf("\nНеправильный идентификатор диска\n");
  exit();
  }
 /* получение информации о FAT текущего диска */
 rr.h.ah=0x1b;
 intdosx(&rr,&rr,&sr);
 info[n].media=peekb(sr.ds,rr.x.bx);
 info[n].tot_clust=rr.x.dx;
 info[n].clust_size=rr.h.al;
 info[n].sect_size=rr.x.cx;
 info[n].free_clust=0xffff;
 /* восстановить номер текущего диска */
 rr.h.ah=0x0e;
 rr.h.dl=cdrive;
 intdos(&rr,&rr);
 /*== 2-й способ ==*/ n++;
 /* получение информации о FAT заданного диска */
 rr.h.ah=0x1c;
 rr.h.dl=drive+1;
 intdosx(&rr,&rr,&sr);
 info[n].media=peekb(sr.ds,rr.x.bx);
 info[n].tot_clust=rr.x.dx;
 info[n].clust_size=rr.h.al;
 info[n].sect_size=rr.x.cx;
 info[n].free_clust=0xffff;
 /*== 3-й способ ==*/ n=0; n++;
 /* получить информацию о дисковом пространстве */
 rr.h.ah=0x36; rr.h.dl=drive+1;
 intdosx(&rr,&rr,&sr);
 info[n].media=0;
 info[n].tot_clust=rr.x.dx;
 info[n].clust_size=rr.x.ax;
 info[n].sect_size=rr.x.cx;
 info[n].free_clust=rr.x.bx;
 /* вывод результатов */
 clrscr();
 printf("Информация о диске %c",drive+'A');
 printf(" |  INT 1B  |  INT 1C  |  INT 36  |\n");
 printf("Тип носителя     | ");
 for(n=0;n<3;n++)
  if (info[n].media) printf("%02x    | ",
   info[n].media);
  else printf("--    |");
 printf("\nВсего кластеров   |");
 for(n=0;n<3;printf(" %-6d  |",info[n++].tot_clust));
 printf("\nСвободных кластеров |");
 for(n=0;n<3;n++)
  if (info[n].free_clust!=0xffff)
   printf(" %-6d  |",info[n].free_clust);
  else printf(" --    |");
 printf("\nСекторов в кластере |");
 for(n=0;n<3;printf(" %-6d  |",info[n++].clust_size));
 printf("\nБайт в секторе    |");
 for(n=0;n<3;printf(" %-6d  |",info[n++].sect_size));
 getch();
}


КаталогИндекс раздела
НазадОглавлениеВперед