编写实模式多任务操作系统模型之(5)

5.切换到下一将被调度运行task2的进程堆栈空间。由于在进程堆栈保存有进程的断点信息,所以找到进程堆栈的顶位置便可转入到该进程运行。scheduler对于各进程的调度采用轮转式的方式,即按照task1 --> task2 --> task3的顺序,而后再从task1 开始依次循环,使各个进程得以运行。所以将current_task的值增1,再判断current_task是否超过最大进程数MAXTASKS,若超过,则使其复位,从0开始重新计数即可。取得合适的current_task值后,按上面步骤的公式便可找到将被调度进程的堆栈指针的存放地址,从中取出值送到SP寄存器即可。如下段程序代码所示:

     
     inc ax           ; 取得下一将被调度进行的进程号
  cmp ax, MAXTASKS	 ; 该进程号是否超过最大进程数
  jb move_on       ; 若没超过,跳到 move_on 继续
  xor ax, ax	     ; 超过最大进程数,清0,从第一个进程开始调度
move_on:
mov [current_task], ax	
;将更改后进程号送入current_task单元保存,按公式进程堆栈指针的存放地址=SPtable+2*进程号 
取得进程堆栈指针
  mov bx, ax
  shl bx, 1    	
  lea si, [SPtable]
  mov SP, [ds:si + bx] ; 将进程堆栈指针送到SP,切换到进程堆栈空间


6. 恢复断点信息,使下一被调度进程从断点处运行。

     
     pop ds 
pop es 
popa   ;恢复所有被保存的CPU内寄存器值
iret     ;激活被调度进程


其它辅助程序

系统中存在3个被调度的程序,分别为task1、task2、task3。由于这是一个实验程序,所以它们的功能较为单一,主要是将自身的一个16位计数器进行循环计数,而后通过直接写屏方式在显示器上显示出来。由于现行机器的运行速度较快,为使用户能看清计数器的变化过程,在各个进程的执行过程中加入了人为的延时控制。每一个进程都是一个无限的循环过程,只要调度程序在运行,各个进程就会无限运行。

此外系统中还有几个辅助程序:

◆ 16进制数显示子程序printhex

这个子程序主要是为系统内的3个演示进程服务,它可以将从堆栈传递过来的一个16位的数以16进制形式显示到屏幕的指定位置,通过直接写屏方式在显示器上显示出来。在VGA的 80X25 彩色文本显示模式下,一个字符在显示缓冲区上占2个字节的空间,其中所显示字符的ASCⅡ码存于低地址单元中,字符的显示属性(前景、背景、高亮和闪烁等)存于高地址单元中,如果将字符及其显示属性存储位置弄错,将会产生花屏的现象。Printhex根据显示位置参数计算出所需显示的准确位置,避免产生花屏的现象。调用者只需提供合法的显示位置即可,行号应在1~25之间,列号在1~80之间。

◆ 键盘中断子程序keybd

它是一个基本的内核级键盘中断处理程序,显示键盘的ASCⅡ码到屏幕上。它不受调度进程的控制,只要有键盘中断发生,该中断服务程序就会运行。在该程序被触发运行时,从60H端口读入键盘的扫描码。扫描码最高位的状态标志着其是键入码还是释放码,如为释放码则不进行处理,直接返回;若为按键的压入码,则以键入码作为索引在键盘扫描码/ASCⅡ码对照表查得对应的ASCⅡ码,通过int 10h功能调用将其输出到显示器上。在键盘中断返回前,要向键盘内的单片机发复位信号, 先将61h端口的最高位置1,而后再置0,这样键盘才不会死锁。

kernel.asm完整代码及注释如下:

     
     ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 内核程序kernel.asm
