По мотивам
http://lwn.net/Articles/51437/ и Documentation/kobject.txt.
Всё, что вы никогда не хотели знать об объектах, множествах и типах объектов ядра.
Отчасти сложность понимания модели драйверов - и объектов ядра (kobject), абстракции, на которой построена эта модель - в том, что здесь нет ясного подхода, с которого можно было бы начать. Работа с объектами требует понимания других типов ядра, которые взаимосвязаны и ссылаются друг на друга. Чтобы упростить объяснение, мы попытаемся рассмотреть многосторонний подход и начнём с определения терминов, по ходу дела проясняя детали. Вот краткие определения сущностей, с которыми мы будем иметь дело:
-
kobject (объект ядра) - это объект типа struct kobject (внезапно :)). Объект имеет имя и счётчик ссылок. Также у него есть указатель на родительский объект, что позволяет огранизовать объекты в иерархию, определённый тип и, обычно, некоторое представление в виртуальной файловой системе sysfs.
В общем и в целом, сами по себе объекты интереса не представляют. Но они внедряются в другие структуры, с которыми код драйвера имеет дело
Подобного рода структура НИКОГДА не должна содержать более одного внедрённого объекта. Если нарушить это правило, подсчёт ссылок объекта наверняка окажется поломанным и ваш код будет работать неправильно. Так что не делайте этого.
-
ktype - тип объекта. Каждая структура, с которой ассоциирован объект ядра (далее, если явно не оговорено иное, просто объект для краткости, kobject), должна принадлежать какому-то типу ktype. Принадлежность объекта к типу ktype определяет, что происходит с объектом при его создании или уничтожении.
-
kset (множество) - группа объектов. Объекты, входящие в данное множество, могут принадлежать к одному или разным типам. Множество kset - это базовый контейнер для группы объектов. Множества kset имеют собственные, ассоциированные с ними объекты, но этой деталью реализации можно смело пренебречь, т.к. с этими объектами работает сам код поддержки kset.
Когда вы видите директорию в sysfs, забитую другими директориями, в общем случае эти директории представляют объекты, принадлежащие одному и тому же множеству.
Далее мы увидим, как создавать эти типы и манипулировать ими. Мы начнём с простого и выйдем снова на объекты.
Внедрение объектов
Ситуация, когда код ядра создаёт самостоятельные объекты, достаточно редка за исключением одного случая, который будет рассмотрен ниже. Вместо этого объекты используются для управления доступом к более сложным подсистемно-специфичным структурам. Таким образом, объект встраивается в управляемую структуру. Если вы привыкли думать в манере объектно-ориентированной парадигмы, kobject - это базовый абстрактный класс, которому наследуют другие классы. kobject реализует некоторые вещи, которые не очень нужны им самим, но очень полезные другим объектам. Язык C не позволяет напрямую пользоваться наследованием и чтобы компенсировать это ограничение, используется внедрение одних структур в другие.
(Для тех, кто знаком с реализацией списков в ядре, это аналогично тому, как сама по себе структура "list_head" обычно бесполезна, но для создания списков объектов её внедряют в другие структуры.)
Итак, для примера код в подсистеме пользовательского ввода-вывода UIO из drivers/uio/uio.c пользуется такой структурой, определяющей регион памяти, присвоенный UIO-устройству:
struct uio_map {
struct kobject kobj;
struct uio_mem *mem;
};
Работая с объектом типа struct uio_map structure, для доступа к ассоциированному объекту ядра вы просто используете поле kobj. В коде, который работает с самими объектами ядра, а не инкапсулирующими их типами возникает проблема, как по указателю на внедрённый объект выяснить, кому принадлежит данный объект? Вы должны избегать трюков и не подразумевать, например, что структура kobject находится в начале той структуры, в которую этот kobject внедрён. Вместо этого используйте макрос container_of() из <linux/kernel.h>:
container_of(pointer, type, member)
где:
- "pointer" указатель на внедрённый kobject,
- "type" тип инкапсулирующего объекта и
- "member" имя поля структуры, на которую указывает "pointer".
Значение, возвращаемое container_of() - это указатель на соответствующий тип-контейнер. Например, указатель "kp" на struct kobject, внедрённый внутрь struct uio_map может быть преобразован к типу-контейнеру struct uio_map таким образом:
struct uio_map *u_map = container_of(kp, struct uio_map, kobj);
Для удобства программисты часто определяют небольшие макросы "обратного приведения к типу" указателя на объект ядра к инкапсулирующему типу:
struct uio_map {
struct kobject kobj;
struct uio_mem *mem;
};
#define to_map(map) container_of(map, struct uio_map, kobj)
Здесь "map" это указатель на поле типа struct kobject в нашей структуре. Вот как этот макрос используется:
struct uio_map *map = to_map(kobj);
Инициализация объектов
Код, создающий объект, должен его инициализировать. Некоторые внутренние поля инициализируются обязательным вызовом kobject_init():
void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
Для объекта должен быть создан тип ktype, так как каждый объект должен иметь тип kobj_type. После вызова kobject_init(), чтобы зарегистрировать объект в sysfs, необходимо вызвать функцию kobject_add():
int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...);
Этим вызовом объекту присваивается родительский объект и имя. Если объект должен входить в какое-то множество, то перед вызовом kobject_add() поле kobj->kset должно быть заполнено соответствующим образом. Если объект сам ассоциирован со множеством kset то указатель на родительский объект может быть NULL, а родительским объектом станет само множество kset.
С момента регистрации объекта в ядре его имя не должно изменяться напрямую. Если необходимо переименовать объект, используйте функцию kobject_rename():
int kobject_rename(struct kobject *kobj, const char *new_name);
kobject_rename не держит блокировок и не определяет, как должно выглядеть имя объекта, так что синхронизацию и прочие проверки должен обеспечивать пользователь.
Есть ещё одна функция, kobject_set_name(), но она устарела и удаляется из кода. Если ваш код содержит этот вызов, вам стоит исправить код.
Чтобы получить доступ к имени объекта, используйте функцию kobject_name():
const char *kobject_name(const struct kobject * kobj);
Для того, чтобы проинициализировать и зарегистрировать объект в ядре, предусмотрена вспомогательная функция с достаточно неожиданным (уроки искромётного юмора от К-Х - перев.) именем kobject_init_and_add():
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
struct kobject *parent, const char *fmt, ...);
Смысл аргументов тот же, что для функций kobject_init() и kobject_add(), описанных выше.
События uevent
После того, как объект был зарегистрирован, необходимо уведомить всех о том, что мы создали объект. Сделать это можно с помощью функции kobject_uevent():
int kobject_uevent(struct kobject *kobj, enum kobject_action action);
При добавлении объекта используйте флаг KOBJ_ADD в качестве аргумента action. Это должно делаться только после того, как были проинициализированы атрибуты дочерних объектов, потому что при уведомлении о новом объекте юзерспейс сразу же обратиться к этим данным.
Когда объект удаляется из ядра (детали, как это делается, см. ниже), создаётся событие uevent для соответствующего действия KOBJ_REMOVE. Пользоателю нет нужды об этом волноваться, т.к. всю работу берёт на себя подсистема объектов.
Подсчёт ссылок
Одна из ключевых функций объектов - это подсчёт ссылок на то, во что он внедрён. До тех пор, пока есть ссылки на данный объект, объект (и код, который его поддерживает) должен существовать. Низкоуровневые функции для манипуляций счётчиком ссылок на объект:
struct kobject *kobject_get(struct kobject *kobj);
void kobject_put(struct kobject *kobj);
Успешный вызов kobject_get() увеличит счётчик ссылок на объект и вернёт указатель на него.
Когда ссылка больше не нужна, вызов kobject_put() уменьшит счётчик и, вероятно, освободит объект. Обратите внимание, что kobject_init() устанавливает счётчик ссылок в 1, так что код, который проинициализировал объект в конечном счёте должен будет вызвать kobject_put(), чтобы освободить объект.
Т.к. по своей природе объекты динамичны, они не должны объявляться статически или на стеке. Вместо этого они должны создаваться динамически. В будущем ядро будет содержать код, выявляющий статически созданные объекты и предупреждающий программиста о неправильном использовании объектов.
Если всё, что вам нужно, это подсчёт ссылок на вашу структуру, используйте kref, а не kobject. Последний будет избыточен для одного лишь аудита объектов. Чтобы узнать больше о kref, прочтите файл Documentation/kref.txt в дереве кода ядра Linux.
Создание "простых" объектов
Иногда программисту нужно просто создать директорию в иерархии sysfs без использования множеств, функций чтения/сохранения и прочих деталей. Это единственное исключение, когда нужно создавать самостоятельный объект. Чтобы создать его, используйте следующую функцию:
struct kobject *kobject_create_and_add(char *name, struct kobject *parent);
Эта функция создаст объект и поместит в sysfs директорию под директорией, которая соответствует родительскому объекту. Чтобы создать простые атрибуты объекта, используйте вызов:
int sysfs_create_file(struct kobject *kobj, struct attribute *attr);
или
int sysfs_create_group(struct kobject *kobj, struct attribute_group *grp);
Оба типа атрибутов, которые здесь используются, для объекта, созданного с помощью kobject_create_and_add(), могут иметь тип kobj_attribute, так что создавать новые пользовательские атрибуты необязательно.
См. примеры реализации простого объекта и атрибутов в модулях в samples/kobject/kobject-example.c.
Типы ktype и деструкторы объекта
До сих пор мы не касались одного важного момента. Что происходит с объектом, когда его счётчик ссылок обнуляется? Обычно код, создавший объект, не знает, когда наступит такой момент; если бы это было возможно, то особого смысла в использовании объектов просто не было бы. Когда мы имеем дело с sysfs даже объекты с предсказуемым жизненным циклом становятся сложными в этом плане, потому что какая-то другая часть ядра может получить ссылку на любой объект, зарегистрированный в системе.
Конечным итогом этой ситуации является то, что объект не может быть освобождён до тех пор, пока счётчик ссылок на него не равен нулю. Подсчёт ссылок не находится под прямым контролем того кода, который создал объект. Код, породивший объект, должен быть асинхронно уведомлён об обнулении счётчика ссылок на данный объект.
Создав свой объект с помощью kobject_add() вы никогда не должны использовать на нём kfree(), чтобы напрямую осводобить объект. Единственный безопасный способ сделать это - использовать kobject_put(). Вызов kobject_put() сразу после kobject_init() является хорошим правилом, которое позволит предотвратить возможные ошибки.
Уведомление об обнулении счётчика ссылок происходит с помощью метода объекта - release(). Обычно это выглядит как-то так:
void my_object_release(struct kobject *kobj)
{
struct my_object *mine = container_of(kobj, struct my_object, kobj);
kfree(mine);
}
Важный момент, которым нельзя пренебречь: каждый объект должен иметь метод release() и объект должен находиться в непротиворечивом состоянии ровно до тех пор, пока не вызван этот метод. Код, нарушающий эти требования нарушены, дефективен. ОБратите внимание, ядро предупредит вас, если объект не имеет метода release(). Не пытайтесь избавиться от этих предупреждений, создавая пустые заглушки вместо реально работающего метода; если вы попытаетесь провернуть такой трюк, вам придётся иметь дело с беспощадным мэйнтейнером подсистемы kobject.
Имейте в виду, имя объекта доступно из метода-деструктора, но оно НЕ ДОЛЖНО изменяться внутри кода деструктора. В противном случае это может привести к утечке памяти в подсистеме управления объектов, а такие вещи печалят пользователей.
Любопытным является то обстоятельство, что метод release() не хранится внутри самого объекта; вместо этого деструктор ассоциирован с соответствующим типом объектов - ktype. Давайте посмотрим на структуру struct kobj_type:
struct kobj_type {
void (*release)(struct kobject *kobj);
const struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
Эта структура описывает определённый тип объектов (если быть более точным, тип инкапсулирующего kobject объекта). Каждый объект должен принадлежать к какому-то типу, описанному структурой kobj_type; указатель на структуру этого типа должен быть передан при вызове функции kobject_init() или kobject_init_and_add().
Поле release в struct kobj_type это, конечно, указатель на метод release() для данного типа объектов. Два других поля (sysfs_ops и default_attrs) определяют, как объекты данного типа представляются в sysfs; эти поля мы рассматривать не будем, потому что их описание выходит за рамки данного документа.
Указатель default_attrs указывает на список атрибутов по умолчанию, которые будут создавать для любого объекта данного типа.
kset
kset - это, в общем-то, множество объектов, которые должны быть ассоциированы друг с другом. Объекты, принадлежащие одному множеству, не обязаны быть одного типа. Но когда множество гетерогенно, будьте осторожны.
Множества выполняют следующие функции:
- Множество - это своеобразный мешок, куда складываются объекты. Оно может использоваться для отслеживания "всех блочных устройств" или "всех драйверов PCI-устройств"."
- Множество - это также поддиректория в sysfs, где отображаются объекты, ассоциированные с данным множеством. Каждое множество содержит объект, который может быть родительским объектом для других объектов; в иерархии sysfs директории верхнего уровня устроены именно так.
- Множества могут поддерживать "горячее подключение" ("hotplugging") объектов и влиять на то, как генерируются события uevent для юзерспейса.
С точки зрения объектно-ориентированной парадигмы множество "kset" это класс-контейнер верхнего уровня; множество имеет собственный kobject, который, однако, управляется подсистемой управления множествами и не должен напрямую манипулироваться другими пользователями.
Объекты, принадлежащие множеству, организованы в обычный связный список ядра. Объекты указывают на содержащее их множество с помощью своего поля kset. Почти во всех случаях объекты, принадлежащие данному множеству, указывают на соответствующий kset, как на своего родителя (точнее, внедрённый kobject данного множества kset).
Поскольку множество имеет свой внедрённый объект, оно всегда должно создаваться динамически, а не объявляться статически или размещаться на стеке. Чтобы создать новое множество, используйте следующие функции:
struct kset *kset_create_and_add(const char *name,
struct kset_uevent_ops *u,
struct kobject *parent);
Когда множество вам уже больше не нужно, вызовите:
void kset_unregister(struct kset *kset);
чтобы уничтожить его. Таким образом множество будет удалено из sysfs, а его счётчик ссылок будет уменьшен (
ну, вероятно множество исчезнет из sysfs только когда его счётчик ссылок дропнется до нуля - перев.) Когда счётчик ссылок обнулится, множество будет уничтожено. Т.к. могут существовать другие ссылки на данное множество, то вполне возможно, что множество будет уничтожено после того, как
kset_unregister() возвратит управление.
Пример использования множества можно найти в файле samples/kobject/kset-example.c дерева исходного кода ядра.
Если множество должно управлять генерацией событий uevent, соответствующие операции можно определить в структуре struct kset_uevent_ops:
struct kset_uevent_ops {
int (*filter)(struct kset *kset, struct kobject *kobj);
const char *(*name)(struct kset *kset, struct kobject *kobj);
int (*uevent)(struct kset *kset, struct kobject *kobj,
struct kobj_uevent_env *env);
};
Функция filter позволяет множеству отфильтровать события, посылаемые юзерспейсу, для некоторого объекта. Если указатель равен 0, события не будут генерироваться.
Функция name будет вызывана для переопределения имени множества, которое посылает событие. По умолчанию сохраняется имя множетва-источника события, но с помощью данной функции это имя можно изменить.
uevent вызывается, когда событие готово к отправке в юзерспейс и позволяет передать дополнительные переменные окружения в событии uevent.
Может возникнуть вопрос, а как, собственно, добавить в множество объекты, если нет специальной функции, которая бы это делала? (но мы-то с вами уже знаем ответ - перев.) Ответ таков: это делает функция kobject_add(). Когда мы передаём kobject_add() объект, его поле kset должно указывать на то множество, в которое этот объект должен быть включён. Остальную работу выполняет сама kobject_add().
Если родительский объект для объекта, принадлежащего данному множеству, не установлен, то он будет добавлен в директорию множества. Не все объекты множества обязательно "сидят" в директории, закреплённой за данным множеством. Если перед добавлением объекта в множество его указатель на родителя определён явным образом, объект регистрируется во множестве, но в файловой иерархии он появится в директории объекта-родителя.
Удаление объектов
После того, как подсистема управления объектами успешно зарегистрировала объект, он должен быть удалён, когда в нём больше нет нужды. Чтобы сделать это, используйте kobject_put(). Этот вызов автоматически освободит ресурсы, занимаемые объектом. Если при регистрации объекта генерируется событие KOBJ_ADD, то при его удалении - KOBJ_REMOVE. Попутно будет выполнена необходимая работа на уровне sysfs.
В случае, если вы хотите удалить объект в два шага (например, ваш код не может спать во время уничтожения объекта), воспользуйтесь вызовом kobject_del(), который разрегистрирует объект в sysfs. Сам объект становится "невидимым", но он продолжает существовать и число ссылок на него остаётся неизменным. Позже вызовите kobject_put(), чтобы освободить память, выделенную под объект.
kobject_del() может быть использована, чтобы убрать ссылку на родительский объект, если созданы циркулярные ссылки. В некоторых случаях это нормальное явление, что родительский объект ссылается на дочерний. Циркулярные ссылки должны устраняться явным вызовом функции kobject_del() так, что будут вызваны деструкторы и объекты, которые были циклически связаны, освободят друг друга.
Где взять код примеров
Чтобы иметь более полные примеры корректного использования объектов и можеств, изучите файлы samples/kobject/{kobject-example.c,kset-example.c}, которые будут скомпонованы как загружаемые модули ядра, если при конфигурировании ядра была активирована опция CONFIG_SAMPLE_KOBJECT.
Aftermath
Мы быстро пробежались по API объектов, вкратце ознакомились с устройством самих объектов и связанных с ними структур. Пришло время спросить, а зачем, собственно, всё это нужно? На самом деле, в тексте К-Х уже есть частичный ответ. Там упоминались события uevent. Хотя бы вот для этого. Но обо всё по порядку.
Как мы увидели, объекты предоставляют средство подсчёта ссылок, они внедряются в тот объект, которым управляют. У объектов одного типа - один деструктор. Объекты могут собираться во множества (или наборы, коллекции - как угодно). Что, помимо учёта ссылок и универсальных деструкторов дают нам объекты? Прежде всего, это структуризация. Если мы рассмотрим некое реальное устройство, то легко увидеть, что жёсткий диск подсоединён к порту SATA, порт SATA сидит на SATA-шине, SATA-шина управляется SATA-контроллером... Иерархия. Объекты позволяют нам выстроить иерархию устройств, подсоединённых к системе. Объекты ядра тесно взаимосвязаны с псевдо-ФС sysfs. Вся та лапша, которую можно увидеть под /sys - это отображения картины подключения и взаимосвязей устройств в системе, как её видит ядро. На самом верхнем уровне в /sys мы можем найти такие директории, как class, devices, bus, power, например. Если вы сталкивались с программированием драйверов для OS X, то имеете преставление об IORegistry и планах устройств. Все устройства в системе могут быть классифицированы по разным признакам. Например, является устройство блочным или символьным, каковы его потребности в питании и откуда оно это питание берёт, на какой шине сидит устройство. На самом деле, директории верхнего уровня в /sys в известной степени выполняют ту же задачу, что планы IORegistry и поэтому одно устройство может быть представлено во многих директориях. Например, тот же диск у нас будет в devices/, а симлинк на него будет и в bus/ и в block/. Кроме того, sysfs экспортирует также атрибуты устройств. Эти атрубуты являются атрибутами объектов и раз уж мы затронули эту тему, то наверно пробежимся по ней быстро. На основе текста, с сокращениями и некоторыми вольностями взятого отсюда: http://lwn.net/Articles/54651/
Как объекты получают своё воплощение в sysfs
Как мы видели ранее, при использовании kobject_init() мы получим самостоятельный объект, не представленный в sysfs. Если же воспользоваться API kobject_register() (или kobject_add()), в sysfs будет создана соответствующая этому объекту директория; других усилий со стороны программиста не требуется.
Именем директории будет имя объекта. Место директории в структуре sysfs определяется его позицией в иерархии объектов. Короче, директория объекта будет находиться в директории, редставляющей родительский объект данного объекта, т.е., того объекта, на который указывает поле parent. Если поле parent вашего объекта указывает на kset, то родителем этого объекта станет объект соответствующего множества kset. Если указатель parent не указфвает ни на kset, ни на другой объект, то директория объекта окажется в sysfs на самом верхнем уровне.
Заполнение директории объекта
Получить представление объекта в sysfs легко, как мы убедились. Однако, эта директория будет пустой и пользы от неё будет не много. Для связи объекта ядра с пользовательским окружением было бы лучше, если директория содержит какие-то атрибуты.Создание атрибутов требует дополнительных шагов, но в целом, это несложно.
Основные атрибуты в sysfs - это атрибуты по умолчанию, которые описываются в типе объекта kobj_type. Эти атрибуты присущи каждому объекту данного типа (см. default_attrs выше). Поле default_attrs - это указатель на массив указателей атрибутов со следующей структурой:
struct attribute {
char *name;
struct module *owner;
mode_t mode;
};
name - это, собственно, имя атрибута (имя файла в sysfs), owner - указатель на модуль ядра (если применимо), который непосредственно имеет дело с этими атрибутами и, наконец, mode - режим доступа к файлу (обычная триада rwx-битов для владельца, группы и остальных). Как правило, режим - S_IRUGO для read-only атрибутов; если атрибут изменяемый (для пользователя из юзерспейса), то режим может быть S_IWUSR, что даст суперпользователю доступ к файлу в режиме записи. Последним элементом в массиве default_attrs должен быть NULL.
Поле default_attrs указывает на то, какие атрибуты есть в принципе, но они ещё никак не реализованы на уровне sysfs. Поле kobj_type->sysfs_ops определяет, как атрибуты отображаются или считываются:
struct sysfs_ops {
ssize_t (*show)(struct kobject *kobj, struct attribute *attr,
char *buffer);
ssize_t (*store)(struct kobject *kobj, struct attribute *attr,
const char *buffer, size_t size);
};
Эти функции будут вызываться дл каждой операции чтения или записи соответственно для каждого объекта данного типа. В каждлом случае kobj - это указатель на тот объект, чьи атрибуты изменяются или читаются, attr - указатель на переменную типа struct attribute, описывающую атрибут и буфер размером в страницу для данных атрибута.
Задача функции show() закодировать полное значение атрибута, при этом не выходя за пределы PAGE_SIZE. Имейте в виду, что по соглашению атрибут sysfs содержит только одно значение или, по крайней мере, массив значений одного типа, так что ограничени на размер буфера не должно быть проблемой. Возвращаемое значение - это число байт, записанных в буфер или -1 в случае ошибки. Рассматривать store() нет особого смысла, суть аргументов та же, только обратное направление (чтение из буфера, а не запись в буфер) и дополнительный аргумент size, определяющий длину входных данных.
Итак, для работы с одним выбранным атрибутом данного типа kobj_type нужна своя пара функций show() и store().
Собственные атрибуты объектов
Часто default_attrs в kobj_type исчерпывает собой все атрибуты, которые имеет объект. Но это вовсе не обязательно всегда так; атрибуты объекта могут быть добавлены или удалены по желанию. Если вам нужно задать новый атрибудт объекта, просто заполните структуру, описывающую атрибут, и передаёте указатель на неё следующему вызову:
int sysfs_create_file(struct kobject *kobj, struct attribute *attr);
Если всё прошло гладко, будет создан файл см именем, заданным соответствующим полем структуры struct attribute и функция возвратит 0; в противном случае код возврата будет меньше нуля.
Как это ни удивительно, но удаляется атрибут таким вызовом:
int sysfs_remove_file(struct kobject *kobj, struct attribute *attr);
Кроме того, атрибуты могут связываться симлинками, они могут быть бинарными. Но эти детали мы опустим, потому что и так уже увлеклись. Суть должна быть уже ясна. Что дальше? Чтобы понять, что дальше, попробуем ответить на вопрос, а как, собственно, пользовательское окружение узнаёт о том, что к сиетеме подключено то или иное устройство? Когда-то, когда деревья были большими, а компьютеры - большими и глупыми в Linux были статичные файлы устройств. О недостатках этого способа работы с железом говорить не будем. Статический /dev сменился динамической файловой системой devfs и демоном devfsd, который заведовал всем этим хозяйством - такой подход был принят в FreeBSD. Ещё позже на смену devfsd пришёл udev (о нём я тоже писал, точнее, переводил :)) Не будем углубляться в то, как был устроен hotplug раньше и вообще, опустим все эти исторические детали. Сейчас udev - это хитрый демон, работающий в режиме пользователя, и слушающий netlink-сокет. Когда в ядре регистрируется или удаляется объект, ядро посылает uevent сообщение в сокет (вспомнили kobject_uevent()?). udev проделывает всю нужную работу по созданию спецфайла в /dev, подгрузке модуля, если необходимо, и созданию ополнительных симлинков. Таким образом, объекты - основа работы хотплага. Но не только его. На этапе, когда ядро уже загрузилось, но udev ещё не запустился, надо сделать так, чтобы и подключенные ранее устройства были учтены, однако посылать сообщения в netlink-сокет нет смысла. Его никто не слушает. Но вот смонтировать и заполнить sysfs мы вполне можем безо всякого udev. Когда udev будет запущен, он сможет пройтись по иерархии sysfs и создать файлы устройств. Вот зачем нам нужна sysfs и атрибуты. В частности. В общем, это нечто более глубокое, чем просто механизм поддержки хотплага. Реализация объектной модели драйверов в Linux делает возможным лёгкую реализацию хотплага, но суть именно в отношениях различных классов устройств, шин, портов и всего такого. Однако, это уже другая и определённо долгая история. Нашей целью было лишь слегка потыкать палочкой в объекты, а не заниматься всеобъемлющим описанием их анатомии, физиологии и экологии. Возможно, как-нибудь уделим внимание и этому.