你好,我是LMOS。
前面三节课,我们为调用Cosmos的**第一个C函数hal_start做了大量工作。**这节课我们要让操作系统Cosmos里的第一个C函数真正跑起来啦,也就是说,我们会真正进入到我们的内核中。
今天我们会继续在这个hal_start函数里,首先执行板级初始化,其实就是hal层(硬件抽象层,下同)初始化,其中执行了平台初始化,hal层的内存初始化,中断初始化,最后进入到内核层的初始化。
这节课的配套代码,你可以从这里下载。
第一个C函数
任何软件工程,第一个函数总是简单的,因为它是总调用者,像是一个管理者,坐在那里发号施令,自己却是啥活也不干。
由于这是第一个C函数,也是初始化函数,我们还是要为它单独建立一个文件,以显示对它的尊重,依然在Cosmos/hal/x86/下建立一个hal_start.c文件。写上这样一个函数。
1 | void hal_start() |
根据前面的设计,Cosmos是有hal层和内核层之分,所以在上述代码中,要分两步走。第一步是初始化hal层;第二步,初始化内核层。只是这两步的函数我们还没有写。
然而最后的死循环却有点奇怪,其实它的目的很简单,就是避免这个函数返回,因为这个返回了就无处可去,避免走回头路。
hal层初始化
为了分离硬件的特性,我们设计了hal层,把硬件相关的操作集中在这个层,并向上提供接口,目的是让内核上层不用关注硬件相关的细节,也能方便以后移植和扩展。(关于hal层的设计,可以回顾[第3节课])
也许今天我们是在x86平台上写Cosmos,明天就要在ARM平台上开发Cosmos,那时我们就可以写个ARM平台的hal层,来替换Cosmos中的x86平台的hal层。
下面我们在Cosmos/hal/x86/下建立一个halinit.c文件,写出hal层的初始化函数。
1 | void init_hal() |
这个函数也是一个调用者,没怎么干活。不过根据代码的注释能看出,它调用的函数多一点,但主要是完成初始化平台、初始化内存、初始化中断的功能函数。
初始化平台
我们先来写好平台初始化函数,因为它需要最先被调用。
这个函数主要负责完成两个任务,一是把二级引导器建立的机器信息结构复制到hal层中的一个全局变量中,方便内核中的其它代码使用里面的信息,之后二级引导器建立的数据所占用的内存都会被释放。二是要初始化图形显示驱动,内核在运行过程要在屏幕上输出信息。
下面我们在Cosmos/hal/x86/下建立一个halplatform.c文件,写上如下代码。
1 | void machbstart_t_init(machbstart_t *initp) |
这个代码中别的地方很好理解,就是kmachbsp你可能会有点奇怪,它是个结构体变量,结构体类型是machbstart_t,这个结构和二级引导器所使用的一模一样。
同时,它还是一个hal层的全局变量,我们想专门有个文件定义所有hal层的全局变量,于是我们在Cosmos/hal/x86/下建立一个halglobal.c文件,写上如下代码。
1 | //全局变量定义变量放在data段 |
前面的EXTERN,在halglobal.c文件中定义为空,而在其它文件中定义为extern,告诉编译器这是外部文件的变量,避免发生错误。
下面,我们在Cosmos/hal/x86/下的bdvideo.c文件中,写好init_bdvideo函数。
1 | void init_bdvideo() |
init_dftgraph()函数初始了dftgraph_t结构体类型的变量kdftgh,我们在halglobal.c文件中定义这个变量,结构类型我们这样来定义。
1 | typedef struct s_DFTGRAPH |
不难发现,我们正是把这些实际的图形驱动函数的地址填入了这个结构体中,然后通过这个结构体,我们就可以调用到相应的函数了。
因为写这些函数都是体力活,我已经帮你搞定了,你直接使用就可以。上面的flush_videoram函数已经证明了这一想法。
来,我们测试一下,看看结果,我们图形驱动程序初始化会显示背景图片——background.bmp,这是在打包映像文件时包含进去的,你自己可以随时替换,只要是满足1024*768,24位的位图文件就行了。
下面我们要把这些函数调用起来:
1 | //在halinit.c文件中 |
接下来,让我们一起make vboxtest,应该很有成就感。一幅风景图呈现在我们面前,上面有Cosmos的版本、编译时间、CPU工作模式,内存大小等数据。这相当一个我们Cosmos的水印信息。

