Kobject-ы, Kset-ы и Subsystem-ы

Предыдущая  Содержание  Следующая V*D*V

kobject является основополагающей структурой, которая содержит модель устройства всю вместе. Она первоначально была задумана как простой счётчик ссылок, но её обязанности выросли с течением времени, так же как и её поля. Задачи, решаемые struct kobject и её поддерживающим кодом, теперь включают в себя:

 

Подсчёт ссылок объектов

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

 

Представление в sysfs

Каждый объект, который появляется в sysfs, имеет под собой kobject, который взаимодействует с ядром для создания его видимого представления.

 

Связующий элемент структуры данных

Модель устройства является, в целом, чрезвычайно сложной структурой данных, состоящей из множества иерархий с многочисленными связями между ними. kobject реализует эту структуру и удерживает всё вместе.

 

Обработка событий горячего подключения

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

 

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

Основы kobject

kobject имеет тип struct kobject; он определён в <linux/kobject.h>. Этот файл подключает также декларацию ряда других структур, связанных с kobject-ами и, конечно, длинный список функций для работы с ними.

Внедрение kobject-ов

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

 

Таким образом, редко (даже неизвестно), чтобы код ядра создавал автономный kobject; Вместо этого, kobject-ы используются для управления доступом к большому, зависимому от области определения объекту. С этой целью kobject-ы находятся внедрёнными в другие структуры. Если вы привыкли думать о вещах в объектно-ориентированных терминах, kobject-ы можно рассматривать как высокоуровневый, абстрактный класс, от которого порождаются все остальные классы. kobject реализует набор возможностей, которые не особенно полезны сами по себе, но которые приятно иметь в других объектах. Язык Си не позволяет прямое выражение наследования, поэтому должны быть использованы другие методы, такие как вложение одной структуры в  другую.

 

В качестве примера давайте посмотрим назад на struct cdev, с которой мы столкнулись в Главе 3. Эта структура, найденная в ядре версии 2.6.10, выглядит следующим образом:

 

struct cdev {

    struct kobject kobj;

    struct module *owner;

    struct file_operations *ops;

    struct list_head list;

    dev_t dev;

    unsigned int count;

};

 

Как мы видим, эта структура cdev имеет внедрённый в неё kobject. Если у вас есть одна из этих структур, получение встроенного в неё kobject  - это всего лишь вопрос использования поля kobj. Однако, код, который работает с kobject-ами, часто имеет противоположную проблему: как имея указатель на struct kobject получить указатель на содержащую его структуру? Вам следует избегать трюков (такого, как предположение, что kobject находится в начале структуры), а вместо этого использовать макрос container_of (введённый в разделе "Метод open" в Главе 3). Таким образом, способом преобразования указателя на struct kobject, названного kp и встроенного в структуру cdev, будет:

 

struct cdev *device = container_of(kp, struct cdev, kobj);

 

Программисты часто определяют простой макрос для "обратного приведения типа" указателей kobject к содержащему их типу.

Инициализация kobject

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

 

Первым из них является просто установка всего kobject в 0, как правило, вызовом memset. Часто такая инициализация происходит как часть обнуления структуры в которую встроен kobject. Отказ от обнуления kobject часто приводит в конечном счёте к очень странным сбоям; это не тот шаг, который вы захотите пропустить.

 

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

 

void kobject_init(struct kobject *kobj);

 

Среди прочего, kobject_init устанавливает счётчик ссылок kobject-а в единицу. Однако, вызова kobject_init не достаточно. Пользователи kobject-а должны, как минимум, установить имя kobject; это имя, которое используется в записях sysfs. Если вы пороетесь в исходном коде ядра, то сможете найти код, который копирует строку непосредственно в поле name kobject-а, но такого подхода следует избегать. Вместо этого используйте:

 

int kobject_set_name(struct kobject *kobj, const char *format, ...);

 

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

 

