当前位置:首页 > 公众号精选 > 嵌入式云IOT技术圈
[导读]嵌入并发,意味着多线程或者多任务,基本上都是使用了系统,linux系统或RTOS系统之类的实现。

嵌入并发,意味着多线程或者多任务,基本上都是使用了系统,linux系统或RTOS系统之类的实现。RTOS系统里任务的调度主要有抢占式和时间片调度两种,具体的区别这里就不详细说明了。此篇章包含了并发的一些术语,如并发性,临界性,资源,死锁等的概念。最好是详细阅读RTOS系统的书籍。

声明:文章基于《C嵌入式编程设计模式》这本书,英文是Design Patterns for Embedded Systems in C。主要是做个笔记,并添加一点个人的理解,分享出来与各位探讨。


1. 嵌入并发和资源管理的设计模式

总共有8个模式,前两个循环执行模式和静态优先级模式,提供了两个不同的方法来调度任务或线程接下来3个模式临界区模式,守卫调用模式和队列模式,为了使解决在多任务环境下串行访问资源的问题。汇合模式讲的是多任务以不同的方式进行同步。最后两个模式是关注预防死锁问题。希望下面的模式能够各位一点启发。

1.1 循环执行模式

循环模式有非常简单的方式调用多个任务的特点,允许所有的任务有同等机会运行,但是不能及时响应紧急事件。一般在资源少的系统里面使用,避免了RTOS的开销,也不需要复杂的任务调度。简单就是最大的优点。

1.1.1 模式结构

CyclicExecutive有一个controlLoop()函数,可以反复调用每个任务的run操作。也需要等待任务的结束再调用下一个任务。

1.1.2 角色

1.1.2.1 抽象任务(AbstractCEThread)

通过声明run()函数为线程提供接口。它是用来循环执行的任务函数。

1.1.2.2 循环控制(CyclicExecutive)

这个类用于循环执行每个任务。此外也有全局栈和任务本身需要的静态数据。模式的一个变体是时间触发循环执行,在这个变体中,CyclicExecutive设置使用CycleTimer来开启每个周期。也就是使用这个变体可以在周期类执行每个函数。

1.1.2.3 循环定时器(CycleTimer)

图中有表示带有“0,1”,这个定时器是可选的。当定时器时间到时,可以调用中断或者返回TRUE给hasElapsed()函数。CyclicExecutive调用start()为下一个周期开始计时。

1.1.2.4 具体任务实现(ConcreteCEThread)

每个ConcreteCEThread都有自己的run()函数,用于具体的任务实现。

1.1.3 效果

如前所述,该模式优点在于简单,一方面很难导致调度程序错误,另一方面对紧急事件响应不足,使得仅使用在内存小的设备。还有缺点是,任务间的通讯会值得考虑,比如一个任务需要另一个任务的数据,那么数据只能保存在全局的内存或共享资源中。我们尽量不要定义太多的全局变量,否则会难以管理维护,和造成内存的浪费。

1.1.4 实现

该模式的实现非常简单。在大多数情况下,循环执行可能仅是应用的main()函数中调用。

1.2 静态优先级模式

大多数的实时操作系统都是静态优先级模式。所以想要使用这个模式直接移植RTOS系统就好了。这里的模式复杂度和完整度是无法比得上RTOS系统的,不过阅读这里也可以使你对RTOS的任务调度有所了解,因为这是基于这个框架的。静态优先级模式能够为任务划分优先级,能够更好响应高优先级时间。

1.2.1 模式结构

除了右下角AbstraceStaticThread,SharedResource,ConcreteStaticThread这三个类,其他一般是由RTOS实现。

1.2.2 角色

1.2.2.1 抽象线程(AbstraceStaticThread)

是一个抽象类,提供run()函数给调度器运行。

1.2.2.2 具体线程(ConcreteStaticThread)

ConcreteThread作为AbstraceStaticThread具体实现run()函数。

1.2.2.3 互斥锁(Mutex)

