【问题标题】:Dependency of Run time library on operating system运行时库对操作系统的依赖
【发布时间】:2013-10-21 04:36:06
【问题描述】:

我正在阅读有关如何编写极简内核的 this 教程。我在这之间读到了这篇文章:

运行时库

为您的操作系统编写代码的主要部分是重写运行时库,也称为 libc。这是因为 RTL 是编译器包中最依赖操作系统的部分:C RTL 提供了足够的功能让您编写可移植的 程序,但 RTL 的内部工作依赖于操作系统 使用。事实上,编译器供应商经常为相同的内容使用不同的 RTL 操作系统:Microsoft Visual C++ 为各种不同的应用程序提供不同的库 调试/多线程/DLL 和旧版 MS-DOS 的组合 编译器为多达 6 种不同的内存提供运行时库 模型。

我对这部分有点困惑。假设我用 C 代码编写我的内核,并根据建议使用内置的 printf() 函数来打印一些东西。最后我的代码将被翻译成机器码。当它被执行时,处理器将直接运行它。作者为什么这么说:

RTL 的内部工作方式取决于所使用的操作系统?

【问题讨论】:

    标签: c operating-system kernel osdev


    【解决方案1】:

    有两个不同的问题:

    1. printf() 在内核中运行时会做什么?很可能它会崩溃或什么都不做,因为您用于开发内核的 C 编译器的 RTL 可能会假设一些带有控制台、操作系统等的运行时环境。即使您使用的是 C/C++ 的独立实现,运行时可能会接管串行端口或其他什么来执行输出。您可能不希望这样,因为您的内核驱动程序将控制 I/O。所以你需要从 RTL 重新实现底层文件 I/O。

    2. printf() 在运行于内核之上的用户进程中运行时会做什么?如果内核保护对硬件资源的访问,它就无能为力。来自 RTL 的底层文件 I/O 代码必须知道如何与内核通信以打开标准输入/输出“文件”的任何通道并执行数据交换。

    您需要了解您使用的是 C/C++ 编译器 + RTL 的独立实现还是托管实现,以及所有影响。对于内核开发,您将使用独立的实现。对于用户空间开发,您需要一个托管实现,也许是一个交叉编译器,但运行时库必须像托管实现一样编写。请注意,在这两种情况下,您都可以使用相同的编译器,只需将其指向适当的头文件和库。例如,在 Linux 上,内核和用户空间的开发可以使用相同的 gcc 编译器完成,但具有不同的头文件和库。

    处理器不知道控制台是什么,或者内核是什么。一些代码必须实际访问硬件。当您从托管的 C/C++ 实现中获取 printf() 时,该实现在其内部深处的某个地方将为它打算运行的特定平台调用系统调用。该系统调用旨在写入一些包装“控制台”的抽象。在此系统调用的另一端是内核代码,它将将此数据推送到某些硬件。它甚至可能不是直接的硬件,它很可能是另一个进程的用户空间。

    例如,每当您在 Unix 机器(KDE 的 Konsole、X11 xterm、OS X 终端等)上的基于 GUI 的终端中运行东西时,调用 printf() 的用户态进程还有很长的路要走任何东西都会碰到硬件。即(即使这是简化的!):

    1. printf() 将数据写入缓冲区
    2. 缓冲区被刷新到(写入)文件句柄。调用了write() 库函数。
    3. write() 库函数调用系统调用,将控制权移交给内核。
    4. 内核代码将数据从用户空间页面复制到内核端非分页缓冲区,因为这些页面随时可能消失。
    5. 内核代码为给定的文件句柄调用写处理程序 - 在许多内核中,文件句柄被实现为具有虚拟方法的类。
    6. 文件句柄恰好是pseudo-terminal (pty) slave。 write 方法将数据传递给 pty master。
    7. pty master 填满给定伪终端的读取缓冲区,并唤醒等待相关文件句柄的进程。
    8. 实现 GUI 终端的进程唤醒并read()s 文件句柄。这会通过库到达系统调用。
    9. 内核调用 pty 主文件句柄的读取处理程序。
    10. 读取处理程序将其缓冲数据复制到用户空间。
    11. 系统调用返回。
    12. 终端进程获取数据,解析控制代码,并更新其表示模拟屏幕的内部数据结构。它还在事件队列中对更新事件进行排队。控制返回到 GUI 库/框架的事件循环。这是通过事件完成的,因为这些事件通常是合并的。如果有大量可用数据,则在重新绘制任何内容之前,都会对其进行处理以更新屏幕数据结构。
    13. 事件分派器将更新/重绘事件分派到“屏幕”小部件/窗口。
    14. 小部件/窗口中的事件处理程序代码使用内部数据结构在某处“绘制”。通常它会在位图后备存储中。
    15. GUI 库/框架代码向操作系统的图形驱动程序发出信号,告知后备存储上有新数据可用。
    16. 同样,通过系统调用,控制权被传递给内核。在内核中运行的图形驱动程序将在图形硬件上执行必要的魔法,将支持位图传递到屏幕。它可能是显式内存副本,也可能是纹理副本与图形硬件的简单排队。

    【讨论】:

    • 无论库函数实现是什么,最终归结为机器指令,对吧?然后这些指令可以由处理器执行。我发现很难理解对操作系统的依赖在哪里出现?
    • @AmitTomar:当然可以,但是这些机器指令通常无法直接访问任何硬件——至少在printf 本身中没有,除非你的printf 运行在某种小型微控制器上.我已经编辑了答案,让您了解发生了什么。甚至这个想法也被大大简化了。在现代操作系统上,列表可能需要 100 项长才能接近准确。
    • 非常感谢您提供如此详细的解释。
    【解决方案2】:

    Printf() 是一个可以独立于操作系统的高级函数。然而,它只是难题的一部分,它本身具有依赖性。它需要能够写入标准输出。这将导致依赖于低级操作系统的系统调用,例如 create() 打开 stdout 流和 write() 在那里发送 printf 输出。不同的操作系统有不同的系统调用,所以总会有一个适配层,你自己的。

    当然,你可以让 printf() 在你的内核中工作。实际上看到调用 printf() 的输出将是真正需要解决的问题。与内核模式下的终端窗口完全不同。

    【讨论】:

    • 无论库函数实现是什么,最终归结为机器指令,对吧?然后这些指令可以由处理器执行。我发现很难理解对操作系统的依赖在哪里出现?
    猜你喜欢
    • 2012-02-06
    • 2020-06-12
    • 2017-03-09
    • 2013-10-22
    • 2021-12-31
    • 2021-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多