极客时间已完结课程限时免费阅读

13 | 第一个C函数:如何实现板级初始化?

13 | 第一个C函数:如何实现板级初始化?-极客时间

13 | 第一个C函数:如何实现板级初始化?

讲述:陈晨

时长21:15大小19.41M

你好,我是 LMOS。
前面三节课,我们为调用 Cosmos 的第一个 C 函数 hal_start 做了大量工作。这节课我们要让操作系统 Cosmos 里的第一个 C 函数真正跑起来啦,也就是说,我们会真正进入到我们的内核中。
今天我们会继续在这个 hal_start 函数里,首先执行板级初始化,其实就是 hal 层(硬件抽象层,下同)初始化,其中执行了平台初始化,hal 层的内存初始化,中断初始化,最后进入到内核层的初始化。
这节课的配套代码,你可以从这里下载。

第一个 C 函数

任何软件工程,第一个函数总是简单的,因为它是总调用者,像是一个管理者,坐在那里发号施令,自己却是啥活也不干。
由于这是第一个 C 函数,也是初始化函数,我们还是要为它单独建立一个文件,以显示对它的尊重,依然在 Cosmos/hal/x86/ 下建立一个 hal_start.c 文件。写上这样一个函数。
void hal_start()
{
//第一步:初始化hal层
//第二步:初始化内核层
for(;;);
return;
}
根据前面的设计,Cosmos 是有 hal 层和内核层之分,所以在上述代码中,要分两步走。第一步是初始化 hal 层;第二步,初始化内核层。只是这两步的函数我们还没有写。
然而最后的死循环却有点奇怪,其实它的目的很简单,就是避免这个函数返回,因为这个返回了就无处可去,避免走回头路。

hal 层初始化

为了分离硬件的特性,我们设计了 hal 层,把硬件相关的操作集中在这个层,并向上提供接口,目的是让内核上层不用关注硬件相关的细节,也能方便以后移植和扩展。(关于 hal 层的设计,可以回顾第 3 节课)
也许今天我们是在 x86 平台上写 Cosmos,明天就要在 ARM 平台上开发 Cosmos,那时我们就可以写个 ARM 平台的 hal 层,来替换 Cosmos 中的 x86 平台的 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);
前面的 EXTERN,在 halglobal.c 文件中定义为空,而在其它文件中定义为 extern,告诉编译器这是外部文件的变量,避免发生错误。
下面,我们在 Cosmos/hal/x86/ 下的 bdvideo.c 文件中,写好 init_bdvideo 函数。
void init_bdvideo()
{
dftgraph_t *kghp = &kdftgh;
//初始化图形数据结构,里面放有图形模式,分辨率,图形驱动函数指针
init_dftgraph();
//初始bga图形显卡的函数指针
init_bga();
//初始vbe图形显卡的函数指针
init_vbe();
//清空屏幕 为黑色
fill_graph(kghp, BGRA(0, 0, 0));
//显示背景图片
set_charsdxwflush(0, 0);
hal_background();
return;
}
init_dftgraph() 函数初始了 dftgraph_t 结构体类型的变量 kdftgh,我们在 halglobal.c 文件中定义这个变量,结构类型我们这样来定义。
typedef struct s_DFTGRAPH
{
u64_t gh_mode; //图形模式
u64_t gh_x; //水平像素点
u64_t gh_y; //垂直像素点
u64_t gh_framphyadr; //显存物理地址
u64_t gh_fvrmphyadr; //显存虚拟地址
u64_t gh_fvrmsz; //显存大小
u64_t gh_onepixbits; //一个像素字占用的数据位数
u64_t gh_onepixbyte;
u64_t gh_vbemodenr; //vbe模式号
u64_t gh_bank; //显存的bank数
u64_t gh_curdipbnk; //当前bank
u64_t gh_nextbnk; //下一个bank
u64_t gh_banksz; //bank大小
u64_t gh_fontadr; //字库地址
u64_t gh_fontsz; //字库大小
u64_t gh_fnthight; //字体高度
u64_t gh_nxtcharsx; //下一字符显示的x坐标
u64_t gh_nxtcharsy; //下一字符显示的y坐标
u64_t gh_linesz; //字符行高
pixl_t gh_deffontpx; //默认字体大小
u64_t gh_chardxw;
u64_t gh_flush;
u64_t gh_framnr;
u64_t gh_fshdata; //刷新相关的
dftghops_t gh_opfun; //图形驱动操作函数指针结构体
}dftgraph_t;
typedef struct s_DFTGHOPS
{
//读写显存数据
size_t (*dgo_read)(void* ghpdev,void* outp,size_t rdsz);
size_t (*dgo_write)(void* ghpdev,void* inp,size_t wesz);
sint_t (*dgo_ioctrl)(void* ghpdev,void* outp,uint_t iocode);
//刷新
void (*dgo_flush)(void* ghpdev);
sint_t (*dgo_set_bank)(void* ghpdev, sint_t bnr);
//读写像素
pixl_t (*dgo_readpix)(void* ghpdev,uint_t x,uint_t y);
void (*dgo_writepix)(void* ghpdev,pixl_t pix,uint_t x,uint_t y);
//直接读写像素
pixl_t (*dgo_dxreadpix)(void* ghpdev,uint_t x,uint_t y);
void (*dgo_dxwritepix)(void* ghpdev,pixl_t pix,uint_t x,uint_t y);
//设置x,y坐标和偏移
sint_t (*dgo_set_xy)(void* ghpdev,uint_t x,uint_t y);
sint_t (*dgo_set_vwh)(void* ghpdev,uint_t vwt,uint_t vhi);
sint_t (*dgo_set_xyoffset)(void* ghpdev,uint_t xoff,uint_t yoff);
//获取x,y坐标和偏移
sint_t (*dgo_get_xy)(void* ghpdev,uint_t* rx,uint_t* ry);
sint_t (*dgo_get_vwh)(void* ghpdev,uint_t* rvwt,uint_t* rvhi);
sint_t (*dgo_get_xyoffset)(void* ghpdev,uint_t* rxoff,uint_t* ryoff);
}dftghops_t;
//刷新显存
void flush_videoram(dftgraph_t *kghp)
{
kghp->gh_opfun.dgo_flush(kghp);
return;
}
不难发现,我们正是把这些实际的图形驱动函数的地址填入了这个结构体中,然后通过这个结构体,我们就可以调用到相应的函数了。
因为写这些函数都是体力活,我已经帮你搞定了,你直接使用就可以。上面的 flush_videoram 函数已经证明了这一想法。
来,我们测试一下,看看结果,我们图形驱动程序初始化会显示背景图片——background.bmp,这是在打包映像文件时包含进去的,你自己可以随时替换,只要是满足 1024*768,24 位的位图文件就行了。
下面我们要把这些函数调用起来:
//在halinit.c文件中
void init_hal()
{
init_halplaltform();
return;
}
//在hal_start.c文件中
void hal_start()
{
init_hal();//初始化hal层,其中会调用初始化平台函数,在那里会调用初始化图形驱动
for(;;);
return;
}
接下来,让我们一起 make vboxtest,应该很有成就感。一幅风景图呈现在我们面前,上面有 Cosmos 的版本、编译时间、CPU 工作模式,内存大小等数据。这相当一个我们 Cosmos 的水印信息。
图形驱动测试

