bsdvbird 发表于 2013-04-12 19:23

在 critical_exit() 中的这段代码很奇怪,求指点。

在kern/kern_switch.c的critical_exit()有这样一段代码:      if (td->td_critnest == 1) {
                td->td_critnest = 0;
                if (td->td_owepreempt && !kdb_active) {
                        td->td_critnest = 1;
                        thread_lock(td);
                        td->td_critnest--;
                        flags = SW_INVOL | SW_PREEMPT;
                        if (TD_IS_IDLETHREAD(td))
                              flags |= SWT_IDLE;
                        else
                              flags |= SWT_OWEPREEMPT;
                              mi_switch(flags, NULL);
                              thread_unlock(td);
                }
      } else
                td->td_critnest--; 为什么不直接这样写呢?      if (td->td_critnest == 1) {
                if (td->td_owepreempt && !kdb_active) {
                        thread_lock(td);
                        td->td_critnest--;
                        flags = SW_INVOL | SW_PREEMPT;
                        if (TD_IS_IDLETHREAD(td))
                              flags |= SWT_IDLE;
                        else
                              flags |= SWT_OWEPREEMPT;
                              mi_switch(flags, NULL);
                              thread_unlock(td);
                } else
                        td->td_critnest = 0;
      } else
                td->td_critnest--; 我不理解先把td->td_critnest设置为0,然后检测到td->td_owepreempt的时候,再把td->td_critnest设置为1,并锁上td再对它减一的用意。另外这样做是否会存在副作用呢?td_critnest = 1 应该是要关闭内核抢占,检测到td->td_owepreempt后把td_critnest设置为1,应该意味着需要这个条件下是需要关闭内核抢占的,那么前一句的td->td_critness = 0开启内核抢占会不会有副作用呢?

gvim 发表于 2013-04-13 02:51

本帖最后由 gvim 于 2013-04-13 02:55 编辑

大概看了一下,仅做讨论

这里临界区是个逻辑概念,允许嵌套临界区。很多其他实现,临界区会加锁,或者屏蔽中断,真正做到串行进入。
内核在每一个 critical_exit 中检测当前线程是否主动召唤调度
直接操作 td_critnest-- 是只离开临界区而不参与调度

线程会被中断或者某些事件打断,在线程的临界区中执行也会被打断,除非实现上在临界区屏蔽中断。显然这里是没有屏蔽中断的。
中断在中断上下文中可以访问线程的 td_critnest ,这个 td_critnest 定义的地方已经说明了(k*)。
好的,那么在有两个甚至更多的地方可以访问 td_critnest也就存在竞争了。

td_critnest==0的时候,当然就被KASSERT了
td_critnest > 1 的时候,当然就走最后那句else了
所以仅当 td_critnest==1 的时候,也就是这一点才可能发送召唤调度兽。

在01句和之前被中断,那么如果中断处理句柄使用了 crit_enter/crit_exit,自然在中断完成再调度回来的时候还是1,禁止了中断上下文对本线程的抢占调度
02句表示已经脱离了临界区,可以在立即可能的下一步操作中被抢占,考虑中断之后设置了 owepreempt,并且再次进入了 crit_exit。(nest的含义)
在 td_critnest 真实被=0之后中断(注意=0操作不是原子的),当然中断处理句柄可能会设置owepreempt,或者它本来就已经设置了owepreempt。调度之后再恢复的时候owepreempt是0,就可以直接跳到后面去了。
如果owepreempt真的是1,不管是02句和03句之间中断设置的,还是本来就是,也就是想主动召唤调度。那么在thread_lock期间(=0都不是原子操作,自然thread_lock更不会原子)直接操作td_critnest 而不参与调度(同时也禁止了其他抢占调度在本线程的发生,有些锁的获取需要退出临界区)。
如果owepreempt刚刚检查了=0,就应该跳出if了,这时如果中断了并且有用 crit_enter/crit_exit,那么还是可以立即响应中断之后的抢占,因为资源已经在02句表示释放了。

官方写法逻辑清晰一些:一上来就退出临界区告诉大家现在可以被调度了,如果需要抢占那就再重新占用一会,反正和抢占调度的开销比较,一个++和一个--实在是太微不足道
你的写法,如果判断了owepreempt=0之后,设置critnest=0之前,中间被中断篡改了owepreempt的内容,那就只有等下一次crit_exit才能响应抢占了。

bsdvbird 发表于 2013-04-13 15:23

回复 2# gvim


    非常感谢您的回复哈!

我晓得了,原来一出临界区就立刻把td->td_critnest设置是为0是为了尽快让内核恢复可抢占。之后再次设置为1只是因为执行thread_lock(td)这个操作的过程中不能允许内核抢占。

今天我在kern/subr_smp.c中也看到了相关的注释:         * Specifically, if a rendezvous handler is invoked via an IPI
         * and the interrupted thread was in the critical_exit()
         * function after setting td_critnest to 0 but before
         * performing a deferred preemption, this routine can be
         * invoked with td_critnest set to 0 and td_owepreempt true.
Thanks! :D
页: [1]
查看完整版本: 在 critical_exit() 中的这段代码很奇怪,求指点。