GNU/Linux >> Belajar Linux >  >> Linux

Bagaimana cara menggunakan ioctl() untuk memanipulasi modul kernel saya?

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.


Linux
  1. Cara Menggunakan Perintah RMmod Di Linux Dengan Contoh

  2. Bagaimana Cara Menemukan Modul Kernel Untuk Perangkat yang Diberikan?

  3. Linux – Bagaimana Cara Menentukan Modul Yang Menodai Kernel?

  1. Linux – Bagaimana Cara Memuat Ulang Modul Kernel dengan Benar?

  2. Bagaimana menemukan versi modul kernel yang dikompilasi?

  3. Bagaimana cara membuat kode modul kernel Linux?

  1. Cara menggunakan BusyBox di Linux

  2. Bagaimana saya menggunakan cron di Linux

  3. Bagaimana cara menggunakan kgdb melalui ethernet (kgdboe)?