嵌入式系统中的分工
伊万·戈达德

您可以选择多种策略,在非一致性处理器内核上划分嵌入式应用程序。以下是关于每种策略的优势和缺陷的实用建议。

越来越多的嵌入式应用需要比单个处理器所能提供的更高的处理能力,即使是采用超长指令字 (VLIW) 或超标量等高性能架构的深度流水线处理器也是如此。在嵌入式领域,简单地提高时钟频率通常是不可行的,因为更高的时钟频率需要成比例的更多功率,而功率在嵌入式系统中通常是稀缺的商品。多处理,即应用程序在两个或多个处理器上同时运行,是在固定功率预算内获得更多处理器周期的自然途径。

今天,这些复数处理器通常作为内核驻留在单个芯片中,作为片上系统 (SoC) 解决方案的一部分,通常与控制电路、本地内存和大量专用不可编程的定制逻辑(统称为外围设备)一起存在。一般来说,多核嵌入式设计中的内核是不一致的——也就是说,没有硬件手段可以让内核对系统的其余部分,特别是对内存内容保持一致的集体视图。

不一致性的来源通常是缓存,每个内核在缓存中保留感兴趣的内存内容的本地副本。一个内核写入的值通常不会在写入后很长时间才出现在内核之间共享的内存中。与此同时,引用该位置的其他内核将收到过时的数据。即使在立即写入共享内存的直写系统中也存在不一致性,因为另一个内核可能已经缓存了先前(现在过时)的值副本,并在后写入读取中使用它。多处理服务器和工作站系统花费大量硬件成本来排除这种不一致性,但这些步骤几乎从不在嵌入式领域出现。

在某些多核硬件设计中,内核具有不同的指令集架构。通常,一个内核将是传统的 RISC,用于运行遗留软件或操作系统,而一个或多个其他内核是数字信号处理器 (DSP),用于执行繁重的工作。需要比单个内核提供的处理能力更多的应用程序必须在多个内核之间进行划分。有几种可能的应用程序划分策略。以下策略按实现复杂性和性能大致递增的顺序排列:

  1. 独立应用程序。每个内核运行一个独立的应用程序,使用其自身静态专用的系统资源集。
  2. 共享代码应用程序。与上述相同,但独立应用程序共享只读资源,例如代码占用的内存。
  3. 弱耦合应用程序。与上述相同,但独立应用程序(以及可能的外围硬件)通过生产者/消费者模式下的缓冲区池进行通信。
  4. 微任务列表。与上述相同,但应用程序被分解为许多独立的应用程序,这些应用程序被动态调度到内核上并运行到完成。微任务在运行时是弱耦合的,并且不并发共享系统资源。
  5. 对称多处理 (SMP)。与上述相同,但微任务可能不会终止,可以被抢占,并且可以在抢占后在不同的内核上恢复。
  6. 多线程。与上述相同,但微任务可能任意共享系统资源。

以下讨论指的是双核系统,但这些策略适用于任何数量的内核。

独立应用程序策略

目标系统可能会自然地呈现为一组完全独立的应用程序,它们之间没有任何交互,可以方便地一对一映射到可用的内核。例如,一个应用程序可以管理通过视频端口的视频流,而第二个应用程序可以管理通过音频端口的不相关的音频流,其中每个流都适合单个内核的处理能力。这样的系统可以实现为两个独立的单核芯片,每个芯片都具有适当的外围端口、内存等等,但为了经济性,该系统也可以组合到单个双核 SoC 上。

在这种策略中,这两个应用程序被独立开发为单独的程序,并独立加载到内存中。如果两个应用程序都使用实时操作系统 (RTOS) 的服务,则每个应用程序都包含 RTOS 代码。同样,C 库或其他应用程序通用的代码也有两个副本(每个链接的应用程序中各有一个)。也就是说,通用代码不共享。启动配置在相应应用程序程序的入口点启动每个内核。

在这种策略中,系统资源(例如外围设备或内存)是静态分配给每个应用程序的。例如,DRAM 地址空间被分成几部分,一部分包含在一个内核中运行的应用程序的代码和数据,另一部分包含在其他内核中运行的代码和数据。同样,外围设备的内存映射输入/输出 (MMIO) 空间、系统控制寄存器等等也在两个应用程序之间进行分配。最后,中断使能被配置为将外围设备中断路由到已分配了中断外围设备的内核。

