Skip to main content

VxWorks Programming Guide: Tasks, IPC, I/O, Semaphores

·2629 words·13 mins
VxWorks RTOS C Programming TaskLib SemLib MsgQLib Socket IPC Embedded Systems
Table of Contents

VxWorks Programming Guide: Tasks, IPC, I/O, Semaphores

VxWorks provides a broad set of C APIs for real-time task management, synchronization, inter-task communication, device I/O, networking, timers, signals, and interrupt handling.

Most VxWorks applications are built by combining a relatively small set of core libraries, including taskLib, msgQLib, semLib, ioLib, wdLib, eventLib, and the socket APIs.

This guide provides a practical reference for the most commonly used VxWorks programming interfaces and highlights how the major primitives differ in real-time applications.

๐Ÿ“š Documentation and Common Headers
#

VxWorks Programming Guide
#

The VxWorks installation typically includes the programming documentation under:

\docs\vxworks\guide\index.html

The exact installation path depends on the VxWorks and development-environment version.

Commonly Used Headers
#

A typical VxWorks application may use headers such as:

#include "taskLib.h"
#include "msgQLib.h"
#include "semLib.h"
#include "ioLib.h"
#include "wdLib.h"
#include "logLib.h"
#include "socket.h"

Additional functionality requires the corresponding subsystem headers, such as:

#include "selectLib.h"
#include "eventLib.h"
#include "sigLib.h"
#include "errnoLib.h"

๐Ÿ’พ VxWorks I/O System
#

VxWorks uses a unified I/O abstraction for many device types. Serial ports, files, pipes, pseudo-devices, and other I/O resources can commonly be accessed through file descriptors.

The general workflow is:

Create or register device
        |
        v
       open()
        |
        v
 read() / write() / ioctl()
        |
        v
      close()

Core I/O Functions
#

Function Purpose
creat() Create a file or device instance
open() Open a file or device and obtain a descriptor
read() Read data from a descriptor
write() Write data to a descriptor
ioctl() Perform device-specific control operations
close() Close a descriptor
remove() Remove a file or device entry

A typical descriptor-based operation looks like:

int fd;

fd = open("/some/device", O_RDWR, 0);

if (fd != ERROR)
{
    /* read(), write(), or ioctl() */

    close(fd);
}

The actual pathname and supported operations depend on the installed VxWorks device driver.

Pseudo-Memory Devices with memDrv
#

The memory device driver provides a pseudo-file interface backed by RAM. It can be useful when software needs file-like I/O semantics without a physical storage device.

Relevant APIs include:

memDrv()
memDevCreate()
memDevCreateDir()
memDevDelete()

Example:

void memDevExample(void)
{
    unsigned char buffer[1024];
    int fd;
    int data = 123;

    memDrv();

    memDevCreate("/mem/mem1", (char *)buffer, sizeof(buffer));

    fd = open("/mem/mem1", O_RDWR, 0644);

    if (fd != ERROR)
    {
        write(fd, (char *)&data, sizeof(data));

        /* Perform additional operations. */

        close(fd);
    }

    memDevDelete("/mem/mem1");
}

The lifetime of the backing memory must be managed carefully. In this example, buffer is automatic storage, so a real application should ensure that the memory remains valid for the entire lifetime of the pseudo-device.

๐Ÿ”€ I/O Multiplexing with select()
#

The select() API allows a task to wait for activity across multiple file descriptors.

It is particularly useful when a task needs to monitor several I/O channels without dedicating a separate task to each descriptor.

The general prototype is:

int select(
    int width,
    fd_set *pReadFds,
    fd_set *pWriteFds,
    fd_set *pExceptFds,
    struct timeval *pTimeOut
);

Descriptor Set Macros
#

The primary descriptor-set operations are:

FD_SET(fd, &fdset);
FD_CLR(fd, &fdset);
FD_ZERO(&fdset);
FD_ISSET(fd, &fdset);

Their purposes are:

Macro Purpose
FD_SET() Add a descriptor to a set
FD_CLR() Remove a descriptor from a set
FD_ZERO() Initialize or clear a descriptor set
FD_ISSET() Test whether a descriptor was reported as ready

