GNU/Linux >> Linux の 問題 >  >> Linux

カーネルモジュールコードにポーリング機能を追加するには?

最小限の実行可能な例

QEMU + Buildroot ボイラープレートを使用した GitHub アップストリーム:

  • poll.ko カーネル モジュール
  • poll.out ユーザーランド テスト

この簡単な例では、別のスレッドからポーリング イベントを生成します。実際には、ポーリング イベントは、ハードウェアが何らかのジョブを終了し、新しいデータがユーザーランドで読み取れるようになったときに、割り込みによってトリガーされる可能性があります。

覚えておくべき主なポイントは、 poll の場合 ゼロを返すと、カーネルはそれを再度呼び出します:poll で poll_wait を呼び出す必要があるのはなぜですか?

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 ユーザーランド:

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

使い方:

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

結果:jiffies ユーザーランドから毎秒 stdout に出力されます。例:

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

ポーリング file_operation を強制する 何が起こるかをより明確に見るために 0 を返す:

insmod poll.ko ret0=1

出力例:

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

このことから、制御がユーザーランドに戻されていないことがわかります。カーネルはポーリング file_operation を呼び出し続けます。 何度も何度も。

Linux 5.4.3 でテスト済み。


カーネル自体にいくつかの良い例があります。次のファイルを見てください:

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

poll() を追加するには 次の手順に従います。

<オール>
  • 必要なヘッダーを含めます:

     #include <linux/wait.h>
     #include <linux/poll.h>
    
  • waitqueue 変数を宣言します:

     static DECLARE_WAIT_QUEUE_HEAD(fortune_wait);
    
  • fortune_poll() を追加 関数を追加して (.poll として) callback) をファイル操作構造に:

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

    POLLIN | POLLRDNORM を返す必要があることに注意してください 読み取る新しいデータがある場合、および 0 読み取る新しいデータがない場合 (poll() コールタイムアウト)。詳細については、man 2 の投票を参照してください。

  • 新しいデータを取得したら、waitqueue に通知します:

     wake_up_interruptible(&fortune_wait);
    
  • これが poll() の実装に関する基本的なことです 手術。タスクによっては、.read でいくつかの waitqueue API を使用する必要がある場合があります。 関数 (wait_event_interruptible() など) ).

    関連する質問:Linux カーネル モジュールでのポーリングの実装も参照してください。


    Linux
    1. 特定のデバイスのカーネルモジュールを見つける方法は?

    2. Linux –どのモジュールがカーネルを汚染しているかを判断する方法は?

    3. Linux –カーネルのプロプライエタリまたはクローズドパーツ?

    1. C++ で C 関数を呼び出す方法、C で C++ 関数を呼び出す方法 (C と C++ の混合)

    2. コンパイルされたカーネルモジュールのバージョンを見つける方法は?

    3. CコードからLinuxカーネルモジュールをロードする方法は?

    1. ターミナルでHZを確認するには?

    2. vi/vimのCコードで関数の呼び出し元と呼び出し先を見つける方法は?

    3. 起動時にLinuxカーネルモジュールをロードするシーケンスは何ですか?それらにどのように優先順位が設定されていますか?