第三章
真正实现多任务前需要了解的寄存器知识
当我们在做中断的时候会进行除了把PC入栈之外的动作,就是会把通用寄存器入栈,这是为什么呢?加入我们正在进行加法运算,使用了ACC寄存器,这时候进入了中断,我们的中断中也使用了ACC寄存器,当中断退出时原来的ACC已经被覆盖了,这样程序就出错了,这种事情在真实情况下是不会发生的。保证这个的方法就是在进入中断前,会把通用寄存器都一一入栈,在退出中断事在一一出栈。
那么为什么普通的函数切换就没有呢?因为编译器认为函数切换都是已知的,因为是程序中写的,并不像中断那样随机,所以函数切换只是根据需要编译器尽量使用不重复的寄存器保存临时变量,除非通用寄存器全部被占用,才会使用堆栈。
我们做的操作系统就是在普通函数中模拟中断的动作,所以我们需要将这个保存通用寄存器的动作补充上。
我们先看一个中断函数
void int0_int (void) interrupt INT0_VECTOR {
u8 i;
unsigned int counter = 0xffff;
EA = 0;
IE0 = 0;
for(i=0; i<5; i++)
{
counter = 0x0fff;
while( (~IE0) && (counter--) );
if(IE0) {
IE0=0;
}
EA = 1;
}
}
不要管他是干什么用的,因为我也不知道,我们主要看他的汇编
54: void int0_int (void) interrupt INT0_VECTOR {
55: u8 i;
C:0x061C C0E0 PUSH ACC(0xE0)
C:0x061E C0F0 PUSH B(0xF0)
C:0x0620 C083 PUSH DPH(0x83)
C:0x0622 C082 PUSH DPL(0x82)
C:0x0624 C0D0 PUSH PSW(0xD0)
C:0x0626 75D000 MOV PSW(0xD0),#0x00
C:0x0629 C000 PUSH 0x00
C:0x062B C006 PUSH 0x06
C:0x062D C007 PUSH 0x07
56: unsigned int counter = 0xffff;
C:0x062F 900013 MOV DPTR,#0x0013
C:0x0632 74FF MOV A,#0xFF
C:0x0634 F0 MOVX @DPTR,A
C:0x0635 A3 INC DPTR
C:0x0636 74FF MOV A,#0xFF
C:0x0638 F0 MOVX @DPTR,A
57: EA = 0;
C:0x0639 C2AF CLR EA(0xA8.7)
58: IE0 = 0;
C:0x063B C289 CLR IE0(0x88.1)
59: for(i=0; i<5; i++)
C:0x063D 900012 MOV DPTR,#0x0012
C:0x0640 E4 CLR A
C:0x0641 F0 MOVX @DPTR,A
C:0x0642 900012 MOV DPTR,#0x0012
C:0x0645 E0 MOVX A,@DPTR
C:0x0646 FF MOV R7,A
C:0x0647 7E00 MOV R6,#0x00
C:0x0649 C3 CLR C
C:0x064A EF MOV A,R7
C:0x064B 9405 SUBB A,#0x05
C:0x064D EE MOV A,R6
C:0x064E 6480 XRL A,#P0(0x80)
C:0x0650 9480 SUBB A,#P0(0x80)
C:0x0652 5030 JNC C:0684
60: {
61: counter = 0x0fff;
C:0x0654 900013 MOV DPTR,#0x0013
C:0x0657 740F MOV A,#0x0F
C:0x0659 F0 MOVX @DPTR,A
C:0x065A A3 INC DPTR
C:0x065B 74FF MOV A,#0xFF
C:0x065D F0 MOVX @DPTR,A
62: while( (~IE0) && (counter--) );
C:0x065E A289 MOV C,IE0(0x88.1)
C:0x0660 B3 CPL C
C:0x0661 5012 JNC C:0675
C:0x0663 900013 MOV DPTR,#0x0013
C:0x0666 74FF MOV A,#0xFF
C:0x0668 75F0FF MOV B(0xF0),#0xFF
C:0x066B 120538 LCALL C?ILDIX(C:0538)
C:0x066E AFF0 MOV R7,B(0xF0)
C:0x0670 FE MOV R6,A
C:0x0671 EF MOV A,R7
C:0x0672 4E ORL A,R6
C:0x0673 70E9 JNZ C:065E
63: if(IE0) {
C:0x0675 308902 JNB IE0(0x88.1),C:067A
64: IE0=0;
C:0x0678 C289 CLR IE0(0x88.1)
65: }
66: EA = 1;
C:0x067A D2AF SETB EA(0xA8.7)
67: }
C:0x067C 900012 MOV DPTR,#0x0012
C:0x067F E0 MOVX A,@DPTR
C:0x0680 04 INC A
C:0x0681 F0 MOVX @DPTR,A
C:0x0682 80BE SJMP C:0642
68: }
C:0x0684 D007 POP 0x07
C:0x0686 D006 POP 0x06
C:0x0688 D000 POP 0x00
C:0x068A D0D0 POP PSW(0xD0)
C:0x068C D082 POP DPL(0x82)
C:0x068E D083 POP DPH(0x83)
C:0x0690 D0F0 POP B(0xF0)
C:0x0692 D0E0 POP ACC(0xE0)
C:0x0694 32 RETI
他会把一些寄存器压入占中,退出前又会从栈中弹出,但是我们发现他并没有把所有的寄存器都压入栈中,这是因为编译器在编译的时候已经知道了这个中断函数会使用哪些寄存器,所以只把中断中使用到的,会覆盖原来值的寄存器入栈了。
作为我们编写的操作系统没有办法像编译器一样提前知道任务都是用了哪些寄存器,所以只能把可能会被改变的寄存器都压入栈中。
我们先来实现一个任务切换函数
void OS_TASK_SW(void)
{
EA=0;
#pragma asm
PUSH ACC
PUSH B
PUSH DPH
PUSH DPL
PUSH PSW
MOV PSW,#00H
PUSH AR0
PUSH AR1
PUSH AR2
PUSH AR3
PUSH AR4
PUSH AR5
PUSH AR6
PUSH AR7
#pragma endasm
os_task_stack_top = SP;
//切换任务栈
SP = os_task_stack_top;
#pragma asm
POP AR7
POP AR6
POP AR5
POP AR4
POP AR3
POP AR2
POP AR1
POP AR0
POP PSW
POP DPL
POP DPH
POP B
POP ACC
#pragma endasm
EA=1;
}
目前这个函数只是入栈,出栈,任务不会切换,但是这个是任务切换的核心,之后的任务切换都是在这个基础上得到的。
下一章我们将正式开始实现操作系统的任务部分,包括管理,切换,状态等。