根据片上内存控制器和总线的配置能力,可以配置芯片,使分区不仅是功能上的,而且也是可见性上的。也就是说,可以配置芯片,使专用于一个内核的资源甚至对其他内核不可见或不可寻址。如果存在这种可见性控制功能,则实现应使用它。当您知道一个内核中的代码不会错误地覆盖另一个内核使用的数据时,调试多核应用程序会容易得多。

同样,在某些芯片上,可以偏置内核看到或发出的地址,以便将内核地址空间放置在 SoC 地址空间中的任意位置(一个孔径)中,并因此放置在物理内存中。这是一种有限形式的虚拟内存,因为应用程序看到的虚拟地址与内存看到的物理地址不同,尽管通常没有在运行时更改配置的方法,也没有对按需分页的支持。

如果硬件中存在这种偏置功能,那么通常方便的做法是让所有独立的应用程序都使用单个虚拟基地址,尽管必然使用不同的偏置物理地址。这允许预先存在的单核应用程序在多核 SoC 的一个内核中运行,而无需重新编译或对通常存在于遗留嵌入式应用程序中的硬编码地址值进行源代码更改。

这种独立应用程序策略的主要优点是其简单性。应用程序可以独立开发,仅使用单个内核或在相同架构的单核芯片上进行调试,然后加载到多核芯片,在多核芯片上,它们将在应用程序看到的单核环境中执行。第二个优点是提高了稳健性和可靠性,从而减少了调试工作量。如果(如建议的那样)芯片被引导配置为隔离每个应用程序使用的资源,那么一个应用程序中的错误将不会覆盖另一个应用程序使用的数据,也不会以其他方式干扰其运行。

然而,这种策略有许多缺点。应用程序无法共享外围设备等资源。由于两个应用程序中存在重复代码,内存将被浪费。也许最重要的是,这种策略无法平衡两个应用程序之间的负载。这导致处理器周期浪费,因为轻负载内核无法帮助处理另一个应用程序的处理器峰值。

在实践中,当内核集是异构的并且每个内核使用不同的指令集时,可能会强制采用这种策略。那么执行无法跨内核迁移,尽管稍后讨论的策略的数据共享仍然是可能的。所有后续策略都假设内核集至少部分同构。

共享代码应用程序策略

共享代码应用程序策略与独立应用程序策略相同,不同之处在于用于指令和常量数据的内存在两个应用程序之间共享。可变数据不共享。为了实现这种策略,可以独立开发这两个应用程序,但随后必须将它们链接在一起并作为一个单元加载到芯片中。孔径(假设硬件支持这个概念)被设置为一个孔径保存代码,并且两个内核都被授予对该孔径的访问权限。外围 MMIO 空间和读/写(数据)内存与独立应用程序策略一样进行分区。

这种策略的优点是由于代码共享而减少了内存使用量。只要两个应用程序都不包含自修改代码,那么开发过程和优点基本上与独立应用程序策略相同。但是,任何自修改、动态生成或采用不同指令集的代码都必须为每个内核提供单独的副本。此要求可能会强制修改源代码,以便所有此类代码都通过动态地址(例如函数指针变量)而不是链接器设置的静态地址输入。

弱耦合应用程序策略

弱耦合应用程序策略类似于独立应用程序策略,因为应用程序功能仍然在内核之间静态分区,并且内核之间不并发共享数据。但是,数据在内核之间进行通信,通常以乒乓缓冲区的形式进行。这种数据通信本质上与单核流处理系统中内核与外围设备之间的数据通信相同;在弱耦合策略中,每个内核本质上将另一个内核视为(智能)外围设备。

通常,数据通过硬件外围设备(例如,将模拟视频信号数字化的视频输入单元)进入系统,并放置在缓冲区中。当缓冲区填满时,外围设备切换到填充备用缓冲区,并通过中断发出转换信号。该中断由静态专用于输入数据的下一个处理功能的内核处理,然后该内核处理充满输入数据的缓冲区,并将计算结果放入与外围设备和内核之间用于输入的缓冲区不同的中间缓冲区中。

