我可以多快读取 /dev/ttyACM0 文件



我有一个定制的USB cdc-acm设备,由于串行通信,它可以将图像发送到计算机。内核驱动程序工作正常,因为设备显示为/dev/ttyACM0,我能够使用打开、写入和读取函数发送命令和获取数据。

当设备不断发送数据时,我在 while 循环中的单独线程中获取此数据:

while (m_isListening)
{
int rd = read(m_file_descriptor, read_buffer, 512);
// Display read data
if (rd > 0)
{
std::cout << rd << " bytes read: ";
for (int i = 0; i < rd; i++)
{
printf(" %x", read_buffer[i]);
}
std::cout << " # END" << std::endl;
}
// Nothing was to be read
else if (rd == 0)
{
std::cout << "No bytes read =(" << std::endl;
}
// Couldn't acces the file for read
else
{
printf("%d %s n", errno, strerror(errno));
}
}
std::cout << std::endl;

我设法读取和显示数据,但我也有很多这样的行(11 = EAGAIN 错误):

11 Ressource Temporarily Unavailable

所以我有几个问题:

  • 我/我应该以多快的速度访问(读入)tty文件?
  • EAGAIN 错误是否意味着我在设备写入文件时无法读取文件?
  • 如果是,这是否意味着当我读取文件时,设备无法写入文件?那么数据会丢失吗?
  • 最后(也是更主观的问题),我用这种代码做了什么肮脏的事情吗?关于通过tty文件访问实时数据的任何意见或建议?

谢谢。

编辑

更精确:此代码的目的是仅在 linux 下与设备具有 C/C++ 接口。我知道该设备以特定的帧速率发送大约 105 KB 的帧(但代码不应依赖于此帧速率,并且在 15 fps 和 1 fps 下都很流畅...... 没有滞后)。

由于这是我第一次编写这种代码,并且我从找到的手册页中不理解evrything,因此我保留了大多数代码(打开和阅读)的示例中的默认设置:

开放:

// Open the port
m_file_descriptor = open("/dev/ttyACM0", O_RDWR | O_NOCTTY | O_NDELAY);
if (m_file_descriptor == -1)
{
std::cerr << "Unable to open port " << std::endl;
}

我在阅读之前所做的唯一设置修改是:

fcntl(m_file_descriptor, F_SETFL, FNDELAY);

。为了设置读数非阻塞(如果我理解得很好,FNDELAY 相当于O_NONBLOCK)。

/dev/ttyACM0

(可能)是一个串行端口,它的速度有限,这取决于它运行的波特率。所以关于你的问题:

我/我应该以多快的速度访问(读入)tty文件?

完全取决于端口的波特率。

EAGAIN 错误是否意味着我在设备写入文件时无法读取文件?

Eagain 表示稍后再试。所以现在串行端口的缓冲区中没有数据,但如果您稍后再试一次,它可能有一些数据。

如果是,这是否意味着当我读取文件时,设备无法写入文件?那么数据会丢失吗?

不。只要您的读取速度或速度与发送数据的设备写入的速度一样快或更快(您就是这样,因为您获得了 EAGAIN),您就不会丢失数据。

最后(也是更主观的问题),我用这种代码做了什么肮脏的事情吗?关于通过tty文件访问实时数据的任何意见或建议?

这里没有什么我能看到的脏东西。

我可以多快读取/dev/ttyACM0 文件?

这取决于许多因素,包括处理器负载、进程/线程的优先级、代码的效率,以及(当然)数据的接收速率。

我有一个定制的USB cdc-acm设备,由于串行通信,它将图像发送到计算机。内核驱动程序工作正常,因为设备显示为/dev/ttyACM0 ...

因此,您有一个通过模拟串行端口进行通信的USB小工具。
您的 Linux 程序作为串行终端访问此设备。

我/我应该以多快的速度访问(读入)tty文件?

您的程序可以/确实比实际接收的数据更频繁地进行非阻塞read()系统调用,但这会导致 EAGAIN 或 EWILLBLOCK 返回代码并且效率低下。
对于典型的波特率(例如远低于 megabps)和运行 Linux 内核的典型处理器,您的程序可能必须等待数据,并且应该使用阻塞read()系统调用来提高效率。
由于您有一个用于输入的专用线程,并且在等待数据时没有要执行的计算/处理(在此线程中),因此阻塞 I/O 肯定是谨慎的方法。

EAGAIN 错误是否意味着我在设备写入文件时无法读取文件?

您似乎知道中间tty缓冲区,但很难确定。
EAGAIN 应解释为:"此时无法满足数据请求,请稍后重试"。 EAGAIN 的典型原因很简单,缓冲区中没有用于"读取"的数据。
设备驱动程序是否在缓冲区上持有锁不需要担心。

如果是,这是否意味着当我读取文件时,设备无法写入文件?那么数据会丢失吗?

tty 子系统会将其缓冲区视为关键资源。 无需担心此缓冲区上的争用。 设备驱动程序有自己的缓冲区,不同于 tty 缓冲区,用于从设备接收数据。 串行驱动程序参见图 3。
您可以控制的丢失数据的一种机制是缓冲区溢出,即如果您的程序没有像接收数据一样快地读取(),那么tty缓冲区最终将被填充,然后被溢出,从而导致数据丢失。
请注意,tty 缓冲区通常为 4KB。

