2

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 and I am able to send commands and get data using open, write and read functions.

As the device continuously sends data, I get this data in a separate thread in a while loop:

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;

I manage to get data read and displayed, but I also have a lot of line like that (11 = EAGAIN error):

11 Ressource Temporarily Unavailable

So I have a couple of questions:

  • 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?
  • 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?
  • 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?

Thanks.

EDIT

Some more precisions: the purpose of this code is to have a C/C++ interface with the device under linux only. I know that the device is sending frames around 105 kbytes at a particular frame rate (but the code should not depend on this framerate and be smooth at 15 fps as well as at 1 fps... no lag).

As this is my first time writing this kind of code and I don't understood evrything from the man pages I found, I kept default settings from the sample I found for most of the code (opening and reading):

Opening:

// 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;
}

The only settings modification I did before reading was:

fcntl(m_file_descriptor, F_SETFL, FNDELAY);

... in order to set the reading non blocking (FNDELAY is equivalent as O_NONBLOCK if I understood well).

Pierre Baret
  • 1,773
  • 2
  • 17
  • 35
  • One question at a time, please. – Jesper Juhl Feb 12 '18 at 19:40
  • @JesperJuhl those questions (except the last one which is more subjective) are all linked and related to each other, should I really open 3 topics for that? – Pierre Baret Feb 12 '18 at 19:44
  • *"am I doing something really dirty with this kind of code?"* -- Well it's (unnecessarily) CPU intensive/inefficient and incomplete. You've shown no termios code to configure the terminal. You can use whatever settings that already exist, but that's poor coding. And since you use nonblocking I/O, a lot of termios settings are ignored. *"...accessing real time data through tty file?"* -- The received data is fully buffered. The "fastest" method of "reading" this buffered data is efficient use (i.e. the fewest number) of syscalls. Typically that means using blocking I/O. – sawdust Feb 13 '18 at 23:30
  • @sawdust I've shown no termios code cause I have not used any. Everything is default params and simplest code I found and understood on the web. If you have something more advanced to suggest, feel free to write an answer with explanations, I would really like to understand the most of what you just threw here. – Pierre Baret Feb 13 '18 at 23:32

3 Answers3

1

/dev/ttyACM0 is (likely) a serial port, and it has a limited speed which depends on the baud rate it is running at. So regarding your questions:

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

Entirely depends on the baud rate of the port.

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

Eagain means try again later. So right now the serial port has no data in its buffer, but if you try again later it may have some data.

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?

No. As long as you are reading as fast or faster than the device sending data is writing (which you are, since you get EAGAIN), you won't lose data.

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?

Nothing dirty here that I can see.

C_Elegans
  • 1,113
  • 8
  • 15
1

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

That depends on numerous factors, including the the processor load, priority of your process/thread, the efficiency of your code, and (of course) the receive rate of the data.

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 ...

So you have a USB gadget that communicates through an emulated serial port.
Your Linux program accesses this device as a serial terminal.

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

Your program could/does make nonblocking read() syscalls more frequently than the data actually is received, but that would result in EAGAIN or EWOULDBLOCK return codes and be inefficient.
For typical baudrates (e.g. much less than megabps) and the typical processor running a Linux kernel, your program will probably have to wait for data, and should use blocking read() syscalls for improved efficiency.
Since you have a dedicated thread for input and no calculations/processing (in this thread) to perform while waiting for the data, then blocking I/O is certainly the prudent method.

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

You seem to be cognizant of the intermediate tty buffer, but it's hard to be sure.
The EAGAIN should be interpreted as: "the request for data cannot be fulfilled at this time, try again later". The typical reason for EAGAIN is simply that there is no data in the buffer for "reading".
Whether the device driver is holding a lock on the buffer need not be a concern.

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?

