Жанр: Учеба

Ассемблер и программирование для IBM PC

сторінка №21

.20 (размер записи) к FCBFLSZ (размер файла). Так
как каждая запись имеет длину 32 байта, то операция заносит в буфер 16
записей и затем записывает весь буфер в сектор диска. Ниже показано
содержимое DTA и буфера:

DTA: |текущая запись|
Буфер: |запись 00|запись 01|запись 02|...|запись 15|

Если пользователь ввел 25 имен, то счетчик записей увеличится от 1 до
25 (шест.19). Размер файла составит:

25 * 32 байта = 800 байтов или шест. 320

Операция закрытия заносит во второй сектор оставшиеся в буфере девять
записей и изменяет в оглавлении дату и размер файла. Размер записывается
байтами в переставленном порядке: 20030000. Последний буфер имеет
следующий вид:

Буфер: |запись 16|запись 17|...|запись 24|шест.1A|...|...|

Для простоты в приведенной программе создаются записи файла,
содержащие только одно поле. Записи большинства других файлов, однако,
содержит различные символьные и двоичные поля и требуют описания записи в
DTA. Если записи содержат двоичные числа, то не следует использовать
маркер конца файла (EOF), так как двоичное число может совпасть с шест.
кодом 1A.
Для того, чтобы сделать программу более гибкой, можно разрешить
пользователю указать дисковод, на котором находится или будет находиться
файл. В начале выполнения программа может выдать на экран сообщение, чтобы
пользователь ввел номер дисковода, а затем изменить первый байт блока FCB.

ПОСЛЕДОВАТЕЛЬНОЕ ЧТЕНИЕ ДИСКОВОГО ФАЙЛА
________________________________________________________________

В базовой версии DOS программа, читающая дисковый файл, содержит блок
управления файлом, который определяет файл точно так, как он был создан. В
начале программа для открытия файла использует функцию шест. OF:

MOV AH,OFH ;Открытие
LEA DX,FCBname ; файла
INT 21H ;Вызов DOS

Операция открытия начинается с поиска в оглавлении элемента с именем
и типом файла, определенными в FCB. Если такой элемент не будет найден в
оглавлении, то в регистре AL устанавливается шест.FF. Если элемент найден,
то в регистре AL устанавливается 00 и в FCB заносится действительный
размер файла, а также устанавливается номер текущего блока в 0, длина
записи в шест.80. После открытия можно заменить длину записи на другое
значение.
DTA должно содержать определение считываемой записи в соответствии с
форматом, который использовался при создании файла. Для установки адреса
DTA используется функция шест.1A (не путать с маркером конца файла EOF
шест.1A) аналогично созданию дискового файла:

MOV AH,1AH ;Установка
LEA DX,DTAname ; адреса DTA
INT 21H ;Вызов DOS

Для последовательного чтения записей с диска используется функция
шест.14:

MOV AH,14H ;Последовательное
LEA DX,FCBname ; чтение записей
INT 21H ;Вызов DOS

Чтение записи с диска по адресу DTA осуществляется на основе
информации в блоке FCB. Операция чтения устанавливает в регистре AL
следующие коды возврата:

00 Успешное чтение.
01 Конец файла, данные не прочитаны.
02 В DTA нет места для чтения одной записи.
03 Конец файла, прочитана частичная запись, заполненная нулями.

Первая операция чтения заносит содержимое всего сектора в буфер DOS.

Затем операция определяет из блока FCB размер записи и пересылает первую
запись из буфера в DTA. После дующие операции чтения пересылают остальные
записи (если имеются) пока буфер не будет исчерпан. После этого операция
чтения определяет адрес следующего сектора и заносит его содержимое в
буфер.
После успешной операции чтения в блоке FCB автоматически
увеличивается номер текущей записи на 1. Завершение последовательного
чтения определяется программой по маркеру конца файла (EOF), для чего в
программе имеется соответствующая проверка. Так как оглавление при чтении
файла не изменяется, то обычно нет необходимости закрывать файл после
завершения чтения. Исключение составляют программы, которые открывают и
читают несколько файлов одновременно. Такие программы должны закрывать
файлы, так как DOS ограничивает число одновременно открытых файлов.

