加速原子同步

97 篇文章 1 订阅
47 篇文章 0 订阅

原子操作的动机来自共享资源时需要同步两个或多个实体。一个简单的例子可能是基于多线程 RTOS 的系统中的两个线程,这两个线程偶尔需要向它们共享的 UART 外设发送消息。

在操作中,一个线程可能会获得 UART 的控制权并保持该控制权,直到它的整个消息被发送,这将是首选。否则,如果另一个线程决定在第一个未完成时发送消息,则两个线程可能会通过 UART 发送交替字符,从而导致控制台端口发出一团无法理解的胡言乱语。

为了解决这个问题,我们可以定义一个资源,就像单个内存位置一样简单,它包含一个数据字,指示 UART 是忙还是可用。假设此位置的 0 表示 UART 空闲,而 1 表示它正在使用中。

当线程 1 有消息要发送时,它会读取锁定字。它看到它是 0,所以它写一个 1 到这个位置。任何其他想要发送消息的线程都会发现这个锁或信号量包含一个 1,并在它尝试获取资源供自己使用之前等待它被清除。

如果检查和设置锁的过程中断,就会出现问题。

考虑以下情况:

任务 1 读取锁定字。
它找到包含零的单词。
任务 1 因更高优先级的任务或导致其挂起的中断而停止。
当任务 1 暂停时,任务 2 以更高的优先级运行。
任务 2 需要发送一条消息并读取它发现包含零的锁定字。
任务 1 将 1 写入锁定字,知道它现在对 UART 具有唯一控制权,直到其消息被发送。
由于所需资源不可用,任务 2 释放控制。
任务 1 继续运行,知道锁包含 0,将 1 写入锁字。
现在,任务 1 和任务 2 都确定它们对 UART 资源具有唯一的控制权,并且将向其发送字符,交错发送,因为它们看到它在逐个字符的基础上变得可用。

原子操作

解决这个问题需要的是一种原子操作,以确保检查和设置锁定字的完整事务在任何其他代理(即使是具有更高优先级的代理)可以中断正在进行的关键操作之前完成。

原子操作只是在一个不间断的序列中完成的操作。即使这是一个复杂的事件序列。

对原子操作的支持内置于 C11 及更高版本等语言标准中。然而,用于在机器指令级和硬件级实现这一点的实际机制必须存在于 CPU 的指令集架构 (ISA) 和系统硬件实现中,以确保正确操作。

在上述情况下,任务 1 必须完成测试和设置锁定的所有步骤,然后任何其他任务或实体才能访问内存中的锁定字。

在单核系统中,这通常可以通过在读取锁定字之前简单地禁用所有中断,并在写入锁定后重新启用它们来确保。通过这种方式,不会发生中断,因此没有什么可以导致内核在测试和设置操作的中间抢占任务 1。

当然,在具有其他总线主控器(例如可以直接写入存储器的 DMA 控制器和外围设备)的单核系统的情况下,这些其他总线主控器之一在错误的时间写入锁定值可能会违反这一点。但是,可以通过确保系统中的任何其他总线主控无法访问存储锁定变量的内存来防止这种情况。

多核复杂度

现在我们看看在同一个芯片上有多个计算元素的情况,比如多核芯片,其中使用内存锁来确保运行在不同内核上的线程也可以共享资源而不会相互破坏。

在这种情况下,我们无法阻止不同的总线主机访问锁定位置。这是因为锁定位置是每个内核必须能够读取和设置的位置,以确保正确共享公共资源,如上述 UART。

现在我们必须确保一个内核一旦启动就可以完成它的读-修改-写序列,在任何其他内核或总线主控可以访问设置锁定的内存位置之前。

在 8.1 版及更高版本的 Arm 架构中,为此添加了新的原子指令。我将把这个例子集中在新的指令上。一种这样的指令是 LDADD 指令及其变体。该指令从内存中读取一个值,将芯片上一个寄存器中的值相加,并将结果写回内存,同时保持内存总线,直到整个操作完成。

