GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Wie füge ich dem Kernel-Modulcode eine Abfragefunktion hinzu?

Minimales lauffähiges Beispiel

GitHub Upstream mit QEMU + Buildroot Boilerplate:

  • poll.ko-Kernelmodul
  • poll.out Userland-Test

In diesem vereinfachten Beispiel generieren wir Umfrageereignisse aus einem separaten Thread. Im wirklichen Leben werden Poll-Ereignisse wahrscheinlich durch Interrupts ausgelöst, wenn die Hardware einen Job beendet hat und neue Daten für das Userland zum Lesen verfügbar wurden.

Der wichtigste Punkt, an den Sie sich erinnern sollten, ist, dass wenn poll Null zurückgibt, ruft der Kernel erneut auf:Warum müssen wir poll_wait in poll aufrufen?

poll.ko

#include <linux/debugfs.h>
#include <linux/delay.h> /* usleep_range */
#include <linux/errno.h> /* EFAULT */
#include <linux/fs.h>
#include <linux/jiffies.h>
#include <linux/kernel.h> /* min */
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/printk.h> /* printk */
#include <linux/uaccess.h> /* copy_from_user, copy_to_user */
#include <linux/wait.h> /* wait_queue_head_t, wait_event_interruptible, wake_up_interruptible  */
#include <uapi/linux/stat.h> /* S_IRUSR */

static int ret0 = 0;
module_param(ret0, int, S_IRUSR | S_IWUSR);
MODULE_PARM_DESC(i, "if 1, always return 0 from poll");

static char readbuf[1024];
static size_t readbuflen;
static struct dentry *debugfs_file;
static struct task_struct *kthread;
static wait_queue_head_t waitqueue;

static ssize_t read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
    ssize_t ret;
    if (copy_to_user(buf, readbuf, readbuflen)) {
        ret = -EFAULT;
    } else {
        ret = readbuflen;
    }
    /* This is normal pipe behaviour: data gets drained once a reader reads from it. */
    /* https://stackoverflow.com/questions/1634580/named-pipes-fifos-on-unix-with-multiple-readers */
    readbuflen = 0;
    return ret;
}

/* If you return 0 here, then the kernel will sleep until an event
 * happens in the queue. and then call this again, because of the call to poll_wait. */
unsigned int poll(struct file *filp, struct poll_table_struct *wait)
{
    pr_info("poll\n");
    /* This doesn't sleep. It just makes the kernel call poll again if we return 0. */
    poll_wait(filp, &waitqueue, wait);
    if (readbuflen && !ret0) {
        pr_info("return POLLIN\n");
        return POLLIN;
    } else {
        pr_info("return 0\n");
        return 0;
    }
}

static int kthread_func(void *data)
{
    while (!kthread_should_stop()) {
        readbuflen = snprintf(
            readbuf,
            sizeof(readbuf),
            "%llu",
            (unsigned long long)jiffies
        );
        usleep_range(1000000, 1000001);
        pr_info("wake_up\n");
        wake_up(&waitqueue);
    }
    return 0;
}

static const struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = read,
    .poll = poll
};

static int myinit(void)
{
    debugfs_file = debugfs_create_file(
        "lkmc_poll", S_IRUSR | S_IWUSR, NULL, NULL, &fops);
    init_waitqueue_head(&waitqueue);
    kthread = kthread_create(kthread_func, NULL, "mykthread");
    wake_up_process(kthread);
    return 0;
}

static void myexit(void)
{
    kthread_stop(kthread);
    debugfs_remove(debugfs_file);
}

module_init(myinit)
module_exit(myexit)
MODULE_LICENSE("GPL");

poll.out Userland:

#define _XOPEN_SOURCE 700
#include <assert.h>
#include <fcntl.h> /* creat, O_CREAT */
#include <poll.h> /* poll */
#include <stdio.h> /* printf, puts, snprintf */
#include <stdlib.h> /* EXIT_FAILURE, EXIT_SUCCESS */
#include <unistd.h> /* read */

