免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1486 | 回复: 2
打印 上一主题 下一主题

[FreeBSD] 在 critical_exit() 中的这段代码很奇怪,求指点。 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2013-04-12 19:23 |只看该作者 |倒序浏览
在kern/kern_switch.c的critical_exit()有这样一段代码:
  1.         if (td->td_critnest == 1) {
  2.                 td->td_critnest = 0;
  3.                 if (td->td_owepreempt && !kdb_active) {
  4.                         td->td_critnest = 1;
  5.                         thread_lock(td);
  6.                         td->td_critnest--;
  7.                         flags = SW_INVOL | SW_PREEMPT;
  8.                         if (TD_IS_IDLETHREAD(td))
  9.                                 flags |= SWT_IDLE;
  10.                         else
  11.                                 flags |= SWT_OWEPREEMPT;
  12.                                 mi_switch(flags, NULL);
  13.                                 thread_unlock(td);
  14.                 }
  15.         } else
  16.                 td->td_critnest--;
复制代码
为什么不直接这样写呢?
  1.         if (td->td_critnest == 1) {
  2.                 if (td->td_owepreempt && !kdb_active) {
  3.                         thread_lock(td);
  4.                         td->td_critnest--;
  5.                         flags = SW_INVOL | SW_PREEMPT;
  6.                         if (TD_IS_IDLETHREAD(td))
  7.                                 flags |= SWT_IDLE;
  8.                         else
  9.                                 flags |= SWT_OWEPREEMPT;
  10.                                 mi_switch(flags, NULL);
  11.                                 thread_unlock(td);
  12.                 } else
  13.                         td->td_critnest = 0;
  14.         } else
  15.                 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开启内核抢占会不会有副作用呢?

论坛徽章:
2
亥猪
日期:2014-03-19 16:36:35午马
日期:2014-11-23 23:48:46
2 [报告]
发表于 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才能响应抢占了。

论坛徽章:
0
3 [报告]
发表于 2013-04-13 15:23 |只看该作者
回复 2# gvim


    非常感谢您的回复哈!

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

今天我在kern/subr_smp.c中也看到了相关的注释:
  1.          * Specifically, if a rendezvous handler is invoked via an IPI
  2.          * and the interrupted thread was in the critical_exit()
  3.          * function after setting td_critnest to 0 but before
  4.          * performing a deferred preemption, this routine can be
  5.          * invoked with td_critnest set to 0 and td_owepreempt true.
复制代码
Thanks!
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP