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

How fast can I read in /dev/ttyACM0 file?

我有一个自定义的 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

所以我有几个问题:

谢谢。

编辑

更精确:此代码的目的是仅在 linux 下与设备建立 C/C++ 接口。我知道设备正在以特定帧速率发送大约 105 kbytes 的帧(但代码不应该依赖于这个帧速率并且在 15 fps 和 1 fps 时平滑......没有滞后)。

由于这是我第一次编写此类代码,而且我无法理解我找到的手册页中的所有内容,因此我保留了我找到的大部分代码(打开和阅读)示例中的默认设置:

开幕式:

// 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(可能)是一个串行端口,它的速度有限,这取决于它 运行 的波特率。所以关于你的问题:

How fast can I / should I access (read in) the tty file ?

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

Does the EAGAIN error mean I can't read in the file while the device write into it?

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

If yes, as a consequence does it mean that while I'm reading in the file, the device can't write into it? So does data get lost?

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

Finally (and more subjective question), am I doing something really dirty with this kind of code? Any comments or suggestions about accessing real time data through tty file?

这里我看不到任何脏东西。

嗯,首先,依靠数据应该多快 read/written 来避免饥饿或拒绝服务是一个坏主意,因为速度会因许多因素而变化(除非你能保证某些确定性响应时间)。最好理解为什么会出现该错误以及如何稳健地处理它。这取决于您使用的 OS。由于问题标签中有 "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 被 returned 指示文件结束。这可能会或可能不会被视为错误。例如,在 TCP/IP 的情况下,如果 read() returns 0,则这意味着对等方关闭了连接。

编辑

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

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

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

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

How fast can I read in /dev/ttyACM0 file?

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

I have a custom USB cdc-acm device which sends images to the computer thanks to serial communication. The kernel driver works fine as the device appears as /dev/ttyACM0 ...

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

How fast can I / should I access (read in) the tty file ?

您的程序 could/does 使非阻塞 read() 系统调用比实际接收的数据更频繁,但这会导致 EAGAIN 或 EWOULDBLOCK return代码和效率低下。
对于典型的波特率(例如远低于 megabps)和典型的处理器 运行 一个 Linux 内核,您的程序可能必须等待数据,并且应该使用阻塞 read() 提高效率的系统调用。
由于您有一个专门的输入线程,并且在等待数据时没有 calculations/processing(在该线程中)执行,因此阻塞 I/O 无疑是明智的方法。

Does the EAGAIN error mean I can't read in the file while the device write into it?

您似乎知道中间 tty 缓冲区,但很难确定。
EAGAIN 应解释为:"the request for data cannot be fulfilled at this time, try again later"。 EAGAIN 的典型原因仅仅是 "reading".
的缓冲区中没有数据 不必担心设备驱动程序是否持有缓冲区锁。

If yes, as a consequence does it mean that while I'm reading in the file, the device can't write into it? So does data get lost?

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

Finally (and more subjective question), am I doing something really dirty with this kind of code?

好吧,您的代码(不必要地)CPU intensive/inefficient 并且不完整。
每当 return 代码为 EAGAIN 时,循环中的重复非阻塞 read() 系统调用是对 CPU 周期的浪费。

您没有显示用于配置终端的 termios 代码。您可以使用任何已经存在的设置,但那是糟糕的编码。每当更改波特率 and/or 线路设置时,您的程序将无法读取任何内容,或者您​​将拥有 .
使用串行终端的编写良好的程序将始终将该终端初始化为它 expects/requires.
的配置 有关示例代码,请参阅 this answer and this answer

Any comments or suggestions about accessing real time data through tty file?

Linux 不是实时 OS,尽管有 "realtime" 补丁可以使延迟更具确定性。但是您没有表达任何超过使用典型硬件的先前嵌入式 Linux 项目的要求。
快速和健壮的串行终端传输技术不依赖于非阻塞 I/O,这可能会导致过多的处理开销(尤其是在处理不当时),从而损害其他 processes/threads.
阻塞非规范终端 I/O 有很多选项可以指定何时应该满足 read() 以及 return 给调用者。参见 this answer for details

要尽量减少 read() 系统调用 scan/parse 一次接收一个字节的数据,请在程序中缓冲数据,例如