美文网首页程序员
内核线程同步之wait_queue

内核线程同步之wait_queue

作者: 网路元素 | 来源:发表于2019-10-12 07:02 被阅读0次

    在《内核线程同步之completion》一文中说到completion完成量也是基于wait_queue等待队列机制实现(这些机制不仅仅用于内核线程的同步,也可用于其他相关场景,这里用线程演示是其比较方便感受到效果),那么接下来就来了解下这一机制的相关内容,其在Linux Kernel源码include/linux/wait.h文件中有如下内容:

    typedef struct __wait_queue wait_queue_t;

    struct __wait_queue {
            unsigned int flags;
            #define WQ_FLAG_EXCLUSIVE 0x01
            void *private;
            wait_queue_func_t func;
            struct list_head task_list;
    };

    struct __wait_queue_head {
            spinlock_t lock;
            struct list_head task_list;
    };

    typedef struct __wait_queue_head wait_queue_head_t;

    上面包含了等待队列和等待队列头部类型的结构体声明。其对应有如下静态初始化宏:

    #define DECLARE_WAITQUEUE(name, tsk) \
            wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)

    #define DECLARE_WAIT_QUEUE_HEAD(name) \
            wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

    还有如下动态初始化宏和函数:

    #define init_waitqueue_head(q) \
            do { \
                    static struct lock_class_key __key; \
                     \
                    __init_waitqueue_head((q), #q, &__key); \
            } while (0)

    static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)
    {
            q->flags = 0;
            q->private = p;
            q->func = default_wake_function;
    }

    static inline void
    init_waitqueue_func_entry(wait_queue_t *q, wait_queue_func_t func)
    {
            q->flags = 0;
            q->private = NULL;
            q->func = func;
    }

    还有添加、移除等待队列的函数头:

    extern void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
    extern void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait);
    extern void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

    其中都是将wait等待队列添加到以q为头的等待队列中,或者是从以q为头的等待队列中移除wait等待队列。 还有如下在等待队列上睡眠等待指定条件(资源)的宏:

    #define wait_event(wq, condition) \
            do { \
                    if (condition) \
                            break; \
                    __wait_event(wq, condition); \
            } while (0)

    #define wait_event_timeout(wq, condition, timeout) \
    ({ \
            long __ret = timeout; \
            if (!___wait_cond_timeout(condition)) \
                    __ret = __wait_event_timeout(wq, condition, timeout); \
            __ret; \
    })

    #define wait_event_interruptible(wq, condition) \
    ({ \
            int __ret = 0; \
            if (!(condition)) \
                    __ret = __wait_event_interruptible(wq, condition); \
            __ret; \
    })

    以及对应的唤醒宏:

    #define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
    #define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

    还有判断waitqueue是否存活的函数:

    static inline int waitqueue_active(wait_queue_head_t *q)
    {
            return !list_empty(&q->task_list);
    }

    具体也不用多解释,从Completion已经可以开始感受到Linux Kernel的一些命名风格了,下面还是以两个内核线程的方式来演示等待队列的使用:

    #include <linux/module.h>
    #include <linux/kthread.h>
    #include <linux/wait.h>

    static struct task_struct * slam1, * slam2;
    static wait_queue_head_t slam_wq_head;
    static int wakeup_condition = 0;

    extern unsigned long msleep_interruptible(unsigned int msecs);

    static int slam1_func(void *data)
    {
            do{
                    printk("I'm in slam1,wating slam_wq_head to wakeup me!\n");
                    wait_event_interruptible(slam_wq_head, wakeup_condition == 5);
                    wakeup_condition=0;
                    printk("slam1:slam_wq_head wakeup me done!\n");
            }while(!kthread_should_stop());

            return 0;
    }

    static int slam2_func(void *data)
    {
            do{
                    printk("I'm in slam2_func,i'll do wakeup!\n");
                    wakeup_condition++;
                    if((wakeup_condition == 5) && waitqueue_active(&slam_wq_head)){
                            wake_up_interruptible(&slam_wq_head);
                    }

                    msleep_interruptible(1000);
            }while(!kthread_should_stop());

            return 0;
    }

    static __init int kthread_waitqueue_init(void)
    {
            init_waitqueue_head(&slam_wq_head);
            slam1 = kthread_run(slam1_func, NULL, "slam1");

            if(IS_ERR(slam1))
            {
                    printk("kthread_run slam1 failed!\n");
                    return 1;
            }

            slam2 = kthread_run(slam2_func, NULL, "slam2");
            if(IS_ERR(slam2))
            {
                    printk("kthread_run slam2 failed!\n");
                    return 1;
            }

            return 0;
    }

    static __exit void kthread_waitqueue_exit(void)
    {
            if(!IS_ERR(slam1))
                    kthread_stop(slam1);
            if(!IS_ERR(slam2))
                    kthread_stop(slam2);
    }

    module_init(kthread_waitqueue_init);
    module_exit(kthread_waitqueue_exit);

    相应的Makefile文件内容如下:

    obj-m += kthread_waitqueue_example.o

    CUR_PATH:=$(shell pwd)
    LINUX_KERNEL_PATH:=/home/xinu/linux-3.13.6

    all:
            make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) modules

    clean:
            make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) clean

    相应的源码文件目录树如下:

    /home/xinu/xinu/linux_kernel_driver_l1/kthread_waitqueue_example/
    ├── kthread_waitqueue_example.c
    └── Makefile 

    注意源码里wait一方在等到条件成立时要修改条件的值,不然就一直不用等了。在卸载模块时也有Completion的速度问题,可参照其修改。还有这里只是简单使用了等待队列头,而等待队列呢?请看下面,只是修改了初始化,把新的等待队列绑定了其处理函数,可从dmesg里查看到:

    #include <linux/module.h>
    #include <linux/kthread.h>
    #include <linux/wait.h>

    static struct task_struct * slam1, * slam2;
    static wait_queue_head_t slam_wq_head;
    static wait_queue_t slam_wq;
    static int wakeup_condition = 0;

    extern unsigned long msleep_interruptible(unsigned int msecs);

    static int slam_wq_func(wait_queue_t *wait, unsigned mode, int flags, void *key)
    {
            printk("I'm in slam_wq_func!\n");
            return 0;
    }

    static int slam1_func(void *data)
    {
            do{
                    printk("I'm in slam1,wating slam_wq_head to wakeup me!\n");
                    wait_event_interruptible(slam_wq_head, wakeup_condition == 5);
                    wakeup_condition=0;
                    printk("slam1:slam_wq_head wakeup me done!\n");
            }while(!kthread_should_stop());

            return 0;
    }

    static int slam2_func(void *data)
    {
            do{
                    printk("I'm in slam2_func,i'll do wakeup!\n");
                    wakeup_condition++;
                    if((wakeup_condition == 5) && waitqueue_active(&slam_wq_head)){
                            wake_up_interruptible(&slam_wq_head);
                    }
                    msleep_interruptible(1000);
            }while(!kthread_should_stop());

            return 0;
    }

    static __init int kthread_waitqueue_init(void)
    {
            init_waitqueue_head(&slam_wq_head);
            init_waitqueue_func_entry(&slam_wq, slam_wq_func);
            add_wait_queue(&slam_wq_head, &slam_wq);

            slam1 = kthread_run(slam1_func, NULL, "slam1");
            if(IS_ERR(slam1))
            {
                    printk("kthread_run slam1 failed!\n");
                    return 1;
            }

            slam2 = kthread_run(slam2_func, NULL, "slam2");
            if(IS_ERR(slam2))
            {
                    printk("kthread_run slam2 failed!\n");
                    return 1;
            }

            return 0;
    }

    static __exit void kthread_waitqueue_exit(void)
    {
            if(!IS_ERR(slam1))
                    kthread_stop(slam1);
            if(!IS_ERR(slam2))
                    kthread_stop(slam2);
    }

    module_init(kthread_waitqueue_init);
    module_exit(kthread_waitqueue_exit);

    这里只是简单加了个等待队列并且绑定相应处理函数,等相应的等待队列被唤醒后,其对应的函数就会被执行,这里的等待队列头部只加入我们新加的等待队列,故而等会唤醒时该函数也会执行,可以多加几个等待队列到队头再对比下,这个自己动手试试吧。 

    参考网址: 

    http://edsionte.com/techblog/archives/1854
    http://www.cnblogs.com/zhuyp1015/archive/2012/06/11/2545702.html
    http://www.cnblogs.com/zhuyp1015/archive/2012/06/09/2542882.html
    http://www.cnblogs.com/zhuyp1015/archive/2012/06/09/2542894.html
    http://linuxinme.blogspot.com/2007/06/wait-queues.html
    http://huenlil.pixnet.net/blog/post/25190567-%5B%E8%BD%89%5Dlinux-kernel—wait-queue.
    http://geeki.wordpress.com/2010/10/30/ways-of-sleeping-in-linux-kernel/
    http://gauss.ececs.uc.edu/Courses/c4029/exams/Spring2013/Review/wait_queue_driver.c
    http://blog.sina.com.cn/s/blog_6a7217e80101awe5.html

    相关文章

      网友评论

        本文标题:内核线程同步之wait_queue

        本文链接:https://www.haomeiwen.com/subject/hczlyctx.html