Example:

void selectExample(void)
{
    fd_set readFds;
    int fds[4];
    int width = 0;
    int i;

    fds[0] = open("/tyCo/0", O_RDWR, 0);
    fds[1] = open("/tyCo/1", O_RDWR, 0);
    fds[2] = open("/pipe/p1", O_RDWR, 0);
    fds[3] = open("/mem/m1", O_RDWR, 0);

    for (i = 0; i < 4; i++)
    {
        if (fds[i] != ERROR && fds[i] > width)
            width = fds[i];
    }

    width++;

    FD_ZERO(&readFds);

    for (i = 0; i < 4; i++)
    {
        if (fds[i] != ERROR)
            FD_SET(fds[i], &readFds);
    }

    if (select(width, &readFds, NULL, NULL, NULL) == ERROR)
    {
        for (i = 0; i < 4; i++)
        {
            if (fds[i] != ERROR)
                close(fds[i]);
        }

        return;
    }

    for (i = 0; i < 4; i++)
    {
        if (fds[i] != ERROR && FD_ISSET(fds[i], &readFds))
        {
            /* Process incoming data on fds[i]. */
        }
    }
}

When using select(), the width argument is conventionally the highest descriptor value plus one.

๐Ÿงต Task Management with taskLib
#

VxWorks tasks are the fundamental execution units used to implement concurrent application behavior.

Common task-management functions include:

Function Description
taskSpawn() Create and activate a task
taskInit() Initialize a task using caller-provided resources
taskActivate() Activate an initialized task
exit() Terminate the calling task
taskDelete() Delete a task
taskDeleteForce() Force task deletion
taskSuspend() Suspend a task
taskResume() Resume a suspended task
taskRestart() Restart a task
taskPrioritySet() Change task priority
taskPriorityGet() Retrieve task priority
taskLock() Disable normal task preemption
taskUnlock() Re-enable task scheduling
taskSafe() Protect a task from deletion
taskUnsafe() Remove deletion protection
taskDelay() Delay execution for a number of clock ticks
taskIdSelf() Obtain the calling task’s ID
taskIdVerify() Verify a task ID
taskTcb() Access task-control information
taskOptionsSet() / taskOptionsGet() Configure or query task options
taskRegsGet() / taskRegsSet() Get or set task register context
taskName() / taskNameToId() Query or resolve task names
taskIsReady() / taskIsSuspended() Query task state
taskIdListGet() Retrieve active task IDs

VxWorks task priorities normally use a numeric range in which lower values represent higher priority.

๐Ÿ“จ Message Queues
#

Message queues provide asynchronous communication between tasks.

They are appropriate when one task needs to send discrete messages to another task without directly sharing synchronization state.

A queue can be created with:

MSG_Q_ID msgQID;

void initMsgQ(void)
{
    msgQID = msgQCreate(8, 1, MSG_Q_FIFO);

    if (msgQID == NULL)
    {
        printf("Message queue creation failed!\n");
    }
}

A sender can enqueue a message:

void taskSend(void)
{
    if (msgQSend(msgQID, "A", 1, NO_WAIT, MSG_PRI_NORMAL) != OK)
    {
        printf("Message send failed!\n");
    }
}

A receiver can block until a message arrives:

void taskReceive(void)
{
    unsigned char ch;

    if (msgQReceive(msgQID, (char *)&ch, 1, WAIT_FOREVER) > 0)
    {
        printf("Received from msgq: %c\n", ch);
    }
}

The queue depth, message size, priority behavior, and timeout should be selected according to the application’s real-time requirements.

๐Ÿšฐ Pipes and File-Descriptor-Based IPC
#

VxWorks pipes expose inter-task communication through the standard I/O interface.

One important advantage is that pipes can integrate with select(), allowing a task to monitor pipe activity together with other file descriptors.

A pipe can be created with:

pipeDevCreate("/pipe/mypipe", 8, 1);

A task can write to it through a normal descriptor:

void taskSendPipe(void)
{
    int pd;

    pd = open("/pipe/mypipe", O_WRONLY, 0644);

    if (pd == ERROR)
        return;

    write(pd, "a", 1);

    close(pd);
}

