简体   繁体   English

线程安全队列实现(或替代数据结构)

[英]Thread safe queue implementation (or alternative data structure)

I'm trying to implement a threadsafe queue that will hold data coming in on a UART buffer.我正在尝试实现一个线程安全队列,该队列将保存进入 UART 缓冲区的数据。 The queue is written to as part of the UART RX-complete-ISR.队列作为 UART RX-complete-ISR 的一部分写入。 This queue now holds the data that came in on the UART RX channel.这个队列现在保存了进入 UART RX 通道的数据。 The queue also needs to be read by the application using another thread to process the data.应用程序还需要使用另一个线程来读取队列来处理数据。 But since I'm running all of this on a bare-metal system without any RTOS support, I'm wondering if there is a better data structure to use here.但是由于我在没有任何 RTOS 支持的裸机系统上运行所有这些,我想知道这里是否有更好的数据结构可以使用。 Because when I'm using queues there is one common variable that both the threads need to access and this might cause a race condition.因为当我使用队列时,两个线程都需要访问一个公共变量,这可能会导致竞争条件。

I realize as I'm writing this that this is the producer-consumer problem and the only way I have solved this in the past is with mutexes.我在写这篇文章时意识到这是生产者-消费者问题,我过去解决这个问题的唯一方法是使用互斥锁。 Is there an alternative to that approach?这种方法有替代方法吗?

Edit:编辑:

The processor being used is a ST micro cortex-M0 based processor.所使用的处理器是基于 ST micro cortex-M0 的处理器。 I looked into some mutex implementations for M0 but couldn't find anything definitive.我研究了 M0 的一些互斥体实现,但找不到任何明确的东西。 This is mostly because the M0 processor does not support LDREX or STREX instructions that are usually present in M3 and M4 systems and are used for implementing atomic operations required for mutexes.这主要是因为 M0 处理器不支持 LDREX 或 STREX 指令,这些指令通常存在于 M3 和 M4 系统中,用于实现互斥体所需的原子操作。

As for the system, the code runs straight to main after booting and has NO OS functionality.至于系统,代码在启动后直接运行到 main 并且没有OS 功能。 Even the scheduler was something that was written by me and simply looks at a table that holds function pointers and calls them.甚至调度程序也是我写的,只是查看一个包含函数指针并调用它们的表。

The requirement is that one thread writes into a memory location from the ISR to store data coming in through the UART RX channel and another thread reads from those memory locations to process the data received.要求是一个线程从 ISR 写入内存位置以存储通过 UART RX 通道传入的数据,而另一个线程从这些内存位置读取以处理接收到的数据。 So my initial thought was that I would push to a queue from the ISR and read from it using the application thread, but that is looking less and less feasible because of the race condition that comes out of a producer-consumer setup (with the ISR being the producer and the application being the consumer).所以我最初的想法是我会从 ISR 推送到一个队列并使用应用程序线程从中读取,但由于生产者-消费者设置(使用 ISR)产生的竞争条件,这看起来越来越不可行是生产者,应用程序是消费者)。

Your M0 is a uniprocessor, so you can disable interrupts to serve basic exclusion:您的 M0 是单处理器,因此您可以禁用中断以提供基本排除服务:

int q_put(int c, Q *q) {
   int ps, n, r;
   ps = disable();
   if ((n = q->tail+1) == q->len) {
        n = 0;
   }
   if (n != q->head) {
        q->buf[q->tail] = c;
        q->tail = n;
        r = 0;
   } else {
        r = -1;
   }
   restore(ps);
   return r;
}

int q_get(Q *q) {
   int ps, n, r;
   ps = disable();
   if ((n=q->head) == q->tail) {
       r = -1;
   } else {
       r = q->buf[n] & 0xff;
       q->head = n+1 == q->len ? 0 : n+1;
   }
   restore(ps);
   return r;
}

where disable disables interrupts returning the previous state, and restore sets the interrupt state to its argument.其中disable禁用返回先前状态的中断,而restore将中断状态设置为其参数。

