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

02 | 几行汇编几行C:实现一个最简单的内核

02 | 几行汇编几行C:实现一个最简单的内核-极客时间

02 | 几行汇编几行C:实现一个最简单的内核

讲述:陈晨

时长13:29大小12.32M

你好,我是 LMOS。
我们知道,在学习许多编程语言一开始的时候,都有一段用其语言编写的经典程序——Hello World。这不过是某一操作系统平台之上的应用程序,却心高气傲地问候世界。
而我们学习操作系统的时候,那么也不妨撇开其它现有的操作系统,基于硬件,写一个最小的操作系统——Hello OS,先练练手、热热身,直观感受一下。
本节课的配套代码,你可以从这里下载。
请注意,这节课主要是演示思路,不要求你马上动手实现。详细的环境安装、配置我们到第十节课再详细展开。有兴趣上手的同学,可以参考留言区置顶的实验笔记探索。

PC 机的引导流程

看标题就知道,写操作系统要用汇编和 C 语言,尽管这个 Hello OS 很小,但也要用到两种编程语言。其实,现有的商业操作系统都是用这两种语言开发出来的。
先不用害怕,Hello OS 的代码量很少。
其实,我们也不打算从 PC 的引导程序开始写起,原因是目前我们的知识储备还不够,所以先借用一下 GRUB 引导程序,只要我们的 PC 机上安装了 Ubuntu Linux 操作系统,GRUB 就已经存在了。这会大大降低我们开始的难度,也不至于打消你的热情。
那在写 Hello OS 之前,我们先要搞清楚 Hello OS 的引导流程,如下图所示:
Hello OS引导流程图
简单解释一下,PC 机 BIOS 固件是固化在 PC 机主板上的 ROM 芯片中的,掉电也能保存,PC 机上电后的第一条指令就是 BIOS 固件中的,它负责检测和初始化 CPU、内存及主板平台,然后加载引导设备(大概率是硬盘)中的第一个扇区数据,到 0x7c00 地址开始的内存空间,再接着跳转到 0x7c00 处执行指令,在我们这里的情况下就是 GRUB 引导程序。
当然,更先进的UEFI BIOS则不同,这里就不深入其中了,你可以通过链接自行了解。

Hello OS 引导汇编代码

明白了 PC 机的启动流程,下面只剩下我们的 Hello OS 了,我们马上就去写好它。
我们先来写一段汇编代码。这里我要特别说明一个问题:为什么不能直接用 C?
C 作为通用的高级语言,不能直接操作特定的硬件,而且 C 语言的函数调用、函数传参,都需要用栈。
栈简单来说就是一块内存空间,其中数据满足后进先出的特性,它由 CPU 特定的栈寄存器指向,所以我们要先用汇编代码处理好这些 C 语言的工作环境。
;彭东 @ 2021.01.09
MBT_HDR_FLAGS EQU 0x00010003
MBT_HDR_MAGIC EQU 0x1BADB002 ;多引导协议头魔数
MBT_HDR2_MAGIC EQU 0xe85250d6 ;第二版多引导协议头魔数
global _start ;导出_start符号
extern main ;导入外部的main函数符号
[section .start.text] ;定义.start.text代码节
[bits 32] ;汇编成32位代码
_start:
jmp _entry
ALIGN 8
mbt_hdr:
dd MBT_HDR_MAGIC
dd MBT_HDR_FLAGS
dd -(MBT_HDR_MAGIC+MBT_HDR_FLAGS)
dd mbt_hdr
dd _start
dd 0
dd 0
dd _entry
;以上是GRUB所需要的头
ALIGN 8
mbt2_hdr:
DD MBT_HDR2_MAGIC
DD 0
DD mbt2_hdr_end - mbt2_hdr
DD -(MBT_HDR2_MAGIC + 0 + (mbt2_hdr_end - mbt2_hdr))
DW 2, 0
DD 24
DD mbt2_hdr
DD _start
DD 0
DD 0
DW 3, 0
DD 12
DD _entry
DD 0
DW 0, 0
DD 8
mbt2_hdr_end:
;以上是GRUB2所需要的头
;包含两个头是为了同时兼容GRUB、GRUB2
ALIGN 8
_entry:
;关中断
cli
;关不可屏蔽中断
in al, 0x70
or al, 0x80
out 0x70,al
;重新加载GDT
lgdt [GDT_PTR]
jmp dword 0x8 :_32bits_mode
_32bits_mode:
;下面初始化C语言可能会用到的寄存器
mov ax, 0x10
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
xor eax,eax
xor ebx,ebx
xor ecx,ecx
xor edx,edx
xor edi,edi
xor esi,esi
xor ebp,ebp
xor esp,esp
;初始化栈,C语言需要栈才能工作
mov esp,0x9000
;调用C语言函数main
call main
;让CPU停止执行指令
halt_step:
halt
jmp halt_step
GDT_START:
knull_dsc: dq 0
kcode_dsc: dq 0x00cf9e000000ffff
kdata_dsc: dq 0x00cf92000000ffff
k16cd_dsc: dq 0x00009e000000ffff
k16da_dsc: dq 0x000092000000ffff
GDT_END:
GDT_PTR:
GDTLEN dw GDT_END-GDT_START-1
GDTBASE dd GDT_START
以上的汇编代码(/lesson02/HelloOS/entry.asm)分为 4 个部分:
1. 代码 1~40 行,用汇编定义的 GRUB 的多引导协议头,其实就是一定格式的数据,我们的 Hello OS 是用 GRUB 引导的,当然要遵循 GRUB 的多引导协议标准,让 GRUB 能识别我们的 Hello OS。之所以有两个引导头,是为了兼容 GRUB1 和 GRUB2。
2. 代码 44~52 行,关掉中断,设定 CPU 的工作模式。你现在可能不懂,没事儿,后面 CPU 相关的课程我们会专门再研究它。
3. 代码 54~73 行,初始化 CPU 的寄存器和 C 语言的运行环境。
4. 代码 78~87 行,GDT_START 开始的,是 CPU 工作模式所需要的数据,同样,后面讲 CPU 时会专门介绍。

