Linux Device Drivers 3 / Драйверы устройств Линукс (третье издание): 2.7. Инициализация и завершение

Оглавление
Английский оригинал Перевод на русский

Сожалеем, но текст оригинала доступен только зарегистрированным пользователям.

2.7. Инициализация и завершение

ximen 22.10.10 в 13:55

0

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

mahoro 23.10.10 в 11:07

0

<code>
static int __init initialization_function(void)
{
/* Здесь идёт код инициализации */
}
module_init(initialization_function);
</code>

ximen 22.10.10 в 13:55

0

Функции инициализации следует определять статически, поскольку они не предназначены быть видимыми за пределами конкретного файла. Хотя, это не жёсткое правило, т.к. никакие функции не экспортируются в остальное ядро без явного требования. Токен __init в определении может показаться немного странным; это подсказка ядру, что данная функция используется только во время инициализации. Загрузчик модуля отбрасывает функцию инициализации после того, как модуль загружен, освобождая память для другого использования. Есть схожий тэг (__initdata) для данных, используемых только во время инициализации. Использование __init и __initdata не обязательно, но оно того стоит. Главное, убедитесь, что они не используются для функций (или структур данных), которые будут использоваться после того, как завершится инициализация. В коде ядра вы также можете встретить __devinit и __devinitdata; они переводятся в __init и __initdata в случае, если ядро было сконфигурированно без поддержки горячего подключения устройств. Мы рассмотрим поддержку горячего подключения в главе 14.

ximen 25.10.10 в 07:15

0

Использование module_init обязательно. Этот макрос добавляет специальную секцию в объектный код модуля, указывая, где располагается функция инициализации модуля. Без этого определения ваша функция инициализации никогда не будет вызвана.

ximen 25.10.10 в 07:17

0

Модули могут регистрировать много различных типов средств обслуживания, включая различные виды устройств, файловых систем, шифровальных платформ, и не только. Для каждого средства, есть определенная функция ядра, которая достигается этой регистрацией. Аргументы, которые передаются регистрационным функциям ядра — это обычно указатели на структуры данных, описывающие новую возможность и её название. Структура данных обычно содержит указатели на функции модуля, которые являются, функциями в теле вызываемого модуля.

hardorsoft 29.10.10 в 13:19

0

Пункты, которые могут быть зарегистрированы, выходят за пределы списка типов устройства, упомянутых в Главе 1. Они включают, среди прочего, последовательные порты, разнообразные устройства, входы sysfs, /proc файлы, используемые домены, и линии запретов. Многие из этих регистрируемых функций, которые не поддерживают на прямую аппаратного обеспечения, но остаются в поле «абстракции программного обеспечения». Эти пункты должны быть зарегистрированы в функциональности драйвера в любом случае (как /proc файлы и дисциплины на пример).

hardorsoft 29.10.10 в 13:19

0

Есть другие средства обслуживания которые могут быть зарегистрированы как дополнения драйвера, но их использование является настолько специфичным, что мы не считаем нужным говорить об этом; они используют технику упаковки (стеккинг), описанную в Секции 2.5. Если у вас есть ещё желание заниматься исследованиями, вы можете поискать утилитой grep EXPORT_SYMBOL в каталоге исходников ядра в результате вы должны найти входные указатели предложенные различными драйверами. Большинство функций регистрации имеют префикс register_, таким образом их можно ещё найти утилитой grep в дереве исходников ядра.

hardorsoft 29.10.10 в 13:19

0

Есть другие средства обслуживания которые могут быть зарегистрированы как дополнения драйвера, но их использование является настолько специфичным, что мы не считаем нужным говорить об этом; они используют технику стеков, описанную в разделе 2.5. Если у вас есть желание заниматься дальнейшими исследованиями, вы можете воспользоваться утилитой grep для поиска EXPORT_SYMBOL в исходных кодах ядра и найти точки входа для различных драйверов. Большинство функций регистрации имеют префикс register_, так что поиск этого префикса - это другой способ того же поиска в исходных кодах ядра.

victor8632 03.11.10 в 07:07

0

2.7.1. Функция очистки (cleanup)2.7.1. The Cleanup Function

hardorsoft 29.10.10 в 23:04

0

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

hardorsoft 29.10.10 в 23:04

0

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

victor8632 03.11.10 в 07:11

0

