内核程序kernel.asm
kernel.asm是系统的核心程序,主要由任务初始化子程序taskinit、进程调度子程序scheduler、键盘中断子程序keybd、16进制数显示子程序printhex及3个参与被调度程序task1~task3等几部分组成,其中进程调度子程序是多任务实现的关键。
内核的工作方式是在系统时钟中断的驱动下,依次使3个进程分别得到调度,使其分时占用CPU完成各自的工作—在屏幕的不同位置分别显示各自的计数器值,即在某一时刻实际只有一个进程在运行,其余的进程都处于挂起状态。被挂起进程的断点信息,如断点地址、断点处的CPU内的各个寄存器状态,均被保存在各进程的堆栈内。这样当挂起进程再次被调度运行时,调度程序将断点的信息恢复出来,挂起进程便接着断点继续运行。内核进程调度如图3所示。
Kernel程序运行时的内存布局图4所示。
图4是图2中的Kernel内存区(80000H~803FF)的放大显示,从图中可见在80000H~8FFFFH的64KB内存区中,低端的1KB区域80000H~803FFH为Kernel的所有程序及数据区。Kernel程序区部分有用于演示目的的3个进程代码。当这3个进程被初始化时,分别给每一个进程分配了各自独立的、长度为1KB的3个堆栈区,如图4中的文字标记所示。分配给进程的堆栈区主要用于保存各自被中断运行时的断点及返回信息。在进行进程切换时,要进行进程堆栈空间的切换,这些进程堆栈区是实现多任务功能的重要内存区。Kernel堆栈区位于内存的高端,足以满足程序运行的需要。
Kernel程序运行流程如图5所示。Kernel在运行时,首先做相关的初始化工作,主要包括数据段、堆栈段寄存器初始化,将其均设定为Kernel的段地址;堆栈指针设在内存的高端;关软盘马达;清屏;初始化3 个进程,使其处于运行就绪状态;重新设置定时器及键盘中断向量;而后在时钟中断的驱动下调度程序运行,使3个进程task1~task3分别运行。
任务初始化子程序Taskinit
Taskinit子程序是用来初始化一个进程的,使一个进程成为运行就绪状态。其所需要的参数有两个:一个为进程的入口地址,另一个为该进程设定的堆栈指针。这两个参数分别送入ax及dx寄存器,利用call指令调用该子程序便可初始化一个进程。在kernel中将进程的可用堆栈区设为绝对地址为 80600H 以上的内存区,每个进程拥有1KB(400H)的堆栈区,这样第一个进程的堆栈指针就设为80A00H,因为 X86 CPU堆栈的增长方向是指向低地址的。如下指令可用来初始化task1:
进入Taskinit后要完成的工作如下:
1.在Kernel堆栈区保存CPU的通用寄存器ax、cx、bx、dx、SP、bp、si、di及ES和DS。
此时Kernel堆栈区如图6状态。
2.将堆栈指针SP用进程的堆栈指针代替,堆栈指针SP指向进程的堆栈,模拟中断发生时的堆栈操作,在进程的堆栈区先压入标志寄存器,CS及进程的入口地址(在AX中),而后再压入中断发生时要保护的通用寄存器及ES和DS,此时进程堆栈状态如图7所示。
由于在调度程序scheduler中,当其选中了一个将要被调度运行的进程后,它将从SPtable中找到该进程的堆栈指针,并将CPU的堆栈指针寄存器SP设定为该进程的堆栈指针,这样SP便指向了进程的堆栈空间。在以后恢复现场工作时,将进程堆栈空间内所压入的值恢复到相应的寄存中,最后的一条中断返回指令iret将把进程被中断的断点及标志分别送到IP、CS及标志寄存器中,这样该进程便得以占用CPU,获得运行权,运行自已的程序。
3.将进程堆栈指针SP保存到数据区的SPtable中。SPtable是一个数组,每个单元2个字节,专用于保存被剥夺运行权的、进程断点处堆栈指针SP的值。在进程被首次初始化时,其保存有进程序堆栈空间的栈顶地址,按进程初始化的先后顺序依次保存,如表1所示。
表1 SPtable保存有进程序堆栈空间栈顶地址
4.恢复SP为Kernel堆栈区的堆栈指针,将在第1步中保存的各寄存器值出栈,返回到调用程序。
5.各个进程初始化后,各自的堆栈空间均如第2步中所示的状态。
在 下一篇文章中,我们将继续进行程序模块的分析,并完成程序的编译和安装运行。
kernel.asm是系统的核心程序,主要由任务初始化子程序taskinit、进程调度子程序scheduler、键盘中断子程序keybd、16进制数显示子程序printhex及3个参与被调度程序task1~task3等几部分组成,其中进程调度子程序是多任务实现的关键。
内核的工作方式是在系统时钟中断的驱动下,依次使3个进程分别得到调度,使其分时占用CPU完成各自的工作—在屏幕的不同位置分别显示各自的计数器值,即在某一时刻实际只有一个进程在运行,其余的进程都处于挂起状态。被挂起进程的断点信息,如断点地址、断点处的CPU内的各个寄存器状态,均被保存在各进程的堆栈内。这样当挂起进程再次被调度运行时,调度程序将断点的信息恢复出来,挂起进程便接着断点继续运行。内核进程调度如图3所示。
Kernel程序运行时的内存布局图4所示。
图4是图2中的Kernel内存区(80000H~803FF)的放大显示,从图中可见在80000H~8FFFFH的64KB内存区中,低端的1KB区域80000H~803FFH为Kernel的所有程序及数据区。Kernel程序区部分有用于演示目的的3个进程代码。当这3个进程被初始化时,分别给每一个进程分配了各自独立的、长度为1KB的3个堆栈区,如图4中的文字标记所示。分配给进程的堆栈区主要用于保存各自被中断运行时的断点及返回信息。在进行进程切换时,要进行进程堆栈空间的切换,这些进程堆栈区是实现多任务功能的重要内存区。Kernel堆栈区位于内存的高端,足以满足程序运行的需要。
Kernel程序运行流程如图5所示。Kernel在运行时,首先做相关的初始化工作,主要包括数据段、堆栈段寄存器初始化,将其均设定为Kernel的段地址;堆栈指针设在内存的高端;关软盘马达;清屏;初始化3 个进程,使其处于运行就绪状态;重新设置定时器及键盘中断向量;而后在时钟中断的驱动下调度程序运行,使3个进程task1~task3分别运行。
任务初始化子程序Taskinit
Taskinit子程序是用来初始化一个进程的,使一个进程成为运行就绪状态。其所需要的参数有两个:一个为进程的入口地址,另一个为该进程设定的堆栈指针。这两个参数分别送入ax及dx寄存器,利用call指令调用该子程序便可初始化一个进程。在kernel中将进程的可用堆栈区设为绝对地址为 80600H 以上的内存区,每个进程拥有1KB(400H)的堆栈区,这样第一个进程的堆栈指针就设为80A00H,因为 X86 CPU堆栈的增长方向是指向低地址的。如下指令可用来初始化task1:
mov dx, 0x0600 ; 进程的堆栈基地址 add dx, STACKSIZE ; 进程1的堆栈指针 = 0xa00 mov ax, task1 ; 进程1入口地址 call taskinit ; 初始化task1 |
进入Taskinit后要完成的工作如下:
1.在Kernel堆栈区保存CPU的通用寄存器ax、cx、bx、dx、SP、bp、si、di及ES和DS。
pusha push es push ds |
此时Kernel堆栈区如图6状态。
2.将堆栈指针SP用进程的堆栈指针代替,堆栈指针SP指向进程的堆栈,模拟中断发生时的堆栈操作,在进程的堆栈区先压入标志寄存器,CS及进程的入口地址(在AX中),而后再压入中断发生时要保护的通用寄存器及ES和DS,此时进程堆栈状态如图7所示。
由于在调度程序scheduler中,当其选中了一个将要被调度运行的进程后,它将从SPtable中找到该进程的堆栈指针,并将CPU的堆栈指针寄存器SP设定为该进程的堆栈指针,这样SP便指向了进程的堆栈空间。在以后恢复现场工作时,将进程堆栈空间内所压入的值恢复到相应的寄存中,最后的一条中断返回指令iret将把进程被中断的断点及标志分别送到IP、CS及标志寄存器中,这样该进程便得以占用CPU,获得运行权,运行自已的程序。
3.将进程堆栈指针SP保存到数据区的SPtable中。SPtable是一个数组,每个单元2个字节,专用于保存被剥夺运行权的、进程断点处堆栈指针SP的值。在进程被首次初始化时,其保存有进程序堆栈空间的栈顶地址,按进程初始化的先后顺序依次保存,如表1所示。
SPtable | SPtable+2 | SPtable+4 |
Task1堆栈指针SP | Task2堆栈指针SP | Task3堆栈指针SP |
4.恢复SP为Kernel堆栈区的堆栈指针,将在第1步中保存的各寄存器值出栈,返回到调用程序。
5.各个进程初始化后,各自的堆栈空间均如第2步中所示的状态。
在 下一篇文章中,我们将继续进行程序模块的分析,并完成程序的编译和安装运行。