• Linux内核机制—wait唤醒机制 Hello


    一、wait唤醒步骤

    1. 定义并初始化等待队列头 wait_queue_head

    struct wait_queue_head { //include/linux/wait.h
        spinlock_t        lock;
        struct list_head    head;
    };
    typedef struct wait_queue_head wait_queue_head_t;
    
    //示例:
    static wait_queue_head_t wait_head;
    init_waitqueue_head(&wait_head); //初始化 wait_head->head 链表

    2. 定义并初始化 wait_queue_entry

    struct wait_queue_entry {  //include/linux/wait.h
        unsigned int        flags;
        void            *private;
        wait_queue_func_t    func;
        struct list_head    entry;
    };
    
    //有定义好的宏:
    #define DEFINE_WAIT_FUNC(name, function)                    \
        struct wait_queue_entry name = {                    \
            .private    = current,                    \
            .func        = function,                    \
            .entry        = LIST_HEAD_INIT((name).entry),            \
        }
    
    #define init_wait(wait)                                \
        do {                                    \
            (wait)->private = current;                    \
            (wait)->func = autoremove_wake_function;            \
            INIT_LIST_HEAD(&(wait)->entry);                    \
            (wait)->flags = 0;                        \
        } while (0)
    
    #define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)
    
    //示例:
    DEFINE_WAIT(wait);

    autoremove_wake_function() 就是实际执行唤醒本线程的回调函数,其支持的唤醒mode是通过参数传入的,也就是说由显示调用的唤醒函数决定的。如下:

    __sched int autoremove_wake_function(struct wait_queue_entry *wq_entry, unsigned int mode, int sync, void *key) //kernel/sched/wait.c
    {
        //调用默认的任务唤醒函数。
        int ret = default_wake_function(wq_entry, mode, sync, key);
    
        //上面函数成功执行了唤醒动作返回1,没有执行唤醒逻辑返回0。
        if (ret)
            //将此 wait_queue_entry 结构从等待队列头上删除,并重新init删除后的entry
            list_del_init_careful(&wq_entry->entry);
    
        return ret;
    }
    EXPORT_SYMBOL(autoremove_wake_function);
    
    int default_wake_function(wait_queue_entry_t *curr, unsigned mode, int wake_flags, void *key) //kernel/sched/core.c
    {
        WARN_ON_ONCE(IS_ENABLED(CONFIG_SCHED_DEBUG) && wake_flags & ~(WF_SYNC | WF_ANDROID_VENDOR));
        //成功执行了唤醒动作返回1,没有执行唤醒逻辑返回0
        return try_to_wake_up(curr->private, mode, wake_flags);
    }
    EXPORT_SYMBOL(default_wake_function);

    3. 准备休眠

    void prepare_to_wait(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry, int state) //kernel/sched/wait.c
    {
        unsigned long flags;
    
        wq_entry->flags &= ~WQ_FLAG_EXCLUSIVE;
        spin_lock_irqsave(&wq_head->lock, flags);
        //将wait entry挂到wait head的链表上
        if (list_empty(&wq_entry->entry))
            __add_wait_queue(wq_head, wq_entry);
        //设置当前进程为sleep或D状态
        set_current_state(state);
        spin_unlock_irqrestore(&wq_head->lock, flags);
    }
    EXPORT_SYMBOL(prepare_to_wait);
    
    //示例:
    prepare_to_wait(&wait_head, &wait, TASK_INTERRUPTIBLE);

    4. 将当前任务切走,让出CPU,进入等待状态

    //示例:
    schedule();

    5. 等待的事件发生后被唤醒

    (1) 支持的唤醒函数

    #define TASK_NORMAL (TASK_INTERRUPTIBLE | TASK_UNINTERRUPTIBLE)
    
    #define wake_up(x)            __wake_up(x, TASK_NORMAL, 1, NULL)  //唤醒等待队列头上的一个sleep或D状态的任务
    #define wake_up_nr(x, nr)        __wake_up(x, TASK_NORMAL, nr, NULL) //唤醒等待队列头上的nr个sleep或D状态的任务
    #define wake_up_all(x)            __wake_up(x, TASK_NORMAL, 0, NULL) //唤醒等待队列头上的所有的sleep或D状态的任务
    #define wake_up_locked(x)        __wake_up_locked((x), TASK_NORMAL, 1) //调用前需要提前获取到 x->lock 才行,没有加_locked的宏在函数内会持 x->lock 锁。
    #define wake_up_all_locked(x)        __wake_up_locked((x), TASK_NORMAL, 0)
    
    #define wake_up_interruptible(x)    __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL) //唤醒等待队列头上的一个sleep状态的任务
    #define wake_up_interruptible_nr(x, nr)    __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
    #define wake_up_interruptible_all(x)    __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
    #define wake_up_interruptible_sync(x)    __wake_up_sync((x), TASK_INTERRUPTIBLE) //同步唤醒等待队列头上的一个sleep状态的任务

    (2) 示例

    wake_up_interruptible(&wait_head); //非同步唤醒一个sleep状态的任务

    展开为:

    void __wake_up(struct wait_queue_head *wq_head, unsigned int mode, int nr_exclusive, void *key) //kernel/sched/wait.c
    {
        __wake_up_common_lock(wq_head, mode, nr_exclusive, 0, key);
    }
    EXPORT_SYMBOL(__wake_up);
    
    /* wake_up_interruptible(x): (x, TASK_INTERRUPTIBLE, 1, 0, NULL)*/
    static void __wake_up_common_lock(struct wait_queue_head *wq_head, unsigned int mode, int nr_exclusive, int wake_flags, void *key) //kernel/sched/wait.c
    {
        unsigned long flags;
        wait_queue_entry_t bookmark;
    
        bookmark.flags = 0;
        bookmark.private = NULL;
        bookmark.func = NULL;
        INIT_LIST_HEAD(&bookmark.entry);
    
        do {
            //注意是获取spin_lock并且关着中断调用的(注意使用对wakeup函数,防止这A-A死锁)
            spin_lock_irqsave(&wq_head->lock, flags);
            nr_exclusive = __wake_up_common(wq_head, mode, nr_exclusive, wake_flags, key, &bookmark);
            spin_unlock_irqrestore(&wq_head->lock, flags);
        } while (bookmark.flags & WQ_FLAG_BOOKMARK);
    }
    
    static int __wake_up_common(struct wait_queue_head *wq_head, unsigned int mode, int nr_exclusive, int wake_flags, void *key, wait_queue_entry_t *bookmark)
    {
        wait_queue_entry_t *curr, *next;
        int cnt = 0;
    
        lockdep_assert_held(&wq_head->lock); //进入这个函数是需要持 wq_head->lock 锁的
    
        if (bookmark && (bookmark->flags & WQ_FLAG_BOOKMARK)) {
            curr = list_next_entry(bookmark, entry);
    
            list_del(&bookmark->entry);
            bookmark->flags = 0;
        } else {
            //从链表首取下一个 wait entry
            curr = list_first_entry(&wq_head->head, wait_queue_entry_t, entry);
        }
    
        //若是个空的等待队列头就退出,返回要唤醒的线程数
        if (&curr->entry == &wq_head->head)
            return nr_exclusive;
    
        //只遍历curr以及curr以后的任务
        list_for_each_entry_safe_from(curr, next, &wq_head->head, entry) {
            //取出 wait_queue_entry 的 flag 成员
            unsigned flags = curr->flags;
            int ret;
    
            if (flags & WQ_FLAG_BOOKMARK)
                continue;
    
            //执行 wait_queue_entry 的 func 回调函数来执行真正的唤醒动作!成功执行了唤醒动作返回1,没有执行唤醒动作返回0.
            ret = curr->func(curr, mode, wake_flags, key);
            if (ret < 0)
                break;
    
            //若指定了 WQ_FLAG_EXCLUSIVE 标志,且已经唤醒的数量达到了参数nr的数量要求
            if (ret && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
                break;
    
            //单次唤醒超过64个就释放暂停一下,以免持有 rq->lock 的时间过长
            if (bookmark && (++cnt > WAITQUEUE_WALK_BREAK_CNT) && (&next->entry != &wq_head->head)) {
                //若是唤醒的数量没有达到要求,就赋上 WQ_FLAG_BOOKMARK 标志,表示下次从这个断点位置重新开始唤醒
                bookmark->flags = WQ_FLAG_BOOKMARK;
                //bookmark是个只起标记作用的dummy的 wait_queue_entry 结构
                list_add_tail(&bookmark->entry, &next->entry);
                break;
            }
        }
    
        return nr_exclusive;
    }

    示例中的线程唤醒回调函数是 autoremove_wake_function()。

    6. 结束等待

    void finish_wait(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry)
    {
        unsigned long flags;
    
        //设置当前进程状态为 running 状态
        __set_current_state(TASK_RUNNING);
    
        /*
         * 若wait条目还在等待队列头上挂着,就将其摘取下来。由前面的唤醒回调函数可知,
         * 对于实际唤醒的entry已经摘下来了,对于p->state判断不满足唤醒匹配条件的还在
         * 等待队列头上挂着,在这个时候才摘下来。
         */
        if (!list_empty_careful(&wq_entry->entry)) {
            spin_lock_irqsave(&wq_head->lock, flags);
            list_del_init(&wq_entry->entry);
            spin_unlock_irqrestore(&wq_head->lock, flags);
        }
    }
    EXPORT_SYMBOL(finish_wait);
    
    //示例:
    finish_wait(&wait_head, &wait);

    二、使用Demo举例

    1. binder驱动中的一个删减后的例子

    struct binder_thread {
        ...
        wait_queue_head_t wait;
        ...
    };
    
    //休眠等待函数
    static int binder_wait_for_work(struct binder_thread *thread, bool do_proc_work)
    {
        /* 1. 定义并初始化一个wait entry,指定实际的唤醒回调函数 */
        DEFINE_WAIT(wait);
        int ret = 0;
    
        /*
         * 冻结时跳过此任务,freeze_task()和系统休眠流程中的冻结判断有此标志位的任务
         * 就会跳过它,使用 freezer_should_skip()来判断此标志位.
         */
        freezer_do_not_count();
        for (;;) {
            /* 2. 将entry挂入等待队列头中,并将当前线程设置为INTERRUPTIBLE状态,准备休眠 */
            prepare_to_wait(&thread->wait, &wait, TASK_INTERRUPTIBLE);
            
            /* 3. 休眠循环退出条件 */
            if (binder_has_work_ilocked(thread, do_proc_work))
                break;
            
            /* 4. 将当前任务切走,让出cpu */
            schedule();
    
            /* TASK_INTERRUPTIBLE 类型的休眠需要处理是否是信号唤醒 */
            if (signal_pending(current)) {
                ret = -EINTR;
                break;
            }
        }
        /*
         * 5. 设置唤醒的任务为running状态(唤醒函数中也会设置,部分Case下重复设置了),
         *  并将p->state为不支持的唤醒类型的任务从等待队列头链表上删除。
         */
        finish_wait(&thread->wait, &wait);
    
        /* 取消冻结跳过标志位 */
        freezer_count();
    
        return ret;
    }
    
    
    //唤醒
    static void binder_wakeup_thread_ilocked(struct binder_proc *proc, struct binder_thread *thread, bool sync)
    {
        if (thread) {
            if (sync)
                wake_up_interruptible_sync(&thread->wait);
            else
                wake_up_interruptible(&thread->wait);
            return;
        }
    }

    三。总结

    1. 必须要根据wait时指定的休眠类型,来选择唤醒的类型。

    2. wake_up_xxx() 函数只是提供了一个唤醒框架,最终还是要调用到wait entry的回调函数执行实际的唤醒,回调函数中调用的 try_to_wake_up() 应该是最终归一的唤醒函数。

    3. wake_up_xxx() 函数只是提供了一个唤醒框架中,采用书签这个虚拟的wait entry机制,来限制每次最大唤醒任务数量为64个,以免持有 rq->lock 过长时间。

  • 相关阅读:
    spring boot学习01【搭建环境、创建第一个spring boot项目】
    C#窗体学生成绩管理系统
    七、整合SQL基础和PL-SQL基础
    六、异常处理概念
    五、PL/SQL循环、游标、函数和过程
    四、SQL基础知识--约束和视图
    三、Oracle常用内置函数
    二、事务
    一、SQL基础知识点补充
    前端未掌握知识点记录
  • 原文地址:https://www.cnblogs.com/hellokitty2/p/16514441.html
Copyright © 2020-2023  润新知