- Add translation of geom-class article

PR:		docs/130742
Submitted by:	Taras Korenko <ds@ukrhub.net>
This commit is contained in:
Gabor Kovesdan 2009-10-19 21:04:42 +00:00
parent 832840e4cd
commit 9b34b39c11
Notes: svn2git 2020-12-08 03:00:23 +00:00
svn path=/head/; revision=34809
3 changed files with 840 additions and 0 deletions
ru_RU.KOI8-R/articles

View file

@ -25,6 +25,7 @@ SUBDIR+= fbsd-from-scratch
SUBDIR+= fonts
SUBDIR+= formatting-media
SUBDIR+= freebsd-questions
SUBDIR+= geom-class
SUBDIR+= hats
SUBDIR+= hubs
SUBDIR+= ipsec-must

View file

@ -0,0 +1,19 @@
#
# $FreeBSD$
#
# Article: Writing a GEOM Class
DOC?= article
FORMATS?= html
WITH_ARTICLE_TOC?= YES
INSTALL_COMPRESSED?= gz
INSTALL_ONLY_COMPRESSED?=
SRCS= article.sgml
URL_RELPREFIX?= ../../../..
DOC_PREFIX?= ${.CURDIR}/../../..
.include "${DOC_PREFIX}/share/mk/doc.project.mk"

View file

