NetBurner 3.5.6
PDF Version
File Descriptors

File Descriptors

Overview

The NetBurner environment provides a unified I/O abstraction layer through file descriptors (FDs), seamlessly integrating the RTOS, TCP/IP stack, and hardware peripherals into a standard POSIX-like interface. A file descriptor serves as a handle to various I/O resources including network sockets, serial ports, system peripherals, and custom I/O devices.

This unified approach enables consistent I/O operations across different resource types using standard functions like read(), write(), select(), and close().


File Descriptor Architecture

Application Layer
v
┌─────────────────────────────────────────────────────┐
│ File Descriptor Interface │
read() │ write() │ select() │ close() │
└──────┬──────────────────────────────────────────────┘
v
┌─────────────────────────────────────────────────────┐
│ File Descriptor Table (0-254) │
├─────────────────────────────────────────────────────┤
│ Standard I/O │ Serial │ Network │ Expansion │
│ (0-2) │ (3-4) │ (5-128) │ (129-250) │
└──────┬─────────┴─────┬────┴─────┬─────┴──────┬──────┘
│ │ │ │
v v v v
┌────────┐ ┌─────────┐ ┌─────┐ ┌──────────┐
│ stdio │ │ UART │ │ TCP │ │ Custom │
│ │ │ Ports │ │Stack│ │ Drivers │
└────────┘ └─────────┘ └─────┘ └──────────┘
int read(int fd, char *buf, int nbytes)
Read data from a file descriptor (fd).
int close(int fd)
Close the specified file descriptor and free the associated resources.
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, unsigned long timeout)
Wait for events to occur on one or more I/O resources associated with a set of file descriptors (fds)...
int write(int fd, const char *buf, int nbytes)
Write data to the stream associated with a file descriptor (fd). Can be used to write data to stdio,...

File Descriptor Allocation

NetBurner provides a maximum of 255 file descriptors (0-254), organized into specific ranges:

Allocation Map

┌────────────────────────────────────────────────────────────┐
│ File Descriptor Space │
├─────────┬──────────┬──────────────────┬────────────────────┤
│ 0-2 │ 3-4 │ 5-128 │ 129-250 │
├─────────┼──────────┼──────────────────┼────────────────────┤
│ Standard│ Serial │ TCP │ Expansion │
│ I/O │ Ports │ Sockets │ Pool │
├─────────┼──────────┼──────────────────┼────────────────────┤
│ 3 │ 2 │ 124 │ 122 │
│ FDs │ FDs │ FDs │ FDs │
└─────────┴──────────┴──────────────────┴────────────────────┘
Fixed Fixed 32 TCP Available for
System UART 0-1 Connections Custom Use

Detailed Breakdown

Range Count Purpose Description
0-2 3 Standard I/O stdin (0), stdout (1), stderr (2)
3-4 2 Serial Ports UART 0 (fd=3), UART 1 (fd=4)
5-128 124 TCP Sockets Network connections (32 sockets)
129-250 122 Expansion Custom drivers, additional UARTs, buffers

Expansion Pool Usage

The expansion range (129-250) can accommodate:

Expansion File Descriptors (129-250)
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ External │ │ Additional │ │ Custom │
│ UARTs │ │ TCP Sockets │ │ Buffers │
│ │ │ │ │ │
│ SPI-to- │ │ Beyond the │ │ Ring │
│ Serial │ │ 32 default │ │ Buffers │
└──────────────┘ └──────────────┘ └──────────────┘
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
I2C │ │ Custom │ │ Virtual │
│ Devices │ │ Protocols │ │ Devices │
│ │ │ │ │ │
I2C bus │ │ Proprietary │ │ Memory │
│ access │ │ interfaces │ │ streams │
└──────────────┘ └──────────────┘ └──────────────┘
I2C Peripheral Class.
Definition i2c.h:213

Creating Custom I/O Drivers

Driver Architecture

Custom file descriptors enable integration of any I/O device into the standard file descriptor framework.

