13 | 第一个C函数:如何实现板级初始化?
下载APP
关闭
渠道合作
推荐作者
13 | 第一个C函数:如何实现板级初始化?
2021-06-07 LMOS 来自北京
《操作系统实战45讲》
课程介绍
讲述:陈晨
时长21:15大小19.41M
你好,我是 LMOS。
前面三节课,我们为调用 Cosmos 的第一个 C 函数 hal_start 做了大量工作。这节课我们要让操作系统 Cosmos 里的第一个 C 函数真正跑起来啦,也就是说,我们会真正进入到我们的内核中。
今天我们会继续在这个 hal_start 函数里,首先执行板级初始化,其实就是 hal 层(硬件抽象层,下同)初始化,其中执行了平台初始化,hal 层的内存初始化,中断初始化,最后进入到内核层的初始化。
第一个 C 函数
任何软件工程,第一个函数总是简单的,因为它是总调用者,像是一个管理者,坐在那里发号施令,自己却是啥活也不干。
由于这是第一个 C 函数,也是初始化函数,我们还是要为它单独建立一个文件,以显示对它的尊重,依然在 Cosmos/hal/x86/ 下建立一个 hal_start.c 文件。写上这样一个函数。
根据前面的设计,Cosmos 是有 hal 层和内核层之分,所以在上述代码中,要分两步走。第一步是初始化 hal 层;第二步,初始化内核层。只是这两步的函数我们还没有写。
然而最后的死循环却有点奇怪,其实它的目的很简单,就是避免这个函数返回,因为这个返回了就无处可去,避免走回头路。
hal 层初始化
为了分离硬件的特性,我们设计了 hal 层,把硬件相关的操作集中在这个层,并向上提供接口,目的是让内核上层不用关注硬件相关的细节,也能方便以后移植和扩展。(关于 hal 层的设计,可以回顾第 3 节课)
也许今天我们是在 x86 平台上写 Cosmos,明天就要在 ARM 平台上开发 Cosmos,那时我们就可以写个 ARM 平台的 hal 层,来替换 Cosmos 中的 x86 平台的 hal 层。
下面我们在 Cosmos/hal/x86/ 下建立一个 halinit.c 文件,写出 hal 层的初始化函数。
这个函数也是一个调用者,没怎么干活。不过根据代码的注释能看出,它调用的函数多一点,但主要是完成初始化平台、初始化内存、初始化中断的功能函数。
初始化平台
我们先来写好平台初始化函数,因为它需要最先被调用。
这个函数主要负责完成两个任务,一是把二级引导器建立的机器信息结构复制到 hal 层中的一个全局变量中,方便内核中的其它代码使用里面的信息,之后二级引导器建立的数据所占用的内存都会被释放。二是要初始化图形显示驱动,内核在运行过程要在屏幕上输出信息。
下面我们在 Cosmos/hal/x86/ 下建立一个 halplatform.c 文件,写上如下代码。
这个代码中别的地方很好理解,就是 kmachbsp 你可能会有点奇怪,它是个结构体变量,结构体类型是 machbstart_t,这个结构和二级引导器所使用的一模一样。
同时,它还是一个 hal 层的全局变量,我们想专门有个文件定义所有 hal 层的全局变量,于是我们在 Cosmos/hal/x86/ 下建立一个 halglobal.c 文件,写上如下代码。
前面的 EXTERN,在 halglobal.c 文件中定义为空,而在其它文件中定义为 extern,告诉编译器这是外部文件的变量,避免发生错误。
下面,我们在 Cosmos/hal/x86/ 下的 bdvideo.c 文件中,写好 init_bdvideo 函数。
init_dftgraph() 函数初始了 dftgraph_t 结构体类型的变量 kdftgh,我们在 halglobal.c 文件中定义这个变量,结构类型我们这样来定义。
不难发现,我们正是把这些实际的图形驱动函数的地址填入了这个结构体中,然后通过这个结构体,我们就可以调用到相应的函数了。
因为写这些函数都是体力活,我已经帮你搞定了,你直接使用就可以。上面的 flush_videoram 函数已经证明了这一想法。
来,我们测试一下,看看结果,我们图形驱动程序初始化会显示背景图片——background.bmp,这是在打包映像文件时包含进去的,你自己可以随时替换,只要是满足 1024*768,24 位的位图文件就行了。
下面我们要把这些函数调用起来:
接下来,让我们一起 make vboxtest,应该很有成就感。一幅风景图呈现在我们面前,上面有 Cosmos 的版本、编译时间、CPU 工作模式,内存大小等数据。这相当一个我们 Cosmos 的水印信息。
图形驱动测试
初始化内存
首先,我们在 Cosmos/hal/x86/ 下建立一个 halmm.c 文件,用于初始化内存,为了后面的内存管理器作好准备。
hal 层的内存初始化比较容易,只要向内存管理器提供内存空间布局信息就可以。
你可能在想,不对啊,明明我们在二级引导器中已经获取了内存布局信息,是的,但 Cosmos 的内存管理器需要保存更多的信息,最好是顺序的内存布局信息,这样可以增加额外的功能属性,同时降低代码的复杂度。
不难发现,BIOS 提供的结构无法满足前面这些要求。不过我们也有办法解决,只要以 BIOS 提供的结构为基础,设计一套新的数据结构就搞定了。这个结构可以这样设计。
有些情况下内核要另起炉灶,不想把所有的内存空间都交给内存管理器去管理,所以要保留一部分内存空间,这就是上面结构中那两个 pmr_rrvmsaddr、pmr_rrvmend 字段的作用。
有了数据结构,我们还要写代码来操作它:
结合上面的代码,你会发现这是根据 e820map_t 结构数组,建立了一个 phymmarge_t 结构数组,init_one_pmrge 函数正是把 e820map_t 结构中的信息复制到 phymmarge_t 结构中来。理解了这个原理,即使不看我的,你自己也会写。
下面我们把这些函数,用一个总管函数调动起来,这个总管函数叫什么名字好呢?当然是 init_halmm,如下所示。
这里 init_halmm 函数中还调用了 init_memmgr 函数,这个正是这我们内存管理器初始化函数,我会在内存管理的那节课展开讲。而 init_halmm 函数将要被 init_hal 函数调用。
初始化中断
什么是中断呢?为了帮你快速理解,我们先来看两种情景:
你在开车时,突然汽车引擎坏了,你需要修复它才能继续驾驶汽车……
你在外旅游,你女朋友突然来电话了,你可以选择接电话或者不接电话,当然不接电话的后果很严重(笑)……
在以上两种情景中,虽然不十分恰当,但都是在做一件事时,因为一些原因而要切换到另一件事上。其实计算机中的 CPU 也是一样,在做一件事时,因为一些原因要转而做另一件事,于是中断产生了……
根据原因的类型不同,中断被分为两类。
异常,这是同步的,原因是错误和故障,就像汽车引擎坏了。不修复错误就不能继续运行,所以这时,CPU 会跳到这种错误的处理代码那里开始运行,运行完了会返回。
为啥说它是同步的呢?这是因为如果不修改程序中的错误,下次运行程序到这里同样会发生异常。
中断,这是异步的,我们通常说的中断就是这种类型,它是因为外部事件而产生的,就好像旅游时女朋友来电话了。通常设备需要 CPU 关注时,会给 CPU 发送一个中断信号,所以这时 CPU 会跳到处理这种事件的代码那里开始运行,运行完了会返回。
由于不确定何种设备何时发出这种中断信号,所以它是异步的。
在 x86 CPU 上,最多支持 256 个中断,还记得前面所说的中断表和中断门描述符吗,这意味着我们要准备 256 个中断门描述符和 256 个中断处理程序的入口。
下面我们来定义它,如下所示:
说到这里你会发现,中断表其实是个 gate_t 结构的数组,由 CPU 的 IDTR 寄存器指向,IDTMAX 为 256。
但是光有数组还不行,还要设置其中的数据,下面我们就来设计这个函数,建立一个文件 halsgdidt.c,在其中写一个函数,代码如下。
上面的代码,正是按照要求,把这些数据填入中断门描述符中的。有了中断门之后,还差中断入口处理程序,中断入口处理程序只负责这三件事:
1. 保护 CPU 寄存器,即中断发生时的程序运行的上下文。
2. 调用中断处理程序,这个程序可以是修复异常的,可以是设备驱动程序中对设备响应的程序。
3. 恢复 CPU 寄存器,即恢复中断时程序运行的上下文,使程序继续运行。
以上这些操作又要用汇编代码才可以编写,我觉得这是内核中最重要的部分,所以我们建立一个文件,并用 kernel.asm 命名。
我们先来写好完成以上三个功能的汇编宏代码,避免写 256 遍同样的代码,代码如下所示。
别看前面的代码这么长,其实最重要的只有两个指令:push、pop,这两个正是用来压入寄存器和弹出寄存器的,正好可以用来保存和恢复 CPU 所有的通用寄存器。
有的 CPU 异常,CPU 自动把异常码压入到栈中,而有的 CPU 异常没有异常码,为了统一,我们对没有异常码的手动压入一个常数,维持栈的平衡。
有了中断异常处理的宏,我们还要它们变成中断异常的处理程序入口点函数。汇编函数其实就是一个标号加一段汇编代码,C 编译器把 C 语言函数编译成汇编代码后,也是标号加汇编代码,函数名就是标号。
下面我们在 kernel.asm 中写好它们:
为了突出重点,这里没有全部展示代码 ,你只用搞清原理就行了。那有了中断处理程序的入口地址,下面我们就可以在 halsgdidt.c 文件写出函数设置中断门描述符了,代码如下。
上面的代码已经很明显了,一开始把所有中断的处理程序设置为保留的通用处理程序,避免未知中断异常发生了 CPU 无处可去,然后对已知的中断和异常进一步设置,这会覆盖之前的通用处理程序,这样就可以确保万无一失。
下面我们把这些代码整理一下,安装到具体的调用路径上,让上层调用者调用到就好了。
我们依然在 halintupt.c 文件中写上 init_halintupt() 函数:
到此为止,CPU 体系层面的中断就初始化完成了。你会发现,我们在 init_halintupt() 函数中还调用了 init_intfltdsc() 函数,这个函数是干什么的呢?请往下看。
我们先来设计一下 Cosmos 的中断处理框架,后面我们把中断和异常统称为中断,因为它们的处理方式相同。
前面我们只是解决了中断的 CPU 相关部分,而 CPU 只是响应中断,但是并不能解决产生中断的问题。
比如缺页中断来了,我们要解决内存地址映射关系,程序才可以继续运行。再比如硬盘中断来了,我们要读取硬盘的数据,要处理这问题,就要写好相应的处理函数。
因为有些处理是内核所提供的,而有些处理函数是设备驱动提供的,想让它们和中断关联起来,就要好好设计中断处理框架了。
下面我们来画幅图,描述中断框架的设计:
中断框架设计图
可以看到,中断、异常分发器的左侧的东西我们已经处理完成,下面需要写好中断、异常分发器和中断异常描述符。
我们先来搞定中断异常描述,结合框架图,中断异常描述也是个表,它在 C 语言中就是个结构数组,让我们一起来写好这个数组:
上面结构中,记录了中断的优先级。因为有些中断可以稍后执行,而有的中断需要紧急执行,所以要设计一个优先级。其中还有中断号,中断计数等统计信息。
中断可以由线程的方式执行,也可以是一个回调函数,该函数的地址放另一个结构体中,这个结构体我已经帮你写好了,如下所示。
如果内核或者设备驱动程序要安装一个中断处理函数,就要先申请一个 intserdsc_t 结构体,然后把中断函数的地址写入其中,最后把这个结构挂载到对应的 intfltdsc_t 结构中的 i_serlist 链表中。
你可能要问了,为什么不能直接把中断处理函数放在 intfltdsc_t 结构中呢,还要多此一举搞个 intserdsc_t 结构体呢?
这是因为我们的计算机中可能有很多设备,每个设备都可能产生中断,但是中断控制器的中断信号线是有限的。你可以这样理解:中断控制器最多只能产生几十号中断号,而设备不止几十个,所以会有多个设备共享一根中断信号线。
这就导致一个中断发生后,无法确定是哪个设备产生的中断,所以我们干脆让设备驱动程序来决定,因为它是最了解设备的。
这里我们让这个 intfltdsc_t 结构上的所有中断处理函数都依次执行,查看是不是自己的设备产生了中断,如果是就处理,不是则略过。
好,明白了这两个结构之后,我们就要开始初始化了。首先是在 halglobal.c 文件定义 intfltdsc_t 结构。
下面我们再来实现中断、异常分发器函数,如下所示。
前面的代码确实是按照我们的中断框架设计实现的,下面我们去实现 hal_run_intflthandle 函数,它负责调用中断处理的回调函数。
上述代码已经很清楚了,循环遍历 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 文件中,如下所示:
如果你要了解 8259A 的细节,就是上述代码中为什么要写入这些数据,你可以自己在 Intel 官方网站上搜索 8259A 的数据手册,自行查看。
这里你只要在 init_halintupt() 函数的最后,调用这个函数就行。你有没有想过,既然我们是研究操作系统不是要写硬件驱动,为什么要在初始化中断控制器后,屏蔽所有的中断源呢?因为我们 Cosmos 在初始化阶段还不能处理中断。
到此,我们的 Cosmos 的 hal 层初始化就结束了。关于内存管理器的初始化,我会在内存管理模块讲解,你先有个印象就行。
进入内核层
hal 层的初始化已经完成,按照前面的设计,我们的 Cosmos 还有内核层,我们下面就要进入到内核层,建立一个文件,写上一个函数,作为本课程的结尾。
但是这个函数是个空函数,目前什么也不做,它是为 Cosmos 内核层初始化而存在的,但是由于课程只进行到这里,所以我只是写个空函数,为后面的课程做好准备。
由于内核层是从 hal 层进入的,必须在 hal_start() 函数中被调用,所以在此完成这个函数——init_krl()。
下面我们在 hal_start() 函数中调用它就行了,如下所示
从上面的代码中,不难发现 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的结构
精选留言(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 - neohope2021-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 - pedro2021-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 - LunaElf2021-10-19Cosmos 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 - Victor2021-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-31hal 层可以隔离不同芯片平台架构,那不同芯片平台架构下BIOS有区别,导致的GRUB2这套二级引导器是不是也不同?
作者回复: 是的 也不同的
- 逝水2021-12-17init_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的问题
- Qfeng2022-05-29hal_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_03885e2021-12-29其他的都好理解, 就是为什么出去旅游不带女朋友呢?
编辑回复: 华生,你发现了盲点😄。
- 1782021-12-01hal层的初始化和二级引导器的初始化,之间有什么区别和联系,愚蠢的我居然认为是是同样的事做了两遍?
作者回复: 当然 不是
- 1782021-11-281. 新建Cosmos虚拟机(与第10课,同样类型的虚拟机,只是名不同) 2. 执行make vboxtest 问题:只出现黑底百花的Cosmos,为什么没有背景图片和打印机器信息呢,百撕不得骑姐
作者回复: 可能是虚拟机设置有问题