当保存第一个内核输出的中间缓冲区填满,或者发生其他处理条件时,第一个内核切换到备用缓冲区,并反过来通过(不同的)中断发出转换信号。该中断由第二个内核处理,该内核处理来自第一个内核的缓冲区,并将结果放入输出缓冲区。当输出缓冲区填满(或发生其他条件)时,第二个内核切换到备用输出缓冲区,并使用 MMIO 触发输出外围设备(例如,将修改后的数字视频转换为模拟形式的视频输出单元)以将充满输出的缓冲区发送出芯片。

因此,整个应用程序被静态分区为一个桶式传送带,其中包含静态定义的处理步骤,包括外围设备和内核。这些步骤通过内存中的缓冲区进行通信,但在任何时候,任何一个缓冲区中都只有一个步骤在处理。也就是说,对数据的任何部分都没有并发访问,并且系统组件通过消息传递(以缓冲区的形式)进行通信。

对于单核流处理器的用户来说,这种弱耦合策略将是熟悉的,因为它与单核中外围设备和内核之间的数据流处理相同。因此,它受到相同的约束,其中最重要的是需要在核心中存在数据缓存的情况下手动控制内存一致性。

数据缓存维护从内存中读取的数据的本地副本。缓存可能是回写缓存,这意味着内核执行的写入操作被捕获在缓存中,并且不会立即反映到物理 DRAM 中。只有当修改后的(“脏”)缓存行从缓存中逐出时,内核的写入才会对其他内存用户可见。

回写缓存的使用对在内核和某些其他设备(如外围设备或第二个内核)之间共享或通信内存数据的应用程序施加了纪律要求。特别是,应用程序必须确保内核对要共享或通信的数据的任何写入操作都必须在使用者尝试读取数据之前从缓存中刷新。如果没有这种纪律,使用者可能会从内存中读取过时的先前值,而不是仍在缓存中的更新值,从而从过时的数据中产生错误的结果。

很容易让人认为,使用直写缓存时不会发生这种过时数据问题,在直写缓存中,写入操作会立即传播到内存,而不是被保留以供以后从缓存中逐出。但是,应用程序设计人员仍然必须谨慎地确保即使使用直写缓存也具有一致性,因为写入不是瞬时的。如果一个内核写入数据,然后立即通知使用者开始处理写入的数据,则使用者可能在写入的数据实际到达那里之前读取写入的位置。这种竞争条件是否可能发生取决于内存控制器的优先级分配算法的详细信息。由这种竞争引起的错误(如果存在)是不可重现且难以找到的。最好的避免方法是使用硬件特殊指令(几乎总是存在或易于模拟),该指令会阻止向使用者发出通知,直到所有未完成的写入都完成。

输入缓冲区传递到内核时也会发生类似的问题。也就是说,输入缓冲区中的某个位置也可能由于在同一缓冲区中进行早期处理而存在于缓存中。如果内核读取该位置,它将收到过时的缓存值,而不是缓冲区生产者更新的内存值。为了防止使用过时的数据,必须修改应用程序以添加显式缓存控制操作,以丢弃过时的数据,并可选择预取该位置的更新数据。

确保生产者/消费者内存缓冲区与缓存内容之间一致性的方法包括:

在缓冲区生成/消费时显式刷新。使用弱耦合策略的应用程序可以向其代码添加一个步骤,以便在每次要将输出缓冲区传递给数组中的下一个使用者时执行。此步骤必须迭代缓冲区的地址范围,并使用处理器的缓存控制指令强制将该范围内的任何脏行写回内存。只有当所有写回都发生后,才能安全地向下一个处理阶段发出信号,表明其输入已准备就绪。

在输入时,应用程序必须确保缓存不包含使用者缓冲数据过时的副本。因此,它必须步进输入缓冲区的地址范围,并使用处理器的缓存控制操作来丢弃任何过时的数据,并可选择将新数据预取到缓存。请注意,如果预取的缓冲区大小大于缓存(在考虑其他内存使用的工作集以及缓存替换策略和路数之后),则预取将无效。

增量刷新。或者,如果输出缓冲区是顺序填充的,则可以将缓存刷新插入到数据输出循环中,而不是在末尾形成一个单独的循环。因此,当应用程序完成每个输出缓存行时,它会执行缓存控制操作以将该单独的行刷新到内存。虽然这与在末尾进行刷新循环执行的刷新操作次数完全相同,但刷新与其他处理混合在一起,并且可能会由于与其他处理的执行重叠而提供更好的吞吐量。

