Жанр: Учеба

Справочник программиста на IBM PC

сторінка №35

дет равен 256, буфер не полон.
Средний уровень.

Функция 2 прерывания 14H BIOS ожидает символ из последовательного
порта, помещает его в AL при получении и затем возвращается
в программу. При входе надо поместить номер порта (0-1) в DX. При
возврате AX равен нулю, если не было ошибки. Если AH не равен 0,
то может быть возвращен байт статуса, в котором имеют значение
только 5 битов. Это следующие биты:

бит 1 ошибка переполнения (новый символ поступил раньше, чем
был удален старый)
2 ошибка четности (вероятно, из-за проблем в линии)
3 ошибка оформления (стартовый или стоп-биты неверны)
4 обнаружен перерыв (получена длинная строка битов 0)
5 ошибка таймаута (не получен сигнал DSR)

MS DOS также предоставляет коммуникационную функцию для приема
одного символа, это функция 3 прерывания 21H. Функция ожидает
символ из COM1 и помещает его в AL. Отметим, что при этом нет
функции инициализации порта, которую надо делать через процедуру
BIOS или непосредственно, как показано в [7.1.2]. По умолчанию
порт инициализируется со значениями 2400 бод, нет контроля четности,
один стоп-бит и 8 битов на символ. Эта функция не имеет
никаких достоинств по сравнению с функцией BIOS и не возвращает
информации о статусе.

Низкий уровень.

При получении данных без использования коммуникационного прерывания
[7.1.8] программа должна постоянно проверять регистр
статуса линии, адрес порта которого на 5 больше базового адреса
используемого коммуникационного адаптера. Бит 0 этого регистра
будет равен нулю, до тех пор пока не будет получен символ в регистр
данных приемника. Когда бит 0 становится равным 1, то надо
немедленно считать его из регистра, с тем чтобы на него не наложился
следующий принимаемый символ. После того как символ считан,
бит 0 опять становится равным 0 и остается таковым, пока не прибудет
новый символ.
Хотя здесь об этом не говорилось, но коммуникационные процедуры
обычно создают циклический буфер для сбора поступающих символов.
Циклические буфера обсуждались в [3.1.1]. Вы должны также
знать, что если поступающие данные подавать на экран со скоростью
1200 бод, то процедура сдвига экрана BIOS [4.5.1] не будет успевать
и произойдет переполнение. Простое решение этих проблем
состоит в использовании коммуникационного прерывания, как объяснено
в [7.1.8].
Следующий пример частично дублирует содержимое предыдущего
раздела, относящегося к передаче символов. Как и в том случае код
начинается с бесконечного цикла. Объедините эти 2 процедуры с
процедурами инициализации из [7.1.2] и [7.1.5] для создания законченной
процедуры ввода/вывода через коммуникационный канал.

KEEP_TRYING: MOV DX,BASE_ADDRESS ;базовый адрес
ADD DX,5 ;указываем на регистр статуса линии
IN AL,DX ;получаем байт статуса
TEST AL,00011110B ;проверяем на ошибку
JNZ ERROR_ROUTINE ;если да, то на обработку ошибки
TEST AL,00000001B ;проверяем получены ли данные
JNZ RECEIVE ;на процедуру приема данных
TEST AL,00100000B ;проверяем готовность к передаче
JZ KEEP_TRYING ;если нет, то к началу цикла
(здесь расположена процедура передачи - см. [7.1.6])
;---получаем данные и выводим их на экран
RECEIVE: MOV DX,BASE_ADDRESS ;базовый адрес
IN AL,DX ;читаем полученный символ
CMP AL,19 ;проверка на XOFF
JE XOFF_ROUTINE ;
(и т.д.)
MOV DL,AL ;готовим символ для вывода на экран
MOV AH,2 ;функция вывода символа
INT 21H ;выводим его
JMP SHORT KEEP_TRYING ;возвращаемся на начало цикла
7.1.8 Посылка/получение данных с помощью коммуникационного
прерывания.


