Лекции по Вычислительным машинам, системам и сетям   

5. Программирование на языке Ассемблер

5.17. Работа с гибкими дисками

Любой диск разбит на дорожки, а дорожки, в свою очередь, разбиты на сектора. Стандартный формат сектора – 512 байт. Сектора имеют нумерацию, при этом сектор имеет физический и логический номера. Физическая нумерация секторов более сложная и ее рассматривать не будем. При логической нумерации каждому сектору присваивается номер: 0 (сектор 0), 1 (сектор 1), …

На любом диске в секторе 0 располагается «загрузочная запись» (boot record), далее (в секторах 1, 2, …) располагаются две копии «таблицы размещения файлов» FAT (file allocate table), затем идет «корневой каталог» (root dir) и наконец собственно информация (файлы). В лабораторной работе предполагается работа с FAT и корневым каталогом дискеты, формата 1,44 Мбайта.

Помимо понятия сектор, с дисками связано еще одно понятие – «кластер». Кластер – это минимальная часть диска, которая выделяется на данном диске под запись одного файла. Один кластер занимает на диске несколько смежных секторов, например, для дискеты 1,44 Мбайта, кластер занимает два смежных сектора. То есть, если создали файл размером 1 байт и записали его на нашу дискету, то этот файл займет один кластер (1024 байта на дискете). И если есть файл размером 1000 байт, то он тоже займет на дискете один кластер. А если файл имеет размер 9,3 Кбайта? Тогда этот файл на дискете расположится в десяти кластерах, причем необязательно подряд идущих. Именно для описания таких файлов, расположенных «не в подряд идущих» кластерах, и создается FAT.

В FAT для каждого записанного на диск файла, содержится описание «цепочки кластеров», отведенных дл этого файла. Каждый кластер имеет номер, причем нумерация кластеров начинается не с нуля, а с 2 (2, 3, 4, …). FAT состоит из элементов, в первые два из которых (элементы 0 и 1) записана служебная информация, а каждый следующий элемент FAT содержит описание одного соответствующего кластера (элемент 2 описывает кластер 2, …). Если, например, в элементе 2 записано число 5, то продолжение файла находится в кластере 5. Таким образом, если наш файл располагается на дискете в кластерах 2, 3, 7 и 12 (кластер 2 – начало файла, кластер 12 – конец файла), в FAT будет записана следующая информация:

- элемент 2 – 3;

- элемент 3 – 7;

- элемент 7 – 12;

- элемент 12 – информация о том, что это последний кластер данного файла.

Для гибких дисков используется FAT, каждый элемент которой имеет формат 12 бит (FAT12). Сделано это для того, чтобы FAT занимала на дискете как можно меньше места. Работа с нестандартными 1,5-байтными элементами создает для программиста трудности. Для дискеты 1,44 Майта первая копия FAT располагается в секторах 1 – 9, вторая копия FAT – в секторах 10 – 18. Структура FAT для дискеты 1,44 Мбайта приведена на рис. 5.9. Здесь:

 

 ‑ служебная информация;

 

 ‑ четный элемент FAT;

 

 ‑ нечетный элемент FAT.

байт

 
 
 
 
   
 
 
   
 

……….

 
   
 

Рис. 5.9

В общем случае элемент FAT (описание кластера) может содержать следующую информацию:

000h ‑ кластер свободен;

002hff0h ‑ кластер занят, а записанное в нем число задает следующий кластер цепочки;

ff1hff7h ‑ кластер испорчен (плохой);

ff8hfffh ‑ кластер занят и является последним кластером цепочки.

Алгоритмы работы с FAT будут приведены ниже.

Корневой каталог для дискеты 1,44 Мбайта занимает сектора 19‑33 и содержит описания всех файлов и поддиректорий, расположенных в корневой директории. Один элемент каталога занимает 32 байта и имеет структуру, приведенную в табл. 5.1.

                                                                     Таблица 5.1

№ байта (байтов)

Описание

0 – 7

Имя файла в ASCII-кодах (большими буквами!!). Если в байте 0 записан 0, это означает,  что данный элемент каталога свободен и никогда не использовался. Соответственно и все последующие элементы каталога тоже будут свободны. Если в байте 0 записано 0e5h – это означает, что данный файл с диска удален. Примечание: данные сведения приведены для DOS, для Windows действуют иные правила, но здесь они не рассматриваются.