初始化内存

首先,我们在 Cosmos/hal/x86/ 下建立一个 halmm.c 文件,用于初始化内存,为了后面的内存管理器作好准备。
hal 层的内存初始化比较容易,只要向内存管理器提供内存空间布局信息就可以。
你可能在想,不对啊,明明我们在二级引导器中已经获取了内存布局信息,是的,但 Cosmos 的内存管理器需要保存更多的信息,最好是顺序的内存布局信息,这样可以增加额外的功能属性,同时降低代码的复杂度。
不难发现,BIOS 提供的结构无法满足前面这些要求。不过我们也有办法解决,只要以 BIOS 提供的结构为基础,设计一套新的数据结构就搞定了。这个结构可以这样设计。
#define PMR_T_OSAPUSERRAM 1
#define PMR_T_RESERVRAM 2
#define PMR_T_HWUSERRAM 8
#define PMR_T_ARACONRAM 0xf
#define PMR_T_BUGRAM 0xff
#define PMR_F_X86_32 (1<<0)
#define PMR_F_X86_64 (1<<1)
#define PMR_F_ARM_32 (1<<2)
#define PMR_F_ARM_64 (1<<3)
#define PMR_F_HAL_MASK 0xff
typedef struct s_PHYMMARGE
{
spinlock_t pmr_lock;//保护这个结构是自旋锁
u32_t pmr_type; //内存地址空间类型
u32_t pmr_stype;
u32_t pmr_dtype; //内存地址空间的子类型,见上面的宏
u32_t pmr_flgs; //结构的标志与状态
u32_t pmr_stus;
u64_t pmr_saddr; //内存空间的开始地址
u64_t pmr_lsize; //内存空间的大小
u64_t pmr_end; //内存空间的结束地址
u64_t pmr_rrvmsaddr;//内存保留空间的开始地址
u64_t pmr_rrvmend; //内存保留空间的结束地址
void* pmr_prip; //结构的私有数据指针,以后扩展所用
void* pmr_extp; //结构的扩展数据指针,以后扩展所用
}phymmarge_t;
有些情况下内核要另起炉灶,不想把所有的内存空间都交给内存管理器去管理,所以要保留一部分内存空间,这就是上面结构中那两个 pmr_rrvmsaddr、pmr_rrvmend 字段的作用。
有了数据结构,我们还要写代码来操作它:
u64_t initpmrge_core(e820map_t *e8sp, u64_t e8nr, phymmarge_t *pmargesp)
{
u64_t retnr = 0;
for (u64_t i = 0; i < e8nr; i++)
{
//根据一个e820map_t结构建立一个phymmarge_t结构
if (init_one_pmrge(&e8sp[i], &pmargesp[i]) == FALSE)
{
return retnr;
}
retnr++;
}
return retnr;
}
void init_phymmarge()
{
machbstart_t *mbsp = &kmachbsp;
phymmarge_t *pmarge_adr = NULL;
u64_t pmrgesz = 0;
//根据machbstart_t机器信息结构计算获得phymmarge_t结构的开始地址和大小
ret_phymmarge_adrandsz(mbsp, &pmarge_adr, &pmrgesz);
u64_t tmppmrphyadr = mbsp->mb_nextwtpadr;
e820map_t *e8p = (e820map_t *)((adr_t)(mbsp->mb_e820padr));
//建立phymmarge_t结构
u64_t ipmgnr = initpmrge_core(e8p, mbsp->mb_e820nr, pmarge_adr);
//把phymmarge_t结构的地址大小个数保存machbstart_t机器信息结构中
mbsp->mb_e820expadr = tmppmrphyadr;
mbsp->mb_e820exnr = ipmgnr;
mbsp->mb_e820exsz = ipmgnr * sizeof(phymmarge_t);
mbsp->mb_nextwtpadr = PAGE_ALIGN(mbsp->mb_e820expadr + mbsp->mb_e820exsz);
//phymmarge_t结构中地址空间从低到高进行排序,我已经帮你写好了
phymmarge_sort(pmarge_adr, ipmgnr);
return;
}
结合上面的代码,你会发现这是根据 e820map_t 结构数组,建立了一个 phymmarge_t 结构数组,init_one_pmrge 函数正是把 e820map_t 结构中的信息复制到 phymmarge_t 结构中来。理解了这个原理,即使不看我的,你自己也会写。
下面我们把这些函数,用一个总管函数调动起来,这个总管函数叫什么名字好呢?当然是 init_halmm,如下所示。
void init_halmm()
{
init_phymmarge();
//init_memmgr();
return;
}
这里 init_halmm 函数中还调用了 init_memmgr 函数,这个正是这我们内存管理器初始化函数,我会在内存管理的那节课展开讲。而 init_halmm 函数将要被 init_hal 函数调用。

