您的位置:首页 > 编程语言

RTOS学习之:多任务编程要点

2017-10-18 18:03 232 查看
一.   分析阶段
1.  需求分析,予以文档描述;
2.  一些初始化问题,探究需求分析中的关键点;
3.  解决时序问题,系统中算法的分析;
4.  决定使用RTOS,依赖于时间响应和任务数量;
5.  划分任务,确定系统所需的任务和模块;
6.  系统间通信,消息机制是最优的方法之一;
7.  共享数据处理,创建独立的模块;
8.  结论,绘制系统设计图。
二.   编码实现
1.  系统组成的基本单元是模块,即单个的C源文件,它封装了数据与程序;
2.  模块之间交互通过调用函数来实现,没有使用任何全局变量;
3.  防止竞态发生的手段是信号量;
4.  非立即获取的数据由回调函数予以处理;
5.  向任务发送消息的方法封装在“外壳”函数之中;
6.  所有模块调用的函数原型全部组织在一个头文件之中;
7.  模块中都有初始化函数,负责任务创建、初始化信号量和队列等,并由main()主函数统一调用。
三.   防止竞态(以下任务包括ISR)
1.  竞态只可能发生在全局的资源上(硬件/全局变量/静态变量),而像任务的私有资源(仅被自己访问)和堆栈数据是不可能发生竞态的;
2.  一个函数被哪个任务调用,它就代表该任务的访问行为,与该函数定义位置无关(回调函数是最让人迷惑的,它定义在A任务中,但它被任务B调用,所以它代表任务B的访问行为);
3.  凡是被多个任务访问(包括读/写)的资源,必定发生竞态;
4.  需要保护的共享资源必须确保在使用范围之内都是处于保护状态;
5.  保护的手段:禁止/启用IRQ、禁止/启用任务调度、信号量。
四.   线程通信
方式一 实时操作系统中任务通信的经典方式是:任务阻塞在等待消息上,直到中断服务程序或者其他任务给自己发送消息。如:
    void TaskA(void)
    {
      while (FOREVER)
         {
           OSQPend(p_QData, 0, &err);  /* waiting for events */
        /**** deal with received events ****/
         }     
    }
  当其他任务向自己发送消息时,把该消息封装在“外壳函数”中,这样可以避免使用全局变量,同时增加了安全性:
    void taska_SendPrompt(Int IMsg)
    {
         assert(IMsg >= MIN_VAL && Imsg <= MAX_VAL);
      OSQPost(p_QData, (void *)IMsg);
}
 
方式二结合邮箱和信号量可以让任务进行更高级的通信方式,如下代码所示,任务A先创建一信号量,绑定信号量和它希望任务B所做的事情到消息中,再投递该消息到邮箱,然后在信号量上进行等待,当结束等待时说明任务B已经干完该事情,则删除信号量。
    void TaskA(void)
{
  msg.sem = CreateSemaphore(0);
  msg.func = TaskAFunc;
  PostMBox(mbox, &msg);  /* tell TaskB do something */
  WaitSemaphore(msg.sem);  /* waiting until TaskB done */
  FreeSemaphore(msg.sem);
    }
任务B首先在邮箱上等待,当接收到消息后调用函数,完成任务A希望它干的事情,再释放信号量通知任务A工作完毕。
void TaskB(void)
{
  msg = PendMBox(mbox);
  msg.func(&msg);  /* do something that TaskA desired */
  SignalSemaphore(msg.sem);  /* tell TaskA thing is done */
    }
五.   线程共享
1)简单共享,如图1所示,任务A写数据块DataStructure后给任务B发送消息,任务B提取消息并从任务A复制数据块DataStructure到自己的数据区TaskBData中。
优点:简单,容易实现,适合于慢速通信。
缺点:①任务B如果响应速度不够快则有可能任务A又改写了数据块而发生错误(Write两次,Read一次);②DataStructure同时被任务A和任务B访问,为防止“竞态”错误需要进行保护。



图1 简单共享

 
2)抽象成数据模块,加信号量予以保护,如图2所示。
优点:简单,安全,模块化,适应于被多个任务共享的数据。
缺点:①任务可能会在该信号上阻塞很长时间;②任务为了方便操作数据一般会建立自己的副本,这样一来占用更多的数据存储区。



图2  带信号量的数据模块

 
3)回调函数,如图3所示,任务A把自己的回调函数传递给任务B的消息队列中,任务B从队列中提取并调用该函数,此时回调函数即可以访问任务B的数据,又可以访问任务A的数据,给编程带来极大的自由度。
优点:自由灵活,如任务A和任务C对任务B中的数据进行不同的运算,只需要修改任务对应的回调函数即可,特别适应于任务中数据被多个其他任务使用且运算方式不同的场合。
缺点:①较为复杂,需要清晰了解回调函数的数据流才能正确使用;②回调函数定义在任务A但被任务B使用,它就代表任务B的行为,这样一来TaskAData就被任务A和任务B(通过TaskACallback)共享,为避免“竞态”带来错误,需要对TaskAData进行保护(参见上述三),同理,TaskCData也需要进行保护。



图3 回调函数

 
4)生产者与消费者队列,如图4所示,任务A把“生产”的数据存入RingBuffer中,再将该数据的指针传递给任务B的消息队列中,任务B提取该数据的指针后从RingBuffer复制数据到TaskBData中。
优点:简单可靠,适应于尺寸相同且“生产”速率恒定的场合。
缺点:①对数据尺寸有限制,必须是相同大小的数据才能建立RingBuffer(循环缓冲区);②“生产“速率必须恒定且RingBuffer的大小设置合理,才能确保不会发生“溢出”错误。



图4 生产者与消费者队列
 
5)动态生成消息,如图5所示,任务A动态生成(常见为malloc())消息msg,将该消息指针传递到任务B的消息队列中,任务B提取并处理该消息后,释放(常见为free())该消息所占用的内存。
优点:适用于多任务之间进行自由通信,消息类型和大小都不受限制。
缺点:①较复杂,消息类型可能会非常多,需要较好她组织;②因为动态分配和释放内存,需要细心设计,小心内存泄露;③动态分配内存时间不可控,对于实时性要求特别高的任务需要考虑这个限制。



图5 动态生成消息
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: