《操作系统真象还原》第十三章——编写硬盘驱动程序
文章目录
- 前言
- 硬盘及分区表
- 创建从盘及获取安装的磁盘数
- 创建磁盘分区表
- 硬盘分区表浅析
- 编写硬盘驱动程序
- 硬盘初始化
- 修改interrupt.c
- 编写ide.h
- 编写ide.c
- 实现thread_yield和idle线程
- 修改thread.c
- 实现简单的休眠函数
- 修改timer.c
- 完善硬盘驱动程序
- 继续编写ide.c
- 获取硬盘信息,扫描分区表
- ide.h
- 测试
- 编写makefile
- 结果截图
- 结语
前言
上章完成了系统调用,博客链接如下。
系统调用框架:《操作系统真象还原》第十二章(1)——进一步完善内核-CSDN博客
用户打印函数:《操作系统真象还原》第十二章(2)——进一步完善内核-CSDN博客
堆内存管理:《操作系统真象还原》第十二章(3)——进一步完善内核-CSDN博客
我们在加载loader.bin 的时候,已经接触了硬盘基本操作。但那时的操作方法还不成体系,咱们为了实现文件系统,必须建立好一套完整的方法,这套方法就是磁盘驱动程序,本章咱们要完成它。
硬盘及分区表
创建从盘及获取安装的磁盘数
原有硬盘c.img我们作为系统盘,现在我们创建一个新的文件盘,给它分配80mb空间。我打算顺带调整一下原有硬盘的名字和路径,这样更清晰一些。
先创建新硬盘吧。路径在/bin/下,用bximage命令,按照要求输入即可,硬盘名我设置为file_hd_80M。创建好的目录截图如下。
然后修改配置文件,代码如下
######################################
#### Configuration file for Bochs ###
######################################
megs: 32romimage: file=/home/hongbai/bochs/share/bochs/BIOS-bochs-latest
vgaromimage: file=/home/hongbai/bochs/share/bochs/VGABIOS-lgpl-latestboot: disklog: bochsout.txtmouse: enabled=0
keyboard: keymap=/home/hongbai/bochs/share/bochs/keymaps/x11-pc-us.mapata0:enabled=1,ioaddr1=0x1f0,ioaddr2=0x3f0,irq=14
ata0-master: type=disk, path="./bin/c.img",mode=flat,cylinders=121,heads=16,spt=63
ata0-slave: type=disk, path="./bin/file_hd_80M.img", mode=flat,cylinders=162,heads=16,spt=63
############### end ###############
用xp/b 0x475查看两块硬盘是否都已经载入,结果如图
目前我们这部分的任务已经完成了,但是我想顺带修改一下系统盘名称。我计划是重新再创建一个60mb的硬盘,修改好名字,然后把之前的文件(mbr、loader、kernel)重新写入即可。
正好补一张创建硬盘过程的截图,大部分直接回车即可。
然后修改我们写入代码时的路径,顺带贴出来我目前写入时的指令
以下是写入mbr和loader的,放在我创建的一个文本文档build.txt里
nasm -I include/ -o root/mbr.bin root/mbr.S
dd if=/home/hongbai/bochs/root/mbr.bin of=/home/hongbai/bochs/bin/os_hd_60M.img bs=512 count=1 conv=notruncnasm -I include/ -o root/loader.bin root/loader.S
dd if=/home/hongbai/bochs/root/loader.bin of=/home/hongbai/bochs/bin/os_hd_60M.img bs=512 count=4 seek=2 conv=notruncmake all
./bochs -f ./bin/bochsrc这是修改后的makefile写入路径,是makefile文件的一部分
hd:dd if=$(BUILD_DIR)/kernel.bin \of=/home/hongbai/bochs/bin/os_hd_60M.img \bs=512 count=200 seek=10 conv=notrunc
使用了新的系统盘后的运行截图
旧的系统盘c.img以后就不在使用了,我们目前的系统盘是os_hd_60M.img,文件盘是file_hd_80M.img。
创建磁盘分区表
文件系统是运行在操作系统中的软件模块,是操作系统提供的一套管理磁盘文件读写的方法和数据组织、存储形式,因此,文件系统=数据结构+算法,所以它是程序。它的管理对象是文件,管辖范围是分区,因此它建立在分区的基础上,每个分区都可以有不同的文件系统。但咱们刚创建了磁盘而已,磁盘还是裸盘,即raw disk,本节的任务是把刚创建的磁盘分区。
我们先快速回顾一下(机械)硬盘的基础知识:
扇区:硬盘读写的基本单位,通常是512字节大小。
磁道:扇区的载体,是一个个同心圆,一个磁道上有一圈扇区。
对于周长不同的磁道,扇区数是否一致?老硬盘是一样的, 新式硬盘中已经改进了,外圈磁道会容纳更多的扇区,在新硬盘中有个地址转换器来兼容老硬盘的扇区寻址, 因此依然可以认为硬盘中每个磁道上的扇区数一样多。
磁头:读取数据的装置。一个盘片有正反两个磁头,目前硬盘有多个盘片,就有2x盘片个磁头。磁头有编号,磁头号等于盘面号。
柱面:为了提高读写速度,把若干盘片摞起来形成一个圆柱,让若干个磁头并行工作。圆柱的工作面称为柱面,一个圆柱上下盘的磁道号是一致的,因而柱面号=磁道号。
分区:是由多个编号连续的柱面组成的,因此分区在物理上的表现是由某段范围内的所有柱面组成的 通心环,并不是像“饼图”那种逻辑表示,当然若整个硬盘只有1个分区,那这个分区就是个所有柱面组 成的圆柱体。分区不能跨柱面,也就是同一个柱面不能包含两个分区,一个柱面只属于一个分区,分区的 起始和终止都落在完整的柱面上,并不会出现多个分区共享同一柱面的情况,这就是所谓的“分区粒度”。 因此,分区大小等于“每柱面上的扇区数”乘以“柱面数”,这就是我们实际分区时,键入的大小往往与 实际大小不同的原因,分区大小总会是“每柱面上的扇区数”的整数倍,也就是会以柱面向上取整。假如 硬盘上有n个柱面,1~10柱面是a分区,11~23是b分区,这两个分区不共享11号柱面。
现在得出公式。
(1)硬盘容量=单片容量×磁头数。
(2)单片容量=每磁道扇区数×磁道数×512字节。
磁道数又等于柱面数,因此将公式2代入公式1后: 硬盘容量=每磁道扇区数×柱面数×512字节×磁头数
这些知识有助于我们更好的使用fdisk命令。
关于分区,总结一下知识点:
硬盘的mbr中有一个64字节的分区表结构,分区表的表项16字节,一个表项指示一个分区,因而原生只支持4个分区。后续为了实现更多的分区,专门增加一种id属性值(id为5),用来表示该分区可被再次划分出更多的子分区。反正分区本质上也只是一种逻辑过程,就无所谓了。
原生的四个分区,一个作为拓展分区,可以划分为若干个子分区,剩下三个就称为主分区。第一个逻辑子分区编号为5。
开始实践部分,这部分完整过程书里很详细,但是和我的fdisk软件版本不同,所以下面给出我的步骤,供大家参考
查看属性指令:
fdisk -l ./bin/file_hd_80M.img
开始分区
fdisk ./bin/file_hd_80M.img开始设置部分
输入m得到命令菜单
输入x得到专家命令菜单
输入c设置柱面 设置为163
输入h设置磁头 输入16
输入r返回主菜单开始硬盘创建
输入n,选择主分区,分区号1,起始扇区终止扇区见下图
输入n,选择扩展分区,分区号4
然后继续输入n,提示开始逻辑分区,创建5-9逻辑分区
然后输入t修改5-9的分区id,设置为0x66
最后输入w保存再次查看属性:
fdisk -l ./bin/file_hd_80M.img
5.6上午就这样,吃饭去了,下午还要复习信号与系统。
硬盘分区表浅析
这部分介绍硬盘分析表细节。其实没太看懂,可能因为我用的fdisk和郑钢老师的版本不同,分区后呈现出的信息也不同,做不到对应,理解起来就有点困难。把书上这部分文字梳理一遍吧:
我们之前已经引入了硬盘分区表DPT,64字节,包括4个项。每个分区都有一个分区表。
第一个分区表在file_hd_80m.img的mbr扇区中。关于这个我想说一下我的理解:对于系统盘,我们编写过mbr引导程序,让bios运行完之后运行。这个mbr程序所在的0.0.1扇区就是mbr扇区。对于系统盘,我们写了加载loader的程序,对于文件盘,上面什么都没有。
现在告诉我们mbr扇区可以分为三部分(1)主引导记录MBR。 (2)磁盘分区表DPT。 (3)结束魔数55AA,表示此扇区为主引导扇区,里面包含控制程序。对于系统盘,我们没有第二部分,系统盘也没分过区。文件盘肯定是三部分都要有。
下一部分,分区的起始地址必须是柱面的整数倍,因而mbr那个柱面我们就不能划入分区。
最早硬盘很小,一个硬盘1个分区表4个分区足够使用。随着硬盘容量变大,4个分区1个分区表不够用,我们引入了扩展分区概念,那么对应的,每个子扩展分区在逻辑上被视作一个硬盘,它也有自己的分区表,这个分区表同样是4个表项。我们用链式结构把所有的分区表串在一起。
对于每个子扩展分区(逻辑分区),它们有一个类似于mbr扇区的扇区,称为EBR引导扇区,结构和mbr扇区一模一样。只不过,mbr的分区表1个表项,16字节对应一个分区。到了ebr就比较奢侈,一个分区表只对应1个分区,因而EBR中分区表的第一分区 表项用来描述所包含的逻辑分区的元信息,第二分区表项用来描述下一个子扩展分区的地址,第三、四表 项未用到。
这是分区表表项的截图,来自真象还原。重点关注两个4字节数据,起始地址和扇区数。
后面比较简单了,先放一张截图。