Хорошая коммуникационная программа имеет слишком много работы,
чтобы посвятить себя целиком вводу/выводу. Поступающие данные
должны анализироваться, передаваемые данные должны собираться, а
большие блоки данных могут записываться на диск или считываться с
него. Коммуникационное прерывание позволяет программе не тратить
на ввод/вывод больше времени, чем он того требует. Например,
после установки прерывания, управление передается процедуре передачи
данныз только в том случае, когда регистр хранения передатчика
пуст и возвращается программе, как только послан байт данных,
позволяя ей продолжать свою работу до тех пор, пока регистр
хранения передатчика не будет снова готов. Не забудьте ознакомиться
с обсуждением прерываний в [1.2.3], прежде чем продолжить
чтение.
IBM PC отводит два аппаратных прерывания для коммуникационных
каналов, номер 3 (COM1) и 4 (COM2). Отметим, что у PCjr, встроенный
модем имеет номер 3, а COM1 - номер 4. Микросхема UART 8250
допускает 4 класса прерываний для каждого канала, используя следующие
двоичные кодовые числа:

00 изменение в регистре статуса модема
01 регистр хранения передатчика пуст
10 получены данные
11 ошибка приема, или получено условие перерыва

Эти коды содержатся в битах 2-1 регистра идентификации прерывания,
адрес порта которого на 2 больше, чем базовый адрес используемого
коммуникационного адаптера. Бит 0 этого регистра устанавливается
при возникновении прерывания, а остальные биты не используются
и всегда равны 0.
Чтобы выбрать одно или более прерываний, надо запрограммировать
регистр разрешения прерывания, адрес которого на 1 больше
базового адреса. Значение его битов такое:

бит 0 1 = прерывание при получении данных
1 1 = прерывание когда регистр хранения передатчика пуст
2 1 = прерывание при ошибке приема данных
3 1 = прерывание при изменении регистра статуса модема
7-4 не используются, всегда 0

Когда одно из этих событий происходит, то инициируется аппаратное
прерывание, возникающее в микросхеме обработки прерываний 8259 по
каналу 3 для COM1 и по каналу 4 для COM2. Процедура обработки
прерываний передает управление тому коду, на который указывает
соответствующий вектор прерывания. Поскольку это аппаратное прерывание,
то оно может быть маскировано [1.2.2]. Помните, что
процедура обработки прерывания должна завершаться стандартным
кодом выхода из аппаратного прерывания MOV AL,20H/OUT 20H,AL. На
рис. 7-3 показано коммуникационное прерывание.
Любое число типов прерывания может быть разрешено одновременно.
Но если разрешен более чем один тип, то процедура обработки
прерывания должна сама определять какой из типов прерывания произошел,
проверяя регистр идентификации прерывания. Одновременно
могут происходить более чем одно прерывание, поэтому бит 0 регистра
идентификации сообщает о том, что поступило еще одно прерывание.
Когда два или более прерываний поступило в один и тот же
момент времени, то они обрабатываются в порядке, указанном в
нижеприведенной таблице. Добавочные прерывания должны быть обработаны
до завершения процедуры обработки прерывания. Условия
предшествующих прерываний "отменяются" с помощью действий, приведенных
в правом столбце следующей таблицы:

Код Тип Действия для "сброса"

11 ошибка или перерыв чтение регистра статуса линии
10 получены данные чтение регистра приемника данных
01 передатчик готов вывод символа в регистр хранения
передатчика
00 изменение статуса модема чтение регистра статуса модема

Низкий уровень.

Вот общая форма программы, обрабатывающей коммуникационные
прерывания:

;---установка вектора коммуникационного прерывания
PUSH DS ;сохраняем DS
MOV DX,OFFSET IO_INT ;DS:DX указывают на процедуру
MOV AX,SEG IO_INT ;
MOV DS,AX ;
MOV AL,0BH ;номер вектора для COM1
MOV AH,25H ;функция изменения вектора
INT 21H ;меняем вектор прерывания
;---инициализация регистра разрешения прерывания (COM1)
MOV AX,40H ;DS указывает на данные BIOS
MOV DS,AX ;
MOV DX,DS:[0] ;получаем базовый адрес COM1
INC DX ;указываем на регистр разрешения
MOV AL,3 ;прерываний и разрешаем прерывания
OUT DX,AL ;приема и передачи
POP DS ;восстанавливаем регистр

