专栏名称: OSC开源社区
OSChina 开源中国 官方微信账号
目录
相关文章推荐
程序员小灰  ·  真的建议赶紧搞个软考证书!(红利期) ·  昨天  
待字闺中  ·  AI 编程-从 bolt.new 学习 ... ·  2 天前  
OSC开源社区  ·  深入了解Java泛型——从前世今生到PECS原则 ·  3 天前  
程序员的那些事  ·  啪啪啪!谷歌 CEO 爆猛料,立马就被员工打脸了 ·  6 天前  
程序员小灰  ·  朋友的新书,冲上了京东榜一! ·  6 天前  
51好读  ›  专栏  ›  OSC开源社区

Linux Kernel 中的 C++ 代码

OSC开源社区  · 公众号  · 程序员  · 2016-11-06 08:46

正文

#长按上图识别二维码,参与OSC源创会年终盛典#


英文:C++ in the Linux Kernel
链接:https://dzone.com/articles/c-in-the-linux-kernel
译者:Tocy, wnull, 雷神短歌

我见过很多疯狂的事,我也做过很多疯狂的事。今天我就给你们讲一个。

一个开发走进一间酒吧。他喝的非常非常醉后跟他的老板聊天。那段对话最终的结果是他接受了一个任务—— 用 C++ 写一个 Linux 内核模块。我就是那个开发,不算走进酒吧并喝醉的那部分。当我提倡做 C 的发展能取得一些成绩时,这个提议被推翻了。随后我只能满怀热情投入到任务中。



回想起来,我不会建议走这条路。但是,你也许会想用 C++ 能做一个跨平台的代码库。也许你觉得你能发现做 C++ 工程师比 C 工程师简单。也许你会觉得 Linus Torvalds’ 的观点奇怪并且过时了。在我年轻的时候我很无知,并且那些注意事项影响着我。在那个时候我不想说服每一点。也许在未来我会再看一次,但是现在,我只想重申我强烈反对用 C++ 写 Linux 内核模块。


还在看吗?假设你觉得自己与众不同,可以超越这些条条框框。让我知道后来怎么样了。无论如何,我现在分享一些我的资源和经验。也许你也能在这努力中找到成就感(一脸怀疑)。

学习Linux内核开发技术

你可以从阅读《学习通用的 Linux 内核开发》一书开始。 如果你以前没有涉及过这个部分,那么祝你好运。我不知道是否存在可以解决一切问题的银弹。我找到了一本有用的 O'Reilly 书,作为我的整个冒险过程中的参考:Jonathan Corbet,Alessandro Rubini和Greg Kroah-Hartman编著的《 Linux 设备驱动开发》。 通读这本书并加入一些邮件列表。虽然有点简洁和神秘,我还推荐“不可靠指南之破解 Linux 内核”一文(Unreliable Guide to Hacking the Linux Kernel)。 这个标题应该可以指引你进入你所努力的项目类型中。



一个必要且必要的基础知识是 Makefile 结构,虽然它看起来很不相干。同时我强烈建议阅读下关于如何使用 Linux 内核 makefile 的指南(a guide)(再来一份多个指南和我在最后一段提到的书),它是相当容易开始。 内核模块的 makefile 与应用程序 makefile 的不同之处在于内核模块 makefile 在内核构建环境中读入并运行。 相当一部分烦恼是如何获得你想要的环境到你实际生成对象的环境。

内核中的C++

我并不是将 C++ 应用到 Linux 内核的第一人,因此可以说我是站在前人的肩膀上。Pograph 的博文 Porting C++ code to Linux kernel 看起来相当不错。我发现我在过去某个版本中寻求帮助的评论如下: 

这个示例貌似不适合我。是代码太陈旧还是我做错了什么呢?

其他人在评论中说参考这个博文成功过,所以我认为我可能是错过了什么。一遍遍的重复、尝试。 现在,一个用户名为 korisk 的 GitHub 用户设置了一个代码仓库(包含更新),为基于该博客文章的 an empty C++ module scaffolding 提供源码。 不幸的是,它在我写这个时候没有明确的授权许可,但至少它是一个东西,对不对?



我在 OSDev.org 中发现了关于 C ++ 内核模块开发知识的真正宝藏。 他们有一篇关于 C ++ 问题的完整文章。 其中包括如何规避模板和异常、虚函数的处理以及内存操作符的定义。 它甚至包括大量的代码示例。 你可能会问为什么我不以此开篇呢。 好吧,我更想要你像我一样经历同样的遭遇。 实际上,像大多数内核文档一样,该页面上的信息很少或没有上下文。 这使得深入其中相当困难。该页面还主要关心自身的目标,如何构建完整的系统内核。虽然学术上很有趣,但绝大多数人只需要构建一个 hook 到现有 Linux 内核的模块即可。

