01
编译四步骤:预处理(gcc -E -o)、编译(gcc -s -c)、汇编(gcc -c)、链接(gcc -o)
02 操作系统实战-实现一个简单的内核:HelloOS
1. 引导流程:
PC机的BIOS(即引导程序)是烧写在主板的ROM芯片上,断电也能保存。
- 当PC机上电后执行的第一条指令就是BIOS固件中的指令,负责检测和初始化CPU、内存和平台;
- 然后加载引导设备中的第一个扇区数据,到0x7c00地址开始的内存空间;
- 接着跳转到0x7c00地址处执行指令(这里即GRUB引导程序)。
2. 引导汇编代码
c语言不能直接操作特定硬件,需要使用汇编准备c语言所需要的环境:栈(C语言的函数调用、函数传参都需要调用栈)。
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
| ;彭东 @ 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
|
3. 主函数
这里的printf,并不是c语言标准输入输出的printf函数,而是我们自己实现的:将字符输出到屏幕上。
| #include "vgastr.h"
int main() { printf("Hello OS!"); return 0; }
|
4. printf函数:vgastr.h vgastr.c
我们需要在屏幕上显示字符,就要编程操作显卡。所有的显卡都支持VESA的标准,这种便准有两种工作模式:
- 字符模式
- 图形模式
我们需要在屏幕上打印字符,则可以着重思考字符模式:
它把屏幕分成 24 行,每行 80 个字符,把这(24*80)个位置映射到以 0xb8000 地址开始的内存中,每两个字节对应一个字符,其中一个字节是字符的 ASCII 码,另一个字节为字符的颜色值。如下图所示:
计算机显卡文本模式明白了显卡的字符模式的工作细节,下面我们开始写代码。
vgastr.h
| void _strwrite(char* str); void printf(char* str, ...);
|
vgastr.c:
| void _strwrite(char* str) { char* p_strdst = (char*)(0x8000); while (*str) { *p_strdst = *str++; p_strdst += 2; } return; }
int printf(char* fmt, ...) { _strwrite(fmt); return 0; }
|
5. 链接脚本:hello.lds
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
|
ENTRY(_start) OUTPUT_ARCH(i386) OUTPUT_FORMAT(elf32-i386) SECTIONS { . = 0x200000; __begin_start_text = .; .start.text : ALIGN(4) { *(.start.text) } __end_start_text = .;
__begin_text = .; .text : ALIGN(4) { *(.text) } __end_text = .;
__begin_data = .; .data : ALIGN(4) { *(.data) } __end_data = .;
__begin_rodata = .; .rodata : ALIGN(4) { *(.rodata) *(.rodata.*) } __end_rodata = .;
__begin_kstrtab = .; .kstrtab : ALIGN(4) { *(.kstrtab) } __end_kstrtab = .;
__begin_bss = .; .bss : ALIGN(4) { *(.bss) } __end_bss = .; }
|
6. 编译和安装HelloOS
1. 编写makefile
make:强大的控制编译过程的工具,读取一个名为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 44 45 46
|
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 #为目标all依赖于伪目标clean build link bin
clean: $(RM) -f *.o *.bin *.elf
build: $(HELLOOS_OBJS) #伪目标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 $@ $<
|
2. 执行make命令
编写好makefile,就可以执行make编译,编译完成后得到HelloOS.bin文件
如果编写的makefile有误,或者缺少编译依赖文件,会报错,根据提示的行数排查问题,修改后重新执行make命令
7. 启动
可以使用虚拟机vmware安装ubuntu,此时默认的引导工具即是grub。
- 编辑ubuntu的grub配置,添加启动项
- 添加后启动虚拟机时有读条则按下SHIFT键即可加载驱动项界面,选择手动加的启动项
1. 编写启动项
把下面的代码复制,然后插入到/boot/grub/grub.cfg文件中
1.
| menuentry 'HelloOS' { insmod part_msdos insmod ext2 set root='hd0,msdos4' #注意boot目录挂载的分区,这是我机器上的情况 multiboot2 /boot/HelloOS.bin boot }
|
2. 复制HelloOS.bin
把HelloOS.bin文件复制到/boot/目录下
3. 重启计算机
重启计算机,当界面读条时,按住shift键,
加载启动项选择界面,选择HelloOS
可以看到HelloOS启动选项了
