可执行文件是否需要操作系统内核才能运行?

可执行文件是否需要操作系统内核才能运行?

我知道,当源代码(例如 C++)被编译时,编译器的输出是机器代码(可执行文件),我认为这是直接对 CPU 的指令。最近我在阅读内核时发现程序无法直接访问硬件,而必须通过内核。

因此,当我们编译一些简单的源代码(例如仅包含一个printf()函数)并且编译生成可执行机器码时,该机器码中的每一条指令是否会直接从内存中执行(一旦代码被操作系统加载到内存中)还是机器码中的每个命令仍需要经过操作系统(内核)才能执行?

我读过了类似的问题。它没有解释编译后生成的机器代码是否直接向 CPU 发出指令,或者是否需要再次经过内核来为 CPU 创建正确的指令。也就是说,机器代码加载到内存后会发生什么?它会经过内核还是直接与处理器对话?

答案1

作为一个编写过无需操作系统即可执行的程序的人,我给出了一个明确的答案。

可执行文件是否需要操作系统内核才能运行?

这取决于该程序是如何编写和构建的。
你可以编写一个完全不需要操作系统的程序(假设你有这方面的知识)。
这样的程序被描述为独立.
引导加载程序和诊断程序是独立程序的典型用途。

然而,在某些主机操作系统环境中编写和构建的典型程序默认在同一主机操作系统环境中执行。
编写和构建独立程序需要非常明确的决策和操作。


...编译器的输出是机器代码(可执行文件),我认为它是直接发送给 CPU 的指令。

正确的。

最近我在阅读内核,我发现程序不能直接访问硬件,而必须通过内核。

这是操作系统执行程序时使用的 CPU 模式所施加的限制,并由某些构建工具(如编译器和库)实现。
它并不是每个程序的内在限制。


因此,当我们编译一些简单的源代码(例如仅包含 printf() 函数)并且编译生成可执行机器代码时,该机器代码中的每一条指令是否会直接从内存中执行(一旦代码被操作系统加载到内存中)还是机器代码中的每个命令仍需要经过操作系统(内核)才能执行?

每条指令都由 CPU 执行。
不支持或非法的指令(例如进程权限不足)将立即引发异常,CPU 将改为执行例程来处理这种异常情况。

Aprintf()函数不应该被用作“简单源代码”
从面向对象的高级编程语言到机器代码的转换可能并不像你暗示的那么简单。
然后你从执行数据转换的运行时库中选择最复杂的函数之一输入/输出。

请注意,您的问题规定了一个具有操作系统(和运行时库)的环境。
一旦系统启动,操作系统便控制了计算机,程序可以执行的操作就会受到限制(例如,I/O 必须由操作系统执行)。
如果您希望执行独立程序(即没有操作系统),则您不能启动计算机来运行操作系统。


... 机器代码加载到内存后会发生什么?

这取决于环境。

对于独立程序,它可以被执行,即通过跳转到程序的起始地址来移交控制权。

对于由操作系统加载的程序,该程序必须动态链接到它所依赖的共享库。操作系统必须为将要执行该程序的进程创建执行空间。

它会通过内核还是直接与处理器对话?

机器代码是执行由 CPU 控制。
它们不“穿过内核”,但他们也没有“与处理器对话”机器码
(由操作码和操作数组成)是给CPU的指令,经过解码并执行操作。

也许你应该研究的下一个主题是CPU 模式

答案2

内核“只是”更多的代码。它只是位于系统最底层和实际硬件之间的一层。

所有这些都直接在 CPU 上运行,您只需通过层层转换即可执行任何操作。

您的程序“需要”内核,就像它printf首先需要标准 C 库才能使用该命令一样。

程序的实际代码在 CPU 上运行,但是代码在屏幕上打印某些内容的分支会经过 Cprintf函数的代码,通过各种其他系统和解释器,每个系统和解释器都会进行自己的处理以完成计算。如何 hello world!实际打印在你的屏幕上。

假设您有一个在桌面窗口管理器上运行的终端程序,该程序在您的内核上运行,而内核又在您的硬件上运行。

还有很多事情要做,但让我们保持简单......

  1. 在您的终端程序中运行程序来打印hello world!
  2. 终端看到程序已经(通过 C 输出例程)写入hello world!控制台
  3. 终端程序进入桌面窗口管理器,说“我收到了写入hello world!,您能把它放在位置吗?”xy
  4. 桌面窗口管理器向内核发出信息“我的一个程序希望你的图形设备在这个位置放置一些文本,赶快行动吧老兄!”
  5. 内核将请求传递给图形设备驱动程序,该驱动程序将其格式化为显卡可以理解的方式
  6. 根据显卡的连接方式,需要调用其他内核设备驱动程序将数据推送到 PCIe 等物理设备总线上,处理诸如确保选择了正确的设备以及数据可以通过相关桥接器或转换器等事宜
  7. 硬件显示内容。

这只是为了描述而进行的过于简化。这里有龙。

实际上,你所做的一切都需要硬件访问,无论是显示、内存块、文件位还是诸如此类的东西通过内核中的一些设备驱动程序来准确计算如何与相关设备通信。可以是位于 SATA 硬盘控制器驱动程序之上的文件系统驱动程序,而 SATA 硬盘控制器驱动程序本身位于 PCIe 桥接设备之上。