If it is bare metal, then you won't have any mutex or higher level concepts, so you need to implement something similar yourself.如果是裸机,那么您将不会有任何互斥锁或更高级的概念,因此您需要自己实现类似的东西。 This is a common scenario however.然而,这是一个常见的场景。

The normal datatype to use for this is a ring buffer, which is a manner of queue, implemented over a circular array.用于此的正常数据类型是环形缓冲区,这是一种队列方式,通过循环数组实现。 You should write that one as a separate module, but include two parameters: interrupt register and bit mask for setting/clearing that register.你应该把它写成一个单独的模块,但包括两个参数:中断寄存器和用于设置/清除该寄存器的位掩码。 Then let the ring buffer code temporarily disable the UART RX interrupt during copy from the ring buffer to the caller application.然后让环形缓冲区代码在从环形缓冲区复制到调用者应用程序期间暂时禁用 UART RX 中断。 This will protect against race conditions.这将防止竞争条件。

Since UART is most of the time relatively slow (< 115.2kbps), disabling the RX interrupt for a brief moment is harmless, since you only need a couple of microseconds to do the copy.由于 UART 大部分时间相对较慢(< 115.2kbps),因此暂时禁用 RX 中断是无害的,因为您只需要几微秒即可完成复制。 The theory behind this is that the ISR will run once per data byte received, but the caller runs completely asynchronous in relation to the data.这背后的理论是,ISR 将在每个接收到的数据字节运行一次,但调用者运行与数据完全异步。 So the caller should not be allowed to block the ISR for longer than the time it takes to clock in 2 data bytes, in which case there will be overrun errors and data losses.因此,不应允许调用者阻塞 ISR 的时间超过计时 2 个数据字节所需的时间,在这种情况下,将出现溢出错误和数据丢失。

Which in practice means that the caller should only block the ISR for shorter time than it takes to clock in 1 data byte, because we don't know how far the UART has gotten in clocking in the current byte, at the time we disable the RX interrupt.这在实践中意味着调用者应该只在比在 1 个数据字节中计时更短的时间内阻止 ISR,因为我们不知道 UART 在当前字节中计时有多远,当时我们禁用了接收中断。 If the interrupt is disabled at the point the byte is clocked in, that should still be fine since it should become a pending interrupt and trigger once you enable the RX interrupt once again.如果中断在字节被计时时被禁用,那应该仍然没问题,因为它应该成为挂起的中断并在您再次启用 RX 中断时触发。 (At least all UART hardware I've ever used works like this, but double-check the behavior of your specific one just to be sure.) (至少我使用过的所有 UART 硬件都是这样工作的,但请仔细检查您的特定硬件的行为以确保。)

So this all assuming that you can do the copy faster than the time it takes to clock in 1+8+1 new bits on the UART (no parity 1 stop).所以这一切都假设您可以比在 UART 上输入 1+8+1 个新位所需的时间更快地进行复制(无奇偶校验 1 停止)。 So if you are running for example 115.2kbps, your code must be faster than 1/115200 * (1+8+1) = 86.8us.因此,如果您正在运行例如 115.2kbps,您的代码必须比 1/115200 * (1+8+1) = 86.8us 快。 If you are only copying less than a 32 bit word during that time, a Cortex M should have no trouble keeping up, assuming you run a sensible clock speed (8-48MHz something like that) and not some low power clock.如果您在那段时间只复制少于 32 位的字,那么 Cortex M 应该可以轻松跟上,假设您运行合理的时钟速度(8-48MHz 之类的)而不是一些低功耗时钟。

You always need to check for overrun and framing errors.您始终需要检查超限和帧错误。 Depending on UART hardware, these might be separate interrupts or the same one as RX.根据 UART 硬件,这些可能是单独的中断或与 RX 相同的中断。 Then handle those errors in whatever way that makes sense for the application.然后以对应用程序有意义的任何方式处理这些错误。 If both sender & receiver is configured correctly and you didn't mess up the timing calculations, you shouldn't have any such errors.如果发送方和接收方都配置正确并且您没有搞乱时间计算,那么您就不应该有任何此类错误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM