4. Программирование в системе GEOM
Пред. |  |

## 4. Программирование в системе GEOM

### 4.1. Ggate

Если максимальная производительность не требуется, то более простой способ совершать преобразования данных — это выполнять их в пространстве пользовательских процессов посредством ggate (GEOM gate). К недостаткам следует отнести невозможность простого переноса кода в ядро.

### 4.2. Класс GEOM

Класс 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 потока ядра.

### 4.3. Softc

«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.

### 4.4. Метаданные

Формат метаданных в той или иной мере зависит от конкретного класса, но обязан начинаться с:

- 16-байтного буфера для подписи — строки с завершающим нулем (обычно это имя класса)

- uint32 идентификатора версии

Подразумевается, что классы geom знают как обращаться с метаданными с идентификаторами версий ниже, чем их собственные.

Метаданные размещаются в последнем секторе поставщика geom (поэтому обязаны целиком умещаться в нем).

(Все это зависит от реализации, но весь существующий код работает подобно описанному и поддерживается библиотеками.)

### 4.5. Маркирование/создание экземпляра geom

Последовательность событий следующая:

- пользователь запускает служебную программу geom(8)

- программа решает каким классом geom ей придется управлять и ищет библиотеку `geom_`CLASSNAME`.so` (которая обычно находится в `/lib/geom`).

- она открывает библиотеку при помощи dlopen(3), извлекает вспомогательные функции и определения параметров командной строки.

Вот так происходит создание/маркирование нового экземпляра geom:

- geom(8) ищет команду в аргументах командной строки (обычно это `label`) и вызывает вспомогательную функцию.

- Вспомогательная функция проверяет параметры и собирает метаданные, которые записываются во все вовлеченные поставщики geom.

- Это «повреждает (spoil)» существующие экземпляры geom (если они были) и порождает новый виток «тестирования» поставщиков geom. Целевой класс geom опознает метаданные и активирует экземпляр geom.

(Приведенная выше последовательность событий зависит от конкретной реализации, но весь существующий код работает подобно описанному и поддерживается библиотеками.)

### 4.6. Структура команд 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.

### 4.7. Экземпляры geom

У экземпляров классов GEOM есть внутренние данные, которые хранятся в структурах softc, а также есть некоторые функции, посредством которых они реагируют на внешние события.

Функции событий:

- `.access` : просчитывает права доступа (чтение/запись/исключительный доступ)

- `.dumpconf` : возвращает информацию о экземпляре geom; формат XML

- `.orphan` : вызывается, когда отсоединяется любой из низлежащих поставщиков geom

- `.spoiled` : вызывается, когда производится запись в низлежащий поставщик geom

- `.start` : обрабатывает ввод/вывод

Эти функции вызываются из ядерного потока `g_down` и в этом контексте не может быть блокировок (поищите определение «блокировка» в других источниках), что немного ограничивает свободу действий, но способствует быстроте обработки.

Из вышеупомянутых, наиболее важной и выполняющей полезную работу функцией является `.start`(), которая вызывается всякий раз, когда поставщику geom, управляемому экземпляром класса, приходит запрос BIO.

### 4.8. Потоки выполнения системы geom

Системой 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 призвано избежать от «засорения» пути запроса ввода/вывода, так как блокировки обычно не имеют четких временных границ, и нет гарантий на занимаемое время (также на то есть и другие технические причины). Это также значит, что в вышеупомянутых потоках сколь-нибудь сложные операции выполнить нельзя, например: любое сложное преобразование требует выделения памяти. К счастью решение есть: создание дополнительных ядерных потоков.

### 4.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). С другой стороны, если вся ваша работа выполняется в одном потоке, вы можете обойтись вообще без мьютексов.

Пред. |  |
3. Программирование в ядре FreeBSD | Начало |

Этот, и другие документы, могут быть скачаны с http://ftp.FreeBSD.org/pub/FreeBSD/doc/.

По вопросам, связанным с FreeBSD, прочитайте документацию прежде чем писать в <questions@FreeBSD.org>.
По вопросам, связанным с этой документацией, пишите в рассылку <doc@FreeBSD.org>.