Linux设备驱动程序第三版学习(6)- 高级字符驱动程序操作(续1)- 进程休眠 .

 

第六章:高级字符驱动程序操作(续1)
以下是第2部分:掌握如何使进程休眠(并唤醒)
分为4个小的部分(都是通过分析源码的形式,必要时加以总结):
1、进程休眠的细节
2、进程唤醒的细节
3、scullpipe中read的实现
4、scullpipe中write的实现

1、 进程休眠的细节
    Linux内核中最简单的休眠方式就是称为wait_event的宏(以及它的几个变种),形式如下:

  1. wait_event(queue, condition)  
  2. wait_event_interruptible(queue, condition)  
  3. wait_event_timeout(queue, condition, timeout)  
  4. wait_event_interruptible_timeout(queue, condition, timeout)  


    进程调用上面某一个宏进入休眠,最常用的是wait_event_interruptible,这个宏的具体细节如下:

  1. #define wait_event_interruptible(wq, condition)                /   
  2.     ({                                    /  
  3.         int __ret = 0;                            /  
  4.         if (!(condition)) //这里面包含了另一个宏                /   
  5.             __wait_event_interruptible(wq, condition, __ret);    /  
  6.         __ret;                                /  
  7.     })  
    

    看一看__wait_event_interruptible这个宏

  1. #define __wait_event_interruptible(wq, condition, ret)            /   
  2. do {              
  3.     //第一个步骤是建立并初始化一个等待队列入口   
  4.     //也就是分配并初始化一个wait_queue_t结构   
  5.     //通过调用DEFINE_WAIT宏来实现   
  6.     // 这个宏的定义如下:   
  7.     // #define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)   
  8.     // #define DEFINE_WAIT_FUNC(name, function)                /   
  9.     //    wait_queue_t name = {                        /   
  10.     //        .private    = current,                /   
  11.     //        .func        = function,                /   
  12.     //        .task_list    = LIST_HEAD_INIT((name).task_list),    /   
  13.     //    }    
  14.   
  15.     DEFINE_WAIT(__wait);    //建立并初始化了一个名为__wait的等待队列入口   
  16.       
  17.     //第二个步骤是将等待队列入口添加到队列中,并设置进程状态    
  18.     for (;;) {  
  19.         // 调用prepare_to_wait函数,可以在wait.c中看到定义。此函数的功能是:   
  20.         // 1. 将等待队列入口添加到队列中。这步通过__add_wait_queue完成   
  21.         // 2. 设置进程状态为TASK_INTERRUPTIBLE。 这步通过set_current_state(state)完成    
  22.         prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);    /  
  23.   
  24.         //在进行上面的操作是条件可能变化了,所以这里还要再判断一次    
  25.         if (condition)                        /  
  26.             break;                        /  
  27.   
  28.         if (!signal_pending(current)) {                /  
  29.             //调用schedule函数,对于这个进程调度函数我没有研究。大概的理解是进程在这里让出了CPU,用某一个进程替换了当前的进程。    
  30.             schedule();                    /  
  31.             continue;                    /  
  32.         }                            /  
  33.           
  34.         //一旦schedule返回,则退出for循环    
  35.         ret = -ERESTARTSYS;                    /  
  36.         break;                            /  
  37.     }                                /  
  38.   
  39.     //接下来进行清理工作。调用finish_wait函数,可以在wait.c中看到定义。该函数的作用和前面的   
  40.     //prepare_to_wait相反。   
  41.     // 1.设置进程状态为TASK_RUNNING   
  42.     // 2.将__wait从等待队列中移除。该步调用了list_del_init函数            
  43.     finish_wait(&wq, &__wait);                    /  
  44. while (0)  

总之,调用了wait_event或其变种,则进程进入休眠。