相同的增量方法可以用于输入。优点是它可以与数据预取相结合,以获得更好的性能,即使缓冲区超过缓存的大小。

对缓冲区使用不可缓存内存。可以将被缓冲区包围的地址范围标记为不可缓存,或分配给未缓存的孔径。这将导致对缓冲区的任何写入绕过缓存并直接写入内存,并且任何读取绕过缓存并直接从内存加载到寄存器。然而,绕过缓存也有其自身的成本,即更频繁的内存事务和停顿。结果可能是性能低于使用刷新循环。基本上,所有带有缓存的处理器都提供某种绕过缓存的引用方式。

缓存清洗。在某些情况下,您可以省略显式缓存控制操作。如果缓冲区大于缓存,并且缓冲区中的数据是严格顺序处理的,那么对于大多数缓存架构,缓存的正常操作将清洗缓冲区内容通过缓存。这意味着每行在重用以供后续缓冲区填满之前都会被写回(在输出时)或丢弃并重新读取(在输入时)。在这种情况下,输入时不可能出现过时的数据,如果输出使用者比生产者落后两个或更多缓冲区,则输出时也不可能出现过时的数据。以这种方式省略缓存控制可以产生很大的性能优势。但是,算法上很难确保前提条件(尤其是在输出时),并且如果将生成的代码移植到具有不同大小缓存或不同硬件缓存行为的硬件,则代码往往会崩溃。

弱耦合策略的主要优点是它提供的概念简单性和易于模块化的应用程序。主要缺点(除了缓存复杂性之外)是应用程序工作负载在内核上的静态分区。与之前的策略一样,在弱耦合策略中,过载的内核不可能动态地将处理卸载到其负载较轻的伙伴。

微任务列表策略

微任务列表策略与弱耦合策略类似,因为任何给定的处理单元在任何时候都只由一个内核执行,并且处理单元通过传递数据缓冲区进行通信,而不共享数据。然而,在这种策略中,处理单元不是由静态专用的硬件执行的,而是动态分配给可用的内核以进行负载均衡。

整个应用程序(静态地)分解为多个独立的处理步骤,每个步骤都可以独立于所有其他步骤运行到完成。每个步骤都是一个微任务,每个微任务在激活时都会收到一个参数记录,其中包含完成任务所需的所有数据,以及执行该任务的代码的指示(通常是函数指针)。也就是说,任务及其参数缓冲区可以在与系统其余部分完全隔离的情况下执行。

应用程序创建一个全局工作列表,其中包含可运行的微任务队列。每个内核独立获取队列头部的任务并执行它以完成。然后,它获取并执行下一个任务,依此类推。新的微任务通常作为执行的副产品创建并输入到工作列表中,以便应用程序成为微任务的连续时间排序流,这些微任务共同实现应用程序。哪个内核执行哪个任务完全是偶然的,并且(对于任务的执行粒度)整个应用程序负载将在内核之间自动平衡。

每个微任务本身都具有类似于弱耦合策略的静态分配任务的特征。也就是说,用于在微任务之间进行通信的参数块等效于(并且通常将包含与)用于在弱耦合策略中内核和外围设备之间进行通信的缓冲区相同的数据。因此,它们与缓冲区一样受到相同的缓存考虑因素的约束。特别是,为新任务创建参数块的任务必须确保从其缓存中刷新数据,以防新任务将由另一个内核执行。

类似地,开始处理新任务的内核必须在其缓存中清除包含新参数块的任何位置的过时副本,然后再执行任务。但是,可以进行优化:如果参数块标记有创建它的内核的身份,则如果创建者与执行者是同一个内核,则可以省略清除缓存。请注意,执行者仍然必须清除参数块中包含创建者标记的行;否则,执行者可能会使用过时的创建者指示符,并认为它是创建者,而实际上并非如此。

在这种策略中,所有应用程序数据都存在于参数块中,这些参数块被通信但不共享。与早期策略中重复且不相交的数据空间不同,在微任务列表策略中,某些系统数据在微任务之间共享。这些包括:

