保护模式微型内核程序

;文件说明:保护模式微型系统内核程序

;常量部分
core_code_seg_sel equ 0x38;内核代码段选择子
core_data_seg_sel equ 0x30;内核数据段选择子;注意内核数据段选择子为110000B就是第六个加三个0
sys_routine_seg_sel equ 0x28;系统公用例程代码段的选择子
video_ram_seg_sel equ 0x20;视频显示缓冲区的段选择子
core_stack_seg_sel equ 0x18;内核堆栈段选择子
mem_0_4_gb_seg_sel equ 0x08;整个0~4G内存的段的选择子

;以下是系统核心的头部,用于加载内核程序
core_length dd core_end;核心程序总长度#00

sys_routine_seg dd section.sys_routine.start;系统公用例程段位置#04

core_data_seg dd section.core_data.start;核心数据位置#8

core_code_seg dd section.core_code.start;核心代码段位置#0c

core_entry dd start;核心代码段入口点#10
		   dw core_code_seg_sel


[bits 32]

SECTION sys_routine vstart=0;系统公用例程代码段
;字符串显示例程

put_string:				;显示0终止的字符串并移动光标
						;输入DS:EBX=串地址
	push ecx
.getc:
	mov cl,[ebx]
	or cl,cl;如果传入的字节为0
	jz .exit;就跳转到.exit
	call put_char;否则打印到屏幕
	inc ebx;下一个字符
	jmp .getc;循环

.exit:
	pop ecx
	retf;段间返回
;-----------------------------
put_char:			;在当前光标处显示一个字符,并推进
					;光标。仅用于段内调用
					;输入:CL=字符ASCII码
	pushad
	;以下取当前光标位置
	mov dx,0x3d4;给dx显卡索引寄存器的端口号0x3d4
	mov al,0x0e;把索引值0x0e给al
	out dx,al;告诉显卡要当前光标的位置
	inc dx;0x0f
	in  al,dx;取得当前光标的位置
	mov ah,al;得到屏幕光标的高8位给ah
	
	dec dx;0x0e
	mov al,0x0f
	out dx,al
	inc dx
	in  al,dx
	mov bx,ax;得到光标位置给bx

	cmp cl,0x0d;判断输入的是不是回车符
	jnz .put_0a;如果不是就跳转
	mov ax,bx  ;如果是就设置光标到当前行第一列的位置
	mov bl,80
	div bl
	mul bl
	mov bx,ax
	jmp .set_cursor

.put_0a:
	cmp cl,0x0a;判断输入的是不是换行符
	jnz .put_other;如果不是就跳转
	add bx,80;如果是就设置光标到下一行的当前列且看看是不是超出能显示的最大数量了
			 ;如果超出了就向上滚动一行(.roll_screen里做的)
	jmp .roll_screen

.put_other:
	push es
	mov eax,video_ram_seg_sel;0xb8000段的选择子
	mov es,eax;把显卡的内存地址给es
	shl bx,1;bx乘以2,指向下一个字
	mov [es:bx],cl;把cl赋值给显卡的[es:bx]显示字符
	pop es

	;以下将光标位置推进一个字符
	shr bx,1;bx除以2
	inc bx;bx加1

.roll_screen:
         cmp bx,2000                        ;光标超出屏幕?滚屏
         jl .set_cursor

         push ds
         push es
         mov eax,video_ram_seg_sel
         mov ds,eax
         mov es,eax
         cld
         mov esi,0xa0                       ;小心!32位模式下movsb/w/d 
         mov edi,0x00                       ;使用的是esi/edi/ecx 
         mov ecx,1920
         rep movsd
         mov bx,3840                        ;清除屏幕最底一行
         mov ecx,80                         ;32位程序应该使用ECX
  .cls:
         mov word[es:bx],0x0720
         add bx,2
         loop .cls

         pop es
         pop ds

         mov bx,1920

  .set_cursor:
         mov dx,0x3d4
         mov al,0x0e
         out dx,al
         inc dx                             ;0x3d5
         mov al,bh
         out dx,al
         dec dx                             ;0x3d4
         mov al,0x0f
         out dx,al
         inc dx                             ;0x3d5
         mov al,bl
         out dx,al

         popad
         ret        
