Как использовать ioctl() для управления моим модулем ядра?
Итак, я пытаюсь написать модуль ядра, который использует файл linux/timer.h. Я получил его для работы внутри модуля, и теперь я пытаюсь заставить его работать из пользовательской программы.
Вот мой модуль ядра:
//Necessary Includes For Device Drivers.
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#include <linux/timer.h>
#include <linux/ioctl.h>
#define DEVICE_NAME "mytimer"
#define DEVICE_FILE_NAME "mytimer"
#define MAJOR_NUM 61
#define MINOR_NUM 0
MODULE_LICENSE("Dual BSD/GPL");
static struct timer_list my_timer;
struct file_operations FileOps =
{
//No File Operations for this timer.
};
//Function to perform when timer expires.
void TimerExpire(int data)
{
printk("Timer Data: %d\n", data);
}
//Function to set up timers.
void TimerSetup(void)
{
setup_timer(&my_timer, TimerExpire, 5678);
mod_timer(&my_timer, jiffies + msecs_to_jiffies(5000));
}
//Module Init and Exit Functions.
int init_module(void)
{
int initResult = register_chrdev(MAJOR_NUM, "mytimer", &FileOps);
if (initResult < 0)
{
printk("Cannot obtain major number %d\n", MAJOR_NUM);
return initResult;
}
printk("Loading MyTimer Kernel Module...\n");
return 0;
}
void cleanup_module(void)
{
unregister_chrdev(MAJOR_NUM, "mytimer");
printk("Unloading MyTimer Kernel Module...\n");
}
В частности, я хочу, чтобы моя пользовательская программа вызывала функцию TimerSetup(). Я знаю, что мне нужно будет использовать ioctl(), но я не уверен, как указать в моем файле MODULE FILE, что TimerSetup() должен быть вызван через ioctl().
Кроме того, мой второй вопрос: мне удалось включить мой модуль, а также mknod в /dev/mytimer с правильным основным номером. Но когда я попытался открыть(), чтобы я мог получить дескриптор файла из него, он продолжал возвращать -1, что я предполагаю, это неправильно. Я удостоверился, что разрешения были прекрасны (на самом деле, я сделал это 777, чтобы быть уверенным)... Это все еще не работает... Есть ли что-то, что мне не хватает?
Вот программа пользователя на всякий случай:
#include <stdio.h>
int main(int argc, char* argv[])
{
int fd = open("/dev/mytimer", "r");
printf("fd: %d\n", fd);
return 0;
}
Ответы
Ответ 1
Пример кода, который вам нужен, можно найти в drivers/watchdog/softdog.c
(из Linux 2.6.33 на момент написания), который иллюстрирует правильные операции с файлами, а также как разрешить пользовательской области заполнять структуру с помощью ioctl().
На самом деле это отличный, рабочий учебник для тех, кому нужно писать тривиальные драйверы символов.
Я рассмотрел интерфейс softdog ioctl, когда ответил на мой вопрос, который может быть вам полезен.
Вот его суть (хотя и далеко не исчерпывающая)...
В softdog_ioctl()
вы видите простую инициализацию struct watchdog_info, которая рекламирует функциональность, версию и информацию об устройстве:
static const struct watchdog_info ident = {
.options = WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE,
.firmware_version = 0,
.identity = "Software Watchdog",
};
Затем мы рассмотрим простой случай, когда пользователь просто хочет получить эти возможности:
switch (cmd) {
case WDIOC_GETSUPPORT:
return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
... который, конечно же, заполнит соответствующее пользовательское пространство watchdog_info с инициализированными значениями выше. Если copy_to_user() терпит неудачу, возвращается -EFAULT, что вызывает вызов ioctl() соответствующего пользовательского пространства для возврата -1 с установленным значением errno.
Обратите внимание, что магические запросы на самом деле определены в linux/watchdog.h, так что ядро и пользовательское пространство делятся ими:
#define WDIOC_GETSUPPORT _IOR(WATCHDOG_IOCTL_BASE, 0, struct watchdog_info)
#define WDIOC_GETSTATUS _IOR(WATCHDOG_IOCTL_BASE, 1, int)
#define WDIOC_GETBOOTSTATUS _IOR(WATCHDOG_IOCTL_BASE, 2, int)
#define WDIOC_GETTEMP _IOR(WATCHDOG_IOCTL_BASE, 3, int)
#define WDIOC_SETOPTIONS _IOR(WATCHDOG_IOCTL_BASE, 4, int)
#define WDIOC_KEEPALIVE _IOR(WATCHDOG_IOCTL_BASE, 5, int)
#define WDIOC_SETTIMEOUT _IOWR(WATCHDOG_IOCTL_BASE, 6, int)
#define WDIOC_GETTIMEOUT _IOR(WATCHDOG_IOCTL_BASE, 7, int)
#define WDIOC_SETPRETIMEOUT _IOWR(WATCHDOG_IOCTL_BASE, 8, int)
#define WDIOC_GETPRETIMEOUT _IOR(WATCHDOG_IOCTL_BASE, 9, int)
#define WDIOC_GETTIMELEFT _IOR(WATCHDOG_IOCTL_BASE, 10, int)
WDIOC, очевидно, означает "Watchdog ioctl"
Вы можете легко сделать этот шаг дальше, указав, что ваш драйвер что-то сделал, и поместите результат этого что-то в структуру и скопируйте его в пользовательское пространство. Например, если у struct watchdog_info также был член __u32 result_code
. Примечание. __u32
- это только версия ядра uint32_t
.
С помощью ioctl() пользователь передает адрес объекта, будь то структура, целое число, независимо от того, что ядро ожидает, чтобы ядро записало свой ответ в идентичном объекте и скопировал результаты по адресу, который был предоставлен.
Второе, что вам нужно сделать, это убедиться, что ваше устройство знает, что делать, когда кто-то открывает, читает его, записывает на него или использует крючок вроде ioctl(), который вы можете легко увидеть, изучая softdog.
Интересно:
static const struct file_operations softdog_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = softdog_write,
.unlocked_ioctl = softdog_ioctl,
.open = softdog_open,
.release = softdog_release,
};
Где вы видите обработчик unlocked_ioctl, который... вы догадались, softdog_ioctl().
Я думаю, вы могли бы сопоставить уровень сложности, который на самом деле не существует при работе с ioctl(), это действительно так просто. По этой же причине большинство разработчиков ядра недовольны новыми добавляемыми интерфейсами ioctl, если они не являются абсолютно необходимыми. Слишком легко потерять информацию о типе, который ioctl() собирается заполнить, и магии, которую вы используете для этого, что является основной причиной того, что copy_to_user() часто не получается, что приводит к гниению ядра с ордами процессов пользовательского пространства, застрявшими в дисковый сон.
Для таймера, я согласен, ioctl() - это самый короткий путь к здравомыслию.
Ответ 2
В структуре file_operations
отсутствует указатель функции .open
, чтобы указать функцию, вызываемую при попытке процесса открыть файл устройства. Вам также нужно указать указатель функции .ioctl
для вашей функции ioctl.
Попробуйте прочитать Руководство по программированию модулей ядра Linux, в частности главы 4 (Файлы персональных устройств) и 7 (Разговор с файлами устройств).
В главе 4 представлена структура file_operations
, которая содержит указатели на функции, определенные модулем/драйвером, которые выполняют различные операции, такие как open
или ioctl
.
В главе 7 содержится информация о связи с модулем/диском через ioctls.
Драйверы устройств Linux, третье издание - еще один хороший ресурс.
Ответ 3
Минимальный пример runnable
Протестировано в полностью воспроизводимой среде QEMU + Buildroot, поэтому может помочь другим получить работу ioctl
. GitHub вверх по течению:
модуль ядра |
общий заголовок |
userland.
Самая раздражающая часть заключалась в понимании того, что некоторые низкие идентификаторы захвачены: ioctl не вызывается, если cmd = 2, вы должны использовать макросы _IOx
.
Модуль ядра:
#include <asm/uaccess.h> /* copy_from_user, copy_to_user */
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/printk.h> /* printk */
#include "ioctl.h"
MODULE_LICENSE("GPL");
static struct dentry *dir;
static long unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long argp)
{
void __user *arg_user;
union {
int i;
lkmc_ioctl_struct s;
} arg_kernel;
arg_user = (void __user *)argp;
pr_info("cmd = %x\n", cmd);
switch (cmd) {
case LKMC_IOCTL_INC:
if (copy_from_user(&arg_kernel.i, arg_user, sizeof(arg_kernel.i))) {
return -EFAULT;
}
pr_info("0 arg = %d\n", arg_kernel.i);
arg_kernel.i += 1;
if (copy_to_user(arg_user, &arg_kernel.i, sizeof(arg_kernel.i))) {
return -EFAULT;
}
break;
case LKMC_IOCTL_INC_DEC:
if (copy_from_user(&arg_kernel.s, arg_user, sizeof(arg_kernel.s))) {
return -EFAULT;
}
pr_info("1 arg = %d %d\n", arg_kernel.s.i, arg_kernel.s.j);
arg_kernel.s.i += 1;
arg_kernel.s.j -= 1;
if (copy_to_user(arg_user, &arg_kernel.s, sizeof(arg_kernel.s))) {
return -EFAULT;
}
break;
default:
return -EINVAL;
break;
}
return 0;
}
static const struct file_operations fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = unlocked_ioctl
};
static int myinit(void)
{
dir = debugfs_create_dir("lkmc_ioctl", 0);
/* ioctl permissions are not automatically restricted by rwx as for read / write,
* but we could of course implement that ourselves:
* https://stackoverflow.com/info/29891803/user-permission-check-on-ioctl-command */
debugfs_create_file("f", 0, dir, NULL, &fops);
return 0;
}
static void myexit(void)
{
debugfs_remove_recursive(dir);
}
module_init(myinit)
module_exit(myexit)
Общий заголовок:
#ifndef IOCTL_H
#define IOCTL_H
#include <linux/ioctl.h>
typedef struct {
int i;
int j;
} lkmc_ioctl_struct;
#define LKMC_IOCTL_MAGIC 0x33
#define LKMC_IOCTL_INC _IOWR(LKMC_IOCTL_MAGIC, 0, int)
#define LKMC_IOCTL_INC_DEC _IOWR(LKMC_IOCTL_MAGIC, 1, lkmc_ioctl_struct)
#endif
Самодельная:
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "../ioctl.h"
int main(int argc, char **argv)
{
int fd, arg_int, ret;
lkmc_ioctl_struct arg_struct;
if (argc < 2) {
puts("Usage: ./prog <ioctl-file>");
return EXIT_FAILURE;
}
fd = open(argv[1], O_RDONLY);
if (fd == -1) {
perror("open");
return EXIT_FAILURE;
}
/* 0 */
{
arg_int = 1;
ret = ioctl(fd, LKMC_IOCTL_INC, &arg_int);
if (ret == -1) {
perror("ioctl");
return EXIT_FAILURE;
}
printf("arg = %d\n", arg_int);
printf("ret = %d\n", ret);
printf("errno = %d\n", errno);
}
puts("");
/* 1 */
{
arg_struct.i = 1;
arg_struct.j = 1;
ret = ioctl(fd, LKMC_IOCTL_INC_DEC, &arg_struct);
if (ret == -1) {
perror("ioctl");
return EXIT_FAILURE;
}
printf("arg = %d %d\n", arg_struct.i, arg_struct.j);
printf("ret = %d\n", ret);
printf("errno = %d\n", errno);
}
close(fd);
return EXIT_SUCCESS;
}