切换CPU到长模式
在调用第一个c函数之前,我们仍然要写一段汇编,切换CPU进入长模式,初始化CPU寄存器和C语言要用的栈。
因为目前代码执行流在二级引导器中,进入到 Cosmos 中这样在二级引导器中初始过的东西都不能用了
因为 CPU 进入了长模式,寄存器的位宽都变了,所以需要重新初始化。让我们一起来写这段汇编代码吧,我们先在 Cosmos/hal/x86/ 下建立一个 init_entry.asm 文件,写上后面这段代码
[section .start.text]
[BITS 32]
_start:
cli
mov ax,0x10
mov ds,ax
mov es,ax
mov ss,ax
mov fs,ax
mov gs,ax
lgdt [eGdtPtr]
;开启 PAE
mov eax, cr4
bts eax, 5 ; CR4.PAE = 1
mov cr4, eax
mov eax, PML4T_BADR ;加载MMU顶级页目录
mov cr3, eax
;开启 64bits long-mode
mov ecx, IA32_EFER
rdmsr
bts eax, 8 ; IA32_EFER.LME =1
wrmsr
;开启 PE 和 paging
mov eax, cr0
bts eax, 0 ; CR0.PE =1
bts eax, 31
;开启 CACHE
btr eax,29 ; CR0.NW=0
btr eax,30 ; CR0.CD=0 CACHE
mov cr0, eax ; IA32_EFER.LMA = 1
jmp 08:entry64
[BITS 64]
entry64:
mov ax,0x10
mov ds,ax
mov es,ax
mov ss,ax
mov fs,ax
mov gs,ax
xor rax,rax
xor rbx,rbx
xor rbp,rbp
xor rcx,rcx
xor rdx,rdx
xor rdi,rdi
xor rsi,rsi
xor r8,r8
xor r9,r9
xor r10,r10
xor r11,r11
xor r12,r12
xor r13,r13
xor r14,r14
xor r15,r15
mov rbx,MBSP_ADR
mov rax,KRLVIRADR
mov rcx,[rbx+KINITSTACK_OFF]
add rax,rcx
xor rcx,rcx
xor rbx,rbx
mov rsp,rax
push 0
push 0x8
mov rax,hal_start ;调用内核主函数
push rax
dw 0xcb48
jmp $
[section .start.data]
[BITS 32]
x64_GDT:
enull_x64_dsc: dq 0
ekrnl_c64_dsc: dq 0x0020980000000000 ; 64-bit 内核代码段
ekrnl_d64_dsc: dq 0x0000920000000000 ; 64-bit 内核数据段
euser_c64_dsc: dq 0x0020f80000000000 ; 64-bit 用户代码段
euser_d64_dsc: dq 0x0000f20000000000 ; 64-bit 用户数据段
eGdtLen equ $ - enull_x64_dsc ; GDT长度
eGdtPtr: dw eGdtLen - 1 ; GDT界限
dq ex64_GDT
上述代码中,1~11 行表示加载 70~75 行的 GDT,13~17 行是设置 MMU 并加载在二级引导器中准备好的 MMU 页表,19~30 行是开启长模式并打开 Cache,34~54 行则是初始化长模式下的寄存器,55~61 行是读取二级引导器准备的机器信息结构中的栈地址,并用这个数据设置 RSP 寄存器。
最关键的是 63~66 行,它开始把 8 和 hal_start 函数的地址压入栈中。dw 0xcb48 是直接写一条指令的机器码——0xcb48,这是一条返回指令。这个返回指令有点特殊,它会把栈中的数据分别弹出到 RIP,CS 寄存器,这正是为了调用我们 Cosmos 的第一个 C 函数 hal_start。
由于这是第一个 C 函数,也是初始化函数,我们还是要为它单独建立一个文件,以显示对它的尊重,依然在 Cosmos/hal/x86/ 下建立一个 hal_start.c 文件。写上这样一个函数。
void hal_start()
{
//第一步:初始化hal层
//第二步:初始化内核层
for(;;);
return;
}
hal层(硬件抽象层)初始化
平台初始化,hal 层的内存初始化,中断初始化,最后进入到内核层的初始化。
为了分离硬件的特性,我们设计了 hal 层,把硬件相关的操作集中在这个层,并向上提供接口,目的是让内核上层不用关注硬件相关的细节,也能方便以后移植和扩展。
下面我们在 Cosmos/hal/x86/ 下建立一个 halinit.c 文件,写出 hal 层的初始化函数
void init_hal()
{
//初始化平台
//初始化内存
//初始化中断
return;
}
这个函数也是一个调用者,没怎么干活。不过根据代码的注释能看出,它调用的函数多一点,但主要是完成初始化平台、初始化内存、初始化中断的功能函数。
初始化平台
我们先来写好平台初始化函数,因为它需要最先被调用。这个函数主要负责完成两个任务,
一是把二级引导器建立的机器信息结构复制到 hal 层中的一个全局变量中,方便内核中的其它代码使用里面的信息,之后二级引导器建立的数据所占用的内存都会被释放。
二是要初始化图形显示驱动,内核在运行过程要在屏幕上输出信息。
下面我们在 Cosmos/hal/x86/ 下建立一个 halplatform.c 文件,写上如下代码。
void machbstart_t_init(machbstart_t *initp)
{
//清零
memset(initp, 0, sizeof(machbstart_t));
return;
}
void init_machbstart()
{
machbstart_t *kmbsp = &kmachbsp;
machbstart_t *smbsp = MBSPADR;//物理地址1MB处
machbstart_t_init(kmbsp);
//复制,要把地址转换成虚拟地址
memcopy((void *)phyadr_to_viradr((adr_t)smbsp), (void *)kmbsp, sizeof(machbstart_t));
return;
}
//平台初始化函数
void init_halplaltform()
{
//复制机器信息结构
init_machbstart();
//初始化图形显示驱动
init_bdvideo();
return;
}
kmachbsp 你可能会有点奇怪,它是个结构体变量,结构体类型是 machbstart_t,这个结构和二级引导器所使用的一模一样。同时,它还是一个 hal 层的全局变量,我们想专门有个文件定义所有 hal 层的全局变量,于是我们在 Cosmos/hal/x86/ 下建立一个 halglobal.c 文件,写上如下代码。
//全局变量定义变量放在data段
#define HAL_DEFGLOB_VARIABLE(vartype,varname) \
EXTERN __attribute__((section(".data"))) vartype varname
HAL_DEFGLOB_VARIABLE(machbstart_t,kmachbsp);
初始化图形显示驱动
下面,我们在 Cosmos/hal/x86/ 下的 bdvideo.c 文件中,写好 init_bdvideo 函数。
void init_bdvideo()
{
dftgraph_t *kghp = &kdftgh;
//初始化图形数据结构,里面放有图形模式,分辨率,图形驱动函数指针
init_dftgraph();
//初始bga图形显卡的函数指针
init_bga();
//初始vbe图形显卡的函数指针