static void _ _exit cleanup_function(void)
{
/* Здесь идет код отчистки*/
}
module_exit(cleanup_function);

hardorsoft 29.10.10 в 23:05

0

Функция очистки не возвращает не одного значения, поэтому она определяется как void. Модификатор __exit говорит что код функции применяется только при выгрузке модуля указывая компилятору поместить её (функцию) в специальную отдел. Если ваш модуль был собран прямо в ядре, или если ваше ядро сконфигурировано, с запретом выгрузки модулей, от функции, с префиксом _ _exit просто не выполняются. По этой причине, функцию с префиксом __exit нужно вызывать только при выгрузке модуля или при выключении системы, любое другое использование — ошибочно. Ещё раз, скажем что определение функций с module_exit необходимо ядру для поиска функции отчистки.

hardorsoft 29.10.10 в 23:05

0

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

hardorsoft 29.10.10 в 23:05

0

2.7.2. Управление ошибками во время инициализации.

hardorsoft 29.10.10 в 23:05

0

Есть одна вещь, о которой нужно помнить всегда при регистрации возможностей ядра это возможные ошибки регистрации. Даже самое простое действие часто требует перераспределения памяти, и требуемая память может быть не доступна. Поэтому код должен всегда проверять возвращаемые значения и нужно быть уверенным что операции на самом деле успешно вызваны.

hardorsoft 29.10.10 в 23:05

1

Если есть хоть одна ошибка при регистрации утилиты, то первым делом решается вопрос может ли модуль продолжать инициализацию. Часто модуль может продолжать работу даже в случае неудачной регистрации, но с ограниченными возможностями. Как бы то ни было, модуль должен продолжать работу и предоставлять его функциональность возможно и после ошибки.

hardorsoft 29.10.10 в 23:05

1

Если оказывается, что модуль просто не может загрузиться после определенного типа ошибок необходимо отменить любые регистрационные действия, выполненные перед ошибкой. В Линуксе нет реестра возможностей зарегистрированных модулей, поэтому модуль должен все вернуть как было до момента когда произошла ошибка. Если вы не смогли снять с регистрации полученный модуль, то ядро остается в нестабильном состоянии и продолжает содержать указатели на код которого уже не существует. В таких случаях может помочь только перезагрузка системы. Необходимо позаботится о том как поведет себя система в случае ошибки инициализации.

hardorsoft 30.10.10 в 03:37

1

Для обработки ошибок иногда лучше всего подходит оператор <code>goto</code>. Обычно, мы ненавидим использовать <code>goto</code>, но, по нашему мнению, это тот случай, где он полезен. Осторожное использование <code>goto</code> в ситуациях с ошибками может устранить большое количество сложной, глубоко вложенной "структурированной" логики. Таким образом, в ядре часто используется <code>goto</code> для обработки ошибок.

ximen 26.10.10 в 12:22

0

Иногда лучший выход исправления ошибки это использование оператора goto. Обычно использование оператора goto в коде стараются избегать, но в данном случае это оправданно. Осторожное использование этого оператора в случае возникновения ошибки, может помочь избежать сложной и запутанной «структурированной» логики кода. Так, в ядре, оператор goto часто используется для обработки ошибок.

hardorsoft 30.10.10 в 03:41

1

Следующий пример кода (использующий фиктивные функции регистрации и отмены регистрации) ведёт себя корректно при ошибке инициализации на любом этапе:

ximen 25.10.10 в 12:16

1

Следующий пример правильного кода (использование фиктивных функций регистрационных и отмены регистрации) в случае возникновения ошибки при выполнении какого-нибудь пункта во время инициализации:

hardorsoft 30.10.10 в 03:43

0

<code>
int __init my_init_function(void)
{
int err;
/* для регистрации нужны указатель и имя */
err = register_this(ptr1, "skull");
if (err) goto fail_this;
err = register_that(ptr2, "skull");
if (err) goto fail_that;
err = register_those(ptr3, "skull");
if (err) goto fail_those;
return 0; /* успех */
fail_those: unregister_that(ptr2, "skull");
fail_that: unregister_this(ptr1, "skull");
fail_this: return err; /* ошибка */
}
</code>

ximen 22.10.10 в 13:57

0

Этот код пытается зарегистрировать 3 (фиктивных) объекта. Оператор <code>goto</code> используется в случае ошибки для отмены регистрации только тех объектов, которые были успешно зарегистрированы до того, как произошёл сбой.

