可执行文件需要OS内核才能运行吗?


53

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

因此,当我们编译一些简单的源代码(例如仅使用一个printf()函数)并且编译产生可执行的机器代码时,该机器代码中的每条指令将直接从内存中执行(一旦代码由OS加载到内存中)或机器代码中的每个命令仍然需要通过操作系统(内核)才能执行?

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


29
如果您正在为Arduino编写代码,则不需要操作系统。
stib

12
printf不是一个很好的例子。C规范将其明确定义为仅在“托管”实现中可用的函数(意味着在内核上运行,而不是“独立”(可能不需要一个))。在大多数平台上,printf这只是您提供的功能libc,可以代表您做很多事情(最终包括一个系统调用以打印到stdout)。实际上,它与调用libvlc_media_list_add_media或没有什么不同PyObject_GetAttr,除了printf可以保证某些实现可链接而不添加额外的非标准-ls。
abarnert

1
这个存在!(不隶属于,只是觉得很酷)erikyyy.de/invaders
Nonny Moose

9
这实际上取决于您对术语“可执行”,“内核”,“运行”,“需要”,“交谈”和“通过”的精确定义。如果没有这些术语的精确定义,这个问题是无法回答的。
约尔格W¯¯米塔格

3
@JörgWMittag-如果您要做书呆子,那为什么只检查这些术语和这个问题呢?需要定义的真正显着术语是“操作系统”,它可疑地应用于MS-DOS(以及类似的单任务运行时环境)。如果有一些人(错误消息)认为PC BIOS是OS,那么一切是否值得抓住?我想不是。OP在看起来合理(尤其是非英语母语人士)或非技术性上下文中使用这些词。
木屑

Answers:


86

作为编写无需操作系统即可执行的程序的人,我提供了明确的答案。

可执行文件需要OS内核才能运行吗?

这取决于该程序的编写和构建方式。
您可以编写一个完全不需要操作系统的程序(假设您已经掌握了该知识)。
这样的程序被称为独立程序。
引导加载程序和诊断程序是独立程序的典型用法。

但是,在某些主机OS环境中编写和构建的典型程序将默认在同一主机OS环境中执行。
编写和构建独立程序需要非常明确的决策和动作。


...编译器的输出是机器代码(可执行),我认为这是直接向CPU发出的指令。

正确。

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

这是操作系统用于执行程序的CPU模式所施加的限制,而某些编译工具(例如编译器和库)促进了这种限制。
这并不是对所编写的每个程序的固有限制。


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

每个指令均由CPU执行。
不支持或非法的指令(例如,进程权限不足)将导致立即异常,而CPU将执行例程来处理这种异常情况。

不应将printf()函数用作“简单源代码”的示例。
从面向对象的高级编程语言到机器代码的转换可能并不像您所暗示的那么琐碎。
然后从运行时库中选择最复杂的函数之一,该库执行数据转换 I / O。

请注意,您的问题规定了带有OS(和运行时库)的环境。
一旦系统启动,并且操作系统获得了计算机的控制权,就会对程序可以执行的操作施加限制(例如,I / O必须由操作系统执行)。
如果您希望执行一个独立程序(即没有操作系统),则一定不要启动计算机来运行该操作系统。


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

这取决于环境。

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

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

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

机器代码由CPU 执行
它们不“通过内核”,也不“与处理器对话”
机器码(由操作码和操作数组成)是对CPU的指令,该指令被解码并执行操作。

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


2
“如果希望执行独立程序(即不使用操作系统),则不得启动计算机以运行操作系统。” 并不完全正确。在DOS之后加载了许多DOS程序,然后完全忽略了DOS服务(通过直接位敲打或直接调用BIOS)。Win3.x是一个很好的示例,(在某些有趣的极端情况中除外)忽略了DOS。Win95 / 98 / Me也这样做。有许多支持独立程序的OS示例,其中许多来自8/16位时代。
埃里克·塔

