我正在创建一个操作系统,并且在过去的几个月里一直在努力寻找一种在其中实现文件系统的方法。我可以在启动服务中读取文件,但在 UEFI 中退出启动服务后,该功能无法读取文件。我正在尝试使用 GPT 在引导服务之外创建一个文件系统驱动程序(尽管可能是 MBR,但基于我所看到的几乎不可能)。我见过的所有源代码和示例都使用带有 GRUB 的多重引导,但我不使用 GRUB 引导加载程序。相反,我遵循了 Poncho 的 OSDev 2 系列中的那个。我从 WYOOS 看到了一个示例,他使用 MSDos 分区系统,但它依赖于多重引导,因此它在我的场景中不起作用。所有帮助将不胜感激。谢谢。
1 回答
您在问题中没有提供太多信息,但我假设您想要一个能够从文件系统上的驱动器读取文件的硬盘驱动器。最后,您并没有真正实现文件系统驱动程序。您实现了一个能够读取/写入硬盘驱动器的硬盘驱动器驱动程序。文件系统逻辑位于该驱动程序之上。
在此之下,您需要一个 ACPI 解释器和一个用于管理 PCI 设备的 PCI 子系统。它真的很复杂,而且不仅仅是调用一个完成工作的函数。一旦你得到了一个 PCI 子系统、一个 ACPI 解释器和一个文件系统驱动程序,你就得到了一个相当先进的操作系统。这不会是一个函数调用。它需要规划和阅读规范。
话虽如此,我目前正在自己编写一个 x86-64 操作系统,我打算做的事情可能会感兴趣(或不感兴趣)。从 UEFI,我使用以下内容从磁盘加载我的内核:
EFI_GUID FSPGuid = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
EFI_HANDLE* Handles = NULL;
UINTN HandleCount = 0;
Status = BS->LocateHandleBuffer(ByProtocol, &FSPGuid, NULL, &HandleCount, &Handles);
if (EFI_ERROR(Status)){
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Kernel Panic!!: Could not locate the handle buffer.\n");
goto DONE;
}else{
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Located the handle buffer for file system protocol.\n");
}
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* FS = NULL;
EFI_FILE_PROTOCOL* Root = NULL;
EFI_FILE_PROTOCOL* Token = NULL;
for (UINTN index = 0; index < (UINTN)HandleCount; index++)
{
Status = BS->HandleProtocol(Handles[index], &FSPGuid, (void**)&FS);
Status = FS->OpenVolume(FS, &Root);
Status = Root->Open(Root, &Token, L"startup.elf", EFI_FILE_MODE_READ, EFI_FILE_MODE_WRITE);
if(!EFI_ERROR(Status))
break;
}
if (Token == NULL){
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Kernel Panic!!: Could not locate startup file.\n");
goto DONE;
}else
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Located the startup file.\n");
UINTN BufferSize = 50000;
CHAR8 StartupBuffer[50000];
Status = Token->Read(Token, &BufferSize, StartupBuffer);
if(EFI_ERROR(Status)){
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Kernel Panic!!: Located startup.elf, but could not read from it.\n");
goto DONE;
}else
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Could read startup.elf properly now jumping to it's entry point.\n");
Status = Root->Open(Root, &Token, L"kernel.elf", EFI_FILE_MODE_READ, EFI_FILE_MODE_WRITE);
if(EFI_ERROR(Status)){
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Kernel Panic!!: Could not locate kernel file.\n");
}else
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Could locate the kernel file.\n");
if (Token == NULL){
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Kernel Panic!!: Could not locate kernel file.\n");
goto DONE;
}else
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Located the kernel file.\n");
EFI_PHYSICAL_ADDRESS PhysicalBuffer;
UINT64* KernelBuffer;
Status = BS->AllocatePages(AllocateAnyPages, EfiBootServicesData, 4096, &PhysicalBuffer);
KernelBuffer = (UINT64*)(UINTN)PhysicalBuffer;
if(EFI_ERROR(Status)){
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Kernel Panic!!: Could not allocate pages for the kernel.\n");
goto DONE;
}else
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Allocated pages for the kernel\n");
BufferSize = 4096 * 4096;
Status = Token->Read(Token, &BufferSize, KernelBuffer);
if(EFI_ERROR(Status)){
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Kernel Panic!!: Located kernel.elf, but could not read from it.\n");
goto DONE;
}else
SystemTable->ConOut->OutputString(SystemTable->ConOut, L"Could read kernel.elf properly.\n");
我加载了一个名为 startup.elf 的文件,它将为内核设置虚拟内存的高半映射。然后我为内核分配了很多页面(4096),这些页面比这小得多。然后,startup.elf 可执行文件从一个固定地址获取内核第一个字节的地址,并将内核的可加载段从 0x400000 开始映射到物理内存中。因此,虚拟地址空间的高 2GB 从 0x0 映射到物理内存中的 2GB。因此内核在虚拟内存中的位置是 0xffff_ffff_8040_0000。这是我从 startup.elf 文件跳转到的地方。可能有几种方法可以做到这一点,但我更喜欢为我的内核提供 2 个单独的文件(启动可执行文件和内核的可执行文件)。
从那里,我打算做的是实现一个内存管理子系统。内存管理是您要实现的第一件事,因为其他所有子系统都在使用内存。您希望能够为内核的需要分配内存。
之后,我计划实现一个 ACPI 子系统,该子系统将解析所有 ACPI 表并解释 AML 语言,该语言将允许收集有关我需要驱动的主板上存在的信息的信息。为此,我从 UEFI 获取 RSDP,并从 RSDP 中找到其他表(请参阅https://wiki.osdev.org/RSDP)。ACPI 表允许收集有关计算机上硬件的所有信息。例如,MCFG 告诉您 PCI 设备的配置空间将从哪里开始(参见https://wiki.osdev.org/PCI)。
在 ACPI 之上,我计划实现一个 PCI 子系统,它允许某种通用接口读取和写入 PCI 寄存器和某种通用中断号分配器。PCI 目前主要与 MSI/MSI-X 配合使用。我打算忘记所有遗留下来的东西,只使用最现代的东西,并假设所有这些硬件的存在(今天 99% 的台式 x64 计算机上都存在)。Windows 对其最新要求也是如此。他们只是给出了运行操作系统的一些基本要求,其余的不受支持。这与 Linux 内核不同,它试图支持所有东西,直到非常古老的恐龙计算机。这使得代码非常臃肿并且大部分时间都不是很有用。
AHCI驱动程序将使用 PCI 子系统来管理硬盘。AHCI 是使用 MSI 触发中断的 PCI 设备(请参阅https://www.intel.ca/content/www/ca/en/io/serial-ata/serial-ata-ahci-spec-rev1-3-1 .html)。UEFI 固件附带 AHCI 驱动程序。这就是它设法写入/读取硬盘驱动器的原因。
您会发现,在真正开始从硬盘驱动器加载内容之前,必须完成很多工作。您需要编写内存管理方案、解释 AML 和 ACPI 表、解析 PCI 总线以查找设备及其配置空间,最后编写 AHCI 驱动程序。即便如此,您可能还没有完成,因为现代 SATA SSD 具有非常高的吞吐量。因此,它们一次在几个 MB 中加载和存储数据。您需要某种有效的算法来缓存硬盘的某些部分,并确保不会一直加载/存储,因为它需要大量的 RAM 空间。对于较旧的硬盘,它会更容易但并不容易。