8 – 10

Расширение файла (большими буквами!!).

11

Атрибуты файла (формат смотри ниже)

12 – 21

Не используются

22 –23

Время создания (формат смотри ниже)

24 –25

Дата создания (формат смотри ниже)

26 –27

Номер начального кластера цепочки

28 –31

Размер файла в байтах

Формат байта атрибутов следующий:

- бит 0  =  1 ‑ файл только для чтения;

- бит 1  =  1 ‑ скрытый файл;

- бит 2  =  1 ‑ системный файл;

- бит 3  =  1 ‑ это не файл, а метка тома;

- бит 4  =  1 ‑ это поддиректория;

- бит 5  =  1 ‑ архивный файл.

Формат времени создания следующий:

hhhhhmmmmmmsssss (байт 23: hhhhhmmm, байт 22: mmmsssss) – здесь: h – часы, m – минуты, s – пары секунд.

Формат даты создания следующий:

yyyyyyymmmmddddd (байт 25: yyyyyyym, байт 24: mmmddddd) – здесь: yгод, вычисляемый по формуле y = текущий год – 1980, m – месяц, dдень.

Например, в 23 байте элемента каталога записано 85h, в 22 байте – 0eh, в 25 байте – 2ch и в 24 байте – 55h. Тогда:

850eh = 1000010100001110b = 16 ч 40 мин 14*2 с.

2c55h = 0010110001010101b = (22+1980) = 2002 год 2 месяц (февраль) 21 число.

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

Для работы с дисками можно использовать прерывания DOS int 25h и int 26h (имеются и другие).

Прерывание int 25h считывает с указанного диска, начиная с заданного сектора, требуемое количество секторов и помещает эту информацию в созданный в программе буфер. Прерывание int 26h записывает информацию из буфера в заданные сектора на указанном диске.

Входные данные для этих прерываний одинаковы.

В dx ‑ номер начального сектора, в cx ‑ количество считываемых секторов, в ds:bx ‑ начальный адрес буфера, в al ‑ номер дисковода (0 – А, 1 – В, 2 – С,…). Прерывания int 25h и int 26h имеют особенность: возврат из обработчиков этих прерываний производится командой ret (не iret). В результате получается «неправильная вершина стека». Иногда, хотя и редко, это приводит к неправильной работе программы. Найти же в этом случае причину неправильной работы исключительно трудно. Поэтому проще сразу исключить эту причину, поставив сразу за командой int 25h (int 26h) команду выталкивания из стека (pop) в какой-нибудь ненужный регистр (фиктивное выталкивание). Таким образом, если, допустим, надо прочитать содержимое корневого каталога дискеты 1,44 Мбайта, то можно использовать такой фрагмент:

buf db 512*15 dup (0)                 ;создали буфер 15

                                                    ;секторов ;по 512 байт

.

mov dx, 19

mov cx, 15

mov bx, offset buf

mov al, 0

int 25h

pop cx.

После того как содержимое корневого каталога считано в буфер, можно работать с этим буфером, например, так:

- устанавливаем si (или di или…) на начало буфера;

- проверяем содержимое байта, расположенного по этому адресу;

- если в этом байте 0, заканчиваем проверку (больше в корневом каталоге ничего нет!) и уходим на пункт 6 (скорее всего на выход или на вывод какой-либо информации на экран);

- если в этом байте e5h – это удаленный файл, прибавляем к si (или di или…) 32 (увеличиваем адрес на 32, переходя тем самым на следующий элемент корневого каталога) и идем на пункт 2;

- если в байте не 0 и не e5h, значит это описание какого-то файла или поддиректории или метка тома. Производим необходимые действия, прибавляем к si (или di или…) 32 и идем на пункт 2.

Примечание: Этот алгоритм рассчитан на DOS. Длинные имена файлов и удаленные файлы Windows могут приводить к неправильным результатам. Самый простой способ: взять чистую дискету или отформатировать дискету под DOS и записать на нее только файлы с короткими именами.

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

Для того чтобы вывести на экран в привычном человеку десятичном виде какое-либо двоичное число, надо разбить это число на отдельные десятичные цифры и перевести каждую цифру в ASCII-код. Разбиение на отдельные десятичные цифры удобно организовать с помощью операции деления (в какой бы системе счисления не делили число на 10, в остатке получим младшую десятичную цифру этого числа). Например, 123/10 = частное 12, остаток 3; 12/10 = частное 1, остаток 2, т.е. разбили число 123 на отдельные десятичные числа.

