Асинхронные файловые операции
Классическая схема чтения/записи файла выглядит следующим образом: вызвать системную функцию чтения/записи, указать ей параметры доступа к файлу, дождаться конца ее выполнения. В данном случае по окончанию работы функции гарантируется, что операция чтения/записи окончена. Это так называемый случай синхронной файловой операции.
Асинхронные файловые операции реализованы по другому принципу. В этом случае функция чтения/записи лишь инициирует соответствующую процедуру, которая ставит задачу чтения/записи в очередь операций ввода/вывода, которой в дальнейшем занимается ядро ОС. Это означает, что функция чтения/записи возвращает управление немедленно, в то время как сама операция может выполняться еще некоторое время после этого в отдельном потоке. Асинхронные файловые операции, как правило, ускоряют работу приложения, однако в большинстве случаев необходимо убедиться, что операция завершена и все данные переданы. Поскольку реализация инициированной операции программе не видна, нужны средства, позволяющие определить текущий статус операции.
Реализация самих асинхронных операций, а также механизмы отслеживания результата их выполнения различны для разных операционных систем.
Linux
Асинхронные файловые операции в Linux реализованы в библиотеке aio (asynchronous input-output).
Все функции, реализующие асинхронный ввод-вывод, используют специальную структуру aiocb. Она имеет следующие поля:
· int aio_fildes – дескриптор файла;
· off_t aio_offset – смещение, по которому будет осуществлено чтение/запись;
· volatile void *aio_buf – адрес буфера в памяти;
· size_t aio_nbytes – число передаваемых байт;
· int aio_reqprio – величина понижения приоритета;
· struct sigevent aio_sigevent – сигнал, отвечающий за синхронизацию операции, т.е. за оповещение о ее окончании;
· int aio_lio_opcode – запрошенная операция.
Для асинхронных операций чтения/записи данных используются функции aio_read() и aio_write(), которые принимают адрес сформированной структуры aiocb в качестве единственного аргумента. Если запрос был принят и поставлен в очередь, функции возвращают 0, в противном случае возвращается -1.
Определить статус выполняемой операции можно либо при помощи поля aio_sigevent структуры aiocb, либо при помощи функции aio_error(). Если операция была завершена успешно, функция вернет нулевой результат. Если выполнение операции все еще продолжается, возвращаемое значение будет равно EINPROGRESS.
Windows
Выполнение асинхронных файловых операций в Windows схоже с библиотекой aio в Linux. Аналогом структуры aiocb в Windows является структура OVERLAPPED, имеющая следующие поля:
· Internal – код ошибки для операции;
· InternalHigh – количество переданных байт. Устанавливается после успешного завершения операции;
· Offset – младшее слово смещения в файле, с которого производится операция;
· OffsetHigh – старшее слово смещения;
· hEvent – дескриптор синхронизирующего события.
Для асинхронных файловых операций чтения/записи могут использоваться два семейства функций: стандартные и расширенные.
Во-первых, стандартные функции ReadFile() и WriteFile() могут работать в асинхронном режиме. Для этого надо передать им последним параметром указатель на сформированную структуру OVERLAPPED. Основным способом узнать, закончилась ли операция асинхронного ввода-вывода, в данном случае является использование поля hEvent. Его необходимо проинициализировать дескриптором существующего события. Перед началом операции функции ReadFile()/WriteFile() переводят его в несигнальное состояние. Переход же события в сигнальное состояние свидетельствует о том, что операция была завершена.
Во-вторых, возможно так же использование расширенного семейства функций ReadFileEx()/WriteFileEx(). Этим функциям также надо передавать указатель на структуру OVERLAPPED, однако поле hEvent ими игнорируется. Вместо этого последним параметром эти функции принимают указатель на процедуру обратного вызова. Иными словами, можно сообщить Ex-функциям, какую функцию следует вызвать по завершении асинхронной операции.
Динамические библиотеки
В программировании часто возникает ситуация, когда запущенные приложения используют одни и те же функции или участки кода. Многократно продублированные таким образом, фрагменты кода приводят к избыточному использованию памяти. Очевидное решение: выделить общие функции в отдельные библиотеки, загружая в память лишь одну их копию, которая используется всеми приложениями. Такие библиотеки называются разделяемыми или динамически подключаемыми. Рассмотрим детали реализации механизма динамических библиотек в различных операционных системах.
Linux
Создание динамической библиотеки в Linux полностью аналогично обычному бинарному исполняемому файлу и происходит по следующей схеме:
· компиляция исходного кода в объектный файл;
· линковка объектного файла в результирующий формат.
При помощи ключа –shared можно указать компилятору, что на выходе он должен породить динамическую библиотеку:
gcc –shared –o mylibrary.so mylibrary.o
Имя результирующей библиотеки в данном случае выглядит как mylibrary.so. Расширение .so (shared object) является обязательным.
Подключение динамической библиотеки к приложению можно осуществить двумя способами. Во-первых, библиотеку можно указать при линковке приложения с помощью ключа gcc –l, в этом случае при загрузке приложения система будет автоматически подгружать указанную библиотеку. Однако нередки случаи, когда для работы приложения нет необходимости постоянно иметь в памяти загруженную библиотеку либо когда из библиотеки используется лишь небольшое количество функций. В такой ситуации используется механизм динамического подключения библиотеки.
Для загрузки библиотеки в произвольный момент времени служит функция dlopen(). Первым параметром она принимает имя загружаемой библиотеки, а вторым – флаг загрузки, который может иметь одно из следующих значений:
· RTLD_LAZY – разрешение всех неопределенных ссылок в коде будет произведено при непосредственном обращении к загружаемым функциям;
· RTLD_NOW – разрешение всех символов производится сразу при загрузке библиотеки;
· RTLD_GLOBAL – внешние ссылки, определенные в библиотеке, будут доступны загруженным после библиотекам. Флаг может указываться через ORс перечисленными выше значениями.
Функция dlopen() возвращает дескриптор загруженной библиотеки. Для непосредственной работы с функциями и переменными, определенными в библиотеке, необходимо получить их адрес в памяти. Это можно сделать с помощью функции dlsym(), передав ей дескриптор подключенной библиотеки и имя соответствующей функции.
После завершения работы с библиотекой ее можно выгрузить из памяти функцией dlclose().
Windows
Создание динамически подключаемых библиотек (DLL – Dynamically Linked Library) практически в любой среде разработки Windows сводится к выбору соответствующего шаблона при создании проекта, так что нет необходимости останавливаться на этом пункте подробнее. Гораздо больший интерес представляет динамическое подключение библиотеки.
Загрузка библиотеки производится функцией LoadLibrary(), которая принимает строку с именем dll-файла и возвращает дескриптор загруженной библиотеки. Получить адрес переменной (или функции) из библиотеки можно функцией GetProcAddress(), сообщив ей дескриптор библиотеки и имя соответствующего символа. Выгрузка библиотеки осуществляется при помощи функции FreeLibrary().
Задание
Задание выполняется в двух вариантах: под Linux и Windows. В каталоге имеется набор текстовых файлов. Необходимо разработать приложение из двух потоков, которые работают по следующей схеме:
1) первый поток (поток-читатель) асинхронным образом считывает содержимое одного файла;
2) поток-читатель уведомляет второй поток (поток-писатель) о том, что содержимое файла прочитано и может быть передано писателю;
3) поток-писатель получает от потока-читателя содержимое файла и асинхронным образом записывает полученную строку в конец выходного файла;
4) поток-читатель получает уведомление от потока-писателя о том, что строка записана в выходной файл и можно приступать к чтению следующего файла;
5) процедура повторяется с п.1, пока не закончится список файлов.
В результате должна быть произведена конкатенация (объединение) входных текстовых файлов в один результирующий.
Функции чтения-записи должны быть выделены в динамическую библиотеку, подключены на этапе выполнения программы и выгружены после отработки основного цикла.
Лабораторная работа №6
Разработка менеджера памяти
Цель работы: ознакомиться с основами функционирования менеджеров памяти, реализовать собственный алгоритм учета памяти.
Общие сведения
В отличие от предыдущих лабораторных работ, которые сводились к изучению соответствующих библиотечных функций, данная работа посвящена, в первую очередь, архитектурному проектированию собственной системы, т.е. собственной реализации стандартных функций.
При программировании различных приложений часто возникает необходимость динамически выделить участок памяти заданного размера, получить указатель на него, изменить размер этого участка, освободить выделенную память и т.д. Для программиста эти операции сводятся к вызову функций malloc(), realloc(), free(), использованию операторов new и delete и т.д. Непосредственным же манипулированием участками памяти занимается так называемый менеджер памяти.
В стандартных библиотеках С/С++ менеджер памяти представляет собой реализацию перечисленных выше функций и операторов. Однако часто возникает ситуация, когда стандартного функционала менеджера памяти недостаточно. К примеру, может потребоваться индексирование выделенных участков памяти, их дефрагментация, сборка мусора и т.д. В этих случаях наиболее логично написание собственного менеджера памяти.
Традиционно выделяют следующие составные компоненты менеджера памяти:
· управляющие структуры, описывающие размещение выделенных областей памяти;
· набор функций, позволяющих оперировать выделением памяти (аналог malloc(), free() и т.д.);
· дополнительные внутренние функции и компоненты, осуществляющие сервисные операции (автоматическая сборка мусора, например).
Для выполнения задания необходимо также рассмотреть следующие сервисные операции:
· Сборка мусора – автоматическое освобождение менеджером памяти неиспользуемых участков памяти принудительно или в фоновом режиме. При этом подразумевается отсутствие функции free() (оператора delete) или совместная с ней работа. Как правило, неиспользуемым считается участок памяти, на который отсутствуют ссылки.
· Дефрагментация – процесс упорядочивания выделенных областей памяти и устранения пустого неиспользуемого пространства между ними. Фрагментация оперативной памяти возникает при последовательном выделении и освобождении памяти. Это означает, что возможна ситуация, когда общего объема свободной памяти достаточно для выделения, однако вся память сосредоточена в небольших пустых областях между выделенными участками. В такой ситуации невозможно выделить непрерывный участок памяти требуемого размера, несмотря на то, что нужный объем памяти в принципе помечен как свободный.
· Свопинг – процесс сброса на жесткий диск наименее используемых участков памяти для их освобождения под другие нужды. Это происходит, когда общего объема памяти становится недостаточно, и ведет к замедлению работы программы. При последующем обращении к освобожденному таким образом участку менеджер памяти должен считывать его с жесткого диска и вновь выделять для него память нужного объема.
Задание
Задание выполняется в одном варианте под любую операционную систему (по выбору студента).
Разработать собственный менеджер памяти, реализующий аналоги функций malloc() и free(). Архитектура менеджера и детали реализации остаются на усмотрение студента. Предусмотреть дополнительную функциональность в одном из следующих вариантов:
· динамическое изменение размеров выделенной области (realloc());
· автоматическая сборка мусора;
· дефрагментация;
· механизм свопинга при превышении максимально доступной памяти.
Лабораторная работа №7
Эмулятор файловой системы
Цель работы: ознакомиться с основами функционирования и проектирования файловых систем, разработать собственную файловую систему.
Общие сведения
Под файловой системой традиционно понимается способ хранения данных в виде файлов на жестком диске, внутренняя архитектура распределения данных, а также алгоритмы манипулирования файлами и их составными компонентами.
При проектировании файловой системы перед программистом обычно встает необходимость поддержки:
· иерархии размещения файлов и каталогов. Как правило, файлы хранятся в древовидной системе каталогов, которую надо спроецировать в физическое представление на конкретном носителе;
· алгоритмов добавления, удаления, модификации файлов;
· быстрого доступа как к описанию файлов (имя, атрибуты доступа и т.д.), так и к произвольным их участкам.
На сегодняшний момент существует большое количество файловых систем как общего назначения, так и специализированных. Обычно их классифицируют следующим образом:
· файловые системы для носителей с произвольным доступом (жестких дисков), например: ext2, ext3, ReiserFS, FAT32, NTFS, XFS и др. В Unix-системах обычно применяются первые три, использование FAT32 и NTFS характерно для ОС семейства Windows;
· файловые системы для носителей с последовательным доступом (магнитных лент): QIC и др.;
· файловые системы для оптических носителей: ISO9660, ISO9690, HFS, UDF и др.;
· виртуальные файловые системы: AEFS и др.;
· сетевые файловые системы: NFS, SMBFS, SSHFS и др.
В качестве примера рассмотрим основные принципы организации файловых систем Unix (ext2, ext3, ReiserFS и др.).
Основные компоненты физического представления файловой системы Unix:
· суперблок – область на жестком диске, содержащая общую информацию о файловой системе;
· массив индексных дескрипторов – содержит метаданные всех файлов файловой системы. Каждый индексный дескриптор (inode) содержит информацию о статусе файла и его размещении. Один дескриптор является корневым, и через него производится доступ ко всей структуре файловой системы. Размер массива дескрипторов фиксирован и задается при создании файловой системы;
· блоки хранения данных – блоки, в которых непосредственно хранится содержимое файлов. Ссылки на блоки хранятся в индексном дескрипторе файла.
В суперблоке хранится большое количество служебной информации. Особый интерес для нас представляет количество свободных блоков и свободных индексных дескрипторов, размер логического блока файловой системы, список номеров свободных индексных дескрипторов и список адресов свободных блоков.
Два последних списка по понятным причинам могут занимать довольно большое пространство, поэтому их хранение непосредственно в суперблоке непрактично. Эти списки содержатся в отдельных блоках данных, на первый из которых имеется ссылка в суперблоке. Блоки данных организованы в виде списка; каждый блок, входящий в его состав, первым своим элементом указывает на следующий блок.
Индексный дескриптор ассоциирован с одним файлом и содержит его метаданные, т.е. информацию, которая может потребоваться для доступа к нему. Основной интерес для нас составляет физическое представление файла на жестком диске с учетом того, что файл может занимать довольно большой объем, т.е. дробиться на небольшие блоки данных.
Каждый дескриптор содержит 13 указателей. Первые 10 указателей ссылаются непосредственно на блоки данных файла. Если файл большего размера, то 11-й указатель ссылается на первый косвенный блок (indirection block) из 128 (256) ссылок на блоки данных. В случае, если и этого недостаточно, 12-й указатель, в свою очередь, ссылается на дважды косвенный блок, содержащий 128 (256) ссылок на косвенные блоки. Наконец последний, 13-й указатель ссылается на трижды косвенный блок из 128 (256) ссылок на дважды косвенные блоки. Количество элементов в косвенном блоке зависит от его размера.
Следует отметить, что в Unix нет четкого разделения на файлы и директории. Индексный дескриптор файла содержит поле тип файла, в котором указывается, что именно представляет собой данный файл. В числе возможных вариантов этого поля – обычный файл, директория, специальный файл устройства, канал (pipe), связь (link) или сокет.
Подобная архитектура файловой системы позволяет оптимальным образом разрешить перечисленные выше проблемы и получить быстрый и удобный доступ к файлам и директориям, а также их метаинформации.
Для получения более подробного представления о внутреннем устройстве файловых систем рекомендуется также ознакомиться с системами FAT32 и NTFS.
Задание
Задание выполняется в одном варианте под любую операционную систему (по выбору студента) и выполняется на протяжении 4 академических часов (двух аудиторных занятий).
Необходимо разработать собственную модель файловой системы, в которой физический носитель будет эмулироваться файлом фиксированного размера. Архитектура файловой системы остается на усмотрение студента. В результате выполнения задания должны быть реализованы следующие компоненты:
· библиотека функций по добавлению, удалению и модификации файлов;
· простой файловый менеджер, основанный на данной библиотеке.
Все изменения, внесенные в файловую систему (иерархия директорий, файлы, их атрибуты), должны сохраняться в эмулирующем файле и быть доступными при последующем запуске приложения.
Литература
1. Вахалия, Ю. UNIX изнутри / Ю. Вахалия. – СПб. : Питер, 2003.
2. Гордеев, А. В. Операционные системы: учебник для вузов / А. В. Гордеев. – Спб. : Питер, 2005.
3. Карпов, В. Е. Основы операционных систем. Курс лекций: учебное пособие / В. Е. Карпов, К. А. Коньков. – М. : Интернет-университет информационных технологий, 2004.
4. Робачевский, А. М. Операционная система UNIX / А. М. Робачевский, С. А. Немнюгин, О. Л. Стесик. – СПб. : БХВ-Петербург, 2005.
5. Рочкинд, М. Программирование для UNIX / М. Рочкинд. – СПб. : БХВ-Петербург, 2005.
6. Руссинович, М. Внутреннее устройство Microsoft Windows: Windows Server 2003, Windows XP и Windows 2000 / М. Руссинович, Д. Соломон. – М. : ИТД “Русская редакция”; СПб. : Питер, 2005.
7. Столлингс, В. Операционные системы / В. Столлингс. – М. : ИД “Вильямс”, 2004.
8. Таненбаум, Э. Современные операционные системы / Э. Таненбаум. – СПб. : Питер, 2004.
9. Харт, Д. Системное программирование в среде Windows / Д. Харт. – М. : ИД “Вильямс”, 2005.
Св. план 2008, поз. 63.
Учебное издание
Супонев Виктор Алексеевич
Уваров Андрей Александрович
Прытков Валерий Александрович
Системное программное обеспечение ЭВМ
Лабораторный практикум
для студентов специальности 1 – 40 02 01
«Вычислительные машины, системы и сети»
всех форм обучения
В 2-х частях