模块化内核 vs 微内核 / 单体内核

7
我是C程序员,对Linux内核编程新手。我发现有3种类型的内核:单体内核、微内核和模块化内核。在搜索时,我发现有些网站说Linux具有单体内核(在Stack overflow上),而其他一些网站则说它是微内核,还有一些则称其为混合内核。因此,在阅读模块化概念时,我感到非常困惑,因为该概念表明可以添加新的驱动程序模块而无需重新编译内核,这与我的假设相矛盾,即Linux使用单体内核。单体内核在单个地址空间和单个进程中运行,这也有点让人困惑。

这是一个庞大的、单olithic内核。与Plan 9Mach进行比较,以更好地了解它们之间的区别。 - tadman
3个回答

16
在你试图理解这些不同之前,你必须先了解其他概念:
1. 模块化编程。
模块是程序的一个功能完整部分。模块通常具有以下属性:
- 接口和实现的分离。 - 初始化和去初始化例程。两者都是可选的。在具有垃圾回收(Garbage Collector)环境中,可能会缺少去初始化例程。 - 程序使用的模块组成有向无环图,也称为依赖关系图(您可能听说过这个 - 不允许循环依赖性,在依赖模块之前初始化依赖项模块)。
模块化编程在构建大型系统时至关重要。每个大内核都是“模块化内核”,无论它是单片式、混合式还是微内核。
有时可以动态加载和卸载模块。动态模块是任何可扩展系统的重要组成部分。这些可以是插件,或者如果我们谈论内核,则是从内核分开开发和分发的驱动程序。
2. 安全语言和不安全语言。
安全语言非常严格地定义程序中可能发生的情况。最重要的是,它们没有“畸形”程序(或“无意义”程序)的概念。每个程序都是有效的,并且其执行始终遵循语言规范。在这种情况下,程序是否执行程序员所期望的操作都是无关紧要的。
安全语言的常见特点:
  1. 他们使用垃圾回收。
  2. 他们没有指针算术。这意味着不允许写入或读取任意地址。
  3. 他们防止数组越界访问(如果有这样的概念)。异常或类似机制可用于信号和从此类故障中恢复。
  4. 引用(或指针)只有两种可能的状态:空引用和对有效对象的引用。这由垃圾回收器保证。实际上,GC 是关键组件。一些语言甚至进一步禁止了空引用。
  5. 每个对象(正在使用的内存块)都有分配给它的类型信息,对象只能通过适当类型的引用访问,例如您不能通过字符串引用访问整数数组。

您可以向此列表添加更多条目,但基本思想是确保程序只能使用有效操作访问有效的内存区域。请记住,某些不安全的语言可能共享其中一些或甚至所有这些特征。

安全语言的示例:Python、Java、C# 的安全子集。

不安全语言定义了程序中可以和不可以做什么,但通常很少或根本没有阻止程序员做错事情的措施。违反这些规则的程序称为格式错误的程序。从语言的角度来看,这种程序是没有意义的,语言甚至不尝试定义它的行为,因为这通常几乎不可能做到。在 C 中,这种程序的行为是未定义的

不安全语言的示例:汇编语言、C、C++、Pascal。

3. 硬件是不安全的,因此必须使用不安全的语言进行编程。

大多数硬件并未提供安全环境。曾经有一些处理器会将类型信息附加到每个存储单元上(参见tagged architecture),但现代处理器不再这样做,因为它会使硬件更加复杂、变慢、更昂贵和不够通用。
尽管如此,在不安全的硬件环境中仍然提供了一些功能,以实现安全环境,例如内存保护、独立的地址空间以及在用户模式和内核模式(又称监督者模式)之间分离执行模式。
内核是在裸机上运行的,因此其中很多部分必须使用像C和汇编这样的不安全语言编写。另一个原因是性能——安全环境意味着巨大的开销。

微内核和单体内核

