File Descriptor Monitor API

API Reference


In a POSIX environment, like Linux, file descriptors (fds) are used for most process I/O. Many components need to be notified when one or more fds are ready to read from or write to, or if there's an error or hang-up.

Although it's common to block a thread on a call to read(), write(), accept(), select(), poll() (or some variantion of these), if that's done in a thread shared with other components, the other components won't run when needed. To avoid this, Legato has methods to monitor fds reporting related events so they won't interfere with other software sharing the same thread.

Start/Stop Monitoring

le_fdMonitor_Create() creates a File Descriptor Monitor and starts monitoring an fd. A handler function and set of events is also provided to le_fdMonitor_Create().

// Monitor for data available to read.
le_fdMonitor_Ref_t fdMonitor = le_fdMonitor_Create("Serial Port", // Name for diagnostics
fd, // fd to monitor
SerialPortHandler, // Handler function
POLLIN); // Monitor readability

When an fd no longer needs to be monitored, the File Descriptor Monitor object is deleted by calling le_fdMonitor_Delete().

Warning
Always delete the Monitor object for an fd before closing the fd . After an fd is closed, it could get reused for something completely different. If monitoring of the new fd incarnation is started before the old Monitor object is deleted, deleting the old Monitor will cause monitoring of the new incarnation to fail.

Event Types

Events that can be handled:

  • POLLIN = Data available to read.
  • POLLPRI = Urgent data available to read (e.g., out-of-band data on a socket).
  • POLLOUT = Writing to the fd should accept some data now.
  • POLLRDHUP = Other end of stream socket closed or shutdown.
  • POLLERR = Error occurred.
  • POLLHUP = Hang up.

These are bitmask values and can be combined using the bit-wise OR operator ('|') and tested for using the bit-wise and ('&') operator.

Note
POLLRDHUP, POLLERR and POLLHUP can't be disabled. Monitoring these events is always enabled as soon as the File Descriptor Monitor is created regardless of the set of events given to le_fdMonitor_Create().

FD Types

The fd type affects how events are monitored:

Files

  • POLLIN and POLLOUT are always SET
  • NONE of the other EVENTS are ever set

Pipes

Pipe fd events indicate two conditions for reading from a pipe and two conditions for writing to a pipe.

Event Condition
READING from a pipe POLLHUP NO DATA in the pipe and the WRITE END is closed
POLLIN DATA in the pipe and the WRITE END is open
POLLIN + POLLHUP DATA in the pipe BUT the WRITE END is closed
WRITING to the pipe POLLERR NO SPACE in the pipe and the READ END is closed
POLLOUT SPACE in the pipe and the READ END is open
POLLOUT + POLLERR SPACE in the pipe BUT the READ END is closed

Sockets

Socket activity (establishing/closing) is monitored for connection-orientated sockets including SOCK_STREAM and SOCK_SEQPACKET. Input and output data availability for all socket types is monitored.

Event Condition
POLLIN Input is available from the socket
POLLOUT Possible to send data on the socket
POLLIN Incoming connection being established on the listen port
POLLPRI Out of band data received only on TCP
POLLIN + POLLOUT + POLLRDHUP Peer closed the connection in a connection-orientated socket

Terminals and Pseudo-Terminals

Terminals and pseudo-terminals operate in pairs. When one terminal pair closes, an event is generated to indicate the closure. POLLIN, POLLOUT and POLLPRI are the event indicators related to terminal status.

Event Condition
POLLIN Ready to receive data
POLLOUT Ready to send data
POLLPRI Master/pseudo terminal detects slave state has changed (in packet mode only).
POLLHUP Either half of the terminal pair has closed.

Handler Functions

Parameters to the fd event handler functions are the fd and the events active for the fd. The events are passed as a bit mask; the bit-wise AND operator ('&') must be used to check for specific events.

{
// Open the serial port.
int fd = open("/dev/ttyS0", O_RDWR|O_NONBLOCK);
LE_FATAL_IF(fd == -1, "open failed with errno %d (%m)", errno);
 
// Create a File Descriptor Monitor object for the serial port's file descriptor.
// Monitor for data available to read.
le_fdMonitor_Ref_t fdMonitor = le_fdMonitor_Create("Serial Port", // Name for diagnostics
fd, // fd to monitor
SerialPortHandler, // Handler function
POLLIN); // Monitor readability
}
 