8
@EricTowers- 大概用“ DOS”来表示MS-DOS(因为我使用了与MS或Intel不相关的DOS)?您所引用的“操作系统”甚至不符合我1970年代关于操作系统概念和设计的大学教科书中的标准。MS-DOS的起源(通过Seattle Computer Products)追溯到CP / M,其作者Gary Kildall明确没有将其称为OS。FWIW允许程序接管系统的OS在管理系统资源的基本功能上失败了。 “有许多支持独立程序的操作系统示例” - “支持”还是无法阻止?
锯末


3
我喜欢GCC的“独立”术语。英文单词具有无需操作系统即可运行的代码的所有正确含义,甚至可能比“独立”更好。例如,您可以编译gcc -O2 -ffreestanding my_kernel.c special_sauce.S以使可执行文件不包含任何常规库或OS的内容。(当然,您通常需要一个链接描述文件才能有效地链接到引导加载程序要加载的文件格式!)
Peter Cordes

4
@PeterCordes“独立”是C标准中使用的术语,可以认为IMO具有一定权威性。或者,也可以
称其

38

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

所有这些都直接在CPU上运行,您只需逐步过渡到CPU即可执行任何操作。

您的程序“需要”内核的方式与需要标准C库的方式完全相同,以便首先使用该printf命令。

程序的实际代码在CPU上运行,但是该代码使在屏幕上打印内容的分支经过C printf函数的代码,其他各种系统和解释器的处理,每个系统都进行自己的处理以找出如何 hello world!实际上被打印在屏幕上。

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

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

  1. 在终端程序中,运行程序以进行打印 hello world!
  2. 终端看到程序已经(通过C输出例程)hello world!写入了控制台
  3. 终端程序上升到桌面窗口管理器说:“我得到了hello world!我写的,你能不能把它放在位置xy好吗?”
  4. 桌面窗口管理器使用“我的一个程序希望您的图形设备在此位置放置一些文本,让它变得花哨!”进入内核。
  5. 内核将请求传递到图形设备驱动程序,以图形卡可以理解的方式对其进行格式化
  6. 根据图形卡的连接方式,需要调用其他内核设备驱动程序以将数据推送到物理设备总线(如PCIe)上,并进行诸如确保选择了正确设备的操作,以及数据可以通过相关桥或转换器
  7. 硬件显示内容。

这仅是描述的过度简化。这是龙。

实际上,您需要硬件访问的所有操作,例如显示,内存块,文件位或类似的东西,必须通过内核中的某些设备驱动程序才能确定出如何与相关设备进行通讯。它是位于SATA硬盘控制器驱动程序之上的文件系统驱动程序,该驱动程序本身位于PCIe桥设备之上。

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

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

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

所有这些都由代码的每一层处理。


不仅硬件访问,程序之间的大多数通信还通过内核。通常至少不涉及内核的部分正在建立更直接的通道。但是,出于这个问题的目的,在更简单的情况下也可以将所有代码压缩为一个程序。
克里斯·斯特拉顿

实际上,您的终端程序甚至不必与向其写入内容的程序在同一台计算机上运行。
jamesqf

由于可能需要在这个问题中明确指出它-请注意,当我们谈论程序彼此“交谈”时,它是隐喻的。
user253751 '18

21

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

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

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

对于大容量存储设备,OS在所有这些文件的顶部提供文件系统驱动程序,该文件系统驱动程序为目录和文件提供相同的接口,无论在何处以及如何实现存储。再次,操作系统担心访问控制和序列化。例如,通常,不应一次打开多个程序来打开同一文件,而不必跳过某些箍(但同时读取通常是可以的)。

因此,在现代通用环境中,是的-您确实需要一个OS。但是即使到了今天,诸如实时控制器之类的计算机并没有足够复杂到需要一台计算机。

例如,在Arduino环境中,实际上没有操作系统。当然,构建环境将一堆库代码合并到其构建的每个“二进制文件”中。但是由于该代码从一个程序到下一个程序没有持久性,因此它不是操作系统。


10

我认为许多答案会误解这个问题,归结为:

编译器输出机器代码。该机器代码是由CPU直接执行还是由内核“解释”?