The tty subsystem will treat its buffers as a critical resource. You need not be concerned about contention over this buffer. The device driver has its own buffer(s) distinct from the tty buffer, for receiving data from the device. See Figure 3 of Serial Drivers.
The one mechanism for losing data that you have control over would be buffer overrun, i.e. if your program does not read() as fast as the data is received, then the tty buffer will eventually be filled and then be overrun, which results in lost data.
Note that the tty buffer is typically 4KB.

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

Well your code is (unnecessarily) CPU intensive/inefficient and incomplete.
Repetitive non-blocking read() syscalls in a loop are a waste of CPU cycles whenever the return code is EAGAIN.

You've shown no termios code to configure the terminal. You can use whatever settings that already exist, but that's poor coding. Your program will fail to read anything whenever the baudrate and/or line setttings are changed, or you'll have this problem.
A well-written program that uses a serial terminal will always initialize that terminal to the configuration that it expects/requires.
For sample code see this answer and this answer

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

Linux is not a realtime OS, although there are "realtime" patches to make latencies more deterministic. But you have not expressed any requirements that exceed prior embedded Linux projects using typical HW.
The techniques for fast and robust serial terminal transfers do not rely on nonblocking I/O, which is likely to incur excessive processing overhead (especially when done poorly) to the detriment of other processes/threads.
Blocking non-canonical terminal I/O has many options to specify when the read() should be satisfied and return to the caller. See this answer for details.

To minimize read() syscalls yet scan/parse received data a byte at a time, buffer the data in your program, e.g. sample code

sawdust
  • 16,103
  • 3
  • 40
  • 50
  • Thanks for your very detailed post. It is definetly one big and clear source of knowledge for a novice in Linux serial port like I am. – Pierre Baret Feb 14 '18 at 02:18
0

Well, first of all, relying on how fast data should be read/written to avoid starvation or denial of service is a bad idea, because the speed can vary due to many factors (unless you can guarantee certain deterministic response times). It is better to understand why you're getting that error and how to handle it robustly. This would depend on the OS you're using. Since you have "linux" among the question tags, I will assume you're using Linux. From the man pages (see 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.

Now, you aren't showing us how you opened the device or what possible fcntrl options you set. I am assuming that the device is nonblocking (achieved by setting O_NONBLOCK flag on the file descriptor), or that the device driver implements nonblocking I/O by default.

In that case, EAGAIN and EWOULDBLOCK indicate that no data is currently present, and read() would normally block at this point until data becomes available.