static void SerialPortHandler(int fd, short events)
{
if (events & POLLIN) // Data available to read?
{
char buff[MY_BUFF_SIZE];
 
ssize_t bytesRead = read(fd, buff, sizeof(buff));
 
...
}
 
if ((events & POLLERR) || (events & POLLHUP) || (events & POLLRDHUP)) // Error or hang-up?
{
...
}
}

Enable/Disable Event Monitoring

The set of fd events being monitored can be adjusted using le_fdMonitor_Enable() and le_fdMonitor_Disable(). However, POLLRDHUP, POLLERR and POLLHUP can't be disabled.

CPU cycles (and power) can be saved by disabling monitoring when not needed. For example, POLLOUT monitoring should be disabled while nothing needs to be written to the fd, so that the event handler doesn't keep getting called with a POLLOUT event because the fd is writeable.

static void StartWriting()
{
// Enable monitoring for POLLOUT. When connection is ready, handler will be called.
le_fdMonitor_Enable(FdMonitorRef, POLLOUT);
}
 
static void ConnectionEventHandler(int fd, int event)
{
if (event & POLLOUT)
{
// Connection is ready for us to send some data.
le_result_t result = SendWaitingData();
if (result == LE_NOT_FOUND)
{
// Buffer empty, stop monitoring POLLOUT so handler doesn't keep getting called.
}
...
}
...
}

If an event occurs on an fd while monitoring of that event is disabled, the event will be ignored. If that event is later enabled, and that event's trigger condition is still true (e.g., the fd still has data available to be read), then the event will be reported to the handler at that time. If the event trigger condition is gone (e.g., the fd no longer has data available to read), then the event will not be reported until its trigger condition becomes true again.

If events occur on different fds at the same time, the order in which the handlers are called is implementation-dependent.

Handler Function Context

Calling le_fdMonitor_GetMonitor() inside the handler function fetches a reference to the File Descriptor Monitor object for the event being handled. This is handy to enable and disable event monitoring from inside the handler.

If additional data needs to be passed to the handler function, the context pointer can be set to use le_fdMonitor_SetContextPtr() and retrieved inside the handler function with le_fdMonitor_GetContextPtr(). le_event_GetContextPtr() can also be used, but le_fdMonitor_GetContextPtr() is preferred as it double checks it's being called inside a File Descriptor Monitor's handler function.

static void SerialPortHandler(int fd, short events)
{
MyContext_t* contextPtr = le_fdMonitor_GetContextPtr();
 
// Process the fd event(s).
...
}
 
static void StartDataTransmission(const char* port, uint8_t* txBuffPtr, size_t txBytes)
{
// Open the serial port.
int fd = open(port, O_RDWR|O_NONBLOCK);
LE_FATAL_IF(fd == -1, "open failed with errno %d (%m)", errno);
 
// Create a File Descriptor Monitor object for the serial port's file descriptor.
// Monitor for write buffer space availability.
le_fdMonitor_Ref_t fdMonitor = le_fdMonitor_Create("Port", fd, SerialPortHandler, POLLOUT);
 
// Allocate a data block and populate with stuff we need in SerialPortHandler().
MyContext_t* contextPtr = le_mem_ForceAlloc(ContextMemPool);
contextPtr->txBuffPtr = txBuffPtr;
contextPtr->bytesRemaining = txBytes;
 
// Make this available to SerialPortHandler() via le_fdMonitor_GetContextPtr().
le_fdMonitor_SetContextPtr(fdMonitor, contextPtr);
}

Power Management

If your process has the privilege of being able to block the system from going to sleep, whenever the fd that is being monitored has a pending event, the system will be kept awake. To allow the system to go to sleep while this fd has a pending event, you can call le_fdMonitor_SetDeferrable() with isDeferrable flag set to 'true'.

Threading

fd monitoring is performed by the Event Loop of the thread that created the Monitor object for that fd. If that the is blocked, events won't be detected for that fd until the thread is unblocked and returns to its Event Loop. Similarly, if the thread that creates a File Descriptor Monitor object doesn't run an Event Loop at all, no events will be detected for that fd.

It's not recommended to monitor the same fd in two threads at the same time, because the threads will race to handle any events on that fd.

Troubleshooting

The "fdMonitor" logging keyword can be enabled to view fd monitoring activity.