Another task can receive the data:

void taskReceivePipe(void)
{
    int pd;
    unsigned char ch;

    pd = open("/pipe/mypipe", O_RDONLY, 0644);

    if (pd == ERROR)
        return;

    if (read(pd, (char *)&ch, 1) > 0)
    {
        printf("Received from pipe: %c\n", ch);
    }

    close(pd);
}

If multiple producers share a resource around the pipe operation, a mutex may be used to protect the critical section.

๐Ÿ” Semaphore Types
#

VxWorks provides several semaphore types for different synchronization patterns.

The principal creation APIs are:

SEM_ID semBCreate(int queueOptions, SEM_B_STATE initialState);

SEM_ID semMCreate(int options);

SEM_ID semCCreate(int queueOptions, int initialCount);

Binary Semaphores
#

Binary semaphores represent a two-state synchronization object:

SEM_FULL
SEM_EMPTY

They are commonly used for task synchronization and signaling.

Example:

SEM_ID semBID;

void initSemB(void)
{
    semBID = semBCreate(SEM_Q_FIFO, SEM_EMPTY);
}

void taskSyncGive(void)
{
    semGive(semBID);
}

void taskSyncWait(void)
{
    semTake(semBID, WAIT_FOREVER);
}

A binary semaphore is not the same as a mutex. It is primarily a signaling/synchronization primitive rather than an ownership-based resource lock.

Mutex Semaphores
#

Mutex semaphores are intended for mutual exclusion and resource ownership.

SEM_ID semMID;

void initMutex(void)
{
    semMID = semMCreate(SEM_Q_FIFO);
}

Depending on the VxWorks version and configured options, mutexes can provide facilities such as:

  • Priority inheritance.
  • Recursive locking.
  • Deletion safety.
  • Ownership tracking.

Mutex semaphores should not be used from interrupt service routines.

Counting Semaphores
#

Counting semaphores maintain an integer count and are useful when representing multiple identical resources.

SEM_ID semCID;

void initCountingSem(void)
{
    semCID = semCCreate(SEM_Q_FIFO, 4);
}

Each successful semTake() consumes one count, while semGive() increments the count.

Semaphore Comparison
#

Semaphore Primary Use Key Characteristic
semBCreate() Task synchronization / signaling Binary full/empty state
semMCreate() Mutual exclusion Ownership and mutex-specific protection
semCCreate() Resource counting Integer resource count

The correct primitive depends on whether the application needs signaling, exclusive ownership, or resource counting.

๐Ÿ“ก VxWorks Events
#

The VxWorks event mechanism allows one task to send an event to another task identified by its task ID.

A sender can issue an event with:

void taskSendEvent(TASK_ID targetTaskId)
{
    if (eventSend(targetTaskId, 0x00000001) != OK)
    {
        printf("Event send failed!\n");
    }
}

A receiving task can wait for events:

void taskReceiveEvent(void)
{
    UINT32 ev;

    if (eventReceive(
            0x00ffffff,
            EVENTS_WAIT_ANY,
            WAIT_FOREVER,
            &ev) == OK)
    {
        if (ev & 0x00000001)
        {
            printf("Event 0x1 received!\n");
        }
    }
}

Events are useful when the notification itself is small and the receiving task does not need an associated message payload.

โฑ๏ธ Watchdog Timers
#

The wdLib API provides software watchdog timers.

A watchdog can be created with:

WDOG_ID wdID;

void initWatchdog(void)
{
    wdID = wdCreate();

    if (wdID == NULL)
    {
        printf("Watchdog create failed!\n");
    }
}

The watchdog can then be started using system clock ticks:

void startWatchdog(void)
{
    if (wdStart(
            wdID,
            sysClkRateGet() * 5,
            (FUNCPTR)proc_wd,
            0) != OK)
    {
        printf("Watchdog start failed!\n");
    }
}

The callback should be designed with the watchdog execution context in mind:

int proc_wd(int param)
{
    logMsg(
        "Watchdog timer expired!\n",
        0, 0, 0, 0, 0, 0);

    return 0;
}

A watchdog callback should generally perform minimal, deterministic work and avoid operations that can block unexpectedly.