内核知道如何将所有这些设备连接在一起,并为程序提供一个相对简单的界面来执行操作,而无需知道如何自己完成所有这些操作。

桌面窗口管理器提供了一个层,这意味着程序不必知道如何绘制窗口并与试图同时显示内容的其他程序很好地协作。

最后,终端程序意味着您的程序不需要知道如何绘制窗口,也不需要知道如何与内核显卡驱动程序对话,也不需要知道如何处理屏幕缓冲区和显示时序以及实际将数据线摆动到显示器的所有复杂性。

一切都由一层又一层的代码来处理。

答案3

这取决于环境。在许多较旧(和较简单!)的计算机(例如 IBM 1401)中,答案是“否”。您的编译器和链接器会发出一个独立的“二进制文件”,它完全不需要任何操作系统即可运行。当您的程序停止运行时,您会加载另一个程序,它也不需要操作系统即可运行。

现代环境中需要操作系统,因为您一次运行的程序不止一个。多个程序同时共享 CPU 核心、RAM、大容量存储设备、键盘、鼠标和显示器需要协调。操作系统提供了这一点。因此,在现代环境中,您的程序不能只读写磁盘或 SSD,它必须要求操作系统代表它执行此操作。操作系统从所有想要访问存储设备的程序中获取此类请求,实现访问控制等功能(不能允许普通用户写入操作系统的文件),将它们排队到设备,并将返回的信息分类到正确的程序(进程)。

此外,现代计算机(与 1401 不同)支持连接各种各样的 I/O 设备,而不仅仅是 IBM 过去向您出售的设备。您的编译器和链接器不可能知道所有可能性。例如,您的键盘可能通过 PS/2 或 USB 进行连接。操作系统允许您安装设备特定的“设备驱动程序”,这些驱动程序知道如何与这些设备通信,但为​​操作系统提供了设备类的通用接口。因此,您的程序甚至操作系统都不必做任何不同的事情来从 USB 和 PS/2 键盘获取击键,或者访问本地 SATA 磁盘、USB 存储设备和 NAS 或 SAN 上某个位置的存储。这些细节由各种设备控制器的设备驱动程序处理。

对于大容量存储设备,操作系统在所有这些设备之上都提供了一个文件系统驱动程序,无论存储在哪里以及如何实现,该驱动程序都会向目录和文件提供相同的接口。同样,操作系统担心访问控制和序列化。例如,一般来说,同一个文件不应该被多个程序同时打开进行写入,除非经过一些步骤(但同时读取通常是可以的)。

因此,在现代通用环境中,确实需要操作系统。但即使在今天,也有一些计算机(例如实时控制器)还不够复杂,不需要操作系统。

例如,在 Arduino 环境中,实际上没有操作系统。当然,构建环境会将一堆库代码整合到它构建的每个“二进制文件”中。但由于这些代码无法从一个程序持久保存到另一个程序,因此它不是操作系统。

答案4

当您编译代码时,您会创建所谓的“对象”代码,该代码(在大多数情况下)依赖于系统库(printf例如),然后您的代码由链接器包装,该链接器将添加您的特定操作系统可以识别的程序加载器(这就是为什么您无法在 Linux 上运行为 Windows 编译的程序的原因)并知道如何解开代码并执行。因此,您的程序就像三明治里的肉,只能作为一个整体打包食用。

最近我在阅读有关内核的资料,我发现程序不能直接访问硬件,而必须通过内核。

嗯,这只对了一半;如果您的程序是内核模式驱动程序,那么如果您知道如何与硬件“对话”,那么实际上您可以直接访问硬件,但通常(尤其是对于未记录或复杂的硬件)人们使用的驱动程序是内核库。这样,您就可以找到知道如何以几乎人类可读的方式与硬件对话的 API 函数,而无需知道地址、寄存器、时序和其他一些东西。

该机器代码中的每一条指令是否会直接从内存中执行(一旦代码被操作系统加载到内存中)还是机器代码中的每个命令仍需要通过操作系统(内核)才能执行

好吧,内核就像一个女服务员,她的职责是带你到餐桌前并为你服务。它唯一不能做的事情就是为你吃饭,你应该自己做。与你的代码一样,内核会将你的程序解压到内存中并启动你的代码,这些代码是直接由 CPU 执行的机器码。内核只需要监督你——允许你做什么,不允许你做什么。

它不能解释编译后生成的机器代码是否直接是发送给 CPU 的指令,还是需要再次经过内核来为 CPU 创建正确的指令?

编译后生成的机器代码是直接对 CPU 的指令。毫无疑问。你唯一需要记住的是,编译文件中的所有代码并非都是实际的机器/CPU 代码。链接器用一些只有内核才能解释的元数据包装你的程序,作为线索 - 如何处理你的程序。

机器代码加载到内存后会发生什么?它会经过内核还是直接与处理器对话。

如果您的代码只是简单的操作码,例如两个寄存器的添加,那么它将直接由 CPU 执行,而无需内核协助,但如果您的代码使用来自库的函数,那么此类调用将由内核协助,例如女服务员,如果你想在餐馆吃饭,他们会给你一个工具 - 叉子、勺子(这仍然是他们的资产)但你会用它做什么 - 这取决于你的“代码”。

好吧,只是为了防止评论中的激烈争论——这确实是一个过于简单的模型,我希望它能够帮助 OP 理解基本的东西,但欢迎提出改进这个答案的好建议。

相关内容