前言
这个章节会在这个 hal_start 函数里,首先执行板级初始化,其实就是 hal 层(硬件抽象层,下同)初始化,其中执行了平台初始化,hal 层的内存初始化,中断初始化,最后进入到内核层的初始化。
初始化平台
我们先来写好平台初始化函数,因为它需要最先被调用。
这个函数主要负责完成两个任务,一是把二级引导器建立的机器信息结构复制到 hal 层中的一个全局变量中(init_machbstart),方便内核中的其它代码使用里面的信息,之后二级引导器建立的数据所占用的内存都会被释放。二是要初始化图形显示驱动(init_bdvideo),内核在运行过程要在屏幕上输出信息。
初始化内存
首先,我们建立一个 halmm.c 文件,用于初始化内存,为了后面的内存管理器作好准备。
hal 层的内存初始化比较容易,只要向内存管理器提供内存空间布局信息就可以。
你可能在想,不对啊,明明我们在二级引导器中已经获取了内存布局信息,是的,但 Cosmos 的内存管理器需要保存更多的信息,最好是顺序的内存布局信息,这样可以增加额外的功能属性,同时降低代码的复杂度。
主要的原理就是根据 e820map_t 结构数组,建立了一个 phymmarge_t 结构数组,init_one_pmrge 函数正是把 e820map_t 结构中的信息复制到 phymmarge_t 结构中来。
初始化中断
在 x86 CPU 上,最多支持 256 个中断,还记得前面所说的中断表和中断门描述符吗,这意味着我们要准备 256 个中断门描述符和 256 个中断处理程序的入口。
有了中断门之后,还差中断入口处理程序,中断入口处理程序只负责这三件事:
-
保护 CPU 寄存器,即中断发生时的程序运行的上下文。
-
调用中断处理程序,这个程序可以是修复异常的,可以是设备驱动程序中对设备响应的程序。
-
恢复 CPU 寄存器,即恢复中断时程序运行的上下文,使程序继续运行。
以上这些操作又要用汇编代码才可以编写,我觉得这是内核中最重要的部分,所以我们建立一个文件,并用 kernel.asm 命名。
一开始把所有中断的处理程序设置为保留的通用处理程序,避免未知中断异常发生了 CPU 无处可去,然后对已知的中断和异常进一步设置,这会覆盖之前的通用处理程序,这样就可以确保万无一失。
前面我们只是解决了中断的 CPU 相关部分,而 CPU 只是响应中断,但是并不能解决产生中断的问题。
比如缺页中断来了,我们要解决内存地址映射关系,程序才可以继续运行。再比如硬盘中断来了,我们要读取硬盘的数据,要处理这问题,就要写好相应的处理函数。
因为有些处理是内核所提供的,而有些处理函数是设备驱动提供的,想让它们和中断关联起来,就要好好设计中断处理框架了。
下面我们来画幅图,描述中断框架的设计:
初始化中断控制器
我们把 CPU 端的中断搞定了以后,还有设备端的中断,这个可以交给设备驱动程序,但是 CPU 和设备之间的中断控制器,还需要我们出面解决。
多个设备的中断信号线都会连接到中断控制器上,中断控制器可以决定启用或者屏蔽哪些设备的中断,还可以决定设备中断之间的优先线,所以它才叫中断控制器。
调用流程
一、建造二级引导器
1、grub启动后,选择对应的启动菜单项,grub会通过自带文件系统驱动,定位到对应的eki文件
2、grub会尝试加载eki文件【eki文件需要满足grub多协议引导头的格式要求】
这些是在imginithead.asm中实现的,所以要包括:
A、grub文件头,包括魔数、grub1和grub2支持等
B、定位的_start符号等
3、grub校验成功后,会调用_start,然跳转到_entry
A、_entry中:关闭中断
B、加载GDT
C、然后进入_32bits_mode,清理寄存器,设置栈顶
D、调用inithead_entry【C】
4、inithead_entry.c
A、从imginithead.asm进入后,首先进入函数调用inithead_entry
B、初始化光标,清屏
C、从eki文件内部,找到initldrsve.bin文件,并分别拷贝到内存的指定物理地址
D、从eki文件内部,找到initldrkrl.bin文件,并分别拷贝到内存的指定物理地址
E、返回imginithead.asm
5、imginithead.asm中继续执行
jmp 0x200000
而这个位置,就是initldrkrl.bin在内存的位置ILDRKRL_PHYADR
所以后面要执行initldrkrl.bin的内容
6、这样就到了ldrkrl32.asm的_entry
A、将GDT加载到GDTR寄存器【内存】
B、将IDT加载到IDTR寄存器【中断】
C、跳转到_32bits_mode
初始寄存器
初始化栈
调用ldrkrl_entry【C】
7、ldrkrlentry.c
A、初始化光标,清屏
B、收集机器参数init_bstartparm【C】
8、bstartparm.c
A、初始化machbstart_t
B、各类初始化函数,填充machbstart_t的内容
C、返回
9、ldrkrlentry.c
A、返回
10、ldrkrl32.asm
A、跳转到0x2000000地址继续执行
二、HAL层调用链
hal_start()
A、先去处理HAL层的初始化
->init_hal()
->->init_halplaltform()初始化平台
->->->init_machbstart()
主要是把二级引导器建立的机器信息结构,复制到了hal层一份给内核使用,同时也为释放二级引导器占用内存做好准备。
其做法就是拷贝了一份mbsp到kmbsp,其中用到了虚拟地址转换hyadr_to_viradr
->->->init_bdvideo()
初始化图形机构
初始化BGA显卡 或 VBE图形显卡信息【函数指针的使用】
清空屏幕
找到"background.bmp",并显示背景图片
->->->->hal_dspversion()
输出版本号等信息【vsprintfk】
其中,用ret_charsinfo根据字体文件获取字符像素信息
->->move_img2maxpadr()
将移动initldrsve.bin到最大地址
->->init_halmm()初始化内存
->->->init_phymmarge
申请phymmarge_t内存
根据 e820map_t 结构数组,复制数据到phymmarge_t 结构数组
按内存开始地址进行排序
->->init_halintupt();初始化中断
->->->init_descriptor();初始化GDT描述符x64_gdt
->->->init_idt_descriptor();初始化IDT描述符x64_idt,绑定了中断编号及中断处理函数
->->->init_intfltdsc();初始化中断异常表machintflt,拷贝了中断相关信息
->->->init_i8259();初始化8529芯片中断
->->->i8259_enabled_line(0);好像是取消mask,开启中断请求
最后,跳转去处理内核初始化
->init_krl()
三、中断调用链,以硬件中断为例
A、kernel.inc中,通过宏定义,进行了中断定义。以硬件中断为例,可以在kernel.inc中看到:
宏为HARWINT,硬件中断分发器函数为hal_hwint_allocator
%macro HARWINT 1
保存现场…
mov rdi, %1
mov rsi,rsp
call hal_hwint_allocator
恢复现场…
%endmacro
B、而在kernel.asm中,定义了各种硬件中断编号,比如hxi_hwint00,作为中断处理入口
ALIGN 16
hxi_hwint00:
HARWINT (INT_VECTOR_IRQ0+0)
C、有硬件中断时,会先到达中断处理入口,然后调用到硬件中断分发器函数hal_hwint_allocator
第一个参数为中断编号,在rdi
第二个参数为中断发生时的栈指针,在rsi
然后调用异常处理函数hal_do_hwint
D、hal_do_hwint
加锁
调用中断回调函数hal_run_intflthandle
释放锁
E、hal_run_intflthandle
先获取中断异常表machintflt
然后调用i_serlist 链表上所有挂载intserdsc_t 结构中的中断处理的回调函数,是否处理由函数自己判断
F、中断处理完毕
G、异常处理类似,只是触发源头不太一样而已
流程思维导图:
以下贴一张树下野鹿大佬整理的操作系统Cosmos hal层的函数调用思维导图:
参考资料
以上内容是我学习彭东老师的《操作系统实战45讲》后所进行的一个笔记记录,如有错误,还请各位大佬多多指教。
我主要参考了以下资料,十分感谢:
操作系统实战45讲——彭东老师
评论区大佬—neohope、艾恩凝、卖薪沽酒等