Создать символьное устройство linux

Символьное устроство в модуле ядра

Мы уже создали наш первый модуль ядра и научились передавать параметры в ядро через командную строку.
Теперь посмотрим как создать символьное устроство.
Зачем это нужно?
Как мы уже знаем, в пространстве ядра пользовательские приложения ничего менять не могут.
Через командную строку можно передать параметры только при запуске модуля. В дальнешем мы по сути теряем связь с нашим творением и можем только наблюдать. Это не всегда удобно, скорее всегда не удобно.
Таким образом нам нужен интерфейс который был бы доступен на протяжении всего времени работы с модулем. Таким интерфесом является файл. Ядро может писать в файл, пользовательские приложения могут писать в файл. Идилия!

Приступим к реализации.
В Linux есть такая штука — character device. У нас его зовут символьным устройством. Он вполне себе подходит для наших целей.
Для создания этого устройства воспользуемся функцией register_chrdev. Ей нужно передать номер, имя для файла и структуру file_operations. Каждому зарегистрированному символьному устройству присвоен номер (Major number). Его можно указать в качестве первого параметра функции register_chrdev. А можно указать туда 0, тогда ядро присвоит этот номер из своих соображений.

Как всегда Makefile берем из первой статьи.
Создаем заголовочный файл с прототипами необходимых функций.

#define DEVICE_NAME "chardev" /* Dev name as it appears in /proc/devices */ #define BUF_LEN 80 /* Max length of the message from the device */ static int __init blablamod_init(void); static void __exit blablamod_exit(void); static int device_open(struct inode *, struct file *); static int device_release(struct inode *, struct file *); static ssize_t device_read(struct file *, char *, size_t, loff_t *); static ssize_t device_write(struct file *, const char *, size_t, loff_t *); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Dmitrey Salnikov ");

Теперь создаем файл исходных кодов.

#include #include #include #include #include "blablamod.h" static int Major; /* Major number assigned to our device driver */ static int Device_Open = 0; /* Is device open? * Used to prevent multiple access to device */ static char msg[BUF_LEN]; /* The msg the device will give when asked */ static char *msg_Ptr;

Тут мы объявили переменную для номера файла — символьного устройства, переменную-флаг для индикации открытого устройства, буфер для сообщения и указатель на него.
Теперь создаем структуру file_operations.

static struct file_operations fops = < .read = device_read, .write = device_write, .open = device_open, .release = device_release >;

Тут мы указали функции — обработчики.
Функция инициализации модуля выглядит так:

static int __init blablamod_init( void ) < printk(KERN_NOTICE "BlablaModule loaded!\n" ); Major = register_chrdev(0, DEVICE_NAME, &fops); if (Major < 0) < printk(KERN_ALERT "Registering char device failed with %d\n", Major); return Major; >printk(KERN_INFO "I was assigned major number %d. To talk to\n", Major); printk(KERN_INFO "the driver, create a dev file with\n"); printk(KERN_INFO "'mknod /dev/%s c %d 0'.\n", DEVICE_NAME, Major); printk(KERN_INFO "Try various minor numbers. Try to cat and echo to\n"); printk(KERN_INFO "the device file.\n"); printk(KERN_INFO "Remove the device file and module when done.\n"); return 0; >

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

Читайте также:  User list command in linux

static void __exit blablamod_exit( void )

Регистрируем функции загрузки/выгрузки.

module_init( blablamod_init ); module_exit( blablamod_exit );

static int device_open(struct inode *inode, struct file *file)

Думаю тут нечего объяснять.

static int device_release(struct inode *inode, struct file *file) < Device_Open--; /* We're now ready for our next caller */ /* * Decrement the usage count, or else once you opened the file, you'll * never get get rid of the module. */ module_put(THIS_MODULE); return 0; >
static ssize_t device_read(struct file *filp, /* see include/linux/fs.h */ char *buffer, /* buffer to fill with data */ size_t length, /* length of the buffer */ loff_t * offset) < /* * Number of bytes actually written to the buffer */ int bytes_read = 0; /* * If we're at the end of the message, * return 0 signifying end of file */ if (*msg_Ptr == 0) return 0; /* * Actually put the data into the buffer */ while (length && *msg_Ptr) < /* * The buffer is in the user data segment, not the kernel * segment so "*" assignment won't work. We have to use * put_user which copies data from the kernel data segment to * the user data segment. */ put_user(*(msg_Ptr++), buffer++); length--; bytes_read++; >/* * Most read functions return the number of bytes put into the buffer */ return bytes_read; >

Тут по идее тоже все должно быть ясно.
Запись поддерживать не будем.

static ssize_t device_write(struct file *filp, const char *buff, size_t len, loff_t * off)

