内核开发与C++

30

据我所, 即使常见的操作系统有部分是用其他语言编写的, 内核也完全是用C语言编写的。

我想知道是否可以用C++编写内核,如果不行,会有什么缺点。


23
“来自:Linus Torvalds”……是的,很平衡…… - avakar
2
@honk 好的。那是Linus的观点,但如果现在有人开始开发内核,C++会是一个如此糟糕的选择吗?关于Linus指出的一些问题,你可以忽略STL和Boost,只需开发自己的类即可。 - coredump
4
LKML FAQ item #15.3 是我见过的最糟糕的宣传例子之一,任何高中生都应该看穿。其中使用了循环推理、草人论证以及非常巧妙的“find / wc”逻辑来证明写几个“find / sed”会带来“噩梦”。他们可能不想要或者不知道 C ++,这没关系,但他们不应该在这方面胡说八道。 - DevSolar
5
可以通读http://msdn.microsoft.com/en-us/library/windows/hardware/gg487420.aspx了解一些问题的概述。 - ta.speot.is
6
@coredump:我已经用C++开发内部内核组件多年了。有些地方需要小心谨慎,但总的来说比C语言更容易一些,因为你可以使用RAII来进行关键清理工作等类似操作(参考计数也更容易处理)。 - PlasmaHH
显示剩余4条评论
9个回答

30

有很多使用C ++实现的操作系统(或其部分)的良好示例- IOKit- MacOSX和IOS的设备驱动程序子系统是用EC ++实现的。然后还有eCOS RTOS-内核是用C ++实现的,甚至使用了模板。

传统上,操作系统中充满了在C中以艰难的方式实现的OO概念的示例。在Linux设备模型中,kobject实际上是驱动程序和设备对象的基类,完整地具备DIY虚函数表和一些通过宏实现的奇特排列用于向上和向下转换。

Windows NT内核具有更深层次的内核对象继承层次结构。对于所有反对在内核代码中使用异常处理机制的人,正是提供了这样的机制。

传统上,反对在内核代码中使用C ++的论点有:

  • 可移植性:所有目标平台都有C++编译器可用。这不再是一个问题。
  • C++语言机制的成本,例如RTTI和异常处理。显然,如果要使用它们,则标准实现不适用,并且需要使用特定于内核的变体。这通常是使用EC ++的驱动程序的原因。
  • C++ API的健壮性,特别是脆弱基类问题

毫无疑问,使用异常和RAII范例会极大地提高内核代码质量-您只需查看BSD或Linux的源代码即可看到替代方案-大量的错误处理代码使用goto实现。


嗯,不行。你可能会在C++的模板和其他部分中遇到问题,但是在内核中却没有异常的情况...因为内核存在的目的就是为了提供运行时所需的异常处理。 - J. M. Becker
1
去看一下BSD或Linux内核,数一下代码中散布的goto语句和本地分支标签的数量。这就是异常处理的情况 - 这将允许广泛应用RAII习惯用法。正如所述,WindowsNT内核具有异常机制。你可以确定的是它不是用户空间异常处理机制。 - marko

29

这一点在OSDev Wiki中有明确说明。

基本上,你要么必须为某些事情(如RTTI、异常)实现运行时支持,要么不使用它们(只留下C++的一个子集可用)。

除此之外,C++是一种更复杂的语言,因此需要更具有竞争力的开发人员才能避免出错。当然,Linus Torvalds憎恨C++纯属巧合。


3
据我所知,在许多项目中实际上只使用了C++的子集。我的意思是,C++可以像C一样编写,但是在需要时,您可以使用一些封装或其他较难在C中获得的语言特性(我的意思是即使在C中也可以使用函数指针获取某种类)。 - coredump
3
通常使用C++子集的原因是,许多试图开发自己内核的爱好者并没有深入理解C++完整运行时支持的能力。例如,异常处理是一个相当复杂的问题,编译器实现者们花费了数年时间才从效率的角度来说实现得"正确"。使用现有的实现会涉及软件许可证问题。此外,需要运行时支持的内容通常也涉及到运行时成本,如果没有必要,你不想要那些成本。 - DevSolar
@coredump:哦,还有一件事我忘了说……诸如异常处理之类的东西不是白板上实现的,而是依赖于编译器……而GCC技术文档也不好理解,哈哈。;-) - DevSolar

19
为了解决Torvalds和其他人提出的问题: 在使用C++编写的硬实时系统中,不使用STL/RTTI/exceptions,同样的原则也可以应用于更为宽松的Linux内核。有关“OOP内存模型”或“多态性开销”的其他问题基本上表明程序员从未真正检查程序的汇编级别或内存结构。 C++是高效的,并且由于经过优化的编译器,比使用查找表的C程序员更加高效,因为他手头没有虚函数。 在普通程序员手中,C++与C编写的代码相比不会增加任何额外的汇编代码。阅读过大多数C++结构和机制的汇编代码翻译后,我可以说,编译器甚至具有比C更多的优化空间,有时甚至可以创建更简洁的代码。因此,就性能而言,使用C++与C一样有效,同时仍然利用C++中的面向对象编程功能。 所以答案是这与事实无关,基本上围绕着偏见和不知道CPP创建的代码。我个人喜欢C和C ++一样,我并不介意,但没有理性反对在Linux之上或内核本身中添加面向对象的设计,这将对Linux产生很大的好处。