ПРОГРАММА: ИСПОЛЬЗОВАНИЕ FCB ДЛЯ ЧТЕНИЯ ДИСКОВОГО ФАЙЛА
________________________________________________________________

__________________________________________________________________________

TITLE FCBREAD (EXE) Чтение записей созданных в CREATDSK
; -------------------------------------------------------
STACKSG SEGMENT PARA STACK 'Stack'
DW 80 DUP(?)
STACKSG ENDS
;--------------------------------------------------------
DATASG SEGMENT PARA 'Data'
FCBREC LABEL BYTE ;FCB для файла
FCBDRIV DB 04 ; дисковод D
FCBNAME DB 'NAMEFILE' ; имя файла
FCBEXT DB 'DAT' ; тип файла
FCBBLK DW 0000 ; номер текущего блока
FCBRCSZ DW 0000 ; длина логической записи
DD ? ; размер файла (DOS)
DW ? ; дата (DOS)
DT ? ; зарезервировано (DOS)
FCBSQRC DB 00 ; текущий номер записи
DD ? ; относительный номер

RECLEN EQU 32 ;Длина записи
NAMEFLD DB RECLEN DUP(' '), 13, 10, '$'

ENDCDE DB 00
OPENMSG DB '*** Open error ***', '$'
READMSG DB '*** Read error ***', '$'
ROW DB 00
DATASG ENDS
;--------------------------------------------------------
CODESG SEGMENT PARA 'Code'
BEGIN PROC FAR
ASSUME CS:CODESG,DS:DATASG,SS:STACKSG,ES:DATASG
PUSH DS
SUB AX,AX
PUSH AX
MOV AX,DATASG
MOV DS,AX
MOV ES,AX
MOV AX,0600H
CALL Q10SCR ;Очистить экран
CALL Q20CURS ;Установить курсор
CALL E10OPEN ;Открыть файл,
; установить DTA
CMP ENDCDE,00 ;Открытие без ошибок?
JNZ A90 ; нет - завершить
A20LOOP:
CALL F10READ ;Прочитать запись
CMP ENDCDE,00 ;Чтение без ошибок?
JNZ A90 ; нет - выйти
CALL G10DISP ;Выдать имя на экран
JMP A20LOOP ;Продолжить
A90: RET ;Завершить
BEGIN ENDP
; Открытие файла на диске:
; -----------------------
E10OPEN PROC NEAR
LEA X,FCBREC
MOV AH,0FH ;Функция открытия
INT 21H
CMP AL,00 ;Файл найден?

JNZ E20 ; нет - ошибка

MOV FCBRCSZ,RECLEN ;Длина записи (EQU)
MOV AH,1AH
LEA DX,NAMEFLD ;Адрес DTA
INT 21H
RET
E20:
MOV ENDCDE,01 ;Сообщение об ошибке
LEA DX,OPENMSG
CALL X10ERROR
RET
E10OPEN ENDP
; Чтение дисковой записи:
; ----------------------
F10READ PROC NEAR
MOV AH,14H ;Функция чтения
LEA DX,FCBREC
INT 21H
CMP NAMEFLD,1AH ;Считан маркер EOF?
JNE F20 ; нет
MOV ENDCDE,01 ; да
JMP F90
F20:
CMP AL,00 ;Чтение без ошибок?
JZ F90 ; да - выйти
MOV ENDCDE,01 ;Нет:
CMP AL,01 ;Конец файла?
JZ F90 ; да - выйти,
LEA DX,READMSG ; нет - значит
CALL X10ERR ; ошибка чтения
F90:
RET
F10READ ENDP
; Вывод записи на экран:
; ---------------------
G10DISP PROC NEAR
MOV AH,09 ;Функция вывода на экран
LEA DX,NAMEFLD
INT 21H
CMP ROW,20 ;Последняя строка экрана?
JAE G30 ; нет...
INC ROW ; да - увеличить строку
JMP G90
G30:
MOV AX,0601H
CALL Q10SCR ; прокрутить
CALL Q20CURS ; установить курсор
G90: RET
G10DISP ENDP
; Прокрутка (скроллинг) экрана:
; ----------------------------
Q10SCR PROC NEAR ;AX уже установлен
MOV BH,1EH ;Установить цвет
MOV CX,0000
MOV DX,184FH ;Функция прокрутки
INT 10H
RET
Q1OSCR ENDP
; Установка курсора:
; -----------------
Q20CURS PROC NEAR
MOV AH,02
MOV BH,00
MOV DH,ROW
MOV DL,00
INT 10H
RET
Q20CURS ENDP
; Вывод сообщения об ошибке на диске:
; ----------------------------------
X10ERR PROC NEAR
MOV AH,09 ;DX содержит адрес
INT 21H ; сообщения
RET
X10ERR ENDP