这样,系统就可以保证没有其他总线主控可以修改内存中的值,从而使两个主控都认为他们拥有共享资源的所有权。

完成此代码后,处理器可以检查读取的值以验证它实际上是资源的唯一所有者,并且其值与操作开始前可用的资源相对应。

现实世界的影响

好消息是,如果您使用 RTOS 或操作系统在单核或多核线程环境中管理线程,那么这一切都在较低级别的系统代码中得到处理。然而,了解底层指令集和内存硬件必须设计为支持这些锁定机制才能使这一切正常工作是很有用的。如果这些机制设计不正确或通过直接操作寄存器而被误用,则多个内核可能会在不经意间同时控制专用于在使用时独占的资源。要调试这些类型的情况,需要先进的多核调试功能,其中可以观察和控制系统中多个内核上运行的代码。

调试多核同步

多核调试器可以通过显示在多个内核或线程上运行的程序来帮助查找同步问题,以及基于另一个内核上的断点有选择地停止和启动内核的能力应该是确定这种机制问题的理想选择。

在图 1 中,我们可以在 IAR Embedded Workbench 中看到带有 4 个 CPU 的 NXP i.MX 8。所有内核都可以单独启动和停止。
在这里插入图片描述
图 2 显示了在不同 CPU 上运行的代码中使用多个断点并结合使用互斥锁(Arm 提供的示例):_mutex_acquire() 和 _mutex_release(),它们设置标志以阻止所使用的对象的在素数计算中。
在这里插入图片描述
最常见的错误之一是误用或未使用交叉触发接口 (CTI)。对于 Arm,CoreSight 交叉触发接口 (CTI) 通过交叉触发矩阵 (CTM) 连接到每个内核。CTI 使调试逻辑、ETM 跟踪单元和 PMU 能够相互交互以及与其他 CoreSight 组件交互。这使得可以独立地停止和重置每个内核。必须操纵“自制”CTI 解决方法,手动控制和停止内核,也许动态使用宏是一项不可能完成的任务。默认情况下,这应该并且需要由来自探针(CTI 接口信号)和软件调试端的良好调试器处理。图 3 显示了完全控制 CTI 的用例。
在这里插入图片描述
一旦全部结合在一起,具有多核支持的调试器可以在非对称和对称场景甚至组合中控制内核。图 4 显示了运行 4 个 Cortex-A53 和 1 个 Cortex-M4 的 NXP i.MX 8 设备。MCU 和 MPU 可以独立停止、监视和控制。虽然所有 4 x Cortex-A53 内核或单个内核都从主会话运行,但可以在 Cortex-M4 合作伙伴端设置断点,并专注于可能正在运行整个设备的安全监视器的应用程序。

在这里插入图片描述
在应用程序中使用并行性和并发性旨在更有效地使用可用内核。然而,它的代价是增加了应用程序的复杂性,以及如何将源代码分成更小的部分以尽可能高效地运行。

概括

在单个芯片上或系统中同步多个内核需要原子操作和执行这些操作的硬件。当首次开发这种硬件/软件组合时,支持多核调试和观察的全功能调试器对于发现此类系统的问题至关重要。无法想象如何通过在代码中使用打印语句来实现相同的控制并使所有内容完美同步。每个开发人员都值得拥有一个可以处理多核并完全控制所有线程的调试解决方案。IAR Embedded Workbench 及其调试器功能提供了这样一种工具,在开发和调试这些复杂系统时非常有用。
相关实战:https://www.99qibang.cn/information/4b467a831de646528c4572004e78a675.html
https://www.99qibang.cn/information/70853d57e9604776b1c9520df7943373.html
https://www.99qibang.cn/information/5bfd3bd6ba484d81a106351d1a738310.html
https://www.99qibang.cn/information/78e0df09b86443a1b14a7b5efd3f85ea.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值