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

时间:2010-01-11 01:34:25

标签: c++ c

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

我听说有技术原因。

有谁知道这个?

很多爱, 路易丝

15 个答案:

答案 0 :(得分:31)

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

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

但是,由于Windows确实支持C ++ stdlib,因此大多数Windows驱动程序都是用C ++编写的(有限的一部分)。

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

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

答案 1 :(得分:26)

内核中的代码在与用户空间不同的环境中运行。没有进程分离,因此错误很难从中恢复;异常几乎是不可能的。存在不同的内存分配器,因此使newdelete在内核上下文中正常工作变得更加困难。可用的标准库越来越少,使得有效地使用像C ++这样的语言变得更加困难。

Windows允许在内核驱动程序中使用very limited subset C ++;基本上,那些可以简单地转换为C的东西,例如除了块开头之外的地方的变量声明。他们建议不要使用newdelete,并且不支持RTTI或大多数C ++标准库。

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

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

答案 2 :(得分:11)

1)“因为它总是这样” - 这实际上解释得比你想象的要多 - 因为几乎所有当前系统上的API最初写入基于C或ASM的模型,并且考虑到很多先验代码存在于C和ASM中,通常更容易“顺应流程”而不是弄清楚如何利用C ++。

2)环境 - 要使用C ++的所有功能,您需要一个完整的运行时环境,其中一些环境只是给驱动程序带来的痛苦。如果你限制你的功能集会更容易,但除此之外,如果你没有太多的堆,内存管理可以在C ++中变得非常有趣。在这种环境中,例外也非常有趣,RTTI也是如此。

3)“我看不出它的作用”。任何技术熟练的程序员都可以查看一行C并且很清楚在机器代码级别发生了什么以实现该行。显然,优化会有所改变,但在大多数情况下,您可以分辨出正在发生的事情。在C ++中,给定运算符重载,构造函数,析构函数,异常等,很难知道在给定的代码行上会发生什么。在编写设备驱动程序时,这可能是致命的,因为您通常必须知道您是否要与内存管理器进行交互,或者代码行是否影响(或依赖于)中断级别或屏蔽。

完全可以使用C ++在Windows下编写设备驱动程序 - 我自己完成了。需要注意的是,您必须注意使用哪些C ++功能以及使用它们的位置。

答案 3 :(得分:10)

除了更广泛的工具支持和硬件可移植性之外,我认为没有令人信服的理由将自己限制在C语言中。我经常看到用C语言完成的复杂的手工编码,在C ++中可以更自然地完成:

  • 分组为仅在同一数据结构上工作的函数(非通用)的“模块”(通常称为“对象”) - >使用C ++类。
  • 使用“句柄”指针,以便模块功能可以使用数据结构的“实例” - >使用C ++类。
  • 使用类似函数的宏 - > C ++模板和内联函数
  • 不同的运行时行为取决于具有手工制作的vtable(“描述符”)或使用switch语句调度的类型ID - > C ++多态性
  • 易于出错的指针算法,用于从/向通信端口编组/解组数据,或使用非便携式结构 - > C ++流概念(不一定是std :: iostream)
  • 为了避免名称冲突而将所有内容排除在外:C ++名称空间

上面描述的C ++功能都不比手写的C实现花费更多。我可能会错过更多。我认为C在这个领域的惯性更多地与C主要使用有关。

当然,您可能无法在受限环境中自由地(或根本不)使用STL,但这并不意味着您不能将C ++用作“更好的C”。

答案 4 :(得分:7)

使用C的最大原因是使用非常谨慎的Java,因为很容易看到用于给定操作的内存。 C非常面向寻址。编写内核代码的关键是避免引用可能在不方便的时刻导致页面错误的内存。

可以使用C ++ 但仅当运行时特别适合于在隐式调用运行时机制时仅引用固定内存中的内部表(不可分页)时,例如使用vtable时调用虚函数。这种特殊的改编在大多数时候并不是“开箱即用”。

将C与平台集成更容易,因为它很容易剥离其标准库的C并且完全明确地控制内存访问。所以它也是一种众所周知的语言,它通常是内核工具设计者的选择。

编辑:删除了对新来电和删除电话的引用(这是错误的/误导性的);取而代之的是更为通用的“运行时机械”一词。

答案 5 :(得分:7)

使用C而不是C ++的原因是:

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

这是因为C ++使用异常。 大多数C ++语言异常的实现在驱动程序代码中都是不可用的,因为当OS响应硬件中断时会调用驱动程序。在硬件中断期间,不允许驱动程序代码使用异常,因为这会导致递归中断。此外,在中断的上下文中可用于代码的堆栈空间通常非常小(并且由于无例外规则而不可增长)。

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

这也是因为C ++放弃了C的一些功能: - 在驱动程序中,代码放置很重要。设备驱动程序需要能够响应中断。中断代码必须放在“非分页”的代码段中,或永久映射到内存中,因为如果代码在分页内存中,它可能在被调用时被分页,这将导致异常,这是被禁止的。 在用于驱动程序开发的C编译器中,有#pragma指令可以控制最终的哪种类型的内存函数。 由于非分页池是非常有限的资源,因此您不希望将整个驱动程序标记为非分页:但是C ++会生成大量隐式代码。例如,默认构造函数。没有办法将C ++隐式生成的代码括起来来控制它的位置,并且因为转换操作符被自动调用,所以代码审计无法保证调用分页代码没有副作用。