int main(int argc, char **argv) {
    char buf[1024];
    int fd, i, n;
    short revents;
    struct pollfd pfd;

    if (argc < 2) {
        fprintf(stderr, "usage: %s <poll-device>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    fd = open(argv[1], O_RDONLY | O_NONBLOCK);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }
    pfd.fd = fd;
    pfd.events = POLLIN;
    while (1) {
        puts("poll");
        i = poll(&pfd, 1, -1);
        if (i == -1) {
            perror("poll");
            assert(0);
        }
        revents = pfd.revents;
        printf("revents = %d\n", revents);
        if (revents & POLLIN) {
            n = read(pfd.fd, buf, sizeof(buf));
            printf("POLLIN n=%d buf=%.*s\n", n, n, buf);
        }
    }
}

Verwendung:

insmod poll.ko
mount -t debugfs none /sys/kernel/debug
./kernel_modules/poll.out /sys/kernel/debug/lkmc_poll

Ergebnis:jiffies wird jede Sekunde aus dem Userland auf stdout ausgegeben, z. B.:

poll
<6>[    4.275305] poll
<6>[    4.275580] return POLLIN
revents = 1
POLLIN n=10 buf=4294893337
poll
<6>[    4.276627] poll
<6>[    4.276911] return 0
<6>[    5.271193] wake_up
<6>[    5.272326] poll
<6>[    5.273207] return POLLIN
revents = 1
POLLIN n=10 buf=4294893588
poll
<6>[    5.276367] poll
<6>[    5.276618] return 0
<6>[    6.275178] wake_up
<6>[    6.276370] poll
<6>[    6.277269] return POLLIN
revents = 1
POLLIN n=10 buf=4294893839

Erzwingen Sie die Umfrage file_operation um 0 zurückzugeben, um klarer zu sehen, was passiert:

insmod poll.ko ret0=1

Beispielausgabe:

poll
<6>[   85.674801] poll
<6>[   85.675788] return 0
<6>[   86.675182] wake_up
<6>[   86.676431] poll
<6>[   86.677373] return 0
<6>[   87.679198] wake_up
<6>[   87.680515] poll
<6>[   87.681564] return 0
<6>[   88.683198] wake_up

Daraus sehen wir, dass die Kontrolle nicht an das Userland zurückgegeben wird:Der Kernel ruft einfach weiterhin die Abfrage file_operation auf immer wieder.

Getestet auf Linux 5.4.3.


Sie können einige gute Beispiele im Kernel selbst finden. Sehen Sie sich die nächsten Dateien an:

  • drivers/rtc/dev.c, drivers/rtc/interface.c
  • kernel/printk/printk.c
  • drivers/char/random.c

Um poll() hinzuzufügen Funktion zu Ihrem Code folgen Sie den nächsten Schritten.

  1. Erforderliche Header einschließen:

     #include <linux/wait.h>
     #include <linux/poll.h>
    
  2. Warteschlangenvariable deklarieren:

     static DECLARE_WAIT_QUEUE_HEAD(fortune_wait);
    
  3. Fügen Sie fortune_poll() hinzu Funktion und fügen Sie sie hinzu (als .poll Callback) zu Ihrer Dateioperationsstruktur:

     static unsigned int fortune_poll(struct file *file, poll_table *wait)
     {
         poll_wait(file, &fortune_wait, wait);
         if (new-data-is-ready)
             return POLLIN | POLLRDNORM;
         return 0;
     }
    
     static const struct file_operations proc_test_fops = {
         ....
         .poll = fortune_poll,
     };
    

    Beachten Sie, dass Sie POLLIN | POLLRDNORM zurückgeben sollten wenn Sie neue Daten zum Lesen haben, und 0 falls es keine neuen Daten zum Lesen gibt (poll() Anruf abgelaufen). Weitere Informationen finden Sie in der Umfrage von Mann 2.

  4. Benachrichtigen Sie Ihre Warteschlange, sobald Sie neue Daten haben:

     wake_up_interruptible(&fortune_wait);
    

Das ist das Grundlegende zur Implementierung von poll() Betrieb. Abhängig von Ihrer Aufgabe müssen Sie möglicherweise eine Warteschlangen-API in Ihrem .read verwenden Funktion (wie wait_event_interruptible() ).

Siehe auch verwandte Frage:Implementing poll in a Linux kernel module.


Linux
  1. Linux – Proprietäre oder geschlossene Teile des Kernels?

  2. Wie füge ich Kernel-Modulparameter hinzu?

  3. Wie verwende ich ioctl(), um mein Kernel-Modul zu manipulieren?

  4. Wie finde ich die Version eines kompilierten Kernelmoduls?

  5. Wie überprüfe ich HZ im Terminal?

Wie der Linux-Kernel mit Interrupts umgeht

So laden oder entladen Sie ein Linux-Kernel-Modul

So überprüfen Sie die Kernel-Version in Linux

So fügen Sie Text am Anfang einer Datei in Linux hinzu

So fügen Sie eine Anwendung zum Dock in einem elementaren Betriebssystem hinzu

Wie lade ich Linux-Kernel-Module aus C-Code?