问题
当前的设计中,内核与应用之间的界限是否清楚?
D.T.OS 当前架构图
重新架构
系统启动流程回顾
重构方案
将应用代码和内核代码分离 (app & kernel)
在实模式下分别加载应用和内核到不同内存区域
进入保护模式并跳转到内核代码执行
D.T.OS 内存布局
关键问题
如何分离应用和内核?他们之间又如何交互?
如何加载应用到指定的内存区域(0xF000)?
具体实现
1. 修改 LoadTarget 函数,用参数替代全局变量
2. 创建 app 入口文件 aentry.asm (定义入口函数)
3. 修改 makefile 分开编译 kernel 和 app
4. 修改 task.c 通过共享内存区获取 app 接口
内核与应用的物理分离
blfunc.asm
jmp short _start
nop
header:
BS_OEMName db "D.T.Soft"
BPB_BytsPerSec dw 512
BPB_SecPerClus db 1
BPB_RsvdSecCnt dw 1
BPB_NumFATs db 2
BPB_RootEntCnt dw 224
BPB_TotSec16 dw 2880
BPB_Media db 0xF0
BPB_FATSz16 dw 9
BPB_SecPerTrk dw 18
BPB_NumHeads dw 2
BPB_HiddSec dd 0
BPB_TotSec32 dd 0
BS_DrvNum db 0
BS_Reserved1 db 0
BS_BootSig db 0x29
BS_VolID dd 0
BS_VolLab db "D.T.OS-0.01"
BS_FileSysType db "FAT12 "
const:
RootEntryOffset equ 19
RootEntryLength equ 14
SPInitValue equ BaseOfStack - EntryItemLength
EntryItem equ SPInitValue
EntryItemLength equ 32
FatEntryOffset equ 1
FatEntryLength equ 9
_start:
jmp BLMain
; ushort LoadTarget(char* Target, notice ==> sizeof(char*) == 2
; ushort TarLen,
; ushort BaseOfTarget,
; ushort BOT_Div_0x10
; char* Buffer);
; return:
; dx --> (dx != 0) ? success : failure
LoadTarget:
mov bp, sp
mov ax, RootEntryOffset
mov cx, RootEntryLength
mov bx, [bp + 10] ; mov bx, Buffer
call ReadSector
mov si, [bp + 2] ; mov si, Target
mov cx, [bp + 4] ; mov cx, TarLen
mov dx, 0
call FindEntry
mov bp, sp
cmp dx, 0
jz finish
mov si, bx
mov di, EntryItem
mov cx, EntryItemLength
call MemCpy
mov ax, FatEntryLength
mov cx, [BPB_BytsPerSec]
mul cx
mov bx, [bp + 6] ; mov bx, BaseOfTarget
sub bx, ax
mov ax, FatEntryOffset
mov cx, FatEntryLength
call ReadSector
mov dx, [EntryItem + 0x1A]
mov es, [bp + 8] ; mov si, BaseOfTarget / 0x10 mov es, si
xor si, si
loading:
mov ax, dx
add ax, 31
mov cx, 1
push dx
push bx
mov bx, si
call ReadSector
pop bx
pop cx
call FatVec
cmp dx, 0xFF7
jnb finish
add si, 512
cmp si, 0
jnz continue
mov si, es
add si, 0x1000
mov es, si
mov si, 0
continue:
jmp loading
finish:
ret
; cx --> index
; bx --> fat table address
;
; return:
; dx --> fat[index]
FatVec:
push cx
mov ax, cx
shr ax, 1
mov cx, 3
mul cx
mov cx, ax
pop ax
and ax, 1
jz even
jmp odd
even: ; FatVec[j] = ( (Fat[i+1] & 0x0F) << 8 ) | Fat[i];
mov dx, cx
add dx, 1
add dx, bx
mov bp, dx
mov dl, byte [bp]
and dl, 0x0F
shl dx, 8
add cx, bx
mov bp, cx
or dl, byte [bp]
jmp return
odd: ; FatVec[j+1] = (Fat[i+2] << 4) | ( (Fat[i+1] >> 4) & 0x0F );
mov dx, cx
add dx, 2
add dx, bx
mov bp, dx
mov dl, byte [bp]
mov dh, 0
shl dx, 4
add cx, 1
add cx, bx
mov bp, cx
mov cl, byte [bp]
shr cl, 4
and cl, 0x0F
mov ch, 0
or dx, cx
return:
ret
; ds:si --> source
; es:di --> destination
; cx --> length
MemCpy:
cmp si, di
ja btoe
add si, cx
add di, cx
dec si
dec di
jmp etob
btoe:
cmp cx, 0
jz done
mov al, [si]
mov byte [di], al
inc si
inc di
dec cx
jmp btoe
etob:
cmp cx, 0
jz done
mov al, [si]
mov byte [di], al
dec si
dec di
dec cx
jmp etob
done:
ret
; es:bx --> root entry offset address
; ds:si --> target string
; cx --> target length
;
; return:
; (dx !=0 ) ? exist : noexist
; exist --> bx is the target entry
FindEntry:
push cx
mov dx, [BPB_RootEntCnt]
mov bp, sp
find:
cmp dx, 0
jz noexist
mov di, bx
mov cx, [bp]
push si
call MemCmp
pop si
cmp cx, 0
jz exist
add bx, 32
dec dx
jmp find
exist:
noexist:
pop cx
ret
; ds:si --> source
; es:di --> destination
; cx --> length
;
; return:
; (cx == 0) ? equal : noequal
MemCmp:
compare:
cmp cx, 0
jz equal
mov al, [si]
cmp al, byte [di]
jz goon
jmp noequal
goon:
inc si
inc di
dec cx
jmp compare
equal:
noequal:
ret
; ax --> logic sector number
; cx --> number of sector
; es:bx --> target address
ReadSector:
mov ah, 0x00
mov dl, [BS_DrvNum]
int 0x13
push bx
push cx
mov bl, [BPB_SecPerTrk]
div bl
mov cl, ah
add cl, 1
mov ch, al
shr ch, 1
mov dh, al
and dh, 1
mov dl, [BS_DrvNum]
pop ax
pop bx
mov ah, 0x02
read:
int 0x13
jc read
ret
我们修改了 LoadTarget 函数,需要通过调用者将参数入栈的方式来获得参数,而不是通过全局变量来获得参数。
boot.asm
%include "blfunc.asm"
%include "common.asm"
org BaseOfBoot
BaseOfStack equ BaseOfBoot
Loader db "LOADER "
LdLen equ ($-Loader)
BLMain:
mov ax, cs
mov ss, ax
mov ds, ax
mov es, ax
mov sp, SPInitValue
push Buffer
push BaseOfLoader / 0x10
push BaseOfLoader
push LdLen
push Loader
call LoadTarget
cmp dx, 0
jz output
jmp BaseOfLoader
output:
mov ax, cs
mov es, ax
mov bp, ErrStr
mov cx, ErrLen
xor dx, dx
mov ax, 0x1301
mov bx, 0x0007
int 0x10
jmp $
ErrStr db "NOLD"
ErrLen equ ($-ErrStr)
Buffer:
times 510-($-$$) db 0x00
db 0x55, 0xaa
加载 loader 到 0x9000 地址处,并跳转到 loader 处执行。
loader.asm
%include "blfunc.asm"
%include "common.asm"
org BaseOfLoader
BaseOfStack equ BaseOfLoader
Kernel db "KERNEL "
KnlLen equ ($-Kernel)
App db "APP "
AppLen equ ($-App)
[section .gdt]
; GDT definition
; Base, Limit, Attribute
GDT_ENTRY : Descriptor 0, 0, 0
CODE32_DESC : Descriptor 0, Code32SegLen - 1, DA_C + DA_32 + DA_DPL0
VIDEO_DESC : Descriptor 0xB8000, 0x07FFF, DA_DRWA + DA_32 + DA_DPL0
CODE32_FLAT_DESC : Descriptor 0, 0xFFFFF, DA_C + DA_32 + DA_DPL0
DATA32_FLAT_DESC : Descriptor 0, 0xFFFFF, DA_DRW + DA_32 + DA_DPL0
TASK_LDT_DESC : Descriptor 0, 0, 0
TASK_TSS_DESC : Descriptor 0, 0, 0
; GDT end
GdtLen equ $ - GDT_ENTRY
GdtPtr:
dw GdtLen - 1
dd 0
; GDT Selector
Code32Selector equ (0x0001 << 3) + SA_TIG + SA_RPL0
VideoSelector equ (0x0002 << 3) + SA_TIG + SA_RPL0
Code32FlatSelector equ (0x0003 << 3) + SA_TIG + SA_RPL0
Data32FlatSelector equ (0x0004 << 3) + SA_TIG + SA_RPL0
; end of [section .gdt]
[section .idt]
align 32
[bits 32]
IDT_ENTRY:
; IDT definition
; Selector, Offset, DCount, Attribute
%rep 256
Gate Code32Selector, DefaultHandler, 0, DA_386IGate + DA_DPL0
%endrep
IdtLen equ $ - IDT_ENTRY
IdtPtr:
dw IdtLen - 1
dd 0
; end of [section .idt]
[section .s16]
[bits 16]
BLMain:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, SPInitValue
; initialize GDT for 32 bits code segment
mov esi, CODE32_SEGMENT
mov edi, CODE32_DESC
call InitDescItem
; initialize GDT pointer struct
mov eax, 0
mov ax, ds
shl eax, 4
add eax, GDT_ENTRY
mov dword [GdtPtr + 2], eax
; initialize IDT pointer struct
mov eax, 0
mov ax, ds
shl eax, 4
add eax, IDT_ENTRY
mov dword [IdtPtr + 2], eax
; load app
push Buffer
push BaseOfApp / 0x10
push BaseOfApp
push AppLen
push App
call LoadTarget
add sp, 10
cmp dx, 0
jz AppErr
; reset es register
mov ax, cs
mov es, ax
; load kernel
push Buffer
push BaseOfKernel / 0x10
push BaseOfKernel
push KnlLen
push Kernel
call LoadTarget
add sp, 10
cmp dx, 0
jz KernelErr
call StoreGlobal
; 1. load GDT
lgdt [GdtPtr]
; 2. close interrupt
; load IDT
; set IOPL to 3
cli
lidt [IdtPtr]
pushf
pop eax
or eax, 0x3000
push eax
popf
; 3. open A20
in al, 0x92
or al, 00000010b
out 0x92, al
; 4. enter protect mode
mov eax, cr0
or eax, 0x01
mov cr0, eax
; 5. jump to 32 bits code
jmp dword Code32Selector : 0
KernelErr:
mov bp, NoKernel
mov cx, NKLen
jmp output
AppErr:
mov bp, NoApp
mov cx, NALen
jmp output
output:
mov ax, cs
mov es, ax
mov dx, 0
mov ax, 0x1301
mov bx, 0x0007
int 0x10
jmp $
; esi --> code segment label
; edi --> descriptor label
InitDescItem:
push eax
mov eax, 0
mov ax, cs
shl eax, 4
add eax, esi
mov word [edi + 2], ax
shr eax, 16
mov byte [edi + 4], al
mov byte [edi + 7], ah
pop eax
ret
;
;
StoreGlobal:
mov dword [RunTaskEntry], RunTask
mov dword [LoadTaskEntry], LoadTask
mov dword [InitInterruptEntry], InitInterrupt
mov dword [EnableTimerEntry], EnableTimer
mov dword [SendEOIEntry], SendEOI
mov eax, dword [GdtPtr + 2]
mov dword [GdtEntry], eax
mov dword [GdtSize], GdtLen / 8
mov eax, dword [IdtPtr + 2]
mov dword [IdtEntry], eax
mov dword [IdtSize], IdtLen / 8
ret
[section .sfunc]
[bits 32]
;
;
Delay:
%rep 5
nop
%endrep
ret
;
;
Init8259A:
push ax
; master
; ICW1
mov al, 00010001B
out MASTER_ICW1_PORT, al
call Delay
; ICW2
mov al, 0x20
out MASTER_ICW2_PORT, al
call Delay
; ICW3
mov al, 00000100B
out MASTER_ICW3_PORT, al
call Delay
; ICW4
mov al, 00010001B
out MASTER_ICW4_PORT, al
call Delay
; slave
; ICW1
mov al, 00010001B
out SLAVE_ICW1_PORT, al
call Delay
; ICW2
mov al, 0x28
out SLAVE_ICW2_PORT, al
call Delay
; ICW3
mov al, 00000010B
out SLAVE_ICW3_PORT, al
call Delay
; ICW4
mov al, 00000001B
out SLAVE_ICW4_PORT, al
call Delay
pop ax
ret
; al --> IMR register value
; dx --> 8259A port
WriteIMR:
out dx, al
call Delay
ret
; dx --> 8259A
; return:
; ax --> IMR register value
ReadIMR:
in ax, dx
call Delay
ret
;
; dx --> 8259A port
WriteEOI:
push ax
mov al, 0x20
out dx, al
call Delay
pop ax
ret
[section .gfunc]
[bits 32]
;
; parameter ===> Task* pt
RunTask:
push ebp
mov ebp, esp
mov esp, [ebp + 8]
lldt word [esp + 96]
ltr word [esp + 98]
pop gs
pop fs
pop es
pop ds
popad
add esp, 4
mov dx, MASTER_IMR_PORT
in ax, dx
%rep 5
nop
%endrep
and ax, 0xFE
out dx, al
%rep 5
nop
%endrep
iret
; void LoadTask(Task* pt);
;
LoadTask:
push ebp
mov ebp, esp
mov eax, [ebp + 8]
lldt word [eax + 96]
leave
ret
;
;
InitInterrupt:
push ebp
mov ebp, esp
push ax
push dx
call Init8259A
sti
mov ax, 0xFF
mov dx, MASTER_IMR_PORT
call WriteIMR
mov ax, 0xFF
mov dx, SLAVE_IMR_PORT
call WriteIMR
pop dx
pop ax
leave
ret
;
;
EnableTimer:
push ebp
mov ebp, esp
push ax
push dx
mov dx, MASTER_IMR_PORT
call ReadIMR
and ax, 0xFE
call WriteIMR
pop dx
pop ax
leave
ret
; void SendEOI(uint port);
; port ==> 8259A port
SendEOI:
push ebp
mov ebp, esp
mov edx, [ebp + 8]
mov al, 0x20
out dx, al
call Delay
leave
ret
[section .s32]
[bits 32]
CODE32_SEGMENT:
mov ax, VideoSelector
mov gs, ax
mov ax, Data32FlatSelector
mov ds, ax
mov es, ax
mov fs, ax
mov ax, Data32FlatSelector
mov ss, ax
mov esp, BaseOfLoader
jmp dword Code32FlatSelector : BaseOfKernel
;
;
DefaultHandlerFunc:
iret
DefaultHandler equ DefaultHandlerFunc - $$
Code32SegLen equ $ - CODE32_SEGMENT
NoKernel db "No KERNEL"
NKLen equ ($-NoKernel)
NoApp db "No KERNEL"
NALen equ ($-NoApp)
Buffer db 0
在实模式下加载 app 到 0xF000 地址处,加载 kernel 到 0xB000 地址处,跳转到保护模式后,初始化相关段寄存器,然后跳转到 kernel 处执行。
aentry.asm
%include "common.asm"
global _start
global AppModInit
extern AppMain
extern GetAppToRun
extern GetAppNum
[section .text]
[bits 32]
_start:
AppModInit:
push ebp
mov ebp, esp
mov dword [GetAppToRunEntry], GetAppToRun
mov dword [GetAppNumEntry], GetAppNum
call AppMain
leave
ret
app.c
void AppMain()
{
RegApp("TaskA", TaskA, 255);
RegApp("TaskB", TaskB, 230);
RegApp("TaskC", TaskC, 230);
RegApp("TaskD", TaskD, 255);
}
task.c
static AppInfo* (*GetAppToRun)(uint index) = NULL;
static uint (*GetAppNum)() = NULL;
void TaskModInit()
{
int i = 0;
GetAppToRun = (void*)(*((uint*)GetAppToRunEntry));
GetAppNum = (void*)(*((uint*)GetAppNumEntry));
Queue_Init(&gFreeTaskNode);
Queue_Init(&gReadyTask);
Queue_Init(&gRunningTask);
Queue_Init(&gWaitingTask);
for(i = 0; i < MAX_TASK_NUM; i++)
{
Queue_Add(&gFreeTaskNode, (QueueNode*)AddrOff(gTaskBuff, i));
}
SetDescValue(AddrOff(gGdtInfo.entry, GDT_TASK_TSS_INDEX), (uint)&gTSS, sizeof(gTSS)-1, DA_386TSS + DA_DPL0);
InitTask(&(gIdleTask.task), 0, "IdleTask", IdleTask, 255);
ReadyToRunning();
CheckRunningTask();
}
kmain.c
#include "task.h"
#include "interrupt.h"
#include "screen.h"
void KMain()
{
void (*AppModInit)() = (void*)BaseOfApp;
int n = PrintString("D.T.OS\n");
PrintString("GDT Entry: ");
PrintIntHex((uint)gGdtInfo.entry);
PrintChar('\n');
PrintString("GDT Size: ");
PrintIntDec((uint)gGdtInfo.size);
PrintChar('\n');
PrintString("IDT Entry: ");
PrintIntHex((uint)gIdtInfo.entry);
PrintChar('\n');
PrintString("IDT Size: ");
PrintIntDec((uint)gIdtInfo.size);
PrintChar('\n');
AppModInit();
TaskModInit();
IntModInit();
LaunchTask();
}
makefile
.PHONY : all clean rebuild
KERNEL_SRC := kmain.c \
screen.c \
kernel.c \
utility.c \
task.c \
interrupt.c \
ihandler.c \
list.c \
queue.c
APP_SRC := screen.c \
utility.c \
app.c
KERNEL_ADDR := B000
APP_ADDR := F000
IMG := D.T.OS
IMG_PATH := /mnt/hgfs
DIR_DEPS := deps
DIR_EXES := exes
DIR_OBJS := objs
DIRS := $(DIR_DEPS) $(DIR_EXES) $(DIR_OBJS)
KENTRY_SRC := kentry.asm
AENTRY_SRC := aentry.asm
BLFUNC_SRC := blfunc.asm
BOOT_SRC := boot.asm
LOADER_SRC := loader.asm
COMMON_SRC := common.asm
BOOT_OUT := boot
LOADER_OUT := loader
KERNEL_OUT := kernel
KENTRY_OUT := $(DIR_OBJS)/kentry.o
APP_OUT := app
AENTRY_OUT := $(DIR_OBJS)/aentry.o
KERNEL_EXE := kernel.out
KERNEL_EXE := $(addprefix $(DIR_EXES)/, $(KERNEL_EXE))
APP_EXE := app.out
APP_EXE := $(addprefix $(DIR_EXES)/, $(APP_EXE))
KERNEL_OBJS := $(KERNEL_SRC:.c=.o)
KERNEL_OBJS := $(addprefix $(DIR_OBJS)/, $(KERNEL_OBJS))
KERNEL_DEPS := $(KERNEL_SRC:.c=.dep)
KERNEL_DEPS := $(addprefix $(DIR_DEPS)/, $(KERNEL_DEPS))
APP_OBJS := $(APP_SRC:.c=.o)
APP_OBJS := $(addprefix $(DIR_OBJS)/, $(APP_OBJS))
APP_DEPS := $(APP_SRC:.c=.dep)
APP_DEPS := $(addprefix $(DIR_DEPS)/, $(APP_DEPS))
all : $(DIR_OBJS) $(DIR_EXES) $(IMG) $(BOOT_OUT) $(LOADER_OUT) $(KERNEL_OUT) $(APP_OUT)
@echo "Build Success ==> D.T.OS!"
ifeq ("$(MAKECMDGOALS)", "all")
-include $(KERNEL_DEPS) $(APP_DEPS)
endif
ifeq ("$(MAKECMDGOALS)", "")
-include $(KERNEL_DEPS) & (APP_DEPS)
endif
$(IMG) :
bximage $@ -q -fd -size=1.44
$(BOOT_OUT) : $(BOOT_SRC) $(BLFUNC_SRC)
nasm $< -o $@
dd if=$@ of=$(IMG) bs=512 count=1 conv=notrunc
$(LOADER_OUT) : $(LOADER_SRC) $(COMMON_SRC) $(BLFUNC_SRC)
nasm $< -o $@
sudo mount -o loop $(IMG) $(IMG_PATH)
sudo cp $@ $(IMG_PATH)/$@
sudo umount $(IMG_PATH)
$(KENTRY_OUT) : $(KENTRY_SRC) $(COMMON_SRC)
nasm -f elf $< -o $@
$(KERNEL_OUT) : $(KERNEL_EXE)
./elf2kobj -c$(KERNEL_ADDR) $< $@
sudo mount -o loop $(IMG) $(IMG_PATH)
sudo cp $@ $(IMG_PATH)/$@
sudo umount $(IMG_PATH)
$(KERNEL_EXE) : $(KENTRY_OUT) $(KERNEL_OBJS)
ld -s $^ -o $@
$(AENTRY_OUT) : $(AENTRY_SRC) $(COMMON_SRC)
nasm -f elf $< -o $@
$(APP_OUT) : $(APP_EXE)
./elf2kobj -c$(APP_ADDR) $< $@
sudo mount -o loop $(IMG) $(IMG_PATH)
sudo cp $@ $(IMG_PATH)/$@
sudo umount $(IMG_PATH)
$(APP_EXE) : $(AENTRY_OUT) $(APP_OBJS)
ld -s $^ -o $@
$(DIR_OBJS)/%.o : %.c
gcc -fno-builtin -fno-stack-protector -c $(filter %.c, $^) -o $@
$(DIRS) :
mkdir $@
ifeq ("$(wildcard $(DIR_DEPS))", "")
$(DIR_DEPS)/%.dep : $(DIR_DEPS) %.c
else
$(DIR_DEPS)/%.dep : %.c
endif
@echo "Creating $@ ..."
@set -e; \
gcc -MM -E $(filter %.c, $^) | sed 's,\(.*\)\.o[ :]*,objs/\1.o $@ : ,g' > $@
clean :
rm -fr $(IMG) $(BOOT_OUT) $(LOADER_OUT) $(KERNEL_OUT) $(APP_OUT) $(DIRS)
rebuild :
@$(MAKE) clean
@$(MAKE) all
aentry.asm 中将 AppModInit 定义为一个函数,kmain.c 将函数指针 AppModInit 设置为 0xF000,通过设置这个函数指针,可以调用 aentry.asm 中的 AppModInit 函数,AppModInit 函数用于将 app 的相关函数的入口地址通过共享内存来给内核提供接口,之后调用 AppMain(),来初始化任务。
task.c 定义两个函数指针,用于保存 app 的相关接口函数的地址。
这样内核和应用程序的代码分开编译,并且能正常正确的运行了。