1

问题定义:

我们正在为运行 Linux 的工业嵌入式系统设计一个应用程序。

该系统由来自外部世界的事件驱动。系统的输入可以是以下任何一种:

  1. 很少以数字 IO 线的形式向系统输入(连接到处理器的 GPIO,如急停)。
  2. 该系统运行一个网络服务器,允许通过网络浏览器控制系统。
  3. 系统运行 TCP 服务器。任何 PC 或 HMI 设备都可以通过 TCP/IP 发送命令。

系统需要使用 Modbus 通过 UART 驱动或控制 RS485 从设备。系统还需要控制少量的 IO 线,如 Cooler ON/OFF 等。我们认为状态机对于定义此应用程序至关重要。核心应用程序应该是一个多线程应用程序,它应该具有以下线程......

  1. 主线程
  2. 控制 RS485 从站的线程。
  3. 用于处理来自 Web 界面的事件的线程。
  4. 处理数字 I/O 事件的线程。
  5. 通过 TCP/IP(套接字)处理命令的线程

对于线程间通信,我们使用 Pthread 条件信号和等待。根据我们最初的设计方法(主线程中的一个状态机),系统的任何输入事件(web 或 tcp/ip 或数字 I/O)都应中继到主线程,并应与相应的线程通信事件是注定的。一个典型的场景是通过 Web 界面获取 RS485 从站的状态。在这种情况下,Web 界面线程应将事件中继到应更改状态的主线程,然后将事件传达给控制 RS485 从站的线程并做出响应。主线程应将响应发送回 Web 界面线程。

问题:

  1. 每个线程是否应该有自己的状态机,从而降低主线程的复杂性?在这种情况下,我们是否还需要在主线程中有一个状态机?
  2. 任何处理输入事件的线程都可以绕过主线程直接与处理事件的线程通信?例如,Web 界面线程可以直接与控制 RS485 从站的线程通信吗?
  3. 使用 pthread 条件信号并等待线程间通信是否可以,还是有更好的方法?
  4. 我们如何让一个线程等待来自外部的事件和来自其他线程的响应?例如,Web 界面线程通常等待 POSIX 消息队列上的事件,以便从 Web 服务器 CGI 箱进行进程间通信。CGI bin 的发送事件通过这个消息队列发送到 web 界面线程。在处理此事件时,Web 界面线程将等待其他线程的响应。在这种情况下,它无法处理来自 Web 界面的任何新事件,直到它完成对前一个事件的处理并返回到 POSIX 消息队列上的等待状态。

抱歉解释太大了……我希望我以最好的方式提出我的解释,以便其他人理解和帮助我。

如果需要,我可以提供更多输入。

4

2 回答 2

3

对于这样的要求,我总是尝试使用一台状态机,由一个“SM”线程运行,该线程可能是主线程。该线程在“EventQueue”输入生产者-消费者队列上等待超时。超时用于运行内部增量队列,该队列可以在需要时将超时事件提供给状态机。

所有其他线程通过将消息推送到 EventQueue 来将它们的事件传递给状态引擎,SM 线程以串行方式处理它们。

如果 SM 中的动作例程决定它必须做某事,它不能同步等待任何事情,因此它必须通过将请求消息推送到任何线程/辅助系统可以执行它的输入队列来请求该动作。

我的消息类(好吧,在你的 C 案例中是 *struct),通常包含一个“命令”枚举、“结果”枚举、一个数据缓冲区指针(以防它需要传输批量数据)、一个错误消息指针, (如果没有错误,则为 null),以及允许异步排队任何类型的请求并返回完整结果(无论是成功还是失败)所需的尽可能多的其他状态。

这种消息传递,一种 SM 设计是我发现的唯一一种能够以灵活、可扩展的方式执行此类任务而不会进入死锁、不受控制的通信和不可重复、不可调试的交互的噩梦世界的设计。

任何设计都应该问的第一个问题是“好的,如果出现一些奇怪的问题,如何调试系统?”。在我上面的设计中,我可以直接回答:'我们记录所有在 SM 线程中出列的事件——它们都是连续出现的,所以我们总是知道基于它们采取了哪些行动'。如果建议任何其他设计,请提出上述问题,如果没有立即给出好的答案,它将永远无法工作。

所以:

  1. 如果一个线程或线程子系统可以使用一个单独的状态机来执行它自己的内部功能,好的,很好。这些 SM 应该对系统的其余部分不可见。

  2. 不!

  3. 使用 pthread 条件信号 & wait 来实现生产者-消费者阻塞队列。

  4. 每个线程/子系统一个输入队列。所有输入都以消息的形式进入这个队列。每条消息中的命令/状态标识消息以及应该如何处理它。

顺便说一句,我会 100% 在 C++ 中做到这一点,除非霰弹枪在头上 :)

于 2013-08-21T13:12:30.197 回答
1

我已经实现了一个遗留的嵌入式库,它最初是为终端控制器的克隆( EC115/EC270)编写的。Siemens ES122C该库和操作系统或多或少包含您所描述的内容。原始硬件基于 80186 cpu。RMOS对于西门子来说,对于我们来说,操作系统FXMOS(不要谷歌它从未发布过)拥有基本控制器工作所需的所有东西。它具有抢先式多任务、任务到任务通信、信号量、定时器和 I/O 事件,但没有内存保护。我将这些东西移植到了 RaspberryPi(即 Linux)。

我使用 pthread 来模拟我们遗留的“任务”,因为我们没有内存保护,所以线程在语义上是最接近的。然后实现的其余部分围绕epollAPI。这意味着一切都会产生一个事件。事件是指当某事发生、定时器到期、另一个线程发送数据、TCP 套接字连接、IO 引脚更改状态等时。这需要将所有事件源转换为文件描述符。Linux 提供了几个系统调用来实现这一点:对于任务到任务的通信,我使用了经典的 Unix 管道。timerfd对于我使用API的计时器事件。对于 TCP 通信,我使用了普通的套接字。对于串行 I/OI,只需打开正确的设备/dev/???。在我的情况下,信号不是必需的,但如果需要,Linux 会提供“signalfd”。

然后我epoll_wait环绕以模拟原始语义。

我的工作就像一个魅力。

TL;博士

深入了解epoll它可以完成您可能需要的 API。

编辑:是的,Martin James 的建议非常好,尤其是 4。每个线程应该只在一个循环中通过 epoll_wait 等待事件。

于 2014-03-24T07:17:34.787 回答