必须通过同步机制协调对共享可变数据的所有访问,以防止更新时出现竞争条件。处理器指令集在它们提供的同步原语方面差异很大,从经典的读-修改-写原语(例如,测试和设置或提取和添加)到通过 MMIO 寄存器访问的单个或多个硬件同步器。幸运的是,大多数实现都抽象了这种复杂性,并在支持库中提供了更高级别的互斥原语;您不必求助于机器级操作即可使用它们。

共享调度列表。每个内核独立且异步地访问工作列表,以添加新的微任务并删除最旧的微任务以供执行。必须通过同步机制协调此访问,以防止更新队列时出现竞争条件。通常,使用微任务列表策略的应用程序静态地将可用的互斥锁之一专用于管理对工作列表的访问。在操作中,希望访问工作列表的内核将首先获取关联的互斥锁,然后执行其列表操作,最后释放互斥锁。

请注意,列表头和其他关联数据在内核之间共享,因此会引起常见的缓存问题。虽然肯定可以在读取列表头之前清除缓存位置,并在写入后从缓存中刷新该位置,但手动执行此操作很可能容易出错。相反,应用程序设计人员应考虑将工作列表数据结构(但不一定是参数块条目)放置在不可缓存的内存中。这避免了手动管理缓存的需要,并且额外的性能开销可能是微不足道的。

设计人员还可以考虑将参数块放置在不可缓存的内存中。假设大部分数据都在单独的缓冲区中(如弱耦合策略中那样),从参数块指向,则块本身可能非常小。因此,通过消除对块的访问的缓存控制来提高应用程序的简单性可能非常值得使这些块不可缓存的开销。主缓冲区仍然是可缓存的,并且与弱耦合策略中一样,需要进行必要的手动缓存控制。

共享堆。大多数 C 库堆实现都假定堆是全局的。这在微任务列表策略应用程序中是否需要为真取决于任务的详细信息。一般来说,如果堆分配可以持续超过单个任务的执行(即,堆数据是在微任务之间创建和传递的),那么堆必须是全局的。这意味着对堆操作的访问必须通过互斥,以便并发堆操作不会相互干扰。此外,所有可能在任务之间传递的堆数据都需要进行手动缓存控制,包括读取和写入。最后,所有堆控制结构都必须位于不可缓存的内存中,或者对控制结构的访问必须小心地伴随显式缓存清除和/或刷新操作。请注意,大多数堆实现都将其数据结构线程化在被管理的空间中,这通常必须是可缓存的。

或者,如果堆数据的生命周期总是小于任何单个微任务(即,每个微任务总是删除它分配的任何内存),则可以使用不同的方法。在第二种方法中,堆被静态地分成两个单独的堆,每个堆专用于一个内核。堆管理数据被复制,以便两个堆完全彼此独立。正确的管理由执行操作的内核的内核编号选择,通常通过读取 MMIO 位置或通过特殊指令确定。由于活动位置永远不会跨任务(因此,跨内核)传递,因此无需对堆数据的读取使用手动缓存控制。但是,手动缓存控制对于写入仍然是必要的,或者更确切地说是对于释放是必要的;否则,来自一个内核的延迟缓存写回(到释放的堆内存)可能会污染另一个内核(重新分配后)使用的数据。可以将此缓存控制放在库“free”函数内部,以确保始终执行它。

这些复杂性表明,使用多核的应用程序设计人员最好尽一切努力从应用程序中删除所有堆的使用。请注意,许多标准 C 库函数在内部使用堆来创建它们的返回值,尽管通常存在替代函数,这些函数将其结果放在参数缓冲区中而不是使用堆。

共享 errno。另一个潜在的数据共享问题的来源是 C 语言变量 errno,它通常是全局的。此变量由该语言的基本上所有系统和库调用设置。因此,errno 访问可能导致竞争条件,其中一个内核上的任务中的函数报告错误并设置 errno,但在该任务中检查 errno 时,errno 已具有另一个内核中的任务设置的新值。此事件不太可能发生,因为每个内核都可能在其自己的缓存中维护其自己的 errno 副本,但这并非不可能。由此产生的软件问题将是不可重现且非常难以找到的。

