Ucore Lab1(中)
Column: August 3, 2021
Tags: kernel study, learning experience
关于A20地址线的一些东东:
诞生的原因: 之前CPU 8086的最大寻址范围为0xffff0+0xffff=0x10ffef, 由于当时技术的限制(地址线也只有20根), 所以当地址大于0xfffff后会发生"回滚"的现象, 然而到了CPU Intel 80286后地址线变为了24根(当然后面的地址线会更多就是了), 当地址大于0xfffff后不是回滚, 而是访问到超出1MB的地址了, 所以设置了A20地址线, 为了防止访问到大于1MB的地址
现在的作用: 实模式下, 可访问地址应该限制到1MB以内, 因此该模式下A20是关闭的, 但是到了后面的保护模式以及操作系统彻底启动之后, 由于要访问很大的内存, 所以A20会打开咯
启动流程:
其它的叽里呱啦: 现在还有fast A20啥的, 不过超纲了, 而且也没啥资料就没了解, 对了这玩意是在60h端口上的(读写端口), 剩下一个实模式经常用的端口是64h(Status Register状态端口)
关于保护模式的一些东东:
只看懂了保护模式下cs段的一些作用: cs的末二位是记录当前特权级
其它虽然懂一些, 但不是特别懂
练习三:
- 关闭中断, flag置零
- 设置数据段的段寄存器
- 打开cr0和A20地址线, A20是为了访问高地址, cr0的PE位置打开代表切换到了保护模式
- 设置段表GDT, 构建虚拟地址和物理地址的映射
- 通过长跳转更新cs的基地址
- 设置段寄存器, 构造栈
- call bootmain
练习四:
注释一波源码吧, 看看自己懂了:
#include <defs.h>
#include <x86.h>
#include <elf.h>
#define SECTSIZE 512
#define ELFHDR ((struct elfhdr *)0x10000) // scratch space
/* 等待磁盘空闲: 其实就是看看io寄存器0x1f7是否忙碌 */
static void
waitdisk(void) {
while ((inb(0x1F7) & 0xC0) != 0x40)
/* do nothing */;
}
/* 读入磁盘: 以*dst为目标地址大小为secno读入一个单独的磁盘 */
static void
readsect(void *dst, uint32_t secno) {
//等待磁盘空闲
waitdisk();
//告诉io寄存器0x1f2读入的扇区数为1
outb(0x1F2, 1);
//下面三行是分别向io寄存器0x1f3, 0x1f4, 0x1f5读入参数第0->7, 8->15, 16->23位
outb(0x1F3, secno & 0xFF);
outb(0x1F4, (secno >> 8) & 0xFF);
outb(0x1F5, (secno >> 16) & 0xFF);
//先用&获取参数第24->27位和主盘/从盘的选择, 再通过|使得第5 6 7位为强制为1
//第6位为1是为了LAB模式, 而5 7位是强制要求
outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0); //firstly get 24th-27th as arg, second "or 11100000" get choosing main/secondary sector
//先发送命令0x20给io寄存器0x1f7: 读入磁盘
outb(0x1F7, 0x20);
//等待磁盘空闲
waitdisk();
//读数据给dst, 因为insl是四字节四字节的读, 所以SECTSIZE要/4
insl(0x1F0, dst, SECTSIZE / 4);
}
/* 其实就是包装了一下readsect, 主要是为了防止写读入需要的内存大于我们之前申请的内存 */
static void
readseg(uintptr_t va, uint32_t count, uint32_t offset) {
//获得下边界
uintptr_t end_va = va + count;
//调整上边界开始的位置
va -= offset % SECTSIZE;
//从字节数转换为磁盘数, 且磁盘开始从1号磁盘读入, 而不是0号
uint32_t secno = (offset / SECTSIZE) + 1;
//如果嫌太慢, 可以几个磁盘一起读
//可以读入比申请空间多的内容, 因为我们这里循环处理过
for (; va < end_va; va += SECTSIZE, secno ++) {
readsect((void *)va, secno);
}
}
/* bootmain: bootloader的入口*/
void
bootmain(void) {
//从磁盘读取第一页
readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0);
//判断是否为有效elf文件: elfhdr结构中的幻数magic是否等于ELF_MAGIC
if (ELFHDR->e_magic != ELF_MAGIC) {
goto bad;
}
struct proghdr *ph, *eph;
// 加载查程序中的各个段(忽视ph寄存器)
//ph通过elfhdr结构体中phoff记录program header的偏移找到proghdr结构
ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff);
//eph记录了program header表中的入口数目, 代表等等要多少个段要被加载?
eph = ph + ELFHDR->e_phnum;
/*
*从一开始ph所指向的段开始, 直到eph结束, 读入eph-ph个段
*每个段的开头由该段的proghdr中的p_va记录, 代表第一个字节将被放入内存的虚拟地址
*proghdr中的p_memsz记录了该段的大小, p_offset记录了该段进入磁盘的偏移
*这些偏移会导致后面io寄存器上参数的不同
*/
for (; ph < eph; ph ++) {
readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset);
}
//从elf头调用入口点且不再返回
((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();
bad:
outw(0x8A00, 0x8A00);
outw(0x8A00, 0x8E00);
/* do nothing */
while (1);
}