初始化中断

什么是中断呢?为了帮你快速理解,我们先来看两种情景:
你在开车时,突然汽车引擎坏了,你需要修复它才能继续驾驶汽车……
你在外旅游,你女朋友突然来电话了,你可以选择接电话或者不接电话,当然不接电话的后果很严重(笑)……
在以上两种情景中,虽然不十分恰当,但都是在做一件事时,因为一些原因而要切换到另一件事上。其实计算机中的 CPU 也是一样,在做一件事时,因为一些原因要转而做另一件事,于是中断产生了……
根据原因的类型不同,中断被分为两类。
异常,这是同步的,原因是错误和故障,就像汽车引擎坏了。不修复错误就不能继续运行,所以这时,CPU 会跳到这种错误的处理代码那里开始运行,运行完了会返回。
为啥说它是同步的呢?这是因为如果不修改程序中的错误,下次运行程序到这里同样会发生异常。
中断,这是异步的,我们通常说的中断就是这种类型,它是因为外部事件而产生的,就好像旅游时女朋友来电话了。通常设备需要 CPU 关注时,会给 CPU 发送一个中断信号,所以这时 CPU 会跳到处理这种事件的代码那里开始运行,运行完了会返回。
由于不确定何种设备何时发出这种中断信号,所以它是异步的。
在 x86 CPU 上,最多支持 256 个中断,还记得前面所说的中断表和中断门描述符吗,这意味着我们要准备 256 个中断门描述符和 256 个中断处理程序的入口。
下面我们来定义它,如下所示:
typedef struct s_GATE
{
u16_t offset_low; /* 偏移 */
u16_t selector; /* 段选择子 */
u8_t dcount; /* 该字段只在调用门描述符中有效。如果在利用调用门调用子程序时引起特权级的转换和堆栈的改变,需要将外层堆栈中的参数复制到内层堆栈。该双字计数字段就是用于说明这种情况发生时,要复制的双字参数的数量。*/
u8_t attr; /* P(1) DPL(2) DT(1) TYPE(4) */
u16_t offset_high; /* 偏移的高位段 */
u32_t offset_high_h;
u32_t offset_resv;
}__attribute__((packed)) gate_t;
//定义中断表
HAL_DEFGLOB_VARIABLE(gate_t,x64_idt)[IDTMAX];
说到这里你会发现,中断表其实是个 gate_t 结构的数组,由 CPU 的 IDTR 寄存器指向,IDTMAX 为 256。
但是光有数组还不行,还要设置其中的数据,下面我们就来设计这个函数,建立一个文件 halsgdidt.c,在其中写一个函数,代码如下。
//vector 向量也是中断号
//desc_type 中断门类型,中断门,陷阱门
//handler 中断处理程序的入口地址
//privilege 中断门的权限级别
void set_idt_desc(u8_t vector, u8_t desc_type, inthandler_t handler, u8_t privilege)
{
gate_t *p_gate = &x64_idt[vector];
u64_t base = (u64_t)handler;
p_gate->offset_low = base & 0xFFFF;
p_gate->selector = SELECTOR_KERNEL_CS;
p_gate->dcount = 0;
p_gate->attr = (u8_t)(desc_type | (privilege << 5));
p_gate->offset_high = (u16_t)((base >> 16) & 0xFFFF);
p_gate->offset_high_h = (u32_t)((base >> 32) & 0xffffffff);
p_gate->offset_resv = 0;
return;
}
上面的代码,正是按照要求,把这些数据填入中断门描述符中的。有了中断门之后,还差中断入口处理程序,中断入口处理程序只负责这三件事:
1. 保护 CPU 寄存器,即中断发生时的程序运行的上下文。
2. 调用中断处理程序,这个程序可以是修复异常的,可以是设备驱动程序中对设备响应的程序。
3. 恢复 CPU 寄存器,即恢复中断时程序运行的上下文,使程序继续运行。
以上这些操作又要用汇编代码才可以编写,我觉得这是内核中最重要的部分,所以我们建立一个文件,并用 kernel.asm 命名。
我们先来写好完成以上三个功能的汇编宏代码,避免写 256 遍同样的代码,代码如下所示。
//保存中断后的寄存器
%macro SAVEALL 0
push rax
push rbx
push rcx
push rdx
push rbp
push rsi
push rdi
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
xor r14,r14
mov r14w,ds
push r14
mov r14w,es
push r14
mov r14w,fs
push r14
mov r14w,gs
push r14
%endmacro
//恢复中断后寄存器
%macro RESTOREALL 0
pop r14
mov gs,r14w
pop r14
mov fs,r14w
pop r14
mov es,r14w
pop r14
mov ds,r14w
pop r15
pop r14
pop r13
pop r12
pop r11
pop r10
pop r9
pop r8
pop rdi
pop rsi
pop rbp
pop rdx
pop rcx
pop rbx
pop rax
iretq
%endmacro
//保存异常下的寄存器
%macro SAVEALLFAULT 0
push rax
push rbx
push rcx
push rdx
push rbp
push rsi
push rdi
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
xor r14,r14
mov r14w,ds
push r14
mov r14w,es
push r14
mov r14w,fs
push r14
mov r14w,gs
push r14
%endmacro
//恢复异常下寄存器
%macro RESTOREALLFAULT 0
pop r14
mov gs,r14w
pop r14
mov fs,r14w
pop r14
mov es,r14w
pop r14
mov ds,r14w
pop r15
pop r14
pop r13
pop r12
pop r11
pop r10
pop r9
pop r8
pop rdi
pop rsi
pop rbp
pop rdx
pop rcx
pop rbx
pop rax
add rsp,8
iretq
%endmacro
//没有错误码CPU异常
%macro SRFTFAULT 1
push _NOERRO_CODE
SAVEALLFAULT
mov r14w,0x10
mov ds,r14w
mov es,r14w
mov fs,r14w
mov gs,r14w
mov rdi,%1 ;rdi, rsi
mov rsi,rsp
call hal_fault_allocator
RESTOREALLFAULT
%endmacro
//CPU异常
%macro SRFTFAULT_ECODE 1
SAVEALLFAULT
mov r14w,0x10
mov ds,r14w
mov es,r14w
mov fs,r14w
mov gs,r14w
mov rdi,%1
mov rsi,rsp
call hal_fault_allocator
RESTOREALLFAULT
%endmacro
//硬件中断
%macro HARWINT 1
SAVEALL
mov r14w,0x10
mov ds,r14w
mov es,r14w
mov fs,r14w
mov gs,r14w
mov rdi, %1
mov rsi,rsp
call hal_intpt_allocator
RESTOREALL
%endmacro
别看前面的代码这么长,其实最重要的只有两个指令:push、pop,这两个正是用来压入寄存器和弹出寄存器的,正好可以用来保存和恢复 CPU 所有的通用寄存器。
有的 CPU 异常,CPU 自动把异常码压入到栈中,而有的 CPU 异常没有异常码,为了统一,我们对没有异常码的手动压入一个常数,维持栈的平衡。
有了中断异常处理的宏,我们还要它们变成中断异常的处理程序入口点函数。汇编函数其实就是一个标号加一段汇编代码,C 编译器把 C 语言函数编译成汇编代码后,也是标号加汇编代码,函数名就是标号。
下面我们在 kernel.asm 中写好它们:
//除法错误异常 比如除0
exc_divide_error:
SRFTFAULT 0
//单步执行异常
exc_single_step_exception:
SRFTFAULT 1
exc_nmi:
SRFTFAULT 2
//调试断点异常
exc_breakpoint_exception:
SRFTFAULT 3
//溢出异常
exc_overflow:
SRFTFAULT 4
//段不存在异常
exc_segment_not_present:
SRFTFAULT_ECODE 11
//栈异常
exc_stack_exception:
SRFTFAULT_ECODE 12
//通用异常
exc_general_protection:
SRFTFAULT_ECODE 13
//缺页异常
exc_page_fault:
SRFTFAULT_ECODE 14
hxi_exc_general_intpfault:
SRFTFAULT 256
//硬件1~7号中断
hxi_hwint00:
HARWINT (INT_VECTOR_IRQ0+0)
hxi_hwint01:
HARWINT (INT_VECTOR_IRQ0+1)
hxi_hwint02:
HARWINT (INT_VECTOR_IRQ0+2)
hxi_hwint03:
HARWINT (INT_VECTOR_IRQ0+3)
hxi_hwint04:
HARWINT (INT_VECTOR_IRQ0+4)
hxi_hwint05:
HARWINT (INT_VECTOR_IRQ0+5)
hxi_hwint06:
HARWINT (INT_VECTOR_IRQ0+6)
hxi_hwint07:
HARWINT (INT_VECTOR_IRQ0+7)
为了突出重点,这里没有全部展示代码 ,你只用搞清原理就行了。那有了中断处理程序的入口地址,下面我们就可以在 halsgdidt.c 文件写出函数设置中断门描述符了,代码如下。
void init_idt_descriptor()
{
//一开始把所有中断的处理程序设置为保留的通用处理程序
for (u16_t intindx = 0; intindx <= 255; intindx++)
{
set_idt_desc((u8_t)intindx, DA_386IGate, hxi_exc_general_intpfault, PRIVILEGE_KRNL);
}
set_idt_desc(INT_VECTOR_DIVIDE, DA_386IGate, exc_divide_error, PRIVILEGE_KRNL);
set_idt_desc(INT_VECTOR_DEBUG, DA_386IGate, exc_single_step_exception, PRIVILEGE_KRNL);
set_idt_desc(INT_VECTOR_NMI, DA_386IGate, exc_nmi, PRIVILEGE_KRNL);
set_idt_desc(INT_VECTOR_BREAKPOINT, DA_386IGate, exc_breakpoint_exception, PRIVILEGE_USER);
set_idt_desc(INT_VECTOR_OVERFLOW, DA_386IGate, exc_overflow, PRIVILEGE_USER);
//篇幅所限,未全部展示
set_idt_desc(INT_VECTOR_PAGE_FAULT, DA_386IGate, exc_page_fault, PRIVILEGE_KRNL);
set_idt_desc(INT_VECTOR_IRQ0 + 0, DA_386IGate, hxi_hwint00, PRIVILEGE_KRNL);
set_idt_desc(INT_VECTOR_IRQ0 + 1, DA_386IGate, hxi_hwint01, PRIVILEGE_KRNL);
set_idt_desc(INT_VECTOR_IRQ0 + 2, DA_386IGate, hxi_hwint02, PRIVILEGE_KRNL);
set_idt_desc(INT_VECTOR_IRQ0 + 3, DA_386IGate, hxi_hwint03, PRIVILEGE_KRNL);
//篇幅所限,未全部展示
return;
}
上面的代码已经很明显了,一开始把所有中断的处理程序设置为保留的通用处理程序,避免未知中断异常发生了 CPU 无处可去,然后对已知的中断和异常进一步设置,这会覆盖之前的通用处理程序,这样就可以确保万无一失。
下面我们把这些代码整理一下,安装到具体的调用路径上,让上层调用者调用到就好了。
我们依然在 halintupt.c 文件中写上 init_halintupt() 函数:
void init_halintupt()
{
init_idt_descriptor();
init_intfltdsc();
return;
}
到此为止,CPU 体系层面的中断就初始化完成了。你会发现,我们在 init_halintupt() 函数中还调用了 init_intfltdsc() 函数,这个函数是干什么的呢?请往下看。
我们先来设计一下 Cosmos 的中断处理框架,后面我们把中断和异常统称为中断,因为它们的处理方式相同。
前面我们只是解决了中断的 CPU 相关部分,而 CPU 只是响应中断,但是并不能解决产生中断的问题。
比如缺页中断来了,我们要解决内存地址映射关系,程序才可以继续运行。再比如硬盘中断来了,我们要读取硬盘的数据,要处理这问题,就要写好相应的处理函数。
因为有些处理是内核所提供的,而有些处理函数是设备驱动提供的,想让它们和中断关联起来,就要好好设计中断处理框架了。
下面我们来画幅图,描述中断框架的设计:
中断框架设计图
可以看到,中断、异常分发器的左侧的东西我们已经处理完成,下面需要写好中断、异常分发器和中断异常描述符。
我们先来搞定中断异常描述,结合框架图,中断异常描述也是个表,它在 C 语言中就是个结构数组,让我们一起来写好这个数组:
typedef struct s_INTFLTDSC{
spinlock_t i_lock;
u32_t i_flg;
u32_t i_stus;
uint_t i_prity; //中断优先级
uint_t i_irqnr; //中断号
uint_t i_deep; //中断嵌套深度
u64_t i_indx; //中断计数
list_h_t i_serlist; //也可以使用中断回调函数的方式
uint_t i_sernr; //中断回调函数个数
list_h_t i_serthrdlst; //中断线程链表头
uint_t i_serthrdnr; //中断线程个数
void* i_onethread; //只有一个中断线程时直接用指针
void* i_rbtreeroot; //如果中断线程太多则按优先级组成红黑树
list_h_t i_serfisrlst;
uint_t i_serfisrnr;
void* i_msgmpool; //可能的中断消息池
void* i_privp;
void* i_extp;
}intfltdsc_t;
上面结构中,记录了中断的优先级。因为有些中断可以稍后执行,而有的中断需要紧急执行,所以要设计一个优先级。其中还有中断号,中断计数等统计信息。
中断可以由线程的方式执行,也可以是一个回调函数,该函数的地址放另一个结构体中,这个结构体我已经帮你写好了,如下所示。
typedef drvstus_t (*intflthandle_t)(uint_t ift_nr,void* device,void* sframe); //中断处理函数的指针类型
typedef struct s_INTSERDSC{
list_h_t s_list; //在中断异常描述符中的链表
list_h_t s_indevlst; //在设备描述描述符中的链表
u32_t s_flg;
intfltdsc_t* s_intfltp; //指向中断异常描述符
void* s_device; //指向设备描述符
uint_t s_indx;
intflthandle_t s_handle; //中断处理的回调函数指针
}intserdsc_t;
如果内核或者设备驱动程序要安装一个中断处理函数,就要先申请一个 intserdsc_t 结构体,然后把中断函数的地址写入其中,最后把这个结构挂载到对应的 intfltdsc_t 结构中的 i_serlist 链表中。
你可能要问了,为什么不能直接把中断处理函数放在 intfltdsc_t 结构中呢,还要多此一举搞个 intserdsc_t 结构体呢?
这是因为我们的计算机中可能有很多设备,每个设备都可能产生中断,但是中断控制器的中断信号线是有限的。你可以这样理解:中断控制器最多只能产生几十号中断号,而设备不止几十个,所以会有多个设备共享一根中断信号线。
这就导致一个中断发生后,无法确定是哪个设备产生的中断,所以我们干脆让设备驱动程序来决定,因为它是最了解设备的。
这里我们让这个 intfltdsc_t 结构上的所有中断处理函数都依次执行,查看是不是自己的设备产生了中断,如果是就处理,不是则略过。
好,明白了这两个结构之后,我们就要开始初始化了。首先是在 halglobal.c 文件定义 intfltdsc_t 结构。
//定义intfltdsc_t结构数组大小为256
HAL_DEFGLOB_VARIABLE(intfltdsc_t,machintflt)[IDTMAX];
下面我们再来实现中断、异常分发器函数,如下所示。
//中断处理函数
void hal_do_hwint(uint_t intnumb, void *krnlsframp)
{
intfltdsc_t *ifdscp = NULL;
cpuflg_t cpuflg;
//根据中断号获取中断异常描述符地址
ifdscp = hal_retn_intfltdsc(intnumb);
//对断异常描述符加锁并中断
hal_spinlock_saveflg_cli(&ifdscp->i_lock, &cpuflg);
ifdscp->i_indx++;
ifdscp->i_deep++;
//运行中断处理的回调函数
hal_run_intflthandle(intnumb, krnlsframp);
ifdscp->i_deep--;
//解锁并恢复中断状态
hal_spinunlock_restflg_sti(&ifdscp->i_lock, &cpuflg);
return;
}
//异常分发器
void hal_fault_allocator(uint_t faultnumb, void *krnlsframp)
{
//我们的异常处理回调函数也是放在中断异常描述符中的
hal_do_hwint(faultnumb, krnlsframp);
return;
}
//中断分发器
void hal_hwint_allocator(uint_t intnumb, void *krnlsframp)
{
hal_do_hwint(intnumb, krnlsframp);
return;
}
前面的代码确实是按照我们的中断框架设计实现的,下面我们去实现 hal_run_intflthandle 函数,它负责调用中断处理的回调函数。
void hal_run_intflthandle(uint_t ifdnr, void *sframe)
{
intserdsc_t *isdscp;
list_h_t *lst;
//根据中断号获取中断异常描述符地址
intfltdsc_t *ifdscp = hal_retn_intfltdsc(ifdnr);
//遍历i_serlist链表
list_for_each(lst, &ifdscp->i_serlist)
{
//获取i_serlist链表上对象即intserdsc_t结构
isdscp = list_entry(lst, intserdsc_t, s_list);
//调用中断处理回调函数
isdscp->s_handle(ifdnr, isdscp->s_device, sframe);
}
return;
}
上述代码已经很清楚了,循环遍历 intfltdsc_t 结构中,i_serlist 链表上所有挂载的 intserdsc_t 结构,然后调用 intserdsc_t 结构中的中断处理的回调函数。
我们 Cosmos 链表借用了 Linux 所用的链表,代码我已经帮你写好了,放在了 list.h 和 list_t.h 文件中,请自行查看。

