关注了就能看到更多这么棒的文章哦~

Four short stories about preempt_count()

By Jonathan Corbet
September 18, 2020
https://lwn.net/Articles/831678/
DeepL assisted translation

这个话题是从 Thomas Gleixner 的一个简单 patch 开始的,它对 preemption count(抢占计数)的处理方式做了一个小小的改动。由它引起的讨论迅速蔓延开来,在很少几个邮件中就涵盖了许多与内核开发相关的问题。其中每一个话题都值得看一下,我们从 preemption counter 本身的工作方式开始吧。有时候,一个简单的计数其实也并不像看起来那么简单。

preempt_count()

在像 Linux 这样的多任务系统中,没有任何一个线程可以保证它每次想运行的时候都能独占处理器。内核总是有能力(多数情况下)抢占一个正在运行的线程,而选择一个优先级更高的线程来执行。那个新线程可能是另一个不同的进程,但也可能是一个硬件中断,或者什么其他外部事件。为了正确地协调系统中所有任务能正确运行,内核必须跟踪当前的执行状态(execution state),包括已经被抢占或可能阻止线程被抢占的各种情况。

用来进行这个追踪记录的基础,就是在系统中每个任务里存储的 preemption counter。这个计数器是通过 preempt_count() 函数来访问的,它的通用定义是这样的:

static __always_inline int preempt_count(void)
{return READ_ONCE(current_thread_info()->preempt_count);
}

这个 counter 可以用来指示当前线程的状态、它是否可以被抢占,以及它是否被允睡眠。要实现这个功能的话,就必须在这个 counter 里面记录若干种不同状态,因此这个 preempt_count 也被分成了几个字段(sub-fields):

[preempt_count]

最低位的这个 byte 是用来记录 preempt_disable()嵌套调用的次数,也就是到目前为止 preemption 被 disable 的次数。接下来的三个字段记录了当前正在运行的线程被软中断、硬中断和 NMI(non-maskable interrupt,不可屏蔽中断)打断的次数。这里预留的 bit 所支持的最大值可能大大超过了实际执行中可能会发生的中断次数,不过反正这里 bit 也有富余。最后,最高位表示内核是否已经决定当前进程需要在后面执行时一有机会就马上被调度出去,让给其他任务。

只要看一下 preempt_count 的值,内核就可知道当前的情况如何。比如,preempt_count 是非零值,就表示当前线程不能被 scheduler 抢占:要么是 preemption 已经被明确 disable 了,要么是 CPU 当前正在处理某种中断。同理,非零值也表示当前线程不能睡眠,因为它此刻在运行的上下文必须要持续执行完成。"reschedule needed" 这个 bit 告诉内核,当前有一个优先级较高的进程应该在第一时间获得 CPU。必须要在 preempt_count 为非零值的情况下,才会设置这个 bit。否则的话,内核早就可以直接对这个进程进行 prempt 抢占,而不是设置此 bit 并等待。

内核代码到处都在使用 preempt_count 来决定在某个时刻可以进行哪些操作。不过目前看来这可能是有问题的,原因有以下几点。

Misleading counts

preempt_disable()只适用于线程在 kernel 里运行的情况。而用户空间的代码则总是可以被抢占的。在很久很久以前,内核根本不支持对内核空间代码的抢占。而当增加这个功能的时候(目的是作为改善延迟的一种可选功能),它也是可以配置来打开或者关闭的。因此,仍然有一些系统在运行时完全不需要支持 kernel preemption(内核抢占)。对于一些非常重视吞吐量的工作场景来说,这种配置也是很有道理的。

如果内核代码不能被抢占,那么就没有必要跟踪记录 preempt_disable()了。抢占是永远被关闭。所以内核不会浪费时间去维护这些信息。因此此时 preempt_count 的 preempt-disable 这几个 bit 总是为零。preemptible()函数将总是返回 false,因为内核确实是不可抢占的。这一切似乎都很合理。

