为什么驱动程序和固件几乎总是用C或ASM而不是C ++编写的?

我只是好奇,为什么驱动程序和固件几乎总是用C或汇编语言写的,而不是C ++?

我听说有这个技术的原因。

有谁知道这个?

很多的爱,路易丝

因为在大多数情况下,操作系统(或“运行时库”)提供了C ++所需的stdlibfunction。

在C和ASM中,您可以创build裸露的可执行文件,其中不包含外部依赖项。

但是,由于Windows不支持C ++ stdlib,因此大多数Windows驱动程序都是用C ++编写的(有限的子集)。

另外,当固件写入ASM时,通常是因为(A)它正在执行的平台没有C ++编译器,或者(B)有极端的速度或大小限制。

请注意,(B)自2000年初以来一般不是问题。

内核中的代码运行在与用户空间完全不同的环境中。 没有stream程分离,所以错误难以复原。 例外几乎是不可能的。 有不同的内存分配器,所以在内核上下文中newdelete可能会比较困难。 可用的标准库比较less,使得像C ++这样的语言更加难以有效地使用。

Windows允许在内核驱动程序中使用非常有限的C ++ 子集 ; 基本上是那些可以平凡地翻译成C的东西,比如在块的开始之外的地方的variables声明。 他们build议不要使用newdelete ,并且不支持RTTI或大多数C ++标准库。

Mac OS X使用I / O Kit ,它是一个基于C ++有限子集的框架,尽pipe我可以告诉它比在Windows上允许的更完整。 它基本上是C ++,没有例外和RTTI。

大多数类Unix操作系统(Linux,BSD)都是用C语言编写的,我认为从来没有人真正看到将C ++支持添加到内核中的好处,因为内核中的C ++通常非常有限。

1)“因为它总是这样” – 这实际上解释了比你想象的更多的东西 – 考虑到几乎所有当前系统上的API最初被写入基于C或ASM的模型,并且假定存在大量先前的代码C和ASM,那么“顺其自然”往往比想出如何利用C ++更容易。

2)环境 – 为了使用所有的C ++特性,你需要一个相当的运行时环境,其中一些只是一个痛苦的提供给驱动程序。 如果限制了你的特性,那么做起来容易,但是除此之外,如果你没有多less堆,那么内存pipe理可以在C ++中变得非常有趣。 在这个环境中,例外情况也是非常有趣的,RTTI也是如此。

3)“我看不到它做什么”。 对于任何合理熟练的程序员来说,都可以查看一行C,并且很好地了解在机器代码级实现该行的过程。 很明显,优化有所改变,但在大多数情况下,你可以告诉发生了什么事情。 在C ++中,给定操作符重载,构造函数,析构函数,exception等等,对于给定的代码行将会发生什么事情是非常困难的。 在编写设备驱动程序时,这可能是致命的,因为您通常必须知道是否要与内存pipe理器交互,或者如果代码行影响(或取决于)中断级别或掩码。

完全可以使用C ++在Windows下编写设备驱动程序 – 我自己做过。 需要注意的是,您必须小心使用哪些C ++function以及从哪里使用它们。

除了更广泛的工具支持和硬件可移植性,我不认为有一个令人信服的理由来限制自己到C了。 我经常看到用C语言完成的复杂的手工编写的东西,可以更自然地用C ++完成:

  • 将function(非通用目的)的“模块”分组,只能在相同的数据结构(通常称为“对象”)上使用 – >使用C ++类。
  • 使用“句柄”指针,以便模块函数可以与数据结构的“实例”一起工作 – >使用C ++类。
  • 使用类似function的macros – > C ++模板和内联函数
  • 不同的运行时行为取决于typesID与手工制作的vtable(“描述符”)或调用switch语句 – > C ++多态
  • 对来自/去往通信端口的数据进行编组/解码或使用非可移植结构的易错指针algorithm – > C ++stream概念(不一定是std :: iostream)
  • 为了避免名称冲突,把所有的东西都加上了前缀:C ++名称空间

上面描述的C ++特性没有一个比手写的C实现花费更多。 我可能会错过一些。 我认为C在这个领域的惯性与C被广泛使用有很大的关系。

当然,在一个受限制的环境中,你可能无法使用STL(或根本不能),但这并不意味着你不能使用C ++作为“更好的C”。

C被用来代替保守的Java的最大的原因是很容易了解给定操作使用什么内存。 C非常面向地址。 编写内核代码时需要注意的一点是避免在不方便的时候引用可能导致页面错误的内存。