;-----------------------------------------
read_hard_disk_0:                           ;从硬盘读取一个逻辑扇区
                                            ;EAX=逻辑扇区号
                                            ;DS:EBX=目标缓冲区地址
                                            ;返回:EBX=EBX+512
         push eax 
         push ecx
         push edx
      
         push eax
         
         mov dx,0x1f2
         mov al,1
         out dx,al                          ;读取的扇区数

         inc dx                             ;0x1f3
         pop eax
         out dx,al                          ;LBA地址7~0

         inc dx                             ;0x1f4
         mov cl,8
         shr eax,cl
         out dx,al                          ;LBA地址15~8

         inc dx                             ;0x1f5
         shr eax,cl
         out dx,al                          ;LBA地址23~16

         inc dx                             ;0x1f6
         shr eax,cl
         or al,0xe0                         ;第一硬盘  LBA地址27~24
         out dx,al

         inc dx                             ;0x1f7
         mov al,0x20                        ;读命令
         out dx,al

  .waits:
         in al,dx
         and al,0x88
         cmp al,0x08
         jnz .waits                         ;不忙,且硬盘已准备好数据传输 

         mov ecx,256                        ;总共要读取的字数
         mov dx,0x1f0
  .readw:
         in ax,dx
         mov [ebx],ax
         add ebx,2
         loop .readw

         pop edx
         pop ecx
         pop eax
      
         retf                               ;段间返回 
;------------------------------------------------------------
allocate_memory;分配内存
               ;输入:ECX=希望分配的字节数
               ;输出:ECX=起始线性地址
        push ds
        push eax
        push ebx

        mov eax,core_data_seg_sel;内核数据段选择子赋值给ds
        mov ds,eax

        mov eax,[ram_alloc];ds指向了数据段所以可以直接使用数据段的内容
        add eax,ecx;下一次分配时的起始地址;就是说这一次分配完了下一次的起始地址

        ;这里应当有检测可用内存数量的指令

        mov ecx,[ram_alloc];返回分配的起始地址

        mov ebx,eax
        and ebx,0xfffffffc
        add ebx,4;强制4字节对齐
        test eax,0x00000003;测试下次分配的起始地址是不是也是4字节
        cmovnz eax,ebx;如果不是就mov eax,ebx如果是,就什么都不做
        mov [ram_alloc],eax;把下一次分配内存的起始地址写回ram_alloc
        pop ebx
        pop eax
        pop ds
        retf
;-------------------------------
set_up_gdt_descriptor:;在GDT内安装一个新的描述符
                      ;输入:EDX:EAX=描述符
                      ;输出:CX=描述符的选择子
        push eax
        push ebx
        push edx
        push ds
        push es

        mov ebx,core_data_seg_sel;切换到核心数据段
        mov ds,ebx

        sgdt [pgdt];以便开始处理GDT;sgdt将GDTR寄存器的基地址和边界信息
                   ;保存到指定的内存位置

        mov ebx,mem_0_4_gb_seg_sel;0~4G全局数据段选择子给es
        mov es,ebx

        movzx ebx,word [pgdt];把GDT界限传给ebx
        inc bx;bx加1,这里有个小技巧(不重要),GDT总字节数,也是下一个描述符偏移
        add ebx,[pgdt+2];下一个描述符的线性地址

        mov [es:ebx],eax;描述符的低32位
        mov [es:ebx+4],edx;描述符的高32位

        add word [pgdt],8;增加一个描述符的大小

        lgdt [pgdt];对GDT的更改生效pgdt

        mov ax,[pgdt];得到GDT界限值
        xor dx,dx;
        mov bx,8;eax除以8,去掉余数就是索引号(因为每个描述符8个字节,
                ;又是从0开始计算的,所以余7舍掉,就是索引号)
        div bx
        mov cx,ax;
        shl cx,3;将索引号移到正确位置

        pop es
        pop ds
        
        pop edx
        pop ebx
        pop eax

        retf

