Жанр: Учеба

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

сторінка №26

и даже однобайтного кода - все определяется
тем, что при повторном чтении файла программа должна понимать
используемый формат. Для соместимости рекомендуется записывать
числа в виде строк.
Необязательно, чтобы каждое число строки было отделено парой
возврат каретки/перевод строки, однако если эта пара опущена, то
программа должна обеспечить способ отделения данных. Например, 10
целых чисел могут быть записаны как 20-байтный элемент данных. С
другой стороны, очень большие элементы данных, такие как параграфы
текста, могут быть разделены на несколько элементов данных
(стандартный текстовый файл представляет из себя документ, разбитый
на строки удобного размера, записанные последовательно).
Поскольку элементы данных имеют переменную длину, то невозможно
узнать где в файле расположен определенный элемент. Поэтому для
того чтобы найти нужный элемент программа должна читать файл,
начиная с начала и отсчитывая нужное число пар возврат каретки/перевод
строки. По этой причине файлы такого формата называют
последовательными. Как правило с диска в память передается весь
такой файл.
Файлы прямого доступа заранее отводят фиксированное место под
каждый элемент данных. Если какой-то элемент данных не занимает
все отведенное пространство, то остаток заполняется пробелами.
Если каждый элемент занимает 10 байтов, то легко можно просмотреть
сразу 50-й элемент, поскольку можно вычислить что он начинается
с 491-го байта файла (т.е. с байта #490, поскольку отсчет
начинается с 0). Как правило связанный набор элементов группируется
в запись. Каждая запись содержит несколько полей, которые
создают набор номеров байтов, начиная с которых пишутся данные
элементы. Например, запись может иметь поля возраст, вес и рост.
Соответствующие поля могут занимать 2, 3 и 5 байтов. Вместе они
образуют запись длиной в 10 байтов. Файл прямого доступа может
состоять из тысяч таких записей. Каждая запись следует непосредственно
за предшествующей без всяких ограничителей, таких как
пары возврат каретки/перевод строки, используемые в последовательных
файлах. При этом записи могут писаться в любом порядке и
можно записать запись 74, хотя запись 73 еще не была записана
(при этом записи 73 отведено дисковое пространство и она будет
содержать те данные, которые случайно оказались в том секторе, в
котором отведено место для этой записи). В отличии от последовательных
файлов файлы прямого доступа остаются на диске. В памяти
присутствуют только определенные записи, с которыми идет работа в
данный момент времени.
Когда для прямого доступа к файлу используется управляющий
блок файла, то системе сообщается размер записи файла (все записи
данного файла должны иметь одинаковую длину). Это позволяет программе
запросить любую запись по номеру, а MS DOS точно вычислит
где эта запись расположена на диске. При работе с файлами прямого
доступа методом дескриптора файла программа должна сама вычислять
положение требуемой записи.
Система хранит файловый указатель для каждого буфера файла. Он
указывает на n-ный байт файла, определяя место в файле, с которого
будет начинаться следующая операция чтения или записи. При
последовательной операции перезаписи файловый указатель первоначально
устанавливается на начало файла и постоянно сдвигается по
мере того, как все новые и новые данные записываются в файл.
Когда данные добавляются к последовательному файлу, то файловый
указатель первоначально устанавливается на конец файла. При доступе
к одной записи в файле прямого доступа положение записи
вычисляется в виде смещения относительно начала файла и указатель
устанавливается равным этому значению; затем нужная запись читается
или пишется. Обычно за файловым указателем следит система,
однако программа может сама управлять им и манипулировать указателем
для своих специальных нужд.
Единственным примером низкого уровня в данном разделе является
чтение/запись одного сектора. Чтение или запись целых файлов
состоит в последовательности таких чтений или записей одного
сектора, программируя микросхему контроллера НГМД заново для
каждого сектора. Полномасштабные файловые операции очень сложны
на этом уровне, что следует хотя бы из больших размеров файла
COMMAND.COM. Однако, изучив обсуждение операций низкого уровня, а
также имея информацию о таблице размещения файлов [5.1.1] и дисковых
каталогах [5.2.1] Вы можете представить как работает дисковая
операционная система.
5.4.1 Программирование контроллера НГМД 765 и микросхемы прямого
доступа к памяти 8237.