初始化中断控制器

我们把 CPU 端的中断搞定了以后,还有设备端的中断,这个可以交给设备驱动程序,但是 CPU 和设备之间的中断控制器,还需要我们出面解决。
多个设备的中断信号线都会连接到中断控制器上,中断控制器可以决定启用或者屏蔽哪些设备的中断,还可以决定设备中断之间的优先线,所以它才叫中断控制器。
x86 平台上的中断控制器有多种,最开始是 8259A,然后是 IOAPIC,最新的是 MSI-X。为了简单的说明原理,我们选择了 8259A 中断控制器。
8259A 在任何 x86 平台上都可以使用,x86 平台使用了两片 8259A 芯片,以级联的方式存在。它拥有 15 个中断源(即可以有 15 个中断信号接入)。让我们看看 8259A 在系统上的框架图:
8259A在系统上的框架图
上面直接和 CPU 连接的是主 8259A,下面的是从 8259A,每一个 8259A 芯片都有两个 I/O 端口,我们可以通过它们对 8259A 进行编程。主 8259A 的端口地址是 0x20,0x21;从 8259A 的端口地址是 0xA0,0xA1。
下面我们来做代码初始化,我们程序员可以向 8259A 写两种命令字: ICW 和 OCW;ICW 这种命令字用来实现 8259a 芯片的初始化。而 OCW 这种命令用来向 8259A 发布命令,以对其进行控制。OCW 可以在 8259A 被初始化之后的任何时候被使用。
我已经把代码定好了,放在了 8259.c 文件中,如下所示:
void init_i8259()
{
//初始化主从8259a
out_u8_p(ZIOPT, ICW1);
out_u8_p(SIOPT, ICW1);
out_u8_p(ZIOPT1, ZICW2);
out_u8_p(SIOPT1, SICW2);
out_u8_p(ZIOPT1, ZICW3);
out_u8_p(SIOPT1, SICW3);
out_u8_p(ZIOPT1, ICW4);
out_u8_p(SIOPT1, ICW4);
//屏蔽全部中断源
out_u8_p(ZIOPT1, 0xff);
out_u8_p(SIOPT1, 0xff);
return;
}
如果你要了解 8259A 的细节,就是上述代码中为什么要写入这些数据,你可以自己在 Intel 官方网站上搜索 8259A 的数据手册,自行查看。
这里你只要在 init_halintupt() 函数的最后,调用这个函数就行。你有没有想过,既然我们是研究操作系统不是要写硬件驱动,为什么要在初始化中断控制器后,屏蔽所有的中断源呢?因为我们 Cosmos 在初始化阶段还不能处理中断。
到此,我们的 Cosmos 的 hal 层初始化就结束了。关于内存管理器的初始化,我会在内存管理模块讲解,你先有个印象就行。