是一个互斥的信号量类,用来串行访问SharedResource。当一个任务调用了互斥量的lock()函数,其他任务尝试锁定的同一个互斥量时候,会被阻塞,直到互斥量的解锁或超时退出。

1.2.2.4 队列(PriorityQueue)

PriorityQueue是根据优先级,对指向StaticTaskControlBlock的指针进行排序,也就是说队列里存储的其实每个线程的排队。一般在RTOS系统里,存在不止一个队列,有就绪队列,阻塞队列等,调度器会从就绪队列取出第一个执行。

1.2.2.5 资源(SharedResource)

该资源可能在一个或多个线程里共享,需要保证资源的正常,在下面模式会说明资源共享的问题。

1.2.2.6 栈(Stack)

每个AbstraceStaticThread都有一个栈用于返回地址和传递参数。

1.2.2.7 调度器(StaticPriorityScheduler)

最简单的法则:总是运行最高优先级的准备线程。RTOS系统里,任务创建,任务切换等都需要经过调度器。任务创建成功后,会把任务按优先级加入到就绪列表中,任务挂起就会加入到挂起列表。系统有个滴答时钟中断或其他能够进行任务切换,查找下一个运行的任务可以有通用方法,就是从就绪列表取。另一种是硬件方法,使用处理器自带的硬件指令来实现,需要硬件本身支持。

1.2.2.8 程序控制块(StaticTaskControlBlock)

包含了它相应的AbstraceStaticThread对象的调度信息。有线程的优先级,默认开始地址,目前地址,只要线程还在没被销毁,这个块就会伴随着存在。

1.2.3 效果

静态优先级模式能够对事件提供及时响应,可以对CPU大程序优化,避免单线程因等待时占用CPU这种浪费。因RTOS系统的支持,线程间通讯也有很多保证,邮箱,信号量机制,避免了过多的全局变量。

1.1.4 实现

最好的方式是直接移植成熟的RTOS系统来实现。使用这种模式,需要对前期开发有个设计,对内存分配,优先级分配等因素,需要在程序开发前有个规划,否则可能会造成后面存在各种问题。复杂度比单线程的高,所以需要你有个深入的理解,才能对RTOS系统运用掌握,但是也不用害怕,RTOS始终还是中小的系统,有时间可以研究源码,RTOS对指针,数据结构的运用非常的成熟高效。

1.3 临界区模式

临界区模式是任务协调最简单的方式。它直接禁止了任务的切换,在临界区内安全访问之后,再退出临界区。

1.3.1 模式结构

模式结构非常简单,在进入临界区后才访问资源。调度程序不参与临界区的开启和结束过程,知识提供服务禁止和重启任务切换。如果调度系统不提供,则临界区能够在硬件级别使用C的asm直接开关中断处理。

1.3.2 角色

1.3.2.1 临界区(CRShaaredResource)

使用这个元素来禁止任务切换,以防止任务同时访问资源。这个例子里,受保护资源是Value属性,相关的服务都必须使用临界区来保护,setValue()和getValue()函数必须独立实现临界区。

1.3.2.2 任务集合(TaskWithSharedResource)

这个元素代表所有想要访问共享资源的任务集。这些任务并不知道保护资源的方法,因为它被封装在共享资源内。

1.3.3 效果

模式特点就是禁止调度任务的切换,更严格的,禁止所有的中断。注意的是,一旦元素离开了临界区,将重启任务切换,另外使用了临界区,就注定会影响到其他任务的时序,所以尽量保证临界区的时间不要长。

1.3.4 实现

绝大多数的RTOS系统直接提供函数,调用即可。

1.4 守卫调用模式

守卫调用模式提供了锁定的机制串行访问,可以阻止当锁定后来自其他线程的调用资源。在RTOS系统里,直白的说就是信号量。使用这个模式可能会导致优先级导致,或死锁的问题发生。

1.4.1 模式结构