初始化内存
首先,我们在Cosmos/hal/x86/下建立一个halmm.c文件,用于初始化内存,为了后面的内存管理器作好准备。
hal层的内存初始化比较容易,只要向内存管理器提供内存空间布局信息就可以。
你可能在想,不对啊,明明我们在二级引导器中已经获取了内存布局信息,是的,但Cosmos的内存管理器需要保存更多的信息,最好是顺序的内存布局信息,这样可以增加额外的功能属性,同时降低代码的复杂度。
不难发现,BIOS提供的结构无法满足前面这些要求。不过我们也有办法解决,只要以BIOS提供的结构为基础,设计一套新的数据结构就搞定了。这个结构可以这样设计。
1 | #define PMR_T_OSAPUSERRAM 1 |
有些情况下内核要另起炉灶,不想把所有的内存空间都交给内存管理器去管理,所以要保留一部分内存空间,这就是上面结构中那两个pmr_rrvmsaddr、pmr_rrvmend字段的作用。
有了数据结构,我们还要写代码来操作它:
1 | u64_t initpmrge_core(e820map_t *e8sp, u64_t e8nr, phymmarge_t *pmargesp) |
结合上面的代码,你会发现这是根据e820map_t结构数组,建立了一个phymmarge_t结构数组,init_one_pmrge函数正是把e820map_t结构中的信息复制到phymmarge_t结构中来。理解了这个原理,即使不看我的,你自己也会写。
下面我们把这些函数,用一个总管函数调动起来,这个总管函数叫什么名字好呢?当然是init_halmm,如下所示。
1 | void init_halmm() |
这里init_halmm函数中还调用了init_memmgr函数,这个正是这我们内存管理器初始化函数,我会在内存管理的那节课展开讲。而init_halmm函数将要被init_hal函数调用。
初始化中断
什么是中断呢?为了帮你快速理解,我们先来看两种情景:
- 你在开车时,突然汽车引擎坏了,你需要修复它才能继续驾驶汽车……
- 你在外旅游,你女朋友突然来电话了,你可以选择接电话或者不接电话,当然不接电话的后果很严重(笑)……
在以上两种情景中,虽然不十分恰当,但都是在做一件事时,因为一些原因而要切换到另一件事上。其实计算机中的CPU也是一样,在做一件事时,因为一些原因要转而做另一件事,于是中断产生了……
根据原因的类型不同,中断被分为两类。
异常,这是同步的,原因是错误和故障,就像汽车引擎坏了。不修复错误就不能继续运行,所以这时,CPU会跳到这种错误的处理代码那里开始运行,运行完了会返回。
为啥说它是同步的呢?这是因为如果不修改程序中的错误,下次运行程序到这里同样会发生异常。
中断,这是异步的,我们通常说的中断就是这种类型,它是因为外部事件而产生的,就好像旅游时女朋友来电话了。通常设备需要CPU关注时,会给CPU发送一个中断信号,所以这时CPU会跳到处理这种事件的代码那里开始运行,运行完了会返回。
由于不确定何种设备何时发出这种中断信号,所以它是异步的。
在x86 CPU上,最多支持256个中断,还记得前面所说的中断表和中断门描述符吗,这意味着我们要准备256个中断门描述符和256个中断处理程序的入口。
下面我们来定义它,如下所示:
1 | typedef struct s_GATE |
说到这里你会发现,中断表其实是个gate_t结构的数组,由CPU的IDTR寄存器指向,IDTMAX为256。
但是光有数组还不行,还要设置其中的数据,下面我们就来设计这个函数,建立一个文件halsgdidt.c,在其中写一个函数,代码如下。
1 | //vector 向量也是中断号 |
上面的代码,正是按照要求,把这些数据填入中断门描述符中的。有了中断门之后,还差中断入口处理程序,中断入口处理程序只负责这三件事:
1.保护CPU 寄存器,即中断发生时的程序运行的上下文。
2.调用中断处理程序,这个程序可以是修复异常的,可以是设备驱动程序中对设备响应的程序。
3.恢复CPU寄存器,即恢复中断时程序运行的上下文,使程序继续运行。
以上这些操作又要用汇编代码才可以编写,我觉得这是内核中最重要的部分,所以我们建立一个文件,并用kernel.asm命名。
我们先来写好完成以上三个功能的汇编宏代码,避免写256遍同样的代码,代码如下所示。
1 | //保存中断后的寄存器 |
别看前面的代码这么长,其实最重要的只有两个指令:push、pop,这两个正是用来压入寄存器和弹出寄存器的,正好可以用来保存和恢复CPU所有的通用寄存器。
有的CPU异常,CPU自动把异常码压入到栈中,而有的CPU异常没有异常码,为了统一,我们对没有异常码的手动压入一个常数,维持栈的平衡。
有了中断异常处理的宏,我们还要它们变成中断异常的处理程序入口点函数。汇编函数其实就是一个标号加一段汇编代码,C编译器把C语言函数编译成汇编代码后,也是标号加汇编代码,函数名就是标号。
下面我们在kernel.asm中写好它们:
1 | //除法错误异常 比如除0 |
为了突出重点,这里没有全部展示代码 ,你只用搞清原理就行了。那有了中断处理程序的入口地址,下面我们就可以在halsgdidt.c文件写出函数设置中断门描述符了,代码如下。
1 | void init_idt_descriptor() |
上面的代码已经很明显了,一开始把所有中断的处理程序设置为保留的通用处理程序,避免未知中断异常发生了CPU无处可去,然后对已知的中断和异常进一步设置,这会覆盖之前的通用处理程序,这样就可以确保万无一失。
下面我们把这些代码整理一下,安装到具体的调用路径上,让上层调用者调用到就好了。
我们依然在halintupt.c文件中写上init_halintupt()函数:
1 | void init_halintupt() |
到此为止,CPU体系层面的中断就初始化完成了。你会发现,我们在init_halintupt()函数中还调用了init_intfltdsc()函数,这个函数是干什么的呢?请往下看。
我们先来设计一下Cosmos的中断处理框架,后面我们把中断和异常统称为中断,因为它们的处理方式相同。
前面我们只是解决了中断的CPU相关部分,而CPU只是响应中断,但是并不能解决产生中断的问题。
比如缺页中断来了,我们要解决内存地址映射关系,程序才可以继续运行。再比如硬盘中断来了,我们要读取硬盘的数据,要处理这问题,就要写好相应的处理函数。
因为有些处理是内核所提供的,而有些处理函数是设备驱动提供的,想让它们和中断关联起来,就要好好设计中断处理框架了。
下面我们来画幅图,描述中断框架的设计:

