学堂在线-清华大学-操作系统实验Lab1【练习3-4】

2024-04-13 02:48

本文主要是介绍学堂在线-清华大学-操作系统实验Lab1【练习3-4】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

练习3:分析bootloader进入保护模式的过程。

BIOS将通过读取硬盘主引导扇区到内存,并转跳到对应内存中的位置执行bootloader。请分析bootloader是如何完成从实模式进入保护模式的。

lab1/boot/bootasm.S源码如下:

#include <asm.h># Start the CPU: switch to 32-bit protected mode, jump into C.
# The BIOS loads this code from the first sector of the hard disk into
# memory at physical address 0x7c00 and starts executing in real mode
# with %cs=0 %ip=7c00..set PROT_MODE_CSEG,        0x8                     # kernel code segment selector
.set PROT_MODE_DSEG,        0x10                    # kernel data segment selector
.set CR0_PE_ON,             0x1                     # protected mode enable flag# start address should be 0:7c00, in real mode, the beginning address of the running bootloader
.globl start
start:
.code16                                             # Assemble for 16-bit modecli                                             # Disable interruptscld                                             # String operations increment# Set up the important data segment registers (DS, ES, SS).xorw %ax, %ax                                   # Segment number zeromovw %ax, %ds                                   # -> Data Segmentmovw %ax, %es                                   # -> Extra Segmentmovw %ax, %ss                                   # -> Stack Segment# Enable A20:#  For backwards compatibility with the earliest PCs, physical#  address line 20 is tied low, so that addresses higher than#  1MB wrap around to zero by default. This code undoes this.
seta20.1:inb $0x64, %al                                  # Wait for not busy(8042 input buffer empty).testb $0x2, %aljnz seta20.1movb $0xd1, %al                                 # 0xd1 -> port 0x64outb %al, $0x64                                 # 0xd1 means: write data to 8042's P2 portseta20.2:inb $0x64, %al                                  # Wait for not busy(8042 input buffer empty).testb $0x2, %aljnz seta20.2movb $0xdf, %al                                 # 0xdf -> port 0x60outb %al, $0x60                                 # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1# Switch from real to protected mode, using a bootstrap GDT# and segment translation that makes virtual addresses# identical to physical addresses, so that the# effective memory map does not change during the switch.lgdt gdtdescmovl %cr0, %eaxorl $CR0_PE_ON, %eaxmovl %eax, %cr0# Jump to next instruction, but in 32-bit code segment.# Switches processor into 32-bit mode.ljmp $PROT_MODE_CSEG, $protcseg.code32                                             # Assemble for 32-bit mode
protcseg:# Set up the protected-mode data segment registersmovw $PROT_MODE_DSEG, %ax                       # Our data segment selectormovw %ax, %ds                                   # -> DS: Data Segmentmovw %ax, %es                                   # -> ES: Extra Segmentmovw %ax, %fs                                   # -> FSmovw %ax, %gs                                   # -> GSmovw %ax, %ss                                   # -> SS: Stack Segment# Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00)movl $0x0, %ebpmovl $start, %espcall bootmain# If bootmain returns (it shouldn't), loop.
spin:jmp spin# Bootstrap GDT
.p2align 2                                          # force 4 byte alignment
gdt:SEG_NULLASM                                     # null segSEG_ASM(STA_X|STA_R, 0x0, 0xffffffff)           # code seg for bootloader and kernelSEG_ASM(STA_W, 0x0, 0xffffffff)                 # data seg for bootloader and kernelgdtdesc:.word 0x17                                      # sizeof(gdt) - 1.long gdt                                       # address gdt

分析代码如下:

.set PROT_MODE_CSEG,        0x8                     # kernel code segment selector
.set PROT_MODE_DSEG,        0x10                    # kernel data segment selector
.set CR0_PE_ON,             0x1                     # protected mode enable flag

.set相当于宏定义。

.globl start
start:
.code16                                             # 启动CPU为16位模式cli                                             # 关中断cld                                             # 清方向标志# Set up the important data segment registers (DS, ES, SS).xorw %ax, %ax                                   # 寄存器置零movw %ax, %ds                                   # -> 数据段寄存器movw %ax, %es                                   # -> 附加段寄存器movw %ax, %ss                                   # -> 堆栈段寄存器

CLI 全称 Clear Interupt,CLD 全称 Clear Director。

seta20.1:											# 等待8042输入缓冲区空inb $0x64, %al                                  # 从0x64端口读入一个字节的数据到al中testb $0x2, %al									# 测试al的第2位jnz seta20.1									# al的第2位为0,则跳出循环movb $0xd1, %al                                 # 将0xd1写入al中outb %al, $0x64                                 # 将al中的数据写入到端口0x64中 seta20.2:inb $0x64, %al                                  # Wait for not busy(8042 input buffer empty).testb $0x2, %aljnz seta20.2movb $0xdf, %al                                 # 将0xdf写入al中outb %al, $0x60                                 # 将al写入到0x60端口中,即将A20置1

