Linux设备驱动程序第三版学习(6)- 高级字符驱动程序操作(续1) - 进程休眠 .
来源:互联网 发布:java线程wait 编辑:程序博客网 时间:2024/05/23 18:49
第六章:高级字符驱动程序操作(续1)
以下是第2部分:掌握如何使进程休眠(并唤醒)
分为4个小的部分(都是通过分析源码的形式,必要时加以总结):
1、进程休眠的细节
2、进程唤醒的细节
3、scullpipe中read的实现
4、scullpipe中write的实现
1、 进程休眠的细节
Linux内核中最简单的休眠方式就是称为wait_event的宏(以及它的几个变种),形式如下:
01.wait_event(queue, condition) 02.wait_event_interruptible(queue, condition) 03.wait_event_timeout(queue, condition, timeout) 04.wait_event_interruptible_timeout(queue, condition, timeout)
进程调用上面某一个宏进入休眠,最常用的是wait_event_interruptible,这个宏的具体细节如下:
01.#define wait_event_interruptible(wq, condition) / 02. ({ / 03. int __ret = 0; / 04. if (!(condition)) //这里面包含了另一个宏 / 05. __wait_event_interruptible(wq, condition, __ret); / 06. __ret; / 07. })看一看__wait_event_interruptible这个宏
01.#define __wait_event_interruptible(wq, condition, ret) / 02.do { 03. //第一个步骤是建立并初始化一个等待队列入口 04. //也就是分配并初始化一个wait_queue_t结构 05. //通过调用DEFINE_WAIT宏来实现 06. // 这个宏的定义如下: 07. // #define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function) 08. // #define DEFINE_WAIT_FUNC(name, function) / 09. // 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。这个宏的具体细节如下:
01.#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL) 02. 03. //__wake_up函数定义在sched.c中 04. void __wake_up(wait_queue_head_t *q, unsigned int mode, 05. int nr_exclusive, void *key) 06. { 07. unsigned long flags; 08. 09. 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中可以看到:
- typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key);
- int default_wake_function(wait_queue_t *wait, unsigned mode, int flags, void *key)
其中default_wake_function定义在sched.c中, 如下
01.int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags, 02. void *key) 03.{ 04. return try_to_wake_up(curr->private, mode, wake_flags); 05.}
看看try_to_wake_up函数,这是唤醒进程的核心函数:
01./*** 02. * try_to_wake_up - wake up a thread 03. * @p: the to-be-woken-up thread 04. * @state: the mask of task states that can be woken 05. * @sync: do a synchronous wakeup? 06. * 07. * Put it on the run-queue if it's not already there. The "current" 08. * thread is always on the run-queue (except when the actual 09. * 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的实现(简单休眠方法)
static ssize_t scull_p_read (struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { //scull_pipe是我们定义的一个设备结构体,在open的时候保存到了file->private_data中,其中包含了: //wait_queue_head_t inq, outq; /* 读取和写入队列*/ //char *buffer, *end; /* 缓冲区的起始和结尾 */ //int buffersize; /* 用于指针计算 */ //char *rp, *wp; /* 读取和写入的位置 */ //int nreaders, nwriters; /* 用于读写打开的数量 */ //struct fasync_struct *async_queue; /* 异步读取者 */ //struct semaphore sem; /* 互斥信号量 */ //struct cdev cdev; /* 字符设备结构 */ struct scull_pipe *dev = filp->private_data; if (down_interruptible(&dev->sem)) /*获取互斥信号量,加锁*/ return -ERESTARTSYS; while (dev->rp == dev->wp) { /* 读写地址指针相同,表示没有可读数据,不能读*/ up(&dev->sem); /* 释放锁 */ if (filp->f_flags & O_NONBLOCK) //在数据没就绪时如果是非阻塞read,则马上返回 return -EAGAIN; PDEBUG("/"%s/" reading: going to sleep/n", current->comm); //如果是阻塞read,则在此处进入休眠,让出CPU //休眠时使用了wait_event_interruptible宏 if (wait_event_interruptible(dev->inq, (dev->rp != dev->wp))) //因为进程是可中断休眠的,所以可能进程接收到一个信号而被唤醒,这种唤醒 //的情况不应该继续该进程,而要让内核上层去处理事件,所以返回一个-ERESTARTSYS return -ERESTARTSYS; //signal: tell the fs layer to handle it //另外如果不是因为有信号而被唤醒,也不能确定有数据可读,所以还是要再进入while循环 //检查数据是否就绪。在进入循环前一定要再次获得信号量,不然没的释放了 if (down_interruptible(&dev->sem)) return -ERESTARTSYS; } /* ok, data is there, return something */ //虽说read函数已经传递进来了一个读取长度的参数count了,但是根据实际情况这个count可能 //会有变化的,下面的if...else根据不同情况重新确定了count的值,可以正确读取了。 if (dev->wp > dev->rp) //如果写入的位置大于读取的位置,这是比较正常的情况 //请求读取的数据不能超过写入的位置吧,还没写入怎么读呢?所以需要取两者最小值, //这个好理解。 count = min(count, (size_t)(dev->wp - dev->rp)); else /* the write pointer has wrapped, return data up to dev->end */ //如果写入指针回卷,则取count 和 读指针到文件尾这个块的最小值作为读取大小 count = min(count, (size_t)(dev->end - dev->rp)); //开始读了,使用copy_to_user if (copy_to_user(buf, dev->rp, count)) {//读取失败了,返回还需要拷贝的内存数量值 up (&dev->sem); //释放信号量 return -EFAULT; } //读取成功了,copy_to_user返回0 dev->rp += count; //移动rp到新的位置 if (dev->rp == dev->end) //如果已经到了文件尾,则回卷到头部 dev->rp = dev->buffer; /* wrapped */ up (&dev->sem); //释放信号量 /* finally, awake any writers and return */ wake_up_interruptible(&dev->outq); PDEBUG("/"%s/" did read %li bytes/n",current->comm, (long)count); return count; }
4、scullpipe中write的实现(高级休眠方法)
01.static ssize_t scull_p_write(struct file *filp, const char __user *buf, size_t count, 02. loff_t *f_pos) 03. { 04. struct scull_pipe *dev = filp->private_data; //不解释 05. int result; 06. 07. if (down_interruptible(&dev->sem)) //不解释 08. return -ERESTARTSYS; 09. 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.}
- Linux设备驱动程序第三版学习(6)- 高级字符驱动程序操作(续1) - 进程休眠
- Linux设备驱动程序第三版学习(6)- 高级字符驱动程序操作(续1)- 进程休眠 .
- Linux设备驱动程序第三版学习(6)- 高级字符驱动程序操作(续1) - 进程休眠 .
- Linux设备驱动程序第三版学习(5)- 高级字符驱动程序操作 - ioctl
- Linux设备驱动程序第三版学习(5)- 高级字符驱动程序操作 - ioctl .
- Linux设备驱动程序第三版学习(5)- 高级字符驱动程序操作 - ioctl .
- Linux设备驱动程序第三版学习(9)- 高级字符驱动程序操作(续4) - llseek定位设备
- Linux设备驱动程序第三版学习(9)- 高级字符驱动程序操作(续4) - llseek定位设备
- Linux设备驱动程序第三版学习(7)- 高级字符驱动程序操作(续2)- poll/select
- Linux设备驱动程序第三版学习(8)- 高级字符驱动程序操作(续3)- 异步通知
- Linux设备驱动程序第三版学习(7)- 高级字符驱动程序操作(续2)- poll/select .
- Linux设备驱动程序第三版学习(8)- 高级字符驱动程序操作(续3)- 异步通知 .
- Linux设备驱动程序学习(5) -高级字符驱动程序操作[(2)阻塞型I/O和休眠]
- Linux设备驱动程序学习(5)-高级字符驱动程序操作〔(2)阻塞型I/O和休眠〕
- Linux设备驱动程序学习(5)-高级字符驱动程序操作[(2)阻塞型I/O和休眠]
- Linux设备驱动程序学习(6)-高级字符驱动程序操作[(4)异步通知fasync]
- Linux设备驱动程序学习(6)-高级字符驱动程序操作-ioctl and llseek
- 高级字符驱动程序操作(Linux设备驱动程序)
- 伸长的守候
- maven configuration
- 从串口驱动的移植看linux2.6内核中的驱动模型 platform device & platform driver
- JS 光标应用
- 论文阅读和撰写方法总结
- Linux设备驱动程序第三版学习(6)- 高级字符驱动程序操作(续1) - 进程休眠 .
- 【视频课程】Android底层开发关键技术—Android系统移植与HAL框架开发
- sqlserver根据逗号截取字符
- 详解Android完完全全退出应用程序
- Mysql 网站大全
- poj 2002 Squares(枚举+点hash)
- java synchronized详解
- 支持无线充电 诺基亚全新Luna蓝牙耳机发布
- TCP连接图,保存了以后看