2010年11月3日星期三

创建/sys入口和使用waitqueue的小例子

一个简单的示例程序。创建一个/sys的接口,可以读写,每次回读都是上次写入的内容。每次读写都会触发一次event。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/kthread.h>

static struct cdev test_cdev;
static dev_t test_devno;
static struct class *test_class;
struct device *test_device;

struct test_thread {
    wait_queue_head_t    wqueue;
    unsigned long           flags;
    struct task_struct    *tsk;
    unsigned long        timeout;
}test_thread;

#define FROM_SHOW  0
#define FROM_STORE 1
static const struct file_operations test_fops =
{
    .owner          = THIS_MODULE,
};


static int test_fun(void *arg)
{
    struct test_thread *thread = arg;
    allow_signal(SIGKILL);

    while (!kthread_should_stop()) {

        /* We need to wait INTERRUPTIBLE so that
         * we don't add to the load-average.
         * That means we need to be sure no signals are
         * pending
         */
        if (signal_pending(current))
            flush_signals(current);

        wait_event_interruptible_timeout
            (thread->wqueue,
                test_bit(FROM_SHOW, &thread->flags)
                || test_bit(FROM_STORE, &thread->flags)
                || kthread_should_stop(),
                thread->timeout);
        printk("thread %s is waken up by %s\n",
            thread->tsk->comm,
            test_bit(FROM_SHOW, &thread->flags) ? "show" :
            test_bit(FROM_STORE, &thread->flags) ? "store" : "kill");
        thread->flags = 0;
    }

    return 0;
}

#define TEST_LEN  4096
static char test_buf[TEST_LEN];
static ssize_t test_show(struct device *ddev,
            struct device_attribute *attr, char *buf)
{
    int len;
    struct test_thread *thread;

    thread = dev_get_drvdata(ddev);
    len = strlen(test_buf) + 1;
    memcpy(buf, test_buf, len);

    set_bit(FROM_SHOW, &thread->flags);
    wake_up(&thread->wqueue);

    return len;
}

static ssize_t test_store(struct device *ddev,
            struct device_attribute *attr, const char *buf, size_t count)
{
    size_t len;
    struct test_thread *thread;

    thread = dev_get_drvdata(ddev);

    if (count < TEST_LEN - 1)
        len = count;
    else
        len = TEST_LEN - 1;

    memcpy(test_buf, buf, len);
    test_buf[len] = 0;

    set_bit(FROM_STORE, &thread->flags);
    wake_up(&thread->wqueue);

    return count;
}

static DEVICE_ATTR(test1, 0644, test_show, test_store);

static int test_init(void)
{
    int ret;

    ret = alloc_chrdev_region(&test_devno, 0, 255, "test");
    if (ret) {
        printk(KERN_INFO "alloc_chrdev_region failed, ret=%d\n", ret);
        return ret;
    }

    cdev_init(&test_cdev, &test_fops);
    test_cdev.owner = THIS_MODULE;
    ret = cdev_add(&test_cdev, test_devno, 1);
    if (ret) {
        printk(KERN_INFO "cdev_add failed, ret=%d\n", ret);
        goto  free_devno;
    }

    test_class =  class_create(THIS_MODULE, "test_class");
    if (IS_ERR(test_class)) {
        ret = PTR_ERR(test_class);
        printk(KERN_INFO "class_create failed, ret=%d\n", ret);
        goto free_cdev;
    }

    test_device = device_create(test_class, NULL, test_devno, NULL, "test");
    if (IS_ERR(test_device)) {
        ret = PTR_ERR(test_device);
        printk(KERN_INFO "device_create failed, ret=%d\n", ret);
        goto free_class;
    }

    ret = device_create_file(test_device, &dev_attr_test1);
    if (ret) {
        printk(KERN_INFO "device_create_file failed, ret=%d\n", ret);
        goto free_device;
    }

    init_waitqueue_head(&test_thread.wqueue);
    test_thread.timeout = MAX_SCHEDULE_TIMEOUT;
    test_thread.flags = 0;
    dev_set_drvdata(test_device, &test_thread);
    test_thread.tsk = kthread_run(test_fun, &test_thread, "test_thread");
    if (IS_ERR(test_thread.tsk)) {
        ret = PTR_ERR(test_thread.tsk);
        printk(KERN_INFO "kthread_run failed, ret=%d\n", ret);
        goto free_file;
    }

    return 0;

free_file:
    device_remove_file(test_device, &dev_attr_test1);
free_device:
    device_destroy(test_class, test_devno);
free_class:
    class_destroy(test_class);
free_cdev:
    cdev_del(&test_cdev);
free_devno:
    unregister_chrdev_region(test_devno, 255);
    return ret;
}

void test_exit(void)
{
    kthread_stop(test_thread.tsk);
    device_remove_file(test_device, &dev_attr_test1);
    device_destroy(test_class, test_devno);
    class_destroy(test_class);
    cdev_del(&test_cdev);
    unregister_chrdev_region(test_devno, 255);
}
MODULE_LICENSE("GPL");
module_init (test_init);
module_exit (test_exit);

没有评论:

发表评论