C ++ 可以使用,但只有当运行时被特别调整,以便在运行时机器被隐含地调用时,例如在调用虚拟函数时使用vtable时,仅引用固定存储器中的内部表(不可分页)。 大多数情况下,这种特殊的适应并不是“开箱即用”的。

将C集成到平台上要容易得多,因为它很容易剥离标准库的C,并且完全明确地控制内存的访问。 所以它也是一个众所周知的语言,它通常是内核工具devise者的select。

编辑 :删除引用新的和删除调用(这是错误的/误导); 取而代之的是更通用的“运行时机器”这个词组。

C,而不是C ++的原因不是:

  • 因为C ++比较慢
  • 或者是因为c-runtime已经存在。

这是因为C ++使用exception。 C ++语言exception的大多数实现在驱动程序代码中是不可用的,因为当操作系统响应硬件中断时调用驱动程序。 在硬件中断期间,驱动程序代码不允许使用exception,因为这可能会导致recursion中断。 而且,在中断的情况下可用于编码的堆栈空间通常非常小(并且由于没有例外规则,所以不可扩展)。

你当然可以使用new(std :: nothrow),但是因为c ++中的exception现在是无处不在的,这意味着你不能依赖任何库代码来使用std :: nothrow语义。

这也是因为C ++放弃了C的一些function: – 在驱动程序中,代码放置非常重要。 设备驱动程序需要能够响应中断。 中断代码必须放置在“非分页”的代码段中,或者永久映射到内存中,因为如果代码在分页的内存中,调用时可能会被调出,这将导致exception,这是一个被禁止的。 在用于驱动程序开发的C编译器中,有#pragma指令可以控制哪种types的内存函数结束。 由于非页面缓冲池是非常有限的资源,因此不要将整个驱动程序标记为非分页:但是,C ++会生成很多隐式代码。 默认的构造函数例如。 无法将C ++隐式生成的代码包含在控件中,因为转换操作符被自动调用,所以代码审计无法保证调用分页代码时没有任何副作用。

所以,总结一下:C驱动程序开发使用C而不是C ++,原因是用C ++编写的驱动程序要么消耗不合理数量的非分页内存,要么会导致操作系统内核崩溃。

我遇到的评论是为什么一家商店将C用于embedded式系统而不是C ++:

  1. C ++产生代码膨胀
  2. C ++exception占用太多空间。
  3. C ++多态性和虚拟表使用太多的内存或执行时间。
  4. 店里的人不懂C ++语言。

唯一有效的原因可能是最后一个。 我看过包含OOP,函数对象和虚函数的C语言程序。 它非常快速地变得丑陋,并且使代码膨胀。

C中的exception处理在正确执行时占用大量的空间。 我会说与C ++相同。 C ++例外的好处是:它们是语言的,程序员不必重新devise轮子。

在embedded式系统中,我更喜欢C ++到C的原因是C ++是一种更强大的types语言。 编译时可以find更多的问题,从而缩短开发时间。 而且,C ++比C更容易实现面向对象的概念。

大多数针对C ++的原因都是围绕devise概念而不是实际的语言。

C非常接近独立于机器的汇编语言。 大多数OStypes的编程在“裸机”级别下降。 用C,你读的代码是实际的代码。 C ++可以隐藏C不能的东西。

这只是我的意见,但我花了很多时间在我的生活中debugging设备驱动程序和操作系统相关的东西。 通常通过查看汇编语言。 在低级别保持简单,让应用程序级别变得有趣。

Windows驱动程序是用C ++编写的。
Linux驱动程序是用c编写的,因为内核是用c写的。

驱动程序和固件主要用C或ASM编写的原因是,不存在对实际运行时库的依赖。 如果你想象这个用C写成的虚构驱动程序

 #include <stdio.h>

 #define OS_VER 5.10
 #define DRIVER_VER“1.2.3”

 int drivermain(driverstructinfo ** dsi){
   如果((* dsi) - > version> OS_VER){
        (* DSI) - > InitDriver();
        printf(“FooBar Driver Loaded \ n”);
        printf(“Version:%s”,DRIVER_VER);
        (* dsi) - > Dispatch = fooDispatch;
    }其他{
        (* DSI) - >退出(0);
    }
 }

 void fooDispatch(driverstructinfo * dsi){
    printf(“Dispatched%d \ n”,dsi-> GetDispatchId());
 }

