web-dev-qa-db-ja.com

カーネルモジュールコードにポーリング関数を追加するにはどうすればよいですか?

私が知っているように、カーネル空間からユーザー空間に通知するための1つの方法は、pollを使用することです。つまり、カーネルドライバは最初にポーリングメソッドを提供する必要があります。以下のコードはインターネットから見つけられ、それは本当に機能します!

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <linux/vmalloc.h>
#include <asm/uaccess.h>

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Fortune Cookie Kernel Module");
MODULE_AUTHOR("M. Tim Jones");

#define MAX_COOKIE_LENGTH       PAGE_SIZE

static struct proc_dir_entry *proc_entry;
static char *cookie_buf;  // Space for fortune strings
static int write_index;   // Index to write next fortune
static int read_index;    // Index to read next fortune

ssize_t fortune_write( struct file *filp, const char __user *buff,
                        unsigned long len, void *data )
// Refer to: ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
{
  int space_available = (MAX_COOKIE_LENGTH-write_index);

  if (len > space_available) {
    printk(KERN_INFO "fortune: cookie buffer is full!\n");
    return -ENOSPC;
  }

  if (copy_from_user( &cookie_buf[write_index], buff, len )) {
    return -EFAULT;
  }

  write_index += len;
  cookie_buf[write_index-1] = 0;

  return len;
}

ssize_t fortune_read(struct file *file, char *buf, size_t count, loff_t *f_pos){
// Refer to: ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    int len;

    //there's no fortune or a fortune has already been read
    //the *f_pos > 0 hack is needed because `cat /proc/fortune` would otherwise
    //display every thing in the cookie_buf
    if(write_index == 0 || *f_pos > 0){
        return 0;
    }

    // cicle through fortunes
    if(read_index >= write_index){
        read_index = 0;
    }

    len = sprintf(buf, "%s\n", &cookie_buf[read_index]);

    read_index += len;
    *f_pos += len;

    return len;
}

static const struct file_operations proc_test_fops = {
   .owner        = THIS_MODULE,
//    .open        = led_proc_open,
   .read        = fortune_read,
//    .llseek        = seq_lseek,
//    .release    = single_release,
   .write        = fortune_write,
//    unsigned int (*poll) (struct file *, struct poll_table_struct *);
//    int (*fasync) (int, struct file *, int);
};

int __init init_fortune_module( void )
{
    int ret = 0;
    cookie_buf = (char *)vmalloc( MAX_COOKIE_LENGTH );
    if (!cookie_buf) {
        ret = -ENOMEM;
    } else {
        memset( cookie_buf, 0, MAX_COOKIE_LENGTH );
//        proc_entry = create_proc_entry( "fortune", 0644, NULL );
        proc_entry = proc_create( "fortune", 0644, NULL, &proc_test_fops );

        if (proc_entry == NULL) {
            ret = -ENOMEM;
            vfree(cookie_buf);
            printk(KERN_INFO "fortune: Couldn't create proc entry\n");
        } else {
            write_index = 0;
            read_index = 0;
            printk(KERN_INFO "fortune: Module loaded.\n");
        }
    }

    return ret;
}

void __exit exit_fortune_module( void )
{
//    remove_proc_entry("fortune", &proc_entry);
    proc_remove(proc_entry);
    vfree(cookie_buf);
    printk(KERN_INFO "fortune: Module unloaded.\n");
}

module_init( init_fortune_module );
module_exit( exit_fortune_module );

私はそれを機能させるためにこのようにすることができます:

echo "hello" > /proc/fortune

その後

cat /proc/fortune

結果を確認します。

しかし、それにポーリングメソッドを追加する方法は?何度か試しましたが、失敗しました。誰か助けてもらえますか?ありがとう!

12
Tom Xue

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

コードにpoll()関数を追加するには、次の手順に従います。

  1. 必要なヘッダーを含める:

    _#include <linux/wait.h>
    #include <linux/poll.h>
    _
  2. ウェイトキュー変数を宣言します。

    _static DECLARE_WAIT_QUEUE_HEAD(fortune_wait);
    _
  3. fortune_poll()関数を追加し、それを(_.poll_コールバックとして)ファイル操作構造に追加します。

    _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 poll を参照してください。

  4. 新しいデータを取得したら、待機キューに通知します。

    _wake_up_interruptible(&fortune_wait);
    _

これが、poll()操作の実装に関する基本的なことです。タスクによっては、_.read_関数(wait_event_interruptible()など)で waitqueue API を使用する必要がある場合があります。


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

15
Sam Protsenko

実行可能な最小限の例

使用法:

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

結果:1秒ごとに、以下が画面に出力されます。

loop
POLLIN n=10 buf=<jiffies>

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

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

poll.ko:

#include <asm/uaccess.h> /* copy_from_user, copy_to_user */
#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/wait.h> /* wait_queue_head_t, wait_event_interruptible, wake_up_interruptible  */
#include <uapi/linux/stat.h> /* S_IRUSR */

MODULE_LICENSE("GPL");

static char readbuf[1024];
static size_t readbuflen;
static struct dentry *dir;
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.

This gets called again every time an event happens in the wait queue.
*/
unsigned int poll(struct file *filp, struct poll_table_struct *wait)
{
    poll_wait(filp, &waitqueue, wait);
    if (readbuflen)
        return POLLIN;
    else
        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);
        wake_up(&waitqueue);
    }
    return 0;
}

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

static int myinit(void)
{
    dir = debugfs_create_dir("lkmc_poll", 0);
    debugfs_create_file("f", S_IRUSR | S_IWUSR, dir, 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_recursive(dir);
}

module_init(myinit)
module_exit(myexit)

poll.outユーザーランド:

#define _XOPEN_SOURCE 700
#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], path[1024];
    int fd, i, n;
    short revents;
    struct pollfd pfd;

    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("loop");
        i = poll(&pfd, 1, -1);
        if (i == -1) {
            perror("poll");
            exit(EXIT_FAILURE);
        }
        revents = pfd.revents;
        if (revents & POLLIN) {
            n = read(pfd.fd, buf, sizeof(buf));
            printf("POLLIN n=%d buf=%.*s\n", n, n, buf);
        }
    }
}