Hello OS 的主函数

到这,不知道你有没有发现一个问题? 上面的汇编代码调用了 main 函数,而在其代码中并没有看到其函数体,而是从外部引入了一个符号。
那是因为这个函数是用 C 语言写的在(/lesson02/HelloOS/main.c)中,最终它们分别由 nasm 和 GCC 编译成可链接模块,由 LD 链接器链接在一起,形成可执行的程序文件:
//彭东 @ 2021.01.09
#include "vgastr.h"
void main()
{
printf("Hello OS!");
return;
}
以上这段代码,你应该很熟悉了吧?不过这不是应用程序的 main 函数,而是 Hello OS 的 main 函数。
其中的 printf 也不是应用程序库中的那个 printf 了,而是需要我们自己实现了。你可以先停下歇歇,再去实现 printf 函数。

控制计算机屏幕

接着我们再看下显卡,这和我们接下来要写的代码有直接关联。
计算机屏幕显示往往是显卡的输出,显卡有很多形式:集成在主板的叫集显,做在 CPU 芯片内的叫核显,独立存在通过 PCIE 接口连接的叫独显,性能依次上升,价格也是。
独显的高性能是游戏玩家们所钟爱的,3D 图形显示往往要涉及顶点处理、多边形的生成和变换、纹理、着色、打光、栅格化等。而这些任务的计算量超级大,所以独显往往有自己的 RAM、多达几百个运算核心的处理器。因此独显不仅仅是可以显示图像,而且可以执行大规模并行计算,比如“挖矿”。
我们要在屏幕上显示字符,就要编程操作显卡。
其实无论我们 PC 上是什么显卡,它们都支持一种叫 VESA 的标准,这种标准下有两种工作模式:字符模式和图形模式。显卡们为了兼容这种标准,不得不自己提供一种叫 VGABIOS 的固件程序。
下面,我们来看看显卡的字符模式的工作细节。
它把屏幕分成 24 行,每行 80 个字符,把这(24*80)个位置映射到以 0xb8000 地址开始的内存中,每两个字节对应一个字符,其中一个字节是字符的 ASCII 码,另一个字节为字符的颜色值。如下图所示:
计算机显卡文本模式
明白了显卡的字符模式的工作细节,下面我们开始写代码。
这里先提个醒:C 语言字符串是以 0 结尾的,其字符编码通常是 utf8,而 utf8 编码对 ASCII 字符是兼容的,即英文字符的 ASCII 编码和 utf8 编码是相等的(关于utf8编码你可以自行了解)。
//彭东 @ 2021.01.09
void _strwrite(char* string)
{
char* p_strdst = (char*)(0xb8000);//指向显存的开始地址
while (*string)
{
*p_strdst = *string++;
p_strdst += 2;
}
return;
}
void printf(char* fmt, ...)
{
_strwrite(fmt);
return;
}
代码很简单,printf 函数直接调用了 _strwrite 函数,而 _strwrite 函数正是将字符串里每个字符依次定入到 0xb8000 地址开始的显存中,而 p_strdst 每次加 2,这也是为了跳过字符的颜色信息的空间。
到这,Hello OS 相关的代码就写好了,下面就是编译和安装了。你可别以为这个事情就简单了,下面请跟着我去看一看。