Микросхема контроллера НГМД 765 фирмы NEC управляет мотором и
головками накопителя на дискетах и обрабатывает потоки данных,
направляемые в или из дисковых секторов. Один контроллер, установленный
на плате адаптора дисков, может обслуживать до четырех
НГМД. За исключением случаев, связанных с защитой от копирования,
программистам не приходится программировать микросхему контроллера
НГМД прямо. Процедуры работы с дисками, предоставляемые DOS и
BIOS эффективны и удобны, кроме того, очень рисковано писать свои
собственные процедуры, поскольку ошибки в них могут разрушить
дисковый каталог или таблицу размещения файлов, что вызовет полное
разрушение информации на диске.
Нижеследующее обсуждение служит цели дать Вам только общее
представление. Листинг ROM-BIOS, приведенный в конце каждого
технического руководства по MS DOS, содержит код тщательно разработанных
процедур для форматирования дискет, чтения и записи
секторов, а также сброса и получения статуса накопителей. После
того, как Вы усвоите приведенный здесь материал, изучите процедуры
ROM-BIOS для продолжения Вашего образования в области операций
с дисками на низком уровне. Вам потребуется также документация по
микросхеме контроллера НГМД 8272A фирмы Intel, которая аналогична
микросхеме фирмы NEC. В данной документации перечислены прерывания,
генерируемые контроллером НГМД, в то время как в документации
по IBM PC этого списка нет. Информация о микросхеме 8272A
может быть найдена во втором томе Справочника по компонентам
микросистем (Microsystem Components Handbook).
Контроллер НГМД может выполнять 15 операций, из которых здесь
будут обсуждаться только три: операции поиска и чтения или записи
одного сектора. Понимание того как они работают позволит Вам
выполнить любую из оставшихся двенадцати, при условии, что у Вас
будет вышеупомянутая информация. Чтение файла состоит в поиске
его в каталоге [5.2.1], определении его положения на диске с
помощью таблицы размещения файлов [5.1.1] и затем наборе операций
чтения одного сектора. Эта процедура включает 6 шагов:

1. Включение мотора и короткое ожидание, пока он наберет обороты.
2. Выполнение операции поиска и ожидание прерывания, указывающего
на завершение этой операции.
3. Инициализация микросхемы DMA для пересылки данных в память.
4. Посылка команды чтения контроллеру НГМД и ожидание прерывания,
указывающего, что пересылка данных завершена.
5. Получение информации о статусе контроллера НГМД.
6. Выключение мотора.

Контроллер НГМД работает через три порта ввода/вывода. На
самом деле микросхема имеет больше, чем три регистра, но доступ
к большинству из них осуществляется через один порт. Эти три
порта такие:

3F2H регистр цифрового вывода
3F4H регистр статуса
3F5H регистр данных

Первый шаг состоит в доступе к регистру цифрового вывода.
Значение его битов следующее:
биты 1-0 выбор накопителя, где 00 = A
01 = B
10 = C
11 = D
2 0 = сброс контроллера НГМД
3 1 = разрешение прерывания FDC и доступа DMA
7-4 1 = включение мотора накопителя D-A (бит 4 = A)

