|
Программа InFilBuf.s, представленная в листинге В.8, являет собой пример произвольного файлового ввода-вывода. Она допускает, что файл состоит из произвольного числа символьных строк, каждая из которых потенциально может отличаться по длине от других. Сначала эта программа считывает файл и формирует таблицу, в которой запись п отражает положение начала строки п в файле. Впоследствии можно запросить конкретную символьную строку, отыскать запись для нее в таблице и считать с помощью системных вызовов 1 seek и read. Имя файла при стандартном вводе указывается в первой вводимой строке. Программа состоит из нескольких относительно независимых кодовых фрагментов, которые можно адаптировать для иных целей.
Листинг В.8. Программа, реализующая буферизованный и произвольный доступ к файлу
#include "../syscalnr.h" ! 1
bufsiz =512 ! 2
.SECT .TEXT ! 3
infbufst: ! 4
MOV BP.SP ! 5
MOV Dl.linein ! 6
PUSH _GETCHAR ! 7
1: SYS ! 8
CMPB AL, *\n* ! 9
JL 9f ! 10
JE If ! 11
STOSB ! 12
JMP lb ! 13
1: PUSH 0 ! 14
PUSH linein ! 15
PUSH _0PEN ! 16
SYS ! 17
CMP AX.O ! 18
JL 9f ! 19
MOV (fiIdes),AX ! 20
MOV SI.linh+2 ! 21
MOV BX.O ! 22
1: CALL fillbuf ! 23
CMP CX.O ! 24
JLE 3f ! 25
2: MOVB AL,'\гГ ! 26
REPNE SCASB ! 27
JNE lb ! 28
INC (count) ! 29
MOV AX.BX ! 30
|
SUB AX.CX
|
! 31
|
|
XCHG SI.DI
|
! 32
|
|
STOS
|
! 33
|
|
XCHG SI.DI
|
! 34
|
|
CMP CX.O
|
! 35
|
|
JNE 2b
|
! 36
|
|
JMP lb
|
! 37
|
|
9: MOV SP.BP
|
! 38
|
|
PUSH linein
|
! 39
|
|
PUSH errmess
|
! 40
|
|
PUSH PRINTF
|
! 41
|
|
SYS
|
! 42
|
|
PUSH EXIT
|
! 43
|
|
PUSH EXIT
|
! 44
|
|
SYS
|
! 45
|
|
3: CALL getnum
|
! 46
|
|
CMP AX.O
|
! 47
|
|
JLE 8f
|
! 48
|
|
MOV BX,(curlin)
|
! 49
|
|
CMP BX.O
|
! 50
|
|
JLE 7f
|
! 51
|
|
CMP BX,(count)
|
! 52
|
|
JG 7f
|
! 53
|
|
SHL BX.l
|
! 54
|
|
MOV AX,linh-2(BX)
|
! 55
|
|
MOV CX.linh(BX)
|
! 56
|
|
PUSH 0
|
! 57
|
|
PUSH 0
|
! 58
|
|
PUSH AX
|
! 59
|
|
PUSH (fildes)
|
! 60
|
|
PUSH LSEEK
|
! 61
|
|
SYS
|
! 62
|
|
SUB CX.AX
|
! 63
|
|
PUSH CX
|
! 64
|
|
PUSH buf
|
! 65
|
|
PUSH (fildes)
|
! 66
|
|
PUSH READ
|
! 67
|
|
SYS
|
! 68
|
|
ADD SP.4
|
! 69
|
|
PUSH 1
|
! 70
|
|
PUSH WRITE
|
! 71
|
|
SYS
|
! 72
|
|
ADD SP.14
|
! 73
|
|
JMP 3b
|
! 74
|
|
8: PUSH scanerr
|
! 75
|
|
PUSH PRINTF
|
! 76
|
|
SYS
|
! 77
|
|
ADD SP.4
|
! 78
|
|
JMP 3b
|
! 79
|
|
7: PUSH 0
|
! 80
|
|
PUSH EXIT
|
! 81
|
|
SYS
|
! 82
|
|
fillbuf:
|
! 83
|
|
PUSH bufsiz
|
! 84
|
|
PUSH buf
|
! 85
|
|
PUSH (fildes)
|
! 86
|
|
PUSH READ
|
! 87
|
|
SYS
|
! 88
|
|
ADD SP.8
|
! 89
|
|
MOV CX.AX
|
! 90
|
|
ADD BX.CX
|
! 91
|
|
MOV Dl.buf
|
! 92
|
|
RET
|
! 93
|
|
getnum:
|
! 94
|
|
MOV Dl.linein
|
! 95
|
|
PUSH GETCHAR
|
! 96
|
|
1: SYS
|
! 97
|
|
CMPB AL,'\n'
|
! 98
|
|
JL 9b
|
! 99
|
|
JE If
|
! 100
|
|
STOSB
|
! 101
|
|
JMP lb
|
! 102
|
|
1: MOVB (DI).1 '
|
! 103
|
|
PUSH curl in
|
! 104
|
|
PUSH numfmt
|
! 105
|
|
PUSH linein
|
! 106
|
|
PUSH SSCANF
|
! 107
|
|
SYS
|
! 108
|
|
ADD SP.10
|
! 109
|
|
RET
|
! 110
|
|
.SECT .DATA
|
! Ill
|
|
errmess:
|
! 112
|
|
.ASCIZ "Open %s failed\n"
|
! 113
|
|
numfmt: .ASCIZ
|
! 114
|
|
scanerr:
|
! 115
|
|
.ASCIZ "Type a number.\n"
|
! 116
|
|
.ALIGN2
|
! 117
|
|
.SECT .BSS
|
! 118
|
|
linein: .SPACE 80
|
! 119
|
|
fildes: .SPACE2
|
! 120
|
|
linh: .SPACE 8192
|
! 121
|
|
curl in: .SPACE4
|
! 122
|
|
buf: .SPACE bufsiz+2
|
! 123
|
|
count: .SPACE2
|
! 124
|
В первых пяти строках кода определяются номера системных вызовов и размер буфера, а указатель базы, как обычно, настраивается на вершину стека. В строках 6-13 из стандартного ввода считывается имя файла, а затем оно сохраняется в символьной строке с меткой linein. Если имя файла не закрыто новой строкой, выводится сообщение об ошибке, а процесс заканчивается с ненулевым состоянием. Все эти действия отражены в строках 38-45. Обратите внимание, что адрес имени файла помещается в стек в строке 39, адрес сообщения об ошибке-в строке 40. Само сообщение об ошибке (представленное в строке ИЗ) представляет собой запрос на строку $s в формате PRINTF. Здесь же выполняется вставка содержимого строки linein.
В случае успешного копирования имени файла его открытие производится в строках 14-20. Если вызов open завершается с ошибкой, возвращаемое значение становится отрицательным, и для отображения сообщения об ошибке производится переход к метке 9 в строке 28. Если вызов проходит успешно, возвращаемым значением является дескриптор файла, который сохраняется в переменной fildes. Этот дескриптор понадобится при последующем выполнении вызовов read и 1 seek.
Далее файл считывается блоками по 512 байт, каждый из которых сохраняется в буфере buf. На самом деле под буфер выделяется на два байта больше необходимого объема (512 байт), но сделано это лишь для того, чтобы показать способ размещения символической константы и целого числа в одном выражении (строка 123). Аналогичным образом, в строке 21 в регистр SI загружается адрес следующего элемента массива linh, в результате на дне массива остается машинное слово с нулевым значением. Регистр ВХ получает адрес файла первого непрочитанного символа файла, а значит, в строке 22 перед первым наполнением буфера он инициализируется нулем.
За наполнение буфера отвечает подпрограмма fі 11 buf, размещенная в строках 83-93. После введения в стек аргументов read делается запрос на системный вызов, который помещает число фактически считанных символов в регистр АХ. Это число копируется в СХ, и впоследствии по значению этого регистра можно будет узнать число оставшихся в буфере символов. Положение в файле первого непрочитанного символа хранится в регистре ВХ, и в строке 91 значение СХ прибавляется к значению ВХ. В строке 92 дно буфера помещается в DI; таким образом, осуществляется подготовка к просмотру буфера на предмет следующего символа новой строки.
После возврата из fill buf в строке 24 проводится проверка того, были ли какие-либо символы фактически считаны. При отрицательном ответе происходит переход из цикла чтения с буферизацией ко второй части программы в строке 25.
После этого начинается просмотр буфера. Символ \п загружается в регистр AL в строке 26, а уже в следующей строке это значение просматривается в цикле REP SCASB и сравнивается с буферизованными символами. Выход из цикла может произойти в двух случаях: когда в регистре СХ оказывается нулевое значение или когда просматриваемый символ оказывается символом новой строки. При установленном нулевом флаге последним просмотренным символом оказывается \п, а положения в файле текущего символа (расположенного после перевода строки) сохраняется в массиве linh. Далее происходит приращение счетчика, а положение в файле определяется по значению ВХ и числу символов, оставшихся в СХ (строки 29-31 кода). В строках 32-34 выполняется сохранение, но так как для команды ST0S целевым адресом является не регистр SI, а регистр DI, перед и после вызова команды ST0S эти регистры меняются местами. В строках 35-37 производится проверка оставшихся в буфере данных, после чего в зависимости от значения СХ выполняется переход.
По достижении конца файла в нашем распоряжении оказывается полный список положений в файле начальных элементов строк. Так как массив 1 і nh начинается с нулевого слова, нам известно, что первая строка начинается с адреса О, следующая строка находится в положении linh + 2, и т. д. Размер строки п можно вычислить путем вычитания начального адреса строки п из начального адреса строки n + 1.
В оставшейся части программы считывается номер символьной строки, эта строка передается в буфер, после чего она выводится при помощи вызова write. Все необходимые для выполнения этих операций данные имеются в массиве linh, где каждая n-ная запись содержит начальное положение строки п в файле. Если номер запрошенной символьной строки равен нулю или выходит за пределы допустимого диапазона, программа завершается путем перехода к метке 7.
Эта часть программы начинается с вызова подпрограммы getnum (строка 46 кода). Она считывает символьную строку из стандартного ввода и сохраняет в буфере linein (строки 95-103 кода). Далее подготавливается вызов SSCANF. Принимая во внимание обратный порядок следования аргументов, в стек сначала помещается адрес буфера curl in, где можно разместить целочисленное значение, затем адрес форматной строки для целочисленного представления numfmt, и, наконец, адрес буфера linein, в котором содержится число в десятичном представлении. Если это возможно, системная подпрограмма SSCANF помещает двоичное значение в curl in. В случае ошибки она возвращает нулевое значение АХ. Проверка возвращаемого значения проводится в строке 48; при ошибке программа генерирует сообщение об ошибке посредством метки 8.
Если подпрограмма getnum возвращает действительное целочисленное значение в curl in, оно сначала копируется в ВХ. Далее это значение проверяется на предмет принадлежности к допустимому диапазону (строки 49-53 кода). Если обнаруживается, что номер строки выходит за пределы этого диапазона, происходит выход (EXIT).
Далее необходимо определить конечное положение данной строки в файле и число считываемых файлов; с этой целью значение ВХ умножается на 2 посредством сдвига влево (SHL). В строке 55 положение текущей символьной строки в файле копируется в регистр АХ. Затем положение следующей символьной строки помещается в регистр СХ, и на его основе вычисляется число байтов в текущей строке.
Для произвольного чтения данных из файла применяется вызов 1 seek; он устанавливает смещение к байту, который предполагается прочесть в следующую очередь. Подпрограмма 1 seek выполняется относительно начала файла, и в этой связи в строке 57 в стек помещается нулевой аргумент. Следующим аргументом является смещение в файле. По определению этот аргумент является длинным (32-разрядным) целым, поэтому сначала в стек вводится пулевое слово, а затем - значение АХ (строки 58 и 59 кода); таким образом формируется 32-разрядное целочисленное значение. Далее в строке 62 в стек отправляются дескриптор файла и код LSEEK, а также выполняется вызов. Возвращаемое значение LSEEK определяет текущее положение в файле, а найти его можно в комбинации регистров DX : АХ. Если число умещается в рамки машинного слова (а размер файла не превышает 65 536 байт, по-другому быть и не может), адрес помещается в АХ; следовательно, если вычесть значение этого регистра из СХ (строка 63), мы получим число байтов, которые необходимо прочесть для помещения строки в буфер.
Все остальное очень просто. В строках 64-68 строка считывается из файла, а затем при помощи дескриптора 1 файла в строках 70-72 она записывается в файл стандартного вывода. Следует иметь в виду, что после частичной очистки стека, которая выполняется в строке 69, значения счетчика и буфера в нем остаются. Наконец, в строке 73, указатель стека сбрасывается полностью, после чего осуществляются обратный переход к метке 3 и очередной вызов getnum.
⇐Таблицы диспетчеризации || Оглавление || Вопросы и задания-l⇒
|