您的位置:首页 > 理论基础 > 数据结构算法

VxWorks 进程调度探索【转贴】

2010-06-11 22:13 232 查看
 

1:总括:
VxWorks 实时内核Wind 调度分析Wind 内核缺省调度机制为基于优先级的抢占式调度。采用这种机制时,系统把处理机分配给优先级最高的进程,使之执行。一旦出现了另一个优先级更高的进程时,进程调度程序剥夺当前任务的执行,将处理机分配给高优先级任务。
而对于相同优先级的多个任务之间, 默认采用当前任务调度完成后,才继续下一个调度任务的方式。当然,可以设置同优先级线程之间采用时间片轮转调度机制。采用这种机制时,当一个任务到达时,它被排在轮转队列的后面,等待分配给自己的时间片的到来,如果在时间片内没有结束,则再等待属于自己的时间片的到来,直到任务完成。
2:几个队列的介绍:
Ø         tickQHead队列:采用优先级链表(Q_PRI_LIST)的方式实现,主要用来搜索,线程之间状态的转变(Pend/Delay/Suspend),假如为Ready时候,该任务将加入readyQHead,同时该队列中的Watchdog会直接处理而不需要加入readyQHead队列;
Ø         readyQHead队列:默认会采用Q_PRI_BMAP方式,不过经过配制可以使用Q_PRI_LIST组织方式。默认初始化和定义方式如下:
               qInit (&readyQHead, Q_PRI_BMAP, (int)&readyQBMap, 256);
           这个也就是我们的系统为什么支持256个优先级别的原因了。
Ø         activeQHead队列:采用Q_FIFO方式,他实际纪录了一个线程从生成到删除过程中的每一个线程信息;
Ø         workQInit队列:以环形队列的方式实现,是VxWorks内核对线程控制相关服务存放的队列;

3:具体实现:
3.1系统调度的驱动
系统的调度是以系统时钟中断作为驱动的(关于具体中断部分的实现将有专门的文章详细分解和描述),该随笔之中,在没有详细了解系统中断处理的机制之前,你可以简要的做如下理解:
Ø         系统所使用的CPU由于晶阵的驱动,回定期的产生一个中断;
Ø         该中断在软件系统中,会对应调用一个函数来处理该中断;
传统上,我们在VxWorks系统中,无论是Loader还是后边的App应用的开发中,都会有一个void usrRoot函数,在此我不想赘述他的具体功能,我们需要关注的是,在它内部会调用一个sysClkConnect ((FUNCPTR) usrClock, 0);。 好了,函数usrClock就是我们系统驱动的最终入口了,我们简要分析一下该处理函数在我们的系统中会是什么样的实现方式:
usrClock-à tickAnnounceà  windTickAnnounce
对于usrClock和tickAnnounce,这里没有太多的描述,而在调用的windTickAnnounce的时候,系统会根据当前是否在核心状态,将windTickAnnounce直接调用或者加入工作队列,需要注意的是,即使直接调用,也需要执行系统当前进入核心状态的操作和设置。
到这里,我们可以知道了,当系统在设置sysClkConnect ((FUNCPTR) usrClock, 0)之前,基本都是单线程的执行
4000
,是需要开发人员控制他的启动和执行顺序,类似操作系统的原始社会;只有经过了始终中断挂接之后,才将系统多任务的调度成为了可能;
 
3.2系统调度—状态机的转换
我们在一些资料上,会经常发现如下图的状态转化。在下图中绿色的部分为当前Ready或者执行的进程,而执行进程和Pended和Ready 进程之间会有一个相互转换的过程,而这个转换的过程就是在这个部分实现的功能其一:
 