ximen 26.10.10 в 11:29

1

Другой вариант, не требующий использования goto, это отслеживание того, что уже было успешно зарегистрировано, и вызов функции очистки вашего модуля в случае возникновения любой ошибки. Функция очистки откатит только те шаги регистрации, которые были успешно выполнены. Однако, этот вариант требует больше кода и больше процессорного времени, так что в целях ускорения по-прежнему прибегают к помощи goto как к лучшему способу восстановления после ошибки.

victor8632 08.11.10 в 13:06

0

В случае ошибки возвращаемым значением функции my_init_function должен быть код ошибки. В ядре Линукса кодами ошибок являются отрицательные числа, множество которых определено в заголовочном файле errno.h&gt;. Если вы хотите генерировать ваши собственные коды ошибок, отличающиеся от тех, которые возвращаются другими функциями, вы должны включить файл errno.h&gt;, чтобы иметь возможность использовать символические значения такие как -ENODEV, -ENOMEM и т.п. Возвращать коды ошибок является очень хорошей практикой, так как пользовательские программы могут превратить их в строки описания ошибок, используя perror или подобные этому средства.

victor8632 08.11.10 в 13:14

0

Очевидно, что функция очистки модуля должна отменить любые регистрации, выполненные функцией инициализации и желательно (но обычно не обязательно) отменить регистрацию в порядке, обратном проведению регистации:

victor8632 08.11.10 в 13:28

0

<pre>
void __exit my_cleanup_function(void)
{
unregister_those(ptr3, "skull");
unregister_that(ptr2, "skull");
unregister_this(ptr1, "skull");
return;
}
</pre>

ximen 22.10.10 в 13:58

0

Если ваши функции инициализации и очистки являются более сложными, чем те, которые нужны для работы с несколькими пунктами, подход с использованием goto может стать сложным для исполнения, потому что весь код очистки должен будет повторяться внутри функции инициализации для различных перемешанных меток перехода. Получается, иногда альтернативный подход оказывается более предпочтительным.

victor8632 08.11.10 в 13:55

0

Минимизировать повторение кода и сохранить его более или менее обтекаемым поможет вызов функции очистки во время инициализации при возникновении ошибки. После вызова функция очистки должна проверить состояние каждого пункта инициализации перед отменой его регистрации. В самой простой форме этот код может выглядеть следующим образом:

victor8632 09.11.10 в 06:09

0

<code>
struct something *item1;
struct somethingelse *item2;
int stuff_ok;

void my_cleanup(void)
{
if (item1)
release_thing(item1);
if (item2)
release_thing2(item2);
if (stuff_ok)
unregister_stuff( );
return;
}

int __init my_init(void)
{
int err = -ENOMEM;

item1 = allocate_thing(arguments);
item2 = allocate_thing2(arguments2);
if (!item1 || !item2)
goto fail;
err = register_stuff(item1, item2);
if (!err)
stuff_ok = 1;
else
goto fail;
return 0; /* успех */

fail:
my_cleanup( );
return err;
}
</code>

ximen 22.10.10 в 14:00

0

Как показано в этом коде, вам могут понадобиться, а, может, и не понадобиться внешние флаги для отметки успешности шага инициализации, это зависит от семантики функций регистрации/выделения памяти, которые вы вызываете. Есть или нет необходимость в флагах, в любом случае этот вид инициализации хорошо масштабируется на большое количество элементов и часто предпочтительнее, чем методика, показанная ранее.

victor8632 09.11.10 в 06:16

0

2.7.3 Состояние гонки при загрузке модулей

victor8632 09.11.10 в 06:25

0

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

victor8632 09.11.10 в 06:27

0

Во-первых, вы всегда должны помнить, что некоторые другие части ядра могут использовать любой объект, который вы зарегистрировали, непосредственно после окончания регистрации. Другими словами, вполне возможно, что ядро будет посылать вызовы вашему модулю в то время, когда функция инициализации будет еще запущена. Так что ваш модуль должен быть готов к вызовам как только произойдет первая регистрация. Не регистрируйте какие-либо объекты до тех пор, пока не закончена внутренняя инициализация, делающая эти объекты полноценными.

victor8632 09.11.10 в 07:06

0

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

victor8632 09.11.10 в 07:17

0
Минутку...