基本上,CPU直接执行机器代码。让内核执行所有应用程序要慢得多。但是,有一些警告。

  1. 当存在OS时,通常会限制应用程序执行某些指令或访问某些资源。例如,如果应用程序执行了修改系统中断表的指令,则CPU将跳转到OS异常处理程序,以便终止有问题的应用程序。同样,通常不允许应用程序读取/写入设备内存。(即“与硬件对话”。)访问这些特殊内存区域的方法是OS与图形卡,网络接口,系统时钟等设备进行通信的方式。

  2. 操作系统对应用程序的限制是通过CPU的特殊功能来实现的,例如特权模式,内存保护和中断。尽管您可以在智能手机或PC中找到的任何CPU都具有这些功能,但某些CPU没有。这些CPU实际上确实需要“解释”应用程序代码的特殊内核,以实现所需的功能。一个非常有趣的例子是Gigatron,它是8指令计算机,您可以用芯片模拟34指令计算机来构建。

  3. 诸如Java之类的某些语言会“编译”为一种称为Bytecode的东西,而这并不是真正的机器代码。尽管过去将它们解释为运行程序,但如今通常使用一种称为“即时编译”的程序,因此它们最终确实会作为机器代码直接在CPU上运行。

  4. 虚拟机中正在运行的软件,用于要求其机器代码由称为Hypervisor的程序“解释” 。由于行业对VM的巨大需求,CPU制造商在其CPU中添加了诸如VTx之类的功能,以允许来宾系统的大多数指令直接由CPU执行。但是,在虚拟机中运行为不兼容的 CPU 设计的软件时(例如,仿真NES),将需要解释机器代码。


1
尽管Java的字节码通常不是机器代码,但仍然存在Java处理器
罗斯兰

管理程序从来都不是解释器。如果虚拟机是与主机不兼容的指令集,那么当然有必要进行解释,但是对于相同体系结构的执行,甚至早期的虚拟机管理程序也直接在CPU上执行代码(对于半虚拟化的内核,对于没有CPU的CPU,您可能会感到困惑。必要的管理程序支持)。
Toby Speight '18

5

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

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

嗯,这是正确的。如果您的程序是内核模式驱动程序,那么实际上,如果您知道如何与硬件“对话”,则实际上您可以直接访问硬件,但是通常(特别是对于未公开文档或复杂的硬件)人们使用的是内核库驱动程序。通过这种方式,您可以找到知道如何以几乎人类可读的方式与硬件通信的API函数,而无需知道地址,寄存器,时序和其他事物。

该机器代码中的每个指令将直接从内存中执行(一旦代码被操作系统加载到内存中),或者机器代码中的每个命令仍然需要通过操作系统(内核)来执行

好吧,内核是作为服务生的,它的责任是将您带到一张桌子上为您服务。唯一不能做的-为您吃,您应该自己做。与您的代码相同,内核会将程序解压缩到内存中,并将启动您的代码,该代码是由CPU直接执行的机器代码。内核只需要监督您-允许您做什么和不允许您做什么。

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

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

将机器代码加载到内存后会怎样?它会通过内核还是直接与处理器对话。

如果您的代码只是简单的操作码(如两​​个寄存器的加法),那么它将直接由CPU执行而无需内核协助,但是如果您的代码使用库中的函数,则此类调用将由内核协助(例如,如果有女服务员的话),如果您愿意在一家餐馆吃饭,他们会给你工具-叉子,汤匙(仍然是他们的资产),但是您将如何使用-取决于您的“代码”。

好吧,只是为了防止在评论中出现火焰-这确实是过于简化的模型,我希望它可以帮助OP理解基本知识,但是欢迎提出改进此答案的好的建议。


3