CODESG ENDS
END BEGIN
__________________________________________________________________________

Рис.16.2. Чтение дискового файла

На рис.16.2 приведена программа, которая выполняет чтение файла,
созданного предыдущей программой, и вывод на экран имен из записей файла.
Обе программы содержат идентичные блоки FCB, хотя, имена полей FCB могут
быть различны. Содержимое полей имени и типа файла должны быть одинаковы.
Программа содержит следующие процедуры:

BEGIN Инициализирует сегментны регистра, вызывает процедуру E10OPEN
для открытия файла и установки DTA и вызывает F10READ для чтения
записей. Если считан маркер конца файла, то программа
завершается, если нет, то вызывается процедура G10DISP.
E10OPEN Открывает файл, устанавливает значение размера и записей,
равное 32 (шест.20), и инициализирует адрес DTA.
F10READ Выполняет последовательное чтение записей. Операция чтения
автоматически увеличивает номер текущей записи в блоке FCB.
G10DISP Выводит на экран содержимое прочитанной записи.
X10ERR Выводит на экран сообщение об ошибке в случае некорректной
операции открытия или чтения.

Операция открытия выполняет поиск имени и типа файла в оглавлении.
Если необходимый элемент оглавления найден, то автоматически в блок FCB
заносятся размер файла, дата и длина записей. Первая операция чтения
записи с номером 00 получает доступ к диску и считывает весь сектор (16
записей) в буфер. После этого первая запись заносится в DTA, а номер
текущей записи в FCB увеличивается с 00 до 01:

Буфер: |запись 00|запись 01|запись 02|... |запись 15|
DTA : |запись 00|

Второй операции чтения нет необходимого обращаться к диску. Так как
требуемая запись уже находится в буфере, то операция просто пересылает
запись 01 из буфера в DTA и увеличивает номер текущей записи на единицу.
Таким же образом выполняются следующие операции чтения пока все 16 записей
из буфера не будут обработаны.
Операции чтения 16-ой записи приводит к физическому чтению следующего
сектора в буфер и пересылка первой записи сектора в DTA. Последующие
операции чтения переносят остальные записи из буфера в DTA. Попытка
прочитать после последней записи вызовет состояние конца файла и в регистр
AL будет записан код возврата шест.01.

ПРЯМОЙ ДОСТУП
________________________________________________________________

До сих пор в этой главе рассматривалась последовательная обработка
дисковых файлов, которая адекватна как для создания файла, так и для
печати его содержимого или внесения изменений в небольшие файлы. Если
программа ограничена только возможностью последовательной обработки, то
для изменения файла она должна считывать каждую запись, вносить изменения
в определенные из них и заносить записи в другой файл (программа может
использовать один DTA, но потребуются различные блоки FCB). Обычной
практикой является чтение входного файла с диска A и запись обновленного
файла на диск B. Преимущество этого способа состоит в том, что он
автоматически оставляет резервную копию.
В некоторых случаях применяется доступ к конкретным записям файла для
получения информации, например, нескольких служащих или о части
ассортимента товаров. Для доступа, скажем, к 300-ой записи файла,
последовательная обработка должна включать чтение всех 299 предшествующих
записей, пока не будет получена 300-я запись. П р и м е ч а н и е: система
может начать обработку с конкретного номера блока и записи).
Несмотря на то, что файл создается последовательно, доступ к записям
может быть последовательным или прямым (произвольным). Требования прямой
обработки, использующей вызов DOS, заключаются в установке требуемого
номера записи в соответствующее поле FCB и выдаче команды прямого чтения
или записи.
Произвольный доступ использует относительный номер записи (байты
33-36) в блоке FCB. Поле имеет размер двойного слова и использует обратную
последовательность байт в словах. Для локализации требуемой записи система
автоматически преобразует относительный номер записи в номер текущего
блока (байты 12-13) и номер текущей записи (байт 32).

