动手写简单的嵌入式操作系统二

简介: 动手写简单的嵌入式操作系统二

接下来需要完成任务间的同步和通信。


任务间同步,为什么需要任务间同步,比如对公共资源的访问,如果不同步,一个任务正在访问资源,另一个任务不知道这个资源正在被访问,也去访问了,这就出现问题了。还有就是任务再等待某一事件的触发,触发后才能运行。实现的一种同步方法就是信号量。何为信号量?


举个简单的例子来说,就像是资源的标识,如停车位,当还有停车位时,车才可以停进来,但没有停车位时,外面的车就必须等待,等到有停车位时再进来。下面是一个信号量的简单实现,原理就是用一个全局变量代表可用的资源。当有资源时,这个变量加一,当这个变量为0时代表没有资源了,任务开始挂起,同时开始切换到其它任务。


/*当前信号量列表*/
OS_SEM Sem[MAX_SEM_NUM];    
/*
 * 创建信号量
*/
OS_SEM* OSSemCreate(int32 conuter)
{
     OS_CPU_SR  cpu_sr = 0;
     uint32  index;
    if (conuter < 0)
    {
       return (OS_SEM*)NULL;
    }
     OS_ENTER_CRITICAL();
    for(index=0;index<MAX_SEM_NUM;index++)
    { 
      if(Sem[index]==-1)
      {
         Sem[index]=conuter;
         OS_EXIT_CRITICAL();
         return(Sem[index]);
      }
    }
   OS_EXIT_CRITICAL();
   return (OS_SEM*)NULL;
}
int8 OSSemDelete(OS_SEM* pSem)
{
 OS_CPU_SR  cpu_sr = 0;
 OS_ENTER_CRITICAL();
 /*当且仅当信号量计数为0的时候,才能释放该信号量*/
 if ((*pSem) != 0)
 {
  OS_EXIT_CRITICAL();
  return OS_Err;
 }
 else
 {
  (*pSem) = (OS_SEM)-1;
  OS_EXIT_CRITICAL();
  return OS_OK;
 }
}
/*这个是一个不完全精确的实现*/
/*申请信号量*/
/*其超时时间不会非常精确*/
int8 OSSemPend(OS_SEM* pSem,uint32 timeout)
{
 uint32  index;
 OS_CPU_SR  cpu_sr = 0;
 for (index = 0;index < timeout;index++)
 {
  OS_ENTER_CRITICAL();
  if ((*pSem) > 0)
  {
   (*pSem)--;
   OS_EXIT_CRITICAL();
   return OS_OK;/*获取到了信号量*/
  }
  else
  {
   /*等待一个时间片*/
   OS_EXIT_CRITICAL();
   OSTimeDly(1);
  }
 }
 return OS_Err;
}
/*不等待,立即返回是否信号量能否获取*/
int8 OSSemGet(OS_SEM* pSem)
{
 OS_CPU_SR  cpu_sr = 0;
 OS_ENTER_CRITICAL();
 if ((*pSem) > 0)
 {
  (*pSem)--;
  OS_EXIT_CRITICAL();
  return OS_OK;/*获取到了信号量*/
 }
 OS_EXIT_CRITICAL();
 return OS_Err;
}
/*释放(发送)一个信号量*/
int8 OSSemPost(OS_SEM* pSem)
{
 OS_CPU_SR  cpu_sr = 0;
 OS_ENTER_CRITICAL();
 (*pSem)++;
 OS_EXIT_CRITICAL();
 return OS_OK;
}


信号量如何使用?如何使用信号量来进行同步?下面是一个简单的应用例子。


我们知道printf函数不可重入,在调用这个函数时,必须保证不能被其他任务占用。所以不同任务需要保持同步,当一个任务释放了信号量后另一个任务方可使用。


OS_SEM*   testSem;
void  task6(void * arg)
{
    testSem=OSSemCreate(1); //创建一个信号量
    while(1 )
    {
         OSSemPend(testSem, 0);
         printf("task 6  Running! 27\r\n");
         OSSemPost(testSem);
         OSTimeDly(100);/*100毫秒10个*/
    }
}