Это регистр только для записи, поэтому необходимо заботиться обо
всех его битах. В нижеприведенном примере используется накопитель
A, поэтому цепочка битов должна выглядеть 00011100. Такая установка
битов выбирает накопитель A, сохраняет установленным бит 2,
разрешающий работу с НГМД и включает мотор накопителя A. Не сбрасывайте
бит 2 в ноль, так как в этом случае Вам придется производить
перекалибровку накопителя, действие, которое необходимо
очень редко.
"Перекалибровка" накопителя подразумевает возврат его головки
на нулевую дорожку. Эта операция осуществляется посылкой простой
последовательности команд контроллеру НГМД. Контроллер НГМД управляет
текущей позицией головки, за счет запоминания всех изменений
позиции головки после ее начальной установки на нулевую
дорожку. Когда контроллер НГМД сбрасывается, за счет изменения
бита 2 регистра цифрового вывода, то значение текущей позиции
головки устанавливается в ноль, независимо от того, на какой
дорожке находится головка на самом деле, что делает необходимым
перекалибровку. Обычно сброс контроллера НГМД производится только
в случае такой серьезной ошибки накопителя, после которой неизвестно
текущее состояние контроллера НГМД и накопителя.

Отметим, что выбор накопителя и включение его мотора - это
отдельные действия. Контроллер НГМД может иметь доступ только к
одному накопителю в данный момент времени, но мотры могут быть
включены у нескольких. Моторы могут оставаться включенными еще
несколько секунд после завершения обмена данными, в ожидании
следующего доступа к накопителю. Такая стратегия позволяет избежать
потери времени на повторное ожидание пока мотор наберет
скорость. Напротив, мотор нельзя оставлять постоянно включенным,
так как это приведет к преждевременному износу дискет.
Работа микросхемы контроллера НГМД разделяется на три фазы:
командная фаза, фаза выполнения и фаза результата. В командной
фазе один или более байтов посылаются в регистр данных. Последовательность
байтов строго фиксирована и она меняется от команды к
команде. Затем контроллер НГМД выполняет команду и в это время он
находится в фазе выполнения. Наконец, во время фазы результата,
ряд байтов статуса считываются из регистра данных. При этом обязательно,
чтобы не было ошибки в числе передаваемых или считываемых
данных в регистр данных в фазах командной и результата.
Число байтов команды и результата меняется в зависимости от
выполняемой контроллером дисковой операции. В техническом руководстве
по IBM PC приведены данные для всех 15 операций. Первый
байт команды является кодом, определяющим требуемую операцию.
Номер кода содержится в младших 5-ти битах байта и в некоторых
случаях в старших трех битах закодирована добавочная информация.
В большинстве случаев второй байт команды содержит номер накопителя
(0-3) в младших двух битах и номер головки (0 или 1) в бите
2, все остальные биты игнорируются контроллером НГМД. При операции
поиска требуется дополнительно еще только один байт, в котором
должен содержаться номер новой дорожки. Чтение или запись
сектора требует семи дополнительных командных байтов, которые
идентичны в этих двух случаях. Байты с третьего по пятый содержат
текущий номер дорожки, номер головки и номер сектора. За ними
следуют четыре байта, содержащие техническую информацию, необходимую
для контроллера НГМД.
Первый байт этой технической информации относится к числу
байтов в секторе, которое кодируется как 0 для 128, 1 для 256, 2
для 512 и 3 для 1024. Конечно дискеты, созданные в MS DOS имеют
сектора размером 512 байт. Затем идут данные конца дорожки (EOT),
которые дают максимальный номер сектора для цилиндра; это значение
равно 9 для дискет емкостью 360K. Наконец, идет байт дающий
длину сдвига (GPL, равный 2AH) и длину данных (DTL, равный FFH).
Техническое руководство по IBM PC содержит таблицу, в которой
объясняются другие вхожные параметры, например те, которые используются
при форматировании диска. MS DOS хранит четыре технических
параметра в памяти, в специальной таблице параметров,
называемой базой диска (disk base). Вектор прерывания 1EH указывает
на эту таблицу. Четыре значения хранятся в том порядке, в
котором они должны быть переданы контроллеру НГМД, начиная со
смещения 3. В следующей таблице показана командная последовательность
для трех операций, используемых в нижеприведенном примере.
В цепочках битов черех X обозначены биты, значение которых несущественно,
через H - номер головки, а через DD - номер накопителя.


Операция # байта Функция Установка для головки 0
дорожки 15, сектора 1

Поиск 1 номер кода 00001111 1FH
2 головка и накопитель 00H
XXXXXHDD

