时间片轮询多任务操作系统( TinyOS51 V1.1 )

时间:2021-05-26 19:50:15

选自<<项目驱动-单片机应用设计基础>>

 

/*    
**        一般来说,操作系统的调度算法主要有三类:时间片轮询,优先级与带优先级的时间片轮询调度
**        不是任务主动放弃CPU而造成的任务调用就是抢占式任务调度


**        在使用时间片轮询调度算法的操作系统中,会在2种情况下进行任务切换
**        (1)任务在调用操作系统提供的"等待"类服务( 如延时,获得信号量,等待消息等 ),会主动请求调度
**        (2)对于完全基于优先级调度算法的操作系统来说,调用任何一个系统函数,或任何一个中断服务程序
**        结束时,都可能让高优先级的任务处于可执行状态,都可能进行任务调度

**        TinyOS51V1.1已经是时间片轮询多任务操作系统,因此不再给用户提供任务切换函数,它仅提供给TinyOS51
**        内核使用
*/

//任务控制块( tiny_os_51_core.c )
#define __TN_TASK_FLG_DEL        0x00        //任务被删除
#define __TN_TASK_FLG_RDY        0x01        //任务就绪
#define __TN_TASK_FLG_DLY         0x02         //任务延时

struct tn_os_tcb
{
    jmp_buf                 jbTaskContext;        //用于存储上下文信息
    unsigned char        ucTaskStat;           //任务状态子
    unsigned int          uiTicks;               //任务延时
};
typedef struct tn_os_tcb         TN_OS_TCB;        //TN_OS_TCB等效于struct tn_os_tcb

static data TN_OS_TCB        __GtcbTasks[ TN_OS_MAS_TASKS ];        //任务控制块的结构体数组




//OS初始化( tiny_os_51_core.c )

void tnOsInit ( void )
{
    TN_OS_TASK_HANDLE  tnTask;        //操作的任务

    for ( tnTask = 0; tnTask < TN_OS_MAX_TASKS; tnTask ++ )
    {
        __GtcbTasks[ tnTask ].ucTaskStat = __TN_TASK_FLG_DEL;        //使任务处于删除状态
        __GtcbTasks[ thTask ].uiTasks = 0;                             //设置初值
    }

    __GthTaskCur = 0;        //初始化任务号为0
}





//创建任务( tiny_os_51_core.c 
//对于时间片轮询多任务操作系统来说,在时钟节拍中断服务的最后,可能会发生任务切换,二时钟节拍中断可能
//中断可能中断可能发生在任何时候,假设此时系统切换到一个任务控制块,而且想要创建一个任务,可想而知,两个
//任务的任务句柄是一样的,且占用同一个任务控制块,这样势必会引起系统混乱.
//因此,在创建任务的过程中,必须禁止任务切换( 如先禁止中断,然后再允许中断 )

TN_OS_TASK_HANDLE    tnOSTaskGreat ( 
                    
                    void ( *pfuncTask )( void ),        //指向任务函数的函数指针
                    idata unsigned char *pucStk            //指向任务堆栈的指针
                                                                
                                    )
{
    TN_OS_TASK_HANDLE  thRt;        //返回值


    //搜索是否有空闲的任务控制块
    for ( thRt = 0; thRt < TN_OS_MAX_TASKS; thRt ++ )
    {
        EA = 0;        

        if ( __GtcbTasks[ thRt ].ucTaskStat == __TN_TASK_FLG_DEL )
        {
            
            //如果搜索到有空闲的TCB,则创建任务
            setTaskJmp ( pfuncTask, pucStk, __GtcbTask[ thRt ].jbTaskContext );
            __GtcbTasks[ thRt ].ucTaskStat = __TN_TASK_FLG_RDY;        //任务就绪


            EA = 1;

            return thRt;
        } 
        
        EA = 1;   
    }

    return -1;            //如果没有空闲的TCB,则创建任务失败,即任务句柄的返回值为-1
}




//启动OS( tiny_os_51_core.c )
//TinyOsV1.1中,如果不允许中断,则时钟节拍中断服务程序不会运行,所以要使能中断
void tnOsStart ( void )
{
    EA = 1;
    longjmp ( __GtcbTasks[ 0 ].jbTaskContext );            //执行0号任务
}





