“Rolling Your Own Bootloader”的版本间差异

来自osdev
跳到导航 跳到搜索
(创建页面,内容为“{{rating|2}} Some people prefer to use their own software for everything, or wish to try their hand at coding a bootloader. This page attempts to describe what steps to take when you write your own bootloader. Before you start writing one, it is best that you know the background theory. == What and Why == === Disclaimer === Okay. You are here because you don't want to use a mainstream boot loader. You may also want to code your own bootloader a…”)
 
 
第1行: 第1行:
{{rating|2}}
{{rating|2}}
Some people prefer to use their own software for everything, or wish to try their hand at coding a bootloader. This page attempts to describe what steps to take when you write your own bootloader. Before you start writing one, it is best that you know the background [[Bootloader|theory]].
有些人更喜欢将自己的软件用于所有内容,或者希望尝试对引导加载程序进行编码。 本页试图描述在编写自己的引导加载程序时要采取的步骤。 在开始编写之前,最好先了解背景 [[Bootloader | 理论]]


== What and Why ==
== 什么和为什么 ==
=== Disclaimer ===
=== 免责声明 ===


Okay. You are here because you don't want to use a mainstream boot loader. You may also want to code your own bootloader as a learning experience to better understand how they function. We also have pages on many [[Bootloaders]] developed by this community, there's the [[Bare bones]] but still people complain we don't have a page explaining how the bootloader can be coded.
好吧。你在这里是因为你不想使用主流引导加载程序。 您可能还希望将自己的引导加载程序编写为学习体验,以更好地了解它们的功能。 我们也有许多由这个社区开发的 [[引导加载程序]] 的页面,有 [[裸露的骨头]],但是人们仍然抱怨我们没有一个页面来解释引导加载程序如何被编码。


I won't try to give you full code that works because if that was what you were looking for, you'd be using one of the [[:Category:Bootloaders|premade bootloaders]] instead. This page plans to tell you what is needed and what could be wished in a bootloader, and optionally points at parts of the FAQ that can help you achieving the goals.
我不会尝试给你完整的代码,因为如果那是你要找的,你会使用 [[:Category:Bootloaders | 预制的bootloaders]] 之一。 此页面计划告诉您在引导加载程序中需要什么以及希望什么,并可选地指向可以帮助您实现目标的常见问题解答部分。


Whether or not you'll use your own bootloader or reuse an existing tool is completely up to you. If you get the feeling you don't understand a thing, make sure you read our page about the [[Boot Sequence]] first.<br />
是否使用自己的引导加载程序或重用现有工具完全取决于您。 如果你有一种不明白的感觉,一定要先阅读我们关于 [[启动顺序]] 的页面。<br />


A good reason to have a custom bootloader would be a custom filesystem.
拥有自定义引导加载程序的一个很好的理由是自定义文件系统。


=== What you need to do ===
=== 你需要做什么 ===


The bootloader ultimately has to bring the kernel (and all the kernel needs to bootstrap) in memory, switch to an environment that the kernel will like and then transfer control to the kernel.
引导加载程序最终必须将内核 (以及所有内核都需要引导) 引入内存,切换到内核会喜欢的环境,然后将控制权转移到内核。


As the scope of this article is protected mode C kernels, I'll assume that "an environment the kernel will like" means [[Protected Mode]], with kernel and additional components being stored at their 'favorite', compile-time known locations, with a wide-enough stack ready and BSS section cleared.
由于本文的范围是受保护模式C内核,因此我假设 “内核会喜欢的环境” 意味着 [[受保护模式]],内核和其他组件存储在其 “最喜欢的”,编译时已知位置,准备好足够宽的堆栈,并清除BSS部分。


=== What you could wish to add ===
=== 您希望添加的内容 ===


Since the bootloader runs in [[Real Mode]], it has easier access to BIOS resources and functions. Therefore it's a good place to perform [[How Do I Determine The Amount Of RAM|memory map detection]], [[Getting VBE Mode Info|detecting available video modes]], loading additional files etc. The bootloader will collect this information and present it in a way the kernel will be able to understand
由于引导加载程序以 [[真实模式]] 运行,因此它更容易访问BIOS资源和功能。 因此,这是执行 [[如何确定RAM | 内存映射检测的数量]][[获取VBE模式信息 | 检测可用视频模式]],加载其他文件等的好地方。 引导加载程序将收集这些信息,并以内核能够理解的方式呈现


== Loading... Please wait... ==
== 载入中... 请稍候... ==


=== Where will you load your kernel? ===
=== 你将在哪里加载你的内核? ===


You will have to decide where in memory you are going to load your kernel. Your kernel generally depends on it.
您将不得不决定要在内存中加载内核的位置。 您的内核通常取决于它。


In Real Mode, the easiest is to stay below the 1MB barrier, which means you practically have 512KB of memory to load things. You may wish the kernel to be loaded at a well-known position, say 0x10000 physical (es=0x1000, bx=0 when calling INT13h).
在实际模式下,最简单的方法是保持在1MB屏障以下,这意味着您实际上有512KB的内存来加载东西。 您可能希望内核加载在一个众所周知的位置,例如0x10000物理 (es = 0x1000,调用INT13h时bx = 0)


If your kernel is bigger (or is expected to grow bigger) than this, you'll probably prefer to have the kernel above the 1MB barrier, which means you need to activate [[A20 Line|A20 gate]] and switch to [[Unreal Mode]] to load the kernel (with A20 alone, you cannot have more than 64K above 1MB).
如果您的内核比这个更大 (或预计会更大),则您可能希望内核超过1MB屏障,这意味着您需要激活 [[A20线 | A20门]] 并切换到 [[虚幻模式]] 来加载内核 (单独使用A20,您不能拥有超过1MB以上的64k)


Note that BIOS will still be unable to write to memory above 1MB, so you need to read stuff in a buffer below 1MB and then perform a rep movsd to place the data where they ultimately should go.
请注意,BIOS仍然无法写入1MB以上的内存,因此您需要在1MB以下的缓冲区中读取内容,然后执行rep movsd将数据放置在它们最终应该去的地方。


=== How will you find your kernel? ===
=== 你将如何找到你的内核? ===


The bits of your kernel are somewhere on some disk (presumably the [[Bootable Disk|booting disk]], but this is not mandatory). Question is: where on the disk? Is it a regular file on a [[FAT|FAT-formatted floppy]]? is it a collection of consecutive sectors in the "reserved area" of the FAT12 floppy (in which case you may need a dedicated tool to format the disk and install the kernel on it)? Or is the floppy simply left unformatted and kernel pasted directly with a [[Disk Images|disk image tool]].
内核的位在某个磁盘上 (大概是 [[可引导磁盘 | 引导磁盘]],但这不是强制性的)。问题是: 磁盘上的位置? 它是 [[FAT | FAT格式软盘]] 上的常规文件吗? 它是FAT12软盘 “保留区域” 中连续扇区的集合 (在这种情况下,您可能需要专用工具来格式化磁盘并在其上安装内核)? 还是软盘简单地留下了未格式化的内核,直接用 [[磁盘映像 | 磁盘映像工具]] 粘贴。


All the above options are possible. Maybe the one I'd choose myself would be to reserve enough space on a FAT12 floppy to store the ''list of sectors'' used by the kernel file. The "advantage" of being fully-FAT12 is that you don't need to re-write the bootsector every time you rewrite the kernel.
以上所有选项都是可能的。 也许我自己选择的是在FAT12软盘上保留足够的空间来存储内核文件使用的 “部门列表”。 被fully-FAT12的 “好处” 是,您不需要在每次重写内核时都重写引导扇区。


=== What else could you need to load? ===
=== 你还需要加载什么? ===


That mainly depends on what's in your kernel. Linux, for instance, requires an additional 'initrd' file that will contain the 'initialization process' (as user level). If your kernel is modular and if [[File Systems]] are understood by some modules, you need to load the modules along with the kernel. Same goes for 'microkernel services' like disk/files/memory services, etc.
这主要取决于你的内核。 例如,Linux需要一个额外的 “初始化” 文件,其中将包含 “初始化过程” (作为用户级别)。 如果您的内核是模块化的,并且 [[文件系统]] 被某些模块理解,则需要将模块与内核一起加载。 磁盘/文件/内存服务等 “微内核服务” 也是如此。


=== What if I get beyond the 512 bytes of the boot sector? ===
=== 如果我得到超过512个字节的引导扇区? ===


Make sure the first 512 bytes are able to load the rest of your loader and you're safe. Some do this with a separate "second stage" loader, others by really inserting a '512-bytes' break in their ASM code, making sure the rest of the loader is put after the bootsector (that is, starting at 0x7e00).
确保前512个字节能够加载加载程序的其余部分,并且您是安全的。 有些人使用单独的 “第二阶段” 加载器来做到这一点,其他人则通过在其ASM代码中真正插入 “512字节” 中断,确保加载器的其余部分放在引导扇区之后 (即从0x7e00开始)


=== What if I wish to offer the user the option to boot several OSes? ===
=== 如果我想为用户提供引导多个操作系统的选项怎么办? ===


The easiest way to boot another OS is a mechanism called ''chainloading''. Windows stores something akin to a second-stage bootloader in the boot sector of the ''partition'' it was installed in. When installing Linux, writing e.g. LILO or GRUB to the ''partition'' boot sector instead of the MBR is also an option. Now, the thing your MBR bootsector can do is to ''relocate'' itself (copying from 0x0000:0x7c00 to, traditionally, 0x0060:0x0000), parse the partition table, display some kind of menu and let the user chose which partition to boot from. Then, your (relocated) MBR bootsector would load that ''partition'' boot sector to 0x0000:0x7c00, and jump there. The partition boot sector would be none the wiser that there already was a bootsector loaded ''before'', and could actually load yet ''another'' bootsector - which is why it's called ''chainloading''. It doesn't really matter where you decide to relocate the boot sector as long as you don't overwrite the [[IVT]] (if IF in EFLAGS is set), the [[Memory Map (x86)#BIOS Data Area (BDA)|BDA]] or the [[Memory Map (x86)#Extended BIOS Data Area (EBDA)|EBDA]].
启动另一个OS的最简单方法是一种称为 “chainloading” 的机制。 Windows在安装的 “分区” 的引导扇区中存储类似于第二阶段引导加载程序的内容。 安装Linux时,将例如LILO或GRUB写入 “分区” 引导扇区而不是MBR也是一种选择。 现在,您的MBR引导部门可以做的是 “重新定位” 自身 (从0x0000:0x7c00复制到传统上0x0060:0x0000),解析分区表,显示某种菜单,并让用户选择从哪个分区启动。 然后,您的 (重新定位的) MBR引导扇区将该 “分区” 引导扇区加载到0x0000:0x7c00,然后跳到那里。 分区引导扇区不会比 “之前” 已经加载了一个引导扇区更明智, 并且实际上可以加载 “另一个” 引导扇区-这就是为什么它被称为 “链式加载” 的原因。 只要您不覆盖 [[IVT]] (如果设置了 “if in eglags”),您决定在哪里重新定位引导扇区并不重要, [[内存映射 (x86)# BIOS数据区 (BDA)| BDA]] [[内存映射 (x86)# 扩展BIOS数据区 (EBDA)| EBDA]]


You see that with displaying a menu in some intelligible way and accepting keystrokes, such a multi-option bootloader can get quite complex rather quickly. We didn't even touch the subject of booting from extended partitions, which would require sequentially reading and parsing multiple extended partition tables before printing the menu.
您会看到,通过以某种可理解的方式显示菜单并接受击键,这样的多选项引导加载程序可以变得相当复杂,相当快。 我们甚至没有触及从扩展分区启动的主题,这将需要在打印菜单之前顺序读取和解析多个扩展分区表。


Taken to the extreme, boot managers like that can become as complex as a simple OS (much like GRUB is, which offers reading from various filesystems, booting [[Multiboot]] kernels, chainloading, loading initrd ramdisks etc. etc. - such internals will not be addressed here.
更极端的是,像这样的引导管理器可以变得像一个简单的操作系统一样复杂 (很像GRUB,它提供了从各种文件系统的读取、引导 [[多引导]] 内核、链式加载、加载initrd ramdisks等等) -- 这样的内部将不会在这里解决。


=== How do I actually load bytes ===
=== 我如何实际加载字节 ===


[[BIOS]] interrupt 13h. Get info about it at [[Ralf Brown's Interrupt List]], make sure you know (the now outdated) floppies may fail one or two times, that you cannot read more than a track at once, you have to use CHS addressing and you're done.
[[BIOS]] interrupt 13h. [[拉尔夫·布朗的中断列表]] 获取关于它的信息,确保你知道 (现在已经过时的) 软盘可能会失败一两次,你不能一次阅读超过一个轨道,你必须使用CHS寻址,你就完成了。


To read from the hard drive (which is the preferred way these days, also used by CDROMs and USB-sticks), you probably want int 13h, ah=0x42, drive number 0x80 that uses simple LBA addressing. Details in the interrupt list.
要从硬盘驱动器读取 (这是这些天的首选方式,CDROMs和USB记忆棒也使用),您可能需要int 13h,ah = 0x42,使用简单LBA寻址的驱动器号0x80。 中断列表中的详细信息。


If you need guidance, feel free to check [http://clicker.cvs.sourceforge.net/clicker/c32-lxsdk/kernel/src/sosflppy/lowlevel.asm?view=log lowlevel.asm]
如果您需要指导,请随时检查 [http://clicker.cvs.sourceforge.net/clicker/ c32-lxsdk/内核/src/sosfflppy/lowlevel.asm?view = log lowlevel.asm]




Note also that most [[File Systems]] involve some conversion between allocation units (blocks/clusters) and physical "Cylinder:Head:Sector" values. Those conversions are simple once you know the ''sectors-per-track'' and ''heads'' counts. Check out [http://www.nondot.org/sabre/os/articles OSRC] for additional info. This is only relevant for outdated floppies; everything else, like hard drives, CDROMs, USB-sticks all use the simple LBA addressing scheme.
还请注意,大多数 [[文件系统]] 涉及分配单元 (/) 和物理 “圆柱体: : 扇区” 值之间的一些转换。 一旦您知道 “每个轨道的扇区” 和 “头” 计数,这些转换就很简单。 查看 [http://www.nondot.org/sabre/os/articles OSRC] 了解更多信息。 这仅与过时的软盘有关; 其他所有内容,例如硬盘驱动器,cdrom,USB记忆棒,都使用简单的LBA寻址方案。


<pre>
<pre>
> Does anyone have a formula for converting DOS Sectors to
> 有没有人有将DOS扇区转换为
> Physical Sectors (Head, Cylinder, Sector) such as used in
> 物理扇区 (头部、圆柱体、扇区),如用于
> INT 13h?
> INT 13h?


第81行: 第81行:
</pre>
</pre>


If you're loading above 1MB, you should proceed in 2 steps: first using BIOS to load in the "conventional" area, and then performing a <tt>rep movsd</tt> to place the data where they ultimately should go.
如果您的加载量超过1MB,则应按2个步骤进行: 首先使用BIOS加载到 “常规” 区域,然后执行 <tt>rep movsd</tt> 将数据放置在它们最终应该去的地方。


== Loaded. Gathering Information ==
== 已加载。收集信息 ==


The next step consist of collecting as much information as you can/need: [[How Do I Determine The Amount Of RAM|amount of installed RAM]], available [[Getting VBE Mode Info|video modes]] and things alike are easier to do in real mode, so better do them while in [[Real Mode]] than trying to come back to real mode for a trip later. Of course the exact requirements depend on your kernel.
下一步包括收集尽可能多的信息,你可以/需要: [[如何确定RAM的数量 | 已安装RAM的数量]],可用的 [[获取VBE模式信息 | 视频模式]] 和类似的事情在实际模式下更容易做到, 因此,在 [[真实模式]] 中进行操作要比以后尝试回到真实模式进行旅行更好。 当然确切的要求取决于你的内核。


A very simple solution here is to organize your information as a flat table (ala [[BIOS|BIOS data area]]). An alternative could be to add those information as a structured flow: you keep an index at a well-known address (or at some address you'll pass to the kernel when loaded) and that index gives for each "key" the address of the corresponding data structure. E.g.
这里一个非常简单的解决方案是将您的信息组织为一个平面表 (ala [[BIOS | BIOS数据区域]])。 另一种选择是将这些信息添加为结构化流: 您将索引保存在一个众所周知的地址 (或加载时会传递给内核的某个地址),并且该索引为每个 “键” 提供相应数据结构的地址。例如。


<pre>
<pre>
第102行: 第102行:
</pre>
</pre>


== Ready. Entering [[Protected Mode]] ... ==
== 准备就绪。进入 [[保护模式]] ... ==


To enter protected mode you should first disable interrupts and set global descriptor table. After it set PE bit of CR0:
要进入保护模式,您应该首先禁用中断并设置全局描述符表。 将PE位设置为CR0后:


<source lang="asm">
<source lang="asm">
第112行: 第112行:
</source>
</source>


After it set registers and do a far jump to kernel.
在它设置寄存器并做一个远跳转到内核之后。
If data selector is 10h, code selector is 8 and kernel offset is 10000h do:
如果数据选择器为10h,则代码选择器为8,内核偏移为10000h,请执行以下操作:
<source lang="asm">
<source lang="asm">
mov ax,10h
mov ax,10h
第124行: 第124行:
</source>
</source>


Notes:
:
* that in this case, the GDT will be ''temporary''. Indeed, the loader has no idea of what the kernel wants to do with the GDT, so all it can do is providing a minimal and let the kernel reload GDTR with an appropriate GDT later.
* 在这种情况下,GDT将是 “暂时的”。 实际上,加载程序不知道内核要对GDT做什么,因此它所能做的就是提供最小限度,并让内核稍后使用适当的GDT重新加载GDTR。
* it's common for the loader to keep interrupts disabled (the kernel will enable them later when an IDT is properly set up)
* 加载程序通常会禁用中断 (当正确设置IDT时,内核将稍后启用中断)
* give yourself the time about thinking whether you'll enable paging now or not. Keep in mind that debugging paging initialization code without the help of exception handlers may quickly become a nightmare!
* 给自己时间考虑是否现在启用分页。 请记住,在没有异常处理程序帮助的情况下调试分页初始化代码可能很快就会成为一场噩梦!
* it is possible to perform more initialization once protected mode is enabled and before kernel is loaded. This will, however, require that you mix 16 bits and 32bits code in a single object file, which can quickly become a nightmare too...
* 一旦启用了保护模式,并且在加载内核之前,可以执行更多的初始化。但是,这将要求您在单个目标文件中混合16位和32位代码,这也可能很快成为噩梦...
* it is very likely that your kernel does not start with an executable code, rather it has an ELF or PE header at 10000h. You'll have to parse that to get the entry point to jump to.
* 很可能您的内核不以可执行代码开头,而是在10000h处具有ELF或PE标头。 你必须解析它才能得到要跳转到的入口点。


== You have long way to continue... ==
== 你还有很长的路要走... ==
Now, you are very distant from using extern and call function for C Code. You will need to enable [[A20]], make something which reads images (so you can actually boot any .bin or .sys file), and so on.
现在,您距离使用extern和call函数的C代码非常遥远。 您将需要启用 [[A20]],制作读取图像的东西 (这样您就可以实际启动任何.bin或.sys文件),等等。


== Help I'm Stuck! ==
== 帮帮我卡住了! ==
The only way to figure out what's wrong with your boot loader is to debug it in a VM. You could probably print out variables, but the limited space makes this uneccesairly hard.
找出引导加载程序有什么问题的唯一方法是在VM中调试它。 您可能可以打印出变量,但是有限的空间使这变得非常困难。
Also reading [[My_Bootloader_Does_Not_Work|common mistakes and gotchas]] may give you ideas about your issue.
此外,阅读 [[My_Bootloader_Does_Not_Work | 常见的错误和陷阱]] 可能会给你关于你的问题的想法。


[[Category:OS Development]]
[[Category:OS Development]]
第143行: 第143行:
[[de:Eigener Bootloader]]
[[de:Eigener Bootloader]]


== A list of things you might want to do ==
== 你可能需要做的任务清单 ==


* Setup 16-bit segment registers and stack
* 设置16位段寄存器和堆栈
* Print startup message
* 打印启动消息
* Check presence of PCI, CPUID, MSRs
* 检查是否存在PCI、CPUID、MSRs
* Enable and confirm enabled A20 line
* 启用并确认启用A20线路
* Load GDTR
* 加载GDTR
* Inform BIOS of target processor mode
* 通知BIOS目标处理器模式
* Get memory map from BIOS
* 从BIOS获取内存映射
* Locate kernel in filesystem
* 在文件系统中定位内核
* Allocate memory to load kernel image
* 分配内存以加载内核映像
* Load kernel image into buffer
* 将内核映像加载到缓冲区中
* Enable graphics mode
* 启用图形模式
* Check kernel image ELF headers
* 检查内核映像ELF标头
* Enable long mode, if 64-bit
* 启用长模式,如果64位
* Allocate and map memory for kernel segments
* 为内核段分配和映射内存
* Setup stack
* 设置堆栈
* Setup COM serial output port
* 设置COM串行输出端口
* Setup IDT
* 设置IDT
* Disable PIC
* 禁用PIC
* Check presence of CPU features (NX, SMEP, x87, PCID, global pages, TCE, WP, MMX, SSE, SYSCALL), and enable them
* 检查是否存在CPU功能 (NX,SMEP,x87,PCID,全局页面,TCE,WP,MMX,SSE,SYSCALL),并启用它们
* Assign a PAT to write combining
* 分配一个PAT来写合并
* Setup FS/GS base
* 设置FS/GS基础
* Load IDTR
* 装载IDTR
* Enable APIC and setup using information in ACPI tables
* 使用ACPI表中的信息启用APIC和设置
* Setup GDT and TSS
* 设置GDT和TSS

2021年12月24日 (五) 02:03的最新版本

难度等级
Difficulty 2.png
中等

有些人更喜欢将自己的软件用于所有内容,或者希望尝试对引导加载程序进行编码。 本页试图描述在编写自己的引导加载程序时要采取的步骤。 在开始编写之前,最好先了解背景 理论

什么和为什么

免责声明

好吧。你在这里是因为你不想使用主流引导加载程序。 您可能还希望将自己的引导加载程序编写为学习体验,以更好地了解它们的功能。 我们也有许多由这个社区开发的 引导加载程序 的页面,有 裸露的骨头,但是人们仍然抱怨我们没有一个页面来解释引导加载程序如何被编码。

我不会尝试给你完整的代码,因为如果那是你要找的,你会使用 预制的bootloaders 之一。 此页面计划告诉您在引导加载程序中需要什么以及希望什么,并可选地指向可以帮助您实现目标的常见问题解答部分。

是否使用自己的引导加载程序或重用现有工具完全取决于您。 如果你有一种不明白的感觉,一定要先阅读我们关于 启动顺序 的页面。

拥有自定义引导加载程序的一个很好的理由是自定义文件系统。

你需要做什么

引导加载程序最终必须将内核 (以及所有内核都需要引导) 引入内存,切换到内核会喜欢的环境,然后将控制权转移到内核。

由于本文的范围是受保护模式C内核,因此我假设 “内核会喜欢的环境” 意味着 受保护模式,内核和其他组件存储在其 “最喜欢的”,编译时已知位置,准备好足够宽的堆栈,并清除BSS部分。

您希望添加的内容

由于引导加载程序以 真实模式 运行,因此它更容易访问BIOS资源和功能。 因此,这是执行 内存映射检测的数量 检测可用视频模式,加载其他文件等的好地方。 引导加载程序将收集这些信息,并以内核能够理解的方式呈现

载入中... 请稍候...

你将在哪里加载你的内核?

您将不得不决定要在内存中加载内核的位置。 您的内核通常取决于它。

在实际模式下,最简单的方法是保持在1MB屏障以下,这意味着您实际上有512KB的内存来加载东西。 您可能希望内核加载在一个众所周知的位置,例如0x10000物理 (es = 0x1000,调用INT13h时bx = 0)。

如果您的内核比这个更大 (或预计会更大),则您可能希望内核超过1MB屏障,这意味着您需要激活 A20门 并切换到 虚幻模式 来加载内核 (单独使用A20,您不能拥有超过1MB以上的64k)。

请注意,BIOS仍然无法写入1MB以上的内存,因此您需要在1MB以下的缓冲区中读取内容,然后执行rep movsd将数据放置在它们最终应该去的地方。

你将如何找到你的内核?

内核的位在某个磁盘上 (大概是 引导磁盘,但这不是强制性的)。问题是: 磁盘上的位置? 它是 FAT格式软盘 上的常规文件吗? 它是FAT12软盘 “保留区域” 中连续扇区的集合 (在这种情况下,您可能需要专用工具来格式化磁盘并在其上安装内核)? 还是软盘简单地留下了未格式化的内核,直接用 磁盘映像工具 粘贴。

以上所有选项都是可能的。 也许我自己选择的是在FAT12软盘上保留足够的空间来存储内核文件使用的 “部门列表”。 被fully-FAT12的 “好处” 是,您不需要在每次重写内核时都重写引导扇区。

你还需要加载什么?

这主要取决于你的内核。 例如,Linux需要一个额外的 “初始化” 文件,其中将包含 “初始化过程” (作为用户级别)。 如果您的内核是模块化的,并且 文件系统 被某些模块理解,则需要将模块与内核一起加载。 磁盘/文件/内存服务等 “微内核服务” 也是如此。

如果我得到超过512个字节的引导扇区?

确保前512个字节能够加载加载程序的其余部分,并且您是安全的。 有些人使用单独的 “第二阶段” 加载器来做到这一点,其他人则通过在其ASM代码中真正插入 “512字节” 中断,确保加载器的其余部分放在引导扇区之后 (即从0x7e00开始)。

如果我想为用户提供引导多个操作系统的选项怎么办?

启动另一个OS的最简单方法是一种称为 “chainloading” 的机制。 Windows在安装的 “分区” 的引导扇区中存储类似于第二阶段引导加载程序的内容。 安装Linux时,将例如LILO或GRUB写入 “分区” 引导扇区而不是MBR也是一种选择。 现在,您的MBR引导部门可以做的是 “重新定位” 自身 (从0x0000:0x7c00复制到传统上0x0060:0x0000),解析分区表,显示某种菜单,并让用户选择从哪个分区启动。 然后,您的 (重新定位的) MBR引导扇区将该 “分区” 引导扇区加载到0x0000:0x7c00,然后跳到那里。 分区引导扇区不会比 “之前” 已经加载了一个引导扇区更明智, 并且实际上可以加载 “另一个” 引导扇区-这就是为什么它被称为 “链式加载” 的原因。 只要您不覆盖 IVT (如果设置了 “if in eglags”),您决定在哪里重新定位引导扇区并不重要, BDA EBDA

您会看到,通过以某种可理解的方式显示菜单并接受击键,这样的多选项引导加载程序可以变得相当复杂,相当快。 我们甚至没有触及从扩展分区启动的主题,这将需要在打印菜单之前顺序读取和解析多个扩展分区表。

更极端的是,像这样的引导管理器可以变得像一个简单的操作系统一样复杂 (很像GRUB,它提供了从各种文件系统的读取、引导 多引导 内核、链式加载、加载initrd ramdisks等等) -- 这样的内部将不会在这里解决。

我如何实际加载字节

BIOS interrupt 13h. 在 拉尔夫·布朗的中断列表 获取关于它的信息,确保你知道 (现在已经过时的) 软盘可能会失败一两次,你不能一次阅读超过一个轨道,你必须使用CHS寻址,你就完成了。

要从硬盘驱动器读取 (这是这些天的首选方式,CDROMs和USB记忆棒也使用),您可能需要int 13h,ah = 0x42,使用简单LBA寻址的驱动器号0x80。 中断列表中的详细信息。

如果您需要指导,请随时检查 c32-lxsdk/内核/src/sosfflppy/lowlevel.asm?view = log lowlevel.asm


还请注意,大多数 文件系统 涉及分配单元 (块/簇) 和物理 “圆柱体: 头: 扇区” 值之间的一些转换。 一旦您知道 “每个轨道的扇区” 和 “头” 计数,这些转换就很简单。 查看 OSRC 了解更多信息。 这仅与过时的软盘有关; 其他所有内容,例如硬盘驱动器,cdrom,USB记忆棒,都使用简单的LBA寻址方案。

> 有没有人有将DOS扇区转换为
> 物理扇区 (头部、圆柱体、扇区),如用于
> INT 13h?

DOS_sector_num = BIOS_sector_num - 1 + Head_num*Sectors_per_track
		+ Track_num*Sectors_per_track*Total_heads

BIOS_sector_num = 1 + (DOS_sector_num MOD Sectors_per_track)
BIOS_Head_num   = (DOS_sector_num DIV Sectors_per_track) MOD Total_heads
BIOS_Track_num  = (DOS_sector_num DIV Sectors_per_track) DIV Total_heads

如果您的加载量超过1MB,则应按2个步骤进行: 首先使用BIOS加载到 “常规” 区域,然后执行 rep movsd 将数据放置在它们最终应该去的地方。

已加载。收集信息

下一步包括收集尽可能多的信息,你可以/需要: 已安装RAM的数量,可用的 视频模式 和类似的事情在实际模式下更容易做到, 因此,在 真实模式 中进行操作要比以后尝试回到真实模式进行旅行更好。 当然确切的要求取决于你的内核。

这里一个非常简单的解决方案是将您的信息组织为一个平面表 (ala BIOS数据区域)。 另一种选择是将这些信息添加为结构化流: 您将索引保存在一个众所周知的地址 (或加载时会传递给内核的某个地址),并且该索引为每个 “键” 提供相应数据结构的地址。例如。

  organization           lookup code (eax == signature)
  +------+------+          mov esi, well_known_index_address
  | RAM. | 1234 |        .loop:
  | VBE. | 5678 |          cmp [esi],'END.'
  | MODS | 9ABC |          je .notfound
  | DISK | DEF0 |          add esi,8
  | END. | ---- |          cmp [esi-4],eax
  +------+------+          jne .loop
                           mov eax,[esi]
                           ret

准备就绪。进入 保护模式 ...

要进入保护模式,您应该首先禁用中断并设置全局描述符表。 将PE位设置为CR0后:

mov eax,cr0
or eax,1
mov cr0,eax

在它设置寄存器并做一个远跳转到内核之后。 如果数据选择器为10h,则代码选择器为8,内核偏移为10000h,请执行以下操作:

mov ax,10h
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
jmp 8:10000h

注:

  • 在这种情况下,GDT将是 “暂时的”。 实际上,加载程序不知道内核要对GDT做什么,因此它所能做的就是提供最小限度,并让内核稍后使用适当的GDT重新加载GDTR。
  • 加载程序通常会禁用中断 (当正确设置IDT时,内核将稍后启用中断)
  • 给自己时间考虑是否现在启用分页。 请记住,在没有异常处理程序帮助的情况下调试分页初始化代码可能很快就会成为一场噩梦!
  • 一旦启用了保护模式,并且在加载内核之前,可以执行更多的初始化。但是,这将要求您在单个目标文件中混合16位和32位代码,这也可能很快成为噩梦...
  • 很可能您的内核不以可执行代码开头,而是在10000h处具有ELF或PE标头。 你必须解析它才能得到要跳转到的入口点。

你还有很长的路要走...

现在,您距离使用extern和call函数的C代码非常遥远。 您将需要启用 A20,制作读取图像的东西 (这样您就可以实际启动任何.bin或.sys文件),等等。

帮帮我卡住了!

找出引导加载程序有什么问题的唯一方法是在VM中调试它。 您可能可以打印出变量,但是有限的空间使这变得非常困难。 此外,阅读 常见的错误和陷阱 可能会给你关于你的问题的想法。 de:Eigener Bootloader

你可能需要做的任务清单

  • 设置16位段寄存器和堆栈
  • 打印启动消息
  • 检查是否存在PCI、CPUID、MSRs
  • 启用并确认启用A20线路
  • 加载GDTR
  • 通知BIOS目标处理器模式
  • 从BIOS获取内存映射
  • 在文件系统中定位内核
  • 分配内存以加载内核映像
  • 将内核映像加载到缓冲区中
  • 启用图形模式
  • 检查内核映像ELF标头
  • 启用长模式,如果64位
  • 为内核段分配和映射内存
  • 设置堆栈
  • 设置COM串行输出端口
  • 设置IDT
  • 禁用PIC
  • 检查是否存在CPU功能 (NX,SMEP,x87,PCID,全局页面,TCE,WP,MMX,SSE,SYSCALL),并启用它们
  • 分配一个PAT来写合并
  • 设置FS/GS基础
  • 装载IDTR
  • 使用ACPI表中的信息启用APIC和设置
  • 设置GDT和TSS