可以看到,中断、异常分发器的左侧的东西我们已经处理完成,下面需要写好中断、异常分发器和中断异常描述符。
我们先来搞定中断异常描述,结合框架图,中断异常描述也是个表,它在C语言中就是个结构数组,让我们一起来写好这个数组:
1 | typedef struct s_INTFLTDSC{ |
上面结构中,记录了中断的优先级。因为有些中断可以稍后执行,而有的中断需要紧急执行,所以要设计一个优先级。其中还有中断号,中断计数等统计信息。
中断可以由线程的方式执行,也可以是一个回调函数,该函数的地址放另一个结构体中,这个结构体我已经帮你写好了,如下所示。
1 | typedef drvstus_t (*intflthandle_t)(uint_t ift_nr,void* device,void* sframe); //中断处理函数的指针类型 |
如果内核或者设备驱动程序要安装一个中断处理函数,就要先申请一个intserdsc_t结构体,然后把中断函数的地址写入其中,最后把这个结构挂载到对应的intfltdsc_t结构中的i_serlist链表中。
你可能要问了,为什么不能直接把中断处理函数放在intfltdsc_t结构中呢,还要多此一举搞个intserdsc_t结构体呢?
这是因为我们的计算机中可能有很多设备,每个设备都可能产生中断,但是中断控制器的中断信号线是有限的。你可以这样理解:中断控制器最多只能产生几十号中断号,而设备不止几十个,所以会有多个设备共享一根中断信号线。
这就导致一个中断发生后,无法确定是哪个设备产生的中断,所以我们干脆让设备驱动程序来决定,因为它是最了解设备的。
这里我们让这个intfltdsc_t结构上的所有中断处理函数都依次执行,查看是不是自己的设备产生了中断,如果是就处理,不是则略过。
好,明白了这两个结构之后,我们就要开始初始化了。首先是在halglobal.c文件定义intfltdsc_t结构。
1 | //定义intfltdsc_t结构数组大小为256 |
下面我们再来实现中断、异常分发器函数,如下所示。
1 | //中断处理函数 |
前面的代码确实是按照我们的中断框架设计实现的,下面我们去实现hal_run_intflthandle函数,它负责调用中断处理的回调函数。
1 | void hal_run_intflthandle(uint_t ifdnr, void *sframe) |
上述代码已经很清楚了,循环遍历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在系统上的框架图:

上面直接和CPU连接的是主8259A,下面的是从8259A,每一个8259A芯片都有两个I/O端口,我们可以通过它们对8259A进行编程。主8259A的端口地址是0x20,0x21;从8259A的端口地址是0xA0,0xA1。
下面我们来做代码初始化,我们程序员可以向8259A写两种命令字: ICW和OCW;ICW这种命令字用来实现8259a芯片的初始化。而OCW这种命令用来向8259A发布命令,以对其进行控制。OCW可以在8259A被初始化之后的任何时候被使用。
我已经把代码定好了,放在了8259.c文件中,如下所示:
1 | void init_i8259() |
如果你要了解8259A的细节,就是上述代码中为什么要写入这些数据,你可以自己在Intel官方网站上搜索8259A的数据手册,自行查看。
这里你只要在init_halintupt()函数的最后,调用这个函数就行。你有没有想过,既然我们是研究操作系统不是要写硬件驱动,为什么要在初始化中断控制器后,屏蔽所有的中断源呢?因为我们Cosmos在初始化阶段还不能处理中断。
到此,我们的Cosmos的hal层初始化就结束了。关于内存管理器的初始化,我会在内存管理模块讲解,你先有个印象就行。
进入内核层
hal层的初始化已经完成,按照前面的设计,我们的Cosmos还有内核层,我们下面就要进入到内核层,建立一个文件,写上一个函数,作为本课程的结尾。
但是这个函数是个空函数,目前什么也不做,它是为Cosmos内核层初始化而存在的,但是由于课程只进行到这里,所以我只是写个空函数,为后面的课程做好准备。
由于内核层是从hal层进入的,必须在hal_start()函数中被调用,所以在此完成这个函数——init_krl()。
1 | void init_krl() |
下面我们在hal_start()函数中调用它就行了,如下所示
1 | void hal_start() |
从上面的代码中,不难发现Cosmos的hal层初始化完成后,就自动进入了Cosmos内核层的初始化。至此本课程已经结束。
重点回顾
写一个C函数是容易的,但是写操作系统的第一个C函数并不容易,好在我们一路坚持,没有放弃,才取得了这个阶段性的胜利。但温故而知新,对学过的东西要学而时习之,下面我们来回顾一下本课程的重点。
1.Cosmos的第一个C函数产生了,它十分简单但极其有意义,它的出现标志着C语言的运行环境已经完善。从此我们可以用C语言高效地开发操作系统了,由爬行时代进入了跑步前行的状态,可喜可贺。
2.第一个C函数,干的第一件重要工作就是**调用hal层的初始化函数。**这个初始化函数首先初始化了平台,初始化了机器信息结构供内核的其它代码使用,还初始化了我们图形显示驱动、显示了背景图片;其次是初始化了内存管理相关的数据结构;接着初始了中断,中断处理框架是两层,所以最为复杂;最后初始化了中断控制器。
3.当hal层初始化完成了,我们就进入了内核层,由于到了课程的尾声,我们先暂停在这里。
在这节课里我帮你写了很多代码,那些代码非常简单和枯燥,但是必须要有它们才可以。综合我们前面讲过的知识,我相信你有能力看懂它们。
思考题
请你梳理一下,Cosmos hal层的函数调用关系。
欢迎你在留言区跟我交流互动,也欢迎把这节课转发给你的朋友和同事。
好,我是LMOS,咱们下节课见!
- 李亮亮 👍(3) 💬(1)
录了一个 win11+virtualbox的操作视频 https://www.bilibili.com/video/BV1yb4y1N7ya/?share_source=copy_web&vd_source=2e2d7d5fe4bfed959537727318688414
2024-01-23 - 老王 👍(44) 💬(10)
有了前面基础实验很快就做通了,只是实验步骤课程没有说 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启动系统
总结:
2021-06-07
要想搞清楚整个程序的流程,除了分析代码本身,还需要深入分析Makefile和各个链接脚本 - neohope 👍(22) 💬(3)
稍微整理了一下:
一、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
恢复现场……
%endmacroB、而在kernel.asm中,定义了各种硬件中断编号,比如hxi_hwint00,作为中断处理入口
ALIGN 16
hxi_hwint00:
HARWINT (INT_VECTOR_IRQ0+0)C、有硬件中断时,会先到达中断处理入口,然后调用到硬件中断分发器函数hal_hwint_allocator
第一个参数为中断编号,在rdi
第二个参数为中断发生时的栈指针,在rsi
然后调用异常处理函数hal_do_hwintD、hal_do_hwint
加锁
调用中断回调函数hal_run_intflthandle
释放锁E、hal_run_intflthandle
先获取中断异常表machintflt
然后调用i_serlist 链表上所有挂载intserdsc_t 结构中的中断处理的回调函数,是否处理由函数自己判断F、中断处理完毕
G、异常处理类似,只是触发源头不太一样而已
2021-06-08 - pedro 👍(16) 💬(4)
[ 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,然后:
graph-easy calltree.txt ```</p>2021-06-07</li><br/><li><span>LunaElf</span> 👍(4) 💬(1)<p>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()`</p>2021-10-19</li><br/><li><span>卢承灏</span> 👍(4) 💬(2)<p>有一个问题,回过头来二刷的时候没想明白,如果中断传递的只是一个中断号,然后中断号是进行共用的,那在hal_run_intflthandle 中的list_for_each 中,每个设备注册的handler方法,怎么判断自己需不需要执行呢? handler传入的s->device 也是从循环中的每一个intserdsc_t取出,和最开始的中断号看不出中什么关联。还希望大神们解答</p>2021-09-07</li><br/><li><span>云师兄</span> 👍(4) 💬(1)<p>太硬了啊,有点磕牙,不过再咬两口试试😬</p>2021-06-09</li><br/><li><span>吴建平</span> 👍(2) 💬(1)<p>代码走查出一个安全问题,下面这个函数里,如果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; } </p>2021-07-01</li><br/><li><span>然</span> 👍(2) 💬(1)<p>很好奇move_img2maxpadr(&kmachbsp);这个函数 功能:move_img2maxpadr(&kmachbsp);这个函数是把镜像文件搬到最大的物理地址处。 作用:我感觉是因为镜像文件是加载在0x4000000处,而空闲地址是从内核文件加载处开始计算的(0x2000000+内核大小),随着内存的分配,空闲地址不断向上增长,迟早会覆盖镜像文件,所以提前把镜像文件搬走了。</p>2021-06-07</li><br/><li><span>Victor</span> 👍(2) 💬(3)<p>在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</p>2021-06-07</li><br/><li><span>卖薪沽酒</span> 👍(1) 💬(1)<p>不简单, 来来回回来到了13课程, 关于课后问题, 习惯用ximd 梳理, 有兴趣的同学可以看看,一起加油https://www.cnblogs.com/iwssea/p/16383412.html</p>2022-06-16</li><br/><li><span>艾恩凝</span> 👍(1) 💬(2)<p>终于快走到内核了,上个月24号到现在,进度有点慢,先放下脚步,有些小地方理解不透,看到评论越来越少,我就知道,能坚持下来的人真的不多,可能自己菜,我一个科班出身的感觉都有点吃力,底层那些东西还是很生疏,这门课真的无敌,干货真的很多,需要自己慢慢体会,理解+实战 yyds</p>2022-04-08</li><br/><li><span>逝水</span> 👍(1) 💬(1)<p>init_idt_descriptor 最后会调用 load_x64_idt,通过汇编调用 lidt 方法,将 x64_idt 的地址到 IDTR 寄存器,使得中断门描述符发挥作用。 想了半天内存中的 x64_idt 是怎么发挥作用的,原来这里调用了汇编。</p>2021-12-17</li><br/><li><span>寻道客小林</span> 👍(1) 💬(1)<p>日拱一卒-Day04 有种本科时学8051,lpc1114的感觉,第一遍还是搞清楚流程,不要在乎细节,先建立一个整体的框架。</p>2021-06-07</li><br/><li><span>momo_坦格利安</span> 👍(0) 💬(1)<p>留言肉眼可见的减少了哈哈哈。是有点复杂,得多看几遍</p>2024-09-01</li><br/> </ul>