编译和安装 Hello OS

Hello OS 的代码都已经写好,这时就要进入安装测试环节了。在安装之前,我们要进行系统编译,即把每个代码模块编译最后链接成可执行的二进制文件。
你可能觉得我在小题大做,编译不就是输入几条命令吗,这么简单的工作也值得一说?
确实,对于我们 Hello OS 的编译工作来说特别简单,因为总共才三个代码文件,最多四条命令就可以完成。
但是以后我们 Hello OS 的文件数量会爆炸式增长,一个成熟的商业操作系统更是多达几万个代码模块文件,几千万行的代码量,是这世间最复杂的软件工程之一。所以需要一个牛逼的工具来控制这个巨大的编译过程。

make 工具

make 历史悠久,小巧方便,也是很多成熟操作系统编译所使用的构建工具。
在软件开发中,make 是一个工具程序,它读取一个叫“makefile”的文件,也是一种文本文件,这个文件中写好了构建软件的规则,它能根据这些规则自动化构建软件。
makefile 文件中规则是这样的:首先有一个或者多个构建目标称为“target”;目标后面紧跟着用于构建该目标所需要的文件,目标下面是构建该目标所需要的命令及参数。
与此同时,它也检查文件的依赖关系,如果需要的话,它会调用一些外部软件来完成任务。
第一次构建目标后,下一次执行 make 时,它会根据该目标所依赖的文件是否更新决定是否编译该目标,如果所依赖的文件没有更新且该目标又存在,那么它便不会构建该目标。这种特性非常有利于编译程序源代码。
任何一个 Linux 发行版中都默认自带这个 make 程序,所以不需要额外的安装工作,我们直接使用即可。
为了让你进一步了解 make 的使用,接下来我们一起看一个有关 makefile 的例子:
CC = gcc #定义一个宏CC 等于gcc
CFLAGS = -c #定义一个宏 CFLAGS 等于-c
OBJS_FILE = file.o file1.o file2.o file3.o file4.o #定义一个宏
.PHONY : all everything #定义两个伪目标all、everything
all:everything #伪目标all依赖于伪目标everything
everything :$(OBJS_FILE) #伪目标everything依赖于OBJS_FILE,而OBJS_FILE是宏会被
#替换成file.o file1.o file2.o file3.o file4.o
%.o : %.c
$(CC) $(CFLAGS) -o $@ $<
我来解释一下这个例子:
make 规定“#”后面为注释,make 处理 makefile 时会自动丢弃。
makefile 中可以定义宏,方法是在一个字符串后跟一个“=”或者“:=”符号,引用宏时要用“$(宏名)”,宏最终会在宏出现的地方替换成相应的字符串,例如:$(CC) 会被替换成 gcc,$( OBJS_FILE) 会被替换成 file.o file1.o file2.o file3.o file4.o。
.PHONY 在 makefile 中表示定义伪目标。所谓伪目标,就是它不代表一个真正的文件名,在执行 make 时可以指定这个目标来执行其所在规则定义的命令。但是伪目标可以依赖于另一个伪目标或者文件,例如:all 依赖于 everything,everything 最终依赖于 file.c file1.c file2.c file3.c file4.c。
虽然我们会发现,everything 下面并没有相关的执行命令,但是下面有个通用规则:“%.o : %.c”。其中的“%”表示通配符,表示所有以“.o”结尾的文件依赖于所有以“.c”结尾的文件。
例如:file.c、file1.c、file2.c、file3.c、file4.c,通过这个通用规则会自动转换为依赖关系:file.o: file.c、file1.o: file1.c、file2.o: file2.c、file3.o: file3.c、file4.o: file4.c。
然后,针对这些依赖关系,分别会执行:$(CC) $(CFLAGS) -o $@ $< 命令,当然最终会转换为:gcc –c –o xxxx.o xxxx.c,这里的“xxxx”表示一个具体的文件名。

