Библиотека сайта rus-linux.net
Цилюрик О.И. Модули ядра Linux | ||
Назад | Внешние интерфейсы модуля | Вперед |
Интерфейс /sys
Одно из главных «приобретений» ядра, начинающееся от версий 2.6 — это появление единой унифицированной модель представления устройств в Linux. Главные составляющие, сделавшие возможным её существование, это файловая система sysfs и дуальный к ней (поддерживаемый ею) пакет пользовательского пространства udev. Модель устройств— это единый механизм для представления устройств и описания их топологии в системе. Декларируется множество преимуществ, которые обусловлены созданием единого представления устройств:
- Уменьшается дублирование кода.
- Используется механизм для выполнения общих, часто встречающихся функций, таких как счетчики использования.
- Возможность систематизации всех устройств в системе, возможность просмотра состояний устройств и определения, к какой шине то или другое устройство подключено.
- Обеспечивается возможность связывания устройств с их драйверами и наоборот.
- Появляется возможность разделения устройств на категории в соответствии с различными классификациями, таких как устройства ввода, без знания физической топологии устройств.
- Обеспечивается возможность просмотра иерархии устройств от листьев к корню и выключения питания устройств в правильном порядке.
Файловая система sysfs возникла первоначально из нужды поддерживать последовательность действий в динамическом управлении электропитанием (иерархия устройств при включении-выключении) и для поддержки горячего подключения устройств (то есть в обеспечение последнего пункта перечисления). Но позже модель оказалась гораздо плодотворнее. Сама по себе эта система является весьма сложной и объёмной, и о ней одной можно и нужно писать отдельную книгу. Но в контексте нашего рассмотрения нас интересует, в первую голову, возможность создания интерфейса из модуля к файловым именам, в файловой системе /sys. Эта возможность весьма напоминает то, как модуль создаёт файловые имена в подсистеме /proc.
Базовым понятием модели представления устройств являются объекты struct kobject (определяется в файле <linux/kobject.h>). Тип struct kobjec по смыслу аналогичен абстрактному базовому классу Object в объектно-ориентированных языков программирования, как С# и Java. Этот тип определяет общую функциональность, такую как счетчик ссылок, имя, указатель на родительский объект, что позволяет создавать объектную иерархию.
Зачастую объекты struct kobject сами по себе не создаются и не используются, они встраиваются в другие структуры данных, после чего те приобретают свойства, присущие struct kobject, например, такие, как встраиваемость в иерархию объектов. Вот как это выражается в определении уже известной нас структуры представления символьного устройства:
struct cdev { struct kobject kobj; struct module *owner; struct file_operations *ops; ... };
Во внешнем представлении в /sys, в интересующем нас смысле, каждому объекту struct kobject соответствует каталог, что видно и из самого определения:
struct kobject { ... struct kobj_type *ktype; struct dentry *dentry; };
Но это вовсе не означает, что каждый инициализированный объект автоматически экспортируется в файловую систему /sys. Для того, чтобы объект сделать видимым в /sys, необходимо вызвать:
int kobject_add( struct kobject*kobj );
Но это не придётся делать явно нам в примерах ниже, просто по той простой причине, что используемые для регистрации имён в /sys высокоуровневые вызовы API (class_create()) делают это за нас.
Таким образом, объекты struct kobject естественным образом отображаются в каталоги пространства имён /sys. Файловая система sysfs это дерево каталогов без файлов. А как создать файлы в этих каталогах, в содержимое которых отображаются данные ядра? Каждый объект struct kobject (каталог) содержит (через свой компонент struct kobj_type) массив структур struct attribute :
struct kobj_type { ... struct sysfs_ops *sysfs_ops; struct attribute **default_attrs; }
Вот каждая такая структура (определена в <linux/sysfs.h>) и является определением одного файла, содержащегося в рассматриваемом каталоге:
struct attribute { ... char *name /* имя атрибута-файла */; mode_t mode struct /* права доступа к файлу */; }
А показанная там же структура таблицы операций (struct sysfs_ops) содержит два поля — определения функций show(...) и store(...), соответственно, чтения и записи символьного поля данных ядра, отображаемых этим файлом (и сами функции и их прототипы показаны в примере ниже).
Этих сведений о sysfs нам должно быть достаточно для создания интерфейса модуля в пространство имён /sys, но перед тем, как переходить к примеру, остановимся в два слова на аналогичностях и различиях /proc и /sys в качестве интерфейса для отображения модулем подконтрольных ему данных ядра. Различия систем /proc и /sys — складываются главным образом на основе негласных соглашений и устоявшихся традиций:
- информация терминальных имён /proc — комплексная, обычно содержит большие объёмы текстовой информации, иногда это таблицы, и даже с заголовками, проясняющими смысл столбцов таблицы;
- информацию терминальных имён /sys (атрибутов) рекомендуется оформлять в виде а). простых, б). символьных значений, в). представляющих значения, соответствующие скалярным типам данных языка C (int, long, char[]);
Сравним:
$ cat /proc/partitions | head -n5
major minor #blocks name 33 0 10022040 hde 33 1 3783276 hde1 33 2 1 hde2
$ cat /sys/devices/audio/dev
14:4
$ cat /sys/bus/serio/devices/serio0/set
2
В первом случае это (потенциально) обширная таблица, с сформированным заголовком таблицы, разъясняющим смысл колонок, а во втором — представление целочисленных значений.
А теперь мы готовы перейти к рассмотрению возможного вида модуля (архив sys.tgz), читающего и пишущего из/в атрибута-имени, им созданного в /sys (большая часть происходящего в этом модуле, за исключения регистрации имён в /sys нам уже известно):
xxx.c :
#include <linux/fs.h> #include <linux/cdev.h> #include <linux/parport.h> #include <asm/uaccess.h> #include <linux/pci.h> #include <linux/version.h> #define LEN_MSG 160 static char buf_msg[ LEN_MSG + 1 ] = "Hello from module!\n"; /* <linux/device.h> LINUX_VERSION_CODE > KERNEL_VERSION(2,6,32) struct class_attribute { struct attribute attr; ssize_t (*show)(struct class *class, struct class_attribute *attr, char *buf); ssize_t (*store)(struct class *class, struct class_attribute *attr, const char *buf, size_t count); }; LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,32) struct class_attribute { struct attribute attr; ssize_t (*show)(struct class *class, char *buf); ssize_t (*store)(struct class *class, const char *buf, size_t count); }; */ /* sysfs show() method. Calls the show() method corresponding to the individual sysfs file */ #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,32) static ssize_t x_show( struct class *class, struct class_attribute *attr, char *buf ) { #else static ssize_t x_show( struct class *class, char *buf ) { #endif strcpy( buf, buf_msg ); printk( "read %d\n", strlen( buf ) ); return strlen( buf ); } /* sysfs store() method. Calls the store() method corresponding to the individual sysfs file */ #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,32) static ssize_t x_store( struct class *class, struct class_attribute *attr, const char *buf, size_t count ) { #else static ssize_t x_store( struct class *class, const char *buf, size_t count ) { #endif printk( "write %d\n" , count ); strncpy( buf_msg, buf, count ); buf_msg[ count ] = '\0'; return count; } /* <linux/device.h> #define CLASS_ATTR(_name, _mode, _show, _store) \ struct class_attribute class_attr_##_name = __ATTR(_name, _mode, _show, _store) */ CLASS_ATTR( xxx, 0666, &x_show, &x_store ); static struct class *x_class; int __init x_init( void ) { int res; x_class = class_create( THIS_MODULE, "x-class" ); if( IS_ERR( x_class ) ) printk( "bad class create\n" ); res = class_create_file( x_class, &class_attr_xxx ); /* <linux/device.h> extern int __must_check class_create_file(struct class *class, const struct class_attribute *attr); */ printk( "'xxx' module initialized\n" ); return 0; } void x_cleanup( void ) { /* <linux/device.h> extern void class_remove_file(struct class *class, const struct class_attribute *attr); */ class_remove_file( x_class, &class_attr_xxx ); class_destroy( x_class ); return; } module_init( x_init ); module_exit( x_cleanup ); MODULE_LICENSE( "GPL" );
Примечание: В первых строках кода (в виде комментариев) приведены варианты определений (взято из хэдер-файлов), отличающихся даже не между версиями ядра, а между близкими подверсиями ядра: код проверялся в версиях 2.6.32 и 2.6.35 - это лишний раз говорит о волатильности API ядра, и, особенно, ещё не устоявшейся подсистемы sysfs.
Тестируем код:
$ sudo insmod ./xxx.ko
$ ls -l /sys/class/x-class
-rw-rw-rw- 1 root root 4096 Мар 30 21:54 xxx
$ ls -lR /sys/class/x-class
/sys/class/x-class:
-rw-rw-rw- 1 root root 4096 Мар 30 21:54 xxx
$ cat /sys/class/x-class/xxx
Hello from module!
$ echo 12345 > /sys/class/x-class/xxx
$ cat /sys/class/x-class/xxx
12345
$ cat /sys/class/x-class/xxx
12345
$ ls /sys/module/xxx/
holders initstate notes refcnt sections srcversion
$ sudo rmmod xxx
$ cat /sys/class/x-class/xxx
cat: /sys/class/x-class/xxx: Нет такого файла или каталога
На этом мы и остановимся в рассмотрении подсистемы /sys. Потому, как сейчас функции /sys в Linux расширились настолько, что об этой файловой подсистеме одной можно и нужно писать отдельную книгу: все устройства в системе (сознательно стараниями его автора, или даже помимо его воли) — находят отображения в /sys, а сопутствующая ей подсистема пользовательского пространства udev динамически управляет правилами создания имён и полномочия доступа к ним. Но это — совершенно другая история. Мы же в кратком примере рассмотрели совершенно частную задачу: как из собственного модуля создать интерфейс к именам в /sys, для создания диагностических или управляющих интерфейсов этого модуля.
Предыдущий раздел: | Оглавление | Следующий раздел: |
Интерфейс /proc | Сеть |