陷阱

让我们来谈谈我遇到的一些陷阱。

搞清如何处理 Linux 内核头文件。我不得不将一些头文件包含在内,因为如果它不与内核交互,那运行内核模块的意义何在? Linux 内核头文件中包括一些不能与 C++ 完美配合的 C 代码。我目前主要考虑保留字冲突问题,但也包括编译知识语句以及其他棘手问题。它需要一点手动操作,以使其准备好由 C ++ 代码消费。 除了它不只需要一次。 我不能只是解决这个问题,然后继续。 虽然各种头部有一些一致性,但它们会改变。 所以,我可能需要调整每次一个新的内核版本出来。 乘以内核修订的频率和受支持的分布使用的内核的数量,并且我最终比我想要手动维护(我有其他事情要做)。   

为了供 C++ 使用,它需要一些手动调整。只是这不只需要一次。 我不能只是解决这个问题,然后转而处理其他问题。虽然各种不同的头文件拥有某些一致性,但是它们会改变。所以,当每次一个新的内核版本出来时我可能都需要重新调整。考虑到乘以内核修订的频次和受支持的发布版本使用的内核的数量,我最终要做比我手动维护数倍的工作(我有诸多其他事情要做)。

所以,我自动化了这个过程。 我使用了 CIL ,一个用于 C 语言的源码的解析器和变换器。它可以读取Linux内核头文件到一个抽象语法树中,操作该树,然后将该树输出为 C ++ 友好的格式。 CIL 内置的转换并没有覆盖我所有的需求,所以我写了一个模块,捕获所有的松散端和奇怪的边界情况。当一个新的内核出来时,我只需要通过这个转换结构处理源代码,然后在我的 C ++ 内核模块中使用它们。



我讨厌字符串. 他们看起来像很简单的概念,但最终,他们需要太多的工作。 像字符宽度那样简单的东西很快变得微妙起来。说实话,这里的大部分痛苦来自于在跨平台代码库中的工作,而不是试图使用 C ++ 做可以用 C 做的事。要考虑OS X、Windows(用户和内核空间)和Linux(用户和内核空间)上的字符宽度,这已经足够烦人了,但后来还要考虑 Unicode 和全球化,在某种程度上,这超出了我能处理的范围。孩子们,丢掉你关于字符串长度、内存分配、以及 null 终止符的假定吧。

你可能会认为你不需要了解寄存器,因为你使用的是像 C++ 这样的第三代编程语言(3GL)。那你有可能错了。我发现我的 C/C++ 编译器使用不同的调用规约。我的 C++ 代码可能包含类似下面的函数原型:



一年级计算机专业的学生会这样告诉你函数参数是下面这样入栈的。 换句话说,调用这个3GL函数导致以下汇编伪代码:



这是大多数程序员将参数传递到函数调用中的心理模型。

但是内核开发者并不是大多数程序员。内核开发人员是一种特殊和独特的品种。内核开发人员关注速度和性能。Linux 内核使用 -mregparm = 3 构建,有时称为 fastcall 。 这意味着,编译器不会执行耗时的栈操作,而只是将参数放入寄存器。

你看到的问题症结了吧。 我的 C++ 代码期望的调用规约是使用栈传递参数,而内核期望我的代码使用寄存器中传递参数。二者的矛盾是无法同时满足的,因此导致诸多未定义的行为。找到问题所在是最困难的部分;修复它只是需要添加编译参数,让编译器使用寄存器传递参数。

包装

到此,嗯?对你有好处。我想我需要回去咨询,重温这个经验。如果你发现自己不得不把 C++ 放在 Linux 内核中,但愿这里的东西可以帮助你,或者帮助你避免一些重大的麻烦。

好消息:Threat Stack 并不是用 Linux 内核模块!我们通过从用户访问的内核 API 来收集实例信息。所以,我们支持我们自己。




推荐阅读
微信开放小程序公测了!这些知识你一定要知道
最佳 Linux 发行版汇总
2016 年 7 款最佳 Java 框架

迪士尼、微软、华为、Linux 在内的7款开源区块链项目推荐

DB-Engines 发布 10 月份全球数据库排名,三甲内硝烟四起


点击“阅读原文”查看更多精彩内容