前言:
经过了前几天汇编代码的洗刷,相信大家肯定已经看的有点头疼了,越往后面走,我们需要写的东西就越多,显然汇编代码对于我们来说还是太不方便了。于是从今天起我们换一把武器,那就是使用世界上最好的语言---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
补档:
中间有一段讲内核特权级的内容,个人感觉十分有用,但是由于实在是太多,我会每天更新一部分,同时不拖沓后面的开发任务,就这样回见捏!