Библиотека сайта 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>.
| К предыдущей статье | Оглавление | К следующей статье |
