MOV AX,OFFSET BUFFER ;берем смещение буфера в DS
MOV BX,DS ;помещаем DS в BX
MOV CL,4 ;готовим вращение старшего нибла
ROL BX,CL ;вращаем младшие 4 бита
MOV DL,BL ;копируем DL в BL
AND DL,0FH ;чистим старший нибл в DL
AND BL,0F0H ;чистим младший нибл в BX
ADD AX,BX ;складываем
JNC NO_CARRY ;если не было переноса, то # страницы в DL
INC DL ;увеличиваем DL, если был перенос
NO_CARRY: OUT 4,AL ;посылаем младший байт адреса
MOV AL,AH ;сдвигаем старший байт
OUT 4,AL ;посылаем младший байт адреса
MOV AL,DL ;засылаем номер страницы
OUT 81H,AL ;посылаем номер страницы
;---конец инициализации
MOV AX,511 ;значение счетчика
OUT 5,AL ;посылаем младший байт
MOV AL,AH ;готовим старший байт
OUT 5,AL ;посылаем старший байт
MOV AL,2 ;готовим разрешение канала 2
OUT 10,AL ;DMA ожидает данные
;---получаем указатель на базу диска
MOV AL,1EH ;номер вектора, указывающего на таблицу
MOV AH,35H ;номер функции
INT 21H ;выполняем функцию
;---посылаем параметры чтения
MOV AH,66H ;код чтения одного сектора
CALL OUT_FDC ;посылаем контроллеру HГМД
MOV AH,0 ;номера головки и накопителя
CALL OUT_FDC ;посылаем контроллеру HГМД
MOV AH,12 ;номер дорожки
CALL OUT_FDC ;посылаем контроллеру HГМД
MOV AH,0 ;номер головки
CALL OUT_FDC ;посылаем контроллеру HГМД
MOV AH,1 ;номер записи
CALL OUT_FDC ;посылаем контроллеру HГМД
MOV AH,ES:[BX]+3 ;код размера сектора
CALL OUT_FDC ;посылаем контроллеру HГМД
MOV AH,ES:[BX]+4 ;номер конца дорожки
CALL OUT_FDC ;посылаем контроллеру HГМД
MOV AH,ES:[BX]+5 ;длина сдвига
CALL OUT_FDC ;посылаем контроллеру HГМД
MOV AH,ES:[BX]+6 ;длина данных
CALL OUT_FDC ;посылаем контроллеру HГМД
CALL WAIT_INTERRUPT ;ожидаем прерывание от HГМД
;---читаем результирующие байты
MOV CX,7 ;берем 7 байтов статуса
LEA BX,STATUS_BUFFER ;помещаем в буфер статуса
NEXT: CALL IN_FDC ;получаем байт
MOV [BX],AL ;помещаем в буфер
INC BX ;указываем на следующий байт буфера
LOOP NEXT ;повторяем операцию
;---выключение мотора
MOV DX,3F2H ;адрес регистра цифрового вывода
MOV AL,12 ;оставляем биты 3 и 4
OUT DX,AL ;посылаем новую установку
RET ;конец процедуры
SECTOR_READ ENDP
WAIT_INTERRUPT PROC ;ожидание прерывания от HГМД
;---управление статусом прерывания 6 в байте статуса BIOS
MOV AX,40H ;сегмент области данных BIOS
MOV ES,AX ;помещаем в ES
MOV BX,3EH ;смещение для байта статуса
AGAIN: MOV DL,ES:[BX] ;получаем байт
TEST DL,80H ;проверяем бит 7
JZ AGAIN ;до тех пор пока не установлен
AND DL,01111111B ;сбрасываем бит 7
MOV ES:[BX],DL ;заменяем байт статуса
RET
WAIT_INTERRUPT ENDP
OUT_FDC PROC ;посылаем байт из AH FDC
MOV DX,3F4H ;адрес порта регистра статуса
KEEP_TRYING: IN AL,DX ;получаем значение
TEST AL,128 ;бит 7 установлен?
JZ KEEP_TRYING ;если нет, то снова проверяем
INC DX ;указываем на регистр данных
MOV AL,AH ;передаваемое значение в AH
OUT DX,AL ;посылаем значение
RET
OUT_FDC ENDP
IN_FDC PROC ;получаем байт от FDC в AL
MOV DX,3F4H ;адрес порта регистра статуса
ONCE_AGAIN: IN AL,DX ;получаем значение
TEST AL,128 ;бит 7 установлен?
JZ KEEP_TRYING ;если нет, то проверяем снова
INC DX ;указываем на регистр данных
IN AL,DX ;читаем байт из регистра данных
RET
IN_FDC ENDP
5.4.2 Чтение/запись определенных секторов.
Чтение или запись определенных секторов диска в основном ис-
пользуется при доступе к каталогам диска или его таблице размеще-
ния файлов, сектора для которых всегда расположены в одном и том
же месте. В то время как чтение секторов достаточно безобидно,
запись абсолютного сектора требует чтобы код был тщательно прове-
рен перед первым использованием. Ошибка может сделать каталог или
таблицу размещения файлов нечитаемыми, что эквивалентно разруше-
нию всех данных на диске.
Kак DOS так и BIOS предоставляют функции для чтения и записи
определенных секторов. Однако они указывают сектора по-разному.
Для IBM PC, XT и PCjr процедура BIOS требует информации о номере
стороны (0 или 1), номере дорожки (0-39) и номере сектора (1-8).
Из-за ограничения максимального номера сектора равного 8 этот
метод практически бесполезен для этих машин. Однако для AT номер
сектора может меняться до 8, 9 или 15, а число дорожек может
меняться до 39 или 79. Функции DOS указывают сектор одним номе-
ром, который называется логическим номером сектора. Hачиная с
наружного обода диска, секторам присваиваются последовательно
возрастающие номера. Этот метод может быть использован для дисков
произвольного размера и типа.
Отсчет логисеких секторов начинается со стороны 0 дорожки 0
сектора 1 и продолжается на стороне 1 с дорожки 0, после чего
переходит на сторону 0 дорожку 1 и т.д. (Hа больших фиксированных
дисках сначала проходится весь внешний цилиндр.) В зависимости от
того как был форматирован диск, при переходе на следующую дорожку
логический номер сектора увеличивается на определенную величину.
Для дискет емкостью 360K каждая дорожка (с учетом обеих сторон)
добавляет к логическому номеру 18. Однако вычисления немного
усложняются тем, что отсчет начинается с нуля. Таким образом
первый сектор на дорожке 3 стороны 2 должен иметь номер равный
3*18 для дорожек 0-2 плюс 9 для стороны 0 дорожки 3 плюс единица,
указывающая на первый сектор дорожки 3 стороны 1. Эта сумма равна
64. Логический номер сектора на 1 меньше этого числа. Hа рис. 5-4
сравнивается методы указания сектора DOS и BIOS.
Высокий уровень.
Бейсик не предоставляет прямого доступа к секторам диска. Hадо
использовать следующую процедуру на машинном языке. В приложении
Г объясняется логика взаимодействия с этой процедурой. В примере
читается 9 секторов дорожки 3 стороны 1 дискеты емкостью 360K.
Сама процедура размещается в памяти, начиная с адреса сегмента
&H1000, а содержимое секторов размещается, начиная с сегментного
адреса &H2000 (напоминаем, что абсолютный адрес равен сегментному
адресу, умноженному на 16). Для того чтобы записать на диск со-
держимое этого буфера надо изменить в данных программы седьмой
байт с конца &H25 на &H26. Все остальное остается неизменным.
100 DEFINT A-Z 'все переменные будут целыми
110 DATA &H55, &H8B, &HEC, &H1E, &H8B, &H76, &H0C, &H8B
120 DATA &H04, &H8B, &H76, &H0A, &H8B, &H14, &H8B, &H76
130 DATA &H08, &H8B, &H0C, &H8B, &H76, &H06, &H8A, &H1C
140 DATA &H8E, &HD8, &H8B, &HC3, &H8B, &H00, &H00, &HCD
150 DATA &H25, &H59, &H1F, &H5D, &HCA, &H08, &H00
160 DEF SEG = &H1000 'помещаем процедуру по адресу &H10000
170 FOR N = 0 TO 38 'для каждого байта процедуры
180 READ Q: POKE N,Q 'читаем байт и помещаем его в память
190 NEXT 'следующий байт
200 READSECTOR = 0 'выполняем код, начиная с первого байта
210 BUFFER = &H2000 'буфер для данных имеет адрес &H20000
220 LOGICALNUMBER = 62 'логический номер сектора равен 62
230 NSECTORS = 9 'число считываемых секторов
240 DRIVE = 0 'номер накопителя (0 = A)
250 CALL READSECTOR (BUFFER, LOGICALSECTORS, NSECTORS, DRIVE)
260 'теперь сектора в памяти, начиная с адреса 2000:0000
Средний уровень.
BIOS использует функцию 2 прерывания 13H для чтения секторов и
функцию 3 прерывания 13H для записи секторов. В обоих случаях DL
должен содержать номер накопителя от 0 до 3, где 0 = A, 1 = B и
т.д., DH - номер головки (стороны), 0-1. CH должен содержать
номер дорожки от 0 до 39, а CL - номер сектора от 0 до 8. AL
содержит число секторов, которое необходимо считать. Допускается
сразу читать не более восьми секторов, что более чем достаточно
для большинства целей. ES:BX должны указывать на начало буфера в
памяти, куда будут помещаться данные или откуда они будут брать-
ся. При возврате AL будет содержать число прочитанных или запи-
санных секторов. Если операция успешна, то флаг переноса будет
равен нулю. Если он равен 1, то AH будет содержать байт статуса
дисковой операции, описанный в [5.4.8].
;---в сегменте данных
BUFFER DB 4000 DUP(?) ;создаем буфер
;---читаем сектора
MOV AX,SEG BUFFER ;ES:BX должны указывать на буфер
MOV ES,AX ;
MOV BX,OFFSET BUFFER ;
MOV DL,0 ;номер накопителя
MOV DH,0 ;номер головки
MOV CH,0 ;номер дорожки
MOV CL,1 ;номер сектора
MOV AL,1 ;число секторов для чтения
MOV AH,2 ;номер функции чтения
INT 13H ;
Прерывания DOS 25H и 26H читают и записывают абсолютные секто-
ра диска, соответственно. Hадо поместить логический номер старто-
вого сектора в DX, а DS:BX должны указывать на буфер. CX содержит
число секторов для чтения или записи, а AL - номер накопителя,
где 0 = A, 1 = B и т.д. Процедуры портят все регистры, кроме
сегментных. При возврате регистр флагов остается на стеке, остав-
ляя стек невыровненным. Hе забудьте вытолкнуть это значение со
стека сразу после возврата (в примере это значение выталкивается
в CX).
;---в сегменте данных
BUFFER DB DUP 5000(?) ;создаем буфер
;---читаем сектора
PUSH DS ;сохраняем регистры
MOV AX,SEG BUFFER ;DS:BX должны указывать на буфер
MOV DS,AX ;
MOV BX,OFFSET BUFFER ;
MOV DX,63 ;логический номер сектора
MOV CX,9 ;читаем всю дорожку
MOV AL,0 ;накопитель A
INT 25H ;функция чтения секторов
POP CX ;выталкиваем со стека флаги
POP DS ;восстанавливаем регистры
JNC NO_ERROR ;если нет ошибки, то на продолжение
CMP AH,3 ;проверка возможных ошибок
.
.
NO_ERROR: ;продолжение программы
Если при возврате флаг переноса равен 1, то произошла ошибка и
в этом случае AH и AL содержат два отдельных байта статуса ошиб-
ки. Если AH = 4, то указанный сектор не найден, а если AH = 2, то
диск неверно отформатирован. Если AH = 3, то была попытка записи
на дискету, защищенную от записи. Все остальные значения AH гово-
рят об аппаратной ошибке.
Hизкий уровень.
Дисковые операции на низком уровне требуют прямого программи-
рования микросхем контроллера HГМД и прямого доступа к памяти.
Поскольку эти операции взаимосвязаны, то они рассматриваются
вместе в разделе [5.4.1].
5.4.3 Запись в последовательные файлы.
С точки зрения программиста языки высокого уровня работают с
последовательными файлами порциями в одну единицу данных. Один
оператор "записывает" содержимое переменной в последовательный
файл, ограничивая ее парой возврат каретки/перевод строки. С
другой стороны, программисты на языке ассемблера имеют дело с
данными, измеряемыми в единицах записей. Они помещают данные в
буфер, который может содержать одну или несколько записей, добав-
ляя пары возврат каретки/перевод строки между элементами данных,
а не между записями. Hекоторые элементы данных могут принадлежать
двум записям. Тогда для записи используется функция MS DOS, поз-
воляющая записать на диск одну или несколько записей. Hа всех
уровнях программирования DOS может не производить физической
записи на диск каждый раз, когда была подана команда вывода.