5
听起来像是一些狂热的、共同强化的“不信任苏格兰人”挑衅。我曾经使用过Rust、SPARK、C++和C裸机,它们各有优劣。一个严格的C++子集(考虑支持RTTI、模板、异常、std、分配器等的成本/风险)加上强制规范肯定是可行的,并且具有更好的命名空间。 - user246672
8
@TechZila 我已经阅读了汇编代码,但你不需要这样做。Scott Meyers为CPP新手提供了易于理解的帮助,关于这些主题: 1)在C中泄漏(内存泄漏)要普遍得多,因为多态性(影响内核代码)是用查找表进行处理的,指针问题在 C 的开源项目中也随处可见,直到它成熟。 2)C++永远不会自动添加额外的功能,您必须明确指定它们。 3)许多军事硬实时系统使用C++,事实上我曾在海法打造铁穹系统的地方授课有关实时C++的内容。许多医疗硬实时技术也是同样的方式。 - SeventyFive
3
4)专业知识?不要使用STL容器,不需要时不要编写“virtual”(影响较小),不要使用RTTI。完成。 5)如果您使用函数对象,可以在常见情况下超越C语言中传递函数指针的性能,因为函数对象可以在传递时进行内联优化。 6)游戏对系统的要求比Linux内核更严格,请不要看轻它们,并且在我之前评论中提到的第3条中提到的系统可靠性要求更高。 7)抱歉,在Linus作出关于CPP与C的决定时,他对C ++的了解比这个网站上的大多数专家都要少。 - SeventyFive
3
效率相当且更好(Meyers书可以协助此处),而且所需的知识要求较低(请参阅我之前评论中的4和5号项目)。可靠性(项目1)则是无与伦比的,仅因为C强制您为内核普遍多态使用查找表指针,但这只是在C中模仿C ++的一个例子,其他例子包括难以阅读的宏。我们在这里只是刚刚开始涉及这个话题。看,14年前我从6年的硬实时C转换到C++时也有同样的怀疑,现在我只在上传到C开源时使用C。不要抱有偏见,读读Scott Meyers,他是一个很好的入门作者。 - SeventyFive
9
@TechZilla:我写过很多C和C++项目。每当我不得不回到手动内存管理、返回码检查和清理过程时,我就感到非常痛苦。如果C++除了构造函数、析构函数和 <vector>之外没有任何东西可供选择,那我会选择它。在过去的15年中,我做的大部分工作都是用C++重新实现C代码(或者是由C / Java程序员编写的非常糟糕的C++代码,他们没有理解其中的区别),以使其更易于维护。我归咎于教师、在线教程和“试错”心态,而不是语言本身。 - DevSolar
显示剩余11条评论

9

您可以使用任何语言编写操作系统内核。

但是,有几个原因更倾向于使用C语言。

  • 它是一种简单的语言!没有太多的魔法。您可以很容易地推断编译器从源代码生成的机器码。
  • 它往往非常快。
  • 没有太多必需的运行时;将其移植到一个新系统所需的工作量很小。
  • 有许多优秀的编译器可用于针对许多不同的CPU和系统架构。

相比之下,C++是一种潜在的非常复杂的语言,它涉及大量的“魔法”来将您日益高级的面向对象编程(OOP)代码转化为机器码。很难理解生成的机器码,当您需要开始调试紧急情况下的内核或不稳定的设备驱动程序时,OOP抽象的复杂性会变得非常恼人......尤其是如果您必须通过用户不友好的调试端口进入目标系统进行调试。

顺便说一下,Linus并不是唯一一个对系统编程语言有强烈看法的操作系统开发者; OpenBSD的Theo de Raadt也对此发表了一些选择性的言论。


10
C语言并不简单。它有一个相当复杂、难看且容易出错的声明语法(在声明指向指针的指针时,曾经错放const或volatile吗?)。对于每位新手来说,它的类型系统和提升都是一种痛苦(有时连专家也感到如此)。它有许多未定义、未指定和实现特定的行为,这意味着更多的魔法(=惊喜)!浮点数,虽然在内核中并不经常使用,但也是另一个问题领域。相比之下,汇编语言更加直接和简单,而且更少令人惊讶。 - Alexey Frunze
15
相反,在基于C的内核中,我们看到DIY实现面向对象(OO)概念的情况——这需要重新发明大量轮子,如果一开始就使用C ++,则可以免费获得这些东西。这是一个非常真实的代码质量问题,因为它导致更复杂的代码,特别是在不太可能得到适当测试的错误路径中。 - marko
2
@Marko:确实,对于许多开发人员来说,没有他们熟悉的抽象层是很困难的,由此产生的hack通常很糟糕,因为他们不是语言开发人员。在高级应用程序中,抽象是有价值的,但在接近底层编码时则不然。一个缺乏纪律的编码人员可以在任何语言中写出垃圾代码;这并不是语言本身的问题。 - Rook
1
@Rook 《Mel的故事》提醒我们,汇编语言程序员浪费时间是因为他们无法处理机器码。如果抽象化没有价值,为什么你要强迫自己通过函数头部,而有时你可以直接跳转到函数中间节省时间呢?(是的,那是在旧时代完成的。)为什么要这么纠结于类型,当它们都是64位长的?(无论是汇编还是BLISS都不关心你是否将整数或指针放入寄存器中,而C则强制你声明并显式转换。)C从硬件中抽象出了很多东西。 - prosfilaes
1
@Raffaello 取决于系统,但在x86中,您可以将它们视为字节、字、dw和quad,而不是整数、指针和浮点数。为什么要添加这种抽象?编译器、链接器和共享对象都会添加抽象。任何语言都可以编译成机器代码,即使没有ASM片段,也经常添加,或者汇编可以链接到外部函数中。(请注意,标准C不支持内联汇编。)微软的研究人员已经用垃圾收集语言(如Singularity)编写了内核。 - prosfilaes
显示剩余7条评论

