简单的招聘网站怎么做,杭州网站建站,苏州网站建设基础型,佛山快速排名优化第13章-编写硬盘驱动程序 
这是一个网站有所有小节的代码实现#xff0c;同时也包含了Bochs等文件 
13.1 硬盘及分区表 
13.1.1 创建从盘及获取安装的磁盘数 
要实现文件系统#xff0c;必须先有个磁盘介质#xff0c;虽然咱们己经有个虚拟磁盘 hd60M.img#xff0c;但它只…第13章-编写硬盘驱动程序 
这是一个网站有所有小节的代码实现同时也包含了Bochs等文件 
13.1 硬盘及分区表 
13.1.1 创建从盘及获取安装的磁盘数 
要实现文件系统必须先有个磁盘介质虽然咱们己经有个虚拟磁盘 hd60M.img但它只充当了启动盘的作用仅用来存储内核是个没有文件系统的裸盘 raw disk 
如同我们之前第一章创建主盘时一样在bochs目录下 
创建磁盘 
bin/bximage 
然后在输入框依次输入以下输入一个按一次回车 
1 
hd 
flat 
80 
hd80M.img 
接下来我们在bochsrc.disk文件中写入 
ata0-slave: typedisk, pathhd80M.img, modeflat,cylinders162,heads16,spt63 这样bochs虚拟机启动时就会识别这个磁盘并且自动挂载。 
xp:用来查看物理地址处的值egxp/b 0x475查看0x475处一个字节的值。这个0x475处存储的是主机上安装的硬盘数量。下面是成功安装了两个硬盘  
13.1.2 创建磁盘分区表 
**文件系统是运行在操作系统中的软件模块是操作系统提供的一套管理磁盘文件读写的方法和数据组织、存储形式**因此文件系统数据结构算法哈哈所以它是程序。它的管理对象是文件管辖范围是分区因此它建立在分区的基础上每个分区都可以有不同的文件系统。咱们刚创建了磁盘而己磁盘还是裸盘即传说中的 raw disk本节的任务是把刚创建的磁盘 hd80M.img 分区。 
磁盘的物理结构 
盘片类似光盘中的一个圆盘上面布满了磁性介质。扇区扇区是硬盘读写的基本单位它在磁道上均匀分布与磁头和磁道不同扇区从 1 开始编号扇区的大小字节数256 × N. N为自然数通常取2因此扇区的大小为512字节磁道盘片上的一个个同心圈就是磁道它是扇区的载体每一个磁道由外向里从 0 开始编号磁头就是磁头哈哈可以粗略理解为磁带中的磁头一个盘片分为上下两个面各面都有一个磁头因此一个盘片包括两个磁头磁头号就表示盘面平时所说的盘面号就是磁头号柱面这些由不同盘面上的编号相同的磁道这些编号相同的同心圆大小一致从上到下所组成的圆柱体的回转面就称为柱面因此柱面的大小等于盘面数磁头数〉乘以每磁道扇区数。分区是由多个编号连续的柱面组成的因此分区在物理上的表现是由某段范围内的所有柱面组成的通心环并不是像“饼图”那种逻辑表示当然若整个硬盘只有 1 个分区那这个分区就是个所有柱面组成的圆柱体 。 分区不能跨柱面也就是同一个柱面不能包含两个分区一个柱面只属于一个分区分区的起始和终止都落在完整的柱面上并不会出现多个分区共享同一柱面的情况这就是所谓的“分区粒度”。 硬盘容量单片容量 x 磁头数单片容量每磁道扇区数 x 磁道数 x 512 
磁道数又等于柱面数硬盘容量每磁道扇区数 x 柱面数 x 512 x 磁头数 
下面是hd80M.img的分区布局图 13.2 编写硬盘驱动程序 
13.2.1 硬盘初始化 
硬件是实实在在的东西要想在软件中管理它们只能从逻辑上抓住这些硬件的特性将它们抽象成一些数据结构然后这些数据结构便代表了硬件用这些数据结构来组织硬件的信息及状态在逻辑上硬件就是这数据结构。硬盘也是“实在”的东东为了管理它们还是得将它们抽象成某些数据结构这就是本节的任务之一。 
硬盘上有两个 ata 通道也称为 IDE 通道。第 1 个 ata 通道上的两个硬盘主和从的中断信号挂在 8259A 从片的 IRQ14 上第 2 个 ata 通道接在 8259A 从片的 IRQ15 上该 ata 通道上可支持两个硬盘 。 来自 8259A从片的中断是由 8259A 主片帮忙向处理器传达的 8259A 从片是级联在 8259A 主片的 IRQ2 接口的因此为了让处理器也响应来自 8259A 从片的中断屏蔽中断寄存器必须也把 IRQ2 打开. 
由于我们的两个磁盘都是挂在了IDE通道0上而IDE通道0又是挂在了IRQ14线上所以我们只需要再打开这条线的中断信号就行 
/* 初始化可编程中断控制器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模式, 正常EOIoutb (PIC_M_DATA, 0xf8);    //IRQ2用于级联从片,必须打开,否则无法响应从片上的中断主片上打开的中断有IRQ0的时钟,IRQ1的键盘和级联从片的IRQ2,其它全部关闭outb (PIC_S_DATA, 0xbf);    //打开从片上的IRQ14,此引脚接收硬盘控制器的中断 put_str(   pic_init done\n);
} 
在以前我们内核态下进行打印一直用的console_put_xxx之类的函数这很不方便因为我们经常打印信息需要调用console_put_int, console_put_str, console_put_ch这三个函数配合使用。所以我们先来实现一个类似于用户态函数printf的内核态函数printk 
/** Author: Adward-DYX 1654783946qq.com* Date: 2024-04-28 17:33:08* LastEditors: Adward-DYX 1654783946qq.com* LastEditTime: 2024-05-06 12:59:59* FilePath: /OS/chapter13/13.2/kernel/stdio-kernel.c* Description: 这是默认设置,请设置customMade, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE*/
#include stdio-kernel.h
#include console.h
#include stdint.h
#include global.h
#include stdio.h#define va_start(args, first_fix) args  (va_list)first_fix
#define va_end(args) args  NULL/*供内核使用的格式化输出函数*/
void printk(const char* format, ...){va_list args;va_start(args, format);char buf[1024]  {0};vsprintf(buf,format,args);va_end(args);console_put_str(buf);
}创建硬盘相关的数据结构 
/** Author: Adward-DYX 1654783946qq.com* Date: 2024-04-28 17:37:39* LastEditors: Adward-DYX 1654783946qq.com* LastEditTime: 2024-05-06 13:18:20* FilePath: /OS/chapter13/13.2/device/ide.h* Description: 这是默认设置,请设置customMade, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE*/
#ifndef __DEVICE_IDE_H
#define __DEVICE_IDE_H#include stdint.h
#include list.h
#include bitmap.h
#include global.h
#include thread.h
#include sync.hstruct 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];     //主分区顶多只有4个struct partition logic_parts[8];    //逻辑分区数量无限但总得有个上限支持这里设置为8
};/*ata通道*/
//port_base咱们这里只处理两个通道的主板每个通道的
//端口范围是不一样的通道1(Primary通道的命令块寄存器端口范围是 Ox1FOOx1F7控制块寄存器
//端口是 0x3F6通道 2 ( Secondary 通道〉命令块寄存器端口范围是 Ox170Ox177 控制块寄存器端口是0x376 
//通道 l 的端口可以以 0x1F0 为基数其命令块寄存器端口在此基数上分别加上 0 7 就可以了
//控制块寄存器端口在此基数上加上 0x206同理通道 2 的基数就是 0xl70 
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]; //一个通道上连接两个硬盘一主一从
};创建并初始化 
#include stdint.h
#include global.h
#include ide.h
#include debug.h
#include sync.h
#include stdio.h
#include stdio-kernel.h
#include interrupt.h
#include memory.h
#include debug.h/* 定义硬盘各寄存器的端口号见书p126 */
#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寄存器的一些关键位见书p128 */
#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        //指定为LBA寻址方式
#define BIT_DEV_DEV	0x10        //指定主盘或从盘DEV位为1表示从盘为0表示主盘/* 一些硬盘操作的指令 */
#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(void){printk(ide_init start\n);uint8_t hd_cnt  *((uint8_t*)(0x475));  //获取硬盘的数量printk(   ide_init hd_cnt:%d\n,hd_cnt);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通道的起始端口号是0x1f0channel-irq_no  0x2014;  //从片8259A上倒数第二个中断引脚 硬盘也就是ide0通道的中断向量号  0x20为起始中断号break;case 1:channel-port_base  0x170; //ide1通道的起始端口号是0x170channel-irq_no  0x2015;  //从片上最后一个中断引脚我们用来相应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,achannel_no*2dev_no);identify_disk(hd);//获取硬盘参数if(dev_no!0){  //内核本身的裸硬盘(hd60M.img)不处理partition_scan(hd,0);   //扫描该硬盘上的分区}p_no0,l_no0;dev_no;}dev_no  0;channel_no;   //下一个channel}printk(\n all partition info\n);/*打印所有分区信息*/list_traversal(partition_list,partition_info,(int)NULL);printk(ide_init done\n);
}在物理地址0x475存储着主机上安装的硬盘数量它是由BIOS检测并写入的。 
13.2.2 实现thread_yied和idle线程 
thread_yield 定义在也read.c 中它的功能是主动把 CPU 使用权让出来它与thread_block 的区别是thread_yield 执行后任务的状态是 TASKREADY即让出 CPU 后它会被加入到就绪队列中下次还能继续被调度器调度执行而 thread_block 执行后任务的状态是 TASK_BLOCKD需要被唤醒后才能加入到就绪队列  所以下次执行还不知道是什么时候 。 
硬盘是一个相对于CPU来说及其低速的设备所以当硬盘在进行需要长时间才能完成的工作时比如写入数据我们最好能让驱动程序把CPU让给其他任务。所以我们来实现一个thread_yield函数就是用于把CPU让出来。实质就是将调用者重新放入就绪队列队尾。 
修改thread.c 
/* 主动让出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);
} 
thread_yield中有个关中断的操作会不会导致切换后由于关闭中断而不响应时钟中断导致一直运行在切换后的进程/线程中呢其实并不会我们讨论两种情况一种是进程/线程第一次上机运行一种是进程/线程之前已经运行过但由于时间片到期而换下过处理器。对于前者我们进程/线程第一次上机运行都会经过kernel_thread这个线程启动器而这个里面是有开中断的代码的。对于后者当切换回进程/线程时它们执行kernel.S中的中断退出代码jmp intr_exit这里面有一条指令iretd会打开中断让处理器能够继续响应中断代理发送来的中断信号。 
接下来我们实现一个idle线程用于在就绪队列为空时运行。需要注意一点我们之前没有idle线程我们的系统没有出现书上说的由于就绪队列为空然后被ASSERT(!list_empty(thread_ready_list);悬停的情况是因为我们的主线程简单理解就是main函数里面的while(1)会一直被不断加入就绪队列所以就绪队列并不存在为空的时候。 
修改thread.c 
struct task_struct* idle_thread;    // idle线程/* 系统空闲时运行的线程 */
static void idle(void* arg UNUSED) {while(1) {thread_block(TASK_BLOCKED);     //执行hlt时必须要保证目前处在开中断的情况下,hlt是停止处理器将进入暂停状态直到发生硬件中断asm volatile (sti; hlt : : : memory);}
}/* 实现任务调度 */
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);   
}/* 初始化线程环境 */
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);
} 
13.2.3 实现简单的休眠函数 
硬盘和 CPU 是相互独立的个体它们各自并行执行但由于硬盘是低速设备其在处理请求时往往消耗很长的时间不过手册上说最慢的情况也能在 31 秒之内完成为避免浪费 CPU 资源在等待硬盘操作的过程中最好把 CPU 主动让出来让 CPU 去执行其他任务为实现这种“明智”的行为我们在 timer.c中定义休眠函数当然这只是简易版精度不是很高能达到目的就可以了 
之前我们实现的thread_yield是将当前任务加入就绪队列队尾仅仅是把CPU让出来一次。我们来实现一个定时让出CPU的函数也就是让一个任务在固定时间内都不执行。 
修改timer.c 
#define mil_seconds_per_intr (1000 / IRQ0_FREQUENCY)/* 以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); 
}13.2.4 完善硬盘驱动程序 现在我们来实现驱动程序的主体部分也就是实际与硬盘打交道的函数实质就是将一系列寄存器操作进行封装 
/** Author: Adward-DYX 1654783946qq.com* Date: 2024-04-29 09:54:07* LastEditors: Adward-DYX 1654783946qq.com* LastEditTime: 2024-05-06 13:17:42* FilePath: /OS/chapter13/13.2/device/ide.c* Description: 这是默认设置,请设置customMade, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE*/
#include ide.h
#include stdint.h
#include global.h
#include list.h
#include bitmap.h
#include debug.h
#include stdio.h
#include thread.h
#include sync.h
#include io.h
#include timer.h
#include string.h
#include stdio-kernel.h/*定义硬盘各寄存器的端口号*/
#define reg_data(charnnel)  (charnnel-port_base  0)
#define reg_error(charnnel)  (charnnel-port_base  1)
#define reg_sect_cnt(charnnel)  (charnnel-port_base  2)
#define reg_lba_l(charnnel)  (charnnel-port_base  3)
#define reg_lba_m(charnnel)  (charnnel-port_base  4)
#define reg_lba_h(charnnel)  (charnnel-port_base  5)
#define reg_dev(charnnel)  (charnnel-port_base  6)
#define reg_status(charnnel)  (charnnel-port_base  7)
#define reg_cmd(charnnel)  (reg_status(charnnel))
#define reg_alt_status(charnnel)  (charnnel-port_base  0x206)
#define reg_ctl(charnnel)  (reg_alt_status(charnnel))/*reg_alt_status寄存器的一些关键位*/
#define BIT_ALT_STAT_BSY    0x80    //硬盘忙
#define BIT_ALT_STAT_DRDY   0x40    //驱动器准备好了
#define BIT_ALT_STAT_DRQ    0x8    //数据传输准备好了
#define BIT_ALT_STAT_ERR    0x1    //有错误发生/*device寄存器的一些关键位*/
#define BIT_DEV_MBS 0xa0        //第7位和第5位固定位1
#define BIT_DEV_LBA 0x40        //指定为LBA寻址方式
#define BIT_DEV_DEV 0x10        //主盘还是从盘 现在为1是从盘为0表示主盘/*一些硬盘操作的指令*/
#define CMD_IDENTIFY    0xec    //identify指令即硬盘识别
#define CMD_READ_SECTOR   0x20  //读扇区指令
#define CMD_WRITE_SECTOR   0x30 //写扇区指令/*定义可读写的最大扇区数调试用的*/
#define max_lba ((80*1024*1024/512)-1)  //只支持80M硬盘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 位为 1reg_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 贝Jj表示写入 256 个扇区/*写入lba地址即扇区号*/outb(reg_lba_l(channel),lba);   //lba 地址的低8位不用单独取出低8位,outb 函数中的汇编指令 outb %b0,%w1会只用aloutb(reg_lba_m(channel),lba8); //lba地址的8-15位outb(reg_lba_h(channel),lba16); //lba地址的16-23位/*因为 lba 地址的第 24 27 位要存储在 device 寄存器的03 位无法单独写入这 4 位所以在此处把 device 寄存器再重新写入一次*/outb(reg_dev(channel),BIT_DEV_MBS|BIT_DEV_LBA|(hd-dev_no1 ? BIT_DEV_DEV : 0)|lba24);
}/*向通道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_cnt0){/*因为 sec_cnt 是自位变量由主调函数将其赋值时若为 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_cnt0){/*因为 sec_cnt 是自位变量由主调函数将其赋值时若为 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_ALT_STAT_BSY))return (inb(reg_status(channel))BIT_ALT_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_done256)sec_cnt)secs_op  256;else secs_op  sec_cnt - secs_done;/*2写入待读入的扇区数和起始扇区号*/select_sector(hd,lbasecs_done,secs_op);/*3执行的命令写入reg_cmd寄存器*/cmd_out(hd-my_channel,CMD_READ_SECTOR);    //准备开始读取数据/*******************  阻塞自己的时机  ***************************** 在硬盘已经开始工作开始在内部读数据或写数据后才能阻塞自己* 现在硬盘已经开始忙了将自己阻塞等待硬盘完成读操作后通过中断处理程序唤醒自己*//*硬盘完成操作后会发中断信号后面介绍的硬盘中断处理程序 intr_hd_handler 会在该通道上执行“ sema_up(channel-disk_done”从而唤醒当前的驱动程序*/sema_down(hd-my_channel-disk_done);/*4 检查硬盘转改是否可读*//*醒来后开始执行下面的代码*/if(!busy_wait(hd)){ //若失败char error[64];sprintf(error,%s read sector %d failed!!!!!!,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(lbamax_lba);ASSERT(sec_cnt0);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_done256)sec_cnt)secs_op  256;else secs_op  sec_cnt - secs_done;/*2 写入待写入的扇区数和起始扇区号*/select_sector(hd, lbasecs_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!!!!!!,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(void){printk(ide_init start\n);uint8_t hd_cnt  *((uint8_t*)(0x475));  //获取硬盘的数量printk(   ide_init hd_cnt:%d\n,hd_cnt);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通道的起始端口号是0x1f0channel-irq_no  0x2014;  //从片8259A上倒数第二个中断引脚 硬盘也就是ide0通道的中断向量号  0x20为起始中断号break;case 1:channel-port_base  0x170; //ide1通道的起始端口号是0x170channel-irq_no  0x2015;  //从片上最后一个中断引脚我们用来相应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,achannel_no*2dev_no);identify_disk(hd);//获取硬盘参数if(dev_no!0){  //内核本身的裸硬盘(hd60M.img)不处理partition_scan(hd,0);   //扫描该硬盘上的分区}p_no0,l_no0;dev_no;}dev_no  0;channel_no;   //下一个channel}printk(\n all partition info\n);/*打印所有分区信息*/list_traversal(partition_list,partition_info,(int)NULL);printk(ide_init done\n);
}select_disk接受一个参数硬盘指针hd功能是选择待操作的硬盘是主盘或从盘 。原理是利用device寄存器中的 dev 位该位为 0 表示是通道中的主盘为 1 表示是通道的从盘。最后通过 outb 函数将变量 reg_device 写入硬盘所在通道的device 寄存器这样就完成了主盘或从盘的选择。 
select_sector接受3个参数硬盘指针hd、扇区起始地址lba、扇区数sec_cnt功能是向硬盘控制器写入起始扇区地址及要读写的扇区数 。 
cmd_out向通道channel发命令cmd即写入操作命令读或者写 
read_from_sector硬盘读入sec_cnt个扇区的数据到buf 
write2sector将buf中的sec_cnt扇区的数据写入硬盘 
busy_wait 通过BIT_ALT_STAT_BSY 判断 status 寄存器的 BSY 位是否为 1 如果为 1 则表示硬盘繁这时候就调用 mtime_sleep(10)去休眠 10 毫秒。如果 BSY 位为 0 则表示硬盘不忙接着在再次读取status寄存器返回其 DRQ 位的值 DRQ 位为 1 表示硬盘己经准备好数据了。 
对于读硬盘来说驱动程序阻塞自己是在硬盘开始读扇区之后对于写硬盘来说驱动程序阻塞自己是在硬盘开始写扇区之后。总之阻塞的时机一定是在硬盘开始真正忙活之后的那段“漫长”的时间里 
13.2.5 获取硬盘信息扫描分区表 
本节该是检验它们的时候了咱们用两件工作来验证一是向硬盘发 identify 命令获取硬盘的信息二是扫描分区表。 Linux中所有的设备都在dev目录下硬盘命名规则是x]d[y][n其中只有字母 d 是固定的其他带中括号的字符都是多选值下面从左到右介绍各个字符。 
x表示硬盘分类硬盘有两大类 IDE 磁盘和 SCSI 磁盘。h代表 IDE 磁盘 s代表 SCSI 磁盘故 x取值为h和s. 
d表示disk即磁盘 
y表示设备号更多操作以区分第几个设备取值范围是小写字符其中a是第1个硬盘b是第2个硬盘依此类推。 
n表示分区号。也就是一个硬盘上的第几个分区。分区以数字 1开始依次类推。 
咱们这里统一用 SCSI 硬盘的命名规则来命名虚拟硬盘hd60M.img 和 hd80M且恕。其中 hd60M.img 为 sda, hd80M.img 为 sdb o hd60M.img 是裸盘没有文件系统和分区因此咱们只处理 hd80M.img将其上的主分区占据 sdb【1 4】逻辑分区占据 sdb【5~】。 
修改ide.c 
/*用于记录总扩展分区的起始 lba 初始为 O, 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];    //分区表中有四项供六十四字节uint16_t signature; //启动扇区的结束标志是 Ox55,0xaa,
}__attribute__ ((packed));/*将dst中len个相邻字节交换位置后存入buf*/
static void swap_pairs_bytes(const char* dst, char* buf, uint32_t len){uint8_t idx;for(idx0;idxlen;idx2){/*buf中存储dst中两相邻袁术交换位置后的字符串*/buf[idx1]  *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!!!!!!,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_idx4){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_no1);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开始,主分区是14.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;  
}swap_pairs_bytes:用来处理identify命令的返回信息硬盘参数信息是以字为单位的包括偏移、长度的单位都是字在这16位的字中相邻字符的位置是互换的所以通过此函数来做转换。