因此,总结一下: - C,而不是C ++用于驱动程序开发的原因是因为用C ++编写的驱动程序会消耗不合理数量的非分页内存,或者使操作系统内核崩溃。

答案 6 :(得分:7)

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

  1. C ++产生代码膨胀
  2. C ++异常占用太多 室。
  3. C ++多态和虚拟表 使用太多内存或执行 时间。
  4. 店里的人不知道 C ++语言。
  5. 唯一有效的理由可能是最后一次。我见过包含OOP,函数对象和虚函数的C语言程序。它会非常快速地变得非常丑陋并使代码膨胀。

    C中的异常处理,如果正确实现,会占用大量空间。我会说与C ++相同。 C ++异常的好处:它们在语言中,程序员不必重新设计轮子。

    我更喜欢嵌入式系统中C ++到C的原因是C ++是一种更强大的类型语言。在编译时可以找到更多问题,这缩短了开发时间。此外,C ++是一种比C更容易实现面向对象概念的语言。

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

答案 7 :(得分:5)

C非常接近于与机器无关的汇编语言。大多数OS类型的编程都处于“裸机”级别。使用C,您读取的代码是实际代码。 C ++可以隐藏C不能的东西。

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

答案 8 :(得分:4)

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

编写的

答案 9 :(得分:2)

驱动程序和固件主要用C或ASM编写的原因是,实际的运行时库没有依赖性。如果你想象一下用C写的这个想象中的驱动程序

#include <stdio.h>

#define OS_VER   5.10
#define DRIVER_VER "1.2.3"

int drivermain(driverstructinfo **dsi){
   if ((*dsi)->version > OS_VER){
       (*dsi)->InitDriver();
       printf("FooBar Driver Loaded\n");
       printf("Version: %s", DRIVER_VER);
       (*dsi)->Dispatch = fooDispatch;
   }else{
       (*dsi)->Exit(0);
   }
}

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

请注意,在编译/链接期间必须引入和链接运行时库支持,它不能用作运行时环境(即操作系统在加载/初始化阶段期间)未完全设置因为没有关于如何printf的线索,并且可能听起来是操作系统的丧钟(Linux的内核恐慌,Windows的蓝屏),因为没有关于如何执行的参考功能。

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

它用汇编语言编写的另一个原因主要是代码大小和原始本机速度,这可能是一种串行端口驱动程序,其中输入/输出对于与函数相关的“关键”时间,延迟,缓冲。

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

有一个工具包可以让您在Visual Studio中构建驱动程序VisualDDK。无论如何,构建一个驱动程序并不适合胆小的人,你会通过盯着蓝屏,内核恐慌并想知道原因,调试驱动程序等来获得压力诱导的活动。

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

此外,这个特权代码可以很容易地践踏内核的内存空间并破坏某些东西,从而导致内核恐慌/蓝屏......

希望这有帮助。

答案 10 :(得分:1)

也许是因为驱动程序不需要面向对象的功能,而C仍然有一些更成熟的编译器这一事实会产生影响。

答案 11 :(得分:1)

可能是因为c仍然经常更快,编译时更小,并且在不同OS版本之间的编译中更加一致,并且具有更少的依赖性。另外,由于c ++实际上建立在c上,问题是你需要它提供什么吗?

可能存在一些事实,即编写驱动程序和固件的人通常习惯于在c级的OS级别(或更低级别)工作,因此习惯于将c用于此类问题。

答案 12 :(得分:1)

程序,功能,面向对象等编程风格很多。面向对象编程更适合现实世界的建模。

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

答案 13 :(得分:1)

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

否则,C是一种可移植的汇编语言,完美地捕捉了冯·诺依曼架构和计算模型,允许直接控制其所有特性和缺点(例如“冯·诺伊曼瓶颈”)。 C完全符合它的设计目标并完全无瑕地捕获它的目标抽象模型(除了单个控制流中的隐式假设,这可以概括为覆盖硬件线程的现实)这就是为什么我认为它是一个美丽的当将不同的计算模型应用于这个事实上的标准时,限制语言对这些基础的表达能力消除了大多数不可预测的转换细节。换句话说,C使您能够坚持基础,并且可以直接控制您正在做的事情,例如,当您使用虚拟函数建模行为通用性时,您可以精确控制函数指针表在与C ++的隐式vtbl分配进行比较时如何存储和使用和管理。在考虑缓存时,这实际上很有用。

话虽如此,基于对象的范例对于表示物理对象及其依赖性非常有用。添加继承我们得到面向对象的范例,这反过来对于表示物理对象的结构和行为层次结构非常有用。没有什么可以阻止任何人使用它并再次在C中表达它,从而可以完全控制对象的创建,存储,销毁和复制。实际上这就是linux设备模型中采用的方法。他们得到了代表设备的“对象”,用于建模电源管理依赖性的对象实现层次结构和用于表示设备系列的黑客继承功能,所有这些都是用C语言完成的。

答案 14 :(得分:0)

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