Прямое чтение
---------------
Операции открытия и установки DTA одинаковы как для прямой, так и для
последовательной обработки. Предположим, что программа должна выполнить
прямой доступ к пятой записи файла. Установим значение 05 в поле FCB для
относительного номера записи и выполним команды для прямого чтения. В
результате успешной операции содержимое пятой записи будет помещено в DTA.
Для прямого чтения записи необходимо поместить требуемое значение
относительного номера записи в FCB и вызвать функцию шест.21:

MOV AH,21H ;Запрос на
LEA DX,FCBname ; прямое чтение
INT 21H ;Вызов DOS

Операция чтения преобразует относительный номер записи в номера
текущего блока и записи. Полученные значения используются для локализации
требуемой дисковой записи, передачи содержимого записи в DTA и установки в
регистр AL следующие значения:

00 Успешное завершение
01 Данные не доступны
02 Чтение прекращено из-за нехватки места в DTA
03 Прочитана частичная запись, заполненная нулями.

Как видно, среди перечисленных кодов возврата отсутствует состояние
конец файла. При корректном чтении записи предполагается единственный код
возврата - 00. Остальные коды возврата могут являться результатом
установки неправильного относительного номера записи или некорректная
установка адреса DTA или FCB. Так как такие ошибки легко допустить, то
полезно выполнять проверку регистра AL на ненулевое значение.
Когда программа выдает первый запрос на прямую запись, операция,
используя оглавление для локализации сектора, на котором находится
требуемая запись, считывает весь сектор с диска в буфер и пересылает
запись в DTA. Предположим, например, что записи имеют размер 128 байт,
т.е. четыре записи в одном секторе. Запрос на прямое чтение записи 23
приводит к чтению в буфер четырех записей, лежащих в одном секторе:

| запись 20 | запись 21 | запись 22 | запись 23 |

Когда программа вновь выдаст прямой запрос на запись, например, 23,
то операция сначала проверит содержимое буфера. Так как данная запись уже
находится в буфере, то она непосредственно пересылается в DTA. Если
программа запросит запись 35, который нет в буфере, операция через
оглавление локализует требуемую запись, считает весь сектор в буфер и
поместит запись в DTA. Таким образом, операции прямого доступа к записям
более эффективны, если номера записей близки друг к другу.

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

MOV AH,22H ;Запрос на
LEA DX,FCBname ; прямую запись
INT 21H ;Вызов DOS

Операция устанавливает в регистре AL следующие коды возврата:

00 Успешная операция
01 На диске нет места
02 Операция прекращена в результате недостаточного места в DTA.

При создании нового файла прямым доступом может быть получен
ненулевой код возврата. Но при прямом чтении и переписывании измененных
записей на том же месте диска код возврата должен быть только 00.
Относительный номер записи в блоке FCB при прямом доступе имеет
размер двойного слова (четыре байта), каждое слово записывается обратной
последовательностью байтов. Для небольших файлов возможно потребуется
установка лишь самого левого байта или слова, но для больших файлов
установка номера записи в трех или в четырех байтах требует некоторой
тщательности.


ПРОГРАММА: ПРЯМОЕ ЧТЕНИЕ ДИСКОВОГО ФАЙЛА
________________________________________________________________

page 60,132
TITLE RANREAD (COM) Прямое чтение записей,
; созданных в FCBCREAT
CODESG SEGMENT PARA 'Code'
ASSUME CS:CODESG,DS:CODESG,SS:CODESG,ES:CODESG
ORG 100H
BEGIN: JMP MAIN
;-----------------------------------------------------
FCBREC LABEL BYTE ;FCB для дискового файла
FCBDRIV DB 04 ; дисковод D
FCBNAME DB 'MAMEFILE' ; имя файла
FCBEXT DB 'DAT' ; тип файла
FCBBLK DW 0000 ; номер текущего блока
FCBRCSZ DW 0000 ; длина логической записи
DD ? ; размер файла (DOS)
DW ? ; дата (DOS)
DT ? ; зарезервировано (DOS)
DB 00 ; номер текущей записи
FCBRNRC DD 000000000 ; относительный номер

