操作系统实战 45 讲
彭东
网名 LMOS,Intel 傲腾项目关键开发者
65203 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 60 讲
尝尝鲜:从一个Hello到另一个Hello (2讲)
特别放送 (1讲)
操作系统实战 45 讲
15
15
1.0x
00:00/00:00
登录|注册

12 | 设置工作模式与环境(下):探查和收集信息

调用Cosmos的第一个C函数hal_start
调用内核主函数
设置RSP寄存器
初始化长模式下的寄存器
调用init_bstartparm()函数
调用logo()函数
显示logo文件中的图像数据
获取logo.bmp文件
调用init_graph()函数
设置VBE模式
获取一个具体VBE模式的信息
获取VBE模式
初始化图形数据结构
调用init_bstartpages()函数
保存页表首地址在机器信息结构中
映射
初始化顶级页目录、页目录指针的空间
调用init_krlfile()和init_defutfont()函数
放置字库文件
放置内核文件
调用init_krlinitstack()函数
初始化内核栈
调用init_mem()函数
检查内存大小
获取内存布局信息
设置机器信息结构的cpu模式为64位
检查CPU是否支持长模式
检查CPU是否支持CPUID指令
调用init_chkcpu()函数
调用init_bstartparm()函数
初始化machbstart_t结构体
调用init_bstartparm()函数
初始化machbstart_t结构体
释放中文字体文件
设置显卡模式
设置操作系统需要的MMU页表
收集内存布局信息
检查CPU是否支持64位的工作模式
进入Cosmos
串联
显示Logo
设置图形模式
建立MMU页表数据
放置内核文件与字库文件
初始化内核栈
获取内存布局
检查CPU
检查与收集机器信息
初始化机器信息结构
完成具体工作的环节
实现二级引导器
二级引导器
设置工作模式与环境

该思维导图由 AI 生成,仅供参考

你好,我是 LMOS。
上节课我们动手实现了自己的二级引导器。今天这节课我们将进入二级引导器,完成具体工作的环节。
在二级引导器中,我们要检查 CPU 是否支持 64 位的工作模式、收集内存布局信息,看看是不是合乎我们操作系统的最低运行要求,还要设置操作系统需要的 MMU 页表、设置显卡模式、释放中文字体文件。
今天课程的配套代码,你可以点击这里,自行下载。

检查与收集机器信息

如果 ldrkrl_entry() 函数是总裁,那么 init_bstartparm() 函数则是经理,它负责管理检查 CPU 模式、收集内存信息,设置内核栈,设置内核字体、建立内核 MMU 页表数据。
为了使代码更加清晰,我们并不直接在 ldrkrl_entry() 函数中搞事情,而是准备在另一个 bstartparm.c 文件中实现一个 init_bstartparm()。
下面我们就来动手实现它,如下所示。
//初始化machbstart_t结构体,清0,并设置一个标志
void machbstart_t_init(machbstart_t* initp)
{
memset(initp,0,sizeof(machbstart_t));
initp->mb_migc=MBS_MIGC;
return;
}
void init_bstartparm()
{
machbstart_t* mbsp = MBSPADR;//1MB的内存地址
machbstart_t_init(mbsp);
return;
}
目前我们的经理 init_bstartparm() 函数只是调用了一个 machbstart_t_init() 函数,在 1MB 内存地址处初始化了一个机器信息结构 machbstart_t,后面随着干活越来越多,还会调用更多的函数的。

检查 CPU

确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入介绍了在操作系统开发中设置工作模式与环境的关键步骤。首先,二级引导器脱离GRUB控制后,进行了CPU检查、内存布局信息获取、内核栈初始化、内核文件和字库文件放置、MMU页表数据建立以及图形模式设置等工作。随后,二级引导器显示操作系统的logo,标志着一切正常。最后,通过跳转到Cosmos的入口,二级引导器结束自身使命,进入Cosmos操作系统。文章内容技术性强,适合对操作系统开发感兴趣的读者阅读。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《操作系统实战 45 讲》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(95)

  • 最新
  • 精选
  • 言C
    置顶
    经过一段时间的摸索,今天终于成功显示了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 的选项了

    作者回复: 厉害了

    2021-10-17
    2
    9
  • neohope
    置顶
    大体上整理了一下,有几处没弄清楚【下半部分】: 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、然后就接上了本节最后一部分内容了,不容易啊!哈哈哈!

    作者回复: 因为有一段 地址是物理地址,二级引导器中,并没有切换到长模式,是没有办法使用高端的物理地址,只能从低端地址切换

    2021-06-05
    2
    20
  • neohope
    置顶
    大体上整理了一下,有几处没弄清楚【上半部分】: 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指向新地址 感觉和内存保护 或 物理地址与虚拟地址之间转换有一定关系

    作者回复: 是的,你学的很透

    2021-06-05
    6
    17
  • pedro
    思考题还是挺麻烦的,主要是没有注释啊,很多字段的含义都是靠猜,文章也没有介绍到这些。 首先是 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 函数的作用是跳过内核初始化内存。 由于代码无注释,文中篇幅有限,如有错误,多多指正,望海涵~

    作者回复: 你猜的很对 看来我确实不用写注释

    2021-06-04
    17
  • blentle
    首先,这个函数为啥这么命名,没整明白

    作者回复: 这个问题不重要啊

    2021-06-04
    7
    13
  • neohope
    大体上整理了一下,有几处没弄清楚【补充最后一段,发漏了】 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函数

    作者回复: 嗯 就是这样的

    2021-06-05
    7
  • 嗣树
    回答下思考题,init_mem820() 的作用? 先说结论,init_mem820 函数的作用是将 init_mem() 中获取到的内存信息转存到字体文件之后,其实就是为它找一个安稳的地方存放。 简单分析一下: init_mem() -> mmap() -> realadr_call_entry() 然后进入实模式调用中断,实模式的访址能力是有限,我们为中断处理指定临时的地址(E80MAP_ADRADR) 存放内存信息数组 #define ETYBAK_ADR 0x2000 #define E80MAP_ADRADR (ETYBAK_ADR+68) 接下来在把内核文件加载到指定内存地址,字体文件紧随其后,然后就是我们收集到的信息,我们还要把放在临时位置的信息复制出来。

    作者回复: 对 的 对的 就是这样的

    2021-06-04
    2
    7
  • scutgj2010
    我是vmware虚拟机,不支持VBE的118模式,一直在报错vbe mode not 118,手动改成117或者120,还是继续报错,这个怎么解决呀?

    作者回复: 必须要118 否则要改图形驱动的

    2021-08-15
    6
    5
  • init_mem820()函数作用是把e820map结构数组从低地址处(0x2068)搬到高地址处(0x2000000之上)。 问什么要搬?在实模式下能用的地址空间有限,只能访问低地址。所以e820map结构数组不能一直存在低地址处。

    作者回复: 对 对 对 你理解的很好

    2021-06-05
    4
    5
  • XF-薛峰
    这一讲的实验,我的配置是Win10+Virtualbox+Ubuntu用来做make编译,使用make vboxtest命令编译,有关Vbox的报错不用理会(我没在虚拟机下的虚拟机里安装),只要找到hd.vdi文件就好了,退出Ubuntu,另新建虚拟机使用这个hd.vdi就行了。如果只使用make,没有转换成hd.vdi

    作者回复: 你好,你需要转换成 hd.vdi

    2021-06-20
    7
    4
收起评论
显示
设置
留言
95
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部