How kernel, compiler, and C library work together

来自osdev
Zhang3讨论 | 贡献2021年12月16日 (四) 07:04的版本 (创建页面,内容为“==内核== 内核是操作系统的核心。 在传统设计中,它负责内存管理、I/O、中断处理和其他各种事情。 即使像Microkernels或exockernels这样的一些现代设计将这些服务中的一些移动到了用户空间,但这在本文档的范围内并不重要。 内核通过一组系统调用使其服务可用;它们的调用方式以及它们所做的工作因内核而异。 ==C 库== :''Main Articles: See C Libr…”)
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
跳到导航 跳到搜索

内核

内核是操作系统的核心。 在传统设计中,它负责内存管理、I/O、中断处理和其他各种事情。 即使像Microkernels或exockernels这样的一些现代设计将这些服务中的一些移动到了用户空间,但这在本文档的范围内并不重要。

内核通过一组系统调用使其服务可用;它们的调用方式以及它们所做的工作因内核而异。

C 库

Main Articles: See C Library, Creating a C Library

首先,当您开始处理内核时,您没有可用的C库。 您必须自己提供所有内容,除了编译器本身提供的一些片段。 You will also have to port an existing C library or write one yourself.

C库实现标准C函数(即<stdlib.h>、<math.h>、<stdio.h>等中声明的内容),并以适合与用户空间应用程序链接的二进制形式提供它们。

除了标准C函数(如ISO标准中所定义的)之外,C库还可以(并且通常确实)实现其他功能,这些功能可能由某些标准定义,也可能不由某些标准定义。 例如,标准C库没有提到网络。 对于类Unix系统,POSIX标准定义了C库的预期内容;其他系统可能会有根本不同。

应该注意的是,为了实现其功能,C库必须调用内核函数。 因此,对于您自己的操作系统,您当然可以使用现成的C库并为您的操作系统重新编译它-但这需要您告诉库如何调用您的内核函数,以及您的内核如何实际提供这些函数。

Library Calls中提供了更详细的示例,或者,您可以使用现有的C Library create your own C Library

Compiler / Assembler

汇编程序获取(明文)源代码并将其转换为(二进制)机器代码;更准确地说,它将源代码转换为“对象”代码,其中包含诸如符号名称、重新定位信息等附加信息。

编译器获取更高级的语言源代码,或者直接将其转换为目标代码,或者(就像GCC一样)将其转换为汇编程序源代码,并在最后一步调用汇编程序。

生成的对象代码“不”包含调用的标准函数的任何代码。如果您包括d,例如<stdio。h> 并且使用printf(),目标代码将仅包含一个“引用”,说明名为printf()的函数(并将常量char*和许多未命名参数作为参数)必须链接到目标代码才能接收完整的可执行文件。

某些编译器“内部”使用标准库函数,这可能导致对象文件引用,例如memset()memcpy(),即使您没有包含标头或使用此名称的函数。 您必须向链接器提供这些函数的实现,否则链接将失败。 GCC独立环境只需要函数memset()memcpy()memcmp()memmove(),以及libgcc库。 某些高级操作(例如32位系统上的64位分区)可能涉及“编译器内部”函数。 对于GCC,这些函数驻留在libgcc中。 这个库的内容与您使用的操作系统无关,它不会因为任何类型的许可问题而污染您编译的内核。

Linker

链接器获取编译器/汇编器生成的目标代码,并根据C库(和/或libgcc.A或您提供的任何链接库)“链接”它。这可以通过两种方式完成:静态和动态。

静态链接

当静态链接时,链接器在编译/汇编程序运行之后的构建过程中被调用。 它获取目标代码,检查未解析的引用,并检查是否可以从可用库解析这些引用。 然后将这些库中的二进制代码添加到可执行文件中;在此过程之后,可执行文件是“完成的”,即运行时,它只需要存在内核。

另一方面,可执行文件可能会变得相当大,库中的代码会在磁盘和内存中反复复制。

动态链接

动态链接时,链接器在“加载”可执行文件期间被调用。 目标代码中未解析的引用将根据系统中当前存在的库进行解析。 这使得磁盘上的可执行文件更小,并允许使用诸如“共享库”之类的内存空间节省策略(见下文)。

另一方面,可执行文件依赖于它引用的库的存在;如果系统没有这些库,则可执行文件无法运行。

共享库

一种流行的策略是跨多个可执行文件“共享”动态链接的库。 这意味着,不是将库的二进制文件附加到可执行映像,而是调整可执行文件中的引用,以便所有可执行文件都引用所需库的相同内存表示形式。

这需要一些技巧。 首先,库不能有任何“状态”(静态或全局数据),或者必须为每个可执行文件提供单独的“状态”。 对于多线程系统来说,这变得更加棘手,在多线程系统中,一个可执行文件可能有多个同步控制流。

其次,在虚拟内存环境中,通常不可能在同一虚拟内存地址为系统中的所有可执行文件提供库。 要在任意虚拟地址访问库代码,需要库代码是“位置独立的”(这可以通过为GCC编译器设置-PIC命令行选项来实现)。这需要二进制格式(重定位表)对该特性的支持,并可能导致某些体系结构上的代码效率稍低。

ABI - 应用程序二进制接口

系统的ABI定义了库函数调用和内核系统调用的实际执行方式。 这包括参数是在堆栈上传递还是在寄存器中传递,函数入口点在库中的位置,以及其他类似的问题。

当使用静态链接时,生成的可执行文件依赖于使用与构建可执行文件的ABI相同的ABI的内核;使用动态链接时,可执行文件取决于库的ABI是否保持不变。

未解析符号

链接器是一个阶段,在这个阶段中,您将发现在您不知情的情况下添加的内容,而这些内容不是由您的环境提供的。 这可能包括对alloca()memcpy()或其他几个的引用。 这通常表明您的工具链或命令行选项没有正确设置以编译自己的操作系统内核,或者您正在使用尚未在C库/运行时环境中实现的功能! 如果您没有使用交叉编译器和libgcc库,并且没有memcpy、memmove、memset和memcmp的实现,那么您肯定会遇到麻烦。

其他符号,如_udiv*或u builtin_saveregs,在libgcc中提供。 如果您发现缺少这些符号的错误,请记住您需要链接libgcc。