@ -0,0 +1,820 @@
<!DOCTYPE article PUBLIC "-//FreeBSD//DTD DocBook V4.1-Based Extension//EN" [
<!--
The FreeBSD Russian Documentation Project
$FreeBSD$
$FreeBSDru$
Original Revision: 1.8
-->
<!ENTITY % articles.ent PUBLIC "-//FreeBSD//ENTITIES DocBook FreeBSD Articles Entity Set//EN">
%articles.ent;
]>
<!-- Перевод: Тарас Коренко -->
<article lang="ru">
<title>Создание класса GEOM</title>
<articleinfo>
<authorgroup>
<author>
<firstname>Ivan</firstname>
<surname>Voras</surname>
<affiliation>
<address><email>ivoras@FreeBSD.org</email>
</address>
</affiliation>
</author>
</authorgroup>
<pubdate>$FreeBSD$</pubdate>
<legalnotice id="trademarks" role="trademarks">
&tm-attrib.freebsd;
&tm-attrib.cvsup;
&tm-attrib.intel;
&tm-attrib.xfree86;
&tm-attrib.general;
</legalnotice>
<abstract>
<para>Эта статья документирует некоторые начальные выкладки
в разработке GEOM-классов, а также модулей ядра в общем.
Предполагается, что читатель близко знаком с программированием
на Си в контексте пространства пользовательских процессов
(userland).</para>
</abstract>
</articleinfo>
<sect1 id="intro">
<title>Вступление</title>
<sect2 id="intro-docs">
<title>Документация</title>
<para>Документация по программированию для ядра скудная, это одна из
немногих областей программирования, где почти нет хороших учебных
пособий, и совет <quote>читай исходники!</quote> &mdash;
сохраняет свою справедливость. Однако, существует несколько
статей и книг разной актуальности, которые рекомендуются к изучению
перед тем, как начать программировать:</para>
<itemizedlist>
<listitem>
<para><ulink url="&url.books.developers-handbook;/index.html">
Руководство FreeBSD для разработчиков</ulink> &mdash; часть
Проекта Документации FreeBSD, ничего специфичного
о программировании ядра в нем нет, зато есть немного общей
полезной информации.</para>
</listitem>
<listitem>
<para><ulink url="&url.books.arch-handbook;/index.html">
Руководство по Архитектуре FreeBSD</ulink> &mdash; также
является частью Проекта Документации FreeBSD, содержит описания
некоторых низкоуровневых средств и процедур. Уделите внимание
разделу номер 13 &mdash;
<ulink url="&url.books.arch-handbook;/driverbasics.html">
Написание драйверов устройств для FreeBSD</ulink>.</para>
</listitem>
<listitem>
<para>Несколько интересных статей об устройстве ядра
можно найти на сайте
<ulink url="http://www.freebsddiary.com">FreeBSD
Diary</ulink>.</para>
</listitem>
<listitem>
<para>Страницы из раздела номер 9 системного справочника,
содержат важную документацию по функциям ядра.</para>
</listitem>
<listitem>
<para>Страница справочника &man.geom.4;, а также <ulink
url="http://phk.freebsd.dk/pubs/">слайды Пола-Хеннинга Кампа
</ulink> &mdash; общее представление о подсистеме GEOM.</para>
</listitem>
<listitem>
<para>Страницы справочника &man.g.bio.9;, &man.g.event.9;,
&man.g.data.9;, &man.g.geom.9;, &man.g.provider.9;,
&man.g.consumer.9;, &man.g.access.9;, а также другие,
связанные с вышеупомянутыми и раскрывающие специфический
функционал подсистемы GEOM.</para>
</listitem>
<listitem>
<para>Страница справочника &man.style.9; &mdash; документирует
соглашения о стиле оформления кода, которые обязаны быть соблюдены
если вы планируете передать ваш код в CVS-древо FreeBSD.</para>
</listitem>
</itemizedlist>
</sect2>
</sect1>
<sect1 id="prelim">
<title>Подготовка</title>
<para>Для того, чтоб заниматься разработками для ядра, желательно
иметь два отдельных компьютера. Один из них предназначен для среды
разработки и исходных кодов, а второй &mdash; для запуска тестов
отлаживаемого кода. Второму компьютеру для работы достаточно
иметь возможность выполнять начальную загрузку по сети и монтирование
файловых систем по сети. В этой ситуации, если отлаживаемый код
содержит ошибки и вызовет аварийную остановку системы, то это не
повлечет порчу или утерю исходного кода
<!-- (and other <quote>live</quote> data). -->.
Второму компьютеру даже не потребуется иметь свой монитор,
достаточно будет соединения асинхронных портов кабелем RS-232 или
соединения при помощи KVM-устройства.</para>
<para>Но так как далеко не у каждого есть два или более компьютеров
под рукой, есть пара способов подготовить иную <quote>живую</quote>
систему для разработки кода для ядра. Один из них &mdash; это
разработка в <ulink url="http://www.vmware.com/">VMWare</ulink>
или <ulink url="http://www.qemu.org/">QEmu</ulink> виртуальной машине
(это лучшее из доступного, после, конечно-же, выделенного для тестов
компьютера).</para>
<sect2 id="prelim-system">
<title>Настройка системы для разработки</title>
<para>Прежде всего необходимо иметь в ядре поддержку
<option>INVARIANTS</option>. Добавьте следующие строки в
файл конфигурации ядра:</para>
<programlisting>options INVARIANT_SUPPORT
options INVARIANTS</programlisting>
<para>Для большей информативности при отладке включите поддержку
WITNESS, которая будет предупреждать вас в случае возникновения
взаимоблокировок:</para>
<programlisting>options WITNESS_SUPPORT
options WITNESS
</programlisting>
<para>Также включите отладочные символы, если планируете
выполнять отладку по дампам аварийных отказов</para>
<programlisting> makeoptions DEBUG=-g</programlisting>
<para>Установка отладочного ядра обычным способом (<command>make
installkernel</command>) не даст привычного результата:
файл ядра будет называться <filename>kernel.debug</filename>
и будет находиться в
<filename>/usr/obj/usr/src/sys/KERNELNAME/</filename>.
Для удобства, отладочное ядро необходимо скопировать в
<filename>/boot/kernel/</filename>.</para>
<para>Также удобно иметь включенный отладчик ядра, так вы сможете
исследовать паники сразу-же после их возникновения. Для включения
отладчика добавьте следующие строки в файл конфигурации ядра:</para>
<programlisting>options KDB
options DDB
options KDB_TRACE</programlisting>
<para>Для автоматического запуска отладчика ядра после возникновения
паники может понадобиться установить переменную sysctl:</para>
<programlisting> debug.debugger_on_panic=1</programlisting>
<para>Паники системы будут происходить, поэтому уделите внимание
кэшу файловой системы. Обычно, при включенном механизме
softupdates, последняя версия файла может быть утеряна если
паника произошла раньше сбрасывания кэша на устройство хранения.
Выключение механизма softupdates (посредством монтирования файловой
системы с опцией <quote>sync</quote>) значительно сказывается на
производительности и, опять-же, не гарантирует целосности
данных. Как компромисс, можно сократить задержки сбрасывания
кэша механизма softupdates. Есть три переменных sysctl, значения
которых необходимо изменить (лучше всего &mdash; прописав их в
<filename>/etc/sysctl.conf</filename>):</para>
<programlisting>kern.filedelay=5
kern.dirdelay=4
kern.metadelay=3</programlisting>
<para>Значения этих переменных &mdash; секунды.</para>
<para>Для отладки паник ядра необходимы дампы памяти.
Так как паника ядра может <quote>сломать</quote> файловую
систему, дамп сначала сохраняется в <quote>сырой</quote>
раздел. Обычно, это своп-раздел. Поэтому, размер своп-раздела
должен быть не меньше размера ОЗУ компьютера. При последующей
загрузке дамп копируется в обычный файл. Это происходит сразу-же
после проверки и монтирования файловых систем, но перед
активированием раздела свопа. Такое поведение контроллируется
следующими переменными <filename>/etc/rc.conf</filename>:</para>
<programlisting>dumpdev="/dev/ad0s4b"
dumpdir="/usr/core" </programlisting>
<para>Переменная <varname>dumpdev</varname> указывает на раздел
подкачки, а <varname>dumpdir</varname> сообщает системе куда
перемещать дамп ядра при следующей загрузке.</para>
<para>Сохранение дампа ядра &mdash; процесс медленный, и, если
у вашего компьютера много оперативной памяти (>256M) и если
паники случаются часто, то ожидание сохранения дампов может начать
раздражать (вспомним, что над дампом происходит две операции:
сохранение в своп-файл и перемещение на файловую систему).
В таком случае может оказаться удобным ограничивание объема
используемой системой памяти путем установки переменной в
<filename>/boot/loader.conf</filename>:</para>
<programlisting> hw.physmem="256M"</programlisting>
<para>Если паники случаются часто и размер файловых систем большой
(или же вы просто не доверяете softupdates и фоновой проверке
файловых систем), рекомендуется отключить фоновую проверку файловых
систем посредством установки переменной в
<filename>/etc/rc.conf</filename>:</para>
<programlisting> background_fsck="NO"</programlisting>
<para>В этом случае файловые системы будут проверяться только при
необходимости. Также заметьте, что в случае использования фоновой
проверки, новая паника может случиться в то время, когда
проверяются диски. Другими словами, наиболее безопасный способ
&mdash; не иметь много локальных файловых систем, а использовать
второй компьютер в качестве NFS-сервера.</para>
</sect2>
<sect2 id="prelim-starting">
<title>Начало проекта</title>
<para>Для написания нового класса GEOM необходимо создать поддиректорию
в любой доступной пользователю директории. Совсем не обязательно,
чтоб ваш модуль изначально размещался в
<filename>/usr/src</filename>.</para>
</sect2>
<sect2 id="prelim-makefile">
<title>Makefile</title>
<para>Правилом хорошего тона является создание
<filename>Makefile</filename>-ов для каждого нетривиального
проекта, примером которого конечно-же является создание
модулей ядра.</para>
<para>Создание <filename>Makefile</filename> &mdash; дело
не сложное благодаря исчерпывающему набору вспомогательных средств,
предоставляемых системой. В кратце, вот как должен выглядеть
<filename>Makefile</filename> для модуля ядра:</para>
<programlisting>SRCS=g_journal.c
KMOD=geom_journal
.include &lt;bsd.kmod.mk&gt;</programlisting>
<para>Этот <filename>Makefile</filename> (с измененными именами
файлов) подойдет к любому модулю ядра. Класс GEOM может размещаться
в одном единственном модуле ядра. Если для сборки вашего модуля
требуется больше, чем один файл, то перечислите их имена,
разделенные пробельными символами, в переменной <envar>SRCS</envar>.
</para>
</sect2>
</sect1>
<sect1 id="kernelprog">
<title>Программирование в ядре FreeBSD</title>
<sect2 id="kernelprog-memalloc">
<title>Выделение памяти</title>
<para>Прочитайте &man.malloc.9; &mdash; выделение памяти лишь
немного отличается от своего эквивалента, используемого в пространстве
пользовательских процессов (userland). Наиболее приметно то, что
<function>malloc</function>() и <function>free</function>()
принимают дополнительные параметры, которые описаны в странице
справочника.</para>
<para>Тип <quote>malloc_type</quote> необходимо объявить в секции
деклараций файла с ихсодным кодом, например:</para>
<programlisting> static MALLOC_DEFINE(M_GJOURNAL, "gjournal data", "GEOM_JOURNAL Data");</programlisting>
<para>Для того, чтобы можно было использовать этот макрос,
необходимо включить следующие заголовочные файлы:
<filename>sys/param.h</filename>,
<filename>sys/kernel.h</filename> и
<filename>sys/malloc.h</filename></para>
<para>Существует еще один механизм выделения памяти &mdash; UMA
(Universal Memory Allocator), описанный в &man.uma.9;.
Это специфический метод, преимущественно предназначенный для
быстрого выделения памяти под списки, состоящие из элементов
одинакового размера (например, динамические массивы структур).</para>
</sect2>
<sect2 id="kernelprog-lists">
<title>Очередя и списки</title>
<para>Ознакомьтесь с &man.queue.3;
Во множестве случаев вам необходимо будет организовывать и
управлять такой структурой данных, как списки. К счастью, эта
структура данных реализована несколькими способами в виде макросов
на Си, а также включена в систему. Наиболее гибкий и часто
употребляемый тип списка &mdash; TAILQ. Этот тип списка также один
из наиболее требовательных к памяти (его элементы - с двойными
связями), а также &mdash; наиболее медленный (однако счет идет
на несколько инструкций ЦПУ, поэтому последнее утверждение не следует
воспринимать в серьез).</para>
<para>Если важна скорость получения данных, то возьмите на вооружение
&man.tree.3; и &man.hashinit.9;.</para>
</sect2>
<sect2 id="kernelprog-bios">
<title>BIOs</title>
<para>Структура <structname>bio</structname> используется для всех
операций ввода/вывода, касающихся GEOM. Она содержит
информацию о том, какое устройство ('поставщик geom') должно ответить
на запрос, тип запроса, смещение, длину и указатель на буффер,
а также набор <quote>определенных пользователем</quote> флагов и
полей
<!-- , которые могут помочь осуществить various hacks-->.
</para>
<para>Важным моментом является то, что <structname>bio</structname>
обрабатываются асинхронно. Это значит, что во многих частях кода
нет аналога к &man.read.2; и &man.write.2; функциям из пространства
пользовательских процессов, которые не возвращают управление пока
не выполнится системный вызов. Скорее, по завершении обработки
запроса (или в случае ошибки при обработке) как извещение вызывается
определенная пользователем функция.</para>
<para>Асинхронная модель программирования в чем-то сложней,
нежели чаще используемая императивная модель, используемая в
пространстве пользовательских процессов; в любом случае, привыкание
займет некоторое время. В некоторых случаях могут быть использованы
вспомогательные функции <function>g_write_data</function>() и
<function>g_read_data</function>(), но <emphasis>далеко не
всегда</emphasis>. В частности, эти функции не могут использоваться
когда захвачен мьютекс; например, мьютекс GEOM-топологии или
внутренний мьютекс, удерживаемый в ходе выполнения
<function>.start</function>() или <function>.stop</function>().</para>
</sect2>
</sect1>
<sect1 id="geom">
<title>Программирование в системе GEOM</title>
<sect2 id="geom-ggate">
<title>Ggate</title>
<para>Если максимальная производительность не требуется,
то более простой способ совершать преобразования данныx &mdash;
это выполнять их в пространстве пользовательских процессов
посредством ggate (GEOM gate). К недостаткам следует отнести
невозможность простого переноса кода в ядро.</para>
</sect2>
<sect2 id="geom-class">
<title>Класс GEOM</title>
<para>Класс GEOM выполняет преобразования данных. Эти преобразования
могут быть скомпонованы друг с другом в виде дерева. Экземпляр
класса GEOM называют <emphasis>geom</emphasis>.</para>
<para>В каждом классе GEOM есть несколько <quote>методов класса</quote>,
которые вызываются когда экземпляра класса нет в наличии (или же
они не привязаны к конкретному экземпляру класса).</para>
<itemizedlist>
<listitem>
<para><function>.init</function> вызывается тогда, когда
системе GEOM становится известно о классе GEOM (например,
когда загружается модуль ядра).</para>
</listitem>
<listitem>
<para><function>.fini</function> будет вызван в случае отказа
GEOM системы от класса (например, при выгрузке модуля).</para>
</listitem>
<listitem>
<para><function>.taste</function> вызывается, когда в
системе появляется новый класс или поставщик geom
(<quote>provider</quote>). Если соответствие найдено, то эта
функция обычно создает и запускает экземпляр geom.</para>
</listitem>
<listitem>
<para><function>.destroy_geom</function> вызывается при
необходимости разрушить экземпляр geom.</para>
</listitem>
<listitem>
<para><function>.ctlconf</function> будет вызван, когда
пользователь запросит изменение конфигурации существующего
экземпляра geom</para>
</listitem>
</itemizedlist>
<para>Также определены функции событий GEOM, которые копируются
в экземпляр geom.</para>
<para>Поле <function>.geom</function> в структуре
<structname>g_class</structname> &mdash; это список (LIST) экземпляров
geom, реализованных из класса.</para>
<para>Эти функции вызываются из g_event потока ядра.</para>
</sect2>
<sect2 id="geom-softc">
<title>Softc</title>
<para><quote>softc</quote> &mdash; это устаревший термин
для <quote>приватных данных драйвера</quote> (<quote>driver
private data</quote>). Название вероятней всего происходит от
устаревшего термина <quote>software control block</quote>.
В системе GEOM softc это структура (точнее: указатель на
структуру) которая может быть присоединена к экземпляру geom
и может содержать приватные данные экземпляра. У большинства
классов GEOM есть следующие члены:</para>
<itemizedlist>
<listitem>
<para><varname>struct g_provider *provider</varname> :
<quote>поставщик geom</quote> предоставляемый данным экземпляром
geom</para>
</listitem>
<listitem>
<para><varname>uint16_t n_disks</varname> : Количество
потребителей geom (<quote>consumer</quote>), обслуживаемых данным
экземпляром geom</para>
</listitem>
<listitem>
<para><varname>struct g_consumer **disks</varname> :
Массив <varname>struct g_consumer*</varname>. (Невозможно
обойтись одинарным указателем, потому что система GEOM создает
для нас структуры struct g_consumer*)</para>
</listitem>
</itemizedlist>
<para>Структура <structname>softc</structname> содержит состояние
экземпляра geom. У каждого экземпляра есть свой softc.</para>
</sect2>
<sect2 id="geom-metadata">
<title>Метаданные</title>
<para>Формат метаданных в той или иной мере зависит от конкретного
класса, но <emphasis>обязан</emphasis> начинаться с:</para>
<itemizedlist>
<listitem>
<para>16-байтного буффера для подписи &mdash; строки с
завершающим нулем (обычно это имя класса)</para>
</listitem>
<listitem>
<para>uint32 идентификатора версии</para>
</listitem>
</itemizedlist>
<para>Подразумевается, что классы geom знают как обращаться с
метаданными с идентификаторами версий ниже, чем их собственные.</para>
<para>Метаданные размещаются в последнем секторе поставщика geom
(поэтому обязаны целиком умещаться в нем).</para>
<para>(Все это зависит от реализации, но весь существующий код работает
подобно описанному и поддерживается библиотеками.)</para>
</sect2>
<sect2 id="geom-creating">
<title>Маркирование/создание экземпляра geom</title>
<para>Последовательность событий следующая:</para>
<itemizedlist>
<listitem>
<para>пользователь запускает служебную программу &man.geom.8;</para>
</listitem>
<listitem>
<para>программа решает каким классом geom ей прийдется
управлять и ищет библиотеку
<filename>geom_<replaceable>CLASSNAME</replaceable>.so</filename>
(которая обычно находится в <filename>/lib/geom</filename>).</para>
</listitem>
<listitem>
<para>она открывает библиотеку при помощи &man.dlopen.3;,
извлекает вспомогательные функции и определения параметров
командной строки.</para>
</listitem>
</itemizedlist>
<para>Вот так происходит создание/маркирование нового экземпляра
geom:</para>
<itemizedlist>
<listitem>
<para>&man.geom.8; ищет команду в аргументах командной
строки (обычно это <option>label</option>) и вызывает
вспомогательную функцию.</para>
</listitem>
<listitem>
<para>Вспомогательная функция проверяет параметры и
собирает метаданные, которые записываются во все вовлеченные
поставщики geom.</para>
</listitem>
<listitem>
<para>Это <quote>повреждает (spoil)</quote> существующие
экземпляры geom (если они были) и порождает новый виток
<quote>тестирования</quote> поставщиков geom. Целевой класс geom
опознает метаданные и активирует экземпляр geom.</para>
</listitem>
</itemizedlist>
<para>(Приведенная выше последовательность событий зависит от
конкретной реализации, но весь существующий код работает
подобно описанному и поддерживается библиотеками.)</para>
</sect2>
<sect2 id="geom-command">
<title>Структура команд geom</title>
<para>Вспомогательная библиотека <filename>geom_CLASSNAME.so</filename>
экспортирует структуру <structname>class_commands</structname>,
которая является массивом элементов
<structname>struct g_command</structname>. Эти команды одинакового
формата и выглядят следующим образом:</para>
<programlisting> команда [-опции] имя_geom [другие]</programlisting>
<para>Общими командами являются:</para>
<itemizedlist>
<listitem>
<para>label &mdash; записать метаданные в устройства,
чтобы они могли быть опознаны в процессе тестирования
и использованы в соответствующих экземплярах geom</para>
</listitem>
<listitem>
<para>destroy &mdash; разрушить метаданные, за которым
последует разрушение экземпляров geom</para>
</listitem>
</itemizedlist>
<para>Общие опции:</para>
<itemizedlist>
<listitem>
<para><literal>-v</literal> : детальный вывод</para>
</listitem>
<listitem>
<para><literal>-f</literal> : принудить</para>
</listitem>
</itemizedlist>
<para>Некоторые операции, к примеру маркирование метаданными и
разрушение метаданных могут быть выполнены из пространства
пользовательских процессов. Для этого, структура
<structname>g_command</structname> содержит поле
<varname>gc_func</varname>, которое может быть установлено на
функцию (в том-же <filename>.so</filename>), которая будет вызвана
для обработки команды. В случае, когда <varname>gc_func</varname>
равно NULL, команда будет передана модулю ядра: функции
<function>.ctlreq</function> класса GEOM.</para>
</sect2>
<sect2 id="geom-geoms">
<title>Экземпляры geom</title>
<para>У экземпляров классов GEOM есть внутренние данные, которые
хранятся в структурах softc, а также есть некоторые функции,
посредством которых они реагируют на внешние события.</para>
<para>Функции событий:</para>
<itemizedlist>
<listitem>
<para><function>.access</function> : просчитывает
права доступа (чтение/запись/исключительный доступ)</para>
</listitem>
<listitem>
<para><function>.dumpconf</function> : возвращает
информацию о экземпляре geom; формат XML</para>
</listitem>
<listitem>
<para><function>.orphan</function> : вызывается, когда
отсоединяется любой из низлежащих поставщиков geom</para>
</listitem>
<listitem>
<para><function>.spoiled</function> : вызывается, когда
производится запись в низлежащий поставщик geom</para>
</listitem>
<listitem>
<para><function>.start</function> : обрабатывает ввод/вывод</para>
</listitem>
</itemizedlist>
<para>Эти функции вызываются из ядерного потока
<function>g_down</function> и в этом контексте не может быть
блокировок (поищите определение <quote>блокировка</quote> в других
источниках), что немного ограничивает свободу действий, но
способствует быстроте обработки.</para>
<para>Из вышеупомянутых, наиболее важной и выполняющей полезную работу
функцией является <function>.start</function>(), которая вызывается
всякий раз, когда поставщику geom, управляемому экземпляром класса,
приходит запрос BIO.</para>
</sect2>
<sect2 id="geom-threads">
<title>Потоки выполнения системы geom</title>
<para>Системой GEOM в ядре ОС создаются и используются три
потока выполнения (kernel threads):</para>
<itemizedlist>
<listitem>
<para><literal>g_down</literal> : Обрабатывает запросы,
приходящие от высокоуровневых сущностей (таких, как запросы из
пространства пользовательских процессов) на пути к физическим
устройствам</para>
</listitem>
<listitem>
<para><literal>g_up</literal> : Обрабатывает ответы от
драйверов устройств на запросы, выполненные высокоуровневыми
сущностями</para>
</listitem>
<listitem>
<para><literal>g_event</literal> : Отрабатывает в остальных
случаях, как-то создание экземпляра geom, просчитывание прав
доступа, события <quote>повреждения</quote> и т.п.</para>
</listitem>
</itemizedlist>
<para>Когда пользовательский процесс запрашивает <quote>прочитать
данные X по смещению Y файла</quote>, происходит следующее:</para>
<itemizedlist>
<listitem>
<para>Файловая система преобразует запрос в экземпляр
структуры bio и передает его системе GEOM. Файловая система
<quote>знает</quote>, что экземпляр geom должен обработать запрос,
так как файловые системы размещаются непосредственно
над экземпляром geom.</para>
</listitem>
<listitem>
<para>Запрос завершается вызовом функции
<function>.start</function>() в потоке g_down и достигает
верхнего экземпляра geom.</para>
</listitem>
<listitem>
<para>Верхний экземпляр geom (например, это секционировщик
разделов (partition slicer)) определяет, что запрос должен быть
переадресован нижестоящему экземпляру geom (к примеру, драйверу
диска). Вышестоящий экземпляр geom создает копию запроса bio
(запросы bio <emphasis>ВСЕГДА</emphasis> копируются при передаче
между экземплярами geom при помощи
<function>g_clone_bio</function>()!),
изменяет поля смещения и целевого поставщика geom и запускает
на обработку копию при помощи функции
<function>g_io_request</function>()</para>
</listitem>
<listitem>
<para>Драйвер диска также получает запрос bio, как вызов
функции <function>.start</function>() в потоке
<literal>g_down</literal>. Драйвер обращается к контроллеру диска,
получает блок данных и вызывает функцию
<function>g_io_deliver</function>() используя копию запроса bio
</para>
</listitem>
<listitem>
<para>Теперь, извещение о завершении bio
<quote>всплывает</quote> в потоке <literal>g_up</literal>.
Сначала в потоке <literal>g_up</literal> вызывается функция
<function>.done</function>() секционировщика разделов,
последний использует полученную информацию, разрушает
клонированный экземпляр структуры bio посредством
<function>g_destroy_bio</function>() и вызывает
<function>g_io_deliver</function>() используя
первоначальный запрос</para>
</listitem>
<listitem>
<para>Файловая система получает данные и передает их
пользовательскому процессу</para>
</listitem>
</itemizedlist>
<para>За информацией о том, как данные передаются в структуре
<structname>bio</structname> между экземплярами geom,
смотрите &man.g.bio.9; (обратите внимание на использование полей
<varname>bio_parent</varname> и <varname>bio_children</varname>).
</para>
<para>Важный момент в том, что <emphasis>НЕЛЬЗЯ ДОПУСКАТЬ БЛОКИРОВОК
В ПОТОКАХ G_UP И G_DOWN</emphasis>. Вот неполный перечень того,
что нельзя делать в этих потоках:</para>
<itemizedlist>
<listitem>
<para>Вызывать функции <function>msleep</function>() или
<function>tsleep</function>().</para>
</listitem>
<listitem>
<para>Использовать функции
<function>g_write_data</function>() и
<function>g_read_data</function>(), так как они блокируются
в момент обмена данными с потребителями geom.</para>
</listitem>
<listitem>
<para>Ожидать ввод/вывод.</para>
</listitem>
<listitem>
<para>Вызывать &man.malloc.9; и
<function>uma_zalloc</function>() с установленным флагом
<varname>M_WAITOK</varname>.</para>
</listitem>
<listitem>
<para>Использовать &man.sx.9; <!-- и другие sleepable locks. -->
</para>
</listitem>
</itemizedlist>
<para>Это ограничение на код GEOM призвано избежать от
<quote>засорения</quote> пути запроса ввода/вывода, так как
блокировки обычно не имеют четких временных границ, и нет гарантий
на занимаемое время (также на то есть и другие технические причины).
Это также значит, что в вышеупомянутых потоках сколь-нибудь сложные
операции выполнить нельзя, например: любое сложное преобразование
требует выделения памяти. К счастью решение есть: создание
дополнительных ядерных потоков.</para>
</sect2>
<sect2 id="geom-kernelthreads">
<title>Ядерные потоки выполнения, предназначенные для использования
в коде geom</title>
<para>Ядерные потоки выполнения создаются функцией
&man.kthread.create.9;, в своем поведении они схожи с потоками,
созданными в пространстве пользовательских процессов, но есть одно
отличие: они не могут известить вызвавший их поток о своем завершении;
по завершению &mdash; необходимо вызывать &man.kthread.exit.9;</para>
<para>В коде GEOM обычное назначение этих потоков &mdash; разгрузить
поток <literal>g_down</literal>
(функцию <function>.start</function>() ) от обработки запросов. Эти
потоки подобны <quote>обработчикам событий</quote>
(<quote>event handlers</quote>):
у них есть очередь событий (которая наполняется событиями от разных
функций из разных потоков; очередь необходимо защищать мьютексом),
события из очереди выбираются одно за другим и обрабатываются
в большом блоке <literal>switch</literal>().</para>
<para>Основное преимущество использования отдельного потока, который
обрабатывает запросы ввода/вывода, то, что он может блокироваться по
мере необходимости. Это, несомненно, привлекательно, но должно быть
хорошо обдумано. Блокирование &mdash; хорошо и удобно, но может
существенно снизить поизводительность преобразований данных в
системе GEOM. Особо требовательные к производительности классы
могут делать всю работу в функции <function>.start</function>(),
уделяя особое внимание ошибкам при работе с памятью.</para>
<para>Еще одно преимущество потока <quote>обработчика событий</quote>
это сериализация всех запростов и ответов, приходящих с разных
потоков geom в один поток. Это также удобно, но может быть медленным.
В большинстве случаев, обработка запросов функцией
<function>.done</function>() может быть оставлена потоку
<literal>g_up</literal>.</para>
<para>У мьютексов в ядре FreeBSD (&man.mutex.9;) есть одно различие
с их аналогами из пространства пользовательских процессов &mdash;
во время удержания мьютекса в коде не должно быть блокировки. Если
в коде необходимо блокирование, то лучше исплользовать &man.sx.9;.
С другой стороны, если вся ваша работа выполняется в одном потоке,
вы можете обойтись вообще без мьютексов.</para>
</sect2>
</sect1>
</article>