The POSIX standard for real-time extensions (1003.1b) specifies a set of interfaces to kernel facilities. To improve application portability, the VxWorks kernel, wind, includes both POSIX interfaces and interfaces designed specifically for VxWorks.
This chapter uses the qualifier "Wind" to identify facilities designed expressly for use with the VxWorks wind kernel. For example, you can find a discussion of Wind semaphores contrasted to POSIX semaphores in 3.6.1 Comparison of POSIX and Wind Semaphores.
POSIX asynchronous Input/Output (AIO) routines are available in the aioPxLib library. The VxWorks AIO implementation meets the specification in the POSIX 1003.1b standard. For more information, see 4.6 Asynchronous Input/Output.
A clock is a software construct (struct timespec, defined in time.h) that keeps time in seconds and nanoseconds. The software clock is updated by system-clock ticks. VxWorks provides a POSIX 1003.1b standard clock and timer interface.
The POSIX standard provides a means of identifying multiple virtual clocks, but only one clock is required--the system-wide real-time clock. No virtual clocks are supported in VxWorks.
The system-wide real-time clock is identified in the clock and timer routines as CLOCK_REALTIME, and is defined in time.h. VxWorks provides routines to access the system-wide real-time clock. For more information, see the reference entry for clockLib.
The POSIX timer facility provides routines for tasks to signal themselves at some time in the future. Routines are provided to create, set, and delete a timer. For more information, see the reference entry for timerLib. When a timer goes off, the default signal, SIGALRM, is sent to the task. To install a signal handler that executes when the timer expires, use the sigaction( ) routine (see 2.3.7 Signals).
/* This example creates a new timer and stores it in timerid. */
/* includes */
#include "vxWorks.h"
#include "time.h"
int createTimer (void)
{
timer_t timerid;
/* create timer */
if (timer_create (CLOCK_REALTIME, NULL, &timerid) == ERROR)
{
printf ("create FAILED\n");
return (ERROR);
}
return (OK);
}
An additional POSIX function, nanosleep( ), provides specification of sleep or delay time in units of seconds and nanoseconds, in contrast to the ticks used by the Wind taskDelay( ) function. Nevertheless, the precision of both is the same, and is determined by the system clock rate. Only the units differ.
Many operating systems perform memory paging and swapping, which copy blocks of memory out to disk and back. These techniques allow you to use more virtual memory than there is physical memory on a system. However, because they impose severe and unpredictable delays in execution time, paging and swapping are undesirable in real-time systems. Consequently, the wind kernel never uses them.
However, the POSIX 1003.1b standard for real-time extensions is also used with operating systems that do perform paging and swapping. On such systems, applications that attempt real-time performance can use the POSIX page-locking facilities to protect certain blocks of memory from paging and swapping.
To facilitate porting programs between other POSIX-conforming systems and VxWorks, VxWorks includes the POSIX page-locking routines. The routines have no adverse affect in VxWorks systems, because all memory is essentially always locked.
The POSIX page-locking routines are part of the memory management library, mmanPxLib, and are listed in Table 3-1. When used in VxWorks, these routines do nothing except return a value of OK (0), since all pages are always kept in memory.
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
POSIX characteristics are called attributes. Each attribute contains a set of values, and a set of access functions to retrieve and set those values. You can specify all thread attributes in an attributes object, pthread_attr_t, at thread creation. In a few cases, you can dynamically modify the attribute values in a running thread.
The POSIX attributes and their corresponding access functions are described below.
The detachstate attribute describes the state of a thread. With POSIX threads, the creator of a thread can block until the thread exits (see the entries for pthread_exit( ) and pthread_join( ) in the VxWorks API Reference). In this case, the thread is a joinable thread. Otherwise, it is a detached thread. A thread that was created as a joinable thread can dynamically make itself a detached thread by calling pthread_detach( ).
The schedpolicy attribute describes the scheduling policy for the thread, and is valid only if the value of the inheritsched attribute is PTHREAD_EXPLICIT_SCHED.
Note that because the default value for the inheritsched attribute is PTHREAD_INHERIT_SCHED, the schedpolicy attribute is not used by default. For more information, see 3.5.3 Getting and Displaying the Current Scheduling Policy.
The schedparam attribute describes the scheduling parameters for the thread, and is valid only if the value of the inheritsched attribute is PTHREAD_EXPLICIT_SCHED.
Note that because the default value the inheritsched attribute is PTHREAD_INHERIT_SCHED, the schedparam attribute is not used by default. For more information, see 3.5.2 Getting and Setting POSIX Task Priorities.
Example 3-2: Creating a pThread Using Explicit Scheduing Attributes
pthread_t tid; pthread_attr_t attr; int ret; pthread_attr_init(&attr); /* set the inheritsched attribute to explicit */ pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); /* set the schedpolicy attribute to SCHED_FIFO */ pthread_attr_setschedpolicy(&attr, SCHED_FIFO); /* create the pthread */ ret = pthread_create(&tid, &attr, entryFunction, entryArg);
Example 3-3: Creating a pThread Using Default Attributes
pthread_t tid; int ret; /* create the pthread with NULL attributes to designate default values */ ret = pthread_create(&tid, NULL, entryFunction, entryArg);
Example 3-4: Designating Your Own Stack for a pThread
pthread_attr_init(&attr);
/* allocate memory for a stack region for the thread */
stackbase = malloc(2 * 4096);
if (stackbase == NULL)
{
printf("FAILED: mystack: malloc failed\n");
exit(-1);
}
/* set the stack pointer to the base address */
stackptr = (void *)((int)stackbase);
/* explicitly set the stackaddr attribute */
pthread_attr_setstackaddr(&attr, stackptr);
/* set the stacksize attribute to 4096 */
pthread_attr_setstacksize(&attr, (4096));
/* set the schedpolicy attribute to SCHED_FIFO */ pthread_attr_setschedpolicy(&attr, SCHED_FIFO); /* create the pthread */ ret = pthread_create(&tid, &attr, mystack_thread, 0);
When a thread needs access to private data, POSIX uses a key to access that data. A location is created by calling to pthread_key_create( ) and released by calling pthread_key_delete( ). The location is then accessed by calling pthread_getspecific( ) and pthread_setspecific( ). The pthread_key_create( ) routine has an option for a destructor function, which is called when the creating thread exits, if the value associated with the key is non-NULL.
POSIX provides a mechanism, called cancellation, to terminate a thread gracefully. There are two types of cancellation, synchronous and asynchronous. Synchronous cancellation causes the thread to explicitly check to see if it was cancelled or to call a function that contains a cancellation point. Asynchronous cancellation causes the execution of the thread to be interrupted and a handler to be called, much like a signal.1
Routines that can be used with cancellation are listed in Table 3-2.
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
The POSIX 1003.1b scheduling routines, provided by schedPxLib, are shown in Table 3-3. These routines let you use a portable interface to get and set task priority, get the scheduling policy, get the maximum and minimum priority for tasks, and if round-robin scheduling is in effect, get the length of a time slice. This section describes how to use these routines, beginning with a list of the minor differences between the POSIX and Wind methods of scheduling.
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
The routines sched_setparam( ) and sched_getparam( ) set and get a task's priority, respectively. Both routines take a task ID and a sched_param structure (defined in installDir/target/h/sched.h). A task ID of 0 sets or gets the priority for the calling task.
When sched_setparam( ) is called, the sched_priority member of the sched_param structure specifies the new task priority. The sched_getparam( ) routine fills in the sched_priority with the specified task's current priority.
Example 3-5: Getting and Setting POSIX Task Priorities
/* This example sets the calling task's priority to 150, then verifies
* that priority. To run from the shell, spawn as a task: -> sp priorityTest
*/
/* includes */
#include "vxWorks.h"
#include "sched.h"
/* defines */
#define PX_NEW_PRIORITY 150
STATUS priorityTest (void)
{
struct sched_param myParam;
/* initialize param structure to desired priority */
myParam.sched_priority = PX_NEW_PRIORITY;
if (sched_setparam (0, &myParam) == ERROR)
{
printf ("error setting priority\n");
return (ERROR);
}
/* demonstrate getting a task priority as a sanity check; ensure it
* is the same value that we just set.
*/
if (sched_getparam (0, &myParam) == ERROR)
{
printf ("error getting priority\n");
return (ERROR);
}
if (myParam.sched_priority != PX_NEW_PRIORITY)
{
printf ("error - priorities do not match\n");
return (ERROR);
}
else
printf ("task priority = %d\n", myParam.sched_priority);
return (OK);
}
The routine sched_setscheduler( ) is designed to set both scheduling policy and priority for a single POSIX process, which corresponds in most other cases to a single Wind task. In the VxWorks kernel, sched_setscheduler( ) controls only task priority, because the kernel does not allow tasks to have scheduling policies that differ from one another. If its policy specification matches the current system-wide scheduling policy, sched_setscheduler( ) sets only the priority, thus acting like sched_setparam( ). If its policy specification does not match the current one, sched_setscheduler( ) returns an error.
The only way to change the scheduling policy is to change it for all tasks; there is no POSIX routine for this purpose. To set a system-wide scheduling policy, use the Wind function kernelTimeSlice( ) described in Round-Robin Scheduling.
The POSIX routine sched_getscheduler( ) returns the current scheduling policy. There are two valid scheduling policies in VxWorks: preemptive priority scheduling (in POSIX terms, SCHED_FIFO) and round-robin scheduling by priority (SCHED_RR). For more information, see Scheduling Policy.
Example 3-6: Getting POSIX Scheduling Policy
/* This example gets the scheduling policy and displays it. */
/* includes */
#include "vxWorks.h"
#include "sched.h"
STATUS schedulerTest (void)
{
int policy;
if ((policy = sched_getscheduler (0)) == ERROR)
{
printf ("getting scheduler failed\n");
return (ERROR);
}
/* sched_getscheduler returns either SCHED_FIFO or SCHED_RR */
if (policy == SCHED_FIFO)
printf ("current scheduling policy is FIFO\n");
else
printf ("current scheduling policy is round robin\n");
return (OK);
}
The routines sched_get_priority_max( ) and sched_get_priority_min( ) return the maximum and minimum possible POSIX priority, respectively.
If round-robin scheduling is enabled, you can use sched_rr_get_interval( ) to determine the length of the current time-slice interval. This routine takes as an argument a pointer to a timespec structure (defined in time.h), and writes the number of seconds and nanoseconds per time slice to the appropriate elements of that structure.
Example 3-7: Getting the POSIX Round-Robin Time Slice
/* The following example checks that round-robin scheduling is enabled,
* gets the length of the time slice, and then displays the time slice.
*/
/* includes */
#include "vxWorks.h"
#include "sched.h"
STATUS rrgetintervalTest (void)
{
struct timespec slice;
/* turn on round robin */
kernelTimeSlice (30);
if (sched_rr_get_interval (0, &slice) == ERROR)
{
printf ("get-interval test failed\n");
return (ERROR);
}
printf ("time slice is %l seconds and %l nanoseconds\n",
slice.tv_sec, slice.tv_nsec);
return (OK);
}
POSIX defines both named and unnamed semaphores, which have the same properties, but use slightly different interfaces. The POSIX semaphore library provides routines for creating, opening, and destroying both named and unnamed semaphores. When opening a named semaphore, you assign a symbolic name,2 which the other named-semaphore routines accept as an argument. The POSIX semaphore routines provided by semPxLib are shown in Table 3-4.
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
POSIX semaphores are counting semaphores; that is, they keep track of the number of times they are given. The Wind semaphore mechanism is similar to that specified by POSIX, except that Wind semaphores offer additional features listed below. When these features are important, Wind semaphores are preferable.
The POSIX terms wait (or lock) and post (or unlock) correspond to the VxWorks terms take and give, respectively. The POSIX routines for locking, unlocking, and getting the value of semaphores are used for both named and unnamed semaphores.
The routines sem_init( ) and sem_destroy( ) are used for initializing and destroying unnamed semaphores only. The sem_destroy( ) call terminates an unnamed semaphore and deallocates all associated memory.
The routines sem_open( ), sem_unlink( ), and sem_close( ) are for opening and closing (destroying) named semaphores only. The combination of sem_close( ) and sem_unlink( ) has the same effect for named semaphores as sem_destroy( ) does for unnamed semaphores. That is, it terminates the semaphore and deallocates the associated memory.
When using unnamed semaphores, typically one task allocates memory for the semaphore and initializes it. A semaphore is represented with the data structure sem_t, defined in semaphore.h. The semaphore initialization routine, sem_init( ), lets you specify the initial value.
Once the semaphore is initialized, any task can use the semaphore by locking it with sem_wait( ) (blocking) or sem_trywait( ) (non-blocking), and unlocking it with sem_post( ).
Semaphores can be used for both synchronization and exclusion. Thus, when a semaphore is used for synchronization, it is typically initialized to zero (locked). The task waiting to be synchronized blocks on a sem_wait( ). The task doing the synchronizing unlocks the semaphore using sem_post( ). If the task blocked on the semaphore is the only one waiting for that semaphore, the task unblocks and becomes ready to run. If other tasks are blocked on the semaphore, the task with the highest priority is unblocked.
When a semaphore is used for mutual exclusion, it is typically initialized to a value greater than zero, meaning that the resource is available. Therefore, the first task to lock the semaphore does so without blocking; subsequent tasks block if the semaphore value was initialized to 1.
Example 3-8: POSIX Unnamed Semaphores
/* This example uses unnamed semaphores to synchronize an action between the
* calling task and a task that it spawns (tSyncTask). To run from the shell,
* spawn as a task:
* -> sp unnameSem
*/
/* includes */
#include "vxWorks.h"
#include "semaphore.h"
/* forward declarations */
void syncTask (sem_t * pSem);
void unnameSem (void)
{
sem_t * pSem;
/* reserve memory for semaphore */
pSem = (sem_t *) malloc (sizeof (sem_t));
/* initialize semaphore to unavailable */
if (sem_init (pSem, 0, 0) == -1)
{
printf ("unnameSem: sem_init failed\n");
free ((char *) pSem);
return;
}
/* create sync task */
printf ("unnameSem: spawning task...\n");
taskSpawn ("tSyncTask", 90, 0, 2000, syncTask, pSem);
/* do something useful to synchronize with syncTask */
/* unlock sem */
printf ("unnameSem: posting semaphore - synchronizing action\n");
if (sem_post (pSem) == -1)
{
printf ("unnameSem: posting semaphore failed\n");
sem_destroy (pSem);
free ((char *) pSem);
return;
}
/* all done - destroy semaphore */
if (sem_destroy (pSem) == -1)
{
printf ("unnameSem: sem_destroy failed\n");
return;
}
free ((char *) pSem);
}
void syncTask
(
sem_t * pSem
)
{
/* wait for synchronization from unnameSem */
if (sem_wait (pSem) == -1)
{
printf ("syncTask: sem_wait failed \n");
return;
}
else
printf ("syncTask:sem locked; doing sync'ed action...\n");
/* do something useful here */
}
The sem_open( ) routine either opens a named semaphore that already exists or, as an option, creates a new semaphore. You can specify which of these possibilities you want by combining the following flag values:
The results, based on the flags and whether the semaphore accessed already exists, are shown in Table 3-5. There is no entry for O_EXCL alone, because using that flag alone is not meaningful.
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
A POSIX named semaphore, once initialized, remains usable until explicitly destroyed. Tasks can explicitly mark a semaphore for destruction at any time, but the semaphore remains in the system until no task has the semaphore open.
If VxWorks is configured with INCLUDE_POSIX_SEM_SHOW, you can use show( ) from the shell to display information about a POSIX semaphore:3
-> show semId value = 0 = 0x0
The output is sent to the standard output device, and provides information about the POSIX semaphore mySem with two tasks blocked waiting for it:
Semaphore name :mySem sem_open() count :3 Semaphore value :0 No. of blocked tasks :2
For a group of collaborating tasks to use a named semaphore, one of the tasks first creates and initializes the semaphore, by calling sem_open( ) with the O_CREAT flag. Any task that needs to use the semaphore thereafter, opens it by calling sem_open( ) with the same name (but without setting O_CREAT). Any task that has opened the semaphore can use it by locking it with sem_wait( ) (blocking) or sem_trywait( ) (non-blocking) and unlocking it with sem_post( ).
To remove a semaphore, all tasks using it must first close it with sem_close( ), and one of the tasks must also unlink it. Unlinking a semaphore with sem_unlink( ) removes the semaphore name from the name table. After the name is removed from the name table, tasks that currently have the semaphore open can still use it, but no new tasks can open this semaphore. The next time a task tries to open the semaphore without the O_CREAT flag, the operation fails. The semaphore vanishes when the last task closes it.
Example 3-9: POSIX Named Semaphores
/* * In this example, nameSem() creates a task for synchronization. The * new task, tSyncSemTask, blocks on the semaphore created in nameSem(). * Once the synchronization takes place, both tasks close the semaphore, * and nameSem() unlinks it. To run this task from the shell, spawn * nameSem as a task: * -> sp nameSem, "myTest" */
/* includes */
#include "vxWorks.h"
#include "semaphore.h"
#include "fcntl.h"
/* forward declaration */
int syncSemTask (char * name);
int nameSem
(
char * name
)
{
sem_t * semId;
/* create a named semaphore, initialize to 0*/
printf ("nameSem: creating semaphore\n");
if ((semId = sem_open (name, O_CREAT, 0, 0)) == (sem_t *) -1)
{
printf ("nameSem: sem_open failed\n");
return;
}
printf ("nameSem: spawning sync task\n");
taskSpawn ("tSyncSemTask", 90, 0, 2000, syncSemTask, name);
/* do something useful to synchronize with syncSemTask */
/* give semaphore */
printf ("nameSem: posting semaphore - synchronizing action\n");
if (sem_post (semId) == -1)
{
printf ("nameSem: sem_post failed\n");
return;
}
/* all done */
if (sem_close (semId) == -1)
{
printf ("nameSem: sem_close failed\n");
return;
}
if (sem_unlink (name) == -1)
{
printf ("nameSem: sem_unlink failed\n");
return;
}
printf ("nameSem: closed and unlinked semaphore\n");
}
int syncSemTask
(
char * name
)
{
sem_t * semId;
/* open semaphore */
printf ("syncSemTask: opening semaphore\n");
if ((semId = sem_open (name, 0)) == (sem_t *) -1)
{
printf ("syncSemTask: sem_open failed\n");
return;
}
/* block waiting for synchronization from nameSem */
printf ("syncSemTask: attempting to take semaphore...\n");
if (sem_wait (semId) == -1)
{
printf ("syncSemTask: taking sem failed\n");
return;
}
printf ("syncSemTask: has semaphore, doing sync'ed action ...\n");
/* do something useful here */
if (sem_close (semId) == -1)
{
printf ("syncSemTask: sem_close failed\n");
return;
}
}
Mutexes and condition variables provide compatibility with the POSIX standard (1003.1c). They perform essentially the same role as mutual exclusion and binary semaphores (and are in fact implemented using them). They are available with pthreadLib. Like POSIX threads, mutexes and condition variables have attributes associated with them.
Mutex attributes are held in a data type called pthread_mutexattr_t, which contains two attributes, protocol and prioceiling.
The protocol mutex attribute describes how the mutex deals with the priority inversion problem described in the section for mutual-exclusion semaphores (Mutual-Exclusion Semaphores).
To create a mutual-exclusion semaphore with priority inheritance, use the SEM_Q_PRIORITY and SEM_PRIO_INHERIT options to semMCreate( ). Mutual-exclusion semaphores created with the priority protection value use the notion of a priority ceiling, which is the other mutex attribute.
The prioceiling attribute is the POSIX priority ceiling for a mutex created with the protocol attribute set to PTHREAD_PRIO_PROTECT.
Note that the POSIX priority numbering scheme is the inverse of the Wind scheme. See 3.5.1 Comparison of POSIX and Wind Scheduling.
The POSIX message queue routines, provided by mqPxLib, are shown in Table 3-6.
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
The POSIX message queues are similar to Wind message queues, except that POSIX message queues provide messages with a range of priorities. The differences between the POSIX and Wind message queues are summarized in Table 3-7.
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
Example 3-10: Setting and Getting Message Queue Attributes
/*
* This example sets the O_NONBLOCK flag and examines message queue
* attributes.
*/
/* includes */
#include "vxWorks.h"
#include "mqueue.h"
#include "fcntl.h"
#include "errno.h"
/* defines */
#define MSG_SIZE 16
int attrEx
(
char * name
)
{
mqd_t mqPXId; /* mq descriptor */
struct mq_attr attr; /* queue attribute structure */
struct mq_attr oldAttr; /* old queue attributes */
char buffer[MSG_SIZE];
int prio;
/* create read write queue that is blocking */
attr.mq_flags = 0;
attr.mq_maxmsg = 1;
attr.mq_msgsize = 16;
if ((mqPXId = mq_open (name, O_CREAT | O_RDWR , 0, &attr))
== (mqd_t) -1)
return (ERROR);
else
printf ("mq_open with non-block succeeded\n");
/* change attributes on queue - turn on non-blocking */
attr.mq_flags = O_NONBLOCK;
if (mq_setattr (mqPXId, &attr, &oldAttr) == -1)
return (ERROR);
else
{
/* paranoia check - oldAttr should not include non-blocking. */
if (oldAttr.mq_flags & O_NONBLOCK)
return (ERROR);
else
printf ("mq_setattr turning on non-blocking succeeded\n");
}
/* try receiving - there are no messages but this shouldn't block */
if (mq_receive (mqPXId, buffer, MSG_SIZE, &prio) == -1)
{
if (errno != EAGAIN)
return (ERROR);
else
printf ("mq_receive with non-blocking didn't block on empty queue\n");
}
else
return (ERROR);
/* use mq_getattr to verify success */
if (mq_getattr (mqPXId, &oldAttr) == -1)
return (ERROR);
else
{
/* test that we got the values we think we should */
if (!(oldAttr.mq_flags & O_NONBLOCK) || (oldAttr.mq_curmsgs != 0))
return (ERROR);
else
printf ("queue attributes are:\n\tblocking is %s\n\t
message size is: %d\n\t
max messages in queue: %d\n\t
no. of current msgs in queue: %d\n",
oldAttr.mq_flags & O_NONBLOCK ? "on" : "off",
oldAttr.mq_msgsize, oldAttr.mq_maxmsg,
oldAttr.mq_curmsgs);
}
/* clean up - close and unlink mq */
if (mq_unlink (name) == -1)
return (ERROR);
if (mq_close (mqPXId) == -1)
return (ERROR);
return (OK);
}
The VxWorks show( ) command produces a display of the key message queue attributes, for either POSIX or Wind message queues. To get information on POSIX message queues, configure VxWorks to include the INCLUDE_POSIX_MQ_SHOW component.
For example, if mqPXId is a POSIX message queue:
-> show mqPXId value = 0 = 0x0
The output is sent to the standard output device, and looks like the following:
Message queue name : MyQueue No. of messages in queue : 1 Maximum no. of messages : 16 Maximum message size : 16
Compare this to the output when myMsgQId is a Wind message queue:
-> show myMsgQId Message Queue Id : 0x3adaf0 Task Queuing : FIFO Message Byte Len : 4 Messages Max : 30 Messages Queued : 14 Receivers Blocked : 0 Send timeouts : 0 Receive timeouts : 0
|
|
|||||||||||||||||||
Before a set of tasks can communicate through a POSIX message queue, one of the tasks must create the message queue by calling mq_open( ) with the O_CREAT flag set. Once a message queue is created, other tasks can open that queue by name to send and receive messages on it. Only the first task opens the queue with the O_CREAT flag; subsequent tasks can open the queue for receiving only (O_RDONLY), sending only (O_WRONLY), or both sending and receiving (O_RDWR).
To put messages on a queue, use mq_send( ). If a task attempts to put a message on the queue when the queue is full, the task blocks until some other task reads a message from the queue, making space available. To avoid blocking on mq_send( ), set O_NONBLOCK when you open the message queue. In that case, when the queue is full, mq_send( ) returns -1 and sets errno to EAGAIN instead of pending, allowing you to try again or take other action as appropriate.
One of the arguments to mq_send( ) specifies a message priority. Priorities range from 0 (lowest priority) to 31 (highest priority); see 3.5.1 Comparison of POSIX and Wind Scheduling.
When a task receives a message using mq_receive( ), the task receives the highest-priority message currently on the queue. Among multiple messages with the same priority, the first message placed on the queue is the first received (FIFO order). If the queue is empty, the task blocks until a message is placed on the queue.
To avoid pending on mq_receive( ), open the message queue with O_NONBLOCK; in that case, when a task attempts to read from an empty queue, mq_receive( ) returns -1 and sets errno to EAGAIN.
To close a message queue, call mq_close( ). Closing the queue does not destroy it, but only asserts that your task is no longer using the queue. To request that the queue be destroyed, call mq_unlink( ). Unlinking a message queue does not destroy the queue immediately, but it does prevent any further tasks from opening that queue, by removing the queue name from the name table. Tasks that currently have the queue open can continue to use it. When the last task closes an unlinked queue, the queue is destroyed.
Example 3-11: POSIX Message Queues
/* In this example, the mqExInit() routine spawns two tasks that
* communicate using the message queue.
*/
/* mqEx.h - message example header */
/* defines */
#define MQ_NAME "exampleMessageQueue"
/* forward declarations */
void receiveTask (void);
void sendTask (void);
/* testMQ.c - example using POSIX message queues */
/* includes */
#include "vxWorks.h"
#include "mqueue.h"
#include "fcntl.h"
#include "errno.h"
#include "mqEx.h"
/* defines */
#define HI_PRIO 31
#define MSG_SIZE 16
int mqExInit (void)
{
/* create two tasks */
if (taskSpawn ("tRcvTask", 95, 0, 4000, receiveTask, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0) == ERROR)
{
printf ("taskSpawn of tRcvTask failed\n");
return (ERROR);
}
if (taskSpawn ("tSndTask", 100, 0, 4000, sendTask, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0) == ERROR)
{
printf ("taskSpawn of tSendTask failed\n");
return (ERROR);
}
}
void receiveTask (void)
{
mqd_t mqPXId; /* msg queue descriptor */
char msg[MSG_SIZE]; /* msg buffer */
int prio; /* priority of message */
/* open message queue using default attributes */
if ((mqPXId = mq_open (MQ_NAME, O_RDWR | O_CREAT, 0, NULL))
== (mqd_t) -1)
{
printf ("receiveTask: mq_open failed\n");
return;
}
/* try reading from queue */
if (mq_receive (mqPXId, msg, MSG_SIZE, &prio) == -1)
{
printf ("receiveTask: mq_receive failed\n");
return;
}
else
{
printf ("receiveTask: Msg of priority %d received:\n\t\t%s\n",
prio, msg);
}
}
/* sendTask.c - mq sending example */
/* includes */
#include "vxWorks.h"
#include "mqueue.h"
#include "fcntl.h"
#include "mqEx.h"
/* defines */
#define MSG "greetings"
#define HI_PRIO 30
void sendTask (void)
{
mqd_t mqPXId; /* msg queue descriptor */\
/* open msg queue; should already exist with default attributes */
if ((mqPXId = mq_open (MQ_NAME, O_RDWR, 0, NULL)) == (mqd_t) -1)
{
printf ("sendTask: mq_open failed\n");
return;
}
/* try writing to queue */
if (mq_send (mqPXId, MSG, sizeof (MSG), HI_PRIO) == -1)
{
printf ("sendTask: mq_send failed\n");
return;
}
else
printf ("sendTask: mq_send succeeded\n");
}
A task can use the mq_notify( ) routine to request notification when a message for it arrives at an empty queue. The advantage of this is that a task can avoid blocking or polling to wait for a message.
The mq_notify( ) call specifies a signal to be sent to the task when a message is placed on an empty queue. This mechanism uses the POSIX data-carrying extension to signaling, which allows you, for example, to carry a queue identifier with the signal (see 3.9 POSIX Queued Signals).
The mq_notify( ) mechanism is designed to alert the task only for new messages that are actually available. If the message queue already contains messages, no notification is sent when more messages arrive. If there is another task that is blocked on the queue with mq_receive( ), that other task unblocks, and no notification is sent to the task registered with mq_notify( ).
Notification is exclusive to a single task: each queue can register only one task for notification at a time. Once a queue has a task to notify, no attempts to register with mq_notify( ) can succeed until the notification request is satisfied or cancelled.
Once a queue sends notification to a task, the notification request is satisfied, and the queue has no further special relationship with that particular task; that is, the queue sends a notification signal only once per mq_notify( ) request. To arrange for one particular task to continue receiving notification signals, the best approach is to call mq_notify( ) from the same signal handler that receives the notification signals. This reinstalls the notification request as soon as possible.
To cancel a notification request, specify NULL instead of a notification signal. Only the currently registered task can cancel its notification request.
Example 3-12: Notifying a Task that a Message Queue is Waiting
/* *In this example, a task uses mq_notify() to discover when a message * is waiting for it on a previously empty queue. */
/* includes */ #include "vxWorks.h" #include "signal.h" #include "mqueue.h" #include "fcntl.h" #include "errno.h"
/* defines */ #define QNAM "PxQ1" #define MSG_SIZE 64 /* limit on message sizes */
/* forward declarations */ static void exNotificationHandle (int, siginfo_t *, void *); static void exMqRead (mqd_t);
/* * exMqNotify - example of how to use mq_notify() * * This routine illustrates the use of mq_notify() to request notification * via signal of new messages in a queue. To simplify the example, a * single task both sends and receives a message. */
int exMqNotify ( char * pMess /* text for message to self */ )
{
struct mq_attr attr; /* queue attribute structure */
struct sigevent sigNotify; /* to attach notification */
struct sigaction mySigAction; /* to attach signal handler */
mqd_t exMqId /* id of message queue */
/* Minor sanity check; avoid exceeding msg buffer */
if (MSG_SIZE <= strlen (pMess))
{
printf ("exMqNotify: message too long\n");
return (-1);
}
/* * Install signal handler for the notify signal and fill in * a sigaction structure and pass it to sigaction(). Because the handler * needs the siginfo structure as an argument, the SA_SIGINFO flag is * set in sa_flags. */
mySigAction.sa_sigaction = exNotificationHandle; mySigAction.sa_flags = SA_SIGINFO; sigemptyset (&mySigAction.sa_mask);
if (sigaction (SIGUSR1, &mySigAction, NULL) == -1)
{
printf ("sigaction failed\n");
return (-1);
}
/* * Create a message queue - fill in a mq_attr structure with the * size and no. of messages required, and pass it to mq_open(). */
attr.mq_flags = O_NONBLOCK; /* make nonblocking */
attr.mq_maxmsg = 2;
attr.mq_msgsize = MSG_SIZE;
if ( (exMqId = mq_open (QNAM, O_CREAT | O_RDWR, 0, &attr)) ==
(mqd_t) - 1 )
{
printf ("mq_open failed\n");
return (-1);
}
/* * Set up notification: fill in a sigevent structure and pass it * to mq_notify(). The queue ID is passed as an argument to the * signal handler. */
sigNotify.sigev_signo = SIGUSR1;
sigNotify.sigev_notify = SIGEV_SIGNAL;
sigNotify.sigev_value.sival_int = (int) exMqId;
if (mq_notify (exMqId, &sigNotify) == -1)
{
printf ("mq_notify failed\n");
return (-1);
}
/* * We just created the message queue, but it may not be empty; * a higher-priority task may have placed a message there while * we were requesting notification. mq_notify() does nothing if * messages are already in the queue; therefore we try to * retrieve any messages already in the queue. */
exMqRead (exMqId);
/* * Now we know the queue is empty, so we will receive a signal * the next time a message arrives. * * We send a message, which causes the notify handler to be invoked. * It is a little silly to have the task that gets the notification * be the one that puts the messages on the queue, but we do it here * to simplify the example. A real application would do other work * instead at this point. */
if (mq_send (exMqId, pMess, 1 + strlen (pMess), 0) == -1)
{
printf ("mq_send failed\n");
return (-1);
}
/* Cleanup */
if (mq_close (exMqId) == -1)
{
printf ("mq_close failed\n");
return (-1);
}
/* More cleanup */
if (mq_unlink (QNAM) == -1)
{
printf ("mq_unlink failed\n");
return (-1);
}
return (0);
}
/* * exNotificationHandle - handler to read in messages * * This routine is a signal handler; it reads in messages from a * message queue. */
static void exNotificationHandle
(
int sig, /* signal number */
siginfo_t * pInfo, /* signal information */
void * pSigContext /* unused (required by posix) */
)
{
struct sigevent sigNotify;
mqd_t exMqId;
/* Get the ID of the message queue out of the siginfo structure. */ exMqId = (mqd_t) pInfo->si_value.sival_int;
/* * Request notification again; it resets each time * a notification signal goes out. */
sigNotify.sigev_signo = pInfo->si_signo;
sigNotify.sigev_value = pInfo->si_value;
sigNotify.sigev_notify = SIGEV_SIGNAL;
if (mq_notify (exMqId, &sigNotify) == -1)
{
printf ("mq_notify failed\n");
return;
}
/* Read in the messages */ exMqRead (exMqId); }
/* * exMqRead - read in messages * * This small utility routine receives and displays all messages * currently in a POSIX message queue; assumes queue has O_NONBLOCK. */
static void exMqRead
(
mqd_t exMqId
)
{
char msg[MSG_SIZE];
int prio;
/* * Read in the messages - uses a loop to read in the messages * because a notification is sent ONLY when a message is sent on * an EMPTY message queue. There could be multiple msgs if, for * example, a higher-priority task was sending them. Because the * message queue was opened with the O_NONBLOCK flag, eventually * this loop exits with errno set to EAGAIN (meaning we did an * mq_receive() on an empty message queue). */
while (mq_receive (exMqId, msg, MSG_SIZE, &prio) != -1)
{
printf ("exMqRead: received message: %s\n",msg);
}
if (errno != EAGAIN)
{
printf ("mq_receive: errno = %d\n", errno);
}
}
The sigqueue( ) routine provides an alternative to kill( ) for sending signals to a task. The important differences between the two are:
VxWorks includes seven signals reserved for application use, numbered consecutively from SIGRTMIN. The presence of these reserved signals is required by POSIX 1003.1b, but the specific signal values are not; for portability, specify these signals as offsets from SIGRTMIN (for example, write SIGRTMIN+2 to refer to the third reserved signal number). All signals delivered with sigqueue( ) are queued by numeric order, with lower-numbered signals queuing ahead of higher-numbered signals.
POSIX 1003.1b also introduced an alternative means of receiving signals. The routine sigwaitinfo( ) differs from sigsuspend( ) or pause( ) in that it allows your application to respond to a signal without going through the mechanism of a registered signal handler: when a signal is available, sigwaitinfo( ) returns the value of that signal as a result, and does not invoke a signal handler even if one is registered. The routine sigtimedwait( ) is similar, except that it can time out.
For detailed information on signals, see the reference entry for sigLib.
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
|
|
|||||||||||||||||||
To configure VxWorks with POSIX queued signals, use the INCLUDE_POSIX_SIGNALS component. This component automatically initializes POSIX queued signals with sigqueueInit( ). The sigqueueInit( ) routine allocates buffers for use by sigqueue( ). which requires a buffer for each currently queued signal. A call to sigqueue( ) fails if no buffer is available.
1: Asynchronous cancellation is actually implemented with a special signal, SIGCANCEL, which users should be careful to not block or ignore.
2: Some host operating systems, such as UNIX, require symbolic names for objects that are to be shared among processes. This is because processes do not normally share memory in such operating systems. In VxWorks, there is no requirement for named semaphores, because all kernel objects have unique identifiers. However, using named semaphores of the POSIX variety provides a convenient way of determining the object's ID.
3: This is not a POSIX routine, nor is it designed for use from programs; use it from the Tornado shell (see the Tornado User's Guide: Shell for details).