硬盘及分区表
磁盘分区表浅析
磁盘分区表( Disk Partition Table )简称 DPT,是由多个分区元信息汇成的表,表中每一个表项都对应一个分区,主要记录各分区的起始扇区地址,大小界限等 。
分区表是由分区工具如 fdisk 创建的,但却是给操作系统使用的。听我这么一说,大伙儿不要误以为操作系统很“弱”,其实操作系统也可以创建分区表,它有底层硬件的一切操作权限,无所不能,只是在通常情况下操作系统直接安装在某个分区上,所以分区表要在内核安装之前建立好,因此分区工具通常独立于操作系统 。 有了分区表,操作系统(的文件系统)可以根据各表项中的信息对硬盘进行分区管理,只要按照表项中的信息访问磁盘,就不会出现分区越界的情况。分区表既然称为“表”,这表示各个表项的数据结构一致,因此磁盘分区表就是个数组,此数组长度固定为 4,数组元素是分区元信息的结构。

讯享网
一个两全其美的方案是视这个扩展分区为总扩展分区,将它划分成多个子扩展分区,每个子扩展分区“在逻辑上”相当于硬盘,因此每个子扩展分区都可以有 1 个分区表。这样一来,各个分区表的长度依然固定为 4,但是允许有无限多个分区表,分区表项多了,自然支持的分区数就多了。如何将这些分区表组织到一起呢?扩展分区表采用链式结构,将所有子扩展分区的分区表串在一起,形成可容纳无限个分区表的单向链表。链表是要有结点的,这里的每个分区表就是结点,一般的链表结点除了包括数据外,还必须要包括下一个结点的地址,分区表也采用了这种结构,其表项就分为两部分, 一部分是描述逻辑分区的信息,另一部分是描述下一个子扩展分区的地址 。
扩展分区被划分出多个子扩展分区,每个子扩展分区都有自己的分区表,所以子扩展分区在逻辑上相当于单独的硬盘,各分区表在各个子扩展分区最开始的扇区中,该扇区同MBR 引导扇区结构相同,由于是经扩展分区划分出来的,所以它们称为 EBR,即扩展引导记录 。( 现在想想看,主分区之所以称为“主气也许是与其分区表项位于岛ffiR 引导扇区中有关吧。 ) MBR 只有 1 个, EBR理论上有无限个, MBR 和 EBR 所在的扇区统称为引导扇区,它们的结构是相同的, MBR 中有的 E岛1R 中也有。我想这下您清楚了我为什么要把子扩展分区视为硬盘,因为各子扩展分区的结构也同整个硬盘结构一样,最开始的扇区都是引导扇区,中间都是空闲一小部分扇区,最后的大片扇区空间作为数据存储的分区 。
每一个逻辑分区所在的子扩展分区都有一个与 MBR 结构相同的 EBR, EBR 中分区表的第一分区表项用来描述所包含的逻辑分区的元信息,第二分区表项用来描述下一个子扩展分区的地址,第三、四表项未用到。位于 EBR 中的分区表相当于链表中的结点,第一个分区表项存的是分区数据,第二个分区表项存的是后继分区的指针。

活动分区是指引导程序所在的分区,活动分区标记是给 MBR 或其他需要移交 CPU 使用权的程序看的,它们通过此位来判断该分区的引导扇区中是否有可执行的程序,也就是引导程序,这个引导程序通常是操作系统内核加载器,故此引导程序通常被称为操作系统引导记录,即 OBR (OS Boot Record ) 。 如果MBR 发现该分区表项的活动分区标记为 Ox80,这就表示该分区的引导扇区中有引导程序(这是 MBR 与分区工具或操作系统约定好的), MBR 就将 CPU 使用权交给此引导程序, 如果此引导程序是操作系统或其加载器,此时操作系统便掌握了 CPU 使用权,也就是大家平时所说的加载内核。这里一直说的“分区引导扇区”是位于分区最开始的扇区,是分区引导程序所在的扇区,由于此引导程序通常都是操作系统内核加载器,故此扇区被称为操作系统引导扇区,也就是 OBR 所在的扇区,即 OBR 引导扇区。注意啦, OBR引导扇区并不是 EBR 或 MBR 引导扇区,它们虽然都包含引导程序,并且都以 Ox55 和 Ox剧结束,但它们最大的区别是分区表只包含在 MBR 和 EBR 中, OBR 中可没有分区表。 MBR 和 EBR 所在的扇区不属于分区范围之内,它们是由分区工具创建并管理的,因此不归操作系统管理,操作系统不可以随意往里面写数据,尽管操作系统有能力这样做。而 OBR 引导扇区是分区中最开始的扇区,归操作系统的文件系统管理,因此操作系统通常往 OBR 引导扇区中添加内核加载器的代码,供 MBR 调用以实现操作系统自举,总之 OBR 引导扇区中绝琦不包括分区表。