errno 问题已在许多系统上的多线程包中得到认识,通用解决方案是将 errno 从全局变量更改为每个线程的数据。在微任务列表策略的上下文中,每个任务都等效于一个线程,因此一种解决方案是将 errno 放入每个参数块中。然后修改 C 库,并将 errno 的全局声明替换为寻址参数块中字段的宏。然后必须重新编译 C 库。或者,可以将 errno 设置为每个内核的数据,并将其放置在专用寄存器中,或作为由内核编号索引的数组;同样,必须修改库以删除全局声明,并用所需的寻址替换宏。

以上两种解决方案(每个任务或每个内核)都假定 errno 的特定值的生命周期不超过创建它的微任务。虽然与语言标准有些不符,但此约束不应引起任何应用程序困难。因此,使用这两种方法中的任何一种都不需要为 errno 进行手动缓存控制。新值的创建和任何使用都将在微任务列表策略下的单个内核中进行,因为任务在启动它们的任何内核中运行到完成。请注意,简单地保留单个全局 errno 但将其放置在不可缓存的内存中是不正确的;尽管这消除了缓存控制问题,但它留下了不同内核中对 errno 的独立访问之间的竞争条件。

微任务列表策略的主要优点是可以在内核之间进行动态负载均衡。主要的缺点是由于跨内核共享数据位置而导致的算法复杂性。第二个缺点是需要手动将应用程序划分为微任务,并手动设置和管理参数块和任务激活。

对称多处理 (SMP)

微任务列表策略将应用程序划分为足够小的单元,以便它们可以运行完成而不会使处理器周期的其他使用者处于饥饿状态。此模型不适用于由一个非常大的低优先级后台任务以及许多小型高优先级前台任务组成的应用程序。当然,如果前台任务总共可以容纳在一个内核中,而后台任务在另一个内核中运行,则不会出现问题,但这种情况最好使用独立应用程序策略建模,其中后台是一个应用程序,而前台集合是第二个(多线程)应用程序。

当前台任务总共需要多个内核时,您必须中断后台任务在其内核上的运行,在前台任务在两个内核上运行一段时间后,然后在前台负载下降时恢复后台任务。这种情况等同于 Unix 桌面上的传统基于优先级的多处理。一个进程被分配到一个内核,该内核执行该进程直至完成、时间片到期或更高优先级的就绪进程到达(可能是由于中断)。然后,该内核被分配给另一个进程。这种情况与微任务列表策略非常相似(特别是,进程不共享数据),除了进程(任务)可以在其执行期间被任意中断,并且可以在另一个内核上恢复。

实现 SMP 需要一个任务切换,将所有处理器瞬态状态折叠到任务参数块中,以便稍后可以恢复。这在微任务列表策略中是不必要的,因为所有任务都运行到完成,并且不存在瞬态状态。存在一些重要的复杂性

共享全局结构。在微任务列表策略中共享的所有数据结构也在 SMP 中共享,并且适用相同的实现考虑因素。进程调度列表必须由互斥锁保护,并且通常比微任务列表有更多的接触点。堆管理可以是全局的(并由互斥锁保护),也可以是每个进程都有堆;每个核心堆是不可能的,因为进程(堆的用户)在核心之间迁移。同样,errno 只能实现为每个进程的数据,而不是每个核心的数据,否则不合时宜的切换可能会丢失进程的 errno 值。

任务切换和缓存。由于中断的进程可能在不同的处理器中恢复,因此您必须确保在任务切换时进行适当的缓存控制。最简单的方法可能是在切换时清除整个数据缓存(写回所有脏位置并忘记所有行)。这确保了正确的切换语义,尽管处理特殊情况,例如切换到上次在同一内核上运行的进程,可能会提供显着的性能改进。切换时的缓存清除可能很昂贵,因此处理器分配粒度必须相当大。特别是,通常需要重写所有中断处理程序以删除隐式线程切换。专用的线程和内核来处理所有中断而不进行切换将提供更好的性能。

堆栈。进程的大部分瞬态状态都位于进程堆栈中,在 SMP 中是每个进程一个堆栈,而在微任务列表中是每个核心一个堆栈。如果进程数量是静态固定的,并且它们的堆栈由链接器分配,则这没有问题。但是,如果进程数量是动态的,则堆栈的分配器本身就是一个共享结构,必须受到互斥锁的保护,并且(通常)管理结构放置在不可缓存的内存中。线程包本身的全局结构也是如此。