在模式下,多个PreemptiveTasks通过他们的函数访问GuardeResource。当一个线程调用一个正在锁定的信号量时,调度服务会把该线程加入到阻塞队列中,等待当那个信号量释放或超时时,解除阻塞。调度服务必须作为临界区实现信号量的lock()功能,以防止可能的竞争条件。

1.4.2 角色

1.4.2.1 共享资源(GuardedResource)

在这个类中使用互斥信号量来互斥访问。在访问资源之前,执行与Semaphore实例关联的lock()函数。如果Semaphore是在非锁定状态,则变为锁定;如果在锁定状态,则Semaphore会调度复位发信号阻塞这个任务。

1.4.2.2 任务(PreemptiveTask)

访问共享资源的任务。

1.4.2.3 互斥信号量(Semaphore)

它串行访问GuardedResource。lock()函数是用于访问资源之前,release()函数是访问资源后,调用释放信号量。

1.4.3 效果

该模式提供及时访问资源,并同时阻止多个能够导致数据损坏和系统错误行为的同时访问。如果资源没有上锁,那么访问资源并不会遭受到延迟。

1.4.4 实现

通过使用RTOS提供的信号量函数。一般都会提供创建信号量,摧毁信号量,上锁,解锁的接口。

1.5 队列模式

队列模式是任务异步通讯常见的实现。它提供了在任务间的通讯方式。发送者将消息队列Cyrus队列中,一段时间过后,接受者从队列取出消息。它也可以实现了串行访问共享资源,把访问消息排队,并且在稍后处理,这避免了共享资源同时访问的问题。

1.5.1 模式结构

QUEUE_SIZE声明决定队列能容纳最大的元素数目。必须足够大来处理最差的情况,也不要太大以免内存的浪费。

1.5.2 角色

1.5.2.1 消息(Message)

它可以任何东西,是简单的数据值,或发送消息的详细数据报结构。

1.5.2.2 消息队列(MessageQueue)

MessageQueue是QTasks间交换的信息存储。提供了getNextIndex()函数来运行计算下一个有效的索引值。insert()函数在头部位置将Message插入到队列中并更新头索引。remove()函数可以用于删除最旧的消息。iFull(),isEmpty()两个用来检测队列是否已满,是否为空。

1.5.2.3 互斥信号量(Mutex)

是互斥信号量,如静态优先级模式中的描述类似。

1.5.2.4 任务(QTask)

QTask是MessageQueue的客户,要么调用insert()插入新消息,要么调用remove()访问最早的数据。

1.5.3 效果

当数据在任务间传递,队列模式十分好用。互斥量可以确保队列本身不会由于同时访问造成损坏。相比守卫调用模式,队列模式接收数据不是很及时。

1.5.4 实现

队列的最简单实现是消息元素数组。有简单的优点,也会有灵活性不足,占用空间固定等缺陷。更多是使用链表的方式来实现队列。MessageQueue还可以添加多个缓冲区,每个优先级一个队列,这样实现优先级策略,或者基于消息优先级,通过插入元素队列中实现。在复杂的系统中,预测最佳队列大小是不可行的,如果使用数组实现队列的方式,会存在超出容量的问题。在这种情况下,可以额外使用一个缓冲队列在作为临时存储。

1.6 汇合模式

任务必须以不同的方式同步。发生同步可能是共享单一资源,或者等待信号量等造成,这些队列模式和守卫调用模式都能够实现。但是如果同步需要的条件更加复杂呢?汇合模式就是解决这个问题。当所有的任务都满足同步条件时,才能继续运行。

1.6.1 模式结构

需要同步的线程至少2个,同时拥有唯一的Rendezvous。

1.6.2 角色

1.6.2.1 聚合(Rendezvous)

用于管理同步。它通过两个方式:reset()函数重置同步标准为初始条件。synchronize()函数,当任务想要同步时调用这个方法。如果不满足标准,则任务阻塞。这个通常可以使用观察者模式或守卫调用模式实现。

1.6.2.2 计数信号量(Semaphore)

