应用程序如何在没有OS的情况下运行?如果没有操作系统要运行,如何告诉计算机运行C语言并在屏幕上执行这些命令?
它与UNIX内核有关吗? ?如果是这样,什么是Unix内核,或者是一般的内核?
我确定操作系统比这更复杂,但是它如何工作?
#1 楼
有很多网站需要进行引导(例如,计算机如何引导)。简而言之,它是一个多阶段的过程,一次可以建立一点系统,直到最终可以启动OS进程。它从主板上的固件开始,该固件试图启动并运行CPU。然后,它会加载BIOS,就像迷你操作系统一样,它可以启动并运行其他硬件。完成后,它将查找引导设备(磁盘,CD等),一旦找到,它将找到MBR(主引导记录)并将其加载到内存中并执行它。然后,这小段代码就知道如何初始化和启动操作系统(或其他启动加载程序,因为事情变得越来越复杂)。在这一点上,诸如内核之类的东西将被加载并开始运行。
它完全可以工作真是令人难以置信!
评论
+1为最后一句话。
–用户
2012年10月24日在9:23
有一个原因叫它“启动”。该术语是“自举”的缩写,例如“被引导者拉起”。
– KeithS
2012年10月24日15:37
曾经有一段时间,有人必须键入或切换引导代码。有时,这只是简单地跳转到ROM中程序的第一条指令。其他时候,它是从设备读取并跳转到读取数据中的第一条程序指令的代码。现在事情变得简单得多。
–BillThor
2012年10月25日的1:07
另请参见ibm.com/developerworks/linux/library/l-grub2/…
– Tobias Kienzler
2012年10月25日在6:12
@BillThor:当然,“简单得多”是指“复杂得多”。它们更易于使用。
–拉斐尔·施维克(Raphael Schweikert)
2012年10月25日7:56
#2 楼
“裸机”操作系统无法在任何程序中运行。它在物理机上运行完整的指令集,并可以访问所有物理内存,所有设备寄存器和所有特权指令,包括那些控制虚拟内存的硬件支持的硬件指令。(如果运行系统在虚拟机上运行,可能会认为与上述情况相同。不同之处在于某些事物是由虚拟机监控程序模拟或以其他方式处理的,即运行虚拟机的级别。 />
无论如何,尽管操作系统可能是用(例如)C语言实现的,但它并没有提供所有正常的C库。特别是,它将没有普通的“ stdio”库。相反,它将实现(例如)允许其读取和写入磁盘块的磁盘设备驱动程序。它将在磁盘块层的顶部实现一个文件系统,并在其之上实现用户应用程序的运行时库为创建,读取和写入文件等而进行的系统调用。
在没有操作系统的情况下如何运行应用程序?
它必须是一种特殊的应用程序(例如操作系统),它知道如何直接与I / O硬件等进行交互。
如果没有操作系统要运行,如何告诉计算机运行C语言并在屏幕上执行这些命令?
您不要。
应用程序(为了用C语言编写的参数而已)在其他计算机上编译和链接以提供本机代码映像。然后将映像写入BIOS可以找到位置的硬盘驱动器。 BIOS将映像加载到内存中,并执行一条指令以跳转到应用程序的入口点。
除非它是一个成熟的操作系统,否则在应用程序中(通常)没有任何“运行C和执行命令”。在这种情况下,操作系统有责任实施所有必需的基础架构以实现这一目标。没魔术只需很多代码即可。
Bill的答案涵盖了引导程序,即从关机的计算机转到正常运行的操作系统的过程。但是,值得注意的是,当BIOS完成其任务时,它(通常)将对硬件的完全控制权交给了主要操作系统,并且不再发挥任何作用-直到下一个系统重新启动为止。主操作系统肯定不是按常规意义在BIOS中“运行”的。
它与UNIX内核有关吗?如果是这样,什么是unix内核,或者一般来说是内核?
是的。
UNIX内核是UNIX操作系统的核心。它是UNIX的一部分,它执行上述所有“裸机”工作。
“内核”的想法是,您尝试将系统软件分离为核心内容(这需要物理上的设备访问权限,所有内存等)和非核心内容。内核由核心内容组成。
实际上,内核/内核与非内核/非内核之间的区别要复杂得多。关于什么真正属于内核,什么不属于内核,存在很多争论。 (例如,查看微内核。)
评论
现象答案。如果可以的话,我会再给您几个投票。
–weberc2
2012年10月24日12:24
这里有很多很好的答案,所以我将其添加为注释,因为到目前为止还没有人提及:OS的关键功能之一是允许多个应用程序从用户的角度“同时”执行。调度进程并保护进程以免改变彼此的行为的能力通常仅在OS中存在,而在固件或BIOS中则没有。
– Sean Barbeau
2012年10月24日14:14
“内核”的想法是,您尝试将系统软件分离为核心内容,并注意到“核心”一词来自德语“ Kern”,即核心/核心,因此容易记住。
–deed02392
2012年10月25日9:36
喜欢这里的答案,因为它提到它是运行在非C的编译和链接二进制代码。
–特拉维斯·佩塞托(Travis Pessetto)
2012年10月25日14:33
“摆脱这一点会使PC用户变得不那么聪明。” -不少聪明...少一些计算机知识。您也可以说这增加了PC用户的数量。
– Stephen C
2012年10月26日4:20
#3 楼
一开始,CPU没电。人说“放电”,CPU开始从内存中的给定地址读取并执行其中存在的指令。然后下一个,依此类推,直到电源关闭。
这是启动。它的任务是加载另一软件来访问主软件所在的环境,然后加载它。
最后,一个友好的屏幕邀请您登录。<br />
评论
该答案应移至christianity.stackexchange.com
–库米
2012年10月24日在8:12
“给定地址”是什么来源很抱歉在这里扮演Charles Darwin。
– Midhat
2012年10月24日9:58
@Midhat-要在内部硬连接CPU要提取的第一个地址。通常为0。
–mouviciel
2012年10月24日上午10:21
...在第7天,该名男子休息了他的游戏
–加拿大卢克
2012年10月24日19:25
@mouviciel对于任何x86兼容体系结构,内存地址均为0x7C00,并且首先必须由BIOS填充,该BIOS通常会加载它喜欢的任何可启动设备的第一个扇区...不错的答案:-7
– Tobias Kienzler
2012年10月25日在6:10
#4 楼
很抱歉,迟到了,但我将这样描述:主板上电了。
计时电路在必要时会启动并稳定,仅基于它们的电气特性。某些较新的设备实际上可能使用非常有限的微处理器或音序器。
应该注意的是,诸如“时序电路在必要时启动并稳定”之类的很多事情都不会真正发生在硬件上了。实际上,大量的工作是在非常有限的子处理器/定序器上运行的非常专业的软件。
-jkerian在10月25日5:20
CPU从BIOS加载(基于其内部接线)数据。在某些计算机上,BIOS可能会镜像到RAM,然后从那里执行,但是这种情况很少发生在IIRC中。
打开后,x86兼容CPU从该地址中的地址0xFFFFFFF0开始空格...
-Micheal Steil,在Xbox安全系统中犯了Microsoft错误(存档)17错误
BIOS调用使用的硬件端口和地址通过主板来处理磁盘和其他硬件IO,并旋转磁盘,使RAM的其余部分正常工作。
BIOS代码(通过CMOS设置存储在硬件中)使用低级IDE或SATA命令读取每个磁盘的引导扇区,顺序由CMOS指定或用户通过菜单覆盖。
第一个具有引导扇区的磁盘将执行其引导扇区。此引导扇区是Assembly,具有从磁盘加载更多数据,加载较大的
NTLDR
,GRUB
的后续阶段等的指令。最后,OS机器码由引导加载程序直接或通过链加载加载间接执行备用或偏移位置的引导扇区。
然后,您会遇到友好的内核恐慌,令人窒息的企鹅,或者由于磁头崩溃而使磁盘停止运转。 =)在替代方案中,您的内核将设置进程表,内存结构,并装入磁盘,装入驱动程序,模块和GUI或一组服务(如果在服务器上)。然后,在读取程序标头的同时执行程序,并将程序集放入内存并进行相应映射。
评论
应当指出,诸如“时序电路在必要时启动并稳定”之类的事情在硬件中不再真正发生。实际上,大量的工作是在非常有限的子处理器/定序器上运行的非常专业的软件。 -友好的社区固件工程师
– jkerian
2012年10月25日5:20
@jkerian您介意我在您的帖子中引用了您的评论吗?
– Nanofarad
2012年11月1日,11:26
嘿,一点也不。
– jkerian
2012年11月1日14:53
BIOS不是操作系统。 BIOS是Basic Input / Output System(基本输入/输出系统)的简写,这就是BIOS的功能。它允许程序员使用制造商提供的驱动程序来使用低级资源。当OS进入“保护”(32位)或“长”(64位)模式时,BIOS不再可用,并且OS使用其自己的驱动程序,该驱动程序基本上替代了BIOS在“较低”级别上提供的功能。现代操作系统(例如Linux和Windows)仅将BIOS用于检测可用的RAM部分并加载自己的更高级的加载程序,这些加载程序可以加载所需的驱动程序。
–汉斯(Hannes Karppila)
16-2-23在23:18
@HannesKarppila更新;现在大约四岁了,我不再活跃于此站点上。
– Nanofarad
16年2月24日在1:23
#5 楼
有很多好的答案,但我想补充一下:您提到您来自Python背景。 Python是一种n解释(或“嵌入”或类似的东西,至少在典型的CPython用例中)语言。这意味着您需要其他一些软件(Python解释器)来查看源代码并以某种方式执行它。这是一个很好的模型,并允许从实际硬件中很好地抽象出相当不错的高级语言。缺点是,您始终总是首先需要此解释器软件。此类解释器软件通常以编译为机器代码的语言编写,例如C或C ++。机器代码是CPU可以处理的。 CPU可以执行的操作是从内存中读取一些字节,然后根据字节值开始特定的操作。因此,一个字节序列是一个命令,用于将内存中的某些数据加载到寄存器中,另一个序列用于将两个值相加,另一个序列用于将寄存器中的值存储回主存储器中,并且很快(寄存器是一个特殊的存储区域, (最能发挥作用的CPU)),大多数命令在该级别上都非常低。这些机器代码指令的人类可读性是汇编代码。基本上,此机器代码是Windows或Linux / Unix二进制文件中.exe或.com文件中存储的内容。
现在,如果计算机启动起来很笨,那么它就有一些接线,尽管接线会读取此类机器代码说明。在PC上,这通常(当前)是主板上的EEPROM芯片,其中包含BIOS(基本输入输出系统),该系统不能做很多事情,可以简化对某些硬件的访问等操作,然后执行关键操作:引导并将前几个字节(也称为主引导记录,MBR)复制到内存中,然后告诉CPU“这里有您的程序”,然后CPU将把那里的字节当作机器代码处理并执行。通常,这是一些操作系统加载程序,它将通过一些参数加载内核,然后将控制权移交给该内核,然后该内核将加载其所有驱动程序以访问所有硬件,加载某些桌面或Shell程序或其他任何东西,并允许用户登录并使用系统。
评论
“插人”?我以前从未听过这个词。
–布莱恩·奥克利(Bryan Oakley)
2012年10月25日14:12
该术语在大约5年前就已经使用了很长时间,用于描述“现代”解释器,该解释器具有与执行不同的独特编译阶段。不知道这个词是否能在任何地方幸存;-)
–约翰内斯
2012年10月26日在9:15
“被解释”?我以前从未听过这个词。
–科尔·约翰逊(Cole Johnson)
14年8月22日在21:55
#6 楼
您询问“如何在没有操作系统的情况下运行应用程序”。简单的答案是“操作系统不是应用程序”。可以使用与应用程序相同的工具来创建OS,并使用相同的原材料制成一个OS,但它们并不是同一回事。操作系统不必遵循与应用程序相同的规则。OTOH,您可以将实际的硬件和固件视为运行操作系统“应用程序”的“操作系统”。硬件是一个非常简单的操作系统-它知道如何运行用机器代码编写的指令,并且知道在启动时应该为第一个指令查看非常特定的内存地址。因此,它将启动,然后立即运行第一个指令,然后执行第二个指令,依此类推。
因此,OS只是存在于已知位置的机器代码,可以直接与硬件进行交互。
评论
+1我认为这是最好的答案。在抽象方面,我认为您正在将其固定在正确的水平上。
– Preet Sangha
2012年10月25日19:43
#7 楼
要回答您的问题,需要了解本机(用于CPU)代码的外观以及如何由CPU解释。通常,整个编译过程都基于翻译您用C,Pascal或甚至Python(使用pypy)和C#都可以被CPU理解,例如,简单的指令,例如“将某些内容存储在[内存地址]下”,“添加存储在寄存器eax和ebx下的数字”,“调用函数foo”,“将eax与10进行比较” 。这些指令一个接一个地执行,可以完成您想对代码执行的操作。
现在考虑一下:您真的不需要操作系统来执行此本地代码!您所需要做的就是将该代码加载到内存中,并告诉CPU它在那里并且您希望它被执行。不过,不要对此太在意。这是BIOS应该担心的工作-它在CPU启动后立即在物理地址0x7C00下加载代码(仅一个扇区和一个扇区)。然后,CPU开始执行代码的这一扇区(512 B)。您可以做任何您想像的!当然,没有操作系统的任何支持。那是因为您是操作系统。酷吧?没有标准库,没有提升,没有python,没有程序,没有驱动程序!您必须自己编写所有内容。
以及如何与硬件通信?好吧,您有两种选择:
您停留在“实模式”下-CPU执行模式只有1 MB的内存(甚至更少),没有高级的CPU功能,例如CPU扩展,内存保护,多任务处理; 16位可执行代码,古老的寻址模式...但是BIOS提供了一些例程,包括简单的屏幕输出,键盘支持,磁盘I / O和电源管理。一言以蔽之,您又回到了MS-DOS和16位CPU的时代。
您将进入“保护模式”,其中包含您的CPU具有的所有功能,已安装的所有内存等。但是在保护模式下,您完全是一个人,您必须自己做所有事情(并且您使用“输入”和“输出”指令与硬件进行通信,以将数据输入/输出到I / O端口并使用中断。 / O)。我必须说从Windows 95到第一个Linux以来的每个OS都选择此选项吗?
现在您要问的是什么内核。
很快,内核就是您看不到的所有内容,直接体验。它可以管理所有内容以及驱动程序,从键盘到PC内几乎所有硬件。您可以通过图形外壳或终端与之通信。或者幸运的是,通过代码内部的函数(现在可以在操作系统的支持下执行)。
为了更好地理解,我可以给您一个建议:尝试编写自己的OS。即使会在屏幕上写“ Hello world”。
#8 楼
操作系统的运行方式存在一些差异,这完全取决于系统。为了有用,系统在启动时需要具有一些可预测的行为,例如“从地址X开始执行”。对于将非易失性存储(例如闪存)映射到程序空间的系统,这很容易,因为您只需确保将启动代码放在处理器程序空间内的正确位置即可。这对于微控制器是极为普遍的。一些系统在执行之前必须从其他位置检索启动程序。这些系统将有一些操作硬连线(或几乎硬连线)到其中。有些处理器通过i2c从另一块芯片检索其启动代码,因此该处理器无需执行任何汇编指令即可执行非平凡的操作,然后在预定的地址处开始执行指令。x86系列处理器通常使用多阶段启动过程,由于其演进和向后兼容性问题,该过程相当复杂。系统执行主板上某些非易失性内存中的某些固件(称为BIOS-基本输入/输出系统,或类似的固件)。有时,部分或全部固件被复制(重定位)到RAM中,以使其执行更快。编写此代码时,要了解会出现什么硬件以及可用于引导的硬件。
通常,在编写启动固件时会假设系统上将配备哪些硬件。多年前,在一台286机器上,可能会假设在I / O地址X处将有一个软盘驱动器控制器,并且如果给出了一组特定的命令(以及扇区0处的代码),则会将扇区0加载到某个内存位置。知道如何使用BIOS自身的功能来加载更多代码,并最终加载了足以成为操作系统的代码)。在微控制器上,可能会假设存在一个以某些设置运行的串行端口,在继续引导过程之前,它应该等待命令X(以更新更复杂的固件)的时间量。
给定系统的确切启动过程对您来说并不重要,因为知道不同系统上的启动过程不同,但是它们都有共同点。通常在启动(引导)代码中,当需要完成I / O时,将轮询I / O设备,而不是依赖中断。这是因为中断很复杂,使用堆栈RAM(可能尚未完全设置),并且当您是唯一的操作时,您不必担心会阻塞其他操作。
先上加载OS内核(内核是大多数OS的主要部分)后,最初会像固件一样工作。它需要通过编程来了解或发现存在的硬件,将一些RAM设置为堆栈空间,进行各种测试,设置各种数据结构,可能发现并安装文件系统,然后可能启动一些更多的程序。就像您习惯于编写的程序(依赖于现有OS的程序)。
OS代码通常以C和汇编语言混合编写。 OS内核的第一个代码很可能始终在汇编中,并且执行诸如设置堆栈(C代码所依赖)然后调用C函数的操作。其他手写汇编也将在那里,因为OS所需执行的某些操作通常无法用C表示(例如上下文切换/交换堆栈)。通常,必须将特殊标志传递给C编译器,以使其不依赖大多数C程序使用的标准库,并且不要期望程序中包含
int main(int argc, char *argv[])
。此外,还必须使用大多数应用程序程序员从未使用过的特殊链接器选项。这些可能使内核程序期望被加载到某个地址或设置某些东西看起来像在某些位置存在外部变量,即使这些变量从未在任何C代码中声明过(这对于内存映射的I / O或其他特殊的内存位置)。最初,整个操作看起来像魔术,但是当您研究并理解其中的一部分后,魔术变成了一组程序,需要更多的计划和系统实施知识。但是,调试它们需要魔术。
#9 楼
要了解操作系统的工作方式,将它们分为两类可能会有所帮助:一类仅根据请求向应用程序提供服务,另一类使用CPU中的硬件功能阻止应用程序执行不应做的事情。 MS-DOS是以前的样式。从3.0版开始,所有版本的Windows都是后一种风格(至少在运行比8086更强大的功能时)。以前的“ OS”风格。如果应用程序希望在屏幕上显示一个字符,则有几种方法可以实现。它可以调用例程,该例程将要求MS-DOS将其发送到“标准输出”。如果这样做,MS-DOS将检查输出是否被重定向,否则,它将调用存储在ROM(在IBM称为“基本输入/输出系统”的例程的集合中)中的例程,该例程将在屏幕上显示一个字符。光标位置并移动光标(“写电传打字机”)。然后,该BIOS例程将在0xB800:0到0xB800:3999范围内的某个位置存储一对字节;彩色图形适配器上的硬件将重复获取该范围内的字节对,使用每对的第一个字节选择字符形状,第二个字节选择前景色和背景色。字节被提取并处理为红色,绿色和蓝色信号,其顺序可产生清晰的文本显示。IBM PC上的程序可以使用DOS“标准输出”例程,或使用BIOS“写电传打字”例程,或将其直接存储到显示内存中来显示文本。许多需要显示大量文本的程序很快选择了后一种方法,因为它的速度实际上是使用DOS例程的数百倍。这不是因为DOS和BIOS例程效率极低。除非显示为空白,否则只能在特定时间写入。设计用于输出字符的BIOS例程,因此可以随时调用它;因此,每个请求都必须重新开始,以等待正确的时间来执行写操作。相比之下,知道需要做什么的应用程序代码可以根据可能的机会来组织自己编写显示的内容。
这里的关键是,尽管DOS和BIOS提供了一种将文本输出到显示器的方法。在显示器上,没有任何关于这种能力的特别“神奇”的东西。至少在显示硬件按应用程序预期的方式工作的情况下,想要向显示器写入文本的应用程序也可以同样有效(如果有人安装了单色显示器适配器,该显示器类似于CGA但具有字符存储功能) (位于0xB000:0000-0xB000:3999),BIOS会在此自动输出字符;被编程为与MDA或CGA一起使用的应用程序也可以这样做,但是仅为CGA编程的应用程序在MDA上将完全没有用。)
在较新的系统上,情况有所不同。处理器具有各种“特权”模式。它们以最特权的模式开始,在该模式下,代码可以执行所需的任何操作。然后,他们可以切换到受限模式,在该模式下,只有选定范围的内存或I / O功能可用。代码无法直接从受限模式切换回特权模式,但是处理器已定义了特权模式入口点,并且受限模式代码可以要求处理器在特权模式下的那些入口点之一处开始运行代码。此外,还有特权模式入口点与一些在受限模式下禁止的操作相关联。例如,假设某人想要同时运行多个MS-DOS应用程序,每个应用程序都有自己的屏幕。如果应用程序可以直接在0xB800:0处写入显示控制器,则无法阻止一个应用程序覆盖另一应用程序的屏幕。另一方面,操作系统可以在受限模式下运行该应用程序,并在对显示内存的任何访问中陷入陷阱。如果发现某个应在“后台”中的应用程序正在尝试写入0xB800:160,则它可以将数据存储到它留作后台应用程序屏幕缓冲区的某个内存中。如果以后将该应用程序切换到前台,则可以将缓冲区复制到实际屏幕。
要注意的关键是(1)尽管拥有一组标准的例程来执行各种标准服务(例如显示文本)通常很方便,但是它们并没有执行以“特权模式”运行的应用程序无法执行的任何操作是否已正确编程以处理已安装的硬件; (2)尽管当今运行的大多数应用程序都将被其操作系统直接阻止执行此类I / O,但以特权模式启动的程序可以执行所需的任何操作,并且可以为受限模式设置所需的任何规则程序。
#10 楼
正如斯蒂芬·C(Stephen C.)所说,这不仅是要启动操作系统,还在于它如何运行,如何与硬件以及与其之上的软件进行交互。我将添加到他的回答是,您可能想看一看“计算系统的元素”。这是一本书和一些工具,解释了计算机,操作系统和编译器之间的交互方式。它的独特之处在于,它为您提供了在模拟环境中非常快速地开发自己的操作系统的工具,而忽略了一个真实环境所需的许多细节,因此您可以掌握这些概念。
如果想进一步了解操作系统与硬件的交互方式,请查看Minix。
#11 楼
您编写了一个OS。它必须以某种方式运行,并且这样才能在另一个OS中运行吗?
您的应用程序正在OS中运行。该操作系统为您的应用程序提供服务,例如打开文件并向其中写入字节。这些服务通常是通过系统调用提供的。
操作系统在硬件中运行。硬件为操作系统提供服务,例如设置串行端口的波特率并向其中写入字节。这些服务通常是通过内存映射寄存器或I / O端口提供的。
给出一个非常简单的示例,说明其工作方式:
您的应用程序告诉操作系统向文件中写入内容。操作系统为您的应用程序提供了文件和目录之类的概念。
在硬件上,这些概念不存在。硬件提供了一些概念,例如将磁盘划分为512字节的固定块。操作系统决定要为您的文件使用哪些块,以及其他一些元数据块,例如文件名,大小和磁盘上的位置。然后,它告诉硬件:将这512个字节与此编号的扇区写入具有该编号的磁盘;将其他512个字节以相同的编号写入具有相同编号的磁盘扇区;等等。
操作系统告诉硬件执行此操作的方式变化很大。操作系统的功能之一是保护应用程序免受这些差异的影响。对于磁盘示例,在一种硬件上,操作系统将必须将磁盘和扇区号写入I / O端口,然后将字节一一写入单独的I / O端口。在另一种硬件上,操作系统将必须将整个512字节的扇区复制到一个内存区域,将该内存区域的位置写入一个特殊的内存位置,然后将磁盘和扇区号写入另一个特殊的内存位置。
当今的高端硬件极其复杂。提供所有编程详细信息的手册是数千页的门挡;例如,最新的英特尔CPU手册有7卷,总共超过4000页-仅适用于CPU。大多数其他组件都公开内存或I / O端口块,操作系统可以告诉它们将CPU映射到其地址空间内的地址。这些组件中的几个组件在几个I / O端口或内存地址后面暴露了更多东西。例如,RTC(实时时钟,在计算机关闭电源时保持其时间的组件)在一对I / O端口后面暴露了数百字节的内存,这是一个非常简单的组件,其历史可以追溯到原始PC / AT。诸如硬盘之类的东西具有完全独立的处理器,操作系统通过标准化命令与之进行对话。 GPU甚至更加复杂。
上面的评论中有几个人建议使用Arduino。我同意他们的观点,这很容易理解-ATmega328手册只有几百页,该手册除了将USB连接器公开为串行端口外,在Arduino Uno上具有所有功能。在Arduino上,您可以直接在硬件上运行,而两者之间没有操作系统。只是一些小的库例程,如果您不想的话就不必使用。
#12 楼
可运行的示例从技术上讲,没有OS的程序就是OS。因此,让我们看看如何创建和运行一些小型的hello world操作系统。
以下所有示例的代码均在该GitHub存储库中提供。
引导扇区
在x86上,您可以最简单,最低级别的操作要做的是创建一种主引导扇区(MBR),它是一种引导扇区,然后将其安装到磁盘上。
在这里,我们用一个
printf
调用创建一个:printf '4%509s52' > main.img
sudo apt-get install qemu-system-x86
qemu-system-x86_64 -hda main.img
结果:
QEMU 2.11.1在Ubuntu 18.04上进行了测试。
main.img
包含以下内容:4
以八进制表示== 0xf4
以in hex:hlt
指令的编码,告诉CPU停止工作。因此我们的程序将不执行任何操作:仅启动和停止。
我们使用八进制,因为POSIX未指定
\x
十六进制数字。我们可以通过以下方式轻松获得此编码:
echo hlt > a.asm
nasm -f bin a.asm
hd a
,但
0xf4
编码也记录在文档中当然是英特尔手册。%509s
产生509个空格。需要填写文件,直到字节510。八进制的
52
== 0x55
,后跟0xaa
:硬件所需的魔术字节。它们必须分别是511和512字节。如果不存在,则硬件不会将其视为可引导磁盘。屏幕上已经打印了几个字符。这些由固件打印,并用于识别系统。
在真实的硬件上运行
乳化器很有趣,但是硬件才是真正的交易。
但是请注意,这样做很危险,您可能会误擦磁盘:仅在不包含关键数据的旧机器上这样做!甚至更好的是Raspberry Pi之类的开发板,请参见下面的ARM示例。
对于典型的笔记本电脑,您必须执行以下操作:
将图像刻录到USB记忆棒(会破坏您的数据!):
sudo dd if=main.img of=/dev/sdX
将USB插头插入计算机
将其打开
告诉它从USB引导。
固件会在硬盘之前选择USB。
如果这不是计算机的默认行为,请在开机后继续按Enter,F12,ESC或其他类似的奇怪键,直到出现启动菜单为止您可以选择从USB引导。
通常可以在那些菜单中配置搜索顺序。例如,在我旧的Lenovo Thinkpad上T430,UEFI BIOS 1.16,我可以看到:
Hello world
现在我们已经做了一个最小的程序,让我们开始吧进入一个问候世界。
显而易见的问题是:IO如何做?一些选项:
询问固件,例如BIOS或UEFI,如果对我们来说是
VGA:写入时将特殊的内存区域打印到屏幕上。可以在保护模式下使用。
编写驱动程序并直接与显示硬件对话。这是执行此操作的“正确”方法:功能更强大,但更复杂。
串行端口。这是一个非常简单的标准化协议,可以从主机终端发送和检索字符。
源。
它没有公开大多数现代笔记本电脑,但这是开发板的常用方法,请参见下面的ARM示例。
这实在令人遗憾,因为此类接口对于调试Linux内核确实很有用。 br />
使用芯片的调试功能。例如,ARM称其为半主机。在实际硬件上,它需要一些额外的硬件和软件支持,但是在仿真器上,它可以是免费的便捷选择。 Example。
这里我们将做一个BIOS示例,因为它在x86上更简单。但是请注意,这不是最可靠的方法。
main.S
.code16
mov $msg, %si
mov SECTIONS
{
. = 0x7c00;
.text :
{
__start = .;
*(.text)
. = 0x1FE;
SHORT(0xAA55)
}
}
x0e, %ah
loop:
lodsb
or %al, %al
jz halt
int gcc -c -g -o main.o main.S
ld --oformat binary -o main.img -T linker.ld main.o
x10
jmp loop
halt:
hlt
msg:
.asciz "hello world"
link.ld
enter a character
got: a
new alloc of 1 bytes at address 0x0x4000a1c0
enter a character
got: b
new alloc of 2 bytes at address 0x0x4000a1c0
enter a character
组装并链接:
q4312078q
结果:
测试于:Lenovo Thinkpad T430,UEFI BIOS 1.16。在Ubuntu 18.04主机上生成的磁盘。
除了标准的userland组装说明,我们还有:
.code16
:告诉GAS输出16位代码cli
:禁用软件中断。这些可能会使处理器在hlt
int --oformat binary
x10
之后重新开始运行:进行BIOS调用。这是一个一个地打印字符的方法。重要的链接标志是:
main
:输出原始二进制汇编代码,不要像普通用户级可执行文件一样,将其扭曲在ELF文件中。使用C代替汇编
由于C编译为汇编,因此使用不带标准库的C是非常简单,您基本上只需要:
链接器脚本即可将内容正确地存储在内存中
告诉GCC不要使用标准库的标志
一个很小的程序集入口点,它为
printf
设置了必需的C状态,特别是:调零BSS
TODO:在GitHub上链接一些x86示例。这是我创建的一个ARM。
但是,如果您想使用标准库,事情会变得更加有趣,因为我们没有Linux内核,该内核实现了许多C标准库。通过POSIX实现功能。
有几种可能,而无需使用像Linux这样的功能强大的OS,包括:
Newlib
以下示例的详细示例:https://electronics.stackexchange.com/questions/223929/c-standard-libraries-on-bare-metal/223931
在Newlib中,您必须实现syscall会自己调用,但是您得到的系统非常简单,实现起来非常容易。
例如,您可以将
exit()
重定向到UART或ARM系统,或者使用半主机实现in
。嵌入式操作系统,例如FreeRTOS和Zephyr。
此类操作系统通常允许您关闭抢先式调度,因此可以完全控制程序的运行时间。
它们可以看作是一种预先实现的Newlib。
ARM
在ARM中,总体思路是相同的。我已经在GitHub上上传了一些
一些简单的QEMU裸机示例。 tips.c示例从您的主机终端获取输入,并通过模拟的UART返回所有输出:
q4312078q
另请参见:https://stackoverflow.com/questions / 38914019 /如何制作裸机臂程序并在qemu上运行它们/ 50981397#50981397
:https://github.com/cirosantilli/raspberry-pi-bare-metal-blinker
另请参见:https://stackoverflow.com/questions/ 29837892 /如何在Raspberry-pi上没有操作系统的情况下运行ac程序/ 40063032#40063032
对于Raspberry Pi,https:// github.com/dwelch67/raspberrypi看起来像今天最受欢迎的教程。
与x86的一些区别包括:
IO由以下人员完成直接写入魔术地址,没有
out
和-hda
指令。 这称为内存映射IO。对于某些真正的硬件(例如Raspberry Pi),您可以自己将固件(BIOS)添加到磁盘映像中。
这是一件好事,因为它使更新固件更加透明。
固件
实际上,您的启动行业不是在系统CPU上运行的第一个软件。
首先运行的是所谓的固件,它是一种软件:
由硬件制造商
通常是封闭源,但可能基于C的存储在只读存储器中,因此未经供应商的同意就很难/不可能进行修改。
众所周知的固件包括:
BIOS:老式的x86固件。 SeaBIOS是QEMU使用的默认开源实现。
UEFI:BIOS的后续产品,标准化程度更高,但功能更强大,而且膨胀得令人难以置信。
Coreboot:高贵的交叉架构源尝试
固件执行以下操作:遍历每个硬盘,USB,网络等,直到找到可引导的东西。
运行QEMU时,
main.img
表示hda
是连接到硬件的硬盘,而0x7c00
是第一个尝试使用的硬盘。将前512个字节加载到RAM内存地址
%ds
,将CPU的RIP放在此处,然后使其运行在显示屏上显示诸如引导菜单或BIOS打印调用之类的内容
Firmware提供大多数OS-es依赖的类似于OS的功能。例如。已将Python子集移植到可在BIOS / UEFI上运行:https://www.youtube.com/watch?v=bYQ_lq5dcvM
可以说固件与操作系统没有区别,并且固件是唯一可以做到的“真正的”裸机编程。
正如此CoreOS开发人员所言:
困难的部分
>给PC通电时,构成芯片组的芯片(北桥,南桥和SuperIO)尚未正确初始化。即使BIOS ROM作为远,因为它可能是CPU去除,这是由CPU访问,因为它必须,否则,CPU就没有要执行的指令。这并不意味着BIOS ROM通常是完全映射的。但是只有足够的映射才能使启动过程继续进行。任何其他设备,只需忘记它即可。
在QEMU下运行Coreboot时,可以尝试使用Coreboot的较高层和有效负载,但是QEMU提供的机会很少,无法尝试使用低级启动代码。一方面,RAM从一开始就可以正常工作。
BIOS后的初始状态
与硬件中的许多事情一样,标准化很薄弱,其中之一当代码在BIOS之后开始运行时,您不应该依赖的是寄存器的初始状态。
所以请自己帮忙,并使用一些初始化代码,如下所示:https://stackoverflow.com/ a / 32509555/895245
%es
和/boot
之类的寄存器具有重要的副作用,因此即使您未明确使用它们,也应将它们归零。 请注意,某些仿真器比真实的硬件更好,并且为您提供了良好的初始状态。然后,当您在真实的硬件上运行时,一切都会中断。
GNU GRUB Multiboot
引导扇区很简单,但它们并不十分方便:
每个磁盘只能有一个操作系统。
加载代码必须非常小,并且可以容纳512字节。这可以通过int 0x13 BIOS调用来解决。
您必须自己进行很多启动,就像进入保护模式一样
正是由于这些原因,GNU GRUB创建了一个更方便的方法名为multiboot的文件格式。
最小的工作示例:https://github.com/cirosantilli/x86-bare-metal-examples/tree/d217b180be4220a0b4a453f31275d38e697a99e0/multiboot/hello-world
我还在GitHub示例存储库中使用了它,从而能够轻松地在真实硬件上运行所有示例,而无需刻录USB一百万遍。在QEMU上,它看起来像这样:
如果将操作系统准备为多引导文件,则GRUB可以在常规文件系统中找到它。
这是大多数发行版所做的,将操作系统映像放在
grub-mkrescue
下。基本上,多重引导文件是带有特殊标头的ELF文件。 GRUB在以下位置指定了它们:https://www.gnu.org/software/grub/manual/multiboot/multiboot.html
您可以使用
grub-mkrescue
将multiboot文件转换为可引导磁盘。 br /> El Torito
可以刻录到CD的格式:https://en.wikipedia.org/wiki/El_Torito_%28CD-ROM_standard%29
也可以生成在ISO或USB上均可使用的混合图像。这可以通过
make isoimage
(示例)完成,也可以由Linux内核在isohybrid
上使用q4312079q完成。资源
http ://wiki.osdev.org是解决这些问题的重要资源。
https://github.com/scanlime/metalkit是一种更加自动化/通用的裸机编译系统,它提供了一个很小的功能自定义API
评论
我相当确定这就是BIOS的用途-这是一个很小的操作系统,可以引导较大的OS运行。操作系统很方便,但是不需要一个操作系统即可在计算机上运行程序。
甚至可以在没有OS的情况下编写非OS软件。传统上,许多Forth解释器在运行时都没有操作系统(或者您可以说它们是操作系统)。甚至没有那么困难。如果您了解C,您可能会喜欢编写这样的程序(也许是一个小游戏)作为学习练习。
这种困惑是当今我们使用的奇妙,安全,高度抽象的计算系统的代价之一:人们可能是非常优秀且能干的程序员,甚至不了解计算机的工作原理。您想走多低?对于非常低但仍高于物理水平的内容,请参阅第一个微处理器是如何编程的?在Electronics.SE上。
在发明当前操作系统概念之前就完成了编程。显然,该级别的功能是启动操作系统的开始。操作系统被引导。通常至少在CS 4年计划中会提到这一点,因为大多数都需要计算机理论的操作系统课程。