任务间如何通讯呢?可以用消息队列来实现。为什么要用消息队列?


消息被发送到队列中。“消息队列”是在消息的传输过程中保存消息的容器。消息队列管理器在将消息从它的源中继到它的目标时充当中间人。队列的主要目的是提供路由并保证消息的传递;如果发送消息时接收者不可用,消息队列会保留消息,直到可以成功地传它。


下面是一个简单的实现,很容易看懂


/*用于对于的标记消息队列是否使用*/
uint8 MsgQueueFlag[MAX_QUEUE_NUMBER];
/*实际的所有消息队列*/
OS_Q MsgQueue[MAX_QUEUE_NUMBER];
/*
 * 创建消息队列
*/
OS_Q* OSQCreate()
{
 OS_CPU_SR  cpu_sr = 0;
 uint32  index;
 OS_ENTER_CRITICAL();
 for(index=0;index<MAX_QUEUE_NUMBER;index++)
 { 
  /*该消息队列未被使用*/
  if (MsgQueueFlag[index]==0)
  {
   MsgQueueFlag[index]=1;
   /*该队列首尾初始化*/
   MsgQueue[index].front=NULL;
   MsgQueue[index].rear=NULL;
   OS_EXIT_CRITICAL();
   return &(MsgQueue[index]);
  }
 }
 OS_EXIT_CRITICAL();
 return (OS_Q*)NULL;
}
/*
*删除消息队列
*/
int8 OSQDelate(OS_Q* q)
{
 OS_CPU_SR  cpu_sr = 0;
 OS_ENTER_CRITICAL();
 /*信号量不存在*/
 if (q == NULL)
 {
  OS_EXIT_CRITICAL();
  return OS_Err;
 }
 /*队列指针越界*/
 if ((( q-MsgQueue ) < 0)||(( q-MsgQueue ) > (MAX_QUEUE_NUMBER-1)))
 {
  OS_EXIT_CRITICAL();
  return OS_Err;
 }
 /*将标记位置0*/
 MsgQueueFlag[q-MsgQueue] = (uint8)0;
 OS_EXIT_CRITICAL();
 return OS_OK;
}
/*
*发送一个消息
*该函数可用在中断函数中
*/
int8 OSQPost(OS_Q* q,OS_MSG msg)
{
    OS_CPU_SR  cpu_sr = 0;
   OS_ENTER_CRITICAL();
 if (q == NULL)
 {
       OS_EXIT_CRITICAL();
      return OS_Err;
 }
 if ((( q-MsgQueue ) < 0)||(( q-MsgQueue ) > (MAX_QUEUE_NUMBER-1)))
 {
      OS_EXIT_CRITICAL();
      return OS_Err;
 }
 if((q->rear+1)%MAX_MSG_NUMBER==q->front)
 { 
      OS_EXIT_CRITICAL();
      return OS_Err;
 }
 else
 {
      q->msgQueue[q->rear]=msg;
      q->rear=(q->rear+1)%MAX_MSG_NUMBER;
      OS_EXIT_CRITICAL();
      return OS_OK; 
 }
}
/*
*在有限时间片内等待一个消息
*该函数不能用在中断函数中,也不能在关中断的地方运行
*/
OS_MSG OSQPend(OS_Q *q, uint32 timeout)
{
 uint32  index;
 uint32  cpu_sr = 0;
 OS_MSG  msg;
 for (index = 0;index < timeout+1;index++)
 {
  OS_ENTER_CRITICAL();
  if (q->front==q->rear)
  { 
   OS_EXIT_CRITICAL();
   OSTimeDly(1);
  }
  else
  {
   msg=q->msgQueue[q->front];
   /*消息个数满时自动从0开始重新计数*/
   q->front=(q->front+1)%MAX_MSG_NUMBER;
   OS_EXIT_CRITICAL();
   return msg;
  }
 }
 OS_EXIT_CRITICAL();
 return NULL ;
}
/*
*直接获取一个消息,可用在中断函数中
*/ 
OS_MSG OSQGet(OS_Q *q)
{
 OS_MSG msg;
 uint32  cpu_sr = 0;
 OS_ENTER_CRITICAL();
 if (q->front==q->rear)
 { 
  OS_EXIT_CRITICAL();
  return NULL; 
 }
 else
 {
  msg=q->msgQueue[q->front];
  q->front=(q->front+1)%MAX_MSG_NUMBER;
  OS_EXIT_CRITICAL();
  return msg;
 }
}