//任务主动切换( tiny_os_51_core.c )
//               任务切换的设计的思想:当发生任务切换时,首先搜索下一个将要执行的任务是否处于
//             就绪状态,如果是的话,则将当前正在运行的任务的上下文保存到该任务的TCB中,然后
//             再从相应的TCB中恢复下一个将要运行的上下文.如果所有的任务都未处于就绪状态,则
//             等待本任务知道就绪为止
//TinyOsV1.1中,这里的任务切换仅提供内核使用
static void __tnOsSched ( void )
{
    TN_OS_TASK_HANDLE tnTask;            //任务句柄即操作的任务
    char              cTmp1;
    TN_OS_TASK_HANDLE thTmp2;
    volatile data char *pucTmp3 = ( void * )0;

    thTmp2 = __GthTaskCur;                

    //执行下一个任务
    EA = 0;

    for ( thTask = 0; thTask < TN_OS_MAX_TASKS; tnTask ++ )
    {
        thTmp2 ++;                //首次运行时thTmp2 = 1
        if ( thTmp2 > TN_OS_MAX_TASKS )
        {
            thTmp2 = 0;
        }

        if ( ( __GtcbTask[ thTmp2 ].ucTaskStat & __TN_TASK_FLG_RDY ) != 0 )
        {
            cTmp1 = setjmp ( __GtcbTasks[ __GthTaskCur ].jbTaskContext );    //保存当前任务的上下文,cTtmp1 = 0
            
            if ( cTmp1 == 0 )        //如果cTmp1 = 0,往下执行
            {
                __GthTaskCur = thTmp2;        //更新当前任务句柄
                longjmp ( __GtcbTasks[ thTmp2 ].jbTaskContext );
            } 
            EA = 1;                                               

            return;        //如果cTmp1 = 1,则返回函数
        }
    }

    EA = 1;

    //如果所有的任务都未就绪,则等待本任务就绪,相当于一般操纵系统的空闲任务
    pucTmp3 = ( volatile data char * )( &( __GtcbTasks[ thTmp2 ].ucTaskStat ) );
    while ( ( *pucTmp3 & __TN_TASK_FLG_RDY ) == 0 )        //任务未就绪,直到就绪为止
    {
    
    }
               
}




//时间片用完切换( tiny_os_51_core.c )
//         TinyOsV1.1是纯粹的时间片轮询多任务操作系统,除了在time0ISR()时钟节拍中断服务程序中
//        切换任务外,在其他的这段服务程序中不进行任务切换操作        

//        由于longjmp()函数是有RET指令返回的,如果继续使用longjmp(),则任务切换后CPU会认为中断仍未退
//        出,同级中断(包括自身)依旧被屏蔽,从而造成整个系统执行错误,所以要用longjmpInIsr();

//        由于tnOsTimeTick()函数是由time0ISR()调用的,一次,当执行time0ISR()s时,由于CPU已经处于中断
//        状态,所以不会执行__tnOsSched()函数,所以不用先禁止中断,然后再允许中断.
void tnOsTimeTick ( void )
{
    TN_OS_TASK_HANDLE tnTask;            //任务句柄即操作的任务
    char              cTmp1;
    TN_OS_TASK_HANDLE thTmp2;
    volatile data char *pucTmp3 = ( void * )0;

    //缩短任务等待时间( 延时管理 )
    for ( thTask = 0; tnTask < TN_OS_MAX_TASKS; thTask ++ )
    {
        if ( __GtcbTasks[ thTask ].uiTicks != 0 )
        {
            __GtcbTasks[ thTask ].uiTicks --;
            
            if ( __GtcbTasks[ thTask ].uiTicks == 0 )
            {
                __GtcbTasks[ thTask ].uiTaskStat |= __TN_TASK_FLG_RDY;
            }
        }
    }

    thTmp2 = __GthTaskCur;                

    //执行下一个任务
    for ( thTask = 0; thTask < TN_OS_MAX_TASKS; tnTask ++ )
    {
        thTmp2 ++;                //首次运行时thTmp2 = 1
        if ( thTmp2 > TN_OS_MAX_TASKS )
        {
            thTmp2 = 0;
        }

        if ( ( __GtcbTask[ thTmp2 ].ucTaskStat & __TN_TASK_FLG_RDY ) != 0 )
        {
            cTmp1 = setjmp ( __GtcbTasks[ __GthTaskCur ].jbTaskContext );    //保存当前任务的上下文,cTtmp1 = 0
            
            if ( cTmp1 == 0 )        //如果cTmp1 = 0,往下执行
            {
                __GthTaskCur = thTmp2;        //更新当前任务句柄
                longjmpInIsr ( __GtcbTasks[ thTmp2 ].jbTaskContext );
            }                                                

            return;        //如果cTmp1 = 1,则返回函数
        } 
    }
}




