为什么嵌入式系统上的寄存器需要读 – 修改 – 写?

我正在阅读http://embeddedgurus.com/embedded-bridge/2010/03/different-bit-types-in-different-registers/ ,其中说:

通过读/写位,固件可在需要时设置和清除位。 它通常首先读取寄存器,修改所需的位,然后将修改后的值写回

我已经遇到了这个问题,同时保留了一些由老盐嵌入式家伙编码的生产代码。 我不明白为什么这是必要的。

当我想设置/清除一点时,我总是只是或者使用位掩码。 在我看来,这解决了任何线程安全问题,因为我假设设置(通过赋值或使用掩码进行设置)寄存器只需要一个周期。 另一方面,如果您首先读取寄存器,然后修改,然后写入,读取和写入之间发生的中断可能导致向寄存器写入旧值。

那么为什么读 – 修改 – 写? 还有必要吗?

这在某种程度上取决于您的特定嵌入式设备的架构。 我将举出三个涵盖常见案例的例子。 然而,它的基本要点是,基本上CPU内核不能直接在I / O设备的寄存器上操作,除了以字节或甚至字方式读取和写入它们。

1)68HC08系列,一个8位独立微控制器。

这包括“位设置”和“位清除”指令。 如果您仔细阅读本手册,实际上内部会自行执行读 – 修改 – 写周期。 它们确实具有primefaces操作的优点,因为作为单个指令它们不能被中断。

您还会注意到它们比单个读取或写入指令花费的时间更长,但是比使用三个指令更少的时间(见下文)。

2)ARM或PowerPC,传统的32位RISC CPU(通常也存在于高端微控制器中)。

这些不包括任何可以同时访问存储器和执行计算(和/或)的指令。 如果你用C写:

*register |= 0x40;

它变成了下面的程序集(对于这个PowerPC示例,r8包含寄存器地址):

 LBZ r4,r8 ORI r4,r4,#0x40 STB r4,r8 

因为这是多个指令,所以它不是primefaces的,可以被中断。 使其成为primefaces甚至SMP安全超出了这个答案的范围 – 它有特殊的指令和技术。

3)IA32(x86)和AMD64。 为什么你将这些用于“嵌入式”是超出我的,但它们是另外两个例子之间的中途。

我忘记了x86上是否存在单指令内存中位设置和位清除。 如果没有,那么请参阅上面的RISC部分,它只需要两个指令而不是三个,因为x86可以在一条指令中加载和修改。

假设有这样的指令,他们需要在内部加载和存储寄存器以及修改它。 现代版本将在内部将指令分解为三个类似RISC的操作。

奇怪的是,x86(与HC08不同)可以通过总线主机在中间事务中在存储器总线上中断,而不仅仅是传统的CPU中断。 因此,您可以手动将LOCK前缀添加到需要执行多个内存周期的指令中,如本例所示。 但是你不会从普通的C中得到这个。

问题是,如果你不想修改寄存器中的其他位,你必须在写它之前知道它们是什么。 因此读/ modiy /写。 请注意,如果您使用C语句,如:

 *pRegister |= SOME_BIT; 

事件虽然在第一次看起来像一个简单的写操作,但编译器必须首先执行读操作以保留值中的其他位(这通常是正确的,即使您不是在讨论硬件寄存器,除非编译器能够使用有关该值的其他知识来优化读取)。

请注意,内存映射硬件寄存器通常标记为volatile特定,因此无法进行这些优化(否则许多硬件寄存器例程将无法正常工作)。

最后,有时硬件支持寄存器,专门设置或清除硬件中的位而无需读/修改/写序列。 我使用过的一些Atmel ARM微控制器具有特定的寄存器,可以清除或设置硬件中的位,只读取写入寄存器时设置的位(仅保留任何未设置的位)。 此外,Cortex M3 ARM CPU支持通过使用他们称之为“位带”的技术访问特定地址空间来访问存储器或硬件寄存器中的单个位(用于读取或写入)。 位带算法乍一看看起来很复杂,但它实际上只是将一个地址中的位偏移映射到另一个“位特定”地址的简单算法。

无论如何,最重要的是,有一些处理器,你可以在没有读/修改/写入系列的情况下离开,但这绝不是普遍真实的。

如果必须修改字中的一部分位,并且该体系结构仅支持字级读/写,则必须读取不得更改的位以知道要写回的内容,以便不修改它们。

某些体系结构支持全局或特定内存区域的位级内存访问。 但即使这样,当修改多个位时,read-modify-write很多会导致更少的指令。 在multithreading系统中,必须注意确保两个线程不能同时对同一个字执行此非primefaces操作。

现代处理器可以使用单指令设置或清除位。 但是,这些说明不能同时设置和清除。 有些情况下,IO端口的某些位必须一起更改而不会影响其他位。 只要读取 – 修改 – 写入的顺序不能被破坏,就没有问题。

rmw可能成为问题的情况需要三个条件。

  1. 变量必须是全局可访问的,例如IO端口或特殊function寄存器或全局定义的变量。

  2. 可以在可以被抢占的函数中修改全局变量。

  3. 在为抢占提供服务时修改了相同的全局变量。

使用rmw非primefaces序列解决多位修改的唯一方法是通过禁用中断服务程序的中断来保护指令序列,中断服务程序也可以修改变量或寄存器。 这类似于digine对LCD或串行端口等资源的独占访问。

当我想设置/清除一点时,我总是只是或者使用位掩码。

对于一些足够好的寄存器。 在这种情况下,CPU的固件仍将进行读 – 修改 – 写。

在我看来,这解决了任何线程安全问题,因为我假设设置(通过赋值或使用掩码进行设置)寄存器只需要一个周期。

如果你让CPU的固件为你做了读 – 修改 – 写,显然它至少会包括一个读周期和一个写周期。 现在,大多数CPU不会在中间中断该指令,因此您的线程将在线程的CPU检查中断之前执行整个指令,但如果您没有锁定总线,则其他CPU可以修改相同的寄存器。 你的线程和其他线程仍然可以遍布彼此。