Библиотека сайта rus-linux.net
Драйверы устройств в Linux
Часть 9: Управление вводом / выводом в Linux
Оригинал: "Device Drivers, Part 9: I/O Control in Linux"
Автор: Anil Kumar Pugalia
Дата публикации: August 1, 2011
Перевод: Н.Ромоданов
Дата перевода: июнь 2012 г.
В этой статье, которая является частью серии статей о драйверах устройств в Linux, рассказывается о типичной реализации ioctl() и ее использовании в Linux.
"Дайте мне ноутбук и расскажите мне о том, что было на практических занятиях по интерфейсам аппаратного обеспечения x86, которые проходили в лаборатории драйверов устройств Linux, а также о том, что планируется на следующих занятиях" — воскликнула Светлана, раздраженная тем, что она оказалась на больничной койке из-за пищевого отравления на вечеринке.
Друзья Светланы рассказали все, что было на предыдущем занятии, и сообщили ей, что им неизвестно, что планируется на следующем занятии, хотя занятие и будет связано с аппаратным обеспечением. Когда врач разрешим им остаться, они воспользовались это возможностью, чтобы поразмышлять о планах и поговорить о самой распространенной операции, используемой для управления аппаратным обеспечением - ioctl()
.
Введение в ioctl()
Операция ввода/вывода Input/Output Control (кратко - ioctl) является общей операцией или системным вызовом, который используется в большинстве драйверов различных категорий. Это один из самых универсальных системных вызовов. Если нет какого-нибудь другого системного вызова, который соответствует определенному требованию, то пользуются только системным вызовом ioctl()
.
К числу практических примеров относятся управление звуком для аудиоустройства, конфигурирование дисплея для видеоустройства, чтение регистров устройства и т. д. - в общем, все, что можно делать при вводе/выводе данных на устройство или выполнения конкретных операций с устройством, причем вызов достаточно универсален для любой работы (например, для отладки драйверов с помощью запроса из драйвера структур данных).
Вопрос: как это все можно получить с помощью единственного прототипа функции? Хитрость заключается в использовании двух основных параметров: команды и аргумента. Командой является число, представляющее некоторую операцию. Аргумент представляет собой соответствующий параметр для этой операции. В реализации функции ioctl()
есть переключатель switch … case для выбора реализации соответствующих функций конкретной команды. До недавнего времени в ядре Linux использовался следующий прототип:
int ioctl(struct inode *i, struct file *f, unsigned int cmd, unsigned long arg);
Но, начиная с ядра 2.6.35, он изменился следующим образом:
long ioctl(struct file *f, unsigned int cmd, unsigned long arg);
Если требуется больше аргументов, то все они помещаются в структуру, и указатель на структуру становится 'одним' аргументом команды. Независимо от того, будет ли это целое число или указатель, аргумент в пространстве ядра рассматривается как длинное целое (long integer) и обрабатывается соответствующим образом.
Вызов ioctl()
обычно реализуется как часть соответствующего драйвера, а затем точно также, как и в других системных вызовах, таких как open()
, read()
и т. д., соответствующим образом инициализуется указатель на функцию. Например, в символьных драйверах это будет поле указателя функции ioctl
или unlocked_ioctl
, которое должно быть инициализировано в struct file_operations
.
Опять же, как и с другими системными вызовами, этот системный вызов
эквивалентеен системному вызову ioctl()
из пользовательского
пространства, прототип которого указан в <sys/ioctl.h>
следующим образом:
int ioctl(int fd, int cmd, ...);
Здесь параметр cmd
точно такой же, как и в реализации ioctl()
в драйвере, а переменная-аргумент construct (...) используется в качестве способа передать в ioctl()
драйвера аргумент любого типа (но только один). Другие параметры будут игнорироваться.
Обратите внимание, что требуется, чтобы оба определения типа команды и типа аргумента команды были доступны как в драйвере (в пространстве ядра), так и в приложении (в пользовательском пространстве). Поэтому эти определения, как правило, помещаются в заголовочные файлы для каждого пространства.
Запрос значений внутренних переменных драйвера
Чтобы лучше понять эту скучную теорию, которая объяснялась выше, далее приводится код уже ранее упоминавшегося примера "отладки драйвера". В этом драйвере есть три статические глобальные переменные: status
, dignity
и ego
, к которым необходимо обратиться и, возможно, работать с ними из приложения. В заголовочном файле query_ioctl.h
определены соответствующий тип команды и соответствующий тип аргумента команды. Ниже приводится листинг:
#ifndef QUERY_IOCTL_H #define QUERY_IOCTL_H #include <linux/ioctl.h> typedef struct { int status, dignity, ego; } query_arg_t; #define QUERY_GET_VARIABLES _IOR('q', 1, query_arg_t *) #define QUERY_CLR_VARIABLES _IO('q', 2) #define QUERY_SET_VARIABLES _IOW('q', 3, query_arg_t *) #endif
Если их использовать, то реализация ioctl()
драйвера в query_ioctl.c
будет выглядеть следующим образом:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/version.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/errno.h> #include <asm/uaccess.h> #include "query_ioctl.h" #define FIRST_MINOR 0 #define MINOR_CNT 1 static dev_t dev; static struct cdev c_dev; static struct class *cl; static int status = 1, dignity = 3, ego = 5; static int my_open(struct inode *i, struct file *f) { return 0; } static int my_close(struct inode *i, struct file *f) { return 0; } #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35)) static int my_ioctl(struct inode *i, struct file *f, unsigned int cmd, unsigned long arg) #else static long my_ioctl(struct file *f, unsigned int cmd, unsigned long arg) #endif { query_arg_t q; switch (cmd) { case QUERY_GET_VARIABLES: q.status = status; q.dignity = dignity; q.ego = ego; if (copy_to_user((query_arg_t *)arg, &q, sizeof(query_arg_t))) { return -EACCES; } break; case QUERY_CLR_VARIABLES: status = 0; dignity = 0; ego = 0; break; case QUERY_SET_VARIABLES: if (copy_from_user(&q, (query_arg_t *)arg, sizeof(query_arg_t))) { return -EACCES; } status = q.status; dignity = q.dignity; ego = q.ego; break; default: return -EINVAL; } return 0; } static struct file_operations query_fops = { .owner = THIS_MODULE, .open = my_open, .release = my_close, #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35)) .ioctl = my_ioctl #else .unlocked_ioctl = my_ioctl #endif }; static int __init query_ioctl_init(void) { int ret; struct device *dev_ret; if ((ret = alloc_chrdev_region(&dev, FIRST_MINOR, MINOR_CNT, "query_ioctl")) < 0) { return ret; } cdev_init(&c_dev, &query_fops); if ((ret = cdev_add(&c_dev, dev, MINOR_CNT)) < 0) { return ret; } if (IS_ERR(cl = class_create(THIS_MODULE, "char"))) { cdev_del(&c_dev); unregister_chrdev_region(dev, MINOR_CNT); return PTR_ERR(cl); } if (IS_ERR(dev_ret = device_create(cl, NULL, dev, NULL, "query"))) { class_destroy(cl); cdev_del(&c_dev); unregister_chrdev_region(dev, MINOR_CNT); return PTR_ERR(dev_ret); } return 0; } static void __exit query_ioctl_exit(void) { device_destroy(cl, dev); class_destroy(cl); cdev_del(&c_dev); unregister_chrdev_region(dev, MINOR_CNT); } module_init(query_ioctl_init); module_exit(query_ioctl_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Anil Kumar Pugalia <email_at_sarika-pugs_dot_com>"); MODULE_DESCRIPTION("Query ioctl() Char Driver");
И, наконец, вызов соответствующих функций из приложения query_app.c
будет выглядеть следующим образом:
#include <stdio.h> #include <sys/types.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <sys/ioctl.h> #include "query_ioctl.h" void get_vars(int fd) { query_arg_t q; if (ioctl(fd, QUERY_GET_VARIABLES, &q) == -1) { perror("query_apps ioctl get"); } else { printf("Status : %d\n", q.status); printf("Dignity: %d\n", q.dignity); printf("Ego : %d\n", q.ego); } } void clr_vars(int fd) { if (ioctl(fd, QUERY_CLR_VARIABLES) == -1) { perror("query_apps ioctl clr"); } } void set_vars(int fd) { int v; query_arg_t q; printf("Enter Status: "); scanf("%d", &v); getchar(); q.status = v; printf("Enter Dignity: "); scanf("%d", &v); getchar(); q.dignity = v; printf("Enter Ego: "); scanf("%d", &v); getchar(); q.ego = v; if (ioctl(fd, QUERY_SET_VARIABLES, &q) == -1) { perror("query_apps ioctl set"); } } int main(int argc, char *argv[]) { char *file_name = "/dev/query"; int fd; enum { e_get, e_clr, e_set } option; if (argc == 1) { option = e_get; } else if (argc == 2) { if (strcmp(argv[1], "-g") == 0) { option = e_get; } else if (strcmp(argv[1], "-c") == 0) { option = e_clr; } else if (strcmp(argv[1], "-s") == 0) { option = e_set; } else { fprintf(stderr, "Usage: %s [-g | -c | -s]\n", argv[0]); return 1; } } else { fprintf(stderr, "Usage: %s [-g | -c | -s]\n", argv[0]); return 1; } fd = open(file_name, O_RDWR); if (fd == -1) { perror("query_apps open"); return 2; } switch (option) { case e_get: get_vars(fd); break; case e_clr: clr_vars(fd); break; case e_set: set_vars(fd); break; default: break; } close (fd); return 0; }
Теперь над файлами query_app.c
и query_ioctl.c
выполните следующие операции:
- С помощью команды make соберите драйвер
query_ioctl
(файлquery_ioctl.ko
) и приложение (файлquery_app
). Используйте следующий файлMakefile:
# If called directly from the command line, invoke the kernel build system. ifeq ($(KERNELRELEASE),) KERNEL_SOURCE := /usr/src/linux PWD := $(shell pwd) default: module query_app module: $(MAKE) -C $(KERNEL_SOURCE) SUBDIRS=$(PWD) modules clean: $(MAKE) -C $(KERNEL_SOURCE) SUBDIRS=$(PWD) clean ${RM} query_app # Otherwise KERNELRELEASE is defined; we've been invoked from the # kernel build system and can use its language. else obj-m := query_ioctl.o endif
- Загрузите драйвер с помощью команды
insmod query_ioctl.ko
. - ./query_app — чтобы отобразить значения переменных драйвера
- ./query_app -c — чтобы очистить значения переменных драйвера
- ./query_app -g - чтобы отобразить значения переменных драйвера
- ./query_app -s — чтобы установить значения переменных драйвера (ранее операция не упоминалась)
- Выгрузите драйвер с помощью команды
rmmod query_ioctl
.
Определение команд ioctl()
"Время посещений закончилось" - крикнул охранник. Светлана поблагодарила своих друзей, поскольку она теперь смогла разобраться с большей частью кода, в том числе необходимого, как было сказано выше, для функции copy_to_user()
. Но ей хотелось бы узнать о макросах _IOR
, _IO
и т.д., которые использовались в определениях команд query_ioctl.h
. Как ранее уже упомимналось для команды ioctl()
, это лишь обычные номера. Но теперь в соответствии с стандартом POSIX для octl
можно с помощью различных макросов в виде таких чисел дополнительно кодировать некоторую полезную информацию, касающуюся команд. В стандарте говорится о номерах для 32-битовых команд, сформированных из четырех компонент и занимающих биты [31:0]:
- Направление действия операции команды [биты 31:30] — чтение, запись, обе операции или ни одна из операций — заполняется соответствующим макросом (
_IOR
,_IOW
,_IOWR
,_IO
). - Размер аргумента команды [биты 29:16] — вычисляется с помощью
sizeof()
для конкретного типа аргумента команды - третий аргумент этих макросов. - 8-битное магического числа [биты 15:8] — чтобы обеспечить относительную уникальность команды - как правило, символ ASCII (первый аргумент этих макросов).
- Оригинальный номер команды [биты 7:0] — фактический номер команды (1, 2, 3, ...), определенный в соответствии с нашим требованием - второй аргумент этих макросов.
Детали реализации смотрите в заголовочном файле <asm-generic/ioctl.h>.
К предыдущей статье | Оглавление | К следующей статье |