不过这会导致一些问题。首先,像 in_atomic()这样的函数(代表内核当前是否在原子上下文中运行)的行为会有变化。在一个配置了 preemption 的内核上,调用 preempt_disable()会导致 in_atomic()返回 true。而如果内核配置中关闭了 preemption,那么 preempt_disable()就是一个 no-op 操作,什么都不做,in_atomic()则会返回 false。在某些情况下,例如当 spinlocks 被持有时(这种情况确实是 atomic context),in_atomic()却会由于这个原因而返回 false。

Gleixner 在他的 patch 中指出了这种不一致的现象所导致的其他一些问题,认为是一个整体问题。

缺乏一个对所有 kernel config 组合都有效的标记方式,这会一直给我们带来麻烦,因为开发人员要么不理解其中的含义,要么试图用奇怪的方式来解决这种不一致。

他的解决方案是去掉 preemption-disable 情况下的条件编译代码,这样一来,这个 counter 即使在不支持 kernel preemption 的内核中也得到了正确的维护。这样一来,在关闭了抢占的机器上会增加一些执行时间,也会增加一些额外代码,但 Gleixner 说,在他的 benchmark 中 "没有测出来可见的变化"。

Linus Torvalds 尚未被说服,他认为,当不可能进行抢占时,spinlock 的代码应该会更加简洁高效。Gleixner 重申,测量中看不出差异。Torvalds 也承认这组 patch 确实让代码变得更简单,"有非常明显的吸引力"。

Using preempt_count

不过 Torvalds 主要的抱怨是关于 preempt_count 会需要根据上下文来改变行为。他说,这样的代码 "根本上就是错误的"。他说,所有根据上下文改变其行为的代码,都应该将上下文作为一个参数传递进来,这样调用者才知道会发生什么。因此,给内存分配函数加上 GFP_ATOMIC 标志是可以接受的,但根据 in_atomic()的返回值来改变行为是不可取的。

在某种程度上,人们普遍同意这个看法。Gleixner 的 patch 邮件中包含了一段信息,介绍说未来计划对 in_atomic()等函数的调用者进行审查和修复,他说,"使用了错误用法的地方显然占绝大多数"。Daniel Vetter 补充说,根据他的经验,"试图根据运行的上下文巧妙地调整其行为的代码更难理解,而且会导致各种有趣的问题"。

Paul McKenney 则认为,有些代码必须能够在不同的上下文中正常运行,否则会导致需要太多的 API:

现在也许你喜欢在可以进行调度的上下文中使用 call_rcu()、在禁用抢占时使用 call_rcu_nosched()、在禁用中断时使用 call_rcu_irqs_are_disabled()、在持有 raw spinlock 的上下文中使用 call_rcu_raw_atomic()等等。然而,从我看到的情况来看,大多数人反而一致倾向于 RCU API 能被整合起来。

对此,Torvalds 澄清说他认为 core-kernel 的代码的要求与其他代码不同。核心代码要处理多个上下文,应该总要能正确工作。而驱动中的代码,则不应该根据上下文来改变自己的行为。

在这个讨论分支中,没有得出任何切实的结论。不过很有可能,今后这些具有上下文依赖行为的代码会得到更多的注意。

Questioning high memory

在讨论的早期阶段,Gleixner 指出了一个在加密(crypto)代码中使用 preempt_count 的值得商榷的例子。这部分代码,如果判断出当前是不可抢占的环境,那么它会以很奇怪的方式来改变内存分配模式。经过一番讨论,据 Ard Biesheuvel 说,原来主要目的是尽可能避免使用 kmap_atomic()。

