查看: 1421|回复: 0
打印 上一主题 下一主题

用KEIL写多任务系统的技巧与注意事项

[复制链接]
跳转到指定楼层
沙发
发表于 2015-9-23 16:03:49 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
用KEIL写多任务系统的技巧与注意事项

C51编译器很多,KEIL是其中比较流行的一种.我列出的所有例子都必须在KEIL中使用.为何?不是因为KEIL好所以用它(当然它的确很棒),而是因为这里面用到了KEIL的一些特性,如果换到其它编译器下,通过编译的倒不是问题,但运行起来可能是堆栈错位,上下文丢失等各种要命的错误,因为每种编译器的特性并不相同.所以在这里先说清楚这一点.
但是,我开头已经说了,这套帖子的主要目的是阐述原理,只要你能把这几个例子消化掉,那么也能够自已动手写出适合其它编译器的OS.

好了,说说KEIL的特性吧,先看下面的函数:

sbit sigl = P1^7;
void func1(){
        register char data i;
        i = 5;
        do{
                sigl = !sigl;
        }while(--i);
}

你会说,这个函数没什么特别的嘛!呵呵,别着急,你将它编译了,然后展开汇编代码再看看:

   193: void func1(){
   194:         register char data i;
   195:         i = 5;
C:0x00C3    7F05     MOV      R7,#0x05
   196:         do{
   197:                 sigl = !sigl;
C:0x00C5    B297     CPL      sigl(0x90.7)
   198:         }while(--i);
C:0x00C7    DFFC     DJNZ     R7,C:00C5
   199: }
C:0x00C9    22       RET      

看清楚了没?这个函数里用到了R7,却没有对R7进行保护!
有人会跳起来了:这有什么值得奇怪的,因为上层函数里没用到R7啊.呵呵,你说的没错,但只说对了一半:事实上,KEIL编译器里作了约定,在调子函数前会尽可能释放掉所有寄存器.通常性况下,除了中断函数外,其它函数里都可以任意修改所有寄存器而无需先压栈保护(其实并不是这样,但现在暂时这样认为,饭要一口一口吃嘛,我很快会说到的).
这个特性有什么用呢?有!当我们调用任务切换函数时,要保护的对象里可以把所有的寄存器排除掉了,就是说,只需要保护堆栈即可!

现在我们回过头来看看之前例子里的任务切换函数:

void task_switch(){
        task_sp[task_id] = SP;//保存当前任务的栈指针

        if(++task_id == MAX_TASKS)//任务号切换到下一个任务
                task_id = 0;

        SP = task_sp[task_id];//将系统的栈指针指向下个任务的私栈.
}

看到没,一个寄存器也没保护,展开汇编看看,的确没保护寄存器.


好了,现在要给大家泼冷水了,看下面两个函数:

void func1(){
        register char data i;
        i = 5;
        do{
                sigl = !sigl;
        }while(--i);
}
void func2(){
        register char data i;
        i = 5;
        do{
                func1();
        }while(--i);
}

父函数fun2()里调用func1(),展开汇编代码看看:
   193: void func1(){
   194:         register char data i;
   195:         i = 5;
C:0x00C3    7F05     MOV      R7,#0x05
   196:         do{
   197:                 sigl = !sigl;
C:0x00C5    B297     CPL      sigl(0x90.7)
   198:         }while(--i);
C:0x00C7    DFFC     DJNZ     R7,C:00C5
   199: }
C:0x00C9    22       RET      
   200: void func2(){
   201:         register char data i;
   202:         i = 5;
C:0x00CA    7E05     MOV      R6,#0x05
   203:         do{
   204:                 func1();
C:0x00CC    11C3     ACALL    func1(C:00C3)
   205:         }while(--i);
C:0x00CE    DEFC     DJNZ     R6,C:00CC
   206: }
C:0x00D0    22       RET      

看清楚没?函数func2()里的变量使用了寄存器R6,而在func1和func2里都没保护.
听到这里,你可能又要跳一跳了:func1()里并没有用到R6,干嘛要保护?没错,但编译器是怎么知道func1()没用到R6的呢?是从调用关系里推测出来的.
一点都没错,KEIL会根据函数间的直接调用关系为各函数分配寄存器,既不用保护,又不会冲突,KEIL好棒哦!!等一下,先别高兴,换到多任务的环境里再试试:

void func1(){
        register char data i;
        i = 5;
        do{
                sigl = !sigl;
        }while(--i);
}
void func2(){
        register char data i;
        i = 5;
        do{
                sigl = !sigl;
        }while(--i);
}

展开汇编代码看看:

   193: void func1(){
   194:         register char data i;
   195:         i = 5;
C:0x00C3    7F05     MOV      R7,#0x05
   196:         do{
   197:                 sigl = !sigl;
C:0x00C5    B297     CPL      sigl(0x90.7)
   198:         }while(--i);
C:0x00C7    DFFC     DJNZ     R7,C:00C5
   199: }
C:0x00C9    22       RET      
   200: void func2(){
   201:         register char data i;
   202:         i = 5;
C:0x00CA    7F05     MOV      R7,#0x05
   203:         do{
   204:                 sigl = !sigl;
C:0x00CC    B297     CPL      sigl(0x90.7)
   205:         }while(--i);
C:0x00CE    DFFC     DJNZ     R7,C:00CC
   206: }
C:0x00D0    22       RET      


看到了吧?哈哈,这回神仙也算不出来了.因为两个函数没有了直接调用的关系,所以编译器认为它们之间不会产生冲突,结果分配了一对互相冲突的寄存器,当任务从func1()切换到func2()时,func1()中的寄存器内容就给破坏掉了.大家可以试着去编译一下下面的程序:

sbit sigl = P1^7;
void func1(){
        register char data i;
        i = 5;
        do{
                sigl = !sigl;
                task_switch();
        }while(--i);
}
void func2(){
        register char data i;
        i = 5;
        do{
                sigl = !sigl;
                task_switch();
        }while(--i);
}

我们这里只是示例,所以仍可以通过手工分配不同的寄存器避免寄存器冲突,但在真实的应用中,由于任务间的切换是非常随机的,我们无法预知某个时刻哪个寄存器不会冲突,所以分配不同寄存器的方法不可取.那么,要怎么办呢?
这样就行了:

sbit sigl = P1^7;
void func1(){
        static char data i;
        while(1){
                i = 5;
                do{
                        sigl = !sigl;
                        task_switch();
                }while(--i);
        }
}
void func2(){
        static char data i;
        while(1){
                i = 5;
                do{
                        sigl = !sigl;
                        task_switch();
                }while(--i);
        }
}

将两个函数中的变量通通改成静态就行了.还可以这么做:

sbit sigl = P1^7;
void func1(){
        register char data i;
        while(1){
                i = 5;
                do{
                        sigl = !sigl;
                }while(--i);
                task_switch();
        }
}
void func2(){
        register char data i;
        while(1){
                i = 5;
                do{
                        sigl = !sigl;
                }while(--i);
                task_switch();
        }
}

即,在变量的作用域内不切换任务,等变量用完了,再切换任务.此时虽然两个任务仍然会互相破坏对方的寄存器内容,但对方已经不关心寄存器里的内容了.

以上所说的,就是"变量覆盖"的问题.现在我们系统地说说关于"变量覆盖".转载

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 加入中科因仑

本版积分规则

快速回复 返回顶部 返回列表