进入内核层

hal 层的初始化已经完成,按照前面的设计,我们的 Cosmos 还有内核层,我们下面就要进入到内核层,建立一个文件,写上一个函数,作为本课程的结尾。
但是这个函数是个空函数,目前什么也不做,它是为 Cosmos 内核层初始化而存在的,但是由于课程只进行到这里,所以我只是写个空函数,为后面的课程做好准备。
由于内核层是从 hal 层进入的,必须在 hal_start() 函数中被调用,所以在此完成这个函数——init_krl()。
void init_krl()
{
//禁止函数返回
die(0);
return;
}
下面我们在 hal_start() 函数中调用它就行了,如下所示
void hal_start()
{
//初始化Cosmos的hal层
init_hal();
//初始化Cosmos的内核层
init_krl();
return;
}
从上面的代码中,不难发现 Cosmos 的 hal 层初始化完成后,就自动进入了 Cosmos 内核层的初始化。至此本课程已经结束。

重点回顾

写一个 C 函数是容易的,但是写操作系统的第一个 C 函数并不容易,好在我们一路坚持,没有放弃,才取得了这个阶段性的胜利。但温故而知新,对学过的东西要学而时习之,下面我们来回顾一下本课程的重点。
1.Cosmos 的第一个 C 函数产生了,它十分简单但极其有意义,它的出现标志着 C 语言的运行环境已经完善。从此我们可以用 C 语言高效地开发操作系统了,由爬行时代进入了跑步前行的状态,可喜可贺。
2. 第一个 C 函数,干的第一件重要工作就是调用 hal 层的初始化函数。这个初始化函数首先初始化了平台,初始化了机器信息结构供内核的其它代码使用,还初始化了我们图形显示驱动、显示了背景图片;其次是初始化了内存管理相关的数据结构;接着初始了中断,中断处理框架是两层,所以最为复杂;最后初始化了中断控制器。
3. 当 hal 层初始化完成了,我们就进入了内核层,由于到了课程的尾声,我们先暂停在这里。
在这节课里我帮你写了很多代码,那些代码非常简单和枯燥,但是必须要有它们才可以。综合我们前面讲过的知识,我相信你有能力看懂它们。