这个通常是计数信号量,有创建,摧毁,上锁和释放标准锁的接口函数。用于存储当前所有任务满足同步条件的数量。当等于预设值时,同步条件满足。

1.6.2.3 线程(SynchronizingThread)

代表使用Rendezvous同步的每个线程。

1.6.3 效果

在这个模式中,两个或更多的任务都同时满足某个条件时,才能继续运行或调用回调函数。

1.6.4 实现

该模式可以通过前面的观察者模式,或者守卫调用模式实现。如果使用的是观察者模式,则任务必须使用函数的地址注册,当满足同步条件时调用。如果使用的是守卫调用模式,则每个Rendezvous对象拥有唯一的信号量,任务想同步时调用synchronize()函数告知给Rendezvous,当Rendezvous满足同步条件时,释放信号量,并且任务随后根据通常的调度策略全部释放运行。

1.7 同时锁定模式

首先不考虑软件自身导致的错误,发生死锁需要满足4个条件:

  1. 互斥锁资源。

  2. 当请求其他资源时,一些资源已经锁定。

  3. 当资源锁定是允许抢断。

  4. 存在循环等待条件。

死锁能够通过打破这4个条件的任意一个避免。使用临界区模式打破的是条件1和条件3。队列模式避免了条件1的发生。

同时锁定模式是通过破坏条件2达到避免死锁的目的。模式以全或无的形式工作。要么所有需要的资源一次都锁定,要么都没有锁定。简单来说在线程需要某个资源的时候,只有把所有的资源都一起上锁成功,才能成功往下执行,这样就避免了两个线程都在请求对方的资源造成的死锁。

1.7.1 模式结构

一般来说,MultimasteredResource是不同资源集合的任意数目的一部分,其中ResourceMaster的单独实例管理一个这样的集合。

1.7.2 角色

1.7.2.1 资源管理(MultimateredResource)

这个元素通过多个ResourceMasters管理,然后他有自己的互斥信号量来避免同时申请锁。使用QueryMutex必须通过tryLock()函数,以便能够通过ResourceMasters决定所有的尝试锁定将会是成功或者失败。

1.7.2.2 互斥量(QueryMutex)

这个算数是一个正常的互斥信号量,与之前的不用,它提供了tryLock()函数。这个函数和lock()函数目的是一样的,都是为了上锁,只是tryLock()函数除此之外,如果锁失败,他将会返回一个错误代码,而不是阻塞当前的线程。

1.7.2.3 客户(ResourceClient)

这个元素是一个客户,想要一次访问所有资源集合来避免死锁。它直接访问MultimateredResource,直到成功接收到ResourceMaster上的锁。在使用完资源后释放。

1.7.2.4 控制锁(ResourceMaster)

ResourceMaster控制整个资源合集的锁。

1.7.3 效果

同时锁定模式通过消除必要条件2,通过一次锁定所有需要的资源或一个都不锁防止死锁。但是这样会增加了其他任务执行的延时,而且很可能发生在甚至没有实际资源的冲突下。在资源更多,更广泛时出现这种情况更明显。此外,模式不能解决优先级倒置问题,事实上可能更严重。

1.7.4 实现

需要保证tryLock()函数错做之前确保成功锁定MultimasteredResource。

1.8 排序锁定

排序锁定是另一种确保死锁不会发生的方法,这次是用过防止条件4发生。通过对资源排序,并且需要客户总是按照那个指定的顺序锁定资源,这样就不可能形成循环等待条件。

1.8.1 模式结构

1.8.2 角色

1.8.2.1 锁(Mutex)

与上面的模式一样,提供两个基本的函数lock()和release()。

1.8.2.2 资源管理(OrderedResource)

这个是模式的核心。它有resourceID属性,是一个唯一的与每个资源关联的ID,并且与ResourceList关联。这个类执行的排序锁定规则永远是:如果资源的resourceID大于任意已锁定资源最大的resourceID,则资源仅能被锁定。ResourceClient首先需要调用lockDyadic(),然后添加到资源列表中,在对资源操作完成之后,调用releaseDyadic()函数。书上把这种访问称作为二元的,与二元不一样的一元,差异在一元是在内部完成上锁,使用资源,解锁。而二元是可以保持在锁的状态,等到资源使用完之后在释放。