๐ŸŒ Network Programming with BSD Sockets
#

VxWorks provides BSD-style socket APIs for TCP/IP networking.

Common functions include:

Function Purpose
socket() Create a socket endpoint
bind() Assign a local address and port
listen() Put a socket into passive listening mode
accept() Accept an incoming connection
connect() Establish an active connection
connectWithTimeout() Establish a connection with a timeout
send() / sendto() / sendmsg() Transmit data
recv() / recvfrom() / recvmsg() Receive data
setsockopt() Configure socket options
getsockopt() Query socket options
getsockname() Obtain the local socket address
getpeername() Obtain the remote socket address
shutdown() Shut down part or all of a socket connection

A typical TCP server follows this sequence:

socket()
   |
   v
bind()
   |
   v
listen()
   |
   v
accept()
   |
   v
send() / recv()
   |
   v
shutdown() / close()

A TCP client typically uses:

socket()
   |
   v
connect()
   |
   v
send() / recv()
   |
   v
shutdown() / close()

Because VxWorks is an RTOS, socket operations should be designed with explicit timeout and blocking behavior in mind when deterministic task execution is required.

๐Ÿšจ Error Handling with errnoLib
#

VxWorks maintains an error value that can be queried with:

errnoGet()

and explicitly modified with:

errnoSet()

For example:

#define MEMORY_LEAK 0x20005

int currentErr;

currentErr = errnoGet();

errnoSet(MEMORY_LEAK);

When diagnosing a failing VxWorks API call, check the return value first and then inspect errno where the relevant API documents it as meaningful.

For example:

if (someApiCall() == ERROR)
{
    printf("Error = 0x%x\n", errnoGet());
}

Application-specific error values should be selected so that they do not conflict with the error-number conventions used by the particular VxWorks release.

๐Ÿ“ฃ Signal Handling
#

Signals provide asynchronous notification between tasks or processes.

A signal handler can be registered with:

void proc_sig(int param)
{
    logMsg(
        "Signal %d received\n",
        param, 0, 0, 0, 0, 0);
}

void task1(void)
{
    signal(30, proc_sig);
}

Another task can send the signal:

void task2(int targetTaskId)
{
    kill(targetTaskId, 30);
}

Signal handlers operate under restrictions associated with asynchronous execution. Complex work is generally better deferred to a normal task context rather than performed directly inside a signal handler.

โšก Interrupt Handling
#

VxWorks provides architecture-specific interrupt interfaces for connecting application or driver interrupt handlers to hardware interrupt vectors.

On x86 systems, hardware IRQ numbers and processor interrupt vectors are not necessarily identical. A common legacy mapping is:

IRQ 0  -> vector 0x20
IRQ 1  -> vector 0x21
...
IRQ 15 -> vector 0x2F

For example, IRQ 9 corresponds to:

0x20 + 9 = 0x29

A simplified connection might look like:

void Int9Handler(int param)
{
    /* Handle interrupt source. */
}

void initInterrupt(void)
{
    intConnect(
        INUM_TO_IVEC(9 + 0x20),
        Int9Handler,
        0);

    sysIntEnablePIC(9);
}

The exact interrupt controller, vector mapping, and enable/acknowledge sequence are architecture- and BSP-dependent.

Interrupt handlers should execute quickly and avoid blocking operations. Longer processing should normally be deferred to a task or other appropriate execution context.

๐Ÿ—๏ธ Understanding taskSpawn()
#

taskSpawn() provides a convenient way to create and activate a VxWorks task.

A representative prototype is:

int taskSpawn(
    char *name,
    int priority,
    int options,
    int stackSize,
    FUNCPTR mainFunc,
    int arg1,
    int arg2,
    int arg3,
    int arg4,
    int arg5,
    int arg6,
    int arg7,
    int arg8,
    int arg9,
    int arg10
);

The important parameters are:

Parameter Meaning
name Task name
priority Task priority
options Task execution options
stackSize Stack allocation size
mainFunc Task entry function
arg1โ€“arg10 Arguments passed to the entry function

A task can be created with:

taskSpawn(
    "tWorker",
    100,
    0,
    8192,
    (FUNCPTR)workerTask,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0);

