Ucore Lab1(中)

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的末二位是记录当前特权级

其它虽然懂一些, 但不是特别懂

练习三:

  1. 关闭中断, flag置零
  2. 设置数据段的段寄存器
  3. 打开cr0和A20地址线, A20是为了访问高地址, cr0的PE位置打开代表切换到了保护模式
  4. 设置段表GDT, 构建虚拟地址和物理地址的映射
  5. 通过长跳转更新cs的基地址
  6. 设置段寄存器, 构造栈
  7. 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);
}

猜你喜欢

转载自blog.csdn.net/eeeeeight/article/details/119356303