Чтение 1 номер кода 01100110 66H
сектора 2 головка и накопитель 00H
XXXXXHDD
3 номер дорожки 0FH
4 номер головки 00H
5 номер сектора 01H
6 байтов в секторе 02H
7 конец дорожки 09H
8 длина сдвига 1AH
9 длина данных FFH

Запись 1 номер кода 01000101 45H
сектора 2-9 те же, что и для чтения сектора

Вы должны быть уверены, что контроллер НГМД готов прежде чем
Вы пошлете или прочитаете байт из регистра данных. Биты 7 и 6
регистра статуса предоставляют эту информацию. Вот значение битов
этого регистра:

биты 3-0 1 = накопитель D-A в режиме поиска
4 1 = контроллер НГМД выполняет команду чтения/записи
5 1 = контроллер НГМД не в режиме DMA
6 1 = регистр данных контроллер НГМД готов к приему
данных
0 = готов к посылке данных
7 1 = контроллер НГМД готов к посылке или приему данных
Перед началом дисковых операций неплохо проверить, что бит 6
равен нулю, индицируя что контроллер НГМД ожидает команду. Если
он ожидает посылки данных, то произошла ошибка. Когда байт данных
посылается в регистр данных, то бит 7 регистра статуса становится
равным нулю; продолжайте чтение регистра до тех пор, пока бит не
изменится обратно на 1, а затем посылайте следующий байт команды.
Аналогично, проверяйте этот бит статуса перед чтением байта статуса
в фазе результата. Нижеприведенный пример кончается двумя
процедурами, которые выполняют эти функции.
Когда операция поиска завершена, то контроллер НГМД инициирует
прерывание 6, прерывание от НГМД. Хотя так же просто можно узнать
об окончании операции поиска проверяя регистр статуса, в примере
это делается за счет обработки прерывания. Когда происходит прерывание,
то обработчик прерывания BIOS устанавливает бит 7 байта
статуса поиска в области данных BIOS, расположенного по адресу
0040:003E. Это единственный результат обработки прерывания. Можно
проверять этот байт до тех пор, пока бит 7 не будет установлен, а
затем переходить к следующему шагу операции чтения сектора.
Следующий шаг состоит в инициализации микросхеиы прямого доступа
к памяти 8237. Эта микросхема занимается обменом данных
между периферийными устройствами и памятью, работой, которой
может заниматься также процессор. На самом деле, в PCjr, где нет
микросхемы DMA, контроллер НГМД посылает данные прямо в процессор,
который в свою очередь пересылает их в память. Тактовая
частота процессора адекватна этой задаче, однако при пересылке
данных все прерывания должны быть запрещены, с тем чтобы не
происходило потери данных. Это означает, что в PCjr при передаче
данных ввод с клавиатуры или из модема запрещен. Прерывания таймера
также игнорируются, однако впоследствии счетчик времени
суток обновляется специальной процедурой, использующей канал 1
микросхемы таймера 8253 для подсчета импульсов, прошедших за
время дисковых операций. Все остальные модели IBM PC имеют микросхему
DMA, поэтому процессор свободен при передаче данных.
IBM PC и XT используют 4-хканальную микросхему DMA 8237. Канал
0 предназначен для "освежения" памяти (memory refresh); он постоянно
восстанавливает заряд ячеек оперативной памяти. Если Вы
будете работать по этому каналу, то это приведет скорее всего к
краху машины. Канал 2 предназначен для дисковых операций, а два
другие канала, с номерами 1 и 3, доступны (через разъемы расширения)
для дополнительного оборудования. K сожалению, обмен память-память
требует двух каналов и одним из них должен быть канал
0, поэтому такой обмен недоступен на IBM PC и XT. Однако AT имеет
7 каналов прямого доступа к памяти и DMA автоматически используется
инструкциями MOVS, существенно увеличивая производительность.