2. 进程唤醒的细节
    与休眠细节相似,唤醒是通过调用wake_up宏来实现的,最常用的变种是wake_up_interruptible。这个宏的具体细节如下:

  1. #define wake_up_interruptible(x)    __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)   
  2.   
  3.     //__wake_up函数定义在sched.c中    
  4.     void __wake_up(wait_queue_head_t *q, unsigned int mode,  
  5.                 int nr_exclusive, void *key)  
  6.     {  
  7.         unsigned long flags;  
  8.   
  9.         spin_lock_irqsave(&q->lock, flags); //自旋锁    
  10.         __wake_up_common(q, mode, nr_exclusive, 0, key); //wakeup函数的核心,定义在下边    
  11.         spin_unlock_irqrestore(&q->lock, flags); //解锁    
  12.     }  
  13.   
  14.     static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,  
  15.                 int nr_exclusive, int wake_flags, void *key)  
  16.     {  
  17.         wait_queue_t *curr, *next;  
  18.   
  19.         //下面调用了一个宏list_for_each_entry_safe   
  20.         //关于这个宏可以参考转载的”关于linux内核中等待队列数据结构之思考“一文,感谢wangchaoxjtuse   
  21.         //这个宏展开是一个for循环,功能就是遍历这个链表,把curr逐一指向链表中的每个项   
  22.          //对于每个链表项,都调用该结构中的 wait_queue_func_t func函数来尝试唤醒该项进程   
  23.          //关于func的细节见下面的源码分析    
  24.         list_for_each_entry_safe(curr, next, &q->task_list, task_list) {  
  25.             unsigned flags = curr->flags;  
  26.   
  27.             if (curr->func(curr, mode, wake_flags, key) &&  
  28.                     (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)  
  29.                 break;  
  30.         }  
  31.     }  
 
    ================分析wait_queue_func_t的源码================
    在wait.h中可以看到:
 
  1. typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key);  
  2. int default_wake_function(wait_queue_t *wait, unsigned mode, int flags, void *key)  
     
    其中default_wake_function定义在sched.c中, 如下
  1. int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags,  
  2.           void *key)  
  3. {  
  4.     return try_to_wake_up(curr->private, mode, wake_flags);  
  5. }  
 
    看看try_to_wake_up函数,这是唤醒进程的核心函数:
  1. /*** 
  2.      * try_to_wake_up - wake up a thread 
  3.      * @p: the to-be-woken-up thread 
  4.      * @state: the mask of task states that can be woken 
  5.      * @sync: do a synchronous wakeup? 
  6.      * 
  7.      * Put it on the run-queue if it's not already there. The "current" 
  8.      * thread is always on the run-queue (except when the actual 
  9.      * re-schedule is in progress), and as such you're allowed to do 
  10.      * the simpler "current->state = TASK_RUNNING" to mark yourself 
  11.      * runnable without the overhead of this. 
  12.      * 
  13.      * returns failure only if the task is already active. 
  14.      */   
  15.     static int try_to_wake_up(struct task_struct *p, unsigned int state,  
  16.                   int wake_flags)  
  17.     {  
  18.         int cpu, orig_cpu, this_cpu, success = 0;  
  19.         unsigned long flags;  
  20.         struct rq *rq, *orig_rq;  
  21.   
  22.         // 关于下面的两行代码,需要知道:   
  23.         // #define sched_feat(x) (sysctl_sched_features & (1UL << __SCHED_FEAT_##x))   
  24.         // 对于如何取得__SCHED_FEAT_##x, 参考本博客的一篇:“关于宏的一个应用”。    
  25.         if (!sched_feat(SYNC_WAKEUPS))  
  26.             wake_flags &= ~WF_SYNC;  
  27.   
  28.         //get_cpu函数获得对当前处理器的引用并且返回处理器的ID    
  29.         this_cpu = get_cpu();  
  30.   
  31.         //下个语句是多处理器的写内存屏障。   
  32.         //读写屏障像一堵墙,所有在设置读写屏障之前发起的内存访问,必须先于在设置屏障之后发起的内存访问   
  33.         //之前完成,确保内存访问按程序的顺序完成。详情参照本博客转载的一篇:“优化屏障和内存屏障”。   
  34.         //相关的屏障还有:   
  35.         // mb()    适用于多处理器和单处理器的内存屏障   
  36.         // rmb()    适用于多处理器和单处理器的读内存屏障   
  37.         // wmb()    适用于多处理器和单处理器的写内存那屏障   
  38.         // smp_mb()    适用于多处理器的内存屏障   
  39.         // smp_rmb()    适用于多处理器的读内存屏障   
  40.         // smp_wmb()    适用于多处理器的写内存屏障    
  41.         smp_wmb();  
  42.   
  43.         //对可执行队列操作前,应该先锁住它   
  44.         //上锁和解锁函数原型是:   
  45.         // struct rq *task_rq_lock(struct task_struct *p, unsigned long *flags)   
  46.         // void task_rq_unlock(struct rq *rq, unsigned long *flags)    
  47.         rq = orig_rq = task_rq_lock(p, &flags);  
  48.   
  49.         //刷新队列时钟    
  50.         update_rq_clock(rq);  
  51.   
  52.         //如果当前进程的状态不是要唤醒的进程状态,则不唤醒本进程。直接跳到out处,解锁并返回对当前处理器   
  53.         //的引用    
  54.         if (!(p->state & state))  
  55.             goto out;  
  56.         //如果当前进程就在运行队列(runqueue)中,则无需唤醒本进程。直接跳转到out_running处。    
  57.         if (p->se.on_rq)  
  58.             goto out_running;  
  59.   
  60.         //下面两句返回当前进程p所使用的CPU编号,并把编号保存到orig_cpu中    
  61.         cpu = task_cpu(p);  
  62.         orig_cpu = cpu;  
  63.   
  64.     #ifdef CONFIG_SMP //如果是多CPU的情况    
  65.         //task_running定义在sched.c中,return task_current(rq, p);   
  66.         //task_current也是定义在sched.c中,return rq->curr == p;    
  67.         if (unlikely(task_running(rq, p)))  
  68.             goto out_activate;  
  69.   
  70.         /* 
  71.          * In order to handle concurrent wakeups and release the rq->lock 
  72.          * we put the task in TASK_WAKING state. 
  73.          * 
  74.          * First fix up the nr_uninterruptible count: 
  75.          */  
  76.         // 下面宏定义task_contributes_to_load在linux/sched.h中,如下:   
  77.         // #define task_contributes_to_load(task)    /   
  78.         //        ((task->state & TASK_UNINTERRUPTIBLE) != 0 && /   
  79.         //         (task->flags & PF_FREEZING) == 0)   
  80.         // 判断两个条件:1.任务状态是否是TASK_UNINTERRUPTIBLE 2.标记为是否是PF_FREEZING    
  81.         if (task_contributes_to_load(p)) {  
  82.             if (likely(cpu_online(orig_cpu))) /*检测cpu是否在线,Some places use cpu_online() where they should be using cpu_possible,most commonly  for tallying statistics*/   
  83.                 rq->nr_uninterruptible--; /*nr_uninterruptible记录了该CPU不可中断状 态进程的个数,这里把它减1*/   
  84.             else  
  85.                 this_rq()->nr_uninterruptible--; //this_rq取得当前CPU的运行队列    
  86.         }  
  87.         p->state = TASK_WAKING; //设置进程状态为TASK_WAKING    
  88.   
  89.         if (p->sched_class->task_waking)  
  90.             p->sched_class->task_waking(rq, p); /*调用当前进程调度类的task_waking函数 ,进行唤醒操作 */  
  91.   
  92.         cpu = select_task_rq(rq, p, SD_BALANCE_WAKE, wake_flags);  
  93.         if (cpu != orig_cpu)  
  94.             set_task_cpu(p, cpu);  
  95.         __task_rq_unlock(rq);  
  96.   
  97.         rq = cpu_rq(cpu);  
  98.         spin_lock(&rq->lock);  
  99.         update_rq_clock(rq);  
  100.   
  101.         /* 
  102.          * We migrated the task without holding either rq->lock, however 
  103.          * since the task is not on the task list itself, nobody else 
  104.          * will try and migrate the task, hence the rq should match the 
  105.          * cpu we just moved it to. 
  106.          */   
  107.         WARN_ON(task_cpu(p) != cpu);  
  108.         WARN_ON(p->state != TASK_WAKING);  
  109.   
  110.     #ifdef CONFIG_SCHEDSTATS //对于需要收集调度器状态的情况    
  111.         schedstat_inc(rq, ttwu_count);  
  112.         if (cpu == this_cpu)  
  113.             schedstat_inc(rq, ttwu_local);  
  114.         else {  
  115.             struct sched_domain *sd;  
  116.             for_each_domain(this_cpu, sd) {  
  117.                 if (cpumask_test_cpu(cpu, sched_domain_span(sd))) {  
  118.                     schedstat_inc(sd, ttwu_wake_remote);  
  119.                     break;  
  120.                 }  
  121.             }  
  122.         }  
  123.     #endif /* CONFIG_SCHEDSTATS */    
  124.   
  125.     out_activate:  
  126.     #endif /* CONFIG_SMP */    
  127.         schedstat_inc(p, se.nr_wakeups);  
  128.         if (wake_flags & WF_SYNC)  
  129.             schedstat_inc(p, se.nr_wakeups_sync);  
  130.         if (orig_cpu != cpu)  
  131.             schedstat_inc(p, se.nr_wakeups_migrate);  
  132.         if (cpu == this_cpu)  
  133.             schedstat_inc(p, se.nr_wakeups_local);  
  134.         else  
  135.             schedstat_inc(p, se.nr_wakeups_remote);  
  136.         activate_task(rq, p, 1);  
  137.         success = 1;  
  138.   
  139.         /* 
  140.          * Only attribute actual wakeups done by this task. 
  141.          */   
  142.         if (!in_interrupt()) {  
  143.             struct sched_entity *se = ¤t->se;  
  144.             u64 sample = se->sum_exec_runtime;  
  145.   
  146.             if (se->last_wakeup)  
  147.                 sample -= se->last_wakeup;  
  148.             else  
  149.                 sample -= se->start_runtime;  
  150.             update_avg(&se->avg_wakeup, sample);  
  151.   
  152.             se->last_wakeup = se->sum_exec_runtime;  
  153.         }  
  154.   
  155.     out_running:  
  156.         //下面这两句是干什么用的我也不清楚,请高手指教。多谢多谢!!!    
  157.         trace_sched_wakeup(rq, p, success);  
  158.         check_preempt_curr(rq, p, wake_flags);  
  159.   
  160.         //设置当前进程状态    
  161.         p->state = TASK_RUNNING;  
  162.     #ifdef CONFIG_SMP   
  163.         if (p->sched_class->task_woken)  
  164.             p->sched_class->task_woken(rq, p);  
  165.   
  166.         if (unlikely(rq->idle_stamp)) {  
  167.             u64 delta = rq->clock - rq->idle_stamp;  
  168.             u64 max = 2*sysctl_sched_migration_cost;  
  169.   
  170.             if (delta > max)  
  171.                 rq->avg_idle = max;  
  172.             else  
  173.                 update_avg(&rq->avg_idle, delta);  
  174.             rq->idle_stamp = 0;  
  175.         }  
  176.     #endif   
  177.     out:  
  178.         //解锁    
  179.         task_rq_unlock(rq, &flags);  
  180.         //返回对当前处理器的引用    
  181.         put_cpu();  
  182.   
  183.         return success;  
  184.     }  
    