单体内核及其模块在单个共享地址空间中运行。由于通常所有内容都是用不安全的语言编写的,因此内核的任何部分都可以访问(并损坏)属于其他部分的内存,这是由于代码中的错误导致的。这种不安全的环境使得无法检测或从这些故障中恢复,并且最重要的是无法预测这些故障后内核的行为。
微内核试图通过将内核的各个部分移动到单独的地址空间中来克服这些限制,从而有效地使它们相互隔离,但提供了安全的相互通信方式(通常是通过消息传递)。这种分离创建了由多个不安全进程组成的安全环境,允许内核从其某些子系统的故障中恢复。
同时,单内核可以在单独的地址空间(FUSE)中运行其部分内容,而微内核也可以支持与内核主要部分共享地址空间的模块。如果大多数内核在单个地址空间中运行,则被认为是单内核。如果大多数内核在单独的地址空间中运行,则该内核被认为是微内核。如果内核介于两者之间并积极使用两种方法,则有一个混合内核。
混合内核的概念意味着结合两种方法的优点,并由微软在90年代发明以提高Windows NT销售量。开玩笑!但这几乎是真的。 Windows NT的每个重要部分都在共享的地址空间中运行,因此它是另一种单内核设计的示例。
许多人似乎在描述能够动态加载模块的单内核时使用此术语。这是因为过去的单内核不支持动态模块加载,并且必须在每次添加模块到内核时重新编译。微内核不仅仅是动态模块加载,还涉及内核的可靠性,以及其从子系统故障中恢复的能力。

答案:Linux是一个单片式内核。


单内核可以是模块化的,并且可以动态加载模块。另一方面,微内核必须是模块化的,并且必须能够动态加载模块 - 整个想法是在一个单独的地址空间中运行它们。


使用微内核并不是克服单体内核的不安全性的唯一方法。另一种方法是用安全语言编写单体内核。这种方法的一个问题是,安全环境应该由硬件提供(并且将非常有限)或者应该使用不安全语言在软件中实现。这种环境的实现将非常复杂,并且很可能会有许多漏洞(想想在JVM中发现的所有漏洞)。

这种方法的一个例子是实验性操作系统 Singularity


0

请参考此前的StackOverflow问题获取有关您问题的一些信息。简而言之,您似乎想知道...

...读取模块化概念,即可以添加新驱动程序模块而无需重新编译内核,这与我对Linux使用单片内核的假设相矛盾。单片内核在单个地址空间和单个进程中运行...

这两个概念(“模块化内核”和“单个地址空间”)实际上并不矛盾。您可以构建一个新的内核模块,而无需重新编译整个Linux内核。当您加载此新的内核模块时,它实际上将被加载到正在运行的内核本身的同一地址空间中。来自上面链接的内容...

不要将模块化内核的术语与单片内核混淆。某些单片内核可以编译为模块化(例如Linux),重要的是模块被插入并从处理核心功能(内核空间)的相同空间运行。

正如您发现的那样,有几种方法可以对内核进行分类,不同类型并不一定互斥。


0

考虑到明天我可能会有一次关于这个问题的测验,我应该能够帮助你。然而,我还在学习中,虽然我的帖子可能会有一些技术上的错误,但概念上是正确的。

基本上,正如您所了解的,操作系统有不同类型的内核。

微内核与单体内核相反,它将最少的系统程序和服务放在微内核上。之前被认为是内核一部分(在单体内核版本中)的大多数服务,例如进程调度等,现在处于用户空间,并称为服务器。这些服务器通过微内核使用进程间通信进行通信,这是一种由微内核制定的通信形式。这种模块化方法通过使这些“服务器”动态可加载来构建。因此,在不需要内核重新编译的情况下,可以动态加载特定的“服务器”(在这种内核类型中称为模块)。

Linux内核是一个单体内核,但大多数Linux的变种如Ubuntu、Solaris使用混合内核,即单体内核和模块化内核方法的混合。这是相当普遍的,不同的内核结构有不同的优缺点,需要采用混合结构来取得平衡。


是的,我从上面的解释中得到了一些突破。正如你所说的,单片内核占用单个地址空间,因此如果用户进程正在运行,它会共享单片地址空间,还是为该用户进程分配一个完全不同于单片单一地址空间的独立地址空间? - shanker
据我所知,用户地址空间在所有情况下都是相同的。这样做的一个主要原因是,进程可能会在不同的时间使用一个模块和另一个模块。这些模块将位于不同的地址空间中。此外,考虑到Linux操作系统(例如Ubuntu)使用混合模块化单片内核(其中内核是一个大型的东西,具有可以加载到内核的模块),拥有不同的地址空间并没有太多意义。 - Aditya Bhattacharya

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