Custom Driver Implementation Flow
┌─────────────────────────────────────────────────────┐
│ Your Custom Driver │
├─────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │
│ │ read() │ │ write() │ │ close() │ │
│ │ function │ │ function │ │ function │ │
│ └──────┬───────┘ └──────┬───────┘ └─────┬─────┘ │
│ │ │ │ │
│ └─────────────────┴────────────────┘ │
│ │ │
│ v │
│ ┌─────────────────────────┐ │
│ │ IoExpandStruct │ │
│ │ • Function pointers │ │
│ │ • Extra data pointer │ │
│ └───────────┬─────────────┘ │
└────────────────────────┼────────────────────────────┘
v
┌──────────────────────┐
GetExtraFD() │
│ Returns: fd handle │
└──────────────────────┘
v
┌──────────────────────┐
│ File Descriptor │
│ Now Available │
└──────────────────────┘
int GetExtraFD(void *extra_data, struct IoExpandStruct *pFuncs)
Returns a file descriptor for the structure passed as the IoExpandStruct. FreeExtraFd( ) will release...

Core Header File

#include <iointernal.h> // Located in: <NNDK_ROOT>/include/

State Management Functions

Functions to control the state of your file descriptor:

State Control Functions
┌─────────────────────┐ ┌─────────────────────┐
│ SetDataAvail() │ │ ClrDataAvail() │
│ Data ready to read │ │ No data available │
└─────────────────────┘ └─────────────────────┘
┌─────────────────────┐ ┌─────────────────────┐
│ SetWriteAvail() │ │ ClrWriteAvail() │
│ Ready for write │ │ Cannot write now │
└─────────────────────┘ └─────────────────────┘
┌─────────────────────┐ ┌─────────────────────┐
│ SetHaveError() │ │ ClrHaveError() │
│ Error condition │ │ No error │
└─────────────────────┘ └─────────────────────┘

Purpose:** These functions update the state of your file descriptor for use with I/O functions such as select(), read(), and write().


IoExpandStruct Definition

The core structure for custom file descriptor implementation:

struct IoExpandStruct
{
int (*read)(int fd, char *buf, int nbytes); // Read function
int (*write)(int fd, const char *buf, int nbytes); // Write function
int (*close)(int fd); // Close function
void *extra; // Custom data pointer
};

Structure Members

Member Type Purpose
read Function pointer Implements read operations for your device
write Function pointer Implements write operations for your device
close Function pointer Cleanup and release resources
extra void* Optional pointer to device-specific data

Data Flow

Application Your Driver
│ │
read(fd, buf, nbytes) │
├─────────────────────────────────>│
│ v
│ ┌────────────────────────┐
│ │ Your read() function │
│ │ • Access device │
│ │ • Fill buffer │
│ │ • Return bytes read
│ └─────────────┬──────────┘
│ │
│<─────────────────────────────────┤
│ Returns: bytes read
│ │
write(fd, buf, nbytes) │
├─────────────────────────────────>│
│ v
│ ┌────────────────────────┐
│ │ Your write() function │
│ │ • Write to device │
│ │ • Return bytes written │
│ └─────────────┬──────────┘
│ │
│<─────────────────────────────────┤
│ Returns: bytes written │

File Descriptor Management API

Core Functions

// Allocate a new file descriptor
int GetExtraFD(void *extra_data, struct IoExpandStruct *pFuncs);
// Retrieve custom data associated with a file descriptor
void* GetExtraData(int fd);
// Release a file descriptor back to the pool
void FreeExtraFd(int fd);
void FreeExtraFd(int fd)
Free a file descriptor and associated resources.
void * GetExtraData(int fd)
Returns the extra structure value from IoExpandStruct associated with the file descriptor.

Function Details

GetExtraFD()

Purpose:** Allocate a file descriptor for your custom I/O device.

Parameters:**

  • extra_data: Optional pointer to device-specific data
  • pFuncs: Pointer to IoExpandStruct containing your function implementations

    Returns:** File descriptor handle (int), or -1 on failure

    Usage Example:**

    IoExpandStruct myDeviceFuncs = {
    .read = myReadFunction,
    .write = myWriteFunction,
    .close = myCloseFunction,
    .extra = &myDeviceData
    };
    int fd = GetExtraFD(&myDeviceData, &myDeviceFuncs);
    if (fd < 0) {
    printf("Failed to allocate file descriptor\n");
    }

