本篇将介绍CS的核心部分代码。核心部分实现的主要功能如下:
- 在gdt中安装四个调用门接口,分别是:显示一个点、用户进程sleep、画矩形、获取鼠标在界面上的点击信息;
- 启动中断功能,一共安装了两个中断:时钟中断、鼠标中断;
- 将核心作为一个单独的进程,写入当前进程寄存器;
- 启动两个用户进程;
- 核心进入循环过程:显示鼠标的移动、在界面的最上面一行显示一个不断移动的直线;
- 使用两个双向链表进行进程的控制:就绪链表、暂停链表。其中,就绪链表中的进程每40毫秒切换一次;用户进程使用sleep调用时,进入暂停链表。sleep到点后,用户进程重新进入就绪链表;
- 整个系统的内存分布使用扁平模式;
- 用户进程的安装由Load_usr_task函数负责;
具体的core代码如下所示:
;===============================================================================
;=== 本程序为系统Core程序,其主要功能有: ===
;=== 1.在gdt中安装四个调用门 ===
;=== 2.安装core进程 ===
;=== 3.安装两个用户进程 ===
;=== 4.启动中断功能 ===
;=== 5.用户进程和系统进程以40ms的间隔切换 ===
;=== 6.两个进程分别在界面上显示图形 ===
;=== 7.通过鼠标的点击控制用户进程图形的显示 ===
;===============================================================================
;===============================================================================
;=== 该处用于保存常量 ===
;===============================================================================
flat_4gb_code_seg_sel equ 0x0008 ;平坦模式的代码段选择子
flat_mode_code equ 0x0008 ;平坦模式的代码段选择子
flat_mode_data equ 0x0010 ;平坦模式的数据段选择子
;-------------------------------------------------------------------------------
show_mem_addr equ 0xc00b8000 ;文本显示区域的起始地址
pic_mem_addr equ 0xc00a0000 ;图形显示区域的起始地址
gdt_base_address equ 0xc0008000 ;gdt表的虚拟地址
idt_linear_address equ 0xc0019000 ;中断描述符表的线性基地址(100K处)
mouse_buf_addr equ 0xc0020000 ;存放鼠标数据的缓冲区基地址
mouse_buf_end equ 8192 ;存放鼠标数据的缓冲区大小(一共8K)
;-------------------------------------------------------------------------------
ldt_code_high_dd equ 0x00cff800 ;常数,ldt表中代码段的高双字 base-0x0;TYPY-8 只执行;P-1;DPL-3;DT-1;G-1;D-1;limit-0xf;base-0;
ldt_code_low_dd equ 0x0000ffff ;常数,ldt表中代码段的低双字 limit-0xffff;base-0x0;
ldt_data_high_dd equ 0x00cff200 ;常数,ldt表中数据段的高双字 base-0x0;TYPY-2 读/写;P-1;DPL-3;DT-1;G-1;D-1;limit-0xf;base-0;
ldt_data_low_dd equ 0x0000ffff ;常数,ldt表中数据段的低双字 limit-0xffff;base-0x0;
;-------------------------------------------------------------------------------
task_running equ 0xa5a5 ;常数,表示进程正在运行
task_spending equ 0xaaaa ;常数,表示进程暂定运行
task_closed equ 0x5555 ;常数,表示进程已经停止运行
;-------------------------------------------------------------------------------
;进程控制管理结构。还是使用双向链表便于查找
struc proc_ctrl
.num: resw 1 ;进程编号
.state: resw 1 ;当前状态
.wakeup: resd 1 ;进程休眠的截止时间(ticks)
.pre_task: resd 1 ;上一个进程控制结构的地址
.next_task: resd 1 ;下一个进程控制结构的地址
.task_rsvr: resd 1 ;保留
.task_sel: resw 1 ;进程在gdt中的选择子
.ldt_rsvr: resd 1 ;保留
.ldt_sel: resw 1 ;ldt在gdt中的选择子
.ldt: resb 16 ;任务对应的ldt段
.tss: resb 104 ;任务对应的tss段
.cmd resb 8 ;命令缓冲区(这里主要用于鼠标点击动作)
endstruc
;===============================================================================
;=== 该处开始Core代码 ===
;===============================================================================
SECTION core vstart=0xc0010000
[bits 32]
;-------------------------------------------------------------------------------
call Set_color_palette ;初始化调色板
;-------------------------------------------------------------------------------
;安装画点调用接口
mov eax, draw_point
mov ebx, flat_mode_code
call Set_gate_sel
mov word [far_caller_1 + 4], cx ;保存远程调用的选择子
;图形显示测试
mov eax, 0
mov ebx, 6 ;第0行第6个点显示亮红
mov cl, 1
call far [far_caller_1]
;-------------------------------------------------------------------------------
;安装sleep调用接口
mov eax, task_sleep
mov ebx, flat_mode_code
call Set_gate_sel
mov word [far_caller_2 + 4], cx ;保存远程调用的选择子
;-------------------------------------------------------------------------------
;图形显示测试
mov eax, 0
mov ebx, 8 ;第0行第8个点显示亮蓝
mov cl, 4
call far [far_caller_1]
;-------------------------------------------------------------------------------
;安装画矩形调用接口
mov eax, draw_rectangle
mov ebx, flat_mode_code
call Set_gate_sel
mov word [far_caller_3 + 4], cx ;保存远程调用的选择子
;-------------------------------------------------------------------------------
;安装获取命令字调用接口
mov eax, get_cmd
mov ebx, flat_mode_code
call Set_gate_sel
mov word [far_caller_4 + 4], cx ;保存远程调用的选择子
;-------------------------------------------------------------------------------
;启动中断功能
call interrupt_init
;-------------------------------------------------------------------------------
;内核任务的TSS段填写,清空缓冲区,使段内所有保留位置0
mov eax, SYS_PROC + proc_ctrl.tss
mov ecx, 104
_init_tss:
mov byte [eax], 0x0
inc eax
loop _init_tss
mov eax, SYS_PROC + proc_ctrl.tss
mov ecx, cr3
mov dword [eax + 0x1c], ecx ;此处的CR3的写入必须要有,因为Set_TSS_sel不会主动加入页目录!!!
;在gdt中写入tss段描述符
call Set_TSS_sel ;安装TSS,段首地址在eax中
mov [SYS_PROC + proc_ctrl.task_sel], cx
ltr cx ;将内核任务写入系统
mov eax, SYS_PROC
mov [SYS_PROC + proc_ctrl.pre_task], eax ;系统进程的前一进程指向自己
mov [SYS_PROC + proc_ctrl.next_task], eax ;系统进程的后一进程也指向自己
;记录当前工作进程
mov eax, SYS_PROC
mov [_task_now], eax ;当前工作进程为系统进程
add eax, proc_ctrl.next_task
mov dword [eax], SYS_PROC ;系统进程控制结构的下一个进程地址 指向自己
mov eax, SYS_PROC
add eax, proc_ctrl.state
mov word [eax], task_running ;工作进程状态为 正在运行
;-------------------------------------------------------------------------------
;创建用户任务1
mov eax, 100 ;用户进程代码起始扇区号
mov ebx, 8 ;用户进程代码扇区个数
mov ecx, USR1_PROC ;用户进程控制模块proc_ctrl的偏移地址
call Load_usr_task
mov [USR1_PROC + proc_ctrl.task_sel], cx
;如果用户任务正常返回,显示亮点
mov eax, 0
mov ebx, 10 ;第0行第10个点显示亮绿
mov cl, 2
call far [far_caller_1]
;创建用户任务2
mov eax, 200 ;用户进程代码起始扇区号
mov ebx, 8 ;用户进程代码扇区个数
mov ecx, USR2_PROC ;用户进程控制模块proc_ctrl的偏移地址
call Load_usr_task
mov [USR2_PROC + proc_ctrl.task_sel], cx
;如果用户任务正常返回,显示亮点
mov eax, 0
mov ebx, 12 ;第0行第12个点显示亮黄
mov cl, 3
call far [far_caller_1]
;-------------------------------------------------------------------------------
;安装实时时钟中断
call install_0x20_interrupt
;-------------------------------------------------------------------------------
;安装鼠标中断
call install_0x74_interrupt
;启动鼠标
call enable_mouse
;启动用户进程
mov eax, USR1_PROC
mov [_task_now], eax ;当前执行进程为用户进程
jmp far [USR1_PROC + proc_ctrl.task_rsvr];启动用户进程
;-------------------------------------------------------------------------------
_sleep:
call move_mouse ;处理鼠标动作
call move_point ;显示一个移动的亮点
hlt
jmp _sleep
;-------------------------------------------------------------------------------
Set_TSS_sel: ;安装TSS段
;输入参数: eax-—TSS段基地址
;输出参数: cx—-gdt中的选择子
push edx ;保存edx
xor ecx, ecx ;ecx清零
sgdt [pgdt] ;获取gdt表长度和基址
mov cx, word [pgdt] ;段界限
mov edx, dword [pgdt+2] ;段基址
inc cx ;gdt表长度
add edx, ecx ;安装地址
mov word [edx], 103 ;TSS段长度的低16位
mov word [edx+2], ax ;TSS段基址的低16位
shr eax, 16 ;TSS段基址右移16位
mov byte [edx+4], al ;TSS段基址的中8位
mov byte [edx+7], ah ;TSS段基址的高8位
mov byte [edx+5], 1_00_0_1001B ;P-1,TYPE-9(可用386TSS段)
mov byte [edx+6], 0_1_0_0_0000B ;G-0,X-1,limit-0
add word [pgdt], 8 ;gdt表长度增加8字节
lgdt [pgdt] ;重新安装gdt表到系统
pop edx ;弹出edx
ret
;-------------------------------------------------------------------------------
Set_LDT_sel: ;安装LDT段
;输入参数: eax-—LDT段基地址
;输出参数: cx—-gdt中的选择子
push edx ;保存edx
xor ecx, ecx ;ecx清零
sgdt [pgdt] ;获取gdt表长度和基址
mov cx, word [pgdt] ;段界限
mov edx, dword [pgdt+2] ;段基址
inc cx ;gdt表长度
add edx, ecx ;安装地址
mov word [edx], 15 ;LDT段长度的低16位
mov word [edx+2], ax ;LDT段基址的低16位
shr eax, 16 ;LDT段基址右移16位
mov byte [edx+4], al ;LDT段基址的中8位
mov byte [edx+7], ah ;LDT段基址的高8位
mov byte [edx+5], 1_11_0_0010B ;P-1,DPL=3,TYPE-2(LDT段)
mov byte [edx+6], 0_1_0_0_0000B ;G-0,X-1,limit-0
add word [pgdt], 8 ;gdt表长度增加8字节
lgdt [pgdt] ;重新安装gdt表到系统
pop edx ;弹出edx
ret
;-------------------------------------------------------------------------------
make_gate_descriptor: ;构造门的描述符(调用门等)
;输入:EAX=门代码在段内偏移地址
; BX=门代码所在段的选择子
; CX=段类型及属性等(各属
; 性位都在原始位置)
;返回:EDX:EAX=完整的描述符
push ebx
push ecx
mov edx,eax
and edx,0xffff0000 ;得到偏移地址高16位
or dx,cx ;组装属性部分到EDX
and eax,0x0000ffff ;得到偏移地址低16位
shl ebx,16
or eax,ebx ;组装段选择子部分
pop ecx
pop ebx
ret
;-------------------------------------------------------------------------------
Set_gate_sel: ;安装系统调用门
;输入参数: eax-—offset, ebx-—sector
;输出参数: cx—-gdt中的选择子
push edx ;保存edx寄存器
xor ecx, ecx ;ecx清零
sgdt [pgdt] ;获取gdt表长度和基址
mov cx, word [pgdt] ;段界限
mov edx, dword [pgdt+2] ;段基址
inc cx ;gdt表长度
add edx, ecx ;安装地址
mov word [edx], ax ;低16位offset
shr eax, 16
mov word [edx+6], ax ;高16位offset
mov word [edx+2], bx ;安装Selecter
mov word [edx+4], 0x0ec00 ;安装属性。P-1,DPL-3,TYPE-12
add word [pgdt], 8 ;gdt表长度增加8字节
lgdt [pgdt] ;重新安装gdt表到系统
pop edx ;弹出edx
ret
;-------------------------------------------------------------------------------
Alloc_sys_page: ;分配一页系统内存(从128K-640K处,都是可分配系统页)
;输入参数: 无
;输出参数: eax—-系统页的线性地址(返回0表示没有系统内存了)
mov eax, dword [sys_mem_addr] ;获取未分配的页线性地址
add dword [sys_mem_addr], 0x1000
ret
;-------------------------------------------------------------------------------
Alloc_usr_page: ;分配一页用户内存(1M-2M,都是可分配用户页)
;输入参数: 无
;输出参数: eax—-用户页的硬件地址(返回0表示没有用户内存了)
mov eax, dword [usr_page_addr] ;获取未分配的页硬件地址
add dword [usr_page_addr], 0x1000 ;计算下一页硬件地址
ret
;-------------------------------------------------------------------------------
Load_usr_task: ;创建用户进程(权限3)
;输入参数: eax--用户进程的起始扇区号
;输入参数: ebx--用户进程扇区个数
;输入参数: ecx--进程PROC_CTRL结构的偏移地址
;输出参数: cx—-用户进程TSS段在gdt中的选择子
push eax ;[ebp + 24] 起始扇区号
push ebx ;[ebp + 20] 扇区个数
push ecx ;[ebp + 16] 进程控制结构proc_ctrl的偏移
;用户内存区分配用户“页目录”、“页表1”、“app页”、“用户堆栈esp3”三页
call Alloc_usr_page
push eax ;[ebp + 12] 页目录页
call Alloc_usr_page
push eax ;[ebp + 8] 页表1
call Alloc_usr_page
push eax ;[ebp + 4] app页
call Alloc_usr_page
push eax ;[ebp] 用户堆栈
mov ebp, esp
;将用户页目录、页表1、app页映射到系统进程的前三页
mov eax, [ebp + 12]
or eax, 0x7
mov dword [0xffc00000], eax
mov eax, [ebp + 8]
or eax, 0x7
mov dword [0xffc00004], eax
mov eax, [ebp + 4]
or eax, 0x7
mov dword [0xffc00008], eax
;拷贝系统页目录到用户页目录
mov ecx, 1024
xor edx, edx
_dir_cpy:
mov eax, dword [0xfffff000 + edx]
mov dword [edx], eax
add edx, 4
loop _dir_cpy
;用户页目录最后一项指向自己
mov eax, [ebp + 12]
or eax, 0x7
mov dword [4092], eax
;用户页目录第一项指向页表1
mov eax, [ebp + 8]
or eax, 0x7
mov dword [0], eax
;用户页表1第一项指向用户app页(对应线性地址:0x0-0x1000)
mov eax, [ebp + 4]
or eax, 0x7
mov dword [0x1000], eax
;用户页表1第二项指向权限3堆栈(对应线性地址:0x1000-0x2000)
mov eax, [ebp]
or eax, 0x7
mov dword [0x1004], eax
;将用户进程数据从逻辑扇区拷贝到到用户app页中
mov eax, [ebp + 24]
mov ebx, 0x2000
mov ecx, [ebp + 20]
_cpy_disk:
call read_hard_disk_0
inc eax
loop _cpy_disk
;将调用门信息写入用户进程
mov ax, word [far_caller_1 + 4]
mov [0x2004], ax
mov ax, word [far_caller_2 + 4]
mov [0x200a], ax
mov ax, word [far_caller_3 + 4]
mov [0x2010], ax
mov ax, word [far_caller_4 + 4]
mov [0x2016], ax
;初始化用户ldt段(代码段、数据段)
mov eax, [ebp + 16]
add eax, proc_ctrl.ldt
mov dword [eax], ldt_code_low_dd
mov dword [eax + 4], ldt_code_high_dd
mov dword [eax + 8], ldt_data_low_dd
mov dword [eax + 0x0c], ldt_data_high_dd
;将用户ldt段加入gdt
call Set_LDT_sel
mov eax, [ebp + 16]
add eax, proc_ctrl.ldt_sel
mov [eax], cx
;初始化用户进程TSS页,段内所有保留位都清空
mov eax, [ebp + 16]
add eax, proc_ctrl.tss
push eax
mov ecx, 26
xor edx, edx
_init_usr_tss:
mov dword [eax + edx], 0x0
add edx, 4
loop _init_usr_tss
;逐项填写TSS段
call Alloc_sys_page ;分配用户进程使用的R0权限下的堆栈页
mov edx, eax
add edx, 4092 ;esp0指针指向页的最后
pop eax ;恢复指向tss段的首地址
mov dword [eax+0x4], edx ;esp0
mov word [eax+0x8], flat_mode_data ;ss0
mov ebx, [ebp + 12]
mov dword [eax+0x1c], ebx ;cr3
mov dword [eax+0x20], 24 ;eip
pushfd
pop ebx
mov dword [eax+0x24], ebx ;eflag
mov dword [eax+0x38], 0x2000-4 ;esp
mov dword [eax+0x4c], 0x7 ;cs(list-0,TI-1,RPL-3)
mov dword [eax+0x50], 0x0f ;ss(list-1,TI-1,RPL-3)
mov dword [eax+0x54], 0x0f ;ds(list-1,TI-1,RPL-3)
mov ebx, [ebp + 16]
add ebx, proc_ctrl.ldt_sel
mov cx, [ebx]
mov word [eax+0x60], cx ;ldt选择子
mov word [eax+0x66], 103 ;TSS段长度
;将用户tss写入gdt
call Set_TSS_sel
;修改进程状态为 正在运行
mov eax, [ebp + 16] ;进程控制段
add eax, proc_ctrl.state ;进程状态字段
mov word [eax], task_running ;进程状态字段设置为 正在运行
;将进程控制段放入进程链表
mov edx, [ebp + 16]
call mount_task_running_que
add esp, 28
ret
;-------------------------------------------------------------------------------
mount_task_running_que: ;将进程结构安装到就绪队列中
;输入参数: edx--进程PROC_CTRL结构的偏移地址
cli ;关中断
pushad
mov eax, [SYS_PROC + proc_ctrl.pre_task] ;前一个进程结构地址
mov [eax + proc_ctrl.next_task], edx ;前一个进程的next指向新进程结构
mov [SYS_PROC + proc_ctrl.pre_task], edx ;系统进程的pre指向新进程结构
mov [edx + proc_ctrl.pre_task], eax ;新进程的pre指向前一个进程结构
mov ebx, SYS_PROC ;新进程的next指向系统进程
mov [edx + proc_ctrl.next_task], ebx
popad
sti ;开中断
ret
;-------------------------------------------------------------------------------
mount_task_run_to_pend_que: ;将进程结构从就绪队列放入暂停队列
;输入参数: edx--进程PROC_CTRL结构的偏移地址
cli ;关中断
pushad
;将进程结构从队列中取出
mov eax, [edx + proc_ctrl.pre_task] ;进程所在队列的前一个结构
mov ebx, [edx + proc_ctrl.next_task] ;进程所在队列的后一个结构
mov [_task_next], ebx ;记录下一个进程结构地址
mov [eax + proc_ctrl.next_task], ebx ;前一个结构的next指向后一个结构
mov [ebx + proc_ctrl.pre_task], eax ;后一个结构的pre指向前一个结构
;将进程结构放入pending队列
cmp dword [_task_pend_que], 0x0 ;暂定队列是否为空
jnz _chg_pnd_que1 ;如果包含进程结构
mov [_task_pend_que], edx ;暂定队列头部指向该进程结构
mov [edx + proc_ctrl.pre_task], edx ;该进程结构的pre指向自己
mov [edx + proc_ctrl.next_task], edx ;该进程结构的next指向自己
jmp _chg_pnd_que2 ;退出函数
_chg_pnd_que1:
mov eax, dword[_task_pend_que] ;eax指向暂定队列头部
mov ebx, [eax + proc_ctrl.pre_task] ;ebx指向暂定队列的最后一个结构
mov [ebx + proc_ctrl.next_task], edx ;队列最后一个结构的next指向本进程结构
mov [eax + proc_ctrl.pre_task], edx ;暂定队列头部的pre指向本进程结构
mov [edx + proc_ctrl.pre_task], ebx ;本进程结构的pre指向原队列最后一个结构
mov [edx + proc_ctrl.next_task], eax ;本进程结构的next指向队列头部
_chg_pnd_que2:
popad
sti ;开中断
;跳转到下一个就绪进程执行
mov eax, [_task_next] ;下一个进程结构地址
mov [_task_now], eax ;当前进程
add eax, proc_ctrl.task_rsvr
jmp far [eax] ;跳转到新的进程执行
ret
;-------------------------------------------------------------------------------
mount_task_pend_to_run_que: ;将进程结构从暂停队列放入就绪队列(就绪队列头部永远指向系统进程)
;输入参数: edx--进程PROC_CTRL结构的偏移地址
;cli ;关中断
pushad
;先将进程结构从暂定队列取出
mov edx, [_task_pend_que] ;首先判断暂定队列是否只有一个结构
mov eax, [edx + proc_ctrl.next_task] ;eax指向暂停队列头部的下一个结构
cmp eax, edx ;如果暂定队列头部结构的next指向自己,暂停队列只有一个结构
jnz _fix_pend_que_head
xor eax, eax
mov [_task_pend_que], eax ;暂停队列头部清空
jmp _mount_to_run_que
_fix_pend_que_head: ;将暂停队列头部结构取出
mov ebx, [edx + proc_ctrl.pre_task] ;ebx指向暂停队列的尾部
mov [ebx + proc_ctrl.next_task], eax ;尾部结构的next指向原队列的第二个结构
mov [eax + proc_ctrl.pre_task], ebx ;原队列的第二个结构的pre指向队列尾部结构
mov [_task_pend_que], eax ;暂停队列头部修改为原第二个结构地址
_mount_to_run_que:
;将进程结构放入就绪队列
mov eax, SYS_PROC ;eax指向系统进程
mov ebx, [eax + proc_ctrl.pre_task] ;ebx指向就绪队列最后一个结构
mov [eax + proc_ctrl.pre_task], edx ;系统进程的pre指向该进程结构
mov [ebx + proc_ctrl.next_task], edx ;就绪队列最后一个结构的next指向该进程结构
mov [edx + proc_ctrl.pre_task], ebx ;该进程的pre指向队列原最后一个结构
mov [edx + proc_ctrl.next_task], eax ;该进程的next指向系统进程结构
popad
;sti ;开中断
ret
;-------------------------------------------------------------------------------
switch_task2: ;轮转切换当前执行的进程
mov eax, [_task_now] ;eax指向当前进程
mov ebx, [eax + proc_ctrl.next_task] ;ebx指向下一个进程
cmp eax, ebx ;是否是同一个进程
je _switch_task_ok ;如果是同一个进程,无需切换
mov [_task_now], ebx ;更新当前进程记录
add ebx, proc_ctrl.task_rsvr
jmp far [ebx] ;跳转到新的进程执行
_switch_task_ok:
ret
;-------------------------------------------------------------------------------
wakeup_task: ;检查进程休眠时间是否到时,并唤醒(在中断中处理,无需关中断)
mov eax, [_task_pend_que] ;eax指向休眠队列头部
cmp eax, 0x0 ;休眠队列是否为空
jz wakeup_task_finished ;如果为空,直接跳出
mov ebx, [sys_click] ;当前的系统时间
mov ecx, [eax + proc_ctrl.wakeup] ;ecx为进程被唤醒时间
cmp ebx, ecx ;如果系统时间大于等于进程的唤醒时间
jb wakeup_task_finished
mov edx, eax ;调用时,edx指向被调整的进程地址
call mount_task_pend_to_run_que ;将进程结构从暂定队列放入就绪队列
wakeup_task_finished:
ret
;-------------------------------------------------------------------------------
draw_point: ;画点
;EAX=Y值
;EBX=X值
;CL=颜色
pushad
xor edx, edx ;edx清零,为乘法做准备
push ebx
mov ebx, 320
mul ebx ;Y乘以320
pop ebx
add eax, ebx ;结果再加上X,得到点在图形缓存中的偏移
mov [pic_mem_addr + eax], cl ;写入点颜色
popad
retf
;-------------------------------------------------------------------------------
draw_line: ;画横线
;EAX-低16位为起点Y值,高16位为起点X的值
;EBX-横线长度
;CL=颜色
push eax
push ebx
push edx
mov edx, eax
and eax, 0x0000ffff
push eax ;记录Y值,[ebp+8]
shr edx, 16
push edx ;记录X值,[ebp+4]
push ebx ;记录长度,[ebp]
mov ebp, esp
xor edx, edx
mov eax, [ebp+8]
mov ebx, 320
mul ebx
mov ebx, [ebp+4]
mov edx, [ebp]
_dr_line:
mov [pic_mem_addr + eax + ebx], cl
inc ebx
dec edx
cmp edx, 0
ja _dr_line
add esp, 12 ;修正堆栈指针
pop edx
pop ebx
pop eax
ret
;-------------------------------------------------------------------------------
draw_rectangle: ;画矩形
;EAX-低16位为Y值,高16位为Y的高度
;EBX-低16位为X值,高16位为X的宽度
;CL=颜色
pushad
mov edx, eax
and eax, 0x0000ffff
push eax ;Y值[ebp+12]
shr edx, 16
push edx ;Y的高度[ebp+8]
mov edx, ebx
and ebx, 0x0000ffff
push ebx ;X值[ebp+4]
shr edx, 16
push edx ;X的宽度[ebp]
mov ebp, esp
mov eax, [ebp+4]
shl eax, 16
mov ebx, [ebp+12]
or eax, ebx
mov ebx, [ebp]
mov edx, [ebp+8]
_drw_rectangle:
call draw_line
inc eax
dec edx
cmp edx, 0
ja _drw_rectangle
add esp, 16
popad
retf
;-------------------------------------------------------------------------------
get_cmd: ;获取命令
;输出参数:al-cmd的值
push ebx
mov ebx, [_task_now] ;当前进程控制结构
add ebx, proc_ctrl.cmd
mov al, [ebx]
mov byte [ebx], 0
pop ebx
retf
;-------------------------------------------------------------------------------
task_close: ;关闭用户进程,回到系统进程
;输入参数: eax--用户进程的编号
;iretd
pushad
mov eax, [_task_now] ;当前进程控制结构
add eax, proc_ctrl.state
mov word [eax], task_closed ;进程状态为 退出
popad
retf
;-------------------------------------------------------------------------------
task_sleep: ;用户进程休眠
;输入参数: eax--休眠时间(10毫秒的倍数)
pushad
mov edx, [_task_now] ;edx指向当前进程
mov ebx, dword [sys_click] ;当前的ticks
add ebx, eax ;增加休眠的ticks
mov [edx + proc_ctrl.wakeup], ebx ;休眠截止时间
call mount_task_run_to_pend_que ;将进程放入暂定队列
popad
retf
;-------------------------------------------------------------------------------
interrupt_init: ;关闭用户进程,回到系统进程
;前20个向量是处理器异常使用的
mov eax,general_exception_handler ;门代码在段内偏移地址
mov bx,flat_4gb_code_seg_sel ;门代码所在段的选择子
mov cx,0x8e00 ;32位中断门,0特权级
call make_gate_descriptor
mov ebx,idt_linear_address ;中断描述符表的线性地址
xor esi,esi
_idt0:
mov [ebx+esi*8],eax
mov [ebx+esi*8+4],edx
inc esi
cmp esi,19 ;安装前20个异常中断处理过程
jle _idt0
;其余为保留或硬件使用的中断向量
mov eax,general_interrupt_handler ;门代码在段内偏移地址
mov bx,flat_4gb_code_seg_sel ;门代码所在段的选择子
mov cx,0x8e00 ;32位中断门,0特权级
call make_gate_descriptor
mov ebx,idt_linear_address ;中断描述符表的线性地址
_idt1:
mov [ebx+esi*8],eax
mov [ebx+esi*8+4],edx
inc esi
cmp esi,255 ;安装普通的中断处理过程
jle _idt1
;准备开放中断
mov word [pidt],256*8-1 ;IDT的界限
mov dword [pidt+2],idt_linear_address
lidt [pidt] ;加载中断描述符表寄存器IDTR
;设置8259A中断控制器
mov al,0x11
out 0x20,al ;ICW1:边沿触发/级联方式
mov al,0x20 ;设置主片的8个中断号从0x20开始
out 0x21,al ;ICW2:起始中断向量
mov al,0x04
out 0x21,al ;ICW3:从片级联到IR2
mov al,0x01
out 0x21,al ;ICW4:非总线缓冲,全嵌套,正常EOI
mov al,0x11
out 0xa0,al ;ICW1:边沿触发/级联方式
mov al,0x70 ;设置从片的8个中断号从0x70开始
out 0xa1,al ;ICW2:起始中断向量
mov al,0x04
out 0xa1,al ;ICW3:从片级联到IR2
mov al,0x01
out 0xa1,al ;ICW4:非总线缓冲,全嵌套,正常EOI
sti ;开放硬件中断
ret
;-------------------------------------------------------------------------------
general_interrupt_handler: ;通用的中断处理过程
push eax
mov al,0x20 ;中断结束命令EOI
out 0xa0,al ;向从片发送
out 0x20,al ;向主片发送
pop eax
iretd
;-------------------------------------------------------------------------------
general_exception_handler: ;通用的异常处理过程
mov byte [show_mem_addr+1600], '!'
mov byte [show_mem_addr+1601], 0x7
hlt
;-------------------------------------------------------------------------------
rtm_0x20_interrupt_handle: ;实时时钟中断处理过程
pushad
mov al,0x60 ;中断结束命令EOI
out 0x20,al ;向8259A主片发送
mov eax, [sys_click]
inc eax ;时钟click增加
mov [sys_click], eax
push eax
call wakeup_task ;尝试唤醒暂停的进程
pop eax
test eax, 0x03 ;每中断4次就切换一次进程(4×10ms=40ms)
jz _time_to_swicth ;到了40ms,进程切换
popad
iretd
_time_to_swicth:
call switch_task2
popad
iretd
;-------------------------------------------------------------------------------
install_0x20_interrupt: ;安装0x20号中断(实时时钟)
cli ;关中断
sidt [pidt] ;取idt段地址
;设置实时时钟中断处理过程
mov eax, rtm_0x20_interrupt_handle ;门代码在段内偏移地址
mov bx, flat_4gb_code_seg_sel ;门代码所在段的选择子
mov cx, 0x8e00 ;32位中断门,0特权级
call make_gate_descriptor
mov ebx, [pidt + 2] ;中断描述符表的线性地址
mov [ebx + 0x20*8], eax ;中断描述符低32位
mov [ebx + 0x20*8 + 4], edx ;中断描述符高32位
lidt [pidt] ;重新加载中断描述符表寄存器IDTR
;设置计时时钟的中断频率
mov al,34h ;设控制字值
out 43h,al ;写控制字到控制字寄存器
mov ax,1193182/100 ;每秒100次中断(10ms一次)
out 40h,al ;写计数器0的低字节
mov al,ah ;AL=AH
out 40h,al ;写计数器0的高字节
in al,0x21 ;读8259主片的IMR寄存器
and al,0xfe ;清除bit 0(计时器)
out 0x21,al ;写回此寄存器
sti ;开中断
ret
;-------------------------------------------------------------------------------
rtm_0x74_interrupt_handle: ;实时时钟中断处理过程
pushad
mov al,0x64 ;中断结束命令EOI
out 0xa0,al ;向8259A从片发送
mov al,0x62
out 0x20,al ;向8259A主片发送
mov ebx, [mouse_buf_wpnt] ;写偏移
in al, 0x60 ;获取鼠标数据
cmp al, 0xfa ;判断是否是标志字0xfa
jnz _data_ok ;如果不是
mov edx, [mouse_enabled] ;再判断是否鼠标已经启动成功
cmp edx, 0x0 ;是否已经启动成功
jnz _data_ok ;已经启动成功
mov dword [mouse_enabled], 0x55 ;确实是启动成功标志,设置启动成功标志,跳转出去
jmp _out
_data_ok:
;将收到的数据放入缓冲区
mov [mouse_buf_addr + ebx], al ;数据写入鼠标缓冲区
inc ebx ;写指针加一
cmp ebx, mouse_buf_end ;判断写指针的位置
jb _out ;如果未到了缓冲区尾部,结束
xor ebx, ebx ;已到缓冲区尾部,写指针翻转
_out:
mov [mouse_buf_wpnt], ebx ;保存新的写指针
popad
iretd
;-------------------------------------------------------------------------------
install_0x74_interrupt: ;安装0x74号中断(鼠标)
cli ;关中断
sidt [pidt] ;取idt段地址
;设置实时时钟中断处理过程
mov eax, rtm_0x74_interrupt_handle ;门代码在段内偏移地址
mov bx, flat_4gb_code_seg_sel ;门代码所在段的选择子
mov cx, 0x8e00 ;32位中断门,0特权级
call make_gate_descriptor
mov ebx, [pidt + 2] ;中断描述符表的线性地址
mov [ebx + 0x74*8], eax ;中断描述符低32位
mov [ebx + 0x74*8 + 4], edx ;中断描述符高32位
lidt [pidt] ;重新加载中断描述符表寄存器IDTR
;打开鼠标中断在MASK寄存器中的对应位
in al,0xa1 ;读8259从片的IMR寄存器
and al,0xf7 ;清除bit 0(此位连接鼠标)
out 0xa1,al ;写回此寄存器
;配置鼠标相关寄存器
call init_keyboard
sti ;开中断
ret
;-------------------------------------------------------------------------------
init_keyboard: ;初始化鼠标相关寄存器
_check1:
in al,0x64 ;键盘缓冲区状态
and al, 0x02
jne _check1
mov al, 0x60
out 0x64, al
_check2:
in al,0x64 ;键盘缓冲区状态
and al, 0x02
jne _check2
mov al, 0x47
out 0x60, al
ret
;-------------------------------------------------------------------------------
enable_mouse: ;启动鼠标
_check3:
in al,0x64 ;键盘缓冲区状态
and al, 0x02
jne _check3
mov al, 0xd4
out 0x64, al
_check4:
in al,0x64 ;键盘缓冲区状态
and al, 0x02
jne _check4
mov al, 0xf4
out 0x60, al
ret
;-------------------------------------------------------------------------------
move_mouse: ;从鼠标数据缓冲区获取数据,并在屏幕上移动鼠标
pushad
cli ;关中断
_check_loop:
;判断缓冲区是否有数据
mov eax, [mouse_buf_wpnt] ;获取写指针
mov ebx, [mouse_buf_rpnt] ;获取读指针
cmp eax, ebx ;读、写指针比较
jz _check_finished ;如果读、写指针相同,退出
;从缓冲区获取一个字符
xor cx, cx ;cx清零
mov cl, [mouse_buf_addr + ebx] ;从读指针处获取一个字符
inc ebx ;读指针后移
cmp ebx, mouse_buf_end ;读指针是否已经到缓冲区末尾
jb _rpnt_wrt ;未到末尾,直接回写新的读指针值
xor ebx, ebx ;读指针已经到了缓冲区末尾,清零
_rpnt_wrt:
mov [mouse_buf_rpnt], ebx ;保存新的读指针
;根据解析状态来处理字符
mov eax, [mouse_chk_state] ;当前处理状态
cmp eax, 1 ;是否是状态1
jz _handle_x ;处理x的值
cmp eax, 2 ;是否是状态2
jz _handle_y ;处理y的值
;处理鼠标命令字 [mouse_chk_state] = 0
_handle_cmd:
mov byte [mouse_cmd], cl ;保存命令字
mov dword [mouse_chk_state], 1 ;状态变为1
jmp _move_mouse_out
;处理x的值 [mouse_chk_state] = 1
_handle_x:
xor eax, eax
mov ax, [mouse_x_pnt] ;获取原鼠标x轴的值
mov [mouse_x_old_pnt], ax ;保存原x轴的值
cmp cl, 0x80 ;x的值是否大于0x80。如果x的值大于0x80,鼠标向左移动
jb _x_mov_right
not cl ;取反加一
inc cl
cmp cx, ax ;要判断x轴是否移动到了屏幕最左端
jb _x_left_ok ;如果移动值小于原x轴的值
mov cx, ax ;鼠标已经移到屏幕最左侧了,调整减数,使鼠标停留在屏幕最左侧
_x_left_ok:
sub ax, cx ;原x轴的值减去鼠标左移的值,得到新的x轴的值
jmp _x_check_ok ;鼠标左移处理完毕,保存新值,并改变命令字状态
_x_mov_right: ;鼠标右移
add ax, cx ;原鼠标x轴的值加上右移步数
cmp ax, 320 ;是否已经超过屏幕右侧
jb _x_check_ok ;未超过,直接保存x轴的值即可
mov ax, 319 ;否则,让x的值保持在320
_x_check_ok:
mov [mouse_x_pnt], ax ;将新的x轴的值写入内存
mov dword [mouse_chk_state], 2
jmp _move_mouse_out
;处理y的值 [mouse_chk_state] = 2
_handle_y:
xor eax, eax
mov ax, [mouse_y_pnt] ;获取原鼠标y轴的值
mov [mouse_y_old_pnt], ax ;保存原y轴的值
cmp cl, 0x80 ;y的值是否大于0x80。如果y的值大于0x80,鼠标向下移动
jb _y_mov_up
not cl
inc cl
add ax, cx ;要判断y轴是否移动到了屏幕最下端
cmp ax, 199
jb _y_down_ok ;如果移动值小于原y轴的值
mov ax, 199 ;鼠标已经移到屏幕最上端了,调整减数,使鼠标停留在屏幕最上侧
_y_down_ok:
jmp _y_check_ok ;鼠标上移处理完毕,保存新值,并改变命令字状态
_y_mov_up: ;鼠标上移
cmp ax, cx ;原鼠标y轴的值加上下移步数
jae _y_up_ok ;是否已经超过屏幕最下端了
mov cx, ax
_y_up_ok:
sub ax, cx ;否则,让的值保持在199
_y_check_ok:
mov [mouse_y_pnt], ax ;将新的y轴的值写入内存
mov dword [mouse_chk_state], 0
;覆盖旧的坐标点
xor eax, eax
mov bx, [mouse_y_old_pnt]
mov ax, 320
mul bx
xor ebx, ebx
mov bx, [mouse_x_old_pnt]
mov cl, [mouse_old_point]
mov byte [pic_mem_addr + eax + ebx], cl
;按照新的坐标绘图
xor eax, eax
mov bx, [mouse_y_pnt]
mov ax, 320 ;y轴做乘法(y×320)
mul bx
xor ebx, ebx
mov bx, [mouse_x_pnt]
mov cl, [pic_mem_addr + eax + ebx] ;记录被覆盖点的颜色
mov [mouse_old_point], cl
mov byte [pic_mem_addr + eax + ebx], 0x1 ;被覆盖点用红色填写
;判断是否点击了鼠标左键
cmp byte [mouse_cmd], 0x09
jnz _move_mouse_out ;如果没有点击鼠标,继续判断后续
cmp word [mouse_y_pnt], 100 ;如果没有点击在屏幕的下半部分,退出
jb _move_mouse_out
cmp word [mouse_x_pnt], 160
jb _click_other_app ;x轴小于160,对应APP1,否则是APP0
mov byte [USR1_PROC + proc_ctrl.cmd], 0x55 ;APP0被点击了
jmp _move_mouse_out
_click_other_app:
mov byte [USR2_PROC + proc_ctrl.cmd], 0x55 ;APP1被点击了
_move_mouse_out:
jmp _check_loop ;在缓冲区中数据处理完毕之前,死循环
_check_finished:
sti ;开中断
popad
ret
;-------------------------------------------------------------------------------
move_point: ;在屏幕的最上方显示一个连续的亮点
push eax
push ecx
xor eax, eax
xor ecx, ecx
mov ax, [_sys_ponit_x]
mov cl, [_sys_color]
mov [pic_mem_addr + eax], cl
inc ax
cmp ax, 320
jb _wrt_bck_x
mov ax, 0
inc cl
cmp cl, 16
jb _wrt_bck_clr
mov cl, 1
_wrt_bck_clr:
mov [_sys_color], cl
_wrt_bck_x:
mov [_sys_ponit_x], ax
pop ecx
pop eax
ret
;-------------------------------------------------------------------------------
Set_color_palette: ;设置前16个调色板的值
;返回:无
cli ;关中断
pushad ;保存寄存器
xor eax, eax
mov dx, 0x3c8
out dx, al ;起始调色板号(我从从0开始设置)
mov ecx, 48 ;一共要写入48个字节,三个对应一个调色板
xor ebx, ebx
mov dx, 0x3c9
_color_palette_loop: ;依次将每个调色板的R、B、G写入
mov al, byte [palette_color_buf + ebx]
shr al, 2
out dx, al
inc ebx
loop _color_palette_loop
popad ;恢复寄存器
ret
;-------------------------------------------------------------------------------
read_hard_disk_0: ;从硬盘读取一个逻辑扇区(平坦模型)
;EAX=逻辑扇区号
;EBX=目标缓冲区线性地址
;返回:EBX=EBX+512
cli
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
sti
ret
;--------------------------------全局变量---------------------------------------
align 4
pidt dw 0
dd 0
pgdt dw 0
dd gdt_base_address ;GDT的性地址
align 4
sys_click dd 0 ;时钟click
_task_now dd 0 ;当前工作进程控制结构地址
_task_next dd 0 ;当前工作进程的下一个结构
_task_pend_que dd 0x0 ;暂停进程队列头
_task_stop_que dd 0x0 ;停止进程队列头
_task_old dd 0 ;保存上一个进程的控制结构地址
_sys_ponit_x dw 0 ;亮点在X轴的值
_sys_color db 1,0 ;亮点的颜色
align 4
SYS_PROC:
istruc proc_ctrl
at proc_ctrl.num, resw 0
at proc_ctrl.state, resw 0
at proc_ctrl.wakeup, resd 0
at proc_ctrl.pre_task, resd 0
at proc_ctrl.next_task, resd 0
at proc_ctrl.task_rsvr, resd 0
at proc_ctrl.task_sel, resw 0
at proc_ctrl.ldt_rsvr, resd 0
at proc_ctrl.ldt_sel, resw 0
at proc_ctrl.ldt, times 16 db 0
at proc_ctrl.tss, times 104 db 0
at proc_ctrl.cmd, times 8 db 0
iend
USR1_PROC:
istruc proc_ctrl
at proc_ctrl.num, resw 0
at proc_ctrl.state, resw 0
at proc_ctrl.wakeup, resd 0
at proc_ctrl.pre_task, resd 0
at proc_ctrl.next_task, resd 0
at proc_ctrl.task_rsvr, resd 0
at proc_ctrl.task_sel, resw 0
at proc_ctrl.ldt_rsvr, resd 0
at proc_ctrl.ldt_sel, resw 0
at proc_ctrl.ldt, times 16 db 0
at proc_ctrl.tss, times 104 db 0
at proc_ctrl.cmd, times 8 db 0
iend
USR2_PROC:
istruc proc_ctrl
at proc_ctrl.num, resw 0
at proc_ctrl.state, resw 0
at proc_ctrl.wakeup, resd 0
at proc_ctrl.pre_task, resd 0
at proc_ctrl.next_task, resd 0
at proc_ctrl.task_rsvr, resd 0
at proc_ctrl.task_sel, resw 0
at proc_ctrl.ldt_rsvr, resd 0
at proc_ctrl.ldt_sel, resw 0
at proc_ctrl.ldt, times 16 db 0
at proc_ctrl.tss, times 104 db 0
at proc_ctrl.cmd, times 8 db 0
iend
;-------------------------------------------------------------------------------
;设备一共2M内存,其中前1M为系统内存,系统内存可从132K到640K进行分配
sys_mem_addr dd 0xc0022000 ;系统可分内存线性地址
sys_mem_end equ 0xc00a0000 ;系统可分内存线性地址末尾
;设备一共2M内存,后1M-2M为用户可用内存
usr_page_addr dd 0x00100000 ;用户可分页内存硬件地址
usr_page_end equ 0x04000000 ;用户可分页内存硬件地址末尾
;远程调用的地址
far_caller_1 dd 0x0 ;偏移(不用)
dw 0x0 ;选择子
far_caller_2 dd 0x0 ;偏移(不用)
dw 0x0 ;选择子
far_caller_3 dd 0x0 ;偏移(不用)
dw 0x0 ;选择子
far_caller_4 dd 0x0 ;偏移(不用)
dw 0x0 ;选择子
;下列变量用于鼠标控制
mouse_cmd db 0 ;鼠标命令字
mouse_old_point db 0
filling db 0,0
mouse_x_pnt dw 160 ;鼠标当前在屏幕上的x值
mouse_y_pnt dw 100 ;鼠标当前在屏幕上的y值
mouse_x_old_pnt dw 160 ;鼠标在原屏幕上的x值
mouse_y_old_pnt dw 100 ;鼠标在原屏幕上的y值
mouse_enabled dd 0x0 ;鼠标是否初始化成功
mouse_buf_wpnt dd 0x0 ;鼠标数据写指针
mouse_buf_rpnt dd 0x0 ;鼠标数据读指针
mouse_chk_state dd 0x0 ;处理数据数据时的状态标志
;调色板的颜色配置数据,共16色
palette_color_buf db 0x00, 0x00, 0x00, ;黑
db 0xff, 0x00, 0x00, ;亮红
db 0x00, 0xff, 0x00, ;亮绿
db 0xff, 0xff, 0x00, ;亮黄
db 0x00, 0x00, 0xff, ;亮蓝
db 0xff, 0x00, 0xff, ;亮紫
db 0x00, 0xff, 0xff, ;浅亮蓝
db 0xff, 0xff, 0xff, ;白
db 0xc6, 0xc6, 0xc6, ;亮灰
db 0x84, 0x00, 0x00, ;暗红
db 0x00, 0x84, 0x00, ;暗绿
db 0x84, 0x84, 0x00, ;暗黄
db 0x00, 0x00, 0x84, ;暗青
db 0x84, 0x00, 0x84, ;暗紫
db 0x00, 0x84, 0x84, ;浅暗蓝
db 0x84, 0x84, 0x84 ;暗灰
;-------------------------------------------------------------------------------
SECTION core_trail
;-------------------------------------------------------------------------------
core_end: