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

问题描述

我正在尝试实现一个线程安全队列,该队列将保存来自UART缓冲区的数据。该队列被写为UART RX-complete-ISR的一部分。现在,该队列保存了UART RX通道中输入的数据。应用程序还需要使用另一个线程来处理该队列,以处理数据。但是,由于我是在没有任何RTOS支持的裸机系统上运行所有这些功能,因此我想知道是否可以使用更好的数据结构。因为当我使用队列时,两个线程都需要访问一个公共变量,这可能会导致竞争状态。

我在写这篇文章时意识到这是生产者-消费者问题,过去我唯一解决此问题的方法就是使用互斥锁。除了这种方法,还有其他选择吗?

编辑:

正在使用的处理器是基于ST micro cortex-M0的处理器。我研究了M0的一些互斥量实现,但是找不到任何确定的东西。这主要是因为M0处理器不支持M3和M4系统中通常存在的LDREX或STREX指令,它们用于实现互斥量所需的原子操作。

对于系统,代码在引导后直接运行到main,并具有操作系统功能。甚至调度程序都是我写的东西,只是看一个保存函数指针的表并调用它们。

要求是,一个线程从ISR写入一个存储位置以存储通过UART RX通道进入的数据,而另一个线程从这些存储位置读取以处理接收到的数据。因此,我最初的想法是,我将把ISR推送到一个队列并使用应用程序线程从中读取内容,但是由于生产者-消费者设置(使用ISR产生了竞争条件),这看起来越来越不可行。是生产者,而应用程序是消费者)。

解决方法

您的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,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;
}

其中 disable 禁用返回前一状态的中断,而 restore 将中断状态设置为其参数。

,

如果它是裸机,那么您将不需要任何互斥或更高级别的概念,因此您需要自己实现一些类似的东西。但是,这是常见的情况。

用于此的常规数据类型是环形缓冲区,这是一种队列方式,是在圆形数组上实现的。您应该将其写为一个单独的模块,但应包含两个参数:中断寄存器和用于设置/清除该寄存器的位掩码。然后,在从环形缓冲区复制到调用者应用程序的过程中,让环形缓冲区代码暂时禁用UART RX中断。这样可以避免比赛条件。

由于UART在大多数情况下都相对较慢(

在实践中,这意味着调用方只应在比ISR 1个数据字节更短的时间内阻塞ISR,因为我们不知道当时UART在当前字节中的时钟已到达多远我们禁用RX中断。如果在时钟输入字节的那一刻禁用了中断,那应该还是可以的,因为它应该成为挂起的中断并在再次启用RX中断后触发。 (至少我曾经使用过的所有UART硬件都是这样工作的,但是请确保仔细检查特定硬件的行为,以确保安全。)

因此,所有这些都假定您可以比在UART上输入1 + 8 + 1新位(没有奇偶校验1停止)的时间更快地进行复制。因此,例如,如果您正在运行115.2kbps,则您的代码必须比1/115200 *(1 + 8 + 1)= 86.8us更快。如果您在此期间仅复制了少于32位的字,那么假设您运行的是合理的时钟速度(这样的时钟频率为8-48MHz),而不是低功耗时钟,那么Cortex M应该不会出现问题。

您始终需要检查溢出和取景错误。根据UART硬件的不同,这些中断可能是单独的中断,也可能是与RX相同的中断。然后以对应用程序有意义的任何方式处理这些错误。如果发送方和接收方均已正确配置,并且您没有弄乱时序计算,那么您应该不会有任何此类错误。