GetExtraData()

Purpose:** Retrieve the custom data pointer associated with a file descriptor.

Parameters:**

  • fd: File descriptor handle

    Returns:** Pointer to extra data (void*), or NULL if not set

    Usage:**

    MyDeviceStruct *device = (MyDeviceStruct*)GetExtraData(fd);
    if (device != NULL) {
    // Access device-specific data
    device->status = READY;
    }

FreeExtraFd()

Purpose:** Release a file descriptor back to the available pool.

Parameters:**

  • fd: File descriptor to release

    Important:** Always call this when your device is closed to prevent descriptor leaks.

void myCloseFunction(int fd) {
// Cleanup device resources
MyDeviceStruct *device = (MyDeviceStruct*)GetExtraData(fd);
if (device) {
// Device-specific cleanup
device->shutdown();
}
// Release the file descriptor
}

TCP Socket Notifications

Callback System

NetBurner provides a callback mechanism for TCP socket events:

TCP Event Flow
TCP Socket
│ Data arrives or
│ Error occurs
v
┌───────────────────────────────┐
│ System TCP Stack │
└───────────────┬───────────────┘
│ Trigger
v
┌───────────────────────────────┐
│ tcp_read_notify_handler │
│ Your callback function │
└───────────────┬───────────────┘
v
┌───────────────────────────────┐
│ Your Application Logic │
│ • Process new data │
│ • Handle error condition │
└───────────────────────────────┘

Callback Definition

// Callback function type
typedef void (tcp_read_notify_handler)(int tcp_fd);
// Registration function
void RegisterTCPReadNotify(int tcp_fd, tcp_read_notify_handler *newhandler);
void RegisterTCPReadNotify(int tcpFd, tcp_notify_handler *notifyHandler)
Register a callback for read event notifications.

Callback Parameters

When your callback is invoked:

Condition tcp_fd Value Meaning
Data available fd >= 0 Socket has new data to read
Error state fd < 0 TCP connection has entered error state

Implementation Example

// Your callback handler
void myTcpNotifyHandler(int tcp_fd) {
if (tcp_fd >= 0) {
// New data available
char buffer[256];
int bytesRead = read(tcp_fd, buffer, sizeof(buffer));
if (bytesRead > 0) {
// Process received data
processIncomingData(buffer, bytesRead);
}
} else {
// Error condition
printf("TCP socket error detected\n");
handleTcpError();
}
}
// Register the callback
void setupTcpSocket(int tcp_fd) {
RegisterTCPReadNotify(tcp_fd, myTcpNotifyHandler);
}

Using select() with File Descriptors

Multiplexing Multiple I/O Sources

One of the most powerful features of the file descriptor system is the ability to wait on multiple I/O sources simultaneously using select().

select() Multiplexing
┌─────────────────────────────────────────────────────┐
│ Application Thread │
│ │
select(readfds, ...) │
│ │ │
│ │ Blocks until activity │
│ v │
│ ┌──────────────────────────────────────────────┐ │
│ │ Waiting on Multiple FDs: │ │
│ │ │ │
│ │ fd3 (Serial) fd5 (TCP) fd129 (Custom) │ │
│ │ │ │ │ │ │
│ └──────┼────────────┼────────────┼─────────────┘ │
│ │ │ │ │
└─────────┼────────────┼────────────┼─────────────────┘
│ │ │
v v v
┌───────┐ ┌───────┐ ┌───────┐
│ UART │ │ TCP │ │Custom │
│Device │ │Socket │ │Driver │
└───────┘ └───────┘ └───────┘

select() Usage Pattern

