Linux kernel device create
Библиотека сайта rus-linux.net
Автоматическое создание файлов устройств
Ранее, в ядре 2.4, автоматическое создание файлов устройств выполнялось самим ядром в devfs с помощью вызова соответствующего API. Однако, по мере того, как ядро развивалось, разработчики ядра поняли, что файлы устройств больше связаны с пользовательским пространством и, следовательно, они должны быть именно там, а не в ядре. Исходя из этого принципа, теперь для рассматриваемого устройства в ядре в /sys только заполняется соответствующая информация о классе устройства и об устройстве. Затем в пользовательском пространстве эту информацию необходимо проинтерпретировать и выполнить соответствующее действие. В большинстве настольных систем Linux эту информацию собирает демон udev, и создает, соответственно, файлы устройств.
Демон udev можно с помощью его конфигурационных файлов настроить дополнительно и точно указать имена файлов устройств, права доступа к ним, их типы и т. д. Так что касается драйвера, требуется с помощью API моделей устройств Linux, объявленных в , заполнить в /sys соответствующие записи. Все остальное делается с помощью udev . Класс устройства создается следующим образом:
struct class *cl = class_create(THIS_MODULE, "");
Затем в этот класс информация об устройстве () заносится следующим образом:
device_create(cl, NULL, first, NULL, "", . );
Здесь, в качестве first указывается dev_t . Соответственно, дополняющими или обратными вызовами, которые должны вызыватся в хронологически обратном порядке, являются:
device_destroy(cl, first); class_destroy(cl);
Посмотрите на рис.1 на записи /sys , созданные с помощью chardrv — запись ( ) и с помощью mynull — запись ( ). Здесь также показан файл устройства, созданный с помощью udev по записи : , находящейся в файле dev .
Рис.1: Автоматическое создание файла устройства
В случае, если указаны несколько младших номеров minor, API device_create() и device_destroy() могут вызываться в цикле и в этом случае окажется полезной строка ( ). Например, вызов функции device_create() в цикле с использованием индекса i будет иметь следующий вид:
device_create(cl, NULL, MKNOD(MAJOR(first), MINOR(first) + i), NULL, "mynull%d", i);
Операции с файлами
Независимо от того, что системные вызовы (или, в общем случае, операции с файлами), о которых мы рассказываем, применяются к обычным файлам, их также можно использовать и с файлами устройств. Т.е. мы можем сказать: если смотреть из пользовательского пространства, то в Linux почти все является файлами. Различие — в пространстве ядра, где виртуальная файловая система (VFS) определяет тип файла и пересылает файловые операции в соответствующий канал, например, в случае обычного файла или директория — в модуль файловой системы, или в соответствующий драйвер устройства в случае использования файла устройства. Мы будем рассматривать второй случай.
Теперь, чтобы VFS передала операции над файлом устройства в драйвер, ее следует об этом проинформировать. И это то, что называется регистрацией драйвером в VFS файловых операций. Регистрация состоит из двух этапов. (Код, указываемый в скобках, взят из кода «null -драйвера», который приведен ниже).
Во-первых, давайте занесем нужные нам файловые операции ( my_open , my_close , my_read , my_write , …) в структуру, описывающую файловые операции ( struct file_operations pugs_fops ) и ею инициализируем структуру, описывающую символьное устройство ( struct cdev c_dev ); используем для этого обращение cdev_init() .
Затем передадим эту структуру в VFS с помощью вызова cdev_add() . Обе операции cdev_init() и cdev_add() объявлены в . Естественно, что также надо закодировать фактические операции с файлами ( my_open , my_close , my_read , my_write ).
Итак, для начала, давайте все это сделаем как можно проще — скажем, максимально просто в виде «null драйвера».
null — драйвер
#include #include #include #include #include #include #include #include static dev_t first; // Global variable for the first device number static struct cdev c_dev; // Global variable for the character device structure static struct class *cl; // Global variable for the device class static int my_open(struct inode *i, struct file *f) < printk(KERN_INFO "Driver: open()\n"); return 0; >static int my_close(struct inode *i, struct file *f) < printk(KERN_INFO "Driver: close()\n"); return 0; >static ssize_t my_read(struct file *f, char __user *buf, size_t len, loff_t *off) < printk(KERN_INFO "Driver: read()\n"); return 0; >static ssize_t my_write(struct file *f, const char __user *buf, size_t len, loff_t *off) < printk(KERN_INFO "Driver: write()\n"); return len; >static struct file_operations pugs_fops = < .owner = THIS_MODULE, .open = my_open, .release = my_close, .read = my_read, .write = my_write >; static int __init ofcd_init(void) /* Constructor */ < printk(KERN_INFO "Namaskar: ofcd registered"); if (alloc_chrdev_region(&first, 0, 1, "Shweta") < 0) < return -1; >if ((cl = class_create(THIS_MODULE, "chardrv")) == NULL) < unregister_chrdev_region(first, 1); return -1; >if (device_create(cl, NULL, first, NULL, "mynull") == NULL) < class_destroy(cl); unregister_chrdev_region(first, 1); return -1; >cdev_init(&c_dev, &pugs_fops); if (cdev_add(&c_dev, first, 1) == -1) < device_destroy(cl, first); class_destroy(cl); unregister_chrdev_region(first, 1); return -1; >return 0; > static void __exit ofcd_exit(void) /* Destructor */ < cdev_del(&c_dev); device_destroy(cl, first); class_destroy(cl); unregister_chrdev_region(first, 1); printk(KERN_INFO "Alvida: ofcd unregistered"); >module_init(ofcd_init); module_exit(ofcd_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Anil Kumar Pugalia "); MODULE_DESCRIPTION("Our First Character Driver");
Светлана повторила обычный процесс сборки, добавив при этом некоторые новые проверочные шаги, а именно:
- Собрала драйвер (файл .ko ) с помощью запуска команды make .
- Загрузила драйвер с помощью команды insmod .
- С помощью команды lsmod получила список всех загруженных модулей.
- С помощью команды cat /proc/devices . получила список используемых старших номеров major.
- Поэкспериментировала с «null драйвером» (подробности смотрите на рис.2).
- Выгрузила драйвер с помощью команды rmmod .
Рис.2: Эксперименты с «null драйвером»
Подведем итог
Светлана олпределенно была довольна; она сама написала символьный драйвер, который работает точно также, как и стандартный файл устройства /dev/null . Чтобы понять, что это значит, проверьте пару для файла /dev/null , а также выполните с ним команды echo и cat .
Но Светлану стала беспокоить одна особенность. В своем драйвере она использовала свои собственные вызовы ( my_open , my_close , my_read , my_write ), но, к удивлению, они, в отличие от любых других вызовов файловой системы, работают таким необычным образом. Что же тут необычного? Необычно, по крайней мере с точки зрения обычных файловых операций, то, что чтобы Светлана не записывала, при чтении она ничего не могла получить. Как она сможет решить эту проблему? Читайте следующую статью.
The Basic Device Structure¶
The bus driver that discovers the device uses this to register the device with the core:
int device_register(struct device * dev);
The bus should initialize the following fields:
A device is removed from the core when its reference count goes to 0. The reference count can be adjusted using:
struct device * get_device(struct device * dev); void put_device(struct device * dev);
get_device() will return a pointer to the struct device passed to it if the reference is not already 0 (if it’s in the process of being removed already).
A driver can access the lock in the device structure using:
void lock_device(struct device * dev); void unlock_device(struct device * dev);
Attributes¶
Attributes of devices can be exported by a device driver through sysfs.
Please see sysfs — _The_ filesystem for exporting kernel objects for more information on how sysfs works.
As explained in Everything you never wanted to know about kobjects, ksets, and ktypes , device attributes must be created before the KOBJ_ADD uevent is generated. The only way to realize that is by defining an attribute group.
Attributes are declared using a macro called DEVICE_ATTR:
#define DEVICE_ATTR(name,mode,show,store)
static DEVICE_ATTR(type, 0444, type_show, NULL); static DEVICE_ATTR(power, 0644, power_show, power_store);
Helper macros are available for common values of mode, so the above examples can be simplified to::
static DEVICE_ATTR_RO(type); static DEVICE_ATTR_RW(power);
This declares two structures of type struct device_attribute with respective names ‘dev_attr_type’ and ‘dev_attr_power’. These two attributes can be organized as follows into a group:
static struct attribute *dev_attrs[] = < &dev_attr_type.attr, &dev_attr_power.attr, NULL, >; static struct attribute_group dev_group = < .attrs = dev_attrs, >; static const struct attribute_group *dev_groups[] = < &dev_group, NULL, >;
A helper macro is available for the common case of a single group, so the above two structures can be declared using::
This array of groups can then be associated with a device by setting the group pointer in struct device before device_register() is invoked:
dev->groups = dev_groups; device_register(dev);
The device_register() function will use the ‘groups’ pointer to create the device attributes and the device_unregister() function will use this pointer to remove the device attributes.
Word of warning: While the kernel allows device_create_file() and device_remove_file() to be called on a device at any time, userspace has strict expectations on when attributes get created. When a new device is registered in the kernel, a uevent is generated to notify userspace (like udev) that a new device is available. If attributes are added after the device is registered, then userspace won’t get notified and userspace will not know about the new attributes.
This is important for device driver that need to publish additional attributes for a device at driver probe time. If the device driver simply calls device_create_file() on the device structure passed to it, then userspace will never be notified of the new attributes.
How can I create a device node from the init_module code of a Linux kernel module?
I am writing a module for the Linux kernel, and I want to create some device nodes in the init() function:
I also want the kernel to assign a minor number for my first node, and then I will assign the other nodes’ minor numbers by myself. How can I do this in the code? I don’t want to create devices from the shell using mknod().
3 Answers 3
To have more control over the device numbers and the device creation, you could do the following steps (instead of register_chrdev() ):
- Call alloc_chrdev_region() to get a major number and a range of minor numbers to work with.
- Create a device class for your devices with class_create() .
- For each device, call cdev_init() and cdev_add() to add the character device to the system.
- For each device, call device_create() . As a result, among other things, Udev will create device nodes for your devices. There isn’t any need for mknod() or the like. device_create() also allows you to control the names of the devices.
There are probably many examples of this on the Internet, and one of them is here.
Sorry for digging this up from the past, but is there an equivalent method of doing this when the license is not GPL? class_create cannot be used with non-GPL licenses.
Looks good. Thanks for sharing. Question: how do I cleanup the registered device(s) and file(s) under /dev/my_dev_files ?
@Nikita Vorontsov, the following cleanup operations are executed: device_destroy (it also takes care of deleting the device node), cdev_del unregisters the device from the kernel. After each device has been deleted, class_destroy is called to delete the class and then — unregister_chrdev_region . What is done when creating the devices is undone in reverse order, as usual.
I think device_create() should be called after cdev_add() . cdev_add() prepares the in kernel structures to maintain the device and device_create() makes the device available to the user space among other things. And as soon as you make your device available to the user space, that device should be ready to handle requests from there.
static int __init ofcd_init(void) /* Constructor */ < printk(KERN_INFO "Welcome!"); if (alloc_chrdev_region(&first, 0, 1, "char_dev") < 0) //$cat /proc/devices < return -1; >if ((cl = class_create(THIS_MODULE, "chardrv")) == NULL) //$ls /sys/class < unregister_chrdev_region(first, 1); return -1; >if (device_create(cl, NULL, first, NULL, "mynull") == NULL) //$ls /dev/ < class_destroy(cl); unregister_chrdev_region(first, 1); return -1; >cdev_init(&c_dev, &fops); if (cdev_add(&c_dev, first, 1) == -1) < device_destroy(cl, first); class_destroy(cl); unregister_chrdev_region(first, 1); return -1; >return 0; >
Minimal runnable example
Minimized from other answers. GitHub upstream with test setup.
#include #include #include /* register_chrdev, unregister_chrdev */ #include #include /* seq_read, seq_lseek, single_release */ #define NAME "lkmc_character_device_create" static int major = -1; static struct cdev mycdev; static struct class *myclass = NULL; static int show(struct seq_file *m, void *v) < seq_printf(m, "abcd"); return 0; >static int open(struct inode *inode, struct file *file) < return single_open(file, show, NULL); >static const struct file_operations fops = < .llseek = seq_lseek, .open = open, .owner = THIS_MODULE, .read = seq_read, .release = single_release, >; static void cleanup(int device_created) < if (device_created) < device_destroy(myclass, major); cdev_del(&mycdev); >if (myclass) class_destroy(myclass); if (major != -1) unregister_chrdev_region(major, 1); > static int myinit(void) < int device_created = 0; /* cat /proc/devices */ if (alloc_chrdev_region(&major, 0, 1, NAME "_proc") < 0) goto error; /* ls /sys/class */ if ((myclass = class_create(THIS_MODULE, NAME "_sys")) == NULL) goto error; /* ls /dev/ */ if (device_create(myclass, NULL, major, NULL, NAME "_dev") == NULL) goto error; device_created = 1; cdev_init(&mycdev, &fops); if (cdev_add(&mycdev, major, 1) == -1) goto error; return 0; error: cleanup(device_created); return -1; >static void myexit(void) < cleanup(1); >module_init(myinit) module_exit(myexit) MODULE_LICENSE("GPL");