SMP 的优点是可以对太大而无法运行完成的应用程序单元进行负载共享。缺点是为了确保对全局变量的并发访问和缓存一致性问题不会导致应用程序行为不一致,需要大大增加复杂性。

多线程

多线程策略与 SMP 策略相同,只是并发线程可以共享任意应用程序数据,并且访问时间可以是任意的。此策略将任何机器上任何多线程应用程序的所有众所周知的困难都强加于人。特别是,对任何共享内存对象的所有访问都必须受到适当的互斥原语的保护,并且数据本身必须指定为 volatile 以供编译器使用。

除了多线程应用程序的常见问题之外,多核嵌入式硬件通常会对多线程策略施加一些额外的困难。由于内核缺乏硬件缓存并发性,共享数据必须放置在不可缓存的内存中,或者所有访问都必须用手动缓存控制括起来,以确保内存映像的一致视图。此外,必须像 SMP 策略一样在任务切换时清除缓存。

多线程策略的好处是,它可以为任何应用程序并发性提供可用处理周期的最大利用率。缺点(一旦代码工作)是任务切换将很昂贵,因此使用不太激进的策略(例如弱耦合策略)可能会获得更好的整体性能。

选择策略

本文提供了一种用于非相干多核嵌入式应用程序的应用程序分区策略的分类。在实践中,独立应用程序策略是迄今为止最简单的,并且将具有最佳的上市时间,但在硬件方面可能太昂贵。另一方面,在没有硬件缓存一致性支持的情况下,多线程策略几乎不可能实现。介于这些极端之间的策略在难度和性能上有所不同。策略选择通常很容易:对于任何给定的应用程序,其中一种或另一种策略都会跃出并提供与应用程序功能的明显映射。

然后硬件预算减少了,或者应用程序被重新定义了,乐趣就开始了...

IVAN GODARD 曾任 Trimedia 的高级架构师,Trimedia 是嵌入式处理器内核的开发商。在他之前的各种职业生涯中,他设计、实现或领导了三个指令集架构、九个编译器、一个操作系统、一个 OODB 以及大量嵌入式和通用应用程序的团队。他正在努力创办一家基于新型架构的无晶圆厂芯片公司,该架构在通用代码中提供的性能增益比超标量处理器高出 10 倍或更多。

acmqueue

最初发表于 Queue vol. 1, no. 2
数字图书馆 中评论本文





更多相关文章

George W. Fitzmaurice, Azam Khan, William Buxton, Gordon Kurtenbach, Ravin Balakrishnan - 通过多样化的设备社会实现情境数据访问
自从 ATM 和杂货店 UPC 收银台等“信息设备”被引入以来,已经过去了十年多。对于办公环境,Mark Weiser 在 1991 年开始阐明普适计算的概念,并确定了趋势的一些显着特征。嵌入式计算也变得越来越普及。例如,微处理器正发现自己嵌入到看似传统的笔中,这些笔可以记住它们写了什么。汽车中的防抱死制动系统由模糊逻辑控制。


Rolf Ernst - 整合一切
随着嵌入式系统复杂性的日益增加,系统中越来越多的部分被重用或提供,通常来自外部来源。这些部分从单个硬件组件或软件进程到硬件-软件 (HW-SW) 子系统不等。它们必须与新开发的部分合作并共享资源,以满足所有设计约束。简单来说,这就是集成任务,理想情况下应该是一个即插即用的过程。然而,这在实践中并没有发生,不仅是因为不兼容的接口和通信标准,还因为专业化。


Homayoun Shahri - 模糊硬件和软件之间的界限
在技术进步的推动下,芯片上可用的门数达到数百万,一种新的设计范式正在兴起。这种新的范式允许在一个芯片上集成和实现整个系统。


Telle Whitney, George Neville-Neil - SoC:软件、硬件、噩梦、幸福
片上系统 (SoC) 设计方法允许设计人员从较小的工作块或系统创建复杂的硅系统。通过提供一种在更大的环境中轻松支持专有功能的方法,该环境包括许多现有的设计组件,SoC 设计向更广泛的受众开放了硅设计工艺。





© 保留所有权利。

© . All rights reserved.