消息队列使用的一个例子:


void  task6(void * arg)
{
     testQ=OSQCreate();//创建一个消息队列
    testSem=OSSemCreate(1); //创建一个信号量
 while(1 )
 {
     OSSemPend(testSem, 0);
     printf("task 6  Running! 27\r\n");
     OSSemPost(testSem);
     OSTimeDly(AppTaskDelay);/*100毫秒10个*/
 }
}
void  task3(void * arg)
{
 int i=0;
 char buf[]={1,2,3,4,5};
 while(1 )
 {
      OSSemPend(testSem, 0);
      printf("task 3  Running! 24\r\n");
      for(i=0;i<5;i++)
     {
         OSQPost(testQ,&buf[i]);//发送五个消息
         printf("send MSG %d\r\n",buf[i]);
     }
    OSSemPost(testSem);
    OSTaskSuspend(OSCurTCB);//挂起任务
    OSTimeDly(AppTaskDelay);/*100毫秒10个*/
 }
}
void  task4(void * arg)
{
    char* s;
    while(1 )
    {
          OSSemPend(testSem, 0);
          printf("task 4  Running! 25\r\n");
          s=(char*)OSQPend(testQ,0); //接收消息
          printf("recv MSG is %d\r\n",*s);
          OSSemPost(testSem);
          OSTimeDly(AppTaskDelay);/*100毫秒10个*/
    }
}


实时性和相关的优先级反转问题,在实时领域,是个很关键的问题


首先说多任务,任务就是让一段“流程”,一般都是一遍又一遍的循环运行(死循环)。

一次“流程”运行一遍之后,常常会等待一段时间,自己休息休息,也让其他任务也运行一下,这就是多任务并行。


在多任务的系统之中,实时性,就是让当前最高优先级的任务优先运行;


若当前最高优先级的任务不是当前正在运行的任务,那么就要给一个时机(时钟中断),


让高优先级的任务运行,正在运行的(低优先级)任务等下再运行。


这就是实时系统中的抢占调度。


实时操作系统的本质就是,让当前最高优先级的任务以最快的速度运行!


(如果有同优先级的任务,则大家轮流运行)


由此看来,实时的多任务设计,难度在于:


要保证系统性能满足的需求,


在硬性保证高优先级任务在deadline之前运行完的同时


也要保证低优先级的任务顺利的完成自己的工作。


当然,这里就提出了优先级反转的问题了


典型情况如下:


高优先级的任务A要请求的资源被低优先级任务C所占用,


但是任务C的优先级比任务B的优先级低


于是任务B一直运行,比A低优先级的其他任务也一直能运行,


反而高优先级的任务A不能被运行了。


从实时性上讲,若高优先级在等待一个某个资源,


那么为了保证高优先级任务能顺利运行,


则必须要让当前占用该资源的任务赶紧运行下去,直到把资源释放。


再让高优先级的任务来占用这个资源。


优先级反转在RTOS中是一个很深刻的课题,


目前还没有非常好的解决方案。


在这个问题上,目前业界比较典型的做法是VxWorks的做法


原理如下:


当任务A请求的资源被任务C所占用的时候


则将C的优先级提升到任务A的级别,让占有资源的任务先运行,


这样能在一定程度上解决优先级反转的问题。