首先等待8042 input buffer为空,向其发送写数据的指令,再次等待8042 input buffer为空,将0xdf发送至0x60,打开A20。

通过修改A20地址线可以完成从实模式到保护模式的转换。只有在保护模式下,80386的全部32根地址线有效,可寻址高达4G字节的线性地址空间和物理地址空间,可访问64TB(有2^14个段,每个段最大空间为2^32字节)的逻辑地址空间,可采用分段存储管理机制和分页存储管理机制。

接下来加载GDT表,并且将cr0置为1开启保护模式。

	lgdt gdtdescmovl %cr0, %eax									# 加载cro到eaxorl $CR0_PE_ON, %eax							# 将eax的第0位置为1movl %eax, %cr0									# 将cr0的第0位置为1

cr0的第0位为1表示处于保护模式
cr0的第0位为0表示处于实模式

长跳转指令更新cs的基地址

ljmp $PROT_MODE_CSEG, $protcseg
.code32												# 使用32位模式编译
protcseg:

设置寄存器并建立堆栈

    movw $PROT_MODE_DSEG, %ax                       # ax赋0x8movw %ax, %ds                                   # ds赋0x8movw %ax, %es                                   # es赋0x8movw %ax, %fs                                   # fs赋0x8movw %ax, %gs                                   # gs赋0x8movw %ax, %ss                                   # ss赋0x8movl $0x0, %ebp									# 设置帧指针movl $start, %esp								# 设置栈指针

转到保护模式完成,进入boot主方法

call bootmain

练习4:分析bootloader加载ELF格式的OS的过程。

1.bootloader如何读取硬盘扇区的?

答:①等待磁盘准备好;②发出读取扇区的命令;③等待磁盘准备好;④把磁盘扇区数据读到指定内存。这些是readsect()函数所做的事。但读取多个扇区用readseg() 函数,其中循环调用了readsect()。

2.bootloader是如何加载ELF格式的OS?

答:这是bootmain函数的任务。首先从硬盘读取elf的文件头,它包含整个执行文件的控制结构,然后利用它得到 program header 表,再根据表将所有段读入内存,最后启动程序。

查看bootmani.c文件内容:

#include <defs.h>
#include <x86.h>
#include <elf.h>#define SECTSIZE        512
#define ELFHDR          ((struct elfhdr *)0x10000)      // scratch spacestatic void
waitdisk(void) {while ((inb(0x1F7) & 0xC0) != 0x40)
}/* readsect - 读一个扇区@secno到@dst */
static void
readsect(void *dst, uint32_t secno) {waitdisk();								// 等待磁盘准备好outb(0x1F2, 1);                         // 要读写扇区的个数为1outb(0x1F3, secno & 0xFF);outb(0x1F4, (secno >> 8) & 0xFF);outb(0x1F5, (secno >> 16) & 0xFF);outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0);outb(0x1F7, 0x20);                      // cmd 0x20 - 读取扇区// 等待磁盘就绪waitdisk();// 把磁盘扇区数据读到指定内存insl(0x1F0, dst, SECTSIZE / 4);
}/* ** readseg - read @count bytes at @offset from kernel into virtual address @va,* might copy more than asked.* */
static void
readseg(uintptr_t va, uint32_t count, uint32_t offset) {uintptr_t end_va = va + count;// round down to sector boundary// 四舍五入到扇区边界va -= offset % SECTSIZE;// translate from bytes to sectors; kernel starts at sector 1// 从字节转换到扇区;kernel从扇区1开始uint32_t secno = (offset / SECTSIZE) + 1;// If this is too slow, we could read lots of sectors at a time.// We'd write more to memory than asked, but it doesn't matter --// we load in increasing order.for (; va < end_va; va += SECTSIZE, secno ++) {readsect((void *)va, secno);}
}/* bootmain - the entry of bootloader */
// 根据elfhdr和proghdr的结构描述,bootloader就可以完成对ELF格式的ucore操作系统的加载过程
void
bootmain(void) {// 先从磁盘读出第一个pagereadseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0);// 有效的ELF?if (ELFHDR->e_magic != ELF_MAGIC) {goto bad;}struct proghdr *ph, *eph;// phoff 是 program header 表的位置偏移// phnum 是 program header表中的入口数目// ELF头部有描述ELF文件应加载到内存什么位置的描述表,这里读取出来将之存入ph// 按照程序头表的描述,将ELF文件中的数据载入内存ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff);eph = ph + ELFHDR->e_phnum;for (; ph < eph; ph ++) {// 段的第一个字节的虚拟地址,段在内存映像中占用的字节数,段相对文件头的偏移值readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset);}// 根据ELF程序入口的虚拟地址,找到入口开始运行// note: does not return((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();bad:outw(0x8A00, 0x8A00);outw(0x8A00, 0x8E00);while (1);
}