;---процедура обработки прерывания - сначала определяем его тип
IO_INT PROC FAR
NEXT_INT: MOV DX,BASEADDRESS ;базовый адрес
INC DX ;указываем на регистр идентификации
INC DX ;прерывания
IN AL,DX ;читаем его значение
TEST AL,10B ;это прерывание передатчика?
JNZ TRANSMIT ;если да, то на передачу

RECEIVE: ;иначе на прием
JMP SHORT ANOTHER ;проверяем нет ли другого прерывания

TRANSMIT: ;здесь код для передачи
;---перед выходом, проверяем нет ли другого прерывания
ANOTHER: MOV DX,BASEADDRESS ;базовый адрес
INC DX ;указываем на регистр идентификации
INC DX ;прерывания
IN AL,DX ;читаем его значение
TEST AL,1 ;проверяем бит 1
JNZ NEXT_INT ;если он установлен, то на начало
MOV AL,20H ;иначе код завершения аппаратного
OUT 20H,AL ;прерывания
IRET
IO_INT ENDP
7.1.9 Сводка управляющих кодов, используемых при коммуникации.

Эта таблица содержит 32 управляющих кода ASCII, которые используются
при коммуникации, а также при работе принтера и других
устройств. Добавлен также код ASCII 127 - DEL (Забой), который
обычно используется как управляющий код, хотя его и нельзя выдать
с клавиатуры с помощью сочетания Ctrl + клавиша. Применение некоторых
кодов, таких как возврат каретки, инвариантно. Но большинство
других управляющих кодов имеют широкий диапазон интерпретации,
во многом из-за отсутствия совместимости оборудования.

Номер кода Комби- Мне- Назначение
ASCII нация мо10-й
16-й с Ctrl ника

00 00 ^@ NUL Символ-разделитель (не имеющий значени-
я, поэтому полезен для задержек)
01 01 ^A SOH Начало заголовка.Начинает передачу бло-
ка данных или нового файла.
02 02 ^B STX Начало текста. Отмечает начало текста,
следующего за заголовком данных.
03 03 ^C ETX Конец текста. Может отмечать начало да-
нных, служащих для контроля ошибок.
04 04 ^D EOT Конец передачи. Код остановки,но иногда
он просто отмечает конец файла.
05 05 ^E ENQ Запрос.Запрашивает статусную информацию
у отдаленной станции.
06 06 ^F ACK Подтверждение.Подтверждает успешный об-
мен между станциями.
07 07 ^G BEL Звонок.Инициирует звонок,чтобы привлечь
внимание.
08 08 ^H BS Возврат на шаг.
09 09 ^I HT Горизонт. табуляция.
10 0A ^J LF Перевод строки.

11 0B ^K VT Вертик. табуляция.
12 0C ^L FF Перевод формата.
13 0D ^M CR Возврат каретки.
14 0E ^N SO Сдвиг выключен. Переключает набор сим-
волов.
15 0F ^O SI Сдвиг включен. Переключает набор симво-
лов.
16 10 ^P DLE Data Link Escape. Модифицирует значение
следующих символов (аналогично Esc).
17 11 ^Q DC1 Управление устройством 1. Используется
как сигнал XON для удаленной станции на
передачу.
18 12 ^R DC2 Управление устройством 2. Сигнал перек-
лючения общего назначения.
19 13 ^S DC3 Управление устройством 3. Используется
как сигнал XOFF для удаленной станции
для прекращения передачи.
20 14 DC4 Управление устройством 4. Сигнал перек-
лючения общего назначения.
21 15
NAK Отрицание. Передача неуспешна.
22 16 ^V SYN Промежуток синхронизации. Используется
между блоками данных при синхронной
связи.
23 17 ^W TB Конец блока передачи. Вариант ETX.
24 18 ^X CAN Отмена. Обычно сигнализирует об ошибке
передачи.
25 19 ^Y EM Конец среды. Сигнализирует о физическом
конце источника данных.
26 1A ^Z SUB Подстановка. Заменяет символы, которые
незаконны или невозможно вывести.
27 1B ^[ ESC Отмечает последующие символы, как упра-
вляющую последовательность.
28 1C ^/ FS Разделитель файлов. Отмечает логическую
границу между файлами.
29 1D ^] GS Разделитель групп. Отмечает логическую
границу между группами данных.
30 1E ^^ RS Разделитель записей.Отмечает логическую
границу между записями данных.
31 1F ^_ US Разделитель объектов. Отмечает логичес-
кую границу между объектами данных.
127 7F нет DEL Забой. Удаляет другие символы.