Перед инициализацией канала программа должна послать в микросхему
код, сообщающий будет ли происходить чтение или запись в
контроллер НГМД. Этот однобайтный код равен 46H для чтения и 4AH
- для записи. Этот код должен быть послан в каждый из двух портов
с адресами 0BH и 0CH.
Каждый канал микросхемы 8237 использует три регистра. Один
16-битный регистр, регистр счетчика, содержит число передаваемых
байтов данных. Его величина должна быть на единицу меньше, чем
требуемое число байтов. Для канала 2 доступ к этому регистру
осуществляется через порт 05H; пошлите в него два последовательных
байта, причем сначала младший байт.
Остальные два регистра содержат адрес буфера в памяти, с которым
будет происходить обмен данными. Этот адрес задается как
20-битное число, поэтому, например, адрес 3000:ABCD задается как
3ABCD. Младшие 16 битов посылаются в регистр адреса, который для
канала 2 имеет адрес порта 04H. Сначала посылается младший байт.
Старшие 4 бита идут в регистр страницы, который для канала 2
имеет адрес порта 81H. Когда байт посылается по этому адресу, то
имеют значение только 4 младших бита. Если буфер создается в
сегменте данных, то Вам нужно сложить значение DS и смещение
буфера для получения 20-битного значения. Сложение может привести
к переносу в значение регистра страницы. Например, если DS равен
1F00H, а смещение буфера - 2000H, то результирующий адрес будет
равен 1F00 + 2000 = 21000H.

После того как эти три регистра установлены, пошлите 2 в порт
с адресом 0AH, чтобы разрешить канал 2. Это оставляет микросхему
DMA в состоянии ожидания данных от накопителя, а программа должна
немедленно начать посылку командных байтов в контроллер НГМД. Вот
краткий перечень шагов при программировании микросхемы 8237:

1. Послать код чтения или записи.
2. Вычислить 20-битный адрес памяти буфера, в который будут посланы
данные, и заслать его в регистры адреса и страницы канала 2.
3. Поместить значение числа передаваемых байтов (минус 1) в регистр
счетчика канала 2.
4. Разрешить канал.

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

Операция # байта Функция

Поиск нет

Чтение 1 байт статуса 0
2 байт статуса 1
3 байт статуса 2
4 номер дорожки
5 номер головки
6 номер сектора
7 код байтов на сектор (0-3)

Запись 1-7 то же, что и для чтения

Вот значения битов трех байтов статуса:

Байт статуса 0:
биты 7-6 00 = нормальное завершение
01 = начато выполнение, не может завершиться
10 = неверная команда
11 = невыполнено, т.к. накопитель не подключен
5 1 = выполняется операция поиска
4 1 = ошибка накопителя
3 1 = накопитель не готов
2 номер выбранной головки
1-0 номер выбранного накопителя
Байт статуса 1:
бит 7 1 = номер затребованного сектора больше максимума
6 не используется (всегда 0)
5 1 = ошибка передачи данных
4 1 = переполнение данных
3 не используется (всегда 0)
2 1 = не может найти или прочитать сектор
1 1 = не может записать из-за защиты от записи
0 1 = отсутствует адресная метка при форматизации

Байт статуса 2:
бит 7 не используется (всегда 0)
6 1 = встречена адресная метка удаленных данных
5 1 = ошибка циклического контроля четности данных
4 1 = проблема с идентификацией дорожки
3 1 = условие команды сканирования удовлетворено
2 1 = условие команды сканирования не удовлетворено
1 1 = плохая дорожка
0 1 = отсутствует адресная метка

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

Байт статуса 3:
бит 7 1 = ошибка накопителя
6 1 = диск защищен от записи
5 1 = накопитель готов
4 1 = текущая позиция головки известна
3 1 = дискета двухсторонняя
2 номер выбранной головки
1-0 номер выбранного накопителя