对于那些对 kmap_atomic()不是很熟悉的人来说,看看这篇关于 high memory 的文章(https://lwn.net/Articles/813201/ )会有帮助。简而言之:32 位机器只能将有限的内存映射到内核的地址空间中,在大多数架构和配置中,都只映射了不超过 1GB 的空间。任何没有被直接映射的内存都被认为是 "high memory",high memory 中的任何 page 都必须在内核访问它的内容之前被显式(临时地)映射到内核中。函数 kmap()和 kmap_atomic()就是用来进行这种映射的。

这两个函数有一些区别,首先,只有 kmap_atomic() 可以在 atomic context 中调用。此外,kmap_atomic()的效率更高,因此被认为在任何可以使用它的情况下都是强烈推荐优先使用它的,无论调用者在调用前是否在 atomic context 中运行(在创建 mapping 的时候 CPU 始终都是在 atomic context 中运行的)。不过正如 Biesheuvel 所指出的,文档中并没有指出这种建议,而是鼓励使用 kmap(),所以他就是这么做的。

他补充说,kmap() 还有一个优势:调用 kmap_atomic()的话,哪怕在 64 位架构上也会禁用抢占,而 64 位架构上不存在 high memory,也就不需要临时创建 mapping。使用 kmap_atomic()将导致 WireGuard VPN 的大部分代码运行都关闭了 preemption,这是完全没有必要的。Torvalds 指出,这么做是有原因的:那些在带有 high memory 的 32 位机器上无法工作的代码,这样在 64 位机器上也就同样会报出错误了。这本质上是一种调试辅助手段,可以减少因为很少有开发者在用 32 位机器而遗漏的 bug。

Gleixner 说,在 64 位系统上优化 kmap_atomic()的方法之一,是让 kmap_atomic()可以被 preempt,换句话说,也就是不再是作为原子操作。他说,这种方法已经在 realtime kernel 中得到采用,"这并没有导致太大问题"。这里付出的代价仅仅是导致 kmap_atomic()在使用 high memory 的系统上会慢一点。

这种代价,在开发社区中似乎越来越多人愿意付出了。Torvalds 回答说,他希望能开始彻底取消 kmap()的工作。32-Bit 系统还将存在一段时间,但它们越来越不可能被用在需要大量内存的情况下。或者,正如 Torvalds 所说 "并不是 32 位系统不重要,而是 32 位系统天生不适合大内存的场景"。每当人们意识到支持 high memory 的成本(它给内存管理子系统增加了大量的复杂性)时,就会越来越强烈地希望把它拿掉。

当然,没有人会马上来删除 high memory 的代码。但那些让 high memory 系统更难用、而有利于现在使用的主流系统的改动(比如让 kmap_atomic()不再是原子的),越来越有可能被接受合并入 mainline。同时,围绕 preempt_count 的其他问题大多仍未解决,但最终很有可能这些带来正确性并降低复杂性的改动会能够取得胜利。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

LWN:关于preempt_count()的四个小讨论!相关推荐

  1. 四个小故事—体验经济:互联网生存的秘密

    转载自:http://www.programmer.com.cn/11425/ 作者以四个互联网创新的小故事诠释了做互联网产品的秘诀:多想想怎样才能让产品有趣.下面四个小故事,能帮你一窥互联网生存的小 ...

  2. 【机器学习基础】四个小项目完全解读支持向量机

    支持向量机算法的基础是最大间隔分类器,最大间隔分类器虽然很简单,但不能应用于大部分数据,因为大部分属是非线性数据,无法用线性分类器进行分类,解决方案是对特征空间进行核函数映射,然后再运行最大间隔分类器 ...

  3. C#正在被人用来做什么?--在CSDN上引发小讨论的帖子

    C#正在被人用来做什么?--在CSDN上引发小讨论的帖子 主 题:  C#正在被人用来做什么?  作 者:  manio (马牛)  等 级:    信 誉 值:  100  所属社区:  .NET技 ...

  4. MathType使用中的四个小技巧

    MathType是一种比较常见的数学公式编辑器,常常与office搭配着使用,我们在使用的时候有一些要注意的小技巧,下面我们就来给大家介绍介绍MathType使用中的四个小技巧? 技巧一:调整工具栏显 ...

  5. 卢伟冰:“四摄小金刚”的诞生是为了让更多用户享受全场景拍摄的乐趣

    8月31日消息,作为红米品牌的负责人,卢伟冰经常在微博上发表自己的个人观点,为新机预热,顺便怼一下友商.前两天,在发布了新款红米Note 8系列之后,卢伟冰今天在微博上发表自己的看法,向外界解释为何要 ...

  6. Exchange Server 2013系列四:小企业邮件系统部署

    2019独角兽企业重金招聘Python工程师标准>>> Exchange Server 2013 SP1 系列四:小企业部署邮件服务器 杜飞 Exchange 服务器功能强大,不再只 ...

  7. 四个小诀窍 告诉你雪景怎么拍才能更好看

    近几天,全国普降大雪,而且据说还很大.摄影圈有这么一句话--坏天气是摄影师的好朋友.刮风下雨也就算了,相信没有摄影爱好者下雪天不想出去拍片的吧.简单奉上一片拍摄雪景教程,四个小诀窍,告诉你雪景怎么拍才 ...

  8. 《计算机应用基础》第四次作业,[业务]计算机应用基础四次小作业

    [业务]计算机应用基础四次小作业 (8页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 9.9 积分 本科专业第一次小作业辅导:课本第39页,第52页的填空 ...

  9. 小厨房设计软件测试,案例:就是这么“任性” 四款小厨房橱柜设计

    有种东西别人家的,别人家的房子好大,别人家的厨房装修好豪华,其实拥有小户型的厨房也是可以很"任性"的,不需拘谨,不信请看,下面四款小厨房橱柜设计,美观且实用. 现代简约高效的厨房 ...

  10. 运用spss modeler运用支持向量机_四个小项目完全解读支持向量机

    支持向量机算法的基础是最大间隔分类器,最大间隔分类器虽然很简单,但不能应用于大部分数据,因为大部分属是非线性数据,无法用线性分类器进行分类,解决方案是对特征空间进行核函数映射,然后再运行最大间隔分类器 ...

最新文章

  1. 一道有意思的阶乘计算题
  2. 三维植物树木模型 Maxtree – Plant Models Vol 74
  3. 微服务网关从零搭建——(七)更改存储方式为oracle
  4. DevExpress z
  5. linux 压缩解压打包
  6. Redis命令:INCR key加1
  7. Weblogic服务端请求伪造漏洞(SSRF)和反射型跨站请求伪造漏洞(CSS)修复教程...
  8. JVM学习笔记之-执行引擎(Execution Engine)
  9. 喜报!985大学首次登上Nature封面,这所学校可太不容易了!
  10. office数据集dslr_DSLR的完整形式是什么?
  11. java数组数据结构_Java数据结构之数组
  12. 渐变,类Flash的菜单
  13. 字符串与整数、浮点数、无符号整数之间的转换常用函数
  14. java运行matlab代码
  15. 辗转相除法求最大公因数
  16. 学习笔记之MOOC《计算机程序设计C++》第5周编程作业
  17. SCU - 4572 醉后不知天在水,满船清梦压星河【思维】
  18. Android Java 网络 OS等笔试题 -- 全
  19. 【苹果推iMessage】软件安装通过ApplseScript节制iMessage客户端
  20. UTC时间与北京时间的关系

热门文章

  1. 一文说清楚什么是时区,夏令时,GMT和CST
  2. linux中inotify+unison实现数据双向实时同步
  3. MySQL 中 TIMESTAMP 类型返回日期时间数据中带有 T
  4. windows下载安装ElasticSearch
  5. Xftp5 安装教程
  6. 新建的module没有蓝色小块
  7. springcloud5-服务网关zuul及gateway
  8. 【C++]参数的缺省值
  9. java happen-before_Java happen-before规则
  10. apue第三版P106:ftw程序中使用的path_alloc