一段时间以来,我一直对编写操作系统感兴趣。通过几个不同的站点,我遇到了一个有趣的概念(解释一下):如果您开始使用#include 编写引导加载程序,那么您已经犯了一个致命的错误。
我已经阅读了 K&R,整本书在每节课中都包含了它。在学习 C 的过程中一直使用它,我不知道我学到了什么使用 stdio,什么没有。如果没有 stdio,你可以在 C 中做什么?
一段时间以来,我一直对编写操作系统感兴趣。通过几个不同的站点,我遇到了一个有趣的概念(解释一下):如果您开始使用#include 编写引导加载程序,那么您已经犯了一个致命的错误。
我已经阅读了 K&R,整本书在每节课中都包含了它。在学习 C 的过程中一直使用它,我不知道我学到了什么使用 stdio,什么没有。如果没有 stdio,你可以在 C 中做什么?
C 标准 (ISO/IEC 9899:1999) 承认两种类型的实现(§4 一致性,¶6):
独立式 - 实现(编译器加库)仅提供七个标头:
<float.h>
<iso646.h>
<limits.h>
<stdarg.h>
<stdbool.h>
<stddef.h>
<stdint.h>
这些为语言提供了基本设施并且不声明任何功能(其中的设施在<stdarg.h>
标准中明确定义为宏)。请注意,它不包括复数。
托管 - 实现提供标准定义的完整库。
独立实现的全部意义在于允许您编写所需的任何代码,而不受一般标准库的阻碍,特别是标准 I/O 函数。缺点是您不仅可以而且必须提供这些功能 - 或使用实现提供的替代方案。请注意,在独立实现中,不需要调用程序的入口点main
。
您正在寻找的是一个独立的实施 - 或使用托管实施的独立部分。您将使用头文件(除非您很疯狂),但它们可能不是标准 C 库头文件,而不是列出的用于独立实现的头文件。
stdio
代表标准输入/输出。顾名思义,它包含与标准 IO 相关的东西。stdio.h的维基百科文章列出了 stdio.h 的内容。如果你使用它们,你将需要 stdio.h。您也可以查看stdio.h 的手册页以获取更多详细信息。
而对于操作系统部分,编写操作系统不仅仅是简单的编程,即使是学术性的。在开始之前,您应该学习数据结构、算法、操作系统理论等等。The Design of the UNIX Operating System是一本非常好的学习 OS 的书。Nachos是一个学术操作系统模拟程序。你也可以检查一下。如果你对 OS 很狂热,那么你应该阅读 Linus Torvalds 的自传,只是为了好玩。好吧,这不是一本技术书籍,但您会感觉到编写操作系统意味着什么。
任何引导加载程序几乎肯定都会有#include 指令,除非它设计得很糟糕。我不知道你从哪里得到这句话,但也许你误解了它。引导加载程序总是从一段纯汇编代码开始,用于初始低级处理器初始化和初始化 C 运行时环境。这段代码根本不可能用 C 语言编写。但是其余代码可以在环境(内存等)的限制范围内根据需要变得复杂。
stdio 只是一组连接到某种可以接收输入和输出的设备的文件流。您可以实现标准输入、标准输出、标准错误或它们的任何子集。您还可以实现一个文件系统,您可以为其打开任意文件流。在现代操作系统中,这通常会连接到某种终端,其中涉及相当多的层,因为它是显示在显示器上的虚拟终端,因此需要图形驱动程序等。在原始嵌入式系统上,stdio 可能连接到串行端口或 LED 显示器。
stdio 是用read()
andwrite()
函数实现的。如果这些没有实现,你就不能使用 stdio 函数,比如printf()
, fprintf()
,fgets()
等等。这并不意味着你不能写你的图形显示器、串行端口或其他任何东西。这只是意味着您没有标准设施来执行此操作,并且需要调用自定义函数。
回答你的问题:你可以在没有 stdio 的情况下用 C 做任何事情。无论如何,它都是用 C 编写的,你只是失去了在任何 C 标准库中实现的一些通用功能。
关于最小 C 环境的其他答案很好,但这是另一个更抽象的角度。
C 标准定义了两种“可观察到的副作用”,这基本上意味着您的程序所做的事情不能被编译器优化或以其他方式修改。这些首先是对 I/O 函数的调用,其次是volatile
对象的读取和写入。
如果您无权访问stdio
依赖于操作系统存在的 C 实现或其他部分,则不能使用 I/O 功能。这使得对对象的读取和写入volatile
成为您的程序实际“做”的唯一事情。在实践中,您可能必须或可能不必实际标记所有内容volatile
,因为您的 C 实现可能提供超出标准的保证。如果有一条 CPU 指令发出哔声,您的编译器会将其(以及一般的内联汇编)视为 I/O 函数,即使它不是真正的函数,等等。
换句话说,您将不得不使用低级硬件访问来实现引导加载程序应该实现的任何目标(这相当于:在屏幕上显示一条消息,建立操作系统运行所需的一些资源,加载它并运行它)。这种访问当然完全依赖于实现——硬件供应商可能会提供库来帮助你,它们不会是标准的 C 头文件。
正如 taskinoor 所提到的,Wikipedia 提供了有关stdio.h中包含的内容的出色参考。
至于没有 stdio.h 的可能性,几乎所有可能的事情都可以,尽管您必须为某些事情付出更多努力。例如,如果你想使用文件,你必须定义你自己的文件抽象,以及你自己的处理它的接口。如果您想从键盘读取输入,那么scanf()
您必须使用汇编代码来调用 BIOS 中断,而不是调用。
这就是编程操作系统的本质。在您定义它们和使用它们的 API 之前,这些更高级别的构造都不存在。您需要从最小的基本功能部分开始,然后将它们组合在一起以创建越来越有用和复杂的结构。