;------------------------------------------
make_seg_descriptor:                        ;构造存储器和系统的段描述符
                                            ;输入:EAX=线性基地址
                                            ;      EBX=段界限
                                            ;      ECX=属性。各属性位都在原始
                                            ;          位置,无关的位清零 
                                            ;返回:EDX:EAX=描述符
         mov edx,eax
         shl eax,16
         or ax,bx                           ;描述符前32(EAX)构造完毕

         and edx,0xffff0000                 ;清除基地址中无关的位
         rol edx,8
         bswap edx                          ;装配基址的31~2423~16  (80486+)

         xor bx,bx
         or edx,ebx                         ;装配段界限的高4or edx,ecx                         ;装配属性

         retf
;-----------------------------------------------
SECTION core_data vstart=0
        pgdt dw 0;用于设置和修改GDT
             dd 0
        ram_alloc dd 0x00100000;下次分配内存时的起始地址
        ;符号地址检索表
        salt:
        salt_1 db '@PrintString'
            times 256-($-salt_1) db 0
               dd put_string
               dw sys_routine_seg_sel
        salt_2 db '@ReadDiskData'
            times 256-($-salt_2) db 0
               dd read_hard_disk_0
               dw sys_routine_seg_sel

        salt_3 db '@PrintDwordAsHexString'
               times 256-($-salt_3) db 0
               dd put_hex_dword
               dw sys_routine_seg_sel
        salt_4 db '@TerminateProgram'
               times 256-($-salt_4) db 0
               dd return_point
               dw core_code_seg_sel

        salt_item_len equ $-salt_4;每个符号地址对照表的长度
        salt_items    equ ($-salt)/salt_item_len;符号地址对照表的总长度
        
        message_1 db 'If you seen this message,that means we'
                  db 'are now in protect mode,and the system'
                  db 'core is loaded,and the video display'
                  db 'routine works perfectly.',0x0d,0x0a,0

        message_5 db 'Loading user program'

        do_status db 'Done.',0x0d,0x0a,0
        message_6 db 0x0d,0x0a,0x0d,0x0a,0x0d,0x0a;3行
                  db 'User program terminated,control returned.',0

        bin_hex   db '0123456789ABCDEF'
                        ;put_hex_dword子过程用的查找表
        core_buf times 2048 db 0;内核用的缓冲区

        esp_pointer dd 0;内核用来临时保存自己的栈指针

        cpu_brnd0   dd 0x0d,0x0a,' ',0
        cpu_brand times 52 db 0
        cpu_brnd1   db 0x0d,0x0a,0x0d,0x0a,0
;===========================================================
SECTION core_code vstart=0

load_relocate_program:              ;加载并重定位用户程序
                                    ;输入:ESI=起始逻辑扇区号
                                    ;返回:AX=指向用户程序头部的选择子
        push ebx
        push ecx
        push edx
        push esi
        push edi

        push ds
        push es

        mov eax,core_data_seg_sel
        mov ds,eax;切换DS到内核数据段

        mov eax,esi;读取程序头部数据
        mov ebx,core_buf
        call sys_routine_seg_sel:read_hard_disk_0

        ;以下判断整个程序有多大
        mov eax,[core_buf];程序尺寸
        mov ebx,eax
        and ebx,0xfffffe00;使程序尺寸512字节对齐(能被512整除的数,)
        add ebx,512;再加一个512就是取512倍数的尺寸大小
        test eax,0x000001ff;9位都为0,程序的大小正好是512的倍数嘛?
        cmovnz eax,ebx;如果不是,就使用凑整的结果,如果是就直接使用eax

        mov ecx,eax;实际需要申请的内存数量
        call sys_routine_seg_sel:allocate_memory;申请分配内存
        mov ebx,ecx;ebx->申请到的内存地址赋值给ebx
        push ebx;保存该首地址
        xor edx,edx;清零
        mov ecx,512;
        div ecx;EDX:EAX除以512,得到扇区数
        mov ecx,eax;总扇区数赋值给ecx

        mov eax,mem_0_4_gb_seg_sel;切换到DS到0-4GB的段
        mov ds,eax
        mov eax,esi;起始扇区号赋值给eax