//longjmpInISR()定义( _setjmp.c )
//在SDCC51编译器中,若使用__naked修饰函数,则说明此函数无保护函数
char longjmpInISR( jmp_buf jbBuf ) __naked
{
    unsigned char ucSpSave;            //用于保存堆栈指针的变量
    data unsigned char *pucBuf = ( data void * )0;            //指向上下文信息存储位置的指针

    pucBuf = ( data unsigned char * )jbBuf;
    ucSpSave = *pucBuf ++;
    bp = *pucBuf ++;
    *( ( data unsigned char * )ucSpaSave ) = *pucBuf ++;
    *( ( data unsigned char * )( ( char )( unSpSave - 1 ) ) ) = *pucBuf;
    SP = ucSpSave;

    DPL = 1;
    __asm
    RETI
    __endasm;    
}



//任务延时
//任务在延时期间,其他任务仍然可以继续运行
void delay ( unsigned int uiDly )
{
    unsigned int i, j;

    for ( i = 0; i < uiDly; i ++ )
    {
        for ( j = 0;  j < 1000; j ++ )
        {
        
        }
    }
}




//任务延时( tiny_os_51_core.c )
//由于在延时期间还要继续运行任务,最好的办法是设置一个周期性的中断,然后用这个中断服务程序
//来记录当前任务的剩余延时时间

//由于所有的任务可能同时延时,所以将记录当前任务剩余延时时间的变量uiTicks放在TCB中
void tnOsTimeDly ( unsigned int uiTick )
{
    //设置任务为等待时间状态
    if ( uiTick != 0 )
    {
        EA = 0;
        __GtcbTasks[ __GtcbTaskCur ].ucTaskStat = __TN_TASK_FLG_DLY;
        __GtcbTasks[ __GtcbTaskCur ].uiTicks = uiTIck;
        EA = 1;
    } 

    __tnOsSched ();
    __GtcbTasks[ __GthTaskCur ].ucTaskStat = __TN_TASK_FLG_RDY;        //等待结束
}





//删除任务( tiny_os_51_core.c )
//( 句柄为-1时删除自身,并且要转换为真实的句柄在合法范围内,才能进行任务调度 )
void tnOSTaskDel ( TN_OS_TASK_HANDLE tnTask )
{
    //检查参数
    if ( thTask == -1 )
    {
        thTask = __GthTaskCur;                //转换为真实的句柄

        if ( thTask >= TN_OS_MAX_TASKS || thTask < 0 )            //检查参数是否合法
        {
            return;                           //不合法不执行
        }
    }

    EA = 0;
    __GtcbTasks[ thTask ].ucTaskStat = __TN_TASK_FLG_DEL;            //删除任务
     __GtcbTasks[ thTask ].uiTicks = 0;

   EA = 1; if ( thTask == GthTaskCur ) //删除自身,则执行下一个任务 { __thOsSched(); } }

 

举例:

//时间片轮询多任务操作系统范例( main.c )

//必须将时钟节拍中断设置为最低优先级,只有这样才能保证在其他中断服务程序的执行过程中禁止
//任务切换,以保证其他中断服务程序的完整性

static idata unsigned char         __GucTaskStks[2][32];        //分配任务堆栈

static unsigned char     __GucTask0;        //任务0测试变量
static unsigned char     __GucTask1;        //任务1测试变量

void task0 ( void )
{
    TMOD = ( TMOD & 0xF0 ) | 0x01;
    TL0 = 0x00;
    TH0 = 0x00;
    TR0 = 1;
    ET0 = 1;
    TF0 = 0;
                            //允许time0中断

    while ( 1 )
    {
        __GucTask0 ++;
    }
}

void task1 ( void )
{
    while ( 1 )
    {
        __GucTask1 ++;
    }
}

void timer0ISR( void ) __interrupt 1        //时钟节拍中断服务程序
{
    tnOSTimeTick();                            //时钟节拍处理程序
}

void main ( void )
{
    tnOsInit ();
    tnOsTaskGreate ( task0, __GucTaskStks[0] );
    tnOsTaskGreate ( task1, __GucTaskStks[1] );
    tnOsStart ();
}