RECLEN EQU 32 ;Длина записи
RECDPAR LABEL BYTE ;Список параметров:
MAXLEN DB 3 ;
ACTLEN DB ? ;
RECDNO DB 3 DUP(' ') ;

NAMEFLD DB RECLEN DUP(' '),13,10,'$' ;DTA

OPENMSG DB '*** Open error ***',13,10,'$'
READMSG DB '*** Read error ***',13,10,'$'
COL DB 00
PROMPT DB 'Record number? $'
ROW DB 00
ENDCDE DB 00
;-----------------------------------------------------
MAIN PROC NEAR
CALL Q10CLR ;Очистить экран
CALL Q20CURS ;Установить курсор
CALL C10OPEN ;Открыть файл,
; установить DTA
CMP ENDCDE,00 ;Открытие без ошибок?
JZ A20LOOP ; да - продолжить,
RET ; нет - завершить
A20LOOP:
CALL D10RECN ;Получить номер записи
CMP ACTLEN,00 ;Есть запрос?
JE A40 ; нет - выйти
CALL F10READ ;Чтение (прямой доступ)
CMP ENDCDE,00 ;Есть ошибки чтения?
JNZ A30 ; да - обойти
CALL G10DISP ;Вывести на экран
A30:
JMP A20LOOP
A40: RET ;Завершить программу
MAIN ENDP
; Подпрограмма открытия файла на диске:
; ------------------------------------
C10OPEN PROC NEAR
MOV AH,0FH ;Функция открытия
LEA DX,FCBREC
INT 21H
CMP AL,00 ;Открытие нормальное?
JNZ C20 ; нет - ошибка
MOV FCBRCSZ,RECLEN ;Длина записи (EQU)
MOV AH,1AH
LEA DX,NAMEFLD ;Установить адрес DTA
INT 21H
RET
C20:
LEA DX,OPENMSG
CALL X10ERR
RET
C10OPEN ENDP
; Ввод с клавиатуры номера записи:
; -------------------------------
D10RECN PROC NEAR
MOV AH,09H ;Функция вывода на экран
LEA DX,PROMPT
INT 21H
MOV AH,0AH ;Функция ввода с клавиатуры
LEA DX,RECDPAR
INT 21H
CMP ACTLEN,01 ;Проверить длину (0,1,2)
JB D40 ;Длина 0, завершить
JA D20
SUB AH,AH ;Длина 1
MOV AL,RECDNO
JMP D30
D20:
MOV AH,RECDNO ;Длина 2
MOV AL,RECDNO+1
D30:
AND AX,0F0FH ;Удалить ASCII тройки
AAD ;Преобразовать в двоичное
MOV WORD PTR FCBRNRC,AX
D40:
MOV COL,20
CALL Q20CURS ;Установить курсор
RET
D10RECN ENDP
; Чтение дисковой записи:
; ----------------------
F10READ PROC NEAR
MOV ENDCDE,00 ;Очистить код завершения
MOV AH,21H ;Функция прямого чтения
LEA DX,FCBREC
INT 21H
CMP AL,00 ;Чтение без ошибок?

JZ F20 ; да - выйти
LEA DX,READMSG ; нет - выдать
CALL X10ERR ; сообщение об ошибке
F20: RET
F10READ ENDP
; Вывод имени на экран:
; --------------------
G10DISP PROC NEAR
MOV AH,09 ;Функция вывода на экран
LEA DX,NAMEFLD
INC 21H
INC ROW
MOV COL,00
RET
G10DISP ENDP
; Очистка экрана:
; --------------
Q10CLR PROC NEAR
MOV AX,0600H ;Функция прокрутки
MOV BH,41H ;Цвет (07 для ч/б)
MOV CX,0000
MOV DX,184FH
INT 10H
RET
Q10CLR ENDP
; Установка курсора:
; -----------------
Q20CURS PROC NEAR
MOV AH,02 ;Функция установки
MOV BH,00 ; курсора
MOV DH,ROW
MOV DL,COL
INT 10H
RET
Q20CURS ENDP
; Вывод сообщения об ошибке на диске:
; ----------------------------------
X10ERR PROC NEAR
MOV AH,09 ;DX содержит адрес
INT 21H ; сообщения
INC ROW
MOV ENDCDE,01
RET
X10ERR ENDP
CODESG ENDS
END BEGIN
__________________________________________________________________________