该部分代码实现见如下部分,简要过程为:
Ø         首先搜索先前所描述的tickQHead,将找到的任务依次处理;
Ø         假如为普通进程且Delay状态,则转化Ready状态,若Pend状态设置,将从Pend队列移出,且放入readyQHead;
Ø         假如为看门狗性质则直接调度;
Ø         其后,检测当前运行进程,加入为轮循调度,同时时间片已经达到,则将该任务从readyQHead队头取出,并放到队尾;
{   
    while ((pNode = (Q_NODE *) Q_GET_EXPIRED (&tickQHead)) != NULL)
       {    ……
              if ((pTcb->objCore.pObjClass == taskClassId)     )
           {
pTcb->status &= ~WIND_DELAY;       
                   if (pTcb->status == WIND_READY)           
                     {    ……                               }
                   else if (pTcb->status & WIND_PEND)        
                     {    status = windPendQRemove (pTcb);      }
 
                    if (pTcb->status == WIND_READY)           
                            Q_PUT (&readyQHead, pTcb, pTcb->priority);
           }
              else                                   
           {       ……
                     (* wdRtn) (wdArg);                      /* invoke wdog rtn */
                   workQDoWork ();                        /* do deferred work */
           }
       }
 
      if ((roundRobinOn) && (taskIdCurrent->lockCnt == 0) &&
                     (taskIdCurrent->status == WIND_READY) &&
                            (++ taskIdCurrent->tslice >= roundRobinSlice))
       {
              Q_REMOVE (&readyQHead, taskIdCurrent);            
              Q_PUT (&readyQHead, taskIdCurrent, taskIdCurrent->priority);
}
}
该部分实际完成了,部分状态机的转换,看门狗的处理和同优先级的进程的分片调度;
3.3 进程调度—优先级的处理
在系统中根据线程的优先级别调度的实际函数为reschedule,由于执行速度的原因,在部分CPU结构的代码中,该部分代码的实现可能是以汇编的形式实现的。在不同的CPU结构中,他的调用大体是为如下几个函数:
windExit
intExit
intCrtExit
当reschedule被调度的时候,一个新的根据优先级强占的过程将有可能发生,我们称该过程为重新调度;重新调度大体发生在如下情况下:
WD的创建/开始和销毁;TICK时钟中断;进程相关的操作:生成/删除/挂起/恢复/Lock/Unlock..;信号量的某些操作Give/Take; Event的发送和接受;MsgQ销毁;Posix消息队列的销毁/接受和发送;Posix的mutex的销毁/锁定/解锁…;以及中断执行退出等等。
其中我们使用的一个较为典型的例子就是TaskDelay(0),他虽然没有真正的实行Delay的操作,但是实际触发了系统线程的依次重新调度。好了,我们看一下这个重新调度执行了一个什么样的操作呢?
unlucky:
    taskIdPrevious = taskIdCurrent;            /* remember old task */
    workQDoWork ();                             /* execute all queued work */
 
    /* idle here until someone is ready to execute */
    kernelIsIdle = TRUE;                        /* idle flag for spy */
    while (((WIND_TCB *) Q_FIRST (&readyQHead)) == NULL)
    {
#if     (CPU_FAMILY == PPC)
              if (((WIND_TCB *) Q_FIRST (&readyQHead)) != NULL)
                   break;
              vxPowerDown ();
#endif  /* (CPU_FAMILY == PPC) */
       }
 
    taskIdCurrent = (WIND_TCB *) Q_FIRST (&readyQHead);
    kernelIsIdle = FALSE;                       /* idle flag for spy */
 
    if (taskIdCurrent != taskIdPrevious) /* switching in a new task? */
       {
              /* do swap hooks */
              mask = taskIdCurrent->swapInMask | taskIdPrevious->swapOutMask;
              for (ix = 0; mask != 0; ix++, mask = mask << 1)
                   if (mask & 0x8000)
                            (* taskSwapTable[ix]) (taskIdPrevious, taskIdCurrent);
 
              /* do switch hooks */
              for (ix = 0; (ix < VX_MAX_TASK_SWITCH_RTNS) && (taskSwitchTable[ix] != NULL);          ++ix)
           {             (* taskSwitchTable[ix]) (taskIdPrevious, taskIdCurrent);    }
       }
 
    oldLevel = intLock ();                    /* LOCK INTERRUPTS */
 
    if (!workQIsEmpty)
       {
              intUnlock (oldLevel);                    /* UNLOCK INTERRUPTS */
              goto unlucky;                       /* take it from the top... */
       }
    else
       {
              kernelState = FALSE;                  /* KERNEL EXIT */
              windLoadContext ();                    /* dispatch the selected task */
       }
}
通过代码可以看出,系统功能会从
a2a4
当前队列中获取队头信息,然后TaskSwap和TaskSwithch; 需要注意的有两点:
1:先前讲过,这个readyQHead队列是一个关于优先级的队列,实际上这个队头,也就是系统优先级最高的一个线程,系统借此实现了优先级的强占;
2:关于这个taskSwitchTable表,作过VXWORKS嵌入式系统软件开发的人可能知道,他可以用来保存系统进程切换的相关信息,并且保留到LOG或者TRACE中间,以便死机的时候分析,看看问题到底在那边,这个会在后继的一些小随笔中描述;
 
3.4 系统调度—系统调试任务的执行
大家看,在先前的几个队列,在上述的实现描述中都派上了用场,唯独没有workQInit。并且您细心的话,在系统中还会发现一个可疑函数:workQDoWork,我们一直没有对它展开来描述。欲知其作用为何,本着人道主义的精神,我们当然不能留到后文去分解。
其实一句话你就明白了,有好多的系统相关的调试函数,比如taskSpend….,我们必须让他尽快的执行,而等同于操作系统的调度,在实际的实现中,我们就是把这些命令最终的放置到workQInit中去的,以实现该调试函数和系统调度的同步,甚至其运行要优于任何级别的线程;
 
4:进程调度的现实应用意义:
 4.1 在线程切换的时候纪录系统信息
void TaskSwitchHook(void *OldTCBptr, void *NewTCBptr)
{
……
}
 
taskSwitchHookAdd((FUNCPTR ) TaskSwitchHook);  
 
4.2 系统某些代码的实时调度
……
TaskDelay(0);
 
4.1 系统执行信息诊断和调试
比如系统当前所有线程不调度了….
两个有用的数据结构分别为
typedef struct        /* Q_HEAD */
{
    Q_NODE  *pFirstNode;         /* first node in queue based on key */
    UINT     qPriv1;                   /* use is queue type dependent */
    UINT     qPriv2;                   /* use is queue type dependent */
    Q_CLASS *pQClass;                   /* pointer to queue class */
} Q_HEAD;
和(适用于系统采用Q_PRI_BMAP方式的优先级队列)
typedef struct        /* Q_PRI_NODE */
{
    DL_NODE       node;             /* 0: priority doubly linked node */
    ULONG    key;        /* 8: insertion key (ie. priority) */
} Q_PRI_NODE;
诊断简要示例如下:
 



当然,该示例中队列为空,然后根据系统大小字节,可以获取pFirstNode的值,再根据指针位置,可以获取Q_PRI_NODE的结构,分析优先级别和当前进程。
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息