понедельник, 18 октября 2010 г.

2.7 Инициирование и выключение

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

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

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

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



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

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

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

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

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

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

Для каждого нетривиального модуля требуется функция очистки, которая убирает регистрацию интерфейсов и возвращает все ресурсы системе перед удалением модуля. Эта функция определяется следующим образом:
static void _ _exit cleanup_function(void)
{
/* Здесь идет код отчистки*/
}
module_exit(cleanup_function);

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

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

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

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

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

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

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

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

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; /* ошибка */
}



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

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

В случае ошибки возвращаемым значением функции my_init_function должен быть код ошибки. В ядре Линукса кодами ошибок являются отрицательные числа, множество которых определено в заголовочном файле errno.h>. Если вы хотите генерировать ваши собственные коды ошибок, отличающиеся от тех, которые возвращаются другими функциями, вы должны включить файл errno.h>, чтобы иметь возможность использовать символические значения такие как -ENODEV, -ENOMEM и т.п. Возвращать коды ошибок является очень хорошей практикой, так как пользовательские программы могут превратить их в строки описания ошибок, используя perror или подобные этому средства.
Очевидно, что функция очистки модуля должна отменить любые регистрации, выполненные функцией инициализации и желательно (но обычно не обязательно) отменить регистрацию в порядке, обратном проведению регистации:
void __exit my_cleanup_function(void) 

{ 

    unregister_those(ptr3, "skull"); 

    unregister_that(ptr2, "skull"); 

    unregister_this(ptr1, "skull"); 

    return; 

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

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;
}



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

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

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

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

Переведено на сайте www.notabenoid.com
http://notabenoid.com/book/11832/38207
Внимание! Этот перевод, возможно, ещё не готов,
так как модераторы установили для него статус
"перевод редактируется"

Комментариев нет:

Отправить комментарий

Примечание. Отправлять комментарии могут только участники этого блога.