本文根据慕课资料进行粗略学习操作系统的知识,不打算写lab
ucore课程文档
课程地址
其他大佬的lab答案地址
推荐博客1
推荐博客2
建议先阅读《编码:隐匿在计算机软硬件背后的语言》和《X86汇编语言-从实模式到保护模式》
CPU加电后会进行初始化,然后在内存读第一条指令。内存有一部分是ROM、一部分是RAM。断电后RAM信息会消失,但是ROM内容一直都在。
读的第一个指令是CS:IP指向的地址(值应该是默认的),刚加电的CPU处于16位实模式下,寻址空间大小为2的20次方(1MB),CS、IP都是16位的。CS*16+IP(0xFFFF0)是第一条指令的地址,同时CS:IP要在2的20次方的寻址空间内。第一条指令在最底下的1MB空间内。这第一条指令及其跟着的指令就是BIOS,它要提供一些服务,然后CPU才能访问磁盘设备。
BIOS从磁盘读引导扇区(512字节)里的加载程序,将其写到内存0x7c00,然后CS:IP跳到那里去执行加载程序(bootloader)。
详细一点说,BIOS只能在实模式下运行,它先是检查硬件,进行设备初始化。修电脑时,显示器不工作,可以猜测内存有问题。因为BIOS先检查到内存有问题,后面它就不用检查了,也不启动系统了。然后检查插入的U盘、磁盘什么的。检查产生的信息成为BIOS数据。虽然ROM数据不会消失,但是因为每次插入的硬件不一样,所以BIOS会改这些数据。最后读我们磁盘的第一个扇区。BIOS先读磁盘的主引导扇区(512字节)。因为这个东西提供的信息可以帮我们选择启动磁盘里的哪个操作系统。根据主引导扇区的信息来选择并读取活动分区(分区引导扇区)。先执行分区引导扇区的跳转指令,跳转到启动代码启动到加载程序。
加载程序把磁盘的ucore操作系统数据和代码加载到内存,再跳到ucore起始地址。把控制权交给操作系统。
详细地说,加载程序会先从文件系统中读取启动配置信息,依据这些信息决定怎么加载内核,这个地方如果我们可以弄一个选项在显示屏(启动菜单),可以改参数就很好。最后跳到内核。
ucore结构简单,应该是直接到加载程序。
不够详细知道这些还是很粗糙,如果写实际程序,还要根据CPU手册、BIOS规范(从哪里读第一条,上文是0xFFFF0)。所以自己写操作系统还是要查很多资料的。
系统启动规范 BIOS这里的主引导扇区的硬盘分区表只有四个分区信息,每个分区信息16字节(BIOS-MBR)。BIOS-GPT则支持超过四个分区。PXE是网络启动,从服务器下载资料到磁盘来启动,此时BIOS要有网络下载功能,BIOS变复杂了。前面说的是在本地磁盘启动。
UEFI在所有平台一致地启动操作系统。启动磁盘的任何系统。为了安全,检查引导记录是否可信。只读取满足签名的引导记录。
查资料知乎BIOS与UEFI区别
加载程序干什么加载程序(bootloader)先定义全局描述符表,然后让CPU从实模式进入保护模式,最后加载内核文件。
定义全局描述符表我们知道,为了让程序在内存中能自由浮动而又不影响它的正常执行,处理器将内存划分成逻辑上的段,并在指令中使用段内偏移地址。在保护模式下,对内存的访问仍然使用段地址和偏移地址,但是,在每个段能够访问之前,必须先进行登记。
《X86汇编语言-从实模式到保护模式》
每个段由8字节的段描述符描述,描述表存放描述符。
最主要的描述表是全局描述表(Global Descriptor Table,GDT)。还有一个是局部描述表。
CPU有一个48位全局描述表寄存器(GDTR)。它分为32位的线性地址和16位的边界。GDTR 的线性地址部分保存的是全局描述符表在内存中的起始线性地址。边界保存的是全局描述表的边界,其在数值上等于表的大小(总字节数)减一。
GDT最大大小是16位(216字节),由于在实模式下只能访问1MB 的内存,所以GDT 通常都定义在1MB 以下的内存范围中。
加载程序源代码分析 bootasm.S#include
.set PROT_MODE_CSEG, 0x8 # 代码段
.set PROT_MODE_DSEG, 0x10 # 数据段
.set CR0_PE_ON, 0x1 # 保护模式标志
# BIOS根据引导扇区加载bootloader到内存 然后从0x7c00开始执行
.globl start # .globl表示整个程序入口
start:
.code16 # 以16位(实模式)执行
cli # 禁止中断发生
cld # 使方向标志位为0 程序往地址增加方向执行
xorw %ax, %ax # ax清零
movw %ax, %ds # ds清零
movw %ax, %es # es清零
movw %ax, %ss # ss清零
# 看官方文档 https://chyyuu.gitbooks.io/ucore_os_docs/content/lab1/lab1_appendix_a20.html
# 再看 《X86汇编语言 从实模式到保护模式》 11.5 关于第21条地址线A20的问题
# 为了进入32位保护模式 必须先开启A20 大概知道一下 我感觉源代码也没有完全按文档来
seta20.1:
inb $0x64, %al # 读64h端口获得StatusRegister
testb $0x2, %al # 如果StatusRegister从低到高第2位为0
# 说明无输入 可以进入保护模式
jnz seta20.1 # 如果al低2位为0 则ZF=0 则不跳转 说明低2位为0
movb $0xd1, %al # 向64h发送0d1h命令
outb %al, $0x64
seta20.2:
inb $0x64, %al
testb $0x2, %al
jnz seta20.2
movb $0xdf, %al
outb %al, $0x60
# 设置GDT
lgdt gdtdesc
# 修改CRO寄存器中的保护模式允许位 进入保护模式
movl %cr0, %eax
orl $CR0_PE_ON, %eax # 与0x1进行或运算
movl %eax, %cr0 # cr0设置为1
ljmp $PROT_MODE_CSEG, $protcseg # 以PROT_MODE_CSEG为段地址,protcseg为段内偏移地址
.code32 # 以32位(保护模式)执行
protcseg:
# 设置数据段
movw $PROT_MODE_DSEG, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %ax, %ss
# Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00)
movl $0x0, %ebp
movl $start, %esp
call bootmain # 调用bootmain
# 如果bootmain返回了 继续循环
spin:
jmp spin
# Bootstrap GDT
.p2align 2 # 4比特对齐 不知道啥意思
# 32位的处理器具有32根地址线 可以访问的地址范围是0x00000000到0xffffffff
# 所以 base为0x0 lim为0xffffffff
gdt:
SEG_NULLASM # 空段 8字节
SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # 代码段 8字节 type为可读可执行段
SEG_ASM(STA_W, 0x0, 0xffffffff) # 数据段 8字节 type为只写段
# GDT创建
gdtdesc:
.word 0x17 # 边界(表的总字节数减一)
# 3*8-1=23=0x17字节 用一个字的空间储存
.long gdt # 线性基地址(GDT首地址)
asm.h里面是实现如何创建GDT,不清楚原理



