|
|||||||
9. Взаимодействие с файлами устройств
Время создания: 20.09.2022 09:47
Текстовые метки: linux, ядро, модуль, программирование, язык, C, Си, пособие, документация
Раздел: Компьютер - Linux - Ядро - Пособие по программированию модулей ядра Linux
Запись: xintrea/mytetra_syncro/master/base/166365646278nx3tatmd/text.html на raw.github.com
|
|||||||
|
|||||||
9. Взаимодействие с файлами устройств Файлы устройств представляют физические устройства. Большинство таких устройств используются для вывода и ввода, а значит необходим некий механизм, который бы позволил их находящимся в ядре драйверам получать вывод от процессов для его перенаправления самим устройствам. Для этого файл открывается, и в него производится запись, в точности аналогично стандартной операции записи в файл. В примере ниже это реализовано с помощью device_write. Но этого не всегда оказывается достаточно. Представьте, что у вас к последовательному порту подключен модем (даже если модем внутренний, эта схема с точки зрения процессора все равно реализуется как модем, подключенный к последовательному порту, так что воображение особо напрягать не нужно). Естественным решением здесь будет использовать файл устройства как для записи на модем (к примеру, команд или данных для отправки), так и для чтения с него (например, ответов на команды или полученных данных). Тем не менее остается вопрос о том, что же делать, когда нужно взаимодействовать с самим последовательным портом, например, для настройки скорости отправки/получения данных. В Unix ответом будет использовать специальную функцию ioctl (сокращенно от Input Output ConTroL). Каждое устройство может иметь собственные команды ioctl, реализующие чтение (для отправки информации от процесса ядру), запись (для возвращения информации процессу), и то и другое, либо ни одно из этих действий. Имейте ввиду, что в ioctl роли чтения и записи снова реверсируются, то есть при чтении происходит отправка информации ядру, а при записи ее получение от ядра. Вызывается функция ioctl с тремя параметрами: дескриптором соответствующего файла устройства, номером ioctl и параметром, имеющим тип long, чтобы можно было использовать приведение, позволяющее с его помощью передавать почти все, что захочется. Таким способом не удастся передать структуру, но можно будет передать указатель на нее. Вот пример: ioctl.c /* * ioctl.c */ #include <linux/cdev.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/ioctl.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/uaccess.h>
struct ioctl_arg { unsigned int val; };
/* Documentation/ioctl/ioctl-number.txt */ #define IOC_MAGIC '\x66'
#define IOCTL_VALSET _IOW(IOC_MAGIC, 0, struct ioctl_arg) #define IOCTL_VALGET _IOR(IOC_MAGIC, 1, struct ioctl_arg) #define IOCTL_VALGET_NUM _IOR(IOC_MAGIC, 2, int) #define IOCTL_VALSET_NUM _IOW(IOC_MAGIC, 3, int)
#define IOCTL_VAL_MAXNR 3 #define DRIVER_NAME "ioctltest"
static unsigned int test_ioctl_major = 0; static unsigned int num_of_dev = 1; static struct cdev test_ioctl_cdev; static int ioctl_num = 0;
struct test_ioctl_data { unsigned char val; rwlock_t lock; };
static long test_ioctl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct test_ioctl_data *ioctl_data = filp->private_data; int retval = 0; unsigned char val; struct ioctl_arg data; memset(&data, 0, sizeof(data));
switch (cmd) { case IOCTL_VALSET: if (copy_from_user(&data, (int __user *)arg, sizeof(data))) { retval = -EFAULT; goto done; }
pr_alert("IOCTL set val:%x .\n", data.val); write_lock(&ioctl_data->lock); ioctl_data->val = data.val; write_unlock(&ioctl_data->lock); break;
case IOCTL_VALGET: read_lock(&ioctl_data->lock); val = ioctl_data->val; read_unlock(&ioctl_data->lock); data.val = val;
if (copy_to_user((int __user *)arg, &data, sizeof(data))) { retval = -EFAULT; goto done; }
break;
case IOCTL_VALGET_NUM: retval = __put_user(ioctl_num, (int __user *)arg); break;
case IOCTL_VALSET_NUM: ioctl_num = arg; break;
default: retval = -ENOTTY; }
done: return retval; }
static ssize_t test_ioctl_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { struct test_ioctl_data *ioctl_data = filp->private_data; unsigned char val; int retval; int i = 0;
read_lock(&ioctl_data->lock); val = ioctl_data->val; read_unlock(&ioctl_data->lock);
for (; i < count; i++) { if (copy_to_user(&buf[i], &val, 1)) { retval = -EFAULT; goto out; } }
retval = count; out: return retval; }
static int test_ioctl_close(struct inode *inode, struct file *filp) { pr_alert("%s call.\n", __func__);
if (filp->private_data) { kfree(filp->private_data); filp->private_data = NULL; }
return 0; }
static int test_ioctl_open(struct inode *inode, struct file *filp) { struct test_ioctl_data *ioctl_data;
pr_alert("%s call.\n", __func__); ioctl_data = kmalloc(sizeof(struct test_ioctl_data), GFP_KERNEL);
if (ioctl_data == NULL) return -ENOMEM;
rwlock_init(&ioctl_data->lock); ioctl_data->val = 0xFF; filp->private_data = ioctl_data;
return 0; }
static struct file_operations fops = { .owner = THIS_MODULE, .open = test_ioctl_open, .release = test_ioctl_close, .read = test_ioctl_read, .unlocked_ioctl = test_ioctl_ioctl, };
static int ioctl_init(void) { dev_t dev; int alloc_ret = -1; int cdev_ret = -1; alloc_ret = alloc_chrdev_region(&dev, 0, num_of_dev, DRIVER_NAME);
if (alloc_ret) goto error;
test_ioctl_major = MAJOR(dev); cdev_init(&test_ioctl_cdev, &fops); cdev_ret = cdev_add(&test_ioctl_cdev, dev, num_of_dev);
if (cdev_ret) goto error;
pr_alert("%s driver(major: %d) installed.\n", DRIVER_NAME, test_ioctl_major); return 0; error: if (cdev_ret == 0) cdev_del(&test_ioctl_cdev); if (alloc_ret == 0) unregister_chrdev_region(dev, num_of_dev); return -1; }
static void ioctl_exit(void) { dev_t dev = MKDEV(test_ioctl_major, 0);
cdev_del(&test_ioctl_cdev); unregister_chrdev_region(dev, num_of_dev); pr_alert("%s driver removed.\n", DRIVER_NAME); }
module_init(ioctl_init); module_exit(ioctl_exit);
MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("This is test_ioctl module"); Вы можете заметить в функции test_ioctl_ioctl() аргумент cmd. Это номер ioctl. Он кодирует старший (major) номер устройства, тип ioctl, команду и тип параметра. Обычно этот номер создается вызовом макроса (_IO, _IOR, _IOW или _IOWR — в зависимости от типа) в заголовочном файле. Этот заголовочный файл должен быть включен и в программы, которые будут использовать ioctl (чтобы они могли генерировать подходящие ioctl), и в модуль ядра (чтобы он мог эту функцию понимать). В примере ниже заголовочным файлом является chardev.h, а использующей его программой userspace_ioctl.c. Если вы хотите использовать ioctl в собственных модулях, то лучше будет получить для нее официальное назначение. Тогда, если у вас каким-то образом окажется чужая ioctl, то сразу станет понятно, что что-то не так. Более подробную информацию можно получить в дереве исходного кода ядра на странице Documentation/userspace-api/ioctl/ioctl-number.rst Кроме того, необходимо иметь ввиду, что конкурентное обращение к ресурсам приведет к состоянию гонки. Решением будет использовать атомарную инструкцию сравнения с обменом (CAS), которая упоминалась в разделе 6.5, чтобы организовать индивидуальный доступ. chardev2.c /* * chardev2.c – создание символьного устройства ввода/вывода */
#include <linux/cdev.h> #include <linux/delay.h> #include <linux/device.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/irq.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/poll.h>
#include "chardev.h" #define SUCCESS 0 #define DEVICE_NAME "char_dev" #define BUF_LEN 80
enum { CDEV_NOT_USED = 0, CDEV_EXCLUSIVE_OPEN = 1, };
/* Открыто ли сейчас устройство? Служит для предотвращения * конкурентного доступа к одному устройству. */ static atomic_t already_open = ATOMIC_INIT(CDEV_NOT_USED);
/* Сообщение, которое устройство будет выдавать при обращении. */ static char message[BUF_LEN];
static struct class *cls;
/* Вызывается, когда процесс пытается открыть файл устройства. */ static int device_open(struct inode *inode, struct file *file) { pr_info("device_open(%p)\n", file);
try_module_get(THIS_MODULE); return SUCCESS; }
static int device_release(struct inode *inode, struct file *file) { pr_info("device_release(%p,%p)\n", inode, file);
module_put(THIS_MODULE); return SUCCESS; }
/* Эта функция вызывается, когда процесс, уже открывший файл, * пытается считать из него. */ static ssize_t device_read(struct file *file, /* см. include/linux/fs.h */ char __user *buffer, /* Буфер для заполнения. */ size_t length, /* Длина буфера. */ loff_t *offset) { /* Количество байтов, фактически записываемых в буфер. */ int bytes_read = 0; /* Как далеко зашел процесс, считывающий * сообщение? Пригождается, когда сообщение больше размера буфера * в device_read. */ const char *message_ptr = message;
if (!*(message_ptr + *offset)) { /* Мы в конце сообщения. */ *offset = 0; /* Сброс смещения. */ return 0; /* Обозначение конца файла. */ }
message_ptr += *offset;
/* Фактически помещает данные в буфер. */ while (length && *message_ptr) { /* Поскольку буфер находится в пользовательском сегменте данных, * а не в сегменте ядра, присваивание не сработает. Вместо этого * нужно использовать put_user, которая скопирует данные из * сегмента ядра в сегмент пользователя. */ put_user(*(message_ptr++), buffer++); length--; bytes_read++; }
pr_info("Read %d bytes, %ld left\n", bytes_read, length);
*offset += bytes_read;
/* Функции чтения должны возвращать количество байтов, реально * вставляемых в буфер. */ return bytes_read; }
/* Вызывается, когда кто-то пытается произвести запись в файл устройства. */ static ssize_t device_write(struct file *file, const char __user *buffer, size_t length, loff_t *offset) { int i;
pr_info("device_write(%p,%p,%ld)", file, buffer, length);
for (i = 0; i < length && i < BUF_LEN; i++) get_user(message[i], buffer + i);
/* Также возвращает количество использованных во вводе символов. */ return i; }
/* Эта функция вызывается, когда процесс пытается выполнить ioctl для * файла устройства. Мы получаем два дополнительных параметра * (дополнительных для структур inode и file, которые получают все * функции устройств): номер ioctl и параметр, заданный для этой ioctl. * * Если ioctl реализует запись или запись/чтение (то есть ее вывод * возвращается вызывающему процессу), вызов ioctl возвращает вывод * этой функции. */ static long device_ioctl(struct file *file, /* То же самое. */ unsigned int ioctl_num, /* Число и параметр для ioctl */ unsigned long ioctl_param) { int i; long ret = SUCCESS;
/* Мы не хотим взаимодействовать с двумя процессами одновременно */ if (atomic_cmpxchg(&already_open, CDEV_NOT_USED, CDEV_EXCLUSIVE_OPEN)) return -EBUSY;
/* Переключение согласно вызванной ioctl. */ switch (ioctl_num) { case IOCTL_SET_MSG: { /* Получение указателя на сообщение (в пользовательском * пространстве) и установка его как сообщения устройства. * Получение параметра, передаваемого ioctl процессом. */ char __user *tmp = (char __user *)ioctl_param; char ch;
/* Определение длины сообщения. */ get_user(ch, tmp); for (i = 0; ch && i < BUF_LEN; i++, tmp++) get_user(ch, tmp);
device_write(file, (char __user *)ioctl_param, i, NULL); break; } case IOCTL_GET_MSG: { loff_t offset = 0;
/* Передача текущего сообщения вызывающему процессу. Получаемый * параметр является указателем, который мы заполняем. */ i = device_read(file, (char __user *)ioctl_param, 99, &offset);
/* Помещаем в конец буфера нуль, чтобы он правильно завершился. */ put_user('\0', (char __user *)ioctl_param + i); break; } case IOCTL_GET_NTH_BYTE: /* Эта ioctl является и вводом (ioctl_param), и выводом * (возвращаемым значением этой функции). */ ret = (long)message[ioctl_param]; break; }
/* Теперь можно принимать следующий вызов. */ atomic_set(&already_open, CDEV_NOT_USED);
return ret; }
/* Объявления модулей. */
/* Эта структура будет хранить функции, вызываемые при выполнении * процессом действий с созданным нами устройством. Поскольку указатель * на эту структуру содержится в таблице устройств, он не может быть * локальным для init_module. NULL для не реализованных функций. */ static struct file_operations fops = { .read = device_read, .write = device_write, .unlocked_ioctl = device_ioctl, .open = device_open, .release = device_release, /* Аналогично закрытию. */ };
/* Инициализация модуля – регистрация символьного устройства. */ static int __init chardev2_init(void) { /* Регистрация символьного устройства (по меньшей мере попытка). */ int ret_val = register_chrdev(MAJOR_NUM, DEVICE_NAME, &fops);
/* Отрицательные значения означают ошибку. */ if (ret_val < 0) { pr_alert("%s failed with %d\n", "Sorry, registering the character device ", ret_val); return ret_val; }
cls = class_create(THIS_MODULE, DEVICE_FILE_NAME); device_create(cls, NULL, MKDEV(MAJOR_NUM, 0), NULL, DEVICE_FILE_NAME);
pr_info("Device created on /dev/%s\n", DEVICE_FILE_NAME);
return 0; }
/* Очистка – снятие регистрации соответствующего файла из /proc. */ static void __exit chardev2_exit(void) { device_destroy(cls, MKDEV(MAJOR_NUM, 0)); class_destroy(cls);
/* Снятие регистрации устройства. */ unregister_chrdev(MAJOR_NUM, DEVICE_NAME); }
module_init(chardev2_init); module_exit(chardev2_exit);
MODULE_LICENSE("GPL"); chardev.h /* * chardev.h – заголовочный файл с определениями ioctl. * * Объявления нужны в заголовочном файле, поскольку их должен знать * как модуль ядра (из chardev2.c), так и процесс, вызывающий ioctl() * (из userspace_ioctl.c). */
#ifndef CHARDEV_H #define CHARDEV_H
#include <linux/ioctl.h>
/* Старший номер устройства. Мы больше не можем полагаться на * динамическую регистрацию, поскольку функции ioctl должны его знать. */ #define MAJOR_NUM 100
/* Установка сообщения драйвера устройства. */ #define IOCTL_SET_MSG _IOW(MAJOR_NUM, 0, char *) /* _IOW означает, что мы создаем номер команды ioctl для передачи * информации от пользовательского процесса модулю ядра. * * Первый аргумент, MAJOR_NUM, это используемый старший номер устройства * * Второй аргумент – это номер команды (их может быть несколько с * разными смыслами). * * Третий аргумент – это тип, который мы хотим передать от процесса ядру */
/* Получение сообщения драйвера устройства. */ #define IOCTL_GET_MSG _IOR(MAJOR_NUM, 1, char *) /* Эта IOCTL используется для вывода с целью получить сообщение * драйвера устройства. Однако нам все равно нужен буфер для размещения * этого сообщения в качестве ввода при его передаче процессом. */
/* Получение n-ного байта сообщения. */ #define IOCTL_GET_NTH_BYTE _IOWR(MAJOR_NUM, 2, int) /* Эта IOCTL используется как для ввода, так и для вывода. Она получает * от пользователя число, n, и возвращает message[n]. */
/* Имя файла устройства. */ #define DEVICE_FILE_NAME "char_dev" #define DEVICE_PATH "/dev/char_dev"
#endif userspace_ioctl.c /* userspace_ioctl.c – процесс, позволяющий контролировать модуль ядра * с помощью ioctl. * * До этого момента можно было использовать для ввода и вывода cat. * Теперь необходимо использовать ioctl, для чего нужно написать свой * процесс. */
/* Детали устройства, такие как номера ioctl и старший файл устройства. */ #include "../chardev.h"
#include <stdio.h> /* Стандартный ввод-вывод. */ #include <fcntl.h> /* Открытие. */ #include <unistd.h> /* Закрытие. */ #include <stdlib.h> /* Выход. */ #include <sys/ioctl.h> /* ioctl */
/* Функции для вызовов ioctl. */
int ioctl_set_msg(int file_desc, char *message) { int ret_val;
ret_val = ioctl(file_desc, IOCTL_SET_MSG, message);
if (ret_val < 0) { printf("ioctl_set_msg failed:%d\n", ret_val); }
return ret_val; }
int ioctl_get_msg(int file_desc) { int ret_val; char message[100] = { 0 };
/* Внимание! Это опасно, так как мы не сообщаем ядру, до куда * можно производить запись, то есть рискуем вызвать переполнение * буфера. В реальной программе мы бы использовали две ioctl - одну * для информирования ядра о длине буфера и вторую для предоставления * ему самого буфера под заполнение. */ ret_val = ioctl(file_desc, IOCTL_GET_MSG, message);
if (ret_val < 0) { printf("ioctl_get_msg failed:%d\n", ret_val); } printf("get_msg message:%s", message);
return ret_val; }
int ioctl_get_nth_byte(int file_desc) { int i, c;
printf("get_nth_byte message:");
i = 0; do { c = ioctl(file_desc, IOCTL_GET_NTH_BYTE, i++);
if (c < 0) { printf("\nioctl_get_nth_byte failed at the %d'th byte:\n", i); return c; }
putchar(c); } while (c != 0);
return 0; }
/* Main – вызов функций ioctl. */ int main(void) { int file_desc, ret_val; char *msg = "Message passed by ioctl\n";
file_desc = open(DEVICE_PATH, O_RDWR); if (file_desc < 0) { printf("Can't open device file: %s, error:%d\n", DEVICE_PATH, file_desc); exit(EXIT_FAILURE); }
ret_val = ioctl_set_msg(file_desc, msg); if (ret_val) goto error; ret_val = ioctl_get_nth_byte(file_desc); if (ret_val) goto error; ret_val = ioctl_get_msg(file_desc); if (ret_val) goto error;
close(file_desc); return 0; error: close(file_desc); exit(EXIT_FAILURE); } |
|||||||
Так же в этом разделе:
|
|||||||
|
|||||||
|