Вы можете получить этот четвертый байт статуса, послав контроллеру
НГМД команду "Определи статус накопителя" (Sense Drive Status).
Первый байт этой двухбайтной команды это число 4, а второй
байт содержит номер накопителя в битах 1 и 0, и номер головки в
бите 2. Единственным результатом этой операции является байт
статуса 3. Отметим, что после каждой дисковой операции, если Вы
используете процедуры DOS или BIOS, результирующие байты статуса
помещаются в область данных BIOS, начиная с адреса 0040:0042.
Операционная система хранит также байт статуса дискеты по адресу
0040:0041, значение битов которого следующее:

Значение бита Ошибка

80H нет ответа на присоединение накопителя
40H операция поиска неуспешна
20H ошибка контроллера НГМД
10H ошибка данных при чтении (ошибка CRC)
09H попытка прямого доступа за границу 64K
08H переполнение DMA
04H затребованный сектор не найден
02H не найдена адресная марка
01H послана неверная команда контроллеру НГМД
В заключение приводим полную процедуру чтения диска, которая
читает один сектор данных с дорожки 12, сектор 1, сторона 0 накопителя
A в 512-байтный буфер в сегменте данных. Семь байтов статуса
также считываются в отведенный буфер. Эта процедура предназначена
для IBM PC и XT. Вам необходимо воспользоваться техническим
руководством по PCjr или AT, если Вы работаете на этих машинах.
На AT надо изменить циклы задержки, чтобы учесть большую
скорость процессора, и не забывать добавлять оператор JMP SHORT
$+2 между последовательными командами OUT, относящимися к одному
и тому же порту. Работа с фиксированным диском осуществляется
аналагично, поэтому Вы можете перенести изученные Вами концепции
на другие ситуации.

;---в сегменте данных
BUFFER DB 512 DUP(?)
STATUS_BUFFER DB 7 DUP(?)

SECTOR_READ PROC ;начало процедуры чтения одного сектора
;---включение мотора
STI ;прерывания должны быть разрешены
MOV DX,3F2H ;адрес регистра цифрового вывода
MOV AL,28 ;устанавливаем биты 2, 3 и 4
OUT DX,AL ;посылаем команду
;---ожидаем пока мотор наберет скорость (около 1/2 сек.)
MOV CX,3500 ;счетчик цикла задержки (для IBM PC и XT)
MOTOR_DELAY: LOOP MOTOR_DELAY ;ожидаем 1/2 секунды
;---выполняем операцию поиска
MOV AH,15 ;номер кода
CALL OUT_FDC ;посылаем контроллеру НГМД
MOV AH,0 ;номер накопителя
CALL OUT_FDC ;посылаем контроллеру НГМД
MOV AH,12 ;номер дорожки
CALL OUT_FDC ;посылаем контроллеру НГМД
CALL WAIT_INTERRUPT ;ожидаем прерывания от НГМД
;---ожидаем установки головки (25 мсек.)
MOV CX,1750 ;счетчик цикла задержки (для IBM PC и XT)
WAIT_SETTLE: LOOP WAIT_SETTLE ;ожидаем 25 мсек.
;---начинаем инициализацию микросхемы DMA
MOV AL,46H ;код чтения данных контроллера НГМД
OUT 12,AL ;посылаем код по двум адресам
OUT 11,AL ;
;---вычисляем адрес буфера
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 ;посылаем контроллеру НГМД
MOV AH,0 ;номера головки и накопителя
CALL OUT_FDC ;посылаем контроллеру НГМД
MOV AH,12 ;номер дорожки
CALL OUT_FDC ;посылаем контроллеру НГМД
MOV AH,0 ;номер головки
CALL OUT_FDC ;посылаем контроллеру НГМД
MOV AH,1 ;номер записи
CALL OUT_FDC ;посылаем контроллеру НГМД
MOV AH,ES:[BX]+3 ;код размера сектора
CALL OUT_FDC ;посылаем контроллеру НГМД
MOV AH,ES:[BX]+4 ;номер конца дорожки
CALL OUT_FDC ;посылаем контроллеру НГМД
MOV AH,ES:[BX]+5 ;длина сдвига
CALL OUT_FDC ;посылаем контроллеру НГМД
MOV

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

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

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

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

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