Другими полями kobject-а, которые должны быть установлены создателем прямо или косвенно, являются ktype, kset и parent. Мы узнаем о них далее в этой главе.

Манипуляция счётчиком ссылок

Одной из ключевых функций kobject-а является обслуживание счётчика ссылок для объекта, в который он внедрён. Пока ссылки на объект существуют, объект (и код, который его содержит) должен продолжать существование. Низкоуровневыми функциями для манипулирования счётчиками ссылок kobject-а являются:

 

struct kobject *kobject_get(struct kobject *kobj);

void kobject_put(struct kobject *kobj);

 

Вызов kobject_get увеличивает счётчик ссылок kobject-а и возвращает указатель на kobject.

Старая версия абзаца:

Успешный вызов kobject_get увеличивает счётчик ссылок kobject-а и возвращает указатель на kobject. Если, однако, kobject уже находится в процессе уничтожения, операция заканчивается неудачно и kobject_get возвращает NULL. Этот код возврата всегда должен быть проверен, или это может привести к бесконечным неприятным состояниям гонок.

Видимо, также следует исправить и приведённый ниже пример?

 

При освобождении ссылки вызов kobject_put уменьшает счётчик ссылок и, возможно, освобождает объект. Помните, что kobject_init устанавливает счётчик ссылок в единицу; таким образом, когда вы создаёте kobject, вы должны убедиться, что соответствующий вызов kobject_put производится, когда эта начальная ссылка уже больше не требуется.

 

Отметим, что во многих случаях счётчика ссылок в kobject-е самого по себе не может быть достаточно, чтобы предотвратить состояния гонок. Например, существование kobject-а (и содержащей его структуры) может, конечно, требовать дальнейшего существования модуля, который создал этот kobject. Он бы не допустил выгрузку этого модуля, пока kobject всё ещё где-то используется. Вот почему структура cdev, которую мы видели выше, содержит указатель struct module. Подсчёт ссылок на struct cdev осуществляется следующим образом:

 

struct kobject *cdev_get(struct cdev *p)

{

    struct module *owner = p->owner;

    struct kobject *kobj;

 

    if (owner && !try_module_get(owner))

        return NULL;

    kobj = kobject_get(&p->kobj);

    if (!kobj)

        module_put(owner);

    return kobj;

}

 

Создание ссылки на структуру cdev требует также создания ссылки на модуль, которому она принадлежит. Таким образом, cdev_get использует try_module_get для попытки увеличения счётчика использования модуля. Если эта операция успешна, kobject_get также используется для увеличения счётчика ссылок kobject-а. Эта операция может быть, конечно, неудачной, так что код проверяет возвращаемое значения kobject_get и освобождает его ссылку на модуль, если что-то не получилось.

Функции освобождения и типы kobject

Одной важной вещью, всё ещё пропущенной в ходе обсуждения, является то, что происходит с kobject-ом, когда его счётчик ссылок становится 0. Код, который создал kobject, обычно не знает, когда это произойдёт; если бы знал, во-первых, не было бы никакого смысла в использовании счётчика ссылок. Даже жизненные циклы предсказуемых объектов становятся более сложными, когда они включены в sysfs; программы пользовательского пространства могут хранить ссылку на kobject (на поддержание открытым одного из связанных с ним файлов в sysfs) произвольный период времени.

 

Конечным результатом является то, что структура, защищённая kobject-ом, не может быть освобождена в какой-либо одной, предсказуемой точке в жизненном цикле драйвера, а только в коде, который должен быть готов к запуску в любом момент, когда счётчик ссылок kobject-а стал 0. Счётчик ссылок не находится под прямым контролем кода, который создал этот kobject. Так что код должен получить асинхронное уведомление всякий раз, когда последняя ссылка на один из его kobject-ов уходит.

 

Такое уведомление осуществляется посредством метода release kobject-а. Как правило, этот метод имеет такую форму:

 

void my_object_release(struct kobject *kobj)

