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