因此,当我们编译一个简单的源代码(例如仅使用printf()函数)并且编译生成可执行的机器代码时,该机器代码中的每个指令都将直接从内存中执行(一旦将代码加载到内存中还是通过机器代码中的每个命令仍然需要通过操作系统(内核)来执行?

本质上,只有系统调用进入内核。与I / O或内存分配/取消分配有关的任何事情通常最终都会导致系统调用。某些指令只能在内核模式下执行,并且会导致CPU触发异常。异常会导致切换到内核模式并跳转到内核代码。

内核不会处理程序中的所有指令。它仅执行系统调用并在运行的程序之间切换以共享CPU。

无法在用户模式(没有内核)下进行内存分配,如果您没有权限访问内存,则以前由内核编程的MMU会注意到并导致CPU级别的“分段错误”异常,这会触发内核,并且内核会杀死程序。

如果您访问设备的I / O端口或寄存器或连接到设备的地址(执行任何I / O所需的一个或两个),则无法在用户模式(无内核)下进行I / O。以同样的方式例外。


可执行文件需要OS内核才能运行吗?

取决于可执行文件的类型。

内核除了协调对RAM和硬件的共享访问外,还执行加载程序功能。

许多“可执行格式”,例如ELF或PE,除了代码以及可执行程序的工作之外,在可执行文件中还具有元数据。阅读有关Microsoft PE格式的详细信息,以获取更多信息。

这些可执行文件还引用库(Windows .dll或Linux共享对象.so文件)-必须包含它们的代码。

如果您的编译器生成了要由操作系统加载程序处理的文件,并且该加载程序不存在,则它将无法正常工作。

  • 您可以包含执行加载程序工作的代码吗?

当然。您需要说服操作系统以某种方式运行原始代码,而不处理任何元数据。如果您的代码调用内核API,它将仍然无法正常工作。

  • 如果不调用内核API怎么办?

如果您以某种方式从操作系统加载了该可执行文件(即,如果它允许加载并执行原始代码),它将仍然处于用户模式。如果您的代码访问的是用户模式下禁止的内容,而不是内核模式下访问的内容,例如未分配的内存或I / O设备地址/寄存器,则它将因特权或段冲突而崩溃(同样,异常进入内核模式并得到处理在那里)仍然无法正常工作。

  • 如果从内核模式运行该怎么办。

然后它将起作用。



这并不完全正确。硬件访问必须通过内核,甚至需要有内核,这一要求是当今许多系统上肯定的设计决定,但在许多简单系统上(甚至是今天)都是负面的决定。
克里斯·斯特拉顿

我在解释如果A)有一个内核,B)如果您在具有用户/主管模式和MMU的CPU上运行代码,则情况如何。是的,有不带MMU或用户/管理程序模式的CPU和微控制器,是的,某些系统在不使用整个用户/管理程序基础结构的情况下运行。微软的第一个Xbox就是这样-即使从用户的角度来看,具有用户/管理者模式的标准x86 CPU,据我所知,它永远不会离开内核模式-加载的游戏可以做任何想要的事情。
LawrenceC

1
在MacOS X之前的Macintosh系统是一台通用计算机的操作系统,在通用CPU(68000家族,PowerPC)上运行,几十年来一直支持内存保护(我认为第一台基于68000的计算机除外)从未使用过内存保护:任何程序都可以访问内存中的任何内容。
curiousguy18年

3

TL; DR号

在没有OS的当前环境中,人们想到了Arduino开发。相信我,在其中一个婴儿中,您没有足够的空间来安装操作系统。

同样,世嘉创世纪的游戏也没有世嘉提供的操作系统可供调用。您只是用68K汇编程序制作了游戏,直接将其写入裸机。

或在我咬牙的地方,在Intel 8051上进行嵌入式工作。同样,当您拥有的是2716 eprom且占地面积为2k * 8时,您就没有足够的操作系统空间了。

当然,这假定单词application的使用非常广泛。作为一个反问,应该问自己一个Arduino草图是否真的是一个应用程序。


3

尽管我不想暗示其他答案本身并不是正确的,但是它们提供的细节太多了,恐怕您还是很不清楚。

基本答案是该代码将直接在处理器上执行。不,机器代码不会与任何人“交谈”,反之亦然。处理器是活动的组件,您在计算机上所做的一切都将由该处理器完成(我在这里简化了一些步骤,但是现在还可以)。处理器将读取并执行代码并吐出结果,而机器代码只是处理器的食物。