编译

下面我们用一张图来描述我们 Hello OS 的编译过程,如下所示:
Hello OS编译过程

安装 Hello OS

经过上述流程,我们就会得到 Hello OS.bin 文件,但是我们还要让 GRUB 能够找到它,才能在计算机启动时加载它。这个过程我们称为安装,不过这里没有写安装程序,得我们手动来做。
经研究发现,GRUB 在启动时会加载一个 grub.cfg 的文本文件,根据其中的内容执行相应的操作,其中一部分内容就是启动项。
GRUB 首先会显示启动项到屏幕,然后让我们选择启动项,最后 GRUB 根据启动项对应的信息,加载 OS 文件到内存。
下面来看看我们 Hello OS 的启动项:
menuentry 'HelloOS' {
insmod part_msdos #GRUB加载分区模块识别分区
insmod ext2 #GRUB加载ext文件系统模块识别ext文件系统
set root='hd0,msdos4' #注意boot目录挂载的分区,这是我机器上的情况
multiboot2 /boot/HelloOS.bin #GRUB以multiboot2协议加载HelloOS.bin
boot #GRUB启动HelloOS.bin
}
如果你不知道你的 boot 目录挂载的分区,可以在 Linux 系统的终端下输入命令:df /boot/,就会得到如下结果:
文件系统 1K-块 已用 可用 已用% 挂载点
/dev/sda4 48752308 8087584 38158536 18% /
其中的“sda4”就是硬盘的第四个分区(硬件分区选择 MBR),但是 GRUB 的 menuentry 中不能写 sda4,而是要写“hd0,msdos4”,这是 GRUB 的命名方式,hd0 表示第一块硬盘,结合起来就是第一块硬盘的第四个分区。
把上面启动项的代码插入到你的 Linux 机器上的 /boot/grub/grub.cfg 文件末尾,然后把 Hello OS.bin 文件复制到 /boot/ 目录下,一定注意这里是追加不是覆盖。最后重启计算机,你就可以看到 Hello OS 的启动选项了。
选择 Hello OS,按下 Enter 键(或者重启按 ESC 键),这样就可以成功启动我们自己的 Hello OS 了。

重点回顾

有没有很开心?我们终于看到我们自己的 OS 运行了,就算它再简单也是我们自己的 OS。下面我们再次回顾下这节课的重点。
首先,我们了解了从按下 PC 机电源开关开始,PC 机的引导过程。它从 CPU 上电,到加载 BIOS 固件,再由 BIOS 固件对计算机进行自检和默认的初始化,并加载 GRUB 引导程序,最后由 GRUB 加载具体的操作系统。
其次,就到了我们这节课最难的部分,即用汇编语言和 C 语言实现我们的 Hello OS。
第一步,用汇编程序初始化 CPU 的寄存器、设置 CPU 的工作模式和栈,最重要的是加入了 GRUB 引导协议头;第二步,切换到 C 语言,用 C 语言写好了主函数和控制显卡输出的函数,其间还了解了显卡的一些工作细节。
最后,就是编译和安装 Hello OS 了。我们用了 make 工具编译整个代码,其实 make 会根据一些规则调用具体的 nasm、gcc、ld 等编译器,然后形成 Hello OS.bin 文件,你把这个文件写复制到 boot 分区,写好 GRUB 启动项,这样就好了。
这里只是上上手,下面我们还会去准备一些别的东西,然后就真正开始了。但你此刻也许还有很多问题没有搞清楚,比如重新加载 GDT、关中断等,先不要担心,我们后面会一一解决的。

思考题

以上 printf 函数定义,其中有个形式参数很奇怪,请你思考下:为什么是“…”形式参数,这个形式参数有什么作用?
欢迎你在留言区分享你的思考或疑问。
我是 LMOS,我们下节课见!
分享给需要的人,Ta购买本课程,你将得20
生成海报并分享

赞 157

提建议

上一篇
01 | 程序的运行过程:从代码到机器运行
下一篇
03 | 黑盒之中有什么:内核结构与设计
unpreview
 写留言

