几行汇编几行C:实现一个最简单的内核
Posted Love and Share
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了几行汇编几行C:实现一个最简单的内核相关的知识,希望对你有一定的参考价值。
1)现有的操作系统都是用什么语言开发出来的?
-
汇编和 C 语言
2)GRUB是什么?
-
叫操作系统起床的。允许我们的计算机上有多个操作系统。要用哪个的时候就把哪个的内核叫醒。
-
帮我来管理操作系统的管家。
3)GRUB哪里能获得呢?
-
Ubuntu Linux 操作系统自带
4)我们要实现一个Hello OS,那Hello OS的引导流程是怎样的?
5)上图中的BIOS是什么?
-
Basic Input Output System
-
保存着计算机最重要的基本输入输出的程序、开机后自检程序和系统自启动程序
-
为计算机提供最底层的、最直接的硬件设置和控制
-
就像计算机的心脏一样
-
它负责检测和初始化 CPU、内存及主板平台,然后加载引导设备(大概率是硬盘)中的第一个扇区数据,到 0x7c00 地址开始的内存空间,再接着跳转到 0x7c00 处执行指令,在我们这里的情况下就是 GRUB 引导程序。
6)为什么写操作系统一上来不能直接用C写?
-
C 作为通用的高级语言,不能直接操作特定的硬件,而且 C 语言的函数调用、函数传参,都需要用栈。
-
所以我们要先用汇编代码处理好这些 C 语言的工作环境。把地基打好。
7)基本环境的汇编代码是怎样的?
-
文件名为entry.asm
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
8)能解释一下上面代码的含义吗?
-
1~40 行定义的 GRUB 的多引导协议头,有两个引导头,是为了兼容 GRUB1 和 GRUB2。
-
44~52 行,关掉中断,设定 CPU 的工作模式。
-
54~73 行,初始化 CPU 的寄存器和 C 语言的运行环境。
-
78~87 行,GDT_START 开始的,是 CPU 干活所需要的数据。
9)上面汇编程序中的mian函数为什么没有函数体?
-
单独放置,引进去就行。
文件名为 main.c
#include "vgastr.h"
void main()
printf("Hello OS!");
return;
注意这个函数并不是应用程序的main函数,而是我们Hello OS操作系统的main函数。一开机启动就能显示hello os。
10)既然说我们上面的函数不是应用程序的函数,那怎么会有应用程序库中的printf?
-
这可不是应用程序中的printf,这个需要我们自己去实现的。
11)现在我们启动的程序有了,接下来该干嘛?
-
控制计算机屏幕
12)如何控制计算机屏幕?
-
编程操作显卡。计算机屏幕显示往往是显卡的输出。
13)显卡有哪些类型?
-
集成在主板的叫集显
-
做在 CPU 芯片内的叫核显
-
独立存在通过 PCIE 接口连接的叫独显
无论我们 PC 上是什么显卡,它们都支持一种叫 VESA 的标准,这种标准下有两种工作模式:字符模式和图形模式。显卡们为了兼容这种标准,不得不自己提供一种叫 VGABIOS 的固件程序。
14)显卡的字符模式是怎样工作的?
-
它把屏幕分成 24 行,每行 80 个字符,把这(24*80)个位置映射到以 0xb8000 地址开始的内存中,每两个字节对应一个字符,一个字节是字符的 ASCII 码,另一个字节为字符的颜色值。
15)知道了显卡的字符工作模式,那我们的控制代码应该怎么写?
-
文件名为vgastr.c
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;
15.1)为什么printf 函数的第二参数是“..."形式?
-
自行查阅资料
16)现在3个文件代码写好了,那么怎么编译 Hello OS?
-
编译每个文件,然后把这三个文件组合起来成可执行的二进制文件。
17)现在我们只有3个文件,那么我们可以手动来编译链接,但是以后系统成熟了,有几万,几十万个文件,那应该怎么办?
-
使用make工具
18)make工具是怎样工作的?
-
它读取一个叫“makefile”的文本文件,这个文件中写好了构建软件的规则,它根据这些规则自动化构建软件。
19)makefile 文件中规则是怎样的?
-
有一个或者多个构建目标称为“target”,目标后面紧跟着用于构建该目标所需要的文件,目标下面是构建该目标所需要的命令及参数。(你媳妇说想吃辣子鸡(目标),买好了三黄鸡(文件),还有一些干辣椒,花椒,鸡精,味精,香油(命令和参数))。
-
与此同时,它也检查文件的依赖关系,如果需要的话,它会调用一些外部软件来完成任务。
-
一次构建目标后,下一次执行 make他能记得上次我构建过了。不用再构建了(昨天不是才吃过辣子鸡吗,怎么今天又要吃,不做了)
20)这个make工具从哪里获取呢?
-
linux系统自带,不用我们操心。
21)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 $@ $<
-
%”表示通配符,表示所有以“.o”结尾的文件依赖于所有以“.c”结尾的文件。
-
a:b表示a依赖于b:例如: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。然后,针对这些依赖关系,分别会执行:(CFLAGS) -o < 命令,当然最终会转换为:gcc –c –o xxxx.o xxxx.c,这里的“xxxx”表示一个具体的文件名。
22)编译的整体图是怎样的?
23)上图中的nasm是啥?
-
汇编程序和计算机之间的翻译官。
24)现在我们编译得到了Hello OS.bin,那接下来我们应该做什么?
-
让 GRUB 能够找到它,在计算机启动时加载它。这个过程就叫安装。
25)那如何才能让GRUB找到他呢?
-
GRUB 在启动时会加载一个 grub.cfg 的文本文件,根据其中的内容执行相应的操作,其中一部分内容就是启动项。
-
GRUB 首先会显示启动项到屏幕,然后让我们选择启动项,最后 GRUB 根据启动项对应的信息,加载 OS 文件到内存。
25.1)我们这个grub.cfg在哪里?
-
/boot/grub/grub.cfg
26)那我们的hello os在grub.cfg文件中启动项应该是怎样的?
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
27)怎么查看你的boot目录挂载的分区呢?
-
输入命令:df /boot/
文件系统 1K-块 已用 可用 已用% 挂载点
/dev/sda4 48752308 8087584 38158536 18% /-
其中的“sda4”就是硬盘的第四个分区(硬件分区选择 MBR),但是 GRUB 的 menuentry 中不能写 sda4,而是要写“hd0,msdos4”,这是 GRUB 的命名方式,hd0 表示第一块硬盘,结合起来就是第一块硬盘的第四个分区。
-
28)一切就绪,整体安装的步骤是怎样的?
-
把上面启动项的代码插入到你的 Linux 机器上的 /boot/grub/grub.cfg 文件末尾,
-
然后把 Hello OS.bin 文件复制到 /boot/ 目录下,一定注意这里是追加不是覆盖。
-
最后重启计算机,你就可以看到 Hello OS 的启动选项了。
选择 Hello OS,按下 Enter 键(或者重启按 ESC 键),这样就可以成功启动我们自己的 Hello OS 了。
29)centos 7的步骤是怎样的?
-
1)把HelloOS目录传到linux的目录下
-
2)在HelloOS目录下执行:make -f Makefile 就可以得到HelloOS.bin文件了
-
3)把 Hello OS.bin 文件复制到 /boot/ 目录下
-
4)在/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
-
5)重新生成/boot/grub2/grub.cfg文件 [root@node01 grub2]# grub2-mkconfig -o /boot/grub2/grub.cfg
-
6)重启VM,即可在grub引导项中看到多了HelloOS
以上是关于几行汇编几行C:实现一个最简单的内核的主要内容,如果未能解决你的问题,请参考以下文章