您的困惑源于对“硬件”一词的使用。尽管该部门没有以前那样明确,但最好是从外围设备的角度考虑,而不是简单地将所有硬件称为硬件。因此,如果您的计算机上装有操作系统或类似操作系统,则程序必须使用其服务来访问外围设备,但是处理器本身不是外围设备,它是程序直接在其上运行的主要处理单元。

内核,操作系统和类似的中间层通常仅在较大的系统中使用,这些系统会运行多个程序,并且需要系统来管理这些程序如何使用计算机的外围设备(通常在同时)。在这些情况下,正在运行的程序只能使用将决定如何共享它们并确保没有冲突的系统访问这些外围设备。在竞争性程序之间无需进行任何管理的小型系统,因为它们根本不需要,也常常根本没有底层系统,并且通常在这些系统上运行的单个程序或多或少地可以自由地对外围设备执行所需的操作。


2

开机时在计算机上运行的BIOS是存储在ROM中的可执行代码。它由机器指令和数据组成。有一个编译器(或汇编器)可从源代码汇编此BIOS。这是特例。

其他特殊情况包括引导程序加载内核和内核本身。这些特殊情况通常使用C ++以外的语言进行编码。

在一般情况下,让编译器生成一些指令来调用由内核或库例程提供的系统服务,这是更为实际的做法。它使编译器更加轻巧。这也使编译后的代码更轻便。

另一端是Java。在Java中,编译器不会将源代码转换为机器指令,因为通常会理解该术语。取而代之的是,将源代码转换为虚构的机器(称为Java虚拟机)的“机器指令”。在Java程序可以运行之前,必须将其与Java运行时结合使用,该Java运行时包括Java虚拟机的解释器。


2

在过去的好日子里,您的程序负责执行程序执行过程中需要做的所有事情,要么您自己执行,要么添加其他人向程序中编写的库代码。在计算机上运行的唯一一件事就是在编译后的程序中读取的代码-如果幸运的话。有些计算机必须先通过交换机输入代码,然后才能执行更多操作(原始的“引导”过程),甚至整个程序都以这种方式输入。

很快发现,运行代码能够加载和执行程序真是太好了。后来发现,计算机具有强大的功能,可以通过在它们之间切换CPU来支持同时运行多个程序,特别是在硬件可以帮助的情况下,但是程序的复杂性增加,彼此之间无法相互踩踏(例如,如何处理试图将数据一次发送到打印机的多个程序?)。

所有这些导致大量帮助程序代码从标准程序中移出各个程序,并移入“操作系统”,并且从用户程序中调用了帮助程序代码。

那就是我们今天的位置。您的程序可以全速运行,但是每当它们需要由操作系统管理的内容时,它们就会调用由操作系统提供的帮助程序例程,并且该代码不是必需的,也不存在于用户程序本身中。这包括写入显示器,保存文件,访问网络等。

已经编写了微内核,它们提供了给定程序在没有完整操作系统的情况下运行所需的功能。这对于有经验的用户而言具有一些优势,同时还可以提供其他大多数优势。如果您想了解更多信息,则可能需要阅读有关它的Wikipedia页面-https: //en.wikipedia.org/wiki/Microkernel

我尝试了一种能够运行Java虚拟机的微内核,但后来发现,最吸引人的地方是Docker。


1

在典型的台式机操作系统中,内核本身是可执行文件。(Windows具有ntoskrnl.exe; Linux具有vmlinux等等。)如果您需要内核才能运行可执行文件,则这些操作系统可能不存在。

您需要一个内核来执行内核要做的事情。允许多个可执行文件一次运行,在它们之间进行裁判,对硬件进行抽象处理等。大多数程序无法自己胜任地完成这些工作,即使可能,您也不希望它们这样做。在DOS时代(几乎不能称为操作系统本身),游戏使用OS的方式几乎不同于加载器,并且直接访问硬件的方式就像内核一样。但是在购买游戏之前,您通常必须知道计算机中装有哪些品牌和型号的硬件。许多游戏仅支持某些系列的视频和声卡,如果完全起作用,则在竞争品牌上的表现非常差。那'

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.