Рис.16.3. Прямое чтение дисковых записей.

На рис.16.3 приведена программа, которая считывает файл, созданный
предыдущей программой (см. рис.16.1). Вводя любой относительный номер
записи, лежащей в границах файла, пользователь запрашивает вывод на экран
любой записи файла. Если файл содержит 25 записей, то правильными номера
являются номера от 00 до 24. Номер вводится с клавиатуры в ASCII формате и
должен быть в нашем случае одно- или двузначным числом.
Программа содержит следующие процедуры:

C10OPEN Открывает файл, устанавливает размер записи 32 и устанавливает
адрес DTA.
D10RECN Вводит номер записи с клавиатуры, преобразует его в двоичный
формат и записывает полученное значение в FCB. В качестве
усовершенствования процедуры можно вставить проверку вхождения
номера в границы от 00 до 24.
F10READ Помещает требуемую запись в DTA в соответствии с относительным
номером записи в FCB.
G10DISP Выводит запись на экран.

Процедура D10RECN вводит номер записи с клавиатуры и проверяет длину
ввода в списке параметров. Возможны три варианта:

00 Запрошен конец обработки
01 Введено однозначное число (в регистре AL)
02 Введено двузначное число (в регистре AX)

Данная процедура преобразует введенное число из ASCII формата в
двоичный формат. Так как значение находится в регистре AX, то лучше
использовать команду AAD для преобразования. После преобразования двоичный
код из регистра AX пересылается в два левых байта поля относительного
номера записи в блоке FCB. Если, например, введено число 12 в ASCII
формате, то AX будет содержать 3132. Команда AND преобразует это значение
в 0102, а команда AAD - в 000C. Результат преобразования заносится в поле
относительного номера записи блока FCB в виде С000 0000.

ПРЯМОЙ БЛОЧНЫЙ ДОСТУП
________________________________________________________________

Если в программе имеется достаточно места, то одна прямая блочная
операция может записать весь файл из DTA на диск, а также прочитать весь
файл с диска в DTA. Данная особенность весьма полезна для записи на диск
таблиц, которые другие программы могут считывать в память для обработки.
Начать можно с любого правильного относительного номера записи. Число
записей также может быть любым, хотя блок должен находится в пределах
файла. Перед началом необходимо открыть файл и инициализировать DTA.
Для операции прямой блочной записи необходимо установить в регистре
CX требуемое число записей, установить в FCB стартовый относительный номер
записи и выдать функцию шест.28:

MOV AH,28H ;Операция прямой блочной записи
MOV CX,records ;Установка числа записей
LEA DX,FCBname ;
INT 21H ;Вызов DOS

Операция преобразует относительный номер записи в текущие номер блока
и номер записи. Полученные значения используются для определения
начального адреса на диске. В результате операции в регистре AL
устанавливаются следующие коды возврата:

00 Успешное завершение для всех записей
01 На диске недостаточно места.

Кроме того операция устанавливает в FCB в поле относительного номера
записи и полях текущих номеров блока и записи значения, соответствующие
следующему номеру записи. Например, если были записаны записи с 00 до 24,
то следующий номер записи будет 25 (шест.19).
Для операции прямого блочного чтения необходимо установить в регистре
CX требуемое число записей и использовать функцию шест.27:

MOV AH,27H ;Операция прямого блочного чтения
MOV CX,records ;Установка числа записей
LEA DX,FCBname ;
INT 21H ;Вызов DOS

Операция чтения возвращает в регистре AL следующие значения:

00 Успешное чтение всех записей
01 Прочитана последняя запись файла
02 Прочитано предельное для DTA число записей
03 Прочитана последняя запись файла не полностью.

В регистре CX остае

Список сторінок

Поділитися у соцмережах

☏ Замовлення реклами: +3805044-68-872

© Асоціація електронних бібліотек України

☝ Усі матеріали сайту (зокрема статті, зображення, рекламні оголошення тощо) призначені лише для попереднього ознайомлення. Усі права на публікації, представлені на сайті, належать їхнім законним власникам. Просимо Вас не зберігати копії інформації.