我正在编写一个操作系统项目。我正在用 C++ 和一些汇编语言开发我的项目 linux mint(并使用 gcc 和 GAS 编译它)并在 qemu 机器中运行它(我从编译后的代码中创建一个 .iso 文件,然后将其加载到 qemu 机器中)。 我目前正处于编写文件系统的阶段,为此我选择了FAT16,因为我已经开发了一个磁盘接口(具体来说是ATA PIO)。虚拟硬盘驱动器以及 .iso 文件被加载到 qemu 机器中。然而,在将虚拟硬盘加载到 qemu 机器之前,我首先将其格式化(使用 GParted 工具),使其拥有一个跨越整个磁盘且定义为 FAT16 的分区,具体方式如下:
/usr/bin/qemu-img create -f qcow2 os-disl.qcow2 512M
# Start GParted virtual machine to define ext2 partition
/usr/bin/qemu-system-i386 -cdrom gparted-live-1.6.0-3-i686.iso -hda os-disk.qcow2 -boot d -m 512
此外,一旦虚拟硬盘驱动器存在,我会将其与我的 iso 文件一起加载到新的 qemu 机器中:
/usr/bin/qemu-system-i386 -cdrom ./objects/mykernel.iso -drive file="$DISK_PATH",cache=none -boot d -m 512 -no-reboot -no-shutdown -d int -M smm=off -s
在打开的GParted虚拟机中我创建一个新的分区表,然后添加一个新分区,包括图片:
创建虚拟硬盘后,我想对其执行读取操作,并解析数据,以便我可以真正开始实现文件系统。 我首先通过以下方式实现磁盘通信接口:
磁盘.h:
#pragma once
#include <types.h>
#include <port/port.h>
void printf(uint8_t* ltr, int flag);
void printfHex(uint8_t key);
class ata
{
protected:
bool master;
Port16Bit dataPort;
Port8Bit errorPort;
Port8Bit sectorCountPort;
Port8Bit lbaLowPort;
Port8Bit lbaMidPort;
Port8Bit lbaHiPort;
Port8Bit devicePort;
Port8Bit commandPort;
Port8Bit controlPort;
public:
ata(bool master, uint16_t portBase);
~ata();
void Identify();
void Read28(uint32_t sectorNum, int count = 512, uint8_t* ptr=nullptr);
void Write28(uint32_t sectorNum, uint8_t* data, uint32_t count);
void Flush();
};
磁盘.cpp:
#include <fat16/disk.h>
ata::ata(bool master, uint16_t portBase)
: dataPort(portBase),
errorPort(portBase + 0x1),
sectorCountPort(portBase + 0x2),
lbaLowPort(portBase + 0x3),
lbaMidPort(portBase + 0x4),
lbaHiPort(portBase + 0x5),
devicePort(portBase + 0x6),
commandPort(portBase + 0x7),
controlPort(portBase + 0x206)
{
this->master = master;
}
ata::~ata()
{
}
void ata::Identify()
{
devicePort.Write(master ? 0xA0 : 0xB0);
controlPort.Write(0);
devicePort.Write(0xA0);
uint8_t status = commandPort.Read();
if(status == 0xFF)
return;
devicePort.Write(master ? 0xA0 : 0xB0);
sectorCountPort.Write(0);
lbaLowPort.Write(0);
lbaMidPort.Write(0);
lbaHiPort.Write(0);
commandPort.Write(0xEC); // identify command
status = commandPort.Read();
if(status == 0x00)
return;
while(((status & 0x80) == 0x80)
&& ((status & 0x01) != 0x01))
status = commandPort.Read();
if(status & 0x01)
{
return;
}
for(int i = 0; i < 256; i++)
{
uint16_t data = dataPort.Read();
char *text = " \0";
text[0] = (data >> 8) & 0xFF;
text[1] = data & 0xFF;
}
}
void ata::Read28(uint32_t sectorNum, int count, uint8_t* ptr)
{
if(sectorNum > 0x0FFFFFFF)
return;
devicePort.Write((master ? 0xE0 : 0xF0) | ((sectorNum >> 24) & 0x0F));
errorPort.Write(0);
sectorCountPort.Write(1);
lbaLowPort.Write(sectorNum & 0x000000FF);
lbaMidPort.Write((sectorNum & 0x0000FF00) >> 8);
lbaHiPort.Write((sectorNum & 0x00FF0000) >> 16);
commandPort.Write(0x20);
uint8_t status = commandPort.Read();
while (((status & 0x80) == 0x80) && ((status & 0x01) != 0x01))
status = commandPort.Read();
if (status & 0x01)
return;
for (int i = 0; i < count; i += 2)
{
uint16_t wdata = dataPort.Read();
ptr[i] = wdata & 0xFF;
if (i + 1 < count)
ptr[i + 1] = (wdata >> 8) & 0xFF;
}
for (int i = count + (count % 2); i < 512; i += 2)
dataPort.Read();
}
void ata::Write28(uint32_t sectorNum, uint8_t* data, uint32_t count)
{
if(sectorNum > 0x0FFFFFFF)
return;
if(count > 512)
return;
// Set the drive and LBA high 4 bits
devicePort.Write((master ? 0xE0 : 0xF0) | ((sectorNum >> 24) & 0x0F));
errorPort.Write(0);
sectorCountPort.Write(1);
lbaLowPort.Write(sectorNum & 0xFF); // LBA low byte
lbaMidPort.Write((sectorNum >> 8) & 0xFF); // LBA mid byte
lbaHiPort.Write((sectorNum >> 16) & 0xFF); // LBA high byte
commandPort.Write(0x30); // Write sectors command
// Wait until the drive is ready
uint8_t status = commandPort.Read();
while ((status & 0x80) && !(status & 0x08)) {
status = commandPort.Read();
}
// Write the data
for (int i = 0; i < count; i += 2)
{
uint16_t wdata = data[i];
if (i + 1 < count)
wdata |= ((uint16_t)data[i + 1]) << 8;
dataPort.Write(wdata);
}
// Pad remaining data with zeros if count is less than 512
for (int i = count + (count % 2); i < 512; i += 2)
dataPort.Write(0x0000);
}
void ata::Flush()
{
devicePort.Write( master ? 0xE0 : 0xF0 );
commandPort.Write(0xE7);
uint8_t status = commandPort.Read();
if(status == 0x00)
return;
while(((status & 0x80) == 0x80)
&& ((status & 0x01) != 0x01))
status = commandPort.Read();
if(status & 0x01)
{
return;
}
}
创建磁盘接口后,我创建该类的实例并开始读取操作:
ata ata0m(true, 0x1F0);
ata0m.Identify();
FAT16.h:
#pragma once
#include <types.h>
#include <fat16/disk.h>
#include <stddef.h>
extern ata ata0m;
typedef struct
{
// The mbr contains three different parts
// Boot code - first 446 bytes
// Partition tables - 4 entries with each sized 16 bytes that describe different partitions on the disk
// The struct describes a single entry
uint8_t bootIndicator;
uint8_t startHead;
uint8_t startSector;
uint8_t startCylinder;
uint8_t partitionType;
uint8_t endHead;
uint8_t endSector;
uint8_t endCylinder;
uint32_t relativeSector;
uint32_t totalSectors;
} __attribute__((packed)) MBR_PartitionEntry;
typedef struct
{
// Implement mbr strcure, we will load this into sector 0
uint8_t bootstrap[446];
MBR_PartitionEntry partitionTable[4];
uint16_t signature; // 0x55AA
} __attribute__((packed)) MBR;
typedef struct
{
// The first sector of each partition is called the boot sector and contains some metadata about the partition and the file system
uint8_t BS_jmpBoot[3];
uint8_t BS_OEMName[8];
uint16_t BPB_BytsPerSec; // 512
uint8_t BPB_SecPerClus; // 8
uint16_t BPB_RsvdSecCnt;
uint8_t BPB_NumFATs; // usually 2
uint16_t BPB_RootEntCnt; // garbage values, must be set
uint16_t BPB_TotSec16;
uint8_t BPB_Media;
uint16_t BPB_FATSz16;
uint16_t BPB_SecPerTrk;
uint16_t BPB_NumHeads;
uint32_t BPB_HiddSec;
uint32_t BPB_TotSec32;
uint8_t BS_DrvNum;
uint8_t BS_Reserved1;
uint8_t BS_BootSig;
uint32_t BS_VolID;
uint8_t BS_VolLab[11];
uint8_t BS_FilSysType[8];
} __attribute__((packed)) FAT16_BootSector;
void Read_MBR();
void readBootSector(); // Use ata_read_sector
FAT16_BootSector parseBootSector(uint8_t* bootSectorBuffer); // Receive the data from previous function and parse
// MBR information about our FAT16 partition
extern uint32_t lba_start;
extern uint32_t lba_limit;
extern uint8_t partition_type;
// Boot sector information about FAT16
extern uint16_t bytes_per_sector;
extern uint8_t sectors_per_cluster;
extern uint16_t reserved_sectors;
extern uint8_t number_of_FATs;
extern uint16_t root_entries;
extern uint16_t total_sectors;
extern uint16_t sectors_per_FAT;
extern uint32_t root_directory_sectors;
extern uint32_t first_data_sector;
extern uint32_t total_clusters;
// Calculate the starting sector of the FAT and root directory
extern uint32_t fat_start_sector;
extern uint32_t root_dir_start_sector;
FAT.cpp:
#include <fat16/fat16.h>
// This is the beggining of the file system - which is FAT16
MBR mbr;
FAT16_BootSector boot_sector;
uint32_t lba_start;
uint32_t lba_limit;
void Read_MBR()
{
// Write to the first sector of the disk - which is the MBR
// In: struct sized 512 that includes a partition table
// Out: None
ata0m.Read28(0, 512, (uint8_t*)&mbr); // Load MBR into mbr struct, this will actually load correctly the data
if (mbr.signature != 0xaa55) printf((uint8_t*)"error", 0);
MBR_PartitionEntry* firstPartition = &(mbr.partitionTable[0]); // pointer to the first partition table entry
uint32_t lba_start = firstPartition->relativeSector; // Actual beggining of our partition
uint32_t lba_limit = firstPartition->totalSectors; // Size of partition
uint8_t partition_type = firstPartition->partitionType;
printfHex(partition_type);
printf((uint8_t*)"\n",0);
// Purpose of function: to set up values in the lba global variables
}
void readBootSector()
{
// Implement reading of the boot sector using your disk I/O functions
// First we read the entire sector into the bootsector structure
// Then parse it anf gain the useful data
ata0m.Read28(lba_start, 512, (uint8_t*)&boot_sector);
uint16_t bytes_per_sector = boot_sector.BPB_BytsPerSec;
uint8_t sectors_per_cluster = boot_sector.BPB_SecPerClus;
uint16_t reserved_sectors = boot_sector.BPB_RsvdSecCnt;
uint8_t number_of_FATs = boot_sector.BPB_NumFATs;
uint16_t root_entries = boot_sector.BPB_RootEntCnt;
uint16_t total_sectors = boot_sector.BPB_TotSec16;
if (total_sectors == 0)
{
total_sectors = boot_sector.BPB_TotSec32;
}
uint16_t sectors_per_FAT = boot_sector.BPB_FATSz16;
uint32_t root_directory_sectors = ((root_entries * 32) + (bytes_per_sector - 1)) / bytes_per_sector;
uint32_t first_data_sector = reserved_sectors + (number_of_FATs * sectors_per_FAT) + root_directory_sectors;
uint32_t total_clusters = (total_sectors - first_data_sector) / sectors_per_cluster;
// Calculate the starting sector of the FAT and root directory - this is the important thing
uint32_t fat_start_sector = lba_start + reserved_sectors;
uint32_t root_dir_start_sector = fat_start_sector + (number_of_FATs * sectors_per_FAT);
printfHex(sectors_per_cluster);
printf((uint8_t*)"\n",0);
printfHex(number_of_FATs);
printf((uint8_t*)"\n",0);
}
我首先调用Read_MBR,然后调用readBootSector。在这些函数中,我使用 print 语句来产生以下输出:
这对我来说很奇怪。根据我的理解,我应该在像 number_of_FATs 这样的变量中看到不同的值(通常是 2)。因此,我使用以下命令查看了虚拟硬盘文件 (os-disk.qcow2) 的十六进制转储:
sudo dd if=/dev/loop0 bs=4096 count=1 | hexdump -C
在此之前我使用
将虚拟硬盘安装到我的机器上sudo losetup -P /dev/loop0 os-disk.qcow2
sudo fdisk -l /dev/loop0
sudo dd if=/dev/loop0 bs=512 count=1 | hexdump -C
最后一个命令查看文件的前 512 个字节。我得到的输出是:
fridkin@fridkin-virtual-machine:~/os/yb-os$ sudo dd if=/dev/loop0 bs=512 count=1 | hexdump -C
00000000 51 46 49 fb 00 00 00 03 00 00 00 00 00 00 00 00 |QFI.............|
00000010 00 00 00 00 00 00 00 10 00 00 00 00 20 00 00 00 |............ ...|
00000020 00 00 00 00 00 00 00 01 00 00 00 00 00 03 00 00 |................|
00000030 00 00 00 00 00 01 00 00 00 00 00 01 00 00 00 00 |................|
00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000060 00 00 00 04 00 00 00 70 00 00 00 00 00 00 00 00 |.......p........|
00000070 68 03 f8 57 00 00 01 80 00 00 64 69 72 74 79 20 |h..W......dirty |
00000080 62 69 74 00 00 00 00 00 00 00 00 00 00 00 00 00 |bit.............|
00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000000a0 00 00 00 00 00 00 00 00 00 01 63 6f 72 72 75 70 |..........corrup|
000000b0 74 20 62 69 74 00 00 00 00 00 00 00 00 00 00 00 |t bit...........|
000000c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000000d0 00 00 00 00 00 00 00 00 00 02 65 78 74 65 72 6e |..........extern|
000000e0 61 6c 20 64 61 74 61 20 66 69 6c 65 00 00 00 00 |al data file....|
000000f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000100 00 00 00 00 00 00 00 00 00 03 63 6f 6d 70 72 65 |..........compre|
00000110 73 73 69 6f 6e 20 74 79 70 65 00 00 00 00 00 00 |ssion type......|
00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000130 00 00 00 00 00 00 00 00 00 04 65 78 74 65 6e 64 |..........extend|
00000140 65 64 20 4c 32 20 65 6e 74 72 69 65 73 00 00 00 |ed L2 entries...|
00000150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000160 00 00 00 00 00 00 00 00 01 00 6c 61 7a 79 20 72 |..........lazy r|
00000170 65 66 63 6f 75 6e 74 73 00 00 00 00 00 00 00 00 |efcounts........|
00000180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000190 00 00 00 00 00 00 00 00 02 00 62 69 74 6d 61 70 |..........bitmap|
000001a0 73 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |s...............|
000001b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000001c0 00 00 00 00 00 00 00 00 02 01 72 61 77 20 65 78 |..........raw ex|
000001d0 74 65 72 6e 61 6c 20 64 61 74 61 00 00 00 00 00 |ternal data.....|
000001e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
1+0 records in
1+0 records out
512 bytes copied, 0.0314317 s, 16.3 kB/s
00000200
这对我来说根本不正确。因此我怀疑我在格式化文件时在某个地方犯了错误。有人有想法吗?
我希望至少能在文件的最后两个字节处看到 MBR 签名,即
0x55AA
。再次感谢!
对于感兴趣的人,这里是 github 存储库的链接:https://github.com/yfrandom2020/yb-os/tree/main
这里有多个问题。
1。代码错误
uint32_t lba_start;
void Read_MBR()
{
/// ...
uint32_t lba_start = firstPartition->relativeSector; // Actual beggining of our partition
/// ...
}
您看到这里的问题了吗?
以后如何避免这种情况?打开编译器警告。如果它们已打开,请阅读它们。如果您愿意的话,gcc 会警告您此错误。更好的是,将警告变成错误。
-Wall -Werror
是一种很好的生活方式——你会收到一些烦人的警告来处理,但这比不得不寻找一些愚蠢的错误要好得多。另外,删除-fpermissive
。
此外,学习如何调试内核也是个好主意。对于您当前的设置来说,这并不是一件难事,如果您知道如何进行调试,它可能会有很大帮助。只需将
-s -S
添加到您的 qemu 命令中,这样它就会等待 gdb 连接到它,运行 qemu,运行 gdb -ex "target remote :1234" objects/mykernel.bin
,在您想要挂钩的任何位置设置一些断点(比方说 break initializers()
),然后开始加载使用 cont
进行处理。
2。 Qcow 文件格式
告诉过你一次,现在再告诉你一次。使用原始磁盘驱动器格式而不是 qcow2。一般来说,qcow 是一种很好的用于虚拟机的格式,但在 qemu 之外使用它并不是那么透明。使用原始磁盘映像进行操作系统开发 - 您可以使用循环设备安装它,您可以
dd
它,您可以启动该死的十六进制编辑器并查看那里的实际是什么。如果你使用它,你可能会发现第三个问题:
3.没有 FAT 文件系统
正如标题所说。您已创建分区但从未格式化它。没有要读取的 FAT 元数据。