自制32位保护模式内核的FAT/EXT2文件系统C语言实现指引
Got it, let's walk through implementing a FAT32 file system for your custom 32-bit protected mode OS—since FAT32 is simpler to bootstrap than EXT2, I'll focus on that first, then touch on EXT2 basics if you want to dive deeper later. All code here is tailored for your GCC 10.2 cross-compiled environment (no libc dependencies, since you're building a standalone kernel).
First, you need to read/write disk sectors via PIO (Programmed Input/Output)—no fancy drivers needed for this. Here's a minimal implementation:
Assembly Helpers (for port I/O)
Save this as io.asm and link it into your kernel:
global inb global outb inb: mov edx, [esp + 4] in al, dx ret outb: mov edx, [esp + 4] mov al, [esp + 8] out dx, al ret
C Disk Read/Write Functions
#include <stdint.h> // Read a single sector from LBA address into buf void read_sector(uint32_t lba, uint8_t *buf) { // Send LBA address to disk ports outb(0x1F6, 0xE0 | ((lba >> 24) & 0xF)); outb(0x1F2, 1); // Number of sectors to read outb(0x1F3, (uint8_t)lba); outb(0x1F4, (uint8_t)(lba >> 8)); outb(0x1F5, (uint8_t)(lba >> 16)); outb(0x1F7, 0x20); // Issue read command // Wait for disk to be ready while (!(inb(0x1F7) & 0x80)); // Read 512 bytes (1 sector) into buf uint16_t *ptr = (uint16_t*)buf; for (int i = 0; i < 256; i++) { *ptr++ = inw(0x1F0); } } // Write a single sector from buf to LBA address void write_sector(uint32_t lba, uint8_t *buf) { outb(0x1F6, 0xE0 | ((lba >> 24) & 0xF)); outb(0x1F2, 1); outb(0x1F3, (uint8_t)lba); outb(0x1F4, (uint8_t)(lba >> 8)); outb(0x1F5, (uint8_t)(lba >> 16)); outb(0x1F7, 0x30); // Issue write command while (!(inb(0x1F7) & 0x80)); uint16_t *ptr = (uint16_t*)buf; for (int i = 0; i < 256; i++) { outw(0x1F0, *ptr++); } // Wait for write completion while (!(inb(0x1F7) & 0x80)); }
The BPB lives in the first sector of your FAT32 partition and contains critical filesystem metadata (sector size, cluster size, FAT location, etc.). Define a packed struct to map it:
typedef struct { uint8_t jump[3]; uint8_t oem[8]; uint16_t bytes_per_sector; uint8_t sectors_per_cluster; uint16_t reserved_sectors; uint8_t fat_count; uint16_t root_dir_entries; uint16_t total_sectors_16; uint8_t media_type; uint16_t fat_size_16; uint16_t sectors_per_track; uint16_t heads; uint32_t hidden_sectors; uint32_t total_sectors_32; uint32_t fat_size_32; uint16_t ext_flags; uint16_t fs_version; uint32_t root_cluster; uint16_t fs_info_sector; uint16_t backup_boot_sector; uint8_t reserved[12]; uint8_t drive_number; uint8_t reserved1; uint8_t boot_signature; uint32_t volume_id; uint8_t volume_label[11]; uint8_t fs_type[8]; } __attribute__((packed)) FAT32_BPB; FAT32_BPB g_fat_bpb; // Load and validate the FAT32 BPB from the partition's start LBA void load_fat32_bpb(uint32_t partition_start_lba) { read_sector(partition_start_lba, (uint8_t*)&g_fat_bpb); // Check if this is actually a FAT32 partition if (memcmp(g_fat_bpb.fs_type, "FAT32 ", 8) != 0) { // Panic if not FAT32 (replace with your error handling) while(1); } } // Minimal memcmp implementation (no libc) int memcmp(const void *a, const void *b, size_t len) { const uint8_t *ap = (const uint8_t*)a; const uint8_t *bp = (const uint8_t*)b; for (size_t i = 0; i < len; i++) { if (ap[i] != bp[i]) return ap[i] - bp[i]; } return 0; }
The FAT table tracks cluster links (which cluster comes next for a file/directory). Here's how to read and update FAT entries:
// Get the next cluster in the chain for a given current cluster uint32_t get_next_cluster(uint32_t current_cluster, uint32_t partition_start_lba) { uint32_t fat_start_lba = partition_start_lba + g_fat_bpb.reserved_sectors; uint32_t fat_offset = current_cluster * 4; uint32_t sector = fat_start_lba + (fat_offset / g_fat_bpb.bytes_per_sector); uint32_t offset = fat_offset % g_fat_bpb.bytes_per_sector; uint8_t sector_buf[512]; read_sector(sector, sector_buf); uint32_t next_cluster = *(uint32_t*)(sector_buf + offset); // Mask out reserved high 4 bits in FAT32 entries return next_cluster & 0x0FFFFFFF; } // Update a FAT entry to point to a new cluster (or mark as end of chain) void update_fat_entry(uint32_t cluster, uint32_t value, uint32_t partition_start_lba) { uint32_t fat_start_lba = partition_start_lba + g_fat_bpb.reserved_sectors; uint32_t fat_offset = cluster * 4; uint32_t sector = fat_start_lba + (fat_offset / g_fat_bpb.bytes_per_sector); uint32_t offset = fat_offset % g_fat_bpb.bytes_per_sector; uint8_t sector_buf[512]; read_sector(sector, sector_buf); // Preserve reserved high 4 bits *(uint32_t*)(sector_buf + offset) = (value & 0x0FFFFFFF) | 0xF0000000; write_sector(sector, sector_buf); }
FAT32 directories are stored as clusters of 32-byte directory entries. Here's how to list files in a directory (starting with the root directory at g_fat_bpb.root_cluster):
typedef struct { uint8_t name[8]; uint8_t ext[3]; uint8_t attributes; uint8_t reserved; uint8_t create_time_tenths; uint16_t create_time; uint16_t create_date; uint16_t last_access_date; uint16_t first_cluster_high; uint16_t write_time; uint16_t write_date; uint16_t first_cluster_low; uint32_t file_size; } __attribute__((packed)) FAT32_DirEntry; // Minimal printf/putchar for output (use your serial/vga output if you have it) void putchar(char c) { while (!(inb(0x3F8 + 5) & 0x20)); outb(0x3F8, c); } void printf(const char *fmt, ...); // Forward declaration // List all files/directories in a given starting cluster void list_directory(uint32_t start_cluster, uint32_t partition_start_lba) { uint32_t current_cluster = start_cluster; while (1) { // Calculate the starting LBA of the current cluster uint32_t cluster_lba = partition_start_lba + g_fat_bpb.reserved_sectors + (g_fat_bpb.fat_count * g_fat_bpb.fat_size_32) + (current_cluster - 2) * g_fat_bpb.sectors_per_cluster; // Read all sectors in the cluster for (int i = 0; i < g_fat_bpb.sectors_per_cluster; i++) { uint8_t sector_buf[512]; read_sector(cluster_lba + i, sector_buf); FAT32_DirEntry *entries = (FAT32_DirEntry*)sector_buf; for (int j = 0; j < 512 / sizeof(FAT32_DirEntry); j++) { FAT32_DirEntry *entry = &entries[j]; // Skip deleted entries or end of directory if (entry->name[0] == 0xE5 || entry->name[0] == 0x00) { if (entry->name[0] == 0x00) return; continue; } // Skip system/volume label entries if ((entry->attributes & 0x08) || (entry->attributes & 0x10)) continue; // Format filename to 8.3 format char filename[13] = {0}; memcpy(filename, entry->name, 8); memcpy(filename + 8, entry->ext, 3); printf("%s ", filename); // Mark directories if (entry->attributes & 0x10) { printf("[DIR]\n"); } else { printf("(%u bytes)\n", entry->file_size); } } } // Move to next cluster in the directory chain current_cluster = get_next_cluster(current_cluster, partition_start_lba); if (current_cluster >= 0x0FFFFFF8) break; // End of chain } } // Minimal printf implementation (supports %s and %u) #include <stdarg.h> void printf(const char *fmt, ...) { va_list args; va_start(args, fmt); while (*fmt) { if (*fmt == '%') { fmt++; if (*fmt == 's') { char *s = va_arg(args, char*); while (*s) putchar(*s++); } else if (*fmt == 'u') { uint32_t num = va_arg(args, uint32_t); char buf[10] = {0}; int i = 9; if (num == 0) buf[i--] = '0'; else while (num > 0) { buf[i--] = (num % 10) + '0'; num /= 10; } for (int j = i+1; j < 10; j++) putchar(buf[j]); } fmt++; } else { putchar(*fmt++); } } va_end(args); }
To read a file, follow its cluster chain and copy data into a buffer:
// Read a file from a directory entry into buf void read_file(FAT32_DirEntry *entry, uint8_t *buf, uint32_t partition_start_lba) { uint32_t current_cluster = ((uint32_t)entry->first_cluster_high << 16) | entry->first_cluster_low; uint32_t bytes_read = 0; while (bytes_read < entry->file_size) { uint32_t cluster_lba = partition_start_lba + g_fat_bpb.reserved_sectors + (g_fat_bpb.fat_count * g_fat_bpb.fat_size_32) + (current_cluster - 2) * g_fat_bpb.sectors_per_cluster; for (int i = 0; i < g_fat_bpb.sectors_per_cluster; i++) { if (bytes_read >= entry->file_size) break; uint32_t bytes_to_read = g_fat_bpb.bytes_per_sector; if (bytes_read + bytes_to_read > entry->file_size) { bytes_to_read = entry->file_size - bytes_read; } uint8_t sector_buf[512]; read_sector(cluster_lba + i, sector_buf); memcpy(buf + bytes_read, sector_buf, bytes_to_read); bytes_read += bytes_to_read; } current_cluster = get_next_cluster(current_cluster, partition_start_lba); if (current_cluster >= 0x0FFFFFF8) break; } }
Creating a file involves finding an empty directory entry, allocating a cluster, and updating the FAT:
// Find a free cluster in the FAT table uint32_t find_free_cluster(uint32_t partition_start_lba) { uint32_t fat_start_lba = partition_start_lba + g_fat_bpb.reserved_sectors; uint32_t fat_total_bytes = g_fat_bpb.fat_size_32 * g_fat_bpb.bytes_per_sector; for (uint32_t offset = 0; offset < fat_total_bytes; offset += 4) { uint32_t sector = fat_start_lba + (offset / g_fat_bpb.bytes_per_sector); uint32_t sect_offset = offset % g_fat_bpb.bytes_per_sector; uint8_t sector_buf[512]; read_sector(sector, sector_buf); uint32_t cluster_val = *(uint32_t*)(sector_buf + sect_offset) & 0x0FFFFFFF; if (cluster_val == 0) { return (offset / 4) + 2; // Cluster numbers start at 2 } } return 0; // No free clusters } // Minimal string helpers char *strchr(const char *s, int c) { while (*s) { if (*s == c) return (char*)s; s++; } return NULL; } char *strncpy(char *dest, const char *src, size_t n) { size_t i; for (i = 0; i < n && src[i]; i++) dest[i] = src[i]; for (; i < n; i++) dest[i] = '\0'; return dest; } // Create a new file in the given parent directory cluster int create_file(const char *filename, uint32_t parent_cluster, uint32_t partition_start_lba) { // Find an empty directory entry FAT32_DirEntry *empty_entry = NULL; uint32_t entry_sector = 0; int entry_idx = 0; uint32_t current_cluster = parent_cluster; while (1) { uint32_t cluster_lba = partition_start_lba + g_fat_bpb.reserved_sectors + (g_fat_bpb.fat_count * g_fat_bpb.fat_size_32) + (current_cluster - 2) * g_fat_bpb.sectors_per_cluster; for (int i = 0; i < g_fat_bpb.sectors_per_cluster; i++) { uint8_t sector_buf[512]; read_sector(cluster_lba + i, sector_buf); FAT32_DirEntry *entries = (FAT32_DirEntry*)sector_buf; for (int j = 0; j < 512 / sizeof(FAT32_DirEntry); j++) { if (entries[j].name[0] == 0xE5 || entries[j].name[0] == 0x00) { empty_entry = &entries[j]; entry_sector = cluster_lba + i; entry_idx

