1. MBR 只有512B,放不下所有代码
2. boot -> loader -> kernel
硬盘读取:
- 硬盘控制端口
| Primary 通道 | Secondary 通道 | in 操作 | out 操作 |
| ----------------------- | -------------- | ------------ | ------------ |
| 0x1F0 | 0x170 | Data | Data |
| 0x1F1 | 0x171 | Error | Features |
| 0x1F2 | 0x172 | Sector count | Sector count |
| 0x1F3 | 0x173 | LBA low | LBA low |
| 0x1F4 | 0x174 | LBA mid | LBA mid |
| 0x1F5 | 0x175 | LBA high | LBA high |
| 0x1F6 | 0x176 | Device | Device |
| 0x1F7 | 0x177 | Status | Command |
BIOS 0x15 0xe820
注意:将ards_buffer放到最后,因为不知道会有多长
1. 实模式缺点:
- 实模式下操作系统和用户程序属于同一特权级
- 用户程序所引用的地址都是指向真实的物理地址
- 用户程序可以自由修改段基址,不受阻碍地访问所有内存
- 访问超过 64KB 的内存区域时要切换段基址
2. 保护模式如何解决:
- 地址转换:在开启分页机制后,程序引用的地址、CPU执行机器指令时处理的地址都变成了虚拟地址,
需要转化为物理地址后再去访问,地址转换由 处理器 和 操作系统 共同完成。处理器提供硬件,操
作系统提供页表。
- 兼容实模式:实模式是 32位CPU 运行在 16位模式 下的状态,此时 CPU 相当于更加强大的16位CPU,
可以处理32位的操作数。
- 段保护:在实模式下,程序访问内存段时,CPU 会完成对用户的权限检查,防止出现错误
3. 需要哪些步骤:
- 打开A20地址线:通过0x92端口
- 加载GDT:OS 会在 loader 代码中定义GDT,然后更新 gdtr 指向这一段内存,CPU 通过段寄存器存储段选择子,
访问地址首先通过段描述符表获得段描述符,然后通过段描述符得到段基址去访问,在这一过程,加载段选择子
和获得访问地址的过程中都会进行内存保护,如果保护失败就抛出异常。
- 将 cr0 的pe置为0
注意:一个nasm文件里,同时会处理16或者32位,例如在写实模式跳转到保护模式,在初始化保护模式中最后一句跳转指令所跳到
的标号地址一定再[bits 32]下面。(16位代码段和32位代码段的主要区别是,在16位代码段中,跳转目标的偏移用16位表示,
而在32位代码段中,跳转目标的偏移用32位表示。)
将loader中保存的全局描述符表和指针保存在内核中
- lgdt [gdt_ptr]; 加载 gdt
- sgdt [gdt_ptr]; 保存 gdt
注意:selector
```c++
typedef struct selector_t
{
u8 RPL : 2;
u8 TI : 1;
u16 index : 13;
} selector_t;
// 代码段 1 << 3
```
- 实模式下:
中断向量表(0x000 ~ 0x3ff)中保存256个中断函数指针(4字节, 段地址<<4+偏移地址, 低位偏移地址,高位段地址)
1. 注册中断向量表
2. 调用方式:
- int / iret: 保存 [错误码], eip, cs, eflags 值
- int nr ; 中断函数编号
3. 异常中断一般在异常处理函数中结束程序
- 保护模式下:
中断描述符表可放在内核任意位置
需要先注册中断处理函数
- 引发中断的方式有三种: 外中断 异常 软中断
1. 外中断就是由外部中断控制器通知 CPU 需要执行的,CPU 在当前指令执行完成之后,回去检测是否有中断产生,
如果有,并且 IF 位有效,也就是允许中断,那么就会执行中断处理函数,这种方式直接的体验就是,CPU 可以
在任意两个指令间插入一个中断函数调用,中断函数调用与普通函数调用稍有区别,在调用时栈中多压了一些数
据,在中断返回时,会弹出;
- 异常和软中断统称为 内中断,也就是这个中断时 CPU 和 软件内部产生的,与外部硬件无关;
2. 异常是 CPU 在执行过程中,执行不下去了,引发的中断调用,比如 除零异常,缺页异常,一般保护错误,有一
些异常在处理后程序是可以继续执行的,比如缺页异常,而有一些异常就不行了,比如一般保护,这种情况下一
般是软件访问了不该访问的内存或者寄存器,自己没有权限,于是CPU会调用一般保护异常函数,这个函数中,一
般会终止该进程的执行,试图访问自己没有权限的内容,应该是危险的程序,可能是恶意程序,或者是程序有漏洞;
3. 软中断,可以认为是应用程序和操作系统沟通的一种方式,应用程序运行在较低的特权级,一般来说没有直接访
问硬件的权限,当应用程序想要访问硬件的时候,比如典型的读写文件,就需要调用系统调用,系统调用就是用
软中断实现的,也就是应用程序调用软中断函数来请求操作系统,以访问硬件,访问硬件的函数是操作系统实现
的,于是被认为是安全的。
注意:有的中断会压入错误码,可以压入一个随机数,保持后续操作一致
错误码
- 外中断原理
指令执行流程:
取指:将 eip 指向的指令读入处理器
译码:将指令的微程序写入流水线 (多级 cache)
执行:执行指令
中断:处理中断
IF: Interrupt Flag 外中断允许标志,CPU 外中断的总开关
sti; 设置 IF 位 (set interrupt),允许外中断
cli; 清除 IF 位 (clear interrupt),禁止外中断
pushf; 将 elags 压入栈中
popf; 将栈顶弹出到 eflags
检测IF位(是否开启中断),检测INTR引脚(级联8259a芯片),得到中断编号,执行
利用外中断(时钟中断)进行任务切换:
![](./images/interrupt_context.drawio.svg)
TODO: 初始化外中断步骤
1. 初步
- 得到loader中内存检查结果(将储存结果的地址压栈,当做函数参数)
- 找到可用内存(type=1,基址1M 0x100000)
2. 物理内存管理
- 可用内存开始位置一些页用于管理物理内存
- 每一个物理页用一个字节表示引用数量
- get_page(), put_page();
3. 分页机制(平坦模型)
- 虚拟地址 -> 页目录(cr3寄存器) -> 页表 -> 页 + 偏移
4M = 4K * 1024,也就是需要1024个页存储;
用页目录表示这1024个页,用到了哪些页,一页页表4B,总共4KB。
- 使用
(1) 按照地址结构将逻辑地址拆成三个部分(10,10,12)。
(2) 从PCB中读取页目录起始地址,再根据一级页号查页目录表,找到下一级页表在内存中存放位置。
(3) 根据二级页号查表,找到最终想要访问的内存块号。
(4) 结合页内偏移量得到物理地址。
4. 启用分页
- 首先准备一个页目录,若干页表
- 将映射的地址写入页表,将页表写入页目录
- 将页目录写入 cr3 寄存器
- 将 cr0 最高位 (PG) 置为 1,启用分页机制
与同步区别就是在磁盘准备数据的时候是否忙等待(异步:阻塞,中断唤醒)
PIO方式读命令的执行过程如下:
1) 根据要读的扇区位置,向控制寄存器1F2H~1F6H发命令参数,等驱动器的状态寄存
器1F7H的DRDY置位后进入下一步;
2) 主机向驱动器命令控制器1F7H发送读命令20H;
3) 驱动器设置状态寄存器1F7H中的BSY位,并把数据发送到硬盘缓冲区;
4) 驱动器读取一个扇区后,自动设置状态寄存器1F7H的DRQ数据请求位,并清除BSY
位忙信号。DRQ位通知主机现在可以从缓冲区中读取512字节的数据,同时向主机发
INTRQ中断请求信号;
5) 主机响应中断请求,开始读取状态寄存器1F7H,以判断读命令执行的情况,同时
驱动器清除INTRQ中断请求信号;
6) 根据状态寄存器,如果读取的数据命令执行正常,进入7)
7) 主机通过数据寄存器1F0H读取硬盘缓冲区中的数据到主机缓冲区中,当一个扇区
数据被读完,扇区计数器−1,如果扇区计数器不为0,进入3),否则进入8);
1) 当所有的请求扇区的数据被读取后,命令执行结束。
![参考](https://silverrainz.me/blog/minix-v1-file-system.html)
实现 minix 文件系统
Minix v1 file system structure
1 zone = 2 block = 1024 byte
| bootsector | superblock | inode bitmap ... | zone bitmap ... | inodes zone | data zone |
| ...... | ...... | ...... | ...... | ...... | ...... |
| 主引导扇区 | 超级块 | inode位图 | 数据块位图 | inode块 | 数据块 |
- 系统层次:
整个文件系统的实现被分为五个层次
1. 磁盘驱动层:这一层通过 ins outs 指令, 负责从磁盘读取扇区到高速缓冲区
buf。
ide_ctrl_init(控制器初始化,识别硬盘,硬盘分区检测)
2. 块缓冲层:维护了一个高速缓冲的hashtable(拉链), 为上层提供bread bwrite
函数, 而getblk则用来分配缓冲区,brelse用来释放缓冲块
3. inode节点层:这一层开始和文件系统密切相关, inode节点层为使用中的磁盘中
的inode节点(inode_descriptor_t) 提供了内存中的拷贝(inode_t), 可以类比
块缓冲和虚拟块的关系.
4. 目录层
5. 文件和系统调用
vsprintf
sprintf
printk
显示驱动
- 映射: 所谓的映射就是访问某个地址空间中的内容时,就会自动定位到被映射的目标
物理设备中进行访问,这是由硬件来保证的。
- BIOS 所做的事情包括:
侦测硬件设备:系统中有哪些硬件设备,工作状态是什么;
对硬件设备进行初始化:比如最初始的中断向量表;
侦测操作系统启动设备:选择好一个系统盘之后,把系统盘中主引导扇区中的引导程
序读取到内存中;
- 特权级相关:
不论是访问数据,还是跳转到代码,特权级检查仅发生在 重新加载选择子 时,而不是每条指令都检查一遍。
对于 访问数据 来说,只能高特权级的指令访问地特权级的数据
对于 跳转到代码 来说,只能平级跳转,如果想从低特权级跳到高特权级需要通过 门,如果想从高特权级跳到低特权级需要通过 返回指令
| 门 | type值 | 存在位置 | 用法 |
| ---- | ---- | ---- | ---- |
| 任务门 | 0101 | GDT、LDT、IDT | 与TSS配合实现任务切换,不过大多数操作系统都不这么玩 |
| 中断门 | 1110 | IDT | 进入中断后屏蔽中断(eflags的IF位置0),linux利用此实现系统调用,int 0x80 |
| 陷阱门 | 1111 | IDT | 进入中断后不屏蔽中断 |
| 调用门 | 1100 | GDT、LDT | 用户用call或jmp指令从用户进程进入0特权级 |
-