; 在系统时钟中断的驱动下,依次使系统内的3个进程轮流占用CPU完成各
; 自的工作:在屏幕上指定位置循环显示自己的计数器。具有内核级键盘中
; 断,显示出键盘ASCII码
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[BITS 16]
[ORG 0x00]		
STACKSIZE  equ 0x400      ; 各进程的堆栈空间大小为1k个字节
MAXTASKS   equ 3           ; 最大进程数 
VIDEORAM   equ 0xb800     ; 显示缓存段地址 
KERNELSEG  equ 0x8000     ; kernel 程序段地址 
;------------------------------------------------------------------
start:	
     	mov ax,KERNELSEG
	mov ds, ax	          ; 数据段,堆栈段寄存器的
	mov ss, ax	          ; 值均设为 KERNELSEG
	mov sp, 0xFFFF	     	  ; kernel 堆栈指针=0xFFFF 
	mov dx,0x3f2	
	mov al,0x0c
	out dx,al             	  ;关闭软驱马达
	mov ax, 0x0003  
	int 0x10                  ; 清屏 
	mov dx, 0x0600	  	  ; 进程的堆栈基地址 
	add dx, STACKSIZE	  ; 进程1的堆栈指针= 0xA00
	mov ax, task1     	  ; 进程1入口地址 
	call taskinit     	  ; 初始化进程1
	add dx, STACKSIZE  	  ; 进程2的堆栈指针= 0xD00
	mov ax, task2       	  ; 进程2入口地址 
	call taskinit      	  ; 初始化进程2
	add dx, STACKSIZE  	  ; 进程3的堆栈指针= 0x1000
	mov ax, task3      	  ; 进程3入口地址 
	call taskinit     	  ; 初始化进程3
	; 重新设置系统定时器的中断向量
	cli                 	  ;关中断,以免在设置中断向量发生意外
        mov ax, KERNELSEG
        push ds 	          ;保存ds值
        mov bx,0
        mov ds,bx       	  ;ds=0
	mov word [ds:0x08*4], scheduler  ; 定时器中断向量偏移送到0:20~0:21
	mov word [ds:0x08*4+2], ax       ; 定时器中断向量段地址送到0:22~0:23
        ; 重新设置键盘的中断向量 
	mov word [ds:0x09*4], keybd      ; 键盘的中断向量偏移送到0:24~0:25
	mov word [ds:0x09*4+2], ax       ; 键盘的中断向量段地址 0:26~0:27
	pop ds 				 ; 恢复ds值
	sti	  			 ; 开中断,允许中断进入
	jmp $       			 ; kernel在此等待时钟中断的到来 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 被调度进程task1 
; 功能:对于自身的计数器进行计数,而后在屏幕的3行1显示输出
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
task1:
.l1     dec word [task1ctra]       ; 产生短时间延迟,以免计数显示过快
        jnz .l1 
        inc word [task1ctr]        ; 计数器增1
        push word 3                ; 输出行号
        push word 0                ; 输出列号
        push word [task1ctr]       ; 将参数通过堆栈进行传递
        call printhex	           ; 调用16进制显示子程序
	jmp task1                  ; 循环显示
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 被调度进程task2 
; 功能:对于自身的计数器进行计数,而后在屏幕的6行1显示输出
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
task2:                  
.l1     dec word [task2ctra]
        jnz .l1 
        inc word [task2ctr]
        push word 6 
        push word 0 
        push word [task2ctr]
        call printhex	
	jmp task2 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 被调度进程task3
; 功能:对于自身的计数器进行计数,而后在屏幕的9行1显示输出
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
task3:                  
.l1     dec word [task3ctra]
        jnz .l1 
        inc word [task3ctr]
        push word 9 
        push word 0 
        push word [task3ctr]
        call printhex	
	jmp task3 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;任务初始化子程序taskinit
;功能:     在被初始化进程的堆栈空间中压入Flag寄存器,进程的入口
;          地址,及要被保护的CPU寄存器值,同时将进程的栈顶地址
;          写入sptable数组中。
;参数:    bx= 进程的堆栈指针
;         ax= 进程的入口地址偏移
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
taskinit:             
	pusha
 	push es
	push ds      	   ; 将现场保护到在kernel的的堆栈空间中。
 	mov bp, sp	   ; 保存kernel的堆栈指针。
 	mov sp, dx	   ; 切换到进程的堆栈空间中
 	pushf	     	   ; 在进程的堆栈空间中压入标志寄存器Flag
 	push cs      	   ; 在进程的堆栈空间中压入代码段值
 	push ax		   ; 在进程的堆栈空间中压入进程的入口地址偏移
     pusha 
 	push es
 	push ds		   ; 在进程的堆栈空间中保护CPU寄存器值
        ; 按公式:进程堆栈指针的存放地址=sptable+2*进程号
        ; 得到进程堆栈指针的存放地址,将进程堆栈指针的值存
        ; 到该内存单元中,
 	mov dx, sp	
 	mov bx, [taskcount]
 	shl bx, 1	
 	lea si, [sptable]
 	mov [ds:si + bx], dx
 	inc word [taskcount]  	      ; 将进程号增1,指向下一个将被初始化的进程
 	mov sp, bp	              ; SP恢复为kernel的堆栈指针
 	pop ds
 	pop es
     	popa                   	      ; 恢复现场
 	ret                   	      ; 返回到kernel
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;进程调度子程序scheduler
 ;功能:1。保存被剥夺运行权的进程的堆栈指针
 ;      2。恢复下一个将被调度运行的进程的堆栈指针
 ;      3。从将被调度运行的进程的堆栈中恢复断点信息,
 ;         使CPU转到该进程去执行
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 scheduler:
        pusha
 	push es
 	push ds                   ; 在当前进程的堆栈中保存断点处的CPU寄存器值
 	mov al, 0x20	
	out 0x20, al	          ; 给中断控制器8259送EOI指令
	pushf		
	call 0xF000:0xFEA5        ; 使原系统时钟中断处理运行
	mov ax, [current_task]
	mov bx, ax
	shl bx, 1	
	lea si, [sptable]
	mov [ds:si + bx], sp      ; 保存将被剥夺执行权的进程的堆栈指针
	inc ax                    ; 取得下一将被调度的进程的进程号
	cmp ax, MAXTASKS          ; 该进程号是否超过最大进程数	
	jb move_on                ; 若没超过,跳到 move_on 继续
	xor ax, ax	          ; 超过最大进程数,清0,从第一个进程开始调度