4
在C++中编写内核的可行性可以很容易地得到证实:这已经完成了。EKA2是Symbian OS的内核,它是用C++编写的。
然而,在Symbian环境中,对某些C++特性的使用有一些限制

2
虽然(ANSI)C具有“诚实”的特点,但C++也以不同的方式具有“诚实”的特点。无论应用领域如何,C++对抽象对象的语法支持都非常有价值。可用于误解缓解的工具越多,越好...而类就是这样一种工具。如果现有的某个C++编译器的某些部分与内核级别的实际情况不兼容,则可以制作一个修改后的编译器版本,并使用它。就程序员水平和代码质量而言,人们可以在C或C++中编写丑陋或崇高的代码。禁止在内核级别使用OOP会歧视那些真正擅长编写OOP的人,我认为这是不正确的。话虽如此,即使是经验丰富的程序员,我也怀念在汇编语言中编写代码的旧日子。我喜欢它们两个... C++和ASM ...只要我能使用Emacs和源代码级调试器(:-)。

1
谷歌新推出的操作系统Fuchsia是基于称为Zircon的内核,它主要使用C++编写,部分使用汇编语言[1] [2]。此外,该操作系统的其余部分也主要使用C++编写[3]。我认为现代C++给程序员提供了许多理由将其用作大型代码库的通用编程环境。它具有许多出色的功能,并且会定期添加新功能。我认为这是谷歌决定背后的主要动机。我认为C++很容易成为系统编程的未来。

1
近来一直有所发展,而确认将在操作系统在适当范围内展示出良好性能时得到。然而,目前还未达到这一点,而且时间越长,实现这一目标的可能性就越小。 - J. M. Becker

1

多年后的修订:

回顾过去,我认为最大的问题实际上在于 C++ 中大量高级特性的存在,这些特性可能被隐藏或超出程序员的控制。标准并不强制要求以任何特定方式实现事物,即使大多数实现都遵循常见的理智,但有许多充分的理由要求100%明确并完全控制操作系统内核中的实现方式。

这样做可以(只要您知道自己在做什么)减少内存占用,根据访问模式优化数据布局而不是面向对象编程范例,从而提高缓存友好性和性能,并避免可能隐藏在 C++ 的大量高级特性中的潜在错误。

请注意,即使更简单的 C 语言在某些情况下也难以预测,这也是内核代码中存在大量平台特定汇编的原因之一。


5
既然我们已经在比较C++和Java,我认为值得强调的是OOP并不等同于“所有内容都在一个多态层次结构中”。对我而言,unique_ptr与抽象工厂单例包装器bean一样是OOP的应用,甚至更加重要。 - Kerrek SB
2
这个问题是关于C++,而不是面向对象编程。C++有许多与面向对象编程无关的特性(我不同意@KerrekSB的观点)。 - Fred Foo
2
这是最初的区别,但绝不再是主要区别,而且我认为性能损失并不重要——它将替换已经在内核代码中普遍存在的调度调用。(顺便说一句,-1 不是来自我。) - Fred Foo
6
面向对象编程并不是主要的区别,而是像模板、更强的类型检查、函数重载、命名空间以及拥有成员函数的类等方面,这些可以更好地实现抽象化。这些内容并不影响性能或用于内核的适用性。 - Bo Persson
7
在这里提出的论点是,C ++ 中的多态性会带来运行时开销,但忽略了使用 C 实现的内核绝对充满自制 vtable 和多态函数调用。 - marko
显示剩余6条评论

-6
C语言的一个重要优点就是其易读性。如果你有很多代码,哪种更易读:
foo.do_something(); 

或者:

my_class_do_something(&foo); 

C版本在每次使用foo时都明确指定了foo的类型。而在C++中,背后有很多模糊的“魔法”正在发生。因此,如果你只看一小段代码,可读性会大大降低。


7
完全虚构的例子,只是为了提出一个值得怀疑的观点。另外,这两个例子中哪个是C语言,哪个是(你理解的)C++语言? - DevSolar
顺便说一句,C++ 对类型安全和类型转换的要求比 C 还要严格! - Alex D

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接