linux中的唤醒丢失问题,是同步机制中的一个经典问题。
在下面的文章中:
https://www.linuxjournal.com/article/8144
第一个问题比较好理解:
Process A:
1 spin_lock(&list_lock);
2 if(list_empty(&list_head)) {
3 spin_unlock(&list_lock);
4 set_current_state(TASK_INTERRUPTIBLE);
5 schedule();
6 spin_lock(&list_lock);
7 }
8
9 /* Rest of the code ... */
10 spin_unlock(&list_lock);
Process B:
100 spin_lock(&list_lock);
101 list_add_tail(&list_head, new_node);
102 spin_unlock(&list_lock);
103 wake_up_process(processa_task);
如果先执行完进程A的第3行,然后再执行进程B的100~103行,接着再执行进程A的第4行及之后的代码。这样进程A将进入睡眠状态,进程B前面的那次唤醒操作就丢失了。
第二个问题就没有这么好理解:
4253 /* Wait for kthread_stop */
4254 set_current_state(TASK_INTERRUPTIBLE);
4255 while (!kthread_should_stop()) {
4256 schedule();
4257 set_current_state(TASK_INTERRUPTIBLE);
4258 }
4259 __set_current_state(TASK_RUNNING);
4260 return 0;
这份代码是linux-2.6.11/kernel/sched.c,migration_thread线程里的。其他地方会置上停止标志并唤醒它,让其退出。
直接这么看代码,没有看出问题在哪里,跟唤醒丢失有什么关系。
先改写一下代码,去掉set_current_state的部分,代码变成:
4253 /* Wait for kthread_stop */
4254 while (!kthread_should_stop()) {
4255 schedule();
4256 }
4257 return 0;
乍一看,也没有问题,其他地方置上停止标志,唤醒它,等它被调度到了,它就退出了。
问题是,本意是让这个线程大部分时间睡眠,等其他地方置上停止标志,唤醒它了,它才醒来并退出。但上面这份代码,就会频繁的(只要调度到了)测试停止标志,与本意不符。
为了这个线程大部分时间都睡眠,就改一下代码:
4253 /* Wait for kthread_stop */
4254 while (!kthread_should_stop()) {
4255 set_current_state(TASK_INTERRUPTIBLE);
4256 schedule();
4257 }
4258 return 0;
这时候就看出问题了,如果这个线程执行完4254行,在执行4255行之前,其他地方置上停止标志,并尝试唤醒此线程。由于此线程虽然被调度出去了,但还是TASK_RUNNING状态,因此唤醒操作无效。然后此线程被调度进来,并把状态设置成TASK_INTERRUPTIBLE,然后再触发调度进入睡眠。至此,前面那个唤醒操作就丢失了。
如果只在一处将线程状态设置成TASK_INTERRUPTIBLE,都是有隐患的(先不考虑最后设成TASK_RUNNING的部分)。
比如,在最开始处设置:
4253 /* Wait for kthread_stop */
4254 set_current_state(TASK_INTERRUPTIBLE);
4255 while (!kthread_should_stop()) {
4256 schedule();
4257 }
4258 return 0;
如果此线程睡眠之后,被信号唤醒了,它的状态就变成TASK_RUNNING,此后就会频繁的测试停止标志了。
如果在schedule之后设置:
4253 /* Wait for kthread_stop */
4254 while (!kthread_should_stop()) {
4255 schedule();
4256 set_current_state(TASK_INTERRUPTIBLE);
4257 }
4258 return 0;
这个问题好像也不大?只是跟最开始正确的代码相比,正确的代码第一次进入schedule就睡眠了,而这里的代码,第一次进入schedule时还是TASK_RUNNING,后面还会被正常调度到,第二次进入schedule时才开始睡眠。
另外,正确的代码语义比较完整,每次测试停止标志前都设置了TASK_INTERRUPTIBLE,而这里的代码的语义相对就不完整,第一次没有设置TASK_INTERRUPTIBLE,后面才开始设置TASK_INTERRUPTIBLE。