An easy solution, not taking into account any other requirements (because you haven't stated any) is to check for EAGAIN (and EWOULDBLOCK for portability), and in case either occurs, sleep for some number of milliseconds before you resume. Here is your code slightly modified:

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;

By the way, depending on how the device driver handles read(), the clause if (rd == 0) may be redundant. I say this depends, because in some cases 0 is returned to indicate end of file. This may or may not be treated as error. For example, in case of TCP/IP, if read() returns 0, then this means that peer closed the connection.

EDIT

Reflecting the edits to the original question: yes, O_NDELAY is synonymous with O_NONBLOCK (in fact, you do not need that extra call to fcntrl to set O_NDELAY because you're already opening the device with that option set). So that means your device is non-blocking, so if data is not available, rather than blocking and waiting for data to arrive, the driver throws EAGAIN (EWOULDBLOCK would also be legal).

If you do not have strict time constraints and can tolerate blocks, you can simply remove the O_NDELAY option. Otherwise, do what I suggested above.

Regarding loss of data: if O_NDELAY is not set (or, equivalently, O_NONBLOCK), read() will return as soon as data becomes available (but will not wait to fill the buffer up to the number of requested bytes -- third parameter in the call to read() -- and rather return the number of bytes available, up to the specified requested number). However, if data is not available, it will block (again, assuming that's the driver's behavior). If you want it to return 0 instead -- well, this is up to the driver. This is exactly the reason O_NONBLOCK option is provided.

One disadvantage here is that unless the driver provides some mechanism of controlling delays, there is no telling for how long the device might block (subject to how quickly data arrives). This is the reason that one typically sets O_NONBLOCK and then manually controls read() delays (e.g. under real-time requirements).

  • Thank you for your answer. I put some more precision in the question. In my case, I want data to be read as soon as it arrived cause it is like a streaming device, I don't want any pause so making the device sleep might not be a good idea. My principal concern is about queue of data and loss of data. – Pierre Baret Feb 12 '18 at 20:19
  • Ok thanks. Just to be sure: blocking means that the function will not return until the device sends me some data right? – Pierre Baret Feb 12 '18 at 20:26
  • @PierreBaret Yep, that's exactly what it means. But you don't a priori know for how long it could block. –  Feb 12 '18 at 20:29
  • So if I know that the device sends me data at a particular framerate, there is no problem at all in the end as this runs in a separate thread, right ? What solution would you then suggest? Blocking, non blocking but getting EAGAIN a lot (like it is now), or non blocking + sleep ? – Pierre Baret Feb 12 '18 at 20:32
  • @PierreBaret Depends on additional requirements. All three would work in principle. Normally if you require that the thread does not block (e.g. real-time constraints), you'll want nonblocking with sleep. But on every iteration, you'll check an additional flag, say `bool shutdown` which, if set, should break you out of the loop. That gives you control over the loop. This flag should be visible outside the scope of this thread, so that it can be set by others. –  Feb 12 '18 at 23:47
  • Thank you very much, you helped me a lot today!! – Pierre Baret Feb 12 '18 at 23:53
  • Both versions of code use CPU-intensive polling on the terminal buffers. If all you're going to do is read and sleep, then you should not be using nonblocking I/O. A blocking read can do that a lot more efficiently. And if you want "fast" response, nonblocking I/O is no guarantee of that. – sawdust Feb 13 '18 at 07:54
  • @sawdust A blocking read, eh? How about this scenario: you have hard real-time requirements. You cannot guarantee how long a read() would block, but you must ensure response time of 10ms. What do you do? Another option is to use select/poll with a timeout. Perhaps slightly better. However, it is possible that the device driver might not support a blocking read at all (e.g. real-time systems). Then what? –  Feb 13 '18 at 17:31
  • Linux is not for *"hard real-time requirements"*. You seem confused about blocking. When *waiting* for data why are you concerned *"how long a read() would block"*? With judicious termios configuration, the return from a blocking read() (after the data are buffered) can be as fast or even faster than sleeping() and then reading(). Linux tty device drivers are not concerned with blocking; that's handled by the tty subsystem. – sawdust Feb 13 '18 at 23:27
  • @sawdust I believe my answer is general enough to cover all scenarios given lack of additional requirements. Moreover, if you think that Linux isn't used in real-time environments, you're wrong. Finally, "lame" is certainly not justified as a qualitative description of my answer. –  Feb 14 '18 at 16:26
  • @sawdust To address your questions: I am concerned with how long read() would block because in principle it can block forever if there is no data (and end of file hasn't been reached). This all depends on the device and the device driver. This isn't a discussion about buffered vs. nonbuffered read. Blocking is a different beast (again, given the original post, which severely lacked on the requirements and obviously used nonblocking read by default). –  Feb 14 '18 at 16:39
  • @sawdust From my answer: "If you do not have strict time constraints and can tolerate blocks, you can simply remove the O_NDELAY option. Otherwise, do what I suggested above." Cheers! –  Feb 14 '18 at 16:48
  • *"Moreover, if you think that Linux isn't used in real-time environments"* -- That's not exactly what I wrote. As a professional, I'm well aware of the RTLinux patches. See https://stackoverflow.com/questions/25871579/what-is-the-difference-between-rtos-and-embedded-linux A blocking read() does not necessarily block forever. The POSIX termios allows many configurations. Terminals do not have "end-of-file". – sawdust Feb 14 '18 at 20:22