Раздел 2. Создание драйвера устройства.


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

1. Можно поместить код для всех драйверов прямо в программу.
Например, чтобы поддерживать различные принтеры, можно создать
таблицу управляющих последовательностей и искать в ней нужный код
каждый раз когда он потребуется. Этот подход тратит много памяти
и может быть достаточно медленным.

2. Создать ряд драйверов устройств и потребовать, чтобы программа
загружала необходимый в качестве оверлея (т.е. помещать его
в область программы, специально оставленную для этой цели
[1.3.5]).

3. Создать драйвер устройства как отдельную программу, которая
указывается в командном файле, выполняемом при загрузке системы.
Программа запускается и устанавливает драйвер устройства как
программу обработки прерывания. После этого программа завершается,
но остается резидентной в памяти, как объяснено в [1.3.4].
Впоследствии наша программа использует этот драйвер через вектор
прерывания.

4. Создать полноценный драйвер устройства, который будет загружаться
при старте с помощью файла CONFIG.SYS. MS DOS поддерживает
такой тип драйверов устройств и однажды загруженный он может
использовать все возможности команд DOS, включая проверку ошибок.
Специальная команда IOCTL (Контроль ввода/вывода) позволяет программе
узнать статус драйвера и послать ему управляющую строку,
помимо обычного потока данных.


Первые три стратегии легко реализуются с помощью информации,
приведенной в остальных частях данной книги. Но устанавливаемые
драйверы устройств очень сложны. Зато когда он есть, то он очень
мощен. В этом случае система будет работать с устройством настолько
же тесно, как с клавиатурой или дисковым накопителем.
Устройству может быть присвоено имя, например, SERIALPR для последовательного
принтера, и затем это устройство может быть открыто
для доступа из любого языка. В Бейсике оператор OPEN "SERIALPR"
FOR OUTPUT AS #2 подготовит последовательный принтер для
вывода. В языке ассемблера Вы сможете получить доступ к принтеру
как с помощью метода управляющего блока файла, так и с помощью
метода дескриптора файла, включая очень мощную функцию IOCTL. При
этом пользователь имеет возможность доступа к устройству на уровне
операционной системы и может просто ввести команду COPY A:MYFILE
SERIALPR:, чтобы скопировать содержимое файла на принтер.
Устанавливаемые драйверы устройств могут быть написаны только
на языке ассемблера. Они могут обслуживать два типа устройств:
символьные и блочные. Эти имена описывают единицы, которыми устройство
обрабатывает данные. Обычно драйверы блочных устройств
обслуживают дисковые накопители, а драйверы символьных - все
остальное, начиная от последовательных принтеров и кончая роботами.
Блочные устройства обмениваются блоками данных, поэтому они
занимаются накоплением данных. Символьные устройства обмениваются
данными побайтно, поэтому они лучше подходят для управляющих
устройств, а также для устройств, которые не могут обеспечить
высокую скорость обмена данными. Драйверы блочных устройств очень
сложны и здесь нет достаточно места, чтобы объяснить их структуру.
Очень редко кому требуется написать такой драйвер. Техническое
руководство по MS DOS предоставляет всю необходимую информацию
и содержит полный пример драйвера виртуального диска в оперативной
памяти. Вы можете просмотреть эту информацию после того
как изучите обсуждение драйверов символьных устройств, приведенное
здесь.
Устанавливаемые драйверы устройств беспощадны к программистским
ошибкам. Поскольку драйверы автоматически загружаются системой
при загрузке, то невозможно использовать отладчики для выявления
причин неполадок. Поэтому будьте предельно внимательны при
их написании.
Программа драйвера устройства разбивается на три части, каждая
из которых обсуждается отдельно в следующих разделах. Это (1)
заголовок драйвера, который именует устройство и содержит информацию
об остальных частях драйвера, (2) стратегия драйвера, которая
хранит информацию об области данных, создаваемой MS DOS,
которая называетя заголовком запроса, и (3) обработчик прерывания
устройства, который и содержит код, управляющий устройством.
7.2.1 Создание заголовка драйвера.

