练习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】相关推荐

  1. 学堂在线-清华大学-操作系统实验Lab1【练习1-2】

    实验手册:https://chyyuu.gitbooks.io/ucore_os_docs/content/ 练习1:理解通过make生成执行文件的过程 1. 操作系统镜像文件ucore.img是如何 ...

  2. 学堂在线-清华大学-操作系统实验Lab1【练习5-6】

    练习5:实现函数调用堆栈跟踪函数 (需要编程) 我们需要在lab1中完成kdebug.c中函数print_stackframe的实现,可以通过函数print_stackframe来跟踪函数调用堆栈中记 ...

  3. 学堂在线_操作系统_notes_第0-2讲_OS概述、OS实验环境准备

    学堂在线_操作系统_notes_第0-2讲_OS概述.OS实验环境准备 - 20220626.No.1821 - 操作系统OS 综合了 C语言 + 数据结构与算法DSA + 计算机组成. OS 是 控 ...

  4. 北航linux内核编译及烧录实验报告,北航操作系统实验Lab1笔记

    Loading... # 北航操作系统实验Lab1 ## Exercise 1.1 - **修改交叉编译路径为 `/OSLAB/compiler/usr/bin/mips_4KC-`** ![ex1_ ...

  5. 清华操作系统实验lab1

    第一次写的lab1练习1太冗杂,没有重点,理解不到位,后续进一步研究后感觉务必重新写一篇...... [练习1.1] 操作系统镜像文件 ucore.img 是如何一步一步生成的 生成ucore.img ...

  6. 计算机控制系统期末测试,学堂在线计算机操作系统考试题及答案

    一.单项选择题(每题1分,共20分)(更多试题及答案,尽在优题宝) 1.操作系统的发展过程是(  C    ) A.原始操作系统,管理程序,操作系统 B.原始操作系统,操作系统,管理程序 C.管理程序 ...

  7. MIT操作系统实验lab1(pingpong案例:附代码、详解)

    1.题目描述:在xv6上实现pingpong程序,即两个进程在管道两侧来回通信.父进程将"ping"写入管道,子进程从管道将其读出并打印<pid>:received p ...

  8. ucore操作系统实验笔记 - Lab1

    最近一直都在跟清华大学的操作系统课程,这个课程最大的特点是有一系列可以实战的操作系统实验.这些实验总共有8个,我在这里记录实验中的一些心得和总结. Task1 这个Task主要是为了熟悉Makfile ...

  9. 中断处理过程示意图_ucore操作系统实验笔记 - Lab1

    最近一直都在跟清华大学的操作系统课程,这个课程最大的特点是有一系列可以实战的操作系统实验.这些实验总共有8个,我在这里记录实验中的一些心得和总结. Task1 这个Task主要是为了熟悉Makfile ...

最新文章

  1. 『高级篇』docker之DockerSwarm的集群环境搭建(28)
  2. Leaflet获取可视范围内4个顶点
  3. 2017 ZSTU寒假排位赛 #8
  4. [全]php-redis函数使用
  5. [YTU]_2866(结构体---点坐标结构体)
  6. 【Spark Summit EU 2016】在在线学习中使用Structured Streaming流数据处理引擎
  7. dock run mysql v3_Docker入门(三) - 搭建mysql
  8. 快手2021年营收810亿元 经调整净亏损188亿元
  9. 如何判断单链表里面是否有环【转载】
  10. SQL内连接和外连接的区别、where和on的区别详细介绍
  11. 计算机前景和需求,计算机类专业薪资排行:这6个收入更高,发展前景和就业需求很大...
  12. CDH 6系列(CDH 6.0.0、CHD 6.1.0等)安装和使用
  13. 2021年中国果蔬汁行业供需分析:产量同比增长2.4%[图]
  14. 黑平台Seener Tech Limtied在MT5上面搭建虚假交易 鼓动操作爆仓
  15. Zabbix5.0如何发送短信
  16. 微信跳一跳刷分代码剖析
  17. 世界级3D渲染大赛TOP3大佬们的制作流程大揭秘!
  18. 百度、Google.yahoo排名机制和优化规则
  19. 【Python】第1次作业:圆面积的计算A,计算矩形面积,说句心里话A
  20. 明纬电源、航嘉电源真假美猴王 你怎么识别?

热门文章

  1. 百度API的基本介绍和使用场景
  2. java 模拟投票代码_求投票系统(Java源代码)
  3. 开源框架 crux的生成
  4. eggs和egg是什么意思_eggs怎么读,是什么意思?
  5. 设计模式看这篇就够了
  6. C++实现酒桌上”砸桌子“游戏
  7. Apache Storm-2.0.0 Trident 新版
  8. Gephi使用详解 实现图可视化(janusgraph)
  9. 可以两人一起记日记的共享记事本app有哪些
  10. Lind.DDD.Caching分布式数据集缓存介绍