Contoh minimal yang dapat dijalankan
Diuji dalam lingkungan QEMU + Buildroot yang dapat direproduksi sepenuhnya, sehingga dapat membantu orang lain mendapatkan ioctl
mereka bekerja. GitHub upstream:modul kernel |header bersama |userland.
Bagian yang paling menyebalkan adalah memahami bahwa beberapa id rendah dibajak:ioctl tidak dipanggil jika cmd =2 , Anda harus menggunakan _IOx
makro.
Modul 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)
Header bersama antara modul kernel dan userland:
ioctl.h
#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
Negara pengguna:
#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;
}
Kode contoh yang Anda butuhkan dapat ditemukan di drivers/watchdog/softdog.c
(dari Linux 2.6.33 pada saat ini ditulis), yang mengilustrasikan operasi file yang tepat serta cara mengizinkan userland mengisi struktur dengan ioctl().
Ini sebenarnya tutorial yang bagus dan berfungsi untuk siapa saja yang perlu menulis driver perangkat karakter sepele.
Saya membedah antarmuka ioctl softdog saat menjawab pertanyaan saya sendiri, yang mungkin berguna bagi Anda.
Inilah intinya (meskipun jauh dari lengkap) ...
Di softdog_ioctl()
Anda melihat inisialisasi sederhana dari struct watchdog_info yang mengiklankan fungsionalitas, versi, dan informasi perangkat:
static const struct watchdog_info ident = {
.options = WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE,
.firmware_version = 0,
.identity = "Software Watchdog",
};
Kami kemudian melihat kasus sederhana di mana pengguna hanya ingin mendapatkan kemampuan ini:
switch (cmd) {
case WDIOC_GETSUPPORT:
return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
... yang tentu saja, akan mengisi watchdog_info ruang pengguna yang sesuai dengan nilai yang diinisialisasi di atas. Jika copy_to_user() gagal, -EFAULT dikembalikan yang menyebabkan panggilan ioctl() userspace terkait untuk mengembalikan -1 dengan errno yang berarti sedang disetel.
Perhatikan, permintaan ajaib sebenarnya ditentukan di linux/watchdog.h , sehingga kernel dan ruang pengguna membagikannya:
#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 jelas menandakan "Watchdog ioctl"
Anda dapat dengan mudah mengambil langkah lebih jauh, meminta driver Anda melakukan sesuatu dan menempatkan hasil dari sesuatu itu dalam struktur dan menyalinnya ke ruang pengguna. Misalnya, jika struct watchdog_info juga memiliki anggota __u32 result_code
. Catatan, __u32
hanyalah versi kernel dari uint32_t
.
Dengan ioctl(), pengguna meneruskan alamat suatu objek, baik itu struktur, bilangan bulat, apa pun ke kernel mengharapkan kernel untuk menulis balasannya dalam objek yang identik dan menyalin hasilnya ke alamat yang disediakan.
Hal kedua yang perlu Anda lakukan adalah memastikan perangkat Anda tahu apa yang harus dilakukan ketika seseorang membuka, membaca darinya, menulis padanya, atau menggunakan pengait seperti ioctl(), yang dapat Anda lihat dengan mudah dengan mempelajari softdog.
Yang menarik adalah:
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,
};
Di mana Anda melihat penangan unlocked_ioctl pergi ke ... Anda dapat menebaknya, softdog_ioctl().
Saya pikir Anda mungkin menyandingkan lapisan kerumitan yang benar-benar tidak ada saat berhadapan dengan ioctl(), sesederhana itu. Untuk alasan yang sama, sebagian besar pengembang kernel tidak menyukai antarmuka ioctl baru yang ditambahkan kecuali benar-benar diperlukan. Terlalu mudah untuk kehilangan jejak jenis yang akan diisi oleh ioctl() vs keajaiban yang Anda gunakan untuk melakukannya, yang merupakan alasan utama mengapa copy_to_user() sering gagal mengakibatkan pembusukan kernel dengan gerombolan proses ruang pengguna macet di disk tidur.
Untuk penghitung waktu, saya setuju, ioctl() adalah jalur terpendek menuju kewarasan.
Anda kehilangan .open
penunjuk fungsi di file_operations
Anda struktur untuk menentukan fungsi yang akan dipanggil ketika proses mencoba untuk membuka file perangkat. Anda harus menentukan .ioctl
penunjuk fungsi untuk fungsi ioctl Anda juga.
Coba baca Panduan Pemrograman Modul Kernel Linux, khususnya bab 4 (File Perangkat Karakter) dan 7 (Berbicara dengan File Perangkat).
Bab 4 memperkenalkan file_operations
structure, yang menyimpan pointer ke fungsi yang ditentukan oleh modul/driver yang melakukan berbagai operasi seperti open
atau ioctl
.
Bab 7 memberikan informasi tentang berkomunikasi dengan modul/drive melalui ioctls.
Driver Perangkat Linux, Edisi Ketiga adalah sumber lain yang bagus.