VV的操作系统笔记(一)操作系统I SeeYou!!!!

注:与本系列博客同时同步的还有后面需要学习和研究的FreeRTOS和linux0.11-linux1.0内核代码VV的Linux操作系统内核笔记系列,即使笔者已经自己写了个操作系统了,但是为了能够使博客能读懂,笔者需要把每一个lab和代码打出来做出解释同时笔者也有自己繁重的学习和工作(本科狗),所以进度会非常非常慢

准备工作

Ubuntu16.04-i386 32位操作系统镜像

话不多说,迅雷下载下载地址

  1. 安装镜像到到虚拟机

安装过程不多赘述,安装完成如图所示:

  1. 安装ubuntu的一些软件和包
apt-get install docker docker.io docker-compose qemu virtualbox 
  1. 安装IDE

Eclipse的CDT原生支持Makefile工程,而且虚拟机内存占用较小,所以这里我们就用Eclipse CDT注意是32位的Eclipse表问我为啥安装Eclipse Indigo这种老玩意,因为最新版本的EC并不支持32位。

在我们编写内核的过程中,我们使用GRUB来启动我们的内核。 至于为什么用GRUB,因为它可以设置多系统共存,这样的话你就可以打包多个系统内核同时存在并且启动的镜像文件。

操作系统启动流程

为了直观和形象,我们直接上图

  • BIOS(Basic Input/Output System),基本输入输出系统,该系统存储于主板的ROM芯片上,计算机在开机时,会最先读取该系统,然后会有一个加电自检过程,这个过程其实就是检查CPU和内存,计算机最基本的组成单元(控制器、运算器和存储器),还会检查其他硬件,若没有异常就开始加载BIOS程序到内存当中。详细的BIOS功能,这边就不说了,BIOS主要的一个功能就是存储了磁盘的启动顺序,BIOS会按照启动顺序去查找第一个磁盘头的MBR信息,并加载和执行MBR中的Bootloader程序,若第一个磁盘不存在MBR,则会继续查找第二个磁盘(PS:启动顺序可以在BIOS的界面中进行设置),一旦BootLoader程序被检测并加载内存中,BIOS就将控制权交接给了BootLoader程序。
  • MBR(Master Boot Record),主引导记录,MBR存储于磁盘的头部,大小为512bytes,其中,446bytes用于存储BootLoader程序,64bytes用于存储分区表信息,最后2bytes用于MBR的有效性检查。
  • GRUB(Grand Unified Bootloader),多系统启动程序,其执行过程可分为三个步骤:
    • Stage1:这个其实就是MBR,它的主要工作就是查找并加载第二段Bootloader程序(stage2),但系统在没启动时,MBR根本找不到文件系统,也就找不到stage2所存放的位置,因此,就有了stage1_5
    • Stage1_5:该步骤就是为了识别文件系统
    • Stage2:GRUB程序会根据/boot/grub/grub.conf文件查找Kernel的信息,然后开始加载Kernel程序,当Kernel程序被检测并在加载到内存中,GRUB就将控制权交接给了Kernel程序。

注意!现代操作系统使用了UEFI启动,但是我们现在不说UEFI,请自行忽略

但是这样也需要我们的Boot程序按照Mutileboot 规范来编译内核,才可以被GRUB引导。 按照Mutileboot规范,内核必须在起始的8KB中的(512字节)包含这一个多引导项头(Multiboot header)。 而且,这个多引导项头里面必须有3个4字节对齐的块。

一个魔术块:包含了魔数[0x1BADB002],是多引导项头结构的定义值。
一个标志块:我们不关心这个块的内容,我们简单设定为0。
一个校检块:校检块,魔术块和标志块的数值的总和必须是0。

我的内核启动代码如下: boot.s

.set MAGIC, 0x1badb002;GRUB魔术块
.set FLAGS, (1<<0 | 1<<1);GRUB标志块
.set CHECKSUM, -(MAGIC + FLAGS);校验块

.section .multboot
	.long MAGIC
	.long FLAGS
	.long CHECKSUM
.section .text
.extern kernel_main;导入kernel_main
.extern system_constructors;导入系统构造函数
.global laoder

loader:
	mov $kernel_stack, %esp
	call system_constructors
	push %eax
	push %ebx
	call kernel_main

stop:
	cli
	hlt
	jmp stop

.section .bss
.space 2*1024*1024
kernel_stack:

一些code解释:

  • CLI:将IF置0,屏蔽掉“可屏蔽中断”,当可屏蔽中断到来时CPU不响应,继续执行原指令
  • STI:将IF置1,允许“可屏蔽中断”,中断到来转而处理中断
  • HLT:本指令是处理器“暂停”指令。
  • JMP:命令跳转指令
  • .global .global 用来让一个符号对链接器可见,可以供其他链接对象模块使用。 .global boot 让_start符号成为可见的标示符,这样链接器就知道跳转到程序中的什么地方并开始执行。linux寻找这个 bootbootbootstart标签作为程序的默认进入点。 在汇编和C混合编程中,汇编程序中要使用.global伪操作声明汇编程序为全局的函数,意即可被外部函数调用,同时C程序中要使用extern声明要调用的汇编语言程序。
  • .extern .extern XXXX 说明xxxx为外部函数,调用的时候可以遍访所有文件找到该函数并且使用它。
  • .long MAGIG .long指示声明变量占用空间,占32位
  • .set 给一个全局变量或局部变量赋值