3、scullpipe中read的实现(简单休眠方法)
  1. static ssize_t scull_p_read (struct file *filp, char __user *buf, size_t count,  
  2.                 loff_t *f_pos)  
  3.     {  
  4.         //scull_pipe是我们定义的一个设备结构体,在open的时候保存到了file->private_data中,其中包含了:   
  5.          //wait_queue_head_t inq, outq;       /* 读取和写入队列*/   
  6.         //char *buffer, *end;                /* 缓冲区的起始和结尾 */   
  7.         //int buffersize;                    /* 用于指针计算 */   
  8.         //char *rp, *wp;                     /* 读取和写入的位置 */   
  9.         //int nreaders, nwriters;            /* 用于读写打开的数量 */   
  10.         //struct fasync_struct *async_queue; /* 异步读取者 */   
  11.         //struct semaphore sem;              /* 互斥信号量 */   
  12.         //struct cdev cdev;                  /* 字符设备结构 */    
  13.         struct scull_pipe *dev = filp->private_data;  
  14.   
  15.         if (down_interruptible(&dev->sem)) /*获取互斥信号量,加锁*/   
  16.             return -ERESTARTSYS;  
  17.   
  18.         while (dev->rp == dev->wp) { /* 读写地址指针相同,表示没有可读数据,不能读*/   
  19.             up(&dev->sem); /* 释放锁 */   
  20.             if (filp->f_flags & O_NONBLOCK) //在数据没就绪时如果是非阻塞read,则马上返回    
  21.                 return -EAGAIN;  
  22.             PDEBUG("/"%s/" reading: going to sleep/n", current->comm);  
  23.             //如果是阻塞read,则在此处进入休眠,让出CPU   
  24.             //休眠时使用了wait_event_interruptible宏    
  25.             if (wait_event_interruptible(dev->inq, (dev->rp != dev->wp)))  
  26.               //因为进程是可中断休眠的,所以可能进程接收到一个信号而被唤醒,这种唤醒   
  27.                 //的情况不应该继续该进程,而要让内核上层去处理事件,所以返回一个-ERESTARTSYS    
  28.                 return -ERESTARTSYS; //signal: tell the fs layer to handle it    
  29.               //另外如果不是因为有信号而被唤醒,也不能确定有数据可读,所以还是要再进入while循环   
  30.                 //检查数据是否就绪。在进入循环前一定要再次获得信号量,不然没的释放了    
  31.             if (down_interruptible(&dev->sem))  
  32.                 return -ERESTARTSYS;  
  33.         }  
  34.         /* ok, data is there, return something */  
  35.        //虽说read函数已经传递进来了一个读取长度的参数count了,但是根据实际情况这个count可能   
  36.         //会有变化的,下面的if...else根据不同情况重新确定了count的值,可以正确读取了。    
  37.         if (dev->wp > dev->rp) //如果写入的位置大于读取的位置,这是比较正常的情况   
  38.             //请求读取的数据不能超过写入的位置吧,还没写入怎么读呢?所以需要取两者最小值,   
  39.             //这个好理解。    
  40.             count = min(count, (size_t)(dev->wp - dev->rp));  
  41.         else /* the write pointer has wrapped, return data up to dev->end */   
  42.             //如果写入指针回卷,则取count 和 读指针到文件尾这个块的最小值作为读取大小    
  43.             count = min(count, (size_t)(dev->end - dev->rp));  
  44.   
  45.         //开始读了,使用copy_to_user    
  46.         if (copy_to_user(buf, dev->rp, count)) {//读取失败了,返回还需要拷贝的内存数量值    
  47.             up (&dev->sem); //释放信号量    
  48.             return -EFAULT;  
  49.         }  
  50.         //读取成功了,copy_to_user返回0    
  51.         dev->rp += count; //移动rp到新的位置    
  52.         if (dev->rp == dev->end) //如果已经到了文件尾,则回卷到头部    
  53.             dev->rp = dev->buffer; /* wrapped */   
  54.         up (&dev->sem); //释放信号量    
  55.   
  56.         /* finally, awake any writers and return */   
  57.         wake_up_interruptible(&dev->outq);  
  58.         PDEBUG("/"%s/" did read %li bytes/n",current->comm, (long)count);  
  59.         return count;  
  60.     }  