.b1:
        call sys_routine_seg_sel:read_hard_disk_0;读一个扇区
        inc eax;eax++;读下一个扇区
        loop .b1;循环

        ;建立程序头部段描述符
        pop edi;恢复程序装载的首地址
        mov eax,edi;程序头部起始线性地址
        mov ebx,[edi+0x04];段长度
        dec ebx;段界限
        mov ecx,0x00409200;属性
        call sys_routine_seg_sel:make_seg_descriptor;制作描述符
        call sys_routine_seg_sel:set_up_gdt_descriptor;安装描述符
        mov [edi+0x04],cx;把描述符选择子写回用户程序edi+0x04;建立程序代码段描述符
        mov eax,edi
        add eax,[edi+0x14]
        mov ebx,[edi+0x18]
        dec ebx
        mov ecx,0x00409800
        call sys_routine_seg_sel:make_seg_descriptor;制作描述符
        call sys_routine_seg_sel:set_up_gdt_descriptor;安装描述符
        mov [edi+0x14],cx;把描述符选择子写回用户程序edi+0x04;建立程序数据段描述符
        mov eax,edi
        add eax,[edi+0x1c]
        mov ebx,[edi+0x20]
        dec ebx
        mov ecx,0x00409200
        call sys_routine_seg_sel:make_seg_descriptor;制作描述符
        call sys_routine_seg_sel:set_up_gdt_descriptor;安装描述符
        mov [edi+0x1c],cx;把描述符选择子写回用户程序edi+0x04;建立程序堆栈段描述符
        mov ecx,[edi+0x0c];4KB的倍率
        mov ebx,0x000fffff;堆栈段的段界限为0x000fffff-倍率
        sub ebx,ecx;得到段界限
        mov eax,4096;
        mul dword[edi+0x0c];4096乘以倍率就是所需要的栈的大小
        mov ecx,eax;准备为堆栈分配内存
        call sys_routine_seg_sel:allocate_memory
        add eax,ecx;因为返回的是低地址,而堆栈需要得到高端物理地址
                   ;所以需要返回地址加上堆栈的大小得到真正的堆栈起始物理地址
        mov ecx,0x00c09600;4KB粒度的堆栈段描述符
        call sys_routine_seg_sel:make_seg_descriptor
        call sys_routine_seg_sel:set_up_gdt_descriptor
        mov [edi+0x08],cx;把描述符选择子写回用户程序edi+0x08;重定位SALT
        mov eax,[edi+0x04];用户程序头部段选择子给es
        mov es,eax
        mov eax,core_data_seg_sel;核心数据段选择子给ds
        mov ds,eax
        
        cld;清除方向标志位

        mov ecx,[es:0x24];用户程序的SALT条目数
        mov edi,0x28     ;用户程序内的SALT位于头部内0x28.b2:
        push ecx;用户程序的SALT条目数入栈
        push edi;用户程序的SALT偏移地址入栈

        mov ecx,salt_items;核心程序的SALT条目数
        mov esi,salt;核心程序内的SALT的偏移
