Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Драйверы устройств в Linux

Часть 17: Взаимодействие с модулями

Оригинал: "Device Drivers, Part 17: Module Interactions"
Автор: Anil Kumar Pugalia
Дата публикации: May 3, 2012
Перевод: Н.Ромоданов
Дата перевода: июнь 2012 г.

В этой статье, которая является частью серии статей о драйверах устройств в Linux, демонстрируются различные варианты взаимодействия с модулями Linux.

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

Глобальные переменные и функции

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

Простым примером может стать драйвер, переключающийся между несколькими файлами, с функцией (функциями), находящейся в одном файле и вызываемой из другого файла. Сейчас для того, чтобы даже в такой ситуации избежать какой-нибудь коллизии в пространстве имен ядра, каждый модуль должен находиться в своем собственном пространстве имен. И мы знаем, что одновременно нельзя загружать два модуля с одним и тем же именем. Таким образом, можно достигнуть полного отсутствия коллизий. Но это также значит, что ничего, что есть внутри модуля, нельзя даже в том случае, если мы этого захотим, использовать в ядре в качестве глобальных переменных. И именно для таких случаев в заголовочном файле <linux/module.h> определяются следующие макросы:

  • EXPORT_SYMBOL(sym)
  • EXPORT_SYMBOL_GPL(sym)
  • EXPORT_SYMBOL_GPL_FUTURE(sym)

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

#include <linux/module.h>
#include <linux/device.h>
 
static struct class *cool_cl;
static struct class *get_cool_cl(void)
{
    return cool_cl;
}
EXPORT_SYMBOL(cool_cl);
EXPORT_SYMBOL_GPL(get_cool_cl);
 
static int __init glob_sym_init(void)
{
    if (IS_ERR(cool_cl = class_create(THIS_MODULE, "cool")))
    /* Creates /sys/class/cool/ */
    {
        return PTR_ERR(cool_cl);
    }
    return 0;
}
 
static void __exit glob_sym_exit(void)
{
    /* Removes /sys/class/cool/ */
    class_destroy(cool_cl);
}
 
module_init(glob_sym_init);
module_exit(glob_sym_exit);
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anil Kumar Pugalia <email_at_sarika-pugs.com>");
MODULE_DESCRIPTION("Global Symbols exporting Driver");

Для каждой экспортируемой символьной переменной также есть соответствующая структура, которая для того, чтобы к ней был глобальный доступ, размещается в (каждой из следующих) таблице символов ядра (__ksymtab), таблице строк ядра (__kstrtab) и в таблице контрольных сумм CRC ядра (__kcrctab).

На рис.1 показан фрагмент директория /proc/kallsyms до и после загрузки модуля our_glob_syms.ko, который компилировался с использованием обычного файла Makefile, предназначенного для драйверов.

Рис.1: Наш модуль глобальных символьных переменных

В следующем коде показан вспомогательный заголовочный файл (our_glob_syms.h), который должен добавляться в модули, использующие экспортные символьные переменные ool_cl и get_cool_cl:

#ifndef OUR_GLOB_SYMS_H
#define OUR_GLOB_SYMS_H
 
#ifdef __KERNEL__
#include <linux/device.h>
 
extern struct class *cool_cl;
extern struct class *get_cool_cl(void);
#endif
 
#endif

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

Обратите внимание, что в заголовочном файле <linux/device.h>, используемом в приводимых выше примерах, добавляются объявления и определения для разнообразных классов, о чем уже ранее упоминалось в более раннем обсуждении символьных драйверов

Параметры модуля

Зная о том, как аргументы командной строки передаются в приложение, было бы естественно спросить, можно ли что-то подобное сделать с модулем - и ответом будет: да, это возможно. Параметры можно передать в модуль при его загрузке, например, с помощью команды insmod. Интересно, что в отличие от аргументов командной строки, указываемых для приложения, параметры для модуля можно будет впоследствии изменить, если получить доступ к sysfs.

Параметры модуля настаиваются с помощью следующего макроса (определяется в <linux/moduleparam.h>, который находится в <linux/module.h>):

module_param(name, type, perm)

Здесь name является именем параметра, type является типом параметра, а с помощью perm задаются права доступа к файлу sysfs, соответствующему данному параметру. Поддерживаются значения следующих типов: byte, short, ushort, int, uint, long, ulong, charp (символьный указатель), bool и invbool (инвертированный Boolean).

В следующем коде модуля (module_param.c) продемонстрировано использование в модуле параметра:

#include <linux/module.h>
#include <linux/kernel.h>
 
static int cfg_value = 3;
module_param(cfg_value, int, 0764);
 
static int __init mod_par_init(void)
{
    printk(KERN_INFO "Loaded with %d\n", cfg_value);
    return 0;
}
 
static void __exit mod_par_exit(void)
{
    printk(KERN_INFO "Unloaded cfg value: %d\n", cfg_value);
}
 
module_init(mod_par_init);
module_exit(mod_par_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anil Kumar Pugalia <email@sarika-pugs.com>");
MODULE_DESCRIPTION("Module Parameter demonstration Driver");

Рис.2: Эксперименты с параметром модуля

Рис.3: Эксперименты с параметром модуля (в роли пользователя root)

Обратите внимание, что прежде, чем настраивать параметр, необходимо определить переменную с тем же самым именем и совместимым типом данных. Затем выполните следующие действия и поэкспериментируйте так, как это показано на рис.2 и 3:

  • Соберите драйвер (файл module_param.ko) — используйте для этого обычный файл Makefile, предназначенный для драйверов.
  • Загрузите драйвер с помощью команды insmod (с параметрами и без параметров)
  • Проведете эксперименты с соответствующими записями в /sys
  • И, наконец, выгрузите драйвер с помощью команды rmmod

Обратите внимание на следующее:

  • Когда команда insmod запускается без параметров, то значением, используемым по умолчанию, станет начальное значение (равное 3) переменной cfg_value.
  • Для файла параметров cfg_value модуля module_param, находящегося в /sys/module/, установлены права доступа 0764, что означает rwx — для пользователя, rw- - для группы и r-- - для всех остальных.

Проверьте самостоятельно:

  • Посмотрите результат работы команды dmesg/tail для всех данных, выдаваемых операцией печати printk в каждой из команд insmod и rmmod
  • Попытайтесь в роли обычного пользователя (не имеющего прав доступа root) сделать запись в файл /sys/module/module_param/parameters/cfg_value

Подведем итог

Благодаря последним полученным знаниям у наших двух героев достаточно хорошее представление о драйверах в Linux, и они готовы начать работу над завершающим проектом семестра. Есть предположения о теме, которой будет посвящен их проект? Подсказка: они выбрали одну из самых сложных тем, связанную с драйверами в Linux. Давайте в следующей статье посмотрим, как они с ней справятся.


К предыдущей статье Оглавление