Обозначение кластеров в FAT
FAT12 FAT16 Что означает 000h 0000h Свободный кластер ff0h - ff6h fff0h - fff6h Зарезервированный кластер ff7h fff7h Плохой кластер ff8h - fffh fff8h - ffffh Последний кластер в списке 002h - fefh 0002h - ffefh Номер следующего кластера в списке
Обработка критических ошибок
Операционная система MS-DOS позволяет программам устанавливать собственный обработчик критических ошибок аппаратуры. Мы уже говорили о том, что вектор 0000:0090, соответствующий прерыванию INT 24h, содержит адрес обработчика критических ошибок. Этот обработчик получает управление от операционной системы, когда драйвер какого-либо устройства обнаруживает ошибку аппаратуры.
Обратите внимание на то, что обработчик критических ошибок не вызывается при работе с диском через прерывания MS-DOS INT 25h/26h, и, тем более, при работе с диском на уровне прерывания INT 13h
BIOSBIOS.
При запуске программы MS-DOS копирует адрес обработчика в префикс сегмента программы PSP, а после завершения работы программы - восстанавливает его из PSP.
Стандартный обработчик MS-DOS выводит на экран сообщение:
Abort, Retry, Ignore, Fail?
Если ваша программа должна сама обрабатывать ошибки аппаратуры, она может установить свой собственный обработчик критических ошибок.
Когда обработчик получает управление, регистры процессора содержат информацию, необходимую для определения причины и места появления ошибки:
AH | информация об ошибке: Биты: 0 - тип операции: 0 - чтение, 1 - запись 1...2 область диска, где произошла ошибка: 00 - системные файлы; 01 - область FAT 10 - область каталога; 11 - область данных 3 - 1 - возможен выход с кодом FAIL 4 - 1 - возможен выход с кодом RETRY 5 - 1 - возможен выход с кодом IGNORE 6 зарезервирован, равен 0 7 тип устройства: 0 - диск; 1 - символьное устройство |
AL | номер диска (если бит 7 регистра AH
равен 0) |
DI | код ошибки (биты 0...7, остальные биты не определены) |
BP:SI | адрес заголовка драйвера устройства, на котором произошла ошибка |
Биты 3, 4, 5 определены только для DOS версии 3.0 и для более поздних версий.
Обработчик критических ошибок не должен пользоваться функциями MS-DOS с кодами, большими чем 0Ch (из-за того, что функции MS-DOS не реентерабельны).
Программа может вывести на экран сообщение об ошибке и запросить оператора о необходимых действиях. Ей разрешено также получить дополнительную уточняющую информацию об ошибке с помощью функции 59h прерывания INT 21h
или узнать версию DOS с помощью функции 30h
этого же прерывания. Дополнительная информация об устройстве, в котором произошла ошибка, может быть получена с использованием адреса заголовка драйвера устройства, который передается операционной системой при вызове обработчика в регистрах BP:SI.
Для определения номера функции DOS, в которой произошла критическая ошибка, программа-обработчик может выполнить анализ стека. Когда обработчик получает управление, стек имеет следующую структуру:
Адрес возврата в DOS для команды IRET
IP CS FLAGS
Содержимое регистров программы перед вызовом INT_21h
AX, BX, CX, DX, SI, DI, BP, DS, ES
Адрес возврата в программу, вызвавшую функцию DOS
IP CS FLAGS
Выполнив анализ регистра AH, можно определить номер функции DOS, при вызове которой произошла ошибка, а зная содержимое остальных регистров - и все параметры этой функции.
После выполнения всех необходимых действий, программа обработки критических ошибок должна возвратить в регистре AL код действия, которое должна выполнить операционная система для обработки данной ошибки:
0 | игнорировать ошибку; |
1 | повторить операцию; |
2 | аварийно закончить задачу, используя адрес завершения, записанный в векторе прерывания INT 23h; |
3 | вернуть программе управление с соответствующим кодом ошибки (этот код можно задавать только для DOS версии 3.0 и для более поздних версий). |
Для составления программы обработки критических ошибок вы можете воспользоваться языком ассемблера или функциями стандартных библиотек трансляторов Microsoft QC 2.5 и C 6.0 _dos_getvect(), _dos_setvect(), _chain_intr(). Однако лучше всего использовать специально предназначенные для этого (и входящие в состав стандартных библиотек указанных трансляторов) функции _harderr(), _hardresume()
и _hardretn().
Функция _harderr() предназначена для установки нового обработчика критических ошибок, она имеет следующий прототип:
void _harderr(void (_far *handler)());
Параметр handler - указатель на новую функцию обработки критических ошибок.
Функции _hardresume() и _hardretn() должны быть использованы в обработчике критичеких ошибок, установленном функцией _harderr().
Функция _hardresume() возвращает управление операционной системе, она имеет прототип:
_hardresume(int result);
Парметр result может иметь следующие значения (в соответствии с необходимыми действиями):
_HARDERR_ABORT | аварийно завершить программу; |
_HARDERR_FAIL | вернуть код ошибки; |
_HARDERR_IGNORE | игнорировать ошибку; |
_HARDERR_RETRY | повторить операцию. |
Функция _hardretn() возвращает управление непосредственно программе, передавая ей код ошибки, определяемый параметром функции error:
void _hardretn(int error);
При этом программа получает после возврата из вызванной ей функции DOS код ошибки error. Если ошибка произошла при выполнении функции с номером, большим чем 38h, дополнительно устанавливается в 1 флаг переноса. Если номер функции был меньше указанного значения, в регистр AL записывается величина FFh.
Функция обработки критических ошибок handler
имеет следующие параметры:
void _far handler(unsigned deverror, unsigned errcode, unsigned _far *devhdr);
Первый параметр - код ошибки устройства. Он равен содержимому регистра AX при вызове обработчика прерывания INT 24h. Аналогично, параметр errcode соответствует содержимому регистра DI - код ошибки. Третий параметр - devhdr
- это указатель на заголовок драйвера устройства (передаваемый в регистрах BP:SI).
Для демонстрации использования функций установки обработчика критических ошибок приведем программу, которая пытается создать каталог на диске А:. Эта программа сама обрабатывает критические ошибки, запрашивая у оператора информацию о необходимых действиях.
// Эту программу можно запускать только из командной // строки. При запуске из интегрированной среды QC // или PWB возможен конфликт с используемым в этих // средах обработчиком критических ошибок.
#include <stdio.h> #include <conio.h> #include <stdlib.h> #include <direct.h> #include <string.h> #include <dos.h> #include <bios.h>
void main(void); void _far hhandler( unsigned deverr, unsigned doserr, unsigned _far *hdr); void _bios_str(char *p);
void main() {
// Устанавливаем обработчик критических ошибок
_harderr(hhandler);
// Моделируем критическую ошибку. Выполняем попытку создать // каталог на диске А:. Если мы "забудем" вставить // в дисковод дискету, будет вызван обработчик // критической ошибки
printf("\nВставьте (или не вставляйте) дискету в дисковод A:" "\nи нажмите любую клавишу..." "\n");
getch();
// Создаем каталог
if(mkdir("a:\test_ctl")) {
printf("\nОшибка при создании каталога" ); exit(-1);
} else {
printf("\nУспешное создание каталога");
// Удаляем только что созданный каталог
rmdir("a:test_ctl"); exit(0);
} }
// Новый обработчик критических ошибок
void _far hhandler(unsigned deverr, unsigned doserr, unsigned _far *hdr) {
int ch; static char buf[200], tmpbuf[10];
// Выводим сообщение о критической ошибке
sprintf(buf,"\n\r" "\n\rКод ошибки устройтсва: %04.4X" "\n\rКод ошибки DOS: %d" "\n\r\n\r" "\n\rВыполняемые действия:" "\n\r 0 - повторить" "\n\r 1 - отменить" "\n\r 2 - завершить" "\n\r----> ?", deverr, doserr);
_bios_str(buf);
// Вводим ответ с клавиатуры
ch = _bios_keybrd(_KEYBRD_READ) & 0x00ff; _bios_str("\n\r");
switch(ch) {
case '0': // Пытаемся повторить операцию default:
_hardresume(_HARDERR_RETRY);
case '2': // Завершаем работу программы
_hardresume(_HARDERR_ABORT);
case '1': // Возврат в DOS с кодом ошибки
_hardretn(doserr);
} }
// Программа для вывода строки символов на экран // с помощью функции BIOS 0Eh
void _bios_str(char *ptr) {
union REGS inregs, outregs; char *start = ptr;
inregs.h.ah = 0x0e; for(; *ptr; ptr++) {
inregs.h.al = *ptr; int86(0x10, &inregs, &outregs);
} }
Общее управление вводом/выводом
Подфункция 0Dh функции 44h прерывания INT21h
обеспечивает механизм взаимодействия между прикладным программным обеспечением и драйверами блочных устройств. Эта подфункция позволяет программам читать и изменять параметры устройств, предоставляет возможность выполнять аппаратно-независимое чтение, запись, форматирование и проверку дорожек диска.
Эта подфункция была уже нами описана в разделе, посвященном драйверам.
Для удобства приведем формат вызова этй подфункции еще раз:
Ограничение доступа при загрузке MS-DOS
Операционная система MS-DOS не содержит каких-либо средств разграничения доступа. Однако если компьютером пользуется несколько человек, или если возможен доступ к вашему компьютеру посторонних лиц, желательно использовать программу, запрашивающую пароль при загрузке операционной системы.
Одним из примеров коммерческой системы разграничения доступа для MS-DOS может служить системный администратор ADM. Эта система обеспечивает разграничение доступа к отдельным логическим дискам для нескольких пользователей. Логический диск может быть предоставлен пользователю с правом на чтение, чтение/запись или полностью "спрятан" от пользователя. Для создания логических дисков используется нестандартная схема разделов диска, поэтому, загрузив операционную систему с дискеты, вы "увидите" только диск C:, остальные диски будут для вас недоступны.
Системный администратор ADM в целом удовлетворяет требованиям защиты от НСД, но, к сожалению, в настоящее время эта система полностью исчерпала запас "секретности" - доступна информация о способах получения статуса привилегированного пользователя, которому предоставлены все логические диски. Имеется даже программа для удобного и легкого взламывания этой системы защиты.
В ответственных случаях едва ли целесообразно использовать такие широко распространенные средства защиты от НСД, так как почти наверняка кто-то уже подобрал к ним ключи. В любом случае, эти ключи есть в руках фирмы-разработчика системы защиты.
Как же можно доработать MS-DOS, для того, чтобы она обеспечивала ограничение доступа при загрузке?
Для ответа на этот вопрос вспомним, как выполняется загрузка операционной системы.
После тестирования аппаратуры и инициализации векторов прерываний BIOS происходит попытка выполнить чтение самого первого сектора на нулевой дорожке и нулевой стороне диска А: или, при отсутствии дискеты в дисководе А:,
первого сектора нулевой дорожки нулевой стороны накопителя на жестком диске С:. В любом случае, содержимое сектора записывается в оперативную память по адресу 7C00:0000, после чего по этому же адресу передается управление.
Если загрузка операционной системы выполняется с жесткого диска, считанный сектор содержит главную загрузочную запись - Master Boot Record и таблицу разделов диска.
Идея заключается в замене программы загрузки, находящейся в главной загрузочной записи на свою, разработанную специальным образом - по аналогии с печально известным вирусом Stone.
При установке системы защиты первый сектор, содержащий главную загрузочную запись, переписывается на другое место, например, во второй или третий сектор нулевой дорожки нулевой стороны. Это возможно, так как обычно на нулевой дорожке нулевой стороны используется только самый первый сектор.
Таблица разделов, находящаяся в этом секторе, зашифровывается специальным системным паролем, доступным только системному программисту.
В первый сектор (на место главной загрузочной записи) записывается специальная программа и таблица имен пользователей и их, разумеется зашифрованные, пароли.
После передачи управления эта программа переписывает себя в другое место оперативной памяти и запрашивает имя и пароль пользователя. Если имя и пароль указаны правильно, программа считывает главную загрузочную запись в память по адресу 7C00:0000 и там (в памяти, а не на диске) расшифровывает таблицу разделов. После этого управление передается главной загрузочной записи по адресу 7C00:0000 и загрузка продолжается обычным образом.
Теперь, если выполнить загрузку операционной системы с дискеты, разделы жесткого диска станут недоступны, так как, во-первых, таблица разделов находится в другом секторе диска, а во-вторых, она зашифрована.
Используя указанную выше схему защиты от НСД, не забывайте о такой программе, как Norton Disk Doctor. Эта программа сможет восстановить и таблицу разделов, и корневые каталоги, и все остальное...
Вы можете усложнить приведенную выше схему, добавив специальный драйвер. Этот драйвер, переназначив дисковые прерывания (INT13h и INT 40h - используется MS-DOS вместо INT 13h) и прерывания для работы с каталогами, будет отслеживать все обращения к каталогам и расшифровывать их при необходимости. Однако при этом производительность системы может несколько снизиться.
Если все, что вам нужно, это ограничить доступ к загрузке операционной системы с жесткого диска, вы можете использовать драйвер, который запрашивает пароль на стадии инициализации. Если пароль введен правильно, драйвер завершает работу без установки себя резидентно в памяти, вслед за чем продолжается обычная загрузка операционной системы.
Приведем пример такого драйвера. Вы можете записать его на диск С: и первой строкой в файле CONFIG.SYS записать:
device=c:\stop.sys
Для продолжения загрузки введите пароль sys.
.MODEL tiny .CODE ; Драйвер состоит из одного ; сегмента кода
org 0 ; Эта строка может отсутствовать
include sysp.inc
;=========================================================== stop PROC far ;драйвер - это FAR-процедура ;===========================================================
E_O_P: ;Метка конца программы, ;устанавливаем ее в начало ;драйвера, т.к. нам не надо ;оставлять драйвер резидентно ;в памяти
; Заголовок драйвера
dd 0ffffffffh ;адрес следующего драйвера dw 8000h ;байт атрибутов dw dev_strategy ;адрес процедуры стратегии dw dev_interrupt ;адрес процедуры прерывания db 'STOP ' ;имя устройства (дополненное пробелами)
;=========================================================== ; Программа стратегии
dev_strategy: mov cs:req_seg,es mov cs:req_off,bx ret
; Здесь запоминается адрес заголовка запроса
req_seg dw ? req_off dw ?
;=========================================================== ;Обработчик прерывания
dev_interrupt: push es ;сохраняем регистры push ds push ax push bx push cx push dx push si push di push bp
; Устанавливаем ES:BX на заголовок запроса
mov ax,cs:req_seg mov es,ax mov bx,cs:req_off
; Получаем код команды из заголовка запроса и умножаем ; его на два, чтобы использовать в качестве индекса ; таблицы адресов обработчиков команд
mov al,es:[bx]+2 shl al,1
sub ah,ah ; Обнуляем AH lea di,functions ; DI указывает на смещение таблицы add di,ax ; Добавляем смещение в таблице jmp word ptr [di] ; Переходим на адрес из таблицы
functions LABEL WORD ; Таблица функций
dw initialize dw check_media dw make_bpb dw ioctl_in dw input_data dw nondestruct_in dw input_status dw clear_input dw output_data dw output_verify dw output_status dw clear_output dw ioctl_out dw Device_open dw Device_close dw Removable_media
; Выход из драйвера, если функция не поддерживается
check_media: make_bpb: ioctl_in: nondestruct_in: input_status: clear_input: output_verify: output_status: clear_output: ioctl_out: Removable_media: Device_open: Device_close: output_data: input_data:
or es:word ptr [bx]+3,8103h jmp quit
;=========================================================== quit: or es:word ptr [bx]+3,100h pop bp pop di pop si pop dx pop cx pop bx pop ax pop ds pop es ret
;=========================================================== ; Процедура выводит на экран строку ; символов в формате ASCIIZ
dpc proc near push si dpc_loop: cmp ds:byte ptr [si],0 jz end_dpc mov al,ds:byte ptr [si] @@out_ch al inc si jmp dpc_loop
end_dpc: pop si ret dpc endp
;=========================================================== hello db 13,10,'++' db 13,10,'¦ *STOP* (C)Frolov A., 1990 ¦' db 13,10,'++' db 13,10 db 13,10,'Enter password...' db 13,10,0
;=========================================================== initialize:
lea ax,E_O_P ;смещение конца программы в AX mov es:word ptr [bx]+14,ax ;помещаем его в заголовок mov es:word ptr [bx]+16,cs ;
; Стираем экран
mov dh,18h mov dl,80h
xor cx,cx mov bh,7 xor al,al mov ah,6 int 10h
; Устанавливаем курсор в левый верхний угол экрана
mov bh,0 xor dx,dx mov ah,2 int 10h
; Выводим сообщение
mov ax,cs mov ds,ax mov si,offset hello call dpc
; Ожидаем ввода правильного пароля
loop_password:
mov ax,0 int 16h cmp al,'s' jne loop_password
mov ax,0 int 16h cmp al,'y' jne loop_password
mov ax,0 int 16h cmp al,'s' jne loop_password
jmp quit
stop ENDP
END stop
Участок программы, ответственный за ввод пароля, имеет смысл усложнить. Предоставляем это сделать вам самим.
Описанный выше драйвер эффективен в тех случаях, когда посторонний пользователь не может по тем или иным причинам загрузить операционную систему с дискеты.
Дополнительным средством защиты информации (возможно, наилучшим) может служить шифрование файлов данных. Для этого можно воспользоваться готовыми программами или написать собственные.
Одна из готовых программ, которая может быть использована для шифрования данных - архиватор PKZIP. Эта программа кроме шифрования файлов еще и уплотняет их, что полезно и само по себе. После образования зашифрованного архива программа PKZIP
удаляет исходные файлы. К сожалению, эти файлы можно легко восстановить, например, утилитой Нортона QU. Для того, чтобы удалить эти файлы окончательно, можно в конце рабочего дня запускать утилиту Нортона WIPEINFO, которая прописывает сектора свободных кластеров нулями (при вызове с соответствующими параметрами).
Для удобства шифрования файлов программой PKZIP
мы подготовили две программы (соответственно, для шифрования и дешифрования).
Первая программа запрашивает пароль, не отображая его на экране. Затем она ищет программу PKZIP в каталогах, описанных переменной среды PATH
и запускает ее с соответствующими параметрами. Вторая программа выполняет аналогичные действия с программой PKUNZIP, использующейся для дешифрования.
Программа шифрования всех файлов текущего каталога:
#include <stdio.h> #include <conio.h> #include <process.h>
void main(int argc, char *argv[]) { char passw[80], filebuf[80], parms[100], ch;
_searchenv( "pkzip.exe", "PATH", filebuf ); if(!(*filebuf) ) { printf("PKZIP not found.\n"); exit(-2); }
printf( "Enter password: " ); getpw(passw, 80); sprintf(parms,"-m -s%s %s",passw,argv[1]); execl( filebuf, parms, parms, NULL );
exit(-1); }
int getpw( char *buf, int size) {
int i; char ch;
for(i=0; i<size; ) { ch = getch(); if(ch == 0) ch=getch(); putch('.'); *buf = ch; if(*buf == 0xd) break; i++; buf++;
} *buf = 0; }
Программа дешифрования:
#include <stdio.h> #include <conio.h> #include <process.h>
void main(int argc, char *argv[]) { char passw[80], filebuf[80], parms[100], ch;
_searchenv( "pkunzip.exe", "PATH", filebuf ); if(!(*filebuf) ) { printf("PKUNZIP not found.\n"); exit(-2); }
printf( "Enter password: " ); getpw(passw, 80); sprintf(parms,"-s%s %s",passw,argv[1]); execl( filebuf, parms, parms, NULL );
exit(-1); }
int getpw( char *buf, int size) {
int i; char ch;
for(i=0; i<size; ) { ch = getch(); if(ch == 0) ch=getch(); putch('.'); *buf = ch; if(*buf == 0xd) break; i++; buf++;
} *buf = 0;
}
В качестве параметра программам надо задать имя образуемого архива (можно без расширения). Если это имя не указывать, используется имя (NULL)
- на эту строку указывает неинициализированный параметр argv[1].
Опишем подробно формат FAT.
Первый байт FAT называется "Описатель среды" (Media Descriptor) или байт ID
идентификации FAT. Он имеет такое же значение, как и байт-описатель среды, находящийся в BOOT-секторе логического диска.
Следующие 5 байтов для 12-битового формата или 7 байтов для 16-битового формат всегда содержат значение 0ffh.
Остальная часть FAT состоит из 12-битовых или 16-битовых ячеек, каждая ячейка соответствует одному кластеру диска. Эти ячейки могут содержать следующие значения:
FAT12 | FAT16 | Что означает |
000h | 0000h | Свободный кластер |
ff0h - ff6h | fff0h - fff6h | Зарезервированный кластер |
ff7h | fff7h | Плохой кластер |
ff8h - fffh | fff8h - ffffh | Последний кластер в списке |
002h - fefh | 0002h - ffefh | Номер следующего кластера в списке |
Непосредственный доступ к FAT может потребоваться вам для организации сканирования каталогов для поиска нужных файлов, для чтения каталогов как файлов, для организации защиты информации от несанкционированного копирования. Общая схема использования FAT такая:
Читаем FAT целиком в память. Обычно FAT
располагается сразу после BOOT-сектора (логический сектор с номером 1). Для точного определения начального сектора FAT следует прочитать в память BOOT-сектор и проанализировать содержимое блока параметров BIOS. В поле ressecs записано количество зарезервированных секторов, которые располагаются перед FAT. Поле fatsize содержит размер FAT в секторах. Кроме того, следует учитывать, что на диске может находиться несколько копий FAT. Операционная система использует только первую копию, остальные нужны для утилит восстановления содержимого диска, таких как CHKDSK. Количество копий FAT
находится в поле fatcnt BOOT-сектора.
Затем необходимо узнать номер первого кластера файла, для которого необходимо определить его расположение на диске.
Используем номер первого кластера как индекс в FAT
для извлечения номера следующего кластера.
Повторяем предыдущую процедуру до тех пор, пока извлеченное из FAT значение не будет соответствовать концу файла.
Процедура извлечения номера кластера из FAT
зависит от формата таблицы размещения файлов.
16-битовую FAT можно представить как массив 16-битовых чисел. Для определения номера следующего кластера вам надо просто извлечь 16-битовое значение из FAT, использовав в качестве индекса номер предыдущего кластера.
Для 12-битовой FAT процедура значительно сложнее. Необходимо выполнить следующие действия:
Умножить номер начального кластера на 3.
Разделить результат на 2 (так как каждый элемент таблицы имеет длину 1.5 байта).
Прочитать 16-битовое слово из FAT, используя в качестве смещения значение, полученное после деления на 2.
Если номер начального кластера четный, на выбранное из FAT слово надо наложить маску 0fffh, оставив младшие 12 битов. Если номер начального кластера нечетный, выбранное из FAT значение необходимо сдвинуть вправо на 4 бита, оставив старшие 12 битов.
Полученный результат - номер следующего кластера в цепочке, значение 0fffh
соответствует концу цепочки кластеров.
Используя описанные выше методики чтения FAT, вы сможете для каждого файла определить цепочку занимаемых им кластеров. Для чтения файла при помощи прерывания DOS INT25h вам будет нужно установить соответствие между номерами кластеров и номерами секторов, в которых располагаются эти кластеры. Для того чтобы это сделать, необходимо определить расположение и размер корневого каталога. Поэтому следующий раздел книги будет посвящен каталогам и файлам. Там же будут приведены примеры программ для работы с FAT.
Параметры флоппи-дисков
++ ¦Тип¦Емкость,¦Диаметр,¦Количество секторов¦Количество¦ ¦ ¦Кбайтов ¦дюймы ¦на одну дорожку ¦цилиндров ¦ +++++¦ ¦ 1 ¦ 360 ¦ 5 ¦ 9 ¦ 40 ¦ ¦ 2 ¦ 1200 ¦ 5 ¦ 15 ¦ 80 ¦ ¦ 3 ¦ 720 ¦ 3 ¦ 9 ¦ 40 ¦ ¦ 4 ¦ 1440 ¦ 3 ¦ 18 ¦ 80 ¦ ++
Парковка головок (НМД)
На входе: | AH = 19h |
DL = Адрес дисковода (80h, 81h, ...) | |
На выходе: | AH = Состояние дисковода после завершения последней операции |
CF = 1, если произошла ошибка, 0, если ошибки нет |
|
Примечание: | PS/2 |
Парковка головок - это их установка в нерабочую область, т.е. на нерабочую дорожку. Эту операцию обычно выполняют перед транспортировкой компьютера для исключения повреждения дисковода.
Поиск дорожки (НМД)
На входе: | AH = 0Ch |
CH = Номер дорожки | |
CL = Номер сектора | |
DH = Номер головки | |
DL = Адрес дисковода (0, 1, ..., 80h, 81h, ...) | |
На выходе: | AH = Состояние дисковода после завершения последней операции |
CF = 1, если произошла ошибка, 0, если ошибки нет |
|
Примечание: | PC, XT, AT, PS/2 |
С помощью функции 0Ch программа может подвести головки к дорожке с заданным номером. Функции чтения/записи секторов не требуют предварительного поиска дорожки, они выполняют поиск сами.
Поиск в каталогах
Часто перед программистом стоит задача определения текущего содержимого каталога. При описании логической структуры диска мы приводили текст программы, выводящей на экран содержимого корневого каталога и других каталогов. Эта программа использовала загрузочный сектор логического диска и таблицу размещения файлов. Вы можете использовать такой способ, однако, если вам не требуется информация о номерах начальных кластеров файлов и дескрипторы удаленных файлов, лучше применить специальные функции MS-DOS, предназначенные для поиска файлов в каталогах.
Пара функций 4Eh и 4Fh предназначены для сканирования каталогов.
Эти функции используются вместе следующим образом:
Вызывается функция 4Eh для поиска в каталоге файла, соответствующего образцу. В образце можно использовать символы ? и *, которые означают, соответственно, один любой символ и любое количество любых символов. Информация о найденном файле располагается в специальной области, назначенной каждой работающей программе - области DTA.
Вызывается в цикле функция 4Fh для поиска остальных файлов, удовлетворяющих образцу, заданному при вызове функции 4Eh. Условие завершения цикла - отсутствие в каталоге указанных файлов.
Приведем формат вызова функций 4Eh и 4Fh.
Функция 4Eh:
На входе: | AH = 4Eh |
CX = Атрибуты файла, которые будут использованы при поиске. Будут найдены файлы, имеющие заданный в этом регистре атрибут. | |
DS:DX = Адрес строки в формате ASCIIZ, содержащей путь каталогаили файла. | |
На выходе: | AL = Код ошибки, если был установлен в 1
флаг переноса CF. |
Функция 4Fh:
На входе: | AH = 4Fh |
На выходе: | AL = Код ошибки, если был установлен в 1
флаг переноса CF. |
Обе функции устанавливают флаг переноса в том случае, когда каталог не содержит файлов, удовлетворяющих заданному критерию поиска.
Для работы с областью DTA MS-DOS имеет две функции. Это функция 2Fh, позволяющая получить адрес области DTA (она возвращает этот адрес в регистрах ES:BX), и функция 1Ah, предназначенная для установки своей области DTA
(адрес новой области DTA должен быть указан в регистрах DS:DX).
Напомним, что по умолчанию область DTA
занимает 128 байтов в префиксе сегмента программы PSP
со смещением 80h.
В случае успешного поиска функции 4Eh и 4Fh
помещают в DTA информацию о найденных файлах в следующем формате:
(0) | 20 | Зарезервировано. |
(+21) | 1 | Атрибуты найденного файла |
(+22) | 2 | Поле времени последнего обновления фалйла |
(+24) | 2 | Поле даты последнего обновления фалйла |
(+26) | 4 | Длина файла |
(+30) | 13 | Имя файла и расширение в формате ASCIIZ |
Стандартные библиотеки трансляторов Microsoft QC 2.5 и C 6.0 содержат две функции, предназначенные для сканирования каталогов - _dos_findfirst() и _dos_findnext().
Приведем прототипы этих функций, описанные в файле dos.h:
int _dos_findfirst(char *pattern, struct find_t *found, unsigned attr); int _dos_findnext(struct find_t *found);
В этих функциях параметр pattern определяет образец для поиска файлов, параметр attr - атрибуты файла - используется в качестве дополнительного критерия поиска. Параметр found
представляет собой указатель на структуру, в которую будет записываться информация о найденных файлах. Эта структура определена в файле dos.h:
struct find_t { char reserved[21]; // Зарезервировано для DOS char attrib; // Атрибуты файла unsigned wr_time; // Время изменения файла unsigned wr_date; // Дата изменения файла long size; // Размер файла в байтах char name[13]; // Имя файла и расширение };
Приведем текст программы просмотра содержимого каталога. Программа принимает из командной строки параметр - образец для показа файлов. Если вы укажете параметр *.*, будет выведена информация обо всех файлах. Можно задавать полный путь: c:\*.*.
#include <stdlib.h> #include <stdio.h> #include <dos.h>
void main(int argc, char *argv[]) ; void print_info(struct find_t *find); char *time_conv(unsigned time, char *char_buf); char *date_conv(unsigned date, char *char_buf);
void main(int argc, char *argv[]) {
struct find_t find;
// Находим первый файл, удовлетворяющий критериям поиска. // В качестве критерия используем образец, полученный // из командной строки. Для поиска используем файлы с любыми // атрибутами.
if(!_dos_findfirst(argv[1], 0xffff, &find)) {
printf("\n" "\nИмя файла Аттр. Дата Время Размер" "\n------------ ----- ---------- -------- ------");
// Выводим информацию о первом найденном файле на экран
print_info(&find); } else { printf("Задайте образец для поиска файлов !"); exit(-1); }
// Выводим информацию об остальных найденных файлах
while(!_dos_findnext(&find)) print_info( &find );
exit(0); }
// Функция для вывода информации о найденных файлах
void print_info(struct find_t *pfind) {
char timebuf[10], datebuf[12];
// Преобразуем формат даты и времени последнего изменения файла
date_conv(pfind->wr_date, datebuf); time_conv(pfind->wr_time, timebuf);
// Выводим содержимое дескриптора файла
printf("\n%-12s",pfind->name); printf(" %02X %8s %8s %8ld ", pfind->attrib, datebuf, timebuf, pfind->size); }
// Функция преобразования формата времени
char *time_conv(unsigned t, char *buf) {
int h, m;
h = (t >> 11) & 0x1f, m = (t >> 5) & 0x3f; sprintf(buf, "%2.2d:%02.2d:%02.2d", h % 12, m, (t & 0x1f) * 2); return buf; }
// Функция преобразования формата даты
char *date_conv(unsigned d, char *buf) {
sprintf(buf, "%2.2d.%02.2d.%04.2d", d & 0x1f,(d >> 5) & 0x0f, (d >> 9) + 1980); return buf; }
При запуске программы с параметром *.com на экран будет выведена информация:
Имя файла Аттр. Дата Время Размер ------------ ----- ---------- -------- ------ CURDIR.COM 20 24.02.1991 08:40:24 5879 DIRCTL.COM 20 24.02.1991 09:10:30 6273 DISK_CTL.COM 20 13.01.1991 03:26:34 8177 DISKA.COM 20 19.01.1991 10:08:58 27 DISKB.COM 20 04.02.1991 08:57:34 6186 DISKINFO.COM 20 24.02.1991 04:08:42 6075 DISKSHOW.COM 20 31.01.1991 09:48:34 6989 PARTSHOW.COM 20 07.02.1991 06:12:02 7357 DIRM.COM 20 26.02.1991 11:59:50 6346
Получение различной информации
С помощью подфункции 00h можно получить информацию об открытом файле или устройстве по файловому индексу.
Для удобства работы с этой подфункцией мы подготовили следующую программу:
/** *.Name get_devi * *.Title Получить информацию об устройстве * *.Descr Функция получает информацию о файле * или об устройстве по его файловому индексу. * * *.Params int get_devi(int handle, int *info); * * handle - файловый индекс для * которого необходимо получить * информацию * * info - указатель на слово, в которое * должна быть записана информация * *.Return 0 - если нет ошибок; * Код ошибки - если произошла ошибка. **/
#include <stdio.h> #include <dos.h> #include "sysp.h"
int get_devi(int handle, int *info) {
union REGS reg;
// Заполняем регистровые структуры для вызова // прерывания DOS INT 21h. Код используемой // подфункции - 00h.
reg.x.ax = 0x4400; reg.x.bx = handle;
// Вызываем прерывание
intdos(®, ®);
// Проверяем флаг переноса
if(reg.x.cflag == 0) {
// Если флаг переноса сброшен в 0, ошибок нет. // Записываем информацию по адресу *info
*info = reg.x.dx; return(0);
}
// Если флаг переноса установлен в 1, возвращаем // код ошибки
else return(reg.x.ax); }
Приведенная выше функция принимает в качестве первого параметра файловый индекс открытого файла или устройства и адрес слова, в который она запишет информацию об устройстве или файле, связанном с этим файловым индексом.
Вы можете использовать эту функцию так, как это сделано в следующем примере:
#include <dos.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h>
void main(int argc, char *argv[]);
void main(int argc, char *argv[]) {
int handle; int info; int rc;
// Открываем файл или устройство с заданным именем
handle = open(argv[1], O_RDONLY);
// Если открыть невозможно, выводим сообщение // и завершаем работу программы
if(handle == -1) { printf("Не могу открыть файл!\n"); exit(-1); }
// Получаем информацию для файлового индекса handle
rc = get_devi(handle, &info);
if(rc != 0) { printf("Ошибка с кодом %d\n", rc); exit(-1); }
printf("\nСлово информации об устройстве: %04X",info);
close(handle); }
В качестве параметра при запуске этой программы попробуйте задавать имена файлов или имена устройств, например - CON, AUX, и т.д.
Мы уже описывали формат слова, содержащего информацию об устройстве или файле, для удобства приведем его еще раз.
Для устройства:
Бит | Значение |
0 | Это устройство является стандартным устройством ввода. |
1 | Стандартное устройство вывода. |
2 | NUL-устройство. |
3 | Часы. |
4 | Специальное устройство. |
5 | 1 - двоичный режим работы; 0 - режим ASCII |
6 | 0 - при чтении достигнут конец файла. |
7 | 1 - это слово информации относится к устройству (данный handle относится к устройству); 0 - слово информации относится к файлу. |
8-10 | Зарезервировано. |
11 | 1 - Устройство поддерживает команды открытия/закрытия. |
12 | Сетевое устройство (только для DOS версии 3.0 и более поздних версий). |
13 | Устройство поддерживает вывод до состояния занятости. |
14 | Устройство может обрабатывать управляющие строки IOCTL, посылаемые подфункциями 2, 3, 4, 5 функции 44h. Этот бит может быть только прочитан, его установка подфункцией 1 функции 44h не производится. |
15 | Зарезервировано. |
Бит | Значение |
0-5 | Номер дисковода (0-А:, 1-В: и т.д.). |
6 | 0 - Была запись в выходной файл. |
7 | 1 - это слово информации относится к устройству (данный handle относится к устройству); 0 - слово информации относится к файлу. |
8-11 | Зарезервировано. |
12 | Сетевое устройство (только для DOS версии 3.0 и более поздних версий). |
13-14 | Зарезервировано. |
15 | 1 - Данный файл является удаленным при работе в сети (только для DOS версии 3.0 и более поздних версий). |
поможет вам определить момент достижения конца файла или готовность устройства посимвольной обработки.
Для проверки условия "Конец файла" или готовности устройства можно использовать следующую функцию:
/** *.Name heof * *.Title Проверить условие "Конец файла" * *. Descr Функция позволяет проверить факт достижения * конца файла или готовность устройства * *.Params int heof(int handle); * * handle - файловый индекс для * которого необходимо получить * информацию о состоянии * *.Return 0 - конец файла не достигнут (для файла), * устройство готово (для устройства); * * 1 - достигнут конец файла (для файла), * устройство не готово (для устройства); * * -1 - произошла ошибка. **/
#include <stdio.h> #include <dos.h> #include "sysp.h"
int heof(int handle) {
union REGS reg;
// Заполняем регистровые структуры для вызова // прерывания DOS INT 21h. Код используемой // подфункции - 06h.
reg.x.ax = 0x4406; reg.x.bx = handle;
// Вызываем прерывание
intdos(®, ®);
// Проверяем флаг переноса
if(reg.x.cflag == 0) {
// Если флаг переноса сброшен в 0, ошибок нет.
if(reg.h.al == 0) return(1); else return(0);
}
// Если флаг переноса установлен в 1, возвращаем // признак ошибки
else return(-1); }
Вы можете использовать эту функцию аналогично функции eof(). В приведенном ниже примере программы, копирующей файлы, мы так и поступили:
#include <stdio.h> #include <fcntl.h> #include <sys\types.h> #include <sys\stat.h> #include <malloc.h> #include <errno.h>
void main(int, char *[]); void main(int argc, char *argv[]) {
int source, target, i; char *buffer; unsigned count;
if(argc == 3) {
// Открываем исходный копируемый файл
if((source = open(argv[1], O_BINARY | O_RDONLY)) == - 1) {
printf("\nОшибка при открытии исходного файла: %d", errno); exit(-1);
}
// Открываем выходной файл. При необходимости создаем // новый. Если файл уже существует, выводим на экран // запрос на перезапись содержимого существующего файла
target = open(argv[2], O_BINARY | O_WRONLY | O_CREAT | O_EXCL, S_IREAD | S_IWRITE); if(errno == EEXIST) {
printf("\nФайл существует. Перезаписать? (Y,N)\n");
// Ожидаем ответ оператора и анализируем его
i = getch(); if((i == 'y') (i == 'Y')) target = open(argv[2], O_BINARY | O_WRONLY | O_CREAT | O_TRUNC, S_IREAD | S_IWRITE);
}
// Если выходной файл открыть невозможно, выводим // сообщение об ошибке и завершаем работу программы
if(target == -1){ printf("\nОшибка при открытии выходного файла: %d", errno); exit(-1); }
// Будем читать и писать за один раз 10000 байтов
count = 10000;
// Заказываем буфер для передачи данных
if((buffer = (char *)malloc(count)) == NULL) { printf("\nНедостаточно оперативной памяти"); exit(-1); }
// Копируем исходный файл
while(!heof(source)) {
// Читаем count байтов в буфер buffer
if((count = read(source, buffer, count)) == -1) { printf("\nОшибка при чтении: %d", errno); exit(-1); }
// Выполняем запись count байтов из буфера в выходной файл
if((count = write(target, buffer, count)) == - 1) { printf("\nОшибка при записи: %d", errno); exit(-1); } }
// Закрываем входной и выходной файлы
close(source); close(target);
// Освобождаем память, заказанную под буфер
free(buffer); }
// Если при запуске программы не были указаны // пути для входного или выходного файла, // выводим сообщение об ошибке
else printf("\n" "Задайте пути для исходного" " и результирующего файлов!\n"); }
Подфункция 0Ah функции 44h прерывания INT 21h
поможет программе, работающей в сети, определить расположение открытого файла или устройства - на рабочей станции или на сервере.
Перед вызовом запишите в регистр BX файловый индекс проверяемого файла или устройства.
После возврата из подфункции регистр DX
содержит слово атрибутов для файла или устройства. Если самый старший бит в этом слове равен 0, то файл или устройство является локальным и расположено на рабочей станции. Если же этот бит равен 1, то файл или устройство удаленное и находится на сервере (подключено к серверу, если проверяется устройство).
Обычно программы составляют таким образом, чтобы их работа не зависела от расположения. Но если такая информация вам когда-либо понадобится, вы можете воспользоваться подфункцйией 0Ah.
Аналогично для проверки расположения дисковода можно использовать подфункцию 09h.
Перед вызовом запишите в регистр BL код дисковода (0-текущий дисковод, 1 - А:, 2 - В:,
и т.д.). Двенадцатый бит регистра DX после вызова этой функции покажет вам расположение дисковода: 0 - локальное, 1 - удаленное.
Для проверки возможности замены носителя данных в дисководе вы можете воспользоваться подфункцией 08h. Используя эту подфункцию, вы сможете отличить НГМД от НМД. Это может вам понадобиться, например, для операции форматирования, так как форматирование НГМД и НМД выполняется по-разному.
Перед вызовом подфункции 08h запишите код устройства в регистр BL (0 -текущий дисковод, 1 - А:, 2 - В:, и т.д.). Если носитель данных сменный, то после выполнения подфункции регистр AL будет содержать 0, в противном случае - 1.
Эта подфункция не предназначена для работы с сетевыми устройствами.
Получение справочной информации
Прежде чем мы начнем обзор функций получения справочной информации о состоянии и параметрах дисковой подсистемы, введем понятие текущего диска и текущего каталога.
Если вы запускаете программу, которая находится в каком-либо каталоге на одном из дисков, то эти диск и каталог становятся текущими для MS-DOS. Это можно понимать в том смысле, что программе не требуется каждый раз при работе с файлами указывать требуемый диск или каталог.
В любой момент времени программа может узнать текущие диск или каталог, а также заменить их. Для этого она должна использовать специальные функции прерывания INT 21h.
Для установки текущего диска можно использовать функцию 0Eh, которая имеет следующий формат вызова:
На входе: | AH = 0Eh | |
DL = Номер дисковода (0 - А:, 1 - В:, и т.д.) | ||
На выходе: | AL = Общее количество дисководов в системе. Эта величина соответствует параметру LASTDRIVE
файла CONFIG.SYS. |
Для того чтобы узнать номер текущего дисковода, программа может воспользоваться функцией 19h:
На входе: | AH = 19h |
На выходе: | AL = Номер текущего дисковода (0 - А:, 1 - В:, и т.д.). |
Функция 3Bh предназначена для установки текущего каталога:
На входе: | AH = 3Bh |
DL = Номер дисковода (0 - текущий, 1 - А:, 2 - В:, и т.д.) | |
DS:DX = Адрес буфера, содержащего путь каталога, который должен стать текущим. | |
На выходе: | AX = Код ошибки, если CY установлен в 1. |
Буфер пути может иметь максимальный размер 64 байта. Он должен содержать путь в формате ASCIIZ, т.е. строку, закрытую двоичным нулем, например: "path\dirname",0. Строка не должна содержать литеры, обозначающие диск. Если текущим должен стать корневой каталог, строка должна состоять только из одного двоичного нуля.
Для того чтобы узнать текущий каталог, вы можете воспользоваться функцией 47h:
На входе: | AH = 47h |
DL = Номер дисковода (0 - текущий, 1 - А:, 2 - В:, и т.д.) | |
DS:SI = Адрес буфера для записи пути текущего каталога. | |
На выходе: | AX = Код ошибки, если флаг переноса CF
установлен в 1. |
Буфер должен иметь размер не менее 64 байтов, текущий каталог возвращается в формате ASCIIZ без литеры, обозначающей диск. Если текущим является корневой каталог, регистровая пара DS:SI будет указывать на нулевую строку (состоящую из одного двоичного нуля).
Функции MS-DOS могут помочь вам в получении информации, необходимой для организации доступа к диску на уровне секторов и кластеров. При этом вы будете избавлены от необходимости читать в память и анализированть содержимое загрузочного сектора логического диска.
Информация о таблице размещения файлов FAT
для текущего диска может быть получена с помощью функции 1Bh прерывания INT 21h, имеющего следующий формат:
На входе: | AH = 1Bh |
На выходе: | DS:BX = Адрес первого байта FAT. Это байт ID идентификации среды носителя данных, соответствует байту media в блоке параметров BIOS. |
DX = Общее количество кластеров на диске. | |
AL = Количество секторов в одном кластере. | |
CX = Количество байтов в одном секторе. |
Для получения аналогичной информации не о текущем, а о любом диске, используйте функцию 1Ch. Эта функция полностью аналогична предыдущей, за исключением того, что в регистре DL должен быть указан код дисковода: 0 - текущий, 1 - А:, 2 - В: и т.д. Эта функция доступна в MS-DOS версии 2.0 и в более поздних версиях.
Если вас интересует размер свободного места на диске, вы можете его узнать с помощью функции 36h, имеющей следующий формат:
На входе: | AH = 36h |
DL = Номер дисковода (0 - текущий, 1 - А:, 2 - В:, и т.д.) | |
На выходе: | AX = Количество секторов в кластере; 0FFFFh, если был задан неправильный номер дисковода; |
BX = Количество свободных кластеров на диске. | |
CX = Количество байтов в одном секторе. | |
DX = Общее количество кластеров на диске. |
Эта функция возвращает в регистре AX число 0FFFFh, если вы неправильно указали номер дисковода.
При обсуждении векторной таблицы связи мы рассказывали о блоках управления устройствами DDCB. Поле dev_cb векторной таблицы связи содержит FAR-адрес цепочки этих блоков.
Приведем еще раз формат блока DDCB. Напомним, что он изменяется в зависимости от версии DOS. Для версий 2.х и 3.х блок DDCB имеет следующий формат:
(0) | 1 | drv_num | номер устройства (0 соответствует устройству А:, 1 - В: и т.д.) |
(+1) | 1 | drv_numd | дополнительный номер устройства внутри драйвера |
(+2) | 2 | sec_size | размер сектора в байтах |
(+4) | 1 | clu_size | число, на единицу меньшее количества секторов в кластере |
(+5) | 1 | clu_base | если содержимое этого поля не равно нулю, то для получения общего числа секторов в кластере надо возвести 2 в степень clu_base и получившееся число прибавить к clu_size |
(+6) | 2 | boot_siz | количество зарезервированных секторов (boot-сектора, начало корневого каталога) |
(+8) | 1 | fat_num | количество копий FAT |
(+9) | 2 | max_dir | максимальное число дескрипторов файлов в корневом каталоге (т.е. максимальное число файлов, которое может содержать корневой каталог на этом устройстве) |
(+11) | 2 | data_sec | номер первого сектора данных на диске (номер сектора, соответствующего кластеру номер 2) |
(+13) | 2 | hi_clust | максимальное количество кластеров (равно увеличенному на 1 количеству кластеров данных) |
(+15) | 1 | fat_size | количество секторов, занимаемых одной копией FAT |
(+16) | 2 | root_sec | номер первого сектора корневого каталога |
(+18) | 4 | drv_addr | FAR-адрес заголовка драйвера, обслуживающего данное устройство |
(+22) | 1 | media | байт описания среды носителя данных |
(+23) | 1 | acc_flag | флаг доступа, 0 означает, что к устройству был доступ |
(+24) | 4 | next | адрес следующего блока DDCB, для последнего блока в поле смещения находится число FFFF |
--------------- только для DOS 2.x -------------- | |||
(+28) | 2 | dir_clu | номер начального кластера текущего каталога (0 для корневого каталога) |
(+30) | 64 | dir_path | строка в формате ASCIIZ, содержащая путь к текущему каталогу |
--------------- DOS 3.х ---------------------------- | |||
(+28) | 2 | reserv1 | зарезервировано, обычно равно 0 |
(+30) | 2 | built | число FFFF в этом поле означает, что блок DDCB был построен |
Формат блока DDCB для DOS версии 4.х:
(0) | 1 | drv_num | номер устройства (0 соответствует устройству А:, 1 - В: и т.д.) |
(+1) | 1 | drv_numd | дополнительный номер устройства внутри драйвера |
(+2) | 2 | sec_size | размер сектора в байтах |
(+4) | 1 | clu_size | число, на единицу меньшее количества секторов в кластере |
(+5) | 1 | clu_base | если содержимое этого поля не равно нулю, то для получения общего числа секторов в кластере надо возвести 2 в степень clu_base и получившееся число прибавить к clu_size |
(+6) | 2 | boot_siz | количество зарезервированных секторов (boot-сектора, начало корневого каталога) |
(+8) | 1 | fat_num | количество копий FAT |
(+9) | 2 | max_dir | максимальное число дескрипторов файлов в корневом каталоге (т.е. максимальное число файлов, которое может содержать корневой каталог на этом устройстве) |
(+11) | 2 | data_sec | номер первого сектора данных на диске (номер сектора, соответствующего кластеру номер 2) |
(+13) | 2 | hi_clust | максимальное количество кластеров (равно увеличенному на 1 количеству кластеров данных) |
(+15) | 1 | fat_size | количество секторов, занимаемых одной копией FAT |
(+16) | 1 | reserv1 | зарезервироано |
(+17) | 2 | root_sec | номер первого сектора корневого каталога |
(+19) | 4 | drv_addr | FAR-адрес заголовка драйвера, обслуживающего данное устройство |
(+23) | 1 | media | байт описания среды носителя данных |
(+24) | 1 | acc_flag | флаг доступа, 0 означает, что к устройству был доступ |
(+25) | 4 | next | адрес следующего блока DDCB, для последнего блока в поле смещения находится число FFFF |
(+29) | 2 | reserv2 | зарезервироано |
(+31) | 2 | built | число FFFF в этом поле означает, что блок DDCB был построен |
для MS-DOS версии 4.х:
/* Блок управления устройством DOS */
#pragma pack(1)
typedef struct _DDCB_ { unsigned char drv_num; unsigned char drv_numd; unsigned sec_size; unsigned char clu_size; unsigned char clu_base; unsigned boot_siz; unsigned char fat_num; unsigned max_dir; unsigned data_sec; unsigned hi_clust; unsigned char fat_size; char reserv1; unsigned root_sec; void far *drv_addr; unsigned char media; unsigned char acc_flag; struct _DDCB_ far *next; unsigned reserv2; unsigned built; } DDCB;
#pragma pack()
При описании векторной таблицы связи мы приводили примеры использования блоков DDCB. Для получения адреса блока DDCB конкретного дисковода можно воспользоваться недокументированной функцией 32h. Она имеет следующий формат вызова:
На входе: | AH = 32h |
DL = Номер дисковода (0 - текущий, 1 - А:, 2 - В:, и т.д.) | |
На выходе: | AL = 0, если был задан правильный номер дисковода; 0FFh, если был задан неправильный номер дисковода; |
DS:BX = Адрес блока DDCB |
Какая еще полезная информация может быть получена при использовании функций MS-DOS?
С помощью функции 33h программа может проверить или установить флаг Ctrl-Break и узнать номер диска, с которого выполнялась загрузка операционной системы:
На входе: | AH = 33h |
AL = Код подфункции: 0 - Проверить текущее состояние флага Ctrl-Break; 1 - Установить флаг Ctrl-Break; 5 - Определить номер диска, который был использован для загрузки операционной системы. |
|
DL = Значение устанавливаемого флага Ctrl-Break для подфункции 1 (0 - OFF, 1 - ON). | |
На выходе: | DL = Текущее состояние флага Ctrl-Break для подфункции 0; Номер диска, использованного для загрузки операционной системы для подфункции 5 (1 - А:, 2 - В:, и т.д.). |
Состояние флага Ctrl-Break влияет на возможность прервать выполнение программы нажатием комбинации клавиш Ctrl-Break или Ctrl-C. Если флаг находится в состоянии OFF, DOS проверяет эту комбинацию клавиш только при вызове функций стандартного ввода/вывода на консоль, принтер и последовательный порт. Если флаг установлен в состояние ON, комбинация клавиш проверяется и при вызове других функций MS-DOS. Если операционная система зафиксировала нажатие указанной комбинации клавиш, она выполняет прерывание INT 23h, которое завершает работу текущей программы.
Функция 2Fh возвращает в регистровой паре ES:BX
адрес текущей области DTA (Disk Transfer Area), которая используется при поиске файлов в каталогах.
Функция 54h позволяет программе узнать текущее состояние флага проверки записывающейся на диск информации. В регистре AL эта функция возвращает текущее состояние флага. Если содержимое регистра равно 1, операционная система после записи сектора считывает его для проверки. Разумеется, такая проверка снижает скорость работы программы. Если после вызова функции регистр AL содержит 0, проверка записи не выполняется.
Для установки флага проверки записи можно использовать функцию 2Eh. Перед вызовом функции в регистр AL необходимо занести новое значение флага проверки: 0 - проверка не нужна; 1 - должна выполняться проверка записанной информации.
Стандартные библиотеки трансляторов Microsoft QC 2.5 и C 6.0 содержат несколько функций, облегчающих получение справочной информации о состоянии дисковой подсистемы.
Функция _dos_getdiskfree() использует функцию 36h
для получения информации о диске. Файл dos.h
содержит описание этой функции:
unsigned _dos_getdiskfree(unsigned drive, struct diskfree_t *diskspace);
Параметр drive задает номер используемого дисковода: 0 - текущий, 1 - А:, и т.д.
Информация возвращается в структуре diskfree_t, которая определена в файле dos.h:
struct diskfree_t { unsigned total_clusters; unsigned avail_clusters; unsigned sectors_per_cluster; unsigned bytes_per_sector; };
В этой структуре:
unsigned total_clusters | общее количество кластеров на диске; |
unsigned avail_clusters | количество свободных кластеров; |
unsigned sectors_per_cluster | количество секторов, занимаемых кластером; |
unsigned bytes_per_sector | размер сектора в байтах. |
и _dos_setdrive().
Функция _dos_getdrive() имеет формат:
void _dos_getdrive(unsigned *drive);
Эта функция пользуется функцией 19h для получения номера текущего диска, который записывается по адресу, задаваемому параметром. Значение 1 соответствует диску А:, 2 - В:, и т.д.
Функция _dos_setdrive() предназначена для установки текущего диска и может быть использована для определения общего числа дисков в системе:
void _dos_setdrive(unsigned drive, unsigned *drivecount);
Параметр drive опеределяет текущий диск (1 - А:, и_т.д.), по адресу, задаваемому вторым параметром, функция записывает общее количество логических дисков, установленных в системе. Функция _dos_setdrive() использует функцию 0Eh
прерывания INT 21h.
Для иллюстрации способов использования функций _dos_getdrive(), _dos_setdrive(), _dos_getdiskfree() мы составили следующую программу:
#include <dos.h> #include <bios.h> #include <conio.h> #include <stdio.h>
void main(void); void main(void) {
struct diskfree_t dinfo; unsigned drive, drivecount;
printf("\n" "\nОпределение параметров текущего логического диска" "\n (C)Фролов А., 1991" "\n");
// Определяем номер текущего диска
_dos_getdrive(&drive);
// Выводим на экран литеру текущего диска
printf("\nТекущий диск: %c:\n", 'A' + drive - 1);
// Вызываем функцию установки текущего диска. // Мы не изменяем текущий диск, вызов этой функции // нужен нам для определения количества установленных // в системе логических дисков
_dos_setdrive(drive, &drivecount);
// Получаем характеристики текущего диска
_dos_getdiskfree(drive, &dinfo);
printf("\nОбщее количество кластеров на диске: %d" "\nКоличество свободных кластеров: %d" "\nКоличество секторов в кластере: %d" "\nКоличество байтов в секторе: %d" "\nРазмер диска в байтах: %ld" "\n", dinfo.total_clusters, dinfo.avail_clusters, dinfo.sectors_per_cluster, dinfo.bytes_per_sector, (long)dinfo.avail_clusters * dinfo.sectors_per_cluster * dinfo.bytes_per_sector );
printf("\nКоличество логических дисков: %d" "\n", drivecount);
}
Получить состояние дисковода (НМД)
На входе: | AH = 10h |
DL = Адрес дисковода (80h, 81h, ...) | |
На выходе: | AH = Состояние дисковода после завершения последней операции |
CF = 1, если произошла ошибка, 0, если ошибки нет |
|
Примечание: | PC, XT, AT, PS/2 |
О готовности дисковода можно судить по байту состояния, передаваемому функцией в регистре AH. Этот байт аналогичен возвращаемому в регистре AH функцией 01h.
Получить состояние дисковой подсистемы
На входе: | AH = 01 |
DL = Адрес дисковода (0, 1, ...,80h, 81h, ...) | |
На выходе: | AL = Состояние дисковода после завершения последней операции |
Примечание: | PC, XT, AT, PS/2 |
Эта функция может быть использована для анализа результата выполнения дисковой операции и получения кода ошибки. Передаваемый в регистре AL код ошибки функция берет из области данных BIOS - из байта с адресом 0000:0441h.
Код ошибки может принимать следующие значения:
00h | Успешное завершение операции |
01h | Неправильная команда |
02h | Не найдена адресная метка |
03h | Попытка записи на диск, защищенный от записи |
04h | Сектор не найден |
05h | Ошибка при сбросе (НМД) |
06h | Произошла замена дискеты |
07h | Неправильные параметры дисковода (НМД) |
08h | Переполнение канала ПДП (НГМД) |
09h | Переход за границу 64К при работе с ПДП |
0Ah | Обнаружен плохой сектор (НМД) |
0Bh | Обнаружена плохая дорожка (НМД) |
0Ch | Неправильный номер дорожки |
0Dh | Неправильный номер сектора при форматировании (НМД) |
0Eh | Обнаружена адресная метка управляющих данных (НМД) |
0Fh | Ошибка ПДП (НМД) |
10h | Обнаружена ошибка в CRC/ECC |
11h | Данные скорректированы с использованием ECC (НМД) |
20h | Сбой контроллера |
40h | Сбой при поиске дорожки |
80h | Таймаут - программа не успевает обрабатывать данные |
AAh | Дисковод не готов (НМД) |
BBh | Неизвестная ошибка (НМД) |
CCh | Сбой при записи (НМД) |
E0h | Ошибка регистра состояния (НМД) |
FFh | Ошибка операции считывания (НМД) |
Получить текущие параметры дисковода (НМД)
На входе: | AH = 08h |
DL = Адрес дисковода (0, 1, ..., 80h, 81h, ...) | |
На выходе: | AH = Состояние дисковода после завершения последней операции |
CF = 1, если произошла ошибка, 0, если ошибки нет |
|
BL = тип дисковода (только для AT и PS2) | |
DL = количество НМД, обслуживаемых первым контроллером | |
DH = максимальный номер головки | |
CL = максимальный номер сектора | |
CH = максимальный номер цилиндра | |
ES:DI = адрес таблицы параметров дисковода | |
Примечание: | PC, XT, AT, PS/2 |
С помощью этой функции программа может определить тип дисковода, количество дисководов, обслуживаемых первым дисковым контроллером и другие параметры дисковода, которые нужны программе для организации доступа к диску на физическом уровне. Тип дисковода, возвращаемый в регистре BL, может принимать следующие значения:
0 | не используется; |
1 | 360К, 40 дорожек, 5,25 дюймов; |
2 | 1,2М, 80 дорожек, 5,25 дюймов; |
3 | 720 К, 80 дорожек, 3,5 дюйма; |
4 | 1,44М, 80 дорожек, 3,5 дюйма. |
Получить тип дисковода
На входе: | AH = 15h |
DL = Адрес дисковода (0, 1, ..., 80h, 81h, ...) | |
На выходе: | AH = Тип дисковода |
CX:DX = количество секторов размером 512 байтов | |
Примечание: | AT, PS/2 |
Возвращаемый этой функцией тип дисковода может принимать следующие значения:
0 | диск отсутствует; |
1 | НГМД без аппаратных средств обнаружения замены дискеты; |
2 | НГМД оснащенный средствами обнаружения замены дискеты; |
3 | НМД. |
С помощью этой функции программа может определить тип диска и возможность обнаружения замены магнитного носителя (дискеты).
Позиционирование
Управляя содержимым файлового указателя позиции, программа может произвольно считывать или перезаписывать различные участки файла, то есть организовать прямой доступ к содержимому файла. Прямой доступ к файлу может вам понадобится, например, для создания систем управления базами данных.
Установить файловый указатель в нужную вам позицию можно с помощью функции 42h прерывания
INT21h MS-DOS:
На входе: | AH = 42h |
AL = метод кодирования: 00h абсолютное смещение от начала файла 01h смещение от текущей позиции 02h смещение от конца файла |
|
BX = файловый индекс открытого файла | |
CX = старший байт смещения | |
DX = младший байт смещения | |
На выходе: | AX = Код ошибки, если был установлен в 1
флаг переноса CF; Младший байт текущей позиции, если флаг переноса CF сброшен в 0. |
DX = Старший байт текущей позиции |
Функция позволяет указывать новое значение указателя либо как абсолютное смещение от начала файла, либо как смещение от текущей позиции, либо как смещение от конца файла. В последних двух случаях используется смещение со знаком. Для указания смещения или абсолютной позиции программа должна задать в регистрах CX, DX
32-битовое значение.
Если использовать метод кодирования 02h и задать нулевое смещение, функция установит указатель на конец файла. Это обстоятельство может быть использовано для определения размера файла в байтах.
Что произойдет, если при использовании методов кодирования 01h или 02h попытаться установить указатель позиции до начала файла?
Функция 42h при этом не возвратит признак ошибки, однако если будет сделана попытка прочитать или записать данные, то соответствующая функция чтения/записи завершится с ошибкой.
Стандартные библиотеки трансляторов Microsoft QC 2.5 и C 6.0 содержат функции, предназначенные для управления содержимым файлового указателя позиции и получения текущего значения этого указателя. Это функции lseek(), tell(), filelength().
Функция lseek() работает аналогично только что описанной функции 42h. Приведем ее прототип:
long lseek( int handle, long offset, int origin);
Первый параметр определяет файл, для которого выполняется операция позиционирования. Параметр offset определяет смещение. Последний параметр задает метод кодирования смещения. Он может принимать следующие значения, описанные в фале stdio.h:
SEEK_SET | Абсолютное смещение от начала файла |
SEEK_CUR | Смещение относительно текущей позиции |
SEEK_END | Смещение относительно конца файла |
Вы, конечно, можете использовать функцию lseek()
для определения размера файла или текущей файловой позиции. Однако для того, чтобы узнать размер файла, лучше воспользоваться специальной функцией filelength():
long filelength(int handle);
Функция возвращает размер файла в байтах. Файл задается параметром handle. В случае ошибки функция возвращает значение -1L.
Для того, чтобы определить текущую файловую позицию, можно использовать функцию tell():
long tell(int handle);
Эта функция возвращает текущую позицию для файла, определенного параметром handle, или -1L, в случае ошибки.
Для демонстрации использования функций позиционирования приведем простую программу, которая для заданного файла и позиции внутри файла отображает содержимое одного байта. Дополнительно программа определяет размер файла и текущую позицию после чтения байта.
#include <io.h> #include <stdio.h> #include <fcntl.h>
void main(void); void main(void) {
int handle; long position, length; char buffer[2], fname[80];
// Запрашиваем имя файла, с которым будем работать
printf("Введите имя файла: "); gets(fname);
// Открываем файл
handle = open(fname, O_BINARY | O_RDONLY);
// Если такого файла нет, выводим сообщение об ошибке // и завершаем работу программы
if(handle == -1) { printf("\nНет такого файла!"); exit(-1); }
// Определяем и выводим на экран размер файла в байтах
length = filelength(handle);
printf("\nДлина файла %s составляет %ld байтов\n", fname, length);
// Запрашиваем позицию для чтения и отображения байта
do {
printf("Введите позицию: "); scanf("%ld", &position);
} while(position > length);
// Устанавливаем заданную позицию
lseek(handle, position, SEEK_SET);
// Читаем один байт в буфер, начиная с установленной // позиции
if(read(handle, buffer, 1) == -1) {
// Для вывода сообщения об ошибке используем функцию perror(), // которая добавляет к сообщению, заданному в параметре, // расшифрованное системное сообщение об ошибке. // Код ошибки функция perror() берет из переменной errno.
perror("Ошибка при чтении"); exit(-1); }
// Выводим считанный байт на экран
printf( "Смещение: %ld; байт: %02.2x ('%c')\n", position, (unsigned char)*buffer, *buffer);
// Определяем текущую позицию и выводим ее // на экран
position = tell(handle); printf("\nТекущая позиция в файле: %ld\n", position);
// Закрываем файл
close(handle); }
Префикс программного сегмента PSP
(0) 2 | int20h | двоичный код команды int20h (программы могут использовать эту команду для завершения своей работы) |
(+2) 2 | mem_top | нижняя граница доступной памяти в системе в параграфах |
(+4) 1 | reserv1 | зарезервировано |
(+5) 5 | call_dsp | команда вызова FAR CALL диспетчера MS-DOS |
(+10) 4 | term_adr | адрес завершения (Terminate Address) |
(+14) 4 | cbrk_adr | адрес обработчика Ctrl-Break |
(+18) 4 | crit_err | адрес обработчика критической ошибки |
(+22) 2 | parn_psp | сегмент PSP программы, запустившей данную программу (программы-родителя) |
(+24) 20 | file_tab | таблица открытых файлов, если здесь находятся байты 0FFH, то таблица не используется |
(+44) 2 | env_seg | сегмент блока памяти, содержащего переменные среды |
(+46) 4 | ss_sp | адрес стека SS:SP программы |
(+50) 2 | max_open | максимальное число открытых файлов |
(+52) 4 | file_tba | адрес таблицы открытых файлов |
(+56) 24 | reserv2 | зарезервировано |
(+80) 3 | disp | диспетчер функций DOS |
(+83) 9 | reserv3 | зарезервировано |
(+92) 16 | fcb1 | форматируется как стандартный FCB, если первый аргумент командной строки содержит правильное имя файла |
(+108) 20 | fcb2 | заполняется для второго аргумента командной строки аналогично fcb1 |
(+128) 1 | p_size | число значащих символов в неформатированной области параметров, либо буфер обмена с диском DTA, назначенный по умолчанию |
(+129) 127 | parm | неформатированная область параметров, заполняется при запуске программы из командной строки |
Программа FDISK и диск-менеджеры
В этом разделе мы сделаем несколько замечаний, касающихся программ, формирующих логическую структуру дисков.
После низкоуровневого форматирования, формирующего адресные маркеры, маркеры данных и сектора, необходимо создать разделы диска. Операционная система MS-DOS позволяет сделать это с помощью утилиты FDISK. Версия 4.0 MS-DOS и более поздние версии не накладывают ограничений на размер разделов и логических дисков. Однако при использовании утилиты FDISK вы не сможете организовать защиту логических дисков от записи или от несанкционированного доступа.
Программы диск-менеджеров, такие как ADM и SpeedStor, в некоторой степени решают вопросы защиты от записи и несанкционированного доступа. Но появляются новые проблемы.
Диск-менеджеры используют свой собственный механизм разбиения диска на разделы, и, следовательно, логическая структура диска, подготовленного программами диск-менеджеров, отличается от стандартной для MS-DOS. Прежде всего это касается таблицы разделов диска (Partition Table), находящейся в главной загрузочной записи. Элементы таблицы разделов имеют отличный от используемого MS-DOS код системы. Этот код зависит от используемой программы диск-менеджера.
Если ваш диск подготовлен программой SpeedStor, то все элементы таблицы разделов будут заняты (MS-DOS оставляет два элемента неиспользованными). Для того, чтобы установить на этот же диск другую операционную систему (например, XENIX или OS/2) вам придется выгрузить содержимое всего диска на дискеты или стриммер (кассетный накопитель на магнитной ленте), удалить все разделы SpeedStor, создать разделы другой операционной системы, и уже затем разделы MS-DOS. Если бы диск был подготовлен утилитой FDISK, то зарезервировав заранее место для другой операционной системы, вы смогли бы без проблем использовать два оставшихся элемента таблицы разделов.
В таблице разделов диска, подготовленной программой FDISK, находится информация об используемом формате таблицы размещения файлов FAT. Вы можете пользоваться этой информацией, но только в том случае, если диск подготовлен утилитой FDISK.
Из сказанного выше следует, что если работа вашей программы не должна зависеть от того, каким способом были подготовлены разделы диска, вам не следует работать непосредственно с таблицей разделов диска. Для многих приложений, включая защиту от несанкционированного копирования, вам будет достаточно прочитать загрузочный сектор логического диска и воспользоваться той информацией, которая в нем содержится.
Программирование контроллера НГМД
Большинство дисковых операций можно выполнить на уровне функций BIOS. Это самый простой и надежный способ работы с диском на физическом уровне. Однако в отдельных случаях вам может потребоваться непосредственный доступ к контроллеру НГМД - например, если вы разрабатываете систему защиты данных от копирования.
Информация, приведенная в этом разделе, ориентирована прежде всего не на выполнение операций чтения/записи (которые лучше выполнять с помощью функций BIOS), а на управление контроллером и получение состояния контроллера. Именно эти возможности требуются для организации защиты данных от копирования.
Для лучшего понимания работы контроллера мы приведем схему расположения зон данных на дорожке флоппи-диска:
++ Прединдексный синхронизирующий промежуток ¦ FF ¦ ¦ 00 ¦ +¦ Индексная адресная метка ¦ IAM ¦ +¦ Промежуток 1 ¦ ¦ +¦ Сектор 1 ¦ ¦ +¦ Промежуток GPL ¦ ¦ +¦ Сектор 2 ¦ ¦ +¦ Промежуток GPL ¦ ¦ +¦ ¦ ¦ ¦ * * * ¦ ¦ ¦ +¦ Промежуток GPL ¦ ¦ +¦ Сектор N ¦ ¦ +¦ Промежуток GPL ¦ ¦ +¦ Конечный промежуток ¦ ¦ ++
Каждый сектор на дорожке состоит из областей индексных данных и данных. Сектора разделены промежутком GPL, в конце дорожки располагается конечный промежуток, его размер зависит от скорости вращения диска, длин секторов и других промежутков. Область индексных данных содержит информацию о номере дорожки, головки, сектора, код длины сектора. Область данных содержит сами данные. Приведем формат сектора:
++ Адресная метка индексных данных ¦ IDAM ¦ +¦ Номер дорожки ¦ ¦ +¦ Номер головки ¦ ¦ +¦ Номер сектора ¦ ¦ +¦ Код длины сектора ¦ ¦ +¦ Два байта циклического контроля ¦ ¦ +¦ Промежуток ¦ FF ¦ ¦ 00 ¦ +¦ Адресная метка данных ¦ ¦ +¦ Данные ¦ ¦ ¦********¦ ¦ ¦ +¦ Два байта циклического контроля ¦ ¦ ++
Программа обращается к контроллеру для выполнения различных операций с помощью команд ввода/вывода. Для машин IBM PC и XT используются три порта с адресами 3F2h, 3F4h и 3F5h. В машинах класса AT дополнительно используются два порта с адресами 3F6h и 3F7h.
Порт 3F2h работает только на запись, это порт вывода. С его помощью можно выбирать для работы один из дисководов (одновременно можно работать только с одним дисководом), сбрасывать контроллер в исходное состояние, разрешать или запрещать прерывания от контроллера и работу схем прямого доступа к памяти, включать или выключать двигатели дисководов.
Назначение отдельных битов этого порта:
0-1 | Выбор дисковода. Машины AT не используют бит 1, так как в этих машинах только два НГМД. |
2 | 0 - сброс контроллера; 1 - разрешение работы контроллера. |
3 | 1 - разрешение прерываний и прямого доступа к памяти. |
4-7 | Значение 1 в каждом разряде вызывает включение соответствующего двигателя дисковода. Для машин AT биты 6-7 не используются. |
0-3 | Значение 1 говорит о том, что соответствующий дисковод занят, он выполняет операцию поиска. Для машины AT биты 2-3 не используются. |
4 | Контроллер занят выполнением команды чтения или записи. |
5 | 0 - используется режим прямого доступа к памяти; 1 - режим прямого доступа к памяти не используется. |
6 | Направление передачи данных: 0 - от процессора к контроллеру; 1 - от контроллера к процессору. |
7 | Запрос на передачу данных - контроллер готов к записи или чтению данных. |
Выполнение любой операции начинается с того, что программа посылает в этот порт байт кода операции, за которым следует один или несколько байтов параметров. Количество байтов параметров и их назначение зависит от кода операции (т.е. от первого байта). После выполнения операции программа считывает несколько байтов результата для анализа правильности выполнения операции.
Порт 3F7h работает на запись и чтение, он используется только в машинах AT.
При записи биты 0-1 определяют скорость передачи данных:
00 | 500 Кбайтов/с (высокая плотность HD); |
01 | 300 Кбайтов/с (двойная плотность DD); |
10 | 250 Кбайтов/с (одинарная плотность SD); |
11 | зарезервировано. |
0 | 1 - выбран дисковод 0 |
1 | 1 - выбран дисковод 1 |
2-5 | Выбраны головки, бит 2 соответствует головке 0, бит 3 - головке 1 и т.д. |
6 | Переключатель записи. |
7 | 1 - признак замены дискеты. |
Информация, необходимая для выполнения команды, передается контроллеру через порт данных 3F5h. В соответствии с форматом команды программа должна последовательно вывести в этот порт код команды и все параметры.
Прежде чем программа начнет командную фазу, она должна убедиться в том, что контроллер завершил выполнение предыдущей операции и готов к приему команды. Для этого программа должна считать байт основного состояния контроллера из порта с адресом 3F4h и проверить биты 6 и 7. Бит 6
должен быть установлен в 0. Это означает, что данные будут передаваться от процессора к контроллеру. Бит 7 должен быть установлен в 1
- это готовность контроллера к приему команды.
Фаза выполнения начинается после установки битов 6 и 7 байта основного состояния в 1. После завершения выполнения команды контроллер формирует сигнал запроса прерывания.
В фазе результата процессор считывает состояние контроллера. Это состояние хранится в нескольких внутренних регистрах контроллера:
RS - регистр основного состояния;
ST0, ST1, ST2, ST3 - регистры дополнительного состояния.
Регистр основного состояния доступен через порт 3F4h, содержимое остальных регистров процессор считывает после выполнения контроллером команды через порт данных 3F5h.
Приведем форматы для всех команд контроллера НГМД.
Команда Байты команды
Чтение данных ++ ¦MT ¦MFM¦SK ¦ 0 ¦ 0 ¦ 1 ¦ 1 ¦ 0 ¦ ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Чтение удаленных данных ++ ¦ MT ¦MFM¦SK ¦ 0 ¦ 1 ¦ 1 ¦ 0 ¦ 0 ¦ ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Запись данных ++ ¦MT ¦MFM¦ 0 ¦ 0 ¦ 0 ¦ 1 ¦ 0 ¦ 0 ¦ ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Запись удаленных данных ++ ¦MT ¦MFM¦ 0 ¦ 0 ¦ 1 ¦ 0 ¦ 0 ¦ 1 ¦ ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Чтение данных с дорожки ++ ¦MT ¦MFM¦SK ¦ 0 ¦ 0 ¦ 0 ¦ 1 ¦ 0 ¦ ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Сканирование до "равно" ++ ¦MT ¦MFM¦SK ¦ 1 ¦ 0 ¦ 0 ¦ 0 ¦ 1 ¦ ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Сканирование до "меньше" или "равно" ++ ¦MT ¦MFM¦SK ¦ 1 ¦ 1 ¦ 0 ¦ 0 ¦ 1 ¦ ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Сканирование до "больше" или "равно" ++ ¦MT ¦MFM¦SK ¦ 1 ¦ 1 ¦ 1 ¦ 0 ¦ 1 ¦ ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Форматирование дорожки ++ ¦ 0 ¦MFM¦ 0 ¦ 0 ¦ 1 ¦ 1 ¦ 0 ¦ 1 ¦ ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Считывание индексных данных ++ ¦ 0 ¦MFM¦ 0 ¦ 0 ¦ 1 ¦ 0 ¦ 1 ¦ 1 ¦ ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Инициализация ++ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 1 ¦ 1 ¦ 1 ¦ ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 1 ¦ 0 ¦ ++
Чтение состояния прерывания ++ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 1 ¦ 0 ¦ 0 ¦ 0 ¦ ++
Определить параметры ++ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 1 ¦ 1 ¦ ++
Чтение состояния накопителя ++ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 1 ¦ 0 ¦ 0 ¦ ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Поиск ++ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 1 ¦ 1 ¦ 1 ¦ 1 ¦ ++++++++¦ ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦ 0 ¦HDS¦DS1¦DS0¦ ++
Первые несколько команд имеют одинаковый формат параметров и байтов результата.
Приведем байты параметров, которые должны следовать за командами и байты результата, которые процессор должен считать после выполнения команды.
Команда | Байты параметров | Байты результата |
Чтение данных | C, H, R, N, EOT,EOT, GPL, DTL | ST0, ST1, ST2,C, H, R, N |
Чтение удаленных данных | ||
Запись данных | ||
Запись удаленных данных | ||
Чтение данных с дорожки | ||
Сканирование до "равно" | ||
Сканирование до "меньше" или "равно" | ||
Сканирование до "больше" или "равно" | ||
Форматирование дорожки | N, SC, GPL, D | ST0, ST1, ST2,C, H, R, N |
Чтение индексных данных | отсутствуют | ST0, ST1, ST2,C, H, R, N |
Инициализация | отсутствуют | отсутствуют |
Чтение состояния прерывания | отсутствуют | ST0, PCN |
Определить параметры | 1 байт: мл. тетрада - HUT ст. тетрада - SRT 2 байт: бит 0 - ND биты 1-7 - HLT |
отсутствуют |
Чтение состояния накопителя | отсутствуют | ST3 |
Поиск | C | отсутствуют |
После выполнения команды центральный процессор должен получить от контроллера байты результата. Среди них - содержимое внутренних регистров состояния контроллера ST0, ST1, ST2, ST3. Опишем назначение отдельных битов этих регистров.
Формат регистра ST0:
Биты | Название | Назначение |
1, 0 | US1, US2 | Эти биты содержат код накопителя при прерывании |
2 | HD | Номер головки. |
3 | NC | Накопитель не готов, устанавливается,если накопитель не готов выполнить команду чтения или записи. |
4 | EC | Сбой оборудования |
5 | SE | Завершена команда "Поиск" |
7, 6 | I, C | Код прерывания: 00 - нормальное завершение; 01 - аварийное завершение; 10 - некорректная команда; 11 - нет готовности дисковода. |
Биты | Название | Назначение |
0 | MA | Пропуск адресной метки. Этот бит устанавливается в 1, если контроллер не может найти адресную метку |
1 | NN | Защита записи, устанавливается , если при выполнении операции контроллер получает от дисковода сигнал защиты записи. |
2 | ND | Не найден сектор. |
3 | - | Зарезервирован |
4 | OR | Переполнение, процессор не успевает выполнять обмен данными с контроллером |
5 | DE | Ошибка в данных при проверке контрольной суммы |
6 | - | Зарезервирован. |
7 | EN | Несуществующий сектор, устанавливается, когда контроллер пытается прочесть сектор с адресом, большим существующего. |
Биты | Название | Назначение |
0 | MD | Пропущен адресный маркер в поле данных. |
1 | BC | Нечитающаяся дорожка. |
2 | SN | Ошибка сканирования. Устанавливается, если при выполнении команды сканирования контроллер не может найти требуемую дорожку. |
3 | SH | Сканирование выполнено, дорожка найдена. |
4 | WC | Ошибка адреса дорожки. |
5 | DD | Ошибка в поле данных. |
6 | CM | Во время операции чтения или сканирования не обнаружен сектор с маркером удаленных данных. |
7 | - | Зарезервирован. |
Биты | Название | Назначение |
1, 0 | US1, US2 | Код выбранного дисковода. |
2 | HD | Номер выбранной головки. |
3 | TS | Используется режим двухсторонней записи. |
4 | T0 | Головка установлена на дорожку 0. |
5 | RDY | Дисковод готов к работе. |
6 | WP | Защита записи на диске. |
7 | FT | Неисправность дисковода. |
Дополнительно перед выполнением операции и после ее завершения надо проанализировать содержимое описанного выше регистра основного состояния контроллера RS.
В форматах команд и таблицах используются следующие обозначения:
MT | двухсторонняя операция |
MFM | двойная/одинарная плотность записи |
SK | пропуск удаленных данных |
HDS | номер головки для двухстороннего накопителя |
DS1, DS0 | номер выбираемого накопителя |
C | номер цилиндра |
H | номер головки для двухстороннего накопителя |
R | номер сектора |
N | число байтов в секторе |
EOT | номер последнего сектора на дорожке |
GPL | размер промежутка |
DTL | число считываемых/записываемых байтов |
SC | число секторов в цилиндре |
D | данные |
PCN | номер цилиндра после выполнения команды чтения состояния прерывания |
SRT | время шага, мс |
HUT | время разгрузки головки |
HLT | время загрузки головки |
ND | режим прерывания |
NCN | номер цилиндра после поиска |
задает времена задержки для трех внутренних таймеров контроллера. Первый байт параметров состоит из двух полей - SRT и HUT. Поле SRT
задает временной интервал между шаговыми импульсами двигателя перемещения головки. Это поле имеет ширину 4 бита. Поле HUT определяет время разгрузки головки и тоже имеет ширину 4 бита.
Второй байт параметров состоит из полей HLT и ND. Поле HLT имеет ширину 7 битов и определяет время загрузки головки. Бит ND
определяет использование канала прямого доступа - если этот бит установлен в 0, то ПДП используется, иначе обмен данными идет через центральный процессор.
Параметры для команды "Определить параметры" лучше всего взять из таблицы параметров дискеты, заполняющейся BIOS во время инициализации системы. Конечно, если вам нужны нестандартные параметры, вы можете попробовать использовать свои, ориентируясь на значения из таблицы параметров дискеты.
Команда "Инициализация" может выполняться одновременно для всех накопителей. По этой команде головки перемещаются на нулевую дорожку.
Команда "Поиск" используется для установки головки на нужную дорожку. Поиск может выполняться одновременно для нескольких накопителей.
Команда "Чтение состояния прерывания" может вырабатываться после завершения других команд для выяснения состояния контроллера после прерывания. Эту команду удобно использовать после команд "Поиск" или "Инициализация".
После поступления команды "Чтение данных" загружается головка, контроллер считывает метки адреса идентификатора ID и поля ID. Контроллер последовательно считывает номера секторов, и как только считанный номер совпадет с требуемым, считывает данные сектора байт за байтом и передает их либо центральному процессору, либо каналу прямого доступа к памяти. При передаче данных контроллер должен обслуживаться каждые 27 мкс в режиме одинарной плотности и 13 мкс в режиме двойной плотности, иначе в регистре состояния ST3
устанавливается флаг переполнения OR.
Если контроллер не может найти нужный сектор, то в регистре ST1 устанавливается флаг отсутствия данных ND. При ошибке считывания данных, обнаруженной схемами избыточного циклического контроля CRC, устанавливается флаг ошибки данных DE.
При считывании адресной метки удаленных данных в регистре ST2 и сброшенном в 0 бите SK
команды флаг CM устанавливается в 1, читаются все данные из этого сектора, затем выполнение команды прекращается.
Поле команды MT позволяет задать выполнение многодорожечной операции, при которой контроллер считывает данные с обеих сторон дискеты. Поле MFM определяет плотность обрабатываемой информации: значение 0
соответствует одинарной плотности, 1 - двойной.
Если поле команды N содержит 0, то поле DTL
определяет объем передаваемых данных. Если поле N
содержит отличное от нуля значение, поле DTL
игнорируется и должно содержать значение 0FFh.
Выполнение команды "Запись"
аналогично. В режиме записи обмен данными процессора с контроллером должен происходить каждые 31 мкс в режиме одинарной плотности и каждые 15 мкс в режиме двойной плотности.
По команде "Запись удаленных данных" в начале поля данных записывается адресная метка удаленных данных вместо обычной адресной метки данных.
По команде "Чтение данных дорожки"
считываются все поля данных с каждого сектора дорожки как непрерывные блоки данных. С помощью этой команды можно производить многодорожечные операции, пропуски.
Команда "Чтение индексных данных"
позволяет определить положение головки.
Команда "Форматирование дорожки"
форматирует всю дорожку - на нее записываются интервалы, адресные метки, поля индексных данных и поля данных. Вам не обязательно располагать сектора в порядке увеличения номеров, т.к. при форматировании контроллер запрашивает параметры C, H, R, N.
Группа команд "Сканирование" позволяет сравнивать данные, поступающие от контроллера и от центрального процессора. Контроллер выполняет побайтное сравнение и ищет сектор, удовлетворяющий заданному условию. При выполнении условия сканирования в регистре состояния ST2 устанавливается флаг SH, в противном случае - флаг SN.
Как пользоваться всеми этими командами?
Выполнив сброс контроллера, вам надо его проинициализировать, задав все рабочие параметры. Затем можно выдавать контроллеру команды, каждый раз проверяя регистр его основного состояния ST и анализируя байты результата ST0...ST3. Можно предложить следующую последовательность действий:
Сброс контроллера выдачей в порт 3F2h байта с битом 2, установленным в 0.
Разрешение работы контроллера выдачей в этот же порт байта с битом 2, установленным в 1.
Выдача контроллеру команды "Инициализация".
Выдача контроллеру команды "Определить параметры".
Включить двигатель и выждать примерно 0,5 с (время разгона двигателя).
Установить головки в нужное положение командой "Поиск".
Проверить результаты установки командой "Чтение состояния прерывания".
Для машины AT установить нужную скорость передачи данных, выдав в порт 3F7h байт с соответствующим значением: 0 для дискет с высокой плотностью записи (HD), 1 для двойной плотности (DD) и 2 для одинарной (SD).
Если установка головок произведена правильно, можно выдавать команды чтения/записи данных. Перед этим надо правильно запрограммировать контроллер прямого доступа к памяти, если вы собираетесь использовать режим ПДП.
Программирование контроллера прямого доступа к памяти будет подробно описано во втором томе книги, сейчас мы приведем только основные сведения, необходимые для того, чтобы разобраться в программе, демонстрирующей использование команд контроллера НГМД.
Контроллер прямого доступа к памяти (КПДП) имеет несколько каналов и для машин AT состоит из двух микросхем Intel 8237A. Контроллер НГМД использует канал 2.
Перед началом инициализации КПДП программа должна послать в порты 0Bh и 0Ch код операции, которая будет выполняться КПДП - 46h
для операции чтения и 4Ah для операции записи.
В процессе инициализации программа должна сообщить КПДП адрес буфера, куда ему следует поместить данные или откуда надо взять данные, и длину передаваемых данных в байтах.
Адрес необходимо представить в виде номера страницы и смещения. Для КПДП машины AT используется восьмибитовый номер страницы и 16-битовое смещение. Например, для адреса 23456 номер страницы - 2, смещение - 3456.
Для программирования канала 2 КПДП программа должна сначала вывести младший байт смещения в порт с адресом 4, затем вывести в этот же порт старший байт смещения и, наконец, вывести байт номера страницы в порт с адресом 81h.
Длина передаваемых данных выводится аналогично в порт с адресом 5 - сначала младший байт длины, затем старший.
После определения режима работы канала, адреса буфера и длины передаваемых данных, программа должна разрешить работу КПДП, выдав в порт с адресом 0Ch байт 2. Теперь канал прямого доступа готов к работе и будет ждать данных от контроллера НГМД.
Приведенная ниже демонстрационная программа использует несколько наиболее характерных команд контроллера НГМД. Она предназначена для работы на машине AT. Для того, чтобы она правильно работала и на машинах PC/XT, ее надо немного изменить. Изменения касаются программирования контроллера ПДП и программирования скорости передачи контроллера НГМД.
Контроллер КПДП PC/XT использует 4- битовый номер страницы буфера вместо 8-битового. Скорость передачи контроллера НГМД в машинах PC/XT не программируется, вам надо убрать соответствующие строки из программы. Еще надо обратить внимание на различное быстродействие машин AT и PC/XT и скорректировать константы в строках программы, выполняющих задержку.
Программа не проверяет, установлен ли флоппи-диск в приемный карман дисковода, поэтому перед запуском не забудьте установить диск.
#include <stdio.h> #include <stdlib.h> #include <conio.h> #include <dos.h> #include "sysp.h"
#define CYL 0
void main(void); void fdc_out(unsigned char byte); int fdc_inp(void); void int_wait(void); void dma_init(char *);
void main(void) {
unsigned i; long l; char buffer[512]; char status[7], main_status; DPT _far *fdpt; FILE *sect;
printf("\n" "\nРабота с контроллером НГМД" "\n (C)Фролов А., 1991" "\n");
// Эта программа предназначена только для IBM AT
if(pc_model() != 0xfc) { printf("Эта программа предназначена только для IBM AT\n"); exit(-1); }
// Открываем файл, в который будем записывать // содержимое самого первого сектора на дискете
sect = fopen("!sector.dat","wb+");
// Устанавливаем указатель на таблицу // параметров дискеты
fdpt = get_dpt();
// Включаем мотор дисковода А: // Перед этим разрешаем прерывания
_enable(); outp(0x3F2, 0x1C);
// Выполняем задержку для разгона двигателя
for(l=0;l<200000;l++);
// Показываем содержимое регистра основного // состояния контроллера
printf("Мотор включен.\t\t"); printf("Основное состояние: %02.2X\n",inp(0x3F4));
// Перед чтением сектора необходимо установить // головку на нужную дорожку, в нашем случае это // дорожка с номером CYL.
// Выдаем контроллеру команду "Поиск"
fdc_out(0xf);
// Для команды "Поиск" требуется два байта параметров: // номер головки/номер накопителя и номер дорожки. // Мы работаем с нулевой головкой накопителя А:, // поэтому первый параметр равен 0, второй - CYL
fdc_out(0); fdc_out(CYL);
// Показываем содержимое регистра основного // состояния контроллера
printf("\n<<<Поиск>>> \t\t"); printf("Основное состояние: %02.2X\n",inp(0x3F4));
// Ожидаем прерывание по завершению операции
int_wait();
// Задержка для позиционирования головки
for(l=0;l<20000;l++);
// Для проверки результата выполнения команды // "Поиск" выдаем контроллеру команду // "Чтение состояния прерывания"
// Выводим содержимое регистра состояния // ST0 и номер дорожки после выполнения команды // "Поиск" PCN
fdc_out(0x8); printf("Состояние прерывания:\t"); printf(" ST0: %02.2X, \t", fdc_inp()); printf("PCN: %02.2X\n", fdc_inp());
// Для более глубокой диагностики состояния // контроллера выдаем контроллеру команду // "Чтение состояния накопителя", выводим // содержимое регистра состояния ST3
fdc_out(4); fdc_out(0); printf("Состояние накопителя:\t ST3: %02.2X\n",fdc_inp());
// Устанавливаем скорость передачи данных 500 Кбайтов/с, // это значение может различаться для разных типов дискет
outp(0x3F7, 0);
// Инициализация канала прямого // доступа к памяти
dma_init(buffer);
// Выдаем команду "Чтение данных"
fdc_out(0x66); fdc_out(0x0); // накопитель 0, головка 0
fdc_out(CYL); // цилиндр CYL fdc_out(0); // головка 0 fdc_out(1); // номер сектора - 1
// Передаем контроллеру технические параметры // дисковода, берем их из таблицы параметров дискеты. // Это такие параметры: // - размер сектора; // - номер последнего сектора на дорожке; // - размер промежутка; // - число считываемых/записываемых байтов
fdc_out(fdpt->sec_size); fdc_out(fdpt->eot); fdc_out(fdpt->gap_rw); fdc_out(fdpt->dtl);
// Ожидаем прерывание по завершению операции
int_wait();
// Считываем и выводим на экран байты результата // операции "Чтение данных"
printf("\n<<<Чтение сектора>>> \n"); printf(" Байты состояния (ST0,ST1,ST2,C,H,R,N):\n");
for(i=0; i<7; i++) printf("%02.2X\t", (char) fdc_inp()); printf("\n");
// Выводим содержимое считанного сектора в файл
for(i=0; i<512; i++) fputc(buffer[i],sect); fclose(sect);
// Выключаем мотор
outp(0x3F2, 0xC); }
// Вывод байта в контроллер дисковода
void fdc_out(unsigned char parm) {
_asm { mov dx,3F4h // Порт основного состояния loop_fdc_out:
in al,dx test al,80h // Проверяем готовность jz loop_fdc_out // контроллера
inc dx // Выводим байт в порт данных mov al, parm // контроллера out dx, al } }
// Ввод байта из порта данных контроллера дисковода
int fdc_inp(void) {
_asm { mov dx,3F4h // Порт основного состояния loop_fdc_inp: in al,dx test al,80h // Проверяем готовность jz loop_fdc_inp // контроллера
inc dx // Введенный байт записываем in al, dx // в регистр AX } }
// Ожидание прерывания от контроллера
void int_wait(void) {
// Разрешаем прерывания
_enable(); _asm { mov ax,40h // После прихода прерывания mov es,ax // программа обработки прерывания mov bx,3Eh // устанавливает в 1 старший бит wait_loop: // байта в области данных BIOS mov dl,es:[bx] // по адресу 0040:003E. test dl,80h // Мы ждем, когда этот бит будет jz wait_loop // установлен в 1, а затем // сбрасываем его. and dl,01111111b mov es:[bx],dl } }
// Инициализация канала прямого доступа к памяти
void dma_init(char *buf) {
unsigned long f_adr; unsigned sg, of;
// Вычисляем 24-разрядный адрес буфера для данных
f_adr = ((unsigned long)_psp << 4) + (((unsigned long)buf) & 0xffff);
// Расщепляем адрес на номер страницы // и смещение
sg = (f_adr >> 16) & 0xff; of = f_adr & 0xffff;
// На время программирования контроллера прямого // доступа запрещаем прерывания
_disable();
_asm { mov al,46h // Команда чтения данных от // контроллера НГМД.
out 12,al // Сброс триггера-указателя байта // для работы с 16-разрядными портами. // Следующий байт, выводимый в 16-разрядный // порт будет интерпретироваться // как младший.
out 11,al // Установка режима контроллера ПДП
mov ax,of // Смещение буфера, младший байт out 4,al mov al,ah // Смещение буфера, старший байт out 4,al
mov ax,sg // Номер страницы out 81h,al
mov ax,511 // Длина передаваемых данных out 5,al mov al,ah out 5,al
mov al,2 // Разблокировка канала 2 контроллера ПДП out 10,al }
// Инициализация контроллера закончена, // разрешаем прерывания.
_enable(); }
Остальные команды вы можете попробовать сами. Для получения дополнительной информации по контроллеру НГМД обратитесь к техническому руководству по IBM PC. Многое можно почерпнуть из описания микросхем дискового контроллера 765 фирмы NEC и аналогов этой микросхемы - Intel 8272A и отечественной КР1810ВГ72А.
Проверка дисковода (НМД)
На входе: | AH = 13h |
DL = Адрес дисковода (80h, 81h, ...) | |
На выходе: | AH = Состояние дисковода после завершения последней операции |
CF = 1, если произошла ошибка, 0, если ошибки нет |
|
Примечание: | PC, XT |
Функция используется для запуска внутренней диагностики контроллера.
Проверка контроллера (НМД)
На входе: | AH = 14h |
На выходе: | AH = Состояние дисковода после завершения последней операции |
CF = 1, если произошла ошибка, 0, если ошибки нет |
|
Примечание: | PC, XT, AT, PS/2 |
Функция запускает внутреннюю диагностику контроллера.
Проверка памяти контроллера (НМД)
На входе: | AH = 12h |
DL = Адрес дисковода (80h, 81h, ...) | |
На выходе: | AH = Состояние дисковода после завершения последней операции |
CF = 1, если произошла ошибка, 0, если ошибки нет |
|
Примечание: | PC, XT |
Эта функция предназначена для запуска встроенной диагностики дискового контроллера, она проверяет внутренний буфер сектора и возвращает байт состояния.
Проверка сектора
На входе: | AH = 04h |
AL = Количество секторов, которые нужно проверить | |
CH = Номер дорожки | |
CL = Номер сектора | |
DH = Номер головки | |
DL = Адрес дисковода (0, 1, ..., 80h, 81h, ...) | |
На выходе: | AH = Состояние дисковода после завершения последней операции |
AL = Число проверенных секторов | |
CF = 1, если произошла ошибка, 0, если ошибки нет | |
Примечание: | PC, XT, AT, PS/2 |
С помощью этой функции можно убедиться, что указанные сектора существуют и их можно прочесть. Данные проверяются по методу циклического избыточного контроля (CRC). Адрес буфера не нужен, так как чтения данных в оперативную память при проверке секторов не происходит.
Если вы используете компьютер со старой BIOS, выпущенной ранее 11/15/85, регистры ES:BX должны указывать на буфер соответствующего размера, как и при выполнении операции чтения.
Перед использованием этой функции убедитесь, что мотор НГМД раскрутился до рабочей скорости, в противном случае вы получите признак ошибки.
Проверка замены диска
На входе: | AH = 16h |
DL = Адрес дисковода (0, 1, ..., 80h, 81h, ...) | |
На выходе: | AH = Признак замены дискеты: 0 - замены дискеты не было; 6 - дискета была заменена. |
Примечание: | AT, PS/2 |
В некоторых случаях замена дискеты нежелательна до выполнения определенных действий (мы говорили об этом при обсуждении драйверов дисковых устройств). С помощью этой функции программа может убедиться в том, что в дисководе установлена все та же дискета, что и в начале цикла операций. Если дискета была по ошибке заменена раньше времени, программа может потребовать установить старую дискету для завершения работы с ней.
Работа с дисками на физическом уровне
1.1.
1.2.
1.3.
1.4.
1.5.
1.6.
1.7.
Начнем мы с того, что расскажем об аппаратном обеспечении дисковой подсистемы - о контроллерах и дисководах.
Работа с файлами
В этом разделе мы рассмотрим функции MS-DOS, предназначенные для создания, переименования, удаления и перемещения файлов. Операции чтения из файла и записи в файл будут описаны в следующем разделе.
Для создания файла предназначена функция 3Ch
прерывания INT 21h. С помощью этой функции может быть создан файл как в текущем, так и в любом другом каталоге. Если файл с указанным именем уже существует, он обрезается до нулевой длины. Будьте осторожны при использовании этой функции - она может уничтожить нужный вам файл.
Дополнительно функция 3Ch выполняет операцию открытия только что созданного файла, возвращая программе файловый индекс. При создании файла программа может указать требуемые атрибуты, которые будут использованы другими функциями для определения возможности предоставления доступа к файлу.
Приведем формат вызова функции создания файла:
На входе: | AH = 3Ch |
CX = Атрибуты создаваемого файла: 00h - обычный файл; 01h - только читаемый файл; 02h - скрытый файл; 04h - системный файл. |
|
DS:DX = Адрес строки, содержащей путь создаваемого файла | |
На выходе: | AX = Код ошибки, если был установлен в 1
флаг переноса CF; Файловый индекс, если флаг переноса сброшен в 0. |
При выполнении этой функции возможны следующие ошибки:
отсутствует какой-либо элемент в пути для создаваемого файла, например, диск или каталог;
была сделана попытка создать файл в корневом каталоге, но корневой каталог переполнен;
в указаннном каталоге уже есть файл с таким именем, и этот файл имеет атрибут "Только читаемый";
пользователь, который работает в сети, не имеет прав доступа для выполнения указанной операции.
Операционная система игнорирует попытки создания с помощью этой функции каталогов или метки диска.
Для того, чтобы случайно не уничтожить содержимое файла с таким же именем, как и создаваемый, программа может использовать функцию 5Bh. Эта функция проверяет заданный путь на предмет наличия указанного файла. Если такой файл уже существует, функция возвращает программе признак ошибки.
Формат вызова функции:
На входе: | AH = 5Bh |
CX = Атрибуты создаваемого файла: 00h - обычный файл; 01h - только читаемый файл; 02h - скрытый файл; 04h - системный файл. |
|
DS:DX = Адрес строки, содержащей путь создаваемого файла | |
На выходе: | AX = Код ошибки, если был установлен в 1 флаг переноса CF; Файловый индекс, если флаг переноса сброшен в 0. |
На входе: | AH = 5Ah |
CX = Атрибуты создаваемого файла: 00h - обычный файл; 01h - только читаемый файл; 02h - скрытый файл; 04h - системный файл. |
|
DS:DX = Адрес буфера, в который функция запишет путь созданного временного файла. Размер этого буфера должен быть по крайней мере 13 байтов. | |
На выходе: | AX = Код ошибки, если был установлен в 1 флаг переноса CF; Файловый индекс, если флаг переноса сброшен в 0. |
Перед тем, как начать работу с файлом, его нужно открыть. Функции, создающие новые файлы, открывают новые файлы автоматически. Для того, чтобы открыть существующий файл, вы можете воспользоваться функцией 3Dh:
На входе: | AH = 3Dh |
AL = Требуемый режим доступа: Бит 7: флаг наследования 0 - файловый индекс наследуется порожденным процессом 1 - файловый индекс не наследуется порожденным процессом Биты 4...6: режим разделения 000 - режим совместимости 001 - запрещение всех видов доступа 010 - запрещение записи 011 - запрещение чтения 100 - разрешение всех видов доступа Бит 3:0 - зарезервировано Биты 0...2: вид доступа 000 - чтение 001 - запись 010 - чтение/запись |
|
DS:DX = Адрес строки, содержащей путь открываемого файла | |
На выходе: | AX = Код ошибки, если флаг переноса CF был установлен в 1; Файловый индекс, если флаг переноса сброшен в 0. |
Для использования битов 4...7 (управляющих доступом к файлу другими программами в сети) должна быть запущена программа SHARE.EXE.
Если используется бит наследования, то порожденному процессу наследуются запрошенный при открытии файла вид доступа.
Операционная система MS-DOS версии 4.0 имеет в своем составе функцию 6Ch, обладающую расширенными возможностями по созданию и открытию файлов:
На входе: | AH = 6Ch |
AL = 00h | |
BX = байт флагов расширенного режима открытия файла | |
CX = атрибуты создаваемого файла, используется только при создании файлов | |
DX = выполняемая функция, если файл существует или не существует: Биты 0-3 регистра DX задают действие, если файл существует: 0000h - если файл существует, вернуть признак ошибки 0001h - если файл существует, открыть его 0002h - если файл существует, заместить и открыть его Биты 4-7 регистра DX задают действие, если файл не существует: 0000h - если файл не существует, вернуть признак ошибки 0001h - если файл не существует, создать и открыть его |
|
DS:SI = Адрес строки, содержащей путь открываемого файла | |
На выходе: | AX = Код ошибки, если флаг переноса CF был установлен в 1; Файловый индекс, если флаг переноса сброшен в 0. |
CX = Код выполненных действий: 0 - файл был открыт 1 - файл был создан и открыт 2 - файл был замещен и открыт |
Биты | Назначение |
0...2 | Режим доступа при чтении/записи |
3 | Зарезервировано, должно быть равно 0 |
4...6 | Режим разделения |
7 | Флаг наследования |
8...12 | Зарезервировано, должно быть равно 0 |
13 | 0 - Режим обычного использования обработчика критических ошибок INT24h 1 - Блокировка обработчика критических ошибок INT 24h. Для того, чтобы узнать причину ошибки, программа должна использовать функцию 59h прерывания INT 21h MS-DOS. |
14 | Управление буферизацией: 0 - Использование стандартной для MS-DOS буферизации. 1 - Отмена буферизации. Использование этого режима замедлит работу с диском, однако вероятность потери информации при аварии в питающей сети уменьшится. |
Описанная выше функция является как бы комбинацией функций 3Dh и 3Ch (открытие и создание файла). Она удобна, но при ее использовании программа должна убедиться в том, что версия используемой операционной системы не ниже, чем 4.0.
Удалить файл можно при помощи функции 41h
прерывания INT 21h:
На входе: | AH = 41h |
DS:DX = Адрес строки в формате ASCIIZ, содержащей имя удаляемого файла. | |
На выходе: | AL = Код ошибки, если был установлен в 1 флаг переноса CF. |
Под удалением файла понимается вычеркивание файла из каталога и освобождение всех принадлежавших ранее этому файлу кластеров. Эти кластеры отмечаются в таблице размещения файлов как свободные. Уничтожения информации на диске при стирании файлов не происходит, поэтому в некоторых случаях можно полностью восстановить случайно удаленные файлы.
Стандартные библиотеки трансляторов Microsoft QC 2.5 и C 6.0 содержат функции для работы с файлами. Эти функции можно разделить на две группы - функции ввода/вывода низкого уровня и функции ввода/вывода потоком. Вторая группа функций использует буферизацию и будет рассмотрена в разделе, посвященном буферизованному вводу/выводу.
Функции ввода/вывода низкого уровня отображаются на описанные выше функции прерывания INT 21h (и функции этого же прерывания, предназначенные для чтения/записи, позиционирования и т.д.).
Для создания файла можно использовать функцию
creat():
int creat(char *filename, int mode);
Эта функция и ее параметры описаны в файлах io.h, sys\types.h, sys\stat.h, errno.h.
Первый параметр определяет путь создаваемого файла и его имя. Если файл с указанным именем существует, и не имеет атрибута "Только читаемый", функция сбрасывает длину файла до нуля. Предыдущее содержимое файла при этом уничтожается.
Второй параметр позволяет задать атрибуты создаваемого файла. Он может иметь следующие значения:
S_IWRITE | для создаваемого файла разрешена операция записи; |
S_IREAD | для создаваемого файла разрешена операция чтения; |
S_IREAD | S_IWRITE | для создаваемого файла разрешены операции чтения и записи. |
В операционных системах MS-DOS и OS/2 невозможно создать файл, в который можно было бы писать, но из которого было бы нельзя читать информацию. Поэтому задание второго параметра как S_IWRITE
приведет к созданию такого файла, для которого разрешены как операция записи, так и операция чтения.
После создания файла функция creat() открывает новый файл и возвращает файловый индекс (handle) или код ошибки.
Мощная функция open() предназначена как для открытия существующих файлов, так и для создания новых:
int open(char *filename, int oflag [, int pmode]);
Первый и третий параметры в этой функции аналогичны параметрам функции creat(), причем третий параметр нужен только при создании нового файла. Квадратные скобки указывают на то, что этот параметр является необязательным.
Параметр oflag может являться результатом битовой операции ИЛИ над следующими константами, определенными в файле fcntl.h:
O_APPEN | при записи в файл информация будет добавляться в конец файла; |
O_BINARY | файл открывается для работы в двоичном режиме (игнорируются управляющие символы, такие как конец строки); |
O_CREAT | создается новый файл и открывается для записи; эта константа игнорируется, если указанный в первом параметре файл уже существует; |
O_EXCL | используется вместе с O_CREAT; если указанный в первом параметре файл существует, функция возвратит признак ошибки; |
O_RDONLY | файл открывается только для чтения, попытка записи в файл приведет к тому, что функция записи вернет признак ошибки; |
O_RDWR | файл открывается как для чтения, так и для записи; |
O_TEXT | файл открывается в текстовом режиме; |
O_TRUNC | существующий файл открывается и обрезается до нулевой длины (если для этого файла разрешена операция записи); |
O_WRONLY | файл открывается только для записи (в операционных системах MS-DOS и OS/2 для файла, открытого с признаком O_WRONLY разрешено выполнение операции чтения). |
int close(int handle);
В качестве параметра функции передается файловый индекс, полученный при открытии или создании файла. Функция возвращает 0 при успешном закрытии файла, или -1 при ошибке.
Код ошибки для этой и других функций cтандартных библиотек трансляторов Microsoft QC 2.5 и C 6.0 записывается в глобальную переменную errno.
Расширенный блок параметров BIOS
2 sect_siz
Количество байтов в одном секторе диска.
(+2) 1 clustsiz Количество секторов в одном кластере.
(+3) 2 res_sect Количество зарезервированных секторов.
(+5) 1 fat_cnt Количество таблиц FAT.
(+6) 2 root_siz Максимальное количество дескрипторов файлов, содержащихся в корневом каталоге диска.
(+8) 2 tot_sect Общее количество секторов на носителе данных (в разделе DOS).
(+10) 1 media Байт-описатель среды носителя данных.
(+11) 2 fat_size Количество секторов, занимаемых одной копией FAT.
--------------- Расширение стандартного BPB --------------
(+13) 2 sectors Количество секторов на дорожке
(+15) 2 heads Количество магнитных головок
(+17) 2 hidden_l Количество скрытых секторов для раздела, который по размеру меньше 32 мегабайтов.
(+19) 2 hidden_h Количество скрытых секторов для раздела, превышающего по размеру 32 мегабайта. (Только для DOS 4.0).
(+21) 4 tot_secs Общее количество секторов на логическом диске для раздела, превышающего по размеру 32 мегабайта.
Регистры состояния контроллера
Формат регистра ST0:
Биты Название Назначение 1, 0 US1, US2 Эти биты содержат код накопителя при прерывании.
2 HD Номер головки.
3 NC Накопитель не готов, устанавливается, если накопитель не готов выполнить команду чтения или записи.
4 EC Сбой оборудования
5 SE Завершена команда "Поиск"
7, 6 I, C Код прерывания: 00 - нормальное завершение; 01 - аварийное завершение; 10 - некорректная команда 11 - нет готовности дисковода.
Формат регистра ST1:
Биты Название Назначение 0 MA Пропуск адресной метки. Этот бит устанавливается в 1, если контроллер не может найти адресную метку
1 NN Защита записи, устанавливается , если при выполнении операции контроллер получает от дисковода сигнал защиты записи.
2 ND Не найден сектор.
3 - Зарезервирован
4 OR Переполнение, процессор не успевает выполнять обмен данными с контроллером
5 DE Ошибка в данных при проверке контрольной суммы
6 - Зарезервирован.
7 EN Несуществующий сектор, устанавливается, когда контроллер пытается прочесть сектор с адресом, большим существующего.
Формат регистра ST2:
Биты Название Назначение 0 MD Пропущен адресный маркер в поле данных.
1 BC Нечитающаяся дорожка.
2 SN Ошибка сканирования. Устанавливается, если при выполнении команды сканирования контроллер не может найти требуемую дорожку.
3 SH Сканирование выполнено, дорожка найдена.
4 WC Ошибка адреса дорожки.
5 DD Ошибка в поле данных.
6 CM Во время операции чтения или сканирования не обнаружен сектор с с маркером удаленных данных.
7 - Зарезервирован.
Формат регистра ST3:
Биты Название Назначение 1, 0 US1, US2 Код выбранного дисковода.
2 HD Номер выбранной головки.
3 TS Используется режим двухсторонней записи.
4 T0 Головка установлена на дорожку 0.
5 RDY Дисковод готов к работе.
6 WP Защита записи на диске.
7 FT Неисправность дисковода.
В форматах команд и таблицах используются следующие обозначения:
MT | двухсторонняя операция |
MFM | двойная/одинарная плотность записи |
SK | пропуск удаленных данных |
HDS | номер головки для двухстороннего накопителя |
DS1, DS0 | номер выбираемого накопителя |
C | номер цилиндра |
H | номер головки для двухстороннего накопителя |
R | номер сектора |
N | число байтов в секторе |
EOT | номер последнего сектора на дорожке |
GPL | размер промежутка |
DTL | число считываемых/записываемых байтов |
SC | число секторов в цилиндре |
D | данные |
PCN | номер цилиндра после выполнения команды |
чтения состояния прерывания | |
SRT | время шага, мс |
HUT | время разгрузки головки |
HLT | время загрузки головки |
ND | режим прерывания |
NCN | номер цилиндра после поиска |
Рекалибровка дисковода (НМД)
На входе: | AH = 11h |
DL = Адрес дисковода (80h, 81h, ...) | |
На выходе: | AH = Состояние дисковода после завершения последней операции |
CF = 1, если произошла ошибка, 0, если ошибки нет |
|
Примечание: | PC, XT, AT, PS/2 |
Вызов функции приводит к позиционированию головок выбранного дисковода на нулевую дорожку. Дополнительно в регистре AH возвращается байт состояния дисковода.
Сброс дисковой подсистемы
На входе: | AH = 00h |
DL = Адрес дисковода (0, 1, ...,80h, 81h, ...) | |
На выходе: | - |
Примечание: | PC, XT, AT, PS/2 |
Эта функция вызывает сброс и рекалибровку дискового контроллера (головки устанавливаются на нулевой цилиндр). Если в адресе дисковода старший бит (бит 7) установлен в 1, выполняется сброс контроллера НМД. Сброс рекомендуется выполнять после того, как произошла ошибка при выполнении других операций, таких как чтение или запись. После сброса можно попытаться повторить операцию.
Адрес дисковода 0 соответствует первому флоппи-диску (A:), 1 - второму (B:) и т.д. Адреса 80, 81 соответствуют первому и второму физическим накопителям на жестком магнитном диске.
Сегментная адресация памяти
Особенностью архитектуры процессоров INTEL 8086, 80286, 80386, 80486 является использование механизма сегментации адресного пространства. Сегментация вызывает трудности у тех программистов, которые раньше работали на ЭВМ типов PDP, СМ ЭВМ, ЕС ЭВМ. В этих машинах программа имеет дело с логическими адресами, которые тем или иным способом отображаются на физические адреса. Программа может не знать подробностей отображения логических адресов на физические, она работает только с логическим адресом.
Прообраз процессора 8086 - оригинальный микропроцессор INTEL 8080 - имел линейное адресное пространство размером 64 килобайта. В этом микропроцессоре логический и физический адреса совпадали - все 16 адресных линий (адресных шин) использовались непосредственно для адресации памяти, а программы оперировали абсолютными шестнадцатиразрядными адресами.
Однако быстро растущие потребности программ в оперативной памяти привели к необходимости расширения адресного пространства. Следующий микропроцессор 8086 имел уже 20 адресных линий, что позволило непосредственно адресовать до мегабайта оперативной памяти. Архитектурное решение этого микропроцессора позволило легко адаптировать накопленное в большом количестве программное обеспечение для микропроцессора 8080.
Микропроцессор 8086 является шестнадцатиразрядным, поэтому использование двадцатиразрядного адреса в 16-разрядных командах неэффективно. Вместо указания в командах полного 20-разрядного адреса используется двухкомпонентная адресация, причем каждая компонента использует только 16 разрядов.
Эти компоненты называются сегментной компонентой адреса и компонентой смещения. Логический 20-разрядный адрес получается сложением двух компонент, причем сегментный адрес перед сложением умножается на 16 (сдвигается влево на 4 разряда). Сложение и сдвиг выполняется аппаратно, поэтому на формирование 20-разрядного адреса дополнительно время не затрачивается.
На рисунке показано, как в процессоре 8086 происходит формирование 20-разрядного адреса из адреса сегмента и смещения:
19 4 3 0 +----------------------------------+ ¦ Сегментный адрес ¦ 0 0 0 0 ¦ +----------------------------------+
+ 19 16 15 0 +----------------------------------+ ¦ 0 0 0 0 ¦ Смещение ¦ +----------------------------------+
= 19 0 +----------------------------------+ ¦ Полный 20-разрядный адрес ¦ +----------------------------------+
Адрес сегмента сдвигается влево на 4 бита с заполнением младших битов нулями, смещение расширяется до 20 битов и складывается со сдвинутым адресом сегмента. Например, если адрес сегмента равен 1234h, а смещение равно 1116h, то полный 20-разрядный адрес будет 12340h + 01116h = 13456h.
Таким образом, оперируя 16-разрядными адресами сегмента и смещением, процессор может адресовать мегабайт памяти. Для хранения сегментных адресов и смещений процессор имеет специальные регистры.
Каждая выполняющаяся программа в любой момент времени может адресоваться сразу к четырем сегментам памяти. Это сегмент кода, сегмент данных, дополнительный сегмент данных, сегмент стека. Сегмент кода содержит выполняющиеся машинные команды, сегменты данных и дополнительных данных используются для размещения используемых программой переменных, массивов и других структур данных, сегмент стека используется при вызове подпрограмм.
Сегменты могут перекрываться или не перекрываться.
Для хранения сегментных адресов процессор имеет 4 сегментных регистра: CS, DS, ES, SS. Эти регистры содержат соответственно адреса сегментов кода, данных, дополнительных данных и стека.
При адресации выполняющегося кода вместе с регистром CS используется регистр смещения IP. Пара регистров CS:IP всегда указывает на текущую выполняющуюся команду.
Адресация данных возможна относительно любого сегментного регистра. При этом смещение может указываться как непосредственно в команде, так и с помощью регистров. Программа должна сама следить за правильной загрузкой и использованием сегментных регистров.
Мы приведем несколько примеров программ, составленных на языке ассемблера. Эти программы используют различное количество сегментов и могут служить шаблоном для составления ваших собственных программ.
Первая программа использует всего один сегмент. В этом сегменте расположены выполняющиеся машинные команды и данные, используемые программой. Заметьте, что размер программы, состоящей из одного сегмента, не может превышать 64 килобайта.
Текст программы:
; В этом месте расположен сегмент кода. Он содержит ; выполняющуюся программу.
code segment
; Директива assume сообщает ассемблеру, как будут ; использоваться сегментные регистры. Эта директива ; не выполняет загрузку сегментных регистров, она ; нужна ассемблеру только для правильного вычисления ; смещений.
assume cs:code, ds:code
; Эта строка нужна для создания com-программы.
org 100h
; При запуске программы управление будет передано ; на оператор с меткой start. ; Первое, что должна сделать программа - правильно ; загрузить сегментные регистры. ; Регистр CS загружается операционной системой ; при запуске программы, поэтому его загружать не надо. ; Регистры DS и ES должны указывать на начало ; сегмента кода, так как программа состоит из одного ; сегмента.
start: mov ax, cs mov ds, ax
; Выводим сообщение msg из сегмента данных
mov ah, 9h mov dx, OFFSET msg int 21h
; Завершаем работу программы
mov ax, 4C00h int 21h
; Строка, которую программа выведет на экран.
msg db "Hello, world.", 13, 10, "$"
code ends
end start
Если для размещения данных и буферов недостаточно одного сегмента, необходимо организовать отдельные сегменты для кода и данных, как это сделано в следующем примере:
; Создаем сегмент стека. Размер стека - 256 байт, ; стек выравнен на границу параграфа (para).
stack segment para stack
; Резервируем 256 байт для стека.
db 100h dup (?)
stack ends
; Создаем сегмент данных. Этот сегмент выравнен на ; границу двухбайтового слова (word).
data segment word
; Строка, которую программа выведет на экран.
msg db "Hello, world.", 13, 10, "$"
data ends
; В этом месте расположен сегмент кода. Он содержит ; выполняющуюся программу.
code segment
; Директива assume сообщает ассемблеру, как будут ; использоваться сегментные регистры. Эта директива ; не выполняет загрузку сегментных регистров, она ; нужна ассемблеру только для правильного вычисления ; смещений.
assume cs:code, ds:data, ss:stack
; При запуске программы управление будет передано ; на оператор с меткой start. ; Первое, что должна сделать программа - правильно ; загрузить сегментные регистры. ; ; Следующие два оператора инициализируют сегментный ; регистр данных DS.
start: mov ax, data mov ds, ax
; Инициализируем сегментный регистр стека и ; указатель стека (регистры SS и SP). ; Эта операция должна выполняться в состоянии ; процессора с запрещенными прерываниями, иначе ; если регистр SS будет содержать уже новое значение, ; а SP - старое и если в этот момент произойдет ; прерывание, адрес возврата и значение регистра флагов ; будут записаны в не предназначенную для этого область.
cli mov ss, ax mov sp, OFFSET stack sti
; Выводим сообщение msg из сегмента данных
mov ah, 9h mov dx, OFFSET msg int 21h
; Завершаем работу программы
mov ax, 4C00h int 21h
code ends
end start
Макроассемблер MASM версии 5.0 и более поздних версий, а также Quick Assembler содержит директивы, упрощающие описание сегментов. Это такие директивы, как .CODE, .DATA, .MODEL и другие. Вы найдете подробное описание этих директив в соответствующей документации по ассемблеру. Остановимся подробнее на директиве .MODEL.
Эта директива задает так называемую модель памяти, используемую программой. Что это такое?
Мы уже говорили о том, что программа должна состоять из одного или нескольких сегментов, в зависимости от размера кода и данных, которыми она оперирует. Существует несколько стандартных вариантов использования сегментов, которые называются моделями памяти. Всего используются шесть моделей памяти:
Tiny;
Small;
Medium;
Compact;
Large;
Huge.
Модель памяти Tiny используется небольшими программами, состоящими из одного сегмента и имеющими формат COM. Использование этой модели памяти - единственный способ получения загрузочного модуля в формате COM.
В модели Small один сегмент используется для кода, один для хранения данных и размещения стека программы. Общий размер программы в этом случае ограничен величиной 128 килобайтов. Большинство небольших программ используют именно эту модель памяти.
Если ваша программа оперирует небольшим объемом данных, но размер кода превышает 64 килобайта, вам подходит модель Medium. В этой модели используется несколько сегментов для хранения кода и только один - для данных.
Модель Compact, в отличие от Medium, использует один сегмент для кода и несколько - для данных. Эта модель больше всего подходит для небольших программ, обрабатывающих большие массивы данных.
Модель памяти Large предоставляет возможность использовать несколько сегментов для кода и несколько сегментов для данных. Эта модель обычно используется большими программами, которые обрабатывают большие объемы данных.
И, наконец, модель памяти Huge. Эта модель аналогична Large, но для программ, составленных на языке Си, она позволяет использовать массивы данных, имеющие размер более одного сегмента.
Приведем два примера использования моделей памяти в программах, составленных на языке ассемблера. Эти примеры аналогичны тем, которые мы только что рассмотрели. Первая программа использует модель памяти Tiny:
; Определяем используемую модель памяти
.model tiny
; Определяем сегмент данных.
.data msg db "Hello, world.", 13, 10, "$"
; Определяем сегмент кода.
.code
; Макрокоманда startup выполняет все необходимые ; инициализирующие действия, которые зависят от ; модели памяти.
.startup
; Выводим сообщение на экран
mov ah, 9h mov dx, OFFSET msg int 21h
; Завершаем выполнение программы
.exit
END
Вторая программа использует модель памяти Small, в ней мы дополнительно определили свой стек:
; Определяем используемую модель памяти
.model small
; Определяем сегмент данных.
.data msg db "Hello, world.", 13, 10, "$"
; Определяем свой стек, его размер - 256 байтов
.stack 100h
; Определяем сегмент кода.
.code
.startup
; Выводим сообщение на экран
mov ah, 9h mov dx, OFFSET msg int 21h
; Завершаем выполнение программы
.exit
END
Для программ, составленных на языке Си, модель памяти указывается при трансляции. Если используется пакетный транслятор, модель указывается при помощи опций в командной строке. Если вы работаете в интегрированной среде, такой как Quick C, модель задается при помощи соответствующего меню, а сама программа не содержит каких-либо директив, определяющих используемую модель памяти.
Как правильно выбрать модель памяти?
Если ваша программа небольшая по размеру, то вам подойдут модели TINY или SMALL. При использовании остальных моделей памяти возможно увеличение размера загрузочного модуля и времени выполнения программы из-за того, что в операциях с данными и при вызове подпрограмм используются полные адреса, состоящие из сегмента и смещения. Это означает, в частности, что если при трансляции программы была использована модель LARGE, то при обращении к каждой переменной и при вызове каждой подпрограммы (функции) будет использоваться полный адрес.
Для сокращения накладных расходов отдельные переменные и функции можно разместить в отдельном сегменте. Для этого их надо описать специальным образом - используя ключевое слово near (для С 6.0 и QC 2.5 можно использовать _near).
Ключевое слово near (_near) сообщает транслятору, что данные должны быть размещены в некотором общем сегменте данных и доступ к ним должен осуществляться с использованием 16-битового адреса (только компонента смещения). Если с этим ключевым словом описана функция, то транслятор поместит ее в текущий сегмент кода, для вызова функции будет также использован 16-битовый адрес.
В противоположность к только что описанному ключевое слово far (_far для С 6.0 и QC 2.5) говорит о том, что данные или функция могут располагаться в любом месте памяти, не обязательно в текущем сегменте, и для адресации необходимо использовать полный 32-битовый адрес.
Ключевое слово huge (_huge) необходимо использовать при описании массвов, которые по своим размерам могут превышать 64К. Для адресации при этом будет использоваться полный 32-битовый адрес. Для функций это ключевое слово не применяется.
Приведем несколько примеров описания данных и функций с использованием ключевых слов near, far, huge.
// Используемая модель памяти - SMALL
char dim1[250]; char _far dim2[45000]; char _huge dim3[80000]; char _far *far_ptr; char _far * _far * far_ptr1;
int _far function1(void); // Используемая модель памяти - LARGE
char _near dim4[2000];
char _far * _near function2(void);
Исчерпывающие сведения об использовании моделей памяти можно почерпнуть из документации на используемый транслятор.
Сектора, головки, цилиндры...
Что же, собственно, представляет из себя диск?
Флоппи-диск - это круглая пластинка, покрытая с двух сторон магнитным материалом, напоминающим используемый в магнитных лентах для обычных бытовых магнитофонов, только отличающимся по некоторым характеристикам (например, по форме и ширине петли гистерезиза). Ближе к центру в диске находится маленькое отверстие, предназначенное для синхронизации:
Когда флоппи-диск вставляется в дисковод, с обеих сторон (сверху и снизу) к нему прижимаются магнитные головки. Они действительно прижимаются, зазора между головками и поверхностью флоппи-диска нет.
С помощью специального шагового двигателя головки могут перемещаться скачкообразно вдоль радиуса диска, как бы прочерчивая по поверхности диска концентрические окружности. Эти окружности называются дорожками, треками или цилиндрами - в литературе можно встретить различные названия:
Жесткий диск состоит из нескольких жестких круглых пластинок, покрытых магнитным материалом:
Эти пластинки вращаются с огромной скоростью (порядка 3600 оборотов в минуту) в герметичном корпусе. Как и для флоппи-диска, около каждой стороны пластинки располагается по одной магнитной головке, но эти головки не соприкасаются с поверхностью диска, а плавают на воздушной подушке в непосредственной близости от диска.
Подавая команды дисковому контроллеру, программа может перемещать блок головок вдоль радиуса диска, переходя таким образом от одного цилиндра к другому. Такие команды обычно выдаются не прикладной программой, а модулями BIOSBIOS, обслуживающими дисковый накопитель. Однако при необходимости программа может сама управлять положением блока головок.
Перемещаясь вдоль окружности дорожки, магнитная головка может записывать или считывать информацию примерно так, как это происходит в бытовом магнитофоне. Запись производится по битам, при этом добавляется различная служебная информация и информация для контроля правильности данных.
Данные записываются не сплошным потоком, а блоками определенного размера. Эти блоки называются секторами. Сектор - это наименьший объем данных, который записывается или прочитывается контроллером. Для сектора выполняется контроль правильности записи или чтения. При записи сектора вычисляется контрольная сумма всех байтов, находящихся в секторе, и эта контрольная сумма записывается на диск в служебную область, находящуюся после сектора. При чтении эта контрольная сумма вычисляется заново и сравнивается со считанной из служебной области. При несовпадении контроллер сообщает программе об ошибке.
Дорожки нумеруются начиная от нулевой, головки тоже начиная от нулевой, а вот сектора - начиная с первого. Почему так было сделано - сказать трудно, но именно такая нумерация используется при работе с контроллером диска и функциями прерывания BIOSBIOS, обслуживающими дисковую подсистему.
Итак, подведем некоторые итоги.
С точки зрения программы, работающей с контроллером НГМД или НМД, диск разбит на дорожки.
Каждый диск обслуживают несколько головок, в зависимости от количества круглых пластинок, покрытых магнитным материалом, из которых состоит диск.
Информация записывается и читается блоками, поэтому все дорожки как бы разбиты на сектора.
В операциях чтения или записи на физическом уровне необходимо указывать номер дорожки (0,1,...), головки (0,1,...), номер сектора (1,2,...).
На самом деле для правильной работы с дисками на физическом уровне программа должна располагать существенно большей информацией о дисках, чем просто номера дорожек или головок. Например, она должна знать, сколько головок и сколько дорожек имеет то или иное дисковое устройство, сколько байтов содержится в одном секторе и многое другое.
Следущий раздел книги посвящен тому, как узнать конфигурацию дисковой подсистемы и основные параметры установленных дисковых накопителей.
Слово состояния устройства
Бит | Назначение |
0-7 | Код ошибки устройства (если команда выполнилась с ошибкой и драйвер установил признак ошибки (бит 15) в единицу, то в это поле он должен записать код ошибки). |
8 | Команда выполнена. Этот бит всегда устанавливается драйвером перед тем, как он возвращает управление операционной системе. |
9 | Занято. Этот бит устанавливается обработчиком команды, когда физическое устройство занято выполнением предыдущей операции и поэтому не может выполнить требуемую команду. Этот бит используется также для передачи такой информации, как "буфер клавиатуры не пуст", "среда носителя данных заменяемая" (в команде проверки возможности замены среды носителя данных). |
10-14 | Зарезервировано. |
15 | Признак ошибки. Устанавливается драйвером, когда он не может обработать запрос или произошла физическая либо логическая ошибка при обработке правильного запроса. Биты 0-7
при этом должны содержать код ошибки. |
Содержимое файла sysp.h
/* SYSP.H - include-файл для примеров, приведенных в книге */
/** *.Name FP_MAKE * *.Title Макро для составления FAR-указателя * *.Descr Макро составляет FAR-указатель, пользуясь * значениями сегмента и смещения * *.Params FP_MAKE(seg,off) * seg - сегмент; * off - смещение * *.Return FAR-указатель seg:off **/
#define FP_MAKE(seg,off) ((void far *) \ ((((unsigned long) (unsigned)(seg)) << 16L) | \ ((unsigned long) (unsigned) (off))))
/* Структура векторной таблицы связи DOS */
#pragma pack(1)
typedef struct _CVT_ { unsigned mcb_seg; void far *dev_cb; void far *file_tab; void far *clock_dr; void far *con_dr; unsigned max_btbl; void far *disk_buf; void far *drv_info; void far *fcb_tabl; unsigned fcb_size; unsigned char num_bdev; unsigned char lastdriv; } CVT;
/* Блок управления памятью MCB */
typedef struct _MCB_ { unsigned char type; unsigned owner; unsigned size; char reserve[11]; } MCB;
/* Префикс программного сегмента PSP */
typedef struct _PSP_ { unsigned char int20h[2]; unsigned mem_top; unsigned char reserv1; unsigned char call_dsp[5]; void far *term_adr; void far *cbrk_adr; void far *crit_err; unsigned parn_psp; unsigned char file_tab[20]; unsigned env_seg; void far *ss_sp; unsigned max_open; void far *file_tba; unsigned char reserv2[24]; unsigned char disp[3]; unsigned char reserv3[9]; unsigned char fcb1[16]; unsigned char fcb2[20]; unsigned char p_size; unsigned char parm[127]; } PSP;
/* Блок управления устройством DOS */
typedef struct _DDCB_ { unsigned char drv_num; unsigned char drv_numd; unsigned sec_size; unsigned char clu_size; unsigned char clu_base; unsigned boot_siz; unsigned char fat_num; unsigned max_dir; unsigned data_sec; unsigned hi_clust; unsigned char fat_size; char reserv1; unsigned root_sec; void far *drv_addr; unsigned char media; unsigned char acc_flag; struct _DDCB_ far *next; unsigned reserv2; unsigned built; } DDCB;
/* Управляющий блок DOS для файлов */
typedef struct _DFCB_ { unsigned handl_num; unsigned char access_mode; unsigned reserv1; unsigned dev_info; void far *driver; unsigned first_clu; unsigned time; unsigned date; unsigned long fl_size; unsigned long offset; unsigned reserv2; unsigned reserv7; unsigned reserv3; char reserv4; char filename[11]; char reserv5[6]; unsigned ownr_psp; unsigned reserv6; unsigned last_clu; char reserv8[4]; } DFCB;
/* Таблица файлов DOS */
typedef struct _DFT_ { struct _DFT_ far *next; unsigned file_count; DFCB dfcb; } DFT;
/* Управляющий блок дискового буфера BCB */
typedef struct _BCB_ { struct _BCB_ far *next; unsigned char drive; unsigned char flag; unsigned sect_num; unsigned reserv1; DDCB far *ddcb; unsigned reserv2; } BCB;
/* Информация о диске */
typedef struct _DINFO_ { char path[64]; unsigned reserv1; unsigned reserv2; unsigned char reserv3; DDCB far *ddcb; unsigned cdir_clu; unsigned reserv4; unsigned reserv5; unsigned reserv6; unsigned char reserv7[7]; } DINFO;
/* Заголовок EXE-программы */
typedef struct _EXE_HDR_ { unsigned signature; unsigned part_pag; unsigned file_size; unsigned rel_item; unsigned hdr_size; unsigned min_mem; unsigned max_mem; unsigned ss_reg; unsigned sp_reg; unsigned chk_summ; unsigned ip_reg; unsigned cs_reg; unsigned relt_off; unsigned overlay; } EXE_HDR;
/* таблица расположения сегментов EXE-программы */
typedef struct _RELOC_TAB_ { unsigned offset; unsigned segment; } RELOC_TAB;
/* конфигурация дисковой подсистемы */
typedef struct _DISK_CONFIG_ { int n_floppy; int n_hard; int t_floppy1; int t_floppy2; int t_hard1; int t_hard2; } DISK_CONFIG;
/* таблица параметров дискеты */
typedef struct _DPT_ { unsigned char srt_hut; unsigned char dma_hlt; unsigned char motor_w; unsigned char sec_size; unsigned char eot; unsigned char gap_rw; unsigned char dtl; unsigned char gap_f; unsigned char fill_char; unsigned char hst; unsigned char mot_start; } DPT;
/* таблица параметров диска */
typedef struct _HDPT_ { unsigned max_cyl; unsigned char max_head; unsigned srwcc; unsigned swpc; unsigned char max_ecc; unsigned char dstopt; unsigned char st_del; unsigned char fm_del; unsigned char chk_del; unsigned char reserve[4]; } HDPT;
/* Элемент таблицы разделов */
typedef struct _PART_ENTRY_ { unsigned char flag; unsigned char beg_head; unsigned beg_sec_cyl; unsigned char sys; unsigned char end_head; unsigned end_sec_cyl; unsigned long rel_sec; unsigned long size; } PART_ENTRY;
/* Главная загрузочная запись */
typedef struct _MBOOT_ { char boot_prg[0x1be]; PART_ENTRY part_table[4]; unsigned char signature[2]; } MBOOT;
/* Расширенный блок параметров BIOS */
typedef struct _EBPB_ { unsigned sectsize; char clustsize; unsigned ressecs; char fatcnt; unsigned rootsize; unsigned totsecs; char media; unsigned fatsize; unsigned seccnt; unsigned headcnt; unsigned hiddensec_low; unsigned hiddensec_hi; unsigned long drvsecs; } EBPB;
/* Загрузочная запись для MS-DOS 4.01 */
typedef struct _BOOT_ { char jmp[3]; char oem[8]; EBPB bpb; char drive; char reserved; char signature; unsigned volser_lo; unsigned volser_hi; char label[11]; char fat_format[8]; char boot_code[450];
} BOOT;
/* Время последнего обновления файла */
typedef struct _FTIME_ { unsigned sec : 5, min : 6, hour : 5; } FTIME;
/* Дата последнего обновления файла */
typedef struct _FDATE_ { unsigned day : 5, month : 4, year : 7; } FDATE;
/* Дескриптор файла в каталоге */
typedef struct _FITEM_ { char name[8]; char ext[3]; char attr; char reserved[10]; FTIME time; FDATE date; unsigned cluster_nu; unsigned long size; } FITEM;
/* Формат дорожки для GENERIC IOCTL */
typedef struct _TRK_LY_ { unsigned no; unsigned size; } TRK_LY;
/* Параметры устройства для GENERIC IOCTL */
typedef struct _DPB_ {
char spec; char devtype; unsigned devattr; unsigned numofcyl; char media_type;
EBPB bpb; char reserved[6];
unsigned trkcnt; TRK_LY trk[100];
} DPB;
/* Параметры для форматирования функцией GENERIC IOCTL */
typedef struct _DPB_FORMAT_ {
char spec; unsigned head; unsigned track;
} DPB_FORMAT;
/* Параметры для чтения/записи функцией GENERIC IOCTL */
typedef struct _DPB_WR_ {
char spec; unsigned head; unsigned track; unsigned sector; unsigned sectcnt; void _far *buffer;
} DPB_WR;
/* Идентификатор BIOS */
typedef struct _BIOS_ID_ {
char date[8]; unsigned reserve; char pc_type;
} BIOS_ID;
#pragma pack()
void far *get_cvt(void); /* получить адрес векторной таблицы связи */ CVT far *get_mcvt(void); /* получить адрес векторной таблицы связи */
MCB far *get_fmcb(CVT far *); /* получить адрес первого MCB */ MCB far *get_nmcb(MCB far *); /* получить адрес следующего MCB */
DDCB far *get_fddcb(CVT far *); /* получить адрес первого DDCB */ DDCB far *get_nddcb(DDCB far *); /* получить адрес следующего DDCB */ DDCB far *get_ddcb(unsigned char); /* получить адрес DDCB для диска */
DFT far *get_fdft(CVT far *); /* получить адрес первой DFT */ DFT far *get_ndft(DFT far *); /* получить адрес следующей DFT */
BCB far *get_fbcb(CVT far *); /* получить адрес первого BCB */ BCB far *get_nbcb(BCB far *); /* получить адрес следующего BCB */
int get_exeh(EXE_HDR *,RELOC_TAB **, FILE *); /* прочитать заголовок EXE */
char unsigned pc_model(void); /* получить модель компьютера */ void disk_cfg(DISK_CONFIG*); /* определить конфигурацию дисковой подсистемы */ DPT _far *get_dpt(void); /* получить адрес DPT */ HDPT _far *get_hdp1(void); /* получить адрес первой HDPT */ HDPT _far *get_hdp2(void); /* получить адрес второй HDPT */
BIOS_ID _far *getbiosi(void); /* получить адрес идентификатора BIOS */
Содержимое файла sysp.inc
; ; Это макроопределение печатает символы на экране ; @@out_ch MACRO c1,c2,c3,c4,c5,c6,c7,c8,c9,c10 mov ah,02h IRP chr,<c1,c2,c3,c4,c5,c6,c7,c8,c9,c10> IFB <chr> EXITM ENDIF mov dl,chr int 21h ENDM ENDM
@@out_str MACRO mov ah,9 int 21h ENDM
Создание, удаление и переименование каталогов
После форматирования логический диск содержит корневой каталог. Если диск форматируется как системный, в этом каталоге могут находится дескрипторы файлов операционной системы IO.SYS, MSDOS.SYS, COMMAND.COM.
Операционная система предоставляет программам пользователя удобный сервис для создания, уничтожения и переименования каталогов. Используя сведения, приведенные в этой книге, вы сможете изменять структуру каталогов сами, не прибегая к услугам MS-DOS. Однако это следует делать только тогда, когда операции с каталогами по каким-то причинам нежелательно выполнять с использованием функций операционной системы.
Для создания каталога используйте функцию 39h
прерывания INT21h:
На входе: | AH = 39h |
DS:DX = Адрес строки в формате ASCIIZ, содержащей имя создаваемого каталога. | |
На выходе: | AL = Код ошибки, если был установлен в 1
флаг переноса CF. |
Строка, имени создаваемого каталога может содержать полный путь, состоящий из имени диска и имени каталога, в котором должен быть создан каталог, или она может состоять только из одного имени каталога. В последнем случае каталог создается в текущем каталоге на текущем диске.
Размер строки с именем каталога не должен превышать по длине 64 байта.
Удалить существующий каталог можно с помощью функции 3Ah. Формат вызова этой функции аналогичен предыдущему:
На входе: | AH = 3Ah |
DS:DX =Адрес строки в формате ASCIIZ, содержащей имя удаляемого каталога. | |
На выходе: | AL = Код ошибки, если был установлен в 1
флаг переноса CF. |
Необходимо заметить, что удалить можно только пустой каталог. И это понятно, так как если вы в обход операционной системы удалите непустой каталог, то описанные в этом каталоге файлы и каталоги будут потеряны для вас, а занимаемое ими место невозможно будет распределить другим файлам.
Для изменения имени каталогов и файлов предназначена функция 56h:
На входе: | AH = 56h |
DS:DX = Адрес строки в формате ASCIIZ, содержащей старое имя каталога или файла. | |
ES:DI = Адрес строки в формате ASCIIZ, содержащей новое имя каталога или файла. | |
На выходе: | AL = Код ошибки, если был установлен в 1
флаг переноса CF. |
// Запоминаем текущий каталог
getcwd( current_dir, _MAX_DIR );
// Пытаемся создать в текущем каталоге новый каталог
if(!mkdir(test_dir)) {
// Если удалось создать каталог, делаем его текущим
chdir(test_dir); printf("\nКталог создан, для удаления нажмите" "\nлюбую клавишу, для сохранения - Ctrl-C");
getch();
// Для удаления только что созданного каталога // возвращаемся в каталог более высокого уровня
chdir(current_dir); rmdir(test_dir); }
// Если каталог с таким именем уже существует или // произошла другая ошибка при создании каталога, // выводим сообщение о невозможности создания каталога.
else printf("\nНе могу создать каталог!"); }
Для переименования каталогов (и файлов) предназначена функция rename():
int rename(char *oldname, char *newname);
Способ ее использования очевиден: необходимо задать старое и новое имена каталогов. Можно задавать как полный путь, так и просто имя каталога/файла. В последнем случае операция переименования выполняется над каталогами или файлами, находящимися в текущем каталоге.
Функция может возвращать один из кодов ошибки:
ENOENT | Нет такого файла или каталога |
EACCES | Нет прав доступа |
EXDEV | Другой диск |
Важное замечание: если вы задаете полный путь, в строке пути повторяйте символ '\' два раза. Это нужно для того, чтобы избежать конфликта с форматом представления констант в языке С. Например:
ret_code = rename("c:\\games","c:\\games_new");
Таблица открытых файлов
В первой книге при описании векторной таблицы связи мы говорили о том, что для всех открытых файлов MS-DOS хранит различную информацию в специальной таблице. Ее адрес находится в поле file_tab
векторной таблицы связи.
В этой таблице для каждого открытого файла хранится информация о количестве файловых индексов (file handle), связанных с данным файлом, режиме открытия файла (чтение, запись и т.д.), слово информации об устройстве, указатель на заголовок драйвера, обслуживающего данное устройство, элемент дескриптора файла (дата, время, имя файла, номер начального кластера, распределенного файлу), номер последнего прочитанного кластера и т.д.
Теперь, когда вы изучили способы работы с файлами, имеет смысл еще раз вернуться к разделу, посвященному таблице открытых файлов.
Вы можете самостоятельно экспериментировать с этой таблицей. Можно, например, попробовать создать несколько файловых индексов для какого-либо файла и посмотреть после этого содержимое поля, в котором находится количество файловых индексов, связанных с данным файлом.
Можно попробовать организовать чтение файла порциями размером в один кластер, при этом каждый раз выводить содержимое поля, в котором находится номер последнего прочитанного кластера. Это один из самых простых способов получить список кластеров, распределенных данному файлу.
Однако не обольщайтесь - все что связано с таблицей файлов, отсутствует в документации по операционной системе MS-DOS. Используя эту таблицу для определения списка кластеров или для каких-либо других целей, вы рискуете потерять совместимость с последующими версиями операционной системы.
Таблица параметров дискеты
1 srt_hut
Биты 0...3 - SRT (Step Rate Time) - задержка для переключения головок, лежит в пределах 1-16 мс и задается с интервалом 1 мс (0Fh - 1mc, 0Eh - 2 mc, 0Dh - 3 mc, ...); биты 4...7 - задержки разгрузки головки, лежит в пределах 16-240 мс и задается с интервалом 16 мс (1 - 16 mc, 2 - 32 mc, ..., 0Fh - 240 mc).
(+1) 1 dma_hlt Бит 0 - значение этого бита, равное 1, говорит о том, что используется прямой доступ к памяти (DMA); биты 2...7 - время загрузки головок HLT_- интервал между сигналом загрузки головок и началом операции чтение/запись, лежит в пределах 2-254 мс и задается с интервалом 2 мс (1 - 2 mc, 2 - 4 mc, ..., 0FFh - 254 mc).
(+2) 1 motor_w Задержка перед выключением двигателя.
(+3) 1 sec_size Код размера сектора в байтах (0 - 128 байтов, 1 - 256, 2 - 512, 3 - 1024).
(+4) 1 eot Номер последнего сектора на дорожке
(+5) 1 gap_rw Длина межсекторного промежутка для чтения/записи.
(+6) 1 dtl Максимальная длина передаваемых данных, используется когда не задана длина сектора.
(+7) 1 gap_f Длина межсекторного промежутка для опрации форматирования.
(+8) 1 fill_char Байт-заполнитель для форматирования (обычно используется F6h).
(+9) 1 hst Время установки головки в миллисекундах.
(+10) 1 mot_start Время запуска двигателя в 1/8 долях секунды.
Таблица параметров для жестких дисков
+ ¦Тип ¦ Количество ¦ Количество ¦ Емкость диска ¦ ¦ цилиндров ¦ головок ¦ в байтах ++++ ¦ 1 ¦ 306 ¦ 4 ¦ 10.653.696 ¦ 2 ¦ 615 ¦ 4 ¦ 21.411.840 ¦ 3 ¦ 615 ¦ 6 ¦ 32.117.760 ¦ 4 ¦ 940 ¦ 8 ¦ 65.454.080 ¦ 5 ¦ 940 ¦ 6 ¦ 49.090.560 ¦ 6 ¦ 615 ¦ 4 ¦ 21.411.840 ¦ 7 ¦ 462 ¦ 8 ¦ 32.169.984 ¦ 8 ¦ 733 ¦ 5 ¦ 31.900.160 ¦ 9 ¦ 900 ¦ 15 ¦ 117.504.000 ¦ 10 ¦ 820 ¦ 3 ¦ 21.411.840 ¦ 11 ¦ 855 ¦ 5 ¦ 37.209.600 ¦ 12 ¦ 855 ¦ 7 ¦ 52.093.440 ¦ 13 ¦ 306 ¦ 8 ¦ 21.307.392 ¦ 14 ¦ 733 ¦ 7 ¦ 44.660.224 ¦ 15 ¦ 0 ¦ 0 ¦ 0 ¦ 16 ¦ 612 ¦ 4 ¦ 21.307.392 ¦ 17 ¦ 977 ¦ 5 ¦ 42.519.040 ¦ 18 ¦ 977 ¦ 7 ¦ 59.526.656 ¦ 19 ¦ 1024 ¦ 7 ¦ 62.390.272 ¦ 20 ¦ 733 ¦ 5 ¦ 31.900.160 ¦ 21 ¦ 733 ¦ 7 ¦ 44.660.224 ¦ 22 ¦ 733 ¦ 5 ¦ 31.900.160 ¦ 23 ¦ 306 ¦ 4 ¦ 10.653.696 ¦ 24 ¦ 977 ¦ 5 ¦ 42.519.040 ¦ 25 ¦ 1024 ¦ 9 ¦ 80.216.064 ¦ 26 ¦ 1224 ¦ 7 ¦ 74.575.872 ¦ 27 ¦ 1224 ¦ 11 ¦ 117.190.656 ¦ 28 ¦ 1224 ¦ 15 ¦ 159.805.440 ¦ 29 ¦ 1024 ¦ 8 ¦ 71.303.168 ¦ 30 ¦ 1024 ¦ 11 ¦ 98.041.856 ¦ 31 ¦ 918 ¦ 11 ¦ 87.892.992 ¦ 32 ¦ 925 ¦ 9 ¦ 72.460.800 ¦ 33 ¦ 1024 ¦ 10 ¦ 89.128.960 ¦ 34 ¦ 1024 ¦ 12 ¦ 106.954.752 ¦ 35 ¦ 1024 ¦ 13 ¦ 115.867.648 ¦ 36 ¦ 1024 ¦ 14 ¦ 124.780.544 ¦ 37 ¦ 1024 ¦ 2 ¦ 17.825.792 ¦ 38 ¦ 1024 ¦ 16 ¦ 142.606.336 ¦ 39 ¦ 918 ¦ 15 ¦ 119.854.080 ¦ 40 ¦ 820 ¦ 6 ¦ 42.823.680 +----
Таблица параметров жесткого диска
2 max_cyl
Максимальное количество цилиндров на диске.
(+2) 1 max_head Максимальное количество магнитных головок.
(+3) 2 srwcc Начальный цилиндр для предварительной записи (Starting reduced-write current cylinder).
(+5) 2 swpc Начальный цилиндр для предварительной компенсации при записи (Starting write precompensation cylinder).
(+7) 1 max_ecc Максимальная длина блока коррекции ошибок ECC (Maximum ECC data burst length).
(+8) 1 dstopt Опции устройства: бит 7 - запрет восстановления; бит 6 - запрет восстановления по блоку коррекции ошибок ECC (Error Correction Code); биты 2-0 - опции устройства.
(+9) 1 st_del Стандартная величина задержки.
(+10) 1 fm_del Величина задержки для форматирования диска.
(+11) 1 chk_del Величина задержки для проверки диска.
(+12) 4 reserve Зарезервировано.
Таблица разделов и логические диски
Персональный компьютер обычно комплектуется одним или двумя НМД. Однако операционная система позволяет вам разбивать НМД на части, причем каждая часть будет рассматриваться DOS как отдельный, "логический" диск.
Зачем нужно разбивать диск на логические диски?
Первые персональные компьютеры IBM PC были укомплектованы только НГМД. Дискеты позволяют хранить относительно небольшие объемы информации, поэтому делить флоппи-диск на части не имеет смысла. Следующая модель компьютера - IBMXT- имела жесткий диск объемом 10 или 20 мегабайт. Диск объемом 20 мегабайтов имели и некоторые экземпляры IBM AT. При использовании таких дисков и операционных систем MS-DOS версий до 3.20 у пользователей не возникало никаких проблем и желания разбить диск относительно малого объема на еще меньшие части.
Проблемы возникли, когда производители НМД освоили выпуск дисков объемом 40 мегабайтов и больше. Оказалось, что используемый DOS механизм 16-ти разрядной адресации секторов не позволяет использовать диски объемом, большим, чем 32 мегабайта.
Операционная система MS-DOS версии 3.30 предложила некоторый выход из создавшегося положения. С помощью утилиты FDISK можно было разбить физический диск на логические, каждый из которых не должен превышать по объему 32 мегабайта.
Впоследствии в версиях 4.00 MS-DOS и 3.31 COMPAQ DOS указанное выше ограничение на размер логического диска было снято, однако схема разделения физического диска на логические полностью сохранилась. Существуют и другие причины, по которым может быть полезно разделение большого диска на части:
В случае повреждения логического диска пропадает только та информация, которая находилась на этом логическом диске.
Реорганизация и выгрузка диска маленького размера проще и быстрее, чем большого.
Возможно разделение дискового пространства между отдельными пользователями персонального компьютера. Такая практика "коллективной" работы на персональном компьютере очень распространена.
При использовании специальных утилит для разбиения диска на части (диск-менеджеров) возможна установка для отдельных логических дисков защиты от записи. Вы можете записывать на такие диски неизменяющуюся информацию. Вред от программ-вирусов также будет меньше - вирус не сможет записать себя на защищенный диск.
Один диск может содержать несколько различных операционных систем, расположенных в разных разделах диска. В ходе начальной загрузки вы можете указать раздел диска, из которого должна производиться загрузка операционной системы.
По своей внутренней структуре логический диск полностью соответствует дискете, поэтому сначала мы изучим логическую структуру жесткого диска, затем сделаем некоторые замечания, касающиеся дискет.
Самый первый сектор жесткого диска (сектор 1, дорожка 0, головка 0) содержит так называемую
главную загрузочную запись (Master Boot Record). Эта запись занимает не весь сектор, а только его начальную часть. Сама по себе главная загрузочная запись является программой. Эта программа во время начальной загрузки операционной системы с жесткого диска помещается по адресу 7C00:0000, после чего ей передается управление. Загрузочная запись продолжает процесс загрузки операционной системы.
В конце самого первого сектора жесткого диска располагается таблица разделов диска (Partition Table). Эта таблица содержит четыре элемента, описывающих максимально четыре раздела диска. В последних двух байтах сектора находится число 55AA. Это признак таблицы разделов.
Для просмотра и изменения содержимого таблицы разделов жесткого диска используется утилита DOS FDISK, или аналогичная утилита другой операционной системы.
Что представляет из себя элемент таблицы разделов диска?
Это структура размером 16 байтов, описывающая часть диска, называемую разделом. В структуре описаны границы раздела в терминах номеров сектора, дорожки и головки, там располагается информация о размере раздела в секторах и о назначении раздела. Разделы диска могут быть активными, активный раздел может быть использован для загрузки операционной системы. Заметьте, что диск может содержать одновременно несколько активных разделов, которые могут принадлежать разным операционным системам.
Приведем формат первого сектора жесткого диска:
Смещение | Размер | Содержимое |
(+0) | 1BEh | Загрузочная запись - программа, которая загружается и выполняется во время начальной загрузки операционной системы |
(+1BEh) | 10H | Элемент таблицы разделов диска |
(+1CEh) | 10H | Элемент таблицы разделов диска |
(+1DEh) | 10H | Элемент таблицы разделов диска |
(+1EEh) | 10H | Элемент таблицы разделов диска |
(+1FEh) | 2 | Признак таблицы разделов - 55AAh |
Все элементы таблицы разделов диска имеют одинаковый формат:
Смещение | Размер | Содержимое |
(+0) | 1 | Признак активного раздела: 0 - раздел не активный; 80h - раздел активный. |
(+1) | 1 | Номер головки для начального сектора раздела. |
(+2) | 2 | Номер сектора и цилиндра для начального сектора раздела в формате функции чтения сектора INT 13h. |
(+4) | 1 | Код системы: 0 - неизвестная система; 1, 4 - DOS; 5 - расширенный раздел DOS. |
(+5) | 1 | Номер головки для последнего сектора раздела. |
(+6) | 2 | Номер сектора и цилиндра для последнего сектора раздела в формате функции чтения сектора INT 13h. |
(+8) | 4 | Относительный номер сектора начала раздела. |
(+12) | 4 | Размер раздела в секторах. |
В самом первом секторе активного раздела расположена загрузочная запись (Boot Record), которую не следует путать с главной загрузочной записью (Master Boot Record). Загрузочная запись считывается в оперативную память главной загрузочной записью, после чего ей передается управление. Загрузочная запись и выполняет загрузку операционной системы.
Таким образом, загрузка операционной системы с жесткого диска - двухступенчатый процесс. Вначале модули инициализации BIOS считывают главную загрузочную запись в память по адресу 7C00:0000
и ей передается управление. Главная загрузочная запись просматривает таблицу разделов и находит активный раздел. Если активных разделов несколько, на консоль выводится сообщение о необходимости выбора активного раздела для продолжения загрузки.
После того как активный раздел найден, главная загрузочная запись считывает самый первый сектор раздела в оперативную память. Этот сектор содержит загрузочную запись, которой главная загрузочная запись и передает управление.
Загрузочная запись активного раздела выполняет загрузку операционной системы, находящейся в активном разделе.
Такой двухступенчатый метод загрузки операционной системы необходим по той причине, что способ загрузки зависит от самой операционной системы, поэтому каждая операционная система имеет свой собственный загрузчик. Фиксированным является только расположение загрузочной записи - самый первый сектор активного раздела.
Расскажем подробнее о некоторых полях элемента таблицы раздела диска.
Байт со смещением 0, как мы уже говорили, является флагом активного раздела и может принимать одно из двух значений - 0 или 80h
соответственно для неактивного и активного разделов диска.
Двухбайтовое слово, расположенное со смещением 8, содержит относительный номер первого сектора раздела. Как он вычисляется?
Значение 0 соответствует дорожке 0, головке 0, сектору 1. При увеличении относительного номера сектора вначале увеличивается номер сектора на дорожке, затем номер головки, и, наконец, номер дорожки. Для вычисления относительного номера сектора можно использовать следующую формулу:
RelSect = (Cyl * Sect * Head) + (Head * Sect) + (Sect -1)
В этой формуле:
Cyl - номер дорожки; Sect - номер сектора на дорожке; Head - номер головки.
Замечание, касающееся границ разделов диска: обычно разделы начинаются с четных номеров дорожек, за исключением самого первого раздела. Этот раздел может начинаться с сектора 2
нулевой дорожки (головка 0), так как самый первый сектор диска занят главной загрузочной записью.
Байт со смещением 4 - это код системы, использующей раздел диска. Для DOS зарезервированы значения 0, 1, 4, 5.
Значение 0 соответствует неиспользуемому разделу диска.
Если код системы в элементе таблицы раздела равен 1 или 4, это означает, что раздел используется DOS в качестве первичного раздела
(Primary Partition). Первичный раздел используется DOS как логический диск. Этот раздел обычно является активным и из него выполняется загрузка операционной системы. В зависимости от того, какой код системы используется для обозначения первичного раздела DOS (1 или 4) меняется одна из характеристик логического диска - размер элемента таблицы размещения файлов (FAT). Код 1
используется для обозначения 12-битовой FAT, 4 - для 16-битовой FAT. Таблица размещения файлов будет описана ниже в этой главе.
Значение кода системы, равное 5, обозначает расширенный раздел DOS (Extended DOS Partiton).
Нетрудно заметить, что даже используя все элементы таблицы разделов для создания логических дисков, невозможно создать более четырех дисков. А что делать с винчестерами объемом 300 или 700 мегабайтов? Использование расширенного раздела DOS позволит вам создать любое количество логических дисков. Все эти диски будут располагаться в пределах одного расширенного раздела.
Утилита MS-DOS FDISK позволяет вам создать один первичный раздел DOS и один расширенный раздел. Первичный раздел должен быть активным, он используется как диск С: и из него выполняется загрузка операционной системы. Расширенный раздел разбивается утилитой на логические диски D:, E: и т.д. Расширенный раздел не может быть активным, следовательно, невозможно выполнить загрузку операционной системы с логических дисков, расположенных в этом разделе.
Если в элементе таблицы разделов байт кода системы имеет значение 5, то в начале раздела, указанном в этом элементе, располагается сектор, содержащий таблицу логических дисков. Фактически эта таблица является расширением таблицы разделов диска, расположенной в самом первом секторе физического диска. Таблица логических дисков имеет формат, аналогичный таблице разделов диска, но имеет только два элемента. Один из них указывает на первый сектор логического диска DOS, он имеет код системы 1
или 4. Второй элемент может иметь код системы, равный 5 или 0. Если этот код равен 5, то элемент указывает на следующую таблицу логических дисков. Если код системы равен 0, то соответствующий элемент не используется.
Из сказанного выше следует, что таблицы логических дисков связаны в список, на начало этого списка указывает элемент таблицы разделов диска с кодом системы, равным 5.
Для таблицы логических дисков имеется отличие в использовании полей границ логических дисков: если код системы равен 1 или 4, эти границы вычисляются относительно начала расширенного раздела; для элемента с кодом системы 5
используется абсолютная адресация (относительно физического начала диска).
Приведем конкретный пример. Пусть на диске создано два раздела - первичный и расширенный. Первичный раздел используется для загрузки MS-DOS (диск С:), расширенный раздел содержит логические диски D:, E:, F:. На рисунке показано расположение разделов на диске:
++ ¦ ГЛАВНАЯ ЗАГРУЗОЧНАЯ ЗАПИСЬ ¦ | Сектор главной ¦ ¦ | загрузочной ¦ ¦ | записи. ¦ Таблица разделов диска: ¦ | +¦ | Сектор 1, ¦ Элемент 1 ++ | дорожка 0, +¦ ¦ | головка 0. ¦ Элемент 2 +++ | +¦ ¦ ¦ | ¦ Элемент 3 ¦ ¦ ¦ | +¦ ¦ ¦ | ¦ Элемент 4 ¦ ¦ ¦ ¦¦ ¦ ¦ ¦ ++ ¦ | Диск С: ¦ ПЕРВИЧНЫЙ РАЗДЕЛ DOS ¦ ¦ | ¦ ¦ ¦ | ¦ ¦ ¦ | ¦¦ ¦ ¦ РАСШИРЕННЫЙ РАЗДЕЛ DOS ++ ¦ ¦ | Сектор ¦ ¦ | таблицы ¦ ¦ | логических ¦ Таблица логических дисков: ¦ | дисков +¦ | ¦ Элемент 1 ++ | +¦ ¦ | ¦ Элемент 2 +++ ¦¦ ¦ ¦ ¦ ЛОГИЧЕСКИЙ ДИСК ++ ¦ | Диск D: ¦ ¦ ¦ | ¦ ¦ ¦ ¦¦ ¦ ¦ РАСШИРЕННЫЙ РАЗДЕЛ DOS ++ ¦ ¦ | Сектор ¦ ¦ | таблицы ¦ ¦ | логических ¦ Таблица логических дисков: ¦ | дисков +¦ | ¦ Элемент 1 ++ | +¦ ¦ | ¦ Элемент 2 +++ ¦¦ ¦ ¦ ¦ ЛОГИЧЕСКИЙ ДИСК ++ ¦ | Диск E: ¦ ¦ ¦ | ¦ ¦ ¦ | ¦¦ ¦ ¦ РАСШИРЕННЫЙ РАЗДЕЛ DOS ++ ¦ ¦ | Сектор ¦ ¦ | таблицы ¦ ¦ | логических ¦ Таблица логических дисков: ¦ | дисков +¦ | ¦ Элемент 1 ++ | +¦ ¦ | ¦ Элемент 2 ¦ ¦ ¦¦ ¦ ¦ ЛОГИЧЕСКИЙ ДИСК ++ | Диск F: ¦ ¦ | ¦ ¦ ++
Операционная система не предоставляет программам никаких средств для работы с главной загрузочной записью и таблицей разделов диска. Мы постараемся восполнить этот недостаток, подготовив свои функции для работы с этими данными.
Файл sysp.h содержит определения типов для главной загрузочной записи и таблицы разделов диска:
#pragma pack(1)
/* Элемент таблицы разделов */
typedef struct _PART_ENTRY_ { unsigned char flag; unsigned char beg_head; unsigned beg_sec_cyl; unsigned char sys; unsigned char end_head; unsigned end_sec_cyl; unsigned long rel_sec; unsigned long size; } PART_ENTRY;
/* Главная загрузочная запись */
typedef struct _MBOOT_ { char boot_prg[0x1be]; PART_ENTRY part_table[4]; unsigned char signature[2]; } MBOOT;
#pragma pack()
Для того, чтобы прочитать главную загрузочную запись для одного из установленных в компьютере НМД, вы можете использовать следующую функцию:
/** *.Name getmboot * *.Title Считать главную загрузочную запись * *.Descr Функция считывает главную загрузочную запись * для указанного НМД. * *.Params int getmboot(MBOOT *master_boot, int drive); * * master_boot - указатель на буфер, в который * будет считана главная загрузочная * запись * * drive - номер физического НМД * (0 - первый НМД, 1 - второй,...) * *.Return 0 - если главная загрузочная запись считана * успешно; * Код ошибки, полученный от функции BIOS "Чтение * сектора" - если чтение главной загрузочной * записи выполнить невозможно. **/
#include <stdio.h> #include <bios.h> #include "sysp.h"
int getmboot(MBOOT *master_boot, int drive) {
struct diskinfo_t di; int status;
// Подготавливаем структуру для чтения // главной загрузочной записи
di.drive = drive | 0x80; di.head = 0; di.track = 0; di.sector = 1; di.nsectors = 1; di.buffer = (char*)master_boot;
// Читаем сектор, содержащий главную // загрузочную запись
status = _bios_disk( _DISK_READ, &di ) >> 8;
return(status); }
Как пример использования этой функции приведем текст программы, определяющей количество установленных в системе дисков и выводящей на экран для каждого диска содержимое таблицы разделов. В программе используется функция disk_cfg(), определяющая конфигурацию дисковой подсистемы.
#include <stdio.h> #include <dos.h> #include "sysp.h"
void main(void); void main(void) {
DISK_CONFIG cfg; MBOOT mb; int i,j, k, status;
printf("\n" "\nТаблицы разделов диска" "\n (C)Фролов А., 1991" "\n");
// Определяем конфигурацию дисковой подсистемы
disk_cfg(&cfg);
// Записываем в переменную i количество // установленных в системе НМД
j = cfg.n_hard; printf("\nУстановлено дисков: %d", j);
// Для каждого НМД выводим содержимое // таблицы разделов
for(i=0;i<j;i++) {
// Читаем главную загрузочную запись
status = getmboot(&mb,i); if(status != 0) { printf("\nОшибка чтения диска %d, код ошибки: %d", i, status); exit(1); }
printf("\n\nТаблица разделов диска %d",i); printf("\n"
"\n------------------------------------------------------------" "\n|Флаг|Начало раздела |Конец раздела |Код |Размер |Отн. |" "\n| |---------------|---------------|сист.|раздела|номер |" "\n| |Гол.|Сект.|Цил.|Гол.|Сект.|Цил.| | |сектора|" "\n|----|----|-----|----|----|-----|----|-----|-------|-------|" "\n");
for(k=0; k<4; k++) { printf("|%3d |%4d|%4d |%4d|%4d|%4d |%4d|%5d|%7u|", mb.part_table[k].flag, mb.part_table[k].beg_head, mb.part_table[k].beg_sec_cyl & 0x3f, (mb.part_table[k].beg_sec_cyl >> 6) & 0x3ff, mb.part_table[k].end_head, mb.part_table[k].end_sec_cyl & 0x3f, (mb.part_table[k].end_sec_cyl >> 6) & 0x3ff, mb.part_table[k].sys, mb.part_table[k].size); printf("%7u|\n", mb.part_table[k].rel_sec); } printf("------------------------------------------------------------"); } }
Таблица размещения файлов
Сразу после загрузочного сектора на логическом диске находятся сектора, содержащие таблицу размещения файлов FAT (File Allocation Table). В отечественной литературе иногда можно встретить аббревиатуру ТРФ, однако мы будем пользоваться общепринятым сокращением - FAT.
Для того, чтобы назначение этой таблицы стало более понятным, вспомним, как организовано хранение информации на различных носителях данных.
Магнитные ленты. Этот вид носителей информации использовался еще в самых первых ЭВМ. В современных компьютерах магнитные ленты используются для разгрузки магнитных дисков.
При использовании магнитных лент информация записывается в виде файлов с последовательным доступом. Последовательный доступ означает, что для чтения какого-либо файла требуется вначале прочитать все предыдущие файлы. При записи информация может добавляться в конец ленты, после той информации, которая была записана в последний раз.
Если вы попытаетесь перезаписать файл, то это может привести к потере всех файлов, расположенных на магнитной ленте после перезаписываемого.
Доступ к информации, записанной на магнитном диске, может выполняться либо последовательным, либо прямым методом доступа. Использование прямого метода доступа позволяет позиционировать головки сразу на тот файл, который вам нужен (или на нужную запись файла). Например, вы можете задать номер сектора на оперделенной дорожке и номер головки.
Но метод доступа - это еще не все. Важное значение имеет способ распределения места на диске для файлов. От правильного выбора способа распределения зависит эффективность работы программ.
Операционная система ОС ЕС для ЭВМ ряда ЕС позволяет задать начальное количество цилиндров диска для размещения набора данных и размер области диска, которая может быть использована для этого набора дополнительно. Если при записи в файл все распределенное для файла место на диске окажется исчерпанным, программа завершится аварийно, даже если на диске еще есть свободные цилиндры.
Операционные системы, подобные DOS, UNIX, OS/2 используют дисковое пространство другим способом.
В этих операционных системах при создании файла для него не задается начальное распределение памяти в дорожках или секторах. По мере того, как файл увеличивается в размерах, операционная система распределяет этому файлу сектора из числа свободных, не используемых другими файлами. При этом файл располагается не обязательно в смежных областях диска, он может быть разбросан по разным дорожкам и секторам.
Очевидно, что в этом случае операционная система должна вести учет используемых участков диска. Для каждого файла она должна хранить где-то информацию о том, какому файлу какие участки диска распределены.
В операционной системе MS-DOS для хранения этой информации используется таблица размещения файлов.
Весь диск разбивается операционной системой на участки одинакового размера, называемые кластерами. Кластер может содержать несколько секторов. Для каждого кластера FAT имеет свою индивидуальную ячейку, в которой хранится информация об использовании данного кластера. Другими словами, таблица размещения файлов - это массив, содержащий информацию о кластерах. Размер этого массива определяется общим количеством кластеров на логическом диске. (Именно кластеров, а не секторов!).
Что же хранится в таблице размещения файлов?
Все свободные кластеры помечены в ней нулями. Если файл занимает несколько кластеров, то эти кластеры связаны в список. Для связанных в список кластеров элементы таблицы FAT содержат номера следующих используемых данным файлом кластеров. Конец списка отмечен в таблице специальным значением. Номер первого кластера, распределенного файлу, хранится в элементе каталога, описывающего данный файл.
Утилиты операционной системы и некоторые специальные утилиты проверяют диск на предмет наличия дефектных областей. Кластеры, которые находятся в этих дефектных областях, отмечаются в FAT как плохие и не используются операционной системой.
Итак, FAT - массив информации об использовании кластеров диска, содержит односвязные списки кластеров, распределенных файлам. Номера начальных кластеров файлов хранятся в каталогах, о которых мы будем говорит в разделе "Файлы и каталоги". Прежде чем углубляться в тонкости таблицы размещения файлов, приведем рисунок, иллюстрирующий сказанное выше.
++ ++ ¦ ¦AUTOEXECBAT¦ ... ¦Номер 1- го кластера: 11¦..¦ ¦ ++++¦ ¦ ¦CONFIG SYS¦ ... ¦Номер 1-го кластера: 27¦..¦ ¦ ++ ¦ ¦ ¦ ¦ ¦ +++ ++ ¦ ++ ¦ ¦...¦12¦13¦14¦15¦16¦17¦18¦19¦20¦FF¦ 0¦ 0¦ 0¦ 0¦...¦ ¦ ++ ¦ ++ ¦ ¦...¦28¦29¦30¦FF¦...¦ ¦ ++ ¦ ++
На этом рисунке показаны фрагменты корневого каталога диска С: и элементы FAT для файлов autoexec.bat
и config.sys. Реально эти файлы не используют столько кластеров. Из рисунка видно, что в каталоге для файлов указаны номера первых кластеров (соответственно 11 и 27). Таблица FAT в одиннадцатой ячейке содержит число 12 - номер следующего кластера, распределенного файлу autoexec.bat. Ячейка с номером 12 содержит число 13, и так далее. Последняя ячейка, соответствующая последнему кластеру распределенному этому файлу, содержит специальное значение - FF. В этом примере все кластеры файлов расположены подряд, но это может быть и не так.
Существуют два формата FAT - 12-битовый и 16-битовый. Эти форматы используют, соответственно, 12 и 16 битов для хранения информации об одном кластере диска.
12-битовый формат удобен для дискет с небольшим количеством секторов - вся таблица размещения файлов помещается целиком в одном секторе. Если размер диска такой, что для представления всех секторов двенадцати разрядов недостаточно, можно увеличить размер кластера, например до восьми секторов. Однако большой размер кластера приводит к неэффективному использованию дискового пространства. Это происходит из-за того, что минимальный выделяемый файлу элемент - кластер - имеет слишком большой размер. Даже для файла, имеющего длину 1 байт выделяется целиком кластер. Значит, если размер кластера составляет 8 секторов, то для хранения одного байта будет использовано 4 килобайта дисковой памяти.
При использовании FAT 16-битового формата
операционная система может работать с диском, который имеет размер более 32 мегабайт. DOS версии 4.0 при использовании 16-битового формата FAT и кластеров размером 4 сектора может работать с разделами, по размеру достигающими 134 мегабайтов.
Управляющие блоки MS-DOS
7.1.1.
7.1.2.
7.1.3.
7.1.4.
7.1.5.
7.1.6.
7.1.7.
7.1.8.
7.1.9.
7.1.10.
7.1.11.
7.1.12.
7.1.13.
7.1.14.
7.1.15.
7.1.16.
7.1.17.
7.1.18.
7.1.19.
7.1.20.
7.1.21.
7.1.22.
7.1.23.
7.1.24.
7.1.25.
7.1.26.
7.1.27.
7.1.28.
7.1.29.
7.1.30.
7.1.31.
7.1.32.
7.1.33.
7.1.34.
7.1.35.
7.1.36.
7.1.37.
7.1.38.
Установка среды для форматирования (НГМД)
На входе: | AH = 18h |
DL = Адрес дисковода (0, 1, ...) | |
CH = Младшие 8 битов количества дорожек | |
CL = Количество секторов на дорожку (биты 0-5) | |
На выходе: | AH = 00h Требуемая комбинация количества дорожек и количества секторов на дорожку поддерживается операцией форматирования; |
AH = 01h Функция недоступна; | |
AH = 0Ch Функция не поддерживается или неизвестен тип дисковода; | |
AH = 80h Диск не установлен в дисковод. | |
CF = 1, если произошла ошибка, 0, если ошибки нет |
|
Примечание: | AT, PS/2 |
Эта функция должна быть вызвана перед использованием функции 05h форматирования диска для установки правильной скорости передачи данных через дисковый контроллер. Дополнительно функция сбрасывает флаг замены дискеты (если этот флаг установлен).
Установка типа дискеты
На входе: | AH = 17h |
AL = Устанавливаемый тип | |
DL = Адрес дисковода (0, 1, ..., 80h, 81h, ...) | |
На выходе: | AH = Состояние дисковода после завершения последней операции |
CF = 1, если произошла ошибка, 0, если ошибки нет |
|
Примечание: | AT, PS/2 |
Функцию установки типа дискеты необходимо использовать перед началом работы с дискетой. Устанавливаемый тип может принимать следующие значения:
0 | не используется; |
1 | диск 360К в дисководе 360К; |
2 | диск 360К в дисководе 1.2М (HD); |
3 | диск 1.2М в дисководе 1.2М; |
4 | диск 720К в дисководе 720К. |
Если перед вызовом этой функции был установлен флаг замены дискеты, то он сбрасывается. Дополнительно BIOS BIOSустанавливает скорость передачи данных через контроллер НГМД в зависимости от типа дискеты.
Векторная таблица связи MS-DOS
(-2) 2 | mcb_seg | сегмент первого управляющего блока памяти (MCB) |
(0) 4 | dev_cb | указатель на первый блок управления устройствами DOS (DOS Device Control Block) |
(+4) 4 | file_tab | указатель на таблицу файлов DOS |
(+8) 4 | clock_dr | указатель на драйвер CLOCK$,
установленный или резидентный |
(+12) 4 | con_dr | указатель на актуальный драйвер CON, установленный или резидентный |
----------- DOS 2.x ---------------- | ||
(+16) 1 | num_lgdr | число логических драйверов в системе |
(+17) 2 | max_btbl | максимальное число байтов/блоков любого блочного устройства |
(+19) 4 | disk_buf | указатель на первый дисковый буфер |
(+23) - | null_dr | начало драйвера NUL - первого драйвера в списке драйверов DOS |
----------- DOS 3.x, 4.x , 5.0----------- | ||
(+16) 2 | max_btbl | максимальное число байтов в блоке блочного устройства |
(+18) 4 | disk_buf | указатель на первый дисковый буфер |
(+22) 4 | drv_info | укзатель на массив информации об устройствах |
(+26) 4 | fcb_tabl | указатель на таблицу FCB |
(+30) 2 | fcb_size | размер таблицы FCB |
(+32) 1 | num_bdev | число блочных устройств |
(+33) 1 | lastdriv | значение LASTDRIVE в файле CONFIG.SYS (по умолчанию равно 5) |
(+34) | null_dr | начало драйвера NUL - первого драйвера в списке драйверов DOS |
либо работал на персональном компьютере,
Каждый, кто когда- либо работал на персональном компьютере, знает, что диски предназначены для хранения больших и очень больших объемов информации (десятки и сотни мегабайтов), что информация хранится в виде файлов, а файлы находятся в каталогах. Во всех языках программирования и для всех трансляторов существуют те или иные средства, позволяющие записывать информацию в файлы и читать ее из файлов, как отдельными байтами, так и блоками. Можно также создавать файлы и каталоги, удалять или переименовывать их.
Информация может записываться или читаться как последовательным, так и прямым методом доступа. В первом случае при записи информация всегда добавляется в конец файла, при чтении программа получает информацию из файла последовательно, байт за байтом. Прямой метод доступа позволяет записывать или считывать данные, которые находятся в произвольном месте файла. Программа при этом сама указывает смещение в файле для записи или чтения данных.
Все описанные выше операции с файлами (и многие другие) можно выполнить с помощью стандартных средств, предоставляемых в распоряжение программиста библиотеками трансляторов и систем программирования. Для большинства программ вполне достаточно файлового сервиса стандартных библиотек трансляторов.
Однако системным программистам часто требуются более развитые средства управления дисками и файловой системой, чем те, которые обеспечиваются стандартными библиотеками, функциями DOS, и даже функциями BIOS. В отдельных сложных случаях приходится прибегать к недокументированным прерываниям DOS или использовать недокументированные управляющие блоки, рискуя потерять совместимость разрабатываемой программы с будущими версиями MS-DOS.
В первой книге первого тома мы говорили об использовании недокументированных, т.е. отсутствующих в фирменной документации по MS-DOS, возможностей. Использование недокументированных прерываний и структур данных часто облегчает решение сложных системных задач, однако в различных версиях MS-DOS эти возможности могут быть реализованы по-разному. Отлаженная в MS-DOS версии 3.30 программа, использующая недокументированные возможности, возможно, не будет правильно работать в MS-DOS версии 4.0 или 5.0. Используйте недокументированные прерывания и структуры данных только тогда, когда это действительно необходимо.
В третьей книге первого тома приводится подробное описание работы с дисками на всех уровнях - от программирования контроллера диска до использования функций стандартной библиотеки транслятора Quick C. Мы будем излагать материал, отталкиваясь от самого низкого уровня и постепенно переходя ко все более высокоуровневым средствам управления дисковой подсистемой. Для наглядности на следующем рисунке приведена иерархия средств управления дисками и файловой системой:
Примерно в соответствии с этим рисунком и расположен материал в книге.
Заголовок драйвера
(0) 4 | next | указатель на заголовок следующего драйвера. Если смещение адреса следующего драйвера равно FFFF, это последний драйвер в цепочке |
(+4) 2 | attrib | атрибуты драйвера |
(+6) 2 | strateg | смещение программы стратегии драйвера |
(+8) 2 | interrupt | смещение программы обработки прерывания для драйвера |
(+10) 8 | dev_name | имя устройства для символьных устройств или количество обслуживаемых устройств для блочных устройств. |
Заголовок EXE-файла
(0) 2 | signature | два байта 'MZ' (4Dh, 5Ah),
индентифицирующие файл в формате EXE |
|
(+2) 2 | part_pag | длина последней страницы программы в байтах (страница содержит 512 байт) | |
(+4) 2 | file_size | размер программы в страницах по 512 байт | |
(+6) 2 | rel_item | число элементов в таблице расположения сегментов | |
(+8) 2 | hdr_size | размер заголовка файла в параграфах (длина параграфа - 16 байт) | |
(+10) 2 | min_mem | минимальное количество памяти в параграфах, которое нужно зарезервировать в памяти за концом загруженной программы | |
(+12) 2 | max_mem | максимальное количество памяти в параграфах, которое нужно зарезервировать в памяти за концом загруженной программы | |
(+14) 2 | ss_reg | величина смещения от начала программы, которая используется для загрузки сегментного регистра стека SS | |
(+16) 2 | sp_reg | величина смещения от начала программы, которая используется для загрузки регистра SP | |
(+18) 2 | chk_summ | контрольная сумма всех слов в файле | |
(+20) 2 | ip_reg | значение для регистра IP, которое будет использовано при начальном запуске программы | |
(+22) 2 | cs_reg | смещение от начала программы для установки сегментного регистра кода CS | |
(+24) 2 | relt_off | смещение от начала файла таблицы расположения сегментов программы | |
(+26) 2 | overlay | номер оверлея, равен 0 для основного модуля |
Заголовок запроса
(0) 1 | size | Длина запроса в байтах (длина заголовка запроса плюс длина переменной части запроса) |
(+1) 1 | unit | Номер устройства (используется для блочных устройств, указывает, с каким именно устройством, обслуживаемым драйвером, будет работать операционная система) |
(+2) 1 | cmd | Код команды, которую требуется выполнить (может иметь значение от 0 до 18h) |
(+3) 2 | status | Слово состояния устройства, заполняется драйвером перед возвратом управления операционной системе |
(+5) 8 | reserved | Зарезервировано |
Загрузочная запись BOOT
Самый первый сектор логического диска (и самый первый сектор на системной дискете) занимает загрузочная запись (Boot Record). Эта запись считывается из активного раздела диска программой главной загрузочной записи (Master Boot Record) и запускается на выполнение. Задача загрузочной записи - выполнить загрузку операционной системы. Каждый тип операционной системы имеет свою загрузочную запись. Даже для разных версий одной и той же операционной системы программа загрузки может выполнять различные действия.
Кроме программы начальной загрузки операционной системы в загрузочной записи находятся параметры, описывающие характеристики данного логического диска. Все эти параметры располагаются в самом начале сектора, в его так называемой форматированной области. Формат этой области разный для DOS версий до 4.0 и версии 4.0.
Сначала приведем формат записи BOOT для DOS версий, более ранних, чем 4.0.
Смещение | Размер | Содержимое |
(+0) | 3 | Команда JMP xxxx - переход типа NEAR на программу начальной загрузки |
(+3) | 8 | Название фирмы-производителя операционной системы и версия, например: "IBM 5.0" |
(+11) | 13 | BPB - блок параметров BIOSBIOS |
(+24) | 2 | Количество секторов на дорожке |
(+26) | 2 | Количество головок (поверхностей диска) |
(+28) | 2 | Количество скрытых секторов, эти сектора могут использоваться для схемы разбиения физического диска на разделы |
В самом начале BOOT-сектора располагается команда внутрисегментного перехода JMP. Она нужна для обхода форматированной зоны сектора и передачи управления загрузочной программе, располагающейся со смещением (+30).
Название фирмы-производителя не используется операционной системой.
Со смещением (+11) располагается BPB - блок параметров BIOS, о котором мы уже говорили в разделах книги, посвященных драйверам. Этот блок содержит некоторые характеристики логического диска, о которых мы будем говорить немного позже и используется дисковыми драйверами. Для DOS версий до 4.0 BPB имеет следующий формат:
(0) | 2 | sect_siz | Количество байтов в одном секторе диска. |
(+2) | 1 | clustsiz | Количество секторов в одном кластере. |
(+3) | 2 | res_sect | Количество зарезервированных секторов. |
(+5) | 1 | fat_cnt | Количество таблиц FAT. |
(+6) | 2 | root_siz | Максимальное количество дескрипторов файлов, содержащихся в корневом каталоге диска. |
(+8) | 2 | tot_sect | Общее количество секторов на носителе данных (в разделе DOS). |
(+10) | 1 | media | Байт-описатель среды носителя данных. |
(+11) | 2 | fat_size | Количество секторов, занимаемых одной копией FAT. |
Поля BOOT-сектора со смещениями 24 и 26
содержат соответственно количество секторов на дорожке и количество головок в дисководе. Поле со смещением 28 содержит количество "скрытых" секторов, которые не принадлежат ни одному логическому диску. Эти сектора могут содержать основную или вторичные таблицы разделов диска.
Для MS-DOS версии 4.0 BOOT-сектор имеет другой формат:
Смещение | Размер | Содержимое |
(+0) | 3 | Команда JMP xxxx - переход типа NEAR на программу начальной загрузки |
(+3) | 8 | Название фирмы-производителя операционной системы и версия, например: "IBM 4.0" |
(+11) | 25 | Extended BPB - расширенный блок параметров BIOSBIOS |
(+36) | 1 | Физический номер дисковода (0 -флоппи, 80h - жесткий диск) |
(+37) | 1 | Зарезервировано |
(+38) | 1 | Символ ')' - признак расширенной загрузочной записи DOS 4.0 |
(+39) | 4 | Серийный номер диска (Volume Serial Number), создается во время форматирования диска |
(+43) | 11 | Метка диска (Volume Label) |
(+54) | 8 | Зарезервировано, обычно содержит запись типа 'FAT12 ', которая идентифицирует формат таблицы размещения файлов FAT |
Поле со смещением (+38) всегда содержит символ ')'. Этот символ означает, что используется формат расширенной загрузочной записи операционной системы MS-DOS 4.0.
Серийный номер диска формируется во время форматирования диска на основе даты и времени форматирования. Это поле может быть использовано для определения факта замены диска в дисководе.
Метка диска формируется при форматировании и может быть изменена командой операционной системы LABEL. Одновременно метка диска помещается в корневой каталог.
Поле со смещением 11 содержит расширенный блок параметров BIOS. Он состоит из обычного BPB
и дополнительного расширения:
(0) | 2 | sect_siz | Количество байтов в одном секторе диска. |
(+2) | 1 | clustsiz | Количество секторов в одном кластере. |
(+3) | 2 | res_sect | Количество зарезервированных секторов. |
(+5) | 1 | fat_cnt | Количество таблиц FAT. |
(+6) | 2 | root_siz | Максимальное количество дескрипторов файлов, содержащихся в корневом каталоге диска. |
(+8) | 2 | tot_sect | Общее количество секторов на носителе данных (в разделе DOS). |
(+10) | 1 | media | Байт-описатель среды носителя данных. |
(+11) | 2 | fat_size | Количество секторов, занимаемых одной копией FAT. |
---- Расширение стандартного BPB ----- | |||
(+13) | 2 | sectors | Количество секторов на дорожке |
(+15) | 2 | heads | Количество магнитных головок |
(+17) | 2 | hidden_l | Количество скрытых секторов для раздела, который по размеру меньше 32 мегабайтов. |
(+19) | 2 | hidden_h | Количество скрытых секторов для раздела, превышающего по размеру 32 мегабайта. (Только для DOS 4.0). |
(+21) | 4 | tot_secs | Общее количество секторов на логическом диске для раздела, превышающего по размеру 32 мегабайта. |
Как обычный, так и расширенный блок параметров BIOS содержит байт-описатель среды media. Этот байт может служить для идентификации носителя данных и может содержать следующие величины, характеризующие носитель данных по количеству сторон диска и количеству секторов на дорожке:
FFh | 2 стороны, 8 секторов на дорожке; |
FEh | 1 сторона, 8 секторов на дорожке; |
FDh | 2 стороны, 9 секторов на дорожке; |
FCh | 1 сторона, 9 секторов на дорожке; |
F9h | 2 стороны, 15 секторов на дорожке; |
F8h | жесткий диск. |
байта описателя среды, так как такие дискеты используются крайне редко.
Прежде чем мы продолжим изучение логической структуры диска, покажем, как программа может обратиться к BOOT-сектору.
DOS предоставляет программе возможность работы с так называемыми логическими номерами секторов. Это номера секторов внутри логического диска.
Вы знаете, что для адресации сектора при помощи функций BIOS необходимо указывать номер дорожки, номер головки и номер сектора на дорожке. DOS организует "сквозную" нумерацию секторов, при которой каждому сектору логического диска присваивается свой уникальный номер. Порядок нумерации выбран таким, что при последовательном увеличении номера сектора вначале увеличивается номер головки, затем номер дорожки. Это сделано для сокращения перемещений блока головок при обращении к последовательным логическим номерам секторов.
Пусть, например, у нас есть дискета с девятью секторами на дорожке. Сектор с логическим номером, равным 1, расположен на нулевой дорожке и для обращения к нему используется нулевая головка. Это самый первый сектор на дорожке, в терминах BIOS он имеет номер 1. Следующий сектор на нулевой дорожке имеет логический номер 2, последний сектор на нулевой дорожке имеет логический номер 9. Сектор с логическим номером 10 расположен также на нулевой дорожке. Это тоже самый первый сектор на дорожке, но теперь для доступа к нему используется головка с номером 1. И так далее, по мере увеличения логического номера сектора изменяются номера головок и дорожек.
Для работы с логическим диском ( или дискетой) на уровне логических номеров секторов DOS предоставляет программам два прерывания - INT 25h
(чтение сектора по его логическому номеру) и
INT 26h (запись сектора по его логическому номеру). Вызов этих прерываний имеет различный формат для разных версий DOS. Для тех версий, которые не поддерживают размер логических дисков более 32 М (MS-DOS 3.10, 3.20, 3.30) используется следующий формат:
INT 25h - Чтение сектора по его логическому номеру
На входе: | AL = Адрес дисковода (0 - A, 1 - B, ...) |
CX = Количество секторов, которые нужно прочитать | |
DX = Логический номер начального сектора | |
DS:BX = Адрес буфера для чтения | |
На выходе: | AH = Код ошибки при неуспешном завершении операции |
CF = 1, если произошла ошибка, 0, если ошибки нет |
На входе: | AL = Адрес дисковода (0 - A, 1 - B, ...) |
CX = Количество секторов, которые нужно записать | |
DX = Логический номер начального сектора | |
DS:BX = Адрес буфера, сожержащего записываемые данные | |
На выходе: | AH = Код ошибки при неуспешном завершении операции |
CF = 1, если произошла ошибка, 0, если ошибки нет |
Регистр CX содержит FFFFh - признак того, что работа будет производится с логическим диском, имеющим размер более 32 мегабайтов.
Регистры DS:BX содержат адрес управляющего блока:
(0) | 4 | Начальный номер логического сектора |
(+4) | 2 | Количество секторов для чтения/записи |
(+6) | 4 | FAR-адрес буфера для передачи данных |
Очень важное замечание, касающееся только что рассмотренных прерываний DOS. Эти прерывания оставляют в стеке одно слово - старое значение регистра флагов. Поэтому после вызова прерывания должна следовать, например, такая команда:
pop ax
Содержимое BOOT-сектора может быть использовано для определения общего количества секторов на логическом диске (например, в программах проверки читаемости секторов диска), для работы с таблицей размещения файлов FAT, о которой мы будем говорить ниже, для определения других характеристик логического диска.
Для работы с загрузочной записью мы подготовили структуры, описывающие расширенный блок параметров BIOS EBPB и собственно загрузочную запись BOOT:
#pragma pack(1)
/* Расширенный блок параметров BIOS */
typedef struct _EBPB_ { unsigned sectsize; char clustsize; unsigned ressecs; char fatcnt; unsigned rootsize; unsigned totsecs; char media; unsigned fatsize; unsigned seccnt; unsigned headcnt; unsigned hiddensec_low; unsigned hiddensec_hi; unsigned long drvsecs; } EBPB;
/* Загрузочная запись для MS-DOS 4.01 */
typedef struct _BOOT_ { char jmp[3]; char oem[8]; EBPB bpb; char drive; char reserved; char signature; unsigned volser_lo; unsigned volser_hi; char label[11]; char fat_format[8]; char boot_code[450];
} BOOT;
#pragma pack()
Поле серийного номера диска разбито на две компоненты - volser_lo и volser_hi. Это сделано для облегчения представления серийного номера в виде, аналогичном используемому командой DIR операционной системы MS-DOS 4.0.
Для чтения загрузочной записи логического диска вы можете использовать следующую функцию:
/** *.Name getboot * *.Title Считать загрузочную запись * *.Descr Функция считывает загрузочную запись * для указанного НМД. * *.Params int getmboot(BOOT _far *boot, int drive); * * boot - указатель на буфер, в который * будет считана загрузочная * запись * * drive - номер физического НМД * (0 - первый НМД, 1 - второй,...) * *.Return 0 - если загрузочная запись считана * успешно; * 1 - произошла ошибка **/
#include <stdio.h> #include <dos.h> #include "sysp.h"
int getboot(BOOT *boot, int drive) {
union REGS reg; struct SREGS segreg;
// Заполняем регистровые структуры для вызова // прерывания DOS INT 25h
reg.x.ax = drive; reg.x.bx = FP_OFF(boot); segreg.ds = FP_SEG(boot); reg.x.cx = 1; reg.x.dx = 0; int86x(0x25, ®, ®, &segreg);
// Извлекаем из стека оставшееся там после // вызова прерывания слово
_asm pop ax
return(reg.x.cflag); }
Эта функция используется в следующей программе, показывающей содержимое загрузочной записи для указанного логического диска:
#include <stdio.h> #include <malloc.h> #include <dos.h> #include "sysp.h"
void main(void); void main(void) {
BOOT _far *boot_rec; int i, status; char drive;
printf("\n" "\nЧтение загрузочной записи логического диска" "\n (C)Фролов А., 1991" "\n");
// Заказываем буфер для чтения BOOT-записи. // Адрес буфера присваиваем FAR-указателю.
boot_rec = _fmalloc(sizeof(*boot_rec));
// Запрашиваем диск, для которого необходимо // выполнить чтение загрузочной записи.
printf("\n" "\nВведите обозначение диска, для просмотра" "\nзагрузочной записи (A, B, ...):");
drive = getche();
// Вычисляем номер дисковода
drive = toupper(drive) - 'A';
// Читаем загрузочную запись в буфер
status = getboot((BOOT _far*)boot_rec, drive);
// Если произошла ошибка (например, неправильно указано // обозначение диска), завершаем работу программы
if(status) { printf("\nОшибка при чтении BOOT-сектора"); exit(-1); }
printf("\nСодержимое BOOT-сектора для диска %c",drive+'A'); printf("\n" "\nOEM - название фирмы и версия DOS - ");
for(i=0;i<8;i++) printf("%c",boot_rec->oem[i]);
printf("\nНомер диска - %x" "\nПризнак расширенной BOOT-записи - %c" "\nСерийный номер диска - %04X-%04X" "\nМетка диска - ", (unsigned char)boot_rec->drive, boot_rec->signature, boot_rec->volser_hi, boot_rec->volser_lo);
for(i=0;i<11;i++) printf("%c",boot_rec->label[i]);
printf("\nФормат FAT - "); for(i=0;i<8;i++) printf("%c",boot_rec->fat_format[i]);
printf("\n\nИнформация из BPB:\n");
printf("\nКоличество байтов в секторе - %d" "\nКоличество секторов в кластере - %d" "\nЗарезервировано секторов - %d" "\nКоличество копий FAT - %d" "\nМакс. количество файлов в корневом каталоге - %d" "\ nОбщее количество секторов на диске - %d" "\nБайт-описатель среды - %x" "\nКоличество секторов в FAT - %d", boot_rec->bpb.sectsize, boot_rec->bpb.clustsize, boot_rec->bpb.ressecs, boot_rec->bpb.fatcnt, boot_rec->bpb.rootsize, boot_rec->bpb.totsecs, (unsigned char)boot_rec->bpb.media, boot_rec->bpb.fatsize);
printf("\n\nИнформация из расширения BPB:\n");
printf("\nСекторов на дорожке - %d" "\nКоличество головок - %d" "\nСкрытых секторов для диска < 32M - %d" "\nСкрытых секторов для диска >= 32M - %d" "\nВсего секторов на диске - %u", boot_rec->bpb.seccnt, boot_rec->bpb.headcnt, boot_rec->bpb.hiddensec_low, boot_rec->bpb.hiddensec_hi, boot_rec->bpb.totsecs);
// Освобождаем буфер
_ffree(boot_rec); }
Приведенная выше программа использует функции
_fmalloc() и _ffree() соответственно для заказа и освобождения массива памяти. В отличие от широко известных функций malloc() и free(), эти функции используют FAR-указатели на полученную и отдаваемую области памяти.
Запись буфера сектора (НМД)
На входе: | AH = 0Fh |
AL = Количество секторов, которые нужно записать | |
CH = Номер дорожки | |
CL = Номер сектора | |
DH = Номер головки | |
ES:BX = Адрес буфера для данных | |
DL = Адрес дисковода (0, 1, ..., 80h, 81h, ...) | |
На выходе: | AH = Состояние дисковода после завершения последней операции |
CF = 1, если произошла ошибка, 0, если ошибки нет |
|
Примечание: | PC, XT |
Функция полностью аналогична предыдущей, за исключением того, что происходит не чтение, а запись данных из оперативной памяти в буфер контроллера. Она может быть использована для инициализации содержимого буфера сектора перед форматированием диска функцией 05h прерывания INT 13h.
Запись сектора
На входе: | AH = 03h |
AL = Количество секторов, которые нужно прочитать | |
CH = Номер дорожки | |
CL = Номер сектора | |
DH = Номер головки | |
DL = Адрес дисковода (0, 1, ..., 80h, 81h, ...) | |
ES:BX = Адрес буфера для данных | |
На выходе: | AH = Состояние дисковода после завершения последней операции |
CF = 1, если произошла ошибка, 0, если ошибки нет |
|
Примечание: | PC, XT, AT, PS/2 |
Функция записи секторов аналогична предыдущей, за исключением направления перемещения данных - данные записываются из буфера в сектора диска. Необходимо отметить, что при работе с НГМД не всякий BIOS будет ожидать разгона двигателя до рабочей скорости перед выполнением операции записи. В результате программа может получить признак ошибки. Прежде чем делать вывод о причинах ошибки, следует сбросить контроллер НГМД функцией 00H и повторить операцию записи три раза.
Запись секторов длинная (НМД)
На входе: | AH = 0Bh |
AL = Количество секторов, которые нужно записать | |
CH = Номер дорожки | |
CL = Номер сектора | |
DH = Номер головки | |
DL = Адрес дисковода (0, 1, ..., 80h, 81h, ...) | |
ES:BX = Адрес буфера для данных | |
На выходе: | AH = Состояние дисковода после завершения последней операции |
CF = 1, если произошла ошибка, 0, если ошибки нет |
|
Примечание: | PC, XT, AT, PS/2 |
Функция "Запись секторов длинная" отличается от обычной функции записи (код 03h) тем, что она дополнительно записывает на диск из буфера данных 4 байта кода коррекции ошибки (ECC).
Защита дискет от копирования
Дискета, предназначенная для установки защищенного от копирования программного обеспечения должна быть сама защищена от копирования.
Копирование дискет можно выполнить как по файлам (утилитами операционной системы COPY и XCOPY), так и по секторам (утилитой DISKCOPY, программами PCTOOLS, PCSHELL и аналогичными). Кроме того, существуют программы, специально предназначенные для копирования дискет, защищенных от копирования, например COPY2PC. Специальные программы могут копировать дискеты, содержащие только определенные защищенные программные пакеты, например, DBASE, или они могут повторять структуру дорожек диска с точностью до бита.
Наиболее просто обеспечить защиту от программ копирования дискет по секторам. Можно предложить следующие достаточно простые способы, использующие нестандартное форматирование отдельных дорожек дискеты:
форматирование отдельных дорожек с размером сектора, отличного от стандартного для MS-DOS, например, с размером 128 байтов или 1024 байтов;
создание дорожек за пределами рабочей зоны диска, например, создание 41 дорожки для дискеты емкостью 360К или 81 дорожки для дискеты емкостью 1,2М;
создание большего, чем стандартное, количества секторов на дорожке;
форматирование отдельных дорожек с использованием фактора чередования секторов и с последующим анализом времени доступа к секторам для обычных стандартных дорожек и для нестандартных дорожек;
использование нестандартного символа заполнения при форматировании.
Очевидно, что все эти способы непригодны для защиты от таких программ копирования, которые способны копировать битовую структуру дорожек диска. Что можно порекомендовать в этом случае?
Можно использовать специальную аппаратуру при записи инсталляционных дискет, которая позволяет записывать отдельные дорожки или сектора как бы с промежуточным уровнем записи. Эти участки дорожки будут читаться нестабильно, каждый раз будут получаться новые значения битов. Если такая дискета будет скопирована на обычной аппаратуре (с использованием обычных дисководов и программ побитового копирования) то все дорожки будут читаться стабильно. Если при многократном контрольном чтении указанных секторов или дорожек каждый раз будут получены разные данные - мы имеем дело с оригиналом, в противном случае - с незаконной копией.
Однако дискеты с промежуточным уровнем записи все-таки могут быть скопированы с использованием специальной аппаратуры, копирующей содержимое дорожек "аналоговым" способом (как в бытовом магнитофоне).
Для защиты от аналогового копирования можно использовать дискеты, на которых в некоторых местах искусственно созданы дефекты магнитного покрытия - выжженные лазером небольшие точки или просто царапины.
Проверка основывается на том, что в дефектные места невозможно произвести запись информации. Если мы имеем дело с копией, то на месте дефектных секторов окажутся хорошие - копируется только информация, но не дефекты дискеты!
Разумеется, что можно использовать комбинации различных методов защиты от копирования. При этом легко распознаваемые методы (нестандартный размер сектора и т.п.) можно использовать для маскировки какого-либо другого, более тонкого метода защиты.
Более подробно мы остановимся на нестандартном форматировании, как на наиболее простом методе защиты от копирования, для использования которого не требуется ни специальной аппаратуры, ни специально подготовленных дискет с дефектами. Используя сведения о работе с диском на физическом уровне, приведенные в этой книге, вы сможете самостоятельно использовать метод дефектных дискет или дискет с промежуточным уровнем записи.
Самое простое, что можно сделать - изменить размер секторов на дорожке. Приведем простую программу, которая форматирует двадцатую дорожку диска, создавая на ней сектора размером 256 байтов. После форматирования программа записывает в первый сектор нестандартной дорожки строку, введенную с клавиатуры. Затем для контроля содержимое этого сектора считывается и отображается на экране. Обратите внимание на изменения в таблице параметров дискеты - они необходимы для использования нестандартного размера сектора.
#include <stdio.h> #include <conio.h> #include <dos.h> #include <stdlib.h> #include <bios.h> #include "sysp.h"
// Номер форматируемой дорожки
#define TRK 20
// Код размера сектора - 256 байт
#define SEC_SIZE 1
union REGS inregs, outregs; char _far diskbuf[512]; char _far diskbuf1[512]; char buf[80];
void main(void); void main(void) {
struct diskinfo_t di; unsigned status; unsigned char old_sec_size, old_fill_char, old_eot; int i, j; DPT _far *dpt_ptr;
printf("\nПрограмма уничтожит содержимое" "\n20-й дорожки диска А:." "\nЖелаете продолжить? (Y,N)\n");
// Ожидаем ответ оператора и анализируем его
i = getch(); if((i != 'y') && (i != 'Y')) exit(-1);
// Получаем адрес таблицы параметров дискеты
dpt_ptr = get_dpt();
// Сохраняем старые значения из таблицы параметров
old_sec_size = dpt_ptr->sec_size; old_fill_char = dpt_ptr->fill_char; old_eot = dpt_ptr->eot;
// Устанавливаем в таблице параметров дискеты // код размера сектора, символ заполнения при // форматировании, количество секторов на дорожке
dpt_ptr->sec_size = SEC_SIZE; dpt_ptr->fill_char = 0x77; dpt_ptr->eot = 15;
// Устанавливаем тип диска
inregs.h.ah = 0x17; inregs.h.al = 3; inregs.h.dl = 0; int86(0x13, &inregs, &outregs);
// Устанавливаем среду для форматирования
inregs.h.ah = 0x18; inregs.h.ch = TRK; inregs.h.cl = dpt_ptr->eot; inregs.h.dl = 0; int86(0x13, &inregs, &outregs);
// Подготавливаем параметры для функции форматирования
di.drive = 0; di.head = 0; di.track = TRK; di.sector = 1; di.nsectors = 15; di.buffer = diskbuf;
// Подготавливаем буфер формата для 15-ти секторов
for(i=0, j=1; j<16; i += 4, j++) { diskbuf[i] = TRK; diskbuf[i+1] = 0; diskbuf[i+2] = j; diskbuf[i+3] = SEC_SIZE; }
// Вызываем функцию форматирования дорожки
status = _bios_disk(_DISK_FORMAT, &di) >> 8; printf("\nФорматирование завершилось с кодом: %d",status);
// Записываем информацию в нестандартный сектор
printf("\nВведите строку для записи в нестандартный сектор," "\nдлина строки не должна превышать 80 байтов" "\n->");
gets(buf); strcpy(diskbuf,buf);
di.drive = 0; di.head = 0; di.track = 20; di.sector = 1; di.nsectors = 1; di.buffer = diskbuf;
status = _bios_disk(_DISK_WRITE, &di) >> 8;
if(status) { printf("\nОшибка при записи в нестандартный сектор: %d", status); exit(-1); }
di.drive = 0; di.head = 0; di.track = 20; di.sector = 1; di.nsectors = 1; di.buffer = diskbuf1;
for(i = 0; i < 3; i++) { status = _bios_disk(_DISK_READ, &di) >> 8; if( !status ) break; }
printf("\nПрочитано из нестандартного сектора:\n%s\n", diskbuf1);
// Восстанавливаем старые значения в // таблице параметров дискеты
dpt_ptr->sec_size = old_sec_size; dpt_ptr->fill_char = old_fill_char; dpt_ptr->eot = old_eot;
exit(0); }
Какую информацию можно записать в нестандартный сектор?
Если вы делаете инсталляционную (установочную) дискету, которая рассчитана на ограниченное количество инсталляций, нестандартный сектор - самое подходящее место для хранения счетчика инсталляций. Даже такие программы, как Norton Utilities
или Norton Disk Editor, не помогут прочитать или изменить значение этого счетчика. В этот же сектор можно записать и другую информацию, необходимую для правильной инсталляции защищенного программного обеспечения.
Другой пример - использование нестандартного номера дорожки. Программа форматирует дорожку (стандартным образом) с номером 81. Обычно считается, что дискеты могут содержать 40 или 80 дорожек, соответственно, с номерами 0...39 или 0...79, однако возможно использование и дорожек с большими номерами. Обычные программы копирования будут копировать только 40 или 80 дорожек, "не заметив" нашей лишней дорожки.
Этим мы и воспользуемся, записав на 81-ю дорожку контрольную информацию. Для разнообразия в примере используем функции общего управления вводом/выводом GENERIC IOCTL.
Первая программа предназначена для форматирования 81-й дорожки:
#include <dos.h> #include <stdio.h> #include <malloc.h> #include <errno.h> #include "sysp.h"
void main(void); void main(void) {
union REGS reg; struct SREGS segreg; DPB _far *dbp; DPB_FORMAT _far *dbp_f;
int sectors, i;
printf("\nПрограмма уничтожит содержимое" "\n81-й дорожки диска А:." "\nЖелаете продолжить? (Y,N)\n");
// Ожидаем ответ оператора и анализируем его
i = getch(); if((i != 'y') && (i != 'Y')) exit(-1);
// Заказываем память для блока параметров устройства
dbp = malloc(sizeof(DPB));
// Заказываем память для блока параметров устройства, // который будет использован для форматирования
dbp_f = malloc(sizeof(DPB_FORMAT));
if(dbp == NULL dbp_f == NULL) { printf("\nМало оперативной памяти!"); exit(-1); }
// Получаем текущие параметры диска А:
dbp->spec = 0;
// Вызываем подфункцию 0Dh для выполнения // операции чтения текущих параметров диска А:
reg.x.ax = 0x440d; reg.h.bl = 1; reg.x.cx = 0x0860; reg.x.dx = FP_OFF(dbp); segreg.ds = FP_SEG(dbp);
intdosx(®, ®, &segreg);
// Проверяем флаг переноса
if(reg.x.cflag != 0) { printf("\nОшибка: %d",reg.x.ax); exit(-1); }
// Заполняем блок парметров для форматирования.
dbp->spec = 5;
// Считываем из BPB количество секторов на дорожке
sectors = dbp->bpb.seccnt;
// Подготавливаем таблицу, описывающую формат дорожки
// Записываем количество секторов на дорожке
dbp->trkcnt = sectors;
// Для каждого сектора на дорожке в таблицу // записываем его номер и размер.
for(i = 0; i < sectors; i++) { dbp->trk[i].no = i+1; dbp->trk[i].size = 512; }
// Устанавливаем новые параметры для диска А:
reg.x.ax = 0x440d; reg.h.bl = 1; reg.x.cx = 0x0840; reg.x.dx = FP_OFF(dbp); segreg.ds = FP_SEG(dbp);
intdosx(®, ®, &segreg);
// Проверяем флаг переноса
if(reg.x.cflag != 0) { printf("\nОшибка: %d",reg.x.ax); exit(-1); }
// Подготавливаем блок параметров устройства, // который будет использован при вызове // операции проверки возможности форматирования // дорожки
dbp_f->spec = 1; dbp_f->head = 0; dbp_f->track = 81;
reg.x.ax = 0x440d; reg.h.bl = 1; reg.x.cx = 0x0842; reg.x.dx = FP_OFF(dbp_f); segreg.ds = FP_SEG(dbp_f);
intdosx(®, ®, &segreg);
// Проверяем флаг переноса
if(reg.x.cflag != 0) { printf("\nОшибка: %d",reg.x.ax); exit(-1); }
// Если указанный формат дорожки поддерживается, // поле специальных функций будет содержать 0. // Проверяем это.
if(dbp_f->spec != 0) { printf("\nФормат дорожки не поддерживается!"); exit(-1); }
// Заполняем блок параметров для выполнения // операции форматирования
dbp_f->spec = 0; dbp_f->head = 0; dbp_f->track = 81;
// Форматируем дорожку с номером 81, головка 0
reg.x.ax = 0x440d; reg.h.bl = 1; reg.x.cx = 0x0842; reg.x.dx = FP_OFF(dbp_f); segreg.ds = FP_SEG(dbp_f);
intdosx(®, ®, &segreg);
// Проверяем флаг переноса
if(reg.x.cflag != 0) { printf("\nОшибка: %d",reg.x.ax); exit(-1); }
// Освобождаем буфера
free(dbp); free(dbp_f);
exit(0); }
Для записи и последующего чтения информации на дополнительную дорожку можно использовать следующую программу:
#include <dos.h> #include <stdio.h> #include <malloc.h> #include <errno.h> #include "sysp.h"
void main(void); void main(void) {
union REGS reg; struct SREGS segreg; DPB_WR _far *dbp_wr; char buf[1000]; char buf1[80];
int sectors, i;
// Заказываем память для блока параметров устройства, // который будет использован для чтения/записи
dbp_wr = malloc(sizeof(DPB_WR));
if(dbp_wr == NULL) { printf("\nМало оперативной памяти!"); exit(-1); }
// Записываем информацию в нестандартный сектор
printf("\nВведите строку для записи в нестандартный сектор," "\nдлина строки не должна превышать 80 байтов" "\n->");
gets(buf1); strcpy(buf,buf1);
// Заполняем блок параметров для выполнения // операции записи.
dbp_wr->spec = 0; dbp_wr->head = 0; dbp_wr->track = 81; dbp_wr->sector = 0; dbp_wr->sectcnt = 1; dbp_wr->buffer = buf;
// Выполняем операцию записи
reg.x.ax = 0x440d; reg.h.bl = 1; reg.x.cx = 0x0841; reg.x.dx = FP_OFF(dbp_wr); segreg.ds = FP_SEG(dbp_wr);
intdosx(®, ®, &segreg);
// Проверяем флаг переноса
if(reg.x.cflag != 0) { printf("\nОшибка при записи: %d",reg.x.ax); exit(-1); }
// Заполняем блок параметров для выполнения // операции чтения.
dbp_wr->spec = 0; dbp_wr->head = 0; dbp_wr->track = 81; dbp_wr->sector = 0; dbp_wr->sectcnt = 1; dbp_wr->buffer = buf;
// Выполняем операцию чтения дорожки
reg.x.ax = 0x440d; reg.h.bl = 1; reg.x.cx = 0x0861; reg.x.dx = FP_OFF(dbp_wr); segreg.ds = FP_SEG(dbp_wr);
intdosx(®, ®, &segreg);
// Проверяем флаг переноса
if(reg.x.cflag != 0) { printf("\nОшибка при чтении: %d",reg.x.ax); exit(-1); }
printf("\nПрочитано из нестандартного сектора:\n%s\n", buf);
// Освобождаем буфер
free(dbp_wr);
exit(0); }
Более интересный способ защиты дискет от копирования связан с использованием при форматировании нестандартного чередования секторов на дорожке. В приведенной ниже программе использовано "обратное" расположение секторов - вначале идет сектор с номером 15, затем 14 и т.д.
#include <stdio.h> #include <conio.h> #include <dos.h> #include <stdlib.h> #include <bios.h> #include "sysp.h"
// Номер форматируемой дорожки
#define TRK 20
// Код размера сектора - 512 байт
#define SEC_SIZE 2
union REGS inregs, outregs; char _far diskbuf[512];
void main(void); void main(void) {
struct diskinfo_t di; unsigned status; unsigned char old_sec_size, old_fill_char, old_eot; int i, j; DPT _far *dpt_ptr;
printf("\nПрограмма уничтожит содержимое" "\n20-й дорожки диска А:." "\nЖелаете продолжить? (Y,N)\n");
// Ожидаем ответ оператора и анализируем его
i = getch(); if((i != 'y') && (i != 'Y')) exit(-1);
// Получаем адрес таблицы параметров дискеты
dpt_ptr = get_dpt();
// Сохраняем старые значения из таблицы параметров
old_sec_size = dpt_ptr->sec_size; old_fill_char = dpt_ptr->fill_char; old_eot = dpt_ptr->eot;
// Устанавливаем в таблице параметров дискеты // код размера сектора, символ заполнения при // форматировании, количество секторов на дорожке
dpt_ptr->sec_size = SEC_SIZE; dpt_ptr->fill_char = 0xf6; dpt_ptr->eot = 15;
// Устанавливаем тип диска
inregs.h.ah = 0x17; inregs.h.al = 3; inregs.h.dl = 0; int86(0x13, &inregs, &outregs);
// Устанавливаем среду для форматирования
inregs.h.ah = 0x18; inregs.h.ch = TRK; inregs.h.cl = dpt_ptr->eot; inregs.h.dl = 0; int86(0x13, &inregs, &outregs);
// Подготавливаем параметры для функции форматирования
di.drive = 0; di.head = 0; di.track = TRK; di.sector = 1; di.nsectors = 15; di.buffer = diskbuf;
// Подготавливаем буфер формата для 15-ти секторов // Используем обратный порядок расположения секторов // на дорожке
for(i=0, j=15; j>0; i += 4, j--) { diskbuf[i] = TRK; diskbuf[i+1] = 0; diskbuf[i+2] = j; diskbuf[i+3] = SEC_SIZE; }
// Вызываем функцию форматирования дорожки
status = _bios_disk(_DISK_FORMAT, &di) >> 8; printf("\nФорматирование завершилось с кодом: %d",status);
// Восстанавливаем старые значения в // таблице параметров дискеты
dpt_ptr->sec_size = old_sec_size; dpt_ptr->fill_char = old_fill_char; dpt_ptr->eot = old_eot;
exit(0); }
Для анализа используемого чередования секторов можно использовать следующую программу, которая пытается прочитать подряд два расположенных рядом сектора с номерами 1 и 2. Если сектора на дорожке чередуются обычным образом, то сектора с номерами 1 и 2 находятся рядом. Если же дорожка отформатирована приведенной выше программой, то эти сектора находятся на максимальном удалении друг от друга.
Программа анализирует время, необходимое на то, чтобы 50 раз подряд прочитать эти два сектора на двадцатой дорожке. Вначале используется головка 0 - это нестандартная дорожка, затем - головка 1, для которой раньше было выполнено стандартное форматирование.
На экран выводятся не только времена, но и их отношение, которое и может служить критерием при определении того, с чем мы имеем дело - с оригинальной дискетой или с ее копией.
#include <stdio.h> #include <conio.h> #include <bios.h> #include <dos.h> #include <stdlib.h> #include <time.h>
char _far diskbuf[1024];
void main(void);
void main(void) {
unsigned status = 0, i, j; struct diskinfo_t di; time_t start, end; float t1, t2;
// Читаем первый сектор дорожки для синхронизации таймера
di.drive = 0; di.head = 0; di.track = 20; di.sector = 1; di.nsectors = 1; di.buffer = diskbuf;
for(i = 0; i < 3; i++) { status = _bios_disk(_DISK_READ, &di) >> 8; if( !status ) break; }
// Отсчет времени начинаем сразу после чтения сектора, // это позволит компенсировать время, необходимое на разгон // мотора дисковода.
start = clock();
// Повторяем 50 раз чтение секторов с номерами 1 и 2
for(j=0; j<50; j++) { di.drive = 0; di.head = 0; di.track = 20; di.sector = 1; di.nsectors = 2; di.buffer = diskbuf;
for(i = 0; i < 3; i++) { status = _bios_disk(_DISK_READ, &di) >> 8; if( !status ) break; }
}
end = clock(); t1 = ((float)end - start) / CLK_TCK;
printf("Время для головки 0: %5.1f\n",t1);
// Выполняем аналогичную процедуру для дорожки, // которая была отформатирована обычным способом.
di.drive = 0; di.head = 1; di.track = 20; di.sector = 1; di.nsectors = 1; di.buffer = diskbuf;
for(i = 0; i < 3; i++) { status = _bios_disk(_DISK_READ, &di) >> 8; if( !status ) break; }
start = clock();
for(j=0; j<50; j++) { di.drive = 0; di.head = 1; di.track = 20; di.sector = 1; di.nsectors = 2; di.buffer = diskbuf;
for(i = 0; i < 3; i++) { status = _bios_disk(_DISK_READ, &di) >> 8; if( !status ) break; }
} end = clock();
t2 = ((float)end - start) / CLK_TCK;
printf("Время для головки 0: %5.1f\n",t2);
printf("\nОтношение времен чтения для головок 0 и 1: %5.1f", t1/t2);
}
Приведенный выше метод защиты дискет от копирования проверен на программе COPY2PC.
Оказалось, что эта программа не копирует чередование секторов на дорожке.
Защита информации от несанкционированного доступа
6.1.
6.2.
6.3.
6.4.
В этой главе мы рассмотрим некоторые способы организации защиты информации от несанкционированного копирования и несанкционированного доступа. Практически все эти способы используют специальные методы работы с дисками, поэтому мы и расположили данный материал в книге, посвященной дисковой подсистеме.
Прежде чем начинать проектирование системы защиты от несанкционированного доступа, нужно совершенно четко себе представлять, что именно и от кого вы собираетесь защищать. Это необходимо для правильного выбора средств защиты.
Не существует никаких "абсолютно надежных" методов защиты информации, гарантирующих полную невозможность получения несанкционированного доступа (НСД). Можно утверждать, что достаточно квалифицированные системные программисты, пользующиеся современными средствами анализа работы программного обеспечения (отладчики, дизассемблеры, перехватчики прерываний и т.д.), располагающие достаточным временем, смогут преодолеть практически любую защиту от НСД. Этому способствует "открытость" операционной системы MS-DOS, которая хорошо документирована и предоставляет любой программе доступ к любым программным и аппаратным ресурсам компьютера.
Поэтому при проектировании системы защиты от НСД следует исходить из предположения, что рано или поздно эта защита окажется снятой. Целью проектирования, по мнению авторов книги, должен быть выбор такого способа защиты, который обеспечит невозможность получения НСД для заранее определенного круга лиц и в течение ограниченного времени.
Например, если вы собираетесь защитить от копирования коммерческую версию вашей программы, вам необязательно защищать эту версию от копирования "навсегда" - стоимость такой защиты может превысить стоимость самой программы. Вполне достаточно, чтобы способ защиты было невозможно "разгадать" к моменту появления следующей версии вашей программы, так как в новой версии вы сможете изменить этот способ.
Короче говоря, уровень защиты должен быть таким, чтобы было бы выгоднее купить программу, а не заниматься снятием защиты от копирования.
Иногда защищать программы от копирования вообще нецелесообразно. Фирма Microsoft в основном не защищает от копирования свои программные продукты. Для привлечения покупателей она устанавливает низкие цены на свои изделия и обеспечивает сопровождение на высоком уровне. Новые версии программ продаются зарегистрированным пользователям со значительными скидками. Эти версии появляются достаточно быстро, поэтому имеет смысл покупать новые версии, а не копировать старые.
Таким образом, для выбора способа организации защиты от НСД и от копирования необходим индивидуальный подход в каждом конкретном случае.
Все средства защиты от НСД можно разделить на аппаратные и программные.
Аппаратные средства защиты могут быть реализованы в виде специальных модулей, устанавливаемых в слоты расширения материнской платы компьютера, либо подключаемых к последовательному порту ввода/вывода. Эти модули могут содержать однокристальные микро-ЭВМ или специальные заказные микросхемы, выполняющие обмен кодовыми последовательностями с программой. Можно также использовать специальные версии BIOS.
В нашей книге мы будем рассматривать только программные средства защиты от НСД, так как они доступны, не требуют специального оборудования и вместе с тем дают неплохие результаты.
Мы рассмотрим программные средства, которые блокируют доступ к компьютеру для всех посторонних лиц еще на этапе загрузки операционной системы, опишем способы защиты от копирования дискет, защиты от копирования программ, находящихся на жестком диске.
Кроме того, небольшой раздел этой главы посвящен вопросам защиты программ от отладки. Те участки программ, которые отвечают за защиту от НСД, желательно составлять таким образом, чтобы они работали по-разному в зависимости от того, выполняются они в реальном времени, или под управлением какого-либо отладчика, например, CodeView.
Приведенный в этой главе перечень способов защиты от НСД не претендует на полноту, мы продемонстрируем лишь основные приемы защиты от НСД и приведем некоторые программы. Вы сможете использовать их в таком виде, как они есть или в измененном вами виде. Проблема защиты информации от НСД неисчерпаема, приступая к ее решению, помните о вечной борьбе брони и снаряда.
Защита программ на жестком диске
Обычно процесс установки защищенного от копирования программного пакета выглядит следующим образом:
в дисковод вставляется инсталляционная (установочная) дискета, с этой дискеты запускается программа установки;
программа установки уменьшает на единицу счетчик выполненных инсталляций (этот счетчик может находится в нестандартном секторе или в каком-нибудь другом месте на дискете);
если количество инсталляций, выполненных с этой дискеты, равно максимально разрешенному, на экран выдается сообщение об этом и работа программы установки завершается;
если ресурс инсталляций еще не исчерпан, выполняется копирование файлов устанавливаемого программного пакета на жесткий диск и другие необходимые действия;
выполняется настройка программного пакета на параметры используемого компьютера.
Последний шаг необходим для того, чтобы защищенный от копирования программный пакет стало невозможно перенести на другой компьютер, используя программы копирования файлов или разгрузки дисков на дискеты или магнитные ленты с последующим восстановлением на жестком диске другого компьютера.
В этом разделе книги мы приведем несколько методов настройки программного обеспечения на конкретный компьютер:
привязка файлов программного пакета к их физическому расположению на диске (метод основан на том, что при восстановлении файлов на другом компьютере они будут располагаться в других секторах диска);
запись в неиспользуемый участок последнего кластера, распределенного файлу, контрольной информации (при выгрузке и восстановлении файлов эти неиспользуемые участки файлов пропадают);
привязка программы к конкретной версии BIOS, при этом используется дата трансляции BIOS и метод контрольных сумм;
проверка производительности отдельных подсистем компьютера.
Первый способ предполагает исследование расположения какого-либо достаточно длинного файла, записанного на диск в процессе установки на предмет определения его расположения на диске.
Программа установки, пользуясь таблицей размещения файлов FAT, определяет список кластеров, распределенных файлу и записывает этот список в конец защищаемого файла или в отдельный файл. Можно использовать, например, файл конфигурации, предназначенный для хранения текущих параметров программного пакета. Список кластеров можно зашифровать, сложив его, например, с использованием логики "ИСКЛЮЧАЮЩЕЕ ИЛИ", с каким-либо числом.
После запуска программный пакет определяет расположение защищенного файла на диске и сравнивает его с записанным при установке. Если расположение изменилось - запущена незаконная копия программного пакета.
Какие недостатки у этого способа?
Прежде всего, невозможна оптимизация диска такими программами, которые могут изменить расположение файлов на диске, например, Norton Speed Disk.
Второй недостаток связан с тем, что можно взять вторую машину с таким же типом жесткого диска, и с помощью несложной программы переписать содержимое всех секторов с диска одной машины на диск другой.
Первый недостаток можно преодолеть, используя процедуру "деинсталляции", или съема программного пакета с диска компьютера. Эта процедура заключается в том, что с жесткого диска удаляются файлы программного продукта, после чего счетчик инсталляций на дистрибутивной дискете уменьшается на единицу. После выполнения всех необходимых операций с диском можно выполнить повторную установку программного пакета.
Таким образом, можно переносить программный пакет с одной машины на другую, но нельзя его размножить на несколько компьютеров.
Второй недостаток устранить сложнее, так как при идентичности структуры дисков расположение файлов на нем будет идентично и с этим ничего нельзя сделать.
Запись контрольной информации в неиспользуемый участок файла сделает невозможным копирование программного пакета утилитами разгрузки дисков, но по-прежнему остается возможность использования программ копирования по секторам содержимого диска.
Метод проверки версии или контрольных сумм программы BIOS пригоден для защиты от копирования между компьютерами, содержащими разные версии BIOS. Однако при покупке партии компьютеров все они почти наверняка будут иметь одинаковый BIOS, поэтому, хотя этот метод достаточно прост, его эффективность относительно невысока.
Последний метод - проверка производительности отдельных подсистем компьютера - кажется нам наиболее пригодным, так как такие характеристики, как быстродействие диска или процессора являются в достаточной степени уникальными для каждого конкретного компьютера.
Мы приведем несколько примеров программ, демонстрирующих использование некоторых из перечисленных выше методов.
Сначала покажем, как получить список кластеров, распределенных файлу. Вы уже знаете, как получить этот список, пользуясь таблицей размещения файлов и дескриптором файла в каталоге. Сложность здесь заключается в том, что операционная система не предоставляет никакого документированного способа получения номера первого кластера, распределенного файлу. Вам придется последовательно просматривать дерево каталогов до тех пор, пока вы не доберетесь до вашего файла. Для просмотра дерева каталогов вам придется использовать непосредственное чтение диска и таблицу размещения файлов. Лишь найдя нужный вам каталог (содержащий файл, для которого нужно получить список кластеров) и прочитав каталог как файл в память, вы сможете воспользоваться дескриптором файла для определения номера первого кластера, распределенного файлу.
К сожалению, непосредственное чтение диска - единственная документированная возможность получения списка кластеров, распределенных файлу.
Мы рассмотрим более простой, но, увы, недокументированный способ получения списка кластеров, использующий таблицу открытых файлов. Эта таблица была описана в разделах книги, посвященных векторной таблице связи.
Напомним, что для каждого открытого файла эта таблица содержит, кроме всего прочего, номер первого кластера, распределенного файлу, и номер кластера файла, к которому только что выполнялось обращение - last_clu.
Идея заключается в том, чтобы открыв файл и найдя его в таблице файлов, последовательно считывать его порциями, равными по размеру одному кластеру. В процессе считывания файла поле, содержащее номер кластера last_clu будет последовательно принимать значения, равные номерам всех распределенных файлу кластеров.
Размер кластера можно получить из блока параметров BIOS BPB, который находится в загрузочной записи диска.
Приведем пример программы, которая проделывает все это и выводит на экран содержимое таблицы файлов и список кластеров для файла, путь которого передается программе в качестве параметра:
#include <dos.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <io.h> #include "sysp.h"
void main(int argc, char *argv[]); void show(DFCB far *);
void main(int argc, char *argv[]) {
CVT far *cvt; DFT far *dft; unsigned i,j,k; DFCB far *dfcb, far *file_dfcb; int handle, flag, disk;
BOOT far *boot_rec; int status; char *buf;
char drive[_MAX_DRIVE], dir[_MAX_DIR]; char fname[_MAX_FNAME], ext[_MAX_EXT]; char name[12];
printf("Информация о расположении файла\n" "Copyright Frolov A. (C),1990\n");
printf("Исследуем расположение файла '%s'\n", argv[1]);
// Открываем файл, для которого будем получать список кластеров
handle = open(argv[1], O_BINARY); if(handle == 0) { printf("Ошибка при открытии файла!\n"); exit(-1); }
// Разбиваем путь файла на составляющие компоненты: // - буква диска; // - каталог; // - имя файла; // - расширение имени
_splitpath(argv[1], drive, dir, fname, ext);
// Комбинируем строку из имени и расширения
strcpy(name,fname); for(i=0; i<8; i++) { if(name[i] == 0) break; } for(; i<8; i++) name[i] = ' '; name[8] = 0; strcat(name, &ext[1]);
// Преобразуем строку имени в заглавные буквы
strupr(name);
// Вычисляем номер дисковода
drive[0] = toupper(drive[0]); disk = drive[0] - 'A';
printf("%s - %s - %s - %s : %s\n", drive, dir, fname, ext, name);
cvt=get_mcvt(); // Адрес векторной таблицы связи dft=get_fdft(cvt); // Адрес начала таблицы файлов
// Сбрасываем флаг поиска файла
flag = 0;
for(;;) { if(dft == (DDCB far *)0) break; // Конец таблицы файлов
i=dft->file_count;
for(j=0;j<i;j++) { // Цикл по файловым управляющим блокам
dfcb=(&(dft->dfcb))+j; // Адрес DFCB файла
// Ищем файл в таблице открытых файлов
k = memcmp(name,dfcb->filename,11); if(k == 0) {
printf("Файл найден!\n"); printf("\nDFCB файла: %Fp\n\n",dfcb);
// Запоминаем адрес таблицы для найденного файла
file_dfcb = dfcb;
// Показываем содержимое таблицы для найденного файла
show(file_dfcb); flag = 1; break; }
} dft=get_ndft(dft);
if(flag == 1) break; }
if(flag == 0) { printf("Файл не найден"); close(handle); exit(-1); }
// Заказываем буфер для чтения BOOT-записи. // Адрес буфера присваиваем FAR-указателю.
boot_rec = malloc(sizeof(*boot_rec));
// Читаем загрузочную запись в буфер
status = getboot((BOOT far*)boot_rec, disk);
// Вычисляем размер кластера в байтах
i = boot_rec->bpb.clustsize * boot_rec->bpb.sectsize; printf("\nРазмер кластера, байтов : %d",i);
// Если произошла ошибка (например, неправильно указано // обозначение диска), завершаем работу программы
if(status) { printf("\nОшибка при чтении BOOT-сектора"); close(handle); exit(-1); }
buf = malloc(i);
printf("\nСписок кластеров файла:\n");
// Читаем файл по кластерам, выводим номер // последнего прочитанного кластера, который // берем из таблицы файлов
for(;;) {
read(handle, buf, i); if(eof(handle)) break; printf("%d ",file_dfcb->last_clu);
}
close(handle); free(boot_rec); free(buf); exit(0); }
// Функция для отображения содержимого таблицы файлов
void show(DFCB far *dfcb) {
int k;
printf("Имя файла: "); for(k=0;k<11;k++) { putchar(dfcb->filename[k]); }
printf("\nКоличество file handles: %d\n" "Режим доступа: %d\n" "Поле reserv1: %04X\n" "Информация об устройстве: %04X\n" "Адрес драйвера: %Fp\n" "Начальный кластер: %d\n" "Время: %04X\n" "Дата: %04X\n" "Размер файла в байтах: %ld\n" "Текущее смещение в файле: %ld\n" "Поле reserv2: %04X\n" "Последний прочитанный кластер: %d\n" "Сегмент PSP владельца файла: %04X\n" "Поле reserv7: %d\n" "--------------------------------------\n\n", dfcb->handl_num, dfcb->access_mode, dfcb->reserv1, dfcb->dev_info, dfcb->driver, dfcb->first_clu, dfcb->time, dfcb->date, dfcb->fl_size, dfcb->offset, dfcb->reserv2, dfcb->last_clu, dfcb->ownr_psp, dfcb->reserv7); }
Получив список кластеров, распределенных защищаемому файлу, вы можете зашифровать его и записать, например, в конец защищаемого файла. Впоследствии, перед началом работы, защищенная программа может проверить свое расположение на диске и сравнить его с записанным в зашифрованном списке.
Рассмотрим теперь использование BIOS для защиты от копирования программ с жесткого диска.
Для защиты используем поля, уникальные для конкретной версии BIOS. Это дата изготовления и код типа компьютера. Вся эта информация находится в BIOS, начиная с адреса F000:FFF5 и имеет следующий формат:
F000:FFF5 (8) | дата изготовления BIOSBIOS; |
F000:FFFC (2) | не используется; |
F000:FFFE (1) | код типа компьютера. |
Для удобства работы с идентификатором BIOS BIOSфайл sysp.h содержит определение типа BIOS_ID:
#pragma pack(1)
typedef struct _BIOS_ID_ {
char date[8]; unsigned reserve; char pc_type;
} BIOS_ID; #pragma pack()
Мы подготовили функцию, которая возвращает адрес идентификатора BIOSBIOS:
/** *.Name getbiosi * *.Title Получить адрес идентификатора BIOS * *.Descr Функция возвращает адрес идентификатора BIOS, * имеющего следующую структуру: * * typedef struct _BIOS_ID_ { * * char date[8]; * unsigned reserve; * char pc_type; * * } BIOS_ID; * * В этой структуре: * * date - дата изготовления BIOS; * reserve - зарезервировано; * pc_type - код типа компьютера. * *.Params Нет * *.Return Адрес идентификатора BIOS **/
#include <stdio.h> #include "sysp.h"
BIOS_ID _far *getbiosi(void) {
return((BIOS_ID _far *)(FP_MAKE(0xf000, 0xfff5))); }
В качестве примера приведем текст программы, отображающей на экране содержимое идентификатора BIOS:
#include <stdio.h> #include <stdlib.h> #include "sysp.h"
void main(void);
void main(void) {
BIOS_ID _far *id; int i;
id = getbiosi();
printf("\nИдентификатор BIOS:" "\n" "\nДата изготовления: ");
for(i=0; i<8; i++) { putch(id->date[i]); }
printf("\nРезервное поле: %04.4X" "\nТип компьютера: %02.2X" "\n", id->reserve, (unsigned char)id->pc_type);
exit(0); }
Защита программ от трассировки
Защищая свои программы от несанкционированного копирования, не следует забывать о таких средствах "взлома", как пошаговые отладчики - CodeView, Advanced Fullscreen Debug, AT86 и другие.
Используя отладчики, искушенные в своем деле системные программисты рано или поздно смогут обнаружить и отключить (или обмануть) средства защиты от копирования.
В этом разделе книги мы расскажем о некоторых приемах, позволяющих затруднить обнаружение средств защиты.
Стандартным методом, использующимся для обнаружения средств защиты от копирования, является дизассемблирование программы установки программного пакета или выполнение его под управлением пошагового отладчика. Листинг, получаемый в процессе дизассемблирования, оказывает большую услугу при использовании отладчика, поэтому эти два средства - дизассемблирование и использование отладчика - обычно используются вместе.
Соответственно, требуются отдельные средства для борьбы с дизассемблером и для защиты от отладчиков.
Для затруднения дизассемблирования лучше всего подходит шифрование отдельных участков программ или всей программы целиком. Например, часть программы-инсталлятора можно оформить в виде отдельной COM-программы. После трансляции исходного текста этой программы ее можно зашифровать тем или иным способом, и в зашифрованном виде подгружать в память как программный оверлей. После загрузки программу следует расшифровать в оперативной памяти и передать ей управление.
Еще лучше выполнять динамическое расшифровывание программы по мере ее выполнения, когда участки программы расшифровываются непосредственно перед использованием и после использования сразу же уничтожаются.
При расшифровывании можно копировать участки программы в другое место оперативной памяти. Пусть, например, программа состоит из нескольких частей. После загрузки ее в оперативную память управление передается первой части программы. Эта часть предназначена для расшифровки второй части, располагающейся в памяти вслед за первой.
Задача второй части - перемещение третьей части программы на место уже использованной первой части и расшифровка ее там.
Третья часть, получив управление, может проверить свое расположение относительно префикса программного сегмента и, в случае правильного расположения (сразу вслед за PSP), начать загрузку сегментных регистров такими значениями, которые необходимы для выполнения четвертой - инсталляционной - части программы.
Если попытаться дизассемблировать программу, составленную подобным образом, то из этого ничего не получится.
Второй способ борьбы с дизассемблером является, по своей сути, борьбой с человеком, занимающимся дизассемблированием. Он заключается в увеличении размера загрузочного модуля программы до сотни-другой килобайтов и в усложнении структуры программы.
Объем листинга, получающегося при дизассемблировании программы размером в 30-40 килобайтов, достигает 1-1,5 мегабайтов. Поэтому большие размеры инсталляционной программы могут сильно увеличить время обнаружения средств защиты.
Что такое усложнение структуры программы, достаточно понятно само по себе. Авторам известна программа, использующая для обращении к одной и той же области памяти, содержащей многочисленные переменные, разные сегментные адреса. Поэтому очень трудно сразу догадаться, что на самом деле программа работает с одной и той же областью памяти.
Мы не будем дальше рассказывать о способах усложнения структуры программы, оставив простор для фантазии читателя. Вместо этого перейдем к борьбе с трассировкой программы пошаговыми отладчиками.
Можно предложить две группы средств защиты от трассировки. В первую группу входят средства блокировки работы самих отладчиков, делающие невозможным трассировку программы. Вторая группа средств направлена на определение факта работы программы под управлением отладчика.
К первой группе средств относится:
блокировка специальных "отладочных" прерываний процессора;
блокировка прерываний от клавиатуры;
замер времени выполнения контрольных участков программы;
использование прерывания таймера.
Напомним, что прерывание INT 1 и INT 3
(соответственно, прерывание для пошаговой работы и прерывание по однобайтовой команде INT) интенсивно используются отладчиками. Первое прерывание позволяет отладчику получать управление после выполнения каждой команды трассируемой программы. С помощью второго прерывания отладчик может устанавливать точки останова, заменяя байты программы на команду однобайтового прерывания.
Защищенная от трассировки программа должна подготовить свои собственные обработчики для этих прерываний и "незаметно" для человека, занимающегося трассировкой, записать их адреса в таблицу векторов прерываний.
Эти обработчики прерываний могут не делать ничего, т.е. состоять из одной команды IRET, или выполнять какие-либо действия, фиксирующие факт работы программы под контролем отладчика.
В ходе трассировки программы (при ее пошаговом выполнении) вам необходимо нажимать на клавиши - для перехода к очередной команде. Это можно использовать для блокировки отладчика.
Запретив прерывания командой CLI и переназначив клавиатурное прерывание на себя, программа установки может выполнять какие-либо действия, не требующие работы оператора, производящего установку, с клавиатурой. Обработчик клавиатурного прерывания защищенной от трассировки программы должен фиксировать прерывания, например, установкой флага.
Если программа работает не под контролем отладчика, прерывания во время этого участка программы невозможны (они запрещены командой CLI).
Если же используется режим пошагового выполнения программы под управлением отладчика, отладчик разрешает клавиатурные прерывания, невзирая на то, что была выдана команда CLI. Наш обработчик клавиатурного прерывания в этом случае зафиксирует работу в режиме трассировки.
Аналогично можно воспользоваться прерыванием таймера.
Защищенная программа устанавливает свой обработчик для прерывания таймера, который взводит флаг в случае прихода прерывания от таймера. В подходящий момент времени она запрещает прерывания и выполняет какую-либо работу, периодически проверяя флаг.
Если программа работает в пошаговом режиме, команда запрета прерываний CLI не работает, и флаг будет взведен, сигнализируя работу под контролем отладчика.
Последний метод, который мы рассмотрим, основан на использовании архитектурной особенности процессоров 8086, 80286, 80386 и 80486 - наличии конвейера команд.
Для увеличения производительности во всех этих процессорах используется предварительная выборка команд во внутреннюю очередь команд, располагающуюся физически на кристалле процессора.
Метод заключается в модификации команды программы, находящейся вблизи от той команды, которая выполняет эту модификацию.
Если программа работает не под управлением отладчика, к моменту выполнения команды модификации байт модифицируемой команды уже находится во внутренней очереди команд. Следовательно, никакой модификации не произойдет.
Если же программа работает под управлением отладчика, то изменение байта команды будет выполнено правильно, так как очередь команд будет заполнена командами самого отладчика.
Приведем программу, демонстрирующую описанный выше метод:
; Эта программа демонстрирует способ защиты от ; отладки, основанный на использовании архитектурной ; особенности процессоров 8086, 80286, 80386, 80486 - ; наличии внутреннего конвейера команд
TITLE BUG
.MODEL tiny
.STACK 100h
.DATA
msg DB "Программа работает без отладчика!", 13, 10, "$"
.CODE .STARTUP
; Запрещаем прерывания
cli
; Вызываем программу проверки
call _TEST
; Разрешаем прерывания
sti
.EXIT 0
; -------------------------------
; Программа проверки
_TEST PROC near
; Выполняем модификацию команды с меткой next. ; Вместо команды ret записываем команду nop.
mov BYTE PTR next, 90h next: ret
; Если модификация не произошла, это означает, что ; программа выполняется под управлением отладчика. ; Выводим сообщение о работе под контролем отладчика.
mov ah, 9h mov dx, OFFSET msg int 21h
ret
_TEST ENDP
END
Эта программа выводит сообщение о работе без отладчика, если происходит изменение команды, и не выводит ничего, если изменения команды не происходит (т.е. если программа работает под управлением отладчика).
Метод был проверен для отладчиков Code View, AFD, AT86, утилиты отладки MS-DOS DEBUG и дал положительные результаты.