move_on:
	mov [current_task], ax	  ; 将更改后进程号的送入current_task单元保存
   	;按公式 进程堆栈指针的存放地址=sptable+2*进程号 取得进程堆栈指针
   	mov bx, ax
    	shl bx, 1    	
  	lea si, [sptable]
  	mov sp, [ds:si + bx]      ; 将进程堆栈指针送到SP,切换到进程的堆栈空间 
 	pop ds
 	pop es
        popa                     ; 进程的堆栈空间从恢复上次进程被中断时
                                 ; 所有被保存的CPU寄存器
 	iret                     ; 激活被调度进程运行
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;键盘中断子程序keybd
;功能:读入键盘扫描码,转换为ASCII码后在显示器上输出
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
keybd:
        sti
	push ax
	push bx
	push cx
	push di
	push es
	mov ax,0
	in al,60h           ; 读入键盘扫描码
	cmp al,80h          ; 是按键压入码还是释放码?
	jb .l1              ; 是按键压入码则转 .l1处理
	jmp .l2             ; 否则结束中断
.l1: 
	mov ah,0
	mov si,ax
	mov al,[si+asctab]  ; 查表,转换为ASCII码
	mov ah,0eh
	int 10h             ; 在显示器上输出转换后的ASCII码
	cmp al,0dh          ; 是否为回车键?
	jnz .l2             ; 否,则结束中断
	mov ax,0e0ah
	mov bh,07h
	int 10h             ; 是回车键,则产生换行动作。
	in al,61h
.l2
	or al,80h
	out 61h,al
	and al,0x7f
	out 61h,al         ; 给键盘内的单片机发复位信号
	pop es
	pop di
	pop cx
	pop bx
	pop ax
	mov al,20h
	out 20h,al         ; 给中断控制器8259送EOI信号
	iret               ; 键盘中断结束
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;printhex:16 进制数显示子程序 
;; 参数:   参数1=显示行坐标
;;         参数2=显示列坐标
;;         参数3=显示的一个字数值
;;         参数通过堆栈进行传递
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
printhex:
     	push bp
     	mov bp,sp
	push ax
	push bx
	push cx
	push dx
     	push di
     	push es                 ;保存现场
     	mov ax,VIDEORAM
     	mov es,ax               ;显示缓存段地址 0xb800 -> es
	;按公式 (行号*80+列号)*2 将行列值转换为显示缓存的绝对地址
     	xor ax, ax
	mov ax, word [bp+8]   ; 从堆栈中取得行号
     	xor cx,cx
     	mov cl,80
     	mul cl                  ;行号*80 -> ax
     	add ax,[bp+6]         ;从堆栈中取得列号,将列号+行号*80 -> ax
     	add ax,ax              ;(行号*80+列号)*2
     	sub ax,2               ;显示缓存从0编址,调整为实际地址。
     	mov di, ax             ;实际地址送di保存
	mov ax, word [ss:bp+4] ; 从堆栈中取得要输出的16位数据
	;将要输出的数据从低到高每4位一组转换为16进制数,转换规则为
	;若数值<=9,则加0x30,否则加0x37.
	;写到指定的显存中输出;
	mov cx, 0x08			
     	add di,cx
     	std
.continue
	mov bx, ax
	and bl, 0x0F
	cmp bl, 0x9
	ja .hex
	add bl, 0x30
	jmp .print
.hex:
	add bl, 0x37
.print:	
     	push ax
     	mov al,bl
     	mov ah,7 
     	stosw
     	pop ax 
	sub cx, 2
	shr ax, 4
	cmp cx, 0
	jnz .continue
     	pop es	
     	pop di
	pop dx
	pop cx
	pop bx
	pop ax
     	pop bp   	; 恢复现场
	ret 6   	; 返回,清除参数,平衡堆栈
; End of printhex
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 数据区
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
task1ctr  dw 0   	;task1计数器
task1ctra dw 0  	;task1时间延迟计数器
task2ctr  dw 0 		;task2计数器
task2ctra dw 0 		;task2时间延迟计数器
task3ctr  dw 0 		;task3计数器
task3ctra dw 0 		;task3时间延迟计数器
current_task dw MAXTASKS   	 ;当前任务索引号
taskcount    dw 0     	   	 ;当前kernel内的任务总数
sptable:     resw    MAXTASKS	 ;sptable 数组
;键盘扫描码/ascii码对照表
asctab db 0xff,0x1b,"1234567890-=",08h,09h,"qwertyuiop[]asdfghjkl;'zxcvbnm,./"
;;kernel 结束


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值