8 Day:向内核迈进--加载内核

前言:

经过了前几天汇编代码的洗刷,相信大家肯定已经看的有点头疼了,越往后面走,我们需要写的东西就越多,显然汇编代码对于我们来说还是太不方便了。于是从今天起我们换一把武器,那就是使用世界上最好的语言---C语言,来继续编写内核并加载,废话不多说我们开始吧

本日参考资料:

《操作系统真象还原》

 elf详解:ELF文件详解


本日需知:

文中不会详细介绍elf,因为实在是太多了,有点偏离主题,如果了解的就可以不用看了,没有了解的建议还是去看看,链接已给出。


一,用C语言编译内核

大家看到可以用C语言写内核,肯定都会大喊一声好耶!但是不要高兴的太早,即使你能用C语言但是中间有许多标准库你是无法使用的,在0 Day的时候我们有说过,大多数操作都是有标准库帮你做好了,而标准库里面包含了许多对操作系统的调用,我们是写操作系统的所以肯定就无法使用了。

C语言与汇编

我在这里也不想讲太多关于编译,链接方面生硬难懂的东西,C语言是高级语言,汇编可以认为是语言层面的原子不可再分。

平常我们用汇编写代码,就好像我们是司机,你需要控离合,拉手刹,打转向灯,踩油门等等。但是用C语言就相当于我们是乘客,你只需要告诉司机,我要去五一广场,司机就会开车送你去,而你无需去关注油门啊,离合方面的事情。

那我们整个内核的代码是这样的

int main(void) {
	while (1) {
		
	}
	return 0;
}

我给他取名叫 main.c 并存放在我操作系统项目下的kernel文件下。

首先我们先使用gcc编译器讲main.c文件编译为main.o 重定向文件(还未给文件里的符号安排地址)

gcc -c -o main.o main.c

用 nm 命令我们可以看到main.o 文件中的main函数地址确实未被指定

②  然后我们用ld命令来链接程序

ld main.o -Ttext 0xc0001500 -e main -o kernel.bin
# -Ttext指定虚拟地址 0xc0001500是事先设计好的

# -e 指定程序起始地址 main入口

可执行文件和重定向文件都是elf文件,xxd可以查看elf文件,但是参数过多我编写了一个脚本xxd.sh

#!/bin/bash
xxd -u -a -g l -s $2 -l $3 $1
sudo bash  tool/xxd.sh kernel/kernel.bin 0 300

 暂且用不到

③ 接下来要将kernel.bin写入磁盘中

看到这里,我们的loader选手的旅程也到此结束了,他将他火炬传给下一位选手也就是我们的kernel。今后也很难在看到他了5555😭。可是现在不是伤感的时候,这让我想起一句话,“世界山最快乐的人就是从不回头看的人”,我们先着手于现在,来看看kernel要放哪吧


我们的loader.bin目前占了3个扇区,他是被存放在2号扇区,所以2~4我们都不能使用,要使用2~4之后的扇区

#!/bin/bash
#rm -rf /usr/geniux/img/geniusos.img
nasm -I ./include/ -o mbr.bin mbr.S
dd if=mbr.bin of=../geniusos.img bs=512 count=1 conv=notrunc
echo "disk write success!!"
nasm -I ./include/ -o loader.bin loader.S
dd if=loader.bin of=../geniusos.img bs=512 count=3 seek=2 conv=notrunc
gcc -c -o ./kernel/main.o ./kernel/main.c
ld ./kernel/main.o -Ttext 0xc0001500 -e main -o ./kernel/kernel.bin
dd if=./kernel/kernel.bin of=../geniusos.img bs=512 count=200 seek=6 conv=notrunc

④ 重新编写loader


   jmp  dword SELECTOR_CODE:p_mode_start	     ; 刷新流水线,避免分支预测的影响,这种cpu优化策略,最怕jmp跳转,
					     ; 这将导致之前做的预测失效,从而起到了刷新的作用。