Можно компилировать и запускать.
После запуска смотрим dmesg.

$ dmesg [ 2776.539112] I was assigned major number 247. To talk to [ 2776.539115] the driver, create a dev file with [ 2776.539119] 'mknod /dev/chardev c 247 0'. [ 2776.539122] Try various minor numbers. Try to cat and echo to [ 2776.539125] the device file. [ 2776.539128] Remove the device file and module when done.

Теперь создадим сам файл. Такой модуль не умеет создавать файлы. О том, как создать файл автоматически рассмотрим в следующем номере 😉

sudo mknod /dev/chardev c 247 0 sudo chmod 777 /dev/chardev

Теперь пробуем читать из нашего файла. Вы можете набросать простенькую программу на любимом языке, а я воспользуюсь утилитой cat.

С каждым разом число увеличивается.

I already told you 0 times Hello world! I already told you 1 times Hello world! I already told you 2 times Hello world!

Должно появится сообщение в dmesg.

[ 2872.839241] Sorry, this operation isn't supported.

PS Не забываем удалять файл после окончания работы с модулем.

Источник

Записки программиста

Недавно мы научились основам написания модулей ядра Linux. Впрочем, рассмотренные тогда примеры были совсем простые, можно даже сказать, что игрушечные. Сегодня мы напишем модуль поинтереснее. Он будет создавать в каталоге /dev символьное устройство, с которым можно взаимодействовать из юзерспейса.

Кода не много, так что привожу его целиком:

#define DEVICE_NAME «chardev»

static int sensor_value = 0 ;
static DEFINE_MUTEX ( sensor_value_mtx ) ;

static int major ;
static struct class * cls ;

Читайте также:  Linux change network interface name

typedef struct ChardevPrivateData {
char buff [ 32 ] ;
int cnt ;
} ChardevPrivateData ;

static int device_open ( struct inode *, struct file * ) ;
static int device_release ( struct inode *, struct file * ) ;
static ssize_t device_read ( struct file *, char __user *,
size_t , loff_t * ) ;
static ssize_t device_write ( struct file *, const char __user *,
size_t , loff_t * ) ;

static struct file_operations chardev_fops = {
. read = device_read ,
. write = device_write ,
. open = device_open ,
. release = device_release ,
} ;

int init_module ( void ) {
struct device * dev ;
major = register_chrdev ( 0 , DEVICE_NAME , & chardev_fops ) ;

if ( major < 0 ) {
pr_alert ( «register_chrdev() failed: %d \n » , major ) ;
return — EINVAL ;
}

pr_info ( «major = %d \n » , major ) ;

cls = class_create ( THIS_MODULE , DEVICE_NAME ) ;
if ( IS_ERR ( cls ) ) {
pr_alert ( «class_create() failed: %ld \n » , PTR_ERR ( cls ) ) ;
return — EINVAL ;
}

dev = device_create ( cls , NULL , MKDEV ( major , 0 ) , NULL , DEVICE_NAME ) ;
if ( IS_ERR ( dev ) ) {
pr_alert ( «device_create() failed: %ld \n » , PTR_ERR ( dev ) ) ;
return — EINVAL ;
}

pr_info ( «/dev/%s created \n » , DEVICE_NAME ) ;
return 0 ;
}

void cleanup_module ( void ) {
device_destroy ( cls , MKDEV ( major , 0 ) ) ;
class_destroy ( cls ) ;
unregister_chrdev ( major , DEVICE_NAME ) ;
}

static int device_open ( struct inode * inode , struct file * file ) {
ChardevPrivateData * pd ;
int val ;

if ( ! try_module_get ( THIS_MODULE ) ) {
pr_alert ( «try_module_get() failed \n » ) ;
return — EINVAL ;
}

pd = kmalloc ( sizeof ( ChardevPrivateData ) , GFP_KERNEL ) ;
if ( pd == NULL ) {
pr_alert ( «kmalloc() failed \n » ) ;
module_put ( THIS_MODULE ) ;
return — EINVAL ;
}

mutex_lock ( & sensor_value_mtx ) ;
val = sensor_value ;
sensor_value ++;
mutex_unlock ( & sensor_value_mtx ) ;

sprintf ( pd -> buff , «Dummy sensor value: %d \n » , val ) ;
pd -> cnt = 0 ;
file -> private_data = pd ;
return 0 ;
}

static int device_release ( struct inode * inode , struct file * file ) {
kfree ( file -> private_data ) ;
module_put ( THIS_MODULE ) ;
return 0 ;
}

