File Descriptor Monitor API
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.fd, // fd to monitorSerialPortHandler, // Handler functionPOLLIN); // Monitor readability
When an fd no longer needs to be monitored, the File Descriptor Monitor object is deleted by calling le_fdMonitor_Delete().
le_fdMonitor_Delete(fdMonitor);
- 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
andPOLLHUP
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.fd, // fd to monitorSerialPortHandler, // Handler functionPOLLIN); // 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.le_fdMonitor_Disable(le_fdMonitor_GetMonitor(), POLLOUT);}...}...}
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.// 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.
Copyright (C) Sierra Wireless Inc.