这是郑钢老师的分区图,和我的有很大不同。
我查了一下。**现代硬盘为了追求读写效率,要求分区按4kb/1mb对齐分区,而不是传统的512b。1mb=2048*512b,这就是为什么我们每个分区的起始扇区号总是2048的整数倍。**一会编写硬盘驱动的时候,肯定按我们的分区进行组织。
编写硬盘驱动程序
整体流程我们在写完代码后补充。。。
硬盘初始化
简单来说就是把物理上的硬件抽象成逻辑上的数据结构,用代码代表硬件。
我们知道操作系统是由中断驱动的,想要让cpu和硬盘建立关联,一定是硬盘发出中断信号,cpu进行处理。具体而言,一块主板(书上错误的写成了硬盘)上有两个ATA通道,称为主通道和从通道,对于每个通道,又支持一主一从两个设备(2个硬盘)。第一个通道在中断处理芯片8259A从片的IRQ14引脚上,第二个通道在IRQ15引脚上。而8259A从片又级联在8259A主片的IRQ2接口。
修改interrupt.c
...
/* 初始化可编程中断控制器8259A */
static void pic_init(void)
{/* 初始化主片 */outb(PIC_M_CTRL, 0x11); // ICW1: 边沿触发,级联8259, 需要ICW4.outb(PIC_M_DATA, 0x20); // ICW2: 起始中断向量号为0x20,也就是IR[0-7] 为 0x20 ~ 0x27.outb(PIC_M_DATA, 0x04); // ICW3: IR2接从片.outb(PIC_M_DATA, 0x01); // ICW4: 8086模式, 正常EOI/* 初始化从片 */outb(PIC_S_CTRL, 0x11); // ICW1: 边沿触发,级联8259, 需要ICW4.outb(PIC_S_DATA, 0x28); // ICW2: 起始中断向量号为0x28,也就是IR[8-15] 为 0x28 ~ 0x2F.outb(PIC_S_DATA, 0x02); // ICW3: 设置从片连接到主片的IR2引脚outb(PIC_S_DATA, 0x01); // ICW4: 8086模式, 正常EOI//主片打开时钟中断IRQ0、键盘中断IRQ1、级联从片的IRQ3outb(PIC_M_DATA, 0xf8);//从盘打开IRQ14,接受硬盘中断outb(PIC_S_DATA, 0xbf);put_str(" pic_init done\n");
}
...
编写ide.h
路径在device/ide.h
#include "../lib/kernel/stdint.h"
#include "../lib/kernel/list.h"
#include "../thread/sync.h"
/*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]; // 一个通道上可以连接两个硬盘
};
/*硬盘结构*/
struct disk
{char name[8]; // 硬盘名称struct ide_channel *my_channel; // 硬盘使用的ide通道uint8_t dev_no; // 本硬盘是主盘还是从盘,主0从1struct partition prim_parts[4]; // 主分区,上限为4struct partition logic_parts[8]; // 逻辑分区,理论上无上限,我设置为只支持8个
};
/*分区结构*/
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节点队列
};
void ide_init(void);
编写ide.c
#include "ide.h"
#include "../lib/stdio.h"
#include "../kernel/debug.h"
#include "../kernel/global.h"
#include "../thread/sync.h"
// 主IDE通道寄存器端口的基址是0x1F0,从通道基址是0x170
#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) // 起始扇区LBA低八位
#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)) // 命令寄存器,和状态寄存器共用0x1F7端口
#define reg_alt_status(channel->port_base + 0x206) // 备用状态寄存器
#define reg_ctl(channel) (reg_alt_status(channel)) // 设备控制寄存器,和前者共用0x3F6端口
// 记录reg_alt_status备用状态寄存器的一些关键位
#define BIT_ALT_STAT_BSY 0x80 // 硬盘忙
#define BIT_ALT_STAT_DRDY 0x40 // 驱动器准备好了
#define BIT_ALT_STAT_DRQ 0x8 // 数据传输准备好了
// 记录device驱动器/磁头寄存器的一些关键位
#define BIT_DEV_MBS 0xa0
#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]; // 一个主板最多有两个通道/*硬盘数据结构初始化*/
void ide_init()
{printk("ide_init start\n");// BIOS扫描获取的硬盘数保存在物理地址0x475上,低1MB虚拟地址等于物理地址uint8_t hd_cnt = *((uint8_t)(0x475));ASSERT(hd_cnt > 0);channel_cnt = DIV_ROUND_UP(hd_cnt, 2); // 计算通道数,一个通道对应两个硬盘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); // 设置通道名if (channel_no == 0){ // 主通道channel->port_base = 0x1f0;channel->irq_no = 0x20 + 14; // 对应从片IRQ14引脚}else if (channel_no == 1){ // 从通道channel->port_base = 0x170;channel->irq_no = 0x20 + 15;}// 默认不需要等待硬盘中断channel->expecting_intr = false;lock_init(&channel->lock);sema_init(&channel->disk_done, 0);channel_no++;}printk("ide_init done\n");
}
这部分就算完成了。
实现thread_yield和idle线程
先说明一下接下来两部分在干什么。
硬盘和CPU 是相互独立的个体,它们各自并行执行,但由于硬盘是低速设备,其在处理请求时往往 消耗很长的时间,为避免浪费CPU资源,在等待硬盘操 作的过程中最好把CPU主动让出来,让CPU去执行其他任务。
thread_yield函数由当前线程执行,让当前线程主动把cpu使用权交出来。和block不同,交出cpu后,当前线程依旧是ready状态,会加入就绪队列。
idle线程是一个特殊的线程。初始状态为阻塞,当就绪队列为空时,它被唤醒,执行它内部的hlt指令,让cpu停止执行指令,休息。
执行hlt指令后,只有外部中断能再次唤醒cpu,所以在执行hlt之前一定要先sti开中断,保证cpu还能被唤醒。
修改thread.c
...
struct task_struct *idle_thread; // idle线程/*设置系统空闲时运行的线程*/
static void idle(void)
{while (1){thread_block(TASK_BLOCKED); // 初始为阻塞状态// 被唤醒后执行下面的代码asm volatile("sti; hlt" : : : "memory");}
}
...
/*主动让出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));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); // 初始化pid锁make_main_thread(); // 创建主线程//创建idle线程idle_thread = thread_start("idle", 10, idle, NULL);put_str("thread_init done\n");
}
...
记得把void thread_yield(void)声明写进头文件里。
实现简单的休眠函数
修改timer.c
这个文件不长,我就贴出全文吧。
// 时钟中断相关
#include "./timer.h"
#include "../kernel/io.h"
#include "../lib/kernel/print.h"
#include "../kernel/interrupt.h"
#include "../thread/thread.h"
#include "../kernel/debug.h"#define IRQ0_FREQUENCY 100 // 时钟中断频率
#define INPUT_FREQUENCY 1193180 // 8254PIT的输入时钟频率
// 计数器初始值
#define COUNTER0_VALUE INPUT_FREQUENCY / IRQ0_FREQUENCY
#define COUNTER0_PORT 0X40 // Counter0的数据端口,通过该端口写入计数器初始值
#define COUNTER0_NO 0 // 选择Counter0
#define COUNTER_MODE 2 // 选择模式2,进行周期中断
#define READ_WRITE_LATCH 3 // 表示先写低字节,再写高字节
#define PIT_COUNTROL_PORT 0x43 // 控制字寄存器端口
// 计算每多少毫秒发生1次中断
#define mil_second_per_intr (1000 / IRQ0_FREQUENCY)uint32_t ticks; // ticks是内核自中断开启以来的滴答数,一个tick就是一次时钟中断/* 配置8524PIT */
void frequency_set(uint8_t counter_port, uint8_t counter_no, uint8_t rwl, uint8_t counter_mode, uint16_t counter_value)
{outb(PIT_COUNTROL_PORT, (uint8_t)(counter_no << 6 | rwl << 4 | counter_mode << 1));outb(counter_port, (uint8_t)counter_value);outb(counter_port, (uint8_t)counter_value >> 8);return;
}/* 时钟的中断处理函数 */
void intr_timer_handler(void)
{struct task_struct *cur_thread = running_thread();ASSERT(cur_thread->stack_magic == 0x19870916); // 检测栈溢出cur_thread->elapsed_ticks++; // 线程运行的时间加1// 每次时钟中断,ticks加1ticks++;if (cur_thread->ticks == 0){ // 如果当前线程的时间片用完schedule(); // 调度其他线程}else{cur_thread->ticks--; // 否则,当前线程的时间片减1}return;
}/* 初始化PIT8253 */
void timer_init(void)
{put_str("timer_init start\n");frequency_set(COUNTER0_PORT, COUNTER0_NO, READ_WRITE_LATCH, COUNTER_MODE, COUNTER0_VALUE);register_handler(0x20, intr_timer_handler); // 注册时钟中断处理函数put_str("timer_init done\n");return;
}/*以tick为单位的sleep,让当前线程休眠sleep_ticks次中断*每次中断全局变量ticks++,计算某次中断后ticks和开始休眠时的start_tick的差值,*决定是继续休眠还是结束休眠*/
static void ticks_to_sleep(uint32_t sleep_ticks)
{uint32_t start_tick = ticks;while (ticks - start_tick < sleep_ticks) // 如果差值没达到目标,就一直休眠{thread_yield();}
}/*以毫秒ms为单位的休眠*/
void mtime_sleep(uint32_t m_second)
{uint32_t sleep_ticks = DIV_ROUND_UP(m_second, mil_second_per_intr);ASSERT(sleep_ticks > 0);ticks_to_sleep(sleep_ticks);
}
记得把新写的函数写到头文件里。
完善硬盘驱动程序
继续编写ide.c
这部分写完是200多行代码。
#include "ide.h"
#include "../lib/stdio.h"
#include "../kernel/debug.h"
#include "../kernel/global.h"
#include "../thread/sync.h"
#include "../kernel/io.h"
#include "timer.h"
#include "../kernel/interrupt.h"/*主IDE通道寄存器端口的基址是0x1F0,从通道基址是0x170*/
#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) // 起始扇区LBA低八位
#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)) // 命令寄存器,和状态寄存器共用0x1F7端口
#define reg_alt_status(channel->port_base + 0x206) // 备用状态寄存器
#define reg_ctl(channel) (reg_alt_status(channel)) // 设备控制寄存器,和前者共用0x3F6端口/*记录reg_alt_status备用状态寄存器的一些关键位*/
#define BIT_ALT_STAT_BSY 0x80 // 1000 0000,表示硬盘忙
#define BIT_ALT_STAT_DRDY 0x40 // 0100 0000,表示驱动器准备好了
#define BIT_ALT_STAT_DRQ 0x8 // 0000 1000,表示数据传输准备好了/*记录device驱动器/磁头寄存器的一些关键位*/
#define BIT_DEV_MBS 0xa0 // 1010 0000,设置必须的保留位
#define BIT_DEV_LBA 0x40 // 0100 0000,启用LBA模式
#define BIT_DEV_DEV 0x10 // 0001 0000,如果使用此位,则选择了从盘/*一些硬盘操作指令的指令码*/
#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]; // 一个主板最多有两个通道/*选择要读写的硬盘*/
static void select_disk(struct disk *hd)
{uint8_t reg_device = BIT_DEV_MBS | BIT_DEV_LBA;if (hd->dev_no == 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);// 写入0-23位LBAoutb(reg_lba_l(channel), lba);outb(reg_lba_m(channel), lba >> 8);outb(reg_lba_h(channel), lba >> 16);// 还有24-27四位LBA要写在dev寄存器里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 byte = 0;if (sec_cnt == 0) // sec_cnt范围是0-255,如果传入256会变为0{byte = 256 * 512;}else{byte = sec_cnt * 512;}// 按字读取,1字=2字节insw(reg_data(hd->my_channel), buf, byte / 2);
}/*从内存buf向硬盘写入sec_cnt个扇区的数据*/
static void write_to_sector(struct disk *hd, void *buf, uint8_t sec_cnt)
{uint32_t byte = 0;if (sec_cnt == 0) // sec_cnt范围是0-255,如果传入256会变为0{byte = 256 * 512;}else{byte = sec_cnt * 512;}outsw(reg_data(hd->my_channel), buf, byte / 2);
}/*等待30秒,本质是优化了的自选锁*/
static bool busy_wait(struct disk *hd)
{struct ide_channel *channel = hd->my_channel;uint16_t time_limit = 30 * 1000;while (time_limit -= 10 >= 0){// 如果硬盘不忙if (!(inb(reg_status(channel)) & BIT_ALT_STAT_BSY)){// 已经准备好数据传输return (inb(reg_status(channel)) & BIT_ALT_STAT_DRQ);}else{mtime_sleep(10); // 睡眠10ms,10ms后再次判断}}return false;
}/*从硬盘sec_cnt个扇区读取数据到内存buf的全过程*/
void ide_read(struct disk *hd, uint32_t lba, void *buf, uint8_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 = 0; // 本次要处理的扇区数,最大为256uint32_t secs_done = 0; // 已经处理的扇区数while (secs_done < sec_cnt){if (sec_cnt - secs_done >= 256){secs_op = 256;}else{secs_op = sec_cnt - secs_done;}// 2.写入待读取的扇区数和起始扇区号select_sector(hd, lba + secs_done, secs_op);// 3.写入读取命令到cmd寄存器cmd_out(hd->my_channel, CMD_READ_SECTOR);/*硬盘IO最慢的环节是硬盘内部处理环节,机械硬盘涉及到磁头移动等物理过程*此时,硬盘收到信号,开始在内部进行数据处理,我们可以让读取线程先阻塞自己*在读取完成后,通过中断处理程序唤醒线程,实现cpu的高效利用*/sema_down(&hd->my_channel->disk_done);/*此时,硬盘内部处理完成,程序被唤醒,继续接下来的环节*/// 4.检查硬盘状态是否可读if (!busy_wait(hd)) // 30秒内硬盘一直处于忙状态{char error[64];sprintf(error, "%s read sector %d failed!!!!!!\n", hd->name, lba);PANIC(error);}// 5.把读取出数据放到内存buf中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, uint8_t sec_cnt)
{// 写入和读取过程基本一致ASSERT(lba <= max_lba);ASSERT(sec_cnt > 0);lock_acquire(hd->my_channel->lock);select_disk(hd); // 选取硬盘uint32_t secs_op = 0;uint32_t secs_done = 0;while (secs_done < sec_cnt){if (sec_cnt - secs_done >= 256){secs_op = 256;}else{secs_op = sec_cnt - secs_done;}select_sector(hd, lba + secs_done, secs_op); // 写入起始扇区号和扇区数cmd_out(hd->my_channel, CMD_WRITE_SECTOR); // 写入写命令if (!busy_wait(hd)) // 检验状态{char error[64];sprintf(error, "%s write sector %d failed!!!!!!\n", hd->name, lba);PANIC(error);}write_to_sector(hd, (void *)((uint32_t)buf + secs_done * 256), 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);// 0x2e对应14引脚,是主通道,0x2f对应从通道uint32_t no = irq_no - 0x2e;struct ide_channel *channel = channels[no];ASSERT(channel->irq_no == irq_no);if (channel->expecting_intr == true){channel->expecting_intr = false;sema_up(&channel->disk_done);// 修改状态,让硬盘可以执行新的读写inb(reg_status(channel));}
}/*硬盘数据结构初始化*/
void ide_init()
{printk("ide_init start\n");// BIOS扫描获取的硬盘数保存在物理地址0x475上,低1MB虚拟地址等于物理地址uint8_t hd_cnt = *((uint8_t)(0x475));ASSERT(hd_cnt > 0);channel_cnt = DIV_ROUND_UP(hd_cnt, 2); // 计算通道数,一个通道对应两个硬盘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); // 设置通道名if (channel_no == 0){ // 主通道channel->port_base = 0x1f0;channel->irq_no = 0x20 + 14; // 对应从片IRQ14引脚}else if (channel_no == 1){ // 从通道channel->port_base = 0x170;channel->irq_no = 0x20 + 15;}// 默认不需要等待硬盘中断channel->expecting_intr = false;lock_init(&channel->lock);sema_init(&channel->disk_done, 0);// 注册中断处理程序register_handler(channel->irq_no, intr_hd_handler);channel_no++;}printk("ide_init done\n");
}
获取硬盘信息,扫描分区表
我们已经完成了读写命令,下一步完成identify命令和扫描分区表
先介绍identify,用于获取硬盘参数,返回值以字为单位。下图列出了我们需要的三个参数。
再介绍一下扫描分区表,我们需要给每个分区起一个名字,参考linux命名规则,我们的文件硬盘file_hd_80M上主分区名为sdb[1-4],逻辑分区名为sdb[5-n]。
这部分代码依旧写在ide.c文件,我们给出完整的ide.c吧
#include "ide.h"
#include "../lib/stdio.h"
#include "../kernel/debug.h"
#include "../kernel/global.h"
#include "../thread/sync.h"
#include "../kernel/io.h"
#include "timer.h"
#include "../kernel/interrupt.h"
#include "../lib/string.h"/*主IDE通道寄存器端口的基址是0x1F0,从通道基址是0x170*/
#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) // 起始扇区LBA低八位
#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)) // 命令寄存器,和状态寄存器共用0x1F7端口
#define reg_alt_status(channel) (channel->port_base + 0x206) // 备用状态寄存器
#define reg_ctl(channel) (reg_alt_status(channel)) // 设备控制寄存器,和前者共用0x3F6端口/*记录reg_alt_status备用状态寄存器的一些关键位*/
#define BIT_ALT_STAT_BSY 0x80 // 1000 0000,表示硬盘忙
#define BIT_ALT_STAT_DRDY 0x40 // 0100 0000,表示驱动器准备好了
#define BIT_ALT_STAT_DRQ 0x8 // 0000 1000,表示数据传输准备好了/*记录device驱动器/磁头寄存器的一些关键位*/
#define BIT_DEV_MBS 0xa0 // 1010 0000,设置必须的保留位
#define BIT_DEV_LBA 0x40 // 0100 0000,启用LBA模式
#define BIT_DEV_DEV 0x10 // 0001 0000,如果使用此位,则选择了从盘/*一些硬盘操作指令的指令码*/
#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]; // 一个主板最多有两个通道int32_t ext_lba_base = 0; // 总扩展分区LBA基址
uint8_t p_no = 0, l_no = 0; // 主分区和逻辑分区的下标
struct list partition_list; // 分区队列/*分区表项结构体*/
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]; // 446字节的引导代码struct partition_table_entry partition_table[4];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) // 如果是从盘{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);// 写入0-23位LBAoutb(reg_lba_l(channel), lba);outb(reg_lba_m(channel), lba >> 8);outb(reg_lba_h(channel), lba >> 16);// 还有24-27四位LBA要写在dev寄存器里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 byte = 0;if (sec_cnt == 0) // sec_cnt范围是0-255,如果传入256会变为0{byte = 256 * 512;}else{byte = sec_cnt * 512;}// 按字读取,1字=2字节insw(reg_data(hd->my_channel), buf, byte / 2);
}/*从内存buf向硬盘写入sec_cnt个扇区的数据*/
static void write_to_sector(struct disk *hd, void *buf, uint8_t sec_cnt)
{uint32_t byte = 0;if (sec_cnt == 0) // sec_cnt范围是0-255,如果传入256会变为0{byte = 256 * 512;}else{byte = sec_cnt * 512;}outsw(reg_data(hd->my_channel), buf, byte / 2);
}/*等待30秒,本质是优化了的自选锁*/
static bool busy_wait(struct disk *hd)
{struct ide_channel *channel = hd->my_channel;uint16_t time_limit = 30 * 1000;while (time_limit -= 10 >= 0){// 如果硬盘不忙if (!(inb(reg_status(channel)) & BIT_ALT_STAT_BSY)){// 已经准备好数据传输return (inb(reg_status(channel)) & BIT_ALT_STAT_DRQ);}else{mtime_sleep(10); // 睡眠10ms,10ms后再次判断}}return false;
}/*从硬盘sec_cnt个扇区读取数据到内存buf的全过程*/
void ide_read(struct disk *hd, uint32_t lba, void *buf, uint8_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 = 0; // 本次要处理的扇区数,最大为256uint32_t secs_done = 0; // 已经处理的扇区数while (secs_done < sec_cnt){if (sec_cnt - secs_done >= 256){secs_op = 256;}else{secs_op = sec_cnt - secs_done;}// 2.写入待读取的扇区数和起始扇区号select_sector(hd, lba + secs_done, secs_op);// 3.写入读取命令到cmd寄存器cmd_out(hd->my_channel, CMD_READ_SECTOR);/*硬盘IO最慢的环节是硬盘内部处理环节,机械硬盘涉及到磁头移动等物理过程*此时,硬盘收到信号,开始在内部进行数据处理,我们可以让读取线程先阻塞自己*在读取完成后,通过中断处理程序唤醒线程,实现cpu的高效利用*/sema_down(&hd->my_channel->disk_done);/*此时,硬盘内部处理完成,程序被唤醒,继续接下来的环节*/// 4.检查硬盘状态是否可读if (!busy_wait(hd)) // 30秒内硬盘一直处于忙状态{char error[64];sprintf(error, "%s read sector %d failed!!!!!!\n", hd->name, lba);PANIC(error);}// 5.把读取出数据放到内存buf中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, uint8_t sec_cnt)
{// 写入和读取过程基本一致ASSERT(lba <= max_lba);ASSERT(sec_cnt > 0);lock_acquire(&hd->my_channel->lock);select_disk(hd); // 选取硬盘uint32_t secs_op = 0;uint32_t secs_done = 0;while (secs_done < sec_cnt){if (sec_cnt - secs_done >= 256){secs_op = 256;}else{secs_op = sec_cnt - secs_done;}select_sector(hd, lba + secs_done, secs_op); // 写入起始扇区号和扇区数cmd_out(hd->my_channel, CMD_WRITE_SECTOR); // 写入写命令if (!busy_wait(hd)) // 检验状态{char error[64];sprintf(error, "%s write sector %d failed!!!!!!\n", hd->name, lba);PANIC(error);}write_to_sector(hd, (void *)((uint32_t)buf + secs_done * 256), 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);// 0x2e对应14引脚,是主通道,0x2f对应从通道uint32_t no = irq_no - 0x2e;struct ide_channel *channel = &channels[no];ASSERT(channel->irq_no == irq_no);if (channel->expecting_intr == true){channel->expecting_intr = false;sema_up(&channel->disk_done);// 修改状态,让硬盘可以执行新的读写inb(reg_status(channel));}
}/*将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[idx + 1] = *dst;dst++;buf[idx] = *dst;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];// 硬盘序列号起始偏移量10个字,是长度20字节的的字符串uint8_t sn_start = 10 * 2, sn_len = 20;// 硬盘型号起始偏移量27个字,长度40字节uint8_t md_start = 27 * 2, md_len = 40;swap_pairs_bytes(&id_info[sn_start], buf, sn_len);printk(" disk %s info:\n", hd->name);printk(" SN: %s\n", buf); // 打印序列号memset(buf, 0, sizeof(buf));swap_pairs_bytes(&id_info[md_start], buf, md_len);printk(" MODULE: %s\n", buf); // 打印硬盘型号// 找到可供用户使用的扇区数这个参数uint32_t sector = *(uint32_t *)&id_info[60 * 2];// 打印可以使用的扇区数printk(" SECTORS: %d\n", sector);// 打印可以使用的内存容量printk(" CAPACITY: %dMB\n", sector * 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;while (part_idx++ < 4){if (p->fs_type == 0x5) // 若为拓展分区{if (ext_lba == 0) // 第一次读取引导块{ext_lba_base = p->start_lba;partition_scan(hd, p->start_lba); // 递归调用}else{partition_scan(hd, p->start_lba + ext_lba_base);}}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);}else{hd->prim_parts[l_no].start_lba = ext_lba + p->start_lba;hd->prim_parts[l_no].sec_cnt = p->sec_cnt;hd->prim_parts[l_no].my_disk = hd;list_append(&partition_list, &hd->prim_parts[l_no].part_tag);sprintf(hd->prim_parts[l_no].name, "%s%d", hd->name, l_no + 5);l_no++;if (l_no >= 8){return;}}}p++;}sys_free(bs);
}/*打印分区信息*/
static bool partition_info(struct list_elem *pelem, int arg)
{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;
}/*硬盘数据结构初始化*/
void ide_init()
{printk("ide_init start\n");// BIOS扫描获取的硬盘数保存在物理地址0x475上,低1MB虚拟地址等于物理地址uint8_t hd_cnt = *((uint8_t *)(0x475));ASSERT(hd_cnt > 0);channel_cnt = DIV_ROUND_UP(hd_cnt, 2); // 计算通道数,一个通道对应两个硬盘struct ide_channel *channel;uint8_t channel_no = 0, dev_no = 0;list_init(&partition_list);// 处理每个通道上的硬盘while (channel_no < channel_cnt){channel = &channels[channel_no];sprintf(channel->name, "ide%d", channel_no); // 设置通道名if (channel_no == 0) // 主通道{channel->port_base = 0x1f0;channel->irq_no = 0x20 + 14; // 对应从片IRQ14引脚}else if (channel_no == 1) // 从通道{channel->port_base = 0x170;channel->irq_no = 0x20 + 15;}// 默认不需要等待硬盘中断channel->expecting_intr = false;lock_init(&channel->lock);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) // 只处理文件盘{partition_scan(hd, 0); // 扫描文件盘分区}p_no = 0, l_no = 0;dev_no++;}dev_no = 0;channel_no++;}printk("\n all partition info\n");list_traversal(&partition_list, partition_info, (int)NULL);printk("ide_init done\n");
}
ide.h
#ifndef __DEVICE_IDE_H
#define __DEVICE_IDE_H#include "../lib/kernel/stdint.h"
#include "../lib/kernel/list.h"
#include "../thread/sync.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]; // 硬盘名称struct ide_channel *my_channel; // 硬盘使用的ide通道uint8_t dev_no; // 本硬盘是主盘还是从盘,主0从1struct partition prim_parts[4]; // 主分区,上限为4struct 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_read(struct disk *hd, uint32_t lba, void *buf, uint8_t sec_cnt);/*从硬盘sec_cnt个扇区读取数据到内存buf的全过程*/
void ide_write(struct disk *hd, uint32_t lba, void *buf, uint8_t sec_cnt);/*从内存buf写入sec_cnt个扇区数据到硬盘的全过程*/
void intr_hd_handler(uint8_t irq_no);/*硬盘中断处理程序*/
void ide_init(void);/*硬盘驱动程序初始化*/#endif
测试
编写makefile
BUILD_DIR = ./build
ENTRY_POINT = 0xc0001500
AS = nasm
CC = gcc
LD = ld
LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/
ASFLAGS = -f elf
CFLAGS = -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
LDFLAGS = -m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
OBJS = $(BUILD_DIR)/main.o $(BUILD_DIR)/init.o $(BUILD_DIR)/interrupt.o \$(BUILD_DIR)/timer.o $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o \$(BUILD_DIR)/debug.o $(BUILD_DIR)/string.o $(BUILD_DIR)/memory.o \$(BUILD_DIR)/bitmap.o $(BUILD_DIR)/thread.o $(BUILD_DIR)/list.o \$(BUILD_DIR)/switch.o $(BUILD_DIR)/sync.o $(BUILD_DIR)/console.o \$(BUILD_DIR)/keyboard.o $(BUILD_DIR)/ioqueue.o $(BUILD_DIR)/tss.o \$(BUILD_DIR)/process.o $(BUILD_DIR)/syscall-init.o $(BUILD_DIR)/syscall.o \$(BUILD_DIR)/stdio.o $(BUILD_DIR)/ide.o################ c代码编译 ##################
$(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \lib/kernel/stdint.h kernel/init.h kernel/debug.h \kernel/memory.h thread/thread.h kernel/interrupt.h \device/console.h userprog/process.h lib/user/syscall.h \userprog/syscall-init.h lib/stdio.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h \lib/kernel/stdint.h kernel/interrupt.h device/timer.h \kernel/memory.h thread/thread.h device/console.h \device/keyboard.h userprog/tss.h userprog/syscall-init.h \device/ide.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/interrupt.o: kernel/interrupt.c kernel/interrupt.h \lib/kernel/stdint.h kernel/global.h kernel/io.h \lib/kernel/print.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/timer.o: device/timer.c device/timer.h lib/kernel/stdint.h \kernel/io.h lib/kernel/print.h kernel/interrupt.h \thread/thread.h kernel/debug.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/debug.o: kernel/debug.c kernel/debug.h \lib/kernel/print.h kernel/interrupt.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/string.o: lib/string.c lib/string.h \kernel/debug.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/memory.o: kernel/memory.c kernel/memory.h \lib/kernel/stdint.h lib/kernel/bitmap.h kernel/debug.h \lib/string.h thread/sync.h thread/thread.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/bitmap.o: lib/kernel/bitmap.c lib/kernel/bitmap.h \lib/string.h kernel/interrupt.h lib/kernel/print.h \kernel/debug.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/thread.o: thread/thread.c thread/thread.h \lib/kernel/stdint.h lib/kernel/list.h lib/string.h \kernel/memory.h kernel/interrupt.h kernel/debug.h \lib/kernel/print.h userprog/process.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/list.o: lib/kernel/list.c lib/kernel/list.h \lib/kernel/stdint.h kernel/interrupt.h kernel/debug.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/sync.o: thread/sync.c thread/sync.h \lib/kernel/stdint.h thread/thread.h kernel/debug.h \kernel/interrupt.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/console.o: device/console.c device/console.h \lib/kernel/print.h thread/sync.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/keyboard.o: device/keyboard.c device/keyboard.h \lib/kernel/print.h kernel/interrupt.h kernel/io.h \lib/kernel/stdint.h device/ioqueue.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/ioqueue.o: device/ioqueue.c device/ioqueue.h \lib/kernel/stdint.h thread/thread.h thread/sync.h \kernel/interrupt.h kernel/debug.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/tss.o: userprog/tss.c userprog/tss.h \lib/kernel/stdint.h thread/thread.h kernel/global.h \lib/kernel/print.h lib/string.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/process.o: userprog/process.c userprog/process.h \kernel/global.h lib/kernel/stdint.h thread/thread.h \kernel/debug.h userprog/tss.h device/console.h \lib/string.h kernel/interrupt.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/syscall.o: lib/user/syscall.c lib/user/syscall.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/syscall-init.o: userprog/syscall-init.c userprog/syscall-init.h \lib/kernel/stdint.h lib/user/syscall.h thread/thread.h \lib/kernel/print.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/stdio.o: lib/stdio.c lib/stdio.h \lib/kernel/stdint.h lib/string.h kernel/debug.h \lib/user/syscall.h$(CC) $(CFLAGS) $< -o $@$(BUILD_DIR)/ide.o: device/ide.c device/ide.o \lib/stdio.h kernel/debug.h kernel/global.h \thread/sync.h kernel/io.h device/timer.h \kernel/interrupt.h lib/string.h$(CC) $(CFLAGS) $< -o $@############## 汇编代码编译 ###############
$(BUILD_DIR)/kernel.o: kernel/kernel.S$(AS) $(ASFLAGS) $< -o $@$(BUILD_DIR)/print.o: lib/kernel/print.S$(AS) $(ASFLAGS) $< -o $@$(BUILD_DIR)/switch.o: thread/switch.S$(AS) $(ASFLAGS) $< -o $@############## 连接所有目标文件 #############
$(BUILD_DIR)/kernel.bin: $(OBJS)$(LD) $(LDFLAGS) $^ -o $@.PHONY : mk_dir hd clean allmk_dir:if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fihd:dd if=$(BUILD_DIR)/kernel.bin \of=/home/hongbai/bochs/bin/os_hd_60M.img \bs=512 count=200 seek=10 conv=notruncclean:cd $(BUILD_DIR) && rm -f ./*build: $(BUILD_DIR)/kernel.binall: mk_dir build hd
结果截图
结语
现在是2025年5月10日17点12分,我上一篇博客于 2025-05-06 09:00:34 发布,这段时间干了什么?
5月7号我有一门结课考试,从5月4号到5月7号主要在复习课程,中间断断续续写了一点硬盘驱动。5月8号下午有一个大实验,加上我们学校最近在教学评估,就没有推进进度。昨天完成了数据库实验报告,写了3道算法题,也没时间继续写操作系统了。
下周是我们学校教学评估关键周,不允许旷课,上课也不能继续做操作系统了。毕竟我也不想被抓个典型被记过。显然我们的操作系统开发会受到影响,提前说明一下吧。
今天完成了第13章的开发,晚上准备进入第14章。明天一整天写一整天14章,尽量赶赶进度吧。