但是这样做,事实上破坏了实时系统里面运行优先级的意义...


其他,有些商业RTOS也提出了一些解决方案


比如常见的极限优先级方案:


将使用资源的任务优先级提升到系统最高级别


使得任何使用该资源的任务都能快速通过


但是,对优先级意义的破坏性,比优先级继承方案更大!


接下来又有好多事情可以做了。比如可以细读一些其他的开源系统如ucos,freeRTOS,smallRTOS,RAW OS,keil RTX,RTTherad,uclinux,minix,linux以及一些比较著名的开源代码,虽然代码量很大,但是可以慢慢来,先看比较关注的某个模块是如何实现的。


一次看懂少部分,慢慢的就很有提高了。兴趣是最好的老师,多实践,看的再多也不如经手一遍。


相关文章
|
7月前
|
安全 物联网 调度
【软件设计师备考 专题 】网络操作系统和嵌入式操作系统基础知识
【软件设计师备考 专题 】网络操作系统和嵌入式操作系统基础知识
135 0
|
2月前
|
安全 调度 开发工具
嵌入式 开发技巧和经验分享
嵌入式 开发技巧和经验分享
50 4
|
6月前
|
传感器 NoSQL 物联网
嵌入式开发系统学习——干货分享(一)
嵌入式开发系统学习——干货分享(一)
150 0
|
7月前
|
算法 Linux 调度
嵌入式linux面试题目总结
嵌入式linux面试题目总结
174 0
|
编译器 程序员 Linux
嵌入式软件开发第一讲笔记
嵌入式软件开发第一讲笔记
57 0
|
传感器 编解码 算法
爆肝9万字,我已从小白晋升ARM嵌入式工程师!带你从零熟悉常用的M4嵌入式功能,建议收藏(含码源)(6)
通讯协议又称通信规程,是指通信双方对数据传送控制的一种约定。约定中包括对数据格式,同步方式,传送速度,传送步骤,检纠错方式以及控制字符定义等问题做出统一规定,通信双方必须共同遵守。
爆肝9万字,我已从小白晋升ARM嵌入式工程师!带你从零熟悉常用的M4嵌入式功能,建议收藏(含码源)(6)
|
存储 图形学 芯片
爆肝9万字,我已从小白晋升ARM嵌入式工程师!带你从零熟悉常用的M4嵌入式功能,建议收藏(含码源)(1)
嵌入式系统的定义有很多的说法,但是没有一个定义是全面的,下面给到的两种方法我觉得应该算是比较合理的。
爆肝9万字,我已从小白晋升ARM嵌入式工程师!带你从零熟悉常用的M4嵌入式功能,建议收藏(含码源)(1)
|
存储 算法 IDE
动手写简单的嵌入式操作系统一
动手写简单的嵌入式操作系统一
爆肝9万字,我已从小白晋升ARM嵌入式工程师!带你从零熟悉常用的M4嵌入式功能,建议收藏(含码源)(5)
此器件具有两个嵌入式看门狗外设,具有安全性高、定时准确及使用灵活的优点。两个看门 狗外设(独立和窗口)均可用于检测并解决由软件错误导致的故障'
爆肝9万字,我已从小白晋升ARM嵌入式工程师!带你从零熟悉常用的M4嵌入式功能,建议收藏(含码源)(5)
|
芯片 数据格式
爆肝9万字,我已从小白晋升ARM嵌入式工程师!带你从零熟悉常用的M4嵌入式功能,建议收藏(含码源)(2)
什么是中断?中断就是CPU在正常运行程序的时候,由于内部或者外部事件引起的暂时中止现行的程序,转去执行请求CPU为其服务的那个外设或者事件的服务程序,等待这个服务程序执行完毕又要返回到被中止的地方的程序,这么的一个过程。
爆肝9万字,我已从小白晋升ARM嵌入式工程师!带你从零熟悉常用的M4嵌入式功能,建议收藏(含码源)(2)