最后(也是更主观的问题),我用这种代码做了什么肮脏的事情吗?

好吧,您的代码(不必要地)占用大量 CPU 资源/效率低下且不完整。
每当返回代码为 EAGAIN 时,循环中重复的非阻塞read()系统调用都会浪费 CPU 周期。

您没有显示用于配置终端的术语代码。您可以使用已经存在的任何设置,但这是糟糕的编码。 每当波特率和/或行建立更改时,您的程序将无法读取任何内容,否则您会遇到此问题。
使用串行终端的编写良好的程序将始终将该终端初始化为它期望/要求的配置。
有关示例代码,请参阅此答案和此答案

关于通过tty文件访问实时数据的任何意见或建议?

Linux不是实时操作系统,尽管有"实时"补丁使延迟更具确定性。 但是,您没有表达任何超过以前使用典型硬件的嵌入式 Linux 项目的要求。
快速而可靠的串行终端传输技术不依赖于非阻塞 I/O,这可能会导致过多的处理开销(尤其是在做得不好的情况下),从而损害其他进程/线程。
阻止非规范终端 I/O 有许多选项可以指定何时应满足read()并返回给调用方。 有关详细信息,请参阅此答案。

为了最大限度地减少read()系统调用,但一次扫描/解析接收到的数据一个字节,请在程序中缓冲数据,例如示例代码

首先,依靠读取/写入数据的速度来避免饥饿或拒绝服务是一个坏主意,因为速度可能因许多因素而变化(除非您可以保证某些确定性的响应时间)。最好了解为什么会出现该错误以及如何可靠地处理它。这取决于您使用的操作系统。由于问题标签中有"linux",我假设您使用的是Linux。从手册页(见man(2) read):

EAGAIN The file descriptor fd refers to a file other than a socket and has been marked non-
blocking (O_NONBLOCK), and the read would block.  See open(2) for further details on
the O_NONBLOCK flag.
EAGAIN or EWOULDBLOCK
The file descriptor fd refers to a socket and has been  marked  nonblocking  (O_NON-
BLOCK),  and  the read would block.  POSIX.1-2001 allows either error to be returned
for this case, and does not require these constants to have the  same  value,  so  a
portable application should check for both possibilities.

现在,您没有向我们展示您如何打开设备或您设置了哪些可能的 fcntrl 选项。我假设设备是非阻塞的(通过在文件描述符上设置O_NONBLOCK标志来实现),或者设备驱动程序默认实现非阻塞 I/O。

在这种情况下,EAGAINEWOULDBLOCK表示当前不存在任何数据,并且read()通常会在此时阻塞,直到数据可用。

一个简单的解决方案,不考虑任何其他要求(因为你没有说明任何要求)是检查EAGAIN(EWOULDBLOCK可移植性),如果发生任何一种情况,sleep一些毫秒,然后再恢复。以下是您的代码略有修改:

while (m_isListening)
{
errno = 0;
int rd = read(m_file_descriptor, read_buffer, 512);
// Display read data
if (rd > 0)
{
std::cout << rd << " bytes read: ";
for (int i = 0; i < rd; i++)
{
printf(" %x", read_buffer[i]);
}
std::cout << " # END" << std::endl;
}
// Nothing was to be read
else if (rd == 0)
{
std::cout << "No bytes read =(" << std::endl;
}
// Couldn't acces the file for read
else
{
if (errno == EAGAIN || errno == EWOULDBLOCK)
{
usleep(2000); // Sleep for 2 milliseconds.
}
else
{
printf("%d %s n", errno, strerror(errno));
}
}
}
std::cout << std::endl;

顺便说一下,根据设备驱动程序处理read()的方式,子句if (rd == 0)可能是多余的。我说这取决于,因为在某些情况下返回 0 以指示文件结束。这可能会也可能不会被视为错误。例如,在 TCP/IP 的情况下,如果read()返回 0,则表示对等方关闭了连接。

编辑

反映对原始问题的编辑:是的,O_NDELAYO_NONBLOCK的同义词(实际上,您不需要额外的调用来fcntrl设置O_NDELAY,因为您已经在打开设备并设置了该选项)。因此,这意味着您的设备是非阻塞的,因此,如果数据不可用,而不是阻塞并等待数据到达,驱动程序会抛出EAGAIN(EWOULDBLOCK也是合法的)。

如果您没有严格的时间限制并且可以容忍块,则可以简单地删除O_NDELAY选项。否则,请按照我上面的建议进行操作。

关于数据丢失:如果未设置O_NDELAY(或者等效地O_NONBLOCK),read()将在数据可用时立即返回(但不会等待填充缓冲区直到请求的字节数 - 调用read()中的第三个参数 - 而是返回可用的字节数,直到指定的请求数)。但是,如果数据不可用,它将阻止(再次,假设这是驱动程序的行为)。如果您希望它返回 0 - 好吧,这取决于驱动程序。这正是提供O_NONBLOCK选项的原因。

这里的一个缺点是,除非驱动程序提供某种控制延迟的机制,否则无法知道设备可能会阻塞多长时间(取决于数据到达的速度)。这就是为什么人们通常设置O_NONBLOCK然后手动控制read()延迟(例如,在实时要求下)的原因。

最新更新