【问题标题】:Multi threaded embedded linux application state machine design多线程嵌入式linux应用状态机设计
【发布时间】:2013-08-23 19:26:45
【问题描述】:

问题定义:

我们正在为运行 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 消息队列上的事件 来自网络服务器 CGI 箱。 CGI bin 向网络发送事件 接口线程通过这个消息队列。处理这个时 事件,Web界面线程将等待来自其他的响应 线程。在这种情况下,它无法处理来自 Web 界面,直到它完成对上一个的处理 事件并返回等待 POSIX 消息队列。

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

如果需要,我可以提供更多意见。

【问题讨论】:

  • 似乎原始问题中描述的方法重新发明了活动对象(参与者)设计模式。我建议看一下开源 QP 活动对象框架 (state-machine.com/qp)。在state-machine.com/linux 有一个带有 p 线程的 POSIX 框架的端口。
  • 当然,Miro 先生,我会考虑这个选项...非常感谢

标签: c linux embedded embedded-linux


【解决方案1】:

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

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

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

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

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

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

所以:

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

  2. 不!

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

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

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

【讨论】:

  • Martin James 先生,我会考虑您提到的所有要点。我将不得不做一些工作,然后用新设计回复你。谢谢你..
  • @Martin James,我对您的设计方法有一些澄清。如果主状态机或主线程将异步分派输入事件到子系统线程,主线程如何获得结果为主线程将忙于等待输入事件。我也无法理解您提到的内部增量队列?你能详细说明一下吗?
【解决方案2】:

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

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

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

我的工作就像一个魅力。

TL;DR

深入了解epoll API,它可以满足您的需求。

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

【讨论】:

    猜你喜欢
    • 2021-07-14
    • 1970-01-01
    • 2020-12-07
    • 1970-01-01
    • 2014-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多