4、scullpipe中write的实现(高级休眠方法)
  1. static ssize_t scull_p_write(struct file *filp, const char __user *buf, size_t count,  
  2.                 loff_t *f_pos)  
  3.     {  
  4.         struct scull_pipe *dev = filp->private_data; //不解释    
  5.          int result;  
  6.   
  7.         if (down_interruptible(&dev->sem)) //不解释    
  8.             return -ERESTARTSYS;  
  9.   
  10.         /* Make sure there's space to write */   
  11.         result = scull_getwritespace(dev, filp); /*休眠代码在这个函数中,在下面单独学习. 总之这里确保新数据有可用的缓冲区空间并且在必要时休眠。 */  
  12.         if (result) //result不是0表明没有可用的空间,直接返回-EAGAIN或-ERESTARTSYS    
  13.             return result; /* scull_getwritespace called up(&dev->sem) */   
  14.   
  15.         /* ok, space is there, accept something */  
  16.         //下面的就简单了,不解释    
  17.         count = min(count, (size_t)spacefree(dev));  
  18.         if (dev->wp >= dev->rp)  
  19.             count = min(count, (size_t)(dev->end - dev->wp)); /* to end-of-buf */  
  20.         else /* the write pointer has wrapped, fill up to rp-1 */  
  21.             count = min(count, (size_t)(dev->rp - dev->wp - 1));  
  22.         PDEBUG("Going to accept %li bytes to %p from %p/n", (long)count, dev->wp, buf);  
  23.         if (copy_from_user(dev->wp, buf, count)) {  
  24.             up (&dev->sem);  
  25.             return -EFAULT;  
  26.         }  
  27.         dev->wp += count;  
  28.         if (dev->wp == dev->end)  
  29.             dev->wp = dev->buffer; /* wrapped */  
  30.         up(&dev->sem);  
  31.   
  32.         /* finally, awake any reader */  
  33.         wake_up_interruptible(&dev->inq);  /* blocked in read() and select() */  
  34.   
  35.         /* and signal asynchronous readers, explained late in chapter 5 */  
  36.         if (dev->async_queue)  
  37.             kill_fasync(&dev->async_queue, SIGIO, POLL_IN);  
  38.         PDEBUG("/"%s/" did write %li bytes/n",current->comm, (long)count);  
  39.         return count;  
  40.     }  
  41.       
  42. 下面学习以下scull_getwritespace函数,下面英文注释基本上已经清楚了,再细看一下内部实现。  
  43. /* Wait for space for writing; caller must hold device semaphore.  On 
  44.  * error the semaphore will be released before returning. */   
  45. static int scull_getwritespace(struct scull_pipe *dev, struct file *filp)  
  46. {  
  47.     //spacefree是这个样子的,返回空缓冲区的大小   
  48.     //static int spacefree(struct scull_pipe *dev)   
  49.     //{   
  50.     //    if (dev->rp == dev->wp)   
  51.     //        return dev->buffersize - 1;   
  52.     //    return ((dev->rp + dev->buffersize - dev->wp) % dev->buffersize) - 1;   
  53.     //}   
  54.     //如果缓冲区还有可用的地方,则不进入while循环,直接返回0;如果没有,则进入while循环,进行休眠    
  55.     while (spacefree(dev) == 0) { /* full */  
  56.         DEFINE_WAIT(wait); //建立并初始化一个等待队列入口    
  57.       
  58.         up(&dev->sem); //休眠前必须释放信号量,必须必须!!!    
  59.         if (filp->f_flags & O_NONBLOCK) //如果是非阻塞写入,则不休眠直接返回    
  60.             return -EAGAIN;  
  61.         PDEBUG("/"%s/" writing: going to sleep/n",current->comm);  
  62.   
  63.         //prepare_to_wait将等待队列入口添加到队列中,并设置进程状态    
  64.         prepare_to_wait(&dev->outq, &wait, TASK_INTERRUPTIBLE);  
  65.   
  66.         if (spacefree(dev) == 0) //如果还是没有可用空间,则调用schedule,让出CPU,进入休眠   
  67.                                                    //这里必须再做一次检查,否则有可能失去唯一被唤醒的机会    
  68.             schedule();  
  69.         finish_wait(&dev->outq, &wait); //一旦schedule返回,则清理等待队列,设置进程状态    
  70.         if (signal_pending(current)) //如果是中断信号唤醒的,则还是交给上层fs处理    
  71.             return -ERESTARTSYS; /* signal: tell the fs layer to handle it */   
  72.         if (down_interruptible(&dev->sem))  /*如果不是中断信号唤醒的,则再次进入while测试一下可用 的空闲空间,之前要再次获得信号量 */  
  73.             return -ERESTARTSYS;  
  74.     }  
  75.     return 0;  
  76. }    

 

 

原文出处:http://blog.csdn.net/ypoflyer/article/details/6126934

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值