boot.S

 1 #include <inc/mmu.h>
 2 
 3 # 主要功能是完成了从实模式到保护模式的转换(用的临时gdt)    
 4 # 关闭了系统中断
 5 # 设置了堆栈,并且在最后调用main.c中的bootmain函数(作用是从硬盘读取内核)
 6 
 7 
 8 # .set .code16 .code32 是ARM伪指令
 9 .set PROT_MODE_CSEG, 0x8         # 代码段选择子,后面用于保护模式下寻址并跳转
10 .set PROT_MODE_DSEG, 0x10        # 数据段选择子
11 .set CR0_PE_ON,      0x1         # 定义了一个数值为1的变量,后面赋值给CR0寄存器以开启保护模式
12 
13 
14 #定义全局变量 start 作为程序入口
15 .globl start
16 start:
17   .code16                     # 在16位模式下执行汇编语言
18   cli                         # #禁用中断(防止引导过程被打断导致系统崩溃)
19   cld                         # 设置标志寄存器DF=0 在串处理指令(如movsb)中,控制每次操作后si、di的增减。
20 
21   # 重置重要的数据段寄存器 (DS, ES, SS).
22   xorw    %ax,%ax             # Segment number zero
23   movw    %ax,%ds             # -> Data Segment
24   movw    %ax,%es             # -> Extra Segment
25   movw    %ax,%ss             # -> Stack Segment
26 
27   #开启A20地址线,为什么要开启:请参考操作系统真象还原p157
28 # 29 # 30 #激活方式:由于历史原因A20地址位由键盘控制器芯片8042管理。所以要给8042发命令激活A20 31 #8042有两个IO端口:0x60和0x64,激活流程为: 先发送0xd1命令到0x64端口 --> 再发送0xdf到0x60 32 #https://www.cnblogs.com/arthurlin/p/14464824.html 33 seta20.1: 34 inb $0x64,%al # Wait for not busy 35 testb $0x2,%al 36 jnz seta20.1 37 38 movb $0xd1,%al # 0xd1 -> port 0x64 39 outb %al,$0x64 40 41 seta20.2: 42 inb $0x64,%al # Wait for not busy 43 testb $0x2,%al 44 jnz seta20.2 45 46 movb $0xdf,%al # 0xdf -> port 0x60 47 outb %al,$0x60 48 49 # 使用临时自定义(定义在了本代码的最后)的gdt,即全局描述符表 50 # 用 lgdt gdtdesc 将GDT表的首地址加载到 GDTR 寄存器(使用lgdt指令载入了事先定义好的GDT) 51 # 设置寄存器cr0 = 1,真正进入保护模式,寻址方式发生变化 52 lgdt gdtdesc #值得注意的是,lgdt指令需要的参数共6bytes,其中低位的2bytes表示该GDT的大小,高位的4bytes表示指向该GDT的32bits基址(gdtdesc所指向的参数满足这一要求) 53 movl %cr0, %eax 54 orl $CR0_PE_ON, %eax 55 movl %eax, %cr0 56 57 # 此时因为CR0最低位置1,已进入32位保护模式,PROT_MODE_CSEG 为代码段基址选择子, 58 # 这里用到了PROT_MODE_CSEG, 他的值是0x8。根据段选择子的格式定义,0x8就翻译成: 59  #       INDEX         TI CPL 60  #    0000 0000 0000 1 00 0 61 # INDEX代表GDT中的索引,TI代表使用GDTR中的GDT, CPL代表处于特权级。 62 # PROT_MODE_CSEG选择子选择了GDT中的第1个段描述符,即是SEG(STA_X|STA_R, 0x0, 0xffffffff)这一项,0x0 表示段首地址是0 63 # 所以基地址是0x0000,而偏移地址是$protcseg 64 # 所以最终得到的物理地址为0+$protcseg, 65 # 进程便会跳到 protcseg 所标识的位置来执行。 66 ljmp $PROT_MODE_CSEG, $protcseg 67 68 69 # 通过.code32伪指令编码的对各段寄存器的初始化, 70 # 随后利用start标号指向的地址作为esp调用main.c中的bootmain()函数 71 # bootmain函数的作用是从硬盘中读取内核 72 .code32 # Assemble for 32-bit mode 73 protcseg: 74 # Set up the protected-mode data segment registers 设置了堆栈使得C代码可以运行 75 movw $PROT_MODE_DSEG, %ax # Our data segment selector 76 movw %ax, %ds # -> DS: Data Segment 77 movw %ax, %es # -> ES: Extra Segment 78 movw %ax, %fs # -> FS 79 movw %ax, %gs # -> GS 80 movw %ax, %ss # -> SS: Stack Segment 81 82 # Set up the stack pointer and call into C. 83 movl $start, %esp 84 call bootmain 85 86 # If bootmain returns (it shouldn't), loop. 87 spin: 88 jmp spin 89 90 # Bootstrap GDT 91 .p2align 2 # force 4 byte alignment 92 gdt: 93 SEG_NULL # 调用了这个函数SEG_NULL(和下面两个宏一样,定义在了inc.mmu.h中),构建了一个空段(根据习惯,GDT的第一个段都是空段) 94 SEG(STA_X|STA_R, 0x0, 0xffffffff) # 定义了可执行的(STA_X)、可读的(STA_R)、基址为0x0且大小为0xffffffff(即占整个PC内存、大小为4GB的)的代码段 95 SEG(STA_W, 0x0, 0xffffffff) # 定义了可写的、基址为0x0且大小为0xffffffff(同上)的数据段 96 #这个gdt的定义相当于废掉了分段模式(因为Linux只使用分页模式) 97 gdtdesc: 98 .word 0x17 # sizeof(gdt) - 1 99 .long gdt # address gdt

 

所以最终得到的物理地址为0+$protcseg,程序便会跳到protcseg 所标识 的位置来执行

posted @ 2021-11-09 10:56  Pril  阅读(300)  评论(0)    收藏  举报