12 | 设置工作模式与环境(下):探查和收集信息
下载APP
关闭
渠道合作
推荐作者
12 | 设置工作模式与环境(下):探查和收集信息
2021-06-04 LMOS 来自北京
《操作系统实战45讲》
课程介绍
讲述:陈晨
时长14:57大小13.67M
你好,我是 LMOS。
上节课我们动手实现了自己的二级引导器。今天这节课我们将进入二级引导器,完成具体工作的环节。
在二级引导器中,我们要检查 CPU 是否支持 64 位的工作模式、收集内存布局信息,看看是不是合乎我们操作系统的最低运行要求,还要设置操作系统需要的 MMU 页表、设置显卡模式、释放中文字体文件。
检查与收集机器信息
如果 ldrkrl_entry() 函数是总裁,那么 init_bstartparm() 函数则是经理,它负责管理检查 CPU 模式、收集内存信息,设置内核栈,设置内核字体、建立内核 MMU 页表数据。
为了使代码更加清晰,我们并不直接在 ldrkrl_entry() 函数中搞事情,而是准备在另一个 bstartparm.c 文件中实现一个 init_bstartparm()。
下面我们就来动手实现它,如下所示。
目前我们的经理 init_bstartparm() 函数只是调用了一个 machbstart_t_init() 函数,在 1MB 内存地址处初始化了一个机器信息结构 machbstart_t,后面随着干活越来越多,还会调用更多的函数的。
检查 CPU
首先要检查我们的 CPU,因为它是执行程序的关键。我们要搞清楚它能执行什么形式的代码,支持 64 位长模式吗?
这个工作我们交给 init_chkcpu() 函数来干,由于我们要 CPUID 指令来检查 CPU 是否支持 64 位长模式,所以这个函数中需要找两个帮工:chk_cpuid、chk_cpu_longmode 来干两件事,一个是检查 CPU 否支持 CPUID 指令,然后另一个用 CPUID 指令检查 CPU 支持 64 位长模式。
下面我们去写好它们,如下所示。
上述代码中,检查 CPU 是否支持 CPUID 指令和检查 CPU 是否支持长模式,只要其中一步检查失败,我们就打印一条相应的提示信息,然后主动死机。这里需要你留意的是,最后设置机器信息结构中的 mb_cpumode 字段为 64,mbsp 正是传递进来的机器信息 machbstart_t 结构体的指针。
获取内存布局
好了,CPU 已经检查完成 ,合乎我们的要求。下面就要获取内存布局信息了,物理内存在物理地址空间中是一段一段的,描述一段内存有一个数据结构,如下所示。
获取内存布局信息就是获取这个结构体的数组,这个工作我们交给 init_mem 函数来干,这个函数需要完成两件事:一是获取上述这个结构体数组,二是检查内存大小,因为我们的内核对内存容量有要求,不能太小。
下面我们来动手实现这个 init_mem 函数。
上面最难写的是 mmap 函数。不过,我们还是有办法破解的。如果你理解了前面调用 BIOS 的机制,就会发现,只要调用了 BIOS 中断,就能获取 e820map 结构数组。
为了验证这个结论,我们来看一下 mmap 的函数调用关系:
可以看到,mmap 函数正是通过前面讲的 realadr_call_entry 函数,来调用实模式下的 _getmmap 函数的,并且在 _getmmap 函数中调用 BIOS 中断的。
init_mem 函数在调用 mmap 函数后,就会得到 e820map 结构数组,其首地址和数组元素个数由 retemp,retemnr 两个变量分别提供。
初始化内核栈
因为我们的操作系统是 C 语言写的,所以需要有栈,下面我们就来给即将运行的内核初始化一个栈。这个操作非常简单,就是在机器信息结构 machbstart_t 中,记录一下栈地址和栈大小,供内核在启动时使用。
不过,就算操作再简单,我们也要封装成函数来使用。让我们动手来写出这个函数吧,如下所示。
init_krlinitstack 函数非常简单,但是其中调用了一个 move_krlimg 函数你要注意,这个我已经帮你写好啦,它主要负责判断一个地址空间是否和内存中存放的内容有冲突。
因为我们的内存中已经放置了机器信息结构、内存视图结构数组、二级引导器、内核映像文件,所以在处理内存空间时不能和内存中已经存在的他们冲突,否则就要覆盖他们的数据。0x8f000~(0x8f000+0x1001),正是我们的内核栈空间,我们需要检测它是否和其它空间有冲突。
放置内核文件与字库文件
放置内核文件和字库文件这一步,也非常简单,甚至放置其它文件也一样。
因为我们的内核已经编译成了一个独立的二进制程序,和其它文件一起被打包到映像文件中了。所以我们必须要从映像中把它解包出来,将其放在特定的物理内存空间中才可以,放置字库文件和放置内核文件的原理一样,所以我们来一起实现。
以上代码的注释已经很清楚了,都是调用 r_file_to_padr 函数在映像中查找 kernel.bin 和 font.fnt 文件,并复制到对应的空闲内存空间中。
请注意,由于内核是代码数据,所以必须要复制到指定的内存空间中。r_file_to_padr 函数我已经帮你写好了,其中的原理在前面的内容里已经做了说明,这里不再展开。
建立 MMU 页表数据
前面解决了文件放置问题,我们还要解决另一个问题——建立 MMU 页表。
我们在二级引导器中建立 MMU 页表数据,目的就是要在内核加载运行之初开启长模式时,MMU 需要的页表数据已经准备好了。
由于我们的内核虚拟地址空间从 0xffff800000000000 开始,所以我们这个虚拟地址映射到从物理地址 0 开始,大小都是 0x400000000 即 16GB,也就是说我们要虚拟地址空间:0xffff800000000000~0xffff800400000000 映射到物理地址空间 0~0x400000000。
我们为了简化编程,使用长模式下的 2MB 分页方式,下面我们用代码实现它,如下所示。
这个函数的代码写得非常简单,映射的核心逻辑由两重循环控制,外层循环控制页目录指针顶,只有 16 项,其中每一项都指向一个页目录,每个页目录中有 512 个物理页地址。
物理地址每次增加 2MB,这是由 26~30 行的内层循环控制,每执行一次外层循环就要执行 512 次内层循环。
最后,顶级页目录中第 0 项和第 ((KRNL_VIRTUAL_ADDRESS_START) >> KPML4_SHIFT) & 0x1ff 项,指向同一个页目录指针页,这样的话就能让虚拟地址:0xffff800000000000~0xffff800400000000 和虚拟地址:0~0x400000000,访问到同一个物理地址空间 0~0x400000000,这样做是有目的,内核在启动初期,虚拟地址和物理地址要保持相同。
设置图形模式
在计算机加电启动时,计算机上显卡会自动进入文本模式,文本模式只能显示 ASCII 字符,不能显示汉字和图形,所以我们要让显卡切换到图形模式。
切换显卡模式依然要用 BIOS 中断,这个调用原理我们前面已经了如指掌。在实模式切换显卡模式的汇编代码,我已经帮你写好了,下面我们只要写个 C 函数调用它们就好了,代码如下所示。
上面 init_graph 函数中的这些处理 VBE 模式的代码,我已经帮你写好,你可以自己在 graph.c 文件查看。
什么?你不懂 VBE,其实我开始也不懂,后来通过搜寻资料才知道。
其实 VBE 是显卡的一个图形规范标准,它定义了显卡的几种图形模式,每个模式包括屏幕分辨率,像素格式与大小,显存大小。调用 BIOS 10h 中断可以返回这些数据结构。如果你实在对 VBE 感兴趣,可以自行阅读其规范 。
这里我们选择使用了 VBE 的 118h 模式,该模式下屏幕分辨率为 1024x768,显存大小是 16.8MB。显存开始地址一般为 0xe0000000。
屏幕分辨率为 1024x768,即把屏幕分成 768 行,每行 1024 个像素点,但每个像素点占用显存的 32 位数据(4 字节,红、绿、蓝、透明各占 8 位)。我们只要往对应的显存地址写入相应的像素数据,屏幕对应的位置就能显示了。
每个像素点,我们可以用如下数据结构表示:
我们再来看看屏幕像素点和显存位置对应的计算方式:
串联
好了,所有的实施工作的函数已经完成了,现在我们需要在 init_bstartparm() 函数中把它们串联起来,即按照事情的先后顺序,依次调用它们完成相应的工作,实现检查、收集机器信息,设置工作环境。
到这里,init_bstartparm() 函数就成功完成了它的使命。
显示 Logo
前面我们已经设置了图形模式,也应该要展示一下了,检查一下工作成果。
我们来显示一下我们内核的 logo。其实在二级引导器中,我已经帮你写好了显示 logo 函数,而 logo 文件是个 24 位的位图文件,目前为了简单起见,我们只支持这种格式的图片文件。下面我们去调用这个函数。
在图格式的文件中,除了文件头的数据就是图形像素点的数据,只不过 24 位的位图每个像素占用 3 字节,并且位置是倒排的,即第一个像素的数据是在文件的最后,依次类推。我们只要依次将位图文件的数据,按照倒排次序写入显存中,这样就可以显示了。
我们需要把二级引导器的文件和 logo 文件打包成映像文件,然后放在虚拟硬盘中。
复制文件到虚拟硬盘中得先 mount,然后复制,最后转换成 VDI 格式的虚拟硬盘,再挂载到虚拟机上启动就行了。这也是为什么要手动建立硬盘的原因,打包命令如下。
如果手动打命令对你来说还是比较难,也别担心,我已经帮你写好了 make 脚本,你只需要进入代码目录中 make vboxtest 就行了,运行结果如下 。
代码运行结果示意图
啊哈!终于显示了 logo。是不是挺有成就感的?这至少证明我们辛苦写的代码是正确的。
但是目前我们的代码执行流还在二级引导器中,我们的目的是开发自己的操作系统,不,我们是要开发 Cosmos。
后面,我们正式用 Cosmos 命名我们的操作系统。Cosmos 可以翻译成宇宙,尽管它刚刚诞生,但我对它充满期待,所以用了这样一个能够“包括万物,包罗万象”的名字。
进入 Cosmos
我们在调用 Cosmos 第一个 C 函数之前,我们依然要写一小段汇编代码,切换 CPU 到长模式,初始化 CPU 寄存器和 C 语言要用的栈。因为目前代码执行流在二级引导器中,进入到 Cosmos 中这样在二级引导器中初始过的东西都不能用了。
因为 CPU 进入了长模式,寄存器的位宽都变了,所以需要重新初始化。让我们一起来写这段汇编代码吧,我们先在 Cosmos/hal/x86/ 下建立一个 init_entry.asm 文件,写上后面这段代码。
上述代码中,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。
重点回顾
这是我们设置工作模式与环境的最后一课,到此为止我们的二级引导器已经建立起来了,成功从 GRUB 手中接过了权柄,开始了它自己的一系列工作,二级引导器完成的工作不算少,我来帮你梳理一下,重点如下。
1. 二级引导器彻底摆脱了 GRUB 的控制之后,就开始检查 CPU,获取内存布局信息,确认是不是我们要求的 CPU 和内存大小,接着初始化内核栈、放置好内核文件和字库文件,建立 MMU 页表数据和设置好图形模式,为后面运行内核做好准备。
2. 当二级引导器完成了上述功能后,就会显示我们操作系统的 logo,这标志着二级引导器所有的工作一切正常。
3. 进入 Cosmos,我们的二级引导器通过跳转到 Cosmos 的入口,结束了自己光荣使命,Cosmos 的入口是一小段汇编代码,主要是开启 CPU 的长模式,最后调用了 Cosmos 的第一个 C 函数 hal_start。
你想过吗?我们的二级引导器还可以做更多的事情,其实还可以在二级引导器中获取 ACPI 表,进而获取 CPU 数量和其它设备信息,期待你的实现。
思考题
请你想一下,init_bstartparm() 函数中的 init_mem820() 函数,这个函数到底干了什么?
欢迎你在留言区跟我互动。如果你身边有朋友对手写操作系统有热情,也欢迎你把这节课转发给他。
分享给需要的人,Ta购买本课程,你将得20元
生成海报并分享
赞 25
提建议
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
上一篇
11 | 设置工作模式与环境(中):建造二级引导器
下一篇
13 | 第一个C函数:如何实现板级初始化?
精选留言(81)
- neohope置顶2021-06-05大体上整理了一下,有几处没弄清楚【下半部分】: 11、返回到bstartparm.c 调用了chkcpmm.c的init_bstartpages 12、然后调用到了fs.c的move_krlimg函数申请了内存,建立了MMU页表: 顶级页目录,开始于0x1000000 页目录指针目录,开始于0x1001000,,共16项 ,其中每一项都指向一个页目录 页目录,开始于0x1002000, 每页指向512 个物理页,每页2MB【 0x200000】 让物理地址p[0]和虚拟地址p[((KRNL_VIRTUAL_ADDRESS_START) >> KPML4_SHIFT) & 0x1ff],指向同一个页目录指针页,确保内核在启动初期,虚拟地址和物理地址要保持相同 没搞清楚为什么虚拟地址是这个,也暂时没搞清楚为何要指向(u64_t)((u32_t)pdpte | KPML4_RW | KPML4_P) 最后,把页表首地址保存在机器信息结构中 13、返回到bstartparm.c 调用了graph.c的init_graph A、初始化了数据结构 B、调用init_bgadevice 首先获取GBA设备ID 检查设备最大分辨率 设置显示参数,并将参数保存到mbsp结构中 C、如果不是图形模式,要通过BIOS中断进行切换,设置显示参数,并将参数保存到mbsp结构中: 获取VBE模式,通过BIOS中断 获取一个具体VBE模式的信息,通过BIOS中断 设置VBE模式,通过BIOS中断 这三个方法同样用到了realadr_call_entry,调用路径与上面_getmmap类似,不再展开 D、初始化了一块儿内存 感觉会与物理地址与虚拟地址之间转换由一定关系 E、进行logo显示 调用get_file_rpadrandsz定位到位图文件 调用bmp_print,读入像素点,BGRA转换 最后调用write_pixcolor,写入到mbsp->mb_ghparm正确的位置,图像就显示出来了 14、然后一路返回 到bstartparm.c的init_bstartparm 到ldrkrlentry.c的ldrkrl_entry 到ldrkrl32.asm的call ldrkrl_entry 再往下是jmp 0x2000000 这个地址就是IMGKRNL_PHYADR,就是刚才放Cosmos.eki的位置 15、然后就接上了本节最后一部分内容了,不容易啊!哈哈哈!展开
作者回复: 因为有一段 地址是物理地址,二级引导器中,并没有切换到长模式,是没有办法使用高端的物理地址,只能从低端地址切换
16 - neohope置顶2021-06-05大体上整理了一下,有几处没弄清楚【上半部分】: 1、bstartparm.c从init_bstartparm函数开始 A、初始化machbstart_t 2、跳转到chkcpmm.c的init_chkcpu函数,检查是否支持CPUID功能 A、init_chkcpu函数 CPU自带检查方式:无法反转 Eflags第21位,表示CPU支持CPUID功能 如果反转成功,说明不支持CPUID,打印内核错误并退出 B、然后调用CPUID功能,判断是否支持长模式 先通过通过0x80000000参数,调用cpuid命令,判断CPU是否支持扩展处理器信息【返回值比0x80000000大】 如果支持,通过0x80000001参数,调用cpuid命令,获取扩展处理器信息,然后检测第29位,判断是否支持长模式 如果不支持,打印内核错误并退出 C、设置mbsp中cpumode为64位 3、返回chkcpmm.c,继续检测内存信息 A、跳转到chkcpmm.c的init_mem函数 B、通过mmap调用realadr_call_entry(RLINTNR(0),0,0) C、实际会执行ldrkrl32.asm的realadr_call_entry D、跳转到save_eip_jmp E、最后在cpmty_mode处,把 0x18:0x1000 [段描述索引:段内的偏移],装入到 CS:EIP 寄存器中 F、而EIP这个地址恰恰是内存中initldrsve.bin的位置,因为之前write_realintsvefile把数据加载到了REALDRV_PHYADR 0x1000【而且在initldrsve.lds好像也指定了段内偏移0x1000】 同时CS中段描述符为k16cd_dsc,说明是16位代码段,可以执行,CPU继续从EIP地址执行 G、而initldrsve.bin是由realintsve.asm编译得到的,所以实际会继续执行realintsve.asm中代码 H、然后到real_entry这里,通过传入的参数ax,判断调用func_table哪个方法 当前参数位0,ax就是0,也就是调用了func_table的第一个函数_getmmap I、_getmmap中,通过BIOS的15h中断,获取内存信息 J、检查内存信息,如果小于128M,打印内核错误并退出 K、设置machbstart_t内存相关参数 L、然后调用了init_acpi 4、在init_acpi中 通过“RSD PTR ”及校验,判断是否支持ACPI功能 不支持则 打印内核错误并退出 5、返回到bstartparm.c 好像是确认了一下initldrsve.bin的状态,获取了一下文件内存地址及大小 6、返回到bstartparm.c,继续调用到chkcpmm.c的init_krlinitstack函数 7、然后调用到了fs.c的move_krlimg函数 首先判断新申请的地址,是否可用 -》如果可用直接使用 -》如果不可用,则判断申请的内存大小是否超出设备物理大小 -》-》如果超出大小,系统打印内核错误并退出 -》-》如果不超出大小,系统会将内存对齐到0x1000后,将initldrsve.bin移动到新位置,并修正地址 整体来说move_krlimg更像是申请内存,但不知道为何要不断驱赶initldrsve.bin文件 8、返回到chkcpmm.c 初始化栈顶和栈大小 9、返回到bstartparm.c 调用fs.c的init_krlfile函数,将Cosmos.eki加载到了IMGKRNL_PHYADR 并填写了mbsp相关内容 10、返回到bstartparm.c 调用了chkcpmm.c的init_meme820函数 然后调用到了fs.c的move_krlimg函数申请了内存,拷贝了一份e820map_t到Cosmos.eki之后的地址,并修正mbsp指向新地址 感觉和内存保护 或 物理地址与虚拟地址之间转换有一定关系展开
作者回复: 是的,你学的很透
共 6 条评论13 - 言C置顶2021-10-17经过一段时间的摸索,今天终于成功显示了Cosmos的logo界面了,遇到了几个坑,特此来给大家填坑了: 1、出现无法找到Cosmos的错误提示,如下: 正在生成Cosmos内核映像文件,请稍后…… 文件数:6 映像文件大小:4911104 Converting from raw image file="../hd.img" to file="../exckrnl/hd.vdi"... Creating dynamic image with size 104857600 bytes (100MB)... VBoxManage: error: Could not find a registered machine named 'Cosmos' VBoxManage: error: Details: code VBOX_E_OBJECT_NOT_FOUND (0x80bb0001), component VirtualBoxWrap, interface IVirtualBox, callee nsISupports VBoxManage: error: Context: "FindMachine(Bstr(a->argv[0]).raw(), machine.asOutParam())" at line 1044 of file VBoxManageStorageController.cpp make[1]: *** [vbox.mkf:13:idectrnul] 错误 1 make: *** [Makefile:101:VBOXRUN] 错误 2 解决办法:需要自己建立一个虚拟机,命名为 Cosmos 2、运行 make virtualtest 后,virtualbox启动后没有出现logo图片,而出现错误提示:INITKLDR DIE ERROR:S 解决办法:建立虚拟机时,需要选择为 64bit 的系统 3、这个问题是与第二个的关联问题,当你在建立虚拟机时,想选择64bit的系统,却发现只有 32bit 的 解决办法: VirtualBox安装64位的系统需要满足以下条件: 1. 64位的cpu 2. cpu允许硬件虚拟化 先来看第一个条件,64位的CPU,这个嘛,现在的笔记本一般都是64位的了,所以不用担心,除非是好几年之间的电脑。如果你不清楚,可以打开命令行,输入systeminfo,在输出的信息中找到CPU这一行,如果是X86_64的,就是64位CPU; 第二条,是否开启CPU硬件虚拟化1,这个嘛,各大厂商的情况不大相同,有的电脑默认开启了(比如,我的HP),有的没有,所以需要自行开启,开启方法:开机时按某个键进入BIOS设置界面2。 然后,setup==>security==>cpu virtualization,将cpu virtualization这一项由Disable设置为Enable。保存,然后重启电脑,硬件虚拟化就开启成功了。 如果你是通过windows + vmware + virtualbox 的方式实现的,还需要设置 VMware,打开vmware,找到对应的虚拟机,虚拟机设置->硬件->处理器->虚拟化引擎,这里一般会有三个选项,全都勾上 然后在打开该虚拟机,去virtualbox中建立虚拟机时,就会发现有 64bit 的选项了展开
作者回复: 厉害了
5 - blentle2021-06-04首先,这个函数为啥这么命名,没整明白
作者回复: 这个问题不重要啊
共 7 条评论13 - pedro2021-06-04思考题还是挺麻烦的,主要是没有注释啊,很多字段的含义都是靠猜,文章也没有介绍到这些。 首先是 init_mem820 这个函数本身: ```c void init_meme820(machbstart_t *mbsp) { e820map_t *semp = (e820map_t *)((u32_t)(mbsp->mb_e820padr)); // e820数组地址 u64_t senr = mbsp->mb_e820nr; // 个数 e820map_t *demp = (e820map_t *)((u32_t)(mbsp->mb_nextwtpadr)); if (1 > move_krlimg(mbsp, (u64_t)((u32_t)demp), (senr * (sizeof(e820map_t))))) { kerror("move_krlimg err"); } m2mcopy(semp, demp, (sint_t)(senr * (sizeof(e820map_t)))); mbsp->mb_e820padr = (u64_t)((u32_t)(demp)); mbsp->mb_e820sz = senr * (sizeof(e820map_t)); mbsp->mb_nextwtpadr = P4K_ALIGN((u32_t)(demp) + (u32_t)(senr * (sizeof(e820map_t)))); mbsp->mb_kalldendpadr = mbsp->mb_e820padr + mbsp->mb_e820sz; return; } ``` 我们发现这个函数实际上在拷贝内存,即将 semp 指针处的 senr * (sizeof(e820map_t) 内存大小拷贝到 demp 处,而 demp 的地址正是 mb_nextwtpadr,那么这个 mb_nextwtpadr 是怎么来的呢?在init_krlfile函数中可以大致猜测: ```c void init_krlfile(machbstart_t *mbsp) { u64_t sz = r_file_to_padr(mbsp, IMGKRNL_PHYADR, "kernel.bin"); if (0 == sz) { kerror("r_file_to_padr err"); } mbsp->mb_krlimgpadr = IMGKRNL_PHYADR; mbsp->mb_krlsz = sz; // 页对齐 mbsp->mb_nextwtpadr = P4K_ALIGN(mbsp->mb_krlimgpadr + mbsp->mb_krlsz); mbsp->mb_kalldendpadr = mbsp->mb_krlimgpadr + mbsp->mb_krlsz; return; } ``` 没错,mb_nextwtpadr 正是跳过内核起始地址+内核大小后的第一个页地址,注意需要4k对齐。 那么刚才内存拷贝的意图也很明显,对于初始化后的内存,内核本身的内存映射是不可访问的,必须保护充分内核,所以 init_mem820 函数的作用是跳过内核初始化内存。 由于代码无注释,文中篇幅有限,如有错误,多多指正,望海涵~展开
作者回复: 你猜的很对 看来我确实不用写注释
11 - neohope2021-06-05大体上整理了一下,有几处没弄清楚【补充最后一段,发漏了】 15、然后就接上了本节最后一部分内容了,不容易啊!哈哈哈! Cosmos.bin中【前面写错为Cosmos.eki了】,ld设置的程序入口为init_entry.asm的_start: 16、 init_entry.asm中_start: A、关闭中断 B、通过LGDT命令,指定长度和初始位置,加载GDT C、设置页表,开启PAE【CR4第五位设置为1】,将页表顶级目录放入CR3 D、读取EFER,将第八位设置为1,写回EFER,设置为长模式 E、开启保护模式【CR0第0位设置为1】,开启分页【CR0第31位设置为1】,开启CACHE【CR0第30位设置为0】,开启WriteThrough【CR0第29位设置为0】 F、初始化寄存器们 G、将之前复制到Cosmos.bin之后的mbsp地址,放入rsp H、0入栈,0x8入栈, hal_start 函数地址入栈 I、调用机器指令“0xcb48”,做一个“返回”操作,同时从栈中弹出两个数据[0x8:hal_start 函数地址],到[CS:RIP] 长模式下,指令寄存器为RIP,也就是说下一个指令为hal_start 函数地址 CS中为0x8,对应到ekrnl_c64_dsc,对应到内核代码段,可以执行,CPU继续冲RIP地址执行 17、hal_start.c A、执行hal_start函数展开
作者回复: 嗯 就是这样的
7 - 嗣树2021-06-04回答下思考题,init_mem820() 的作用? 先说结论,init_mem820 函数的作用是将 init_mem() 中获取到的内存信息转存到字体文件之后,其实就是为它找一个安稳的地方存放。 简单分析一下: init_mem() -> mmap() -> realadr_call_entry() 然后进入实模式调用中断,实模式的访址能力是有限,我们为中断处理指定临时的地址(E80MAP_ADRADR) 存放内存信息数组 #define ETYBAK_ADR 0x2000 #define E80MAP_ADRADR (ETYBAK_ADR+68) 接下来在把内核文件加载到指定内存地址,字体文件紧随其后,然后就是我们收集到的信息,我们还要把放在临时位置的信息复制出来。展开
作者回复: 对 的 对的 就是这样的
共 2 条评论6 - 然2021-06-05init_mem820()函数作用是把e820map结构数组从低地址处(0x2068)搬到高地址处(0x2000000之上)。 问什么要搬?在实模式下能用的地址空间有限,只能访问低地址。所以e820map结构数组不能一直存在低地址处。
作者回复: 对 对 对 你理解的很好
共 4 条评论4 - 一叉树2022-02-21Mac 主机 + VBox。卡在了文中“你只需要进入代码目录中 make vboxtest 就行了,运行结果如下 。”这一步很久很久。 我是在 Ubuntu 虚拟机中执行 make vboxtest 的,会生成一个 hd.img,同时会有如下报错 恭喜我,系统编译构建完成! ^_^ 恭喜我,系统编译构建完成! ^_^ 正在生成Cosmos内核映像文件,请稍后…… 文件数:6 映像文件大小:4911104 [sudo] password for mie: make[1]: VBoxManage: Command not found make[1]: *** [vbox.mkf:23: tranvdi] Error 127 make: *** [Makefile:101: VBOXRUN] Error 2 这时候需要自己 1. 把生成的 hd.img 搞到外面的 mac 2. 使用第 10 节课用到的命令转化为 vdi 文件: VBoxManage convertfromraw ./hd.img --format VDI ./hd.vdi 3. 新建一个虚拟机,将这个生成的 hd.vdi 当硬盘挂上去 4. 成功运行 ^_^ 曾经郁闷这一步怎么这么跳跃,文中省了这么多步骤。但仔细观察 cosmos/lesson12/Cosmos/build/vbox.mkf 这个文件你会发现,这一切老师早帮你写好了,只是我使用 mac主机 + Ubuntu虚拟机学习 + 另一虚拟机运行结果,用了两个虚拟机而非套娃,所以在 Ubuntu 中报错 VBoxManage: Command not found。老师写的自动拉起虚拟机运行的脚本没能自动运行。展开
作者回复: 66666
3 - scutgj20102021-08-15我是vmware虚拟机,不支持VBE的118模式,一直在报错vbe mode not 118,手动改成117或者120,还是继续报错,这个怎么解决呀?
作者回复: 必须要118 否则要改图形驱动的
共 5 条评论4 - XF-薛峰2021-06-20这一讲的实验,我的配置是Win10+Virtualbox+Ubuntu用来做make编译,使用make vboxtest命令编译,有关Vbox的报错不用理会(我没在虚拟机下的虚拟机里安装),只要找到hd.vdi文件就好了,退出Ubuntu,另新建虚拟机使用这个hd.vdi就行了。如果只使用make,没有转换成hd.vdi
作者回复: 你好,你需要转换成 hd.vdi
共 7 条评论3 - 孙孙孙2021-06-06正在生成Cosmos内核映像文件,请稍后…… 文件数:6 映像文件大小:4911104 Converting from raw image file="../hd.img" to file="../exckrnl/hd.vdi"... Creating dynamic image with size 104857600 bytes (100MB)... VBoxManage: error: Could not find a registered machine named 'Cosmos' VBoxManage: error: Details: code VBOX_E_OBJECT_NOT_FOUND (0x80bb0001), component VirtualBoxWrap, interface IVirtualBox, callee nsISupports VBoxManage: error: Context: "FindMachine(Bstr(a->argv[0]).raw(), machine.asOutParam())" at line 1044 of file VBoxManageStorageController.cpp make[1]: *** [vbox.mkf:13:idectrnul] 错误 1 make: *** [Makefile:101:VBOXRUN] 错误 2 有人知道这个是为什么吗?展开
作者回复: 你好,你需要先建立一个虚拟机
3 - 野欲2021-09-26跟着写完了代码也不知道cosmos是怎么跑起来的,迷惑
作者回复: 那就再来一遍
3 - zhanghj2021-06-13各种数据结构放的位置,比如内存视图,内核文件保存的位置,栈顶指针的地址,这些地址值是如何规划的?
作者回复: 看看内存的布局 进行合理规划
共 3 条评论2 - 熊光红2021-06-06用lession12的hd.img制作了个hd.vdi,加载到虚拟机,运行出现out of memory错误,请问如何建立调试环境调试一下,看看哪儿出错了
作者回复: 你好,你需要给虚拟机配置更大的内存
共 3 条评论2 - didi迪迪2022-09-14 来自湖北作者,您好,想请您解释下下面这段话: 顶级页目录中第 0 项和第 ((KRNL_VIRTUAL_ADDRESS_START) >> KPML4_SHIFT) & 0x1ff 项,指向同一个页目录指针页,这样的话就能让虚拟地址:0xffff800000000000~0xffff800400000000 和虚拟地址:0~0x400000000,访问到同一个物理地址空间 0~0x400000000 通过计算,发现 ((KRNL_VIRTUAL_ADDRESS_START) >> KPML4_SHIFT) & 0x1ff为100000000,也就是256,但是为什么第0项和第256项指向一个页目录指针页就可以实现访问同一个物理地址空间呢?展开
作者回复: 可以看MMU是如何转换地址的
1 - Geek_5041852021-11-187. 然后回到12课代码的根目录有Makefile的目录,利用命令make vboxtest,虚拟机启动了一直提示VT-x不支持。 8.查了资料,从BIOS里查看了已经开启虚拟化了,不知道为啥原因,有说Win10从10月后多Hyper-v需要关闭; 9.关闭了Hyper-V,再次实验还是提示不支持。 10.考虑是否是Virtualbox里没开启VT-x,查了虚拟机Cosmos,设置的system里确实没开启; 11.使用命令开启,开启后还是不行,完全懵了。 12.使用手工创建Cosmos虚拟机,发现Virtualbox就是没有64位系统的选项。 13.这时考虑到Virtualbox的宿主机是VMWare,所以VMWare也开启VT-x!!!! 14.再次运行,需修改Cosmos虚拟机内存,默认128M,改为1024M,实验成功。展开
作者回复: 是的
1 - Geek_5041852021-11-18先说步骤吧: 第一步:开启硬件BIOS里的VT-x; 第二步: 如果和我一样有虚拟机开启虚拟机的VT-x; 第三步:检查Virtualbox创建虚拟机时出现了可以选择创建64位系统的选项就可以了; 第四步:lesson12代码, cd build, make createvm -f vbox.mkf make crtidectr -f vbox.mkf cd .. make vboxtest 其中这几天实验过程记录一下: 我的开始用的环境是:Win10+VMware12 (Ubuntu20.4 64位) 1. 开始从Ubuntu里创建hd.img,安装grub和编译Cosmos.eki放到/boot/目录下; 2. 然后利用StarWindConverter.exe转换成VMware识别的硬盘格式,运行; 3.运行程序提示INITKRL DIE ERROR:not find file ; 4.开始以为是VMWare虚拟机的问题(目前来看应该不是),转战使用Ubuntu20.4 里安装Virtualbox,开启套娃模式。 5.Ubuntu64位操作系统里使用sudo apt-get install virtualbox-6.1提示Package 'virtualbox-6.1' has no installation candidate, 就去官网下载64位virtualbox手动安装,最后提示安装失败,不支持,最后利用Ubuntu自带的App Store安装了Virtualbox6.1; 6.开始利用12课代码做实验,仔细看来makefi展开
作者回复: 哈哈
1 - 余生2021-09-09起动提示INITKLDR DIE ERROR:S ,建虚机时选64位,入坑了很久才爬出来
作者回复: 怎么会有这个错误的
共 5 条评论1 - 李城2021-09-02将Cosmos.eki 更名为HelloOS.eki, 放在./hdisk/boot 目录下,打开虚拟机,报错 not find file, 不知如何下手
作者回复: 用gitee上课程目录中的代码构建 不要自己替换
1