[bits 32]
p_mode_start:
   mov ax, SELECTOR_DATA
   mov ds, ax
   mov es, ax
   mov ss, ax
   mov esp,LOADER_STACK_TOP
   mov ax, SELECTOR_VIDEO
   mov gs, ax

   ;----将kernel从硬盘中读出并加载到内存中----
   mov eax,KERNEL_START_SECTOR
   mov ebx,KERNEL_BIN_BASE_ADDR

   mov ecx,200
   call rd_disk_m_32

   call setup_page

   sgdt [gdt_ptr] ;由于启用分页,所以将gdt的地址和偏移地址暂且存放在内存gdt_ptr位置上
   mov ebx,[gdt_ptr+2]; gdt_ptr前2字节为偏移地址,后4字节为基址,得到gdt基址
   
   or dword [ebx+0x18+4],0xc0000000 ;由于显存将放到内核态所以其基址将改变要给其或上内核虚拟基址0xc0000000,显存是第三个段描述符,一个段描述符8字节,所以是0x18,4代表是段基址的最高位
   add dword [gdt_ptr +2],0xc0000000

   add esp,0xc0000000

   ;把页目录地址赋给cr3
   mov eax,PAGE_DIR_TABLE_POS
   mov cr3,eax

   ;打开cr0的pg位(31w位)
   mov eax,cr0
   or dword eax,0x80000000
   mov cr0,eax

   lgdt [gdt_ptr]

   jmp SELECTOR_CODE:enter_kernel



   jmp $


;.....略.....


rd_disk_m_32:
	
	mov esi,eax 
	mov di,cx

	mov dx,0x1f2
	mov al,cl
	out dx,al
	
	in al,dx

	mov eax,esi

	mov dx,0x1f3
	out dx,al

	
	mov cl,8
	shr eax,cl
	mov dx,0x1f4
	out dx,al

	
	shr eax,cl
	mov dx,0x1f5
	out dx,al

	
	shr eax,cl
	and al,0x0f
	or al,0xe0 
	mov dx,0x1f6
	out dx,al

	mov dx,0x1f7
	mov al,0x20
	out dx,al

not_ready:
	nop         
	in al,dx
	and al,0x88
	cmp al,0x08
	jnz not_ready

	mov ax,di
	mov dx,256
	mul dx
	mov cx,ax
	mov dx,0x1f0

go_read:
	in ax,dx  
	mov [ebx],ax
	add	ebx,2
	loop go_read
	ret

kernel_init:
	xor eax,eax
	xor ebx,ebx
	xor ecx,ecx
	xor edx,edx
	mov dx,[KERNEL_BIN_BASE_ADDR+42] ;elf中属性是e_phentsize,是header的大小
	mov ebx,[KERNEL_BIN_BASE_ADDR+28] ;elf中属性是e_phoff,第一个program header在文件中的偏移量
	add ebx,KERNEL_BIN_BASE_ADDR

	mov cx,[KERNEL_BIN_BASE_ADDR + 44] ;elf中的属性是e_phnum,表示有几个program header

.each_segment:
	cmp byte [ebx+0],PT_NULL
	je .PTNULL
	push dword [ebx+16] ;压入的size
	mov eax,[ebx+4] ;偏移地址
	add eax,KERNEL_BIN_BASE_ADDR ;该header的物理地址
	push eax
	push dword [ebx+8] ;压入vaddr虚拟地址
	call mem_cpy
	add esp,12

.PTNULL:
	add ebx,edx
	loop .each_segment
	ret

mem_cpy: ;(dest,src,size)
	cld				;此指令将elfags寄存器的方向标志位DF置为0,不断往高地址变化,不断递增esi和edi,因为要不断搬运,不可能搬运的时候地址不变
	push ebp		;备份ebp
	mov ebp,esp    
	push ecx		;备份ecx,双层循环要保存cx状态
	mov edi,[ebp+8]  
	mov esi,[ebp+12]
	mov ecx,[ebp+16]

	rep movsb ;代表搬运1字节,默认esi为源地址,edi为目的地址,cx为次数

	pop ecx
	pop ebp
	ret

enter_kernel:
	call kernel_init
	mov esp,0xc009f000
	jmp KERNEL_ENTRY_POINT

boot.inc

;kernel
KERNEL_START_SECTOR equ 0x6
KERNEL_BIN_BASE_ADDR equ 0x70000
KERNEL_ENTRY_POINT 0xc0001500
PT_NULL equ 0

补档:

中间有一段讲内核特权级的内容,个人感觉十分有用,但是由于实在是太多,我会每天更新一部分,同时不拖沓后面的开发任务,就这样回见捏!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值