一个操作系统的实现:第五篇——内核雏形

汇编和C同步使用说明:

1、汇编使用关键字global导出函数
2、导入使用关键字extern声明函数
3、遵循C调用约定(C Calling Convention),后面的参数先入栈,并由调用者(Caller)清理堆栈。

编译链接方法:
(ld的 '-s' 选项意为"stripall")
nasm -f elf foo.asm -o foo.o
gcc -c bar.c -o bar.o
ld -s hello.o bar.o -o foobar

; foo.asm
; 编译链接方法
; (ld 的‘-s’选项意为“strip all”)
;
; $ nasm -f elf foo.asm -o foo.o
; $ gcc -c bar.c -o bar.o
; $ ld -s hello.o bar.o -o foobar
; $ ./foobar
; the 2nd one
; $

extern choose	; int choose(int a, int b);

[section .data]	; 数据在此

num1st		dd	3
num2nd		dd	4

[section .text]	; 代码在此

global _start	; 我们必须导出 _start 这个入口,以便让链接器识别
global myprint	; 导出这个函数为了让 bar.c 使用

_start:
	push	dword [num2nd]	; `.
	push	dword [num1st]	;  |
	call	choose		;  | choose(num1st, num2nd);
	add	esp, 8		; /

	mov	ebx, 0
	mov	eax, 1		; sys_exit
	int	0x80		; 系统调用

; void myprint(char* msg, int len)
myprint:
	mov	edx, [esp + 8]	; len
	mov	ecx, [esp + 4]	; msg
	mov	ebx, 1
	mov	eax, 4		; sys_write
	int	0x80		; 系统调用
	ret
	
/* bar.c */
void myprint(char* msg, int len);

int choose(int a, int b)
{
	if(a >= b){
		myprint("the 1st one\n", 13);
	}
	else{
		myprint("the 2nd one\n", 13);
	}

	return 0;
}

ELF (Executable and Linkable Format):

LF文件由4部分组成,分别是ELF头(ELFheader)、程序头表(Program headertable)、节(Sections)和节头表(Sectionheadertable)。结构图如下:

一个操作系统的实现:第五篇——内核雏形_第1张图片

一个操作系统的实现:第五篇——内核雏形_第2张图片

一个操作系统的实现:第五篇——内核雏形_第3张图片

开头的4字节是固定不变的,第1个字节值为0x7F,紧跟着就是ELF三个字符,这4字节表明这个文件是个ELF文件。
下面,我们就以foobar为例说明ELFheader中各项的含义:
e_type 它标识的是该文件的类型,可能的取值在这里就不一一列出了。文件foobar的e_type是2,表明它是一个可执行文件
(ExecutableFile)。
e_machine foobar中此项的值为3,表明运行该程序需要的体系结构为Intel80386。
e_version这个成员确定文件的版本。
e_entry 程序的入口地址。文件foobar的入口地址为0x80480A0。
e_phoff Programheader table在文件中的偏移量(以字节计数)。这里的值是0x34。
e_shoff Section header table在文件中的偏移量(以字节计数)。这里的值是0x1C0。
e_flags 对IA32而言,此项为0。
e_ehsize ELFheader大小(以字节计数)。这里值为0x34。
e_phentsize Programheader table中每一个条目(一个Programheader)的大小。这里值为0x20。
e_phnumProgramheader table中有多少个条目,这里有3个。
e_shentsize Section header table中每一个条目(一个Sectionheader)的大小,这里值为0x28。
e_shnumSection header table中有多少个条目,这里有6个。
e_shstrndx 包含节名称的字符串表是第几个节(从零开始数)。这里值为5,表示第5个节包含节名称。

二进制查看工具:xxd -u -a -g 1 -c 16 -l 80 foobar

一个操作系统的实现:第五篇——内核雏形_第4张图片

Programheader数据结构如下:

一个操作系统的实现:第五篇——内核雏形_第5张图片

其中各项的意义如下:
p_type 当前Programheader所描述的段的类型。
p_offset 段的第一个字节在文件中的偏移。
p_vaddr段的第一个字节在内存中的虚拟地址。
p_paddr在物理地址定位相关的系统中,此项是为物理地址保留。
p_filesz段在文件中的长度。
p_memsz段在内存中的长度。
p_flags 与段相关的标志。
p_align根据此项值来确定段在文件以及内存中如何对齐。

内存分配图:

一个操作系统的实现:第五篇——内核雏形_第6张图片

0x90000开始的63KB留给了Loader.bin,0x80000开始的64KB留给了Kernel.bin,0x30000开始的320KB留给整理后的
内核,而页目录和页表被放置在了1MB以上的内存空间。

进入内核时寄存器情况示意图:

一个操作系统的实现:第五篇——内核雏形_第7张图片

Makefile:

# Makefile for boot
# Programs, flags, etc.
ASM = nasm
ASMFLAGS = -I include/
# This Program
TARGET = boot.bin loader.bin
# All Phony Targets
.PHONY : everything clean all
# Default starting position
everything : $(TARGET)
clean :
rm -f $(TARGET)
all : clean everything
boot.bin : boot.asm include/load.inc include/fat12hdr.inc
$(ASM) $(ASMFLAGS) -o $@ $<
loader.bin : loader.asm include/load.inc include/fat12hdr.inc include/pm.inc
$(ASM) $(ASMFLAGS) -o $@ $<

以字符#开头的行是注释。=用来定义变量,这里,ASM和ASMFLAGS就是两个变量,要注意的是,使用它们的时候要用$(ASM)和$(ASMFLAGS),而不是它们的原型。其实,明白了这两点,Makefile你就已经明白了一半。.PHONY这个关键字我们暂时不管,来看一下Makefile的最重要的语法:
target : prerequisites
command
上面这样的形式代表两层意思:
1. 要想得到target,需要执行命令command。
2. target 依赖prerequisites,当prerequisites中至少有一个文件比target文件新时,command才被执行。
那么“$(ASM) $(ASMFLAGS) -o $@ $<”又是什么呢?其实$@和$<意义如下:
$@代表target;
$<代表prerequisites 的第一个名字。
在Makefile 中我们容易注意到,不但boot.bin和loader.bin两个文件后面有冒号,everything、clean和all后面也有冒号,可是它们3个并不是3个文件,仅仅是动作名称而已。如果运行“makeclean”,将会执行“rm-f $(TARGET)”,也即“rm-f boot.bin loader.bin”。
all后面跟着的是clean和everything,这表明如果执行“makeall”,clean和everything所表示的动作将分别被执行。下面就是makeall执行的结果:
▹ make all
rm -f boot.bin loader.bin
nasm -I include/ -o boot.bin boot.asm
nasm -I include/ -o loader.bin loader.asm
刚才被我们忽略过去的关键字.PHONY,其实是表示它后面的名字并不是文件,而仅仅是一种行为的标号。

中断和异常:
链接:80386的中断和异常https://www.cnblogs.com/vicrobert/p/4162538.html

 

你可能感兴趣的:(一个操作系统的实现:第五篇——内核雏形)