Como uso o ioctl () para manipular meu módulo de kernel?
-
20-09-2019 - |
Pergunta
Então, estou tentando escrever um módulo de kernel que use o arquivo Linux/Timer.h. Consegui funcionar dentro apenas do módulo e agora estou tentando fazê -lo funcionar em um programa de usuário.
Aqui está o meu módulo de kernel:
//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");
}
Mais especificamente, quero que meu programa de usuário chame a função timersetup (). Sei que vou precisar usar o ioctl (), mas não tenho certeza de como especificar no meu arquivo de módulo que o timerSetup () deve ser chamável via ioctl ().
Além disso, minha segunda pergunta: fui capaz de inserir meu módulo e também MKNOD em /dev /mytimer com o número principal correto. Mas quando tentei abrir () para que eu possa obter o descritor de arquivos, ele continuou retornando -1, o que estou supondo que esteja errado. Eu certifiquei -me de que as permissões estivessem bem (na verdade, fiz 777 apenas para ter certeza) ... ainda não funciona ... há algo que estou perdendo?
Aqui está o programa de usuários apenas no caso:
#include <stdio.h>
int main(int argc, char* argv[])
{
int fd = open("/dev/mytimer", "r");
printf("fd: %d\n", fd);
return 0;
}
Solução
O código de exemplo que você precisa pode ser encontrado em drivers/watchdog/softdog.c
(do Linux 2.6.33 no momento em que isso foi escrito), que ilustra as operações de arquivo adequadas, bem como permitir que o Userland preencha uma estrutura com IOCTL ().
Na verdade, é um ótimo tutorial de trabalho para quem precisa escrever drivers de dispositivos de caracteres triviais.
Eu dissectei a interface IOCTL da SoftDog quando respondendo minha própria pergunta, o que pode ser útil para você.
Aqui está a essência disso (embora longe de ser exaustiva) ...
Dentro softdog_ioctl()
Você vê uma simples inicialização de struct watchdog_info que anuncia as informações da funcionalidade, versão e dispositivo:
static const struct watchdog_info ident = {
.options = WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE,
.firmware_version = 0,
.identity = "Software Watchdog",
};
Em seguida, analisamos um caso simples em que o usuário só deseja obter esses recursos:
switch (cmd) {
case WDIOC_GETSUPPORT:
return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
... que obviamente, preencherá o Usuáriospace Watchdog_info correspondente com os valores inicializados acima. Se copy_to_user () falhar, -efault será retornado, o que faz com que a chamada correspondente do userspace ioctl () retorne -1 com um errno significativo sendo definido.
Observe que as solicitações mágicas são realmente definidas no Linux/Watchdog.h, para que o kernel e o espaço dos usuários os compartilhem:
#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 significando obviamente "Watchdog ioctl"
Você pode facilmente dar um passo adiante, fazendo com que seu driver faça algo e coloque o resultado disso na estrutura e copie -o para o Usuários Space. Por exemplo, se struct watchdog_info também tivesse um membro __u32 result_code
. Observação, __u32
é apenas a versão do kernel de uint32_t
.
Com o ioctl (), o usuário passa o endereço de um objeto, seja uma estrutura, número inteiro, qualquer que seja para o kernel esperando que o kernel escreva sua resposta em um objeto idêntico e copie os resultados para o endereço fornecido.
A segunda coisa que você precisará fazer é garantir que seu dispositivo saiba o que fazer quando alguém abrir, ler, gravar ou usar um gancho como ioctl (), que você pode ver facilmente estudando o SoftDog.
De interesse é:
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,
};
Onde você vê o manipulador desbloqueado_ioctl indo para ... você adivinhou, softdog_ioctl ().
Eu acho que você pode estar justapondo uma camada de complexidade que realmente não existe ao lidar com o ioctl (), é realmente simples assim. Por esse mesmo motivo, a maioria dos desenvolvedores do kernel desaprova novas interfaces de IOCTL sendo adicionadas, a menos que sejam absolutamente necessárias. É muito fácil perder o controle do tipo que ioctl () preencherá vs a mágica que você usa para fazê -lo, que é a principal razão pela qual copy_to_user () falha geralmente resultando no kernel apodrecendo com hordas de processos de espaço de usuários presos sono de disco.
Para um cronômetro, eu concordo, o ioctl () é o caminho mais curto para a sanidade.
Outras dicas
Você está perdendo um .open
ponteiro da função em seu file_operations
Estrutura para especificar a função a ser chamada quando um processo tenta abrir o arquivo do dispositivo. Você precisará especificar um .ioctl
Ponteiro da função para sua função IOCTL também.
Tente ler O guia de programação do módulo do kernel Linux, especificamente os capítulos 4 (arquivos de dispositivo de caracteres) e 7 (conversando com os arquivos do dispositivo).
Capítulo 4 apresenta o file_operations
estrutura, que mantém ponteiros para funções definidas pelo módulo/driver que executam várias operações, como open
ou ioctl
.
Capítulo 7 Fornece informações sobre a comunicação com um módulo/unidade via IOCTLS.
Drivers de dispositivo Linux, terceira edição é outro bom recurso.
Exemplo mínimo de execução
Testado em um ambiente QEMU + Buildroot totalmente reproduzível, por isso pode ajudar outras pessoas a obter seus ioctl
trabalhando. Github a montante:Módulo do kernel |
Cabeçalho compartilhado |
Userland.
A parte mais irritante foi entender que alguns IDs baixos são seqüestrados: O IOCTL não é chamado se CMD = 2 , você tem que usar _IOx
macros.
Módulo de kernel:
#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/questions/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)
Cabeçalho compartilhado:
#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
Userland:
#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;
}