Если максимальная производительность не требуется, то более простой способ совершать преобразования данных — это выполнять их в пространстве пользовательских процессов посредством ggate (GEOM gate). К недостаткам следует отнести невозможность простого переноса кода в ядро.
Класс GEOM выполняет преобразования данных. Эти преобразования могут быть скомпонованы друг с другом в виде дерева. Экземпляр класса GEOM называют geom.
В каждом классе GEOM есть несколько ''методов класса'', которые вызываются когда экземпляра класса нет в наличии (или же они не привязаны к конкретному экземпляру класса).
.init
вызывается тогда, когда системе GEOM
становится известно о классе GEOM (например, когда загружается модуль ядра).
.fini
будет вызван в случае отказа GEOM системы от
класса (например, при выгрузке модуля).
.taste
вызывается, когда в системе появляется
новый класс или поставщик geom (''provider''). Если соответствие найдено, то эта
функция обычно создает и запускает экземпляр geom.
.destroy_geom
вызывается при необходимости
разрушить экземпляр geom.
.ctlconf
будет вызван, когда пользователь запросит
изменение конфигурации существующего экземпляра geom
Также определены функции событий GEOM, которые копируются в экземпляр geom.
Поле .geom
в структуре g_class
— это список (LIST) экземпляров geom,
реализованных из класса.
Эти функции вызываются из g_event потока ядра.
''softc'' — это устаревший термин для ''приватных данных драйвера'' (''driver private data''). Название вероятней всего происходит от устаревшего термина ''software control block''. В системе GEOM softc это структура (точнее: указатель на структуру) которая может быть присоединена к экземпляру geom и может содержать приватные данные экземпляра. У большинства классов GEOM есть следующие члены:
struct g_provider *provider
: ''поставщик geom''
предоставляемый данным экземпляром geom
uint16_t n_disks
: Количество потребителей geom
(''consumer''), обслуживаемых данным экземпляром geom
struct g_consumer **disks
: Массив struct g_consumer*
. (Невозможно обойтись одинарным
указателем, потому что система GEOM создает для нас структуры struct
g_consumer*)
Структура softc
содержит состояние экземпляра
geom. У каждого экземпляра есть свой softc.
Формат метаданных в той или иной мере зависит от конкретного класса, но обязан начинаться с:
16-байтного буфера для подписи — строки с завершающим нулем (обычно это имя класса)
uint32 идентификатора версии
Подразумевается, что классы geom знают как обращаться с метаданными с идентификаторами версий ниже, чем их собственные.
Метаданные размещаются в последнем секторе поставщика geom (поэтому обязаны целиком умещаться в нем).
(Все это зависит от реализации, но весь существующий код работает подобно описанному и поддерживается библиотеками.)
Последовательность событий следующая:
пользователь запускает служебную программу geom(8)
программа решает каким классом geom ей придется управлять и ищет библиотеку geom_CLASSNAME.so (которая обычно находится в /lib/geom).
она открывает библиотеку при помощи dlopen(3), извлекает вспомогательные функции и определения параметров командной строки.
Вот так происходит создание/маркирование нового экземпляра geom:
geom(8) ищет команду в
аргументах командной строки (обычно это label
) и
вызывает вспомогательную функцию.
Вспомогательная функция проверяет параметры и собирает метаданные, которые записываются во все вовлеченные поставщики geom.
Это ''повреждает (spoil)'' существующие экземпляры geom (если они были) и порождает новый виток ''тестирования'' поставщиков geom. Целевой класс geom опознает метаданные и активирует экземпляр geom.
(Приведенная выше последовательность событий зависит от конкретной реализации, но весь существующий код работает подобно описанному и поддерживается библиотеками.)
Вспомогательная библиотека geom_CLASSNAME.so
экспортирует структуру class_commands
, которая
является массивом элементов struct g_command
. Эти
команды одинакового формата и выглядят следующим образом:
команда [-опции] имя_geom [другие]
Общими командами являются:
label — записать метаданные в устройства, чтобы они могли быть опознаны в процессе тестирования и использованы в соответствующих экземплярах geom
destroy — разрушить метаданные, за которым последует разрушение экземпляров geom
Общие опции:
-v : детальный вывод
-f : принудить
Некоторые операции, к примеру маркирование метаданными и разрушение метаданных
могут быть выполнены из пространства пользовательских процессов. Для этого,
структура g_command
содержит поле gc_func
, которое может быть установлено на функцию (в том-же
.so), которая будет вызвана для обработки команды. В
случае, когда gc_func
равно NULL, команда будет
передана модулю ядра: функции .ctlreq
класса GEOM.
У экземпляров классов GEOM есть внутренние данные, которые хранятся в структурах softc, а также есть некоторые функции, посредством которых они реагируют на внешние события.
Функции событий:
.access
: просчитывает права доступа
(чтение/запись/исключительный доступ)
.dumpconf
: возвращает информацию о экземпляре
geom; формат XML
.orphan
: вызывается, когда отсоединяется любой из
низлежащих поставщиков geom
.spoiled
: вызывается, когда производится запись в
низлежащий поставщик geom
.start
: обрабатывает ввод/вывод
Эти функции вызываются из ядерного потока g_down
и
в этом контексте не может быть блокировок (поищите определение ''блокировка'' в
других источниках), что немного ограничивает свободу действий, но способствует
быстроте обработки.
Из вышеупомянутых, наиболее важной и выполняющей полезную работу функцией
является .start
(), которая вызывается всякий раз,
когда поставщику geom, управляемому экземпляром класса, приходит запрос BIO.
Системой GEOM в ядре ОС создаются и используются три потока выполнения (kernel threads):
g_down : Обрабатывает запросы, приходящие от высокоуровневых сущностей (таких, как запросы из пространства пользовательских процессов) на пути к физическим устройствам
g_up : Обрабатывает ответы от драйверов устройств на запросы, выполненные высокоуровневыми сущностями
g_event : Отрабатывает в остальных случаях, как-то создание экземпляра geom, просчитывание прав доступа, события ''повреждения'' и т.п.
Когда пользовательский процесс запрашивает ''прочитать данные X по смещению Y файла'', происходит следующее:
Файловая система преобразует запрос в экземпляр структуры bio и передает его системе GEOM. Файловая система ''знает'', что экземпляр geom должен обработать запрос, так как файловые системы размещаются непосредственно над экземпляром geom.
Запрос завершается вызовом функции .start
() в
потоке g_down и достигает верхнего экземпляра geom.
Верхний экземпляр geom (например, это секционировщик разделов (partition
slicer)) определяет, что запрос должен быть переадресован нижестоящему экземпляру
geom (к примеру, драйверу диска). Вышестоящий экземпляр geom создает копию запроса
bio (запросы bio ВСЕГДА
копируются при передаче между экземплярами geom при помощи g_clone_bio
()!), изменяет поля смещения и целевого
поставщика geom и запускает на обработку копию при помощи функции g_io_request
()
Драйвер диска также получает запрос bio, как вызов функции .start
() в потоке g_down. Драйвер
обращается к контроллеру диска, получает блок данных и вызывает функцию g_io_deliver
() используя копию запроса bio
Теперь, извещение о завершении bio ''всплывает'' в потоке g_up. Сначала в потоке g_up
вызывается функция .done
() секционировщика разделов,
последний использует полученную информацию, разрушает клонированный экземпляр
структуры bio посредством g_destroy_bio
() и вызывает
g_io_deliver
() используя первоначальный
запрос
Файловая система получает данные и передает их пользовательскому процессу
За информацией о том, как данные передаются в структуре bio
между экземплярами geom, смотрите g_bio(9) (обратите
внимание на использование полей bio_parent
и bio_children
).
Важный момент в том, что НЕЛЬЗЯ ДОПУСКАТЬ БЛОКИРОВОК В ПОТОКАХ G_UP И G_DOWN. Вот неполный перечень того, что нельзя делать в этих потоках:
Вызывать функции msleep
() или tsleep
().
Использовать функции g_write_data
() и g_read_data
(), так как они блокируются в момент обмена
данными с потребителями geom.
Ожидать ввод/вывод.
Вызывать malloc(9) и
uma_zalloc
() с установленным флагом M_WAITOK
.
Использовать sx(9)
Это ограничение на код GEOM призвано избежать от ''засорения'' пути запроса ввода/вывода, так как блокировки обычно не имеют четких временных границ, и нет гарантий на занимаемое время (также на то есть и другие технические причины). Это также значит, что в вышеупомянутых потоках сколь-нибудь сложные операции выполнить нельзя, например: любое сложное преобразование требует выделения памяти. К счастью решение есть: создание дополнительных ядерных потоков.
Ядерные потоки выполнения создаются функцией kthread_create(9), в своем поведении они схожи с потоками, созданными в пространстве пользовательских процессов, но есть одно отличие: они не могут известить вызвавший их поток о своем завершении; по завершению — необходимо вызывать kthread_exit(9)
В коде GEOM обычное назначение этих потоков — разгрузить поток g_down (функцию .start
() ) от
обработки запросов. Эти потоки подобны ''обработчикам событий'' (''event
handlers''): у них есть очередь событий (которая наполняется событиями от разных
функций из разных потоков; очередь необходимо защищать мьютексом), события из
очереди выбираются одно за другим и обрабатываются в большом блоке switch().
Основное преимущество использования отдельного потока, который обрабатывает
запросы ввода/вывода, то, что он может блокироваться по мере необходимости. Это,
несомненно, привлекательно, но должно быть хорошо обдумано. Блокирование —
хорошо и удобно, но может существенно снизить производительность преобразований
данных в системе GEOM. Особо требовательные к производительности классы могут
делать всю работу в функции .start
(), уделяя особое
внимание ошибкам при работе с памятью.
Еще одно преимущество потока ''обработчика событий'' это сериализация всех
запросов и ответов, приходящих с разных потоков geom в один поток. Это также удобно,
но может быть медленным. В большинстве случаев, обработка запросов функцией
.done
() может быть оставлена потоку g_up.
У мьютексов в ядре FreeBSD (mutex(9)) есть одно различие с их аналогами из пространства пользовательских процессов — во время удержания мьютекса в коде не должно быть блокировки. Если в коде необходимо блокирование, то лучше использовать sx(9). С другой стороны, если вся ваша работа выполняется в одном потоке, вы можете обойтись вообще без мьютексов.
Этот, и другие документы, могут быть скачаны с ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.
По вопросам, связанным с FreeBSD, прочитайте документацию прежде чем писать в <questions@FreeBSD.org>.
По вопросам, связанным с этой документацией, пишите <doc@FreeBSD.org>.
По вопросам, связанным с русским переводом документации, пишите в рассылку <frdp@FreeBSD.org.ua>.
Информация по подписке на эту рассылку находится на сайте проекта перевода.