Драйверы устройств должны создаваться в виде COM файлов
[1.3.6]. Однако они не являются настоящими программами, поскольку
у них отсутствует префикс программного сегмента. Чтобы добиться
этого не надо включать оператор ORG 100H в начале программы, как
это делается для COM файлов. Либо запишите ORG 0, либо вообще
ничего не пишите. Драйвер должен быть описан как далекая (far)
процедура, как и в любой программе. В нижеприведенном примере
приведен начальный код для драйвера устройства с именем DEVICE12.
Оно заменяет стандартное устройство AUX, используемое MS DOS,
принимая вывод функции 4 прерывания 21H. Весь драйвер устройства
состоит из кода этого раздела вместе с кодом, приведенном в следующих
двух разделах; поместите их подряд один за другим, чтобы
получить полную программу.
Драйвер устройства должен начинаться с заголовка драйвера. Он
имеет длину 18 байтов, разделенных на 5 полей. Первое поле (DD)
всегда содержит значение -1 (FFFFFFFFH), и когда MS DOS загружает
драйвер, то оно заменяется на стартовый адрес следующего драйвера.
Таким образом, система может искать следующий драйвер по
цепочке. У последнего загруженного драйвера в этом поле остается
значение -1.
Второе поле это байт атрибутов драйвера. Имеют значение только
7 битов этого слова:

бит 15 1 = символьное устройство, 0 = блочное устройство
14 1 = поддерживает IOCTL, 0 = не поддерживает IOCTL
13 1 = формат блоков IBM, 0 = другой формат блоков
3 1 = часы, 0 = не часы
2 1 = нулевое устройство, 0 = не нулевое устройство
1 1 = устройство стандартного вывода, 0 = нет
0 1 = устройство стандартного ввода, 0 = нет

Обычно установлен только бит 15, или биты 15 и 14, если устройство
поддерживает IOCTL (как обсуждается в [7.2.4]). Бит 13 устанавливается
только для блочных устройств. Остальные биты используются
для замены устройств, используемых MS DOS по умолчанию
(устройствами стандартного ввода и вывода являются клавиатура и
видеодисплей; устройство часов объединяет часы реального времени
с часами времени суток BIOS; а нулевое устройство (NULL) - это
псевдоустройство, используемое для тестовых целей).
Третье и четвертое поля содержат смещения для процедур стратегии
и обработки прерывания, которые будут рассмотрены в следующих
разделах. Наконец, последнее поле содержит имя устройства. Имя
может содержать до 8 символов и оно должно быть выравнено по
левому краю с завершающими пробелами. Для замены существующих в
DOS устройств, таких как LPT1 или COM1, используйте то же имя
устройства, как в данном примере.

Низкий уровень.

В данном примере создается драйвер для последовательного устройства.
"DEVICE12" - имя файла, который должен быть указан в
файле конфигурации сиситемы, чтобы этот драйвер был загружен. В
байте атрибутов установлен только бит 15, указывая что это символьное
устройство и что оно не поддерживает IOCTL. DEV_STRATEGY
и DEV_INTERRUPT - имена процедур, обсуждаемых в следующих разделах.
Устройство названо AUX, с тем чтобы заменить обычное устройство
MS DOS с этим именем. Это позволяет очень просто обращаться
к этому устройству, поскольку система имеет предопределенный
номер файла для обращения к устройству AUX (последовательному).
В пример включен начальный код для драйвера, определяющий
его как COM программу.

CSEG SEGMENT PUBLIC 'CODE' 'устанавливаем кодовый сегмент
ORG 0 'эта строка необязательна
ASSUME CS:CSEG,DS:CSEG,ES:CSEG
DEVICE12 PROC FAR 'драйвер это далекая процедура
DD 0FFFFFFFFH 'адрес следующего драйвера
DW 8000H 'байт атрибутов
DW DEV_STATEGY 'адрес процедуры стратегии
DW DEV_INTERRUPT 'адрес процедуры прерывания
DB 'AUX ' 'имя устройство (дополненное пробелами)
7.2.2 Создание стратегии устройства.