The exact function prototype and argument conventions should match the VxWorks release being used.

taskSpawn() Versus taskInit()
#

The primary difference is resource ownership and activation.

API Model
taskSpawn() Dynamically creates the task and its required resources, then activates it
taskInit() Initializes a task using caller-supplied resources; activation is performed separately

Conceptually:

taskSpawn()
    |
    +-- Allocate/initialize TCB
    +-- Allocate/initialize stack
    +-- Initialize task context
    +-- Activate task

Whereas:

taskInit()
    |
    +-- Caller provides TCB/stack resources
    +-- Initialize task
    |
    v
taskActivate()

Static task initialization can be useful when resource ownership, memory placement, or startup behavior needs tighter control.

๐Ÿงฉ Choosing the Right IPC Primitive
#

VxWorks offers multiple mechanisms for communication and synchronization. They should not be treated as interchangeable.

Requirement Appropriate Primitive
Transfer a message payload Message queue
Stream/file-descriptor-oriented communication Pipe
Signal a task that an operation occurred Binary semaphore
Protect a shared resource Mutex semaphore
Track multiple identical resources Counting semaphore
Deliver a small event bitmask VxWorks events
Monitor multiple I/O descriptors select()
Communicate over a network BSD sockets
Notify asynchronously Signal
React to hardware Interrupt handler

A common architecture might therefore look like:

Hardware ISR
     |
     v
Binary semaphore / event
     |
     v
Worker task
     |
     +---- Message queue ----> Processing task
     |
     +---- Pipe -------------> I/O task
     |
     +---- Socket ------------> Network peer

This separation helps keep interrupt handlers short while moving potentially blocking or computationally expensive work into normal task context.

๐Ÿ“‹ Practical API Reference
#

The following mapping provides a compact reference for common VxWorks development tasks.

Category Primary API / Header Typical Use
Task management taskLib.h Create, suspend, resume, delete, and configure tasks
Message queues msgQLib.h Task-to-task message passing
Semaphores semLib.h Synchronization and resource protection
Device I/O ioLib.h File and device operations
I/O multiplexing selectLib.h Monitor multiple descriptors
Events eventLib.h Task-directed event notification
Watchdogs wdLib.h Timed callbacks
Logging logLib.h Diagnostic logging
Networking socket.h / socket APIs TCP/IP communication
Signals sigLib.h Asynchronous task/process notification
Error handling errnoLib.h Retrieve and set error values
Interrupts iv.h and BSP APIs Connect and enable hardware interrupts

๐Ÿง  Key Takeaways
#

Effective VxWorks programming depends less on using individual APIs in isolation and more on selecting the correct execution and synchronization model.

The most important distinctions are:

  • Use tasks for independent execution contexts.
  • Use message queues when data must be transferred between tasks.
  • Use pipes when communication should integrate with the VxWorks I/O subsystem.
  • Use binary semaphores for synchronization and notification.
  • Use mutex semaphores for protecting shared resources.
  • Use counting semaphores for finite resource pools.
  • Use events and signals for lightweight notifications.
  • Use watchdogs for timed callbacks.
  • Use sockets for network communication.
  • Use interrupt handlers for short, hardware-driven response paths.
  • Use select() when a task must monitor multiple file descriptors.
  • Use errnoGet() to diagnose APIs that report failures through the VxWorks error mechanism.

For real-time systems, correctness also depends on execution context. Code that is acceptable in a normal task may be inappropriate inside an ISR, watchdog callback, or signal handler. Blocking behavior, priority interactions, stack usage, interrupt latency, and resource ownership should therefore be considered whenever these APIs are combined into a production VxWorks application.

Related

PCI-Based CAN Card Design and VxWorks Driver Development
·683 words·4 mins
VxWorks CAN Bus PCI Device Drivers Embedded Systems RTOS Hardware Design C Programming
VxWorks Target Hardware Configuration: A Practical Guide
·482 words·3 mins
VxWorks RTOS Embedded Systems Hardware BSP
VxWorks select() Guide: Build a Multi-Client TCP Server
·760 words·4 mins
VxWorks RTOS Networking Sockets Select() Multi-Client Embedded Systems Programming