请注意,运行时库支持必须在编译/链接期间被拉入和链接,它不能作为运行时环境(即操作系统处于加载/初始化阶段时)没有完全build立,因此没有关于如何printf线索,并且可能听起来像操作系统的死亡(Linux的内核恐慌,Windows的蓝屏),因为没有关于如何执行该function的参考。

换句话说,用一个驱动程序,该驱动程序代码有权执行代码,将共享相同的空间的内核代码,ring0是最终的代码执行权限(所有指令允许),ring3是前端操作系统运行在(有限的执行权限),换句话说,一个ring3代码不能有一个为ring0保留的指令,内核会通过捕获代码来杀死代码,好像在说“嘿,你没有特权注册ring0的域名“。

使用汇编语言编写的另一个原因主要是代码大小和原生本地速度,这可能是一个串口驱动程序,其中input/输出对于定时,延迟,缓冲。

大多数设备驱动程序(在Windows的情况下)将有一个特殊的编译器工具链( WinDDK ),它可以使用C代码,但没有链接到正常的标准C的运行时库。

有一个工具包,可以使您在Visual Studio, VisualDDK内build立一个驱动程序。 通过一切手段,build立一个驱动程序并不是因为内心的脆弱,你会通过盯着蓝屏,内核恐慌和奇怪的原因,debugging驱动程序等等来获得压力诱发的活动。

debugging方面比较困难,ring0代码不容易被ring3代码访问,因为它的门是closures的,它是通过内核陷阱门(为了更好的词),如果礼貌地问,门仍然保持closures,而内核将任务委托给驻留在ring0上的处理程序,执行它,无论返回结果,都会传回ring3代码,并且门仍然保持closures状态。 这就是用户级代码如何在ring0上执行特权代码的类比概念。

此外,这个特权代码,可以轻易地践踏内核的内存空间,并破坏内核恐慌/蓝屏…

希望这可以帮助。

也许是因为驱动程序不需要面向对象的function,而C仍然有比较成熟的编译器的事实会有所作为。

可能是因为编译后的c通常会更快,编译时更小,并且在不同操作系统版本之间的编译更加一致,并且依赖性更less。 另外,由于c ++是真正build立在c上,所以问题是你需要它提供什么?

有可能是因为编写驱动程序和固件的人通常用于在c级(或更低)的操作系统级别工作,因此习惯于使用c来解决这类问题。

程序devise有很多风格,如程序,function,面向对象等。面向对象的程序devise更适合现实世界的build模。

如果它适应的话,我会使用面向对象的设备驱动程序。 但是,大多数情况下,当你编程设备驱动程序时,你不需要c ++提供的诸如抽象,多态性,代码重用等优点。

那么,MacOSX的IOKit驱动程序用C ++子集编写的(没有例外,模板,多重inheritance)。 甚至有可能在haskell中编写linux内核模块。)

否则,作为一种可移植的汇编语言,C完美地捕捉了冯·诺依曼(von Neumann)体系结构和计算模型,从而可以直接控制所有特性和缺点(如“冯·诺依曼瓶颈”)。 C完全按照它的devise目标,完全捕捉目标抽象模型,完美无瑕(除了单个控制stream程中的隐含假设,本来可以推广到涵盖硬件线程的现实),这就是为什么我认为它是一个美丽的语言)。当将不同的计算模型应用于这个事实上的标准时,将语言的expression能力限制在这样的基础上消除了大多数不可预测的转换细节。 换句话说,C让你坚持基础,并允许非常直接地控制你正在做什么,例如,当build模与虚拟函数的行为通用性时,你可以控制函数指针表如何存储和使用,与C ++的隐式vtbl分配和pipe理。 这在考虑高速caching时实际上是有帮助的。

话虽如此,基于对象的范例对于表示物理对象及其依赖关系非常有用。 joininheritance我们得到了面向对象的范例,这反过来对表示物理对象的结构和行为层次结构是非常有用的。 没有什么能阻止任何人使用它,并在C中再次expression它,从而完全控制你的对象将被创build,存储,销毁和复制的方式。 实际上这是在linux设备模型中采取的方法。 他们得到了代表设备的“对象”,对象实现层次结构来模拟电源pipe理的依赖性和被破解的inheritancefunction来表示设备系列,全部在C中完成。

因为从系统层面来看,驱动程序需要控制内存中每个字节的每一位,其他更高级的语言不能这样做,或者本身不能这样做,只有C / Asm实现〜