操作系统内核的雏形
在Linux下用汇编写hello,world
代码及分析:
; 编译链接方法
; (ld 的‘-s’选项意为“strip all”)
; $ nasm -f elf hello.asm -o hello.o
; $ ld -s hello.o -o hello
; $ ./hello
; Hello, world!
; $
[section .data] ; 数据在此
strHello db "Hello, world!", 0Ah
STRLEN equ $ - strHello
[section .text] ; 代码在此
global _start ; 我们必须导出 _start 这个入口,以便让链接器识别
_start:
mov edx, STRLEN
mov ecx, strHello
mov ebx, 1
mov eax, 4 ; sys_write
int 0x80 ; 系统调用
mov ebx, 0
mov eax, 1 ; sys_exit
int 0x80 ; 系统调用
程序中定义了两个节(Section),一个放数据,一个放代码。在代码中值得注意的一点是,入口点默认的是“_start”,我们不但要定义它,而且要通过global这个关键字将它导出,这样链接程序才能找到它。至于代码本身,它们是两个系统调用,用来显示字符串并退出。
调试结果:
汇编和C同时使用
关键代码及分析:
**foo.asm
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;
}
源代码包含两个文件:foo.asm和bar.c。程序入口_start在foo.asm中,一开始程序将会调用bar.c中的函数choose(),choose()将会比较传入的两个参数,根据比较结果的不同打印出不同的字符串。打印字符串的工作是由foo.asm中的函数myprint()来完成的
运行结果:
从Loader到Kernel内核
用Loader加载ELF:
ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Sections)和节头表(Section header table)。实际上,一个文件中不一定包含全部这些内容,而且它们的位置也未必如图5.2所示这样安排,只有BLF头的位置是固定的,其余各部分的位置、大小等信息由ELF头中的各项值来决定。
ELF文件结构:
代码分析:
执行命令运行:
nasm boot.asm -o boot.bin
nasm loader.asm -o loader.bin
dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc
sudo mount -o loop a.img /mnt/floppy/
sudo cp loader.bin /mnt/floppy/ -v
sudo umount /mnt/floppy
结果:
跳入保护模式,列出内存情况
代码及分析:
;下面跳入保护模式
;加载GDTR
lgdt [GdtPtr]
;关中断
cli
;打开地址线
in al, 92h
or al, 00000010b
out 92h, al
;准备切换到保护模式
mov eax, cr0
or eax, 1
mov cr0, eax
;真正进入保护模式
jmp dword SelectorFlatC:(BaseOfLoaderPhyAddr+LABEL_PM_START)
jmp $
wRootDirSizeForLoop dw RootDirSectors ; Root Directory 占用的扇区数,
; 在循环中会递减至零.
wSectorNo dw 0 ; 要读取的扇区号
bOdd db 0 ; 奇数还是偶数
dwKernelSize dd 0 ;kernel.bin文件大小
;字符串
KernelFileName db "KERNEL BIN", 0 ; kernel.BIN 之文件名
; 为简化代码, 下面每个字符串的长度均为 MessageLength
MessageLength equ 9
LoadMessage: db "loading " ; 9字节, 不够则用空格补齐. 序号 0
Message1 db "ready... " ;
Message2 db "No KERNEL" ; 9字节, 不够则用空格补齐. 序号 2
; 从此以后的代码在保护模式下执行 ----------------------------------------------------
; 32 位代码段. 由实模式跳入 ---------------------------------------------------------
[SECTION .s32]
ALIGN 32
[BITS 32]
LABEL_PM_START:
mov ax, SelectorVideo
mov gs, ax
mov ax, SelectorFlatRW
mov ds, ax
mov es, ax
mov fs, ax
mov ss, ax
mov esp, TopOfStack
push szMemChkTitle
call DispStr
add esp, 4
call DispMemInfo
call SetupPaging
mov ah, 0Fh ; 0000: 黑底 1111: 白字
mov al, 'P'
mov [gs:((80 * 0 + 39) * 2)], ax ; 屏幕第 0 行, 第 39 列。
jmp $
%include "lib.inc"
; 显示内存信息 --------------------------------------------------------------
DispMemInfo:
push esi
push edi
push ecx
mov esi, MemChkBuf
mov ecx, [dwMCRNumber];for(int i=0;i<[MCRNumber];i++)//每次得到一个ARDS
.loop: ;{
mov edx, 5 ; for(int j=0;j<5;j++)//每次得到一个ARDS中的成员
mov edi, ARDStruct ; {//依次显示:BaseAddrLow,BaseAddrHigh,LengthLow
.1: ; LengthHigh,Type
push dword [esi] ;
call DispInt ; DispInt(MemChkBuf[j*4]); // 显示一个成员
pop eax ;
stosd ; ARDStruct[j*4] = MemChkBuf[j*4];
add esi, 4 ;
dec edx ;
cmp edx, 0 ;
jnz .1 ; }
call DispReturn ; printf("\n");
cmp dword [dwType], 1 ; if(Type == AddressRangeMemory)
jne .2 ; {
mov eax, [dwBaseAddrLow];
add eax, [dwLengthLow];
cmp eax, [dwMemSize] ; if(BaseAddrLow + LengthLow > MemSize)
jb .2 ;
mov [dwMemSize], eax ; MemSize = BaseAddrLow + LengthLow;
.2: ; }
loop .loop ;}
call DispReturn ;printf("\n");
push szRAMSize ;
call DispStr ;printf("RAM size:");
add esp, 4 ;
push dword [dwMemSize] ;
call DispInt ;DispInt(MemSize);
add esp, 4 ;
pop ecx
pop edi
pop esi
Ret
分析:
编译链接运行:
nasm boot.asm -o boot.bin
nasm loader.asm -o loader.bin
dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc
sudo mount -o loop a.img /mnt/floppy/
sudo cp loader.bin /mnt/floppy/ -v
sudo umount /mnt/floppy
结果:
将控制权交给内核
关键代码及分析:
; InitKernel
; 将 KERNEL.BIN 的内容经过整理对齐后放到新的位置
InitKernel:
xor esi,esi
mov cx, word [BaseOfKernelFilePhyAddr + 2ch] ;program Header Table 中条目(program header)的个数送cx
movzx ecx,cx ;无符号扩展
mov esi,[BaseOfKernelFilePhyAddr + 1ch] ;program header table 在文件中的偏移量送esi
add esi,BaseOfKernelFilePhyAddr ;此时,esi中为program header table 的物理地址
.Begin:
mov eax,[esi + 0] ;将program header 描述的段的类型送eax(program header table 的条目就是program header,program header的第一个字节是段的类型 )
cmp eax,0 ;判断类型是否为空,如果是则表示该program header所描述的段,是不执行的,
jz .NoAction
;memcpy(段要加载到的虚拟地址,段的地址,段在文件中的长度) 将文件中的段复制到指定虚拟地址(在program header中有描述)处
push dword [esi +010h] ;段的长度
mov eax,[esi + 04h] ;段在文件中的偏移
add eax,BaseOfKernelFilePhyAddr ;段在文件中的偏移+文件的地址=段的地址
push eax
push dword [esi + 08h] ;段要加载到的虚拟地址
call MemCpy
add esp,12
.NoAction:
add esi,20h ;esi指向下一个program header
dec ecx ;ecx-1 ,表已读完了一个program header,所以要读的program header数目减1
jnz .Begin ;program header 还没读完,则循环读下一个
Ret
运行结果: