Day4 进入保护模式并在保护模式下读取磁盘

本文介绍了基于x8632位的操作系统开发过程中,如何从实模式切换到保护模式,包括清除流水线、操作A20地址线、加载GDT表、设置保护模式标志和远跳转等步骤,以及在保护模式下利用LBA模式读取磁盘的技术细节。
摘要由CSDN通过智能技术生成

**该系列文章为bilibili李述铜老师手写基于x86 32位的操作系统课程笔记,如有疑问欢迎评论沟通**


前言:

        CPU上电复位后默认进入实模式,这种模式下没有保护机制,但是提供了BIOS服务 。这种模式的限制:

        1. 只能访问1MB内存内核寄存器最大为16位

        2. 所有的操作数最大为16位

        3. 没有任何保护机制

        4. 没有特权级支持

        5. 没有分页机制虚拟内存的支持 

        保护模式为后面增加的功能,这种模式为操作系统及应用程序的运行添加了很多支持,特性为:

        1. 寄存器位宽扩展到32位最大可访问4GB内存

        2. 所有的操作数最大为32位宽,出入栈也为32位

        3. 提供4种特权级操作系统可以运行在最高特权级应用程序可运行在最低特权级

        4. 支持虚拟内存,可以开启分页机制         

一、实模式切换至保护模式   

1. 切换流程

        从实模式切换至保护模式,需要遵循的流程(此处引用课程截图): 

         因为CPU执行指令是以流水线的形式进行执行,因此开启保护模式后流水线上可能还会存在16位的指令未被执行,但是保护模式执行的都是32位的指令因此需要清空原来的流水线以取消掉原16位的指令。

2. 实现切换

        实现切换的所有相关的函数均封装在comm文件夹下的cpu_instr.h文件中

        ①禁用中断函数 

//关中断函数
static inline void cli(void){
    __asm__ __volatile__("cli");
}

//开中断函数
static inline void sti(void){
    __asm__ __volatile__("sti");
}

        ②打开A20地址线(A20地址线打开方式及为什么要打开可以参考此篇文章:关于A20地址线的简单介绍icon-default.png?t=N7T8https://blog.csdn.net/weixin_47852408/article/details/135827145?spm=1001.2014.3001.5502) :

//读端口函数
static inline uint8_t inb(uint16_t port){
    //返回参数
    uint8_t rv;
    //内联汇编形式读取端口
    __asm__ __volatile__(
        "inb %[p], %[v]"
        :[v]"=a"(rv) : [p]"d"(port)
        );
    return rv;
    /*注意的是:
    inb al, dx是intel的汇编语法,而下面的
    inb %[p], %[v]是GCC的汇编语法
    两者表示同一指令,但是写法不一样。源,目的操作数位置正好相反
    */
}

//写端口函数
static inline void outb(uint16_t port, uint8_t data){
    /*指令格式为
    outb al(数据), dx(端口)
    */
    __asm__ __volatile__(
        "outb %[v], %[p]"
        ::[v]"a"(data), [p]"d"(port)
    );
    /*注意的是:
    outb al, dx是intel的汇编语法,而下面的
    outb %[v], %[p]是GCC的汇编语法
    两者表示同一指令,但是写法不一样。源,目的操作数位置正好相反
    */
}
    //2.开A20地址线
    uint8_t v = inb(0x92);
    outb(0x92, v | 0x2);

        ③加载GDT表(后续的课程会对GDT表进行详解):

//gdt表的配置信息,现在先照写,后续课程会更新为什么这么写
uint16_t gdt_table[][4] = {
    {0, 0, 0, 0},
    {0xFFFF, 0x0000, 0x9a00, 0x00cf},
    {0xFFFF, 0x0000, 0x9200, 0x00cf},

};
//加载GDT表函数,后续的课程会进一步讲解
static inline void lgdt(uint32_t start, uint32_t size){
    //gdt表的结构
    struct{
        uint16_t limit;
        uint16_t start15_0;
        uint16_t start31_16;
    }gdt;
    
    gdt.start31_16 = start >> 16;
    gdt.start15_0 = start & 0xFFFF;
    gdt.limit = size - 1;

    __asm__ __volatile__(
        "lgdt %[g]"
        ::[g]"m"(gdt)
    );
}

        ④开始保护模式使能位 

//读取cr0
static inline uint16_t read_cr0(void){
    uint32_t cr0;
    __asm__ __volatile__(
        "mov %%cr0, %[v]"
        ::[v]"r"(cr0)
    );
    return cr0;
}

//写入cr0
static inline void write_cr0(uint32_t v){
    __asm__ __volatile__(
        "mov %[v], %%cr0"
        ::[v]"r"(v)
    );
}
    //4.开启保护模式的使能位
    uint32_t cr0 = read_cr0();
    write_cr0(cr0 | (1<<0));

        ⑤远跳转

        实现远跳转首先要编写跳转到的函数,该函数写于汇编文件中,用于清空流水线进入保护模式,其中load_kernel函数为loader_32.c文件中的函数:

	.code32 # 声明生成32位的代码
	.text	# 表示下面的为代码
	.global protect_mode_entry # 将函数声明为全局的
	.extern load_kernel # 跳转到C代码环境去
protect_mode_entry:
	# 远跳转过来之后清空流水线
	mov $16, %ax # 为什么设置为16之后的课程会解释
	mov %ax, %ds
	mov %ax, %ss
	mov %ax, %es
	mov %ax, %fs
	mov %ax, %gs
	jmp $8, $load_kernel # 为什么为8,之后的课程会解释
	jmp .

        然后在之前的c环境中跳转到该汇编代码:

//远跳转(后续的课程会对跳转函数进行讲解)
static inline void far_jump(uint32_t selector, uint32_t offset){
    uint32_t addr[] = {offset, selector};

    __asm__ __volatile(
        "ljmpl *(%[a])"
        ::[a]"r"(addr)
    );
}
    //5.远跳转,清空流水线
    far_jump(8, (uint32_t)protect_mode_entry);

        最终的切换保护模式的函数:

//进入保护模式函数
static void enter_protected_mode(void){
    //1.关中断
    cli();

    //2.开A20地址线
    uint8_t v = inb(0x92);
    outb(0x92, v | 0x2);

    //3.加载GDT表
    lgdt((uint32_t)gdt_table, sizeof(gdt_table));

    //4.开启保护模式的使能位
    uint32_t cr0 = read_cr0();
    write_cr0(cr0 | (1<<0));

    //5.远跳转,清空流水线
    far_jump(8, (uint32_t)protect_mode_entry);
}

 二、保护模式读取磁盘

在保护模式下运行的代码为32位的代码即loader_32.c文件

         在保护模式下,BIOS功能无法使用,读取磁盘需要使用LBA模式,对于LBA模式可以参考该文章:LBA模式简单介绍-CSDN博客文章浏览阅读13次。LBA读磁盘https://blog.csdn.net/weixin_47852408/article/details/135830924?spm=1001.2014.3001.5501

#define SECTOR_SIZE 512//磁盘扇区大小宏定义
#define SYS_KERNEL_LOAD_ADDR    (1024*1024) //LBA模式读取磁盘缓存存放开始位置

//读寄存器数据函数
static inline uint16_t inw(uint16_t port){
    //返回参数
    uint16_t rv;
    // in ax, dx
    __asm__ __volatile__(
        "in %[p], %[v]"
        :[v]"=a"(rv) : [p]"d"(port)
        );
    return rv;
}

//使用LBA48位模式读磁盘函数,参数分别为 读取位置,读多少个,读取到的数据缓存存放位置
static void read_disk(uint32_t sector, uint32_t sector_count, uint8_t *buf){
    /*设置LBA模式的参数
    */
    outb(0x1F6, 0xE0);//选择读取硬盘

    outb(0x1F2, (uint8_t)(sector_count >> 8));
    /*
    下面的0x1Fx分别表示LBA参数的从低到高的位数
    */
    outb(0x1F3, (uint8_t) (sector >> 24));
    outb(0x1F4, 0);
    outb(0x1F5, 0);
    
    outb(0x1F2, (uint8_t)sector_count);
    outb(0x1F3,(uint8_t)(sector));
    outb(0x1F4, (uint8_t)(sector >> 8));
    outb(0x1F5, (uint8_t)(sector >> 16));

    outb(0x1F7, 0x24);

    uint16_t * data_buf = (uint16_t *)buf;
    while(sector_count--){
        while((inb(0x1F7) & 0x88) != 0x8){}//通过状态寄存器可知当第三位为1时表示就绪,可以写数据或者读数据
        //开始读一个扇区数据,并将数据写入缓存
        for(int i = 0; i < SECTOR_SIZE /2 ;i++){
            *data_buf++ = inw(0x1F0);//从0x1F0读
        }
    }
}




项目gitee地址:https://gitee.com/jim-jenny/x86-32OS

  • 32
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值