思考题

请你梳理一下,Cosmos hal 层的函数调用关系。
欢迎你在留言区跟我交流互动,也欢迎把这节课转发给你的朋友和同事。
好,我是 LMOS,咱们下节课见!
分享给需要的人,Ta购买本课程,你将得20
生成海报并分享

赞 20

提建议

上一篇
12 | 设置工作模式与环境(下):探查和收集信息
下一篇
14 | Linux初始化(上):GRUB与vmlinuz的结构
unpreview
 写留言

精选留言(28)

  • 老王
    2021-06-07
    有了前面基础实验很快就做通了,只是实验步骤课程没有说 1.下载最新源码 git clone https://gitee.com/lmos/cosmos.git 2 进入课程的目录 cd cosmos/lesson13/Cosmos 3 编译 make all 这个过程中可能会报告错误 ../hal/x86/kernel.asm:6: fatal: unable to open include file `kernel.inc' krnlbuidrule.mk:14: recipe for target 'kernel.o' failed make[2]: *** [kernel.o] Error 1 Makefile.x86:28: recipe for target 'all' failed make[1]: *** [all] Error 2 Makefile:59: recipe for target 'all' failed make: *** [all] Error 2 警告不管 解决错误即可 使用 find -name "kernel.inc" 搜索头文件的位置 ./include/halinc/kernel.inc 把这个头文件拷贝到和kernel.asm相同的目录。或者是更改../hal/x86/kernel.asm 第6行 改为%include "../include/halinc/kernel.inc" 再次make 可以正常编译 4.生成内核镜像文件 make cplmildr (这一步会拷贝 initldrimh.bin initldrkrl.bin initldrsve.bin 到源码顶层目录的release下 ) make cprelease (这一步会拷贝 Cosmos.bin 到源码顶层目录的release下 ) make KIMG (这一步会调用lmoskrlimg 把initldrimh.bin initldrkrl.bin initldrsve.bin Cosmos.bin logo.bmp background.bmp font.fnt按一定的格式打包成Cosmos.eki镜像文件 ) 5.拷贝Cosmos.eki镜像文件到虚拟磁盘 源码目录已经创建了磁盘文件hd.img(如果没有这个文件可以按照前面的课程自己创建) sudo mount -o loop ./hd.img ./hdisk/ (挂载虚拟磁盘到hidsk目录,hd.img hidsk目录已经存在) sudo cp release/Cosmos.eki hdisk/boot (拷贝编译好的镜像Cosmos.eki 到虚拟磁盘中) sudo umount hdisk (卸载挂载目录/或者是目录和磁盘中的内容) VBoxManage convertfromraw ./hd.img --format VDI ./hd.vdi (把hd.img转为hd.vdi格式,因为课程使用的虚拟机是VirtualBox) 6.参考前面课程使用hd.vdi启动系统 总结: 要想搞清楚整个程序的流程,除了分析代码本身,还需要深入分析Makefile和各个链接脚本
    展开

    编辑回复: 感谢老铁的分享,特别赞。

    共 8 条评论
    37
  • neohope
    2021-06-08
    稍微整理了一下: 一、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、异常处理类似,只是触发源头不太一样而已
    展开

    作者回复: 老哥 6666

    共 2 条评论
    16
  • pedro
    2021-06-07
    [ hal_start ] --> [ init_hal ] --> [ init_krl ] [ init_hal ] --> [ init_halplaltform ] --> [ move_img2maxpadr ] --> [ init_halmm ] --> [ init_halintupt ] [ init_krl ] --> [ die ] [ init_halplaltform ] --> [ init_machbstart ] --> [ init_bdvideo ] [ init_halmm ] --> [ init_phymmarge ] [ init_halintupt ] --> [ init_descriptor ] --> [ init_idt_descriptor ] --> [ init_intfltdsc ] --> [ init_i8259 ] --> [ i8259_enabled_line ] 如果有 graph-easy 的同学,直接 CV,然后: ```sh graph-easy calltree.txt ```
    展开

    作者回复: 铁汁牛皮

    共 3 条评论
    13
  • 卢承灏
    2021-09-07
    有一个问题,回过头来二刷的时候没想明白,如果中断传递的只是一个中断号,然后中断号是进行共用的,那在hal_run_intflthandle 中的list_for_each 中,每个设备注册的handler方法,怎么判断自己需不需要执行呢? handler传入的s->device 也是从循环中的每一个intserdsc_t取出,和最开始的中断号看不出中什么关联。还希望大神们解答

    作者回复: 中断共享 需要设备驱动程序自己处理

    共 2 条评论
    4
  • 云师兄
    2021-06-09
    太硬了啊,有点磕牙,不过再咬两口试试😬

    编辑回复: 哈哈哈,舌尖上的操作系统,老铁加油!

    4
  • LunaElf
    2021-10-19
    Cosmos hal 层函数调用关系: 1. `hal_start()` 1. `init_hal()` 1. `init_halplatform()` 1. `init_machbstart()` 1. `machbstart_t_init()` 2. `init_bdvideo()` 2. `init_halmm()` 1. `init_phymmarge()` 1. `initpmrge_core()` 3. `init_halintupt()` 1. `init_idt_descriptor()` 1. `set_idt_desc()` 2. `init_intfltdsc()` 3. `init_i8259()` 2. `init_krl()` 1. `hal_fault_allocator()` 1. `hal_do_hwint()` 1. `hal_run_intflthandle()` 1. `hal_hwint_allocator()` 1. `hal_do_hwint()` 1. `hal_run_intflthandle()`
    展开

    作者回复: 66666

    3
  • 吴建平
    2021-07-01
    代码走查出一个安全问题,下面这个函数里,如果for里一个满足条件的都没找到,那么后面校验的时候 retemp->saddr 就空指针了。 e820map_t *ret_kmaxmpadrcmpsz_e820map(machbstart_t *mbsp, u64_t mappadr, u64_t cpsz) { if (NULL == mbsp) { return NULL; } u64_t enr = mbsp->mb_e820nr; e820map_t *emp = (e820map_t *)phyadr_to_viradr((adr_t)mbsp->mb_e820padr); e820map_t *retemp = NULL; u64_t maxadr = emp[0].saddr; for (u64_t i = 0; i < enr; i++) { if (emp[i].type == RAM_USABLE) { if (emp[i].saddr >= maxadr && //内存区首地址大于已知最大区域起始地址(初始化位第一个区首地址 (mappadr > (emp[i].saddr + emp[i].lsize)) && //内存区尾地址小于内存映射最大地址 (emp[i].lsize >= cpsz)) //内存区大小大于镜像文件大小 { maxadr = emp[i].saddr; //已知最大区域起始地址 retemp = &emp[i]; //更新最后满足条件内存区域 } } } if ((mappadr > (retemp->saddr + retemp->lsize)) && (retemp->lsize >= cpsz)) //校验,但除非一个都不满足条件 { return retemp; } return NULL; }
    展开

    作者回复: 对的 厉害了 找出了BUG

    2
  • 2021-06-07
    很好奇move_img2maxpadr(&kmachbsp);这个函数 功能:move_img2maxpadr(&kmachbsp);这个函数是把镜像文件搬到最大的物理地址处。 作用:我感觉是因为镜像文件是加载在0x4000000处,而空闲地址是从内核文件加载处开始计算的(0x2000000+内核大小),随着内存的分配,空闲地址不断向上增长,迟早会覆盖镜像文件,所以提前把镜像文件搬走了。

    作者回复: 对,你理解的很正确

    2
  • Victor
    2021-06-07
    在ubuntu 18.04环境下make时报错: root@ubuntu1804:~/LMOS/cosmos/lesson13/Cosmos# make all Initldr:清理全部已构建文件... ^_^ *********正在开始编译构建系统************* AS -[M] 正在构建... ../ldrkrl/imginithead.asm CC -[M] 正在构建... ../ldrkrl/inithead.c CC -[M] 正在构建... ../ldrkrl/vgastr.c AS -[M] 正在构建... ../ldrkrl/ldrkrl32.asm CC -[M] 正在构建... ../ldrkrl/ldrkrlentry.c CC -[M] 正在构建... ../ldrkrl/fs.c CC -[M] 正在构建... ../ldrkrl/chkcpmm.c CC -[M] 正在构建... ../ldrkrl/graph.c CC -[M] 正在构建... ../ldrkrl/bstartparm.c AS -[M] 正在构建... ../ldrkrl/realintsve.asm OBJCOPY -[M] 正在构建... initldrimh.bin OBJCOPY -[M] 正在构建... initldrkrl.bin OBJCOPY -[M] 正在构建... initldrsve.bin 恭喜我,Initldr编译构建完成! ^_^ ../hal/x86/kernel.asm:6: fatal: unable to open include file `kernel.inc' krnlbuidrule.mk:14: recipe for target 'kernel.o' failed make[2]: *** [kernel.o] Error 1 Makefile.x86:28: recipe for target 'all' failed make[1]: *** [all] Error 2 Makefile:59: recipe for target 'all' failed make: *** [all] Error 2
    展开

    作者回复: 是不是下载 的完整代码

    共 3 条评论
    2
  • 卖薪沽酒
    2022-06-16
    不简单, 来来回回来到了13课程, 关于课后问题, 习惯用ximd 梳理, 有兴趣的同学可以看看,一起加油https://www.cnblogs.com/iwssea/p/16383412.html

    作者回复: 很强

    1
  • 艾恩凝
    2022-04-08
    终于快走到内核了,上个月24号到现在,进度有点慢,先放下脚步,有些小地方理解不透,看到评论越来越少,我就知道,能坚持下来的人真的不多,可能自己菜,我一个科班出身的感觉都有点吃力,底层那些东西还是很生疏,这门课真的无敌,干货真的很多,需要自己慢慢体会,理解+实战 yyds

    作者回复: 6666

    共 2 条评论
    1
  • 申龙青
    2021-12-31
    hal 层可以隔离不同芯片平台架构,那不同芯片平台架构下BIOS有区别,导致的GRUB2这套二级引导器是不是也不同?

    作者回复: 是的 也不同的

  • 逝水
    2021-12-17
    init_idt_descriptor 最后会调用 load_x64_idt,通过汇编调用 lidt 方法,将 x64_idt 的地址到 IDTR 寄存器,使得中断门描述符发挥作用。 想了半天内存中的 x64_idt 是怎么发挥作用的,原来这里调用了汇编。

    作者回复: 是的 是的

    1
  • 寻道客小林
    2021-06-07
    日拱一卒-Day04 有种本科时学8051,lpc1114的感觉,第一遍还是搞清楚流程,不要在乎细节,先建立一个整体的框架。

    编辑回复: 先抓主干,后看细节,你的学习方法很好。

    1
  • 徐旭
    2022-07-06
    启动报错 INITKLDR DIE ERROR:S ,不知道是不是 和 物理机cpu(Intel(R) Core(TM) i7-10700F CPU @ 2.90GHz 2.90 GHz)有关 (详情见截图: http://t.csdn.cn/tN2Vz)

    作者回复: 应该不是cpu的问题

  • Qfeng
    2022-05-29
    hal_start() init_hal() // 初始化硬件抽象层 init_halplaltform() //初始化平台相关 init_machbstart() // 将grub启动阶段得到的机器信息结构体machbstart_t拷贝一份到hal全局machbstart_t中,以便后续hal使用 init_bdvideo() //初始化显卡驱动(基于hal全局machbstart_t里面的信息) move_img2maxpadr() init_halmm() //初始化内存管理 init_phymmarge() //根据引导阶段获得的内存信息 -- e820map_t 结构数组,hal扩张建立了自己的内存管理结构 -- phymmarge_t 结构数组,将e820map_t 结构数组的信息拷贝了过来 init_halintupt() //hal中断初始化 init_descriptor() //设置中断GDT描述符 init_idt_descriptor() //设置中断IDT描述符:将中断处理程序的入口地址与对应的中断/异常类型vector绑定 init_intfltdsc() //初始化中断管理结构体:管理中断的类型,分发,处理优先级等 init_i8259() //初始化中断控制器 i8259 i8259_enabled_line() init_krl() //初始化内核: 死循环 这节内容确实很多,不过有一条主线,就是前面引导器阶段收集准备好的机器信息结构体 -- MBSPADR ((machbstart_t*)(0x100000)),这里的hal初始化的信息都是来自于此,我理解这是引导器如此不可或缺的原因之一,他做了必不可少的准备工作。
    展开

    作者回复: 是的,你理解的很正确

  • 卖薪沽酒
    2022-05-09
    我还在

    作者回复: 还在干什么

    共 3 条评论
  • Geek_03885e
    2021-12-29
    其他的都好理解, 就是为什么出去旅游不带女朋友呢?

    编辑回复: 华生,你发现了盲点😄。

  • 178
    2021-12-01
    hal层的初始化和二级引导器的初始化,之间有什么区别和联系,愚蠢的我居然认为是是同样的事做了两遍?

    作者回复: 当然 不是

  • 178
    2021-11-28
    1. 新建Cosmos虚拟机(与第10课,同样类型的虚拟机,只是名不同) 2. 执行make vboxtest 问题:只出现黑底百花的Cosmos,为什么没有背景图片和打印机器信息呢,百撕不得骑姐

    作者回复: 可能是虚拟机设置有问题