VxWorks 6.9 Application Programmer Guide
VxWorks 6.9 Application Programmer Guide
VXWORKS ®
APPLICATION PROGRAMMER'S
GUIDE
6.9
EDITION 11
Copyright Notice
Copyright © 2015 Wind River Systems, Inc.
All rights reserved. No part of this publication may be reproduced or transmitted in any
form or by any means without the prior written permission of Wind River Systems, Inc.
Wind River, Tornado, and VxWorks are registered trademarks of Wind River Systems, Inc.
The Wind River logo is a trademark of Wind River Systems, Inc. Any third-party
trademarks referenced are the property of their respective owners. For further information
regarding Wind River trademarks, please see:
[Link]/company/terms/[Link]
This product may include software licensed to Wind River by third parties. Relevant
notices (if any) are provided in your product installation at one of the following locations:
installDir/product_name/3rd_party_licensor_notice.pdf
installDir/legal-notices/
Wind River may refer to third-party documentation by listing publications or providing
Corporate Headquarters
Wind River
500 Wind River Way
Alameda, CA 94501-1153
U.S.A.
Toll free (U.S.A.): 800-545-WIND
Telephone: 510-748-4100
Facsimile: 510-749-2010
For additional contact information, see the Wind River Web site:
[Link]
For information on how to contact Customer Support, see:
[Link]/support
VxWorks
Application Programmer's Guide
6.9
Edition 11
12 Oct 15
Contents
1 Overview ...................................................................................................... 1
iii
VxWorks
Application Programmer's Guide, 6.9
2.7 Other RTP Virtual Memory Options for 32-Bit VxWorks ....................................... 27
2.7.1 About Default RTP Overlapped Virtual Memory for 32-Bit VxWorks ..... 28
Using Default Overlapped RTP VM ............................................................... 29
2.7.2 Flat RTP Virtual Memory for 32-Bit VxWorks .............................................. 29
System Memory Map with RTPs Running .................................................... 30
Using Flat RTP Virtual Memory ...................................................................... 33
Using RTPs Without MMU Support .............................................................. 33
iv
Contents
v
VxWorks
Application Programmer's Guide, 6.9
3.8 Caveat About Using Show Routines With RTP Applications ................................ 61
vi
Contents
vii
VxWorks
Application Programmer's Guide, 6.9
6 Multitasking ................................................................................................. 99
viii
Contents
ix
VxWorks
Application Programmer's Guide, 6.9
x
Contents
xi
VxWorks
Application Programmer's Guide, 6.9
xii
Contents
11.4 I/O Devices, Named Files, and File Systems ............................................................ 253
xiii
VxWorks
Application Programmer's Guide, 6.9
12.3 Serial I/O Devices: Terminal and Pseudo-Terminal Devices .................................. 272
tty Options .......................................................................................................... 273
12.3.1 Raw Mode and Line Mode .............................................................................. 273
12.3.2 tty Special Characters ....................................................................................... 274
xiv
Contents
xv
VxWorks
Application Programmer's Guide, 6.9
13.6 Transaction-Based Reliable File System Support for dosFs: TRFS ....................... 305
13.6.1 Configuring VxWorks With TRFS ................................................................... 305
13.6.2 Automatic Instantiation of TRFS .................................................................... 305
13.6.3 Using TRFS in Applications ............................................................................ 306
TRFS Code Example ........................................................................................ 306
xvi
Contents
15.3 Configuring VxWorks With RTP Core Dump Support ........................................... 329
Basic Configuration ........................................................................................... 330
Core Dump Compression ................................................................................ 330
xvii
VxWorks
Application Programmer's Guide, 6.9
B.2 About the VxWorks 32-Bit and 64-Bit Data Models ................................................. 349
xviii
Contents
xix
VxWorks
Application Programmer's Guide, 6.9
xx
1
Overview
1.1 Introduction 1
1.2 Kernel Architecture 1
1.3 Related Documentation Resources 2
1.4 VxWorks Configuration and Build 3
1.1 Introduction
This guide describes how to use VxWorks facilities in the development of real-time
systems and applications.
NOTE: This guide provides information about facilities available for real-time
processes. For information about facilities available in the VxWorks kernel, see the
the VxWorks Kernel Programmer’s Guide.
1. Exceptions to this generalization include the VxWorks 5.x optional product VxVMI and the
legacy VxWorks variant VxWorks AE.
1
VxWorks
Application Programmer's Guide, 6.9
kernel and applications. This architecture is often referred to as the process model.
VxWorks has adopted this model with a design specifically aimed to meet the
requirements of determinism and speed that are required for hard real-time
systems. (For information about VxWorks processes and developing applications
to run in processes, see VxWorks Application Programmer’s Guide.) VxWorks 6.x
provides full MMU-based protection of both kernel and user space.
At the same time, VxWorks 6.x maintains a high level of backward compatibility
with VxWorks 5.5. Applications developed for earlier versions of VxWorks, and
designed to run in kernel space, can be migrated to VxWorks 6.x kernel space with
minimal effort (in most cases, merely re-compilation). For more information on
this topic, see the VxWorks Application Programmer’s Guide.
Naturally, new applications can be designed for kernel space as well, when other
considerations outweigh the advantages of protection that executing applications
as processes affords. These considerations might include:
■ Size. The overall size of a system is smaller without components that provided
for processes and MMU support.
■ Speed. Depending on the number of system calls an application might make,
or how much I/O it is doing when running as a process in user space, it might
be faster running in the kernel.
■ Kernel-only features. Features such as watchdog timers, ISRs, and VxMP are
available only in the kernel. In some cases, however, there are alternatives for
process-based applications (POSIX timers, for example).
■ Hardware access. If the application requires direct access to hardware, it can
only do so from within the kernel.
VxWorks is flexible in terms of both the modularity of its features and extensibility.
It can be configured as a minimal kernel, or a full-featured operating system with
user mode applications, file systems, networking, error detection and reporting,
and so on, or anything in between.
The operating system can also be extended by adding custom components or
application modules to the kernel itself (for example, for new file systems,
networking protocols, or drivers). The system call interface can also be extended
by adding custom APIs, which makes them available to process-based
applications.
2
1 Overview
1.4 VxWorks Configuration and Build
The VxWorks networking facilities are documented in the Wind River Network Stack
Programmer’s Guide and the VxWorks PPP Programmer’s Guide.
For information about migrating applications, BSPs, drivers, and projects from
previous versions of VxWorks and the host development environment, see the
VxWorks Migration Guide and the Wind River Workbench Migration Guide.
The Wind River IDE and command-line tools are documented in the Wind River
Workbench by Example guide, your Platform user’s guide, the Wind River compiler
and GNU compiler guides, and the Wind River tools API and command-line
references.
! CAUTION: Code built for variants of VxWorks or for different releases of VxWorks
is not binary compatible between variants or releases. Code must be built
specifically for uniprocessor (UP) VxWorks, VxWorks SMP, 32-bit VxWorks, 64-bit
VxWorks, or for a VxWorks system based on variant libraries produced with a
VxWorks source build (VSB) project—or for any supported combination of these
variants. The only exception to this rule is that RTP application executables can be
run on both UP VxWorks and VxWorks SMP (when all else is the same).
3
VxWorks
Application Programmer's Guide, 6.9
4
2
Real-Time Processes: RTPs
2.1 Introduction 5
2.2 About Real-time Processes 6
2.3 Configuring VxWorks For Real-time Processes 14
2.4 About RTP Overlapped Virtual Memory 16
2.5 RTP Overlapped Virtual Memory for 32-Bit VxWorks 19
2.6 RTP Overlapped Virtual Memory for 64-Bit VxWorks 27
2.7 Other RTP Virtual Memory Options for 32-Bit VxWorks 27
2.1 Introduction
VxWorks real-time processes (RTPs) are in many respects similar to processes in
other operating systems—such as UNIX and Linux—including extensive POSIX
compliance.1 The ways in which they are created, execute applications, and
terminate will be familiar to developers who understand the UNIX process model.
The VxWorks process model is, however, designed for use with real-time
embedded systems. The features that support this model include system-wide
scheduling of tasks (processes themselves are not scheduled), preemption of
processes in kernel mode as well as user mode, process-creation in two steps to
separate loading from instantiation, and loading applications in their entirety.
VxWorks real-time processes provide the means for executing applications in user
mode. Each process has its own address space, which contains the executable
program, the program’s data, stacks for each task, the heap, and resources
associated with the management of the process itself (such as memory-allocation
tracking). Many processes may be present in memory at once, and each process
may contain more than one task (sometimes known as a thread in other operating
systems).
For 64-bit VxWorks, RTP virtual memory is overlapped, and RTP applications are
built as absolutely-linked executables. For 32-bit VxWorks, the default is simply
1. VxWorks can be configured to provide POSIX PSE52 support for individual processes.
5
VxWorks
Application Programmer's Guide, 6.9
RTP overlapped virtual memory, but Wind River recommends taking additional
configuration steps to support RTPs as absolutely-linked executables. The legacy
flat RTP virtual memory configuration can also be used with 32-bit VxWorks.
For information about developing RTP applications, see 2. Real-Time Processes:
RTPs.
The primary way in which VxWorks processes support determinism is that they
themselves are simply not scheduled. Only tasks are scheduled in VxWorks
6
2 Real-Time Processes: RTPs
2.2 About Real-time Processes
The manner in which real-time processes are created supports the determinism
required of real-time systems. The creation of an RTP takes place in two distinct
phases, and the executable is loaded in its entirety when the process is created. In
the first phase, the rtpSpawn( ) call creates the process object in the system,
allocates virtual and physical memory to it, and creates the initial process task (see
2.2.5 RTPs and Tasks, p.9). In the second phase, the initial process task loads the
entire executable and starts the main routine.
This approach provides for system determinism in two ways:
■ First, the work of process creation is divided between the rtpSpawn( ) task and
the initial process task—each of which has its own distinct task priority. This
means that the activity of loading applications does not occur at the priority,
or with the CPU time, of the task requesting the creation of the new process.
Therefore, the initial phase of starting a process is discrete and deterministic,
regardless of the application that is going to run in it. And for the second
phase, the developer can assign the task priority appropriate to the
significance of the application, or to take into account necessarily
indeterministic constraints on loading the application (for example, if the
application is loaded from networked host system, or local disk). The
application is loaded with the same task priority as the priority with which it
will run. In a way, this model is analogous to asynchronous I/O, as the task
that calls rtpSpawn( ) just initiates starting the process and can concurrently
perform other activities while the application is being loaded and started.
■
Second, the entire application executable is loaded when the process is created,
which means that the determinacy of its execution is not compromised by
incremental loading during execution. This feature is obviously useful when
systems are configured to start applications automatically at boot time—all
executables are fully loaded and ready to execute when the system comes up.
The rtpSpawn( ) routine has an option that provides for synchronizing for the
successful loading and instantiation of the new process.
At startup time, the resources internally required for the process (such as the heap)
are allocated on demand. The application's text is guaranteed to be
7
VxWorks
Application Programmer's Guide, 6.9
8
2 Real-Time Processes: RTPs
2.2 About Real-time Processes
Note that if a process fails while a shell is running, a message is printed to the shell
console. Error messages can be recorded with the VxWorks error detection and
reporting facilities (see 14. Error Detection and Reporting).
For information about attribute inheritance and what happens to a process’
resources when it terminates, see 2.2.7 RTPs, Inheritance, Zombies, and Resource
Reclamation, p.10.
Each process has its own address space, which contains the executable program,
the program's data, stacks for each task, the heap, and resources associated with
the management of the process itself (such as local heap management). Many
processes may be present in memory at once. For information about virtual
memory management, see 2.5 RTP Overlapped Virtual Memory for 32-Bit VxWorks,
p.19, 2.7 Other RTP Virtual Memory Options for 32-Bit VxWorks, p.27, and 2.6 RTP
Overlapped Virtual Memory for 64-Bit VxWorks, p.27.
Memory Protection
Each process is protected from any other process that is running on the system,
whenever the target system has an MMU, and MMU support has been configured
into VxWorks. Operations involving the code, data, and memory of a process are
accessible only to code executing in that process. It is possible, therefore, to run
several instances of the same application in separate processes without any
undesired side effects occurring between them. The name and symbol spaces of
the kernel and processes are isolated.
As processes run a fully linked image without external references, a process cannot
call a routine in another process, or a kernel routine that is not exported as a system
call—whether or not the MMU is enabled. However, if the MMU is not enabled
(which can only be done with the flat RTP virtual memory configuration on 32-bit
VxWorks), a process can read and write memory external to its own address space,
and could cause the system to malfunction.
VxWorks can run many processes at once, and any number of processes can run
the same application executable. That is, many instances of an application can be
run concurrently.
For general information about tasks, see 6. Multitasking.
Each process can execute one or more tasks. When a process is created, the system
spawns a single task to initiate execution of the application. The application may
then spawn additional tasks to perform various functions. There is no limit to the
number of tasks in a process, other than that imposed by the amount of available
memory. Similarly, there is no limit to the number of processes in the system—but
only for architectures that do not have (or do not use) a hardware mechanism that
manages concurrent address spaces (this mechanism is usually known as an
address space identifier, or ASID). For target architectures that do use ASIDs or
9
VxWorks
Application Programmer's Guide, 6.9
Task creation includes allocation of space for the task's stack from process memory.
As needed, memory is automatically added to the process as tasks are created from
the kernel free memory pool.
Heap management routines are available in user-level libraries for tasks in
processes. These libraries provide the various ANSI APIs such as malloc( ) and
free( ). The kernel provides a pool of memory for each process in user space for
these routines to manage.
Providing heap management in user space provides for speed and improved
performance because the application does not incur the overhead of a system call
for memory during its execution. However, if the heap is exhausted the system
automatically allocates more memory for the process (by default), in which case a
system call is made. Environment variables control whether or not the heap grows
(see 10.2 About VxWorks Memory Allocation Facilities, p.230).
While the address space of each process is invisible to tasks running in other
processes, tasks can communicate across process boundaries through the use of
various IPC mechanisms (including public semaphores, public message queues,
and message channels) and shared data memory regions. See 7.8 Inter-Process
Communication With Public Objects, p.154 and 3.6 Creating and Using Shared Data
Regions, p.47 for more information.
10
2 Real-Time Processes: RTPs
2.2 About Real-time Processes
Inheritance
VxWorks processes inherit certain attributes of their parent. The child process
inherits the file descriptors (stdin, stdout and stderr) of its parent process—which
means that they can access the same files (if they are open), and signal masks. If the
child process is started by the kernel, however, then the child process inherits only
the three standard file descriptors. Environment variables are not inherited, but the
parent can pass its environment, or a sub-set of it, to the child process (for
information in this regard, see 2.2.8 RTPs and Environment Variables, p.12).
While the signal mask is not actually a property of a process as such—it is a
property of a task—the signal mask for the initial task in the process is inherited
from the task that spawned it (that is, the task that called the rtpSpawn( ) routine).
If the kernel created the initial task, then the signal mask is zero, and all signals are
unblocked.
The getppid( ) routine returns the parent process ID. If the parent is the kernel, or
the parent is dead, it returns NULL.
Zombie Processes
By default, when a process is terminated, and its parent is not the kernel, it
becomes a zombie process.2
In order to respond to a SIGCHLD signal (which is generated whenever a child
process is stopped or exits, or a stopped process is started) and get the exit status
of the child process, the parent process must call wait( ) or waitpid( ) before the
child exits or is stopped. In this case the parent is blocked waiting. Alternatively,
the parent can set a signal handler for SIGCHLD and call wait( ) or waitpid( ) in the
signal handler. In this case the parent is not blocked. After the parent process
receives the exit status of a child process, the zombie entity is deleted
automatically.
The default behavior with regard to zombies can be modified in the following
ways:
■
By leaving the parent process unaware of the child process’ termination, and
not creating a zombie. This is accomplished by having the parent process
ignore the SIGCHLD signal. To do so, the parent process make a sigaction( )
call that sets the SIGCHLD signal handler to SIG_IGN.
■
By not transforming a terminating child process into a zombie when it exits.
This is accomplished having the parent process make a sigaction( ) call that
sets the sa_flag to SA_NOCLDWAIT.
2. A zombie process is a “process that has terminated and that is deleted when its exit status
has been reported to another process which is waiting for that process to terminate.” (The
Open Group Base Specifications Issue 6, IEEE Std 1003.1, 2004 Edition.)
11
VxWorks
Application Programmer's Guide, 6.9
Resource Reclamation
When a process terminates, all resources owned by the process (objects, data, and
so on) are returned to the system. The resources used internally for managing the
process are released, as are all resources owned by the process. All information
about that process is eliminated from the system (with the exception of any
temporary zombie process information). Resource reclamation ensures that all
resources that are not in use are immediately returned to the system and available
for other uses.
Note, however, that there are exceptions to this general rule:
■
Public objects—which may be referenced by tasks running in other processes
that continue to run—must be explicitly deleted.
■ Socket objects can persist for some time after a process is terminated. They are
reclaimed only when they are closed, which is driven by the nature of the
TCP/IP state machine. Some sockets must remain open until timeout is
reached.
■ File descriptors are reclaimed only when all references to them are closed. This
can occur implicitly when all child processes—which inherit the descriptors
from the parent process—terminate. It can also happen explicitly when all
applications with references to the file descriptors close them.
For information about object ownership, and about public and private objects, see
7.8 Inter-Process Communication With Public Objects, p.154.
12
2 Real-Time Processes: RTPs
2.2 About Real-time Processes
For more information, see the rtpSpawn( ) API reference and 3.4.1 RTP Application
Structure, p.38 for details.
A task in a process (or in an application library) can create, reset, and remove
environment variables in a process. The getenv( ) routine can be used to get the
environment variables, and the setenv( ) and unsetenv( ) routines to change or
remove them. The environment array can also be manipulated directly—however,
Wind River recommends that you do not do so, as this bypasses the thread-safe
implementation of getenv( ), setenv( ) and putenv( ) in the RTP environment.
13
VxWorks
Application Programmer's Guide, 6.9
NOTE: The default VxWorks configuration for hardware targets does not include
support for running applications in real-time processes (RTPs). VxWorks must be
re-configured and rebuilt to provide these process facilities. The default
configuration of the VxWorks simulator does, however, include full support for
running RTP applications.
The reason that the default configuration of VxWorks (for hardware targets) does
not include process support, is that it facilitates migration of VxWorks 5.5
kernel-based applications to VxWorks 6.x by providing functionally the same basic
set of kernel components, and nothing more.
VxWorks 6.x systems can be created with kernel-based applications and without
any process-based applications, or with a combination of the two. Kernel
applications, however, cannot be provided the same level of protection as
process-based applications. When applications run in kernel space, both the kernel
and those applications are subject to any misbehavior on the part application code.
For more information about kernel-based applications, see the VxWorks Kernel
Programmer’s Guide: Kernel.
14
2 Real-Time Processes: RTPs
2.3 Configuring VxWorks For Real-time Processes
By default, configuring VxWorks for RTPs (with INCLUDE_RTP) provides the basic
RTP overlapped virtual memory. For information about RTP virtual memory
configuration options, see 2.7 Other RTP Virtual Memory Options for 32-Bit VxWorks,
p.27.
The following components provide useful facilities for both development and
deployed systems:
■ INCLUDE_ROMFS for the ROMFS file system.
■ INCLUDE_RTP_APPL_USER, INCLUDE_RTP_APPL_INIT_STRING,
INCLUDE_RTP_APPL_INIT_BOOTLINE, and
INCLUDE_RTP_APPL_INIT_CMD_SHELL_SCRIPT for various ways of
automatically starting applications at boot time.
■ INCLUDE_SHARED_DATA for shared data regions.
■ INCLUDE_SHL for shared libraries.
■ INCLUDE_RTP_HOOKS for the programmatic hook facility, which allows for
registering kernel routines that are to be executed at various points in a
process’ life-cycle.
■ INCLUDE_POSIX_PTHREAD_SCHEDULER and INCLUDE_POSIX_CLOCKS for
POSIX thread support. This replaces the traditional VxWorks scheduler with a
scheduler handling user threads in a manner consistent with POSIX.1.
VxWorks tasks as well as kernel pthreads are handled as usual. Note that the
INCLUDE_POSIX_PTHREAD_SCHEDULER is required for using pthreads in
processes. For more information, see Clock Selection, p.197.
■
INCLUDE_PROTECT_TASK_STACK for stack protection. For deployed systems
this component may be omitted to save on memory usage. See 6.5.5 Task Stack,
p.116 for more information.
The following components provide facilities used primarily in development
systems, although they can be useful in deployed systems as well:
■ The various INCLUDE_SHELL_feature components for the kernel shell, which,
although not required for applications and processes, are needed for running
applications from the command line, executing shell scripts, and on-target
debugging.
■ The INCLUDE_WDB component for using the host tools.
15
VxWorks
Application Programmer's Guide, 6.9
■
Either the INCLUDE_NET_SYM_TBL or the
INCLUDE_STANDALONE_SYM_TBL component, which specify whether
symbols for the shell are loaded or built-in.
■ The INCLUDE_DISK_UTIL and INCLUDE_RTP_SHOW components, which
include useful shell routines.
For information about the kernel shell, symbol tables, and show routines, see the
Wind River Workbench Host Shell User’s Guide.
Component Bundles
For information about configuring and building VxWorks, see the Wind River
Workbench by Example guide and your Platform user’s guide.
Note that the VxWorks simulator includes all of the basic components required for
processes by default.
16
2 Real-Time Processes: RTPs
2.4 About RTP Overlapped Virtual Memory
The rtpMemShow( ) show routine or the rtp meminfo command can be used to
display the private mappings of an RTP. These are included with the
INCLUDE_RTP_SHOW and INCLUDE_RTP_SHOW_SHELL_CMD components,
respectively.
For information about the specifics of RTP overlapped virtual memory for 32-bit
and 64-bit VxWorks, see 2.5 RTP Overlapped Virtual Memory for 32-Bit VxWorks, p.19
and 2.6 RTP Overlapped Virtual Memory for 64-Bit VxWorks, p.27.
! CAUTION: For architectures that do not have (or use) a hardware mechanisms that
handles concurrent address spaces (usually known as an address space identifier,
or ASID), the overlapped virtual memory model also makes a system susceptible
to performance degradation. This is basically because each time a RTP's task is
switched out and another kernel or RTP task is switched in the entire cache must
be flushed and reloaded with the new task's context. With the ASID mechanism
this flushing is not (or less often) necessary because the cache is indexed using the
ASID. The hardware therefore knows when such a flushing is required or not.
17
VxWorks
Application Programmer's Guide, 6.9
VxWorks Overlapped Virtual Memory Model Compared With Other Operating Systems
18
2 Real-Time Processes: RTPs
2.5 RTP Overlapped Virtual Memory for 32-Bit VxWorks
2.5.2 Using Overlapped RTP VM for Absolutely-Linked Executables with 32-Bit VxWorks
RTP overlapped virtual memory is the default when VxWorks is configured with
RTP support (the RTP_OVERLAPPED_ADDRESS_SPACE parameter is set to TRUE).
Performance can be significantly enhanced, however, if VxWorks is also
configured to take advantage of absolutely-linked executables. For information
19
VxWorks
Application Programmer's Guide, 6.9
about the advantages of doing so, see 2.5.1 About Overlapped RTP VM for
Absolutely-Linked Executables, p.19.
To make use of RTP overlapped virtual memory with absolutely-linked
executables, you must do the following:
■
Configure an RTP code region for overlapped RTP application code in virtual
memory.
■
Define the link address for RTP applications.
■
Build the RTP applications as absolutely-linked executables.
In order to properly configure 32-bit VxWorks for absolutely-linked executables,
and to set the link address of those executables, you must have an understanding
of user regions, and how one of those regions is used for the area of overlapped
virtual memory called the RTP code region. This background information is
provided in About User Regions and the RTP Code Region, p.20.
The process of implementing the RTP overlapped virtual memory with an RTP
code region first involves getting information about the user regions that are
available in a system, selecting the RTP code region based on the available user
regions and RTP application requirements, and configuring VxWorks accordingly.
These steps are described in Defining the RTP Code Region, p.22.
In order for RTP applications to make use of this special 32-bit VxWorks
configuration, they must be built as absolutely-linked executables. Selection of the
link address and the required compiler options are described in 3.4.11 Using
Absolutely-Linked RTP Applications, p.45.
The virtual address space of a process in 32-bit VxWorks does not correspond to a
contiguous range of address from 0 to 4 GB. It is generally composed of several
discontinuous blocks of virtual memory.
The blocks of virtual memory that are available for RTPs (that is, not used by the
kernel) are referred to as user regions. User regions are used for the RTP
applications’ text, data, and bss segments, as well as for their heaps and task stacks.
In addition, user regions are used for shared data regions, shared libraries, and so
on. Figure 2-1 illustrates an example of virtual memory layout in VxWorks. It
includes an area of User Region 2 that has been configured for the RTP code region,
which is illustrated in more detail in Figure 2-2.
20
2 Real-Time Processes: RTPs
2.5 RTP Overlapped Virtual Memory for 32-Bit VxWorks
Only one continuous area of the virtual address space of a process can be used to
overlap RTP application code, and this area must correspond—fully or in part—to
one of the user regions available in the target system’s memory (that is, in the state
of the system after it has been booted but before any RTP has been spawned.)
The user region that you select for the overlap is referred to as the RTP code region.
The base address and size of the RTP code region are defined by the user when the
system is configured.
Figure 2-2 illustrates the virtual memory layout of a system running three
applications, with the same three user regions as in Figure 2-1.
21
VxWorks
Application Programmer's Guide, 6.9
In this case, VxWorks has been configured to use a part of the largest of the three
user regions depicted in Figure 2-1—user region 2—for the RTP code region,
because the others were too small for the code (text, data, and bss segments) of the
largest application (RTP C). As the heap for RTP C would not fit in the RTP code
region, the best-fit algorithm automatically placed it in User Region 1 in order to
leave a larger area for other purposes in User Region 2 (a shared library).
Note that in this example the size of the RTP Code Region is defined to be slightly
larger than the size of the text, data, and bss segments of that application.
The system illustrated in Figure 2-2 includes a shared data region used by RTP A
and RTP C, which map the region into their memory context. The location of
shared data regions is determined automatically at runtime on the basis of a
best-fit algorithm when they are created (for information shared data regions, see
3.6 Creating and Using Shared Data Regions, p.47). Note that RTP B is not allowed to
make use of the virtual addresses in the shared data region, even though the
application does not make use of it.
For information about the configuration parameters that define location and size
of the RTP code region (RTP_CODE_REGION_START and
RTP_CODE_REGION_SIZE), see Setting Configuration Parameters for the RTP Code
Region, p.26.
To define the RTP code region in 32-bit VxWorks for absolutely-linked RTP
applications, you must determine which user region is suitable for your
22
2 Real-Time Processes: RTPs
2.5 RTP Overlapped Virtual Memory for 32-Bit VxWorks
applications, and then reconfigure and rebuild VxWorks. This process involves the
following basic steps:
1. Boot an instance of the default VxWorks and get information about the
available user regions.
2. Identify the RTP code region (size and base address), based on the available
user regions and the requirements of your RTP applications.
3. Configure VxWorks for RTP overlapped virtual memory and specify the RTP
code region in that configuration. Then rebuild the system.
These steps are described in detail in the following sections.
Before you can configure VxWorks with an RTP code region, you must determine
what range of virtual memory is available for this purpose. To do so, boot an
instance of VxWorks that has been configured with RTP support, and get a listing
of the user regions that are available on the target.
The adrSpaceShow( ) kernel shell command (in verbose mode) can be used to list
user regions. For example:
-> adrSpaceShow 1
23
VxWorks
Application Programmer's Guide, 6.9
This output shows—at the end of the User Region Virtual Space Info section—that
the following two user regions are available on this target:
■ from 0x10000000 to 0x20000000 (that is, 0x10000000 + 0x10000000)
■ from 0x30000000 to 0xf0000000 (that is, 0x30000000 + 0xc0000000)
Either region can be used for the RTP code region, depending on the requirements
of the system.
The following guidelines should be used when defining the RTP code region for
32-bit VxWorks:
■ The size of the RTP code region should be slightly larger than the combined
size of the text, data, and bss segments of the largest RTP application that will
run on the system. In addition, the text and data segments must be page
aligned. See RTP Code Region Size, p.25.
■ The RTP code region must fit in one user region—it cannot span multiple user
regions. Select a user region that allows the system to accommodate all shared
libraries, shared data regions, and other public mappings that will occur at
runtime. See User Region Choice, p.25.
■ For simplicity sake, set the base address of the RTP code region to the base
address of a user region. See RTP Code Region Base Address Choice, p.25.
! CAUTION: If 32-bit VxWorks is configured for RTP overlapped virtual memory (the
default), the following behaviors apply:
■
If the start address and size of the RTP Code Region are not defined,
absolutely-linked executables will be relocated.
■
If the RTP Code Region is defined but its size is too small, absolutely-linked
executables will be relocated (possibly into another user region if there is
sufficient space there).
■
If there is not sufficient space in any user region, the RTP launch will fail.
■
If the executables are stripped and will not fit in the RTP Code Region, they
cannot be relocated regardless of the availability of space in any user region,
and the RTP launch will fail.
24
2 Real-Time Processes: RTPs
2.5 RTP Overlapped Virtual Memory for 32-Bit VxWorks
The RTP code region should be slightly larger than the combined size of the text,
data, and bss segments of the largest RTP application that will run on the system.
Allowing a bit of extra room allows for a moderate increase in the size of the
applications that you will run on the system. The readelfarch tool (for example,
readelfppc) can be used to determine the size of the text, data, and bss segments.
In addition, the page alignments of the text and data segments must be taken into
account for (the bss is already aligned by virtues of its being included in the data
segment's size). As a basic guideline for the alignment, use readelfarch -l (for
example, readelfppc), and round up to one page for each of the segment’s sizes
displayed in the MemSiz fields. The page size for the target architecture can be
checked via the vmPageSizeGet( ) routine, which can be called directly from the
target shell.
For example, for a text segment with a MemSiz of 0x16c38, the round up value
would be 0x17000; for a data segment with a MemSiz field (do not use the FileSiz
field, which is much smaller as it does not account for the bss area) of 0x012a8, the
round up value would be 0x02000. The sum of the rounded values for the two
segments would then be 0x19000.
The RTP code region cannot span user regions. It must fit in one user region.
While it may be tempting to select the largest user region for the RTP code region
in order to accommodate the largest possible RTP application that the system
might run, this may not leave enough room in the other user regions to
accommodate all of the shared data regions, shared libraries, or other public
mappings required by the system.
In selecting the user region to use for the RTP code region, take into consideration
number and size of the shared data regions, shared libraries and other public
mappings—such as those created by mmap( )—that are going to be used in the
system. For 32-bit VxWorks, the RTP's text + data + heap essentially competes for
room in the user regions with the shared data regions and so on. So if the RTP code
region is too large, it might be impossible to create shared data areas (either
directly or by the loading of shared libraries) of either the desired size or in desired
number, in the space remaining in the user regions.
The RTP code region and public mappings are mutually exclusive because the RTP
code region is intended to hold the text, data, and bss segments of
absolutely-linked executables that cannot be relocated. However, public mappings
appear at the same address in all the RTPs that may want to use them (by design).
Since the location of the public mappings in virtual memory is not controlled by
the applications themselves, and since VxWorks applies a best-fit algorithm when
allocating virtual memory for a public mappings, there would risk of blocking out
a range of virtual addresses at a location meant to be used by an absolutely-linked
application's text, data, and bss segments if public mappings were allowed in the
RTP code region.
For simplicity sake, set the base address of the RTP code region to the base address
of a user region. If the RTP code region is set elsewhere in the user region, make
sure that its base address is page aligned. The page size for the target architecture
25
VxWorks
Application Programmer's Guide, 6.9
can be checked via the vmPageSizeGet( ) routine, which can be called directly
from the target shell.
In order to configure 32-bit VxWorks with an RTP code region, the parameters
listed below must be set appropriately. They serve to define the RTP code region in
virtual memory.
26
2 Real-Time Processes: RTPs
2.6 RTP Overlapped Virtual Memory for 64-Bit VxWorks
The contents of the local CDF file (called, for example, [Link])
would look like this:
Parameter RTP_CODE_REGION_START {
DEFAULT 0xffc00000
}
Parameter RTP_CODE_REGION_SIZE {
DEFAULT 0x100000
}
For information about CDF files, file naming conventions, precedence of files, and
so on, see the VxWorks Custom Component and CDF Developer's Guide.
27
VxWorks
Application Programmer's Guide, 6.9
! CAUTION: For architectures that do not have (or use) a hardware mechanisms that
handles concurrent address spaces (usually known as an address space identifier,
or ASID), the overlapped virtual memory model also makes a system susceptible
to performance degradation. This is basically because each time a RTP's task is
switched out and another kernel or RTP task is switched in the entire cache must
be flushed and reloaded with the new task's context. With the ASID mechanism
this flushing is not (or less often) necessary because the cache is indexed using the
ASID. The hardware therefore knows when such a flushing is required or not.
2.7.1 About Default RTP Overlapped Virtual Memory for 32-Bit VxWorks
By default, 32-bit VxWorks is configured with support for RTP overlapped virtual
memory. Wind River recommends that you take the additional configuration steps
required to take advantage of the performance enhancements that are provided by
absolutely-linked RTP executables. The advantages of doing so, and the additional
steps required, are discussed 2.5 RTP Overlapped Virtual Memory for 32-Bit VxWorks,
p.19.
When VxWorks uses the default configuration of RTP overlapped virtual memory
without support for absolutely-linked executables, the system overlaps RTP
applications in virtual memory as much as possible—using a best-fit algorithm to
make efficient use of the virtual memory space. In addition to holding the RTP
application's text, data, and bss segments, the overlapped area of virtual memory
is used for any elements that are private to a process, such as its heap and task
stacks (as long as there is enough room in the overlapped area—otherwise they are
placed elsewhere). The overlapped area is not, however, used for elements that are
designed to be accessible to other RTPs, such as shared data regions and shared
libraries. For an example, see Figure 2-3.
RTP executables are relocated, whether or not they are absolutely-linked (as with
the flat RTP virtual memory configuration). Depending on the size of the RTP
applications, and order of their creation, the application’s code (text and data
segments), heap, and private mappings may be located at different addresses.
In contrast to the flat RTP virtual memory configuration, the default overlapped
RTP memory configuration makes more efficient use of virtual memory by
reducing the possibility of virtual memory fragmentation and by reducing the
chance or running out of virtual memory when large numbers of applications are
started. It does not, however, provide the full advantages of RTP overlapped
virtual memory with support for absolutely-linked executables (which provides
faster loading times and so on).
For information about using the default configuration, see Using Default Overlapped
RTP VM, p.29 and Figure 2-2.
28
2 Real-Time Processes: RTPs
2.7 Other RTP Virtual Memory Options for 32-Bit VxWorks
Figure 2-3 Virtual Memory Layout With Default RTP Overlapped Virtual Memory
NOTE: While using the default for RTP overlapped virtual memory is convenient,
Wind River recommends taking the additional steps required for using RTP
overlapped virtual memory with absolutely-linked executables. For information
about the advantages, see 2.5.1 About Overlapped RTP VM for Absolutely-Linked
Executables, p.19.
RTP overlapped virtual memory is the default when VxWorks is configured with
RTP support (the RTP_OVERLAPPED_ADDRESS_SPACE parameter is set to TRUE).
For information about the default features, see 2.7.1 About Default RTP Overlapped
Virtual Memory for 32-Bit VxWorks, p.28.
To use the default RTP overlapped virtual memory facilities, simply build and
execute RTP applications (relocatable executables are built by default).
32-bit VxWorks optionally can be configured with flat RTP virtual memory. With
this configuration each VxWorks RTP has its own region of virtual memory—RTPs
do not overlap in virtual memory. Flat RTP virtual memory provides the following
advantages:
■
Speed—context switching is fast.
29
VxWorks
Application Programmer's Guide, 6.9
■
Ease of debugging—the addresses for each process are unique.
■
A flexible programming model that provides the same process-model
regardless of MMU support. VxWorks’ application memory model allows for
running the same applications with and without an MMU. Hard real-time
determinism can be facilitated by using the same programming model, and by
disabling the MMU.
Systems can be developed and debugged on targets with an MMU, and then
shipped on hardware that does not have one, or has one that is not enabled for
deployment. The advantages of being able to do so include facilitating
debugging in development, lower cost of shipped units, as well as footprint
and performance advantages of targets without an MMU, or with one that is
not enabled. (For information in this regard, see Using RTPs Without MMU
Support, p.33.)
With flat RTP virtual memory, however, executable files must be relocated, which
means that their loading time is slower than when RTP overlapped virtual
memory is used with absolutely-linked executables.
In addition, flat RTP virtual memory does not allow for selection of the specific
virtual memory area into which the RTP application's text, data, and bss segments
are installed. Instead a best-fit algorithm automatically selects the most
appropriate area of free virtual memory, based on the memory's current usage,
where those segments are to be loaded.
A VxWorks system configured for RTPs may have one or more applications
executing as RTPs at run-time. It may also have shared libraries and shared data
regions instantiated. The kernel, each of the RTPs, shared libraries, and shared data
regions occupy a discrete space in virtual memory.
The virtual space assigned to a RTP is not necessarily composed of one large
contiguous block of virtual memory. In some cases it will be composed of several
smaller blocks of virtual space which are discontinuous from each other.
Figure 2-4 illustrates the memory map of a system with the kernel areas (RAM and
I/O), two different RTPs (RTP A and RTP B), as well as one shared library, and one
shared data region.
30
2 Real-Time Processes: RTPs
2.7 Other RTP Virtual Memory Options for 32-Bit VxWorks
Each RTP has its own virtual memory context, defined by its MMU translation
table used to map virtual and physical memory, and other information about each
page of memory. This memory context describes the virtual space that all of the
tasks in an RTP can access. In other words, it defines the memory view of an RTP.
The kernel space is mapped with supervisor access privilege in the memory
context of each RTP (but not with user mode privilege). Therefore tasks executing
in an RTP can access kernel memory space only in system calls, during which the
execution is switched to supervisor mode.
A shared library or shared data region is mapped into the virtual context of an RTP
only when the RTP’s application code opens or creates it, and it effectively
disappears from the RTP’s memory view when the application closes or deletes the
shared library or shared data region.
31
VxWorks
Application Programmer's Guide, 6.9
Figure 2-5 illustrates the different memory views of a system with two RTPs (RTP
A and RTP B), a shared library that both RTP A and RTP B opened, as well as a
shared data region that both a kernel application and RTP B opened.
The first memory view corresponds to the memory space accessible by kernel
tasks. The second and third memory views correspond to the memory space
accessible by tasks executing in RTP A and RTP B, respectively. Note that the
grayed areas are only accessible during system calls.
Note that on system without an MMU, or with the MMU disabled, there is only
one memory view shared by the kernel and all RTP tasks. This memory view
corresponds to Figure 2-4. Any task in the system, whether it is a kernel or a task
executing in an RTP, has access to all the memory: kernel space, I/O regions, any
32
2 Real-Time Processes: RTPs
2.7 Other RTP Virtual Memory Options for 32-Bit VxWorks
RTPs’ memory, shared libraries, and shared data regions. In other words, such
configurations do not provide any memory protection.
In order to use flat RTP virtual memory with 32-bit VxWorks, the INCLUDE_RTP
component parameter RTP_OVERLAPPED_ADDRESS_SPACE must be set to
FALSE.
When VxWorks is configured for flat RTP virtual memory, applications are
relocated, regardless of whether or not they are built as absolutely-linked
executables, and each application is installed in a unique space in virtual memory.
33
VxWorks
Application Programmer's Guide, 6.9
■
There is no memory protection. That is, memory cannot be write-protected,
and neither the kernel or any process are protected from other processes.
■ The address space is limited to the available system RAM, which is typically
smaller than it would be available on systems with MMU-based address
translation enabled. Because of the smaller address space, a system is more
likely to run out of large contiguous blocks of memory due to fragmentation.
■
Not all processors and target boards can be used with the MMU disabled. For
the requirements of your system see the hardware manual of the board and
processor used.
For information about architecture and processor-specific limitations, see the
VxWorks Architecture Supplement.
There are no special components needed for the process environment with
software-simulated paging. As with any configurations that provide process
support, the INCLUDE_RTP component must be added to the kernel.
The steps required to enable software-simulated paging are:
1. Add the INCLUDE_RTP component to include process support. This
automatically includes all dependent subsystems, among them
INCLUDE_MMU_BASIC.
2. Change the SW_MMU_ENABLE parameter of the INCLUDE_MMU_BASIC
component to TRUE (the default value is FALSE).
In addition, the following optional configuration steps can reduce the footprint of
the system:
3. Change the VM_PAGE_SIZE parameter of the INCLUDE_MMU_BASIC
component. The default is architecture-dependent; usually 4K or 8K. Allowed
values are 1K, 2K, 4K, 8K, 16K, 32K, 64K. Typically, a smaller page size results
in finer granularity and therefore more efficient use of the memory space.
However, smaller page size requires more memory needed for keeping track
the mapping information.
4. Disable stack guard page protection by changing the
TASK_STACK_OVERFLOW_SIZE and TASK_STACK_UNDERFLOW_SIZE
configuration parameters to zero. Without protection provided by an MMU,
stack overflow and underflow cannot be detected, so the guard pages serve no
purpose.
5. Remove the following components from the VxWorks configuration:
INCLUDE_KERNEL_HARDENING, INCLUDE_PROTECT_TEXT,
INCLUDE_PROTECT_VEC_TABLE, INCLUDE_PROTECT_TASK_STACK,
INCLUDE_TASK_STACK_NO_EXEC, and
INCLUDE_PROTECT_INTERRUPT_STACK. Without an MMU, these features
do not work. Including them only results in unnecessary consumption of
resources.
34
3
RTP Applications
3.1 Introduction 35
3.2 Comparing Kernel Applications with RTP Applications 36
3.3 Configuring VxWorks For RTP Applications 37
3.4 Developing RTP Applications 37
3.5 Developing Static Libraries, Shared Libraries and Plug-Ins 47
3.6 Creating and Using Shared Data Regions 47
3.7 Executing RTP Applications 50
3.8 Caveat About Using Show Routines With RTP Applications 61
3.9 Bundling RTP Applications in a System using ROMFS 61
3.1 Introduction
Real-time process (RTP) applications are user-mode applications similar to those
used with other operating systems, such as UNIX and Linux. This chapter
provides information about writing RTP application code, using shared data
regions, executing applications, and so on. For information about multitasking,
I/O, file system, and other features of VxWorks that are available to RTP
applications, see the respective chapters in this guide.
Before you begin developing RTP applications, you should understand the
behavior of RTP applications in execution—that is, as processes. For information
about RTP scheduling, creation and termination, memory, tasks and so on, see
2. Real-Time Processes: RTPs.
For information about using Workbench and the command-line build
environment for developing RTP applications, see the Wind River Workbench by
Example guide and your Platform user’s guide, respectively.
For information about developing kernel-mode applications (which execute in
VxWorks kernel space) see the VxWorks Kernel Programmer’s Guide: Kernel
Applications.
35
VxWorks
Application Programmer's Guide, 6.9
36
3 RTP Applications
3.3 Configuring VxWorks For RTP Applications
37
VxWorks
Application Programmer's Guide, 6.9
In addition, applications can be bundled into a single image with the operating
system using the ROMFS file system (see 3.9 Bundling RTP Applications in a System
using ROMFS, p.61). The ROMFS technology is particularly useful for deployed
systems. It allows developers to bundle application executables with the VxWorks
image into a single system image. Unlike other operating systems, no root file
system (on NFS or diskette, for example) is required to hold application binaries,
configuration files, and so on.
For information about the special requirements of applications that use shared
libraries and plug-ins, see 4.9 Developing RTP Applications That Use Shared Libraries,
p.84 and 4.11 Developing RTP Applications That Use Plug-Ins, p.86.
RTP applications can be used for both the uniprocessor (UP) and symmetric
multiprocessing (SMP) configurations of VxWorks. They must, however, only use
the subset of APIs provided by VxWorks SMP, use the __thread storage class
instead of tlsLib routines, and be compiled specifically for the system in question
(SMP or UP).
For more information about using the SMP configuration of VxWorks, and about
migrating applications from VxWorks UP to VxWorks SMP, see the VxWorks Kernel
Programmer’s Guide: VxWorks SMP.
38
3 RTP Applications
3.4 Developing RTP Applications
Environment variables are typically inherited from the calling environment, and
can be set programmatically. Use the getenv( ) routine to get environment
variables, and the setenv( ) and unsetenv( ) routines to change or remove them.
(For more information about environment variables , see 2.2.8 RTPs and
Environment Variables, p.12.)
RTP applications often make use of VxWorks operating system facilities or utility
libraries. This usually requires that the source code refer to VxWorks header files.
The following sections discuss the use of VxWorks header files.
VxWorks header files supply ANSI C function prototype declarations for all global
VxWorks routines. VxWorks provides all header files specified by the ANSI
X3.159-1989 standard.
VxWorks system header files for RTP applications are in the directory
installDir/vxworks-6.x/target/usr/h and its subdirectories (different directories are
used for kernel applications).
! CAUTION: Do not reference header files that are for kernel code (which are in and
below installDir/vxworks-6.x/target/h) in application code.
Traditionally, VxWorks has provided many header files that are described by
POSIX.1, although their content only partially complied with that standard. For
user-mode applications the POSIX header files are more strictly compliant with the
POSIX.1 description, in both in their content and in their location. See 9.4 Standard
C Library: libc, p.177 for more information.
! CAUTION: You must include the vxWorks.h header file in your RTP application
before any other header files. This header file provides basic definitions and types
that are used by many VxWorks facilities. It is also required by many other
VxWorks header files.
The only exception to this rule is if your RTP application must conform to the
POSIX PSE52 profile. In this case, do not include vxWorks.h. The vxWorks.h
header file is required for non-POSIX VxWorks facilities, but it also makes
applications non-conformant with the PSE52 profile.
39
VxWorks
Application Programmer's Guide, 6.9
Applications can include other VxWorks header files as needed to access VxWorks
facilities. For example, an application module that uses the VxWorks linked-list
subroutine library must include the lstLib.h file with the following line:
#include <lstLib.h>
The API reference entry for each library lists all header files necessary to use that
library.
All ANSI-specified header files are included in VxWorks. Those that are
compiler-independent or more VxWorks-specific are provided in
installDir/vxworks-6.x/target/usr/h while a few that are compiler-dependent (for
example stddef.h and stdarg.h) are provided by the compiler installation. Each
toolchain knows how to find its own internal headers; no special compile flags are
needed.
Each compiler has its own C++ libraries and C++ headers (such as iostream and
new). The C++ headers are located in the compiler installation directory rather
than in installDir/vxworks-6.x/target/usr/h. No special flags are required to enable
the compilers to find these headers. For more information about C++
development, see 5. C++ Development.
NOTE: In releases prior to VxWorks 5.5 Wind River recommended the use of the
flag -nostdinc. This flag should not be used with the current release since it prevents
the compilers from finding headers such as stddef.h.
Compiler -I Flag
By default, the compiler searches for header files first in the directory of the source
code and then in its internal subdirectories. In general,
installDir/vxworks-6.x/target/usr/h should always be searched before the
compilers’ other internal subdirectories; to ensure this, always use the following
flag for compiling under VxWorks:
-I %WIND_BASE%/target/usr/h %WIND_BASE%/target/usr/h/wrn/coreip
Some header files are located in subdirectories. To refer to header files in these
subdirectories, be sure to specify the subdirectory name in the include statement,
so that the files can be located with a single -I specifier. For example:
#include <vxWorks.h>
#include <sys/stat.h>
Some VxWorks facilities make use of other, lower-level VxWorks facilities. For
example, the tty management facility uses the ring buffer subroutine library. The
40
3 RTP Applications
3.4 Developing RTP Applications
tty header file tyLib.h uses definitions that are supplied by the ring buffer header
file rngLib.h.
It would be inconvenient to require you to be aware of such include-file
interdependencies and ordering. Instead, all VxWorks header files explicitly
include all prerequisite header files. Thus, tyLib.h itself contains an include of
rngLib.h. (The one exception is the basic VxWorks header file vxWorks.h, which
all other header files assume is already included.)
Generally, explicit inclusion of prerequisite header files can pose a problem: a
header file could get included more than once and generate fatal compilation
errors (because the C preprocessor regards duplicate definitions as potential
sources of conflict). However, all VxWorks header files contain conditional
compilation statements and definitions that ensure that their text is included only
once, no matter how many times they are specified by include statements. Thus,
an application can include just those header files it needs directly, without regard
to interdependencies or ordering, and no conflicts will arise.
Some elements of VxWorks are internal details that may change and so should not
be referenced in your application. The only supported uses of VxWorks facilities
are through the public definitions in the header file, and through the public APIs.
Your adherence ensures that your application code is not affected by internal
changes in the implementation of a VxWorks facility.
Some header files mark internal details using HIDDEN comments:
/* HIDDEN */
...
/* END HIDDEN */
Internal details are also hidden with private header files that are stored in the
directory installDir/vxworks-6.x/target/usr/h/private. The naming conventions for
these files parallel those in installDir/vxworks-6.x/target/usr/h with the library
name followed by P.h. For example, the private header file for semLib is
installDir/vxworks-6.x/target/usr/h/private/semLibP.h.
Do not include system header files within extern "C" statement brackets. All
VxWorks system header files are already configured for use with C and C++
compilers. You may invalidate declarations in the VxWorks headers if you force a
C linkage on the entire header file's content. The following is incorrect:
#ifdef _cplusplus
extern "C" {
#endif
#include <stdio.h>
#ifdef _cplusplus
extern "C" {
#endif
41
VxWorks
Application Programmer's Guide, 6.9
In addition, do not include a VxWorks header file within an extern "C" statement
in C++ files. The following is incorrect:
extern "C" {
#include <stdio.h>
}
Because kernel mode and user mode have different instruction sets and MMU
settings, RTP applications—which run in user mode—cannot directly access
kernel routines and data structures (as long as the MMU is on). System calls
provide the means by which applications request that the kernel perform a service
on behalf of the application, which usually involves operations on kernel or
hardware resources.
System calls are transparent to the user, but operate as follows: For each system
call, an architecture-specific trap operation is performed to change the CPU
privilege level from user mode to kernel mode. Upon completion of the operation
requested by the trap, the kernel returns from the trap, restoring the CPU to user
mode. Because they involve a trap to the kernel, system calls have higher overhead
than library routines that execute entirely in user mode.
Note that if VxWorks is configured without a component that provides a system
call required by an application, ENOSYS is returned as an errno by the
corresponding user-mode library API.
Also note that if a system call has trapped to the kernel and is waiting on a system
resource when a signal is received, the system call may be aborted. In this case the
errno EINTR may be returned to the caller of the API.
System calls are identified as such in the VxWorks API references.
The set of system calls provided by VxWorks can be extended by kernel
developers. They can add their own facilities to the operating system, and make
them available to processes by registering new system calls with the VxWorks
system call infrastructure. For more information, see the VxWorks Kernel
Programmer’s Guide: Kernel Customization.
The VxWorks kernel shell provides facilities for monitoring system calls. For more
information, see the VxWorks Kernel Shell User’s Guide, the syscall monitor entry in
42
3 RTP Applications
3.4 Developing RTP Applications
the VxWorks Kernel Shell Command Reference, and the sysCallMonitor( ) entry in the
VxWorks Kernel API Reference.
VxWorks Libraries
VxWorks distributions include libraries of routines that provide APIs for RTP
applications. Some of these routines execute entirely in the process in user mode.
Others are wrapper routines that make one or more system calls, or that add
additional functionality to one or more system calls. For example, printf( ) is a
wrapper that calls the system call write( ). The printf( ) routine performs a lot of
formatting and so on, but ultimately must call write( ) to output the string to a file
descriptor.
Library routines that do not include system calls execute in entirely user mode, and
are therefore more efficient than system calls, which include the overhead of a trap
to the kernel.
Custom Libraries
For information about creating custom user-mode libraries for applications, see
4. Static Libraries, Shared Libraries, and Plug-Ins.
API Documentation
For detailed information about the routines available for use in applications, see
the VxWorks Application API Reference and the Dinkumware library references.
43
VxWorks
Application Programmer's Guide, 6.9
For information about using hook routines, which are called during the execution
of rtpSpawn( ) and rtpDelete( ), see the VxWorks API reference for rtpHookLib
and 6.5.10 Tasking Extensions: Using Hook Routines, p.120.
For information about POSIX APIs available with VxWorks, and a comparison of
native VxWorks and POSIX APIs, see 9. POSIX Facilities.
RTP Applications can be built using Wind River Workbench or the command-line
VxWorks development environment. For information about these facilities, see the
Wind River Workbench by Example guide and your Platform user’s guide,
respectively.
44
3 RTP Applications
3.4 Developing RTP Applications
NOTE: Applications that make use of share libraries or plug-ins must be built as
dynamic executables. For more information, see 4.9 Developing RTP Applications That
Use Shared Libraries, p.84 and 4.11 Developing RTP Applications That Use Plug-Ins,
p.86.
! CAUTION: Code built for variants of VxWorks or for different releases of VxWorks
is not binary compatible between variants or releases. Code must be built
specifically for uniprocessor (UP) VxWorks, VxWorks SMP, 32-bit VxWorks, 64-bit
VxWorks, or for a VxWorks system based on variant libraries produced with a
VxWorks source build (VSB) project—or for any supported combination of these
variants. The only exception to this rule is that RTP application executables can be
run on both UP VxWorks and VxWorks SMP (when all else is the same).
RTP applications provide the greatest performance advantages when they are built
as absolutely-linked executables for use with a system that is configured with RTP
overlapped virtual memory.
For 32-bit VxWorks, RTP applications are not built as absolutely-linked executables
by default. In addition, VxWorks must be specially configured with RTP
overlapped virtual memory to take advantage of the performance advantages
provided by absolutely-linked executables. (for information in this regard, see
2.5 RTP Overlapped Virtual Memory for 32-Bit VxWorks, p.19).
In order to take full advantage of the efficiencies provided by the overlapped
virtual memory model, RTP applications must be built as absolutely-linked
executables with a link address that matches the RTP code region appropriately.
Absolutely-linked RTP applications are started in the same manner as relocatable
RTP executables, and can also be run on 32-bit systems configured with the default
overlapped virtual memory configuration, or with the flat virtual memory model,
but in those cases they will be relocated.
The link address for an absolutely-linked executable must match the VxWorks RTP
code region appropriately (for information about configuring VxWorks with the an
RTP code region, see About User Regions and the RTP Code Region, p.20 and Defining
the RTP Code Region, p.22).
45
VxWorks
Application Programmer's Guide, 6.9
The link address is specified with a special linker option. The option can be used
directly with the linker, or indirectly with either a Wind River Workbench GUI
option or a command-line make macro. For simplicity sake, it is useful to use the
same link address for all executables (with consideration of each of them fitting
within the RTP code region).
While technically the link address of the RTP executable can be anywhere in the
RTP code region (provided the address is low enough to leave room for the
applications text, data, and bss segments), Wind River recommends that it be set to
base address of the RTP code region itself (that is, the address specified with the
VxWorks RTP_CODE_REGION_START configuration parameter; see Setting
Configuration Parameters for the RTP Code Region, p.26).
The RTP_LINK_ADDR make macro can be used to set the link address for
absolutely-linked executables when using the VxWorks build environment from
the command line. For example, as in executing the following command from
installDir/vxworks-6.x/target/usr/apps/sample/helloworld:
% make CPU=PENTIUM4 TOOL=gnu RTP_LINK_ADDR=0xe0000000
-Wl,--defsym,__wrs_rtp_base=0xe0000000
These linker options are automatically invoked when the default makefile rules of
the VxWorks cross-development environment are used.
The link address of the data segment cannot be specified separately. By design the
data segment of an application immediately follows the text segment, with
consideration for page alignment constraints. Both segments are installed together
in one block of allocated memory.
Note that Wind River Workbench provides a GUI option for defining the base
address.
For production systems, it may be useful to strip executables to reduce their size.
The striparch utility can be used with the --strip-unneeded and --strip-debug (or
-d) options for any RTP executables.
The --strip-all (or -s) option should only be used with absolutely-linked
executables. Do not use this option with executables for the 32-bit flat virtual
memory model, because they must be relocatable.
46
3 RTP Applications
3.5 Developing Static Libraries, Shared Libraries and Plug-Ins
All RTP executables for 64-bit VxWorks are built as absolutely-linked executables.
RTP executables for 32-bit VxWorks optionally can also be built as
absolutely-linked executables. Absolutely-linked RTP executables are generated
for execution at a predetermined address. They do not, therefore, need symbolic
information and relocation information during the load phase.
! CAUTION: See Caveat for Using Stripped Executables With 32-bit VxWorks, p.51.
NOTE: Wind River recommends using mmanLib (and associated features shmLib
and devMemLib) instead of sdLib (shared data regions) because of the additional
functionality and POSIX compliance. For more information, see 10.7 Memory
Mapping Facilities, p.236.
The shared data region facility provides no inherent facility for mutual exclusion.
Applications must use standard mutual exclusion mechanisms—such as public
semaphores—to ensure controlled access to a shared data region resources (see
7.2 About Intertask and Interprocess Communication, p.128).
47
VxWorks
Application Programmer's Guide, 6.9
For systems without an MMU enabled, shared data regions simply provide a
standard programming model and separation of data for the applications, but
without the protection provided by an MMU.
A shared data region is a single block of contiguous virtual memory. Any type of
memory can be shared, such as RAM, memory-mapped I/O, flash, or VME.
Multiple shared data regions can be created with different characteristics and
different users.
Common uses of a shared data region would include video data from buffers.
The sdLib shared data region library provides the facilities for the following
activities:
■
Creating a shared data region.
■ Opening the region.
■ Mapping the region to a process’ memory context so that it can be accessed.
■ Changing the protection attributes of a region that has been mapped.
■ Un-mapping the region when a process no longer needs to access it.
■ Deleting the region when no processes are attached to it.
Operations on shared data regions are not restricted to applications—kernel tasks
may also perform these operations.
Shared data regions use memory resources from both the kernel’s and the
application’s memory space. The kernel's heap is used to allocate the shared data
object. The physical memory for the shared data region is allocated from the global
physical page pool.
When a shared data region is created, it must be named. The name is global to the
system, and provides the means by which applications identify regions to be
shared.
Shared data regions can be created in systems with and without MMU support.
Also see 7.3 Shared Data Structures, p.129 and 10.7.2 POSIX Shared Memory Objects,
p.238
Shared data regions can be created with either sdCreate( ) or sdOpen( ). The
sdCreate( ) routine creates a new shared data region. The sdOpen( ) routine
creates a new shared data region if the region referenced by the call does not exist;
otherwise it opens it.
Shared date regions can be created by an application, or from a kernel task such as
the shell. The region is automatically mapped into the creator’s memory context.
The sdOpen( ) routine also creates and maps a region—if the region name used in
the call does not exist in the system.
48
3 RTP Applications
3.6 Creating and Using Shared Data Regions
! WARNING: If the shell is used to create shared data regions, the optional physical
address parameter should not be used with architectures for which the
PHYS_ADDRESS type is 64 bits. The shell passes the physical address parameter as
32 bits regardless. If it should actually be 64 bits, the arguments will not be aligned
with the proper registers and unpredictable behavior will result. See the VxWorks
Architecture Supplement for the processor in question for more information.
The creation routines take parameters that define the name of the region, its size
and physical address, MMU attributes, and two options that govern the regions
persistence and availability to other processes.
The MMU attribute options define access permissions and the cache option for the
process’ page manager:
■
read-only
■
read/write
■
read/execute
■ read/write/execute
■ cache write-through, cache copy-back, or cache off
By default, the creator process always gets read and write permissions for the
region, regardless of the permissions set with the creation call, which affect all
client processes. The creator, can however, change its own permissions with
sdProtect( ). See Changing Shared Data Region Protection Attributes, p.49.
The SD_LINGER creation option provides for the persistence of the region after all
processes have unmapped from it—the default behavior is for it to cease to exist,
all of its resources being reclaimed by the system. The second option, SD_PRIVATE,
restricts the accessibility of the region to the process that created it. This can be
useful, for example, for restricting memory-mapped I/O to a single application.
A shared data region is automatically opened and mapped to the process that
created it, regardless of whether the sdCreate( ) or sdOpen( ) routine was used.
A client process must use the region’s name with sdOpen( ) to access the region.
The region name can be hard-coded into the client process’ application, or
transmitted to the client using IPC mechanisms.
Mutual exclusion mechanisms should be used to ensure that only one application
can access the same shared data region at a time. The sdLib library does not
provide any mechanisms for doing so automatically. For more information about
mutual exclusion, see 7.2 About Intertask and Interprocess Communication, p.128.
For information about accessing shared data regions from interrupt service
routines (ISRs), see the VxWorks Kernel Programmer’s Guide: Multitasking.
The MMU attributes of a shared data region can be changed with sdProtect( ). The
change can only be to a sub-set of the attributes defined when the region was
created. For example, if a region was created with only read and write permissions,
these can only be changed to read-only and no access, and not expanded to other
permissions. In addition, the changes are made only for the caller’s process; they
do not affect the permissions of other processes.
49
VxWorks
Application Programmer's Guide, 6.9
A set of macros is provided with the library for common sets of MMU attribute
combinations.
Shared data regions can be deleted explicitly and automatically. However, deletion
of regions is restricted by various conditions, including how the region was
created, and if any processes are attached to it.
If a shared data region was created without the SD_LINGER option, the region is
deleted if:
■
Only one process is mapped to the region, and its application calls
sdUnmap( ).
■ Only one process is mapped to the region, and the process exits.
If a shared data region is created with the SD_LINGER option, it is never deleted
implicitly. The region is only deleted if sdDelete( ) is called on it after all clients
have unmapped it.
Relocatable executables are supported when the RTP address space is overlapped
(the default for 32-bit VxWorks). The executables are relocated, and their text and
data segments are installed in the RTP code region providing that the RTP code
region is big enough to accommodate them. If the RTP code region is too small for
the segments, they are installed elsewhere, providing again that there is enough
room available in the remaining areas of the user regions.
50
3 RTP Applications
3.7 Executing RTP Applications
For both 32-bit VxWorks and 64-bit VxWorks, the load time of absolutely-linked
executables is noticeably shorter because they are not relocated. In addition, RTP
executables can be executed on different target boards of the same architecture as
long as the VxWorks images running on those boards provide the features that the
applications require.
For 32-bit VxWorks, if the RTP code regions are not at the same location and of the
same size, the RTP executables text, data, and bss segments are relocated. This
would typically happen if the RTP code region cannot accommodate the segments,
for any of the following reasons:
■ The size of the region is not sufficient.
■ The base address of the executable is too close to the top address of the RTP
code region (which prevents the segments from fitting in the remaining space).
■ The executable's base address does not corresponds to an address within a
user region.
Note that if the base address of the executable is completely outside of the RTP
code region but still corresponds to a user region then the executable is not
relocated, and is installed outside of the RTP code region. The side-effect of this
situation is that this may reduce the memory areas available to public mappings
(in particular shared data regions and shared libraries).
51
VxWorks
Application Programmer's Guide, 6.9
-> edrShow
[...]
rtpLoadAndRun(): RTP 0x1415010 Init Task exiting. errno = 0xba006e [...]
For information about stripping executables, see 3.4.12 Reducing Executable File Size
With the strip Facility, p.46. For information about memory models, see 2.5 RTP
Overlapped Virtual Memory for 32-Bit VxWorks, p.19, 2.7 Other RTP Virtual Memory
Options for 32-Bit VxWorks, p.27, and 2.6 RTP Overlapped Virtual Memory for 64-Bit
VxWorks, p.27.
Automatic Termination
By default, a process is terminated when the main( ) routine returns, because the
C compiler automatically inserts an exit( ) call at the end of main( ). This is
undesirable behavior if main( ) spawns other tasks, because terminating the
process deletes all the tasks that were running in it. To prevent this from
happening, any application that uses main( ) to spawn tasks can call taskExit( )
instead of return( ) as the last statement in the main( ) routine. When main( )
includes taskExit( ) as its last call, the process’ initial task can exit without the
kernel automatically terminating the process.
Explicit Termination
A process can explicitly be terminated when a task does either of the following:
■
Calls exit( ) to terminate the process in which it is are running, regardless of
whether or not other tasks are running in the process.
■ Calls the kill( ) routine to terminate the specified process (using the process
ID).
52
3 RTP Applications
3.7 Executing RTP Applications
Application executables can be stored in the VxWorks ROMFS file system on the
target system, on the host development system, or on any other file system
accessible to the target system (another workstation on a network, for example).
Various combinations of startup mechanisms and storage locations can be used for
developing systems and for deployed products. For example, storing application
executables on the host system and using the kernel shell to run them is ideal for
the early phases of development because of the ease of application re-compilation
and of starting applications. Final products, on the other hand, can be configured
and built so that applications are bundled with the operating system, and started
automatically when the system boots, all independently of humans, hosts, and
hard drives.
! CAUTION: The error S_rtp_INVALID_FILE is generated when the path and name of
the RTP executable is not provided, or when the executable cannot be found using
the indicated path. If the file is not stored on the target system, the path must be
valid from the point of view of the target itself. For information in this regard, see
11.5 Remote File System Access From VxWorks, p.254.
Starting Applications
From the shell, applications can be started with shell command variants of the
rtpSpawn( ) routine.
NOTE: If the file is not stored on the target system, the path must be valid from the
point of view of the target itself. For information in this regard, see 11.5 Remote File
System Access From VxWorks, p.254.
53
VxWorks
Application Programmer's Guide, 6.9
Or, the application can be started with the rtp exec command:
rtp exec mars:c:/myInstallDir/vxworks-6.1/target/usr/root/PPC32diab/bin/[Link] first second third
NOTE: For Windows hosts you must use forward slashes (or double backslashes)
as path delimiters. This is the case even when the executable is stored on the host
system.
Regardless of how the process is spawned, the application runs in exactly the same
manner.
Note that you can switch from the C interpreter to the command interpreter with
the cmd command; and from the command interpreter to the C interpreter with the
C command. The command interpreter rtp exec command has options that
provide more control over the execution of an application.
Terminating Applications
54
3 RTP Applications
3.7 Executing RTP Applications
55
VxWorks
Application Programmer's Guide, 6.9
A common string syntax is used with both the startup facility configuration
parameter and the boot loader parameter for identifying applications. The basic
syntax is as follows:
#progPathName^arg1^arg2^arg3#progPathName...
This syntax involves only two special characters:
#
A pound sign identifies what immediately follows as the path and name of an
application executable.
^
A caret delimits individual arguments (if any) to the application. A caret is not
required after the final argument.
The carets are not required—spaces can be used instead—with the startup
configuration parameter, but carets must be used with the boot loader
parameter.
The following examples illustrate basic syntax usage:
#c:/apps/[Link]
Starts c:\apps\[Link]
#c:/apps/[Link]^one^two^three
Starts c:\apps\[Link] with the arguments one, two, three.
#c:/apps/[Link]
Starts c:\apps\[Link] without any arguments.
#c:/apps/[Link]^one^two^three#c:/apps/[Link]
Starts both applications, the first one with its three arguments.
The startup facility also allows for specification of rtpSpawn( ) routine parameters
with additional syntax elements:
%p=value
Sets the priority of the initial task of the process. Priorities can be in the range
of 0-255.
%s=value
Sets the stack size for the initial task of the process (an integer parameter).
%o=value
Sets the process options parameter.
%t=value
Sets task options for the initial task of the process.
When using the boot loader parameter, the option values must be either decimal
or hexadecimal numbers. When using the startup facility configuration parameter,
56
3 RTP Applications
3.7 Executing RTP Applications
If the rtpSpawn( ) options are not set, the following defaults apply: the initial task
priority is 220; the initial task stack size is 64 Kb; the options value is zero; and the
initial task option is VX_FP_TASK.
The maximum size of the string used in the assignment is 160 bytes, inclusive of
names, parameters, and delimiters. No spaces can be used in the assignment, so
application files should not be put in host directories for which the path includes
spaces.
1. In versions of VxWorks 5.x, the boot loader s parameter was used solely to specify a shell
script.
57
VxWorks
Application Programmer's Guide, 6.9
myScript#c:/apps/[Link]^one^two^three
The assignment in the boot console window would look like this:
startup script (s) : myScript#c:/apps/[Link]^one^two^three
Note that for Windows hosts you must use either forward-slashes or double
back-slashes instead of single back-slashes as path delimiters with the shell.
If a shell script is written for the C interpreter, it can be identified interactively
using the boot loader s parameter— in a manner similar to applications—using a
sub-set of the same string syntax. A shell script for the C interpreter can also be
identified statically with the DEFAULT_BOOT_LINE parameter of the
INCLUDE_RTP_APPL_INIT_BOOTLINE component. (See Specifying Applications
with a Boot Loader Parameter, p.57 and Application Startup String Syntax, p.56.)
The operating system must be configured with the kernel shell and the C
interpreter components for use with C interpreter shell scripts (see the VxWorks
Kernel Shell User’s Guide).
A startup shell script file for the C interpreter could contain the following line:
rtpSp "c:/apps/[Link] first second third"
With the shell script file c:\scripts\myVxScript, the boot loader s parameter
would be set interactively at the boot console as follows:
startup script (s) : c:/scripts/myVxScript
58
3 RTP Applications
3.7 Executing RTP Applications
Note that shell scripts can be stored in ROMFS for use in deployed systems (see
3.9 Bundling RTP Applications in a System using ROMFS, p.61).
The VxWorks application startup facility can be used in conjunction with the
usrRtpAppInit( ) initialization routine to start applications automatically when
VxWorks boots. In order to use this method, VxWorks must be configured with the
INCLUDE_RTP_APPL_USER component.
For each application you wish to start, add an rtpSpawn( ) call and associated code
to the usrRtpAppInit( ) routine stub, which is located in
installDir/vxworks-6.x/target/proj/projDir/usrRtpAppInit.c.
The following example starts an application called myVxApp, with three
arguments:
void usrRtpAppInit (void)
{
const char * vxeName = "c:/vxApps/myVxApp/PPC32diab/[Link]";
const char * argv[5];
RTP_ID rtpId = NULL;
argv[0] = vxeName;
argv[1] = "first";
argv[2] = "second";
argv[3] = "third";
argv[4] = NULL;
For information about bundling applications with the system image in ROMFS,
see 3.9 Bundling RTP Applications in a System using ROMFS, p.61.
The VxWorks kernel shell provides facilities for spawning tasks and for calling
application routines in a real-time process. These facilities are particularly useful
for debugging RTP applications.
For more information, see the VxWorks Kernel Shell User’s Guide and the entries for
the task spawn and func call commands in the VxWorks Kernel Shell Command
Reference.
59
VxWorks
Application Programmer's Guide, 6.9
In addition, note that the kernel shell provides facilities for monitoring system
calls. For more information, see the VxWorks Kernel Shell User’s Guide, the
syscall monitor entry in the VxWorks Kernel Shell Command Reference, and the
sysCallMonitor( ) entry in the VxWorks Kernel API Reference.
The rtp symbols override command has the options –g for global symbols, -a for
all symbols (global and local), and –c to cancel the policy override.
The rtpSpawn( ) options parameter RTP_GLOBAL_SYMBOLS (0x01) and
RTP_ALL_SYMBOLS (0x03) can be used to load global symbols, or global and local
symbols (respectively).
The shell’s C interpreter command rtpSp( ) provides the same options with the
rtpSpOptions variable.
Symbols can also be registered and unregistered interactively from the shell, which
is useful for applications that have been started without symbol registration. For
example:
rtp symbols add –a –s 0x10000 –f /romfs/bin/[Link]
rtp symbols remove –l –s 0x10000
rtp symbols help
60
3 RTP Applications
3.8 Caveat About Using Show Routines With RTP Applications
61
VxWorks
Application Programmer's Guide, 6.9
The contents of the romfs directory are automatically built into a ROMFS file
system and combined with the VxWorks image.
The ROMFS directory does not need to be created in the VxWorks project directory.
It can also be created in any location on (or accessible from) the host system, and
the make ROMFS_DIR macro used to identify where it is in the build command.
For example:
make TOOL=diab ROMFS_DIR="c:\allMyVxAppExes"
Note that any files located in the romfs directory are included in the system image,
regardless of whether or not they are application executables.
At run time, the ROMFS file system is accessed as /romfs. The content of the
ROMFS directory can be browsed using the traditional ls and cd shell commands,
and accessed programmatically with standard file system routines, such as open( )
and read( ).
For example, if the directory
installDir/vxworks-6.x/target/proj/wrSbc8260_diab/romfs has been created on the
host, [Link] copied to it, and the system rebuilt and booted, then using ls
from the shell looks like this:
[vxWorks]# ls /romfs
/romfs/.
/romfs/..
/romfs/[Link]
62
3 RTP Applications
3.9 Bundling RTP Applications in a System using ROMFS
ROMFS can be used with any of the application startup mechanisms simply by
referencing the local copy of the application executables. See 3.7.2 Running
Applications Automatically, p.54 for information about the various ways in which
applications can be run automatically when VxWorks boots.
63
VxWorks
Application Programmer's Guide, 6.9
64
4
Static Libraries, Shared Libraries,
and Plug-Ins
4.1 Introduction 65
4.2 About Static Libraries, Shared Libraries, and Plug-ins 65
4.3 Additional Documentation 68
4.4 Configuring VxWorks for Shared Libraries and Plug-ins 68
4.5 Common Development Issues: Initialization and Termination 69
4.6 Common Development Facilities 71
4.7 Developing Static Libraries 72
4.8 Developing Shared Libraries 72
4.9 Developing RTP Applications That Use Shared Libraries 84
4.10 Developing Plug-Ins 85
4.11 Developing RTP Applications That Use Plug-Ins 86
4.12 Using the VxWorks Run-time C Shared Library [Link] 90
4.1 Introduction
Custom static libraries, shared libraries, and plug-ins can be created for use with
RTP applications. This chapter describes their features, comparative advantages
and uses, development procedures, and debugging methods. It also describes the
C run-time shared library provided with the VxWorks distribution, which can be
used with applications as an alternative to statically linking them to C libraries.
65
VxWorks
Application Programmer's Guide, 6.9
! CAUTION: Applications that make use of shared libraries or plug-ins must be built
as dynamic executables to include a dynamic linker in their image. The dynamic
linker carries out the binding of the dynamic shared object and application at run
time. For more information in this regard, see 4.9 Developing RTP Applications That
Use Shared Libraries, p.84 and 4.11 Developing RTP Applications That Use Plug-Ins,
p.86.
NOTE: Shared libraries and plug-ins are not supported for the Coldfire
architecture.
66
4 Static Libraries, Shared Libraries, and Plug-Ins
4.2 About Static Libraries, Shared Libraries, and Plug-ins
At the same time, shared libraries use position-independent code (PIC), which is
slightly larger than standard code, and PIC accesses to data are usually somewhat
slower than non-PIC accesses because of the extra indirection through the global
offset table (GOT). This has more impact on some architectures than on others.
Usually the difference is on the order of a fraction of a percent, but if a
time-sensitive code path in a shared library contains many references to global
functions, global data or constant data, there may be a measurable performance
penalty.
If lazy binding is used with shared libraries, it introduces non-deterministic
behavior. (For information about lazy binding, see 4.8.8 Using Lazy Binding With
Shared Libraries, p.79 and Using Lazy Binding With Plug-ins, p.87.)
The startup cost of shared libraries makes up the largest efficiency cost (as is the
case with UNIX). It is also greater because of more complex memory setup and
more I/O (file accesses) than for static executables.
In summary, shared libraries are most useful when the following are true:
■ Many programs require a few libraries.
■ Many programs that use libraries run at the same time.
■ Libraries are discrete functional units with little unused code.
■ Library code represents a substantial amount of total code.
Conversely, it is not advisable to use shared libraries when only one application
runs at a time, or when applications make use of only a small portion of the
routines provided by the library.
Additional Considerations
There are a number of other considerations that may affect whether to use shared
libraries (or plug-ins):
■ Assembly code that refers to global functions or data must be converted by
hand into PIC in order to port it to a shared library.
■ The relocation process only affects the data section of a shared library.
Read-only data identified with the const C keyword are therefore gathered
with the data section and not with the text section to allow a relocation per
executable. This means that read-only data used in shared libraries are not
protected against erroneous write operations at run-time.
■
Code that has not been compiled as PIC will not work in a shared library. Code
that has been compiled as PIC does not work in an executable program, even
if the executable program is dynamic. This is because function prologues in
code compiled as PIC are edited by the dynamic linker in shared objects.
■
All constructors in a shared library are executed together, hence a constructor
with high priority in one shared library may be executed after a constructor
with low priority in another shared library loaded later than the first one. All
shared library constructors are executed at the priority level of the dynamic
linker’s constructor from the point of view of the executable program.
■ Dynamic shared objects are not cached (they do not linger) if no currently
executing program is using them. There is, therefore, extra processor overhead
if a shared library is loaded and unloaded frequently.
67
VxWorks
Application Programmer's Guide, 6.9
■
There is a limit on the number of concurrent shared libraries, which is 1024.
This limit is imposed by the fact that the GOT table has a fixed size, so that
indexing can be used to look up GOTs (which makes it fast).
68
4 Static Libraries, Shared Libraries, and Plug-Ins
4.5 Common Development Issues: Initialization and Termination
For general information about configuring VxWorks for real-time processes, see &
2.3 Configuring VxWorks For Real-time Processes, p.14.
The routine takes no arguments and returns nothing. It can be useful to use the
same naming convention used for VxWorks libraries; nameLibInit( ), where name
is the basename of the feature. For example, fooLibInit( ) would be the
initialization routine for fooLib.
The code that calls the initialization of application libraries is generated by the
compiler. The _WRS_CONSTRUCTOR compiler macro must be used to identify the
library’s (or plug-in’s) initialization routine (or routines), as well as the order in
which they should be called. The macro takes two arguments, the name of the
routine and a rank number. The routine itself makes up the body of the macro. The
syntax is as follows:
_WRS_CONSTRUCTOR (fooLibInit, rankNumInteger)
{
/* body of the routine */
}
69
VxWorks
Application Programmer's Guide, 6.9
(For information about using the error detection and reporting macro
EDR_USR_FATAL_INJECT, see 14.7 Other Error Handling Options for Processes,
p.323.)
The rank number is used by the compiler to order the initialization routines. (The
rank number is referred to as a priority number in the compiler documentation.)
Rank numbers from 100 to 65,535 can be used—numbers below 100 are reserved
for VxWorks libraries. Using a rank number below 100 does not have detrimental
impact on the kernel, but may disturb or even prevent the initialization of the
application environment (which involves creating resources such as the heap,
semphores, and so on).
Initialization routines are called in numerical order (from lowest to highest). When
assigning a rank number, consider whether or not the library (or plug-in) in
question is dependent on any other application libraries that should be called
before it. If so, make sure that its number is greater.
If initialization routines are assigned the same rank number, the order in which
they are run is indeterminate within that rank (that is, indeterminate relative to
each other).
70
4 Static Libraries, Shared Libraries, and Plug-Ins
4.6 Common Development Facilities
configured with the error detection and reporting facility, it attempts to print
the message to a host console by way of a serial line. For example:
if (mutex = semMCreate (SEM_Q_PRIORITY | SEM_INVERSION_SAFE)) == NULL)
{
EDR_USR_FATAL_INJECT (FALSE, "myLib: cannot create mutex. Abort.");
}
For more information, see 14.7 Other Error Handling Options for Processes, p.323.
Shared libraries and plug-ins are removed from memory when the only (last)
process making use of them exits. A plug-in can also be terminated explicitly when
the only application making use of it calls dlclose( ) on it.
There is no library (or plug-in) termination routine facility comparable to that for
initialization routines (particularly with regard to ranking). If there is a need to
perform cleanup operations in addition to what occurs automatically with RTP
deletion, (such as deleting kernel resources created by the library) then the atexit( )
routine must be used. The call to atexit( ) can be made at anytime during the life of
the process, although it is preferably done by the library (or plug-in) initialization
routine. Cleanup routines registered with atexit( ) are called when exit( ) is called.
Note that if a process’ task directly calls the POSIX _exit( ) routine, none of the
cleanup routines registered with atexit( ) will be executed.
If the cleanup is specific to a task or a thread then taskDeleteHookAdd( ) or
pthread_cleanup_push( ) should be used to register a cleanup handler (for a
VxWorks task or pthread, respectively). These routines are executed in reverse
order of their registration when a process is being terminated.
71
VxWorks
Application Programmer's Guide, 6.9
! CAUTION: Code built for variants of VxWorks or for different releases of VxWorks
is not binary compatible between variants or releases. Code must be built
specifically for uniprocessor (UP) VxWorks, VxWorks SMP, 32-bit VxWorks, 64-bit
VxWorks, or for a VxWorks system based on variant libraries produced with a
VxWorks source build (VSB) project—or for any supported combination of these
variants. The only exception to this rule is that RTP application executables can be
run on both UP VxWorks and VxWorks SMP (when all else is the same).
72
4 Static Libraries, Shared Libraries, and Plug-Ins
4.8 Developing Shared Libraries
! CAUTION: Applications that make use of shared libraries must be built as dynamic
executables to include a dynamic linker in their image. The dynamic linker carries
out the binding of the shared library and application at run time. For more
information, see 4.9 Developing RTP Applications That Use Shared Libraries, p.84.
! CAUTION: Code built for variants of VxWorks or for different releases of VxWorks
is not binary compatible between variants or releases. Code must be built
specifically for uniprocessor (UP) VxWorks, VxWorks SMP, 32-bit VxWorks, 64-bit
VxWorks, or for a VxWorks system based on variant libraries produced with a
VxWorks source build (VSB) project—or for any supported combination of these
variants. The only exception to this rule is that RTP application executables can be
run on both UP VxWorks and VxWorks SMP (when all else is the same).
NOTE: Shared libraries and plug-ins are not supported for the Coldfire
architecture.
Dynamic Linker
73
VxWorks
Application Programmer's Guide, 6.9
VxWorks must be configured with support for shared libraries. For information in
this regard, see 4.4 Configuring VxWorks for Shared Libraries and Plug-ins, p.68
In order for the dynamic linker to determine that an RTP application requires a
shared library, the application must be built in such a way that the executable
includes the name of the shared library.
The name of a shared library—it’s shared object name—must initially be defined
when the shared library itself is built. This creates an ELF SONAME record with the
shared object name in the library’s binary file. A shared object name is therefore
often referred to simply as an soname.
The shared object name is added to an application executable when the application
is built as a dynamic object and linked against the shared library at build time. This
creates an ELF NEEDED record, which includes the name originally defined in the
library’s SONAME record. One NEEDED record is created for each shared library
against which the application is linked.
The application’s NEEDED records are used at run-time by the dynamic linker to
identify the shared libraries that it requires. The dynamic linker loads shared
libraries in the order in which it encounters NEEDED records. It executes the
constructors in each shared library in reverse order of loading. (For information
about the order in which the dynamic linker searches for shared libraries, see
Specifying Shared Library Locations: Options and Search Order, p.76)
Note that dynamic shared objects (libraries and plug-ins) may also have NEEDED
records if they depend on other dynamic shared objects.
For information about the development process, see 4.8.5 Creating Shared Object
Names for Shared Libraries, p.75 and 4.9 Developing RTP Applications That Use Shared
Libraries, p.84. For examples of displaying ELF records (including SONAME and
NEEDED), see Using readelf to Examine Dynamic ELF Files, p.81.
74
4 Static Libraries, Shared Libraries, and Plug-Ins
4.8 Developing Shared Libraries
Each shared library must be created with a shared object name, which functions as
the run-time name of the library. The shared object name is used—together with
other mechanisms—to locate the library at run-time, and it can also be used to
identify different versions of a library.
For more information about shared object names, see 4.8.4 About Shared Library
Names and ELF Records, p.74. For information about identifying the runtime
location of shared libraries, see 4.8.7 Locating and Loading Shared Libraries at
Run-time, p.76.
Note that a plug-in does not require a shared object name. For information about
plug-ins, see 4.10 Developing Plug-Ins, p.85.
Shared object names and versions can be defined with the following methods:
■ Wind River Workbench uses the shared library file name for the shared object
name by default. The SHAREDLIB_VERSION macro can be used to set a
version number; it is not set by default.
■ For the VxWorks command-line build environment, the LIB_BASE_NAME and
SL_VERSION make macros are used for the name and version. By default, a
version one instance of a dynamic shared library is created (that is,
[Link].1).
■ The compiler’s -soname flag can also be used to set the shared object name.
When the library is built, the shared object name is stored in an ELF SONAME
record. For information about using versions of shared libraries, see 4.8.6 Using
Different Versions of Shared Libraries, p.76.
The shared object name must match the name of the shared library object file itself,
less the version extension. That is, the base name and .so extension must match. In
the following example, the -soname compiler option identifies the runtime name
of the library as [Link].1, which is also thereby defined as version one of the
library created with the output file [Link]:
dplus -tPPCEH:rtp -Xansi -XO -DCPU=PPC32 -DTOOL_FAMILY=diab -DTOOL=diab -Xpic
-Xswitch-table-off -Wl, -Xshared -Wl, -Xdynamic -soname=[Link].1
-L$(WIND_BASE)/target/usr/lib/ppc/PPC32/common/PIC -lstlstd
PPC32diab/[Link] PPC32diab/[Link] -o [Link]
If the SONAME information and file name do not match, the dynamic linker will
not be able to locate the library.
For information about displaying shared object names, see Using readelf to Examine
Dynamic ELF Files, p.81.
75
VxWorks
Application Programmer's Guide, 6.9
In addition to being used by the dynamic linker to locate shared libraries at run-
time, shared object names (sonames) can be used to identify different versions of
shared libraries for use by different applications.
For example, if you need to modify libMyFoo to support new applications, but in
ways that would make it incompatible with old ones, you can merely change the
version number when the library is recompiled and link the new programs against
the new version. If the original version of the run-time shared library was
[Link].1, then you would build the new version with the soname
[Link].2 and link new applications against that one (which would then add
this soname to the ELF NEEDED records). You could then, for example, install
[Link].1, [Link].2, and both the old and new applications in a
common ROMFS directory on the target, and they would all behave properly.
For more information creating shared object names, see 4.8.5 Creating Shared Object
Names for Shared Libraries, p.75.
The dynamic linker must be able to locate a shared library at run-time. It uses the
shared object name stored in the dynamic application to identify the shared library
file, but it also needs information about the location of the file (on the target
system, host, or network) as well. There are a variety of mechanisms for identifying
the location of shared libraries at run-time. In addition, the dynamic linker can be
instructed to load a set of libraries at startup time, rather than when the application
that needs them is loaded (that is, to preload the shared libraries).
In conjunction with shared libraries’ shared object names (in ELF SONAME
records), the dynamic linker can use environment variables, configuration files,
compiled location information, and a default location to find the shared libraries
required by its applications.
After determining that required libraries have not already been loaded, the
dynamic linker searches for them in the directories identified with the following
mechanisms, in the order listed:
1. The VxWorks environment variable LD_LIBRARY_PATH.
For more information, see Using the LD_LIBRARY_PATH Environment Variable,
p.77.
2. The configuration file [Link].
For more information, see Using the [Link] Configuration File, p.78.
3. The ELF RPATH record in the application’s executable (created with the –rpath
compiler option).
For more information, see Using the ELF RPATH Record, p.78.
4. The same directory as the one in which the application file is located.
For more information, see Using the Application Directory, p.78.
76
4 Static Libraries, Shared Libraries, and Plug-Ins
4.8 Developing Shared Libraries
NOTE: If the file is not stored on the target system, the path must be valid from the
point of view of the target itself. For information in this regard, see 11.5 Remote File
System Access From VxWorks, p.254.
NOTE: For Windows hosts must use forward slashes (or double backslashes) as
path delimiters. This is the case even when the executable is stored on the host
system.
Using the shell’s command interpreter, for example, the syntax for using
LD_LIBRARY_PATH (in this case within the rtp exec command) would be as
follows:
rtp exec -e "LD_LIBRARY_PATH=libPath1;libPath2" exePathAndName arg1 arg2...
Note in particular that there are no spaces within the quote-enclosed string, that
the paths are identified as a semicolon-separated list; and that the one space
between the string and the executable argument to rtp exec is optional (the shell
parser removes spaces between arguments in command-line mode).
If, for example, the shared libraries were stored in ROMFS on the target in the lib
subdirectory, the command would look quite tidy:
rtp exec -e "LD_LIBRARY_PATH=/romfs/lib" /romfs/app/[Link] one two three
The command for shared libraries stored in an NFS directory would look very
similar, with the path starting with the mount point on VxWorks. For information
about NFS, see 11.5 Remote File System Access From VxWorks, p.254 and the VxWorks
Kernel Programmer’s Guide: Network File System).
In this next example, the command (which would be typed all on one line, of
course), identifies the location of [Link].1 file as well as a custom shared library file
on the host system:
rtp exec -e "LD_LIBRARY_PATH=mars:c:/myInstallDir/vxworks-6.1/target/usr/root/SIMPENTIUMdiab/bin;
mars:c:/wrwb_demo/RtpAppShrdLib/lib/SIMPENTIUMdiab"
mars:c:/wrwb_demo/RtpAppShrdLib/app/SIMPENTIUM/bin/[Link] one two three
77
VxWorks
Application Programmer's Guide, 6.9
Note that in this example the path includes the host name (in this case mars), which
is required for non-NFS network file systems. For more information in this regard,
see 11.5 Remote File System Access From VxWorks, p.254.
The third-priority mechanism for identifying the location of shared libraries is the
ELF RPATH record in the application’s executable (see Specifying Shared Library
Locations: Options and Search Order, p.76).
The ELF RPATH record is created if an application is built with the –rpath linker
option to identify the run-time locations of shared libraries. For example, the
following identifies the lib subdirectory in the ROMFS file system as the location
for shared libraries:
-Wl,-rpath /romfs/lib
NOTE: The the usage for -rpath is different for the Wind River Compiler and the
Wind River GNU compiler. For the latter, an equal sign (=) must be used between
-rpath and the path name.
For more information about the -rpath option and about how to set it with the
makefile system, see your Platform user’s guide and the Wind River compiler
guides.
If none of the other mechanisms for locating a shared library have worked (see
Specifying Shared Library Locations: Options and Search Order, p.76), the dynamic
linker checks the directory in which the application itself is located.
78
4 Static Libraries, Shared Libraries, and Plug-Ins
4.8 Developing Shared Libraries
By default, the VxWorks makefile system creates both the application executable
and run-time shared library files in the same directory, which facilitates running
the application during the development process. Workbench manages projects
differently, and creates applications and shared libraries in different directories.
By default, shared libraries are loaded when an application that makes use of them
is loaded. There are two mechanisms that can be used to load libraries in advance
of the normal loading procedure: using the LD_PRELOAD environment variable
and using a dummy RTP application.
Startup speed can be increased by using a dummy RTP to load all required libraries
in advance, so that the other RTP applications spend less time at startup. The
dummy RTP should be built to require all the necessary libraries, should be
designed to remain in the system at least until the last user of the loaded libraries
has started—the reference counting mechanism in the kernel ensures that the
libraries are not unloaded.
The lazy binding (also known as lazy relocation or deferred binding) option postpones
the binding of shared library symbols until the application actually uses them,
instead of when the library is loaded.
By default, the dynamic linker computes the addresses of all routines and data to
which a dynamic shared object refers when it loads the object. The dynamic linker
can save some work in computing routine addresses by delaying the computation
until a routine is called for the first time. If a routine is never called, the dynamic
linker does not need to compute its address. This feature can improve performance
for large libraries when an application uses only a subset of the routines in the
79
VxWorks
Application Programmer's Guide, 6.9
This section illustrates how to get information about shared libraries from the
shell, using command interpreter commands.
The two commands below illustrate different ways start the [Link]
application in the background, so that the shell is available for other commands:
[vxWorks *]# [Link] 2 &
Launching process '[Link]' ...
Process '[Link]' (process Id = 0x8109a0) launched.
Attachment number for process '[Link]' is %1.
and
[vxWorks *]# rtp exec -e "LD_LIBRARY_PATH=/romfs/lib" [Link] 2 &
Launching process '[Link]' ...
Process '[Link]' (process Id = 0x807c90) launched.
Attachment number for process '[Link]' is %1.
The rtp command can then be used to display information about processes. In this
case it shows information about the process started with the first of the two
commands above.
[vxWorks *]# rtp
The shl command displays information about shared libraries. The REF CNT
column provides information about the number of clients per library. The < symbol
to the left of the shared library name indicates that the full path is not displayed.
In this case, [Link].1 is not in the same place as [Link].1; it is in the same
directory as the executable.
[vxWorks *]# shl
SHL NAME ID TEXT ADDR TEXT SIZE DATA SIZE REF CNT
-------------------- ---------- ---------- ---------- ---------- -------
80
4 Static Libraries, Shared Libraries, and Plug-Ins
4.8 Developing Shared Libraries
Note that the shl info command will provide the full path.
This section describes common problems with the interaction between shared
libraries and the applications that use them, as well as a tool for examining ELF
files.
Failures related to the inability of the application to locate [Link].1 or some other
run-time share library would manifest themselves from the shell as follows:
[vxWorks *]# [Link] 2 &
Launching process '[Link]' ...
Process '[Link]' (process Id = 0x811000) launched.
Attachment number for process '[Link]' is %1.
Shared object "[Link].1" not found
When a shared library cannot be found, make sure that its location has been
correctly identified or that it resides in the same location as the executable
(4.8.7 Locating and Loading Shared Libraries at Run-time, p.76). If the shared libraries
are not stored on the target, also make sure that they are accessible from the target.
The readelf tool can be used to extract dynamic records from an executable or a
dynamic shared object, such as a shared object name and path. This can be
particularly useful to determine whether the build has created the required ELF
81
VxWorks
Application Programmer's Guide, 6.9
records—in particular the SONAME record in the shared library and the NEEDED
record in the dynamic RTP application that uses the library (and optionally the
RPATH record in the application).
Use the version of readelf that is appropriate for the target architecture; for
example, use readelfppc on a PowerPC file. The various versions of the tool are
provided in installDir/gnu/3.3.2-vxworks6x/hostType/bin.
The -d flag causes readelf to list dynamic records by tag type, such as NEEDED,
SONAME, and RPATH. (For information about how these records are used, see
4.8.4 About Shared Library Names and ELF Records, p.74 and Using the ELF RPATH
Record, p.78.
The following example shows information about an RTP application that has been
linked against both [Link].1 and [Link].1 (as indicated by the
NEEDED records). The RPATH record indicates that the dynamic linker should look
for the libraries in /romfs/lib at runtime.
C:\WindRiver\gnu\4.1.2-vxworks-6.6\x86-win32\bin>readelfpentium -d \
C:\workspace3\MyRTP\SIMPENTIUMdiab_RTP\MyRTP\Debug\[Link]
82
4 Static Libraries, Shared Libraries, and Plug-Ins
4.8 Developing Shared Libraries
Note that the information in each of an application’s NEEDED records (the shared
library name), is derived from corresponding shared library SONAME record
when the application is linked against the library at build time.
For information about shared object names, shared library versions, and locating
shared libraries at run-time, see 4.8.5 Creating Shared Object Names for Shared
Libraries, p.75, 4.8.7 Locating and Loading Shared Libraries at Run-time, p.76, and
4.8.6 Using Different Versions of Shared Libraries, p.76.
Loading shared libraries from a Windows host system with FTP (the default
method) can be excessively slow. As an alternative, shared libraries can be
included in the VxWorks system image with the ROMFS file system, or NFS can be
used to provide the target system with access to shared libraries on the host.
While ROMFS is useful for deployed systems, using it for development means
long edit-compile-debug cycles, as you must rebuild the system image and reboot
the target whenever you want to use modified code. During development,
therefore, it is better to maintain shared libraries on the host file system and have
the target load them from the network. The NFS file system provides for much
faster loading than ftp or the Target Server File System.
Using NFS
To use of NFS, you can either install an NFS server on Windows or make use of
remote access to a UNIX machine that runs an NFS server. If you have remote
access, you can use the UNIX machine to boot your target and export its file
system.
If you choose to install an NFS server, you can use the one that Microsoft provides
free of charge as part of its Windows Services for UNIX (SFU) package. It can be
downloaded from [Link] The full SFU 3.5 package is a
223MB self-extracting executable to download, but if you only install the NFS
Server, it takes about 20MB on your hard disk.
To install the Microsoft NFS server, run the SFU [Link] and select NFS Server
only. The setup program prompts you to install NFS User Synchronization as
well, which you should do. The corresponding Windows services are installed and
started automatically.
To configure the Windows NFS server for use with a VxWorks target:
1. In Windows Explorer, select your Workspace and use the context menu to
select Share...
83
VxWorks
Application Programmer's Guide, 6.9
Before you can use NFS to load shared libraries, VxWorks also must be
reconfigured with NFS facilities.
Adding the INCLUDE_NFS_MOUNT_ALL component provides all the necessary
features. Make sure the target the target connection is disconnected before you
rebuild your kernel image.
When you reboot the target it automatically mounts all NFS shares exported by the
host. To verify that VxWorks can access your NFS mount, use the devs and
ls "/Workspace" commands from the kernel shell.
84
4 Static Libraries, Shared Libraries, and Plug-Ins
4.10 Developing Plug-Ins
NOTE: A shared library can be used as a plug-in if the application that requires it
is not linked against it at build time.
! CAUTION: Code built for variants of VxWorks or for different releases of VxWorks
is not binary compatible between variants or releases. Code must be built
specifically for uniprocessor (UP) VxWorks, VxWorks SMP, 32-bit VxWorks, 64-bit
VxWorks, or for a VxWorks system based on variant libraries produced with a
VxWorks source build (VSB) project—or for any supported combination of these
variants. The only exception to this rule is that RTP application executables can be
run on both UP VxWorks and VxWorks SMP (when all else is the same).
85
VxWorks
Application Programmer's Guide, 6.9
NOTE: Shared libraries and plug-ins are not supported for the Coldfire
architecture.
VxWorks must be configured with support for plug-ins. For information in this
regard, see 4.4 Configuring VxWorks for Shared Libraries and Plug-ins, p.68.
For information about initialization and termination of plug-ins, see 4.5 Common
Development Issues: Initialization and Termination, p.69.
The code requirements for developing an RTP application that makes use of
plug-ins are as follows:
■
Include the dlfcn.h header file.
■
Use dlopen( ) to load the plug-in and to access its functions and data.
■
Use dlsym( ) to resolve a symbol (defined in the shared object) to its address.
86
4 Static Libraries, Shared Libraries, and Plug-Ins
4.11 Developing RTP Applications That Use Plug-Ins
■
Use dlclose( ) when the plug-in is no longer needed. The rtld library, which
provides the APIs for these calls, is automatically linked into a dynamic
executable.
For an examples illustrating implementation of these requirements, see Example of
Dynamic Linker API Use, p.87 and Example Application Using a Plug-In, p.88. For
general information about developing RTP applications, see 3.4 Developing RTP
Applications, p.37.
Note that calls made between C and C++ code require special consideration, for
information in this regard, see 5.5 Calls Between C and C++ Code, p.93.
An RTP application can explicitly identify the location of a plug-in for the dynamic
linker with the dlopen( ) call that is used to load the plug-in. It does so by
providing the full path to the plug-in (as illustrated in Example Application Using a
Plug-In, p.88).
If only the plug-in name—and not the full path—is used in the dlopen( ) call, the
dynamic linker relies on same mechanisms as are used to find shared libraries. For
information in this regard, see 4.8.7 Locating and Loading Shared Libraries at
Run-time, p.76.
The following code fragment illustrates basic use of the APIs required to work with
a plug-in:
void *handle;
void * addr;
void (*funcptr)();
funcptr();
dlclose(handle);
87
VxWorks
Application Programmer's Guide, 6.9
Assume, for example, that you have a networking application and you want to be
able to add support for new datagram protocols. You can put the code for
datagram protocols into plug-ins, and load them on demand, using a separate
configuration protocol. The application might look like the following:
#include <dlfcn.h>
[...]
free(path);
return handle;
}
[...]
int
send_packet(PROTO handle, struct in_addr addr, const char *data, size_t len)
{
int (*proto_send_packet)(struct in_addr, const char *, size_t);
/* find the packet sending routine within the plugin and use it to send the
packet as requested */
Assume you implement a new protocol named reliable. You would compile the
code as PIC, then link it using the -Xdynamic -Xshared flags (with the Wind River
compiler) into a shared object named [Link] (the comparable GNU flags
would be -non-static and -shared). You install [Link] as
/romfs/plug-ins/[Link] on the target.
When a configuration request packet arrives on a socket, the application would
take the name of the protocol (reliable) and call attach( ) with it. The attach( )
routine uses dlopen( ) to load the shared object named
/romfs/plug-ins/[Link]. Subsequently, when a packet must be sent to a
88
4 Static Libraries, Shared Libraries, and Plug-Ins
4.11 Developing RTP Applications That Use Plug-Ins
particular address using the new protocol, the application would call
send_packet( ) with the return value from attach( ), the packet address, and data
parameters. The send_packet( ) routine looks up the protocol-specific
send_packet( ) routine inside the plug-in and calls it with the address and data
parameters. To unload a protocol module, the application calls detach( ).
The routines used by an application to manage plug-ins are described in Table 4-1.
Routine Description
For more information about these APIs, see the rtld library entry in the VxWorks
Application API Reference.
Compile any application that uses a plug-in as a dynamic executable—that is, with
the Wind River Compiler -Xdynamic option or the GNU -non-static option. This
links the application with the dynamic linker. Static executables cannot load
plug-ins because they do not have the loader embedded in them (which provides
dlopen( ) and so on).
If the application has symbols that are referenced by a plug-in, use the following
linker option to force the dynamic linker to register the application’s symbols
when it is loaded:
■
-Wl,-E or -Wl,-Xexport-dynamic with the Wind River toolchain.
Note that -E or -Xexport-dynamic can be used directly with the linker, but they
require -Wl to be passed through to the linker by the compiler.
■
-E or -export-dynamic with the GNU toolchain.
Do not link the application against the plug-in when you build the application. If
it is linked against the plug-in, the dynamic linker will attempt to load it when the
application is started (as with a shared library)—and succeed if the shared object
name and run-time path are defined appropriately for this action (that is, as for
shared libraries).
For general information about building applications that use plug-ins, see
4.6 Common Development Facilities, p.71.
89
VxWorks
Application Programmer's Guide, 6.9
NOTE: Note that the default C shared library is intended to facilitate development,
but may not be suitable for production systems because of its size.
90
5
C++ Development
5.1 Introduction 91
5.2 Configuring VxWorks for C++ 92
5.3 C++ Header Files 92
5.4 Spawning Tasks That Use C++ 92
5.5 Calls Between C and C++ Code 93
5.6 C++ Compiler Caveats 93
5.7 C++ Compiler Differences 94
5.8 Namespaces 95
5.9 C++ Exception Handling 96
5.10 Standard Template Library (STL) 96
5.11 C++ Demo Example 97
5.1 Introduction
This chapter provides information about C++ development for VxWorks using the
Wind River and GNU toolchains.
! WARNING: Wind River Compiler C++ and GNU C++ binary files are not
compatible.
NOTE: This chapter provides information about facilities available for real-time
processes. For information about facilities available in the VxWorks kernel, see the
corresponding chapter in the VxWorks Kernel Programmer’s Guide.
91
VxWorks
Application Programmer's Guide, 6.9
92
5 C++ Development
5.5 Calls Between C and C++ Code
! CAUTION: Failure to use the VX_FP_TASK option when spawning a task that uses
C++ can result in hard-to-debug, unpredictable floating-point register corruption
at run-time. C++ exception handlers use floating point registers. The VX_FP_TASK
option ensures that floating point registers are saved and restored properly during
context switching.
Use this syntax to make C symbols accessible to C++ code as well. VxWorks C
symbols are automatically available to C++ because the VxWorks header files use
this mechanism for declarations.
Using the #ifdef construct allows a single header file to be used for both C and
C++.
If you do not use the extern "C" linkage specifier appropriately, the type of error
that results depends on the context in which it occurs (build-time static linkage,
run-time loading and linkage of a plug-in with an RTP application, and so on).
93
VxWorks
Application Programmer's Guide, 6.9
! WARNING: Wind River Compiler C++ and GNU C++ binary files are not
compatible.
The following sections briefly describe the differences in compiler support for
template instantiation and run-time type information.
In C, every function and variable used by a program must be defined in exactly one
place (more precisely one translation unit). However, in C++ there are entities
which have no clear point of definition but for which a definition is nevertheless
required. These include template specializations (specific instances of a generic
template; for example, std::vector int), out-of-line bodies for inline functions, and
virtual function tables for classes without a non-inline virtual function. For such
entities a source code definition typically appears in a header file and is included
in multiple translation units.
To handle this situation, both the Wind River Compiler and the GNU compiler
generate a definition in every file that needs it and put each such definition in its
own section. The Wind River compiler uses COMDAT sections for this purpose,
while the GNU compiler uses linkonce sections. In each case the linker removes
duplicate sections, with the effect that the final executable contains exactly one
copy of each needed entity.
It is highly recommended that you use the default settings for template
instantiation, since these combine ease-of-use with minimal code size. However it
is possible to change the template instantiation algorithm; see the compiler
documentation for details.
94
5 C++ Development
5.8 Namespaces
GNU Compiler
Both compilers support Run-time Type Information (RTTI), and the feature is
enabled by default. This feature adds a small overhead to any C++ program
containing classes with virtual functions.
For the Wind River Compiler, the RTTI language feature can be disabled with the
-Xrtti-off flag.
For the GNU compiler, the RTTI language feature can be disabled with the -fno-rtti
flag.
5.8 Namespaces
Both the Wind River and GNU C++ compilers supports namespaces. You can use
namespaces for your own code, according to the C++ standard.
The C++ standard also defines names from system header files in a namespace
called std. The standard requires that you specify which names in a standard
header file you will be using.
The following code is technically invalid under the latest standard, and will not
work with this release. It compiled with a previous release of the GNU compiler,
but will not compile under the current releases of either the Wind River or GNU
C++ compilers:
#include <iostream.h>
int main()
{
cout << "Hello, world!" << endl;
}
95
VxWorks
Application Programmer's Guide, 6.9
The following examples provide three correct alternatives illustrating how the
C++ standard would now represent this code. The examples compile with either
the Wind River or the GNU C++ compiler:
// Example 1
#include <iostream>
int main()
{
std::cout << "Hello, world!" << std::endl;
}
// Example 2
#include <iostream>
using std::cout;
using std::endl;
int main()
{
cout << "Hello, world!" << endl;
}
// Example 3
#include <iostream>
using namespace std;
int main()
{
cout << "Hello, world!" << endl;
}
96
5 C++ Development
5.11 C++ Demo Example
97
VxWorks
Application Programmer's Guide, 6.9
98
6
Multitasking
6.1 Introduction 99
6.2 About Tasks and Multitasking 100
6.3 VxWorks System Tasks 103
6.4 Task Scheduling 109
6.5 Task Creation and Management 112
6.6 Task Error Status: errno 121
6.7 Task Exception Handling 122
6.8 Shared Code and Reentrancy 123
6.1 Introduction
Modern real-time systems are based on the complementary concepts of
multitasking and intertask communications. A multitasking environment allows a
real-time application to be constructed as a set of independent tasks, each with its
own thread of execution and set of system resources. Tasks are the basic unit of
scheduling in VxWorks. All tasks, whether in the kernel or in processes, are subject
to the same scheduler (VxWorks processes are not themselves scheduled).
For information about POSIX thread support for VxWorks, see 9. POSIX Facilities.
NOTE: This chapter provides information about facilities available for real-time
processes. For information about facilities available in the VxWorks kernel, see the
corresponding chapter in the VxWorks Kernel Programmer’s Guide.
99
VxWorks
Application Programmer's Guide, 6.9
NOTE: The POSIX standard includes the concept of a thread, which is similar to a
task, but with some additional features. For details, see 9.13 POSIX Threads, p.187.
The kernel maintains the current state of each task in the system. A task changes
from one state to another as a result of activity such as certain function calls made
by the application (for example, when attempting to take a semaphore that is not
available) and the use of development tools such as the debugger.
100
6 Multitasking
6.2 About Tasks and Multitasking
The highest priority task that is in the ready state is the task that executes. When
tasks are created with taskSpawn( ), they immediately enter the ready state. For
information about the ready state, see Scheduling and the Ready Queue, p.110.
When tasks are created with taskCreate( ), or taskOpen( ) with the
VX_TASK_NOACTIVATE options parameter, they are instantiated in the suspended
state. They can then be activated with taskActivate( ), which causes them to enter
the ready state. The activation phase is fast, enabling applications to create tasks
and activate them in a timely manner.
Table 6-1 describes the task states and the state symbols that you see when working
with development tools.
Note that task states are additive; a task may be in more than one state at a time.
Transitions may take place with regard to one of multiple states. For example, a
task may transition from pended to pended and stopped. And if it then becomes
unpended, its state simply becomes stopped.
READY The task is not waiting for any resource other than the CPU.
PEND The task is blocked due to the unavailability of some resource
(such as a semaphore).
DELAY The task is asleep for some duration.
SUSPEND The task is unavailable for execution (but not pended or delayed).
This state is used primarily for debugging. Suspension does not
inhibit state transition, only execution. Thus, pended-suspended
tasks can still unblock and delayed-suspended tasks can still
awaken.
STOP The task is stopped by the debugger (also used by the error
detection and reporting facilities).
DELAY + S The task is both delayed and suspended.
PEND + S The task is both pended and suspended.
PEND + T The a task is pended with a timeout value.
STOP + P Task is pended and stopped (by the debugger, error detection and
reporting facilities, or SIGSTOP signal).
STOP + S Task is stopped (by the debugger, error detection and reporting
facilities, or SIGSTOP signal) and suspended.
STOP + T Task is delayed and stopped (by the debugger, error detection and
reporting facilities, or SIGSTOP signal).
PEND + S + T The task is pended with a timeout value and suspended.
STOP +P + S Task is pended, suspended and stopped by the debugger.
101
VxWorks
Application Programmer's Guide, 6.9
The STOP state is used by the debugging facilities when a breakpoint is hit. It is
also used by the error detection and reporting facilities (for more information, see
14. Error Detection and Reporting). Example 6-1 shows output from the i( ) shell
command, displaying task state information.
-> i
Figure 6-1 provides a simplified illustration of task state transitions. For the
purpose of clarity, it does not show the additive states discussed in Tasks States and
State Symbols, p.101, nor does it show the STOP state used by debugging facilities.
The routines listed are examples of those that would cause the associated
transition. For example, a task that called taskDelay( ) would move from the ready
state to the delayed state.
Note that taskSpawn( ) causes a task to enter the ready state when it is created,
whereas taskCreate( ) causes a task to enter the suspended state when it is created
(using taskOpen( ) with the VX_TASK_NOACTIVATE option also achieves the
latter purpose).
102
6 Multitasking
6.3 VxWorks System Tasks
! CAUTION: Do not suspend, delete, or change the priority of any of these tasks.
Doing so will result in unpredictable system behavior.
103
VxWorks
Application Programmer's Guide, 6.9
Root Task
Priority 0
Component N/A
Description The root task is the first task executed by the kernel. The entry
point of the root task is usrRoot( ), which initializes most
VxWorks facilities. It spawns such tasks as the logging task,
the exception task, the network task, and the tRlogind
daemon. Normally, the root task terminates after all
initialization has completed.
Logging Task
Priority 0
Triggering Event A logMsg( ) call from either an ISR context or task context.
Component INCLUDE_LOGGING
Exception Task
Priority 0
Component INCLUDE_EXC_TASK
104
6 Multitasking
6.3 VxWorks System Tasks
Job Task
Triggering Event N/A. All jobs are queued by VxWorks system facilities.
Component INCLUDE_JOB_TASK
Description The job task executes jobs—that is, function calls—on the
behalf of tasks. It runs at priority 0 while waiting for a
request, and dynamically adjusts its priority to match that of
the task that requests job execution. One of its primary
purposes is to handle suicidal task deletion (that is,
self-deletion of a task).
Priority 0
Component INCLUDE_ISR_DEFER
105
VxWorks
Application Programmer's Guide, 6.9
Description The tIsrN task (or tasks) execute function calls on behalf of
device drivers when those drivers execute
isrDeferJobAdd( ). The N in the task name refers to the index
of the CPU on which the deferral task is running. Separate
tIsrN tasks are created as needed, each with CPU affinity to
CPU N. SMP-aware device drivers defer interrupt-level
processing to the tIsrN task that is executing on the local
CPU, to avoid the overhead of cross-processor
communication to schedule the deferral task.
Network Task
Priority 50 (default).
Component INCLUDE_NET_DAEMON
Priority 3 (default).
Component INCLUDE_WDB
Description The WDB target agent task is created if the target agent is set
to run in task mode. It services requests from the host tools by
way of the target server.
Priority 55 (default).
Component INCLUDE_WDB_PROXY
106
6 Multitasking
6.3 VxWorks System Tasks
Description The proxy allows for communication with the WDB agent
when VxWorks is not configured with the network stack. The
proxy communicates over TCP/IP with host tools, and
supports UDP/IP, TIPC, and MIPC communications with the
WDB agent.
The following tasks are examples of some of the additional tasks found in common
configurations of VxWorks.
Priority 1 (configurable)
Component INCLUDE_SHELL
Priority 55
Component INCLUDE_RLOGIN
107
VxWorks
Application Programmer's Guide, 6.9
Description The kernel shell login daemon allows remote users to log in
to VxWorks. It accepts a remote login request from another
VxWorks or host system and spawns tRlogInTask_hexNum
and tRlogOutTask_hexNum (where hexNum is a hexadecimal
number identifying the connection). These tasks exist as long
as the remote user is logged on. In addition, unless the shell
is configured in VxWorks 5.5 compatibility mode, the server
spawns a remote shell task tShellRemdecNum (where
decNum is a decimal number specific to the particular remote
shell session).
References The rlogLib API reference entry, the Wind River Network Stack
Programmer’s Guide, and the VxWorks Kernel Shell User’s
Guide.
Priority 50
Component INCLUDE_IPTELNETS
RPC Task
Priority 54
Component INCLUDE_RPC
Description This daemon is RPC server that acts as a central registrar for
RPC services running on the same machine. RPC clients
query the daemon to find out how to contact the various
servers.
108
6 Multitasking
6.4 Task Scheduling
Task scheduling relies on a task’s priority, which is assigned when it is created. The
VxWorks kernel provides 256 priority levels, numbered 0 through 255. Priority 0 is
the highest and priority 255 is the lowest.
While a task is assigned its priority at creation, you can change it programmatically
thereafter. For information about priority assignment, see 6.5.1 Task Creation and
Activation, p.112 and 6.5.9 Task Scheduling Control, p.120).
All application tasks should be in the priority range from 100 to 255.
NOTE: Network application tasks may need to run at priorities lower than 100. For
information in this regard, see the VxWorks Network Stack Programmer’s Guide.
A priority-based preemptive scheduler preempts the CPU when a task has a higher
priority than the current task running. Thus, the kernel ensures that the CPU is
always allocated to the highest priority task that is ready to run. This means that if
109
VxWorks
Application Programmer's Guide, 6.9
a task—with a higher priority than that of the current task—becomes ready to run,
the kernel immediately saves the current task’s context, and switches to the context
of the higher priority task. For example, in Figure 6-2, task t1 is preempted by
higher-priority task t2, which in turn is preempted by t3. When t3 completes, t2
continues executing. When t2 completes execution, t1 continues executing.
The disadvantage of this scheduling policy is that, when multiple tasks of equal
priority must share the processor, if a single task is never blocked, it can usurp the
processor. Thus, other equal-priority tasks are never given a chance to run.
Round-robin scheduling solves this problem (for more information, see
Round-Robin Scheduling, p.111).
The VxWorks scheduler maintains a FIFO ready queue mechanism that includes
lists of all the tasks that are ready to run (that is, in the ready state) at each priority
level in the system. When the CPU is available for given priority level, the task that
is at the front of the list for that priority level executes.
A task’s position in the ready queue may change, depending on the operation
performed on it, as follows:
■
If a task is preempted, the scheduler runs the higher priority task, but the
preempted task retains its position at the front of its priority list.
■ If a task is pended, delayed, suspended, or stopped, it is removed from the
ready queue altogether. When it is subsequently ready to run again, it is placed
at the end of its ready queue priority list. (For information about task states
and the operations that cause transitions between them, see 6.2.1 Task States
and Transitions, p.100).
■ If a task’s priority is changed with taskPrioritySet( ), it is placed at the end of
its new priority list.
■
If a task’s priority is temporarily raised based on the mutual-exclusion
semaphore priority-inheritance policy (using the SEM_INVERSION_SAFE
option), it returns to the end of its original priority list after it has executed at
the elevated priority. (For more information about the mutual exclusion
semaphores and priority inheritance, see Priority Inheritance Policy, p.136.)
110
6 Multitasking
6.4 Task Scheduling
Round-Robin Scheduling
111
VxWorks
Application Programmer's Guide, 6.9
priority task completes, assuming that no other task of a higher priority is ready to
run. In the case where the task blocks, it is placed at the end of the ready queue list
for its priority level. If preemption is disabled during round-robin scheduling, the
time-slice count of the executing task is not incremented.
Time-slice counts are accrued by the task that is executing when a system tick
occurs, regardless of whether or not the task has executed for the entire tick
interval. Due to preemption by higher priority tasks or ISRs stealing CPU time
from the task, it is possible for a task to effectively execute for either more or less
total CPU time than its allotted time slice.
Figure 6-3 shows round-robin scheduling for three tasks of the same priority: t1, t2,
and t3. Task t2 is preempted by a higher priority task t4 but resumes at the count
where it left off when t4 is finished.
112
6 Multitasking
6.5 Task Creation and Management
Note that a task’s priority can be changed after it has been spawned; see 6.5.9 Task
Scheduling Control, p.120.
The taskSpawn( ) routine creates the new task context, which includes allocating
the stack and setting up the task environment to call the main routine (an ordinary
subroutine) with the specified arguments. The new task begins execution at the
entry to the specified routine.
Routine Description
taskOpen( ) Open a task (or optionally create one, if it does not exist).
The taskOpen( ) routine provides a POSIX-like API for creating a task (with
optional activation) or obtaining a handle on existing task. It also provides for
creating a task as a public object, with visibility across all processes and the kernel
(see 6.5.3 Inter-Process Communication With Public Tasks, p.114). The taskOpen( )
routine is the most general purpose task-creation routine.
The taskSpawn( ) routine embodies the lower-level steps of allocation,
initialization, and activation. The initialization and activation functions are
provided by the routines taskCreate( ) and taskActivate( )—however, Wind River
recommends that you use these routines only when you need greater control over
allocation or activation.
A task must be named explicitly (using an ASCII string of any length) if it is created
with taskOpen( ). It does not have to be named if it is created with taskSpawn( ),
in which case using a null pointer is supplied for the name argument results in
automatic assignment of a unique name. The name is of the form tN, where N is a
decimal integer that is incremented by one for each task that is created without an
explicit name. Regards of how a task is created, a task ID is returned when it is
spawned.
To avoid name conflicts, VxWorks uses a convention of prefixing any kernel task
name started from the target with the letter t, and any task name started from the
host with the letter u. In addition, the name of the initial task of a real-time process
is the executable file name (less the extension) prefixed with the letter i.
A task must be created with a name that begins with a forward slash (/) in order
to be a public object accessible throughout the system (and not just in the memory
context in which it was created—process or kernel). For more information, see
6.5.3 Inter-Process Communication With Public Tasks, p.114.
Most VxWorks task routines take a task ID as the argument specifying a task.
VxWorks uses a convention that a task ID of 0 (zero) always implies the calling
task.
113
VxWorks
Application Programmer's Guide, 6.9
The following rules and guidelines should be followed when naming tasks:
■ The names of public tasks must be unique and must begin with a forward slash
(for example, /tMyTask). Note that public tasks are visible throughout the
entire system—all processes and the kernel. For more information about
public tasks, see 6.5.3 Inter-Process Communication With Public Tasks, p.114.
■
The names of private tasks should be unique. VxWorks does not require that
private task names be unique, but it is preferable to use unique names to avoid
confusing the user. (Note that private tasks are visible only within the entity in
which they were created—either the kernel or a process.)
To use the host development tools to their best advantage, task names should not
conflict with globally visible routine or variable names.
The taskLib routines listed in Table 6-3 manage task IDs and names.
Routine Description
Note that for use within a process, it is preferable to use taskName( ) rather than
taskNameGet( ) from a process, as the former does not incur the overhead of a
system call.
VxWorks tasks can be created as private objects, which are accessible only within
the memory space in which they were created (kernel or process); or as public
objects, which are accessible throughout the system (the kernel and all processes).
Whether tasks are created as private or public objects is determined by how they
are named when created. Public tasks must be explicitly named with a
forward-slash prefix (for example, /tMyTask). For more information about naming
tasks, see 6.5.2 Task Names and IDs, p.113.
Creating a task as a public object allows other tasks from outside of its process to
send signals or events to it (with the taskKill( ) or the eventSend( ) routine,
respectively).
114
6 Multitasking
6.5 Task Creation and Management
For detailed information, see 7.8 Inter-Process Communication With Public Objects,
p.154 and the taskLib entry in the VxWorks Application API references.
When a task is created, you can pass in one or more option parameters, which are
listed in Table 6-4. The result is determined by performing a logical OR operation
on the specified options.
Name Description
VX_NO_STACK_FILL Does not fill the stack with 0xEE (see Filling Task
Stacks, p.116).
VX_NO_STACK_PROTECT Create without stack overflow or underflow guard
zones (see 6.5.5 Task Stack, p.116).
VX_TASK_NOACTIVATE Used with taskOpen( ) so that the task is not
activated.
You must include the VX_FP_TASK option when creating a task that does any of the
following:
■
Performs floating-point operations.
■
Calls any function that returns a floating-point value.
■
Calls any function that takes a floating-point value as an argument.
For example:
tid = taskSpawn ("tMyTask", 90, VX_FP_TASK, 20000, myFunc, 2387, 0, 0,
0, 0, 0, 0, 0, 0, 0);
115
VxWorks
Application Programmer's Guide, 6.9
Note that in addition to using the VX_NO_STACK_FILL task creation option for
individual tasks, you can use the VX_GLOBAL_NO_STACK_FILL configuration
parameter (when you configure VxWorks) to disable stack filling for all tasks and
interrupts in the system.
By default, task and interrupt stacks are filled with 0xEE. Filling stacks is useful
during development for debugging with the checkStack( ) routine. It is generally
not used in deployed systems because not filling stacks provides better
performance during task creation (and at boot time for statically-initialized tasks).
The size of each task’s stack is defined when the task is created (see 6.5.1 Task
Creation and Activation, p.112).
It can be difficult, however, to know exactly how much stack space to allocate. To
help avoid stack overflow and corruption, you can initially allocate a stack that is
much larger than you expect the task to require. Then monitor the stack
periodically from the shell with checkStack( ) or ti( ). When you have determined
actual usage, adjust the stack size accordingly for testing and for the deployed
system.
In addition to experimenting with task stack size, you can also configure and test
systems with guard zone protection for task stacks (for more information, see Task
Stack Protection, p.116).
! CAUTION: For 64-bit VxWorks, due to the larger size of several data types in the
LP64 data model, a task is likely to need a larger stack than what is sufficient for
32-bit VxWorks. Typically an increase of 35% to 50% is enough to most
applications, although there may be cases in which the stack size must be doubled.
Use the checkStack( ) shell command to determine if your tasks’ stacks are too
small (that is, the high water mark is too close to the size of the stack).
Task stacks can be protected with guard zones and by making task stacks
non-executable.
116
6 Multitasking
6.5 Task Creation and Management
Note that guard zones cannot catch instances in which a buffer that causes an
overflow is greater than the page size (although this is rare). For example, if the
guard zone is one page of 4096 bytes, and the stack is near its end, and then a buffer
of a 8000 bytes is allocated on the stack, the overflow will not be detected.
User-mode (RTP) tasks have overflow and underflow guard zones on their
execution stacks by default. This protection is provided by the INCLUDE_RTP
component, which is required for process support. It is particularly important in
providing protection from system calls overflowing the calling task’s stack.
Configuring VxWorks with the INCLUDE_PROTECT_TASK_STACK component
adds overflow (but not underflow) protection for user-mode task exception stacks.
Note that RTP task guard zones for execution stacks do not use any physical
memory—they are virtual memory entities in user space. The guard zones for RTP
task exception stacks, however, are in kernel memory space and mapped to physical
memory.
Note that the INCLUDE_PROTECT_TASK_STACK component does not provide
stack protection for tasks that are created with the VX_NO_STACK_PROTECT task
option (see 6.5.4 Task Creation Options, p.115). If a task is created with this option,
no guard zones are created for that task.
The size of the guard zones are defined by the following configuration parameters:
■ TASK_USER_EXEC_STACK_OVERFLOW_SIZE for user task execution stack
overflow size.
■ TASK_USER_EXEC_STACK_UNDERFLOW_SIZE for user task execution stack
underflow size.
■ TASK_USER_EXC_STACK_OVERFLOW_SIZE for user task exception stack
overflow size.
The value of these parameters can be modified to increase the size of the guard
zones on a system-wide basis. The size of a guard zone is rounded up to a multiple
of the CPU MMU page size. The insertion of a guard zone can be prevented by
setting the parameter to zero.
Note that for POSIX threads in processes, the size of the execution stack guard zone
can be set on an individual basis before the thread is created (using the pthread
attributes object, pthread_attr_t). For more information, see 9.13.1 POSIX Thread
Stack Guard Zones, p.187.
The routines listed in Table 6-5 get information about a task by taking a snapshot
of a task’s context when the routine is called. Because the task state is dynamic, the
information may not be current unless the task is known to be dormant (that is,
suspended).
Routine Description
117
VxWorks
Application Programmer's Guide, 6.9
Routine Description
For information about task-specific variables and their use, see 6.8.3 Task-Specific
Variables, p.124.
Tasks can be dynamically deleted from the system. VxWorks includes the routines
listed in Table 6-6 to delete tasks and to protect tasks from unexpected deletion.
Routine Description
exit( ) Terminates the specified process (and therefore all tasks in it)
and frees the process’ memory resources.
taskExit( ) Terminates the calling task (in a process) and frees the stack and
any other memory resources, including the task control block.a
taskDelete( ) Terminates a specified task and frees memory (task stacks and
task control blocks only).b The calling task may terminate itself
with this routine.
taskSafe( ) Protects the calling task from deletion by any other task in the
same process. A task in a different process can still delete that
task by terminating the process itself with kill( ).
! WARNING: Make sure that tasks are not deleted at inappropriate times. Before an
application deletes a task, the task should release all shared resources that it holds.
A process implicitly calls exit( ), thus terminating all tasks within it, if the process’
main( ) routine returns. For more information see 2.2.3 RTP Termination, p.8.
Tasks implicitly call taskExit( ) if the entry routine specified during task creation
returns.
When a task is deleted, no other task is notified of this deletion. The routines
taskSafe( ) and taskUnsafe( ) address problems that stem from unexpected
118
6 Multitasking
6.5 Task Creation and Management
deletion of tasks. The routine taskSafe( ) protects a task from deletion by other
tasks. This protection is often needed when a task executes in a critical region or
engages a critical resource.
For example, a task might take a semaphore for exclusive access to some data
structure. While executing inside the critical region, the task might be deleted by
another task. Because the task is unable to complete the critical region, the data
structure might be left in a corrupt or inconsistent state. Furthermore, because the
semaphore can never be released by the task, the critical resource is now
unavailable for use by any other task and is essentially frozen.
Using taskSafe( ) to protect the task that took the semaphore prevents such an
outcome. Any task that tries to delete a task protected with taskSafe( ) is blocked.
When finished with its critical resource, the protected task can make itself available
for deletion by calling taskUnsafe( ), which readies any deleting task. To support
nested deletion-safe regions, a count is kept of the number of times taskSafe( ) and
taskUnsafe( ) are called. Deletion is allowed only when the count is zero, that is,
there are as many unsafes as safes. Only the calling task is protected. A task cannot
make another task safe or unsafe from deletion.
The following code fragment shows how to use taskSafe( ) and taskUnsafe( ) to
protect a critical region of code:
taskSafe ();
semTake (semId, WAIT_FOREVER); /* Block until semaphore available */
.
. /* critical region code */
.
semGive (semId); /* Release semaphore */
taskUnsafe ();
Deletion safety is often coupled closely with mutual exclusion, as in this example.
For convenience and efficiency, a special kind of semaphore, the mutual-exclusion
semaphore, offers an option for deletion safety. For more information, see
7.4.4 Mutual-Exclusion Semaphores, p.134.
The routines listed in Table 6-7 provide direct control over a task’s execution.
Routine Description
119
VxWorks
Application Programmer's Guide, 6.9
Delay operations provide a simple mechanism for a task to sleep for a fixed
duration. Task delays are often used for polling applications. For example, to delay
a task for half a second without making assumptions about the clock rate, call
taskDelay( ), as follows:
taskDelay (sysClkRateGet ( ) / 2);
The routine sysClkRateGet( ) returns the speed of the system clock in ticks per
second. Instead of taskDelay( ), you can use the POSIX routine nanosleep( ) to
specify a delay directly in time units. Only the units are different; the resolution of
both delay routines is the same, and depends on the system clock. For details, see
9.9 POSIX Clocks and Timers, p.182.
Note that calling taskDelay( ) removes the calling task from the ready queue.
When the task is ready to run again, it is placed at the end of the ready queue
priority list for its priority. This behavior can be used to yield the CPU to any other
tasks of the same priority by delaying for zero clock ticks:
taskDelay (NO_WAIT); /* allow other tasks of same priority to run */
System clock resolution is typically 60Hz (60 times per second). This is a relatively
long time for one clock tick, and would be even at 100Hz or 120Hz. Thus, since
periodic delaying is effectively polling, you may want to consider using
event-driven techniques as an alternative.
Tasks are assigned a priority when they are created (see 6.5.1 Task Creation and
Activation, p.112). You can change a task’s priority level while it is executing by
calling taskPrioritySet( ). The ability to change task priorities dynamically allows
applications to track precedence changes in the real world. Be aware, however, that
when a task’s priority is changed with taskPrioritySet( ), it is placed at the end of
the ready queue priority list for its new priority. For information about the ready
queue, see Scheduling and the Ready Queue, p.110.
Note that with the optional POSIX pthread scheduler, pthreads and tasks are
handled differently when their priority is programmatically lowered. If the
priority of a pthread is lowered, it moves to the front of its new priority list.
However, if the priority of a task is lowered, it moves to the end of its new priority
list. For more information in this regard, see Differences in Re-Queuing Pthreads and
Tasks With Lowered Priorities, p.204.
VxWorks provides hook management APIs that allow you to register hook
routines (hooks) that are then invoked when a task is created or a task is deleted.
Spare fields in the task control block (TCB) are used for application extension of a
task’s context.
120
6 Multitasking
6.6 Task Error Status: errno
The VxWorks task hook management routines are listed in Table 6-8; for more
information, see the VxWorks API reference for taskHookLib.
Routine Description
Task create hook routines execute in the context of the creator task. Task create
hooks must consider the ownership of any kernel objects (such as watchdog
timers, semaphores, and so on) created in the hook routine.
Since create hook routines execute in the context of the creator task, new kernel
objects are owned by the creator task's process in the case of RTPs. It may be
necessary to assign ownership of these objects to the new task's RTP in order to
prevent unexpected object reclamation from occurring if and when the RTP of the
creator task terminates.
When the creator task is a kernel task, the kernel owns any kernel objects that are
created. There is, therefore, no concern about unexpected object reclamation for
this case.
121
VxWorks
Application Programmer's Guide, 6.9
Almost all VxWorks functions follow a convention that indicates simple success or
failure of their operation by the actual return value of the function. Many functions
return only the status values OK (0) or ERROR (-1). Some functions that normally
return a nonnegative number (for example, open( ) returns a file descriptor) also
return ERROR to indicate an error. Functions that return a pointer usually return
NULL (0) to indicate an error. In most cases, a function returning such an error
indication also sets errno to the specific error code.
The global variable errno is never cleared by VxWorks routines. Thus, its value
always indicates the last error status set. When a VxWorks subroutine gets an error
indication from a call to another routine, it usually returns its own error indication
without modifying errno. Thus, the value of errno that is set in the lower-level
routine remains available as the indication of error type.
VxWorks errno values encode the module that issues the error, in the most
significant two bytes, and uses the least significant two bytes for individual error
numbers. All VxWorks module numbers are in the range 1–500; errno values with
a module number of zero are used for source compatibility.
All other errno values (that is, positive values greater than or equal to 501<<16, and
all negative values) are available for application use.
See the VxWorks API reference on errnoLib for more information about defining
and decoding errno values with this convention.
122
6 Multitasking
6.8 Shared Code and Reentrancy
Many subroutines are pure code, having no data of their own except dynamic stack
variables. They work exclusively on data provided by the caller as parameters. The
123
VxWorks
Application Programmer's Guide, 6.9
linked-list library, lstLib, is a good example of this. Its routines operate on lists and
nodes provided by the caller in each subroutine call.
Subroutines of this kind are inherently reentrant. Multiple tasks can use such
routines simultaneously, without interfering with each other, because each task
does indeed have its own stack. See Figure 6-5.
Some libraries encapsulate access to common data. This kind of library requires
some caution because the routines are not inherently reentrant. Multiple tasks
simultaneously invoking the routines in the library might interfere with access to
common variables. Such libraries must be made explicitly reentrant by providing
a mutual-exclusion mechanism to prohibit tasks from simultaneously executing
critical sections of code. The usual mutual-exclusion mechanism is the mutex
semaphore facility provided by semMLib and described in 7.4.4 Mutual-Exclusion
Semaphores, p.134.
124
6 Multitasking
6.8 Shared Code and Reentrancy
NOTE: The __thread storage class variables can be used for both UP and SMP
configurations of VxWorks, and Wind River recommends their use in both cases as
the best method of providing task-specific variables. The taskVarLib and
tlsOldLib (formerly tlsLib) facilities—for the kernel-space and user-space
respectively—are not compatible with VxWorks SMP. They are now obsolete and
will be removed from a future release of VxWorks. In addition to being
incompatible with VxWorks SMP, the taskVarLib and tlsOldLib facilities increase
task context switch times. For information about migration, see the VxWorks Kernel
Programmer’s Guide: VxWorks SMP.
Also note that each task has a VxWorks events register, which receives events sent
from other tasks, ISRs, semaphores, or message queues. See 7.7 VxWorks Events,
p.148 for more information about this register, and the routines used to interact
with it.
The __thread specifier may be used alone, with the extern or static specifiers, but
with no other storage class specifier. When used with extern or static, __thread
must appear immediately after the other storage class specifier.
The __thread specifier may be applied to any global, file-scoped static,
function-scoped static, or static data member of a class. It may not be applied to
block-scoped automatic or non-static data member.
When the address-of operator is applied to a thread-local variable, it is evaluated
at run-time and returns the address of the current task’s instance of that variable.
The address may be used by any task. When a task terminates, any pointers to
thread-local variables in that task become invalid.
No static initialization may refer to the address of a thread-local variable.
In C++, if an initializer is present for a thread-local variable, it must be a
constant-expression, as defined in 5.19.2 of the ANSI/ISO C++ standard.
For more information see the tlsLib entry in the VxWorks API reference.
! CAUTION: Do not access __thread variables from an ISR. Doing so may have
unpredictable results.
125
VxWorks
Application Programmer's Guide, 6.9
VxWorks provides the task-local storage (TLS) facility and the routines provided
by tlsOldLib (formerly tlsLib) to maintain information on a per-task basis in RTPs.
NOTE: Wind River does not recommend using the tlsOldLib facility, which is
maintained primarily for backwards-compatibility. Use thread-local (__thread)
storage class variables instead.
With VxWorks, it is possible to spawn several tasks with the same main routine.
Each spawn creates a new task with its own stack and context. Each spawn can also
pass the main routine different parameters to the new task. In this case, the same
rules of reentrancy described in 6.8.3 Task-Specific Variables, p.124 apply to the
entire task.
This is useful when the same function must be performed concurrently with
different sets of parameters. For example, a routine that monitors a particular kind
of equipment might be spawned several times to monitor several different pieces
of that equipment. The arguments to the main routine could indicate which
particular piece of equipment the task is to monitor.
In Figure 6-6, multiple joints of the mechanical arm use the same code. The tasks
manipulating the joints invoke joint( ). The joint number (jointNum) is used to
indicate which joint on the arm to manipulate.
126
7
Intertask and Interprocess
Communication
7.1 Introduction
Intertask communication facilities allow tasks to synchronize and communicate in
order to coordinate their activity. In VxWorks, the intertask communication
facilities include various types of semaphores, message queues, pipes, VxWorks
events, and message channels.
For interprocess and kernel-process communication, VxWorks semaphores and
message queues, pipes, and events (as well as POSIX semaphores and events) can
be created as public objects to provide for intertask communication across memory
boundaries (between the kernel and processes, and between different processes).
In addition, message channels provide a socket-based inter-processor and
inter-process communications mechanism.
NOTE: This chapter provides information about facilities available for real-time
processes. For information about facilities available in the VxWorks kernel, see the
corresponding chapter in the VxWorks Kernel Programmer’s Guide.
127
VxWorks
Application Programmer's Guide, 6.9
NOTE: This chapter provides information about multitasking facilities that are
common to both uniprocessor (UP) and symmetric multiprocessor (SMP)
configurations of VxWorks. It also provides information about those facilities that
are specific to the UP configuration. In the latter case, the alternatives available for
SMP systems are noted.
With few exceptions, the symmetric multiprocessor (SMP) and uniprocessor (UP)
configurations of VxWorks share the same API—the difference amounts to only a
few routines. Also note that some programming practices—such as implicit
synchronization techniques relying on task priority instead of explicit locking—are
not appropriate for an SMP system.
For information about SMP programming and migration, see the VxWorks Kernel
Programmer’s Guide: VxWorks SMP.
128
7 Intertask and Interprocess Communication
7.3 Shared Data Structures
Pipes
Provide an alternative messaging interface to the message queue facility. Pipes
operate through the I/O system, which allows for use of standard I/O routines
and select( ). See 7.6 Pipes, p.147.
VxWorks events
Provide a means of communication and synchronization between tasks and
other tasks, interrupt service routines (ISRs) and tasks, semaphores and tasks,
and message queues and tasks. See 7.7 VxWorks Events, p.148.
For information about signals, which are not intended to be used for
general-purpose intertask and interprocess communication, see 8.2 Signals, p.160.
For information about synchronization and communication facilities designed
specifically for multiple-CPU systems, seethe VxWorks Kernel Programmer’s Guide:
Multiprocessing Technologies.
Global variables, linear buffers, ring buffers, linked lists, and pointers can be
referenced directly by code running in different task contexts. Access to shared
129
VxWorks
Application Programmer's Guide, 6.9
7.4 Semaphores
VxWorks semaphores are highly optimized, providing a very fast intertask
communication mechanism. Semaphores are the primary means for addressing
the requirements of both mutual exclusion and task synchronization, as described
below:
■ For mutual exclusion, semaphores interlock access to shared resources.
■ For synchronization, semaphores coordinate a task’s execution with external
events.
VxWorks provides the following types of semaphores, which are optimized for
different types of uses:
binary
The fastest, most general-purpose semaphore. Optimized for synchronization
or mutual exclusion. For more information, see 7.4.3 Binary Semaphores, p.132.
mutual exclusion
A special binary semaphore optimized for problems inherent in mutual
exclusion: priority inversion, deletion safety, and recursion. For more
information, see 7.4.4 Mutual-Exclusion Semaphores, p.134.
counting
Like the binary semaphore, but keeps track of the number of times a
semaphore is given. Optimized for guarding multiple instances of a resource.
For more information, see 7.4.5 Counting Semaphores, p.139.
read/write
A special type of semaphore that provides mutual exclusion for tasks that need
write access to an object, and concurrent access for tasks that only need read
access to the object. This type of semaphore is particularly useful for SMP
systems. For more information, see 7.4.6 Read/Write Semaphores, p.139.
VxWorks not only provides the semaphores designed expressly for VxWorks, but
also POSIX semaphores, designed for portability. An alternate semaphore library
provides the POSIX-compliant semaphore interface; see 9.16 POSIX Semaphores,
p.206.
130
7 Intertask and Interprocess Communication
7.4 Semaphores
NOTE: The semaphores described here are for use with UP and SMP
configurations of VxWorks. The optional product VxMP provides semaphores that
can be used across processors an asymmetric multiprocessor (AMP) system, but
only in the VxWorks kernel (and not in UP or SMP systems). For more information,
see VxWorks Kernel Programmer’s Guide: Shared Memory Objects.
VxWorks semaphores can be created as private objects, which are accessible only
within the memory space in which they were created (kernel or process); or as
public objects, which are accessible throughout the system. To create a semaphore
as a public object, the semOpen( ) routine must be used and the semaphore name
must begin with forward-slash (for example /mySem). The type of semaphore is
specified with the semaphore-type argument.
For detailed information, see 7.8 Inter-Process Communication With Public Objects,
p.154 and the semLib entry in the VxWorks Application API Reference.
Routine Description
131
VxWorks
Application Programmer's Guide, 6.9
Routine Description
semFlush( ) Unblocks all tasks that are waiting for a binary or counting
semaphore.
132
7 Intertask and Interprocess Communication
7.4 Semaphores
When a task gives a binary semaphore, using semGive( ), the outcome also
depends on whether the semaphore is available (full) or unavailable (empty) at the
time of the call; see Figure 7-3. If the semaphore is already available (full), giving
the semaphore has no effect at all. If the semaphore is unavailable (empty) and no
task is waiting to take it, then the semaphore becomes available (full). If the
semaphore is unavailable (empty) and one or more tasks are pending on its
availability, then the first task in the queue of blocked tasks is unblocked, and the
semaphore is left unavailable (empty).
Mutual Exclusion
133
VxWorks
Application Programmer's Guide, 6.9
SEM_ID semMutex;
When a task wants to access the resource, it must first take that semaphore. As long
as the task keeps the semaphore, all other tasks seeking access to the resource are
blocked from execution. When the task is finished with the resource, it gives back
the semaphore, allowing another task to use the resource.
Thus, all accesses to a resource requiring mutual exclusion are bracketed with
semTake( ) and semGive( ) pairs:
semTake (semMutex, WAIT_FOREVER);
.
. /* critical region, only accessible by a single task at a time */
.
semGive (semMutex);
Synchronization
134
7 Intertask and Interprocess Communication
7.4 Semaphores
Note that mutex semaphores can be created as user-level (user-mode) objects. They
are faster than kernel-level semaphores as long as they are uncontested, which
means the following:
■
The mutex semaphore is available during a semTake( ) operation.
■
There is no task waiting for the semaphore during a semGive( ) operation.
The uncontested case should be the most common, given the intended use of a
mutex semaphore.
By default, using the semMCreate() routine in a process creates a user-level mutex
semaphore. However, a kernel-level semaphore can be created when
semMCreate( ) is used with the SEM_KERNEL option. The semOpen() routine can
only be used to create kernel-level semaphores in a process. Note that user-level
semaphores can only be created as private objects, and not public ones.
135
VxWorks
Application Programmer's Guide, 6.9
For situations in which a task owns more than one mutual-exclusion semaphore at
a time (which have priority-inheritance option enabled), the task executes at the
highest priority that it has inherited from the tasks that are blocked on any of the
mutual-exclusion semaphores that it owns. In addition, it continues to run at that
priority until it has relinquished all of the semaphores on which the other tasks are
blocked.
136
7 Intertask and Interprocess Communication
7.4 Semaphores
Figure 7-6 illustrates the following example of the interaction of multiple tasks
with multiple mutex semaphores:
1. Task t3 with normal priority 100 takes mutex semaphore m1.
2. Task t3 takes semaphore m2.
3. Task t2 with priority 90 preempts task t3.
4. Task t2 attempts to take semaphore m1, and blocks.
5. Task t3 executes with priority of 90, inherited from task t2.
6. Task t1 with priority 80 preempts task t3.
7. Task t1 attempts to take semaphore m2, and blocks.
8. Task t3 executes with priority 80, inherited from task t1.
9. Task t3 relinquishes semaphore m2, remains at priority 80.
10. Task t3 relinquishes semaphore m1, and returns to its normal priority 100.
11. Task t1 gets m2, preempts task t3.
While it may not seem optimal to allow task t3 to run at the priority it inherited
from t1 (90) until it has relinquished both semaphores, the VxWorks
priority-inheritance policy takes this approach in order to keep semaphore
operations fast, with deterministic execution times.
Deletion Safety
137
VxWorks
Application Programmer's Guide, 6.9
/* includes */
#include <vxWorks.h>
#include <semLib.h>
SEM_ID mySem;
init ()
{
mySem = semMCreate (SEM_Q_PRIORITY);
}
funcA ()
{
semTake (mySem, WAIT_FOREVER);
printf ("funcA: Got mutual-exclusion semaphore\n");
...
funcB ();
...
semGive (mySem);
printf ("funcA: Released mutual-exclusion semaphore\n");
}
138
7 Intertask and Interprocess Communication
7.4 Semaphores
funcB ()
{
semTake (mySem, WAIT_FOREVER);
printf ("funcB: Got mutual-exclusion semaphore\n");
...
semGive (mySem);
printf ("funcB: Releases mutual-exclusion semaphore\n");
}
Count
Semaphore Call after Call Resulting Behavior
Counting semaphores are useful for guarding multiple copies of resources. For
example, the use of five tape drives might be coordinated using a counting
semaphore with an initial count of 5, or a ring buffer with 256 entries might be
implemented using a counting semaphore with an initial count of 256. The initial
count is specified as an argument to the semCCreate( ) routine.
139
VxWorks
Application Programmer's Guide, 6.9
A read/write semaphore differs from other types of semaphore in that the access
mode must be specified when the semaphore is taken. The mode determines
whether the access is exclusive (write mode), or if concurrent access is allowed
(read mode). Different APIs correspond to the different modes of access, as
follows:
■ semRTake( ) for read (concurrent) mode
■ semWTake( ) for write (exclusive) mode
You can also use semTake( ) on a read/write semaphore, but the behavior is the
same as semWTake( ). And you can use semGive( ) on a read/write semaphore as
long as the task that owns it is in the same mode.
For more information about read/write semaphore APIs, see Table 7-1 and the
VxWorks API references.
When a task takes a read/write semaphore in write mode, the behavior is identical
to that of a mutex semaphore. The task owns the semaphore exclusively. An
attempt to give a semaphore held by one task in this mode by task results in a
return value of ERROR.
When a task takes a read/write semaphore in read mode, the behavior is different
from other semaphores. It does not provide exclusive access to a resource (does not
protect critical sections), and the semaphore may be concurrently held in read
mode by more than one task.
The maximum number of tasks that can take a read/write semaphore in read
mode can be specified when the semaphore is created with the create routine call.
The system maximum for all read/write semaphores can also be set with
SEM_RW_MAX_CONCURRENT_READERS component parameter. By default it is
set to 32.
If the number of tasks is not specified when the create routine is called, the system
default is used.
Read/write semaphores can be taken recursively in both read and write mode.
Optionally, priority inheritance and deletion safety are available for each mode.
140
7 Intertask and Interprocess Communication
7.4 Semaphores
The uniform VxWorks semaphore interface includes four special options: timeout,
queueing, interruptible by signals, and use with VxWorks events. These options
are not available for either the read/write semaphores described in
7.4.6 Read/Write Semaphores, p.139, or the POSIX-compliant semaphores described
in 9.16 POSIX Semaphores, p.206.
Semaphore Timeout
141
VxWorks
Application Programmer's Guide, 6.9
Priority ordering better preserves the intended priority structure of the system at
the expense of some overhead in take operations because of sorting the tasks by
priority. A FIFO queue requires no priority sorting overhead and leads to
constant-time performance. The selection of queue type is specified during
semaphore creation with the semaphore creation routine. Semaphores using the
priority inheritance option (SEM_INVERSION_SAFE) must select priority-order
queuing.
Semaphores can send VxWorks events to a specified task when they becomes free.
For more information, see 7.7 VxWorks Events, p.148.
142
7 Intertask and Interprocess Communication
7.5 Message Queues
Multiple tasks can send to and receive from the same message queue. Full-duplex
communication between two tasks generally requires two message queues, one for
each direction; see Figure 7-8.
There are two message-queue subroutine libraries in VxWorks. The first of these,
msgQLib, provides VxWorks message queues, designed expressly for VxWorks;
the second, mqPxLib, is compliant with the POSIX standard (1003.1b) for real-time
extensions. See 9.17.1 Comparison of POSIX and VxWorks Message Queues, p.215 for
a discussion of the differences between the two message-queue designs.
VxWorks message queues can be created as private objects, which are accessible
only within the memory space in which they were created (kernel or process); or
as public objects, which are accessible throughout the system. To create a message
queue as a public object, the msgQOpen( ) routine must be used and the message
queue name must begin with forward-slash (for example /myMsgQ).
For detailed information, see 7.8 Inter-Process Communication With Public Objects,
p.154 and the msgQLib entry in the VxWorks Application API Reference.
143
VxWorks
Application Programmer's Guide, 6.9
VxWorks message queues are created, used, and deleted with the routines shown
in Table 7-3. This library provides messages that are queued in FIFO order, with a
single exception: there are two priority levels, and messages marked as high
priority are attached to the head of the queue.
Routine Description
144
7 Intertask and Interprocess Communication
7.5 Message Queues
/* In this example, task1 creates the message queue and sends a message
* to task2. task2 receives the message from the queue and simply
* displays the message.
*/
/* includes */
#include <vxWorks.h>
#include <msgQLib.h>
#include <stdio.h>
/* prototypes */
int task1(void);
int task2(vod);
/* defines */
#define MAX_MSGS (10)
#define MAX_MSG_LEN (100)
MSG_Q_ID myMsgQId;
/* display message */
printf ("Message from task 1:\n%s\n", msgBuf);
return (OK);
}
145
VxWorks
Application Programmer's Guide, 6.9
VxWorks message queues include the ability to select the queuing mechanism
employed for tasks blocked on a message queue. The MSG_Q_FIFO and
MSG_Q_PRIORITY options are provided to specify (to the msgQCreate( ) and
msgQOpen( ) routines) the queuing mechanism that should be used for tasks that
pend on msgQSend( ) and msgQReceive( ).
The VxWorks show( ) command produces a display of the key message queue
attributes, for either kind of message queue. For example, if myMsgQId is a
VxWorks message queue, the output is sent to the standard output device, and
looks like the following from the shell (using the C interpreter):
-> 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
Real-time systems are often structured using a client-server model of tasks. In this
model, server tasks accept requests from client tasks to perform some service, and
usually return a reply. The requests and replies are usually made in the form of
intertask messages. In VxWorks, message queues or pipes (see 7.6 Pipes, p.147) are
a natural way to implement this functionality.
For example, client-server communications might be implemented as shown in
Figure 7-9. Each server task creates a message queue to receive request messages
from clients. Each client task creates a message queue to receive reply messages
from servers. Each request message includes a field containing the msgQId of the
client’s reply message queue. A server task’s main loop consists of reading request
messages from its request message queue, performing the request, and sending a
reply to the client’s reply message queue.
146
7 Intertask and Interprocess Communication
7.6 Pipes
The same architecture can be achieved with pipes instead of message queues, or by
other means that are tailored to the needs of the particular application.
Message queues can send VxWorks events to a specified task when a message
arrives on the queue and no task is waiting on it. For more information, see
7.7 VxWorks Events, p.148.
7.6 Pipes
Pipes provide an alternative interface to the message queue facility that goes
through the VxWorks I/O system. Pipe devices are managed by the pipeDrv
virtual I/O device, and use the kernel message queue facility to bear the actual
message traffic. Tasks write messages to pipes, which are then read by other tasks.
This allows you to implement a client-server model of intertask communications;
see 7.5.4 Servers and Clients with Message Queues, p.146.
As I/O devices, pipes also provide an important feature that message queues
themselves cannot—the use of select( ). This routine allows a task to wait for data
to be available on any of a set of I/O devices. The select( ) routine also works with
other asynchronous I/O devices including network sockets and serial devices.
Thus, by using select( ), a task can wait for data on a combination of several pipes,
sockets, and serial devices; see 11.6.9 Pending on Multiple File Descriptors with
select( ), p.263.
147
VxWorks
Application Programmer's Guide, 6.9
Named pipes can be created in RTPs. However, unless they are specifically deleted
by the application they will persist beyond the life of the RTP in which they were
created. Applications should allow for the possibility that the named pipe already
exists, from a previous invocation, when the application is started.
For more information, see the ioLib API reference entry.
The routine pipeDevCreate( ) creates a pipe device and the underlying message
queue associated with that pipe. The call specifies the name of the created pipe, the
maximum number of messages that can be queued to it, and the maximum length
of each message:
status = pipeDevCreate ("/pipe/name", max_msgs, max_length);
The created pipe is a normally named I/O device. Tasks can use the standard I/O
routines to open, read, and write pipes, and invoke ioctl routines. As they do with
other I/O devices, tasks block when they read from an empty pipe until data is
available, and block when they write to a full pipe until there is space available.
Pipe devices respond to the ioctl( ) functions summarized in Table 7-4. The
functions listed are defined in the header file ioLib.h. For more information, see
the reference entries for pipeDrv and for ioctl( ) in ioLib.
Function Description
FIONREAD Gets the size in bytes of the first message in the pipe.
1. VxWorks events are based on pSOS operating system events. VxWorks introduced
functionality similar to pSOS events (but with enhancements) with the VxWorks 5.5 release.
148
7 Intertask and Interprocess Communication
7.7 VxWorks Events
NOTE: VxWorks events, which are also simply referred to as events in this section,
should not be confused with System Viewer events.
Each task has 32 event flags, bit-wise encoded in a 32-bit word (bits 25 to 32 are
reserved for Wind River use), which is referred to as an event register. The event
register is used to store the events that the task receives from tasks, ISRs,
semaphores, and message queues.
Note that an event flag itself has no intrinsic meaning. The significance of each of
the 32 event flags depends entirely on how any given task is coded to respond to a
specific bit being set. There is no mechanism for recording how many times any
given event has been received by a task. Once a flag has been set, its being set again
by the same or a different sender is essentially an invisible operation.
A a task cannot access the contents of its events registry directly; the
eventReceive( ) routine copies the contents of the events register to the variable
specified by its *pEventsReceived parameter.
The VxWorks event flag macros and values that can be used by Wind River
customers are listed in Table 7-5VxWorks Event Flags, p.150.
149
VxWorks
Application Programmer's Guide, 6.9
Macro Value
VXEV01 0x00000001
VXEV02 0x00000002
VXEV03 0x00000004
VXEV04 0x00000008
VXEV05 0x00000010
VXEV06 0x00000020
VXEV07 0x00000040
VXEV08 0x00000080
VXEV09 0x00000100
VXEV10 0x00000200
VXEV11 0x00000400
VXEV12 0x00000800
VXEV13 0x00001000
VXEV14 0x00002000
VXEV15 0x00004000
VXEV16 0x00008000
VXEV17 0x00010000
VXEV18 0x00020000
VXEV19 0x00040000
VXEV20 0x00080000
VXEV21 0x00100000
VXEV22 0x00200000
VXEV23 0x00400000
VXEV24 0x00800000
The routines that affect the contents of the events register are described in
Table 7-6.
150
7 Intertask and Interprocess Communication
7.7 VxWorks Events
eventReceive( ) Clears or leaves the contents of the task’s events register intact,
depending on the options selected.
A task can pend on one or more events, or simply check on which events have been
received, with a call to eventReceive( ). Events are specified by using the value the
relevant event flags for the routine’s events parameter (see 7.7.2 About Event Flags
and the Task Events Register, p.149). The routine provides options for waiting for one
or all of those events, as well as options for how to manage unsolicited events, and
for checking which events have been received prior to the full set being received.
When the events specified with an eventReceive( ) call have been received and the
task unpends, the contents of the events register is copied to a variable (specified
by the routine’s *pEventsReceived parameter), which is accessible to the task.
When eventReceive( ) is used with the EVENTS_WAIT_ANY option—which means
that the task unpends for the first of any of the specified events that it receives—
the contents of the events variable can be checked to determine which event caused
the task to unpend.
Additional Steps for Receiving Events from Semaphores and Message Queues
In order for a task to receive events when a semaphore or a message queue is free
(as opposed to simply another task), it must first register with the specific object,
using semEvStart( ) for a semaphore or msgQEvStart( ) for a message queue. Only
one task can be registered with any given semaphore or message queue at a time.
The semEvStart( ) routine identifies the semaphore and the events that should be
sent to the task when the semaphore is free. It also provides a set of options to
specify whether the events are sent only the first time the semaphore is free, or each
time; whether to send events if the semaphore is free at the time of registration; and
whether a subsequent semEvStart( ) call from another task is allowed to take effect
(and to unregister the previously registered task).
Once a task has registered with a semaphore, every time the semaphore is released
with semGive( ), and as long as no other tasks are pending on it, events are sent to
the registered task.
To request that event-sending be stopped, the registered task calls semEvStop( ).
Registration with a message queue is similar to registration with a semaphore. The
msgQEvStart( ) routine identifies the message queue and the events that should be
sent to the task when a message arrives and no tasks are pending on it. It provides
151
VxWorks
Application Programmer's Guide, 6.9
a set of options to specify whether the events are sent only the first time a message
is available, or each time; and whether a subsequent call to msgQEvStart( ) from
another task is allowed to take effect (and to unregister the previously registered
task).
Once a task has registered with a message queue, every time the message queue
receives a message and there are no tasks pending on it, events are sent to the
registered task.
To request that event-sending be stopped, the registered task calls msgQEvStop( ).
Tasks and ISRs can send specific events to a task explicitly using eventSend( ),
whether or not the receiving task is prepared to make use of them. Events are
specified by using the value the relevant event flags for the routine’s events
parameter (see 7.7.2 About Event Flags and the Task Events Register, p.149).
Events are sent automatically to tasks that have registered for notification when
semaphores and message queues are free—with semEvStart( ) or msgQEvStart( ),
respectively. The conditions under which objects are free are as follows:
Mutex Semaphore
A mutex semaphore is considered free when it no longer has an owner and no
task is pending on it. For example, following a call to semGive( ), events are
not sent if another task is pending on a semTake( ) for the same semaphore.
Binary Semaphore
A binary semaphore is considered free when no task owns it and no task is
waiting for it.
Counting Semaphore
A counting semaphore is considered free when its count is nonzero and no task
is pending on it. Events cannot, therefore, be used as a mechanism to compute
the number of times a semaphore is released or given.
Message Queue
A message queue is considered free when a message is present in the queue
and no task is pending for the arrival of a message in that queue. Events
cannot, therefore, be used as a mechanism to compute the number of messages
sent to a message queue.
Note that just because an object has been released does not mean that it is free. For
example, if a semaphore is given, it is released; but it is not free if another task is
waiting for it at the time it is released. When two or more tasks are constantly
exchanging ownership of an object, it is therefore possible that the object never
becomes free, and never sends events.
Also note that when an event is sent to a task to indicate that a semaphore or
message queue is free, it does not mean that the object is in any way reserved for the
task. A task waiting for events from an object unpends when the resource becomes
free, but the object may be taken in the interval between notification and
unpending. The object could be taken by a higher priority task if the task receiving
the event was pended in eventReceive( ). Or a lower priority task might steal the
object: if the task receiving the event was pended in some routine other than
eventReceive( ), a low priority task could execute and (for example) perform a
semTake( ) after the event is sent, but before the receiving task unpends from the
152
7 Intertask and Interprocess Communication
7.7 VxWorks Events
blocking call. There is, therefore, no guarantee that the resource will still be
available when the task subsequently attempts to take ownership of it.
! WARNING: Because events cannot be reserved for an application in any way, care
should be taken to ensure that events are used uniquely and unambiguously. Note
that events 25 to 32 (VXEV25 to VXEV32) are reserved for Wind River’s use, and
should not be used by customers. Third parties should be sure to document their
use of events so that their customers do not use the same ones for their
applications.
If a semaphore or message queue is deleted while a task is waiting for events from
it, the task is automatically unpended by the semDelete( ) or msgQDelete( )
implementation. This prevents the task from pending indefinitely while waiting
for events from an object that has been deleted. The pending task then returns to
the ready state (just as if it were pending on the semaphore itself) and receives an
ERROR return value from the eventReceive( ) call that caused it to pend initially.
If, however, the object is deleted between a tasks’ registration call and its
eventReceive( ) call, the task pends anyway. For example, if a semaphore is
deleted while the task is between the semEvStart( ) and eventReceive( ) calls, the
task pends in eventReceive( ), but the event is never sent. It is important, therefore,
to use a timeout other than WAIT_FOREVER when object deletion is expected.
For information about timeouts, see 7.9 About VxWorks API Timeout Parameters,
p.157.
If a task is deleted before a semaphore or message queue sends events to it, the
events can still be sent, but are obviously not received. By default, VxWorks
handles this event-delivery failure silently.
It can, however, be useful for an application that created an object to be informed
when events were not received by the (now absent) task that registered for them.
In this case, semaphores and message queues can be created with an option that
causes an error to be returned if event delivery fails (the
SEM_EVENTSEND_ERROR_NOTIFY and MSG_Q_EVENTSEND_ERROR_NOTIFY
options, respectively). The semGive( ) or msgQSend( ) call then returns ERROR
when the object becomes free.
The error does not mean the semaphore was not given or that the message was not
properly delivered. It simply means that events could not be sent to the registered
task. Note that a failure to send a message or give a semaphore takes precedence
over an events failure.
153
VxWorks
Application Programmer's Guide, 6.9
The routines used for working with events are listed in Table 7-7.
Routine Description
eventReceive( ) Pends a task until the specified events have been received. Can
also be used to check what events have been received in the
interim.
For more information about these routines, see the VxWorks API references for
eventLib, semEvLib, and msgQEvLib.
For the purpose of debugging systems that make use of events, the taskShow,
semShow, and msgQShow libraries display event information.
The taskShow library displays the following information:
■
the contents of the event register
■
the desired events
■
the options specified when eventReceive( ) was called
The semShow and msgQShow libraries display the following information:
■
the task registered to receive events
■
the events the resource is meant to send to that task
■
the options passed to semEvStart( ) or msgQEvStart( )
154
7 Intertask and Interprocess Communication
7.8 Inter-Process Communication With Public Objects
objects are visible from all processes and the kernel, and can therefore be used for
interprocess (and kernel-process) communication. There is no difference in
performance between a public and a private object.
An object can only be defined as public or private when it is created—the
designation cannot be changed thereafter. Public objects must be named when they
are created, and the name must begin with a forward slash; for example, /foo.
Private objects do not need to be named.
For information about naming tasks in addition to that provided in this section, see
6.5.2 Task Names and IDs, p.113.
Public objects are always named, and the name must begin with a forward-slash.
Private objects can be named or unnamed. If they are named, the name must not
begin with a forward-slash.
Only one public object of a given class and name can be created. That is, there can
be only one public semaphore with the name /foo. But there may be a public
semaphore named /foo and a public message queue named /foo. Obviously, more
distinctive naming is preferable (such as /fooSem and /fooMQ).
The system allows creation of only one private object of a given class and name in
any given memory context; that is, in any given process or in the kernel. For
example:
■ If process A has created a private semaphore named bar, it cannot create a
second semaphore named bar.
■ However, process B could create a private semaphore named bar, as long as it
did not already own one with that same name.
Note that private tasks are an exception to this rule—duplicate names are
permitted for private tasks; see 6.5.2 Task Names and IDs, p.113.
To create a named object, the appropriate xyzOpen( ) API must be used, such as
semOpen( ). When the routine specifies a name that starts with a forward slash,
the object will be public.
To delete public objects, the xyzDelete( ) API cannot be used (it can only be used
with private objects). Instead, the xyzClose( ) and xyzUnlink( ) APIs must be used
in accordance with the POSIX standard. That is, they must be unlinked from the
name space, and then the last close operation will delete the object (for example,
using the semUnlink( ) and semClose( ) APIs for a public semaphore).
Alternatively, all close operations can be performed first, and then the unlink
operation, after which the object is deleted. Note that if an object is created with the
OM_DELETE_ON_LAST_CLOSE flag, it is be deleted with the last close operation,
regardless of whether or not it was unlinked.
For detailed information about the APIs used to create public user-mode (RTP)
objects, see the msgQLib, semLib, taskLib, and timerLib entries in the VxWorks
Application API Reference.
155
VxWorks
Application Programmer's Guide, 6.9
/*
* Create a binary semaphore to be used for synchronization
* with the RTP.
*/
/*
* Create a binary semaphore to be used for synchronization
* with the RTP.
*/
156
7 Intertask and Interprocess Communication
7.9 About VxWorks API Timeout Parameters
semGive(semSyncRTP);
printf("\tgave it\n");
}
157
VxWorks
Application Programmer's Guide, 6.9
158
8
Signals
8.1 Introduction
Signals are designed for handling exceptional conditions and asynchronously
altering the flow of control. Signals generated by the operating system include
those produced in response to bus errors and floating point exceptions. The
VxWorks signal facility also provides APIs that can be used to generate and
manage signals programmatically. In applications, signals are most appropriate for
error and exception handling.
NOTE: This chapter provides information about facilities available for real-time
processes. For information about facilities available in the VxWorks kernel, see the
corresponding chapter in the VxWorks Kernel Programmer’s Guide.
NOTE: This chapter provides information about multitasking facilities that are
common to both uniprocessor (UP) and symmetric multiprocessor (SMP)
configurations of VxWorks. It also provides information about those facilities that
are specific to the UP configuration. In the latter case, the alternatives available for
SMP systems are noted.
With few exceptions, the symmetric multiprocessor (SMP) and uniprocessor (UP)
configurations of VxWorks share the same API—the difference amounts to only a
few routines. Also note that some programming practices—such as implicit
synchronization techniques relying on task priority instead of explicit locking—are
not appropriate for an SMP system.
For information about SMP programming and migration, see the VxWorks Kernel
Programmer’s Guide: VxWorks SMP.
159
VxWorks
Application Programmer's Guide, 6.9
8.2 Signals
Signals are an operating system facility designed for handling exceptional
conditions and asynchronously altering the flow of control. In many respects
signals are the software equivalent to hardware interrupts. Signals generated by
the operating system include those produced in response to bus errors and floating
point exceptions. The signal facility also provides APIs that can be used to generate
and manage signals programmatically.
In applications, signals are most appropriate for error and exception handling, and
not for a general-purpose inter-task communication. Common uses include using
signals to kill processes and tasks, to send signal events when a timer has fired or
message has arrived at a message queue, and so on.
In accordance with POSIX, VxWorks supports 63 signals, each of which has a
unique number and default action (defined in signal.h). The value 0 is reserved for
use as the NULL signal.
Signals can be raised (sent) from tasks to tasks or to processes. Signals can be either
caught (received) or ignored by the receiving task or process. Whether signals are
caught or ignored generally depends on the setting of a signal mask. In the kernel,
signal masks are specific to tasks, and if no task is set up to receive a specific signal,
it is ignored. In user space, signal masks are specific to processes; and some signals,
such as SIGKILL and SIGSTOP, cannot be ignored.
To manage responses to signals, you can create and register signal handling
routines that allow a task to respond to a specific signal in whatever way is useful
for your application. For information about signal handlers, see 8.2.5 Signal
Handlers, p.167.
A user -space (process-based) task can raise a signal for any of the following:
■ itself
■ any other task in its process
■ any public task in any process in the system
■ its own process
■ any other process in the system
A user-space task cannot raise a signal for a kernel task—even if it is a public task
(for information about public tasks, see 6.5.3 Inter-Process Communication With
Public Tasks, p.114.) By default, signals sent to a task in a process results in the
termination of the process.
Unlike kernel signal generation and delivery, which runs in the context of the task
or ISR that generates the signal, user-space signal generation is performed by the
sender task, but the signal delivery actions take place in the context of the receiving
task.
For processes, signal handling routines apply to the entire process, and are not
specific to any one task in the process.
Signal handling is done on an process-wide basis. That is, if a signal handler is
registered by a task, and that task is waiting for the signal, then a signal to the
process is handled by that task. Otherwise, any task that does not have the signal
blocked will handle the signal. If there is no task waiting for a given signal, the
signal remains pended in the process until a task that can receive the signal
becomes available.
160
8 Signals
8.2 Signals
Each task has a signal mask associated with it. The signal mask determines which
signals the task accepts. When a task is created, its signal mask is inherited from
the task that created it. If the parent is a kernel task (that is, if the process is
spawned from the kernel), the signal mask is initialized with all signals unblocked.
It also inherits default actions associated with each signal. Both can later be
changed with sigprocmask( ).
VxWorks provides a software signal facility that includes POSIX routines and
native VxWorks routines. The POSIX-compliant signal interfaces include both the
basic signaling interface specified in the POSIX standard 1003.1, and the
queued-signals extension from POSIX 1003.1b.
The following non-POSIX APIs are also provided for signals: taskSigqueue( ),
taskKill( ), rtpKill( ), and taskRaise( ). These APIs are provided to facilitate
porting VxWorks kernel applications to RTP applications.
NOTE: POSIX signals are handled differently in the kernel and in real-time
processes. In the kernel the target of a signal is always a task; but in user space, the
target of a signal may be either a specific task or an entire process.
NOTE: The VxWorks implementation of sigLib does not impose any special
restrictions on operations on SIGKILL, SIGCONT, and SIGSTOP signals such as
those imposed by UNIX. For example, the UNIX implementation of signal( )
cannot be called on SIGKILL and SIGSTOP.
For information about using signals in the kernel, see VxWorks Kernel Programmer’s
Guide: Multitasking.
In addition to signals, VxWorks also provides another type of event notification
with the VxWorks events facility. While signal events are completely
asynchronous, VxWorks events are sent asynchronously, but received
synchronously, and do not require a signal handler. For more information, see
7.7 VxWorks Events, p.148.
161
VxWorks
Application Programmer's Guide, 6.9
Process-based (RTP) applications are automatically linked with the sigLib library
when they are compiled. Initialization of the library is automatic when the process
starts.
Signals are in many ways analogous to hardware interrupts. The basic signal
facility provides a set of 63 distinct signals. A signal handler binds to a particular
signal with sigvec( ) or sigaction( ) in much the same way that an ISR is connected
to an interrupt vector with intConnect( ). A signal can be asserted by calling kill( )
or sigqueue( ). This is similar to the occurrence of an interrupt. The sigprocmask( )
routine let signals be selectively inhibited. Certain signals are associated with
hardware exceptions. For example, bus errors, illegal instructions, and
floating-point exceptions raise specific signals.
VxWorks also provides a POSIX and BSD-like kill( ) routine, which sends a signal
to a task.
For a list and description of the basic set of POSIX signal routines provided by
VxWorks for use with processes, see Table 8-1.
Routine Description
For more information about signal routines, see the VxWorks API reference for
sigLib.
162
8 Signals
8.2 Signals
Routine Description
Additional non-POSIX VxWorks queued signal routines are described in Table 8-3.
These routines are provided for assisting in porting VxWorks 5.x kernel
applications to processes. The POSIX routines described in Table 8-2 should be
used for developing new applications that execute as real-time processes.
Note that a parallel set of non-POSIX APIs are provided for the kill( ) family of
POSIX routines—taskKill( ), rtpKill( ), and rtpTaskKill( ).
163
VxWorks
Application Programmer's Guide, 6.9
Routine Description
#include <stdio.h>
#include <signal.h>
#include <taskLib.h>
#include <rtpLib.h>
#ifdef _WRS_KERNEL
#include <private/rtpLibP.h>
#include <private/taskLibP.h>
#include <errnoLib.h>
#endif
void sigMasterHandler
(
int sig, /* caught signal */
#ifdef _WRS_KERNEL
int code,
#else
siginfo_t * pInfo, /* signal info */
#endif
struct sigcontext *pContext /* unused */
);
/****************************************************************************
*
* main - entry point for the queued signal demo
*
* This routine acts the task entry point in the case of the demo spawned as a
* kernel task. It also can act as a RTP entry point in the case of RTP based
* demo.
*/
164
8 Signals
8.2 Signals
for (;;);
/****************************************************************************
*
* sigMasterHandler - signal handler
*
* This routine is the signal handler for the SIGUSR1 signal
*/
void sigMasterHandler
(
int sig, /* caught signal */
#ifdef _WRS_KERNEL
int code,
#else
siginfo_t * pInfo , /* signal info */
#endif
struct sigcontext *pContext /* unused */
)
{
printf ("Task 0x%x got signal # %d signal value %d \n",
taskIdCurrent, sig,
#ifdef _WRS_KERNEL
code
#else
pInfo->si_value.sival_int
#endif
);
}
/****************************************************************************
*
* sig - helper routine to send a queued signal
*
* This routine can send a queued signal to a kernel task or RTP task or RTP.
* <id> is the ID of the receiver entity. <value> is the value to be sent
* along with the signal. The signal number being sent is SIGUSR1.
*/
#ifdef _WRS_KERNEL
STATUS sig
(
int id,
int val
)
{
union sigval valueCode;
valueCode.sival_int = val;
165
VxWorks
Application Programmer's Guide, 6.9
return (OK);
}
#endif
The code provided in this example can be used to do any of the following:
■
Send a queued signal to a kernel task.
■
Send a queued signal to a task in a process (RTP).
■
Send a queued signal to a process.
The sig( ) routine provided in this code is a helper routine used to send a queued
signal.
To use the code as an RTP application, VxWorks must be configured with
BUNDLE_NET_SHELL and BUNDLE_RTP_POSIX_PSE52.
To send a queued signal to a process:
1. Build the application as an RTP application.
2. Spawn the application. For example:
-> rtpSp "signal_ex.vxe"
value = 10531472 = 0xa0b290
-> Task 0x10000 installed signal handler for signal # 30. Ready for signal.
value = 1 = 0x1
4. From a kernel task (note that the kernel shell runs as a kernel task), use the
sig( ) helper routine to send a queued signal to an RTP, where the id parameter
is the RTP ID. For example, using the RTP ID of 0xa0b290 found in the previous
step:
-> sig 0xa0b290, 50
value = 0 = 0x0
-> Task 0x10000 got signal # 30 signal value 50
166
8 Signals
8.2 Signals
The signal event facility allows a pthread or task to receive notification that a
particular event has occurred (such as the arrival of a message at a message queue,
or the firing of a timer) by way of a signal.
The following routines can be used to register for signal notification of their
respective event activities: mq_notify( ), timer_create( ), timer_open( ),
aio_read( ), aio_write( ) and lio_listio( ).
The POSIX 1003.1-2001 standard defines three signal event notification types:
SIGEV_NONE
Indicates that no notification is required when the event occurs. This is useful
for applications that use asynchronous I/O with polling for completion.
SIGEV_SIGNAL
Indicates that a signal is generated when the event occurs.
SIGEV_THREAD
Provides for callback functions for asynchronous notifications done by a
function call within the context of a new thread. This provides a
multi-threaded process with a more natural means of notification than signals.
VxWorks supports this option in user space (processes), but not in the kernel.
The notification type is specified using the sigevent structure, which is defined in
installDir/vxworks-6.x/target/h/sigeventCommon.h. A pointer the structure is
used in the call to register for signal notification; for example, with mq_notify( ).
To use the signal event facility, configure VxWorks with the INCLUDE_SIGEVENT
component.
As noted above, the SIGEV_THREAD option is only supported in processes, and it
requires that VxWorks be configured with the INCLUDE_SIGEVENTS_THREAD
component and full POSIX thread support (the BUNDLE_RTP_POSIX_PSE52
bundle includes everything required for this option).
Signals are more appropriate for error and exception handling than as a
general-purpose intertask communication mechanism. And normally, signal
handlers should be treated like ISRs: no routine should be called from a signal
handler that might cause the handler to block. Because signals are asynchronous,
it is difficult to predict which resources might be unavailable when a particular
signal is raised.
To be perfectly safe, call only those routines listed in Table 8-4. Deviate from this
practice only if you are certain that your signal handler cannot create a deadlock
situation.
In addition, you should be particularly careful when using C++ for a signal
handler or when invoking a C++ method from a signal handler written in C or
assembly. Some of the issues involved in using C++ include the following:
■
The VxWorks intConnect( ) and signal( ) routines require the address of the
function to execute when the interrupt or signal occurs, but the address of a
non-static member function cannot be used, so static member functions must
be implemented.
167
VxWorks
Application Programmer's Guide, 6.9
■
Objects cannot be instantiated or deleted in signal handling code.
■
C++ code used to execute in a signal handler should restrict itself to Embedded
C++. No exceptions nor run-time type identification (RTTI) should be used.
Library Routines
eventLib eventSend( )
logLib logMsg( )
msgQLib msgQSend( )
sigLib kill( )
168
8 Signals
8.2 Signals
■
Leave any modified data structures in a sane state.
Notify the parent process with an appropriate error return value. Mutual exclusion
between signal handlers and tasks must be managed with care. In general, users
should avoid the following activity in signal handlers:
■
Taking mutual exclusion (such as semaphores) resources that can also be taken
by any other element of the application code. This can lead to deadlock.
■
Modifying any shared data memory that may have been in the process of
modification by any other element of the application code when the signal was
delivered. This compromises mutual exclusion and leads to data corruption.
■
Using longjmp( ) to change the flow of task execution. If longjmp( ) is used in
a signal handler to re-initialize a running task, you must ensure that the signal
is not sent to the task while the task is holding a critical resource (such as a
kernel mutex). For example, if a signal is sent to a task that is executing
malloc( ), the signal handler that calls longjmp( ) could leave the kernel in an
inconsistent state.
These scenarios are very difficult to debug, and should be avoided. One safe way
to synchronize other elements of the application code and a signal handler is to set
up dedicated flags and data structures that are set from signal handlers and read
from the other elements. This ensures a consistency in usage of the data structure.
In addition, the other elements of the application code must check for the
occurrence of signals at any time by periodically checking to see if the
synchronizing data structure or flag has been modified in the background by a
signal handler, and then acting accordingly. The use of the volatile keyword is
useful for memory locations that are accessed from both a signal handler and other
elements of the application.
Taking a mutex semaphore in a signal handler is an especially bad idea. Mutex
semaphores can be taken recursively. A signal handler can therefore easily
re-acquire a mutex that was taken by any other element of the application. Since
the signal handler is an asynchronously executing entity, it has thereby broken the
mutual exclusion that the mutex was supposed to provide.
Taking a binary semaphore in a signal handler is an equally bad idea. If any other
element has already taken it, the signal handler will cause the task to block on
itself. This is a deadlock from which no recovery is possible. Counting semaphores,
if available, suffer from the same issue as mutexes, and if unavailable, are
equivalent to the binary semaphore situation that causes an unrecoverable
deadlock.
On a general note, the signal facility should be used only for notifying/handling
exceptional or error conditions. Usage of signals as a general purpose IPC
mechanism or in the data flow path of an application can cause some of the pitfalls
described above.
169
VxWorks
Application Programmer's Guide, 6.9
170
9
POSIX Facilities
171
VxWorks
Application Programmer's Guide, 6.9
9.1 Introduction
The VxWorks user space environment provides the execution environment of
choice for POSIX applications. The compliance to the POSIX standard is very high
for all the APIs and behaviors expected by the PSE52 profile (Realtime Controller
System Profile), which is described by IEEE Std 1003.13-2003 (POSIX.13). Various
APIs which operate on a task in kernel mode, operate on a process in user mode—
such as kill( ), exit( ), and so on.
Support for a conforming application is achieved in a process (RTP) in the
following way:
■
By configuring the VxWorks kernel with the appropriate functionality, such as
a POSIX-compliant file system and scheduler.
■ By using only POSIX PSE52 profile APIs in the application.
This implementation of the POSIX.13 PSE52 profile is based on IEEE Std
1003.1-2001 (POSIX.1). As defined by the PSE52 profile, it is restricted to individual
processes.
VxWorks does provide a process model that allows multiple processes, and
provides many POSIX.1 APIs associated with facilities such as networking and
inter-process communication that are not part of the PSE52 profile.
Note that the only VxWorks file system that is POSIX-compliant is the Highly
Reliable File System (for more information, see 13.4 Highly Reliable File System:
HRFS, p.285).
For detailed information about POSIX standards and facilities, see The Open
Group Web sites at [Link] and [Link]
While VxWorks provides many POSIX compliant APIs, not all POSIX APIs are
suitable for embedded and real-time systems, or are entirely compatible with the
VxWorks operating system architecture. In a few cases, therefore, Wind River has
imposed minor limitations on POSIX functionality to serve either real-time
systems or VxWorks compatibility. For example:
■ Swapping memory to disk is not appropriate in real-time systems, and
VxWorks provides no facilities for doing so. It does, however, provide POSIX
page-locking routines to facilitate porting code to VxWorks. The routines
otherwise provide no useful function—pages are always locked in VxWorks
systems (for more information see 9.12 POSIX Page-Locking Interface, p.186).
■
VxWorks tasks are scheduled on a system-wide basis; processes themselves
cannot be scheduled. As a consequence, while POSIX access routines allow
two values for contention scope (PTHREAD_SCOPE_SYSTEM and
PTHREAD_SCOPE_PROCESS), only system-wide scope is implemented in
VxWorks for these routines (for more information, see 9.13 POSIX Threads,
p.187 and 9.15 POSIX and VxWorks Scheduling, p.198).
Any such limitations on POSIX functionality are identified in this chapter, or in
other chapters of this guide that provide more detailed information on specific
POSIX APIs.
172
9 POSIX Facilities
9.2 Configuring VxWorks with POSIX Facilities
NOTE: This chapter provides information about POSIX facilities available for
real-time processes (RTPs). For information about facilities available in the
VxWorks kernel, see the corresponding chapter in the VxWorks Kernel Programmer’s
Guide.
The components required for POSIX PSE52 support within processes are provided
with the BUNDLE_RTP_POSIX_PSE52 component bundle. If memory constraints
require a finer-grained configuration, individual components can be used for
selected features. See the configuration instructions for individual POSIX features
throughout this chapter for more information in this regard.
If an application requires a PSE52 file system, then VxWorks must be configured
with the following components as well:
■
INCLUDE_HRFS for the highly reliable file system. See 13.4 Highly Reliable File
System: HRFS, p.285.
173
VxWorks
Application Programmer's Guide, 6.9
■
The appropriate device driver component —for example,
INCLUDE_XBD_RAMDRV—and INCLUDE_XBD_BLK_DEV if the device driver
requires it (that is, if it is not XBD-compatible). See the VxWorks Kernel
Programmer’s Guide: I/O System.
In addition, a /tmp directory must be created at run-time and must appear on the
virtual root file system, which is provided with the BUNDLE_RTP_POSIX_PSE52
component bundle (see 13.4.3 HRFS and POSIX PSE52, p.287).
NOTE: Configuring VxWorks with support for POSIX PSE52 conformance (using
BUNDLE_RTP_POSIX_PSE52) provides the /dev/null device required by the PSE52
profile. Note that the devs shell command lists /dev/null with other devices, but
the ls command does not list it under the VRFS root directory (because the name
violates the VRFS naming scheme). Applications can, in any case, use /dev/null as
required. For information about VRFS, see 13.3 Virtual Root File System: VRFS,
p.284. For information about null devices, see 12.6 Null Devices, p.276.
! CAUTION: : Do not include the vxWorks.h header file in your RTP application if it
must conform to the POSIX PSE52 profile. The vxWorks.h header file is required
for non-POSIX VxWorks facilities, but it also makes applications non-conformant
with the PSE52 profile.
174
9 POSIX Facilities
9.2 Configuring VxWorks with POSIX Facilities
■
_POSIX_THREAD_SPORADIC_SERVER
■
_POSIX_THREADS
■
_POSIX_TIMEOUTS
■
_POSIX_TIMERS
For information about POSIX namespace isolation for PSE52-conforming
applications, see 9.5 POSIX Header Files, p.178.
Table 9-1 provides an overview of the individual VxWorks components that must
be configured in the kernel to provide support for the specified POSIX facilities.
175
VxWorks
Application Programmer's Guide, 6.9
Networking facilities are described in the Wind River Network Stack Programmer’s
Guide.
Functionality Library
Math C Library
176
9 POSIX Facilities
9.4 Standard C Library: libc
Functionality Library
The following sections of this chapter describe the POSIX APIs available to
user-mode applications in addition to the native VxWorks APIs.
! CAUTION: Wind River advises that you do not use both POSIX libraries and native
VxWorks libraries that provide similar functionality. Doing so may result in
undesirable interactions between the two, as some POSIX APIs manipulate
resources that are also used by native VxWorks APIs. For example, do not use
tickLib routines to manipulate the system's tick counter if you are also using
clockLib routines, do not use the taskLib API to change the priority of a POSIX
thread instead of the pthread API, and so on.
A POSIX application can use the following APIs at run-time to determine the
status of POSIX support in the system:
■
The sysconf( ) routine returns the current values of the configurable system
variables, allowing an application to determine whether an optional feature is
supported or not, and the precise value of system's limits.
■
The confstr( ) routine returns a string associated with a system variable. With
this release, the confstr( ) routine returns a string only for the system's default
path.
The uname( ) routine lets an application get information about the system on
which it is running. The identification information provided for VxWorks is the
system name (VxWorks), the network name of the system, the system's release
number, the machine name (BSP model), the architecture's endianness, the kernel
version number, the processor name (CPU family), the BSP revision level, and the
system's build date.
177
VxWorks
Application Programmer's Guide, 6.9
! CAUTION: : Do not include the vxWorks.h header file in your RTP application if it
must conform to the POSIX PSE52 profile. The vxWorks.h header file is required
for non-POSIX VxWorks facilities, but it also makes applications non-conformant
with the PSE52 profile.
pthread.h * pthreads
semaphore.h * semaphores
178
9 POSIX Facilities
9.6 POSIX Namespace
signal.h * signals
179
VxWorks
Application Programmer's Guide, 6.9
For VxWorks, this namespace isolation can be enforced for all symbols that can be
included by way of POSIX PSE52 header files, but does not extend to VxWorks
library symbols (for more information, see Limitations to Namespace Isolation,
p.180).
POSIX namespace isolation can, of course, only be enforced for applications that
only include header files that are defined by the POSIX.1 standard. It cannot be
enforced for applications that include native VxWorks header files.
For VxWorks, POSIX features are implemented using native VxWorks features.
When VxWorks system libraries are linked with POSIX applications, therefore,
some native VxWorks public functions and public variable identifiers become part
of the application’s namespace. Since native VxWorks public functions and public
variable identifiers do not abide by POSIX naming conventions (in particular they
do not generally start with an underscore) they may pollute a POSIX application’s
own namespace. In other words, the identifiers used by VxWorks native public
functions and public variables must be considered as reserved even by POSIX
applications.
The POSIX header files that have been implemented in compliance with the POSIX
PSE52 profile are identified in Table 9-3.
These header files are also used by traditional VxWorks applications, and may be
included in native VxWorks header files (that is, header files that are not described
by standards like POSIX or ANSI).
POSIX provides feature-test macros used during the compilation process to ensure
application namespace isolation. These macros direct the compiler’s pre-processor
to exclude all symbols that are not part of the POSIX.1 authorized symbols in the
POSIX header files.
The supported POSIX feature test macros and their values are as follows:
■ _POSIX_C_SOURCE set to 200112L
■ _XOPEN_SOURCE set to 600
■
_POSIX_AEP_RT_CONTROLLER_C_SOURCE set to 200312L
Wind River supports use of these macros for use with POSIX PSE52 header files
(see 9.5 POSIX Header Files, p.178).
The macros must be defined before any other header file inclusions. Defining any
of the three feature test macros with the appropriate value is sufficient to enforce
POSIX namespace isolation with regard to header file symbols.
Note that the macros must be used with the values provided here to ensure the
appropriate results. Also note that the POSIX.1 and POSIX.13 standards could
evolve so that using different supported values for those macros, or defining these
macros in various combinations, might yield different results with regard to which
symbols are made visible to the application.
The _POSIX_SOURCE feature test macro is not supported since it has been
superseded by _POSIX_C_SOURCE.
180
9 POSIX Facilities
9.7 POSIX Process Privileges
! CAUTION: Only POSIX header files may be included in a POSIX application that is
compiled with the POSIX feature test macros. Inclusion of any other header files
may pollute the application's namespace, and may also result in compilation
failures.
! CAUTION: The C99 _Bool type is not compatible with the VxWorks BOOL type.
The C99 _Bool type is intrinsically defined by the Wind River (diab) and GNU
compilers, and has a 8-bit size. The VxWorks BOOL type is a macro (declared in
b_BOOL.h), defined as an integer—that is, with a 32-bit size.
Routine Description
181
VxWorks
Application Programmer's Guide, 6.9
Routine Description
Basic VxWorks process facilities, including these routines, are provided with the
INCLUDE_RTP component.
POSIX Clocks
POSIX defines various software (virtual) clocks, which are identified as the
CLOCK_REALTIME clock, CLOCK_MONOTONIC clock, process CPU-time clocks,
and thread CPU-time clocks. These clocks all use one system hardware timer.
The real-time clock and the monotonic clock are system-wide clocks, and are
therefore supported for both the VxWorks kernel and processes. The process
CPU-time clocks are not supported in VxWorks. The thread CPU-time clocks are
supported for POSIX threads running in processes. A POSIX thread can use the
real-time clock, the monotonic clock, and a thread CPU-time clock for its
application.
The real-time clock can be reset (but only from the kernel). The monotonic clock
cannot be reset, and provides the time that has elapsed since the system booted.
The real-time clock can be accessed with the POSIX clock and timer routines by
using the clock_id parameter CLOCK_REALTIME. A real-time clock can be reset at
run time with a call to clock_settime( ) from within the kernel (not from a process).
The monotonic clock can be accessed by calling clock_gettime( ) with a clock_id
parameter of CLOCK_MONOTONIC. A monotonic clock keeps track of the time
that has elapsed since system startup; that is, the value returned by
clock_gettime( ) is the amount of time (in seconds and nanoseconds) that has
passed since the system booted. A monotonic clock cannot be reset. Applications
can therefore rely on the fact that any measurement of a time interval that they
might make has not been falsified by a call to clock_settime( ).
Both CLOCK_REALTIME and CLOCK_MONOTONIC are defined in time.h.
In addition to system wide clocks, VxWorks supports thread CPU-time clocks for
individual POSIX threads. A thread CPU-time clock measures the execution time
of a specific pthread, including the time that the system spends executing system
182
9 POSIX Facilities
9.9 POSIX Clocks and Timers
services on behalf of the pthread (that is, the time it runs in both user and kernel
mode). The initial execution time is set to zero when the pthread is created.
Thread CPU-time clocks are implemented only for pthreads that run in processes
(and not in the kernel). These clocks are accessible only through POSIX APIs.
Thread CPU-time clocks provide a means for detecting and managing situations in
which a pthread overruns its assigned maximum execution time. Bounding the
execution times of the pthreads in an application increases predictability and
reliability.
Each POSIX thread has its own CPU-time clock to measure its execution time of
pthread, which can be identified by using the pthread_getcpuclockid( ) routine.
The CLOCK_THREAD_CPUTIME_ID clock_id parameter refers to the calling
thread's CPU-time clock.
See Table 9-5 for a list of the POSIX clock routines. The obsolete VxWorks-specific
POSIX extension clock_setres( ) is provided for backwards-compatibility
purposes. For more information about clock routines, see the API reference for
clockLib.
Routine Description
pthread_getcpuclockid( ) Get the clock ID for the CPU-time clock (for use with
clock_gettime( ) and clock_settime( )).
To include the clockLib library in the system, configure VxWorks with the
INCLUDE_POSIX_CLOCKS component. For thread CPU-time clocks, the
INCLUDE_POSIX_PTHREAD_SCHEDULER and
INCLUDE_POSIX_THREAD_CPUTIME components must be used as well.
Process-based (RTP) applications are automatically linked with the clockLib
library when they are compiled. The library is automatically initialized when the
process starts.
POSIX Timers
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.
183
VxWorks
Application Programmer's Guide, 6.9
Timers are created based on clocks. In the kernel, the CLOCK_REALTIME and
CLOCK_MONOTONIC clocks are supported for timers. In processes, the
CLOCK_REALTIME clock, CLOCK_MONOTONIC clock, and thread CPU-time
clocks (including CLOCK_THREAD_CPUTIME_ID clock) are supported.
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 8.2 Signals, p.160).
See Table 9-6 for a list of the POSIX timer routines. The VxWorks timerLib library
includes a set of VxWorks-specific POSIX extensions: timer_open( ),
timer_close( ), timer_cancel( ), timer_connect( ), and timer_unlink( ). These
routines allow for an easier and more powerful use of POSIX timers on VxWorks.
For more information, see the VxWorks API reference for timerLib.
Routine Description
timer_create( ) Allocate a timer using the specified clock for a timing base
(CLOCK_REALTIME, CLOCK_MONOTONIC, or thread
CPU-time of the calling thread).
timer_gettime( ) Get the remaining time before expiration and the reload
value.
timer_settime( ) Set the time until the next expiration and arm timer.
nanosleep( ) Suspend the current pthread (task) until the time interval
elapses.
184
9 POSIX Facilities
9.10 POSIX Asynchronous I/O
NOTE: In VxWorks, a POSIX timer whose name does not start with a forward-slash
(/) character is considered private to the process that has opened it and can not be
accessed from another process. A timer whose name starts with a forward-slash (/)
character is a public object, and other processes can access it (as according to the
POSIX standard). See 7.8 Inter-Process Communication With Public Objects, p.154.
/* includes */
#include <vxWorks.h>
#include <time.h>
/* create timer */
if (timer_create (CLOCK_REALTIME, NULL, &timerid) == ERROR)
{
printf ("create FAILED\n");
return (ERROR);
}
return (OK);
}
185
VxWorks
Application Programmer's Guide, 6.9
186
9 POSIX Facilities
9.13 POSIX Threads
NOTE: The POSIX page-locking routines do not perform their intended function in
VxWorks. They are provided for application portability.
To include the mmanPxLib library in the system, configure VxWorks with the
INCLUDE_POSIX_MEM component.
Process-based (RTP) applications are automatically linked with the mmanPxLib
library when they are compiled.
! CAUTION: POSIX thread APIs must not be invoked in the context of custom system
call handlers—either directly by system call handler itself or by any routine that
the system call handler may use. Use of POSIX thread APIs in the context of system
call handlers produces unspecified behavior and execution failure. For
information about custom system calls, see the VxWorks Kernel Programmer’s Guide.
Execution-stack guard zones can be used with POSIX threads. A SIGSEGV signal
is then generated when a stack overflow or underflow occurs.
Execution stack overflow protection can be provided for individual pthreads with
a call to the pthread_attr_setguardsize( ) routine before the pthread is created. By
187
VxWorks
Application Programmer's Guide, 6.9
A major difference between VxWorks tasks and POSIX threads is the way in which
options and settings are specified. For VxWorks tasks these options are set with the
task creation API, usually taskSpawn( ).
POSIX threads, on the other hand, have characteristics that are called attributes.
Each attribute contains a set of values, and a set of access routines to retrieve and set
those values. You specify all pthread attributes before pthread creation in the
attributes object pthread_attr_t. In a few cases, you can dynamically modify the
attribute values of a pthread after its creation.
Pthread Name
While POSIX threads are not named entities, the VxWorks tasks upon which they
are based are named. By default the underlying task elements are named
pthrNumber (for example, pthr3). The number part of the name is incremented
each time a new thread is created (with a roll-over at 2^32 - 1). It is, however,
possible to name these tasks using the thread name attribute.
■
Attribute Name: threadname
■ Possible Values: a null-terminated string of characters
■ Default Value: none (the default naming policy is used)
■ Access Functions (VxWorks-specific POSIX extensions):
pthread_attr_setname( ) and pthread_attr_getname( )
188
9 POSIX Facilities
9.13 POSIX Threads
Pthread Options
POSIX threads are agnostic with regard to target architecture. Some VxWorks
tasks, on the other hand, may be created with specific options in order to benefit
from certain features of the architecture. For example, for the Altivec-capable
PowerPC architecture, tasks must be created with the VX_ALTIVEC_TASK in order
to make use of the Altivec processor. The pthread options attribute can be used to
set such options for the VxWorks task upon which the POSIX thread is based.
■
Attribute Name: threadoptions
■
Possible Values: the same as the VxWorks task options. See taskLib.h
■
Default Value: none (the default task options are used)
■
Access Functions (VxWorks-specific POSIX extensions):
pthread_attr_setopt( ) and pthread_attr_getopt( )
The following examples create a pthread using the default attributes and using
explicit attributes.
pthread_t tid;
pthread_attr_t attr;
int ret;
pthread_attr_init(&attr);
pthread_t tid;
int ret;
pthread_t threadId;
pthread_attr_t attr;
void * stackaddr = NULL;
int stacksize = 0;
pthread_attr_init (&attr);
/*
* Allocate memory for a stack region for the thread. Malloc() is used
* for simplification since a real-life case is likely to use memPartAlloc()
* on the kernel side, or mmap() on the user side.
*/
189
VxWorks
Application Programmer's Guide, 6.9
if (stackaddr == NULL)
{
printf ("FAILED: mystack: malloc failed\n");
return -1;
}
VxWorks provides many POSIX thread routines. Table 9-8 lists a few that are
directly relevant to pthread creation or execution. See the VxWorks API reference
for information about the other routines, and more details about all of them.
Routine Description
190
9 POSIX Facilities
9.13 POSIX Threads
Routine Description
Routine Description
191
VxWorks
Application Programmer's Guide, 6.9
Routine Description
POSIX threads can store and access private data; that is, pthread-specific data.
They use a key maintained for each pthread by the pthread library to access that
data. A key corresponds to a location associated with the data. It is created by
calling pthread_key_create( ) and released by calling pthread_key_delete( ). The
location is accessed by calling pthread_getspecific( ) and pthread_setspecific( ).
This location represents a pointer to the data, and not the data itself, so there is no
limitation on the size and content of the data associated with a key.
The pthread library supports a maximum of 256 keys for all the pthreads in a
process.
The pthread_key_create( ) routine has an option for a destructor function, which
is called when the creating pthread exits or is cancelled, if the value associated with
the key is non-NULL.
This destructor function frees the storage associated with the data itself, and not
with the key. It is important to set a destructor function for preventing memory
leaks to occur when the pthread that allocated memory for the data is cancelled.
The key itself should be freed as well, by calling pthread_key_delete( ), otherwise
the key cannot be reused by the pthread library.
192
9 POSIX Facilities
9.13 POSIX Threads
■
The pthread calls a function that contains a cancellation point during which the
pthread may be automatically cancelled.
Asynchronous cancellation causes the execution of the pthread to be forcefully
interrupted and a handler to be called, much like a signal.1
Automatic cancellation points are library routines that can block the execution of
the pthread for a lengthy period of time.
NOTE: While the msync( ), fcntl( ), and tcdrain( ) routines are mandated POSIX
1003.1 cancellation points, they are not provided with VxWorks for this release.
Library Routines
aioPxLib aio_suspend( )
semPxLib sem_wait( )
Routines that can be used with cancellation points of pthreads are listed in
Table 9-11.
Routine Description
193
VxWorks
Application Programmer's Guide, 6.9
Routine Description
The routines that can be used to act directly on a mutex object and on the mutex
attribute object are listed in Table 9-12 and Table 9-13 (respectively).
Routine Description
For information about native VxWorks API timeouts and POSIX API timeouts, see
7.9 About VxWorks API Timeout Parameters, p.157.
194
9 POSIX Facilities
9.14 POSIX Thread Mutexes and Condition Variables
Routine Description
The type mutex attribute controls the behavior of the mutex object when a pthread
attempts to lock and unlock a mutex. The possible values are
PTHREAD_MUTEX_NORMAL, PTHREAD_MUTEX_ERRORCHECK,
PTHREAD_MUTEX_RECURSIVE, and PTHREAD_MUTEX_DEFAULT which is
mapped to the PTHREAD_MUTEX_NORMAL type on VxWorks.
Mutexes of types PTHREAD_MUTEX_NORMAL and PTHREAD_MUTEX_DEFAULT
do not detect deadlocks, so a pthread will deadlock if it attempts to lock a mutex it
has locked already. A pthread attempting to unlock a mutex of this type that is
locked by another pthread, or is already unlocked, will get the EPERM error.
Mutexes of type PTHREAD_MUTEX_ERRORCHECK do detect a deadlock situation,
so a pthread will get the EDEADLK error if it attempts to lock a mutex that it has
locked already. A pthread attempting to unlock a mutex of this type that is locked
by another pthread, or is already unlocked, will get the EPERM error.
Mutexes of type PTHREAD_MUTEX_RECURSIVE allow a pthread to re-lock a
mutex that it has already locked. The same number of unlocks as locks is required
to release the mutex. A pthread attempting to unlock a mutex of this type that has
been locked by another pthread, or is already unlocked, will get the EPERM error.
The protocol mutex attribute defines how the mutex variable deals with the
priority inversion problem (which is described in the section for VxWorks
mutual-exclusion semaphores; see 7.4.4 Mutual-Exclusion Semaphores, p.134).
■
Attribute Name: protocol
■
Possible Values: PTHREAD_PRIO_NONE, PTHREAD_PRIO_INHERIT and
PTHREAD_PRIO_PROTECT
195
VxWorks
Application Programmer's Guide, 6.9
■
Access Routines: pthread_mutexattr_getprotocol( ) and
pthread_mutexattr_setprotocol( )
By default, a mutex is created with the PTHREAD_PRIO_NONE protocol, which
ensures that owning the mutex does not modify the priority and scheduling
characteristics of a pthread. This may, however, not be appropriate in situations in
which a low priority pthread can lock a mutex that is also required by higher
priority threads. The PTHREAD_PRIO_INHERIT and PTHREAD_PRIO_PROTECT
protocols can be used to address this issue.
The PTHREAD_PRIO_INHERIT value is used to create a mutex with priority
inheritance—and is equivalent to the association of SEM_Q_PRIORITY and
SEM_INVERSION_SAFE options used with semMCreate( ). A pthread owning a
mutex variable created with the PTHREAD_PRIO_INHERIT value inherits the
priority of any higher-priority pthread waiting for the mutex and executes at this
elevated priority until it releases the mutex, at which points it returns to its original
priority.
Because it might not be desirable to elevate a lower-priority pthread to a priority
above a certain level, POSIX defines the notion of priority ceiling, described below.
Mutual-exclusion variables created with priority protection use the
PTHREAD_PRIO_PROTECT value.
The prioceiling attribute is the POSIX priority ceiling for mutex variables created
with the protocol attribute set to PTHREAD_PRIO_PROTECT.
■ Attribute Name: prioceiling
■ Possible Values: any valid (POSIX) priority value (0-255, with zero being the
lowest).
■ Access Routines: pthread_mutexattr_getprioceiling( ) and
pthread_mutexattr_setprioceiling( )
■ Dynamic Access Routines: pthread_mutex_getprioceiling( ) and
pthread_mutex_setprioceiling( )
Note that the POSIX priority numbering scheme is the inverse of the VxWorks
scheme. For more information see 9.15.2 POSIX and VxWorks Priority Numbering,
p.200.
A priority ceiling is defined by the following conditions:
■
Any pthread attempting to acquire a mutex, whose priority is higher than the
ceiling, cannot acquire the mutex.
■
Any pthread whose priority is lower than the ceiling value has its priority
elevated to the ceiling value for the duration that the mutex is held.
■
The pthread’s priority is restored to its previous value when the mutex is
released.
196
9 POSIX Facilities
9.14 POSIX Thread Mutexes and Condition Variables
more complicated type of synchronization than the one allowed by mutexes only.
Its main advantage is that is allows for passive waiting (as opposed to active
waiting or polling) on a change in the value of the variable. Condition variables are
used in conjunction with mutexes (one mutex per condition variable). The routines
that can be used to act directly on a condition variable and on the condition
variable attribute object are listed in Table 9-12 and Table 9-13 (respectively).
Routine Description
For information about native VxWorks API timeouts and POSIX API timeouts, see
7.9 About VxWorks API Timeout Parameters, p.157.
Routine Description
Clock Selection
The only attribute supported for the condition variable object is the clock ID. By
default the pthread_cond_timedwait( ) routine uses the CLOCK_REALTIME clock.
The clock type can be changed with the pthread_condattr_setclock( ) routine. The
accepted clock IDs are CLOCK_REALTIME (the default) and
CLOCK_MONOTONIC. For information about POSIX clocks, see 9.9 POSIX Clocks
and Timers, p.182.
197
VxWorks
Application Programmer's Guide, 6.9
NOTE: Wind River recommends that you do not use both POSIX APIs and
VxWorks APIs in the same application. Doing so may make a POSIX application
non-compliant, and is in any case not advisable.
Table 9-16 provides an overview of how scheduling works for tasks and pthreads,
for each of the schedulers, in both the kernel and processes (RTPs). The key
differences are the following:
■ The POSIX thread scheduler provides POSIX scheduling support for threads
running in processes.
■ In all other cases, the POSIX thread scheduler schedules pthreads and tasks in
the same (non-POSIX) manner as the traditional VxWorks scheduler. (There is
a minor difference between how it handles tasks and pthreads whose priorities
have been lowered; see Differences in Re-Queuing Pthreads and Tasks With
Lowered Priorities, p.204.)
■
The traditional VxWorks scheduler cannot be used to schedule pthreads in
processes. In fact, pthreads cannot be started in processes unless VxWorks is
configured with the POSIX thread scheduler.
The information provided in Table 9-16 is discussed in detail in subsequent
sections.
198
9 POSIX Facilities
9.15 POSIX and VxWorks Scheduling
Table 9-16 Task and Pthread Scheduling in the Kernel and in Processes
199
VxWorks
Application Programmer's Guide, 6.9
The POSIX priority numbering scheme is the inverse of the VxWorks priority
numbering scheme. In POSIX, the higher the number, the higher the priority. In
VxWorks, the lower the number, the higher the priority, where 0 is the highest
priority.
The priority numbers used with the POSIX scheduling library, schedPxLib, do not,
therefore, match those used and reported by all other components of VxWorks.
You can change the default POSIX numbering scheme by setting the global
variable posixPriorityNumbering to FALSE. If you do so, schedPxLib uses the
VxWorks numbering scheme (a smaller number means a higher priority) and its
priority numbers match those used by the other components of VxWorks.
In the following sections, discussions of pthreads and tasks at the same priority level
refer to functionally equivalent priority levels, and not to priority numbers.
All VxWorks tasks and pthreads are scheduled according to the system-wide
default scheduling policy. The only exception to this rule is for pthreads running
in user mode (in processes). In this case, concurrent scheduling policies that differ
from the system default can be applied to pthreads.
Note that pthreads can be run in processes only if VxWorks is configured with the
POSIX thread scheduler; they cannot be run in processes if VxWorks is configured
with the traditional scheduler.
The system-wide default scheduling policy for VxWorks, regardless of which
scheduler is used, is priority-based preemptive scheduling—which corresponds to
the POSIX SCHED_FIFO scheduling policy.
At run-time the active system-wide default scheduling policy can be changed to
round-robin scheduling with the kernelTimeSlice( ) routine. It can be changed
back by calling kernelTimeSlice( ) with a parameter of zero. VxWorks round-robin
scheduling corresponds to the POSIX SCHED_RR policy.
The kernelTimeSlice( ) routine cannot be called in user mode (that is, from a
process). A call with a non-zero parameter immediately affects all kernel and user
tasks, all kernel pthreads, and all user pthreads using the SCHED_OTHER policy.
Any user pthreads running with the SCHED_RR policy are unaffected by the call;
but those started after it use the newly defined timeslice.
The VxWorks traditional scheduler can be used with both tasks and pthreads in the
kernel. It cannot be used with pthreads in processes. If VxWorks is configured with
the traditional scheduler, a pthread_create( ) call in a process fails and the errno is
set to ENOSYS.
The traditional VxWorks scheduler schedules pthreads as if they were tasks. All
tasks and pthreads executing in a system are therefore subject to the current default
scheduling policy (either the priority-based preemptive policy or the round-robin
scheduling policy; see 9.15.3 Default Scheduling Policy, p.200), and concurrent
policies cannot be applied to individual pthreads. For general information about
200
9 POSIX Facilities
9.15 POSIX and VxWorks Scheduling
the traditional scheduler and how it works with tasks, see 6.4 Task Scheduling,
p.109.
The scheduling options provided by the traditional VxWorks scheduler are similar
to the POSIX ones. The following pthreads scheduling policies correspond to the
traditional VxWorks scheduling policies:
■
SCHED_FIFO is similar to VxWorks priority-based preemptive scheduling.
There are differences as to where tasks or pthreads are placed in the ready
queue if their priority is lowered; see Caveats About Scheduling Behavior with the
POSIX Thread Scheduler, p.203.
■
SCHED_RR corresponds to VxWorks round-robin scheduling.
■
SCHED_OTHER corresponds to the current system-wide default scheduling
policy. The SCHED_OTHER policy is the default policy for pthreads in
VxWorks.
There is no VxWorks traditional scheduler policy that corresponds to
SCHED_SPORADIC.
Concurrent scheduling policies are not supported for pthreads in the kernel, and
care must therefore be taken with pthread scheduling-inheritance and scheduling
policy attributes.
If the scheduling-inheritance attribute is set to PTHREAD_EXPLICIT_SCHED and
the scheduling policy to SCHED_FIFO or SCHED_RR, and this policy does not
match the current system-wide default scheduling policy, the creation of pthreads
fails.
Wind River therefore recommends that you always use
PTHREAD_INHERIT_SCHED (which is the default) as a scheduling-inheritance
attribute. In this case the current VxWorks scheduling policy applies, and the
parent pthread's priority is used. Or, if the pthread must be started with a different
priority than its parent, the scheduling-inheritance attribute can be set to
PTHREAD_EXPLICIT_SCHED and the scheduling policy attribute set to be
SCHED_OTHER (which corresponds to the current system-wide default
scheduling policy.).
In order to take advantage of the POSIX scheduling model, VxWorks must be
configured with the POSIX thread scheduler, and the pthreads in question must be
run in processes (RTPs). See 9.15.5 POSIX Threads Scheduler, p.201.
The POSIX thread scheduler can be used to schedule both pthreads and tasks in a
VxWorks system. Note that the purpose of the POSIX thread scheduler is to
provide POSIX scheduling support for pthreads running in processes. There is no
reason to use it in a system that does not require this support (kernel-only systems,
or systems with processes but without pthreads).
201
VxWorks
Application Programmer's Guide, 6.9
The POSIX thread scheduler is required for running pthreads in processes, where it
provides compliance with POSIX 1003.1 for pthread scheduling (including
concurrent scheduling policies). If VxWorks is not configured with the POSIX
thread scheduler, pthreads cannot be created in processes.
NOTE: The POSIX priority numbering scheme is the inverse of the VxWorks
scheme, so references to a given priority level or same level in comparisons of these
schemes refer to functionally equivalent priority levels, and not to priority
numbers. For more information about the numbering schemes see 9.15.2 POSIX
and VxWorks Priority Numbering, p.200.
The POSIX thread scheduler schedules kernel tasks and kernel pthreads in the same
manner as the traditional VxWorks task scheduler. See 6.4 Task Scheduling, p.109
for information about the traditional scheduler and how it works with VxWorks
tasks, and 9.15.4 VxWorks Traditional Scheduler, p.200 for information about how
POSIX scheduling policies correspond to the traditional VxWorks scheduling
policies.
Scheduling in Processes
When VxWorks is configured with the POSIX thread scheduler, tasks executing in
processes are scheduled according to system-wide default scheduling policy. On
the other hand, pthreads executing in processes are scheduled according to POSIX
1003.1. Scheduling policies can be assigned to each pthread and changed
dynamically. The scheduling policies are as follows:
■
SCHED_FIFO is a preemptive priority scheduling policy. For a given priority
level, pthreads scheduled with this policy are handled as peers of the VxWorks
tasks at the same level. There is a slight difference in how pthreads and tasks
are handled if their priorities are lowered (for more information; see Differences
in Re-Queuing Pthreads and Tasks With Lowered Priorities, p.204).
■
SCHED_RR is a per-priority round-robin scheduling policy. For a given
priority level, all pthreads scheduled with this policy are given the same time
of execution (time-slice) before giving up the CPU.
■
SCHED_SPORADIC is a policy used for aperiodic activities, which ensures that
the pthreads associated with the policy are served periodically at a high
priority for a bounded amount of time, and a low background priority at all
other times.
■
SCHED_OTHER corresponds to the scheduling policy currently in use for
VxWorks tasks, which is either preemptive priority or round-robin. Pthreads
scheduled with this policy are submitted to the system's global scheduling
policy, exactly like VxWorks tasks or kernel pthreads.
Note the following with regard to the VxWorks implementation of the
SCHED_SPORADIC policy:
■
The system periodic clock is used for time accounting.
■
Dynamically changing the scheduling policy to SCHED_SPORADIC is not
supported; however, dynamically changing the policy from
SCHED_SPORADIC to another policy is supported.
202
9 POSIX Facilities
9.15 POSIX and VxWorks Scheduling
■
VxWorks does not impose an upper limit on the maximum number of
replenishment events with the SS_REPL_MAX macro. A default of 40 events is
set with the sched_ss_max_repl field of the thread attribute structure, which
can be changed.
Using the POSIX thread scheduler involves a few complexities that should be
taken into account when designing your system. Care should be taken with regard
to the following:
■
Using both round-robin and priority-based preemptive scheduling policies.
■
Running pthreads with the individual SCHED_OTHER policy.
■
Differences in where pthreads and tasks are placed in the ready queue when
their priorities are lowered.
■
Backwards compatibility issues for POSIX applications designed for the
VxWorks traditional scheduler.
203
VxWorks
Application Programmer's Guide, 6.9
The POSIX thread scheduler repositions pthreads that have had their priority
lowered differently in the ready queue than tasks that have had their priority
lowered. The difference is as follows:
■
When the priority of a pthread is lowered—with the pthread_setschedprio( )
routine—the POSIX thread scheduler places it at the front of the ready queue
list for that priority.
■
When the priority of a task is lowered—with the taskPrioritySet( ) routine—
the POSIX thread scheduler places it at the end of the ready queue list for that
priority, which is the same as what the traditional VxWorks scheduler would
do.
What this means is that lowering the priority of a task and a pthread may have a
different effect on when they will run (if there are other tasks or pthreads of the
same priority in the ready queue). For example, if a task and a pthread each have
their priority lowered to effectively the same level, the pthread will be at the front
of the list for that priority and the task will be at the end of the list. The pthread will
run before any other pthreads (or tasks) at this level, and the task after any other
tasks (or pthreads).
204
9 POSIX Facilities
9.15 POSIX and VxWorks Scheduling
Note that Wind River recommends that you do not use both POSIX APIs and
VxWorks APIs in the same application. Doing so may make a POSIX application
non-compliant.
For information about the ready queue, see Scheduling and the Ready Queue, p.110.
Using the POSIX thread scheduler changes the behavior of POSIX applications that
were written to run with the traditional VxWorks scheduler. For existing POSIX
applications that require backward-compatibility, the scheduling policy can be
changed to SCHED_OTHER for all pthreads. This causes their policy to default to
the active VxWorks task scheduling policy (as was the case before the introduction
of the POSIX thread scheduler).
The POSIX 1003.1b scheduling routines provided by the schedPxLib library for
VxWorks are described in Table 9-17.
Routine Description
For more information about these routines, see the schedPxLib API reference.
Note that the POSIX priority numbering scheme is the inverse of the VxWorks
scheme. For more information see 9.15.2 POSIX and VxWorks Priority Numbering,
p.200.
To include the schedPxLib library in the system, configure VxWorks with the
INCLUDE_POSIX_SCHED component.
Process-based (RTP) applications are automatically linked with the schedPxLib
library when they are compiled.
205
VxWorks
Application Programmer's Guide, 6.9
Note that a non-null result does not imply that the POSIX thread calling this
routine is being scheduled with the SCHED_RR policy. To make this determination,
a pthread must use the pthread_getschedparam( ) routine.
/*
* The following example gets the length of the time slice,
* and then displays the time slice.
*/
/* includes */
#include <time.h>
#include <sched.h>
Routine Description
2. Some 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, named semaphores can be used to share semaphores
between real-time processes. In the VxWorks kernel there is no need 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.
206
9 POSIX Facilities
9.16 POSIX Semaphores
Routine Description
For information about native VxWorks API timeouts and POSIX API timeouts, see
7.9 About VxWorks API Timeout Parameters, p.157.
Note that the behavior of the user space sem_open( ) routine complies with the
POSIX specification, and thus differs from the kernel version of the routine. The
kernel version of sem_open( ) returns a reference copy for the same named
semaphore when called multiple times, provided that sem_unlink( ) is not called.
The user space version of sem_open( ) returns the same ID for the same named
semaphore when called multiple times.
To include the POSIX semPxLib library semaphore routines in the system,
configure VxWorks with the INCLUDE_POSIX_SEM component.
VxWorks also provides semPxLibInit( ), a non-POSIX (kernel-only) routine that
initializes the kernel’s POSIX semaphore library. It is called by default at boot time
when POSIX semaphores have been included in the VxWorks configuration.
Process-based (RTP) applications are automatically linked with the semPxLib
library when they are compiled. The library is automatically initialized when the
process starts.
POSIX semaphores are counting semaphores; that is, they keep track of the number
of times they are given. The VxWorks semaphore mechanism is similar to that
specified by POSIX, except that VxWorks semaphores offer these additional
features:
■
priority inheritance
■
task-deletion safety
■
the ability for a single task to take a semaphore multiple times
■
ownership of mutual-exclusion semaphores
■
semaphore timeouts
■
queuing mechanism options
When these features are important, VxWorks semaphores are preferable to POSIX
semaphores. (For information about these features, see 6. Multitasking.)
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
207
VxWorks
Application Programmer's Guide, 6.9
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 that is 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, setting the semaphore to 0 (locked).
Subsequent tasks will block until the semaphore is released. As with the previous
scenario, when the semaphore is released the task with the highest priority is
unblocked.
When used in a user-mode application, unnamed semaphores can be accessed
only by the tasks belonging to the process executing the application.
208
9 POSIX Facilities
9.16 POSIX Semaphores
NOTE: In VxWorks, a POSIX semaphore whose name does not start with a
forward-slash (/) character is considered private to the process that has opened it
and can not be accessed from another process. A semaphore whose name starts
with a forward-slash (/) character is a public object, and other processes can access
it (as according to the POSIX standard). See 7.8 Inter-Process Communication With
Public Objects, p.154.
/*
* 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 */
/************************************************************************
* unnameSem - test case for unamed semaphores
*
* This routine tests unamed semaphores.
*
* RETURNS: N/A
*
* ERRNOS: N/A
*/
if (pSem == NULL)
{
printf ("pSem allocation failed\n");
return;
}
209
VxWorks
Application Programmer's Guide, 6.9
void syncTask
(
sem_t * pSem
)
{
/* wait for synchronization from unnameSem */
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:
O_CREAT
Create the semaphore if it does not already exist. If it exists, either fail or open
the semaphore, depending on whether O_EXCL is specified.
O_EXCL
Open the semaphore only if newly created; fail if the semaphore exists.
The results, based on the flags and whether the semaphore accessed already exists,
are shown in Table 9-19.
210
9 POSIX Facilities
9.16 POSIX Semaphores
Once initialized, a semaphore remains usable until explicitly destroyed. Tasks can
explicitly mark a semaphore for destruction at any time, but the system only
destroys the semaphore when no task has the semaphore open.
If VxWorks is configured with INCLUDE_POSIX_SEM_SHOW, you can use show( )
from the shell (with the C interpreter) to display information about a POSIX
semaphore. 3
This example shows information about the POSIX semaphore mySem with two
tasks blocked and waiting for it:
-> show semId
value = 0 = 0x0
Semaphore name :mySem
sem_open() count :3
Semaphore value :0
No. of blocked tasks :2
NOTE: POSIX named semaphores may be shared between processes only if their
names start with a / (forward slash) character. They are otherwise private to the
process in which they were created, and cannot be accessed from another process.
See 7.8 Inter-Process Communication With Public Objects, p.154.
/*
* In this example, nameSem() creates a task for synchronization. The
3. The show( ) routine is not a POSIX routine, nor is it meant to be used programmatically. It
is designed for interactive use with the shell (with the shell’s C interpreter).
211
VxWorks
Application Programmer's Guide, 6.9
/* includes */
#include <vxWorks.h>
#include <taskLib.h>
#include <stdio.h>
#include <semaphore.h>
#include <fcntl.h>
/* forward declaration */
/****************************************************************************
*
* nameSem - test program for POSIX semaphores
*
* This routine opens a named semaphore and spawns a task, tSyncSemTask, which
* waits on the named semaphore.
*
* RETURNS: N/A
*
* ERRNO: N/A
*/
void nameSem
(
char * name
)
{
sem_t * semId;
/* give semaphore */
printf ("nameSem: posting semaphore - synchronizing action\n");
if (sem_post (semId) == -1)
{
printf ("nameSem: sem_post failed\n");
sem_close(semId);
return;
}
/* all done */
if (sem_close (semId) == -1)
{
printf ("nameSem: sem_close failed\n");
return;
}
212
9 POSIX Facilities
9.17 POSIX Message Queues
/****************************************************************************
*
* syncSemTask - waits on a named POSIX semaphore
*
* This routine waits on the named semaphore created by nameSem().
*
* RETURNS: N/A
*
* ERRNO: N/A
*/
void 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;
}
213
VxWorks
Application Programmer's Guide, 6.9
Routine Description
For information about native VxWorks API timeouts and POSIX API timeouts, see
7.9 About VxWorks API Timeout Parameters, p.157.
Note that there are behavioral differences between the kernel and user space
versions of mq_open( ). The kernel version allows for creation of a message queue
for any permission specified by the oflags parameter. The user-space version
complies with the POSIX PSE52 profile, so that after the first call, any subsequent
calls in the same process are only allowed if an equivalent or lower permission is
specified.
Table 9-21 describes the permissions that are allowed.
O_RDWR O_RDWR,
O_WRONLY,
O_RDONLY
214
9 POSIX Facilities
9.17 POSIX Message Queues
POSIX message queues are similar to VxWorks message queues, except that POSIX
message queues provide messages with a range of priorities. The differences are
summarized in Table 9-22.
For information about native VxWorks API timeouts and POSIX API timeouts, see
7.9 About VxWorks API Timeout Parameters, p.157.
/*
* This example sets the O_NONBLOCK flag and examines message queue
* attributes.
*/
215
VxWorks
Application Programmer's Guide, 6.9
/* 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;
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");
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");
}
216
9 POSIX Facilities
9.17 POSIX Message Queues
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).
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 (blocking) 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.
217
VxWorks
Application Programmer's Guide, 6.9
NOTE: In VxWorks, a POSIX message queue whose name does not start with a
forward-slash (/) character is considered private to the process that has opened it
and can not be accessed from another process. A message queue whose name starts
with a forward-slash (/) character is a public object, and other processes can access
it (as according to the POSIX standard). See 7.8 Inter-Process Communication With
Public Objects, p.154.
/*
* In this example, the mqExInit() routine spawns two tasks that
* communicate using the message queue.
* To run this test case on the target shell:
*
* -> sp mqExInit
*/
/* defines */
/* forward declarations */
/* includes */
#include <vxWorks.h>
#include <taskLib.h>
#include <stdio.h>
#include <mqueue.h>
#include <fcntl.h>
#include <errno.h>
#include <mqEx.h>
/* defines */
#define HI_PRIO 31
#define MSG_SIZE 16
#define MSG "greetings"
/****************************************************************************
*
* mqExInit - main for message queue send and receive test case
*
* This routine spawns to tasks to perform the message queue send and receive
* test case.
*
* RETURNS: OK, or ERROR
*
* ERRNOS: N/A
*/
218
9 POSIX Facilities
9.17 POSIX Message Queues
return (ERROR);
}
/****************************************************************************
*
* receiveTask - receive messages from the message queue
*
* This routine creates a message queue and calls mq_receive() to wait for
* a message arriving in the message queue.
*
* RETURNS: OK, or ERROR
*
* ERRNOS: N/A
*/
/****************************************************************************
*
* sendTask - send a message to a message queue
*
* This routine opens an already created message queue and
* calls mq_send() to send a message to the opened message queue.
*
* RETURNS: OK, or ERROR
*
* ERRNOS: N/A
*/
void sendTask (void)
{
mqd_t mqPXId; /* msg queue descriptor */
219
VxWorks
Application Programmer's Guide, 6.9
{
printf ("sendTask: mq_open failed\n");
return;
}
A pthread (or task) can use the mq_notify( ) routine to request notification of the
arrival of a message at an empty queue. The pthread can thereby avoid blocking or
polling to wait for a message.
Each queue can register only one pthread for notification at a time. Once a queue
has a pthread to notify, no further attempts to register with mq_notify( ) can
succeed until the notification request is satisfied or cancelled.
Once a queue sends notification to a pthread, the notification request is satisfied,
and the queue has no further special relationship with that particular pthread; that
is, the queue sends a notification signal only once for each mq_notify( ) request. To
arrange for one specific pthread to continue receiving notification signals, the best
approach is to call mq_notify( ) from the same signal handler that receives the
notification signals.
To cancel a notification request, specify NULL instead of a notification signal. Only
the currently registered pthread can cancel its notification request.
The mq_notify( ) mechanism does not send notification:
■ When additional messages arrive at a message queue that is not empty. That
is, notification is only sent when a message arrives at an empty message queue.
■ If another pthread was blocked on the queue with mq_receive( ).
■ After a response has been made to the call to mq_notify( ). That is, only one
notification is sent per mq_notify( ) call.
/*
* In this example, a task uses mq_notify() to discover when a message
* has arrived on a previously empty queue. To run this from the shell:
*
* -> ld < mq_notify_test.o
* -> sp exMqNotify, "greetings"
* -> mq_send
*
*/
/* includes */
#include <vxWorks.h>
#include <signal.h>
#include <mqueue.h>
#include <fcntl.h>
#include <errno.h>
220
9 POSIX Facilities
9.17 POSIX Message Queues
#include <stdio.h>
#include <string.h>
/* defines */
/* forward declarations */
/****************************************************************************
* 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.
*
* RETURNS: 0 on success, or -1
*
* ERRNOS: N/A
*/
int exMqNotify
(
char * pMessage, /* text for message to self */
int loopCnt /* number of times to send a msg */
)
{
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 */
int cnt = 0;
/*
* 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);
attr.mq_flags = 0;
attr.mq_maxmsg = 2;
attr.mq_msgsize = MSG_SIZE;
221
VxWorks
Application Programmer's Guide, 6.9
{
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;
/*
* 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.
*/
/* Cleanup */
/* More cleanup */
return (0);
}
/****************************************************************************
* exNotificationHandle - handler to read in messages
*
* This routine is a signal handler; it reads in messages from a
* message queue.
*
222
9 POSIX Facilities
9.17 POSIX Message Queues
* RETURNS: N/A
*
* ERRNOS: N/A
*/
/*
* 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;
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.
*
* RETURNS: N/A
*
* ERRNOS: N/A
*/
/*
* 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).
*/
223
VxWorks
Application Programmer's Guide, 6.9
if (errno != EAGAIN)
{
printf ("mq_receive: errno = %d\n", errno);
}
}
A trace event is a recorded unit of the trace facility—it is the datum generated. Trace
events include a trace event type identifier, a time-stamp, and other information.
Standard POSIX trace events also have defined payloads; application-defined
224
9 POSIX Facilities
9.20 POSIX Trace
trace events may define a payload. Trace events are initiated either by a user
application or by the trace system itself. The event structure is defined in
installDir/vxworks-6.x/target/usr/h/base/b_struct_posix_trace_event_info.h.
A trace stream is sequence of trace events, which are recorded in memory in a
stream-buffer, and may be written to a file as well. The stream buffer is a ring-based
buffer mechanism. Depending on the options selected when the stream is created,
the buffer may stop logging when it is full, wrap (overwrite old data), or write the
event data to a log file. When the trace facility is used without a trace log, the trace
event data is lost when the tracing process is deleted or exits (note that the tracing
process and the traced process may or may not be the same).
A trace log is a file to which a trace stream is written. A trace stream can be written
to a log automatically (for example, when the posix_trace_shutdown( ) routine is
called) or on demand. The trace log file format is not specified by the POSIX trace
specification. The trace log is write-only while it is being created, and read-only
thereafter.
Trace events are registered by name, and then recorded with a numeric identifier.
Some events are defined in the POSIX standard. These have fixed numeric IDs and
names. The symbolic values, and the textual names, are in the standard (for
example POSIX_TRACE_START and posix_trace_start,
POSIX_TRACE_OVERFLOW and posix_trace_overflow, and so on). These events
types are included in any log you create. Other event type ID and name mappings
can be created with the posix_trace_eventid_open( ) routine.
The posix_trace_eventtypelist_getnext( ) can be used to retrieve an ID, and then
posix_trace_eventid_get_name( ) can be used to get the corresponding name.
There is a per-process limit on the number of events that are obtained, which may
be obtained with the following call:
sysconf(TRACE_USER_EVENT_MAX);
Attempts to create event mappings beyond the limit return the special event ID
POSIX_TRACE_UNNAMED_USEREVENT.
Trace Operation
The trace operation involves three logically distinct roles, each of which has its
own APIs associated with it. These roles are the following:
■
The trace controller, which governs the operation of recording trace events into
the trace stream. This operation includes initializing the attributes of a trace
stream, creating the stream, starting and stopping the stream, filtering the
stream, and shutting the trace stream down, as well as other trace stream
management and information retrieval functions.
■
The trace event generator, which during execution of an instrumented
application injects a trace record into a trace stream whenever a trace point is
reached, as long as that type of trace has not been filtered out.
■ The trace analyzer, which retrieves trace event data. It can either retrieve the
traced events from an active trace stream or from a trace log file, extract stream
attributes, look up event type names, iterate over event type identifiers, and so
on.
225
VxWorks
Application Programmer's Guide, 6.9
While logically distinct, these roles can be performed by a the same process, or by
different processes. A process may trace itself or another process; and may have
several traces active concurrently.A process may also open and read a trace log,
provided the log was generated on the same CPU architecture as the process that
is going to read it.
A trace event filter can be used to define specific types of trace events to collect, and
to reduce the overall amount of data that is collected. All traces have an event filter,
which records all events by default.
Trace APIs
226
9 POSIX Facilities
9.20 POSIX Trace
For more information about these and other APIs, see the pxTraceLib API
reference entries.
To include the trace facility in your system, configure VxWorks with the
INCLUDE_POSIX_TRACE component. This component is included automatically
when the BUNDLE_RTP_POSIX_PSE52 bundle is used.
The section provides an example of code that uses the trace facility and a trace
record that it produces.
switch (pEvent->posix_truncation_status)
{
case POSIX_TRACE_NOT_TRUNCATED:
227
VxWorks
Application Programmer's Guide, 6.9
truncationMsg = "POSIX_TRACE_NOT_TRUNCATED";
break;
case POSIX_TRACE_TRUNCATED_RECORD:
truncationMsg = "POSIX_TRACE_TRUNCATED_RECORD";
break;
case POSIX_TRACE_TRUNCATED_READ:
truncationMsg = "POSIX_TRACE_TRUNCATED_READ";
break;
default:
truncationMsg = "Unknown";
}
228
10
Memory Management
10.1 Introduction
This chapter describes the memory management facilities available to applications
that execute as real-time processes (RTPs). VxWorks provides standard POSIX
memory management facilities, various memory-mapping facilities (both POSIX
and non-POSIX), and memory error detection facilities for debugging problems
with memory use.
NOTE: For information about RTP overlapped virtual memory (and other virtual
memory options), see 2. Real-Time Processes: RTPs.
NOTE: This chapter provides information about facilities available for real-time
processes. For information about the memory management facilities available in
the VxWorks kernel, diagrams of memory for VxWorks configurations that
includes processes, see the VxWorks Kernel Programmer’s Guide.
229
VxWorks
Application Programmer's Guide, 6.9
230
10 Memory Management
10.3 Heap and Memory Partition Management
231
VxWorks
Application Programmer's Guide, 6.9
10.4.1 Configuration
To enable task level memory caching of the system heap, configure VxWorks with
the INCLUDE_MEM_PART_CACHE component. The configuration parameter
MEM_PART_CACHE_GLOBAL_ENABLE is associated with this component. By
default, this parameter is set to FALSE. If it is set to TRUE, the task level memory
caching is enabled globally for all tasks. When any task is created, the task level
memory caching is enabled automatically. When the task is deleted, any memory
that is cached in the memory cache is released and the task level memory caching
is disabled automatically. If this configuration parameter is set to FALSE, the task
level memory caching is not enabled until the memPartCacheCreate( ) the API is
called in the context of the task.
232
10 Memory Management
10.5 Memory Pools
-> make
233
VxWorks
Application Programmer's Guide, 6.9
Routine Description
234
10 Memory Management
10.6 POSIX Memory Management
■
VxWorks provides the POSIX memory locking routines: mlock( ), munlock( ),
mlockall( ), and munlockall( ). However, memory mappings in VxWorks are
always memory-resident. This ensures deterministic memory access for
mapped files, but it also means that physical memory is continuously
associated with mappings, until it is unmapped. Therefore, these POSIX
memory locking routines do not do anything, and are provided simply for
application portability.
■
If the file size changes after a mapping is created, the change is not reflected in
the mapping. It is, therefore, important to make sure that files are not changed
with the standard file operations—ftruncate( ), write( ), and so on—while
mappings are in effect. For example, the second ftruncate( ) call in the
following (abbreviated) example would have no effect on the mapping created
with the mmap( ) call:
fd = shm_open (...)
ftruncate (fd, size1); /* set size */
addr1 = mmap (..., fd); /* map it */
ftruncate (fd, size2); /* change size */
■ Mappings that extend an existing shared mapping may not always succeed.
This is because VxWorks uses memory models that do not ensure that adjacent
virtual memory is available for a specific process.
The POSIX memory mapping features are discussed in 10.7 Memory Mapping
Facilities, p.236.
In VxWorks, memory mappings are always memory resident. Demand paging and
copy-on-write are not performed. This ensures deterministic memory access for
mapped files, but it also means that physical memory is always associated with
mappings, until it is unmapped. The memory locking APIs provided with
VxWorks only perform address space validation, and have no effect on the
mappings.
For more information see the mmanLib API reference.
235
VxWorks
Application Programmer's Guide, 6.9
NOTE: Wind River recommends using mmanLib (and associated features shmLib
and devMemLib) instead of sdLib (shared data regions) because of the additional
functionality and POSIX compliance.
standard API yes no shmLib and mmanLib are POSIX. sdLib is proprietary.
private mapping yes yes sdlib private mapping prohibits any other RTP from
re-mapping shared data regions (the creating RTP has
exclusive access to it). mmanLib conforms to the POSIX
standard, allowing multiple private mappings of a shared
memory object or regular file.
shared memory yes yes mmap( ) supports POSIX shared memory objects (PSE52).
regular files yes no mmap( ) can be used to map regular files as long as the file
system provides POSIX-conforming inode information (for
example, HRFS).
236
10 Memory Management
10.7 Memory Mapping Facilities
global mapping yes no Global mappings refer to mappings that can be accessed
from any virtual memory context—the kernel and all RTPs
(during system calls). It can also be safely accessed in ISR
context. This includes most kernel mappings: text, data, bss,
kernel heap, and anything mapped with mmap( ) by a
kernel task. All mappings created with mmap( ) in the
kernel are global. It does not include shared data regions
mapped by a kernel task. For shared data regions a
significant limitation is that non-global kernel cannot be
accessed from interrupt handlers.
kernel protection yes yes Kernel memory should not be re-mapped by an RTP when
specifying a physical address to be mapped. Shared data
relies on an inverse page table, which can add significant
runtime footprint and prevents remapping of any physical
address. With mmap( ), creation of device memory objects is
allowed by kernel tasks only. RTPs can open device memory
objects that have already been created in the kernel; a
physical address can be alias-mapped, if necessary.
common virtual yes yes Shared data regions are all mapped using the same virtual
address address in all RTPs and the kernel. With mmap( ), shared
mappings use a common address for all RTPs, but a different
address for the kernel. Note that applications should not in
any case share pointers between RTPs. In other words,
applications should not expect the same virtual address to
be assigned (most other operating systems allocate different
virtual address in different process).
ability to map at no no
fixed virtual address
unit of operation MMU entire Unit of operation means the portion of shared memory or a
page shared shared data region that can be operated on (mapped,
data unmapped, or protected). For mmanLib the unit of
region operation is the MMU page (which means that shared
memory can be partially mapped, unmapped, and
protected on page basis). For shared data regions, all
operations are on the entire region.
237
VxWorks
Application Programmer's Guide, 6.9
VxWorks provides support for POSIX shared memory objects. With this type of
mapping, the file descriptor that is used when calling mmap( ) is obtained with
shm_open( ). Both shared and private mappings are supported. Support for this
type of mapping is provided by two kernel components: a pseudo-file system
called shmFs that provides the functionality for shm_open( ) and shm_unlink( ),
and the mmap( ) extension for mapped files. These facilities are provided in the
INCLUDE_POSIX_SHM and INCLUDE_POSIX_MAPPED_FILES components,
respectively.
The shared memory file system provides the name space for shared memory
objects. It is a virtual file system, which means that read, and write operations are
not supported. The contents of shared memory objects can be managed exclusively
by way of their memory mapped images.
For more information, see the shmLib and mmanLib API references. For a code
example, see the shmLib API.
/****************************************************************************
238
10 Memory Management
10.7 Memory Mapping Facilities
int main ()
{
size_t pgSize; /*variable to store page size */
size_t bufSize; /* size of buffer */
char * pBuf; /* buffer */
pgSize=vmPageSizeGet();
bufSize = 4 * pgSize;
if (pBuf == MAP_FAILED)
exit (1);
exit (1);
}
/*
* Unmap the buffer; the unmap() has to be called for the entire buffer.
* Note that unmapping before exit() is not necesary; it is shown here
* only for illustration purpose.
*/
For more information about the mmanLib routines, and an additional code
example, see the VxWorks API reference for mmanLib.
239
VxWorks
Application Programmer's Guide, 6.9
map the device memory object after it has been created and opened. With this kind
of mapping, the file descriptor that is used when calling mmap( ) is obtained with
devMemOpen( ).
For more information, see API reference for devMemLib.
The proprietary shared data regions facility provides a means for RTP applications
to share a common area of memory with each other. RTPs otherwise provide for
full separation and protection of all processes from one another. For more
information, see 3.6 Creating and Using Shared Data Regions, p.47.
NOTE: RTEC is only provided by the Wind River Diab Compiler, and it is the
only feature of the compiler’s Run-Time Analysis Tool Suite (RTA) that is
supported for VxWorks. RTEC is therefore available only for processor
architectures that are supported by the Diab compiler (and not the x86 and
x86-64 processor architectures).
Errors detected by these facilities are reported by the error detection and reporting
facility, which must, therefore be included in the VxWorks kernel configuration.
See 14. Error Detection and Reporting.
NOTE: The memory error detection facilities described in this section are not
included in any shared library provided by Wind River with this release. They can
only be statically linked with application code.
To supplement the error detection features built into memLib and memPartLib
(such as valid block checking), components can be added to VxWorks to perform
automatic, programmatic, and interactive error checks on memLib and
memPartLib operations.
240
10 Memory Management
10.8 Memory Error Detection
Linking
Alternatively, adding the following lines to the application code can also be used:
extern int memEdrEnable;
memEdrEnable = TRUE;
Environment Variables
241
VxWorks
Application Programmer's Guide, 6.9
MEDR_FREE_QUEUE_LEN
Maximum length of the free queue. When a memory block is freed, instead of
immediately returning it to the partition's memory pool, it is kept in a queue.
This is useful for detecting references to a memory block after it has been freed.
When the queue reaches the maximum length allowed, the blocks are returned
to the respective memory pool in a FIFO order. Queuing is disabled when this
parameter is 0. Default setting for this parameter is 64.
MEDR_BLOCK_GUARD_ENABLE
Enable guard signatures in the front and the end of each allocated block.
Enabling this feature aids in detecting buffer overruns, underruns, and some
heap memory corruption. The default setting is FALSE.
MEDR_POOL_SIZE
Set the size of the memory pool used to maintain the memory block database.
The default setting in processes is 64 K. The database uses 32 bytes per
memory block without extended information enabled, and 64 bytes per block
with extended information enabled (call stack trace). This pool is allocated
from the kernel heap.
MEDR_SHOW_ENABLE
Enable heap instrumentation show support in the process. This is needed in
addition to configuring VxWorks with the INCLUDE_MEM_EDR_RTP_SHOW
component. When enabled, the kernel routines communicate with a dedicated
task in the process with message queues. The default setting is FALSE.
Error Types
During execution, errors are automatically logged when the allocation, free, and
re-allocation functions are called. The following error types are automatically
identified and logged:
■
Allocation returns a block address within an already allocated block from the
same partition. This would indicate corruption in the partition data structures.
Detecting this type of errors depends on the compiler assisted instrumentation
(see 10.8.2 Compiler Instrumentation: 32-Bit VxWorks, p.246).
■
Allocation returns block address that is in the task's stack space. This would
indicate corruption in the partition data structures.
■
Allocation returns block address that is in the kernel's static data section. This
would indicate corruption in the partition data structures.
■
Freeing a pointer that is in the task’s stack space.
■
Freeing memory that was already freed and is still in the free queue.
■
Freeing memory that is in the kernel’s static data section. Detecting this type
of errors depends on the compiler assisted instrumentation (see 10.8.2 Compiler
Instrumentation: 32-Bit VxWorks, p.246).
■
Freeing memory in a different partition than the one in which it was allocated.
■
Freeing a partial memory block.
■
Freeing a memory block with the guard zone corrupted, when the
MEDR_BLOCK_GUARD_ENABLE environment variable is TRUE.
■
Pattern in a memory block which is in the free queue has been corrupted, when
the MEDR_FILL_FREE_ENABLE environment variable is TRUE.
242
10 Memory Management
10.8 Memory Error Detection
Shell Commands
The show routines and commands described in Table 10-4 are available for use
with the shell’s C and command interpreters to display information.
Code Example
The following application code can be used to generate various errors that can be
monitored from the shell (line numbers are included for reference purposes). Its
use is illustrated in Shell Session Example, p.244.
#include <vxWorks.h>
#include <stdlib.h>
#include <taskLib.h>
int main ()
{
char * pChar;
243
VxWorks
Application Programmer's Guide, 6.9
First set up the environment variables in the shell task. These variables will be
inherited by processes created with rtpSp( ). The first environment variable
enables trace information to be saved for each allocation, the second one enables
the show command support inside the process.
-> putenv "MEDR_EXTENDED_ENABLE=TRUE"
value = 0 = 0x0
-> putenv "MEDR_SHOW_ENABLE=TRUE"
value = 0 = 0x0
Spawn the process using the executable produced from the example code:
-> rtp = rtpSp ("[Link]")
rtp = 0x223ced0: value = 36464240 = 0x22c6670
At this point, the initial process task (iheapErr), which is executing main( ), is
stopped at the first taskSuspend( ) call (line 9 of the source code). Now mark all
allocated blocks in the process which resulted from the process initialization phase:
-> memEdrRtpBlockMark rtp
value = 27 = 0x1b
Next, clear all entries in the error log. This step is optional, and is used to limit the
number of events displayed by the edrShow( ) command that will follow:
-> edrClear
value = 0 = 0x0
Resume the initial task iheapErr to continue execution of the application code:
-> tr iheapErr
value = 0 = 0x0
After resuming the process will continue execution until the second
taskSuspend( ) call (line 17). Now list all blocks in the process that are unmarked.
These are blocks that have been allocated since memEdrRtpBlockMark( ) was
called, but have not been freed. Such blocks are possible memory leaks:
-> memEdrRtpBlockShow rtp, 0, 0, 0, 5, 1
244
10 Memory Management
10.8 Memory Error Detection
value = 0 = 0x0
Display the error log. The first error corresponds to line 12 in the test code, while
the second error corresponds to line 14.
-> edrShow
ERROR LOG
=========
Log Size: 524288 bytes (128 pages)
Record Size: 4096 bytes
Max Records: 123
CPU Type: 0x5a
Errors Missed: 0 (old) + 0 (recent)
Error count: 2
Boot count: 4
Generation count: 6
==[1/2]==============================================================
Severity/Facility: NON-FATAL/RTP
Boot Cycle: 4
OS Version: 6.0.0
Time: THU JAN 01 00:09:56 1970 (ticks = 35761)
Task: "iheapErr" (0x022c8750)
RTP: "[Link]" (0x022c6670)
RTP Address Space: 0x30000000 -> 0x30057000
<<<<<Traceback>>>>>
==[2/2]==============================================================
Severity/Facility: NON-FATAL/RTP
Boot Cycle: 4
OS Version: 6.0.0
Time: THU JAN 01 00:09:56 1970 (ticks = 35761)
Task: "iheapErr" (0x022c8750)
RTP: "[Link]" (0x022c6670)
RTP Address Space: 0x30000000 -> 0x30057000
<<<<<Traceback>>>>>
245
VxWorks
Application Programmer's Guide, 6.9
Additional errors are detected if the application is compiled using the Run-Time
Error Checker (RTEC) feature of the Wind River Diab Compiler (for 32-bit
VxWorks). The following flag should be used:
-Xrtc=option
NOTE: RTEC is only provided by the Wind River Diab Compiler, and it is the only
feature of the compiler’s Run-Time Analysis Tool Suite (RTA) that is supported for
VxWorks. RTEC is therefore available only for processor architectures that are
supported by the Diab compiler (and not the x86 and x86-64 processor
architectures).
! CAUTION: The RTEC facility only detects errors in code that is compiled with the
Wind River Diab compiler -Xrtc flag. If calls are made to code that is not compiled
with -Xrtc, errors may not be detected.
Code compiled with the -Xrtc flag is instrumented for run-time checks such as
pointer reference check and pointer arithmetic validation, standard library
parameter validation, and so on. These instrumentations are supported through
the memory partition run-time error detection library.
Table 10-5 lists the -Xrtc options that are supported. Using the -Xrtc flag without
specifying any option implements all supported options. An individual option (or
bitwise OR’d combinations of options) can be enabled using the following syntax:
-Xrtc=option
Option Description
0x80 report source code filename and line number in error logs
The errors and warnings detected by the RTEC compile-in instrumentation are
logged by the error detection and reporting facility (see 14. Error Detection and
Reporting). The following error types are identified:
■ Bounds-check violation for allocated memory blocks.
■ Bounds-check violation of static (global) variables.
■
Bounds-check violation of automatic variables.
246
10 Memory Management
10.8 Memory Error Detection
■
Reference to a block in the free queue.
■
Reference to the free part of the task’s stack.
■
De-referencing a NULL pointer.
For information beyond what is provided in this section, see the Wind River
Compiler User’s Guide: Run-Time Error Checker.
Support for this feature in the kernel is enabled by configuring VxWorks with the
basic error detection and reporting facilities. See 14.2 Configuring Error Detection
and Reporting Facilities, p.318.
Shell Commands
Code Example
This application code generates various errors that can be recorded and displayed,
if built with the Wind River Compiler and its -Xrtc option (line numbers are
included for reference purposes). Its use is illustrated in Shell Session Example,
p.247.
#include <vxWorks.h>
#include <stdlib.h>
int main ()
{
char name[] = "very_long_name";
char * pChar;
int state[] = { 0, 1, 2, 3 };
int ix = 0;
Start the process using the executable created from the sample code listed above:
247
VxWorks
Application Programmer's Guide, 6.9
Next, list the error log. As shown below, three errors are detected by the compiler
instrumentation:
-> edrShow
ERROR LOG
=========
Log Size: 524288 bytes (128 pages)
Record Size: 4096 bytes
Max Records: 123
CPU Type: 0x5a
Errors Missed: 0 (old) + 0 (recent)
Error count: 3
Boot count: 4
Generation count: 8
The first one is caused by the code on line 13. A string of length 14 is copied into a
allocated buffer of size 13:
==[1/3]==============================================================
Severity/Facility: NON-FATAL/RTP
Boot Cycle: 4
OS Version: 6.0.0
Time: THU JAN 01 01:55:42 1970 (ticks = 416523)
Task: "irefErr" (0x0229c500)
RTP: "[Link]" (0x0229a450)
RTP Address Space: 0x30000000 -> 0x30058000
Injection Point: main.c:13
<<<<<Traceback>>>>>
The second error refers to line 17. The local state array is referenced with index 4.
Since the array has only four elements, the range of valid indexes is 0 to 3:
==[2/3]==============================================================
Severity/Facility: NON-FATAL/RTP
Boot Cycle: 4
OS Version: 6.0.0
Time: THU JAN 01 01:55:42 1970 (ticks = 416523)
Task: "irefErr" (0x0229c500)
RTP: "[Link]" (0x0229a450)
RTP Address Space: 0x30000000 -> 0x30058000
Injection Point: main.c:17
<<<<<Traceback>>>>>
The last error is caused by the code on line 21. A memory block that has been freed
is being modified:
==[3/3]==============================================================
Severity/Facility: NON-FATAL/RTP
Boot Cycle: 4
OS Version: 6.0.0
Time: THU JAN 01 01:55:42 1970 (ticks = 416523)
Task: "irefErr" (0x0229c500)
248
10 Memory Management
10.8 Memory Error Detection
<<<<<Traceback>>>>>
249
VxWorks
Application Programmer's Guide, 6.9
250
11
I/O System
11.1 Introduction
The VxWorks I/O system is designed to present a simple, uniform,
device-independent interface to any kind of device, including:
■
character-oriented devices such as terminals or communications lines
■
random-access block devices such as disks
■
virtual devices such as intertask pipes and sockets
■
monitor and control devices such as digital and analog I/O devices
■
network devices that give access to remote devices
The VxWorks I/O system provides standard C libraries for both basic and buffered
I/O. The basic I/O libraries are UNIX-compatible; the buffered I/O libraries are
ANSI C-compatible.
For information about VxWorks devices, see 12. Devices.
NOTE: This chapter provides information about facilities available for real-time
processes. For information about facilities available in the VxWorks kernel, see the
corresponding chapter in the VxWorks Kernel Programmer’s Guide.
251
VxWorks
Application Programmer's Guide, 6.9
In VxWorks, file descriptors are unique to the kernel and to each process—as
in UNIX and Windows. The kernel and each process has its own universe of
file descriptors, distinct from each other. When the process is created, its
universe of file descriptors is initially populated by duplicating the file
descriptors of its creator. (This applies only when the creator is a process. If the
creator is a kernel task, only the three standard I/O descriptors 0, 1 and 2 are
duplicated.) Thereafter, all open, close, or dup activities affect only that
process’ universe of descriptors.
In kernel and in each process, file descriptors are global to that entity, meaning
that they are accessible by any task running in it.
In the kernel, however, standard input, standard output, and standard error (0,
1, and 2) can be made task specific.
For more information see 11.6.1 File Descriptors, p.257 and 11.6.3 Standard I/O
Redirection, p.258.
252
11 I/O System
11.3 Configuring VxWorks With I/O Facilities
■
I/O Control
The specific parameters passed to ioctl( ) functions may differ between UNIX
and VxWorks.
Device Naming
Non-block devices are named when they are added to the I/O system, usually at
system initialization time. Block devices are named when they are initialized for
use with a specific file system. The VxWorks I/O system imposes no restrictions on
the names given to devices. The I/O system does not interpret device or filenames
in any way, other than during the search for matching device and filenames.
253
VxWorks
Application Programmer's Guide, 6.9
Devices are handled by device drivers. In general, using the I/O system does not
require any further understanding of the implementation of devices and drivers.
Note, however, that the VxWorks I/O system gives drivers considerable flexibility
in the way they handle each specific device. Drivers conform to the conventional
user view presented here, but can differ in the specifics. For more information, see
12. Devices.
When a file name is specified in an I/O call, the I/O system searches for a device
with a name that matches at least an initial substring of the file name. The I/O
function is then directed at this device.
For example, if the file name is mars:/usr/xeno/foo/myfile, the I/O function is
directed to the remote machine named mars. (For information about the use
remote system prefixes, see 11.5 Remote File System Access From VxWorks, p.254.) If
a matching device name cannot be found, then the I/O function is directed at a
default device.
You can set the default device to any device in the system or no device at all—in
which case failure to match a device name returns an error. To obtain the current
default path, use getcwd( ). To set the default path, use chdir( ).
File I/O
Although all I/O is directed at named files, it can be done at two different levels:
basic and buffered. The two differ in the way data is buffered and in the types of calls
that can be made. For more information, see 11.6 Basic I/O, p.256 and 11.7 Standard
I/O, p.266.
254
11 I/O System
11.5 Remote File System Access From VxWorks
By convention, NFS-based network devices are mounted with names that begin
with a slash. To access directories and files from VxWorks, use the path from the
mount point. For example, if the mount point for /usr/xeno/foo on VxWorks is /foo,
then /usr/xeno/foo/myFile is accessed from VxWorks as:
/foo/myFile
For example:
fd=open("/foo/myFile", O_CREAT | O_RDWR, 0);
For information about NFS, see VxWorks Kernel Programmer’s Guide: Network File
System.
Non-NFS Network File System Access from VxWorks WIth FTP or RSH
To access non-NFS file systems over a network with FTP or RSH, the name of the
remote machine must be specified by prefixing the file system path with the
remote machine name followed by a colon.
The exception to this rule is if the remote machine is a Linux or Solaris host system,
the host and colon prefix is not required (but may still be used). By default
VxWorks uses the host system as the default device if it cannot find a matching
device name, which works for Linux and Solaris hosts. It does not work, however,
with a Windows machine because VxWorks interprets Windows drive letters as
device names—and they must therefore be disambiguated with the host name and
colon prefix.
For example, the file /usr/xeno/foo/myfile on the Linux machine mars and the file
C:\bar\myotherfile on Windows machine jupiter would be identified as follows:
mars:/usr/xeno/foo/myfile
jupiter:C:\bar\myotherfile
If mars is the host machine (for Linux or Solaris), however, the following usage—
without the machine name and colon—would also work:
fd=open("/usr/xeno/foo/myfile", O_CREAT | O_RDWR, 0);
Note that for the files of a remote host to be accessible with RSH or FTP,
permissions and user identification must be established on both the remote and
local systems.
For the host system, the name is defined with the host name boot parameter (for
which the default is simply host). For other remote machines, a network device
must first be created by calling the kernel routine netDevCreate( ). For information
about the host name boot parameter, see the VxWorks Kernel Programmer’s Guide:
Boot Loader. For information about FTP and RSH devices, see the VxWorks Kernel
Programmer’s Guide: Devices.
255
VxWorks
Application Programmer's Guide, 6.9
The host file system is accessed from the VxWorks simulator by way of the
pass-through file system (passFs).
For a Linux or Solaris host, the path must be prefixed with name of this host on
which the simulator is running, followed by a colon. For example:
mars:/usr/xeno/foo/myfile
For a Windows hosts, the path must be prefixed with literal string host followed
by a colon. For example:
host:/C/bar/myotherfile
A colon may be used after the Windows drive letter, but this use is deprecated and
is retained only for backward-compatibility reasons.
NOTE: Do not confuse the host: prefix used with the VxWorks simulator on a
Windows host with the default host device name. The default host name is
specified with the host name boot loader parameter, and it appears as host: in the
list generated by the VxWorks shell devs command.
For more information about the VxWorks simulator, see the Wind River VxWorks
Simulator User’s Guide.
Routine Description
open( ) Opens a file (optionally, creates a file if it does not already exist.)
256
11 I/O System
11.6 Basic I/O
At the basic I/O level, files are referred to by a file descriptor. A file descriptor is a
small integer returned by a call to open( ) or creat( ). The other basic I/O calls take
a file descriptor as a parameter to specify a file.
File descriptors are not global. The kernel has its own set of file descriptors, and
each process (RTP) has its own set. Tasks within the kernel, or within a specific
process share file descriptors. The only instance in which file descriptors may be
shared across these boundaries, is when one process is a child of another process
or of the kernel and it does not explicitly close a file using the descriptors it inherits
from its parent. (Processes created by kernel tasks share only the spawning kernel
task's standard I/O file descriptors 0, 1 and 2.) For example:
■ If task A and task B are running in process foo, and they each perform a write( )
on file descriptor 7, they will write to the same file (and device).
■ If process bar is started independently of process foo (it is not foo’s child) and
its tasks X and Y each perform a write( ) on file descriptor 7, they will be
writing to a different file than tasks A and B in process foo.
■ If process foobar is started by process foo (it is foo’s child) and its tasks M and
N each perform a write( ) on file descriptor 7, they will be writing to the same
file as tasks A and B in process foo. However, this is only true as long as the
tasks do not close the file. If they close it, and subsequently open file descriptor
7 they will operate on a different file.
When a file is opened, a file descriptor is allocated and returned. When the file is
closed, the file descriptor is deallocated.
The size of the file descriptor table, which defines the maximum number of files
that can be open simultaneously in a process, is inherited from the spawning
environment. If the process is spawned by a kernel task, the size of the kernel file
descriptor table is used for the initial size of the table for the new process.
The size of the file descriptor table for each process can be changed
programmatically. The rtpIoTableSizeGet( ) routine reads the current size of the
table, and the rtpIoTableSizeSet( ) routine changes it.
By default, file descriptors are reclaimed only when the file is closed for the last
time. However, the dup( ) and dup2( ) routines can be used to duplicate a file
descriptor. For more information, see 11.6.3 Standard I/O Redirection, p.258.
257
VxWorks
Application Programmer's Guide, 6.9
descriptors to manipulate the input and output for all tasks in a process at once by
changing the files associated with these descriptors.
These standard file descriptors are used to make an application independent of its
actual I/O assignments. If a process sends its output to standard output (where the
file descriptor is 1), then its output can be redirected to any file of any device,
without altering the application’s source code.
If a process is spawned by a kernel task, the process inherits the standard I/O file
descriptor assignments of the spawning kernel task. These may be the same as the
global standard I/O file descriptors for the kernel, or they may be different,
task-specific standard I/O file descriptors. (For more information about kernel
standard I/O assignments, see the VxWorks Kernel Programmer’s Guide: I/O System.)
If a process is spawned by another process, it inherits the standard I/O file
descriptor assignments of the spawning process.
After a process has been spawned, its standard I/O file descriptors can be changed
to any file descriptor that it owns.
The POSIX dup( ) and dup2( ) routines are used for redirecting standard I/O to a
different file and then restoring them, if necessary. (Note that this is a very different
process from standard I/O redirection in the kernel).
The first routine is used to save the original file descriptors so they can be restored
later. The second routine assigns a new descriptor for standard I/O, and can also
be used to restore the original. Every duplicated file descriptor should be closed
explicitly when it is no longer in use. The following example illustrates how the
routines are used.
First use the dup( ) routine to duplicate and save the standard I/O file descriptors,
as follows:
/* Temporary fd variables */
int oldFd0;
int oldFd1;
int oldFd2;
int newFd;
/* Set newFd to fd 0, 1, 2 */
dup2 (newFd, 0);
dup2 (newFd, 1);
dup2 (newFd, 2);
If the process’ standard I/O must be redirected again, the preceding step can be
repeated with a another new file descriptor.
If the original standard I/O file descriptors must be restored, the following
procedure can be performed:
/* When complete, restore the original standard IO */
258
11 I/O System
11.6 Basic I/O
This redirection only affect the process in which it is done. It does not affect the
standard I/O of any other process or the kernel. Note, however, that any new
processes spawned by this process inherit the current standard I/O file descriptors
of the spawning process (whatever they may be) as their initial standard I/O
setting.
For more information, see the VxWorks API references for dup( ) and dup2( ).
Before I/O can be performed on a device, a file descriptor must be opened to the
device by invoking the open( ) routine—or creat( ), as discussed in the next section.
The arguments to open( ) are the filename, the type of access, and the mode (file
permissions):
fd = open ("name", flags, mode);
Flag Description
O_APPEND Set the file offset to the end of the file prior to each write, which
guarantees that writes are made at the end of the file. It has no
effect on devices other than the regular file system.
O_NONBLOCK Non-blocking I/O.
259
VxWorks
Application Programmer's Guide, 6.9
Flag Description
Note the following special cases with regard to use of the file access and mode (file
permissions) parameters to open( ):
■ In general, you can open only preexisting devices and files with open( ).
However, with NFS network, dosFs, and HRFS devices, you can also create
files with open( ) by OR’ing O_CREAT with one of the other access flags.
■ HRFS directories can be opened with the open( ) routine, but only using the
O_RDONLY flag.
■ With both dosFs and NFS devices, you can use the O_CREAT flag to create a
subdirectory by setting mode to FSTAT_DIR. Other uses of the mode parameter
with dosFs devices are ignored.
■ With an HRFS device you cannot use the O_CREAT flag and the FSTAT_DIR
mode option to create a subdirectory. HRFS ignores the mode option and
simply creates a regular file.
■ The netDrv default file system does not support the F_STAT_DIR mode option
or the O_CREAT flag.
■ For NFS devices, the third parameter to open( ) is normally used to specify the
mode of the file. For example:
myFd = open ("fooFile", O_CREAT | O_RDWR, 0644);
■ While HRFS supports setting the permission mode for a file, it is not used by
the VxWorks operating system.
■ Files can be opened with the O_SYNC flag, indicating that each write should be
immediately written to the backing media. This flag is currently supported by
the dosFs file system, and includes synchronizing the FAT and the directory
entries.
■
The O_SYNC flag has no effect with HRFS because file system is always
synchronous. HRFS updates files as though the O_SYNC flag were set.
NOTE: Drivers or file systems may or may not honor the flag values or the mode
values. A file opened with O_RDONLY mode may in fact be writable if the driver
allows it. Consult the driver or file system information for specifics.
See the VxWorks file system API references for more information about the
features that each file system supports.
The open( ) routine, if successful, returns a file descriptor. This file descriptor is
then used in subsequent I/O calls to specify that file. The file descriptor is an
identifier that is not task specific; that is, it is shared by all tasks within the memory
space. Within a given process or the kernel, therefore, one task can open a file and
260
11 I/O System
11.6 Basic I/O
any other task can then use the file descriptor. The file descriptor remains valid
until close( ) is invoked with that file descriptor, as follows:
close (fd);
At that point, I/O to the file is flushed (completely written out) and the file
descriptor can no longer be used by any task within the process (or kernel).
However, the same file descriptor number can again be assigned by the I/O system
in any subsequent open( ).
For processes, files descriptors are closed automatically only when a process
terminates. It is, therefore, recommended that tasks running in processes explicitly
close all file descriptors when they are no longer needed. As stated previously
(11.6.1 File Descriptors, p.257), there is a limit to the number of files that can be open
at one time. Note that a process owns the files, so that when a process is destroyed,
its file descriptors are automatically closed.
File-oriented devices must be able to create and remove files as well as open
existing files.
The creat( ) routine directs a file-oriented device to make a new file on the device
and return a file descriptor for it. The arguments to creat( ) are similar to those of
open( ) except that the filename specifies the name of the new file rather than an
existing one; the creat( ) routine returns a file descriptor identifying the new file.
fd = creat ("name", flag);
Note that with the HRFS file system the creat( ) routine is POSIX-compliant, and
the second parameter is used to specify file permissions; the file is opened in
O_RDWR mode.
With dosFs, however, the creat( ) routine is not POSIX-compliant and the second
parameter is used for open mode flags.
The remove( ) routine deletes a named file on a file-system device:
remove ("name");
After a file descriptor is obtained by invoking open( ) or creat( ), tasks can read
bytes from a file with read( ) and write bytes to a file with write( ). The arguments
to read( ) are the file descriptor, the address of the buffer to receive input, and the
maximum number of bytes to read:
nBytes = read (fd, &buffer, maxBytes);
The read( ) routine waits for input to be available from the specified file, and
returns the number of bytes actually read. For file-system devices, if the number of
bytes read is less than the number requested, a subsequent read( ) returns 0 (zero),
indicating end-of-file. For non-file-system devices, the number of bytes read can be
less than the number requested even if more bytes are available; a subsequent
261
VxWorks
Application Programmer's Guide, 6.9
read( ) may or may not return 0. In the case of serial devices and TCP sockets,
repeated calls to read( ) are sometimes necessary to read a specific number of bytes.
(See the reference entry for fioRead( ) in fioLib). A return value of ERROR (-1)
indicates an unsuccessful read.
The arguments to write( ) are the file descriptor, the address of the buffer that
contains the data to be output, and the number of bytes to be written:
actualBytes = write (fd, &buffer, nBytes);
The write( ) routine ensures that all specified data is at least queued for output
before returning to the caller, though the data may not yet have been written to the
device (this is driver dependent). The write( ) routine returns the number of bytes
written; if the number returned is not equal to the number requested, an error has
occurred.
The read( ) and write( )routines are POSIX-compliant.
It is sometimes convenient to discard part of the data in a file. After a file is open
for writing, you can use the ftruncate( ) routine to truncate a file to a specified size.
Its arguments are a file descriptor and the desired length of the file in bytes:
status = ftruncate (fd, length);
The ioctl( ) routine provides a flexible mechanism for performing I/O functions
that are not performed by the other basic I/O calls. Examples include determining
how many bytes are currently available for input, setting device-specific options,
obtaining information about a file system, and positioning random-access files to
specific byte positions.
The arguments to the ioctl( ) routine are the file descriptor, a code that identifies
the control function requested, and an optional function-dependent argument:
result = ioctl (fd, function, arg);
For ioctl( ) calls made in processes, the arg parameter is optional. Both of the
following are legitimate calls:
262
11 I/O System
11.6 Basic I/O
For example, the following call uses the FIOBAUDRATE function to set the baud
rate of a tty device to 9600:
status = ioctl (fd, FIOBAUDRATE, 9600);
The discussion of each devices in 12. Devices summarizes the ioctl( ) functions
available for that device. The ioctl( ) control codes are defined in ioLib.h. For more
information, see the reference entries for specific device drivers or file systems.
The ioctl( ) routine is POSIX-compliant.
VxWorks also provides the posix_devctl( ) routine for special devices. For more
information, see the API reference entry.
Macro Description
263
VxWorks
Application Programmer's Guide, 6.9
Macro Description
Applications can use select( ) with any character I/O devices that provide support
for this facility (for example, pipes, serial devices, and sockets).
For more information, see the API reference entry for select( ).
#include <vxWorksCommon.h>
#include <unistd.h>
#include <fcntl.h>
#include <strings.h>
#include <sys/select.h>
#include <stdio.h>
#define MAX_FDS 2
#define MAX_DATA 1024
#define PIPEHI "/pipe/highPriority"
#define PIPENORM "/pipe/normalPriority"
/************************************************************************
* selServer - reads data as it becomes available from two different pipes
*
* Opens two pipe fds, reading from whichever becomes available. The
* server code assumes the pipes have been created from either another
* task or the shell. To test this code from the shell do the following:
* -> pipeDevCreate ("/pipe/highPriority", 5, 1024)
* -> pipeDevCreate ("/pipe/normalPriority", 5, 1024)
* -> fdHi = open ("/pipe/highPriority", 1, 0)
* -> fdNorm = open ("/pipe/normalPriority", 1, 0)
* -> rtpSp "[Link]"
* -> i
* At this point you should see selServer¡¯s state as pended. You can now
* write to either pipe to make the selServer display your message.
* -> write fdNorm, "Howdy", 6
* -> write fdHi, "Urgent", 7
*/
STATUS
selServer(void)
{
struct fd_set readFds; /* bit mask of fds to read from */
int fds[MAX_FDS]; /* array of fds on which to pend */
int width; /* number of fds on which to pend */
int i; /* index for fd array */
char buffer[MAX_DATA]; /* buffer for data that is read */
264
11 I/O System
11.6 Basic I/O
close (fds[1]);
return (ERROR);
}
/* step through array and read from fds that are ready */
for (i=0; i< MAX_FDS; i++) {
/* check if this fd has data to read */
if (FD_ISSET (fds[i], &readFds)) {
/* typically read from fd now that it is ready */
read (fds[i], buffer, MAX_DATA);
/* normally service request, for this example print it */
printf ("SELSERVER Reading from %s: %s\n",
(i == 0) ? PIPEHI : PIPENORM, buffer);
}
}
}
}
int
main(int argc, char *argv[])
{
printf("selServer starts..\n");
selServer();
return (0);
}
The POSIX fsPxLib library provides I/O and file system routines for various file
manipulations. These routines are described in Table 11-4.
Routine Description
265
VxWorks
Application Programmer's Guide, 6.9
Routine Description
For more information, see the API references for fsPxLib and ioLib.
The use of the buffered I/O routines provided with standard I/O can provide a
performance advantage over the non-buffered basic I/O routines when an
application performs many small read or write operations. (For information about
non-buffered I/O, see 11.6 Basic I/O, p.256).
Although the VxWorks I/O system is efficient, some overhead is associated with
each low-level (basic I/O) call. First, the I/O system must dispatch from the
device-independent user call (read( ), write( ), and so on) to the driver-specific
routine for that function. Second, most drivers invoke a mutual exclusion or
queuing mechanism to prevent simultaneous requests by multiple users from
interfering with each other.
This overhead is quite small because the VxWorks primitives are fast. However, an
application processing a single character at a time from a file incurs that overhead
for each character if it reads each character with a separate read( ) call, as follows:
n = read (fd, &char, 1);
To make this type of I/O more efficient and flexible, the standard I/O facility
implements a buffering scheme in which data is read and written in large chunks
and buffered privately. This buffering is transparent to the application; it is
handled automatically by the standard I/O routines and macros.
When a file is created or opened with a standard I/O routine, it is commonly
referred to as associating a stream with the file. To open a file, use fopen( ), as
follows:
fp = fopen ("/usr/foo", "r");
266
11 I/O System
11.8 Other Formatted I/O
The fopen( ) call returns a pointer to a FILE object (declared as FILE *), which is
referred to as a file pointer. The FILE object contains information required by the
standard I/O library to manage the stream, including a file descriptor that is
actually used for I/O, information about the buffer used for the stream, and so on.
(By contrast, the low-level I/O routines simply identify a file with a file descriptor,
which is a small integer.)
A file descriptor that is already open can be associated subsequently with a FILE
buffer by calling fdopen( ), as follows:
fp = fdopen (fd, "r");
After a file is opened with fopen( ), data can be read with fread( ), or a character at
a time with getc( ), and data can be written with fwrite( ), or a character at a time
with putc( ). The FILE buffer is deallocated when fclose( ) is called.
The routines and macros to get data into or out of a file are extremely efficient.
They access the buffer with direct pointers that are incremented as data is read or
written by the user. They pause to call the low-level read or write routines only
when a read buffer is empty or a write buffer is full.
! WARNING: The standard I/O buffers and pointers are private to a particular task.
They are not interlocked with semaphores or any other mutual exclusion
mechanism, because this defeats the point of an efficient private buffering scheme.
Therefore, multiple tasks must not perform I/O to the same stdio FILE pointer at
the same time.
As discussed in 11.6 Basic I/O, p.256, there are three special file descriptors (0, 1,
and 2) reserved for standard input, standard output, and standard error. Three
corresponding stdio FILE buffers are automatically created when a task uses the
standard file descriptors, stdin, stdout, and stderr, to do buffered I/O to the standard
file descriptors. Each task using the standard I/O file descriptors has its own stdio
FILE buffers. The FILE buffers are deallocated when the task exits.
267
VxWorks
Application Programmer's Guide, 6.9
NOTE: The asynchronous I/O facilities are not included in any RTP shared library
provided by Wind River for use with this release. They can only be statically linked
with application code. For information about creating a custom shared library that
provides this functionality, please contact Wind River Support.
The VxWorks library aioPxLib provides POSIX AIO routines. To access a file
asynchronously, open it with the open( ) routine, like any other file. Thereafter, use
the file descriptor returned by open( ) in calls to the AIO routines. The POSIX AIO
routines (and two associated non-POSIX routines) are listed in Table 11-5.
Function Description
268
11 I/O System
11.9 Asynchronous Input/Output
Each of the AIO calls takes an AIO control block (aiocb) as an argument. The
calling routine must allocate space for the aiocb, and this space must remain
available for the duration of the AIO operation. (Thus the aiocb must not be
created on the task's stack unless the calling routine will not return until after the
AIO operation is complete and aio_return( ) has been called.) Each aiocb describes
a single AIO operation. Therefore, simultaneous asynchronous I/O operations
using the same aiocb are not valid and produce undefined results.
The aiocb structure is defined in aio.h. It contains the following fields:
aio_fildes
The file descriptor for I/O.
aio_offset
The offset from the beginning of the file.
aio_buf
The address of the buffer from/to which AIO is requested.
aio_nbytes
The number of bytes to read or write.
aio_reqprio
The priority reduction for this AIO request.
aio_sigevent
The signal to return on completion of an operation (optional).
aio_lio_opcode
An operation to be performed by a lio_listio( ) call.
aio_sys
The address of VxWorks-specific data (non-POSIX).
For full definitions and important additional information, see the reference entry
for aioPxLib.
! CAUTION: The aiocb structure and the data buffers referenced by it are used by the
system to perform the AIO request. Therefore, once the aiocb has been submitted
to the system, the application must not modify the aiocb structure until after a
subsequent call to aio_return( ). The aio_return( ) call retrieves the previously
submitted AIO data structures from the system. After the aio_return( ) call, the
calling application can modify the aiocb, free the memory it occupies, or reuse it
for another AIO call. If space for the aiocb is allocated from the stack, the task
should not be deleted (or complete running) until the aiocb has been retrieved
from the system with an aio_return( ) call.
269
VxWorks
Application Programmer's Guide, 6.9
operation, but only whether a request is successful—that is, whether the AIO
routine is able to put the operation on a queue for eventual execution.
After the I/O operations themselves execute, they also generate return values that
reflect the success or failure of the I/O. There are two routines that you can use to
get information about the success or failure of the I/O operation: aio_error( ) and
aio_return( ). You can use aio_error( ) to get the status of an AIO operation
(success, failure, or in progress), and aio_return( ) to obtain the return values from
the individual I/O operations. Until an AIO operation completes, its error status is
EINPROGRESS. To cancel an AIO operation, call aio_cancel( ). To force all I/O
operations to the synchronized I/O completion state, use aio_fsync( ).
A task can determine whether an AIO request is complete in any of the following
ways:
■ Check the result of aio_error( ) periodically, as in the previous example, until
the status of an AIO request is no longer EINPROGRESS.
■ Use aio_suspend( ) to suspend the task until the AIO request is complete.
■ Use signals to be informed when the AIO request is complete.
270
12
Devices
12.1 Introduction
The VxWorks I/O system allows different device drivers to handle the seven basic
I/O functions. VxWorks provides serial devices (pseudo and pseudo-terminal),
pipes, a pseudo I/O device for accessing memory directly, null devices, and block
devices. All of these facilities are described in this chapter, along with the extended
block device (XBD) layer, which provides an interface between block device
drivers and file systems. The unique relationship between the device-independent
I/O system and device drivers is also discussed.
VxWorks provides NFS and non-NFS network devices and sockets, which are
described briefly in this chapter, and in detail inthe VxWorks Kernel Programmer’s
Guide: Network File System and the Wind River Network Stack Programmer’s Guide.
For information about the I/O system itself, see 11. I/O System.
271
VxWorks
Application Programmer's Guide, 6.9
scsi SCSI interface (not supported for 64-bit VxWorks with this
release)
See the VxWorks Kernel Programmer’s Guide: I/O System and the VxWorks Device
Driver Developer’s Guide for more detailed information about I/O device drivers.
272
12 Devices
12.3 Serial I/O Devices: Terminal and Pseudo-Terminal Devices
NOTE: For the remainder of this section, the term tty is used to indicate both tty
and pty devices
tty Options
The tty devices have a full range of options that affect the behavior of the device.
These options are selected by setting bits in the device option word using the
ioctl( ) routine with the FIOSETOPTIONS function. For example, to set all the tty
options except OPT_MON_TRAP:
status = ioctl (fd, FIOSETOPTIONS, OPT_TERMINAL & ~OPT_MON_TRAP);
For more information about I/O control functions, see VxWorks Kernel
Programmer’s Guide: I/O System.
Table 12-2 is a summary of the available options. The listed names are defined in
the header file ioLib.h. For more detailed information, see the API reference entry
for tyLib.
Library Description
OPT_LINE Selects line mode. (See 12.3.1 Raw Mode and Line Mode, p.273.)
OPT_ECHO Echoes input characters to the output of the same channel.
OPT_CRMOD Translates input RETURN characters into NEWLINE (\n);
translates output NEWLINE into RETURN-LINEFEED.
OPT_TANDEM Responds to software flow control characters CTRL+Q and
CTRL+S (XON and XOFF).
OPT_7_BIT Strips the most significant bit from all input bytes.
OPT_MON_TRAP Enables the special ROM monitor trap character, CTRL+X by
default.
OPT_ABORT Enables the special kernel shell abort character, CTRL+C by
default. (Only useful if the kernel shell is configured into the
system)
OPT_TERMINAL Sets all of the above option bits.
OPT_RAW Sets none of the above option bits.
A tty device operates in one of two modes: raw mode (unbuffered) or line mode. Raw
mode is the default. Line mode is selected by the OPT_LINE bit of the device option
word (see tty Options, p.273).
In raw mode, each input character is available to readers as soon as it is input from
the device. Reading from a tty device in raw mode causes as many characters as
possible to be extracted from the input ring, up to the limit of the user’s read buffer.
Input cannot be modified except as directed by other tty option bits.
273
VxWorks
Application Programmer's Guide, 6.9
In line mode, all input characters are saved until a NEWLINE character is input; then
the entire line of characters, including the NEWLINE, is made available in the ring
at one time. Reading from a tty device in line mode causes characters up to the end
of the next line to be extracted from the input ring, up to the limit of the user’s read
buffer. Input can be modified by the special characters CTRL+H (backspace),
CTRL+U (line-delete), and CTRL+D (end-of-file), which are discussed in 12.3.2 tty
Special Characters, p.274.
The following special characters are enabled if the tty device operates in line mode,
that is, with the OPT_LINE bit set:
■ The backspace character, by default CTRL+H, causes successive previous
characters to be deleted from the current line, up to the start of the line. It does
this by echoing a backspace followed by a space, and then another backspace.
■ The line-delete character, by default CTRL+U, deletes all the characters of the
current line.
■ The end-of-file (EOF) character, by default CTRL+D, causes the current line to
become available in the input ring without a NEWLINE and without entering
the EOF character itself. Thus if the EOF character is the first character typed
on a line, reading that line returns a zero byte count, which is the usual
indication of end-of-file.
The following characters have special effects if the tty device is operating with the
corresponding option bit set:
■ The software flow control characters CTRL+Q and CTRL+S (XON and XOFF).
Receipt of a CTRL+S input character suspends output to that channel.
Subsequent receipt of a CTRL+Q resumes the output. Conversely, when the
VxWorks input buffer is almost full, a CTRL+S is output to signal the other side
to suspend transmission. When the input buffer is empty enough, a CTRL+Q
is output to signal the other side to resume transmission. The software flow
control characters are enabled by OPT_TANDEM.
■ The ROM monitor trap character, by default CTRL+X. This character traps to the
ROM-resident monitor program. Note that this is drastic. All normal VxWorks
functioning is suspended, and the computer system is controlled entirely by
the monitor. Depending on the particular monitor, it may or may not be
possible to restart VxWorks from the point of interruption.1 The monitor trap
character is enabled by OPT_MON_TRAP.
■
The special kernel shell abort character, by default CTRL+C. This character
restarts the kernel shell if it gets stuck in an unfriendly routine, such as one that
has taken an unavailable semaphore or is caught in an infinite loop. The kernel
shell abort character is enabled by OPT_ABORT.
The characters for most of these functions can be changed using the tyLib routines
shown in Table 12-3.
1. It will not be possible to restart VxWorks if un-handled external interrupts occur during the
boot countdown.
274
12 Devices
12.4 Pipe Devices
The memory device responds to the ioctl( ) functions summarized in Table 12-4.
The functions listed are defined in the header file ioLib.h.
275
VxWorks
Application Programmer's Guide, 6.9
Function Description
For more information, see the reference entries for memDrv, ioLib, and ioctl( ).
close (fd);
}
276
12 Devices
12.7 Network File System (NFS) Devices
NFS client devices respond to the ioctl( ) functions summarized in Table 12-5. The
functions listed are defined in ioLib.h. For more information, see the reference
entries for nfsDrv, ioLib, and ioctl( ).
Table 12-5 Supported I/O Control Functions for Files Accessed through NFS
IOCTL Description
FIOGETNAME Gets the file name of fd and copies it to the buffer referenced
by nameBuf:
status = ioctl (fd, FIOGETNAME, &nameBuf);
FIOSEEK Sets the current byte offset in the file to the position specified
by newOffset. If the seek goes beyond the end-of-file, the file
grows. The end-of-file pointer gets moved to the new
position, and the new space is filled with zeros:
status = ioctl (fd, FIOSEEK, newOffset);
277
VxWorks
Application Programmer's Guide, 6.9
Table 12-5 Supported I/O Control Functions for Files Accessed through NFS (cont’d)
IOCTL Description
FIOWHERE Returns the current byte position in the file. This is the byte
offset of the next byte to be read or written. It takes no
additional argument:
position = ioctl (fd, FIOWHERE, 0);
FIOREADDIR Reads the next directory entry. Use the third argument in the
ioctl( ) call to supply a pointer to a directory descriptor of
type DIR.
DIR dirStruct;
fd = open ("directory", O_RDONLY);
status = ioctl (fd, FIOREADDIR, &dirStruct);
278
12 Devices
12.9 Sockets
details of the underlying protocol used to effect the transfer of information. (For
more information, see the Wind River Network Stack Programmer’s Guide.)
When a remote file is opened using RSH or FTP, the entire file is copied into local
memory. As a result, the largest file that can be opened is restricted by the available
memory. Read and write operations are performed on the memory-resident copy
of the file. When closed, the file is copied back to the original remote file if it was
modified.
In general, NFS devices are preferable to RSH and FTP devices for performance
and flexibility, because NFS does not copy the entire file into local memory.
However, NFS is not supported by all host systems.
NOTE: Within processes, there are limitations on RSH and FTP usage: directories
cannot be created and the contents of file systems cannot be listed.
RSH and FTP devices respond to the same ioctl( ) functions as NFS devices except
for FIOSYNC and FIOREADDIR. The functions are defined in the header file
ioLib.h. For more information, see the API reference entries for netDrv and ioctl( ).
12.9 Sockets
In VxWorks, the underlying basis of network communications is sockets. A socket
is an endpoint for communication between tasks; data is sent from one socket to
another. Sockets are not created or opened using the standard I/O functions.
Instead, they are created by calling socket( ), and connected and accessed using
other routines in sockLib. However, after a stream socket (using TCP) is created
and connected, it can be accessed as a standard I/O device, using read( ), write( ),
ioctl( ), and close( ). The value returned by socket( ) as the socket handle is in fact
an I/O system file descriptor.
VxWorks socket routines are source-compatible with the BSD 4.4 UNIX socket
functions and the Windows Sockets (Winsock 1.1) networking standard. Use of
these routines is discussed in Wind River Network Stack Programmer’s Guide.
279
VxWorks
Application Programmer's Guide, 6.9
280
13
Local File Systems
13.1 Introduction
VxWorks provides a variety of file systems that are suitable for different types of
applications. The file systems can be used simultaneously, and in most cases in
multiple instances, for a single VxWorks system.
Most VxWorks file systems rely on the extended block device (XBD) facility for a a
standard I/O interface between the file system and device drivers. This standard
interface allows you to write your own file system for VxWorks, and freely mix file
systems and device drivers.
File systems used for removable devices make use of the file system monitor for
automatic detection of device insertion and instantiation of the appropriate file
system on the device.
The relationship between applications, file systems, I/O facilities, device drivers
and hardware devices is illustrated in Figure 13-1. Note that this illustration is
relevant for the HRFS, dosFs, rawFs, and cdromFs file systems. The dotted line
281
VxWorks
Application Programmer's Guide, 6.9
indicates the elements that must be configured and instantiated to create a specific,
functional run-time file system.
For information about the XBD facility, see the VxWorks Kernel Programmer’s Guide:
I/O System.
This chapter discusses the following VxWorks file systems and how they are used:
■
VRFS
A virtual root file system for use with applications that require a POSIX root
file system. The VRFS is simply a root directory from which other file systems
and devices can be accessed. See 13.3 Virtual Root File System: VRFS, p.284.
■
HRFS
282
13 Local File Systems
13.2 File System Monitor
the XBD block wrapper component. Can also be used with the
transaction-based reliable file system (TRFS) facility. See
13.5 MS-DOS-Compatible File System: dosFs, p.293 and 13.6 Transaction-Based
Reliable File System Support for dosFs: TRFS, p.305.
■
rawFS
Provides a simple raw file system that treats an entire disk as a single large file.
See 13.7 Raw File System: rawFs, p.306.
■
cdromFs
Designed for bundling applications and other files with a VxWorks system
image. No storage media is required beyond that used for the VxWorks boot
image. See 13.9 Read-Only Memory File System: ROMFS, p.311.
■ TSFS
Uses the host target server to provide the target with access to files on the host
system. See 13.10 Target Server File System: TSFS, p.313.
For information about the file system monitor, see the VxWorks Kernel Programmer’s
Guide: Local File Systems. For information about the XBD facility, see the VxWorks
Kernel Programmer’s Guide: I/O System.
VxWorks can be configured with file-system support for flash memory devices
using TrueFFS and the HRFS or dosFs file system. For more information, see
13.5 MS-DOS-Compatible File System: dosFs, p.293 and the VxWorks Kernel
Programmer’s Guide: Flash File System Support with TrueFFS.
NOTE: This chapter provides information about facilities available for real-time
processes. For information about creating file systems, and file system facilities
available in the kernel, see the VxWorks Kernel Programmer’s Guide: Local File
Systems.
283
VxWorks
Application Programmer's Guide, 6.9
-> cd "/"
value = 0 = 0x0
-> ll
?--------- 0 0 0 0 Jan 1 00:00 null
drwxrwxr-x 0 15179 100 20 Jan 23 2098 romfs/
?--------- 0 0 0 0 Jan 1 00:00 vio
drwxrwxrwx 1 0 0 0 Jan 1 00:00 shm/
drwxrwxrwx 1 0 0 2048 Jan 1 00:00 ram0/
value = 0 = 0x0
NOTE: Configuring VxWorks with support for POSIX PSE52 conformance (using
BUNDLE_RTP_POSIX_PSE52) provides the /dev/null device. Note that the devs
shell command lists /dev/null with other devices, but the ls command does not list
/dev/null under the VRFS root directory (because the name violates the VRFS
naming scheme). Applications can, in any case, use /dev/null as required. For
information about null devices, see 12.6 Null Devices, p.276. For information about
POSIX PSE52, see 9.2.1 POSIX PSE52 Support, p.173.
284
13 Local File Systems
13.4 Highly Reliable File System: HRFS
! CAUTION: VRFS alters the behavior of other file systems because it provides a root
directory on VxWorks. Changing directory to an absolute path on a host file system
will not work when VRFS is installed without preceding the absolute path with the
VxWorks remote host sytem device name. For example, if the current working
directory is on the host system mars, changing directory to /home/panloki will not
work— it must be named mars:/home/panloki.
For information about using HRFS with flash memory, see the VxWorks Kernel
Programmer’s Guide: Flash File System Support with TrueFFS.
To include HRFS support in VxWorks, configure the kernel with the appropriate
required and optional components.
Required Components
285
VxWorks
Application Programmer's Guide, 6.9
HRFS_DEFAULT_MAX_FILES
Defines how many files can be open simultaneously on an HRFS volume. The
minimum is 1. The default setting is 10. Note that this is not the same as the
maximum number of file descriptors.
The INCLUDE_HRFS_DEFAULT_WRITE_MODE component is included
automatically, providing the default write-mode, which performs a commit with
each write. (For the alternative, see INCLUDE_HRFS_HISPEED_WRITE_MODE,
p.286.)
In addition to either INCLUDE_HRFS or INCLUDE_HRFS_READONLY, HRFS
requires the appropriate component for your block device.
And if you are using a device driver that is not designed for use with the XBD
facility, you must use the INCLUDE_XBD_BLK_DEV wrapper component in
addition to INCLUDE_XBD.
For more information about XBD, see the VxWorks Kernel Programmer’s Guide: I/O
System.
286
13 Local File Systems
13.4 Highly Reliable File System: HRFS
HRFS_DEFAULT_MAX_BUFFERS
Defines how many buffers HRFS uses for its caching mechanism. HRFS needs
a minimum of 6 buffers. The default setting is 16. This parameter applies to all
HRFS volumes. Note that while increasing the number of buffers can increase
performance, it does so at the expense of heap memory. For information about
using this parameter in configuration for performance, see 13.4.5 Optimizing
HRFS Performance, p.288.
HRFS_DEFAULT_MAX_FILES
Defines how many files can be simultaneously open on an HRFS volume. The
minimum is 1. The default setting is 10. Note that is not the same as the
maximum number of file descriptors.
■
The appropriate device driver component (for example,
INCLUDE_XBD_RAMDRV) and INCLUDE_XBD_BLK_DEV if the device driver
requires it. See the VxWorks Kernel Programmer’s Guide: I/O System.
■
The INCLUDE_VRFS component for the VRFS file system, which is included in
the BUNDLE_RTP_POSIX_PSE52 component bundle
In addition, a /tmp directory must be created at run-time. It must appear on the
VRFS file system, and must be formatted as an HRFS file system. The following
examples illustrate creation of a /tmp RAM disk device and formatting the
directory as an HRFS file system:
xbdRamDiskDevCreate (512, 0x10000, 0, "/tmp")
hrfsDiskFormat "/tmp", 1000.
For more information, see 13.3 Virtual Root File System: VRFS, p.284 and
9.2.1 POSIX PSE52 Support, p.173.
For information about creating an HRFS file system, see VxWorks Kernel
Programmer’s Guide: Local File Systems.
287
VxWorks
Application Programmer's Guide, 6.9
HRFS is a transactional file system. Transaction or commit points are set to make
disk changes permanent. Commit policies define specific conditions under which
commit points are set.
HRFS can be configured to automatically commit with each write (the default
commit policy), or to perform commits based on cache usage and elapsed time (the
high-speed commit policy). Some disk operations trigger commits regardless of
the configuration, and under certain circumstances, HRFS rollbacks undo disk
changes made since the last commit, in order to protect the integrity of the file
system.
In addition, applications can perform commits independently of the configured
commit policy.
By default, HRFS automatically performs a commit with each write operation. This
functionality is provided by the INCLUDE_HRFS_DEFAULT_WRITE_MODE
component.
Any operation that changes data on the disk results in a transaction point being set.
This is the safest policy in terms of the potential for data loss. It is also the slowest
in terms of performance, as every write to disk cause a commit. The following
routines, for example, cause modifications to disk and result in a commit when the
automatic commit policy is used:
■ write( )
■ remove( )
■ delete( )
■
mkdir( )
■
rmdir( )
■
link( )
■
unlink( )
■
truncate( )
■
ftruncate( )
■
ioctl( ) when used with a control function that requires modifying the disk.
288
13 Local File Systems
13.4 Highly Reliable File System: HRFS
The high-speed commit policy performs a commit for the current transaction when
either of the following are true:
■ When cache usage is over 40%.
■
When five seconds have elapsed, if the current transaction is still active.
This functionality is provided by the INCLUDE_HRFS_HISPEED_WRITE_MODE
component. For information about using this component in configuration for
performance, see the VxWorks Kernel Programmer’s Guide: Local File Systems.
Mandatory Commits
Rollbacks
A rollback undoes any disk changes since the last commit. Rollbacks usually occur
when the system is unexpectedly powered down or reset. Rollbacks can also occur
when the file system encounters errors (for example, the lack of disk space to
complete a write( ), or an error is reported by the underlying device driver).
Rollbacks of this nature only happen on operations that modify the media. Errors
on read operations do not force a rollback.
A rollback involves HRFS returning to the state of the disk at the last transaction
point, which thereby preserves the integrity of the file system, but at the expense
of losing file data that has changed since the last transaction point. If the manual
or periodic commit policy is specified, there is the potential for losing a lot of
data—although the integrity of the file system is preserved.
289
VxWorks
Application Programmer's Guide, 6.9
HRFS files and directories are stored on disk in data structures called inodes.
During formatting the maximum number of inodes is specified as a parameter to
hrfsFormat( ). The total number of files and directories can never exceed the
number inodes. Attempting to create a file or directory when all inodes are in use
generates an error. Deleting a file or directory frees the corresponding inode.
This section discusses creating and removing directories, and reading directory
entries.
Creating Subdirectories
You can create as many subdirectories as there are inodes. Subdirectories can be
created in the following ways:
■ With open( ). To create a directory, the O_CREAT option must be set in the flags
parameter and the S_IFDIR or FSTAT_DIR option must be set in the mode
parameter. The open( ) calls returns a file descriptor that describes the new
directory. The file descriptor can only be used for reading only and should be
closed when it no longer needed.
■
With mkdir( ) from usrFsLib.
When creating a directory using either of the above methods, the new directory
name must be specified. This name can be either a full pathname or a pathname
relative to the current working directory.
Removing Subdirectories
A directory that is to be deleted must be empty (except for the “.” and “..” entries).
The root directory can never be deleted. Subdirectories can be removed in the
following ways:
290
13 Local File Systems
13.4 Highly Reliable File System: HRFS
■
Using ioctl( ) with the FIORMDIR function and specifying the name of the
directory. The file descriptor used can refer to any file or directory on the
volume, or to the entire volume itself.
■ Using the remove( ), specifying the name of the directory.
■
Use rmdir( ) from usrFsLib.
Files on an HRFS file system device are created, deleted, written, and read using
the standard VxWorks I/O routines: creat( ), remove( ), write( ), and read( ). For
more information, see 11.6 Basic I/O, p.256, and the ioLib API references.
Note that and remove( ) is synonymous with unlink( ) for HRFS.
When a link is created an inode is not used. Another directory entry is created at
the location specified by the parameter to link( ). In addition, a reference count to
the linked file is stored in the file's corresponding inode. When unlinking a file, this
reference count is decremented. If the reference count is zero when unlink( ) is
called, the file is deleted except if there are open file descriptors open on the file. In
this case the directory entry is removed but the file still exists on the disk. This
prevents tasks and processes (RTPs) from opening the file. When the final open file
descriptor is closed the file is fully deleted freeing its inode.
Note that you cannot create a link to a subdirectory only to a regular file.
File Permissions
HRFS files have POSIX-style permission bits (unlike dosFs files, which have
attributes). The bits can be changed using the chmod( ) and fchmod( ) routines. See
the API references for more information.
291
VxWorks
Application Programmer's Guide, 6.9
The HRFS file system supports the ioctl( ) functions. These functions are defined
in the header file ioLib.h along with their associated constants; and they are listed
in Table 13-1.
Decimal
Function Value Description
292
13 Local File Systems
13.5 MS-DOS-Compatible File System: dosFs
For more information, see the API reference for ioctl( ) in ioLib.
For detailed information about crash recovery and volume consistence, see
VxWorks Kernel Programmer’s Guide: Local File Systems.
When an HRFS device is full, attempts to delete or write to files with standard
commands will fail (it is, however, unlikely that a device will become full). This
behavior is not a defect, but is inherent in highly reliable file systems that use
copy-on-write (COW) to implement their transaction mechanism.
During a write operation, the file system writes modified data to a temporary
block during a transaction. For example, in order to write block A, block B is first
allocated, and the data is written to B. When the transaction is complete, block B is
re-mapped to replace block A, and block A is freed. When HRFS removes a file, it
first renames the file. It then allocates a new block, and copies the modified
directory entry block to it.
If the device is full, the allocation operation fails. It is, however, unlikely that a
device will become full, because when a write operation fails due to a lack of free
blocks, HRFS rolls back the failed transaction, and the blocks allocated for the
transaction are freed. It is then possible to delete files. If a transaction does happen
to write the device full, there is no way to free blocks for further operations. Wind
River therefore recommends that you do not allow an HRFS device to become full.
Leaving a margin of five percent of device space free is a useful guideline.
293
VxWorks
Application Programmer's Guide, 6.9
! CAUTION: While dosFs supports path names of 1024 characters, using a path name
of more than 259 characters makes it incompatible with Windows (that is, the file
cannot be used on a Windows host).
For information about dosFs libraries, see the VxWorks API references for
dosFsLib and dosFsFmtLib.
For information about the MS-DOS file system, please see the Microsoft
documentation.
For information about using dosFs with flash memory, see the VxWorks Kernel
Programmer’s Guide: Flash File System Support with TrueFFS.
The dosFs file system can be used with the transaction-based reliable file system
(TRFS) facility; see 13.6 Transaction-Based Reliable File System Support for dosFs:
TRFS, p.305.
To include dosFs support in VxWorks, configure the kernel with the appropriate
required and optional components.
294
13 Local File Systems
13.5 MS-DOS-Compatible File System: dosFs
Required Components
In addition, you must include the appropriate component for your block device.
If you are using a device driver that is not designed for use with the XBD facility,
you must use the INCLUDE_XBD_BLK_DEV wrapper component in addition to
INCLUDE_XBD. See the VxWorks Kernel Programmer’s Guide: I/O System for more
information.
Note that you can use INCLUDE_DOSFS to automatically include the following
components:
■
INCLUDE_DOSFS_MAIN
■ INCLUDE_DOSFS_DIR_VFAT
■
INCLUDE_DOSFS_DIR_FIXED
■
INCLUDE_DOSFS_FAT
■ INCLUDE_DOSFS_CHKDSK
■
INCLUDE_DOSFS_FMT
295
VxWorks
Application Programmer's Guide, 6.9
Several dosFs component configuration parameters can be used to define how the
file system behaves when a dosFs volume is mounted. These parameters are as
follows:
DOSFS_CHK_ONLY
When a dosfs volume is mounted, the media is analyzed for errors, but no
repairs are made.
DOSFS_CHK_REPAIR
Similar to DOSFS_CHK_ONLY, but an attempt to repair the media is made if
errors are found.
DOSFS_CHK_NONE
Media is not checked for errors on mount.
DOSFS_CHK_FORCE
Used in conjunction with DOSFS_CHK_ONLY and DOSFS_CHK_REPAIR to
force a consistency check even if the disk has been marked clean.
DOS_CHK_VERB_SILENT or DOS_CHK_VERB_0
dosFs does not to produce any output to the terminal when mounting.
DOS_CHK_VERB_1
dosFs produces a minimal amount of output to the terminal when mounting.
DOS_CHK_VERB_2
dosFs to produces maximum amount output to the terminal when mounting.
Other parameters can be used to configure physical attributes of the file system.
They are as follows:
DOSFS_DEFAULT_CREATE_OPTIONS
The default parameter for the dosFsLib component. It specifies the action to be
taken when a dosFs file system is instantiated. Its default is
DOSFS_CHK_NONE.
DOSFS_DEFAULT_MAX_FILES
The maximum number of files. The default is 20.
DOSFS_DEFAULT_FAT_CACHE_SIZE
The size of the FAT group cache (for each dosFs volume). The default 16 KB.
DOSFS_DEFAULT_DATA_DIR_CACHE_SIZE
The disk-data and directory-group cache size (for each dosFs volume).
DOSFS_CACHE_BACKGROUND_FLUSH_TASK_ENABLE
The cache-flushing mode. Set to FALSE (default), cache-flushing takes place in
the context of the user’s read/write task. Set to TRUE, a separate high-priority
task is spawned to carry out cache flushing. Use of a separate task speeds up
write operations (especially when dosFs is configured with a larger cache size),
but using a separate high-priority task might not be suitable for systems with
strict real-time requirements.
For information about the use of some of these component parameters in
optimizing dosFs performance, see 13.5.4 Optimizing dosFs Performance, p.297.
Caches can be tuned dynamically for individual instances of the file system using
the dosFsCacheInfo( ) and dosFsCacheTune( ) routines.
296
13 Local File Systems
13.5 MS-DOS-Compatible File System: dosFs
For information about creating a dosFs file system, see VxWorks Kernel
Programmer’s Guide: Local File Systems.
Synchronizing Volumes
When a disk is synchronized, all modified buffered data is physically written to the
disk, so that the disk is up to date. This includes data written to files, updated
directory information, and the FAT. To avoid loss of data, a disk should be
synchronized before it is removed. For more information, see the API references
for close( ) and dosFsVolUnmount( ).
This section discusses creating and removing directories, and reading directory
entries.
NOTE: Filenames and directory names on dosFs devices are often separated by
backslashes (\). These can be used interchangeably with forward slashes (/).
297
VxWorks
Application Programmer's Guide, 6.9
Creating Subdirectories
For FAT32, subdirectories can be created in any directory at any time. For FAT12
and FAT16, subdirectories can be created in any directory at any time, except in the
root directory once it reaches its maximum entry count. Subdirectories can be
created in the following ways:
■
Using ioctl( ) with the FIOMKDIR function. The name of the directory to be
created is passed as a parameter to ioctl( ).
■
Using open( ). To create a directory, the O_CREAT option must be set in the
flags parameter and the FSTAT_DIR option must be set in the mode parameter.
The open( ) call returns a file descriptor that describes the new directory. Use
this file descriptor for reading only, and close it when it is no longer needed.
■ Use mkdir( ) from usrFsLib.
When creating a directory using any of the above methods, the new directory name
must be specified. This name can be either a full pathname or a pathname relative
to the current working directory.
Removing Subdirectories
A directory that is to be deleted must be empty (except for the “.” and “..” entries).
The root directory can never be deleted. Subdirectories can be removed in the
following ways:
■ Using ioctl( ) with the FIORMDIR function, specifying the name of the
directory. The file descriptor used can refer to any file or directory on the
volume, or to the entire volume itself.
■ Using the remove( ) function, specifying the name of the directory.
■ Use rmdir( ) from usrFsLib.
Files on a dosFs file system device are created, deleted, written, and read using the
standard VxWorks I/O routines: creat( ), remove( ), write( ), and read( ). For more
information, see 11.6 Basic I/O, p.256, and the ioLib API references.
298
13 Local File Systems
13.5 MS-DOS-Compatible File System: dosFs
File Attributes
The file-attribute byte in a dosFs directory entry consists of a set of flag bits, each
indicating a particular file characteristic. The characteristics described by the
file-attribute byte are shown in Table 13-2.
DOS_ATTR_RDONLY
If this flag is set, files accessed with open( ) cannot be written to. If the
O_WRONLY or O_RDWR flags are set, open( ) returns ERROR, setting errno to
S_dosFsLib_READ_ONLY.
DOS_ATTR_HIDDEN
This flag is ignored by dosFsLib and produces no special handling. For
example, entries with this flag are reported when searching directories.
DOS_ATTR_SYSTEM
This flag is ignored by dosFsLib and produces no special handling. For
example, entries with this flag are reported when searching directories.
DOS_ATTR_VOL_LABEL
This is a volume label flag, which indicates that a directory entry contains the
dosFs volume label for the disk. A label is not required. If used, there can be
only one volume label entry per volume, in the root directory. The volume
label entry is not reported when reading the contents of a directory (using
readdir( )). It can only be determined using the ioctl( ) function FIOLABELGET.
The volume label can be set (or reset) to any string of 11 or fewer characters,
using the ioctl( ) function FIOLABELSET. Any file descriptor open to the
volume can be used during these ioctl( ) calls.
DOS_ATTR_DIRECTORY
This is a directory flag, which indicates that this entry is a subdirectory, and not
a regular file.
DOS_ATTR_ARCHIVE
This is an archive flag, which is set when a file is created or modified. This flag
is intended for use by other programs that search a volume for modified files
and selectively archive them. Such a program must clear the archive flag, since
VxWorks does not.
All the flags in the attribute byte, except the directory and volume label flags, can
be set or cleared using the ioctl( ) function FIOATTRIBSET. This function is called
after the opening of the specific file with the attributes to be changed. The
attribute-byte value specified in the FIOATTRIBSET call is copied directly; to
299
VxWorks
Application Programmer's Guide, 6.9
preserve existing flag settings, determine the current attributes using stat( ) or
fstat( ), then change them using bitwise AND and OR operators.
This example makes a dosFs file read-only, and leaves other attributes intact.
STATUS changeAttributes
(
void
)
{
int fd;
struct stat statStruct;
/* open file */
/* close file */
close (fd);
return (OK);
}
NOTE: You can also use the attrib( ) routine to change file attributes. For more
information, see the entry in usrFsLib.
The dosFs file system allocates disk space using one of the following methods. The
first two methods are selected based upon the size of the write operation. The last
method must be manually specified.
■
single cluster allocation
Single cluster allocation uses a single cluster, which is the minimum allocation
unit. This method is automatically used when the write operation is smaller
than the size of a single cluster.
■
cluster group allocation (nearly contiguous)
300
13 Local File Systems
13.5 MS-DOS-Compatible File System: dosFs
created and when reading from a file assumed to have been allocated to a
contiguous space. Using this method risks disk fragmentation.
For any allocation method, you can deallocate unused reserved bytes by using the
POSIX-compliant routine ftruncate( ) or the ioctl( ) function FIOTRUNC.
The dosFs file system defines the size of a cluster group based on the media’s
physical characteristics. That size is fixed for each particular media. Since seek
operations are an overhead that reduces performance, it is desirable to arrange files
so that sequential portions of a file are located in physically contiguous disk
clusters. Cluster group allocation occurs when the cluster group size is considered
sufficiently large so that the seek time is negligible compared to the read/write
time. This technique is sometimes referred to as nearly contiguous file access
because seek time between consecutive cluster groups is significantly reduced.
Because all large files on a volume are expected to have been written as a group of
extents, removing them frees a number of extents to be used for new files
subsequently created. Therefore, as long as free space is available for subsequent
file storage, there are always extents available for use. Thus, cluster group
allocation effectively prevents fragmentation (where a file is allocated in small units
spread across distant locations on the disk). Access to fragmented files can be
extremely slow, depending upon the degree of fragmentation.
301
VxWorks
Application Programmer's Guide, 6.9
For more information on the ioctl( ) functions, see 13.5.10 I/O Control Functions
Supported by dosFsLib, p.303.
Subdirectories can also be allocated a contiguous disk area in the same manner:
■ If the directory is created using the ioctl( ) function FIOMKDIR, it must be
subsequently opened to obtain a file descriptor to it.
■ If the directory is created using options to open( ), the returned file descriptor
from that call can be used.
A directory must be empty (except for the “.” and “..” entries) when it has
contiguous space allocated to it.
Fragmented files require following cluster chains in the FAT. However, if a file is
recognized as contiguous, the system can use an enhanced method that improves
performance. This applies to all contiguous files, whether or not they were
explicitly created using FIOCONTIG. Whenever a file is opened, it is checked for
contiguity. If it is found to be contiguous, the file system registers the necessary
information about that file to avoid the need for subsequent access to the FAT table.
This enhances performance when working with the file by eliminating seek
operations.
When you are opening a contiguous file, you can explicitly indicate that the file is
contiguous by specifying the DOS_O_CONTIG_CHK flag with open( ). This
prompts the file system to retrieve the section of contiguous space, allocated for
this file, from the FAT table.
To find the maximum contiguous area on a device, you can use the ioctl( ) function
FIONCONTIG. This information can also be displayed by dosFsConfigShow( ).
In this example, the size (in bytes) of the largest contiguous area is copied to the
integer pointed to by the third parameter to ioctl( ) (count).
302
13 Local File Systems
13.5 MS-DOS-Compatible File System: dosFs
STATUS contigTest
(
void /* no argument */
)
{
int count; /* size of maximum contiguous area in bytes */
int fd; /* file descriptor */
close (fd);
printf ("largest contiguous area = %d\n", count);
return (OK);
}
For information about crash recovery and volume consistence, see VxWorks Kernel
Programmer’s Guide: Local File Systems.
The dosFs file system supports the ioctl( ) functions. These functions are defined
in the header file ioLib.h along with their associated constants, and they are
described in Table 13-3.
Decimal
Function Value Description
303
VxWorks
Application Programmer's Guide, 6.9
Decimal
Function Value Description
For more information, see the API references for dosFsLib and for ioctl( ) in ioLib.
NOTE: SCSI is not supported for 64-bit VxWorks with this release.
For information about booting from a local dosFs file system using SCSI, see
VxWorks Kernel Programmer’s Guide: Local File Systems.
304
13 Local File Systems
13.6 Transaction-Based Reliable File System Support for dosFs: TRFS
NOTE: While TRFS is a I/O layer added to dosFs, it uses a modified on-media
format that is not compatible with other FAT-based file systems, including
Microsoft Windows and the VxWorks dosFs file system without the TRFS layer. It
should not, therefore, be used when compatibility with other systems is a
requirement
For information about dosFs, see 13.5 MS-DOS-Compatible File System: dosFs, p.293.
TRFS is automatically detected and instantiated if the media has already been
formatted for use with TRFS, in a manner very similar to the instantiation of the
dosFs or HRFS file system. The primary difference is that when TRFS is detected
by the file system monitor, it calls the TRFS creation function, and the creation
function then creates another XBD instance and generates an insertion event for it.
The monitor then detects the new XBD and begins probing. In this case, however,
the monitor does not examine the media directly—all commands are routed
305
VxWorks
Application Programmer's Guide, 6.9
Once TRFS and dosFs are created, the dosFs file system may be used with the
ordinary file creation and manipulation commands. No changes to the file system
become permanent, however, until TRFS is used to commit them.
It is important to note that the entire dosFs file system—and not individual files—
are committed. The entire disk state must therefore be consistent before executing
a commit; that is, there must not be a file system operation in progress (by another
task, for example) when the file system is committed. If multiple tasks update the
file system, care must be taken to ensure the file data is in a known state before
setting a transaction point.
To commit a file system from a process, call:
ioctl(fd, CBIO_TRANS_COMMIT, 0);
close (fd);
}
306
13 Local File Systems
13.7 Raw File System: rawFs
Although the dosFs file system provides this ability to varying degrees, the rawFs
file system offers advantages in size and performance if more complex functions
are not required.
The rawFs file system imposes no organization of the data on the disk. It maintains
no directory information; and there is therefore no division of the disk area into
specific files. All open( ) operations on rawFs devices specify only the device
name; no additional filenames are possible.
The entire disk area is treated as a single file and is available to any file descriptor
that is open for the device. All read and write operations to the disk use a
byte-offset relative to the start of the first block on the disk.
A rawFs file system is created by default if inserted media does not contain a
recognizable file system.
To use the rawFs file system, configure VxWorks with the INCLUDE_RAWFS and
INCLUDE_XBD components.
If you are using a device driver that is not designed for use with the XBD facility,
you must use the INCLUDE_XBD_BLK_DEV wrapper component in addition to
INCLUDE_XBD. See the VxWorks Kernel Programmer’s Guide: I/O System for more
information.
Set the NUM_RAWFS_FILES parameter of the INCLUDE_RAWFS component to the
desired maximum open file descriptor count. For information about using
multiple file descriptors with what is essentially a single large file, see 13.7.4 rawFs
File I/O, p.307.
For information about creating a rawFs file system, see VxWorks Kernel
Programmer’s Guide: Local File Systems.
! CAUTION: Because device names are recognized by the I/O system using simple
substring matching, file systems should not use a slash (/) alone as a name or
unexpected results may occur.
To begin I/O operations upon a rawFs device, first open the device using the
standard open( ) routine (or the creat( ) routine). Data on the rawFs device is
307
VxWorks
Application Programmer's Guide, 6.9
written and read using the standard I/O routines write( ) and read( ). For more
information, see 11.6 Basic I/O, p.256.
The character pointer associated with a file descriptor (that is, the byte offset where
the read and write operations take place) can be set by using ioctl( ) with the
FIOSEEK function.
Multiple file descriptors can be open simultaneously for a single device. These
must be carefully managed to avoid modifying data that is also being used by
another file descriptor. In most cases, such multiple open descriptors use FIOSEEK
to set their character pointers to separate disk areas.
The rawFs file system supports the ioctl( ) functions shown in Table 13-4. The
functions listed are defined in the header file ioLib.h. For more information, see
the API references for rawFsLib and for ioctl( ) in ioLib.
Decimal
Function Value Description
308
13 Local File Systems
13.8 CD-ROM File System: cdromFs
! CAUTION: Changing the directory mode un-mounts the file system. Therefore,
any open file descriptors are marked as obsolete.
CDROMFS_STRIP_SEMICOLON
This option sets the readdir( ) strip semicolon setting to FALSE if arg is 0, and
to TRUE otherwise. If TRUE, readdir( ) removes the semicolon and following
version number from the directory entries retrieved.
1. Therefore, mode 2/form 2 sectors are not supported, as they have 2324 bytes of user data
per sector. Both mode 1/form 1 and mode 2/form 1 sectors are supported, as they have 2048
bytes of user data per sector.
2. The first session (that is, the earliest session) is always read. The most commonly desired
behavior is to read the last session (that is, the latest session).
309
VxWorks
Application Programmer's Guide, 6.9
CDROMFS_GET_VOL_DESC
This option returns, in arg, the primary or supplementary volume descriptor
by which the volume is mounted. arg must be of type T_ISO_PVD_SVD_ID, as
defined in cdromFsLib.h. The result is the volume descriptor, adjusted for the
endianness of the processor (not the raw volume descriptor from the CD). This
result can be used directly by the processor. The result also includes some
information not in the volume descriptor, such as which volume descriptor is
in use.
For information on using cdromFs( ), see the API reference for cdromFsLib.
For information about creating and using a CD block device, see VxWorks Kernel
Programmer’s Guide: Local File Systems.
The cdromFs file system supports the ioctl( ) functions. These functions, and their
associated constants, are defined in the header files ioLib.h and cdromFsLib.h.
Table 13-5 describes the ioctl( ) functions that cdromFsLib supports. For more
information, see the API references for cdromFsLib and for ioctl( ) in ioLib.
310
13 Local File Systems
13.9 Read-Only Memory File System: ROMFS
cdromFsLib has a 4-byte version number. The version number is composed of four
parts, from most significant byte to least significant byte:
■ major number
■ minor number
■ patch level
■ build
The version number is returned by cdromFsVersionNumGet( ) and displayed by
cdromFsVersionNumDisplay( ).
311
VxWorks
Application Programmer's Guide, 6.9
automated startup facility so that they run automatically at boot time. ROMFS
thereby provides the ability to create fully autonomous, multi-process systems.
ROMFS can also be used to store applications that are run interactively for
diagnostic purposes, or for applications that are started by other applications
under specific conditions (errors, and so on).
NOTE: For 64-bit VxWorks, the ROMFS file system can have a capacity of up to 1
GB of memory. The size is limited because the file system is part of the kernel text
segment, and is installed in the top 2 GB of the address space with the operating
systems text, data, and BSS segments, along with the kernel proximity heap. If the
contents of ROMFS is too large, the VxWorks image will not link or the system will
not boot.
From the command line, configuring VxWorks with ROMFS—and ROMFS with its
file contents—involves the following steps:
1. In the VIP project directory that you created on the host system using vxprj,
create a ROMFS directory with the name romfs. For example:
cd c:\myInstallDir\vxworks-6.x\target\proj\wrSbc8260_diab
mkdir romfs
2. Copy the files into the romfs directory. For example, copy an RTP application:
copy c:\allMyVxApps\[Link] romfs
The contents of the romfs directory are automatically built into a ROMFS file
system and combined with the VxWorks image.
The ROMFS directory does not need to be created in the VxWorks project directory.
It can also be created in any location on (or accessible from) the host system, and
the make utility’s ROMFS_DIR macro used to identify where it is in the build
command. For example:
make TOOL=diab ROMFS_DIR="c:\allMyVxApps"
NOTE: The Workbench procedure for implementing ROMFS is different from the
one used with a vxprj project; see the Wind River Workbench documentation in
this regard.
At run-time, the ROMFS file system is accessed as /romfs. The content of the
ROMFS directory can be browsed using the ls and cd shell commands, and
312
13 Local File Systems
13.10 Target Server File System: TSFS
accessed programmatically with standard file system routines, such as open( ) and
read( ).
For example, if the directory
installDir/vxworks-6.x/target/proj/wrSbc8260_diab/romfs has been created on the
host, the file foo copied to it, and the system rebuilt and booted; then using cd and
ls from the shell (with the command interpreter) looks like this:
[vxWorks *]# cd /romfs
[vxWorks *]# ls
.
..
foo
[vxWorks *]#
ROMFS can be used with various startup mechanisms to start real-time process
(RTP) applications automatically when VxWorks boots.
See 3.9.4 Using ROMFS to Start Applications Automatically, p.63 for more
information.
! CAUTION: The TSFS is not designed for use with large files (whether application
executables or other files). The TSFS driver does not support files larger than 40
GB, and performance may suffer with files larger than 50 KB. For large files, use
FTP or NFS instead of TSFS.
TSFS provides all of the I/O features of the network driver for remote file access
(see 12.8 Non-NFS Network Devices, p.278), without requiring any target
resources—except those required for communication between the target system
and the target server on the host. The TSFS uses a WDB target agent driver to
transfer requests from the VxWorks I/O system to the target server. The target
server reads the request and executes it using the host file system. When you open
a file with TSFS, the file being opened is actually on the host. Subsequent read( )
and write( ) calls on the file descriptor obtained from the open( ) call read from and
write to the opened host file.
The TSFS VIO driver is oriented toward file I/O rather than toward console
operations. TSFS provides all the I/O features that netDrv provides, without
requiring any target resource beyond what is already configured to support
communication between target and target server. It is possible to access host files
randomly without copying the entire file to the target, to load an object module
313
VxWorks
Application Programmer's Guide, 6.9
from a virtual file source, and to supply the filename to routines such as ld( ) and
copy( ).
Each I/O request, including open( ), is synchronous; the calling target task is
blocked until the operation is complete. This provides flow control not available in
the console VIO implementation. In addition, there is no need for WTX protocol
requests to be issued to associate the VIO channel with a particular host file; the
information is contained in the name of the file.
Consider a read( ) call. The driver transmits the ID of the file (previously
established by an open( ) call), the address of the buffer to receive the file data, and
the desired length of the read to the target server. The target server responds by
issuing the equivalent read( ) call on the host and transfers the data read to the
target program. The return value of read( ) and any errno that might arise are also
relayed to the target, so that the file appears to be local in every way.
For detailed information, see the API reference for wdbTsfsDrv.
Socket Support
TSFS sockets are operated on in a similar way to other TSFS files, using open( ),
close( ), read( ), write( ), and ioctl( ). To open a TSFS socket, use one of the
following forms of filename:
"TCP:hostIP:port"
"TCP:hostname:port"
The flags and permissions arguments are ignored. The following examples show
how to use these filenames:
fd = open("/tgtsvr/TCP:phobos:6164",0,0); /* open socket and connect */
/* to server phobos */
The result of this open( ) call is to open a TCP socket on the host and connect it to
the target server socket at hostname or hostIP awaiting connections on port. The
resultant socket is non-blocking. Use read( ) and write( ) to read and write to the
TSFS socket. Because the socket is non-blocking, the read( ) call returns
immediately with an error and the appropriate errno if there is no data available
to read from the socket. The ioctl( ) usage specific to TSFS sockets is discussed in
the API reference for wdbTsfsDrv. This socket configuration allows VxWorks to
use the socket facility without requiring sockLib and the networking modules on
the target.
Error Handling
Errors can arise at various points within TSFS and are reported back to the original
caller on the target, along with an appropriate error code. The error code returned
is the VxWorks errno which most closely matches the error experienced on the
host. If a WDB error is encountered, a WDB error message is returned rather than
a VxWorks errno.
314
13 Local File Systems
13.10 Target Server File System: TSFS
Security Considerations
While TSFS has much in common with netDrv, the security considerations are
different (also see 12.8 Non-NFS Network Devices, p.278). With TSFS, the host file
operations are done on behalf of the user that launched the target server. The user
name given to the target as a boot parameter has no effect. In fact, none of the boot
parameters have any effect on the access privileges of TSFS.
In this environment, it is less clear to the user what the privilege restrictions to
TSFS actually are, since the user ID and host machine that start the target server
may vary from invocation to invocation. By default, any host tool that connects to
a target server which is supporting TSFS has access to any file with the same
authorizations as the user that started that target server. However, the target server
can be locked (with the -L option) to restrict access to the TSFS.
The options which have been added to the target server startup routine to control
target access to host files using TSFS include:
-R Set the root of TSFS.
For example, specifying -R /tftpboot prepends this string to all TSFS filenames
received by the target server, so that /tgtsvr/etc/passwd maps to
/tftpboot/etc/passwd. If -R is not specified, TSFS is not activated and no TSFS
requests from the target will succeed. Restarting the target server without
specifying -R disables TSFS.
-RW Make TSFS read-write.
The target server interprets this option to mean that modifying operations
(including file create and delete or write) are authorized. If -RW is not
specified, the default is read only and no file modifications are allowed.
NOTE: For more information about the target server and the TSFS, see the tgtsvr
command reference. For information about specifying target server options from
Workbench, see the Wind River Workbench User’s Guide: Setting Up Your Hardware
and the Wind River Workbench User’s Guide: New Target Server Connections.
For information about using the TSFS to boot a targets, see VxWorks Kernel
Programmer’s Guide: Kernel.
315
VxWorks
Application Programmer's Guide, 6.9
316
14
Error Detection and Reporting
14.1 Introduction
VxWorks provides an error detection and reporting facility to help debugging
software faults. It does so by recording software exceptions in a specially
designated area of memory that is not cleared between warm reboots. The facility
also allows for selecting system responses to fatal errors, with alternate strategies
for development and deployed systems.
The key features of the error detection and reporting facility are:
■
A persistent memory region in RAM used to retain error records across warm
reboots.
■
Mechanisms for recording various types of error records.
■
Error records that provide detailed information about run-time errors and the
conditions under which they occur.
■
The ability to display error records and clear the error log from the shell.
■
Alternative error-handing options for the system’s response to fatal errors.
■
Macros for implementing error reporting in user code.
317
VxWorks
Application Programmer's Guide, 6.9
For more information about error detection and reporting routines in addition to
that provided in this chapter, see the API reference for edrLib. Also see the
VxWorks Kernel Programmer’s Guide: Error Detection and Reporting for information
about facilities available only in the kernel.
For information about related facilities, see 10.8 Memory Error Detection, p.240.
NOTE: This chapter provides information about facilities available for real-time
processes. For information about facilities available in the VxWorks kernel, see the
corresponding chapter in the VxWorks Kernel Programmer’s Guide.
To use the error detection and reporting facility, the kernel must be configured with
the following components:
■ INCLUDE_EDR_PM
■
INCLUDE_EDR_ERRLOG
■
INCLUDE_EDR_SHOW
■ INCLUDE_SYSDBG_FLAG
NOTE: The persistent memory region is not supported for all architectures (see the
VxWorks Architecture Supplement), and it is not write-protected for the symmetric
multiprocessing (SMP) configuration of VxWorks. For general information about
VxWorks SMP and for information about migration, see the VxWorks Kernel
Programmer’s Guide: VxWorks SMP.
A cold reboot always clears the persistent memory region. The pmInvalidate( )
routine can also be used to explicitly destroy the region (making it unusable) so
that it is recreated during the next warm reboot.
318
14 Error Detection and Reporting
14.3 Error Records
! WARNING: If the boot loader is not properly configured, this could lead into
corruption of the persistent memory region when the system boots.
The EDR_RECORD_SIZE parameter can be used to change the default size of error
records. Note that for performance reasons, all records are necessarily the same
size.
The pmShow( ) shell command (for the C interpreter) can be used to display the
amount of allocated and free persistent memory.
For more information about persistent memory, see the VxWorks Kernel
Programmer’s Guide: Memory Management, and the pmLib API reference.
! WARNING: A VxWorks 6.x boot loader must be used to ensure that the persistent
memory region is not cleared between warm reboots. Prior versions of the boot
loader may clear this area.
The error detection and reporting facilities provide for two sets of responses to
fatal errors. See 14.5 About Fatal Error Response Modes, p.322 for information about
these responses, and various ways to select one for a run-time system.
319
VxWorks
Application Programmer's Guide, 6.9
■
severity level
The event type identifies the context in which the error occurred (during system
initialization, or in a process, and so on).
The severity level indicates the seriousness of the error. In the case of fatal errors,
the severity level is also associated with alternative system’s responses to the error
(see 14.5 About Fatal Error Response Modes, p.322).
The event types are defined in Table 14-1, and the severity levels in Table 14-2.
Type Description
The information collected depends on the type of events that occurs. In general, a
complete fault record is recorded. For some events, however, portions of the record
are excluded for clarity. For example, the record for boot and reboot events exclude
the register portion of the record.
Error records hold detailed information about the system at the time of the event.
Each record includes the following generic information:
■
date and time the record was generated
■
type and severity
■
operating system version
■
task ID
■
process ID, if the failing task in a process
■
task name
■
process name, if the failing task is in a process
■
source file and line number where the record was created
■
a free form text message
It also optionally includes the following architecture-specific information:
320
14 Error Detection and Reporting
14.4 Displaying and Clearing Error Records
■
memory map
■
exception information
■
processor registers
■ disassembly listing (surrounding the faulting address)
■ stack trace
Command Action
The shell’s command interpreter provides comparable commands. See the API
references for the shell, or use the help edr command.
In addition to displaying error records, each of the show commands also displays
the following general information about the error log:
■
total size of the log
■
size of each record
■
maximum number of records in the log
■
the CPU type
■
a count of records missed due to no free records
■
the number of active records in the log
■
the number of reboots since the log was created
See the edrShow API reference for more information.
321
VxWorks
Application Programmer's Guide, 6.9
NOTE: The operative error handling mode can be changed with system debug flag
(see 14.6 Setting Fatal Error Handling Mode, p.323). The default is deployed mode.
Deployed
Mode
Event Type Debug Mode (default) Error Handling Routine
The error handling routines are called in response to fatal errors—only errors with
the FATAL severity level have handlers associated with them. These handlers are
defined in installDir/vxworks-6.x/target/config/comps/src/edrStub.c. Developers
can modify the routines in this file to implement different system responses to fatal
errors. The names of the routines, however, cannot be changed.
322
14 Error Detection and Reporting
14.6 Setting Fatal Error Handling Mode
Note that when the debugger is attached to the target, it gains control of the system
before the error-handling option is invoked, thus allowing the system to be
debugged even if the error-handling option calls for a reboot.
! CAUTION: The system debug flag is used by both the error detection and reporting
facility and the WDB target agent. Setting the flag for one facility sets it for the
other as well. For information about how the system debug flag is used by WDB,
see the VxWorks Kernel Programmer’s Guide: WDB Target Agent.
The system can be set to either debug or deployed mode with the boot loader’s
flags parameter. The default value of 0x000 specifies deployed mode. Use the
value of 0x400 (SYSFLG_SYS_MODE_DEBUG) to select debug mode. For
information about boot parameters, see the VxWorks Kernel Programmer’s Guide:
Boot Loader.
When a system boots, the banner displayed on the console displays information
about the mode defined by the system debug flag. For example:
ED&R Policy Mode: Deployed
323
VxWorks
Application Programmer's Guide, 6.9
<<<<<Memory Map>>>>>
<<<<<Registers>>>>>
324
14 Error Detection and Reporting
14.9 Sample Error Record
<<<<<Disassembly>>>>>
<<<<<Traceback>>>>>
325
VxWorks
Application Programmer's Guide, 6.9
326
15
RTP Core Dumps
15.1 Introduction
The VxWorks RTP core dump facility allows for generation and storage of RTP core
dumps, for excluding selected memory regions from core dumps, and for
retrieving core dumps from the target system. Core dump files can be analyzed
with the Workbench debugger to determine the reason for a failure or to analyze
the state of a system at a particular moment in its execution.
NOTE: VxWorks can be configured with both kernel core dump and RTP core
dump facilities. For information about the VxWorks kernel core dump facility, see
the VxWorks Kernel Programmer’s Guide.
327
VxWorks
Application Programmer's Guide, 6.9
Generation
An RTP core dump is generated when an RTP gets an exception or when it receives
any unmasked signal except for SIGSTOP, SIGCONT, and SIGCHLD. They can also
be generate on demand (interactively or programmatically).
The VxWorks RTP core dump facility enables core dump generation for all RTPs
that are running in a system. You cannot selectively enabled or disabled individual
RTPs for core dumps. Multiple core dump events are handled sequentially. That is,
if a core dump event is triggered while generation of another core dump is in
process, the second is not processed until the first is complete.
Kernel core dumps take precedence over RTP core dumps. If a kernel core dump
is triggered while an RTP core dump is in process, the RTP core dump is aborted
and the kernel core dump is processed.
For information about generating core dumps, see 15.8 Generating RTP Core
Dumps, p.332.
Data
Size
RTP core dumps can be quite large due to the fact that they must include the text
section of the RTP. (Without the text section, it cannot be used with the Workbench
debugger.) In addition, core dumps will be larger if the RTP is attached to a shared
library at the time the core dump is triggered. This is because the shared library is
included in the runtime image of the RTP and is therefore included in the core
dump.
The total size of the RTP core dump is determined as follows:
RTP text + RTP data + shared library + shared data + some kernel data
328
15 RTP Core Dumps
15.3 Configuring VxWorks With RTP Core Dump Support
The element of kernel data is for internal purposes only, and is not visible when the
debugger is used to analyze the core dump.
Filtering
The RTP core dump facility provides a routine for filtering (excluding) specific
memory regions from RTP core dumps to reduce their size, or simply to eliminate
data that is not of interest. For more information, see 15.5 Eliminating Areas of
Memory Captured by Core Dumps, p.331.
The location to which core dump files are written is specified by the configuration
of the RTP core dump facility. The location can also be changed at runtime.
RTP core dump files are named using the following syntax:
rtpcore[_suffix_]index[.z]
For more information about file names and locations, see 15.6 Setting Core Dump
File Names, p.331 and 15.7 Setting Core Dump File Locations, p.332.
File Compression
The RTP core dump facility provides options for file compression (zlib, RLE, or
none). For more information, see 15.4 Enabling File Compression, p.331.
The core dump facility has a few limitations with regard to register information
and stack traces when used with VxWorks SMP. Note the following behavior with
regard to CPUs other than the one that initiated the core dump:
■ The contents of the CPU registers and stack trace may not reflect the exact state
of a CPU when the core dump was generated. An interprocessor interrupt is
used to retrieve information about each CPU, and if interrupts are locked on
any of those CPUs when the interrupt occurs, then the contents of the registers
and the stack trace may be different when the interrupts are unlocked.
■ The contents of a CPU's registers and the stack trace may be missing. If
interrupts are locked on a CPU, and then not released while the core dump is
being generated, the contents of the registers and the stack trace cannot be
retrieved.
■
The contents of the coprocessor registers are only saved for the CPU that
initiated the core dump.
329
VxWorks
Application Programmer's Guide, 6.9
Basic Configuration
INCLUDE_CORE_DUMP_RTP
This component provides basic RTP core dump support. IT also includes facilities
for excluding (filtering) regions of memory from core dumps and for adding a
suffix element to the default core dump file name.
For more information on excluding regions of memory, see 15.5 Eliminating Areas
of Memory Captured by Core Dumps, p.331. For more information on customizing
core file names, see 15.6 Setting Core Dump File Names, p.331)
INCLUDE_CORE_DUMP_RTP_FS
This component provides facilities for writing RTP core dumps to a local or remote
file system. The following parameter identifies the location:
CORE_DUMP_RTP_PATH
Set this parameter to the file system and path to the directory into which core
dump files are written. For example, /myLocalFs/rtpcore for a local file system,
or remote:/rtpcore for a remote file system. The path must be accessible from
the target.
The INCLUDE_CORE_DUMP_RTP_FS component also provides routines for
getting and setting the storage directory at runtime (for more information, see
15.7 Setting Core Dump File Locations, p.332 ), as well as for producing an
information file for each RTP core dump file (for more information, see 15.9 Getting
Information about RTP Core Dumps, p.333).
INCLUDE_CORE_DUMP_RTP_COMPRESS_RLE
INCLUDE_CORE_DUMP_RTP_COMPRESS_ZLIB
330
15 RTP Core Dumps
15.4 Enabling File Compression
! CAUTION: On some targets compression can take a long time, which may lead you
to believe that the target has hung. If this occurs, Wind River recommends that you
remove the compression component or reconfigure the level of compression.
! CAUTION: Do not use memory filters to exclude the text sections of an RTP or any
shared libraries that are attached to it. If the text sections are not present, the
Workbench debugging tools cannot be used with the core dump.
331
VxWorks
Application Programmer's Guide, 6.9
■
The index is set to 1 for the first core dump generated during a given session
(that is, between reboots), and incremented for each subsequent core dump
during that session. The index is reset to zero each time the system is rebooted.
■ Core dump files remaining from a previous session are overwritten by new
core dump files with the same file name.
■
The suffix element can be used to provide unique names for core dump files
for a given session, so they are not overwritten during a subsequent session.
■
The .z extension is added for compressed files.
In order to preserve core dump files between sessions, you can use the
coreDumpRtpNameSuffixSet( ) routine to add a file-name suffix based on unique
string (such as the time and date of booting VxWorks). This call must be made from
the kernel context. You can make the call from the shell before launching the RTP,
or from usrAppInit( ).
For information about coreDumpRtpNameSuffixSet( ), see the VxWorks
Application API Reference. For information about usrAppInit( ), see the VxWorks
Kernel API Reference and the VxWorks Kernel Programmer’s Guide: Kernel
Applications.
332
15 RTP Core Dumps
15.9 Getting Information about RTP Core Dumps
if (fatalError)
{
/* Fatal error detected: generate RTP Core Dump */
At the end of core dump generation, the RTP is either killed or left stopped
depending on the configuration of the error detection and reporting facility.
For information about signals, see 8.2 Signals, p.160. For information about the
error detection and reporting facilities, see 14. Error Detection and Reporting. For
information about rtpKill( ), see the VxWorks API reference entry.
The Valid field is used to indicate whether or not RTP core dump generation was
successful. If not, the Errno field provides a hint as to why it failed.
This reporting facility is provided by the INCLUDE_CORE_DUMP_RTP_FS
component.
333
VxWorks
Application Programmer's Guide, 6.9
written. For information about the VxWorks commands, see the VxWorks API
reference entry for usrLib.
For information about configuration, see 15.3 Configuring VxWorks With RTP Core
Dump Support, p.329.
NOTE: To successfully debug an RTP core dump, you need the following:
■ The generated RTP core dump.
■ The VxWorks image that was running on the target at the time the core dump
was generated.
■ The RTP image (.vxe file) that generated the core dump.
■ Any shared libraries (.so files) that were attached to the RTP when the core
dump was generated.
334
A
Kernel to RTP Application Migration
A.1 Introduction
This chapter assumes that you have decided to migrate your application out of the
kernel and into a real-time process (RTP). For information about processes and
RTP applications, see 2. Real-Time Processes: RTPs.
335
VxWorks
Application Programmer's Guide, 6.9
For more information about stripping symbols from RTP executables, see
3.4.12 Reducing Executable File Size With the strip Facility, p.46.
One of the key aspects of running an application within a process is that code
running within the process can only access memory owned by the process. It is not
possible for a process to access memory directly within the memory context of
another process, or to access memory owned by the kernel.
When migrating applications, it is important to bear these restrictions in mind. The
approaches discussed in the following sections can be helpful.
While some applications which are closely coupled with the kernel are not suitable
to run in a process, this is not necessarily always the case. Consider the whole
range of solutions for communicating between applications before reaching a
conclusion. In addition to standard inter-process communication methods, the
following options are available.
336
A Kernel to RTP Application Migration
A.2 Migrating Kernel Applications to Processes
■
You might architect code that must remain in the kernel as a VxWorks driver.
Then open the driver from user mode and use the read/write/ioctl( ) model
to communicate with it.
■ You might implement a sysctl( ) method.
■
You might add an additional system call. For more information, see the
VxWorks Application Programmer’s Guide: Kernel.
! WARNING: This is the riskiest option as the possibility exists of breaking the
protection of either the kernel or your process.
For kernel code, the default method for handling C++ program startup and
termination is unchanged from earlier VxWorks releases. However, the method
has changed for process code. For details, see the Wind River Compiler User’s Guide:
Use in an Embedded Environment. The key points are:
■
.init$nn and .fini$nn code sections are replaced by .ctors and .dtors sections.
■
.ctors and .dtors sections contain pointers to initialization and finalization
functions.
■
Functions to be referenced in .ctors and .dtors can exist in any program
module and are identified with __attribute__((constructor)) and
__attribute__((destructor)), respectively, instead of the old _STI__nn_ and
_STD__nn_ prefixes. The priority of initialization and finalization functions can
be specified through optional arguments to the constructor and destructor
attributes. Example:
__attribute__((constructor(75))) void hardware_init()
{
... // hardware initialization code
}
Process code executes in user mode. Any supervisor-level access attempt is illegal,
and is trapped. Access to hardware devices usually falls into this category. The
following are prohibited:
■
Do not access devices directly from user mode even if the device is accessible.
(Access to devices is sometimes possible depending on the memory map and
337
VxWorks
Application Programmer's Guide, 6.9
the mappings for the address area for the device.) Instead of direct access, use
the standard I/O library APIs: open( ), close( ), read( ), and so forth.
An appropriate user-mode alternative is to access a memory-mapped device
directly by creating a shared-data region that maps the physical location of the
device into the process memory space. A private shared-data region can be
created if access to the device must be limited to a single process. For more
information, see the VxWorks Application Programmer’s Guide: Shared Data.
■
Do not use processor-specific features and instructions in application code.
This hampers portability.
Note that interrupts cannot be locked from a process (the intLock( ) routine is not
available).
POSIX Signals
Watchdogs
The wdLib routines cannot be used in user mode. Replace them with POSIX timers
from timerLib as shown in Table A-1.
wdCreate( ) timer_create( )
Routines Routines
wdCreate( ) timer_create( )
wdStart( ) timer_connect( ) +
timer_settime( )
wdCancel( ) timer_cancel( )
wdDelete( ) timer_delete( )
338
A Kernel to RTP Application Migration
A.2 Migrating Kernel Applications to Processes
There are slight differences in the behavior of the two timers, as shown in
Table A-2.
The handler executes when the timer A signal handler executes in the context
expires, right in the context of the of a task; the handler cannot run until
system clock tick handler. the scheduler switches in the task
(which is, of course, based on the task
priority). Thus, there may be a delay,
even though the timeout has expired.
Drivers
Hardware interface services are provided by the kernel in response to API kernel
calls. From a process you should access drivers through ioctl( ), system calls,
message queues, or shared data. For more information, see A.2.4 Eliminating
Hardware Access, p.337.
I/O redirection is possible for the whole process, but not for individual tasks in the
process.
The routines ioTaskStdGet( ) and ioTaskStdSet( ) are not available in user mode.
You can use dup( ) and dup2( ) instead, but these routines change the file
descriptors for the entire process.
The POSIX dup( ) and dup2( ) routines have been introduced to VxWorks for
manipulation of file descriptor numbers. They are used for redirecting standard
I/O to a different file and then restoring it to its previous value when the
operations are complete.
339
VxWorks
Application Programmer's Guide, 6.9
The process shown in Example A-1 is easily emulated using dup( ) and dup2( ).
Use the dup( ) command to duplicate and save the standard file descriptors upon
entry. The dup2( ) command is used to change the standard I/O files and then later
used to restore the standard files that were saved. The biggest difference is the need
to close the duplicates that are created at the start.
/* temporary data values */
int oldFd0;
int oldFd1;
int oldFd2;
int newFd;
/*
Do operations using new standard file for input/output/error
*/
This section highlights API changes that affect applications in real-time processes.
Task Naming
340
A Kernel to RTP Application Migration
A.2 Migrating Kernel Applications to Processes
The traditional means for inter-task communication used in VxWorks 5.5, such as
semaphores and message queues, have been extended such that they can be
defined as private or public, as well as named. Private objects are visible only to
tasks within a process, whereas public objects—which must be named—are visible
to tasks throughout the system. Public objects can therefore be used for
inter-process communication. For more information about public and private
objects and about naming, see 7.8 Inter-Process Communication With Public Objects,
p.154.
In a real-time process, semaphores are available using the traditional VxWorks API
(semTake( ), semGive( ), and so forth). They are known as user-mode semaphores
because they are optimized to generate a system call only when necessary. The
scope of a semaphore object created by a VxWorks application is, however,
restricted to the process it was created in. In other words, two different
applications cannot be synchronized using user-level semaphores. If mutual
341
VxWorks
Application Programmer's Guide, 6.9
Signal Generation
A kernel task or an ISR can send signals to any task in the system, including both
kernel and process tasks.
A process task can send signals to itself, to any task within its process, to its
process, to another process, and to any public tasks in another process. Process
tasks cannot send signals to kernel tasks. For more information, see Private and
Public Objects, p.341.
Signal Delivery
The process of delivering a signal involves setting up the signal context so that the
action associated with the signal is executed, and setting up the return path so that
when the signal handler returns, the target task gets back to its original execution
context.
Kernel signal generation and delivery code runs in the context of the task or ISR
that generates the signal.
Process signal generation is performed by the sender task, but the signal delivery
actions take place in the context of the receiving task.
The kernel is an entity with a single address space. Tasks within the kernel share
that address space, but are really different applications that coexist in that one
address space. Hence, each kernel task can individually install a different handler
for any given signal.
342
A Kernel to RTP Application Migration
A.2 Migrating Kernel Applications to Processes
The signal model in user mode follows the POSIX process model. A process
executes an application. Tasks that belong to the process are equivalent to threads
within a process. Therefore, process tasks are not allowed to register signal
handlers individually. A signal handler is effective for all tasks in a given process.
By default, signals sent to kernel tasks are ignored (in other words, SIG_DFL in
kernel mode means “ignore the signals” or SIG_IGN).
However, by default, signals sent to process tasks result in process termination (in
other words, SIG_DFL for process tasks means “terminate the process”).
Kernel tasks, when created, have all signals unmasked. Process tasks inherit the
signal mask of the task that created them. Thus, if a kernel task created a process,
the initial task of the process has all signals unblocked.
Kernel tasks that receive signals while blocked are immediately unblocked and run
the signal handler. After the handler returns, the task goes back to blocking on the
original object.
Signals sent to a blocked process task are delivered only if the task is blocked on an
interruptible object. In this case, the blocking system call returns ERROR with
errno set to EINTR. After the signal handler returns, it is the responsibility of the
task to re-issue the interrupted call if it wishes.
Signals sent to process tasks blocked on non-interruptible objects are queued. The
signal is delivered whenever the task unblocks.
Table A-3 shows signal APIs that behave differently in the kernel than in a process.
raise( ) sends a signal to the current task sends a signal to the current
task’s process
343
VxWorks
Application Programmer's Guide, 6.9
Socket APIs
routeAdd( )
The most likely issue to arise when you try to move existing code into user mode
is that a header file you used previously is unavailable or no longer contains
something you need, and hence your code fails to compile. This may occur
commonly when transitioning code and suggests that the feature you are trying to
use is not available in user mode.
It may be tempting to find the missing header file on the kernel side of the
VxWorks tree and use that, but this is unlikely to help. Wind River supplies specific
header files for user-mode development in a distinct user-mode part of the
directory tree. These header files only supply features that have been designed and
tested to work under user-mode protections.
If a header file does not exist or exposes less functionality than is available in kernel
mode, this is because those features are not available from user mode. Usually
these features cannot be implemented in user mode due to the nature of the
protection model. For example, layer 2 networking facilities typically access
hardware I/O drivers directly; however, this is not allowed within the protected
user-mode environment.
There is a newly created system for alerting customers to some of the differences
between kernel and user modes. The _WRS_DEPRECATED macro is used to tag an
API as being deprecated. The Wind River Compiler allows for a message to be
applied as well. If the compiler encounters an API tagged as deprecated it issues
an immediate warning with the optional message. Many routines, like
ioGlobalStdSet( ), that are not available in user mode, generates the following
message when using the Wind River Compiler:
fileline ioGlobalStdSet is deprecated not available in RTP.
344
A Kernel to RTP Application Migration
A.3 Differences in Kernel and RTP APIs
In user mode, object IDs such as SEM_ID are not pointers to memory. Instead, they
are handles typically comprised of a small integer reference number and a
generation ID. It is not possible to access the internal structures of objects in user
mode.
Some APIs are not present in user mode because their action is not compatible with
a protected environment.
intLock( ), intUnlock( ), taskLock( ), taskUnlock( )
It is not possible to lock and unlock interrupts from user mode; thus intLock( )
and intUnlock( ) are not present in user mode. Similarly, from a process, it is
not possible to globally lock and unlock the scheduler as is done in the kernel
by taskLock( ) and taskUnlock( ).
taskInit( )
The taskInit( ) routine is not available in user mode. Instead, use taskCreate( ).
taskOptionsSet( )
There are no user-changeable task options available in user mode; thus
taskOptionsSet( ) is not present. Also, not all task options are available; in
particular, VX_UNBREAKABLE and VX_SUPERVISOR are unavailable in user
mode.
taskSwitchHookAdd( ), taskSwitchHookDelete( )
Adding and deleting task switch hooks in user mode is not supported. Thus,
the routines taskSwitchHookAdd( ) and taskSwitchHookDelete( ) do not
exist in user mode. However, task delete and create hooks are supported in
user mode; therefore the routines taskCreateHookAdd( ),
taskCreateHookDelete( ), taskDeleteHookAdd( ), and
taskDeleteHookDelete( ) do exist in user mode. For more information, see
A.3.3 APIs that Work Differently in Processes, p.346.
345
VxWorks
Application Programmer's Guide, 6.9
NOTE: There is no hardware, BSP, or driver access from user-mode. For a list of all
APIs that are present in user-mode, see the reference entries.
Some new user-mode APIs are available in processes only. The largest group of
these is the Dinkumware C and C++ libraries. For more information, see the
reference entries for these libraries.
This section highlights a few APIs that work differently in processes. For more
information, see A.2 Migrating Kernel Applications to Processes, p.335.
346
A Kernel to RTP Application Migration
A.3 Differences in Kernel and RTP APIs
returned, with errno set to ENOSYS. The solution is to add the appropriate
component to the kernel and rebuild VxWorks.
Note that all user-mode APIs that are system calls have all arguments and memory
addresses validated before the call is allowed to complete.
The following are differences between using user-mode APIs in processes and
using kernel-mode APIs:
■
There is no way to get the task list for all tasks in a process.
■
Show routines are not available from user mode.
347
VxWorks
Application Programmer's Guide, 6.9
348
B
Porting C Code from 32-Bit to 64-Bit
B.1 Introduction
VxWorks for 64-bit hardware platforms is based on the LP64 data model, whereas
VxWorks for 32-bit platforms is based on the ILP32 data model. Code that was
written for ILP32 must be carefully examined and modified so as to run correctly
with the LP64 data model implementation of VxWorks. Most of the issues
described in that chapter are common to porting code from 32-to-64 bit platforms
for many operating systems.
With some exceptions—which are largely related to the manipulation of 64-bit
entities—code that has been successfully ported to 64-bit VxWorks will
successfully re-compile for the ILP32 data model and execute on 32-bit VxWorks.
NOTE: For this release, 64-bit VxWorks is only available for the x86-64 architecture.
For detailed information about the X86-64 ABI, see the VxWorks Architecture
Supplement and the System V Application Binary Interface AMD64 Architecture
Processor Supplement.
349
VxWorks
Application Programmer's Guide, 6.9
size (32 or 64 bits) of specific data types, where I stands for integer, L for long, and
P for pointer. Table B-1 shows the size of data types (in bits) for each data model.
char 8 8
short 16 16
int 32 32
long 32 64
pointer 32 64
long long 64 64
float 32 32
double 64 64
size_t 32 64
ssize_t 32 64
ptrdiff_t 32 64
enum 32 32
The long long type comes from C99. In the LP64 data model it does not differ from
the long data type; however it does in the ILP32 data model.
The off_t, size_t, ssize_t, and ptrdiff_t types are POSIX types that represent a file
size, an object's unsigned size, a signed number of bytes read or written (-1
represents an error), and the signed arithmetic difference between two pointers
(respectively). They have been introduced for the data size neutrality of some API
(one does not have to know what size of objects those types represent in order to
use them).
For the ILP32 data model the off_t type is indicated in the table as having either a
32-bit size or a 64-bit size. This is specific to VxWorks and indicates the size of off_t
in the kernel (32 bit) environment and in the RTP environment (64 bits already).
The long double type size is not defined by the C99 standard other than being at
least equal to that of the double type. For the ILP32 data model the Wind River
Compiler (diab) and GNU compiler implement it as a 64-bit type on all
architectures except on x86 where it is a 96-bit (12 bytes) long type. For the LP64
data model gcc 3.x usually implements it as a 64-bit type except on x86, where it is
a 128-bit type. The gcc 4.x compiler usually implements it as a 128-bit type on all
architectures.
350
B Porting C Code from 32-Bit to 64-Bit
B.3 Using the Compiler to Identify Porting Issues
The main difficulty in porting C code that has been written for a 32-bit hardware
platform to a 64-bit hardware platform—or in writing code that is portable
between both platforms—has to do with accounting for the ways in which
differences in the sizes of data types can cause code to malfunction.
In particular, the change in the size of the pointer type is one of the main causes for
the difficulty in porting code from ILP32 to LP64. One can no longer make the
assumption that an int type variable can hold a pointer (or any address for that
matter).
! CAUTION: For 64-bit VxWorks, due to the larger size of several data types in the
LP64 data model, a task is likely to need a larger stack than what is sufficient for
32-bit VxWorks. Typically an increase of 35% to 50% is enough to most
applications, although there may be cases in which the stack size must be doubled.
Use the checkStack( ) shell command to determine if your tasks’ stacks are too
small (that is, the high water mark is too close to the size of the stack).
351
VxWorks
Application Programmer's Guide, 6.9
Use the proper CPU name to select the proper toolchain: CORE selects
ccpentium.
2. Look for the errors and warnings. See Compiler Errors and Warnings, p.352.
3. Look for data size logic issues in your code.
4. Adapt your code following the guidelines provided in B.4 Making C Code
32-bit/64-Bit Portable, p.352 and check that it now compiles properly with the
compiler for the 64-bit architecture.
5. Compile your source file with the CPU type appropriate for the 32-bit
architecture (for example, PENTIUM4 instead of CORE).
6. Check that there are no errors or warnings in the output. If there are, then go
back to step 4 and find another way of adapting your code.
The GNU compiler for x86-64 typically issues one of the following warnings to flag
type cast inconsistencies, when using only the -Wall option:
warning: cast to pointer from integer of different size
warning: cast from pointer to integer of different size
These are typically issued when int type variables are either assigned (with a cast)
with the value of pointer or are representing a pointer to a structure. For example:
tid = (int)taskIdCurrent; (where ‘tid’ is an ‘int’)
((ISR_ID)arg)->isrTag = …; (where ‘arg’ is an ‘int’)
This error is caused by assembly inlines, usually involving macros. This requires
either an update of the inline's assembly code or stopping use of those inlines until
they are updated.
NOTE: The guidelines provided in this section are intended to be a summary of the
key issues involved in making C code portable between 32-bit and 64-bit hardware
platforms. They do not provide an exhaustive treatment of the topic (as well as
code optimization, and so on).
352
B Porting C Code from 32-Bit to 64-Bit
B.4 Making C Code 32-bit/64-Bit Portable
While both the int and long types are both 32 bits in the ILP32 data model, the long
type is 64 bits in the LP64 model. Code written for 32-bit hardware platforms may
well use int and long types indiscriminately; and int variables are often used as
general purpose storage entities. The int type cannot be used as a generic type in
the LP64 programming environment as not all data types or values fit in a 32-bit
storage.
To ensure correct behavior on 64-bit platforms, and portability between 32-bit and
64-bit platforms, make sure that all int and long assignments are correct for the
intended use of the variables and for the platform on which they will be used.
The long double type is a problematic type due to both the architectural and
compiler-related variation in its implementation. If this type is used in your code,
consider carefully whether your code really needs it. If so, determine what the
compiler produces for the architecture in question.
While integers and pointers are the same size on a 32-bit hardware platform, they
are not the same size on a 64-bit platform. Addresses cannot, therefore, be
manipulated through int type variables, function parameters, or function return
values.
Data truncation problems can occur with assignments, initialization, returns and
so on because the long type and pointers are not the same size as int with the LP64
data model. Note that these problems are not detected by the compiler (for
example, while assigning a 64-bit constant to a 32-bit integer triggers a warning,
this does not happen when a 64-bit integer variable is assigned to a 32-bit one).
Some of the most difficult issues with adapting code to the LP64 programming
environment are related to integer promotion rules and sign extension involved in
logic expressions, assignments, and arithmetic operations. For example, integer
promotion rules cause silent truncations when an integer type variable is assigned
the value of a larger-size integer type variable.
The C standard should be consulted with regard to data type promotion.
Pointers and the int type are not the same size in the LP64 data model. The
common practices for the ILP32 programming environment of using int variables
to store the values of pointers, of casting pointers to int for pointer arithmetic, and
so on, will cause pointer corruption.
In addition, because LP64 pointers are 64-bits, it is easy to produce a boundary
overflow of an array of pointers unless sizeof( ) is used to determine the NULL
value at the end of an array
353
VxWorks
Application Programmer's Guide, 6.9
Because the int and long types are different sizes with the LP64 data model, the use
of sizeof( ) should be examined to make sure the operands are used correctly.
While it may be tempting to get rid of compiler warnings by way of type casting,
this may do more harm than good because it silences the compiler when it would
be indicating a genuine problem.
Type casting can lead to value truncation or, worse, memory corruption when a
pointer is involved. For example, consider a routine with a signature that has been
modified to use a size_t * instead of an int * parameter, and that is conventionally
passed the address of variables holding small values by way of this parameter. If
those variables are not updated to be of the size_t type but instead are left as int
and type-cast to size_t * when their address is passed to the routine, then memory
corruption ensues when the routine attempts to read or write 8 bytes at those
addresses and only 4 bytes have been reserved by the caller code.
It is, therefore, much preferable to change the type of the variables involved in
warnings rather than use type casting. There are of course situations where type
casting is required, but they should carefully be identified as such.
String conversion formats for printf( ) and related functions must be used for the
data type in question. The %d format will not work with a long or pointer for the
LP64 programming environment. Use the %ld format for longs and %p for
pointers.
Check the use of all constants. Some of the issues you may encounter include the
following:
Integer Constants
0xFFFFFFFF Value
In the LP64 data model the value 0xFFFFFFFF is not equivalent to -1, and is not
even negative. This may lead to test logic failing to work as intended. In the same
vein, using 0xFFFFFFFF as a mask value may not work with LP64. Note that
adding the L suffix to the constant is not going to help in this case, as it will just tell
the compiler that the constant is a long instead of an int—which does not extend
the constant, that is, does not make 0xFFFFFFFF interpreted as
[Link].
354
B Porting C Code from 32-Bit to 64-Bit
B.4 Making C Code 32-bit/64-Bit Portable
ERROR Status
The VxWorks ERROR return status must now be checked with equality or
difference operators only against the ERROR or OK constants (comparing ERROR
against 0xFFFFFFFF does not work). The greater-than and less-than operators
should not be used with the ERROR constant. Furthermore, in order to avoid
compilation warnings it may be necessary to cast the ERROR macro with the type
of the value against which it is being compared if this value is not of the STATUS
type, is not an int, or may not be equivalent to an int on 64-bit VxWorks.
Bit Masks
All masks used in logical operations involving the long type should be carefully
reviewed due to the change in size of the long type between ILP32 and LP64.
Magic Numbers
The size and alignment of structures will be different in the ILP32 and LP64
programming environments if the structures contain elements that are different
sizes in the respective data models. According to the C language, a structure is
aligned on the same boundary as its most strictly aligned member.
Structures should, therefore, be examined with consideration to changing the
order of members so that as many as possible fall on natural alignment. In general,
move long and pointer members to the beginning of the structure. While explicit
padding may sometimes be useful to counteract automatic padding introduced by
the compiler, it should generally be avoided in the context of portability because of
data bloat.
Unions should be carefully examined to ensure that the size of the members
maintains equivalence with the LP64 data model. For example, the union of an int
type member and a long type member works seemlessly in the ILP32
programming environment but is much more error-prone in the LP64
programming environment where one must be careful not to store the 64-bit value
in the 32-bit member of the union (which will result in truncation—possibly silent
if only standard warning level is used).
355
VxWorks
Application Programmer's Guide, 6.9
By default C compilers assume that a function returns a value of type int. If your
code includes a routine that returns a long or a pointer—but for which there is no
prototype or for which you did not include the appropriate header file—then the
compiler generates code that truncates the return value to fit in 32-bit storage.
Create prototypes for your routines and use the prototypes of routines that are
employed in your code.
All APIs with parameters or return values that represent an offset in memory or in
a file, or the size of a region of memory or of a file, or the size of a memory buffer
or a file should be changed as follows:
■ If the parameter or return value represents a size of a file, or the size of a region
of a file, or an offset in a file, replace int and unsigned int with off_t.
■ If the parameter represents a region of memory or a buffer, replace int and
unsigned int with size_t.
■ If the original return value represents a size in memory, but can be negative
(usually because it can return -1), then the replacement type should be ssize_t.
If the return value can only be positive then use size_t.
As a last resort—when code must implement logic that is not portable between
data models—the _WRS_CONFIG_LP64 macro can be used for dual definitions, as
follows:
#ifdef _WRS_CONFIG_LP64
#else /
Use this macro only when absolutely necessary—you should try to make code
work with both the ILP32 and LP64 programming environments. The macro must,
however, be used when the code must implement a logic that cannot apply to one
or the other of the data models. The typical case is a type definition involving size
when the type name (but not the definition, obviously) must be identical for both
ILP32 and LP64. The same is true for macros defining values that cannot be
identical for both ILP32 and LP64.
The _WRS_CONFIG_LP64 macro is available when the vsbConfig.h header file is
included. The _WRS_CONFIG_LP64 make variable is also provided for use in
makefiles.
356
Index
357
VxWorks
Application Programmer's Guide, 6.9
358
Index
359
VxWorks
Application Programmer's Guide, 6.9
360
Index
361
VxWorks
Application Programmer's Guide, 6.9
362
Index
N P
named semaphores (POSIX) 206 page locking 186
using 210 see also mmanPxLib(1)
nanosleep( ) 119, 120 paging 186
using 185 pended tasks 101
netDrv pended-suspended tasks 101
compared with TSFS 315 persistent memory region 318
netDrv driver 278 pipeDevCreate( ) 148
network devices pipes 147
see also FTP; NFS; RSH see online pipeDrv
NFS 277 ioctl functions, and 148
non-NFS 278 select( ), using with 147
Network File System, see NFS plug-ins 85
network task tNet0 106 POSIX
networks see also asynchronous I/O
transparency 277 asynchronous I/O 268
NFS (Network File System) clocks 182–185
devices 277 see also clockLib(1)
open( ), creating files with 260 file truncation 262
ioctl functions, and 277 memory-locking interface 186, 187
transparency 277 message queues 213
nfsDrv driver 277 see also message queues; mqPxLib(1)
null devices 276 mutex attributes
NUM_RAWFS_FILES 307 prioceiling attribute 196
protocol attribute 195
page locking 186
see also mmanPxLib(1)
O paging 186
priority limits, getting task 205
O_CREAT 298
priority numbering 199, 200
O_NONBLOCK 215
scheduling 205
O_CREAT 210
see also scheduling; schedPxLib(1)
O_EXCL 210
semaphores 206
O_NONBLOCK 217
see also semaphores; semPxLib(1)
open( ) 259
signal functions 163
access flags 259
see also signals; sigLib(1)
files asynchronously, accessing 268
signals, application
files with, creating 260
behavior 342
subdirectories, creating 298
swapping 186
opendir( ) 291, 298
thread attributes 188–190
operating system 187
specifying 189
OPT_7_BIT 273
threads 187
OPT_ABORT 273
timers 182–185
OPT_CRMOD 273
see also timerLib(1)
OPT_ECHO 273
timers, difference from watchdogs 339
OPT_LINE 273
posixPriorityNumbering global variable 200
OPT_MON_TRAP 273
preemptive priority scheduling 109, 110
OPT_RAW 273
printErr( ) 267
OPT_TANDEM 273
prioceiling attribute 196
OPT_TERMINAL 273
priority
overlapped virtual memory
inheritance 136
and applications 45
inversion 135
configuring 22
message queues 144
RTP code region 21
numbering 199, 200
363
VxWorks
Application Programmer's Guide, 6.9
364
Index
365
VxWorks
Application Programmer's Guide, 6.9
366
Index
367
VxWorks
Application Programmer's Guide, 6.9
U
unnamed semaphores (POSIX) 206, 208, 208–210
user regions 20
usrRtpAppInit( ) 59
V
variables
368