#include <iosys.h>
void monitorMultipleSources() {
int serial_fd = 3; // UART 0
int tcp_fd = 5; // TCP socket
int custom_fd = 129; // Custom device
fd_set readfds;
while (1) {
// Initialize the set
FD_ZERO(&readfds);
FD_SET(serial_fd, &readfds);
FD_SET(tcp_fd, &readfds);
FD_SET(custom_fd, &readfds);
// Find the highest fd number
int max_fd = custom_fd;
// Wait for activity on any descriptor
int result = select(max_fd + 1, &readfds, NULL, NULL, NULL);
if (result > 0) {
// Check which descriptor has activity
if (FD_ISSET(serial_fd, &readfds)) {
handleSerialData(serial_fd);
}
if (FD_ISSET(tcp_fd, &readfds)) {
handleTcpData(tcp_fd);
}
if (FD_ISSET(custom_fd, &readfds)) {
handleCustomDevice(custom_fd);
}
}
}
}
int FD_ISSET(int fd, fd_set *pfds)
A fd_set (file descriptor set) holds a set of file descriptors (fds). This function checks whether or...
void FD_SET(int fd, fd_set *pfds)
A fd_set (file descriptor set) holds a set of file descriptors (fds). This function sets or adds a sp...
void FD_ZERO(fd_set *pfds)
Clear (set to 0) a fd_set (file descriptor set) so no file descriptors (fds) are selected.

Mixing Device Types

You can combine any file descriptor types in a single select() call:

Mixed Device Selection
┌────────────────────────┐
select() Operation │
└───────────┬────────────┘
┌───────────┴───────────┐
│ │
v v
┌───────────────┐ ┌───────────────┐
│ Network │ │ Serial │
│ Sockets │ │ Ports │
│ │ │ │
│ TCP │ │ UART 0 │
│ UDP │ │ UART 1 │
└───────┬───────┘ └───────┬───────┘
│ │
└───────────┬───────────┘
v
┌───────────────────────┐
│ Custom Devices │
│ │
│ External UART │
│ Ring Buffers │
│ Virtual Devices │
└───────────────────────┘

Complete Implementation Example

Custom Ring Buffer Device

Here's a complete example of implementing a ring buffer as a file descriptor:

#include <iointernal.h>
#include <nbrtos.h>
// Device-specific data structure
typedef struct {
char buffer[1024];
int read_pos;
int write_pos;
int count;
OS_CRIT critical_section;
} RingBufferDevice;
// Read function implementation
int ringBufferRead(int fd, char *buf, int nbytes) {
RingBufferDevice *dev = (RingBufferDevice*)GetExtraData(fd);
if (!dev) return -1;
OSCritEnter(&dev->critical_section, CRIT_FLAG_NONE);
int bytes_to_read = (nbytes < dev->count) ? nbytes : dev->count;
int bytes_read = 0;
for (int i = 0; i < bytes_to_read; i++) {
buf[i] = dev->buffer[dev->read_pos];
dev->read_pos = (dev->read_pos + 1) % 1024;
dev->count--;
bytes_read++;
}
if (dev->count == 0) {
ClrDataAvail(fd); // No more data available
}
OSCritLeave(&dev->critical_section);
return bytes_read;
}
// Write function implementation
int ringBufferWrite(int fd, const char *buf, int nbytes) {
RingBufferDevice *dev = (RingBufferDevice*)GetExtraData(fd);
if (!dev) return -1;
OSCritEnter(&dev->critical_section, CRIT_FLAG_NONE);
int space_available = 1024 - dev->count;
int bytes_to_write = (nbytes < space_available) ? nbytes : space_available;
int bytes_written = 0;
for (int i = 0; i < bytes_to_write; i++) {
dev->buffer[dev->write_pos] = buf[i];
dev->write_pos = (dev->write_pos + 1) % 1024;
dev->count++;
bytes_written++;
}
if (dev->count > 0) {
SetDataAvail(fd); // Data available for reading
}
if (dev->count < 1024) {
SetWriteAvail(fd); // Space available for writing
} else {
ClrWriteAvail(fd); // Buffer full
}
OSCritLeave(&dev->critical_section);
return bytes_written;
}
// Close function implementation
int ringBufferClose(int fd) {
RingBufferDevice *dev = (RingBufferDevice*)GetExtraData(fd);
if (dev) {
// Clean up resources
delete dev;
}
return 0;
}
// Create ring buffer file descriptor
int createRingBufferFd() {
RingBufferDevice *device = new RingBufferDevice;
device->read_pos = 0;
device->write_pos = 0;
device->count = 0;
OSCritInit(&device->critical_section);
IoExpandStruct funcs = {
.read = ringBufferRead,
.write = ringBufferWrite,
.close = ringBufferClose,
.extra = device
};
int fd = GetExtraFD(device, &funcs);
if (fd >= 0) {
SetWriteAvail(fd); // Initially ready for write
}
return fd;
}

