GNU/Linux >> Belajar Linux >  >> Linux

Bagaimana cara menambahkan fungsi polling ke kode modul kernel?

Contoh minimal yang dapat dijalankan

GitHub upstream dengan QEMU + Buildroot boilerplate:

  • modul kernel poll.ko
  • pengujian wilayah pengguna poll.out

Dalam contoh yang disederhanakan ini, kami membuat acara polling dari utas terpisah. Dalam kehidupan nyata, peristiwa jajak pendapat kemungkinan akan dipicu oleh interupsi, saat perangkat keras menyelesaikan beberapa pekerjaan, dan data baru tersedia untuk dibaca oleh pengguna.

Hal utama yang harus diingat adalah jika poll mengembalikan nol, kernel memanggilnya lagi:Mengapa kita perlu memanggil poll_wait di polling?

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);
        }
    }
}

Penggunaan:

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

Hasil:jiffies dicetak ke stdout setiap detik dari userland, misalnya:

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

Paksa polling file_operation untuk mengembalikan 0 untuk melihat apa yang terjadi dengan lebih jelas:

insmod poll.ko ret0=1

Keluaran sampel:

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

Dari sini kita melihat bahwa kontrol tidak dikembalikan ke userland:kernel terus memanggil polling file_operation lagi dan lagi.

Diuji pada Linux 5.4.3.


Anda dapat menemukan beberapa contoh bagus di kernel itu sendiri. Lihat file berikutnya:

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

Untuk menambahkan poll() fungsi ke kode Anda, ikuti langkah selanjutnya.

  1. Sertakan tajuk yang diperlukan:

     #include <linux/wait.h>
     #include <linux/poll.h>
    
  2. Deklarasikan variabel antrian tunggu:

     static DECLARE_WAIT_QUEUE_HEAD(fortune_wait);
    
  3. Tambahkan fortune_poll() function dan menambahkannya (sebagai .poll callback) ke struktur operasi file Anda:

     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,
     };
    

    Perhatikan bahwa Anda harus mengembalikan POLLIN | POLLRDNORM jika Anda memiliki beberapa data baru untuk dibaca, dan 0 seandainya tidak ada data baru untuk dibaca (poll() batas waktu panggilan). Lihat jajak pendapat pria 2 untuk detailnya.

  4. Beri tahu antrian tunggu Anda setelah Anda memiliki data baru:

     wake_up_interruptible(&fortune_wait);
    

Itulah hal dasar tentang mengimplementasikan poll() operasi. Bergantung pada tugas Anda, Anda mungkin perlu menggunakan beberapa API antrian tunggu di .read Anda fungsi (seperti wait_event_interruptible() ).

Lihat juga pertanyaan terkait:Mengimplementasikan polling dalam modul kernel Linux.


Linux
  1. Bagaimana Cara Menemukan Modul Kernel Untuk Perangkat yang Diberikan?

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

  3. Bagaimana Cara Menambahkan Parameter Modul Kernel?

  1. Bagaimana cara memuat modul kernel Linux dari kode C?

  2. Bagaimana cara membuat kode modul kernel Linux?

  3. Apa urutan memuat modul kernel linux saat startup? Bagaimana prioritas ditetapkan untuk mereka?

  1. Bagaimana cara menambahkan baris baru ke akhir file?

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

  3. Linux – Bagian Kernel yang Dimiliki Atau Tertutup?