{

    struct my_object *mine = container_of(kobj, struct my_object, kobj);

    /* Выполнить любую дополнительную очистку в этом объекте, затем... */

    kfree(mine);

}

 

Нельзя недооценить один важный момент: каждый kobject должен иметь метод release и kobject должен сохраняться (в целостном состоянии), пока вызван этот метод. Если эти ограничения не выполняются, код имеет изъяны. Он рискует освободить объект, когда он всё ещё используется, или это может привести к ошибке при освобождении объекта после возвращения последней ссылки.

 

Интересно, что метод release не хранится в самом kobject-е; вместо этого он связан с типом структуры, которая содержит kobject. Этот тип отслеживается структурой типа struct kobj_type, часто называемой просто "ktype". Эта структура выглядит следующим образом:

 

struct kobj_type {

    void (*release)(struct kobject *);

    struct sysfs_ops *sysfs_ops;

    struct attribute **default_attrs;

};

 

Поле release в struct kobj_type это, конечно, указатель на метод release для этого типа kobject-а. Мы вернёмся к двум другим полям (sysfs_ops и default_attrs) позже в этой главе.

 

Каждый kobject должен иметь связанную с ним структуру kobj_type. Как ни странно, указатель на эту структуру может быть найден в двух разных местах. Структура kobject-а сама содержит поле (называемое ktype), которое может содержать этот указатель. Если, однако, этот kobject является членом kset-а, вместо этого указатель kobj_type обеспечивается kset-ом. (Мы будем рассматривать kset-ы в следующем разделе). Тем временем, макрос:

 

struct kobj_type *get_ktype(struct kobject *kobj);

 

находит указатель kobj_type для данного kobject-а.

Иерархии kobject-а, kset-ы и subsystem-ы

Структура kobject-а часто используется, чтобы связать объекты воедино в иерархическую структуру, которая соответствует структуре моделируемой подсистемы. Есть два отдельных механизма для такого связывания: указатель parent и kset-ы.

 

Поле parent (родитель) в struct kobject является указателем на другой kobject, который представляет следующий более высокий уровень в иерархии. Если, например, kobject представляет USB устройство, его указатель parent может указывать на объект, представляющий концентратор, в который  подключено устройство .

 

Основным использованием для указателя parent является позиционирование объекта в иерархии sysfs. Мы рассмотрим, как это работает в разделе "Низкоуровневые операции в sysfs".

Kset-ы

Во многих отношениях kset выглядит как расширение структуры kobj_type; kset является коллекцией kobject-ов, встроенных в структуры того же типа. Однако, если struct kobj_type имеет отношение к типу объекта, struct kset связана с агрегацией и сбором. Эти два понятия были разделены, чтобы объекты идентичных типов могли появляться в различных наборах.

 

Таким образом, основной функцией kset-а является агрегация; она может рассматриваться как контейнерный класс верхнего уровня для kobject-ов. В самом деле, каждый kset содержит внутри свои собственные kobject и он может, во многом, обрабатываться теми же способами, как и kobject. Стоит отметить, что kset-ы всегда представлены в sysfs; после того, как kset был создан и добавлен в систему, он будет присутствовать в каталоге sysfs. Kobject-ы не обязательно показаны в sysfs, но каждый kobject, являющийся членом kset-а, там представлен. Добавление kobject-а к kset-у обычно делается при создании объекта; это двух этапный процесс. Поле kset kobject-а должно указывать на интересующий kset; затем этот kobject должен быть передан в:

 

int kobject_add(struct kobject *kobj);

 

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

 

extern int kobject_register(struct kobject *kobj);

 

Эта функция - просто комбинация kobject_init и kobject_add.

 

Когда kobject передается в kobject_add, счётчик ссылок увеличивается. Содержимое в kset, в конце концов, является ссылкой на объект. В определенный момент kobject, вероятно, должен быть удалён из kset для очистки этой ссылки; это делается следующим образом:

 

void kobject_del(struct kobject *kobj);

 