1.8.2.3 客户(ResourceClient)

代表了想要调用OrderedResource服务的元素集合。对于客户,不需要知道关于resourceID本身的任何东西。

1.8.2.4 资源列表(ResourceList)

在这个元素里,如果传递的resourceID大于已锁定资源的最大一个,则addLock()返回成功。否则返回失败。

1.8.2.5 已使用资源(ResourceReference)

这仅是一个在有序列表中包含的resourceID数组。只是保存一个最大值是不够的,因为很多资源可能在任何时候锁定。

1.8.3 效果

模式通过确保所有的客户按相同的顺序锁定资源来消除死锁。这个模式需要在设计时做好分析来规划好资源的排序。例如现在有两个线程,都需要用到资源A,B,C,如果线程1按A,B,C的顺序锁定,线程2按C,B,A的顺序锁定,就有可能发生死锁。因此该模式就是为了让资源都按照规定的序列来锁定。

1.8.4 实现

模式的实现需要给每个OrderedResource增加额外的resourceID,并且在ResourceList的逻辑中确保每个OrderedResource的resourceID大于任意当前所的resourceID。还有,已经上锁的resourceID的列表必须维护,当OrderedResource释放时,可以适当地锁定其他。

ResourceList最常见的实现是一个按照锁定顺序表示的resourceID整形数组。

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

机器人操作系统(ROS)驱动程序基于ADI产品而开发,因此可直接在ROS生态系统中使用这些产品。本文将概述如何在应用、产品和系统(例如,自主导航、安全气泡地图和数据收集机器人)中使用和集成这些驱动程序;以及这样将如何有助...

关键字: 电机控制器 机器人 嵌入式

支持高达48V@5A的PD受电模式,达到目前USB PD最高标准。

关键字: 嵌入式 开发板

【2024年4月8日,德国慕尼黑讯】低碳化和数字化是当今时代人们面临的两大核心挑战,人类社会需要依靠创新和先进的技术,才能破除挑战、推动转型进程。在德国纽伦堡举办的2024国际嵌入式展(Embedded World 20...

关键字: 半导体 微控制器 嵌入式

TDK 株式会社(TSE:6762)进一步扩充 Micronas 嵌入式电机控制器系列 HVC 5x,完全集成电机控制器与 HVC-5222D 和 HVC-5422D,以驱动小型有刷(BDC)、无刷(BLDC)或步进电机...

关键字: 嵌入式 电机控制器 内存

嵌入式开发作为信息技术领域的重要分支,在当今智能化社会中的地位日益显著。它不仅在日常生活中的消费电子产品、工业自动化、汽车电子、航空航天等诸多领域发挥着不可或缺的作用,而且随着物联网、大数据、人工智能技术的发展,嵌入式开...

关键字: 嵌入式 信息技术

中国,北京和德国,纽伦堡 - EQS Newswire - 2024年4月2日 - 绿芯将于4月9日至11日在德国纽伦堡举行的2024年嵌入式世界展会 ((embedded world 2024),4A号馆606展位)展...

关键字: 固态硬盘 嵌入式 智能交通

虽然嵌入式芯片架构市场上有明确的引领者,但该行业正在快速扩张,预计未来几年将出现许多新的机会。当然,在这样的热门行业中,永远有创新技术和新产品的一席之地。

关键字: 嵌入式 处理器 RISC-V

2024年3月8日 – 专注于引入新品的全球电子元器件和工业自动化产品授权代理商贸泽电子 (Mouser Electronics) 即日起供货Advantech的VEGA-P110 PCIe Intel® Arc A37...

关键字: 嵌入式 GPU卡 边缘AI

康佳特采用博世力士乐的 ctrlX OS 操作系统

关键字: 计算机模块 嵌入式 机器人
关闭
关闭