• SD卡中的completion实现


     Linux系统提供了一种比信号量更好的同步机制,即completion,它用于一个执行单元等待另一个执行单元执行完某事。
    Linux系统中与completion相关的操作主要有以下4种: (
    1) 定义completion struct completion my_completion; (2) 初始化completion init_completion(&my_completion); 对my_completion的定义和初始化可以通过如下快捷方式实现 DECLEARE_COMPLETION(my_completion); (3) 等待completion void wait_for_completion(struct completion *c); (4) 唤醒completion void complete(struct completion *c); void complete_all(struct completion *c); 前者只唤醒一个等待的执行单元,后者唤醒所有等待同一completion的执行单元。

    二、作用:

    虽然信号量可以用于实现同步,但往往可能会出现一些不好的结果。例如:当进程A分配了一个临时信号量变量,把它初始化为关闭的MUTEX
    并把其地址传递给进程
    B,然后在A之上调用down(),进程A打算一旦被唤醒就撤销给信号量。随后,运行在不同CPU上的进程B在同一个信号量
    上调用
    up()。然而,up()down()的目前实现还允许这两个函数在同一个信号量上并发。因此,进程A可以被唤醒并撤销临时信号量,而进程B
    还在运行up()函数。结果up()可能试图访问一个不存在的数据结构。这样就会出现错误。为了防止发生这种错误就专门设计了completion机制
    专门用于同步。信号量在多cpu的时候效果不好。

    SD卡中表现为:

    void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq)
    {
        DECLARE_COMPLETION_ONSTACK(complete);//创建一个completion结构体放在内核堆栈中,如果不加ONSTACK就是静态申请,会存放在全局变量区
        mrq->done_data = &complete;
        mrq->done = mmc_wait_done;
        mmc_start_request(host, mrq);
        wait_for_completion(&complete);
    }
    static void mmc_wait_done(struct mmc_request *mrq)
    {
        complete(mrq->done_data);
    //mrq->done_data = &complete }

    那么为什么要这样做呢?

      linux/include/linux/completion.h
      13struct completion {
      14        unsigned int done;
    //指示等待的事件是否完成。初始化时为0。如果为0,则表示等待的事件未完成。大于0表示等待的事件已经完成。
    15 wait_queue_head_t wait;
    //一个等待队列wait
    16};
    static inline void init_completion(struct completion *x)
    {
            x->done = 0;
            init_waitqueue_head(&x->wait);//初始化一个新的等待队列,把当前进程添加到等待队列中去,还要确定是信号唤醒还是
    事件唤醒,每次都创建一个新的等待队列,是不是很浪费啊? }
    linux/kernel/sched.c
    void __sched wait_for_completion(struct completion *x)
    {
            wait_for_common(x, MAX_SCHEDULE_TIMEOUT, TASK_UNINTERRUPTIBLE);
    //事件唤醒

    }
    static long __sched
    wait_for_common(struct completion *x, long timeout, int state)
    {
            might_sleep();//当前函数可以睡眠
            spin_lock_irq(&x->wait.lock);
            timeout = do_wait_for_common(x, timeout, state);//最大睡眠时间,如果提前唤醒,返回提前时间
            spin_unlock_irq(&x->wait.lock);
            return timeout;
    }
    static inline long __sched do_wait_for_common(struct completion *x, long timeout, int state)
    {
            if (!x->done) { //初始为0,成功就为1
                    DECLARE_WAITQUEUE(wait, current);//把当前进程添加到wait等待队列中,就是创建一个新的结构体放到链表中
                    __add_wait_queue_tail_exclusive(&x->wait, &wait);
                    do {
                            if (signal_pending_state(state, current)) {
                                    timeout = -ERESTARTSYS;//如果可以被信号唤醒,这是返回值,显然这里是不可以的
                                    break;
                            }
                            __set_current_state(state);//设置进程状态,这里表示只有wake_up()可以唤醒,信号不能唤醒
    //任务只有在TASK_RUNNING状态下才能被内核调度,所以唤醒后就可以被调度了
                            spin_unlock_irq(&x->wait.lock);//释放自旋锁,进程进入睡眠状态
                            timeout = schedule_timeout(timeout);//在有限的时间内调度执行其他进程,想要返回必须改变进程的状态为TASK_RUNNING
                            spin_lock_irq(&x->wait.lock);
                    } while (!x->done && timeout);//知道x->done为1,或者时间耗尽
                    __remove_wait_queue(&x->wait, &wait);//删掉等待队列中的该进程
                    if (!x->done)
                            return timeout;
            }
            x->done--;
            return timeout ?: 1;
    }

    void complete(struct completion *x)
    {
            unsigned long flags;
            spin_lock_irqsave(&x->wait.lock, flags);
            x->done++;//成功执行完,加1
            __wake_up_common(&x->wait, TASK_NORMAL, 1, 0, NULL);//唤醒等待队列中的任务,就是把某个进程设置为TASK_RUNNING状态
            spin_unlock_irqrestore(&x->wait.lock, flags);
    }
    static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,//mode表示是那个进程需要唤醒
                            int nr_exclusive, int wake_flags, void *key)//nr_exclusive需要唤醒的个数
    {
            wait_queue_t *curr, *next;

            list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
                    unsigned flags = curr->flags;

                    if (curr->func(curr, mode, wake_flags, key) &&
                                    (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
                            break;
            }
    }
    curr->func执行的函数如下:
    int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags,
                              void *key)
    {
            return try_to_wake_up(curr->private, mode, wake_flags);
    }
    static int try_to_wake_up(struct task_struct *p, unsigned int state,
                              int wake_flags)
    {
            int cpu, orig_cpu, this_cpu, success = 0;
            unsigned long flags;
            unsigned long en_flags = ENQUEUE_WAKEUP;
            struct rq *rq;
            this_cpu = get_cpu();
            smp_wmb();//跟多核还有关系
            rq = task_rq_lock(p, &flags);
            if (!(p->state & state))
                    goto out;
    
            if (p->se.on_rq)
                    goto out_running;
    
            cpu = task_cpu(p);
            orig_cpu = cpu;
    
    #ifdef CONFIG_SMP
            if (unlikely(task_running(rq, p)))
                    goto out_activate;
    
            /*
             * In order to handle concurrent wakeups and release the rq->lock
             * we put the task in TASK_WAKING state.
             *
             * First fix up the nr_uninterruptible count:
             */
            if (task_contributes_to_load(p)) {
                    if (likely(cpu_online(orig_cpu))
    rq->nr_uninterruptible--;
                    else
                            this_rq()->nr_uninterruptible--;
            }
            p->state = TASK_WAKING;
    
            if (p->sched_class->task_waking) {
                    p->sched_class->task_waking(rq, p);
                    en_flags |= ENQUEUE_WAKING;
            }
    
            cpu = select_task_rq(rq, p, SD_BALANCE_WAKE, wake_flags);
            if (cpu != orig_cpu)
                    set_task_cpu(p, cpu);
            __task_rq_unlock(rq);
    
            rq = cpu_rq(cpu);
            raw_spin_lock(&rq->lock);
    
            /*
             * We migrated the task without holding either rq->lock, however
             * since the task is not on the task list itself, nobody else
             * will try and migrate the task, hence the rq should match the
             * cpu we just moved it to.
             */
            WARN_ON(task_cpu(p) != cpu);
            WARN_ON(p->state != TASK_WAKING);
    
    #ifdef CONFIG_SCHEDSTATS
            schedstat_inc(rq, ttwu_count);
            if (cpu == this_cpu)
                    schedstat_inc(rq, ttwu_local);
            else {
                    struct sched_domain *sd;
                    for_each_domain(this_cpu, sd) {
                            if (cpumask_test_cpu(cpu, sched_domain_span(sd))) {
     schedstat_inc(sd, ttwu_wake_remote);
                                    break;
                            }
                    }
            }
    #endif /* CONFIG_SCHEDSTATS */
    
    out_activate:
    #endif /* CONFIG_SMP */
            schedstat_inc(p, se.statistics.nr_wakeups);
            if (wake_flags & WF_SYNC)
                    schedstat_inc(p, se.statistics.nr_wakeups_sync);
            if (orig_cpu != cpu)
                    schedstat_inc(p, se.statistics.nr_wakeups_migrate);
            if (cpu == this_cpu)
                    schedstat_inc(p, se.statistics.nr_wakeups_local);
            else
                    schedstat_inc(p, se.statistics.nr_wakeups_remote);
            activate_task(rq, p, en_flags);
            success = 1;
    
    out_running:
            trace_sched_wakeup(p, success);
            check_preempt_curr(rq, p, wake_flags);
    
            p->state = TASK_RUNNING;//好吧 我只是为了看到这个。。。。
    #ifdef CONFIG_SMP
            if (p->sched_class->task_woken)
                    p->sched_class->task_woken(rq, p);
    
            if (unlikely(rq->idle_stamp)) {
                    u64 delta = rq->clock - rq->idle_stamp;
                    u64 max = 2*sysctl_sched_migration_cost;
    
                    if (delta > max)
                            rq->avg_idle = max;
                    else
     update_avg(&rq->avg_idle, delta);
                    rq->idle_stamp = 0;
            }
    #endif
    out:
            task_rq_unlock(rq, &flags);
            put_cpu();
    
            return success;
    }

    唤醒进程就是把进程状态改变为TASK_RUNNING,也就是加入内核调度队列。

    现在说说为什么sd卡要用completion同步机制:

    首先,向sd卡发命令必须等待前个命令完成,就是不能同时执行多条命令,本来对于一个cpu的时候是没有问题的,因为有请求队列。对sd卡的操作是一个接着一个的,但是在多个处理器的情况下,情况就不一样了,所以为了防止出错,使用同步机制,而信号量不适合多cpu的场合,所以就选择了completion同步机制。

  • 相关阅读:
    我用微笑剪辑我的微电影 ---六月实习总结
    【Espruino】NO.17 使用平板电脑调试Espruino(OTG方式)
    级联下拉列表
    GG中obey命令的使用
    Android Studio 怎样打开两个项目?
    解决在sdk manager中更新文件后出现This Android SDK requires Android Developer Toolkit version 23.1的错误
    UVA1492
    Codeforces Round #256 (Div. 2)A-D
    C++ bool和string转换
    云计算设计模式(十六)——优先级队列模式
  • 原文地址:https://www.cnblogs.com/autum/p/completion.html
Copyright © 2020-2023  润新知