Процедура стратегии устройства требует только пяти строк.
Когда система загружает устройство, то она создает блок данных,
называемый заголовком запроса. Он имеет две функции. Во-первых он
служит областью данных для внутренних операций системы. Более
важно то, что заголовок запроса служит областью, через которую
происходит обмен информацией между драйвером и вызывающей его
программой. Например, когда драйвер выводит данные, то ему дается
адрес данных через заголовок запроса. Когда же драйвер завершает
свою работу, то он устанавливает в заголовке запроса байт статуса,
который доступен вызывающей программе, тем самым давая возможность
ей узнать об ошибке.
MS DOS создает заголовок запроса при установке драйвера устройства
(когда система загружается). Процедура стратегии устройства
выполняется только один раз в этот момент. При этом ES:BX
указывают на вновь созданный заголовок запроса и процедуре нужно
просто скопировать их, чтобы впоследствии он мог быть обнаружен
при обращении к драйверу. Адреса смещения и сегмента заголовка
помещаются в две переменные. В следующем разделе Вы увидите, что
при обращении к драйверу, первое что он делает - восстанавливает
значения ES:BX, чтобы можно было получить информацию из заголовка
запроса.
Размер заголовка запроса может меняться, в зависимости от типа
сделанного запроса к драйверу (напр. инициализация, вывод данных
или возврат статуса). Однако первые 13 байт заголовка всегда одни
и те же. Их формат таков:

1. Длина заголовка запроса (DB).
2. Код устройства (DB). Определяет номер для блочных устройств.
3. Код команды (DB). Здесь хранится номер последней посланной
драйверу команды. Эти коды перечислены в [7.2.3].
4. Статус (DW). Статус устанавливается каждый раз при вызове
драйвера. Если установлен бит 15, то в младших восьми битах находится
код ошибки. Коды ошибок перечислены в [7.2.3].

5. Резервная область (8 байтов). Используется MS DOS.
6. Данные необходимые для работы драйвера (переменной длины).

Низкий уровень.

Вот 5 строк процедуры стратегии устройства. Отмечаем, что две
словные переменные, хранящие значения ES и BX, следуют за инструкцией
RET, как и положено в формате COM.

DEV_STRATEGY: MOV CS:KEEP_ES,ES
MOV CS:KEEP_BX,BX
RET
KEEP_CS DW ?
KEEP_BX DW ?
7.2.3 Создание обработчика прерывания устройства.

Драйвер устройства начинается с двух порций кода, приведенных
в предыдущих разделах. За ними должна следовать соответствующая
процедура обработки прерывания. На самом деле, это неверно, называть
эту процедуру процедурой обработки прерывания, так как она
вовсе не обслуживает прерывание и завершается обычной инструкцией
RET.
Имеется 13 типов функций, которые может выполнять устанавливаемый
драйвер устройства. Когда драйвер вызывается функцией DOS
(скажем функцией 3FH прерывания 21H, которая читает данные из
файла или устройства), то функция помещает кодовый номер от 1 до
13 в однобайтное поле по смещению 2 в заголовке запроса (для
ввода - кодовый номер 5). Затем управление передается процедуре
обработки прерывания драйвера, адоес которой определяется при
просмотре заголовка драйвера [7.2.1]. Эта процедура в первую
очередь восстанавливает ES:BX, с тем чтобы они указывали на заголовок
запроса, а затем читает кодовый номер команды. По этому
коду процедура обработки прерывания вызывает нужную процедуру,
которая выполнит требуемую функцию. Процедура ищется с помощью
13-словной таблицы, содержащей смещения для 13 типов функций.
Функции всегда перечисляются в следующем порядке:

1. INITIALIZE (инициализация)
2. CHECK_MEDIA (проверка носителя)
3. MAKE_BPB
4. IOCTL_IN
5. INPUT_DATA (ввод данных)
6. NONDESTRUCT_IN
7. INPUT_STATUS (статус ввода)
8. CLEAR_INPUT (очистка ввода)
9. OUTPUT_DATA (вывод данных)
10. OUTPUT_VERIFY (проверка вывода)
11. OUTPUT_STATUS (статус вывода)
12. CLEAR_OUTPUT (очистка вывода)
13. IOCTL_OUT

После завершения процедуры, процедура обработки пр

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

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

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

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

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