static ssize_t device_read ( struct file * file ,
char __user * buffer ,
size_t length ,
loff_t * offset ) {
int bytes_read = 0 ;
ChardevPrivateData * pd = file -> private_data ;

static ssize_t device_write ( struct file * filp ,
const char __user * buff ,
size_t len ,
loff_t * off ) {
pr_alert ( «device_write() is not implemented \n » ) ;
return — EINVAL ;
}

А вот пример использования модуля:

$ sudo insmod chardev.ko
$ dmesg | cut -d ‘ ‘ -f 2- | tail -n 2
major = 240
/dev/chardev created
$ sudo cat /dev/chardev
Dummy sensor value: 0
$ sudo cat /dev/chardev
Dummy sensor value: 1
$ sudo cat /dev/chardev
Dummy sensor value: 2
$ sudo rmmod chardev

Как же это работает? При загрузке модуля вызывается процедура init_module() . В ней при помощи register_chrdev() происходит регистрация символьного устройства. В качестве аргументов нужно передать major-номер устройства, имя устройства и структуру с указателями на кучу колбэков. В приведенном примере в качестве major-номера указан 0. Это означает, что номер выберет ОС среди никем не занятых. Колбэков в примере используется четыре, но на самом деле их доступно куда больше.

Fun fact! Код ядра Linux можно склонировать себе на машину и искать по нему при помощи какого-нибудь Sublime Text. Это позволяет быстро находить определение всех макросов и описание всех структур, а также документацию к ним.

Далее создается класс устройства (device class), а затем и конкретный экземпляр устройства, через device_create() . Один модуль может обслуживать много устройств одного класса. Чтобы модуль мог отличить одно устройство от другого, используются minor-номера. В нашем примере создается одно устройство с minor-номером 0, см макрос MKDEV .

Читайте также:  Настройка trim linux mint

Еще раз подчеркнем разницу между major- и minor-номерами устройства. Major-номера используются системой, чтобы понять, какой модуль обслуживает заданное устройство. Поэтому не должно быть двух модулей, использующих один и тот же major-номер. До minor-номеров ОС нет никакого дела. Эти номера используются только модулем, чтобы отличить один экземпляр устройства от другого.

При выходе из init_module() в системе будет создано символьное устройство /dev/chardev :

Здесь 240 — это выбранный системой major-номер, а 0 представляет собой minor-номер. Буква c указывает на то, что это символьное устройство (character device). Блочным устройствам, таким, как жесткие диски, соответствует букв b .

Fun fact! В приведенном коде в случае любой ошибки возвращается EINVAL . С полным списком доступных кодов ошибок и их описанием можно ознакомиться в man errno .

При открытии устройства управление передается колбэку device_open() . Колбэк первым делом взывает try_module_get() . Эта процедура инкрементирует счетчик использования модуля. Значение счетчика отличное от нуля говорит системе о том, что модуль кем-то используется, и не может быть выгружен. Если счетчик невозможно увеличить (например, пользователь прямо сейчас просит систему выгрузить модуль), процедура возвращает false. Декремент счетчика осуществляется вызовом module_put() . Этот вызов всегда завершается успешно.

Текущее значение счетчика для данного модуля ядра можно увидеть через sysfs:

Далее в device_open() инициализируется структура ChardevPrivateData и указатель на нее записывается в file->private_data . Это специальное поле, где можно сохранить указатель на какие-то интересные модулю данные. Память под структуру выделяется при помощи kmalloc() .

Флаг GFP_KERNEL в аргументах kmalloc() указывает на то, что при выделении памяти допускается приостановить исполнение потока. Пользоваться этим флагом в обработчиках прерываний нельзя. Альтернативным аргументом является GFP_NOWAIT . При его использовании гарантируется, что память будет выделена без остановки потока. Есть и другие флаги. Процедура kfree() , предназначенная для освобождения памяти, никогда не останавливает исполнение потока.

Устройство может быть открыто в параллель несколькими процессами. Чтобы при доступе к глобальной переменной sensor_value не случилось гонки, в device_open() применены мьютексы. Из прочих примитивов синхронизации ядро Linux предлагает спинлоки и атомарные переменные.

При чтении из символьного устройства управление передается в device_read() . Это очень простая процедура, если не считать одного момента. Дело в том, что buffer находится в пользовательском адресном пространстве, и писать в него напрямую не допускается. Вместо этого нужно использовать макрос put_user() . Аналогично, для чтения предусмотрен get_user() . Оба макроса работают с типами, имеющими размер 8, 16, 32 или 64 бита. Для копирования данных большего объема есть процедуры copy_to_user() и copy_from_user() . Названные макросы и процедуры возвращают 0 в случае успеха и иное значение в случае ошибки.

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

Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.

Источник

Оцените статью
Adblock
detector