Существует также функция kobject_unregister, которая представляет собой комбинацию kobject_del и kobject_put.

 

kset держит своих детей в стандартном связном списке ядра. Почти во всех случаях содержащиеся kobject-ы также имеют указатели на kset (или, точнее, его встроенный kobject) в полях parent (родитель). Таким образом, как правило, kset и его kobject-ы выглядят как что-то похожее на то, что вы видите на Рисунке 14-2. Имейте в виду, что:

 

Все содержащиеся на диаграмме kobject-ы фактически внедрены в некоторый другой тип, возможно, даже другие kset-ы.

Не обязательно, чтобы родитель kobject-а содержал kset (хотя любая другая организация была бы странной и редкой).

 

Рисунок 14-2. Простая иерархия kset

Рисунок 14-2. Простая иерархия kset

 

Операции с kset-ами

Для инициализации и настройки kset-ы имеют интерфейс, очень похожий на такой у kobject-ов.

Существуют следующие функции:

 

void kset_init(struct kset *kset);

int kset_add(struct kset *kset);

int kset_register(struct kset *kset);

void kset_unregister(struct kset *kset);

 

По большей части эти функции просто вызывают аналогичную функции kobject_ у встроенного в kset kobject-а.

 

С управлением счётчиками ссылок у kset-ов ситуация примерно такая же:

 

struct kset *kset_get(struct kset *kset);

void kset_put(struct kset *kset);

 

kset также имеет имя, которое сохраняется во встроенном kobject-е. Таким образом, если у вас есть kset, названный my_set, вы должны установить его имя так:

 

kobject_set_name(&my_set->kobj, "The name");

 

Kset-ы имеют также указатель (в поле ktype) на структуру kobj_type, описывающую kobject-ы, которые он содержит. Этому типу отдаётся предпочтение перед полем ktype в самом kobject-е. Как результат, при обычном использовании поле ktype в struct kobject остаётся NULL, потому что на самом деле используются аналогичное поле внутри этого kset.

 

Наконец, kset содержит указатель на subsystem (подсистему) (называемый subsys). Так что пришло время поговорить о подсистемах.

Subsystem-ы

Subsystem (подсистема) является представлением для высокоуровневой части ядра в целом. Подсистемы обычно (но не всегда) отображаются на верху иерархии sysfs. Несколько примеров подсистем в ядре включают block_subsys (/sys/block, для блочных устройств), devices_subsys (/sys/devices, основная иерархия устройств), а также специальные подсистемы для каждого типа шины, известной ядру. Автору драйвера почти никогда не требуется создавать новую подсистему; если вы чувствуете соблазн сделать это, подумайте ещё раз. Что вы, вероятно, захотите в конце концов, так это добавить новый класс, как это описано в разделе "Классы".

 

Подсистема представляет собой простую структуру:

 

struct subsystem {

    struct kset kset;

    struct rw_semaphore rwsem;

};

 

Подсистема, таким образом, является на самом деле просто обёрткой kset-а с добавленным в неё семафором.

 

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

 

Подсистемы часто декларируется специальным макросом:

 

decl_subsys(name, struct kobj_type *type, struct kset_hotplug_ops *hotplug_ops);

 

Этот макрос создаёт struct subsystem с именем, сформированным из переданного в макрос name с добавленным к нему _subsys. Макрос также инициализирует внутренний kset с заданным type и hotplug_ops. (Мы обсудим операции hotplug (горячего подключения) позднее в этом главы).

 

Подсистемы имеют обычный список функции установки и демонтажа:

 

void subsystem_init(struct subsystem *subsys);

int subsystem_register(struct subsystem *subsys);

void subsystem_unregister(struct subsystem *subsys);

struct subsystem *subsys_get(struct subsystem *subsys)

void subsys_put(struct subsystem *subsys);

 

Большинство из этих операций воздействуют только на kset подсистемы.

Предыдущая  Содержание  Следующая