Usage of Custom Device

void useCustomRingBuffer() {
// Create the custom file descriptor
int ring_fd = createRingBufferFd();
if (ring_fd < 0) {
printf("Failed to create ring buffer\n");
return;
}
// Write data
const char *message = "Hello, Ring Buffer!";
write(ring_fd, message, strlen(message));
// Read data
char readBuffer[256];
int bytesRead = read(ring_fd, readBuffer, sizeof(readBuffer));
readBuffer[bytesRead] = '\0';
printf("Read from ring buffer: %s\n", readBuffer);
// Close when done
close(ring_fd);
}

Best Practices

Resource Management

File Descriptor Lifecycle
Creation Use Cleanup
│ │ │
v v v
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
GetExtraFD()│ --> │ read() │ --> │ close() │
│ │ │ write() │ │ │
│ Allocate │ │ select() │ │ FreeExtraFd
│ resources │ │ │ │ Release FDs │
└─────────────┘ └─────────────┘ └─────────────┘

Guidelines

  1. Always Release File Descriptors
    • Call FreeExtraFd() in your close function
    • Prevent descriptor exhaustion
  2. Thread Safety
    • Use critical sections for shared data
    • Protect state changes with OSCritEnter/Leave
  3. State Management
    • Update availability flags appropriately
    • Use SetDataAvail() when data arrives
    • Use ClrDataAvail() when buffer empties
  4. Error Handling
    • Return appropriate error codes
    • Use SetHaveError() for error conditions
    • Check return values from all functions
  5. Resource Initialization
    • Initialize all structure members
    • Verify allocations succeed
    • Set initial state flags correctly

Troubleshooting

Common Issues

Problem Cause Solution
select() never returns State flags not set Call SetDataAvail() when data ready
File descriptor allocation fails Pool exhausted Release unused FDs with FreeExtraFd()
Data corruption Missing critical sections Protect shared data with OSCritEnter/Leave
Memory leaks Not freeing resources Implement proper cleanup in close function

Debugging Tips

Debug Flow
┌─────────────────────────────────────┐
│ Add debug output to functions │
│ │
│ printf("FD %d: read %d bytes\n", │
│ fd, bytes_read); │
└─────────────────┬───────────────────┘
v
┌─────────────────────────────────────┐
│ Verify state flag updates │
│ │
│ printf("Setting data avail\n"); │
│ SetDataAvail(fd); │
└─────────────────┬───────────────────┘
v
┌─────────────────────────────────────┐
│ Check extra data pointer │
│ │
void *extra = GetExtraData(fd); │
if (!extra) // handle error │
└─────────────────────────────────────┘

Summary

Key Concepts

File Descriptor System:**

  • Unified I/O abstraction for all device types
  • POSIX-like interface for consistent access
  • 255 total descriptors with defined ranges

    Custom Driver Implementation:**

  • Define read(), write(), and close() functions
  • Use IoExpandStruct to register functions
  • Manage state with Set/Clr functions

    Multiplexing:**

  • Use select() to wait on multiple sources
  • Mix network, serial, and custom devices
  • Efficient event-driven programming

    Best Practices:**

  • Always release file descriptors
  • Implement thread-safe operations
  • Update state flags correctly
  • Handle errors gracefully

Quick Reference

// Essential includes
#include <iointernal.h>
// Core functions
int fd = GetExtraFD(extra_data, &funcs);
void *data = GetExtraData(fd);
// State management
SetDataAvail(fd); ClrDataAvail(fd);
SetWriteAvail(fd); ClrWriteAvail(fd);
SetHaveError(fd); ClrHaveError(fd);
// TCP callbacks
RegisterTCPReadNotify(tcp_fd, handler);

The file descriptor system provides a powerful and flexible foundation for integrating diverse I/O resources into your NetBurner applications.