嵌入式多任务下资源管理问题的讨论

背景介绍

        在嵌入式驱动开发中会存在以下场景:

        多个任务都有使用串口发送数据的需求,或者多个任务都有EEPROM数据存取的需求。下面以串口数据发送为例来展开论述。

        串口发送一般有三种方案:

        DMA方案:使用DMA发送,每次发送前都必须检查DMA是否空闲;然而有的任务是有严格的执行周期的,不能随便阻塞下来等DMA空闲。

        中断方案:使用发送中断,在每个串口发送中断中检查发送缓冲区中是否存在未发送数据,如果存在,就在中断中继续发送;该方案需要建立一个发送缓冲区,和一套发送缓冲区管理机制;该方案的缺点就是中断太多,效率比DMA低很多;优点就是:只要缓冲区有空间,都可以直接启动发送,而不用理会当前串口是否空闲;

        阻塞方案:将数据一个个发送,每发送一个字节前都必须检查串口发送是否空闲;该方案应用场景很少,主要是因为效率太低了。

多任务资源管理方案

        当存在多个任务都有串口发送数据的需求时,一般有以下几种资源管理方案:
        互斥锁方案:在串口发送数据前后加互斥锁,这样某一个任务A正在操作该资源时,即使是高优先级任务,要想获得该资源使用权,都必须等待任务A释放该资源。

        关调度方案或关中断方案:在资源使用前关中断或者关调度,使用完后再打开;这样某个任务A一旦获取该资源后,由于中断/调度被关了,该任务就不会被其它任务打断,直到其释放资源。

        消息引用方案:建一个专门的资源访问任务B,其它任何任务要想访问该资源,都必须向任务B发消息,而所发消息只是消息的引用,即发送的是指针。

        消息直传方案:同消息引用方案一样,都是建一个专门的资源访问任务,不同的是,该方案中传递的消息不是指针,而是数据拷贝,即直接将待传递消息完整拷贝后发送过来。

方案评价

        互斥锁方案与关调度/关中断方案相比,互斥锁方案存在释放锁时间较长问题,而关中断/关调度又存在高优先级任务得不到立即执行问题;这两个方案有一个共同的优点就是:任意一个任务都可以不受限制的直接发送数据,而不用担心资源竞争问题;但这个优点也是缺点:

        一方面相关模块内聚等级较低,只因要完成逻辑上相关的一组事务而内聚在一起,为逻辑内聚;一旦发送出了问题,涉及到多个模块,排查起来相对比较麻烦;

        另一方面存在因DMA忙、或者发送缓冲区已经满了等情况而阻塞任务。

        消息引用与消息直传方案解决了前面两个方案存在的模块内聚等级较低问题,以及阻塞自身任务问题,但他们自身也存在一些问题。消息引用的问题在于:每次传递的都只是一个引用/指针,这样一方面要求发送方必须保证发送的指针持续有效(必须是静态的或全局的),否则可能引发非法访问等一系列问题;另一方面可能存在这样的场景,任务C发消息请求发送时,发送的内容是X1,等到串口发送任务开始放送时,指针所指向的内容变了,变成了X2,而任务C的希望是串口接收方能够识别到内容X1,而不是X2。

        当然,我们也可以使用堆操作来解决消息引用方案中存在的问题,如在消息发送前通过malloc等机制申请一块堆空间用于存放临时消息内容,然后将该堆地址通过消息传递给其它任务,在其它任务处理完该消息后通过free等机制释放该堆空间。这对于普通的应用来说是一种较好的解决方案,然而堆操作存在固有的安全风险,一些大厂明确禁止在嵌入式开发中使用堆操作,如:据说美国军方禁止在safety-critical的嵌入式航空电子设备代码中使用动态内存分配(是不是真的我也没有去考究,读者可以研究了告诉我一下)。

        消息直传方案由于是消息直接拷贝传递,就解决了消息引用的上述问题。但是消息直传方案也存在问题:每个任务的串口发送数据需求基本完全不一样,即发送数据的结构体大小不一致,难道要为每一个任务单独创建一个对应的消息队列?显然不太现实,因为太耗内存了,而单片机内存本来就很紧张。

总结

        具体选择哪一种方案,需要根据实际的场景来分析,比如根据最长发送帧发送时间,来识别某个任务可能会因等待串口空闲而阻塞的时间,评估该时间是否可接受,可以接受就可以考虑是否使用互斥锁方案或关调度/关中断方案;如果评估该时间对该任务来说太久了不能接受,互斥锁方案与关调度/关中断方案可能就用不了了。

        所以,各种方案本身没有好坏,有的只是是否适合其所在的特定场景,我们要考虑的是:是否所有场景都考虑到了,然后根据场景来确定可适用的方案。

猜你喜欢

转载自blog.csdn.net/liuwuyi1987/article/details/126575782