磁盘IO地址和对应功能

IO地址功能
0x1f0读数据,当0x1f7不为忙状态时,可以读。
0x1f2要读写的扇区数,每次读写前,你需要表明你要读写几个扇区。最小是1个扇区
0x1f3如果是LBA模式,就是LBA参数的0-7位
0x1f4如果是LBA模式,就是LBA参数的8-15位
0x1f5如果是LBA模式,就是LBA参数的16-23位
0x1f6第0~3位:如果是LBA模式就是24-27位 第4位:为0主盘;为1从盘
0x1f7状态和命令寄存器。操作时先给命令,再读取,如果不是忙状态就从0x1f0端口读数据

这篇关于学堂在线-清华大学-操作系统实验Lab1【练习3-4】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/898948

相关文章

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

电力系统中的A类在线监测装置—APView400

随着电力系统的日益复杂和人们对电能质量要求的提高,电能质量在线监测装置在电力系统中得到广泛应用。目前,市场上的在线监测装置主要分为A类和B类两种类型,A类和B类在线监测装置主要区别在于应用场景、技术参数、通讯协议和扩展性。选择时应根据实际需求和应用场景综合考虑,并定期维护和校准。电能质量在线监测装置是用于实时监测电力系统中的电能质量参数的设备。 APView400电能质量A类在线监测装置以其多核

RabbitMQ练习(AMQP 0-9-1 Overview)

1、What is AMQP 0-9-1 AMQP 0-9-1(高级消息队列协议)是一种网络协议,它允许遵从该协议的客户端(Publisher或者Consumer)应用程序与遵从该协议的消息中间件代理(Broker,如RabbitMQ)进行通信。 AMQP 0-9-1模型的核心概念包括消息发布者(producers/publisher)、消息(messages)、交换机(exchanges)、

JavaFX应用更新检测功能(在线自动更新方案)

JavaFX开发的桌面应用属于C端,一般来说需要版本检测和自动更新功能,这里记录一下一种版本检测和自动更新的方法。 1. 整体方案 JavaFX.应用版本检测、自动更新主要涉及一下步骤: 读取本地应用版本拉取远程版本并比较两个版本如果需要升级,那么拉取更新历史弹出升级控制窗口用户选择升级时,拉取升级包解压,重启应用用户选择忽略时,本地版本标志为忽略版本用户选择取消时,隐藏升级控制窗口 2.

Go Playground 在线编程环境

For all examples in this and the next chapter, we will use Go Playground. Go Playground represents a web service that can run programs written in Go. It can be opened in a web browser using the follow

Linux操作系统 初识

在认识操作系统之前,我们首先来了解一下计算机的发展: 计算机的发展 世界上第一台计算机名叫埃尼阿克,诞生在1945年2月14日,用于军事用途。 后来因为计算机的优势和潜力巨大,计算机开始飞速发展,并产生了一个当时一直有效的定律:摩尔定律--当价格不变时,集成电路上可容纳的元器件的数目,约每隔18-24个月便会增加一倍,性能也将提升一倍。 那么相应的,计算机就会变得越来越快,越来越小型化。

【Rust练习】12.枚举

练习题来自:https://practice-zh.course.rs/compound-types/enum.html 1 // 修复错误enum Number {Zero,One,Two,}enum Number1 {Zero = 0,One,Two,}// C语言风格的枚举定义enum Number2 {Zero = 0.0,One = 1.0,Two = 2.0,}fn m

MySql 事务练习

事务(transaction) -- 事务 transaction-- 事务是一组操作的集合,是一个不可分割的工作单位,事务会将所有的操作作为一个整体一起向系统提交或撤销请求-- 事务的操作要么同时成功,要么同时失败-- MySql的事务默认是自动提交的,当执行一个DML语句,MySql会立即自动隐式提交事务-- 常见案例:银行转账-- 逻辑:A给B转账1000:1.查询

STM32(十一):ADC数模转换器实验

AD单通道: 1.RCC开启GPIO和ADC时钟。配置ADCCLK分频器。 2.配置GPIO,把GPIO配置成模拟输入的模式。 3.配置多路开关,把左面通道接入到右面规则组列表里。 4.配置ADC转换器, 包括AD转换器和AD数据寄存器。单次转换,连续转换;扫描、非扫描;有几个通道,触发源是什么,数据对齐是左对齐还是右对齐。 5.ADC_CMD 开启ADC。 void RCC_AD

html css jquery选项卡 代码练习小项目

在学习 html 和 css jquery 结合使用的时候 做好是能尝试做一些简单的小功能,来提高自己的 逻辑能力,熟悉代码的编写语法 下面分享一段代码 使用html css jquery选项卡 代码练习 <div class="box"><dl class="tab"><dd class="active">手机</dd><dd>家电</dd><dd>服装</dd><dd>数码</dd><dd