操作系统45讲-2
|字数总计:1.3k|阅读时长:6分钟|阅读量:|
main函数调用之前的代码
编写如下的汇编入口代码, entry.asm
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
| ;彭东 @ 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
|
因为 需要使用GRUB引导我们的程序,故需要遵循GRUB标准,需要填充两段引导头。
然后关闭终端,设置CPU工作模式。
初始化所有可能用到的寄存器,为C语言提供运行环境
GDT_START是CPU工作模式需要的数据。
进入C语言main函数
1 2 3 4 5
| #include "vgastr.h" void main(){ printf("Hello OS!"); return; }
|
可以看出,该main函数是由之前的汇编代码所调用,其中使用printf输出字符串,但是由于无操作系统支持,所以printf需要我们自己手工实现
vgastr.h便是我们手工实现printf函数的头文件,vgastr.c为具体实现
计算机的显卡
计算机显卡统一支持vesa标准,该标准制定了显卡的两种工作模式,分别为字符模式与图形模式。
我们把屏幕分成25行,每行80字符,即(80*25)个位置映射到以0xb8000开头的内存中,每个字节两字符,前者为ascii码,后者为颜色控制字节。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void _strwrite(char *str){ char *p_dist = (char*)(0xb8000); while(*str){ *p_dist = *str; p_dist += 2; str++; } return; }
void printf(char *fmt, ...){ _strwrite(fmt); return; }
|
头文件编写如下,
1 2
| void _strwrite(char* string); void printf(char* fmt, ...);
|
编译Hello OS
编写Makefile文件如下,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| MAKEFLAGS = -sR MKDIR = mkdir RMDIR = rmdir CP = cp CD = cd DD = dd RM = rm
ASM = nasm CC = gcc LD = ld OBJCOPY = objcopy
ASMBFLAGS = -f elf -w-orphan-labels CFLAGS = -c -Os -std=c99 -m32 -Wall -Wshadow -W -Wconversion -Wno-sign-conversion -fno-stack-protector -fomit-frame-pointer -fno-builtin -fno-common -ffreestanding -Wno-unused-parameter -Wunused-variable LDFLAGS = -s -static -T hello.lds -n -Map HelloOS.map OJCYFLAGS = -S -O binary
HELLOOS_OBJS := HELLOOS_OBJS += entry.o main.o vgastr.o HELLOOS_ELF = HelloOS.elf HELLOOS_BIN = HelloOS.bin
.PHONY : build clean all link bin
all: clean build link bin
clean: $(RM) -f *.o *.bin *.elf
build: $(HELLOOS_OBJS)
link: $(HELLOOS_ELF) $(HELLOOS_ELF): $(HELLOOS_OBJS) $(LD) $(LDFLAGS) -o $@ $(HELLOOS_OBJS) bin: $(HELLOOS_BIN) $(HELLOOS_BIN): $(HELLOOS_ELF) $(OBJCOPY) $(OJCYFLAGS) $< $@
%.o : %.asm $(ASM) $(ASMBFLAGS) -o $@ $< %.o : %.c $(CC) $(CFLAGS) -o $@ $<
|
使用make -n
命令可以使make只显示命令而不真实执行
1 2 3 4 5 6 7 8 9 10 11
| rm -f *.o *.bin *.elf
nasm -f elf -w-orphan-labels -o entry.o entry.asm
gcc -c -Os -std=c99 -m32 -Wall -Wshadow -W -Wconversion -Wno-sign-conversion -fno-stack-protector -fomit-frame-pointer -fno-builtin -fno-common -ffreestanding -Wno-unused-parameter -Wunused-variable -o main.o main.c
gcc -c -Os -std=c99 -m32 -Wall -Wshadow -W -Wconversion -Wno-sign-conversion -fno-stack-protector -fomit-frame-pointer -fno-builtin -fno-common -ffreestanding -Wno-unused-parameter -Wunused-variable -o vgastr.o vgastr.c
ld -s -static -T hello.lds -n -Map HelloOS.map -o HelloOS.elf entry.o main.o vgastr.o
objcopy -S -O binary HelloOS.elf HelloOS.bin
|
上述命令便可看出这个Makefile的all目标究竟会干什么
-
删除所有以o,bin,elf后缀的文件
-
编译汇编代码
-
编译C代码
-
链接目标文件生成elf文件
-
elf文件转原始binary二进制文件
由于本人暂时不太懂makefile的具体语法,暂时写了如下python代码作为编译脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| import os
def call(cmd): print(cmd) return os.system(cmd)
def clean(): call('rm -f *.o *.bin *.elf')
def build_asm(filename): call(f'nasm -f elf -w-orphan-labels -o {filename}.o {filename}.asm')
def build_c(filename): call(f'gcc -c -Os -std=c99 -m32 -Wall -Wshadow -W -Wconversion -Wno-sign-conversion -fno-stack-protector -fomit-frame-pointer -fno-builtin -fno-common -ffreestanding -Wno-unused-parameter -Wunused-variable -o {filename}.o {filename}.c')
def ld(filenames): file_list = ' '.join(map(lambda x:f'{x}.o',filenames)) call(f'ld -s -static -T hello.lds -n -Map HelloOS.map -o HelloOS.elf {file_list}')
def objcopy(): call('objcopy -S -O binary HelloOS.elf HelloOS.bin')
def build(filenames): def ns_list(): return map(lambda x:x.split('.'),filenames)
for name,suffix in ns_list(): if suffix == 'c': build_c(name) elif suffix == 'asm': build_asm(name) else: print(f'Unsupported file {name}.{suffix}') ld(map(lambda x:x[0],ns_list())) objcopy()
filenames = [ 'entry.asm', 'main.c', 'vgastr.c', ] build(filenames)
|
编译完成后,我们得到了最终的文件HelloOS.bin
安装运行Hello OS
为了方便起见,我们的安装将在虚拟PC中完成,将安装在虚拟硬盘上。
参考这篇博客
创造一块虚拟硬盘 | Zhangzqs
现在我们需要手动让grub引导到我们的HelloOS.bin中。