现在建立符号链接来Link我们的所有object文件 linker.ld

ENTRY(boot)
OUTPUT_FORMAT(elf32-i386)
OUTPUT_ARCH(i386:i386)
SECTIONS {
	. = 0x0100000;
	.text :{
		*(.muiltboot)
		*(.text*)
		*(.rodata)
	}
	.data :
	{
		start_ctors = .;
		KEEP(*(.init_array ));
		KEEP(*(SORT_BY_INIT_PRIORITY( .init_array.* )));
		end_ctors = .;
		
		*(.data)
	}
	.bss :
	{
		*(.bss)
	}
	/DISCARD/ : {
		*(.fini_array*) *(.comment)
	}
}

Makefile 没什么好说的,Makefile负责C/C++的 编译依赖过程

GCCPARAMS = -m32 -W -fno-use-cxa-atexit -nostdlib -fno-builtin -fno-builtin -fno-rtti -fno-exceptions -fno-leading-underscore
ASPPARAMS = --32
LDPARAMS = -melf_i386
GCC = g++
ASM = as
LINKER = ld
CFLAGS = -o $@ -c $<
ASMFLAGS = -o $@ $<
LINKERFLAGS = -T $< -o $@ 

objects = boot.o kernel.o

%.o: %.c
	$(GCC) $(GCCPARAMS) $(CFLAGS)
	
%.o: %.s
	$(ASM) $(ASPPARAMS) $(ASMFLAGS)
	
kernel_lab.bin: linker.ld $(objects)
	$(LINKER) $(LINKERFLAGS) $(objects)
	
all: kernel_lab.bin
	echo "build successed"
clean: 
	rm -rf *.o
	rm -rf *.out
	rm -rf iso
	rm -rf *.iso
	rm -rf *.bin
	
rebuild: clean all
	echo "rebuild"
	
install: kernel_lab.bin
	sudo cp $< /boot/kernel_lab.bin

kernel_lab.iso: rebuild
	mkdir iso
	mkdir iso/boot
	mkdir iso/boot/grub
	cp kernel_lab.bin iso/boot/
	cp boot/grub.cfg iso/boot/grub/grub.cfg
	grub-mkrescue -o $@ iso
	rm -rf iso
	
kernel_vm: kernel_lab.iso
	(killall virtualbox) || true
	virtualbox -startvm "kernel_lab" &

下面是操作系统的主要程序,我们由C++编写,用extern "C"导出我们的函数符号 kernel.cpp

#include "kernel.h"
//因为我们的操作系统没有TTY IO,所以我们需要重新写一个printf函数
extern "C" void  printf(char *str){
	u_short *monitor_io_memory=(u_short *)0xb8000;//注意!重点来啦!0xb8000内存地址是显示器地址,往这里写数据就直接能够输出到屏幕上
	for(int i=0;str[i]!='\0';++i){
		//写入字符串,取或0xff00的意思是我们需要把屏幕高四位拉低,否则就是黑色的字体,黑色的字体黑色的屏幕是啥也看不到的
		monitor_io_memory[i]=(monitor_io_memory[i] & 0xff00) | str[i];
	}
}
//操作系统构造函数委托方法
typedef void(*constructor)();
//全局定义构造委托
constructor start_ctors;
//全局定义析构委托
constructor end_ctors;

//轮询函数,并且执行
extern "C" void system_constructors(){
	for(constructor* i=&start_ctors;i!=&end_ctors;i+=1){
		(*i)();
	}
}
//操作系统主启动函数,这里我们打印一个字符串然后让操作系统进入等待
extern "C" void  kernel_main(const void *multiboot_structure,u_int magicnumber){
	printf("Hello Pulsar-V");
	while(1);
}

别忘了/boot/下的grub.cfg文件,这个是GRUB的配置文件,负责在启动器中列出我们需要启动的内核列表 grub.cfg

set timeout=10 #超时时间
set default=0 #默认启动项
menuentry "PulsarV's OS"	{
	multiboot /boot/kernel_lab.bin
	boot
}

现在,来看看我们的工程目录结构

在Ubuntu16.04的grub生成.iso镜像文件的时候如果出现

grub-mkrescue: warning: Your xorriso doesn't support `--grub2-boot-info'. Some features are disabled

的时候,就通过

sudo apt-get install xorriso

来安装好你的xorriso 然后通过

grub-mkrescue -o kernel_lab.iso iso

来打包我们的操作内核 接下来要做的事情就是控制台下输入编译命令先运行一下最基本的kernel

make kernel_vm

现在是show time! 首先看到的是我们的GRUB启动界面

接下来按下回车或者等待10s

大功告成,现在可以看见了我们的操作系统的Hello World了。

后记

如果启动失败了,就用压缩文件(Ubuntu归档管理器)的形式打开iso文件,检查你的目录结构和GRUB Config

参考文档: [Grub配置文件]http://www.jinbuguo.com/linux/grub.cfg.html [MIT公开课]https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-828-operating-system-engineering-fall-2012/ [计算机操作系统第四版]

(adsbygoogle = window.adsbygoogle || []).push({});

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券