.b3:
        push edi;用户程序的SALT偏移地址入栈
        push esi;核心程序的SALT偏移入栈
        push ecx;核心程序的SALT条目数入栈

        mov ecx,64;因为用cmpsd指令比较4字节,所以每个条目之多需要比对64次
        repe cmpsd;重复比较,每次比较一个双字,比较完esi和edi各自都加4
        jnz .b4;如果不相等就跳转到.b4
        mov eax,[esi];如果相等,说明256个字节都比较完了,把ds:esi指向的4字节的例程入口偏移地址赋值给eax
        mov [es:edi-256],eax;edi-256为该SALT初始地址,把eax赋值给它
                            ;4字节为偏移地址,最后两个字节为选择子,
                            ;这里就是把用户程序的SALT里存放的字符串改为偏移地址
        mov ax,[esi+4];把esi指向的内核SALT的选择子给ax
        mov [es:edi-252],ax;把ax赋值给,SALT已经被赋值的偏移量的后面
.b4:
        pop ecx;核心程序的SALT条目数出栈
        pop esi;核心程序的SALT偏移出栈
        add esi,salt_item_len;核心程序的SALT偏移加上一个条目的长度,以指向下一个条目
        pop edi;用户程序的SALT偏移地址出栈
        loop .b3;循环比较

        pop edi;用户程序的SALT偏移地址出栈
        add edi,256;用户程序的SALT偏移地址加上256,指向下一个SALT项
        pop ecx;核心程序的SALT条目数出栈
        loop .b2;循环

        mov ax,[es:0x04];用户程序头部的段选择子给ax

        pop es
        pop ds

        pop edi
        pop esi
        pop edx
        pop ecx
        pop ebx

        ret

;---------------------------------
start:
    mov ecx,core_data_seg_sel
    mov ds,ecx

    mov ebx,message_1
    call sys_routine_seg_sel:put_string

    ;显示处理器品牌信息
    mov eax,0x80000002
    cpuid
    mov [cpu_brand+0x00],eax
    mov [cpu_brand+0x04],ebx
    mov [cpu_brand+0x08],ecx
    mov [cpu_brand+0x0c],edx

    mov eax,0x80000003
    cpuid
    mov [cpu_brand+0x10],eax
    mov [cpu_brand+0x14],ebx
    mov [cpu_brand+0x18],ecx
    mov [cpu_brand+0x1c],edx

    mov eax,0x80000004
    mov [cpu_brand + 0x20],eax
    mov [cpu_brand + 0x24],ebx
    mov [cpu_brand + 0x28],ecx
    mov [cpu_brand + 0x2c],edx
    ;打印CPU信息
    mov ebx,cpu_brnd0
    call sys_routine_seg_sel:put_string
    mov ebx,cpu_brand
    call sys_routine_seg_sel:put_string
    mov ebx,cpu_brnd1
    call sys_routine_seg_sel:put_string

    ;打印加载用户程序通知信息
    mov ebx,message_5
    call sys_routine_seg_sel:put_string
    mov esi,50 ;用户程序位于逻辑50扇区
    ;加载并重定位用户程序
    call load_relocate_program
    ;打印完成状态
    mov ebx,do_status
    call sys_routine_seg_sel:put_string


    mov [esp_pointer],esp;临时保存内核程序的堆栈指针
    
    mov ds,ax;将指向用户程序头部的选择子给ds

    jmp far[0x10];调到用户程序[0x10]存放的指令地址,控制权交给用户程序(入口点)
                 ;堆栈可能切换;[0x10]处,存放的是32位的偏移地址和16位的代码段选择子



return_point:;用户程序返回点
    mov eax,core_data_seg_sel;使ds指向核心数据段
    mov ds,eax
    mov eax,core_stack_seg_sel;切换回内核自己的堆栈
    mov ss,eax
    mov esp,[esp_pointer];恢复esp

    mov ebx,message_6;打印消息
    call sys_routine_seg_sel:put_string

    ;这里可以防止清除用户程序各种描述符的指令
    ;也可以加载并启动其他程序

    hlt
SECTION core_trail
core_end:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值