编写硬盘驱动程序
硬盘初始化
#ifndef __DEVICE_IDE_H #define __DEVICE_IDE_H #include "stdint.h" #include "sync.h" #include "bitmap.h" /* 分区结构 */ struct partition {
uint32_t start_lba; // 起始扇区 uint32_t sec_cnt; // 扇区数 struct disk* my_disk; // 分区所属的硬盘 struct list_elem part_tag; // 用于队列中的标记 char name[8]; // 分区名称 struct super_block* sb; // 本分区的超级块 struct bitmap block_bitmap; // 块位图 struct bitmap inode_bitmap; // i结点位图 struct list open_inodes; // 本分区打开的i结点队列 }; /* 硬盘结构 */ struct disk {
char name[8]; // 本硬盘的名称,如sda等 struct ide_channel* my_channel; // 此块硬盘归属于哪个ide通道 uint8_t dev_no; // 本硬盘是主0还是从1 struct partition prim_parts[4]; // 主分区顶多是4个 struct partition logic_parts[8]; // 逻辑分区数量无限,但总得有个支持的上限,那就支持8个 }; /* ata通道结构 */ struct ide_channel {
char name[8]; // 本ata通道名称 uint16_t port_base; // 本通道的起始端口号 uint8_t irq_no; // 本通道所用的中断号 struct lock lock; // 通道锁 bool expecting_intr; // 表示等待硬盘的中断 struct semaphore disk_done; // 用于阻塞、唤醒驱动程序 struct disk devices[2]; // 一个通道上连接两个硬盘,一主一从 }; void ide_init(void); extern uint8_t channel_cnt; extern struct ide_channel channels[]; #endif
讯享网
讯享网#include "ide.h" #include "sync.h" #include "stdio.h" #include "stdio-kernel.h" #include "interrupt.h" #include "memory.h" #include "debug.h" #include "string.h" /* 定义硬盘各寄存器的端口号 */ #define reg_data(channel) (channel->port_base + 0) #define reg_error(channel) (channel->port_base + 1) #define reg_sect_cnt(channel) (channel->port_base + 2) #define reg_lba_l(channel) (channel->port_base + 3) #define reg_lba_m(channel) (channel->port_base + 4) #define reg_lba_h(channel) (channel->port_base + 5) #define reg_dev(channel) (channel->port_base + 6) #define reg_status(channel) (channel->port_base + 7) #define reg_cmd(channel) (reg_status(channel)) #define reg_alt_status(channel) (channel->port_base + 0x206) #define reg_ctl(channel) reg_alt_status(channel) /* reg_alt_status寄存器的一些关键位 */ #define BIT_STAT_BSY 0x80 // 硬盘忙 #define BIT_STAT_DRDY 0x40 // 驱动器准备好 #define BIT_STAT_DRQ 0x8 // 数据传输准备好了 /* device寄存器的一些关键位 */ #define BIT_DEV_MBS 0xa0 // 第7位和第5位固定为1 #define BIT_DEV_LBA 0x40 #define BIT_DEV_DEV 0x10 /* 一些硬盘操作的指令 */ #define CMD_IDENTIFY 0xec // identify指令 #define CMD_READ_SECTOR 0x20 // 读扇区指令 #define CMD_WRITE_SECTOR 0x30 // 写扇区指令 /* 定义可读写的最大扇区数,调试用的 */ #define max_lba ((80*1024*1024/512) - 1) // 只支持80MB硬盘 uint8_t channel_cnt; // 按硬盘数计算的通道数 struct ide_channel channels[2]; // 有两个ide通道 /* 硬盘数据结构初始化 */ void ide_init() {
printk("ide_init start\n"); uint8_t hd_cnt = *((uint8_t*)(0x475)); // 获取硬盘的数量 ASSERT(hd_cnt > 0); channel_cnt = DIV_ROUND_UP(hd_cnt, 2); // 一个ide通道上有两个硬盘,根据硬盘数量反推有几个ide通道 struct ide_channel* channel; uint8_t channel_no = 0; /* 处理每个通道上的硬盘 */ while (channel_no < channel_cnt) {
channel = &channels[channel_no]; sprintf(channel->name, "ide%d", channel_no); /* 为每个ide通道初始化端口基址及中断向量 */ switch (channel_no) {
case 0: channel->port_base = 0x1f0; // ide0通道的起始端口号是0x1f0 channel->irq_no = 0x20 + 14; // 从片8259a上倒数第二的中断引脚,温盘,也就是ide0通道的的中断向量号 break; case 1: channel->port_base = 0x170; // ide1通道的起始端口号是0x170 channel->irq_no = 0x20 + 15; // 从8259A上的最后一个中断引脚,我们用来响应ide1通道上的硬盘中断 break; } channel->expecting_intr = false; // 未向硬盘写入指令时不期待硬盘的中断 lock_init(&channel->lock); /* 初始化为0,目的是向硬盘控制器请求数据后,硬盘驱动sema_down此信号量会阻塞线程, 直到硬盘完成后通过发中断,由中断处理程序将此信号量sema_up,唤醒线程. */ sema_init(&channel->disk_done, 0); channel_no++; // 下一个channel } printk("ide_init done\n"); }
实现thread_yidld和idle线程
#include "thread.h" #include "stdint.h" #include "string.h" #include "global.h" #include "debug.h" #include "interrupt.h" #include "print.h" #include "memory.h" #include "process.h" #include "sync.h" struct task_struct* main_thread; // 主线程PCB struct task_struct* idle_thread; // idle线程 struct list thread_ready_list; // 就绪队列 struct list thread_all_list; // 所有任务队列 struct lock pid_lock; // 分配pid锁 static struct list_elem* thread_tag;// 用于保存队列中的线程结点 extern void switch_to(struct task_struct* cur, struct task_struct* next); /* 系统空闲时运行的线程 */ static void idle(void* arg UNUSED) {
while(1) {
thread_block(TASK_BLOCKED); //执行hlt时必须要保证目前处在开中断的情况下 asm volatile ("sti; hlt" : : : "memory"); } } /* 获取当前线程pcb指针 */ struct task_struct* running_thread() {
uint32_t esp; asm ("mov %%esp, %0" : "=g" (esp)); /* 取esp整数部分即pcb起始地址 */ return (struct task_struct*)(esp & 0xfffff000); } /* 由kernel_thread去执行function(func_arg) */ static void kernel_thread(thread_func* function, void* func_arg) {
/* 执行function前要开中断,避免后面的时钟中断被屏蔽,而无法调度其它线程 */ intr_enable(); function(func_arg); } /* 分配pid */ static pid_t allocate_pid(void) {
static pid_t next_pid = 0; lock_acquire(&pid_lock); next_pid++; lock_release(&pid_lock); return next_pid; } /* 初始化线程栈thread_stack,将待执行的函数和参数放到thread_stack中相应的位置 */ void thread_create(struct task_struct* pthread, thread_func function, void* func_arg) {
/* 先预留中断使用栈的空间,可见thread.h中定义的结构 */ pthread->self_kstack -= sizeof(struct intr_stack); /* 再留出线程栈空间,可见thread.h中定义 */ pthread->self_kstack -= sizeof(struct thread_stack); struct thread_stack* kthread_stack = (struct thread_stack*)pthread->self_kstack; kthread_stack->eip = kernel_thread; kthread_stack->function = function; kthread_stack->func_arg = func_arg; kthread_stack->ebp = kthread_stack->ebx = kthread_stack->esi = kthread_stack->edi = 0; } /* 初始化线程基本信息 */ void init_thread(struct task_struct* pthread, char* name, int prio) {
memset(pthread, 0, sizeof(*pthread)); pthread->pid = allocate_pid(); strcpy(pthread->name, name); if (pthread == main_thread) {
/* 由于把main函数也封装成一个线程,并且它一直是运行的,故将其直接设为TASK_RUNNING */ pthread->status = TASK_RUNNING; } else {
pthread->status = TASK_READY; } /* self_kstack是线程自己在内核态下使用的栈顶地址 */ pthread->self_kstack = (uint32_t*)((uint32_t)pthread + PG_SIZE); pthread->priority = prio; pthread->ticks = prio; pthread->elapsed_ticks = 0; pthread->pgdir = NULL; pthread->stack_magic = 0x; // 自定义的魔数 } /* 创建一优先级为prio的线程,线程名为name,线程所执行的函数是function(func_arg) */ struct task_struct* thread_start(char* name, int prio, thread_func function, void* func_arg) {
/* pcb都位于内核空间,包括用户进程的pcb也是在内核空间 */ struct task_struct* thread = get_kernel_pages(1); init_thread(thread, name, prio); thread_create(thread, function, func_arg); /* 确保之前不在队列中 */ ASSERT(!elem_find(&thread_ready_list, &thread->general_tag)); /* 加入就绪线程队列 */ list_append(&thread_ready_list, &thread->general_tag); /* 确保之前不在队列中 */ ASSERT(!elem_find(&thread_all_list, &thread->all_list_tag)); /* 加入全部线程队列 */ list_append(&thread_all_list, &thread->all_list_tag); return thread; } /* 将kernel中的main函数完善为主线程 */ static void make_main_thread(void) {
/* 因为main线程早已运行,咱们在loader.S中进入内核时的mov esp,0xc009f000, 就是为其预留了tcb,地址为0xc009e000,因此不需要通过get_kernel_page另分配一页*/ main_thread = running_thread(); init_thread(main_thread, "main", 31); /* main函数是当前线程,当前线程不在thread_ready_list中, * 所以只将其加在thread_all_list中. */ ASSERT(!elem_find(&thread_all_list, &main_thread->all_list_tag)); list_append(&thread_all_list, &main_thread->all_list_tag); } /* 实现任务调度 */ void schedule() {
ASSERT(intr_get_status() == INTR_OFF); struct task_struct* cur = running_thread(); if (cur->status == TASK_RUNNING) {
// 若此线程只是cpu时间片到了,将其加入到就绪队列尾 ASSERT(!elem_find(&thread_ready_list, &cur->general_tag)); list_append(&thread_ready_list, &cur->general_tag); cur->ticks = cur->priority; // 重新将当前线程的ticks再重置为其priority; cur->status = TASK_READY; } else {
/* 若此线程需要某事件发生后才能继续上cpu运行, 不需要将其加入队列,因为当前线程不在就绪队列中。*/ } /* 如果就绪队列中没有可运行的任务,就唤醒idle */ if (list_empty(&thread_ready_list)) {
thread_unblock(idle_thread); } ASSERT(!list_empty(&thread_ready_list)); thread_tag = NULL; // thread_tag清空 /* 将thread_ready_list队列中的第一个就绪线程弹出,准备将其调度上cpu. */ thread_tag = list_pop(&thread_ready_list); struct task_struct* next = elem2entry(struct task_struct, general_tag, thread_tag); next->status = TASK_RUNNING; /* 击活任务页表等 */ process_activate(next); switch_to(cur, next); } /* 当前线程将自己阻塞,标志其状态为stat. */ void thread_block(enum task_status stat) {
/* stat取值为TASK_BLOCKED,TASK_WAITING,TASK_HANGING,也就是只有这三种状态才不会被调度*/ ASSERT(((stat == TASK_BLOCKED) || (stat == TASK_WAITING) || (stat == TASK_HANGING))); enum intr_status old_status = intr_disable(); struct task_struct* cur_thread = running_thread(); cur_thread->status = stat; // 置其状态为stat schedule(); // 将当前线程换下处理器 /* 待当前线程被解除阻塞后才继续运行下面的intr_set_status */ intr_set_status(old_status); } /* 将线程pthread解除阻塞 */ void thread_unblock(struct task_struct* pthread) {
enum intr_status old_status = intr_disable(); ASSERT(((pthread->status == TASK_BLOCKED) || (pthread->status == TASK_WAITING) || (pthread->status == TASK_HANGING))); if (pthread->status != TASK_READY) {
ASSERT(!elem_find(&thread_ready_list, &pthread->general_tag)); if (elem_find(&thread_ready_list, &pthread->general_tag)) {
PANIC("thread_unblock: blocked thread in ready_list\n"); } list_push(&thread_ready_list, &pthread->general_tag); // 放到队列的最前面,使其尽快得到调度 pthread->status = TASK_READY; } intr_set_status(old_status); } /* 主动让出cpu,换其它线程运行 */ void thread_yield(void) {
struct task_struct* cur = running_thread(); enum intr_status old_status = intr_disable(); ASSERT(!elem_find(&thread_ready_list, &cur->general_tag)); list_append(&thread_ready_list, &cur->general_tag); cur->status = TASK_READY; schedule(); intr_set_status(old_status); } /* 初始化线程环境 */ void thread_init(void) {
put_str("thread_init start\n"); list_init(&thread_ready_list); list_init(&thread_all_list); lock_init(&pid_lock); /* 将当前main函数创建为线程 */ make_main_thread(); /* 创建idle线程 */ idle_thread = thread_start("idle", 10, idle, NULL); put_str("thread_init done\n"); }
idle 函数的实现,其原理是在函数体中执行“白read block(TASK_BLOCKED)”阻塞自己,在其被唤醒后,通过内联汇编执行 hit 指令,使系统挂起,达到真正的“空闲”。 hit 指令的功能让处理器停止执行指令,也就是将处理器挂起(并不是类似“jmp$ ”那样空兜 CPU, CPU 利用率 100% ),使处理器得到休息, CPU 利用率一下子就掉下来了,在那一小段时间 CPU 利用率为 0。处理器己经停止运行,因此并不会再产生内部异常,唯一能唤醒处理器的就是外部中断,当外部发生后,处理器恢复执行后面的指令。处理器需要被唤醒,必须要保证在开中断的情况下执行恤,因此内联汇编代码中,先执行 sti,再执行凶。顺便说一下, idle thread 在第一次创建时会被加入到就绪队列,因此会执行一次,然后阻塞。
实现简单的休眠函数
讯享网/* 以tick为单位的sleep,任何时间形式的sleep会转换此ticks形式 */ static void ticks_to_sleep(uint32_t sleep_ticks) {
uint32_t start_tick = ticks; /* 若间隔的ticks数不够便让出cpu */ while (ticks - start_tick < sleep_ticks) {
thread_yield(); } } /* 以毫秒为单位的sleep 1秒= 1000毫秒 */ void mtime_sleep(uint32_t m_seconds) {
uint32_t sleep_ticks = DIV_ROUND_UP(m_seconds, mil_seconds_per_intr); ASSERT(sleep_ticks > 0); ticks_to_sleep(sleep_ticks); }
完善硬盘驱动程序
#include "ide.h" #include "sync.h" #include "io.h" #include "stdio.h" #include "stdio-kernel.h" #include "interrupt.h" #include "memory.h" #include "debug.h" #include "console.h" #include "timer.h" #include "string.h" #include "list.h" /* 定义硬盘各寄存器的端口号 */ #define reg_data(channel) (channel->port_base + 0) #define reg_error(channel) (channel->port_base + 1) #define reg_sect_cnt(channel) (channel->port_base + 2) #define reg_lba_l(channel) (channel->port_base + 3) #define reg_lba_m(channel) (channel->port_base + 4) #define reg_lba_h(channel) (channel->port_base + 5) #define reg_dev(channel) (channel->port_base + 6) #define reg_status(channel) (channel->port_base + 7) #define reg_cmd(channel) (reg_status(channel)) #define reg_alt_status(channel) (channel->port_base + 0x206) #define reg_ctl(channel) reg_alt_status(channel) /* reg_status寄存器的一些关键位 */ #define BIT_STAT_BSY 0x80 // 硬盘忙 #define BIT_STAT_DRDY 0x40 // 驱动器准备好 #define BIT_STAT_DRQ 0x8 // 数据传输准备好了 /* device寄存器的一些关键位 */ #define BIT_DEV_MBS 0xa0 // 第7位和第5位固定为1 #define BIT_DEV_LBA 0x40 #define BIT_DEV_DEV 0x10 /* 一些硬盘操作的指令 */ #define CMD_IDENTIFY 0xec // identify指令 #define CMD_READ_SECTOR 0x20 // 读扇区指令 #define CMD_WRITE_SECTOR 0x30 // 写扇区指令 /* 定义可读写的最大扇区数,调试用的 */ #define max_lba ((80*1024*1024/512) - 1) // 只支持80MB硬盘 uint8_t channel_cnt; // 按硬盘数计算的通道数 struct ide_channel channels[2]; // 有两个ide通道 /* 选择读写的硬盘 */ static void select_disk(struct disk* hd) {
uint8_t reg_device = BIT_DEV_MBS | BIT_DEV_LBA; if (hd->dev_no == 1) {
// 若是从盘就置DEV位为1 reg_device |= BIT_DEV_DEV; } outb(reg_dev(hd->my_channel), reg_device); } /* 向硬盘控制器写入起始扇区地址及要读写的扇区数 */ static void select_sector(struct disk* hd, uint32_t lba, uint8_t sec_cnt) {
ASSERT(lba <= max_lba); struct ide_channel* channel = hd->my_channel; /* 写入要读写的扇区数*/ outb(reg_sect_cnt(channel), sec_cnt); // 如果sec_cnt为0,则表示写入256个扇区 /* 写入lba地址(即扇区号) */ outb(reg_lba_l(channel), lba); // lba地址的低8位,不用单独取出低8位.outb函数中的汇编指令outb %b0, %w1会只用al。 outb(reg_lba_m(channel), lba >> 8); // lba地址的8~15位 outb(reg_lba_h(channel), lba >> 16); // lba地址的16~23位 /* 因为lba地址的24~27位要存储在device寄存器的0~3位, * 无法单独写入这4位,所以在此处把device寄存器再重新写入一次*/ outb(reg_dev(channel), BIT_DEV_MBS | BIT_DEV_LBA | (hd->dev_no == 1 ? BIT_DEV_DEV : 0) | lba >> 24); } /* 向通道channel发命令cmd */ static void cmd_out(struct ide_channel* channel, uint8_t cmd) {
/* 只要向硬盘发出了命令便将此标记置为true,硬盘中断处理程序需要根据它来判断 */ channel->expecting_intr = true; outb(reg_cmd(channel), cmd); } /* 硬盘读入sec_cnt个扇区的数据到buf */ static void read_from_sector(struct disk* hd, void* buf, uint8_t sec_cnt) {
uint32_t size_in_byte; if (sec_cnt == 0) {
/* 因为sec_cnt是8位变量,由主调函数将其赋值时,若为256则会将最高位的1丢掉变为0 */ size_in_byte = 256 * 512; } else {
size_in_byte = sec_cnt * 512; } insw(reg_data(hd->my_channel), buf, size_in_byte / 2); } /* 将buf中sec_cnt扇区的数据写入硬盘 */ static void write2sector(struct disk* hd, void* buf, uint8_t sec_cnt) {
uint32_t size_in_byte; if (sec_cnt == 0) {
/* 因为sec_cnt是8位变量,由主调函数将其赋值时,若为256则会将最高位的1丢掉变为0 */ size_in_byte = 256 * 512; } else {
size_in_byte = sec_cnt * 512; } outsw(reg_data(hd->my_channel), buf, size_in_byte / 2); } /* 等待30秒 */ static bool busy_wait(struct disk* hd) {
struct ide_channel* channel = hd->my_channel; uint16_t time_limit = 30 * 1000; // 可以等待30000毫秒 while (time_limit -= 10 >= 0) {
if (!(inb(reg_status(channel)) & BIT_STAT_BSY)) {
return (inb(reg_status(channel)) & BIT_STAT_DRQ); } else {
mtime_sleep(10); // 睡眠10毫秒 } } return false; } /* 从硬盘读取sec_cnt个扇区到buf */ void ide_read(struct disk* hd, uint32_t lba, void* buf, uint32_t sec_cnt) {
ASSERT(lba <= max_lba); ASSERT(sec_cnt > 0); lock_acquire (&hd->my_channel->lock); /* 1 先选择操作的硬盘 */ select_disk(hd); uint32_t secs_op; // 每次操作的扇区数 uint32_t secs_done = 0; // 已完成的扇区数 while(secs_done < sec_cnt) {
if ((secs_done + 256) <= sec_cnt) {
secs_op = 256; } else {
secs_op = sec_cnt - secs_done; } /* 2 写入待读入的扇区数和起始扇区号 */ select_sector(hd, lba + secs_done, secs_op); /* 3 执行的命令写入reg_cmd寄存器 */ cmd_out(hd->my_channel, CMD_READ_SECTOR); // 准备开始读数据 /* 阻塞自己的时机 * 在硬盘已经开始工作(开始在内部读数据或写数据)后才能阻塞自己,现在硬盘已经开始忙了, 将自己阻塞,等待硬盘完成读操作后通过中断处理程序唤醒自己*/ sema_down(&hd->my_channel->disk_done); /*/ /* 4 检测硬盘状态是否可读 */ /* 醒来后开始执行下面代码*/ if (!busy_wait(hd)) {
// 若失败 char error[64]; sprintf(error, "%s read sector %d failed!!!!!!\n", hd->name, lba); PANIC(error); } /* 5 把数据从硬盘的缓冲区中读出 */ read_from_sector(hd, (void*)((uint32_t)buf + secs_done * 512), secs_op); secs_done += secs_op; } lock_release(&hd->my_channel->lock); } /* 将buf中sec_cnt扇区数据写入硬盘 */ void ide_write(struct disk* hd, uint32_t lba, void* buf, uint32_t sec_cnt) {
ASSERT(lba <= max_lba); ASSERT(sec_cnt > 0); lock_acquire (&hd->my_channel->lock); /* 1 先选择操作的硬盘 */ select_disk(hd); uint32_t secs_op; // 每次操作的扇区数 uint32_t secs_done = 0; // 已完成的扇区数 while(secs_done < sec_cnt) {
if ((secs_done + 256) <= sec_cnt) {
secs_op = 256; } else {
secs_op = sec_cnt - secs_done; } /* 2 写入待写入的扇区数和起始扇区号 */ select_sector(hd, lba + secs_done, secs_op); /* 3 执行的命令写入reg_cmd寄存器 */ cmd_out(hd->my_channel, CMD_WRITE_SECTOR); // 准备开始写数据 /* 4 检测硬盘状态是否可读 */ if (!busy_wait(hd)) {
// 若失败 char error[64]; sprintf(error, "%s write sector %d failed!!!!!!\n", hd->name, lba); PANIC(error); } /* 5 将数据写入硬盘 */ write2sector(hd, (void*)((uint32_t)buf + secs_done * 512), secs_op); /* 在硬盘响应期间阻塞自己 */ sema_down(&hd->my_channel->disk_done); secs_done += secs_op; } /* 醒来后开始释放锁*/ lock_release(&hd->my_channel->lock); } /* 硬盘中断处理程序 */ void intr_hd_handler(uint8_t irq_no) {
ASSERT(irq_no == 0x2e || irq_no == 0x2f); uint8_t ch_no = irq_no - 0x2e; struct ide_channel* channel = &channels[ch_no]; ASSERT(channel->irq_no == irq_no); /* 不必担心此中断是否对应的是这一次的expecting_intr, * 每次读写硬盘时会申请锁,从而保证了同步一致性 */ if (channel->expecting_intr) {
channel->expecting_intr = false; sema_up(&channel->disk_done); /* 读取状态寄存器使硬盘控制器认为此次的中断已被处理,从而硬盘可以继续执行新的读写 */ inb(reg_status(channel)); } } /* 硬盘数据结构初始化 */ void ide_init() {
printk("ide_init start\n"); uint8_t hd_cnt = *((uint8_t*)(0x475)); // 获取硬盘的数量 ASSERT(hd_cnt > 0); channel_cnt = DIV_ROUND_UP(hd_cnt, 2); // 一个ide通道上有两个硬盘,根据硬盘数量反推有几个ide通道 struct ide_channel* channel; uint8_t channel_no = 0; /* 处理每个通道上的硬盘 */ while (channel_no < channel_cnt) {
channel = &channels[channel_no]; sprintf(channel->name, "ide%d", channel_no); /* 为每个ide通道初始化端口基址及中断向量 */ switch (channel_no) {
case 0: channel->port_base = 0x1f0; // ide0通道的起始端口号是0x1f0 channel->irq_no = 0x20 + 14; // 从片8259a上倒数第二的中断引脚,温盘,也就是ide0通道的的中断向量号 break; case 1: channel->port_base = 0x170; // ide1通道的起始端口号是0x170 channel->irq_no = 0x20 + 15; // 从8259A上的最后一个中断引脚,我们用来响应ide1通道上的硬盘中断 break; } channel->expecting_intr = false; // 未向硬盘写入指令时不期待硬盘的中断 lock_init(&channel->lock); /* 初始化为0,目的是向硬盘控制器请求数据后,硬盘驱动sema_down此信号量会阻塞线程, 直到硬盘完成后通过发中断,由中断处理程序将此信号量sema_up,唤醒线程. */ sema_init(&channel->disk_done, 0); register_handler(channel->irq_no, intr_hd_handler); channel_no++; // 下一个channel } printk("ide_init done\n"); }
获取硬盘信息,扫描分区表

讯享网#include "ide.h" #include "sync.h" #include "io.h" #include "stdio.h" #include "stdio-kernel.h" #include "interrupt.h" #include "memory.h" #include "debug.h" #include "console.h" #include "timer.h" #include "string.h" #include "list.h" /* 定义硬盘各寄存器的端口号 */ #define reg_data(channel) (channel->port_base + 0) #define reg_error(channel) (channel->port_base + 1) #define reg_sect_cnt(channel) (channel->port_base + 2) #define reg_lba_l(channel) (channel->port_base + 3) #define reg_lba_m(channel) (channel->port_base + 4) #define reg_lba_h(channel) (channel->port_base + 5) #define reg_dev(channel) (channel->port_base + 6) #define reg_status(channel) (channel->port_base + 7) #define reg_cmd(channel) (reg_status(channel)) #define reg_alt_status(channel) (channel->port_base + 0x206) #define reg_ctl(channel) reg_alt_status(channel) /* reg_status寄存器的一些关键位 */ #define BIT_STAT_BSY 0x80 // 硬盘忙 #define BIT_STAT_DRDY 0x40 // 驱动器准备好 #define BIT_STAT_DRQ 0x8 // 数据传输准备好了 /* device寄存器的一些关键位 */ #define BIT_DEV_MBS 0xa0 // 第7位和第5位固定为1 #define BIT_DEV_LBA 0x40 #define BIT_DEV_DEV 0x10 /* 一些硬盘操作的指令 */ #define CMD_IDENTIFY 0xec // identify指令 #define CMD_READ_SECTOR 0x20 // 读扇区指令 #define CMD_WRITE_SECTOR 0x30 // 写扇区指令 /* 定义可读写的最大扇区数,调试用的 */ #define max_lba ((80*1024*1024/512) - 1) // 只支持80MB硬盘 uint8_t channel_cnt; // 按硬盘数计算的通道数 struct ide_channel channels[2]; // 有两个ide通道 /* 用于记录总扩展分区的起始lba,初始为0,partition_scan时以此为标记 */ int32_t ext_lba_base = 0; uint8_t p_no = 0, l_no = 0; // 用来记录硬盘主分区和逻辑分区的下标 struct list partition_list; // 分区队列 /* 构建一个16字节大小的结构体,用来存分区表项 */ struct partition_table_entry {
uint8_t bootable; // 是否可引导 uint8_t start_head; // 起始磁头号 uint8_t start_sec; // 起始扇区号 uint8_t start_chs; // 起始柱面号 uint8_t fs_type; // 分区类型 uint8_t end_head; // 结束磁头号 uint8_t end_sec; // 结束扇区号 uint8_t end_chs; // 结束柱面号 /* 更需要关注的是下面这两项 */ uint32_t start_lba; // 本分区起始扇区的lba地址 uint32_t sec_cnt; // 本分区的扇区数目 } __attribute__ ((packed)); // 保证此结构是16字节大小 /* 引导扇区,mbr或ebr所在的扇区 */ struct boot_sector {
uint8_t other[446]; // 引导代码 struct partition_table_entry partition_table[4]; // 分区表中有4项,共64字节 uint16_t signature; // 启动扇区的结束标志是0x55,0xaa, } __attribute__ ((packed)); /* 选择读写的硬盘 */ static void select_disk(struct disk* hd) {
uint8_t reg_device = BIT_DEV_MBS | BIT_DEV_LBA; if (hd->dev_no == 1) {
// 若是从盘就置DEV位为1 reg_device |= BIT_DEV_DEV; } outb(reg_dev(hd->my_channel), reg_device); } /* 向硬盘控制器写入起始扇区地址及要读写的扇区数 */ static void select_sector(struct disk* hd, uint32_t lba, uint8_t sec_cnt) {
ASSERT(lba <= max_lba); struct ide_channel* channel = hd->my_channel; /* 写入要读写的扇区数*/ outb(reg_sect_cnt(channel), sec_cnt); // 如果sec_cnt为0,则表示写入256个扇区 /* 写入lba地址(即扇区号) */ outb(reg_lba_l(channel), lba); // lba地址的低8位,不用单独取出低8位.outb函数中的汇编指令outb %b0, %w1会只用al。 outb(reg_lba_m(channel), lba >> 8); // lba地址的8~15位 outb(reg_lba_h(channel), lba >> 16); // lba地址的16~23位 /* 因为lba地址的24~27位要存储在device寄存器的0~3位, * 无法单独写入这4位,所以在此处把device寄存器再重新写入一次*/ outb(reg_dev(channel), BIT_DEV_MBS | BIT_DEV_LBA | (hd->dev_no == 1 ? BIT_DEV_DEV : 0) | lba >> 24); } /* 向通道channel发命令cmd */ static void cmd_out(struct ide_channel* channel, uint8_t cmd) {
/* 只要向硬盘发出了命令便将此标记置为true,硬盘中断处理程序需要根据它来判断 */ channel->expecting_intr = true; outb(reg_cmd(channel), cmd); } /* 硬盘读入sec_cnt个扇区的数据到buf */ static void read_from_sector(struct disk* hd, void* buf, uint8_t sec_cnt) {
uint32_t size_in_byte; if (sec_cnt == 0) {
/* 因为sec_cnt是8位变量,由主调函数将其赋值时,若为256则会将最高位的1丢掉变为0 */ size_in_byte = 256 * 512; } else {
size_in_byte = sec_cnt * 512; } insw(reg_data(hd->my_channel), buf, size_in_byte / 2); } /* 将buf中sec_cnt扇区的数据写入硬盘 */ static void write2sector(struct disk* hd, void* buf, uint8_t sec_cnt) {
uint32_t size_in_byte; if (sec_cnt == 0) {
/* 因为sec_cnt是8位变量,由主调函数将其赋值时,若为256则会将最高位的1丢掉变为0 */ size_in_byte = 256 * 512; } else {
size_in_byte = sec_cnt * 512; } outsw(reg_data(hd->my_channel), buf, size_in_byte / 2); } /* 等待30秒 */ static bool busy_wait(struct disk* hd) {
struct ide_channel* channel = hd->my_channel; uint16_t time_limit = 30 * 1000; // 可以等待30000毫秒 while (time_limit -= 10 >= 0) {
if (!(inb(reg_status(channel)) & BIT_STAT_BSY)) {
return (inb(reg_status(channel)) & BIT_STAT_DRQ); } else {
mtime_sleep(10); // 睡眠10毫秒 } } return false; } /* 从硬盘读取sec_cnt个扇区到buf */ void ide_read(struct disk* hd, uint32_t lba, void* buf, uint32_t sec_cnt) {
// 此处的sec_cnt为32位大小 ASSERT(lba <= max_lba); ASSERT(sec_cnt > 0); lock_acquire (&hd->my_channel->lock); /* 1 先选择操作的硬盘 */ select_disk(hd); uint32_t secs_op; // 每次操作的扇区数 uint32_t secs_done = 0; // 已完成的扇区数 while(secs_done < sec_cnt) {
if ((secs_done + 256) <= sec_cnt) {
secs_op = 256; } else {
secs_op = sec_cnt - secs_done; } /* 2 写入待读入的扇区数和起始扇区号 */ select_sector(hd, lba + secs_done, secs_op); /* 3 执行的命令写入reg_cmd寄存器 */ cmd_out(hd->my_channel, CMD_READ_SECTOR); // 准备开始读数据 /* 阻塞自己的时机 * 在硬盘已经开始工作(开始在内部读数据或写数据)后才能阻塞自己,现在硬盘已经开始忙了, 将自己阻塞,等待硬盘完成读操作后通过中断处理程序唤醒自己*/ sema_down(&hd->my_channel->disk_done); /*/ /* 4 检测硬盘状态是否可读 */ /* 醒来后开始执行下面代码*/ if (!busy_wait(hd)) {
// 若失败 char error[64]; sprintf(error, "%s read sector %d failed!!!!!!\n", hd->name, lba); PANIC(error); } /* 5 把数据从硬盘的缓冲区中读出 */ read_from_sector(hd, (void*)((uint32_t)buf + secs_done * 512), secs_op); secs_done += secs_op; } lock_release(&hd->my_channel->lock); } /* 将buf中sec_cnt扇区数据写入硬盘 */ void ide_write(struct disk* hd, uint32_t lba, void* buf, uint32_t sec_cnt) {
ASSERT(lba <= max_lba); ASSERT(sec_cnt > 0); lock_acquire (&hd->my_channel->lock); /* 1 先选择操作的硬盘 */ select_disk(hd); uint32_t secs_op; // 每次操作的扇区数 uint32_t secs_done = 0; // 已完成的扇区数 while(secs_done < sec_cnt) {
if ((secs_done + 256) <= sec_cnt) {
secs_op = 256; } else {
secs_op = sec_cnt - secs_done; } /* 2 写入待写入的扇区数和起始扇区号 */ select_sector(hd, lba + secs_done, secs_op); // 先将待读的块号lba地址和待读入的扇区数写入lba寄存器 /* 3 执行的命令写入reg_cmd寄存器 */ cmd_out(hd->my_channel, CMD_WRITE_SECTOR); // 准备开始写数据 /* 4 检测硬盘状态是否可读 */ if (!busy_wait(hd)) {
// 若失败 char error[64]; sprintf(error, "%s write sector %d failed!!!!!!\n", hd->name, lba); PANIC(error); } /* 5 将数据写入硬盘 */ write2sector(hd, (void*)((uint32_t)buf + secs_done * 512), secs_op); /* 在硬盘响应期间阻塞自己 */ sema_down(&hd->my_channel->disk_done); secs_done += secs_op; } /* 醒来后开始释放锁*/ lock_release(&hd->my_channel->lock); } /* 将dst中len个相邻字节交换位置后存入buf */ static void swap_pairs_bytes(const char* dst, char* buf, uint32_t len) {
uint8_t idx; for (idx = 0; idx < len; idx += 2) {
/* buf中存储dst中两相邻元素交换位置后的字符串*/ buf[idx + 1] = *dst++; buf[idx] = *dst++; } buf[idx] = '\0'; } /* 获得硬盘参数信息 */ static void identify_disk(struct disk* hd) {
char id_info[512]; select_disk(hd); cmd_out(hd->my_channel, CMD_IDENTIFY); /* 向硬盘发送指令后便通过信号量阻塞自己, * 待硬盘处理完成后,通过中断处理程序将自己唤醒 */ sema_down(&hd->my_channel->disk_done); /* 醒来后开始执行下面代码*/ if (!busy_wait(hd)) {
// 若失败 char error[64]; sprintf(error, "%s identify failed!!!!!!\n", hd->name); PANIC(error); } read_from_sector(hd, id_info, 1); char buf[64]; uint8_t sn_start = 10 * 2, sn_len = 20, md_start = 27 * 2, md_len = 40; swap_pairs_bytes(&id_info[sn_start], buf, sn_len); printk(" disk %s info:\n SN: %s\n", hd->name, buf); memset(buf, 0, sizeof(buf)); swap_pairs_bytes(&id_info[md_start], buf, md_len); printk(" MODULE: %s\n", buf); uint32_t sectors = *(uint32_t*)&id_info[60 * 2]; printk(" SECTORS: %d\n", sectors); printk(" CAPACITY: %dMB\n", sectors * 512 / 1024 / 1024); } /* 扫描硬盘hd中地址为ext_lba的扇区中的所有分区 */ static void partition_scan(struct disk* hd, uint32_t ext_lba) {
struct boot_sector* bs = sys_malloc(sizeof(struct boot_sector)); ide_read(hd, ext_lba, bs, 1); uint8_t part_idx = 0; struct partition_table_entry* p = bs->partition_table; /* 遍历分区表4个分区表项 */ while (part_idx++ < 4) {
if (p->fs_type == 0x5) {
// 若为扩展分区 if (ext_lba_base != 0) {
/* 子扩展分区的start_lba是相对于主引导扇区中的总扩展分区地址 */ partition_scan(hd, p->start_lba + ext_lba_base); } else {
// ext_lba_base为0表示是第一次读取引导块,也就是主引导记录所在的扇区 /* 记录下扩展分区的起始lba地址,后面所有的扩展分区地址都相对于此 */ ext_lba_base = p->start_lba; partition_scan(hd, p->start_lba); } } else if (p->fs_type != 0) {
// 若是有效的分区类型 if (ext_lba == 0) {
// 此时全是主分区 hd->prim_parts[p_no].start_lba = ext_lba + p->start_lba; hd->prim_parts[p_no].sec_cnt = p->sec_cnt; hd->prim_parts[p_no].my_disk = hd; list_append(&partition_list, &hd->prim_parts[p_no].part_tag); sprintf(hd->prim_parts[p_no].name, "%s%d", hd->name, p_no + 1); p_no++; ASSERT(p_no < 4); // 0,1,2,3 } else {
hd->logic_parts[l_no].start_lba = ext_lba + p->start_lba; hd->logic_parts[l_no].sec_cnt = p->sec_cnt; hd->logic_parts[l_no].my_disk = hd; list_append(&partition_list, &hd->logic_parts[l_no].part_tag); sprintf(hd->logic_parts[l_no].name, "%s%d", hd->name, l_no + 5); // 逻辑分区数字是从5开始,主分区是1~4. l_no++; if (l_no >= 8) // 只支持8个逻辑分区,避免数组越界 return; } } p++; } sys_free(bs); } /* 打印分区信息 */ static bool partition_info(struct list_elem* pelem, int arg UNUSED) {
struct partition* part = elem2entry(struct partition, part_tag, pelem); printk(" %s start_lba:0x%x, sec_cnt:0x%x\n",part->name, part->start_lba, part->sec_cnt); /* 在此处return false与函数本身功能无关, * 只是为了让主调函数list_traversal继续向下遍历元素 */ return false; } /* 硬盘中断处理程序 */ void intr_hd_handler(uint8_t irq_no) {
ASSERT(irq_no == 0x2e || irq_no == 0x2f); uint8_t ch_no = irq_no - 0x2e; struct ide_channel* channel = &channels[ch_no]; ASSERT(channel->irq_no == irq_no); /* 不必担心此中断是否对应的是这一次的expecting_intr, * 每次读写硬盘时会申请锁,从而保证了同步一致性 */ if (channel->expecting_intr) {
channel->expecting_intr = false; sema_up(&channel->disk_done); /* 读取状态寄存器使硬盘控制器认为此次的中断已被处理, * 从而硬盘可以继续执行新的读写 */ inb(reg_status(channel)); } } /* 硬盘数据结构初始化 */ void ide_init() {
printk("ide_init start\n"); uint8_t hd_cnt = *((uint8_t*)(0x475)); // 获取硬盘的数量 ASSERT(hd_cnt > 0); list_init(&partition_list); channel_cnt = DIV_ROUND_UP(hd_cnt, 2); // 一个ide通道上有两个硬盘,根据硬盘数量反推有几个ide通道 struct ide_channel* channel; uint8_t channel_no = 0, dev_no = 0; /* 处理每个通道上的硬盘 */ while (channel_no < channel_cnt) {
channel = &channels[channel_no]; sprintf(channel->name, "ide%d", channel_no); /* 为每个ide通道初始化端口基址及中断向量 */ switch (channel_no) {
case 0: channel->port_base = 0x1f0; // ide0通道的起始端口号是0x1f0 channel->irq_no = 0x20 + 14; // 从片8259a上倒数第二的中断引脚,温盘,也就是ide0通道的的中断向量号 break; case 1: channel->port_base = 0x170; // ide1通道的起始端口号是0x170 channel->irq_no = 0x20 + 15; // 从8259A上的最后一个中断引脚,我们用来响应ide1通道上的硬盘中断 break; } channel->expecting_intr = false; // 未向硬盘写入指令时不期待硬盘的中断 lock_init(&channel->lock); /* 初始化为0,目的是向硬盘控制器请求数据后,硬盘驱动sema_down此信号量会阻塞线程, 直到硬盘完成后通过发中断,由中断处理程序将此信号量sema_up,唤醒线程. */ sema_init(&channel->disk_done, 0); register_handler(channel->irq_no, intr_hd_handler); /* 分别获取两个硬盘的参数及分区信息 */ while (dev_no < 2) {
struct disk* hd = &channel->devices[dev_no]; hd->my_channel = channel; hd->dev_no = dev_no; sprintf(hd->name, "sd%c", 'a' + channel_no * 2 + dev_no); identify_disk(hd); // 获取硬盘参数 if (dev_no != 0) {
// 内核本身的裸硬盘(hd60M.img)不处理 partition_scan(hd, 0); // 扫描该硬盘上的分区 } p_no = 0, l_no = 0; dev_no++; } dev_no = 0; // 将硬盘驱动器号置0,为下一个channel的两个硬盘初始化。 channel_no++; // 下一个channel } printk("\n all partition info\n"); /* 打印所有分区信息 */ list_traversal(&partition_list, partition_info, (int)NULL); printk("ide_init done\n"); }
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/12727.html