精选留言(254)

  • pedro
    置顶
    2021-05-12
    不想在物理机上搞的同学们,可以参考一下我的 repo。如下: ```sh git clone https://gitee.com/gaopedro/pos cd pos make && make umount_image make qemu ``` 注意,在 linux 操作系统下操作,记得下载 qemu,如: ```sh sudo apt-get install qemu ``` 这样就可以直接在 qemu 上跑出 hello world了~
    展开

    作者回复: 很牛啊

    共 26 条评论
    87
  • 陈诚
    置顶
    2021-05-12
    写了一个关于这节课实验的笔记,VMware + Ubuntu 16.04环境,供大家参考 https://blog.csdn.net/chenchengwudi/article/details/116707122

    作者回复: 666666

    共 15 条评论
    83
  • Strangeen🚁
    置顶
    2021-05-15
    我是小白,只会java,没学过c,当时也只是抱着了解下操作系统的想法报了课程,学到第2课,发现完全听不懂... 不过跟着课程和陈诚的博客走(https://blog.csdn.net/chenchengwudi/article/details/116707122),然后下载了老师的代码,最终居然还是跑出Hello OS!来了,虽然还不太明白汇编、Makefile、hello.lds代码的含义... 看来又多了一份学下去的信心了! 我是在mac pro(os 10.15.6)上使用parallel安装的ubuntu kylin 18.04,这里我要对像我这样的小白说3个点: 1、安装nasm,才能执行make 先安装nasm:sudo apt-get install -y nasm 然后在HelloOS目录下执行:make -f Makefile 就可以得到HelloOS.bin文件了 2、一定要按照陈诚的博客的2.1中配置下启动项,不然无法选择HelloOS菜单 先修改文件:/etc/default/grub 然后执行:sudo update-grub 3、menuentry直接添加到grub.cfg文件的末尾即可
    展开

    作者回复: 你太用功了

    共 18 条评论
    72
  • Vic
    置顶
    2021-05-12
    终于试成功了,超开心! 'Hello OS' 我是使用虚拟机 df /boot/ 显示的是 Filesystem 1K-blocks Used Available Use% Mounted on /dev/vda1 50633164 1340568 49276212 3% / 一直遇到 error : file '/boot/HelloOS.bin' not found 后来看到网上有文章介绍grub rescue的模式, 在grub boot menu里运行command line, 在boot menu按'c'可进到grub的互动模式 grub> grub> ls (hd0, gpt15) (hd0, gpt14) (hd0, gpt1) 可看到多个分区,一个个确认,直到能正确看到/boot/下的档案表示这个分区是正确的分区 不正确的分区 grub> ls (hd0,gpt15)/boot unknown filesystem 一直到能正确显示档案系统 grub> ls (hd0,gtp1)/boot 重新开机到原来linux kernel的选项 编辑 /boot/grub/grub.cfg 以我的虚拟机的设定 set root='hd0, gpt1' 存档,重开机 大功告成!
    展开

    编辑回复: 太赞了,不但自己实战,还分享给大伙经验了,良性循环,666

    共 7 条评论
    72
  • Shinymimiq
    置顶
    2021-05-22
    我测试了一下M1的Mac也是能跑起了HelloOS的,虽然坑比较多。我大致总结了一下步骤: 首先需要安装x86版的homebrew 用Rosetta 打开iTerm 然后安装x86的homebrew `arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"` 接下来是重点, Apple自带的C/C++工具链是clang,外加用的linker和GUN linker不兼容,没办法使用课程中提供的linker script,所以我们需要编译安装x86的gcc工具链,顺便可以把nasm和qemu给装了 ``` /usr/local/bin/brew tap nativeos/i386-elf-toolchain /usr/local/bin/brew install i386-elf-binutils i386-elf-gcc nasm qemu ``` 安装完成之后确认一下我们的gcc和ld ``` /usr/local/Cellar/i386-elf-gcc/9.2.0/bin/i386-elf-gcc /usr/local/Cellar/i386-elf-binutils/2.31/bin/i386-elf-ld ``` 接下来的步骤我参考了Viz的评论,代码是在 `https://github.com/vizv/learn\_os/tree/master/hello-os` 可以手动修改Makefile里面的宏 ``` ASM := /usr/local/Cellar/nasm/2.15.05/bin/nasm CC := /usr/local/Cellar/i386-elf-gcc/9.2.0/bin/i386-elf-gcc LD := /usr/local/Cellar/i386-elf-binutils/2.31/bin/i386-elf-ld ``` 顺带一提,我使用iTerm跑还需要在qemu命令上加上-nographic,不然好像没法退出 ``` qemu-system-x86_64 -nographic -curses -drive 'file=$<,format=raw,index=0,media=disk' ``` 然后直接`make all`就可以运行我们的hello-os了
    展开

    作者回复: 你好,感谢你的分享,m1的同学有福了

    共 6 条评论
    53
  • 胡永
    置顶
    2021-05-13
    补充两个我遇到的坑: 1. 如何确定boot挂载的分区,参考Geek_993581提供的方法,在grub命令行里确认。 set root='hd0,msdos4' #注意boot目录挂载的分区,修改为自己的分区。 我自己的是(hd0,gpt2)。 把insmod part_msdos 修改为 part_gpt。 2. 很多同学遇到说HelloOS.bin找不到 这个就是要确认boot的挂在路径是/还是/boot 文件系统 1K-块 已用 可用 已用% 挂载点 /dev/sda4 48752308 8087584 38158536 18% / 如果跟老师一样,不用修改。 但是如果挂载点是/boot,需要把路径改为multiboot2 /HelloOS.bin 当然你也可以在grub命令行里面确认, 如果ls (hd0,gpt2)/boot 找不到boot目录,就 ls (hd0,gpt2)/,如果能看到HelloOS.bin,说明grub把boot设为root,路径前面不需要加boot了。
    展开

    作者回复: 你是对的 正确

    共 14 条评论
    42
  • Viz
    置顶
    2021-05-15
    我也搞了份不需要 GRUB 可以直接用 QEMU 或者其他虚拟机软件跑的版本 https://github.com/vizv/learn_os/tree/master/hello-os 克隆仓库后在 hello-os 目录里 只编译链接并创建镜像: make hello-os.img 编译链接并创建镜像后用 QEMU 启动磁盘镜像: make
    展开

    编辑回复: 6666,佩服佩服,感谢你的分享!教学相长,期待之后课程里,你还有更多的输出。

    共 9 条评论
    35
  • 喜欢吃鱼
    置顶
    2021-07-13
    实验注意几个关键问题。 - 将作者提供的代码git到本地; - 安装nasm、gcc等工具,否则make不通过。随后执行“make -f Makefile”,得到HelloOs.bin; - 在/boot/grub/grub.cfg中添加如下引导内容: ```shell menuentry 'HelloOS' { insmod part_msdos #GRUB加载分区模块识别分区 insmod ext2 #GRUB加载ext文件系统模块识别ext文件系统 set root='hd0,msdos1' #注意boot目录挂载的分区,这是我机器上的情况 multiboot2 /boot/HelloOS.bin #GRUB以multiboot2协议加载HelloOS.bin boot #GRUB启动HelloOS.bin } ``` - ```she #其中set root='hd0,msdos1'根据/boot挂载的实际情况填写,如我的机器是: ubuntu@ubuntu-virtual-machine:~$ df /boot Filesystem 1K-blocks Used Available Use% Mounted on /dev/sda1 20509264 7482912 11961496 39% / ``` - 将HelloOs.bin拷贝到/boot/目录; - 重启机器,vmvare长按shift进入引导页面,可以看到HelloOS,选择HelloOS进入。
    展开

    作者回复: 对的

    共 2 条评论
    15
  • 第九天魔王
    置顶
    2021-05-13
    实际上boot目录只是一个目录,helloos文件可以放到其它目录,只要把grub.cfg里面相应位置改下就行了。把内核文件放到boot目录是一个既定的做法,目录下面有个vmlinuz-xxx就是Ubuntu的内核镜像。另外grub本身带有ext驱动,所以可以访问文件系统,取出你的内核镜像执行。

    编辑回复: 对的,666!期待你的更多精彩分享~

    15
  • 青玉白露
    置顶
    2021-05-21
    写了一个基于Vmare和Ubuntu16.04的详细教程,只要手头有电脑就可以运行,大家可以看看: https://zhuanlan.zhihu.com/p/373996858

    作者回复: 谢谢

    共 5 条评论
    11
  • Bo
    置顶
    2021-05-13
    在输入df /boot/ 时,要注意下【挂载点】的值是/还是/boot,东哥这里是/;如果是/boot,就要注意在修改/boot/grub/grub.cfg文件时,multiboot2这一项直接填写/HelloOS.bin即可,而不需要在前面加/boot了,因为/boot被划分为单独的分区了,即: multiboot2 /HelloOS.bin #GRUB以multiboot2协议加载HelloOS.bin 此外,还可以参考楼上Geek_993581的回答去验证HelloOS.bin的位置
    展开

    编辑回复: 感谢老铁的优质分享!

    共 2 条评论
    10
  • Eevee
    置顶
    2021-05-13
    将自己遇到的几个小问题总结下,希望能帮助到其他同学(linux新手) 1. 将HelloOs.bin移动到boot文件夹时无权限 解决办法:打开终端,运行命令sudo nautilus就可以打开一个管理员权限的文件管理器 2.df/boot/失效 解决办法:df-h 查看所有文件挂载,找到boot即可 3.nasm安装,参考教程https://blog.csdn.net/qq_44641344/article/details/104355359?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_baidulandingword-0&spm=1001.2101.3001.4242
    展开

    作者回复: 谢谢

    9
  • Freddy
    置顶
    2021-05-24
    我用的是CentOS 7虚拟机,有点不一样: 环境说明: gcc --version gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-44) [root@node01 ~]# cat /etc/redhat-release CentOS Linux release 7.6.1810 (Core) 下面是添加HelloOS启动项的流程: 1.在/etc/grub.d/40_custom文件中,添加HelloOS在grub2中的配置 #!/bin/sh exec tail -n +3 $0 # This file provides an easy way to add custom menu entries. Simply type the # menu entries you want to add after this comment. Be careful not to change # the 'exec tail' line above. #以下是添加内容 menuentry 'HelloOS' { insmod part_msdos #GRUB加载分区模块识别分区 insmod ext2 #GRUB加载ext文件系统模块识别ext文件系统 set root='hd0,msdos1' #注意boot目录挂载的分区,这是我机器上的情况 multiboot2 /HelloOS.bin #GRUB以multiboot2协议加载HelloOS.bin boot #GRUB启动HelloOS.bin } 2.重新生成/boot/grub2/grub.cfg文件 [root@node01 grub2]# grub2-mkconfig -o /boot/grub2/grub.cfg 3.重启VM,即可在grub引导项中看到多了HelloOS
    展开

    作者回复: 你好,你这样操作也是对的

    共 9 条评论
    8
  • royalfarmer
    置顶
    2021-09-25
    也来交个作业 既然是从0到1,建议像我一样的小白用户,全部代码自己动手写一遍,哪怕很多细节不懂,但从整体上会有一个感性认识,有了框架,后面慢慢补充相关知识,这样学的会更加牢固。 那些折腾虚拟机直接copy老师的代码,是高手玩的,需要有足够的基本功才玩的转。小白一定要先练好自己的基本功,手动抄代码是练基本功非常好的方法,看一遍和写一遍感觉完全不一样。 操作系统算是比较难的了,开始学习之前,自己得先有些基本的知识储备。 首先,要对Linux有基本概念,会在物理机上装Linux,知道Linux常用的命令,知道怎么在Linux里安装程序。 说下我的作业过程: 一、个人PC上直接装的Linux,版本是ubuntu。 二、在自己的家目录下新建一个HelloOS文件夹,在里面新建5个文件,文件内容照着老师给的源码全部手抄一遍,一定要仔细,一个字母都不能差: entry.asm (汇编文件,进行一些针对电脑硬件的操作) main.c (我们自己这个操作系统的主函数源代码,里面就一个printf函数,功能是我们这个操作系统运行时显示的那句话) vgastr.c (printf函数的具体实现代码,需要调用到显卡显存) vgastr.h (主函数的头文件) Makefile (make命令编译时遵照的格式文件) install.md (确切的说,这是一段代码,有没有这个文件都行,把代码补充到/boot/grub/grub.cfg 文件的末尾,目的是更新grub 引导程序的配置文件,让grub能找到我们的helloOS,评论里出现最多的找不到文件的问题,基本都是这段代码没写对,更确切说,就是那一句 multiboot2 /boot 或改为 multiboot2 / ) 三、途中可能会遇到几个问题 1 提示没有装nasm, 无法编译汇编文件 .asm,解决方法,直接根据系统提示,输入命令安装该程序 2 提示make不是命令,无法编译,解决方法,直接根据系统提示,输入命令安装该程序 3 源代码抄写错误,都会有提示具体在哪个文件哪一行,写错字符,缺少横杠,字符大小写等(我甚至都没有用编辑器,抄起来确实比较痛苦,但完成后的成就感也更大) 四、补充一个细节,确认完挂载点后,记得把helloOS.bin文件放到挂载点的目录下,要不然,即使grub.cfg配置写对了,仍然会提示找不到文件。 五、有个小问题没搞明白,为啥我生成的helloOS.bin 文件的首字母是小写的?我看别人的截图都是大写的,不过这个貌似不影响系统启动。
    展开

    作者回复: 最后一个问题 看一下代码 可改的

    共 2 条评论
    5
  • 孟祥毅🇨🇳Xiang...
    置顶
    2021-10-21
    P.S. 不想每次改完程序重启看效果的同学可以考虑用qemu来boot kernel,之前有两个同学给了solution,但是好像一个制作引导镜像的步骤有点问题,另一个没用grub,用的自定义的bootloader。我follow老师的课程做了个grub版的(包括怎么制作引导镜像)放在了github上 https://github.com/xymeng16/helloOS,不想深究直接make qemu-grub就可以用,想深究的话脚本也都在repo里面~之后我会把联合gdb调试的脚本也加进去,方便找bug...
    展开

    作者回复: 666666

    3
  • De_sword
    置顶
    2021-09-10
    补充一点对于内核文件路径设定的方案。 尝试过@Vic, @陈诚 , 的方案。 进入过grub模式查看硬盘下的路径,但是仍然遇到error : file '/boot/HelloOS.bin' not found的问题。 因此,我这边参考了grub.cfg里面,“Ubuntu”的设置: menuentry 'Ubuntu' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-simple-c2b35b75-3fa4-4021-b628-65742342045e' {         insmod gzio         insmod part_msdos         insmod ext2         set root='hd0,msdos1'         initrd  /initrd.img-4.4.0-57-generic } 可以发现,默认载入时就已经以/boot为根目录, 因此,我也照葫芦画瓢,没有额外设定/boot目录。 重新启动,就可以成功运行HelloOS内核了。
    展开

    作者回复: 是的 要看看你的boot目录的挂载路径

    2
  • 大鱼Coo
    置顶
    2021-05-12
    从开机选项中看到自己的OS,很有成就感。会一点C和make,gcc方面不熟,nasm和ld需要额外了解。grub.cfg文件的挂载分区,一个参考命令,也可以参考里面已经有的menuentry

    编辑回复: 太优秀了,再接再厉,这里带大家找找感觉。后面讲解更精彩,敬请期待。

    共 3 条评论
    1
  • 不安好心
    置顶
    2021-08-25
    有一个疑问,汇编代码那里: ;重新加载 GDTlgdt [GDT_PTR] jmp dword 0x8 :_32bits_mode 这里明明没有开启保护模式,为什么能这样跳转,而且经验证,也确实使用了gdt。 但是实模式不是cs << 4 + offset进行寻址吗?
    展开

    作者回复: 因为GRUB已经在保护模式下了

  • 超级励辰
    2021-05-27
    0x7C00 由来的一种说法。当时,搭配的操作系统是86-DOS。这个操作系统需要的内存最少是32KB。我们知道,内存地址从0x0000开始编号,32KB的内存就是0x0000~0x7FFF。8088芯片本身需要占用0x0000~0x03FF,用来保存各种中断处理程序的储存位置。(主引导记录本身就是中断信号INT 19h的处理程序。)所以,内存只剩下0x0400~0x7FFF可以使用。为了把尽量多的连续内存留给操作系统,主引导记录就被放到了内存地址的尾部。由于一个扇区是512字节,主引导记录本身也会产生数据,需要另外留出512字节保存(自己 和 产生)。所以,它的预留位置就变成了:     0x7FFF - 512 - 512 + 1 = 0x7C00 
    展开

    作者回复: 哈哈 你是计算机历史学家

    23
  • 陈诚
    2021-05-12
    如果看过王爽老师的《汇编语言》和李忠老师的《X86汇编语言:从实模式到保护模式》,这段的理解将非常简单,建议大家可以配合使用

    作者回复: 目前不需要搞懂 先感受一下,我后面课程也会介绍 的

    共 2 条评论
    13