在前面的章节当中,我们做过很多实验,这些实验全部都放在了 loader.asm 文件中,这么做显然是不合适的。loader 的功能就应该只有加载内核并跳转到内核执行
操作系统整体规划设计
新建 “KOS” 文件夹,从今以后实现的代码都放在这个文件夹中
新建 “bootloader” 文件夹,将我们前面实现过的代码 boot.asm 和 loader.asm 放入其中, make 一下再运行,一切 OK。
import os
os.system("ls -a") # 显示当前目录下的所有文件
python Build.py
; 将硬盘扇区 2 中的数据读入到内存 0x900 处
mov eax, 0x02
mov bx, 0x900
mov cx, 20
call rd_disk_to_mem
; 将硬盘扇区 1 中的数据读入到内存 0x700 处
mov eax, 0x01
mov bx, 0x700
mov cx, 1
call rd_disk_to_mem
; 将硬盘扇区 2 中的数据读入到内存 0x900 处
mov eax, 0x02
mov bx, 0x900
mov cx, [0x700]
call rd_disk_to_mem
python Build.py
ndisasm -o 0x7c00 boot.bin > boot.txt
<bochs:1> b 0x7c4a
<bochs:2> c
...
<bochs:3> xp 0x700
[bochs]:
0x00000700 <bogus+ 0>: 0x00000009
<bochs:4> reg
eax: 0x00000002 2
ecx: 0x00000009 9
edx: 0x000001f0 496
ebx: 0x00000900 2304
esp: 0x00007c00 31744
ebp: 0x00000000 0
esi: 0x00000001 1
edi: 0x00000001 1
eip: 0x00007c4a
eflags 0x00000016: id vip vif ac vm rf nt IOPL=0 of df if tf sf zf AF PF cf
%include "./bootloader/inc.asm"
%include "./bootloader/common.asm"
pushf
pop eax
or eax, 0x3000 ; bit12-bit13:11b
push eax
popf
; jmp dword FLAT_MODE_SELECTOR:KERNEL_START_ADDR
bootloader 的代码优化工作暂时就到这里吧,接下来我们来实现 kernel 部分,等 kernel 实现后再实现 loader 加载并跳转到 kernel
创建 "init" 文件夹并在其中创建 "kentry.asm" 和 "main.c" 两个文件夹
有了前面讲的 C 与汇编混合编程知识,这里就不做详细介绍了
kentry.asm 内容如下:
[section .text]
global _start
extern main
_start:
mov ax, 8 ; 没啥实际意义,仅用于断点调试,便于查看
mov ax, 9 ; 没啥实际意义,仅用于断点调试,便于查看
call main
jmp $
main.c 内容如下:
void main(void)
{
while(1);
}
编译 kentry.asm
nasm -f elf32 kentry.asm -o kentry.o
编译 main.c【注意必须加 '-nostdinc', 不需要使用系统自带的库及头文件】
gcc -m32 -nostdinc -c main.c -o main.o
链接,-Ttext是链接时将初始地址重定向为 0xB000【注意链接时 kentry.o 必须在最前面】
ld -Ttext 0xB000 -s -m elf_i386 -o kernel.out kentry.o main.o
我们最终链接生成可执行程序 kernel.out,但是这个程序是 linux 使用的 elf 格式的,不能直接加载进内存执行,CPU 只认识代码和数据,无法正确执行 elf 可执行程序。于是我们需要提取 elf 中的代码段和数据段(删除 elf 文件格式信息)
objcopy -O binary kernel.out kernel.bin
接下来使用 dd 命令将 kernel.bin 写入 a.img 中
为了自动化,我们把 kernel.bin 所占的扇区数存入 a.img 扇区 1 的第 3-4 个字节中(共 2 个字节)。kernel.bin 数据接着写到 loader.bin 数据的下一个扇区即可
原先创建虚拟硬盘固定大小 60M,现在也改成自动计算
改动后的 Build.py
有了 kernel.bin 程序, loader 就可以加载并跳转到 kernel 了
; 将硬盘扇区 1 中的数据读入到内存 0x700 处
mov eax, 0x01
mov bx, 0x700
mov cx, 1
call rd_disk_to_mem
; 将硬盘扇中 kernel 数据读入到内存 0xB000 处
mov eax, 0
mov ax, [0x700] ; 0x700 处存的是 loader.bin 所占的扇区数,再 +2 找到 kernel 起始扇区
add ax, 2
mov bx, KERNEL_START_ADDR
mov cx, [0x702]
call rd_disk_to_mem
编译运行一下,发现程序崩溃了,查找了半天,原来是平坦模式段描述符要具有可执行属性
改完以后以为可以了,结果还是崩溃,原来是读扇区 1 到内存 0x700 与栈使用的内存冲突了,我们也可以使用 boot 时使用的栈空间
; mov sp, 0x900 ; 设置栈
mov sp, 0x7c00 ; 设置栈
loader 最终程序:loader.asm
断点调试,验证一下
<bochs:1> b 0xb000
<bochs:2> c
...
(0) [0x0000b000] 0010:0000b000 (unk. ctxt): mov ax, 0x0008 ; 66b80800
<bochs:3> s
Next at t=16770437
(0) [0x0000b004] 0010:0000b004 (unk. ctxt): mov ax, 0x0009 ; 66b80900
在 kernel 入口地址 0xb000 处打断点,单步执行,从调试信息看其执行的汇编指令就是我们在 kentry.asm 中实现的汇编代码
辛苦了那么久,程序终于走到了 main
最后让我们欣赏一下当前程序运行效果
#ifndef __COMMON_H_
#define __COMMON_H_
typedef unsigned char U08;
typedef unsigned short U16;
typedef unsigned int U32;
typedef char S08;
typedef short S16;
typedef int S32;
#endif
#include <common.h>
S32 main(void)
{
while(1);
return 0;
}
gcc -Iinclude_dir1 -Iinclude_dir2 -c main.c -o main.o
KOS
|--- BUILD.json
|--- include
| |--- common.h
|--- bootloader
| |--- BUILD.json
| |--- boot.asm
| |--- common.asm
| |--- inc.asm
| |--- loader.asm
|--- init
| |--- BUILD.json
| |--- kentry.asm
| |--- main.c
{
"dir" : [
"bootloader",
"init"
],
"src" : [
],
"inc" : [
]
}
{
"dir" : [
],
"src" : [
"boot.asm",
"loader.asm"
],
"inc" : [
]
}
{
"dir" : [
],
"src" : [
"kentry.asm",
"main.c"
],
"inc" : [
"include"
]
}
def Parse_BUILD_CFG(path):
JsonPathName = os.path.join(path, BUILD_CFG)
# 以只读方式打开文件 JsonPathName
with open(JsonPathName, 'r') as f:
# 读取文件内容到 json_text
json_text = f.read()
# 将 json_text 内容转为 python 字典 json_dict
json_dict = json.loads(json_text)
# 遍历字典 json_dict
for key in json_dict:
if key == 'src': # 如果是源文件,则将 '源文件文件名':['源文件所在路径', ['源文件所包含的头文件路径1', '源文件所包含的头文件路径1']]
# 这种形式的数据添加到字典 project 中
if json_dict[key]:
for item in json_dict[key]:
project[item] = [path, json_dict['inc']]
elif key == 'dir': # 如果是文件夹,则进入该文件夹内,递归调用 Parse_BUILD_CFG
if json_dict[key]:
for item in json_dict[key]:
new_path = os.path.join(path, item)
Parse_BUILD_CFG(new_path)
elif key == 'inc': # 如果是头文件路径,则不处理
pass
else:
print("Invalid key")
return project
root_path = ''
project = Parse_BUILD_CFG(root_path)
# 打印 project
for item in project:
str_print = item + ': ' + str(project[item])
print(str_print)
boot.asm: ['bootloader', []]
loader.asm: ['bootloader', []]
kentry.asm: ['init', ['include']]
main.c: ['init', ['include']]
; %include "./bootloader/inc.asm"
; %include "./bootloader/common.asm"
%include "inc.asm"
%include "common.asm"
...
.go_on_read:
in ax, dx
mov [bx], ax
add bx, 2
loop .go_on_read
...
mov bx, KERNEL_START_ADDR ; 0xB000
mov cx, [0x702]
call rd_disk_to_mem
; 将硬盘扇中 kernel 数据读入到内存 0xB000 处
mov eax, 0
mov ax, [0x700] ; 0x700 处存的是 loader.bin 所占的扇区数,再 +2 找打 kernel 起始扇区
add ax, 2
mov cx, [0x702]
mov dx, 0xb00
mov ds, dx
mov bx, 0x0
call rd_disk_to_mem
; 需恢复 ds=0, 下面的程序需要 ds 为 0
mov dx, 0x0
mov ds, dx
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。