Перевести десятичную цифру в ее ASCII-код очень просто, достаточно прибавит к этой цифре 30h (ASCII-код цифры 0). Пусть, например, в результате работы программы в регистре al получено количество файлов в корневом каталоге. Следующий фрагмент выведет это число на экран в десятичном виде (максимальное число в регистре al255, т.е. не более трех десятичных цифр):

strok db ‘Всего файлов  = ’, 0, 0, 0, 0ah, 0dh,’$’        

;это заготовка под выводимую строку, вместо нулей ;подставим ASCII-коды наших трех цифр, 0ah и 0dh ;переведут курсор (после вывода) в начало следующей ;строки экрана, а «доллар в апострофах» задает конец ;выводимой строки.

.

;разбиваем на десятичные цифры и переводим их в ASCII - код

mov si, offset strok      ; в si начальный адрес заготовки

mov cl, 10

mov ah, 0

div cl           ; делим ax на cl, остаток в ah,  частное в al

add ah, ‘0’           ; в ah младшая цифра, переводим ее в

;ASCII- код

mov [si + 16], ah      ; и отправляем ее в заготовку на

;место правого нуля

mov ah, 0

div cl

add ah, ‘0’                ; в ah средняя цифра, переводим ее в

;ASCII- код

mov [si + 15], ah      ; и отправляем ее в заготовку на

;место среднего нуля

add al, ‘0’                 ; в al старшая цифра, переводим ее в

;ASCII- код

mov [si + 14], al       ; и отправляем ее в заготовку на ;место левого нуля

; выводим строку на экран

mov ah, 9

mov dx, offset strok

int 21h

Здесь программист должен быть внимателен, так как команда деления может приводить к прерыванию по ошибке деления (см. описание команды div). В приведенном выше фрагменте возникновение такого прерывания исключено.

Иногда надо выводить число на экран в шестнадцатеричном виде. Разбить двоичное число на отдельные шестнадцатеричные цифры просто достаточно разбить это число на отдельные тетрады. Для перевода шестнадцатеричной цифры в ASCII-код можно, например, исследовать эту цифру и, если она находится в диапазоне от 0 до 9 включительно, прибавить к ней ASCII-код цифры 0, в противном случае, прибавить ASCII-код буквы А (латинской) минус 10. Пусть надо вывести на экран в шестнадцатеричной форме число из регистра al:

strok db ‘Всего файлов  = ’, 0, 0, 'h', 0ah, 0dh,’$’  

;это заготовка под выводимую строку, вместо нулей

;подставим ASCII – коды наших двух цифр, 0ah и 0dh ;переведут курсор (после вывода) в начало следующей ;строки экрана, а «доллар в апострофах» задает конец ;выводимой строки.

.

.

mov si, offset strok

; разбиваем на шестнадцатеричные цифры и переводим ;их в ASCII-код

mov ah, al       ; сохраняем дубликат нашего числа

shr al, 4           ; выделяем старшую тетраду (цифру), ;сдвигая число вправо на 4 разряда

cmp al, 9

ja m1

add al, ‘0’

jmp m2

m1:     add al, ‘A’-10

m2:    mov [si + 14], al      ; отправляем ASCII‑код на \

;место левого нуля заготовки

and ah, 0fh      ; выделяем младшую тетраду (цифру)

cmp ah, 9

ja m3

add ah, ‘0’

jmp m4

m3:     add ah, ‘A’-10

m4:    mov [si + 15], ah     ; отправляем ASCII-код на

;место правого нуля заготовки

mov ah, 9

mov dx, offset strok

int 21h

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

1. Считать в буфер корневой каталог.

2. Найти в нем элемент, описывающий искомый файл. Из этого элемента взять номер начального кластера цепочки.

3. Считать в буфер (обычно другой!) FAT.

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

5. Повторять п. 5, пока не дойдем до последнего кластера цепочки.

Основное неудобство возникает при выполнении п. 5 и связано оно с тем, что формат элемента FAT (1,5 байта) плохо согласуется с форматами регистров процессора.

Пусть на диске есть какой-то довольно большой файл, занимающий кластеры 2, 3, 4, 5, … Описание цепочки кластеров в FAT для данного файла показано на рис. 5.10. Здесь во втором элементе FAT записано 003, в третьем – 004,

