学堂在线-清华大学-操作系统实验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】相关推荐
- 学堂在线-清华大学-操作系统实验Lab1【练习1-2】
实验手册:https://chyyuu.gitbooks.io/ucore_os_docs/content/ 练习1:理解通过make生成执行文件的过程 1. 操作系统镜像文件ucore.img是如何 ...
- 学堂在线-清华大学-操作系统实验Lab1【练习5-6】
练习5:实现函数调用堆栈跟踪函数 (需要编程) 我们需要在lab1中完成kdebug.c中函数print_stackframe的实现,可以通过函数print_stackframe来跟踪函数调用堆栈中记 ...
- 学堂在线_操作系统_notes_第0-2讲_OS概述、OS实验环境准备
学堂在线_操作系统_notes_第0-2讲_OS概述.OS实验环境准备 - 20220626.No.1821 - 操作系统OS 综合了 C语言 + 数据结构与算法DSA + 计算机组成. OS 是 控 ...
- 北航linux内核编译及烧录实验报告,北航操作系统实验Lab1笔记
Loading... # 北航操作系统实验Lab1 ## Exercise 1.1 - **修改交叉编译路径为 `/OSLAB/compiler/usr/bin/mips_4KC-`** ![ex1_ ...
- 清华操作系统实验lab1
第一次写的lab1练习1太冗杂,没有重点,理解不到位,后续进一步研究后感觉务必重新写一篇...... [练习1.1] 操作系统镜像文件 ucore.img 是如何一步一步生成的 生成ucore.img ...
- 计算机控制系统期末测试,学堂在线计算机操作系统考试题及答案
一.单项选择题(每题1分,共20分)(更多试题及答案,尽在优题宝) 1.操作系统的发展过程是( C ) A.原始操作系统,管理程序,操作系统 B.原始操作系统,操作系统,管理程序 C.管理程序 ...
- MIT操作系统实验lab1(pingpong案例:附代码、详解)
1.题目描述:在xv6上实现pingpong程序,即两个进程在管道两侧来回通信.父进程将"ping"写入管道,子进程从管道将其读出并打印<pid>:received p ...
- ucore操作系统实验笔记 - Lab1
最近一直都在跟清华大学的操作系统课程,这个课程最大的特点是有一系列可以实战的操作系统实验.这些实验总共有8个,我在这里记录实验中的一些心得和总结. Task1 这个Task主要是为了熟悉Makfile ...
- 中断处理过程示意图_ucore操作系统实验笔记 - Lab1
最近一直都在跟清华大学的操作系统课程,这个课程最大的特点是有一系列可以实战的操作系统实验.这些实验总共有8个,我在这里记录实验中的一些心得和总结. Task1 这个Task主要是为了熟悉Makfile ...
最新文章
- 『高级篇』docker之DockerSwarm的集群环境搭建(28)
- Leaflet获取可视范围内4个顶点
- 2017 ZSTU寒假排位赛 #8
- [全]php-redis函数使用
- [YTU]_2866(结构体---点坐标结构体)
- 【Spark Summit EU 2016】在在线学习中使用Structured Streaming流数据处理引擎
- dock run mysql v3_Docker入门(三) - 搭建mysql
- 快手2021年营收810亿元 经调整净亏损188亿元
- 如何判断单链表里面是否有环【转载】
- SQL内连接和外连接的区别、where和on的区别详细介绍
- 计算机前景和需求,计算机类专业薪资排行:这6个收入更高,发展前景和就业需求很大...
- CDH 6系列(CDH 6.0.0、CHD 6.1.0等)安装和使用
- 2021年中国果蔬汁行业供需分析:产量同比增长2.4%[图]
- 黑平台Seener Tech Limtied在MT5上面搭建虚假交易 鼓动操作爆仓
- Zabbix5.0如何发送短信
- 微信跳一跳刷分代码剖析
- 世界级3D渲染大赛TOP3大佬们的制作流程大揭秘!
- 百度、Google.yahoo排名机制和优化规则
- 【Python】第1次作业:圆面积的计算A,计算矩形面积,说句心里话A
- 明纬电源、航嘉电源真假美猴王 你怎么识别?