Если же посмотреть на эту же информацию, допустим, в отладчике, то увидим следующую картину (жирным выделено содержимое четных элементов FAT):

хх хх хх 03 40 00 05 60 00 …..

Появляется вопрос, а как по известному номеру кластера N найти в FAT соответствующий этому кластеру элемент?

байт

 
 
 

0

3

4

0

0

0

0

5

6

0

0

0

………

Рис. 5.10

Если N ‑ четный, то смещение элемента относительно начала FAT можно вычислить по формуле:

смещение элемента = N+N/2.

При нечетном N формула слегка меняется:

смещение элемента = N+ (N‑1)/2.

Отсюда можно воспользоваться, например, таким фрагментом:

; пусть N находится в регистре bp

mov si, bp       ; дубликат N в si

mov di, bp       ; дубликат N в di

mov bx, offset buf        ; в bx начальный адрес буфера, в

;который считана FAT

and bp, 1         ; если получился ноль, N ‑ четный 

jnz nechet

; четный N

shr di, 1      ; делим (сдвигом вправо на разряд) N

;пополам

add si, di              ; получаем смещение элемента в si

mov ax, [bx+si]   ; элемент в ax

and ax, 0fffh        ; убираем старшую тетраду, ;принадлежащую нечетному элементу, формируя в ax ;содержимое искомого элемента

jmp m1

nechet:

; нечетный N

dec di         ; N-1

shr di, 1      ; (N-1)/2

add si, di

mov ax, [bx+si]

shr ax, 4          ; убираем младшую тетраду, ;принадлежащую четному  элементу, формируя в ax ;содержимое искомого элемента           

m1:                     ; …………….

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

code segment

assume cs: code, ds: code

org 100h

start:

jmp begin

buf db 512*15 dup (0)

mess1 db 0ah, 0dh,’$’       ; для перевода курсора в начало ;новой строки экрана

nomber db 0

mess2 db ‘Нет файлов, начинающихся с t’, 0ah, 0dh, ‘ $’

mess3 db ‘ Для выхода  нажмите любую клавишу$’

begin:

; считываем корневой каталог в буфер

mov dx, 19

mov cx ,15

mov bx, offset buf

mov al, 0

int 25h

pop bx

; настраиваем si на начало буфера

mov si, offset buf

m2:

mov al, [si]      ; читаем первый байт очередного элемента ;каталога

cmp al, 0         ; файлов больше нет?

je exit1            ; да, выходим

cmp al, 0e5h   ; удаленный файл?

jne m1             ; нет, проверяем дальше

add si, 32

jmp m2            ; раз удален, переходим к следующему ;элементу каталога

m1:

cmp al, ‘T’      ; начинается с Т?

je m3               ; да, идем на вывод названия на экран

add si, 32

jmp m2            ; нет, переходим к следующему элементу ;каталога

m3:

; наращиваем счетчик

inc nomber

; выводим название файла на экран, начиная с текущей ;позиции курсора

mov cx, 11           ;число повторений цикла (8 – название, ;3 – расширение)

mov ah, 0eh         ; функция

m4:

mov al, [si]      ; в al  заносим ASCII-код очередного ;выводимого символа

int 10h             ; выводим символ, курсор сам сдвигается ;на позицию вправо

inc si               ; теперь si адресует следующий выводимый символ

loop m4   

; переводим курсор в начало следующей строки экрана

mov ah, 9

mov dx, offset mess1

int 21h

add si, 21        ; 11 мы уже прибавили к si в цикле при ;выводе названия файла

jmp m2            ; переходим к исследованию следующего ;элемента каталога

exit1:

cmp nomber, 0

jne exit            ; были файлы, начинающиеся с Т

;Выводим сообщение, что искомых файлов не было

mov ah, 9

mov dx, offset mess2

int 21h

exit:

; выводим сообщение, с просьбой нажать любую ;клавишу     

mov ah, 9

mov dx, offset mess3

int 21h

mov ah, 7

int 21h                ; ждем, когда клавишу нажмут

mov ah, 4ch

int 21h

code ends

end start



*****

© 2009-2017 Банк лекций siblec.ru
Лекции для преподавателей и студентов. Формальные, технические, естественные, общественные, гуманитарные, и другие науки.