Vxworks Application Programmers Guide 6.7
Vxworks Application Programmers Guide 6.7
Vxworks Application Programmers Guide 6.7
VxWorks
6.7
Copyright 2008 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: www.windriver.com/company/terms/trademark.html This product may include software licensed to Wind River by third parties. Relevant notices (if any) are provided in your product installation at the following location: installDir/product_name/3rd_party_licensor_notice.pdf. Wind River may refer to third-party documentation by listing publications or providing links to third-party Web sites for informational purposes. Wind River accepts no responsibility for the information provided in such third-party documentation.
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: www.windriver.com For information on how to contact Customer Support, see: www.windriver.com/support
Contents
Overview ...............................................................................................
1.1 1.2 1.3 Introduction ............................................................................................................. Related Documentation Resources ..................................................................... VxWorks Configuration and Build .....................................................................
1
1 2 3
5
6 7 8 8 10 10 11 11 11 12 12 12
iii
2.2.6 2.2.7
RTPs and Inter-Process Communication .............................................. RTPs, Inheritance, Zombies, and Resource Reclamation ................... Inheritance ................................................................................................. Zombie Processes ..................................................................................... Resource Reclamation ..............................................................................
13 13 13 14 14 15 15 16 16 16 17 17 18 19 20 20 22 23 23 24 26 26 27 27 29 29 31 33
2.2.8
RTPs and Environment Variables .......................................................... Setting Environment Variables From Outside a Process .................... Setting Environment Variables From Within a Process .....................
2.2.9
2.3
Configuring VxWorks For Real-time Processes .............................................. 2.3.1 2.3.2 2.3.3 2.3.4 Basic RTP Support .................................................................................... MMU Support for RTPs .......................................................................... Additional Component Options ............................................................ Configuration and Build Facilities .........................................................
2.4
Using RTPs Without MMU Support .................................................................. Configuation With Process Support and Without an MMU .............
2.5
About VxWorks RTP Virtual Memory Models ............................................... 2.5.1 2.5.2 Flat RTP Virtual Memory Model .......................................................... Overlapped RTP Virtual Memory Model ............................................
2.6
Using the Overlapped RTP Virtual Memory Model ...................................... 2.6.1 About User Regions and the RTP Code Region ................................. User Regions of Virtual Memory ........................................................... RTP Code Region in Virtual Memory .................................................. 2.6.2 Configuring VxWorks for Overlapped RTP Virtual Memory .......... Getting Information About User Regions ............................................ Identifying the RTP Code Region .......................................................... Setting Configuration Parameters for the RTP Code Region ............
iv
Contents
2.6.3
Using RTP Applications With Overlapped RTP Virtual Memory ... Building Absolutely-Linked RTP Executables ..................................... Stripping Absolutely-Linked RTP Executables .................................. Executing Absolutely-Linked RTP Executables ................................. Executing Relocatable RTP Executables ...............................................
35 35 36 37 38
39
39 40 40 41 42 42 42 42 43 44 44 44 44 45 45 45 46 46 46 47 48 48 48 48 49 49 50
Developing C++ Applications ................................................................ Using POSIX Facilities ............................................................................. Building RTP Applications .....................................................................
50 50 50 50 51 52 52 53 53 54 54 55 55 56 57 57 58 58 59 60 61 62 63 64 65 65 66 67 67 67
Developing Static Libraries, Shared Libraries and Plug-Ins ......................... Creating and Using Shared Data Regions ......................................................... 3.5.1 3.5.2 3.5.3 3.5.4 Configuring VxWorks for Shared Data Regions ................................. Creating Shared Data Regions ............................................................... Accessing Shared Data Regions ............................................................. Deleting Shared Data Regions ................................................................
3.6
Executing RTP Applications ................................................................................ Caveat With Regard to Stripped Executables ...................................... Starting an RTP Application ................................................................... Stopping an RTP Application ................................................................. Storing Application Executables ............................................................ 3.6.1 Running Applications Interactively ...................................................... Starting Applications ............................................................................... Terminating Applications ....................................................................... 3.6.2 Running Applications Automatically ................................................... Startup Facility Options .......................................................................... Application Startup String Syntax ......................................................... Specifying Applications with a Startup Configuration Parameter ... Specifying Applications with a Boot Loader Parameter .................... Specifying Applications with a VxWorks Shell Script ........................ Specifying Applications with usrRtpAppInit( ) .................................. 3.6.3 3.6.4 Spawning Tasks and Executing Routines in an RTP Application .... Applications and Symbol Registration .................................................
3.7
Bundling RTP Applications in a System using ROMFS ................................ 3.7.1 3.7.2 3.7.3 Configuring VxWorks with ROMFS ..................................................... Building a System With ROMFS and Applications ............................ Accessing Files in ROMFS .......................................................................
vi
Contents
3.7.4
68
69
70 70 71 73 73 74 74 76 76 77 77 78 78 78 79 79 79 80 80 80 80 81 82 82
Developing Shared Libraries ............................................................................... 4.8.1 About Dynamic Linking ......................................................................... Dynamic Linker ........................................................................................ Position Independent Code: PIC ............................................................ 4.8.2 4.8.3 4.8.4 4.8.5 Configuring VxWorks for Shared Libraries ......................................... Initialization and Termination ............................................................... About Shared Library Names and ELF Records ................................. Creating Shared Object Names for Shared Libraries .......................... Options for Defining Shared Object Names and Versions ................ Match Shared Object Names and Shared Library File Names ..........
vii
4.8.6 4.8.7
Using Different Versions of Shared Libraries ...................................... Locating and Loading Shared Libraries at Run-time .......................... Specifying Shared Library Locations: Options and Search Order .... Using the LD_LIBRARY_PATH Environment Variable .................... Using the ld.so.conf Configuration File ................................................ Using the ELF RPATH Record ............................................................... Using the Application Directory ............................................................ Pre-loading Shared Libraries ..................................................................
82 83 83 84 85 85 86 86 87 88 88 89 89 90 90 92 93 93 93 94 94 95 95 95 95 96 96 96 97 97 99 99
Using Lazy Binding With Shared Libraries .......................................... Developing RTP Applications That Use Shared Libraries ................. Getting Runtime Information About Shared Libraries ....................... Debugging Problems With Shared Library Use .................................. Shared Library Not Found ...................................................................... Incorrectly Started Application .............................................................. Using readelf to Examine Dynamic ELF Files ......................................
4.8.12
Working With Shared Libraries From a Windows Host .................... Using NFS .................................................................................................. Installing NFS on Windows .................................................................... Configuring VxWorks With NFS ........................................................... Testing the NFS Connection ...................................................................
4.9
Developing Plug-Ins .............................................................................................. 4.9.1 4.9.2 4.9.3 Configuring VxWorks for Plug-Ins ....................................................... Initialization and Termination ............................................................... Developing RTP Applications That Use Plug-Ins ............................... Code Requirements .................................................................................. Build Requirements ................................................................................. Locating Plug-Ins at Run-time ............................................................... Using Lazy Binding With Plug-ins ........................................................ Example of Dynamic Linker API Use ................................................... Example Application Using a Plug-In ................................................... Routines for Managing Plug-Ins ............................................................ 4.9.4 Debugging Plug-Ins .................................................................................
4.10
viii
Contents
ix
Inter-Process Communication With Public Tasks ............................... 124 Task Creation Options ............................................................................. 124 Task Stack .................................................................................................. 125 Task Stack Protection ............................................................................... 126
Task Information ...................................................................................... 127 Task Deletion and Deletion Safety ......................................................... 128 Task Execution Control ........................................................................... 129 Tasking Extensions: Hook Routines ...................................................... 131
Task Error Status: errno ......................................................................................... 132 6.5.1 6.5.2 6.5.3 A Separate errno Value for Each Task .................................................. 133 Error Return Convention ........................................................................ 133 Assignment of Error Status Values ........................................................ 133
6.6 6.7
Task Exception Handling ...................................................................................... 134 Shared Code and Reentrancy ............................................................................... 134 6.7.1 6.7.2 6.7.3 Dynamic Stack Variables ......................................................................... 136 Guarded Global and Static Variables .................................................... 136 Task-Specific Variables ........................................................................... 137 Thread-Local Variables: __thread Storage Class ................................. 137 tlsOldLib and Task Variables ................................................................ 138 6.7.4 Multiple Tasks with the Same Main Routine ....................................... 138
6.8 6.9
Intertask and Interprocess Communication ...................................................... 139 Inter-Process Communication With Public Objects ....................................... 140 Creating and Naming Public and Private Objects ............................... 141
Object Ownership and Resource Reclamation ................................................. 142 Shared Data Structures .......................................................................................... 142 Mutual Exclusion .................................................................................................... 143
Contents
6.13
Semaphores ............................................................................................................. 144 6.13.1 6.13.2 Inter-Process Communication With Public Semaphores ................... 145 Semaphore Control .................................................................................. 145 Options for Scalable and Inline Semaphore Routines ........................ 147 Static Instantiation of Semaphores ........................................................ 148 Scalable and Inline Semaphore Take and Give Routines ................... 149 6.13.3 Binary Semaphores .................................................................................. 149 Mutual Exclusion ..................................................................................... 151 Synchronization ........................................................................................ 152 6.13.4 Mutual-Exclusion Semaphores .............................................................. 152 Priority Inversion and Priority Inheritance .......................................... 153 Deletion Safety .......................................................................................... 156 Recursive Resource Access ..................................................................... 156 6.13.5 6.13.6 Counting Semaphores ............................................................................. 157 Read/Write Semaphores ........................................................................ 158 Specification of Read or Write Mode .................................................... 159 Precedence for Write Access Operations .............................................. 160 Read/Write Semaphores and System Performance ........................... 160 6.13.7 Special Semaphore Options .................................................................... 160 Semaphore Timeout ................................................................................. Semaphores and Queueing ..................................................................... Semaphores Interruptible by Signals ................................................... Semaphores and VxWorks Events ......................................................... 161 161 162 162
6.14
Message Queues ..................................................................................................... 162 6.14.1 6.14.2 Inter-Process Communication With Public Message Queues ........... 163 VxWorks Message Queue Routines ...................................................... 164 Message Queue Timeout ......................................................................... Message Queue Urgent Messages ......................................................... Message Queues Interruptible by Signals ............................................ Message Queues and Queuing Options ............................................... 6.14.3 6.14.4 6.14.5 164 165 166 166
Displaying Message Queue Attributes ................................................. 166 Servers and Clients with Message Queues ........................................... 167 Message Queues and VxWorks Events ................................................. 168
xi
6.15 6.16
Pipes .......................................................................................................................... 168 VxWorks Events ...................................................................................................... 169 6.16.1 6.16.2 6.16.3 6.16.4 6.16.5 6.16.6 Preparing a Task to Receive Events ....................................................... 170 Sending Events to a Task ........................................................................ 171 Accessing Event Flags .............................................................................. 173 Events Routines ........................................................................................ 173 Task Events Register ................................................................................ 174 Show Routines and Events ..................................................................... 174
Message Channels ................................................................................................. 175 Network Communication ..................................................................................... 175 Signals ..................................................................................................................... 176 6.19.1 6.19.2 6.19.3 6.19.4 6.19.5 Configuring VxWorks for Signals ......................................................... 178 Basic Signal Routines ............................................................................... 178 Queued Signal Routines ......................................................................... 179 Signal Events ............................................................................................. 184 Signal Handlers ........................................................................................ 185
6.20
Timers ....................................................................................................................... 188 6.20.1 Inter-Process Communication With Public Timers ............................. 188
General POSIX Support ........................................................................................ 196 Standard C Library: libc ........................................................................................ 198
xii
Contents
POSIX Header Files ............................................................................................... 199 POSIX Namespace .................................................................................................. 201 POSIX Process Privileges ..................................................................................... 203 POSIX Process Support ......................................................................................... 203 POSIX Clocks and Timers .................................................................................... 204 POSIX Asynchronous I/O ..................................................................................... 208 POSIX Advisory File Locking .............................................................................. 209 POSIX Page-Locking Interface ............................................................................ 209 POSIX Threads ........................................................................................................ 210 7.13.1 7.13.2 7.13.3 7.13.4 7.13.5 7.13.6 7.13.7 7.13.8 POSIX Thread Stack Guard Zones ........................................................ 211 POSIX Thread Attributes ........................................................................ 211 VxWorks-Specific Pthread Attributes ................................................... 212 Specifying Attributes when Creating Pthreads .................................. 213 POSIX Thread Creation and Management ........................................... 215 POSIX Thread Attribute Access ............................................................. 216 POSIX Thread Private Data .................................................................... 217 POSIX Thread Cancellation .................................................................... 218
7.14
POSIX Thread Mutexes and Condition Variables ........................................... 220 7.14.1 Thread Mutexes ........................................................................................ 220 Type Mutex Attribute .............................................................................. 221 Protocol Mutex Attribute ....................................................................... 222 Priority Ceiling Mutex Attribute ........................................................... 222 7.14.2 Condition Variables ................................................................................. 223
7.15
POSIX and VxWorks Scheduling ........................................................................ 225 7.15.1 7.15.2 Differences in POSIX and VxWorks Scheduling ................................. 226 POSIX and VxWorks Priority Numbering ........................................... 227
xiii
Default Scheduling Policy ....................................................................... 227 VxWorks Traditional Scheduler ............................................................. 228 POSIX Threads Scheduler ....................................................................... 229 POSIX Scheduling Routines .................................................................... 234 Getting Scheduling Parameters: Priority Limits and Time Slice ....... 234
POSIX Semaphores ................................................................................................ 235 7.16.1 7.16.2 7.16.3 Comparison of POSIX and VxWorks Semaphores .............................. 237 Using Unnamed Semaphores ................................................................. 237 Using Named Semaphores ..................................................................... 241
7.17
POSIX Message Queues ........................................................................................ 245 7.17.1 7.17.2 7.17.3 7.17.4 Comparison of POSIX and VxWorks Message Queues ...................... 246 POSIX Message Queue Attributes ......................................................... 247 Communicating Through a Message Queue ....................................... 249 Notification of Message Arrival ............................................................ 253
7.18 7.19
POSIX Signals ......................................................................................................... 259 POSIX Memory Management .............................................................................. 259 7.19.1 7.19.2 7.19.3 7.19.4 7.19.5 7.19.6 POSIX Memory Management APIs ....................................................... 259 Anonymous Memory Mapping ............................................................. 261 Shared Memory Objects .......................................................................... 263 Memory Mapped Files ............................................................................ 264 Memory Protection .................................................................................. 264 Memory Locking ...................................................................................... 265
7.20
POSIX Trace ............................................................................................................. 265 Trace Events, Streams, and Logs ............................................................ Trace Operation ........................................................................................ Trace APIs ................................................................................................. Trace Code and Record Example ........................................................... 265 266 267 269
xiv
Contents
xv
9.5
Buffered I/O: stdio .................................................................................................. 303 9.5.1 9.5.2 Using stdio ................................................................................................ 303 Standard Input, Standard Output, and Standard Error ..................... 304
9.6 9.7
Other Formatted I/O ............................................................................................... 305 Asynchronous Input/Output ................................................................................ 305 9.7.1 9.7.2 9.7.3 The POSIX AIO Routines ........................................................................ 305 AIO Control Block .................................................................................... 306 Using AIO .................................................................................................. 307 Alternatives for Testing AIO Completion ............................................ 308
9.8
Devices in VxWorks ............................................................................................... 308 9.8.1 Serial I/O Devices: Terminal and Pseudo-Terminal Devices ............ 309 tty Options ................................................................................................. 309 Raw Mode and Line Mode ..................................................................... 310 tty Special Characters .............................................................................. 311 9.8.2 Pipe Devices .............................................................................................. 312 Creating Pipes ........................................................................................... 312 I/O Control Functions ............................................................................. 313 9.8.3 Pseudo I/O Device ................................................................................... 313 I/O Control Functions ............................................................................. 313 9.8.4 Network File System (NFS) Devices ...................................................... 314 I/O Control Functions for NFS Clients ................................................. 314 9.8.5 Non-NFS Network Devices .................................................................... 315 I/O Control Functions ............................................................................. 316 9.8.6 9.8.7 9.8.8 Null Devices ............................................................................................. 316 Sockets ........................................................................................................ 316 Transaction-Based Reliable File System Facility: TRFS ...................... 317 Configuring VxWorks With TRFS ......................................................... Automatic Instantiation of TRFS ............................................................ Using TRFS in Applications .................................................................... TRFS Code Example ............................................................................... 317 318 318 319
xvi
Contents
10
xvii
10.5.4
Working with Volumes and Disks ......................................................... 342 Accessing Volume Configuration Information .................................... 342 Synchronizing Volumes .......................................................................... 343
10.5.5
Working with Directories ........................................................................ 343 Creating Subdirectories ........................................................................... 343 Removing Subdirectories ........................................................................ 343 Reading Directory Entries ....................................................................... 344
10.5.6
Working with Files ................................................................................... 344 File I/O Routines ...................................................................................... 344 File Attributes ........................................................................................... 344
10.5.7
Disk Space Allocation Options ............................................................... 347 Choosing an Allocation Method ............................................................ 347 Using Cluster Group Allocation ............................................................ 348 Using Absolutely Contiguous Allocation ............................................. 348
10.5.8 10.5.9
Crash Recovery and Volume Consistency ........................................... 350 I/O Control Functions Supported by dosFsLib ................................... 350
10.5.10 Booting from a Local dosFs File System Using SCSI .......................... 352 10.6 Raw File System: rawFs ......................................................................................... 352 10.6.1 10.6.2 10.6.3 10.6.4 10.6.5 10.7 Configuring VxWorks for rawFs ........................................................... 353 Creating a rawFs File System ................................................................. 353 Mounting rawFs Volumes ...................................................................... 353 rawFs File I/O ........................................................................................... 354 I/O Control Functions Supported by rawFsLib .................................. 354
CD-ROM File System: cdromFs .......................................................................... 355 10.7.1 10.7.2 10.7.3 10.7.4 Configuring VxWorks for cdromFs ....................................................... 357 Creating and Using cdromFs .................................................................. 357 I/O Control Functions Supported by cdromFsLib ............................. 357 Version Numbers ..................................................................................... 358
10.8
Read-Only Memory File System: ROMFS ........................................................ 359 10.8.1 Configuring VxWorks with ROMFS ..................................................... 359
xviii
Contents
Building a System With ROMFS and Files ........................................... 359 Accessing Files in ROMFS ...................................................................... 360 Using ROMFS to Start Applications Automatically ........................... 361
Target Server File System: TSFS ......................................................................... 361 Socket Support .......................................................................................... Error Handling ......................................................................................... Configuring VxWorks for TSFS Use ...................................................... Security Considerations .......................................................................... Using the TSFS to Boot a Target ............................................................. 362 362 363 363 364
11
Error Records ........................................................................................................... 368 Displaying and Clearing Error Records ............................................................. 370 Fatal Error Handling Options .............................................................................. 371 11.5.1 11.5.2 Configuring VxWorks with Error Handling Options ......................... 372 Setting the System Debug Flag ............................................................... 373 Setting the Debug Flag Statically ........................................................... 373 Setting the Debug Flag Interactively ..................................................... 373
Other Error Handling Options for Processes ................................................... 374 Using Error Reporting APIs in Application Code ........................................... 374 Sample Error Record .............................................................................................. 375
xix
Semaphore Differences ............................................................................ 386 POSIX Signal Differences ........................................................................ 386 Signal Generation ..................................................................................... Signal Delivery ......................................................................................... Scope Of Signal Handlers ....................................................................... Default Handling Of Signals .................................................................. Default Signal Mask for New Tasks ...................................................... Signals Sent to Blocked Tasks ................................................................. Signal API Behavior ................................................................................. 386 387 387 387 388 388 388
A.2.10
Networking Issues .................................................................................. 389 Socket APIs ................................................................................................ 389 routeAdd( ) ................................................................................................ 389
A.2.11
xx
Contents
A.3
Differences in Kernel and RTP APIs .................................................................. 390 A.3.1 A.3.2 A.3.3 A.3.4 A.3.5 A.3.6 APIs Not Present in User Mode ............................................................. 390 APIs Added for User Mode Only .......................................................... 391 APIs that Work Differently in Processes .............................................. 391 ANSI and POSIX API Differences ......................................................... 392 Kernel Calls Require Kernel Facilities ................................................... 392 Other API Differences ............................................................................. 393
xxi
xxii
1
Overview
1.1 Introduction 1 1.2 Related Documentation Resources 2 1.3 VxWorks Configuration and Build 3
1.1 Introduction
This guide describes the VxWorks operating system, and how to use VxWorks facilities in the development of real-time systems and applications. It covers the following topics:
real-time processes (RTPs) RTP applications Static Libraries, Shared Libraries, and Plug-Ins C++ development multitasking facilities POSIX facilities memory management I/O system local file systems error detection and reporting
NOTE: This book provides information about facilities available for real-time
processes. For information about facilities available in the VxWorks kernel, see the the VxWorks Kernel Programmers Guide.
components and their configuration parameters are identified by the names used in component description files. The names take the form, for example, of INCLUDE_FOO and NUM_FOO_FILES (for components and parameters, respectively). You can use these names directly to configure VxWorks using the command-line configuration facilities. Wind River Workbench displays descriptions of components and parameters, as well as their names, in the Components tab of the Kernel Configuration Editor. You can use the Find dialog to locate a component or parameter using its name or description. To access the Find dialog from the Components tab, type CTRL+F, or right-click and select Find.
2
Real-Time Processes
2.1 Introduction 6 2.2 About Real-time Processes 7 2.3 Configuring VxWorks For Real-time Processes 17 2.4 Using RTPs Without MMU Support 20 2.5 About VxWorks RTP Virtual Memory Models 23 2.6 Using the Overlapped RTP Virtual Memory Model 26
2.1 Introduction
VxWorks real-time processes (RTPs) are in many respects similar to processes in other operating systemssuch as UNIX and Linuxincluding 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 programs 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). VxWorks processes can operate with two different virtual memory models: flat (the default) or overlapped (optional). With the flat virtual-memory model each VxWorks process has its own region of virtual memory described by a unique range of addresses. This model provides advantages in execution speed, in a programming model that accommodates systems with and without an MMU, and in debugging applications. With overlapped virtual-memory model, each VxWorks process uses the same range of virtual addresses for the area in which its code (text, data, and bss segments) resides. This model provides more precise control over the virtual memory space and allows for notably faster application load time. For information about developing RTP applications, see 3. RTP Applications.
1. VxWorks can be configured to provide POSIX PSE52 support for individual processes.
The VxWorks task-scheduling model is maintained. Processes are not scheduledtasks are scheduled globally throughout the system. Processes can be preempted in kernel mode as well as in user mode. Every task has both a user mode and a kernel mode stack. (The VxWorks kernel is fully preemptive.) Processes are created without the overhead of performing a copy of the address space for the new process and then performing an exec operation to load the file. With VxWorks, a new address space is simply created and the file loaded. Process creation takes place in two phases that clearly separate instantiation of the process from loading and executing the application. The first phase is performed in the context of the task that calls rtpSpawn( ). The second phase is carried out by a separate task that bears the cost of loading the application text and data before executing it, and which operates at its own priority level distinct from the parent task. The parent task, which called rtpSpawn( ), is not impacted and does not have wait for the application to begin execution, unless it has been coded to wait. Processes load applications in their entiretythere is no demand paging.
All of these differences are designed to make VxWorks particularly suitable for hard real-time applications by ensuring determinism, as well as providing a common programming model for systems that run with an MMU and those that do not. As a result, there are differences between the VxWorks process model and that of server-style operating systems such as UNIX and Linux. The reasons for these differences are discussed as the relevant topic arises throughout this chapter.
First, the work of process creation is divided between the rtpSpawn( ) task and the initial process taskeach 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 timeall 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 write-protected, and the application's data readable and writable, as long as an MMU is present and the operating system is configured to manage it. While memory protection is provided by MMU-enforced partitions between processes, there is no mechanism to provide resource protection by limiting memory usage of processes to a specified amount. For more information, see 8. Memory Management. Note that creation of VxWorks processes involves no copying or sharing of the parent processes page frames (copy-on-write), as is the case with some versions of UNIX and Linux. The flat virtual memory model provided by VxWorks prohibits this approach and the overlapped virtual memory model does not currently support this feature. For information about the issue of inheritance of attributes from parent processes, see 2.2.7 RTPs, Inheritance, Zombies, and Resource Reclamation, p.13. For information about what operations are possible on a process in each phase of its instantiation, see the VxWorks API reference for rtpLib. Also see 3.3.7 Using Hook Routines, p.50. VxWorks processes can be started in the following ways:
interactively from the kernel shell interactively from the host shell and debugger automatically at boot time, using a startup facility programmatically from applications or the kernel
Form more information in this regard, see 3.6 Executing RTP Applications, p.54.
When the last task in the process exits. If any task in the process calls exit( ), regardless of whether or not other tasks are running in the process. If the process main( ) routine returns. This is because exit( ) is called implicitly when main( ) returns. An application in which main( ) spawns tasks can be written to avoid this behaviorand to allow its other tasks to continue operationby including a taskExit( ) call as the last statement in main( ). See 3.3 Developing RTP Applications, p.40.
If the kill( ) routine is used to terminate the process. If rtpDelete( ) is called on the processfrom a program, a kernel module, the C interpreter of the shell, or from Workbench. Or if the rtp delete command is used from the shells command interpreter. If a process takes an exception during its execution. This default behavior can be changed for debugging purposes. When the error detection and reporting facilities are included in the system, and they are set to debug mode, processes are not terminated when an exception occurs.
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 11. 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.13.
10
VxWorks processes can operate with two different virtual memory models: flat (the default) or overlapped (optional). The flat virtual-memory model provides advantages in execution speed, in a programming model that accommodates systems with and without an MMU, and in debugging applications. In this model each VxWorks process has its own region of virtual memory described by a unique range of addresses. The overlapped virtual-memory model provides more precise control over the virtual memory space and allows for notably faster application load time. With this model, each VxWorks process uses the same range of virtual addresses for the area where its code (text, data, and bss segments) resides. The overlapped virtual-memory model will not work unless VxWorks is configured with MMU support and the MMU is turned on. The two virtual memory models are mutually exclusive, and are described in more detail in 2.5 About VxWorks RTP Virtual Memory Models, p.23.
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 callwhether or not the MMU is enabled. However, if the MMU is not enabled, a process can read and write memory external to its own address space, and could cause the system to malfunction.
11
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 systembut 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 equivalent mechanisms, the number of processes is limited to that of the ASID (usually 255). For more information, see the VxWorks Architecture Supplement.
When a process is created, an initial task is spawned to begin execution of the application. The name of the processs initial task is based on the name of the executable file, with the following modifications:
The letter i is prefixed. The first letter of the filename capitalized. The filename extension is removed.
For example, when foobar.vxe is run, the name of the initial task is iFoobar. The initial task provides the execution context for the programs main( ) routine, which it then calls. The application itself may then spawn additional tasks.
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.
12
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 8.3 Heap and Memory Partition Management, p.272).
Inheritance
VxWorks processes inherit certain attributes of their parent. The child process inherits the file descriptors (stdin, stdout and stderr) of its parent processwhich 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.15). While the signal mask is not actually a property of a process as suchit is a property of a taskthe 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).
13
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.
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
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.)
14
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 objectswhich may be referenced by tasks running in other processes that continue to runmust 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 processeswhich inherit the descriptors from the parent processterminate. 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 6.9 Inter-Process Communication With Public Objects, p.140.
While a process is created without environment variables by default, they can be set from outside the process in the following ways:
If the new process is created by a kernel task, the contents of the kernel tasks environment array can be duplicated in the applications environment array. The the envGet( ) routine is used to get the kernel tasks environment, which is then used in the rtpSpawn( ) call. If the new process is created by a process, the child process can be passed the parents environment if the environment array is used in the rtpSpawn( ) call.
15
If the new process is created from the kernel shellusing either rtp exec command or rtpSp( ) routinethen all of the shell's environment is passed to the new process (the process envp is set using the shells environment variables). This makes it simple to set environment variables specifically for a process by first using putenv( ) to set the variable in the shells environment before creating the process. (For example, this method can be used to set the LD_LIBRARY_PATH variable for the runtime locations of shared libraries; see 4.8.7 Locating and Loading Shared Libraries at Run-time, p.83.)
For more information, see the rtpSpawn( ) API reference and 3.3.1 RTP Application Structure, p.42 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 directlyhowever, 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.
process creation with fork( ) and exec( ) memory-mapped files file ownership and file permissions
VxWorks can be configured to provide POSIX PSE52 support (for individual processes, as defined by the profile). For detailed information, see 2.3 Configuring
16
VxWorks For Real-time Processes, p.17, 7.1 Introduction, p.191, 7.2 Configuring VxWorks with POSIX Facilities, p.192, and 10.4.3 HRFS and POSIX PSE52, p.329.
2
17
Configuation With Process Support and Without an MMU, p.22 Setting Configuration Parameters for the RTP Code Region, p.33 4.4 Configuring VxWorks for Shared Libraries and Plug-ins, p.73.
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 Programmers Guide: Kernel.
contrast to other architectures, this support is not added automatically when VxWorks is configured with INCLUDE_RTP. For MIPS, VxWorks must be configured with a mapped kernel by adding the INCLUDE_MAPPED_KERNEL component. For more information in this regard, see the VxWorks Architecture Supplement. To create a system with processes, but without MMU support, the MMU components must be removed from the VxWorks configuration after the INCLUDE_RTP component has been added (for information in this regard see 2.4 Using RTPs Without MMU Support, p.20).
18
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
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_CLOCK 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 7.15 POSIX and VxWorks Scheduling, p.225.
INCLUDE_PROTECT_TASK_STACK for stack protection. For deployed systems this component may be omitted to save on memory usage. See 6.4.5 Task Stack, p.125 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. 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.
19
For information about the kernel shell, symbol tables, and show routines, see the VxWorks Kernel Programmers Guide: Target Tools. For information about the host shell, see the Wind River Workbench Host Shell Users Guide.
Component Bundles
The VxWorks configuration facilities provide component bundles to simplify the configuration process for commonly used sets of operating system facilities. The following component bundles are provided for process support:
BUNDLE_RTP_DEPLOY is designed for deployed systems (final products), and is composed of INCLUDE_RTP, INCLUDE_RTP_APPL, INCLUDE_RTP_HOOKS, INCLUDE_SHARED_DATA, and the BUNDLE_SHL components. BUNDLE_RTP_DEVELOP is designed for the development environment, and is composed of BUNDLE_RTP_DEPLOY, INCLUDE_RTP_SHELL_CMD, INCLUDE_RTP_SHOW, INCLUDE_SHARED_DATA_SHOW, INCLUDE_SHL_SHOW, INCLUDE_RTP_SHOW_SHELL_CMD, INCLUDE_SHL_SHELL_CMD, components. BUNDLE_RTP_POSIX_PSE52 provides POSIX PSE52 support for individual processes (for more information see 7.2 Configuring VxWorks with POSIX Facilities, p.192). It can be used with either BUNDLE_RTP_DEPLOY or BUNDLE_RTP_DEVELOP.
20
With this configuration, a software simulation-based memory page management library keeps track of identity mappings only. This means that there is no address translation, and memory page attributes (protection attributes and cache attributes) are not supported. !
CAUTION: VxWorks SMP does not support MMU-less configurations. For
information about VxWorks SMP (which does support RTPs), see the VxWorks Kernel Programmers Guide: VxWorks SMP. The advantages of a configuration without MMU support are that it:
Enables the process environment on systems without an MMU. It provides private namespace for applications, for building applications independently from the kernel, and for simple migration from systems without an MMU to those with one. Allows application code be run in non-privileged (user) mode. Under certain conditions, it may provide increased performance by eliminating overhead of the TLB miss and reload. This assumes, however, that there is no negative impact due to the changed cache conditions.
Depending on the processor type, BSP configuration, drivers and OS facilities used, disabling the MMU may require disabling the data cache as well. Disabling the data cache results in significant performance penalty that is much greater than the benefit derived from avoiding TLB misses. 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.
21
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. Change the SW_MMU_ENABLE parameter of the INCLUDE_MMU_BASIC component to TRUE (the default value is FALSE).
2.
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. Disable stack guard page protection by changing the
TASK_STACK_OVERFLOW_SIZE and TASK_STACK_UNDERFLOW_SIZE
4.
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
22
Speedcontext switching is fast. Ease of debuggingthe 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 2.4 Using RTPs Without MMU Support, p.20.)
With the flat virtual memory model, however, executable files must be relocated, which means that their loading time is slower than with the overlapped model. In addition, the flat model 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.
23
Faster loading time when applications are built as absolutely-linked executables. The relocation phase is skipped when absolutely-linked executables applications are loaded because they are all installed at the same virtual memory execution address. The improvement in loading times is on the order of 10 to 50 percent. Note that the improvement in loading time is dependent on the number of relocation entries in the file. More precise control of the system's virtual memory. The user selects the area in virtual memory into which the RTP application's text, data, and bss segments are installed. With the flat memory model, on the other hand, 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.
24
More efficient usage of the system's virtual memory by reducing the possibility of memory fragmentation. The memory footprint of absolutely-linked RTP executables stored in ROMFS is about only half that of relocatable executables once they (the absolutely-linked executables) are stripped of their symbols and relocation information. For information about using ROMFS and stripping executables, see 3.7 Bundling RTP Applications in a System using ROMFS, p.66 and Stripping Absolutely-Linked RTP Executables, p.36.
2
The overlapped virtual memory model does, however, require that the system make use of an MMU. !
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.
The VxWorks overlapped virtual memory model is similar to implementations for other operating systems (such as UNIX), but differs in the following ways:
For VxWorks the user-mode virtual address space is not predefined. For Windows, on the other hand, the operating system ensures that 2 GB (or 3 GB with some configuration changes) are reserved for a process. For Solaris, almost the entirety of the 4 GB address space is usable by a process. For Linux almost 3 GB of the address space is reserved for a process. A VxWorks process uses whatever is available and this can be different from target to target (see User Regions of Virtual Memory, p.27). The text, data, and bss segments of VxWorks applications are allocated together in memory. In other operating systems (such as UNIX) there is usually a separate allocation for each. This is significant only in the context of defining the size of the RTP code region so that it is large enough to hold the sum of the text, data, and bss segments, and not simply the text segment (see RTP Code Region in Virtual Memory, p.27). For Unix and Windows the concept of overlapped virtual address space for a process covers the whole 4 GB of the address range and the address space
25
organization is set in advance. In particular shared libraries are mapped differently in each process but usually go in a pre-defined and reserved area of the address space. In VxWorks only the RTP code region and the private elements of an RTP are set in address ranges that are overlapped. The shared libraries and shared data regions are not overlapped and appear at the same virtual address in all processes; furthermore they can use any place in the user regions except for the area covered by the RTP code region (this cannot be controlled by the user). A process that does not use shared libraries still has a portion of its address space unavailable if any other process uses shared libraries and or shared data regions.
26
2 Real-Time Processes 2.6 Using the Overlapped RTP Virtual Memory Model
The virtual address space of a process in 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.
Figure 2-1 Virtual Memory and User Regions
I/O Region 2
User Region 2
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
27
one of the user regions available in the target systems memory. The user region that is selected 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.
RTP Code Region Example
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.
Figure 2-2 Virtual Memory Layout With RTP Code Region
RTP B
RTP C
Heap
User Region 2
(Reserved)
Shared Data 1
VxWorks has been configured to use 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 (for example, it might be used for a large shared data region).
28
2 Real-Time Processes 2.6 Using the Overlapped RTP Virtual Memory Model
Note that 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.5 Creating and Using Shared Data Regions, p.51). 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.33.
2
2. 3.
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 which is by default configured for the flat memory model), and get a listing of the user regions that are available on the target.
29
CAUTION: For the MIPS architecture, in order to get correct information about user
regions, the system must be configured with MMU support. In contrast to other architectures, this support is not provided automatically when VxWorks is configured with INCLUDE_RTP. For MIPS, VxWorks must be configured with a mapped kernel by adding the INCLUDE_MAPPED_KERNEL component. For more information in this regard, see the VxWorks Architecture Supplement. The adrSpaceShow( ) kernel shell command (in verbose mode) can be used to list user regions. For example:
-> adrSpaceShow 1 RAM Physical Address Space Info: ------------------------------Allocation unit size: Total number of units: Number of allocated units: Largest contiguous free block: Number of free units: 1 block(s) of 0x0108a000
0x1000 16384 (67108864 bytes) 12150 (49766400 bytes) 17342464 4234 (17342464 bytes) bytes (0x02f70000-0x03ff9fff)
User Region (RTP/SL/SD) Virtual Space Info: ------------------------------------------Allocation unit size: 0x1000 Total number of units: 851968 (3489660928 bytes) Number of allocated units: 0 (0 bytes) Largest contiguous free block: 3221225472 Number of free units: 851968 (3489660928 bytes) 1 block(s) of 0xf000000 bytes (0x10000000-0x1effffff) 1 block(s) of 0xc0000000 bytes (0x30000000-0xefffffff) Kernel Region Virtual Space Info: --------------------------------Allocation unit size: Total number of units: 1 block(s) of 0x04000000 1 block(s) of 0x0c000000 1 block(s) of 0x08000000 1 block(s) of 0x08000000 1 block(s) of 0x04000000 1 block(s) of 0x01000000 1 block(s) of 0x05000000 1 block(s) of 0x01010000 1 block(s) of 0x00fe0000 1 block(s) of 0x00050000 1 block(s) of 0x00fc0000 1 block(s) of 0x01810000 1 block(s) of 0x00770000 1 block(s) of 0x00010000 1 block(s) of 0x00060000 1 block(s) of 0x00010000 1 block(s) of 0x01000000
0x1000 196608 bytes bytes bytes bytes bytes bytes bytes bytes bytes bytes bytes bytes bytes bytes bytes bytes bytes
(805306368 bytes) (0x00000000-0x03ffffff) (0x04000000-0x0fffffff) (0x20000000-0x27ffffff) (0x28000000-0x2fffffff) (0xf0000000-0xf3ffffff) (0xf4000000-0xf4ffffff) (0xf5000000-0xf9ffffff) (0xfa000000-0xfb00ffff) (0xfb010000-0xfbfeffff) (0xfbff0000-0xfc03ffff) (0xfc040000-0xfcffffff) (0xfd000000-0xfe80ffff) (0xfe810000-0xfef7ffff) (0xfef80000-0xfef8ffff) (0xfef90000-0xfefeffff) (0xfeff0000-0xfeffffff) (0xff000000-0xffffffff)
30
2 Real-Time Processes 2.6 Using the Overlapped RTP Virtual Memory Model
value = 0 = 0x0
This output shows that the following two user regions are available on this target:
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:
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. The RTP code region must fit in one user regionit cannot span multiple user regions. 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 mappingssuch as those created by mmap( )that are going to be used in the system. For simplicity sake, set the base address of the RTP code region to the base address of a user region.
CAUTION: If the overlapped virtual memory model is selected, but the base
address and size are not defined, or if the size is too small, absolutely-linked executables will be relocated. If the executables are stripped, however, they cannot be relocated and the RTP launch will fail.
RTP Code Region Size
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
31
example, readelfppc), and round up to one page for each of the segments sizes displayed in the MemSiz fields. 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. 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.
User Region Choice
The RTP code region cannot span user regions. It must fit in one user region. The RTP code region and public mappings are mutually exclusive because the RTP code region is intended to receive 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 be a 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.
RTP Code Region Base Address Choice
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 can be checked via the vmPageSizeGet( ) routine, which can be called directly from the target shell.
RTP Code Region Size
When determining the size of the RTP code region, make sure that it is page-aligned. The page size for the target architecture can be checked via the vmPageSizeGet( ) routine, which can be called directly from the target shell.
32
2 Real-Time Processes 2.6 Using the Overlapped RTP Virtual Memory Model
By default, VxWorks is configured for the flat virtual memory model. In order to use the overlapped memory model, the configuration parameters listed below must be set appropriately. They serve to select the overlapped virtual memory model itself, and to define the RTP code region in virtual memory. !
CAUTION: The overlapped virtual memory model requires MMU support, which
is provided by default configurations of VxWorksexcept for the MIPS architecture. For MIPS, MMU protection requires a mapped kernel, which is provided by INCLUDE_MAPPED_KERNEL component. This component is not included by default and must be added to the kernel configuration. For more information in this regard, see the VxWorks Architecture Supplement.
RTP Code Region Component Parameters
For information about determining the virtual memory addresses that you need for setting the RTP_CODE_REGION_START and RTP_CODE_REGION_SIZE parameters, see 2.6 Using the Overlapped RTP Virtual Memory Model, p.26, and specifically 2.6.1 About User Regions and the RTP Code Region, p.26.
RTP_OVERLAPPED_ADDRESS_SPACE Set to TRUE to change the virtual memory model from flat to overlapped. By default it is set to FALSE. The following parameters have no effect unless this one is set to TRUE. RTP_CODE_REGION_START
Identifies the virtual memory address within a user region where the RTP code region will start. That is, the base address of the memory area into which the text, data, and bss segments of RTP applications will be installed. 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. For more information, see Identifying the RTP Code Region, p.31. Note that the VxWorks kernel is usually located in low memory addresses except when required by the architecture (MIPS for instance). In that case, the user mode virtual address space appears higher in the address range. This is significant, as changing the configuration of the kernel (adding components or devices) may have an impact on the base addresses of the available user regions and may therefore impact the base address and size of the RTP code region.
33
RTP_CODE_REGION_SIZE
The size (in bytes) of the virtual memory area into which the text, data, and bss segments of RTP applications will be installed. Select a size that is slightly larger than the combined size of the text, data, and bss segments of the largest RTP application that will be run on the system. Make sure that the size is page-aligned. For more information, see Identifying the RTP Code Region, p.31. An RTP code region is defined for a system when
RTP_OVERLAPPED_ADDRESS_SPACE is set to TRUE and both RTP_CODE_REGION_START and RTP_CODE_REGION_SIZE are set to values
appropriately. If they are not defined, or if the size of the region is too small, absolutely-linked executables will be relocated to another user region if there is sufficient space. If not, the RTP launch will fail. In addition, if the executables are stripped, they cannot be relocated regardless of the availability of space in other user regions, and the RTP launch will fail.
Setting RTP Code Region Parameters for Multiple Projects
Once the settings for the RTP_OVERLAPPED_ADDRESS_SPACE, RTP_CODE_REGION_START and RTP_CODE_REGION_SIZE parameters have been determined, configuration for multiple projects can be automated by creating a custom component description file (CDF) file containing those settings. Using a CDF file means that the parameters do not have to be set manually each time a project is created (either with Workbench or vxprj). If the file is copied into a project directory before it is built, it applies the settings to that project. If it is copied into the BSP directory before the project is created, it applies to all projects subsequently based on that BSP. The contents of the local CDF file (called, for example, 00rtpCodeRegion.cdf) would look like this:
34
2 Real-Time Processes 2.6 Using the Overlapped RTP Virtual Memory Model
Parameter RTP_OVERLAPPED_ADDRESS_SPACE { DEFAULT TRUE } 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 Kernel Programmers Guide: Kernel Customization.
In order to take advantage of the efficiencies provided by the overlapped virtual memory model, RTP applications must be built as absolutely-linked executables. This is done by defining the link address with a special linker option. The option can be used directly with the Wind River or GNU toolchain, or indirectly with 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, of course).
Selecting the Link Address
While technically the link address of the RTP executable could 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 RTP_CODE_REGION_START configuration parameter; see Setting Configuration Parameters for the RTP Code Region, p.33).
35
Linker Options
The following linker options must be used to generate executable with a pre-determined link address (the base address of the text segment):
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 GUI options for defining the base address and the command-line build environment provides a make macro for doing the same.
RTP_LINK_ADDR Make Macro
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
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. This information can be stripped out of the executable using the striparch utility with the -s option. The resulting file is notably smaller (on average 30%-50%). This make its footprint in ROMFS noticeably smaller (if the executables are stored in ROMFS, this can reduce overall system footprint), as well as making its load time somewhat shorter. Note, however, that stripping symbols and relocation sections from an absolutely-linked RTP executable means that it cannot be loaded if the predetermined execution address cannot be granted by the system. This may occur under the following circumstances:
36
2 Real-Time Processes 2.6 Using the Overlapped RTP Virtual Memory Model
The RTP code region is too small for the text, data, and bss segments of the executable. The executable file has been generated for an execution address that does not correspond to the system's current RTP code region. The absolutely-linked executable file is used on a system configured for the flat virtual memory model.
2
Note that it may be useful to leave the symbolic and relocation information in executable file situations for situations in which the execution environment may change. For example, if a deployed system is updated with a new configuration of VxWorks for which the existing applications execution addresses are no longer valid (but the applications cannot be updated at the same) the applications suffer the cost of relocation, but still execute. If, however, the applications had been stripped, would be unusable.
Absolutely-linked executables can be started in the same ways as relocatable executables (for information in this regard, see 3.6 Executing RTP Applications, p.54). The load time of absolutely-linked executables, however, is noticeably shorter because they are not relocated. Their text, data, and bss segments are installed according to the link address defined when they are compiled. 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. However, 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).
37
Relocatable executables are supported when the RTP address space is overlapped. They are relocated as usual, 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.
38
3
RTP Applications
3.1 Introduction 39 3.2 Configuring VxWorks For RTP Applications 40 3.3 Developing RTP Applications 40 3.4 Developing Static Libraries, Shared Libraries and Plug-Ins 50 3.5 Creating and Using Shared Data Regions 51 3.6 Executing RTP Applications 54 3.7 Bundling RTP Applications in a System using ROMFS 66
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 executionthat is, as processes. For information
39
about RTP scheduling, creation and termination, memory, tasks and so on, see 2. Real-Time Processes. For information about using Workbench and the command-line build environment for developing RTP applications, see the Wind River Workbench by Example guide and the VxWorks Command-Line Tools Users Guide, respectively. For information about developing kernel-mode applications (which execute in VxWorks kernel space) see the VxWorks Kernel Programmers Guide: Kernel Applications.
system, the build process cannot determine if the instance of VxWorks on which the application will eventually run has been configured with all of the components that the application requires. It is, therefore, important for application code to check for errors indicating that kernel facilities are not available and to respond appropriately. For more information, see 3.3.6 Checking for Required Kernel Support, p.49.
40
executable is produced. By convention, VxWorks RTP executables are named with a .vxe file-name extension. The extension draws on the vx in VxWorks and the e in executable to indicate the nature of the file. Applications are created as either fully-linked or partially linked executables using cross-development tools on a host system. (Partially-linked executables are used with shared libraries.) They can be either relocatable or absolutely-linked objects depending on the virtual memory model in use on the VxWorks system. By default RTP applications are created as relocatable executables so as to be usable with either the flat virtual memory model or the overlapped virtual memory model. They can also be generated absolutely-linked to fully take advantage of the overlapped virtual memory model (for information about RTP virtual memory models, see 2.5 About VxWorks RTP Virtual Memory Models, p.23). During development, processes can be spawned to execute applications from the VxWorks shell or various host tools. Applications can also be started programmatically, and systems can be configured to start applications automatically at boot time for deployed systems. For systems with multiple applications, not all must be started at boot time. They can be started later by other applications, or interactively by users. Developers can also implement their own application startup managers. A VxWorks application can be loaded from any file system for which the kernel has support (NFS, ftp, and so on). RTP executables can be stored on disks, in RAM, flash, or ROM. They can be stored on the target or anywhere else that is accessible over a network connection. In addition, applications can be bundled into a single image with the operating system using the ROMFS file system (see 3.7 Bundling RTP Applications in a System using ROMFS, p.66). 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.
3
For information about the special requirements of applications that use shared libraries and plug-ins, see 4.8.9 Developing RTP Applications That Use Shared Libraries, p.88 and 4.9.3 Developing RTP Applications That Use Plug-Ins, p.95.
41
Relocatable RTP application executables (the default) can be run on a system that has been configured for the overlapped virtual memory model. However, to take advantage of this model, RTP applications must be built as absolutely-linked executables. For more information, see 2.6.3 Using RTP Applications With Overlapped RTP Virtual Memory, p.35
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 and be compiled specifically for the system in question (SMP or UP). Among other things, this means that the RTP application must do the following in order to run on both VxWorks UP and VxWorks SMP systems:
Use semaphores or another mechanism supported for SMP instead of taskRtpLock( ). Use the __thread storage class instead of tlsLib routines.
For more information about using the SMP configuration of VxWorks, and about migrating applications from VxWorks UP to VxWorks SMP, see the VxWorks Kernel Programmers Guide: VxWorks SMP.
For information about migrating VxWorks kernel applications to RTP applications, see A. Kernel to RTP Application Migration.
42
int main ( int argc, char * argv[], char * envp[], void * auxp );
/* /* /* /*
number of arguments */ null-terminated array of argument strings */ null-terminated array of environment variable strings */ implementation specific auxiliary vector */
The envp and auxp arguments are usually not required by the application code. The envp argument is used for passing VxWorks environment variables to the application. These can be set by a user and are typically inherited from the calling environment. Note that the getenv( ) routine can be used to get the environment variables programmatically, 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.15.) Environment variables are general properties of the running system, such as the default pathunlike argv arguments, which are passed to a particular invocation of the application, and are unique to that application. The system uses the auxp vector to pass system information to the new process, including page size, cache alignment size and so on. The argv[0] argument is typically the relative path to the executable.
43
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 7.4 Standard C Library: libc, p.198 for more information.
It is often useful to include header file vxWorks.h in all application modules in order to take advantage of architecture-specific VxWorks facilities. Many other VxWorks header files require these definitions. Include vxWorks.h with the following line:
#include <vxWorks.h>
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.
44
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 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.)
45
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.
46
Because kernel mode and user mode have different instruction sets and MMU settings, RTP applicationswhich run in user modecannot 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 Programmers Guide: Kernel Customization.
Monitoring System Calls
The VxWorks kernel shell provides facilities for monitoring system calls. For more information, see the VxWorks Kernel Programmers Guide: Target Tools, the syscall monitor entry in 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
47
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.
Dinkum C and C++ librariesincluding embedded (abridged) C++ librariesare provided for VxWorks RTP application development. For more information about these libraries, see the Dinkum API references. The VxWorks distribution also provides a C run-time shared library feature that is similar to that of the UNIX C run-time shared library. For information about this library, see 4.10 Using the VxWorks Run-time C Shared Library libc.so, p.100. For more information about C++ development, see 5. C++ Development.
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.
48
information about absolutely-linked RTP executables and the overlapped virtual memory model, see 2.5 About VxWorks RTP Virtual Memory Models, p.23 and 2.6 Using the Overlapped RTP Virtual Memory Model, p.26.
3
the priorities of tasks running in all the different processes any task synchronization requirements between processes as well as within processes
For information about task priorities and synchronization, see 6.2 Tasks and Multitasking, p.110 and 6.8 Intertask and Interprocess Communication, p.139.
49
50
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 applicationskernel tasks may also perform these operations. Shared data regions use memory resources from both the kernels and the applications 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.
51
Shared data regions can be created in systems with and without MMU support. Also see 6.11 Shared Data Structures, p.142 and 7.19.3 Shared Memory Objects, p.263
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.53.
52
The SD_LINGER creation option provides for the persistence of the region after all processes have unmapped from itthe 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.
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 callers process; they do not affect the permissions of other processes. A set of macros is provided with the library for common sets of MMU attribute combinations.
53
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.
Executables that have been stripped of their relocation information will not run on a system configured with the flat virtual memory model (the default). The launch will failsilently if initiated from the shells C interpreter. The error detection and reporting facility can be used to display the reason for failure, as follows (with output abbreviated for purposes of clarity):
-> edrShow [...] rtpLoadAndRun(): RTP 0x1415010 Init Task exiting. errno = 0xba006e [...] -> printErrno 0xba006e errno = 0xba006e : S_loadRtpLib_NO_RELOCATION_SECTION.
54
Executables should only be stripped of their relocation information if they are built as absolutely-linked executables and run on a system that is properly configured for the overlapped virtual memory model. For information about these topics, see 2.5 About VxWorks RTP Virtual Memory Models, p.23 and 2.6 Using the Overlapped RTP Virtual Memory Model, p.26; 3.3.4 Reducing Executable File Size With the strip Facility, p.48; and 11. Error Detection and Reporting.
3
An RTP application can be started and terminated interactively, programmatically, and automatically with various facilities that act on processes. An application can be started by:
a user from Workbench a user from the shell or debugger with rtpSp (for the shell C interpreter) or rtp exec (for the shell command interpreter) other applications or from the kernel with rtpSpawn( ) one of the startup facilities that runs applications automatically at boot time
For more information, see 3.6.1 Running Applications Interactively, p.57 and 3.6.2 Running Applications Automatically, p.58.
RTP applications terminate automatically when the programs main( ) routine returns. They can also be terminated explicitly.
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.
55
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).
Terminating processeseither programmatically or by interactive user commandcan be used as a means to update or replace application code. Once the process is stopped, the application code can be replaced, and the process started again using the new executable.
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. RTP executable files are accessed and loaded from the VxWorks target. Therefore, the path to the executable file must be valid from the point of view of the target itself. Correctly specifying the path may involve including the proper device name as part of the path. For example: host:d:/my.vxe
56
Starting Applications
From the shell, applications can be started with shell command variants of the rtpSpawn( ) routine. Using the traditional C interpreter, the rtpSp command is used as follows:
rtpSp "host:c:/myInstallDir/vxworks-6.1/target/usr/root/PPC32diab/bin/myVxApp.vxe first second third"
In this example, a process is started to run the application myVxApp.vxe, which is stored on the host system in c:\myInstallDir\vxworks-6.x\target\usr\root\PPC32diab\bin. The application takes command-line arguments, and in this case they are first, second, and third. Additional arguments can also be used to specify the initial task priority, stack size, and other rtpSpawn( ) options. Note that some types of connections between the target and host require modifiers to the pathname (NFS is transparent; FTP requires hostname: before the path if it is not on the same system from which VxWorks was booted; the VxWorks simulator requires a host: prefix; and so on). Using the shells command interpreter, the application can be started in two different ways, either directly specifying the path and name of the executable file and the arguments (like with a UNIX shell):
host:c:/myInstallDir/vxworks-6.1/target/usr/root/PPC32diab/bin/myVxApp.vxe first second third
Or, the application can be started with the rtp exec command:
rtp exec host:c:/myInstallDir/vxworks-6.1/target/usr/root/PPC32diab/bin/myVxApp.vxe first second third
Note that you must use forward-slashes as path delimiters with the shell, even for files on Windows hosts. The shell does not work with back-slash delimiters. Regardless of how the process is spawned, the application runs in exactly the same manner.
57
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
An application can be stopped by terminating the process in which it is running. Using the shells command interpreter, a process can be killed with the full rtp delete command, or with either of the command shell aliases kill and rtpd. It can also be killed with CTRL+C if it is running in the foreground (that is, it has not been started using an ampersand after the rtp exec command and the name of the executablewhich is similar to UNIX shell command syntax for running applications in the background). With the shells C interpreter, a process can be terminated with kill( ) or rtpDelete( ). For a description of all the ways in which a process can be terminated, see 2.2.3 RTP Termination, p.10. And, of course, rebooting the system terminates all processes that are not configured to restart at boot time.
58
ignorant of the applications that it will run until the moment it boots and starts them. One might call this a blind-date scenario. For deployed systems, VxWorks can be configured and built with statically defined sets of applications to run at boot time (including their arguments and process-spawn parameters). The applications can also be built into the system image using the ROMFS file system. And this scenario might be characterized as most matrimonial. In this section, use of the startup facility is illustrated with applications that reside on the host system. For information about using ROMFS to bundle applications with the operating system, and for examples illustrating how applications in the ROMFS file system are identified for the startup facility, see 3.7 Bundling RTP Applications in a System using ROMFS, p.66.
3
Various means can be used to identify applications to be started, as well as to provide their arguments and process-spawn parameters for the initial application task. Applications can be identified and started automatically at boot time using any of the following:
an application startup configuration parameter a boot loader parameter a VxWorks shell script the usrRtpAppInit( ) routine
INCLUDE_RTP_APPL_USER
The boot loader parameter and the shell script methods can be used both interactively (without modifying the operating system) and statically. Therefore, they are equally useful for application development, and for deployed systems. The startup configuration parameter and the usrRtpAppInit( ) routine methods require that the operating system be re-configured and rebuilt if the developer
59
wants to change the set of applications, application arguments, or process-spawn parameters. There are no speed or initialization-order differences between the various means of automatic application startup. All of the startup facility components provide much the same performance.
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 requiredspaces can be used insteadwith the startup configuration parameter, but carets must be used with the boot loader parameter. The following examples illustrate basic syntax usage:
#c:/apps/myVxApp.vxe
Starts c:\apps\myVxApp.vxe
#c:/apps/myVxApp.vxe^one^two^three
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:
60
%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, the code is preprocessed before compilation, so symbolic constants may be used as well (for example, VX_FP_TASK). The following string, for example, specifies starting c:\apps\myVxApp.vxe with the arguments one, two, three, and an initial task priority of 125; and also starting c:\apps\myOtherVxApp.vxe with the options value 0x10 (which is to stop the process before running in user mode):
#c:/apps/myVxApp.vxe p=125^one^two^three#c:/apps/myOtherVxApp.vxe %o=0x10
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.
Applications can be specified with the RTP_APPL_INIT_STRING parameter of the INCLUDE_RTP_APPL_INIT_STRING component. The identification string must use the syntax described in Application Startup String Syntax, p.60. And the operating system must be rebuilt thereafter.
61
The VxWorks boot loader includes a parameterthe s parameterthat can be used to identify applications that should be started automatically at boot time, as well as to identify shell scripts to be executed.1 (For information about the boot loader, see the VxWorks Kernel Programmers Guide: Boot Loader.) Applications can be specified both interactively and statically with the s parameter. In either case, the parameter is set to the path and name of one or more executables and their arguments (if any), as well as to the applications process-spawn parameters (optionally). The special syntax described above is used to describe the applications (see Application Startup String Syntax, p.60). This functionality is provided with the INCLUDE_RTP_APPL_BOOTLINE component. Note that the boot loader s parameter serves a dual purpose: to dispatch script file names to the shell, and to dispatch application startup strings to the startup facility. Script files used with the s parameter can only contain C interpreter commands; they cannot include startup facility syntax (also see Specifying Applications with a VxWorks Shell Script, p.63). If the boot parameter is used to identify a startup script to be run at boot time as well as applications, it must be listed before any applications. For example, to run the startup script file myScript and myVxApp.vxe (with three arguments), the following sequence would be required:
myScript#c:/apps/myVxApp.vxe^one^two^three
The assignment in the boot console window would look like this:
startup script (s) : myScript#c:/apps/myVxApp.vxe^one^two^three
The interactively-defined boot-loader parameters are saved in the targets boot media, so that the application is started automatically with each reboot. For the VxWorks simulator, the boot parameter assignments are saved in a special file on the host system, in the same directory as the image that was booted, for example, installDir/vxworks-6.x/target/proj/simpc_diab/default/nvram.vxWorks0. The number appended to the file name is processor ID numberthe default for the first instance of the simulator is zero.
1. In versions of VxWorks 5.x, the boot loader s parameter was used solely to specify a shell script.
62
For a hardware target, applications can be identified statically. The DEFAULT_BOOT_LINE parameter of the INCLUDE_RTP_APPL_BOOTLINE component can be set to an identification string using the same syntax as the interactive method. Of course, the operating system must be rebuilt thereafter.
Applications can be started automatically with a VxWorks shell script. Different methods must be used, however, depending on whether the shell script uses command interpreter or C interpreter commands. If the shell script is written for the command interpreter, applications can be identified statically. The RTP_APPL_CMD_SCRIPT_FILE parameter of the INCLUDE_RTP_APPL_INIT_CMD_SHELL_SCRIPT component can be set to the location of the shell script file. A startup shell script for the command interpreter might, for example, contain the following line:
rtp exec c:/apps/myVxApp.vxe first second third
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 applicationsusing 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_BOOTLINE component. (See Specifying Applications with a Boot Loader Parameter, p.62 and Application Startup String Syntax, p.60.) 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 Programmers Guide: Target Tools). A startup shell script file for the C interpreter could contain the following line:
rtpSp "c:/apps/myVxApp.vxe 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
63
Note that shell scripts can be stored in ROMFS for use in deployed systems (see 3.7 Bundling RTP Applications in a System using ROMFS, p.66).
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) { char * vxeName = "c:/vxApps/myVxApp/PPC32diab/myVxApp.vxe"; char * argv[5]; RTP_ID rtpId = NULL; /* set the application's arguments */ argv[0] argv[1] argv[2] argv[3] argv[4] = = = = = vxeName; "first"; "second"; "third"; NULL;
/* Spawn the RTP. No environment variables are passed */ if ((rtpId = rtpSpawn (vxeName, argv, NULL, 220, 0x10000, 0)) == NULL) { printErr ("Impossible to start myVxApp application (errno = %#x)", errno); } }
Note that in this example, the myVxApp.vxe application executable is stored on the host system in c:\vxApps\myVxApp\PPC32diab. The executable could also be stored in ROMFS on the target system, in which case the assignment statement that identifies the executable would look like this:
char * vxeName = "/romfs/myVxApp.vxe";
For information about bundling applications with the system image in ROMFS, see 3.7 Bundling RTP Applications in a System using ROMFS, p.66.
64
When an application is started from the shell, symbols are registered automatically, as is most convenient for a development environment. When an application is started programmaticallythat is, with a call to rtpSpawn( )symbols are not registered by default. This saves on memory at startup time, which is useful for deployed systems.
The registration policy for a shared library is, by default, the same as the one for the application that loads the shared library. The default symbol-registration policy for a given method of starting an application can be overridden, whether the application is started interactively or programmatically. The shells command interpreter provides the rtp exec options g for global symbols, -a for all symbols (global and local), and z for zero symbols. For example:
rtp exec -a /folk/pad/tmp/myVxApp/ppc/myVxApp.vxe one two three &
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.
65
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 shells 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/myApp.vxe rtp symbols remove l s 0x10000 rtp symbols help
Note that when the flat virtual memory model is in use symbols should not be stripped from executable files (.vxe files) because they are relocatable. It is possible to strip symbols from absolutely-linked executable files (intended to be used with the overlapped virtual memory model) because they are not relocated but it makes debugging them more difficult. The same apply to run-time shared library files (.so files)
66
3.6 Executing RTP Applications, p.54), ROMFS provides the ability to create fully autonomous, multi-process systems. This section provides information about using ROMFS to store process-based applications with the VxWorks operating system in a single system image. For general information about ROMFS, see 10.8 Read-Only Memory File System: ROMFS, p.359.
3
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.
67
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, myVxApp.vxe copied to it, and the system rebuilt and booted, then using ls from the shell looks like this:
[vxWorks]# ls /romfs /romfs/. /romfs/.. /romfs/myVxApp.vxe
And myVxApp.vxe can also be accessed at run time as /romfs/myVxApp.vxe by any other applications running on the target, or by kernel modules (kernel-based applications).
68
4
Static Libraries, Shared Libraries, and Plug-Ins
4.1 Introduction 70 4.2 About Static Libraries, Shared Libraries, and Plug-ins 70 4.3 Additional Documentation 73 4.4 Configuring VxWorks for Shared Libraries and Plug-ins 73 4.5 Common Development Issues: Initialization and Termination 74 4.6 Common Development Facilities 78 4.7 Developing Static Libraries 78 4.8 Developing Shared Libraries 79 4.9 Developing Plug-Ins 94 4.9.4 Debugging Plug-Ins 99
69
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.
Only the elements of a static library that are required by an application (that is, specific .o object files within the archive) are linked with the application. The entire library does not necessarily become part of the system. If multiple applications (n number) in a system use the same library elements, however, those elements are duplicated (n times) in the systemin both the storage media and system memory. The dynamic linker loads the entire shared library when any part of it is required by an application. (As with a .o object file, a shared library .so file is an indivisible unit.) If multiple applications in a system need the shared library, however, they share a single copy. The library code is not duplicated in the system.
70
4 Static Libraries, Shared Libraries, and Plug-Ins 4.2 About Static Libraries, Shared Libraries, and Plug-ins
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.8.9 Developing RTP Applications That Use Shared Libraries, p.88 and 4.9.3 Developing RTP Applications That Use Plug-Ins, p.95.
Both dynamic shared objectsShared libraries and plug-inscan provide advantages of footprint reduction, flexibility, and efficiency, as follows (shared library is used to refer to both here, except where plug-in is used specifically):
The storage requirements of a system can be reduced because the applications that rely on a shared library are smaller than if they were each linked with a static library. Only one set of the required library routines is needed, and they are provided by the run-time library file itself. The extent to which shared libraries make efficient use of mass storage and memory depends primarily on how many applications are using how much of a shared library, and if the applications are running at the same time. Plug-ins provide flexibility in allowing for dynamic configuration of applicationsthey are loaded only when needed by an application (programmatically on demand). Shared libraries are efficient because their code requires fewer relocations than standard code when loaded into RAM. Moreover, lazy binding (also known as lazy relocation or deferred binding) allows for linking only those functions that are required.
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.87 and Using Lazy Binding With Plug-ins, p.96.)
71
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 linkers 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.
72
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).
CAUTION: There is no support for so-called far PIC on PowerPC. Some shared libraries require the global offset table to be larger than 16,384 entries; since this is greater than the span of a 16-bit displacement, specialized code must be used to support such libraries.
Drepper, Ulrich. How to Write Shared Libraries. Red Hat, Inc. 2006. Jelinek, Jakub. Prelink. Red Hat, Inc. 2003.
INCLUDE_RTP, the main component for real-time process support INCLUDE_SHARED_DATA for storing shared library code INCLUDE_RTP_HOOKS for shared library initialization
73
It can also be useful to include support for relevant show routines with these components:
Note that if you use the INCLUDE_SHOW_ROUTINES component, the three above are automatically added. Configuration can be simplified through the use of component bundles. BUNDLE_RTP_DEVELOP and BUNDLE_RTP_DEPLOY provide support for shared libraries for the development systems and for deployed systems respectively (for more information, see Component Bundles, p.20). For general information about configuring VxWorks for real-time processes, see & 2.3 Configuring VxWorks For Real-time Processes, p.17.
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.
74
4 Static Libraries, Shared Libraries, and Plug-Ins 4.5 Common Development Issues: Initialization and Termination
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 librarys (or plug-ins) 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 */ }
The following example is of a routine that creates a mutex semaphore used to protect a scarce resource, which may be used in a transparent manner by various features of the application.
_WRS_CONSTRUCTOR (scarceResourceInit , 101) { /* * Note: a FIFO mutex is preferable to a priority-based mutex * since task priority should not play a role in accessing the scarce * resource. */ if ((scarceResourceMutex = semMCreate (SEM_DELETE_SAFE | SEM_Q_FIFO | SEM_USER)) == NULL) EDR_USR_FATAL_INJECT (FALSE, "Cannot enable task protection on scarce resource\n"); }
(For information about using the error detection and reporting macro EDR_USR_FATAL_INJECT, see 11.7 Using Error Reporting APIs in Application Code, p.374.) 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 usednumbers 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.
75
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).
Check whether the ENOSYS errno has been set, and respond appropriately. For system calls, this errno indicates that the required support component has not been included in the kernel. Release all the resources that have been created or obtained by the initialization routine. Use the EDR_USER_FATAL_INJECT macro to report the error. If the system has been configured with the error detection and reporting facility, the error is recorded in the error log (and the system otherwise responds to the error depending on how the facility has been configured). If the system has not been 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:
76
4 Static Libraries, Shared Libraries, and Plug-Ins 4.5 Common Development Issues: Initialization and Termination
if (mutex = semMCreate (SEM_Q_PRIORITY | SEM_INVERSION_SAFE)) == NULL) { EDR_USR_FATAL_INJECT (FALSE, "myLib: cannot create mutex. Abort."); }
For more information, see 11.7 Using Error Reporting APIs in Application Code, p.374.
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.
77
Use Wind River Workbench. All of the build-related elements are created automatically as part of creating library and application projects. For information in this regard, see the Wind River Workbench by Example guide. Use the make build rules and macros provided with the VxWorks installation to create the appropriate makefiles, and execute the build from the command line. For information in this regard, see the VxWorks Command-Line Tools Users Guide. Write makefiles and rules from scratch, or make use of a custom or proprietary build environment. For information in this regard, see the VxWorks Command-Line Tools Users Guide.
78
4 Static Libraries, Shared Libraries, and Plug-Ins 4.8 Developing Shared Libraries
Dynamic Linker
An application that is built as a dynamic executable contains a dynamic linker library that provides code to locate, read and edit dynamic shared objects at run-time (unlike UNIX, in which the dynamic linker is itself a shared library). The dynamic linker contains a constructor function that schedules its initialization at a very early point in the execution of process (during its instantiation phase). It reads a list of shared libraries and other information about the executable file and uses that information to make a list of shared libraries that it will load. As it reads each
79
shared library, it looks for more of this dynamic information, so that eventually it has loaded all of the code and data that is required by the program and its libraries. The dynamic linker makes special arrangements to share code between processes, placing shared code in a shared memory region. The dynamic linker allocates its memory resources from shared data regions and additional pages of memory allocated on demandand not from process memoryso that the use of process memory is predictable.
Dynamic shared objects are compiled in a special way, into position-independent code (PIC). This type of code is designed so that it requires relatively few changes to accommodate different load addresses. A table of indirections called a global offset table (GOT) is used to access all global functions and data. Each process that uses a given dynamic shared object has a private copy of the librarys GOT, and that private GOT contains pointers to shared code and data, and to private data. When PIC must use the value of a variable, it fetches the pointer to that variable from the GOT and de-references it. This means that when code from a shared object is shared across processes, the same code can fetch different copies of the analogous variable. The dynamic linker is responsible for initializing and maintaining the GOT.
80
4 Static Libraries, Shared Libraries, and Plug-Ins 4.8 Developing Shared Libraries
The name of a shared libraryits shared object namemust initially be defined when the shared library itself is built. This creates an ELF SONAME record with the shared object name in the librarys 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 librarys SONAME record. One NEEDED record is created for each shared library against which the application is linked. The applications 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.83) 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.81 and 4.8.9 Developing RTP Applications That Use Shared Libraries, p.88. For examples of displaying ELF records (including SONAME and NEEDED), see Using readelf to Examine Dynamic ELF Files, p.90.
4
81
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, libName.so.1). The compilers -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.82.
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 libMyFoo.so.1, which is also thereby defined as version one of the library created with the output file libMyFoo.so:
dplus -tPPCEH:rtp -Xansi -XO -DCPU=PPC32 -DTOOL_FAMILY=diab -DTOOL=diab -Xpic -Xswitch-table-off -Wl, -Xshared -Wl, -Xdynamic -soname=libMyFoo.so.1 -L$(WIND_BASE)/target/usr/lib/ppc/PPC32/common/PIC -lstlstd PPC32diab/foo1.sho PPC32diab/foo2.sho -o libMyFoo.so
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.90.
82
4 Static Libraries, Shared Libraries, and Plug-Ins 4.8 Developing Shared Libraries
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 libMyFoo.so.1, then you would build the new version with the soname libMyFoo.so.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 libMyFoo.so.1, libMyFoo.so.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.81.
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.84. 2. The configuration file ld.so.conf. For more information, see Using the ld.so.conf Configuration File, p.85.
83
3.
The ELF RPATH record in the applications executable (created with the rpath compiler option). For more information, see Using the ELF RPATH Record, p.85.
4.
The same directory as the one in which the application file is located. For more information, see Using the Application Directory, p.86.
In addition, the VxWorks LD_PRELOAD environment variable can be used to identify a set of libraries to load at startup time, before loading any other shared libraries. For more information in this regard, see Pre-loading Shared Libraries, p.86. Note that 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.
The LD_LIBRARY_PATH environment variable can be used to specify shared library locations when an application is started. If set, it is the first mechanism used by the dynamic linker for locating libraries (see Specifying Shared Library Locations: Options and Search Order, p.83). The LD_LIBRARY_PATH environment variable is useful as an alternative to the other mechanisms, or as a means to override them. For information about environment variables, see 2.2.8 RTPs and Environment Variables, p.15. Using the shells 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/myVxApp.vxe one two three
In this next example, the command (which would be typed all on one line, of course), identifies the location of libc.so.1 as well as a custom shared library on the host system:
84
4 Static Libraries, Shared Libraries, and Plug-Ins 4.8 Developing Shared Libraries
Note that some types of connections between the target and host require modifiers to the pathname (NFS is transparent; FTP requires hostname: before the path if it is not on the same system from which VxWorks was booted; the VxWorks simulator requires a host: prefix; and so on). Also note that even on 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. For general information about environment variables, see 2.2.8 RTPs and Environment Variables, p.15.
The second-priority mechanism for identifying the location of shared libraries is the configuration file ld.so.conf (see Specifying Shared Library Locations: Options and Search Order, p.83). The ld.so.conf file simply lists paths, one per line. The pound sign (#) can be used at the left margin for comment lines. By default the dynamic linker looks for ld.so.conf in the same directory as the one in which the application executable is located. The location of ld.so.conf can also specified with the VxWorks LD_SO_CONF environment variable. For information about environment variables, see 2.2.8 RTPs and Environment Variables, p.15.
The third-priority mechanism for identifying the location of shared libraries is the ELF RPATH record in the applications executable (see Specifying Shared Library Locations: Options and Search Order, p.83). 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
85
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 the VxWorks Command-Line Tools Users 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.83), the dynamic linker checks the directory in which the application itself is located. 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.
Pre-Loading Shared Libraries with LD_PRELOAD
The VxWorks LD_PRELOAD environment variable can be used to identify a set of libraries to load at startup time, before any other shared libraries are loaded (that is, to preload them). The variable can be set to a semicolon-separated list of absolute paths to the shared libraries that are to be pre-loaded. For example:
/romfs/lib/libMyFoo.so.1;c:/proj/lib/libMyBar.so.1;/home/moimoi/proj/libMoreStuff.so.1
A typical use of pre-loaded shared libraries is to override definitions in shared libraries that are loaded in the normal manner. The definition of symbols by pre-loaded libraries take precedence over symbols defined by an application or any other shared library. Note that even pre-loaded shared libraries are removed from memory immediately if no RTP application makes use of them. To keep them in memory
86
4 Static Libraries, Shared Libraries, and Plug-Ins 4.8 Developing Shared Libraries
the dummy RTP method must be used (for information in this regard, see Pre-Loading Shared Libraries with a Dummy RTP Application, p.87). For information about environment variables, see 2.2.8 RTPs and Environment Variables, p.15.
Pre-Loading Shared Libraries with a Dummy RTP Application 4
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 startedthe reference counting mechanism in the kernel ensures that the libraries are not unloaded.
-Xbind-lazy
Note that you can use VxWorks environment variables when you start an application to select or disable lazy binding, regardless of whether or not the compiler option was used with the shared libraries. Both LD_BIND_NOW and LD_BIND_LAZY override the compiler, and operate in the following manner:
If LD_BIND_LAZY is set (even to nothing) binding is lazy. if LD_BIND_NOW is set to an non-empty string, binding is immediate. If it is not set, or set to nothing, binding is lazy.
87
The actual value of the variable does not matter. For a discussion of lazy binding and plug-ins, see Using Lazy Binding With Plug-ins, p.96.
-Xdynamic for the Wind River Compiler. -non-static for the Wind River GNU compiler.
Use the -l linker option to link the application to each shared library that it needs. Building the application in this manner embeds the dynamic linker within the application and creates an ELF NEEDED record for each shared library that is linked against (based on the shared libraries SONAME records; for more information see 4.8.4 About Shared Library Names and ELF Records, p.80). For information about the various options available for locating a shared library at run-timeincluding using a compiler optionsee 4.8.7 Locating and Loading Shared Libraries at Run-time, p.83. For general information about developing RTP applications, see 3.3 Developing RTP Applications, p.40. For information about building applications that use shared libraries, see 4.6 Common Development Facilities, p.78.
88
4 Static Libraries, Shared Libraries, and Plug-Ins 4.8 Developing Shared Libraries
[vxWorks *]# tmPthreadLib.vxe 2 & Launching process 'tmPthreadLib.vxe' ... Process 'tmPthreadLib.vxe' (process Id = 0x8109a0) launched. Attachment number for process 'tmPthreadLib.vxe' is %1.
and
[vxWorks *]# rtp exec -e "LD_LIBRARY_PATH=/romfs/lib" tmPthreadLib.vxe 2 & Launching process 'tmPthreadLib.vxe' ... Process 'tmPthreadLib.vxe' (process Id = 0x807c90) launched. Attachment number for process 'tmPthreadLib.vxe' 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 NAME ID STATE ENTRY ADDR OPTIONS TASK CNT -------------------- ---------- --------------- ---------- ---------- -------./tmPthreadLib.vxe 0x8109a0 STATE_NORMAL 0x10002360 0x1 1
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, libc.so.1 is not in the same place as threadLibTest.so.1; it is in the same directory as the executable.
[vxWorks *]# shl SHL NAME ID TEXT ADDR TEXT SIZE DATA SIZE REF CNT -------------------- ---------- ---------- ---------- ---------- ------< threadLibTest.so.1 0x30000 0x10031000 0x979c 0x6334 1 ./libc.so.1 0x40000 0x10043000 0x5fe24 0x2550c 1
Note that the shl info command will provide the full path.
Failures related to the inability of the application to locate libc.so.1 or some other run-time share library would manifest themselves from the shell as follows:
89
[vxWorks *]# tmPthreadLib.vxe 2 & Launching process 'tmPthreadLib.vxe' ... Process 'tmPthreadLib.vxe' (process Id = 0x811000) launched. Attachment number for process 'tmPthreadLib.vxe' is %1. Shared object "libc.so.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.83). If the shared libraries are not stored on the target, also make sure that they are accessible from the target.
If an application is started with the incorrect assignment of argv[0], or no assignment at all, the behavior of any associated shared libraries can be impaired. The dynamic linker uses argv[0] to uniquely identify the executable, and if it is incorrectly defined, the linker may not be able to match the executable correctly with shared libraries. For example, if an application is started more than once without argv[0] being specified, a shared library may be reloaded each time; or if the paths are missing for executables with the same filename but different locations, the wrong shared library may be loaded for one or more of the executables. This issue applies only to applications started with rtpSpawn( ), which involves specification of argv[0]. It does not apply to applications started from the shell with rtpSp( ) (for the C interpreter) or with rtp exec (for the command interpreter). Note that shared library symbols are not visible if an application is started in stopped mode. Until execution of _start( ) (the entry point of an application provided by the compiler) calls the dynamic linker, shared library symbols are not yet registered. (For information about symbol registration, see 3.6.4 Applications and Symbol Registration, p.65.)
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 recordsin 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).
90
4 Static Libraries, Shared Libraries, and Plug-Ins 4.8 Developing Shared Libraries
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.80 and Using the ELF RPATH Record, p.85.
readelf Example for RTP Application
The following example shows information about an RTP application that has been linked against both MySharedLibrary.so.1 and libc.so.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\MyRTP.vxe Dynamic section at offset 0x8554 contains 19 entries: Tag Type Name/Value 0x00000001 (NEEDED) Shared library: [MySharedLibrary.so.1] 0x00000001 (NEEDED) Shared library: [libc.so.1] 0x0000000f (RPATH) Library rpath: [/romfs/lib] 0x00000004 (HASH) 0xc0 0x00000006 (SYMTAB) 0x424 0x0000000b (SYMENT) 16 (bytes) 0x00000005 (STRTAB) 0x9a4 0x0000000a (STRSZ) 1268 (bytes) 0x00000017 (JMPREL) 0x15f0 0x00000002 (PLTRELSZ) 160 (bytes) 0x00000014 (PLTREL) REL 0x00000016 (TEXTREL) 0x0 0x00000003 (PLTGOT) 0x18680 0x00000011 (REL) 0xe98 0x00000012 (RELSZ) 1880 (bytes) 0x00000013 (RELENT) 8 (bytes) 0x00000015 (DEBUG) 0x0 0x00000018 (BIND_NOW) 0x00000000 (NULL) 0x0
91
The next example shows information about the MySharedLibrary.so.1 shared library, against which the RTP application was linked.
C:\WindRiver\gnu\4.1.2-vxworks-6.6\x86-win32\bin>readelfpentium -d \ C:\workspace3\MySharedLibrary\SIMPENTIUMdiab_RTP\MySharedLibrary\Debug\mySharedLibrary.so.1 Dynamic section at offset 0x6c8 contains 17 entries: Tag Type Name/Value 0x0000000e (SONAME) Library soname: [MySharedLibrary.so.1] 0x00000004 (HASH) 0xa0 0x00000006 (SYMTAB) 0x218 0x0000000b (SYMENT) 16 (bytes) 0x00000005 (STRTAB) 0x3e8 0x0000000a (STRSZ) 396 (bytes) 0x00000017 (JMPREL) 0x5bc 0x00000002 (PLTRELSZ) 8 (bytes) 0x00000014 (PLTREL) REL 0x00000016 (TEXTREL) 0x0 0x0000000c (INIT) 0x6b8 0x0000000d (FINI) 0x6c0 0x00000003 (PLTGOT) 0x1774 0x00000011 (REL) 0x574 0x00000012 (RELSZ) 72 (bytes) 0x00000013 (RELENT) 8 (bytes) 0x00000000 (NULL) 0x0
Note that the information in each of an applications 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.81, 4.8.7 Locating and Loading Shared Libraries at Run-time, p.83, and 4.8.6 Using Different Versions of Shared Libraries, p.82.
92
4 Static Libraries, Shared Libraries, and Plug-Ins 4.8 Developing Shared Libraries
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 4
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 http://www.microsoft.com/. 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 setup.exe 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. 2. 3. 4. In Windows Explorer, select your Workspace and use the context menu to select Share... Select the NFS Sharing tab. Enter Share this folder, Share name = Workspace Enable Allow anonymous access. This provides the VxWorks target with read-only access to the share without having to set up user mappings or access permissions.
Before you can use NFS to load shared libraries, VxWorks also must be reconfigured with NFS facilities.
93
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.
executables to include a dynamic linker in their image. The dynamic linker carries out the binding of the plug-in and application at run-time. For more information, see 4.9.3 Developing RTP Applications That Use Plug-Ins, p.95.
94
An application that uses a plug-in must make an API call to load the plug-in, whereas the dynamic linker automatically loads a shared library for an application that requires it. An application that uses a plug-in must not be linked against the plug-in at build time, whereas an application that uses a shared library must be linked against the library. Not linking an application against a plug-in means that an ELF NEEDED record is not created for the shared object in the application, and the dynamic linker will not attempt to load the shared object when the associated application is loaded at run-time. For information about the role of ELF NEEDED records in applications that use shared libraries, see 4.8.4 About Shared Library Names and ELF Records, p.80.
Code Requirements
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. 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.
95
For an examples illustrating implementation of these requirements, see Example of Dynamic Linker API Use, p.97 and Example Application Using a Plug-In, p.97. For general information about developing RTP applications, see 3.3 Developing RTP Applications, p.40.
Build Requirements
The requirements for building an RTP application that makes use of plug-ins are as follows:
Compile the application as a dynamic executable (that is, with the Wind River Compiler -Xdynamic option or the GNU -non-static option), which 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). 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.78.
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.97). If only the plug-in nameand not the full pathis 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.83.
The second parameter to dlopen( ) defines whether or not lazy binding is employed for undefined symbols. If RTLD_NOW is used, all undefined symbols
96
are resolved before the call returns (or if they are not resolved the call fails). If RTLD_LAZY is used, symbols are resolved as they are referenced from the dynamic application and the share object code is executed. RTLD_GLOBAL may optionally be ORd with either RTLD_NOW or RTLD_LAZY, in which case the external symbols defined in the shared object are made available to any dynamic shared objects that are subsequently loaded. For a discussion of lazy binding and shared libraries, see 4.8.8 Using Lazy Binding With Shared Libraries, p.87.
The following code fragment illustrates basic use of the APIs required to work with a plug-in:
void *handle; void * addr; void (*funcptr)(); handle = dlopen("/romfs/lib/myLib.so", RTLD_NOW); addr = dlsym(handle, "bar"); funcptr = (void (*)())addr; funcptr(); dlclose(handle);
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> [...] const char plugin_path[] = "/romfs/plug-ins"; void *attach(const char *name) { void *handle; char *path; size_t n;
97
n = sizeof plugin_path + 1 + strlen(name) + 3; if ((path = malloc(n)) == -1) { fprintf(stderr, "can't allocate memory: %s", strerror(errno)); return NULL; } sprintf(path, "%s/%s.so", plugin_path, name); if ((handle = dlopen(path, RTLD_NOW)) == NULL) fprintf(stderr, "%s: %s", path, dlerror()); free(path); return handle; } void detach(PROTO handle) { dlclose(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 */ if ((proto_send_packet = dlsym(handle, "send_packet")) == NULL) { fprintf(stderr, "send_packet: %s", dlerror()); return -1; } return (*proto_send_packet)(addr, data, len); }
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 reliable.so (the comparable GNU flags would be -non-static and -shared). You install reliable.so as /romfs/plug-ins/reliable.so 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/reliable.so. Subsequently, when a packet must be sent to a 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
98
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.
Table 4-1 Plug-In Management Routines
Routine
Description
Load the plug-in. Look up a function or data element in the plug-in. Unload a plug-in (if there are no other references to it). Return the error string after an error in dlopen( ), dlclose( ) or dlsym( ).
For more information about these APIs, see the rtld library entry in the VxWorks Application API Reference.
99
aioPxLib (see 9.7 Asynchronous Input/Output, p.305) memEdrLib (see 8.4 Memory Error Detection, p.274) message channel libraries (see 6.17 Message Channels, p.175) networking libraries (see the Wind River Network Stack Programmers Guide)
All dynamic executables require libc.so.1 at run time. When generating a dynamic executable, the GNU and Wind River toolchains automatically use the corresponding build-time shared object, libc.so, which is located in installDir/vxworks-6.x/target/usr/lib/arch/cpu/common (where arch is a the architecture such as ppc, pentium, or mips). If required, another location can be referred to by using the linker's -L option. The run-time version of the library is libc.so.1, which is located in the directory installDir/vxworks-6.x/target/usr/root/cpuTool/bin, where cpu is the name of the target CPU (such as PPC32, or PENTIUM4) and Tool is the name of a toolchain including modifiers indicating the endianness and the floating point attributes applied when generating the code (for example diab, sfdiable, gnu, or gnule). For example: installDir/vxworks-6.x/target/usr/root/SIMPENTIUMdiab/bin/libc.so.1 For a development environment, various mechanisms can be used for providing the dynamic linker with information about the location of the libc.so.1 file. For deployed systems, the libc.so.1 file can be copied manually to whatever location is appropriate. The most convenient way to make the dynamic linker aware of the location of libc.so.1 is to store the file in the same location as the dynamic application, or to use the -Wl,-rpath compiler flag when the application is built. For more information, see 4.8.7 Locating and Loading Shared Libraries at Run-time, p.83.
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.
100
5
C++ Development
5.1 Introduction 101 5.2 C++ Code Requirements 102 5.3 C++ Compiler Differences 102 5.4 Namespaces 104 5.5 C++ Demo Example 105
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 Programmers Guide.
101
If you reference a (non-overloaded, global) C++ symbol from your C code you must give it C linkage by prototyping it using extern "C":
#ifdef __cplusplus extern "C" void myEntryPoint (); #else void myEntryPoint (); #endif
You can also use this syntax to make C symbols accessible to C++ code. VxWorks C symbols are automatically available to C++ because the VxWorks header files use this mechanism for declarations.
compatible. The following sections briefly describe the differences in compiler support for template instantiation and run-time type information.
102
The Wind River Compiler C++ options controlling multiple instantiation of templates are: -Xcomdat This option is the default. When templates are instantiated implicitly, the generated code or data section are marked as comdat. The linker then collapses identical instances marked as such, into a single instance in memory. -Xcomdat-off Generate template instantiations and inline functions as static entities in the resulting object file. Can result in multiple instances of static member-function or class variables. For greater control of template instantiation, the -Ximplicit-templates-off option tells the compiler to instantiate templates only where explicitly called for in source code; for example:
template class A<int>; template int f1(int); // Instantiate A<int> and all member functions. // Instantiate function int f1{int).
103
GNU Compiler
The GNU C++ compiler options controlling multiple instantiation of templates are: -fimplicit-templates This option is the default. Template instantiations and out-of-line copies of inline functions are put into special linkonce sections. Duplicate sections are merged by the linker, so that each instantiated template appears only once in the output file. -fno-implicit-templates This is the option for explicit instantiation. Using this strategy explicitly instantiates any templates that you require.
5.4 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:
104
#include <iostream.h> int main() { cout << "Hello, world!" << endl; }
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; }
105
106
6
Multitasking
6.1 Introduction 109 6.2 Tasks and Multitasking 110 6.3 Task Scheduling 115 6.4 Task Creation and Management 121 6.5 Task Error Status: errno 132 6.6 Task Exception Handling 134 6.7 Shared Code and Reentrancy 134 6.8 Intertask and Interprocess Communication 139 6.9 Inter-Process Communication With Public Objects 140 6.10 Object Ownership and Resource Reclamation 142 6.11 Shared Data Structures 142 6.12 Mutual Exclusion 143 6.13 Semaphores 144 6.14 Message Queues 162 6.15 Pipes 168 6.16 VxWorks Events 169 6.17 Message Channels 175 6.18 Network Communication 175
107
108
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. Intertask communication facilities allow tasks to synchronize and communicate in order to coordinate their activity. In VxWorks, the intertask communication facilities include semaphores, message queues, message channels, pipes, network-transparent sockets, and signals. For interprocess communication, VxWorks semaphores and message queues, pipes, and events (as well as POSIX semaphores and events) can be created as public objects to provide accessibility 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. VxWorks provides watchdog timers, but they can only be used in the kernel (see VxWorks Kernel Programmers Guide: Multitasking. However, process-based applications can use POSIX timers (see 7.9 POSIX Clocks and Timers, p.204). This chapter discusses the tasking, intertask communication, and interprocess communication facilities that are at the heart of the VxWorks run-time environment. For information about POSIX support for VxWorks, see 7. POSIX Facilities.
NOTE: This chapter provides information about facilities available for real-time 6
processes. For information about facilities available in the VxWorks kernel, see the corresponding chapter in the VxWorks Kernel Programmers Guide.
109
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 APIthe difference amounts to only a few routines. Also note that some programming practicessuch as implicit synchronization techniques relying on task priority instead of explicit lockingare not appropriate for an SMP system. For information about SMP programming and migration, see the VxWorks Kernel Programmers Guide: VxWorks SMP.
a thread of execution; that is, the tasks program counter the tasks virtual memory context (if process support is included) the CPU registers and (optionally) coprocessor registers
110
stacks for dynamic variables and function calls I/O assignments for standard input, output, and error a delay timer a time-slice timer kernel control structures signal handlers error status (errno) debugging and performance monitoring values
6
Note thatconsistent with the POSIX standardall tasks in a process share the same environment variables (unlike kernel tasks, which each have their own set of environment variables). For more information about virtual memory contexts, see the VxWorks Kernel Programmers Guide: Memory Management.
NOTE: The POSIX standard includes the concept of a thread, which is similar to a
task, but with some additional features. For details, see 7.13 POSIX Threads, p.210.
111
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.
Table 6-1 Task State Symbols
State Symbol
Description
READY PEND
The task is not waiting for any resource other than the CPU. The task is blocked due to the unavailability of some resource (such as a semaphore). The task is asleep for some duration. 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. The task is stopped by the debugger (also used by the error detection and reporting facilities). The task is both delayed and suspended. The task is both pended and suspended. The a task is pended with a timeout value. Task is pended and stopped (by the debugger, error detection and reporting facilities, or SIGSTOP signal). Task is stopped (by the debugger, error detection and reporting facilities, or SIGSTOP signal) and suspended. Task is delayed and stopped (by the debugger, error detection and reporting facilities, or SIGSTOP signal). The task is pended with a timeout value and suspended.
DELAY SUSPEND
STOP
STOP + S
STOP + T
PEND + S + T
112
Table 6-1
State Symbol
Description
Task is pended, suspended and stopped by the debugger. Task pended with a timeout and stopped by the debugger. Task is suspended, delayed, and stopped by the debugger. Task is pended with a timeout, suspended, and stopped by the debugger. The task is specified by state (any state or combination of states listed above), plus an inherited priority.
6
state + I
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 11. Error Detection and Reporting). Example 6-1 shows output from the i( ) shell command, displaying task state information.
Example 6-1 -> i NAME ---------tIsr0 tJobTask tExcTask tLogTask tNbioLog tShell0 tWdbTask tErfTask tXbdServic> tNet0 ipcom_sysl> ipnetd ipcom_teln> miiBusMoni> value = 0 = ENTRY TID PRI ------------ -------- --42cb40 25b1f74 0 3732d0 25b5140 0 372850 4f033c 0 logTask 25b7754 0 373f28 25bae18 0 shellTask 2fbdcb4 1 wdbTask 2faca28 3 42e0a0 25bd0a4 10 36e4b4 25ac3d0 50 ipcomNetTask 25cdb00 50 3cba50 27fec0c 50 3e2170 2fa6d10 50 ipcom_telnet 2fa979c 50 429420 25a8010 254 0x0 STATUS PC SP ERRNO DELAY ---------- -------- -------- ------- ----PEND 3bcf54 25b1f2c 0 0 PEND 3bcf54 25b50e8 0 0 PEND 3bcf54 4ef0f8 0 0 PEND 3bb757 25b7670 0 0 PEND 3bcf54 25bad6c 0 0 READY 3c2bdc 2fbc0d4 0 0 PEND 3bcf54 2fac974 0 0 PEND 3bd3be 25bd03c 0 0 PEND+T 3bd3be 25ac36c 3d0004 6 PEND 3bcf54 25cda88 0 0 PEND 3bd3be 27feab0 0 0 PEND 3bcf54 2fa6c98 3d0004 0 PEND 3bcf54 2fa9594 0 0 DELAY 3c162d 25a7fd0 0 93 Task States in Shell Command Output
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.112, nor does it show the STOP state used by debugging facilities.
113
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).
Figure 6-1 Basic Task State Transitions
taskSpawn( )
pended
ready
delayed
suspended
taskCreate( )
ready suspended ready ready ready pended pended delayed delayed suspended suspended suspended pended delayed suspended ready suspended ready suspended ready pended delayed
taskSpawn( ) taskCreate( ) semTake( ) / msgQReceive( ) taskDelay( ) taskSuspend( ) semGive( ) / msgQSend( ) taskSuspend( ) expired delay taskSuspend( ) taskResume( ) / taskActivate( ) taskResume( ) taskResume( )
114
The traditional VxWorks scheduler, which provides priority-based, preemptive scheduling, as well as a round-robin extension. See 6.3.3 VxWorks Traditional Scheduler, p.117. The VxWorks POSIX threads scheduler, which is designed (and required) for running pthreads in processes (RTPs). See 7.15 POSIX and VxWorks Scheduling, p.225.) A custom scheduler framework, which allows you to develop your own scheduler. See the VxWorks Kernel Programmers Guide: Kernel Customization.
6
Routine
Description
taskPrioritySet( ) taskRtpLock( )
Changes the priority of a task. Disables task context switching within a process (as long as the task is not blocked or has not voluntarily give up the CPU). Prevents any other task in the process from preempting the calling task.
115
Table 6-2
Routine
Description
taskRtpUnLock( )
Task Priority
Tasks are assigned a priority when they are created (see 6.4.1 Task Creation and Activation, p.121). You can change a tasks 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. Note that if a tasks 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.118.
Preemption Locks
The scheduler can be explicitly disabled and enabled on a per-task basiswithin a processwith the routines taskRtpLock ( ) and taskRtpUnLock ( ). When a task disables the scheduler by calling taskRtpLock( ), no priority-based preemption can take place by other tasks running in the same process while that task is running. Using a semaphore is, however, preferable to taskRtpLock( ) as a means of mutual exclusion, because preemption lock-outs add preemptive latency to the process. If the task that has disabled the scheduler with taskRtpLock( ) explicitly blocks or suspends, the scheduler selects the next highest-priority eligible task to execute. When the preemption-locked task unblocks, and begins running again, preemption is again disabled. If mutual exclusion between tasks in different processes is required, use a public semaphore. For information about global objects, see 6.9 Inter-Process Communication With Public Objects, p.140.
NOTE: The taskRtpLock ( ) and taskRtpUnLock( ) routines are provided for the UP configuration of VxWorks, but not the SMP configuration. Use semaphores or another mechanism supported for SMP instead of taskRtpLock ( ) and taskRtpUnLock( ). For more information, see the VxWorks Kernel Programmers Guide: VxWorks SMP.
116
Note that preemption locks prevent task context switching, but do not lock out interrupt handling. Preemption locks can be used to achieve mutual exclusion; however, keep the duration of preemption locking to a minimum. For more information, see 6.12 Mutual Exclusion, p.143.
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 a taskwith a higher priority than that of the current taskbecomes ready to run, the kernel immediately saves the current tasks 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.119).
117
Figure 6-2
Priority Preemption
HIGH priority
t3 t2 t1
time
KEY: = preemption = task completion
t2 t1
LOW
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 the list for that priority level executes. A tasks 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.111). If a tasks priority is changed with taskPrioritySet( ), it is placed at the end of its new priority list. If a tasks 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.155.)
118
The taskRotate( )routine can be used to shift a task from the front to the end of its priority list. For more information about this routine, see 6.3.2 Task Scheduling Control, p.115 and the VxWorks API reference entry. 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.233.
Round-Robin Scheduling
VxWorks provides a round-robin extension to priority-based preemptive scheduling. Round-robin scheduling accommodates instances in which there are more than one task of a given priority that is ready to run, and you want to share the CPU amongst these tasks. The round-robin algorithm attempts to share the CPU amongst these tasks by using time-slicing. Each task in a group of tasks with the same priority executes for a defined interval, or time slice, before relinquishing the CPU to the next task in the group. No one of them, therefore, can usurp the processor until it is blocked. See Figure 6-3 for an illustration of this activity. When the time slice expires, the task moves to last place in the ready queue list for that priority (for information about the ready queue, see Scheduling and the Ready Queue, p.118). Note that while round-robin scheduling is used in some operating systems to provide equal CPU time to all tasks (or processes), regardless of their priority, this is not the case with VxWorks. Priority-based preemption is essentially unaffected by the VxWorks implementation of round-robin scheduling. Any higher-priority task that is ready to run immediately gets the CPU, regardless of whether or not the current task is done with its slice of execution time. When the interrupted task gets to run again, it simply continues using its unfinished execution time. It may be useful to use round-robin scheduling in systems that execute the same application in more than one process. In this case, multiple tasks would be executing the same code, and it is possible that a task might not relinquish the CPU to a task of the same priority running in another process (running the same binary). Note that round-robin scheduling is global, and controls all tasks in the system (kernel and processes); it is not possible to set round-robin scheduling for selected processes.
119
Round-robin scheduling is enabled by calling kernelTimeSlice( ), which takes a parameter for a time slice, or interval. It is disabled by using zero as the argument to kernelTimeSlice( ).
Time-slice Counts and Preemption
The time-slice or interval defined with a kernelTimeSlice( ) call is the amount of time that each task is allowed to run before relinquishing the processor to another equal-priority task. Thus, the tasks rotate, each executing for an equal interval of time. No task gets a second slice of time before all other tasks in the priority group have been allowed to run. If round-robin scheduling is enabled, and preemption is enabled for the executing task, the system tick handler increments the tasks time-slice count. When the specified time-slice interval is completed, the system tick handler clears the counter and the task is placed at the end of the ready queue priority list for its priority level. New tasks joining a given priority group are placed at the end of the priority list for that priority with their run-time counter initialized to zero. Enabling round-robin scheduling does not affect the performance of task context switches, nor is additional memory allocated. If a task blocks or is preempted by a higher priority task during its interval, its time-slice count is saved and then restored when the task becomes eligible for execution. In the case of preemption, the task resumes execution once the higher 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.
120
Figure 6-3
Round-Robin Scheduling
HIGH priority
time slice
t4
LOW
t1
t2
t3
t1
time
t2
t2
t3
KEY:
= preemption
= task completion
121
Note that a tasks priority can be changed after it has been spawned; see 6.3.2 Task Scheduling Control, p.115. 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.
Table 6-3 Task Creation Routines
Routine
Description
Spawns (creates and activates) a new task. Creates, but not activates a new task. Open a task (or optionally create one, if it does not exist). Activates an initialized task.
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 either a public or private object (see 6.4.2 Task Names and IDs, p.122). 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.
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 systemin the kernel and any processes.
122
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 createdeither 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. 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. 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). For more information, see 6.4.3 Inter-Process Communication With Public Tasks, p.124. You do not have to explicitly name tasks. If a NULL pointer is supplied for the name argument of taskSpawn( ), then VxWorks assigns a unique name. The name is of the form tN, where N is a decimal integer that is incremented by one for each unnamed task that is spawned. The taskLib routines listed in Table 6-4 manage task IDs and names.
Table 6-4 Task Name and ID Routines
Routine
Description
Gets the task name associated with a task ID (restricted to the contextprocess or kernelin which it is called). Gets the task name associated with a task ID anywhere in the entire system (kernel and any processes). Looks up the task ID associated with a task name. Gets the calling tasks ID. Verifies the existence of a specified task.
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.
123
Name
Description
Execute with Altivec coprocessor support. Execute with DSP coprocessor support. Executes with the floating-point coprocessor. Does not fill the stack with 0xEE. Create without stack overflow or underflow guard zones (see 6.4.5 Task Stack, p.125). Executes a task with a private environment. Used with taskOpen( ) so that the task is not activated.
VX_PRIVATE_ENV VX_TASK_NOACTIVATE
124
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.
6
For example:
tid = taskSpawn ("tMyTask", 90, VX_FP_TASK, 20000, myFunc, 2387, 0, 0, 0, 0, 0, 0, 0, 0, 0);
Some routines perform floating-point operations internally. The VxWorks documentation for each of these routines clearly states the need to use the VX_FP_TASK option.
Filling Task Stacks
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).
125
Task stacks can be protected with guard zones and by making task stacks non-executable.
Task Stack Guard Zones
Systems can be configured with the INCLUDE_PROTECT_TASK_STACK component to provide guard zone protection for task stacks. If memory usage becomes an issue, the component can be removed for final testing and the deployed system. An overrun guard zone prevents a task from going beyond the end of its predefined stack size and corrupting data or other stacks. An under-run guard zone typically prevents buffer overflows from corrupting memory that precedes the base of the stack. The CPU generates an exception when a task attempts to access any of the guard zones. The size of a stack is always rounded up to a multiple of the MMU page size when either a guard zone is inserted or when the stack is made non-executable. 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 tasks 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 memorythey 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.4.4 Task Creation Options, p.124). 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:
overflow size.
126
underflow size.
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 7.13.1 POSIX Thread Stack Guard Zones, p.211.
Routine
Description
taskInfoGet( ) taskPriorityGet( )
taskIsSuspended( ) Checks whether a task is suspended. taskIsReady( ) taskIsPended( ) taskIsDelayed( ) Checks whether a task is ready to run. Checks whether a task is pended. Checks whether or not a task is delayed.
For information about task-specific variables and their use, see 6.7.3 Task-Specific Variables, p.137.
127
Routine
Description
Terminates the specified process (and therefore all tasks in it) and frees the process memory resources. Terminates the calling task (in a process) and frees the stack and any other memory resources, including the task control block.a Terminates a specified task and frees memory (task stacks and task control blocks only).a The calling task may terminate itself with this routine. 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( ). Undoes a taskSafe( ), which makes calling task available for deletion.
taskSafe( )
taskUnsafe( )
a. Memory that is allocated by the task during its execution is not freed when the task is terminated. 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.10. 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 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.
128
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); . . /* critical region code */ . semGive (semId); taskUnsafe (); /* Block until semaphore available */
/* Release semaphore */
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 6.13.4 Mutual-Exclusion Semaphores, p.152.
129
Table 6-8
Routine
Description
Suspends a task. Resumes a task. Restarts a task. Delays a task; delay units are ticks, resolution in ticks. Delays a task; delay units are nanoseconds, resolution in ticks.
Tasks may require restarting during execution in response to some catastrophic error. The restart mechanism, taskRestart( ), recreates a task with the original creation arguments. 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 7.9 POSIX Clocks and Timers, p.204. 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 */
A delay of zero duration is only possible with taskDelay( ); nanosleep( ) considers it an error. For information about the ready queue, see Scheduling and the Ready Queue, p.118.
NOTE: ANSI and POSIX APIs are similar.
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
130
periodic delaying is effectively polling, you may want to consider using event-driven techniques as an alternative.
Routine
Description
Adds a routine to be called at every task create. Deletes a previously added task create routine. Adds a routine to be called at every task delete. Deletes a previously added task delete routine.
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 will be owned by the creator task's process. It may be necessary to assign the ownership of these objects to the new task's process. This will prevent unexpected object reclamation from occurring if and when the process of the creator task terminates. When the creator task is a kernel task, the kernel will own any kernel objects that are created. Thus there is no concern about unexpected object reclamation for this case. User-installed switch hooks are called within the kernel context and therefore do not have access to all VxWorks facilities. Table 6-10 summarizes the routines that can be called from a task switch hook; in general, any routine that does not involve the kernel can be called.
131
Table 6-10
Library
Routines
All routines fppSave( ), fppRestore( ) intContext( ), intCount( ), intVecSet( ), intVecGet( ), intLock( ), intUnlock( ) All routines except lstFree( ) All are callable if fppSave( )/fppRestore( ) are used All routines except rngCreate( ) taskIdVerify( ), taskIdDefault( ), taskIsReady( ), taskIsSuspended( ), taskIsPended( ), taskIsDelayed( ), taskTcb( ) vxTas( )
vxLib
132
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.
133
134
Figure 6-4
Shared Code
TASKS
taskOne (void) { myFunc(); ... } taskTwo (void) { myFunc(); ... }
SHARED CODE
Shared code must be reentrant. A subroutine is reentrant if a single copy of the routine can be called from several task contexts simultaneously without conflict. Such conflict typically occurs when a subroutine modifies global or static variables, because there is only a single copy of the data and code. A routines references to such variables can overlap and interfere in invocations from different task contexts. Most routines in VxWorks are reentrant. However, you should assume that any routine someName( ) is not reentrant if there is a corresponding routine named someName_r( ) the latter is provided as a reentrant version of the routine. For example, because ldiv( ) has a corresponding routine ldiv_r( ), you can assume that ldiv( ) is not reentrant. The majority of VxWorks routines use the following reentrancy techniques: dynamic stack variables global and static variables guarded by semaphores
Wind River recommends applying these same techniques when writing application code that can be called from several task contexts simultaneously.
NOTE: Initialization routines should be callable multiple times, even if logically they should only be called once. As a rule, routines should avoid static variables that keep state information. Initialization routines are an exception; using a static variable that returns the success or failure of the original initialization routine call is appropriate.
135
TASKS
taskOne ( ) { ... comFunc(1); ... }
TASK STACKS
... var = 1 ...
COMMON SUBROUTINE
136
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) facilitiesfor the kernel-space and user-space respectivelyare 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 Programmers 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 6.16 VxWorks Events, p.169 for more information about this register, and the routines used to interact with it.
Thread-local storage is a compiler facility that allows for allocation of a variable such that there are unique instances of the variable for each thread (or task, in VxWorks terms). The __thread storage class instructs the compiler to make the defined variable a thread-local variable. This means one instance of the variable is created for every task in the system. The compiler key word is used as follows:
__thread int i; extern __thread struct state s; static __thread char *p;
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.
137
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 tasks 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.
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.
138
Figure 6-6
Shared memory, for simple sharing of data. Semaphores, for basic mutual exclusion and synchronization. Mutexes and condition variables for mutual exclusion and synchronization using POSIX interfaces. Message queues and pipes, for intertask message passing within a CPU. VxWorks events, for communication and synchronization. Message channels, for socket-based inter-processor and interprocess communication.
139
Sockets and remote procedure calls, for network-transparent intertask communication. Signals, for exception handling, interprocess communication, and process management.
uniprocessor (UP) configurations of VxWorks share the same facilities for intertask and interprocess communicationsthe difference amounts to only a few routines. This section provides information about the APIs that are common to both configurations, as well as those APIs that are specific to the UP configuration. In the latter case, the alternatives available for SMP systems are noted. For information about the SMP configuration of VxWorks, and about migration, see the VxWorks Kernel Programmers Guide: VxWorks SMP. In addition, the VxMP component provides for intertask communication between multiple CPUs that share memory. See the VxWorks Kernel Programmers Guide.
140
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 ruleduplicate names are permitted for private tasks; see 6.4.2 Task Names and IDs, p.122. 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.
141
142
Global variables, linear buffers, ring buffers, linked lists, and pointers can be referenced directly by code running in different contexts. For information about using shared data regions to communicate between processes, see 3.5 Creating and Using Shared Data Regions, p.51.
Figure 6-7 Shared Data Structures
TASKS
task 1 access
MEMORY
sharedData sharedData
access
task 2
sharedData
access
task 3
sharedData
143
6.13 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. They provide mutual exclusion with finer granularity than either interrupt disabling or preemptive locks, discussed in 6.12 Mutual Exclusion, p.143. For synchronization, semaphores coordinate a tasks execution with external events.
significance for the SMP configuration of VxWorks. For more information, see the VxWorks Kernel Programmers Guide: VxWorks SMP. 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 6.13.3 Binary Semaphores, p.149. mutual exclusion A special binary semaphore optimized for problems inherent in mutual exclusion: priority inversion, deletion safety, and recursion. For more information, see 6.13.4 Mutual-Exclusion Semaphores, p.152. 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 6.13.5 Counting Semaphores, p.157. 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 6.13.6 Read/Write Semaphores, p.158. 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 7.16 POSIX Semaphores, p.235.
144
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 Programmers Guide: Shared Memory Objects.
The creation routines, which are specific to each semaphore type. The give and take routines for read/write semaphores, which support read and write modes for each operation. The scalable and inline variants of the give and take routines for binary and mutex semaphores, which provide optimized alternatives to the standard routines.
Table 6-11 lists the semaphore control routines. Table 6-12 lists the options that can be used with the scalable and inline routines. For general information about the scalable and inline routines, see Scalable and Inline Semaphore Take and Give Routines, p.149.
Table 6-11 Semaphore Control Routines
Routine
Description
semBInitialize( ) semCInitialize( )
145
Table 6-11
Routine
Description
semMInitialize( ) semRWInitialize( ) semBCreate( ) semMCreate( ) semCCreate( ) semRWCreate( ) semDelete( ) semTake( ) semRTake( ) semWTake( )
Initializes a pre-allocated mutual-exclusion semaphore. Initializes a pre-allocated read/write semaphore. Allocates and initializes a binary semaphore. Allocates and initializes a mutual-exclusion semaphore. Allocates and initializes a counting semaphore. Allocates and initializes a read/write semaphore. Terminates and frees a semaphore (all types). Takes a binary, mutual-exclusion, or counting semaphore, or a read/write semaphore in write mode. Takes a read/write semaphore in read mode. Takes a read/write semaphore in write mode.
semBTakeScalable( ) Takes a binary semaphore (with scalable functionality). semMTakeScalable( ) Takes a mutual-exclusion semaphore (with scalable functionality). semBTake_inline( ) semMTake_inline( ) semGive( ) semRWGive( ) semMGiveForce( ) semRWGiveForce( ) Takes a binary semaphore (with scalable functionality). Takes a mutual-exclusion semaphore (with scalable functionality). Implemented as an inline function. Gives a binary, mutual-exclusion, or counting semaphore. Gives a read/write semaphore. Gives a mutual-exclusion semaphore without restrictions. Intended for debugging purposes only. Gives a read-write semaphore without restrictions. Intended for debugging purposes only.
146
Table 6-11
Routine
Description
semMGiveScalable( ) Gives a mutual-exclusion semaphore (with scalable functionality). semBGive_inline( ) semMGive_inline( ) semFlush( ) semExchange( ) Gives a binary semaphore (with scalable functionality). Implemented as an inline function. Gives a mutual-exclusion semaphore (with scalable functionality). Implemented as an inline function. Unblocks all tasks that are waiting for a binary or counting semaphore. Provides for an atomic give and exchange of semaphores in SMP systems.
6
The creation routines return a semaphore ID that serves as a handle on the semaphore during subsequent use by the other semaphore-control routines. When a semaphore is created, the queue type is specified. Tasks pending on a semaphore can be queued in priority order (SEM_Q_PRIORITY) or in first-in first-out order (SEM_Q_FIFO). !
WARNING: The semDelete( ) call terminates a semaphore and deallocates all
associated memory. Take care when deleting semaphores, particularly those used for mutual exclusion, to avoid deleting a semaphore that another task still requires. Do not delete a semaphore unless the same task first succeeds in taking it.
Table 6-12 lists the options that can be used with the scalable and inline routines. For general information about scalable and inline routines, see Scalable and Inline Semaphore Take and Give Routines, p.149.
Table 6-12 Scalable and Inline Semaphore Options
Routine
Description
SEM_NO_ID_VALIDATE
147
Table 6-12
Routine
Description
SEM_NO_ERROR_CHECK
Error checking code is not executed. This includes tests for interrupt restriction, task validation of owners selected from the pend queue, and ownership validation for mutex semaphores. Do not send VxWorks events, even if a task has registered to receive event notification on this semaphore. when semaphores are uncontested. If it is necessary to pend on a take call or to unpend a task on a give call, System Viewer events are sent. This differs from calls to the semLib APIs which send events for all invocations as well as second events when pending (or unpending).
SEM_NO_EVENT_SEND
SEM_NO_RECURSE
Do not perform recursion checks (applies only to mutex semaphores only) whether semaphores are contested or not. It is important that this is used consistently during any single thread of execution.
CAUTION: The options listed in Table 6-12 must not be used when semaphores are created. Errors are generated if they are used.
The semXCreate( ) routines listed in Table 6-11 perform a dynamic, two-step operation, in which memory is allocated for the semaphore object at runtime, and then the object is initialized. Semaphores (and other VxWorks objects) can also be statically instantiated using the semXInitialize( ) routineswhich means that their memory is allocated for the object at compile timeand the object is then initialized at runtime with an initialization routine. For information about static instantiation, see the VxWorks Kernel Programmers Guide: Kernel Applications. For information about semaphore initialization routines, see the VxWorks API references.
148
In addition to the standard semaphore give and take routines, VxWorks provides scalable and inline of variants for use with binary and mutex semaphores. These routines provide the following advantages:
Performance improvements for both UP and SMP configurations of VxWorks based on eitheror bothscalable options and inline use. Additional performance improvements for SMP over standard routines even if just inline variants are used, because they provide optimizations for uncontested take and give operations in an SMP system.
6
The scalable routines are designed for use with lightly contested resources when performance is of greater significance than features of the standard routines that provide for their robustness (such as various forms of error checking). Several options are available for de-selecting operational features that would normally be conducted for any given take or give operation. The inline variants of the take and give routines provide the same options as the scalable routines, but also avoid the overhead associated with a function call. As with any inline code, repeated use adds to system footprint. If an application makes numerous calls to a routine for which there is an inline variant, either a wrapper should be created for the inline routine, or the scalable variant should be used instead. The scalable and inline of variants are listed in Table 6-11; and the options for the scalable and inline routines are listed in Table 6-12). Note that in order to use these routines, you must include the semLibInline.h header file.
149
time of the call; see Figure 6-8. If the semaphore is available (full), the semaphore becomes unavailable (empty) and the task continues executing immediately. If the semaphore is unavailable (empty), the task is put on a queue of blocked tasks and enters a state of pending on the availability of the semaphore.
Figure 6-8 Taking a Semaphore
no
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 6-9. 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).
150
Figure 6-9
Giving a Semaphore
no
yes
task continues; semaphore remains unchanged
yes
task at front of queue made ready; semaphore remains unavailable
Mutual Exclusion
Binary semaphores interlock access to a shared resource efficiently. Unlike disabling interrupts or preemptive locks, binary semaphores limit the scope of the mutual exclusion to only the associated resource. In this technique, a semaphore is created to guard the resource. Initially the semaphore is available (full).
/* includes */ #include <vxWorks.h> #include <semLib.h> SEM_ID semMutex; /* Create a binary semaphore that is initially full. Tasks * * blocked on semaphore wait in priority order. */ semMutex = semBCreate (SEM_Q_PRIORITY, SEM_FULL);
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:
151
semTake (semMutex, WAIT_FOREVER); . . /* critical region, only accessible by a single task at a time */ . semGive (semMutex);
Synchronization
When used for task synchronization, a semaphore can represent a condition or event that a task is waiting for. Initially, the semaphore is unavailable (empty). A task or ISR signals the occurrence of the event by giving the semaphore. Another task waits for the semaphore by calling semTake( ). The waiting task blocks until the event occurs and the semaphore is given. Note the difference in sequence between semaphores used for mutual exclusion and those used for synchronization. For mutual exclusion, the semaphore is initially full, and each task first takes, then gives back the semaphore. For synchronization, the semaphore is initially empty, and one task waits to take the semaphore given by another task. Broadcast synchronization allows all processes that are blocked on the same semaphore to be unblocked atomically. Correct application behavior often requires a set of tasks to process an event before any task of the set has the opportunity to process further events. The routine semFlush( ) addresses this class of synchronization problem by unblocking all tasks pended on a semaphore.
It can be used only for mutual exclusion. It can be given only by the task that took it. The semFlush( ) operation is illegal.
152
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.
6
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.
NOTE: User-level semaphores are not supported for the symmetric multiprocess-
ing (SMP) configuration of VxWorks. With the SMP configuration, a semMCreate( ) call in a process creates a kernel-level mutex semaphore. For information about VxWorks SMP and about migration, see VxWorks Kernel Programmers Guide: VxWorks SMP.
153
Figure 6-10
Priority Inversion
HIGH priority
t1 t2 t3 t3
time
= take semaphore = give semaphore = own semaphore
t1
LOW
t3
KEY:
Priority inversion arises when a higher-priority task is forced to wait an indefinite period of time for a lower-priority task to complete. Consider the scenario in Figure 6-10: t1, t2, and t3 are tasks of high, medium, and low priority, respectively. t3 has acquired some resource by taking its associated binary guard semaphore. When t1 preempts t3 and contends for the resource by taking the same semaphore, it becomes blocked. If we could be assured that t1 would be blocked no longer than the time it normally takes t3 to finish with the resource, there would be no problem because the resource cannot be preempted. However, the low-priority task is vulnerable to preemption by medium-priority tasks (like t2), which could inhibit t3 from relinquishing the resource. This condition could persist, blocking t1 for an indefinite period of time.
154
The mutual-exclusion semaphore has the option SEM_INVERSION_SAFE, which enables a priority-inheritance policy. The priority-inheritance policy assures that a task that holds a resource executes at the priority of the highest-priority task that is blocked on that resource. Once the tasks priority has been elevated, it remains at the higher level until all mutual-exclusion semaphores that have contributed to the tasks elevated priority are released. Hence, the inheriting task is protected from preemption by any intermediate-priority tasks. This option must be used in conjunction with a priority queue (SEM_Q_PRIORITY). Note that after the inheriting task has finished executing at the elevated priority level, it returns to the end of the ready queue priority list for its original priority. (For more information about the ready queue, see Scheduling and the Ready Queue, p.118.)
Figure 6-11 Priority Inheritance
HIGH priority
t1
t3
t1 t2
LOW
t3
time
KEY:
In Figure 6-11, priority inheritance solves the problem of priority inversion by elevating the priority of t3 to the priority of t1 during the time t1 is blocked on the semaphore. This protects t3, and indirectly t1, from preemption by t2.
155
The following example creates a mutual-exclusion semaphore that uses the priority inheritance policy:
semId = semMCreate (SEM_Q_PRIORITY | SEM_INVERSION_SAFE);
Deletion Safety
Another problem of mutual exclusion involves task deletion. Within a critical region guarded by semaphores, it is often desirable to protect the executing task from unexpected deletion. Deleting a task executing in a critical region can be catastrophic. The resource might be left in a corrupted state and the semaphore guarding the resource left unavailable, effectively preventing all access to the resource. The primitives taskSafe( ) and taskUnsafe( ) provide one solution to task deletion. However, the mutual-exclusion semaphore offers the option SEM_DELETE_SAFE, which enables an implicit taskSafe( ) with each semTake( ), and a taskUnsafe( ) with each semGive( ). In this way, a task can be protected from deletion while it has the semaphore. This option is more efficient than the primitives taskSafe( ) and taskUnsafe( ), as the resulting code requires fewer entrances to the kernel.
semId = semMCreate (SEM_Q_FIFO | SEM_DELETE_SAFE);
Mutual-exclusion semaphores can be taken recursively. This means that the semaphore can be taken more than once by the task that holds it before finally being released. Recursion is useful for a set of routines that must call each other but that also require mutually exclusive access to a resource. This is possible because the system keeps track of which task currently holds the mutual-exclusion semaphore. Before being released, a mutual-exclusion semaphore taken recursively must be given the same number of times it is taken. This is tracked by a count that increments with each semTake( ) and decrements with each semGive( ).
156
Example 6-2
Recursive Use of a Mutual-Exclusion Semaphore /* Function A requires access to a resource which it acquires by taking * mySem; * Function A may also need to call function B, which also requires mySem: */ /* includes */ #include <vxWorks.h> #include <semLib.h> SEM_ID mySem; /* Create a mutual-exclusion semaphore. */ 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"); } funcB () { semTake (mySem, WAIT_FOREVER); printf ("funcB: Got mutual-exclusion semaphore\n"); ... semGive (mySem); printf ("funcB: Releases mutual-exclusion semaphore\n"); }
157
Table 6-13 shows an example time sequence of tasks taking and giving a counting semaphore that was initialized to a count of 3.
Table 6-13 Counting Semaphore Example
Semaphore Call
Resulting Behavior
3 2 1 0 0 0 1
Semaphore initialized with an initial count of 3. Semaphore taken. Semaphore taken. Semaphore taken. Task blocks waiting for semaphore to be available. Task waiting is given semaphore. No task waiting for semaphore; count incremented.
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.
158
more than one task (running in different CPUs) can have read-mode access to a resource in a truly concurrent manner. In a uniprocessor system, however, access is shared but the concurrency is virtual. More than one task can have read-mode access to a resource at the same time, but since the tasks do not run simultaneously, access is effectively multiplexed. All tasks that hold a read/write semaphore in read mode must give it up before any task can take it in write mode.
6 Specification of Read or Write Mode
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 (exclusive) mode semWTake( ) for write (concurrent) 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 6-11 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.
159
Read/write semaphores can be taken recursively in both read and write mode. Optionally, priority inheritance and deletion safety are available for each mode.
When a read/write semaphore becomes available, precedence is given to pended tasks that require write access, regardless of their task priority relative to pended tasks that require read access. That is, the highest priority task attempting a semWTake( ) operation gets the semaphore, even if there are higher priority tasks attempting a semRTake( ). Precedence for write access helps to ensure that the protected resource is kept current because there is no delay due to read operations occurring before a pending write operation can take place. Note, however, that all read-mode takes must be given before a read/write semaphore can be taken in write mode.
The performance of systems that implement read/write semaphores for their intended use should be enhanced, particularly so in SMP systems. However, due to the additional bookkeeping overhead involved in tracking multiple read-mode owners, performance is likely to be adversely affected in those cases where the feature does fit a clear design goal. In particular, interrupt latency in a uniprocessor system and kernel latency in a multiprocessor system may be adversely affected.
160
Semaphore Timeout
As an alternative to blocking until a semaphore becomes available, semaphore take operations can be restricted to a specified period of time. If the semaphore is not taken within that period, the take operation fails. This behavior is controlled by a parameter to semTake( ) and the take routines for read/write semaphores that specifies the amount of time in ticks that the task is willing to wait in the pended state. If the task succeeds in taking the semaphore within the allotted time, the take routine returns OK. The errno set when a take routine returns ERROR due to timing out before successfully taking the semaphore depends upon the timeout value passed. A semTake( ) with NO_WAIT (0), which means do not wait at all, sets errno to S_objLib_OBJ_UNAVAILABLE. A semTake( ) with a positive timeout value returns S_objLib_OBJ_TIMEOUT. A timeout value of WAIT_FOREVER (-1) means wait indefinitely.
VxWorks semaphores include the ability to select the queuing mechanism employed for tasks blocked on a semaphore. They can be queued based on either of two criteria: first-in first-out (FIFO) order, or priority order; see Figure 6-12.
Figure 6-12 Task Queue Types
FIFO QUEUE
TCB 110
161
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.
By default, a task pending on a semaphore is not interruptible by signals. A signal is only delivered to a task when it is no longer pending (having timed out or acquired the semaphore). This behavior can be changed for binary and mutex semaphores by using the SEM_INTERRUPTIBLE option when they are created. A task can then receive a signal while pending on a semaphore, and the associated signal handler is executed. However, the semTake( ) call then returns ERROR with errno set to EINTR to indicate that a signal occurred while pending on the semaphore (the task does not return to pending).
Semaphores can send VxWorks events to a specified task when they becomes free. For more information, see 6.16 VxWorks Events, p.169.
162
For information about socket-based message communication across memory spaces (kernel and processes), and between multiple nodes, see 6.17 Message Channels, p.175. Message queues allow a variable number of messages, each of variable length, to be queued. Tasks and ISRs can send messages to a message queue, and tasks can receive messages from a message queue.
Figure 6-13 Full Duplex Communication Using Message Queues
message queue 1
message
task 1
task 2
message
message queue 2 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 6-13. 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 7.16.1 Comparison of POSIX and VxWorks Semaphores, p.237 for a discussion of the differences between the two message-queue designs.
163
Routine
Description
Allocates and initializes a message queue. Terminates and frees a message queue. Sends a message to a message queue. Receives a message from a message queue.
A message queue is created with msgQCreate( ). Its parameters specify the maximum number of messages that can be queued in the message queue and the maximum length in bytes of each message. Enough buffer space is allocated for the specified number and length of messages. A task or ISR sends a message to a message queue with msgQSend( ). If no tasks are waiting for messages on that queue, the message is added to the queues buffer of messages. If any tasks are already waiting for a message from that message queue, the message is immediately delivered to the first waiting task. A task receives a message from a message queue with msgQReceive( ). If messages are already available in the message queues buffer, the first message is immediately dequeued and returned to the caller. If no messages are available, then the calling task blocks and is added to a queue of tasks waiting for messages. This queue of waiting tasks can be ordered either by task priority or FIFO, as specified in an option parameter when the queue is created.
Both msgQSend( ) and msgQReceive( ) take timeout parameters. When sending a message, the timeout specifies how many ticks to wait for buffer space to become available, if no space is available to queue the message. When receiving a message, the timeout specifies how many ticks to wait for a message to become available, if no message is immediately available. As with semaphores, the value of the timeout
164
parameter can have the special values of NO_WAIT (0), meaning always return immediately, or WAIT_FOREVER (-1), meaning never time out the routine.
The msgQSend( ) function allows specification of the priority of the message as either normal (MSG_PRI_NORMAL) or urgent (MSG_PRI_URGENT). Normal priority messages are added to the tail of the list of queued messages, while urgent priority messages are added to the head of the list.
Example 6-3 VxWorks Message Queues /* In this example, task t1 creates the message queue and sends a message * to task t2. Task t2 receives the message from the queue and simply * displays the message. */ /* includes */ #include <vxWorks.h> #include <msgQLib.h> /* defines */ #define MAX_MSGS (10) #define MAX_MSG_LEN (100) MSG_Q_ID myMsgQId; task2 (void) { char msgBuf[MAX_MSG_LEN]; /* get message from queue; if necessary wait until msg is available */ if (msgQReceive(myMsgQId, msgBuf, MAX_MSG_LEN, WAIT_FOREVER) == ERROR) return (ERROR); /* display message */ printf ("Message from task 1:\n%s\n", msgBuf); } #define MESSAGE "Greetings from Task 1" task1 (void) { /* create message queue */ if ((myMsgQId = msgQCreate (MAX_MSGS, MAX_MSG_LEN, MSG_Q_PRIORITY)) == NULL) return (ERROR);
165
/* send a normal priority message, blocking if queue is full */ if (msgQSend (myMsgQId, MESSAGE, sizeof (MESSAGE), WAIT_FOREVER, MSG_PRI_NORMAL) == ERROR) return (ERROR); }
By default, a task pending on a message queue is not interruptible by signals. A signal is only delivered to a task when it is no longer pending (having timed out or acquired the message queue). This behavior can be changed by using the MSG_Q_INTERRUPTIBLE option when creating a message queue. A task can then receive a signal while pending on a message queue, and the associated signal handler is executed. However, the msgQSend( ) or msgQReceive( ) call then returns ERROR with errno set to EINTR to indicate that a signal occurred while pending on the message queue (the task does not return to pending).
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( ).
166
reply queue 1
message
client 2
reply queue 2
message
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.
167
6.15 Pipes
Pipes provide an alternative interface to the message queue facility that goes through the VxWorks I/O system. Pipes are virtual I/O devices managed by the driver pipeDrv. 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. As I/O devices, pipes provide one important feature that message queues cannotthe ability to be used with 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 9.4.9 Pending on Multiple File Descriptors with select( ), p.301. Pipes allow you to implement a client-server model of intertask communications; see 6.14.4 Servers and Clients with Message Queues, p.167.
168
To provide events facilities, VxWorks must be configured with the INCLUDE_VXEVENTS component.
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.
169
170
171
semTake( ) after the event is sent, but before the receiving task unpends from the 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 Rivers 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.
Events and Object Deletion
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.
Events and Task Deletion
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 the resource could not send events to the registered task. Note that a failure to send a message or give a semaphore takes precedence over an events failure.
172
Routine
Description
eventSend( ) eventReceive( )
Sends specified events to a task. Pends a task until the specified events have been received. Can also be used to check what events have been received in the interim. Clears the calling tasks event register. Registers a task to be notified of semaphore availability. Unregisters a task that had previously registered for notification of semaphore availability. Registers a task to be notified of message arrival on a message queue when no recipients are pending. Unregisters a task that had previously registered for notification of message arrival on a message queue.
173
For more information about these routines, see the VxWorks API references for eventLib, semEvLib, and msgQEvLib.
Routine
eventReceive( ) Clears or leaves the contents of the tasks events register intact, depending on the options selected. eventClear( ) eventSend( ) semGive( ) msgQSend( ) Clears the contents of the tasks events register. Writes events to a taskss events register. Writes events to the taskss events register, if the task is registered with the semaphore. Writes events to a tasks events register, if the task is registered with the message queue.
the contents of the event register the desired events the options specified when eventReceive( ) was called
174
the task registered to receive events the events the resource is meant to send to that task the options passed to semEvStart( ) or msgQEvStart( )
175
6.19 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. 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 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.4.2 Task Names and IDs, p.122.). 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.
176
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. 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 Programmers 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 6.16 VxWorks Events, p.169.
177
178
Table 6-17
Routine
Description
signal( ) kill( ) raise( ) sigaction( ) sigsuspend( ) sigpending( ) sigemptyset( ) sigfillset( ) sigaddset( ) sigdelset( ) sigismember( ) sigprocmask( ) sigprocmask( ) sigaltstack( )
Specifies the handler associated with a signal. Sends a signal to a process. Sends a signal to the callers process.
6
Examines or sets the signal handler for a signal. Suspends a task until a signal is delivered. Retrieves a set of pending signals blocked from delivery. Manipulates a signal mask.
Sets the mask of blocked signals. Adds to a set of blocked signals. Set or get a signals alternate stack context.
For more information about signal routines, see the VxWorks API reference for sigLib.
The sigqueue( ) routine includes an application-specified value that is sent as part of the signal. This value supplies whatever context is appropriate for the signal handler. This value is of type sigval (defined in signal.h); the signal handler finds it in the si_value field of one of its arguments, a structure siginfo_t.
179
The sigqueue( ) routine enables the queueing of multiple signals for any task. The kill( ) routine, by contrast, delivers only a single signal, even if multiple signals arrive before the handler runs.
VxWorks includes signals reserved for application use, numbered consecutively from SIGRTMIN to SIGRTMAX. The number of signals reserved is governed by the RTSIG_MAX macro (with a value of 16), which defined in the POSIX 1003.1 standard. The signal values themselves are not specified by POSIX. For portability, specify these signals as offsets from SIGRTMIN (for example, use SIGRTMIN+2 to refer to the third reserved signal number). All signals delivered with sigqueue( ) are queued by numeric order, with lower-numbered signals queuing ahead of higher-numbered signals. POSIX 1003.1 also introduced an alternative means of receiving signals. The routine sigwaitinfo( ) differs from sigsuspend( ) or pause( ) in that it allows your application to respond to a signal without going through the mechanism of a registered signal handler: when a signal is available, sigwaitinfo( ) returns the value of that signal as a result, and does not invoke a signal handler even if one is registered. The routine sigtimedwait( ) is similar, except that it can time out. The basic queued signal routines are described in Table 6-18. For detailed information on signals, see the API reference for sigLib.
Table 6-18 POSIX 1003.1b Queued Signal Routines
Routine
Description
Sends a queued signal to a process. Waits for a signal. Waits for a signal with a timeout.
Additional non-POSIX VxWorks queued signal routines are described in Table 6-19. These routines are provided for assisting in porting VxWorks 5.x kernel applications to processes. The POSIX routines described in Table 6-18 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 routinestaskKill( ), rtpKill( ), and rtpTaskKill( ).
180
Table 6-19
Routine
Description
taskSigqueue( )
Sends a queued signal from a task in a process to another task in the same process, to a public task in another process, or from a kernel task to a process task. Sends a queued signal from a kernel task to a process or from a process to another process.
6
rtpSigqueue( )
rtpTaskSigqueue( ) Sends a queued signal from a kernel task to a specified task in a process (kernel-space only).
Example 6-4 Queued Signals #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
typedef void (*FPTR) (int); 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. */ STATUS main (void) { sigset_t sig = sigmask (SIGUSR1);
181
union sigval sval; struct sigaction in; sigprocmask (SIG_UNBLOCK, &sig, NULL); in.sa_handler = (FPTR) sigMasterHandler; in.sa_flags = 0; (void) sigemptyset (&in.sa_mask); if (sigaction (SIGUSR1, &in, NULL) != OK) { printf ("Unable to set up handler for task (0x%x)\n", taskIdCurrent); return (ERROR); } printf ("Task 0x%x installed signal handler for signal # %d.\ Ready for signal.\n", taskIdCurrent, SIGUSR1); 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
182
* 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; if (TASK_ID_VERIFY (id) == OK) { if (IS_KERNEL_TASK (id)) { if (sigqueue (id, SIGUSR1, valueCode) == ERROR) { printf ("Unable to send SIGUSR1 signal to 0xx%x, errno = 0x%x\n", id, errnoGet()); return ERROR; } } else { rtpTaskSigqueue ((WIND_TCB *)id, SIGUSR1, valueCode); } } else if (OBJ_VERIFY ((RTP_ID)id, rtpClassId) != ERROR) { rtpSigqueue ((RTP_ID)id, SIGUSR1, valueCode); } else { return (ERROR); } 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.
183
To send a queued signal to a process: 1. 2. Build the application as an RTP application. Spawn the application. For example:
-> rtpSp "signal_ex.vxe" value = 10531472 = 0xa0b290 -> Task 0x10000 installed signal handler for signal # 30. signal.
Ready for
3.
-> rtpShow
NAME ID STATE ENTRY ADDR OPTIONS TASK CNT -------------------- ---------- --------------- ---------- ---------- -------signal_ex.vxe 0xa0b290 STATE_NORMAL 0x10000298 0x1 1 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
To send a queued signal to a task in a process: 1. 2. 3. Build the code as an RTP application. Spawn the application. From a kernel task (such as the kernel shell), use the sig( ) helper routine to send a queued signal to an RTP task, where the id parameter is the RTP task ID.
For information on using the code in the kernel (as a kernel application), see the VxWorks Application Programmers Guide: Multitasking.
184
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
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).
185
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 implement. 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.
Table 6-20
Library
Routines
bLib errnoLib eventLib logLib lstLib msgQLib rngLib semLib sigLib taskLib
All routines errnoGet( ), errnoSet( ) eventSend( ) logMsg( ) All routines except lstFree( ) msgQSend( ) All routines except rngCreate( ) and rngDelete( ) semGive( ) except mutual-exclusion semaphores, semFlush( ) kill( ) taskSuspend( ), taskResume( ), taskPrioritySet( ), taskPriorityGet( ), taskIdVerify( ), taskIdDefault( ), taskIsReady( ), taskIsSuspended( ), taskIsPended( ), taskIsDelayed( ), taskTcb( ) tickAnnounce( ), tickSet( ), tickGet( )
tickLib
Most signals are delivered asynchronously to the execution of a program. Therefore programs must be written to account for the unexpected occurrence of signals, and handle them gracefully. Unlike ISR's, signal handlers execute in the context of the interrupted task. VxWorks does not distinguish between normal task execution and a signal context, as it distinguishes between a task context and an ISR. Therefore the system
186
has no way of distinguishing between a task execution context and a task executing a signal handler. To the system, they are the same. When you write signal handlers make sure that they:
Release resources prior to exiting: Free any allocated memory. Close any open files. Release any mutual exclusion resources such as semaphores.
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.
187
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.
6.20 Timers
VxWorks provides watchdog timers, but they can only be used in the kernel (see VxWorks Kernel Programmers Guide: Multitasking. However, real-time process (RTP) applications can use POSIX timers. For information in this regard, see 7.9 POSIX Clocks and Timers, p.204).
188
7
POSIX Facilities
7.1 Introduction 191 7.2 Configuring VxWorks with POSIX Facilities 192 7.3 General POSIX Support 196 7.4 Standard C Library: libc 198 7.5 POSIX Header Files 199 7.6 POSIX Namespace 201 7.7 POSIX Process Privileges 203 7.8 POSIX Process Support 203 7.9 POSIX Clocks and Timers 204 7.10 POSIX Asynchronous I/O 208 7.11 POSIX Advisory File Locking 209 7.12 POSIX Page-Locking Interface 209 7.13 POSIX Threads 210 7.14 POSIX Thread Mutexes and Condition Variables 220 7.15 POSIX and VxWorks Scheduling 225 7.16 POSIX Semaphores 235 7.17 POSIX Message Queues 245 7.18 POSIX Signals 259
189
190
7.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 10.4 Highly Reliable File System: HRFS, p.327). For detailed information about POSIX standards and facilities, see The Open Group Web sites at http://www.opengroup.org/ and http://www.unix.org/.
POSIX and Real-Time Systems
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 functionpages are always locked in VxWorks systems (for more information see 7.12 POSIX Page-Locking Interface, p.209).
191
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 7.13 POSIX Threads, p.210 and 7.15 POSIX and VxWorks Scheduling, p.225).
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.
POSIX and VxWorks Facilities
This chapter describes the POSIX support provided by VxWorks and VxWorks-specific POSIX extensions. In addition, it compares native VxWorks facilities with similar POSIX facilities that are also available with VxWorks. The qualifier VxWorks is used in this chapter to identify native non-POSIX APIs for purposes of comparison with POSIX APIs. For example, you can find a discussion of VxWorks semaphores contrasted to POSIX semaphores in 7.16.1 Comparison of POSIX and VxWorks Semaphores, p.237, although POSIX semaphores are also implemented in VxWorks. VxWorks extensions to POSIX are identified as such.
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 Programmers Guide.
192
Note that support for POSIX threads (pthreads) in processes requires that the kernel be configured with the POSIX thread scheduler component INCLUDE_POSIX_PTHREAD_SCHEDULER. . !
CAUTION: The set of components used for POSIX support in user space is not the same as the set of components used for POSIX support in kernel space. For information about the components for kernel space, see Table 7-1 and the VxWorks Kernel Programmer's Guide: POSIX Facilities for the appropriate component bundle. 7
INCLUDE_HRFS for the highly reliable file system. See 10.4 Highly Reliable File System: HRFS, p.327.
The appropriate device driver component for example INCLUDE_ATA or INCLUDE_XBD_RAMDRVand INCLUDE_XBD_BLK_DEV if the device driver requires it (that is, if it is not XBD-compatible). See the VxWorks Kernel Programmers 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 10.4.3 HRFS and POSIX PSE52, p.329).
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 10.3 Virtual Root File System: VRFS, p.325. For information about null devices, see 9.8.6 Null Devices, p.316. VxWorks PSE52 support in processes includes the following units of functionality (POSIX.13):
193
POSIX_C_LANG_JUMP POSIX_C_LANG_MATH POSIX_C_LANG_SUPPORT POSIX_DEVICE_IO POSIX_FD_MGMT POSIX_FILE_LOCKING POSIX_FILE_SYSTEM POSIX_SIGNALS POSIX_SINGLE_PROCESS POSIX_THREADS_BASE XSI_THREAD_MUTEX_EXT XSI_THREADS_EXT
_POSIX_CLOCK_SELECTION _POSIX_FSYNC _POSIX_MAPPED_FILES _POSIX_MEMLOCK _POSIX_MEMLOCK_RANGE _POSIX_MESSAGE_PASSING _POSIX_MONOTONIC_CLOCK _POSIX_NO_TRUNC _POSIX_REALTIME_SIGNALS _POSIX_SHARED_MEMORY_OBJECTS _POSIX_SYNCHRONIZED_IO _POSIX_THREAD_ATTR_STACKADDR _POSIX_THREAD_ATTR_STACKSIZE _POSIX_THREAD_CPUTIME _POSIX_THREAD_PRIO_INHERIT _POSIX_THREAD_PRIO_PROTECT _POSIX_THREAD_PRIORITY_SCHEDULING _POSIX_THREAD_SAFE_FUNCTIONS _POSIX_THREAD_SPORADIC_SERVER _POSIX_THREADS _POSIX_TIMEOUTS _POSIX_TIMERS
For information about POSIX namespace isolation for PSE52-conforming applications, see 7.5 POSIX Header Files, p.199. !
CAUTION: If a PSE52-conforming application relies on C99 constructs, the Wind River Compiler must be used. The version of the GNU compiler provided with this release does not support the C99 language constructs.
194
Standard C library INCLUDE_ANSI_* components Asynchronous I/O INCLUDE_POSIX_AIO, with system driver INCLUDE_POSIX_AIO_SYSDR V and INCLUDE_PIPES Clocks Directory and file utilities ftruncate( ) Memory locking Memory management Memory-mapped files Shared memory objects Message queues pthreads
INCLUDE_POSIX_CLOCKS INCLUDE_POSIX_DIRLIB
INCLUDE_POSIX_FTRUNC INCLUDE_POSIX_MEM
N/A
INCLUDE_POSIX_SEM
N/A
195
Table 7-1
INCLUDE_POSIX_TIMERS
N/A N/A
Networking facilities are described in the Wind River Network Stack Programmers Guide.
196
Table 7-2
POSIX Libraries
Functionality
Library
Asynchronous I/O Buffer manipulation Clock facility Directory handling Environment handling Environment information File duplication File management I/O functions Options handling POSIX message queues POSIX semaphores POSIX timers POSIX threads Standard I/O and some ANSI Math Memory allocation Network/Socket APIs String manipulation Trace facility Wide character support
aioPxLib bLib clockLib dirLib C Library sysconf and uname ioLib fsPxLib and ioLib ioLib getopt mqPxLib semPxLib timerLib pthreadLib C Library C Library memLib network libraries C Library pxTraceLib C library
7
The following sections of this chapter describe the POSIX APIs available to user-mode applications in addition to the native VxWorks APIs.
197
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.
Checking for POSIX Support at Run-time
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.
198
Header File
PSE52 Description
aio.h assert.h complex.h ctype.h dirent.h dlfcn.h errno.h fcntl.h fenv.h float.h inttypes.h iso646.h limits.h locale.h math.h mqueue.h pthread.h
* * * * * * * * * * * * * * * * *
asynchronous input and output verify program assertion complex arithmetic character types format of directory entries dynamic linking system error numbers file control options floating-point environment floating types fixed size integer types alternative spellings implementation-defined constants category macros mathematical declarations message queues pthreads
199
Table 7-3
Header File
PSE52 Description
sched.h search.h semaphore.h setjmp.h signal.h stdbool.h stddef.h stdint.h stdio.h stdlib.h string.h strings.h
sys/mman.h sys/resource.h sys/select.h sys/stat.h sys/types.h sys/un.h sys/utsname.h sys/wait.h
* * * * * * * * * * * * * *
execution scheduling search tables semaphores stack environment declarations signals boolean type and values standard type definitions integer types standard buffered input/output standard library definitions string operations string operations memory management declarations definitions for XSI resource operations select types data returned by the stat( ) function data types definitions for UNIX domain sockets system name structure declarations for waiting type-generic macros time types trace facility
200
Table 7-3
Header File
PSE52 Description
standard symbolic constants and types access and modification times structure wide-character handling wide-character classification and mapping utilities
7
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 applications 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 applications 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.
201
The POSIX header files that have been implemented in compliance with the POSIX PSE52 profile are identified in Table 7-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).
Using POSIX Feature-Test Macros
POSIX provides feature-test macros used during the compilation process to ensure application namespace isolation. These macros direct the compilers 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:
Wind River supports use of these macros for use with POSIX PSE52 header files (see 7.5 POSIX Header Files, p.199). 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. !
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.
202
CAUTION: If a PSE52-conforming application relies on C99 constructs, the Wind River Compiler must be used. The version of the GNU compiler provided with this release does not support the C99 language constructs. 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 integerthat is, with a 32-bit size. 7
203
Table 7-4
Routine
Description
Register a handler to be called at exit( ). Terminate the calling process (system call). Terminate a process, calling atexit( ) handlers. Get the process ID of the current process. Get the process ID of the parent's process ID. Send a signal to a process. Send a signal to the caller's process. Wait for any child process to die. Wait for a specific child process to die.
Basic VxWorks process facilities, including these routines, are provided with the INCLUDE_RTP component.
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.
204
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 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 7-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.
205
Table 7-5
Routine
Description
clock_getres( )
Get the clock resolution (CLOCK_REALTIME, CLOCK_MONOTONIC, and thread CPU-time). Set the clock resolution. Obsolete VxWorks-specific POSIX extension. Get the current clock time (CLOCK_REALTIME, CLOCK_MONOTONIC, and thread CPU-time). Set the thread CPU-time clock (fails for
CLOCK_REALTIME or CLOCK_MONOTONIC).
clock_setres( ) clock_gettime( )
clock_settime( ) clock_nanosleep( )
Suspend the current pthread (or task) for a specified amount of time.
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. 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.
206
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 6.19 Signals, p.176). See Table 7-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.
Table 7-6 POSIX Timer Routines
7
Description
Routine
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). Remove a previously created timer. Open a named timer. VxWorks-specific POSIX extension. Close a named timer. VxWorks-specific POSIX extension. Get the remaining time before expiration and the reload value.
timer_getoverrun( ) Return the timer expiration overrun. timer_settime( ) timer_cancel( ) timer_connect( ) Set the time until the next expiration and arm timer. Cancel a timer. VxWorks-specific POSIX extension. Connect a user routine to the timer signal. VxWorks-specific POSIX extension.
207
Table 7-6
Routine
Description
Unlink a named timer. VxWorks-specific POSIX extension. Suspend the current pthread (task) until the time interval elapses. Delay for a specified amount of time. Set an alarm clock for delivery of a signal.
/* This example creates a new timer and stores it in timerid. */ /* includes */ #include <vxWorks.h> #include <time.h> int createTimer (void) { timer_t timerid; /* create timer */ if (timer_create (CLOCK_REALTIME, NULL, &timerid) == ERROR) { printf ("create FAILED\n"); return (ERROR); } return (OK); }
The POSIX nanosleep( ) routine provides specification of sleep or delay time in units of seconds and nanoseconds, in contrast to the ticks used by the VxWorks taskDelay( ) function. Nevertheless, the precision of both is the same, and is determined by the system clock rate; only the units differ. To include the timerLib library in a system, configure VxWorks with the INCLUDE_POSIX_TIMERS component. Process-based (RTP) applications are automatically linked with the timerLib library when they are compiled. The library is automatically initialized when the process starts.
208
209
Routine
Locks into memory all pages used by a task. Unlocks all pages used by a task. Locks a specified page. Unlocks a specified page.
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.
210
For porting POSIX applications to VxWorks. To make use of the POSIX thread scheduler in real-time processes (including concurrent scheduling policies).
7
For information about POSIX thread scheduler, see 7.15 POSIX and VxWorks Scheduling, p.225.
211
For information about task guard zones, see 6.4.5 Task Stack, p.125.
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( )
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
212
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( )
7
213
Example 7-3
Creating a pthread Using Default Attributes pthread_t tid; int ret; /* create the pthread with NULL attributes to designate default values */ ret = pthread_create(&tid, NULL, entryFunction, entryArg);
Example 7-4
Designating Your Own Stack for a pthread pthread_t threadId; pthread_attr_t attr; void * stackaddr = NULL; int stacksize = 0; /* initialize the thread's attributes */ 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. */ stacksize = 2 * 4096 /* let's allocate two pages */ stackaddr = malloc (stacksize); if (stackbase == NULL) { printf ("FAILED: mystack: malloc failed\n"); return (-1); } /* set the stackaddr attribute */ pthread_attr_setstackaddr (&attr, stackaddr); /* set the stacksize attribute */ pthread_attr_setstacksize (&attr, stacksize); /* set the schedpolicy attribute to SCHED_FIFO */ pthread_attr_setschedpolicy (&attr, SCHED_FIFO); /* create the pthread */ ret = pthread_create (&threadId, &attr, mystack_thread, 0);
214
Routine
Description
pthread_create( ) pthread_cancel( ) pthread_detach( ) pthread_join( ) pthread_getschedparam( ) pthread_setschedparam( ) pthread_setschedprio( ) sched_get_priority_max( ) sched_get_priority_min( ) sched_rr_get_interval( ) sched_yield( ) pthread_getconcurrency( ) pthread_setconcurrency( )
Create a pthread. Cancel the execution of a pthread Detach a running pthread so that it cannot be joined by another pthread. Wait for a pthread to terminate. Dynamically set value of scheduling priority attribute. Dynamically set scheduling priority and policy parameter. Dynamically set scheduling priority parameter. Get the maximum priority that a pthread can get. Get the minimum priority that a pthread can get. Get the time quantum of execution of the round-robin policy. Relinquishes the CPU. Get level of concurrency. In VxWorks this routine does nothing. Set level of concurrency In VxWorks this routine does nothing.
215
Routine
Description
pthread_attr_getstacksize( ) pthread_attr_setstacksize( ) pthread_attr_getstackaddr( ) pthread_attr_setstackaddr( ) pthread_attr_getstack( ) pthread_attr_setstack( ) pthread_attr_getguardsize( ) pthread_attr_setguardsize( ) pthread_attr_getdetachstate( ) pthread_attr_setdetachstate( ) pthread_attr_getscope( )
Get value of the stack size attribute. Set the stack size attribute. Get value of stack address attribute. Set value of stack address attribute. Get the values of the stack address and size attributes. Set the values of the stack address and size attributes. Get the stack guard zone size attribute. Set the stack guard zone size attribute. Get value of detachstate attribute (joinable or detached). Set value of detachstate attribute (joinable or detached). Get contention scope. Only PTHREAD_SCOPE_SYSTEM is supported for VxWorks. Set contention scope. Only PTHREAD_SCOPE_SYSTEM is supported for VxWorks. Get value of scheduling-inheritance attribute. Set value of scheduling-inheritance attribute.
pthread_attr_setscope( )
pthread_attr_getinheritsched( ) pthread_attr_setinheritsched( )
216
Table 7-9
Routine
Description
Get value of the scheduling-policy attribute (which is not used by default). Set scheduling-policy attribute (which is not used by default). Get value of scheduling priority attribute.
7
Set scheduling priority attribute. Get the task options applying to the pthread. VxWorks-specific POSIX extension. Set non-default task options for the pthread. VxWorks-specific POSIX extension. Get the name of the pthread. VxWorks-specific POSIX extension. Set a non-default name for the pthread. VxWorks-specific POSIX extension.
217
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.
The code of the pthread executes calls to pthread_testcancel( ) at regular intervals. 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.
The POSIX cancellation points provided in VxWorks libraries are described in Table 7-10.
1. Asynchronous cancellation is actually implemented with a special signal, SIGCNCL, which users should be careful not to block or to ignore.
218
Table 7-10
Library
Routines
aio_suspend( ) creat( ), open( ), read( ), write( ), close( ), fsync( ), fdatasync( ) mq_receive( ), mq_send( )
7
pthreadLib pthread_cond_timedwait( ), pthread_cond_wait( ), pthread_join( ), pthread_testcancel( ) semPxLib sigLib timerLib sem_wait( ) pause( ), sigsuspend( ), sigtimedwait( ), sigwait( ), sigwaitinfo( ), waitpid( ) sleep( ), nanosleep( )
Routines that can be used with cancellation points of pthreads are listed in Table 7-11.
Table 7-11 Pthread Cancellation Routines
Routine
Description
Cancel execution of a pthread. Create a cancellation point in the calling pthread. Enables or disables cancellation. Selects deferred or asynchronous cancellation. Registers a function to be called when the pthread is cancelled, exits, or calls pthread_cleanup_pop( ) with a non-null run parameter. Unregisters a function previously registered with pthread_cleanup_push( ). This function is immediately executed if the run parameter is non-null.
pthread_cleanup_pop( )
219
Routine
Description
Destroy a mutex. Initialize a mutex. Get the priority ceiling of a mutex. Set the priority ceiling of a mutex. Lock a mutex. Check and lock a mutex if available. Unlock a mutex. Lock a mutex with timeout.
220
Table 7-13
Routine
Description
Initialize mutex attributes object. Destroy mutex attributes object. Get prioceiling attribute of mutex attributes object. Set prioceiling attribute of mutex attributes object. Get protocol attribute of mutex attributes object. Set protocol attribute of mutex attributes object. Get a mutex type attribute. Set a mutex type attribute.
7
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
221
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 6.13.4 Mutual-Exclusion Semaphores, p.152).
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 inheritanceand 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.
222
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 7.15.2 POSIX and VxWorks Priority Numbering, p.227. 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 pthreads priority is restored to its previous value when the mutex is released.
Routine
Description
Destroy condition variables. Initialize condition variables. Broadcast a condition. Signal a condition.
223
Table 7-14
Routine
Description
pthread_cond_wait( ) pthread_cond_timedwait( )
Table 7-15
Routine
Description
Destroy condition variable attributes object. Initialize condition variable attributes object. Get the clock selection condition variable attribute. Set the clock selection condition variable attribute.
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 7.9 POSIX Clocks and Timers, p.204.
224
VxWorks APIs in the same application. Doing so may make a POSIX application non-compliant, and is in any case not advisable. Table 7-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.233.) 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.
225
Table 7-16
Execution Environment
Kernel
Same as task scheduling. No concurrent scheduling policies. POSIX FIFO, round-robin, sporadic, or other (system default). Concurrent scheduling policies available.
Same as task scheduling. No concurrent scheduling policies. N/A. Pthreads cannot be run in processes with traditional VxWorks scheduler.a
Processes
a. The traditional VxWorks scheduler cannot ensure behavioral compliance with the POSIX 1 standard.
POSIX supports a two-level scheduling model that includes the concept of contention scope, by which the scheduling of pthreads can apply system wide or on a process basis. In VxWorks, on the other hand, processes (RTPs) cannot themselves be scheduled, and tasks and pthreads are scheduled on a system-wide (kernel and processes) basis. POSIX applies scheduling policies on a process-by-process and thread-by-thread basis. VxWorks applies scheduling policies on a system-wide basis, for all tasks and pthreads, whether in the kernel or in processes. This means that all tasks and pthreads use either a preemptive priority scheme or a round-robin scheme. The only exception to this rule is that pthreads executing in processes can be subject to concurrent (individual) scheduling policies, including sporadic scheduling (note that the POSIX thread scheduler must be used in this case).
226
POSIX supports the concept of scheduling allocation domain; that is, the association between processes or threads and processors. Since VxWorks does not support multi-processor hardware, there is only one domain on VxWorks and all the tasks and pthreads are associated to it. The POSIX priority numbering scheme is the inverse of the VxWorks scheme. For more information see 7.15.2 POSIX and VxWorks Priority Numbering, p.227. VxWorks does not support the POSIX thread-concurrency feature, as all threads are scheduled. The POSIX thread-concurrency APIs are provided for application portability, but they have no effect.
227
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.
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.232. 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.
228
VxWorks is configured with the traditional scheduler by default. This scheduler is provided by the INCLUDE_VX_TRADITIONAL_SCHEDULER component.
Caveats About Scheduling Behavior with the VxWorks Traditional Scheduler
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 7.15.5 POSIX Threads Scheduler, p.229.
7
229
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 7.15.2 POSIX and VxWorks Priority Numbering, p.227.
Scheduling in the Kernel
The POSIX thread scheduler schedules kernel tasks and kernel pthreads in the same manner as the traditional VxWorks task scheduler. See 6.3 Task Scheduling, p.115 for information about the traditional scheduler and how it works with VxWorks tasks, and 7.15.4 VxWorks Traditional Scheduler, p.228 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.233). 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.
230
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. 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.
To configure VxWorks with the POSIX thread scheduler, add the INCLUDE_POSIX_PTHREAD_SCHEDULER component to the kernel. Note that only the SCHED_FIFO, SCHED_RR, and SCHED_OTHER scheduling policies are provided with the INCLUDE_POSIX_PTHREAD_SCHEDULER component. For the SCHED_SPORADIC scheduling policy, the INCLUDE_PX_SCHED_SPORADIC_POLICY component must be included as well. The bundle BUNDLE_RTP_POSIX_PSE52 includes the INCLUDE_PX_SCHED_SPORADIC_POLICY component as well as the INCLUDE_POSIX_PTHREAD_SCHEDULER component. The configuration parameter POSIX_PTHREAD_RR_TIMESLICE may be used to configure the default time slicing interval for pthreads started with the SCHED_RR policy. To modify the time slice at run time, call kernelTimeSlice( ) with a different time slice value. The new time slice value only affects pthreads created after the kernelTimeSlice( ) call.
NOTE: The INCLUDE_POSIX_PTHREAD_SCHEDULER component is a standalone component. It is not dependent on any other POSIX components nor is it automatically included with any other components.
The POSIX thread scheduler must be added explicitly with either the INCLUDE_POSIX_PTHREAD_SCHEDULER component or the BUNDLE_RTP_POSIX_PSE52 bundle. The POSIX thread scheduler component is independent because it is intended to be used only with pthreads in processes; kernel-only systems that use pthreads, have no need to change from the default VxWorks traditional scheduler.
231
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.
Using a combination of round-robin and priority-based preemptive policies for tasks and pthreads of the same priority level can lead to task or pthread CPU starvation for the entities running with the round-robin policy. When VxWorks is running with round-robin scheduling as the system default, tasks may not run with their expected time slice if there are pthreads running at the same priority level with the concurrent (individual) SCHED_FIFO policy. This is because one of the pthreads may monopolize the CPU and starve the tasks. Even if the usurper pthread is preempted, it remains at the front of the ready queue priority list for that priority (as POSIX mandates), and continues to monopolize the CPU when that priority level can run again. Pthreads scheduled with the SCHED_RR or SCHED_OTHER policy are at the same disadvantage as the tasks scheduled with the round-robin policy. Similarly, when VxWorks is running with preemptive scheduling as the system default, tasks may starve pthreads with the same priority level if the latter have the concurrent (individual) SCHED_RR policy.
Running pthreads with the Concurrent SCHED_OTHER Policy
Pthreads created with the concurrent (individual) SCHED_OTHER policy behave the same as the system-wide default scheduling policy, which means that:
If the system default is currently priority-based preemptive scheduling, the SCHED_OTHER pthreads run with the preemptive policy. If the system default is currently round-robin scheduling, the SCHED_OTHER pthreads run with the round-robin policy.
232
While changing the default system policy from priority-based preemptive scheduling to round-robin scheduling (or the opposite) changes the effective scheduling policy for pthreads created with SCHED_OTHER, it has no effect on pthreads created with SCHED_RR or SCHED_FIFO.
Differences in Re-Queuing Pthreads and Tasks With Lowered Priorities
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 loweredwith the pthread_setschedprio( ) routinethe POSIX thread scheduler places it at the front of the ready queue list for that priority. When the priority of a task is loweredwith 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). 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.118.
Backwards Compatibility Issues for Applications
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).
233
Routine
Description
sched_get_priority_max( ) Gets the maximum pthread priority. sched_get_priority_min( ) Gets the minimum pthread priority. sched_rr_get_interval( ) sched_yield( ) If round-robin scheduling is in effect, gets the time slice length. Relinquishes the CPU.
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 7.15.2 POSIX and VxWorks Priority Numbering, p.227. 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.
234
Example 7-5
Getting the POSIX Round-Robin Time Slice /* * The following example gets the length of the time slice, * and then displays the time slice. */ /* includes */ #include <time.h> #include <sched.h> STATUS rrgetintervalTest (void) { struct timespec slice; if (sched_rr_get_interval (0, &slice) == ERROR) { printf ("get-interval test failed\n"); return (ERROR); } printf ("time slice is %ld seconds and %ld nanoseconds\n", slice.tv_sec, slice.tv_nsec); return (OK); }
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 objects ID.
235
Table 7-18
Routine
Description
sem_init( ) sem_destroy( ) sem_open( ) sem_close( ) sem_unlink( ) sem_wait( ) sem_trywait( ) sem_post( ) sem_getvalue( ) sem_timedwait( )
Initializes an unnamed semaphore. Destroys an unnamed semaphore. Initializes/opens a named semaphore. Closes a named semaphore. Removes a named semaphore. Lock a semaphore. Lock a semaphore only if it is not already locked. Unlock a semaphore. Get the value of a semaphore. Lock a semaphore with a timeout.
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 kernels 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.
236
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 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. !
WARNING: When deleting semaphores, particularly mutual-exclusion
semaphores, avoid deleting a semaphore still required by another task. Do not delete a semaphore unless the deleting task first succeeds in locking that semaphore. Similarly for named semaphores, close semaphores only from the same task that opens them.
237
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 application, unnamed semaphores can be accessed only by the tasks belonging to the process executing the application. Only public named semaphores (that is, named semaphores whose name begins with a forward slash) can be shared between processes or between processes and the kernel. For information about public objects, see 6.9 Inter-Process Communication With Public Objects, p.140.
238
Example 7-6
POSIX Unnamed Semaphores /* * This example uses unnamed semaphores to synchronize an action between the * calling task and a task that it spawns (tSyncTask). To run from the shell, * spawn as a task: * * -> sp unnameSem */ /* includes */ #include <vxWorks.h> #include <semaphore.h> /* forward declarations */ void syncTask (sem_t * pSem); /************************************************************************ * unnameSem - test case for unamed semaphores * * This routine tests unamed semaphores. * * RETURNS: N/A * * ERRNOS: N/A */ void unnameSem (void) { sem_t * pSem; /* reserve memory for semaphore */ pSem = (sem_t *) malloc (sizeof (sem_t)); if (pSem == NULL) { printf ("pSem allocation failed\n"); return; } /* initialize semaphore to unavailable */ if (sem_init (pSem, 0, 0) == -1) { printf ("unnameSem: sem_init failed\n"); free ((char *) pSem); return; } /* create sync task */ printf ("unnameSem: spawning task...\n");
239
if (taskSpawn ("tSyncTask", 90, 0, 2000, syncTask, pSem) == ERROR) { printf ("Failed to spawn tSyncTask\n"); sem_destroy (pSem); free ((char *) pSem); return; } /* do something useful to synchronize with syncTask */ /* unlock sem */ printf ("unnameSem: posting semaphore - synchronizing action\n"); if (sem_post (pSem) == -1) { printf ("unnameSem: posting semaphore failed\n"); sem_destroy (pSem); free ((char *) pSem); return; } /* all done - destroy semaphore */ if (sem_destroy (pSem) == -1) { printf ("unnameSem: sem_destroy failed\n"); return; } free ((char *) pSem); } void syncTask ( sem_t * pSem ) { /* wait for synchronization from unnameSem */ if (sem_wait (pSem) == -1) { printf ("syncTask: sem_wait failed \n"); return; } else printf ("syncTask: sem locked; doing synced action...\n"); /* do something useful here */ }
240
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 7-19.
Table 7-19 Possible Outcomes of Calling sem_open( )
Flag Settings
If Semaphore Exists
None
O_CREAT O_CREAT and O_EXCL O_EXCL
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
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 shells C interpreter).
241
This example shows information about the POSIX semaphore mySem with two tasks blocked and waiting for it:
-> show semId value = 0 = 0x0 Semaphore name sem_open() count Semaphore value No. of blocked tasks
:mySem :3 :0 :2
Note that show( ) takes the semaphore ID as the argument. For a group of collaborating tasks to use a named semaphore, one of the tasks first creates and initializes the semaphore, by calling sem_open( ) with the O_CREAT flag. Any task that must use the semaphore thereafter, opens it by calling sem_open( ) with the same name, but without setting O_CREAT. Any task that has opened the semaphore can use it by locking it with sem_wait( ) (blocking) or sem_trywait( ) (non-blocking), and then unlocking it with sem_post( ) when the task is finished with the semaphore. To remove a semaphore, all tasks using it must first close it with sem_close( ), and one of the tasks must also unlink it. Unlinking a semaphore with sem_unlink( ) removes the semaphore name from the name table. After the name is removed from the name table, tasks that currently have the semaphore open can still use it, but no new tasks can open this semaphore. If a task tries to open the semaphore without the O_CREAT flag, the operation fails. An unlinked semaphore is deleted by the system when the last task closes it.
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 6.9 Inter-Process Communication With Public Objects, p.140.
242
Example 7-7
POSIX Named Semaphores /* * In this example, nameSem() creates a task for synchronization. The * new task, tSyncSemTask, blocks on the semaphore created in nameSem(). * Once the synchronization takes place, both tasks close the semaphore, * and nameSem() unlinks it. To run this task from the shell, spawn * nameSem as a task: * -> sp nameSem, "myTest" */ /* includes */ #include #include #include #include #include <vxWorks.h> <taskLib.h> <stdio.h> <semaphore.h> <fcntl.h>
/* forward declaration */ void syncSemTask (char * name); /**************************************************************************** * * 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; /* create a named semaphore, initialize to 0*/ printf ("nameSem: creating semaphore\n"); if ((semId = sem_open (name, O_CREAT, 0, 0)) == (sem_t *) -1) { printf ("nameSem: sem_open failed\n"); return; } printf ("nameSem: spawning sync task\n"); if (taskSpawn ("tSyncSemTask", 90, 0, 4000, (FUNCPTR) syncSemTask, (int) name, 0, 0, 0, 0, 0, 0, 0, 0, 0) == ERROR) { printf ("nameSem: unable to spawn tSyncSemTask\n"); sem_close(semId);
243
return; } /* do something useful to synchronize with syncSemTask */ /* give semaphore */ printf ("nameSem: posting semaphore - synchronizing action\n"); if (sem_post (semId) == -1) { printf ("nameSem: sem_post failed\n"); sem_close(semId); return; } /* all done */ if (sem_close (semId) == -1) { printf ("nameSem: sem_close failed\n"); return; } if (sem_unlink (name) == -1) { printf ("nameSem: sem_unlink failed\n"); return; } printf ("nameSem: closed and unlinked semaphore\n"); } /**************************************************************************** * * 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; }
244
/* block waiting for synchronization from nameSem */ printf ("syncSemTask: attempting to take semaphore...\n"); if (sem_wait (semId) == -1) { printf ("syncSemTask: taking sem failed\n"); return; } printf ("syncSemTask: has semaphore, doing sync'ed action ...\n"); /* do something useful here */ if (sem_close (semId) == -1) { printf ("syncSemTask: sem_close failed\n"); return; } }
Routine
Description
Opens a message queue. Closes a message queue. Removes a message queue. Sends a message to a queue. Gets a message from a queue. Signals a task that a message is waiting on a queue. Sets a queue attribute. Gets a queue attribute. Sends a message to a queue, with a timeout.
245
Table 7-20
Routine
Description
mq_timedreceive( )
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 7-21 describes the permissions that are allowed.
Table 7-21 Message Queue Permissions
Access Forbidden
Process-based (RTP) applications are automatically linked with the mqPxLib library when they are compiled. Initialization of the library is automatic as well, when the process is started. For information about the VxWorks message queue library, see the msgQLib API reference.
246
Table 7-22
Feature
1 (specified by
MSG_PRI_NORMAL | M SG_PRI_URGENT)
32 (specified by
MAX_PRIO_MAX)
Blocked Message Queues Received with Timeout Task Notification Close/Unlink Semantics Send with Timeout
an optional O_NONBLOCK flag, which prevents a mq_receive( ) call from being a blocking call if the message queue is empty the maximum number of messages in the message queue the maximum message size the number of messages currently on the queue
Tasks can set or clear the O_NONBLOCK flag using mq_setattr( ), and get the values of all the attributes using mq_getattr( ). (As allowed by POSIX, this implementation of message queues makes use of a number of internal flags that are not public.)
Example 7-8 Setting and Getting Message Queue Attributes /* * This example sets the O_NONBLOCK flag and examines message queue * attributes. */
247
/* includes */ #include <vxWorks.h> #include <mqueue.h> #include <fcntl.h> #include <errno.h> /* defines */ #define MSG_SIZE int attrEx ( char * name ) { mqd_t struct mq_attr struct mq_attr char int
16
/* create read write queue that is blocking */ attr.mq_flags = 0; attr.mq_maxmsg = 1; attr.mq_msgsize = 16; if ((mqPXId = mq_open (name, O_CREAT | O_RDWR , 0, &attr)) == (mqd_t) -1) return (ERROR); else printf ("mq_open with non-block succeeded\n"); /* change attributes on queue - turn on non-blocking */ attr.mq_flags = O_NONBLOCK; if (mq_setattr (mqPXId, &attr, &oldAttr) == -1) return (ERROR); else { /* paranoia check - oldAttr should not include non-blocking. */ if (oldAttr.mq_flags & O_NONBLOCK) return (ERROR); else printf ("mq_setattr turning on non-blocking succeeded\n"); } /* try receiving - there are no messages but this shouldn't block */ if (mq_receive (mqPXId, buffer, MSG_SIZE, &prio) == -1) { if (errno != EAGAIN) return (ERROR); else printf ("mq_receive with non-blocking didnt block on empty queue\n"); } else return (ERROR);
248
/* use mq_getattr to verify success */ if (mq_getattr (mqPXId, &oldAttr) == -1) return (ERROR); else { /* test that we got the values we think we should */ if (!(oldAttr.mq_flags & O_NONBLOCK) || (oldAttr.mq_curmsgs != 0)) return (ERROR); else printf ("queue attributes are:\n\tblocking is %s\n\t message size is: %d\n\t max messages in queue: %d\n\t no. of current msgs in queue: %d\n", oldAttr.mq_flags & O_NONBLOCK ? "on" : "off", oldAttr.mq_msgsize, oldAttr.mq_maxmsg, oldAttr.mq_curmsgs); } /* clean up - close and unlink mq */ if (mq_unlink (name) == -1) return (ERROR); if (mq_close (mqPXId) == -1) return (ERROR); return (OK); }
249
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.
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 6.9 Inter-Process Communication With Public Objects, p.140.
250
Example 7-9
POSIX Message Queues /* * 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 */ /* mqEx.h - message example header */ /* defines */ #define MQ_NAME "exampleMessageQueue" /* forward declarations */ void receiveTask (void); void sendTask (void); /* testMQ.c - example using POSIX message queues */ /* includes */ #include #include #include #include #include #include #include <vxWorks.h> <taskLib.h> <stdio.h> <mqueue.h> <fcntl.h> <errno.h> <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 */
251
int mqExInit (void) { /* create two tasks */ if (taskSpawn ("tRcvTask", 151, 0, 4000, (FUNCPTR) receiveTask, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) == ERROR) { printf ("taskSpawn of tRcvTask failed\n"); return (ERROR); } if (taskSpawn ("tSndTask", 152, 0, 4000, (FUNCPTR) sendTask, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) == ERROR) { printf ("taskSpawn of tSendTask failed\n"); return (ERROR); } return (OK); }
/**************************************************************************** * * 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 */ void receiveTask (void) { mqd_t mqPXId; /* msg queue descriptor */ char msg[MSG_SIZE]; /* msg buffer */ int prio; /* priority of message */ /* open message queue using default attributes */ if ((mqPXId = mq_open (MQ_NAME, O_RDWR | O_CREAT, 0, NULL)) == (mqd_t) -1) { printf ("receiveTask: mq_open failed\n"); return; } /* try reading from queue */ if (mq_receive (mqPXId, msg, MSG_SIZE, &prio) == -1) { printf ("receiveTask: mq_receive failed\n"); return; } else
252
{ printf ("receiveTask: Msg of priority %d received:\n\t\t%s\n", prio, msg); } } /**************************************************************************** * * 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 */ /* open msg queue; should already exist with default attributes */ if ((mqPXId = mq_open (MQ_NAME, O_RDWR, 0, NULL)) == (mqd_t) -1) { printf ("sendTask: mq_open failed\n"); return; } /* try writing to queue */ if (mq_send (mqPXId, MSG, sizeof (MSG), HI_PRIO) == -1) { printf ("sendTask: mq_send failed\n"); return; } else printf ("sendTask: mq_send succeeded\n"); }
253
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.
Example 7-10
Message Queue Notification /* * 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 #include #include #include #include #include #include <vxWorks.h> <signal.h> <mqueue.h> <fcntl.h> <errno.h> <stdio.h> <string.h>
/* defines */ #define QNAM "PxQ1" #define MSG_SIZE 64 /* limit on message sizes */ /* forward declarations */ static void exNotificationHandle (int, siginfo_t *, void *); static void exMqRead (mqd_t);
254
/**************************************************************************** * 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; /* Minor sanity check; avoid exceeding msg buffer */ if (MSG_SIZE <= strlen (pMessage)) { printf ("exMqNotify: message too long\n"); return (-1); } /* * Install signal handler for the notify signal and fill in * a sigaction structure and pass it to sigaction(). Because the handler * needs the siginfo structure as an argument, the SA_SIGINFO flag is * set in sa_flags. */ mySigAction.sa_sigaction = exNotificationHandle; mySigAction.sa_flags = SA_SIGINFO; sigemptyset (&mySigAction.sa_mask); if (sigaction (SIGUSR1, &mySigAction, NULL) == -1) { printf ("sigaction failed\n"); return (-1); } /* * Create a message queue - fill in a mq_attr structure with the * size and no. of messages required, and pass it to mq_open(). */ attr.mq_flags = 0; attr.mq_maxmsg = 2; attr.mq_msgsize = MSG_SIZE;
255
if ((exMqId = mq_open (QNAM, O_CREAT | O_RDWR | O_NONBLOCK, 0, &attr)) == (mqd_t) - 1 ) { printf ("mq_open failed\n"); return (-1); } /* * Set up notification: fill in a sigevent structure and pass it * to mq_notify(). The queue ID is passed as an argument to the * signal handler. */ sigNotify.sigev_signo = SIGUSR1; sigNotify.sigev_notify = SIGEV_SIGNAL; sigNotify.sigev_value.sival_int = (int) exMqId; if (mq_notify (exMqId, &sigNotify) == -1) { printf ("mq_notify failed\n"); return (-1); } /* * We just created the message queue, but it may not be empty; * a higher-priority task may have placed a message there while * we were requesting notification. mq_notify() does nothing if * messages are already in the queue; therefore we try to * retrieve any messages already in the queue. */ exMqRead (exMqId); /* * Now we know the queue is empty, so we will receive a signal * the next time a message arrives. * * We send a message, which causes the notify handler to be invoked. * It is a little silly to have the task that gets the notification * be the one that puts the messages on the queue, but we do it here * to simplify the example. A real application would do other work * instead at this point. */ if (mq_send (exMqId, pMessage, 1 + strlen (pMessage), 0) == -1) { printf ("mq_send failed\n"); } /* Cleanup */ if (mq_close (exMqId) == -1) { printf ("mq_close failed\n"); return (-1);
256
} /* More cleanup */ if (mq_unlink (QNAM) == -1) { printf ("mq_unlink failed\n"); return (-1); } return (0); } /**************************************************************************** * exNotificationHandle - handler to read in messages * * This routine is a signal handler; it reads in messages from a * message queue. * * RETURNS: N/A * * ERRNOS: N/A */ static void exNotificationHandle ( int sig, /* signal number */ siginfo_t * pInfo, /* signal information */ void * pSigContext /* unused (required by posix) */ ) { struct sigevent sigNotify; mqd_t exMqId; /* Get the ID of the message queue out of the siginfo structure. */ exMqId = (mqd_t) pInfo->si_value.sival_int; /* * Request notification again; it resets each time * a notification signal goes out. */ sigNotify.sigev_signo = pInfo->si_signo; sigNotify.sigev_value = pInfo->si_value; sigNotify.sigev_notify = SIGEV_SIGNAL; if (mq_notify (exMqId, &sigNotify) == -1) { printf ("mq_notify failed\n"); return; }
257
/* Read in the messages */ exMqRead (exMqId); } /**************************************************************************** * exMqRead - read in messages * * This small utility routine receives and displays all messages * currently in a POSIX message queue; assumes queue has O_NONBLOCK. * * RETURNS: N/A * * ERRNOS: N/A */ static void exMqRead ( mqd_t exMqId ) { char msg[MSG_SIZE]; int prio; /* * Read in the messages - uses a loop to read in the messages * because a notification is sent ONLY when a message is sent on * an EMPTY message queue. There could be multiple msgs if, for * example, a higher-priority task was sending them. Because the * message queue was opened with the O_NONBLOCK flag, eventually * this loop exits with errno set to EAGAIN (meaning we did an * mq_receive() on an empty message queue). */ while (mq_receive (exMqId, msg, MSG_SIZE, &prio) != -1) { printf ("exMqRead: mqId (0x%x) received message: %s\n", exMqId, msg); } if (errno != EAGAIN) { printf ("mq_receive: errno = %d\n", errno); } }
258
Dynamic memory allocationwith the calloc( ) malloc( ) realloc( ) and free( ) routines. See 8.3 Heap and Memory Partition Management, p.272. POSIX shared memory objects (the _POSIX_SHARED_MEMORY_OBJECTS option). See 7.19.3 Shared Memory Objects, p.263. POSIX memory-mapped files (the _POSIX_MAPPED_FILES option). See 7.19.4 Memory Mapped Files, p.264. POSIX memory protection (the _POSIX_MEMORY_PROTECTION options). See 7.19.5 Memory Protection, p.264. POSIX memory locking (the _POSIX_MEMLOCK and _POSIX_MEMLOCK_RANGE options). See 7.19.6 Memory Locking, p.265.
In addition, VxWorks also supports anonymous memory mapping, which is not a POSIX standard, but an implementation-specific extension of it.
259
processes are always memory resident. Similarly, some APIs that are not relevant to real-time operating systems, such as those for memory locking are formally available to facilitate porting, but do not perform any function. The mmanLib and shmLib routines are listed in Table 7-23.
Table 7-23 POSIX Memory Management Routines
Routine
Description
mmap( )
Establishes a mapping between a process' address space and a file, or a shared memory object, or directly with system RAM. In VxWorks the MAP_FIXED flags parameter option is not supported. Un-maps pages of memory. Synchronizes a mapped file with a physical storage. Sets protection of memory mapping. Locks all pages used by a process into memory. In VxWorks this routine does nothing.
munlockall( ) Unlocks all pages used by a process. In VxWorks this routine does nothing. mlock( ) munlock( ) shm_open( ) Locks specified pages into memory. In VxWorks this routine does nothing. Unlocks specified pages. In VxWorks this routine does nothing. Opens a shared memory object.
shm_unlink( ) Removes a shared memory object. The restrictions on the VxWorks implementation of mmanLib are as follows:
Mapping at fixed a address (MAP_FIXED) is not supported. The munmap( ), mprotect( ), msync( ), mlock( ) routines only succeed for memory pages obtained with mmap( ). The msync( ) routine must be called explicitly to ensure that data for memory mapped files is synchronized with the media.
260
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 operationsftruncate( ), write( ), and so onwhile 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); addr1 = mmap (..., fd); ftruncate (fd, size2); /* set size */ /* map it */ /* 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.
261
/* get "hw.mmu.pageSize" info */ mib[0] = CTL_HW; mib[1] = HW_MMU; mib[2] = HW_MMU_PAGESIZE; if (sysctl (mib, 3, &pgSize, &sz, NULL, 0) != 0) exit (1); /* buffer size is 4 pages */ bufSize = 4 * pgSize; /* request mapped memory for a buffer */ pBuf = mmap (NULL, bufSize, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANON), MAP_ANON_FD, 0); /* check for error */ if (pBuf == MAP_FAILED) exit (1); /* write protect the first page */ if (mprotect (pBuf, pgSize, PROT_READ) != 0) { /* * no need to call unmap before exiting as all memory mapped for * a process is automatically released. */ 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. */ if (munmap (pBuf, bufSize) != 0) exit (1); printf ("execution succeded\n"); exit (0); }
For more information about the mmanLib routines, and an additional code example, see the VxWorks API reference for mmanLib.
262
Feature
Shared Memory
proprietary named object object ID yes no yes entire shared data region set at creation
Ability to map at fixed physical address no Ability to map at fixed virtual address Available in the kernel Unit of Operationa Size management no no MMU page dynamicb
263
a. Unit of operation means the portion of shared memory or a shared data region that can be operated on (mapped, unmapped, or protected). For shared memory the unit of 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. b. 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 operationsftruncate( ), write( ), and so onwhile mappings are in effect.
For more information, see the shmLib and mmanLib API references. For a code example, see the shmLib API.
264
A trace event is a recorded unit of the trace facilityit 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 trace events may define a payload. Trace events are initiated either by a user
265
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
266
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.
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
More of the routines used for the controller, generator, and analyzer roles are described in Table 7-25.
Table 7-25 POSIX Trace Routines
Routine
Description
Role
posix_trace_attr_init( ) posix_trace_attr_getgenversion( )
Initializes the trace stream attributes object. Gets the generation version information.
Controller Controller
267
Table 7-25
Routine
Description
Role
posix_trace_attr_getinherited( )
Controller
posix_trace_attr_getmaxusereventsize( ) Calculates the maximum memory size Controller required to store a single user trace event. posix_trace_create( ) posix_trace_clear( ) posix_trace_trid_eventid_open( ) Creates an active trace stream. Re-initializes the trace stream. Controller Controller
Associates a user trace event name with a Controller trace event type identifier Controller Controller Controller Controller Controller Generator Generator Analyzer Analyzer
posix_trace_eventtypelist_getnext_id( ) Returns the next event type. posix_trace_eventset_empty( ) posix_trace_set_filter( ) posix_trace_start( ) posix_trace_get_attr( ) posix_trace_eventid_open( ) posix_trace_event( ) posix_trace_attr_getgenversion( ) posix_trace_attr_getinherited( ) Excludes event types. Gets trace filter from a specified trace. Starts a trace stream. Get a trace attributes. Associates and event name with an event-type. Adds event to trace stream. Gets version information. Gets the inheritance policy.
posix_trace_attr_getmaxusereventsize( ) Gets the maximum total log size, in bytes. Analyzer posix_trace_trid_eventid_open( ) Associates a user trace event name with a Analyzer trace event type identifier. Analyzer Analyzer Analyzer Analyzer
posix_trace_eventtypelist_getnext_id( ) Gets next trace event type identifier. posix_trace_open( ) posix_trace_get_attr( ) posix_trace_getnext_event( ) Opens a trace log file. Gets the attributes of the current trace stream. Gets next trace event.
268
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.
Trace Code Example
LOCAL int eventShow ( trace_id_t const struct posix_trace_event_info const void * int ) { int int char char * char const char *
i; result; eventName [TRACE_EVENT_NAME_MAX]; truncationMsg; payloadData [32]; dataString = (const char *)pData;
result = posix_trace_eventid_get_name (trid, pEvent->posix_event_id, eventName); if (result != 0) sprintf (eventName, "error: %d", result); vxTestMsg (V_GENERAL, "EventId: pEvent->posix_event_id, eventName); vxTestMsg (V_GENERAL, "PID: pEvent->posix_pid); vxTestMsg (V_GENERAL, "Prog address: pEvent->posix_prog_address); %d (%s)",
0x%x", 0x%x",
switch (pEvent->posix_truncation_status) { case POSIX_TRACE_NOT_TRUNCATED: truncationMsg = "POSIX_TRACE_NOT_TRUNCATED"; break; case POSIX_TRACE_TRUNCATED_RECORD: truncationMsg = "POSIX_TRACE_TRUNCATED_RECORD";
269
break; case POSIX_TRACE_TRUNCATED_READ: truncationMsg = "POSIX_TRACE_TRUNCATED_READ"; break; default: truncationMsg = "Unknown"; } vxTestMsg (V_GENERAL, "Truncation: %d (%s)", pEvent->posix_truncation_status, truncationMsg); vxTestMsg (V_GENERAL, "Time: (%d, %d)", pEvent->posix_timestamp.tv_sec, pEvent->posix_timestamp.tv_nsec); vxTestMsg (V_GENERAL, "ThreadId: 0x%x", pEvent->posix_thread_id); vxTestMsg (V_GENERAL, "Payload size: %d", dataSize);
for (i=0; i < (dataSize < 16 ? dataSize : 16); i++) { char ch = dataString [i]; if (isspace(ch) || isgraph(ch)) payloadData [i] = ch; else payloadData [i] = '.'; } payloadData [i] = '\0'; vxTestMsg (V_GENERAL, "Payload data: %s", payloadData); return (0); }
270
8
Memory Management
in Processes
8.1 Introduction 271 8.2 Configuring VxWorks With Memory Management 272 8.3 Heap and Memory Partition Management 272 8.4 Memory Error Detection 274
8.1 Introduction
This chapter describes the memory management facilities available to applications that execute as real-time processes. Each process has its own heap, and can allocate and free buffers from its heap with the routines provided in the memLib and memPartLib libraries. See 8.3 Heap and Memory Partition Management, p.272. In addition, run-time error detection facilities provide the ability to debug memory-related errors in application code. See 8.4 Memory Error Detection, p.274. User applications can also make use of the following facilities for shared memory and memory mapping:
Native VxWorks shared data regions (see 3.5 Creating and Using Shared Data Regions, p.51). POSIX memory management facilities (see 7.19 POSIX Memory Management, p.259).
271
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, and instructions on how to configure VxWorks with process supportbut without MMU support, see the VxWorks Kernel Programmers Guide: Memory Management.
information about VxWorks SMP (which supports RTPs), see the VxWorks Kernel Programmers Guide: VxWorks SMP.
272
for memLib. For information about working with environment variables, see 2.2.8 RTPs and Environment Variables, p.15, 3.3.1 RTP Application Structure, p.42, and the API reference for rtpSpawn( ). Memory partitions are contiguous areas of memory that are used for dynamic memory allocation by applications. Applications can create their own partitions and allocate and free memory from these partitions. The heap and any partition created in a process are private to that process, which means that only that process is allowed to allocate memory to it, or free from it. For more information, see the VxWorks API references for memPartLib and memLib.
Alternative Heap Manager
The VxWorks process heap implementation can be replaced by a custom version simply by linking the replacement library into the application (the replacement library must precede vxlib.a in the link order). The memory used as heap can be obtained with either one of the following:
Using the dynamic memory mapping function, mmap( ). With mmap( ), it is possible to implement automatic or non-automatic growth of the heap. However, it is important to keep in mind that subsequent calls to mmap( ) are not guaranteed to provide memory blocks that are adjacent. For more information about mmap( ) see 7.19.1 POSIX Memory Management APIs, p.259.
In case of applications that are dynamically linked with libc.so (which by default contains memLib.o), the default heap provided by memLib is automatically created. To avoid creation of the default heap, it is necessary to create a custom libc.so file that does not hold memLib.o. For information about creating a custom shared library that provides this functionality, please contact Wind River Support. To ensure that process initialization code has access to the replacement heap manager early enough, the user-supplied heap manager must either:
Initialize the heap automatically the very first time malloc( ) or any other heap routine is called. Have its initialization routine linked with the application, and declared as an automatic constructor using the _WRS_CONSTRUCTOR macro with an
273
initialization order lower than 6 (for information about this macro, see 4.5.1 Library and Plug-in Initialization, p.74).
The memEdrLib library, which performs error checks of operations in the user heap and memory partitions in a process. This library can be linked to an executable compiled with either the Wind River Compiler or the GNU compiler. The Run-Time Error Checking (RTEC) facility, which checks for additional errors, such as buffer overruns and underruns, static and automatic variable reference checks. This feature is only provided by the Wind River Compiler.
Errors detected by these facilities are reported with the logging facility of the error detection and reporting component; which must be included in the VxWorks configuration to provide this functionality. For information about configuring VxWorks for error detection and reporting, as well as information about additional facilities useful for debugging software faults, see 11. 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.
274
A VxWorks kernel configured for process support (with the INCLUDE_RTP component) is sufficient for providing processes with heap instrumentation. The optional INCLUDE_MEM_EDR_RTP_SHOW component can be used to provide show routines for the heap and memory partition instrumentation. Note that the kernels heap instrumentation component (INCLUDE_MEM_EDR) is not required.
Linking
In order to enable heap and memory partition instrumentation of a process, the executable must be linked with the memEdrLib library support included. This can be accomplished by using the following linker option (with either the Wind River or GNU toolchain): -Wl,-umemEdrEnable Note that the syntax is slightly different for the Coldfire architecture, for which underscores are pre-pended to symbol names, as follows: -Wl,-u_memEdrEnable For example, the following makefile based on the provided user-side build environment can be used:
EXE = heapErr.vxe OBJS = main.o LD_EXEC_FLAGS += -Wl,-umemEdrEnable include $(WIND_USR)/make/rules.rtp
Alternatively, adding the following lines to the application code can also be used:
extern int memEdrEnable;
275
memEdrEnable = TRUE;
When executing an application, the following environment variables may be set to override the defaults. The variables have to be set when the process is started. For information about working with environment variables, see 2.2.8 RTPs and Environment Variables, p.15, 3.3.1 RTP Application Structure, p.42, and the API reference for rtpSpawn( ).
MEDR_EXTENDED_ENABLE Set to TRUE to enable saving trace information for each allocated block, but at
the cost of increased memory used to store entries in the allocation database. Without this feature enabled the database entry for each allocated block is 32 bytes, with this feature enabled it is 64 bytes. Default setting is FALSE.
MEDR_FILL_FREE_ENABLE Set to TRUE to enable pattern-filling queued free blocks. This aids detecting writes into freed buffers. Default setting is FALSE. MEDR_FREE_QUEUE_LEN
Maximum allowed length of the free queue. When a memory block is freed by an application, instead of immediately returning it to the memory pool, it is kept in a queue. When the queue reaches the maximum length allowed, the blocks are returned to the memory pool in a FIFO order. Queuing is disabled when this parameter is 0. Default setting 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, under-runs, and some heap memory corruption. Default setting is FALSE.
MEDR_POOL_SIZE
Set the size of the memory pool used to maintain the memory block database. 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).
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.
276
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. Allocation returns a block address which is in the task's stack space. This would indicate corruption in the partition data structures. Allocation returns a block address that is in the kernel's static data section. This would indicate corruption in the partition data structures. Freeing a pointer which is in the tasks stack space. Freeing a memory that was already freed and is still in the free queue. Freeing memory which is in the kernels static data section. Freeing memory in a different partition than where it was allocated. Freeing a partial memory block. Freeing 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.
8
Shell Commands
The show routines and commands described in Table 8-1 are available for use with the shells C and command interpreters to display information.
Table 8-1 Shell Commands
C Interpreter
Command Interpreter
Description
edrShow( ) memEdrRtpPartShow( )
Displays error records. Displays a summary of the instrumentation information for memory partitions located in a given process.
277
Table 8-1
C Interpreter
Command Interpreter
Description
Displays information about allocated blocks in a given process. Blocks can be selected using a combination of various querying criteria: partition ID, block address, allocating task ID, block type.
Marks or unmarks selected blocks allocated at the time mem rtp block unmark of the call. The selection criteria may include partition ID and/or allocating task ID. Can be used to monitor memory leaks by displaying information of unmarked blocks with memEdrRtpBlockShow( ) and mem rtp block list.
278
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.279.
#include #include #include int main <vxWorks.h> <stdlib.h> <taskLib.h> ()
{ char * pChar; taskSuspend(0); pChar = malloc (24); free (pChar + 2); free (pChar); free (pChar); pChar = malloc (32); taskSuspend (0); } /* stop here first */
/* free partial block */ /* double-free block */ /* leaked memory */ /* stop again to keep RTP alive */
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 value = 0 -> putenv value = 0 "MEDR_EXTENDED_ENABLE=TRUE" = 0x0 "MEDR_SHOW_ENABLE=TRUE" = 0x0
Spawn the process using the executable produced from the example code:
-> rtp = rtpSp ("heapErr.vxe") 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:
279
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 Addr Type Size Part ID Task ID Task Name Trace -------- ------ -------- -------- -------- ------------ -----------30053970 alloc 32 30010698 22c8750 iheapErr main() malloc() 0x30004ae4() 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: Record Size: Max Records: CPU Type: Errors Missed: Error count: Boot count: Generation count:
524288 bytes (128 pages) 4096 bytes 123 0x5a 0 (old) + 0 (recent) 2 4 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: "heapErr.vxe" (0x022c6670) RTP Address Space: 0x30000000 -> 0x30057000 freeing part of allocated memory block PARTITION: 0x30010698 PTR=0x30053942 BLOCK: allocated at 0x30053940, 24 bytes <<<<<Traceback>>>>> 0x300001b4 _start 0x300001e4 main +0x4c : main () +0x2c : free ()
280
0x30007280 memPartFree +0x5c : 0x30004a10 () 0x30004ac8 memEdrItemGet+0x6e8: 0x30004514 () 0x30003cb8 memEdrErrorLog+0x138: saveRegs () ==[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: "heapErr.vxe" (0x022c6670) RTP Address Space: 0x30000000 -> 0x30057000 freeing memory in free list PARTITION: 0x30010698 PTR=0x30053940 BLOCK: free block at 0x30053940, 24 bytes <<<<<Traceback>>>>> 0x300001b4 _start +0x4c : main () 0x300001f4 main +0x3c : free () 0x30007280 memPartFree +0x5c : 0x30004a10 () 0x30004ac8 memEdrItemGet+0x6e8: 0x30004514 () 0x30003cb8 memEdrErrorLog+0x138: saveRegs () value = 0 = 0x0
GNU compiler. 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.
281
Table 8-2 lists the -Xrtc options that are supported. Using the -Xrtc flag without specifying any option implements them all. An individual option (or bit-wise ORd combinations of options) can be enabled using the following syntax: -Xrtc=option
Table 8-2 -Xrtc Options
Option
Description
register and check static (global) variables register and check automatic variables pointer reference checks pointer arithmetic checks pointer increment/decrement checks standard function checks; for example memset( ) and bcopy( ) 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 11. 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. Reference to a block in the free queue. Reference to the free part of the tasks stack. De-referencing a NULL pointer.
For information beyond what is provided in this section, see the Wind River Compiler Users Guide: Run-Time Error Checker.
Configuring VxWorks for RTEC Support
Support for this feature in the kernel is enabled by configuring VxWorks with the basic error detection and reporting facilities. See 11.2 Configuring Error Detection and Reporting Facilities, p.366.
282
Shell Commands
The compiler provided instrumentation automatically logs errors detected in applications using the error detection and reporting facility. For information about using shell commands with error logs, see 11.4 Displaying and Clearing Error Records, p.370.
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.283.
#include <vxWorks.h> #include <stdlib.h> int main () { char name[] = "very_long_name"; char * pChar; int state[] = { 0, 1, 2, 3 }; int ix = 0; pChar = (char *) malloc (13); memcpy (pChar, name, strlen (name)); /* bounds check violation */ /* of allocated block */ for (ix = 0; ix < 4; ix++) state[ix] = state [ix + 1]; /* bounds check violation */ /* of automatic variable */ free (pChar); *pChar = '\0'; /* reference a free block */ }
In the following shell session example, the C interpreter is used to execute edrClear( ), which clears the error log of any existing error records. Then the application is started with rtpSp( ). Finally, the errors are displayed with edrShow( ). First, clear the error log. This step is only performed to limit the number of events that are later displayed, when the events are listed:
-> edrClear value = 0 = 0x0
Start the process using the executable created from the sample code listed above:
283
Next, list the error log. As shown below, three errors are detected by the compiler instrumentation:
-> edrShow ERROR LOG ========= Log Size: Record Size: Max Records: CPU Type: Errors Missed: Error count: Boot count: Generation count:
524288 bytes (128 pages) 4096 bytes 123 0x5a 0 (old) + 0 (recent) 3 4 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: "refErr.vxe" (0x0229a450) RTP Address Space: 0x30000000 -> 0x30058000 Injection Point: main.c:13 memory block bounds-check violation PTR=0x30054940 OFFSET=0 SIZE=14 BLOCK: allocated at 0x30054940, 13 bytes <<<<<Traceback>>>>> 0x300001b4 _start 0x300002ac main +0x4c : main () +0xf4 : __rtc_chk_at ()
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: "refErr.vxe" (0x0229a450) RTP Address Space: 0x30000000 -> 0x30058000 Injection Point: main.c:17 memory block bounds-check violation PTR=0x30022f34 OFFSET=16 SIZE=4
284
BLOCK: automatic at 0x30022f34, 16 bytes <<<<<Traceback>>>>> 0x300001b4 _start 0x300002dc main +0x4c : main () +0x124: __rtc_chk_at ()
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) RTP: "refErr.vxe" (0x0229a450) RTP Address Space: 0x30000000 -> 0x30058000 Injection Point: main.c:21 pointer to free memory block PTR=0x30054940 OFFSET=0 SIZE=1 BLOCK: free block at 0x30054940, 13 bytes <<<<<Traceback>>>>> 0x300001b4 _start 0x30000330 main value = 0 = 0x0 +0x4c : main () +0x178: __rtc_chk_at ()
285
286
9
I/O System
9.1 Introduction 287 9.2 Configuring VxWorks With I/O Facilities 289 9.3 Files, Devices, and Drivers 290 9.4 Basic I/O 292 9.5 Buffered I/O: stdio 303 9.6 Other Formatted I/O 305 9.7 Asynchronous Input/Output 305 9.8 Devices in VxWorks 308
9.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
287
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. The diagram in Figure 9-1 illustrates the relationships between the different elements of the VxWorks I/O system available to real-time processes (RTPs). All of these elements are discussed in this chapter.
Figure 9-1 Overview of the VxWorks I/O System for Processes
fread( ) fwrite( )
Basic I/O Routines (device independent)
read( ) write( )
send( ) recv( )
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 Programmers Guide.
288
INCLUDE_IO_BASICprovides basic I/O functionality. INCLUDE_IO_FILE_SYSTEMprovides file system support. INCLUDE_POSIX_DIRLIBprovides POSIX directory utilities. INCLUDE_POSIX_FSprovides POSIX file system APIs. INCLUDE_IO_REMOVABLEprovides support for removable file systems. INCLUDE_IO_POSIXProvides POSIX I/O support. INCLUDE_IO_RTPprovides I/O support for RTPs. INCLUDE_IO_MISCmiscellaneous IO functions that are no longer referenced but are provided for backwards compatibility. 9
The component INCLUDE_IO_SYSTEM is provided for backward compatibility. It includes all the components listed above. Components that provide support for additional features are described throughout this chapter.
289
An unstructured raw device such as a serial communications channel or an intertask pipe. A logical file on a structured, random-access device containing a file system.
Consider the following named files: /usr/myfile /pipe/mypipe /tyCo/0 The first refers to a file called myfile, on a disk device called /usr. The second is a named pipe (by convention, pipe names begin with /pipe). The third refers to a physical serial channel. However, I/O can be done to or from any of these in the same way. Within VxWorks, they are all called files, even though they refer to very different physical objects. 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. See 9.8 Devices in VxWorks, p.308. 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. These two levels are discussed in later sections.
A filename is specified as a character string. An unstructured device is specified with the device name. In the case of file system devices, the device name is followed by a filename. Thus, the name /tyCo/0 might name a particular serial I/O channel, and the name DEV1:/file1 indicates the file file1 on the DEV1: device. When a filename 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 filename. The I/O function is then directed at this device.
290
If a matching device name cannot be found, then the I/O function is directed at a default device. You can set this default device to be any device in the system, including no device at all, in which case failure to match a device name returns an error. You can obtain the current default path by using ioDefPathGet( ). You can set the default path by using ioDefPathSet( ). 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. It is useful to adopt some naming conventions for device and file names: most device names begin with a forward-slash (/), except non-NFS network devices, and VxWorks HRFS and dosFs file system devices.
NOTE: To be recognized by the virtual root file system, device names must begin 9
with a single leading forward-slash, and must not contain any other slash characters. For more information, see 10.3 Virtual Root File System: VRFS, p.325. By convention, NFS-based network devices are mounted with names that begin with a slash. For example:
/usr
Non-NFS network devices are named with the remote machine name followed by a colon. For example:
host:
The remainder of the name is the filename in the remote directory on the remote system. File system devices using dosFs are often named with uppercase letters and digits followed by a colon. For example:
DEV1:
NOTE: Filenames and directory names on dosFs devices are often separated by
backslashes (\). These can be used interchangeably with forward slashes (/). !
CAUTION: Because device names are recognized by the I/O system using simple
substring matching, a slash (/ or \) should not be used alone as a device name, nor should a slash be used as any part of a device name itself.
291
Routine
Description
Creates a file. Deletes a file. Opens a file (optionally, creates a file if it does not already exist.) Closes a file. Reads a previously created or opened file. Writes to a previously created or opened file. Performs special control functions on files.
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 foos 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.
292
If process foobar is started by process foo (it is foos 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 9.4.3 Standard I/O Redirection, p.294.
0 is used for standard input (stdin). 1 is used for standard output (stdout). 2 is used for standard error output (stderr).
All real time processes read their standard inputlike getchar( )from file descriptor 0. Similarly file descriptor 1 is used for standard outputlike printf( ). And file descriptor 2 is used for outputting error messages. You can use these 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 applications source code.
293
294
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; /* Save the original standard file descriptor. */ oldFd0 = dup(0); oldFd1 = dup(1); oldFd2 = dup(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 */ dup2 (oldFd0, 0); dup2 (oldFd1, 1); dup2 (oldFd2, 2); /* Close them after they are duplicated to fd 0, 1, 2 */ close (oldFd0); close (oldFd1); close (oldFd2);
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( ).
295
For open( ) calls made in processes, the mode parameter is optional. The file-access options that can be used with the flags parameter to open( ) are listed in Table 9-2.
Table 9-2 File Access Options
Flag
Description
Open for reading only. Open for writing only. Open for reading and writing. Create a file if it does not already exist. Error on open if the file exists and O_CREAT is also set. Write on the file descriptor complete as defined by synchronized I/O file integrity completion. Write on the file descriptor complete as defined by synchronized I/O data integrity completion. Read on the file descriptor complete at the same sync level as O_DSYNC and O_SYNC flags. 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. Non-blocking I/O. If the named file is a terminal device, don't make it the controlling terminal for the process.
O_DSYNC
O_RSYNC
O_APPEND
O_NONBLOCK O_NOCTTY
296
Table 9-2
Flag
Description
O_TRUNC
Open with truncation. If the file exists and is a regular file, and the file is successfully opened, its length is truncated to 0. It has no effect on devices other than the regular file system.
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 ORing 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.
297
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 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 (9.4.1 File Descriptors, p.292), 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.
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.
298
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");
Files should be closed before they are removed. With non-file-system devices, the creat( ) routine performs the same function as open( ). The remove( ) routine, however has no effect.
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 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.
299
If it succeeds in truncating the file, ftruncate( ) returns OK. If the file descriptor refers to a device that cannot be truncated, ftruncate( ) returns ERROR, and sets errno to EINVAL. If the size specified is larger than the actual size of the file, the result depends on the file system. For both dosFs and HRFS, the size of the file is extended to the specified size; however, for other file systems, ftruncate( ) returns ERROR, and sets errno to EINVAL (just as if the file descriptor referred to a device that cannot be truncated). The ftruncate( ) routine is part of the POSIX 1003.1b standard. It is fully supported as such by the HRFS. The dosFs implementation is, however, only partially compliant: creation and modification times are not changed. Also note that with HRFS the seek position is not modified by truncation, but with dosFs the seek position is set to the end of the file.
For ioctl( ) calls made in processes, the arg parameter is optional. Both of the following are legitimate calls:
fd = ioctl (fd, func, arg); fd = ioctl (fd, func);
For example, the following call uses the FIOBAUDRATE function to set the baud rate of a tty device to 9600:
300
The discussion of specific devices in 9.8 Devices in VxWorks, p.308 summarizes the ioctl( ) functions available for each 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.
The VxWorks select facility provides a UNIX- and Windows-compatible method for pending on multiple file descriptors. The library selectLib provides both task-level support, allowing tasks to wait for multiple devices to become active, and device driver support, giving drivers the ability to detect tasks that are pended while waiting for I/O on the device. To use this facility, the header file selectLib.h must be included in your application code. Task-level support not only gives tasks the ability to simultaneously wait for I/O on multiple devices, but it also allows tasks to specify the maximum time to wait for I/O to become available. An example of using the select facility to pend on multiple file descriptors is a client-server model, in which the server is servicing both local and remote clients. The server task uses a pipe to communicate with local clients and a socket to communicate with remote clients. The server task must respond to clients as quickly as possible. If the server blocks waiting for a request on only one of the communication streams, it cannot service requests that come in on the other stream until it gets a request on the first stream. For example, if the server blocks waiting for a request to arrive in the socket, it cannot service requests that arrive in the pipe until a request arrives in the socket to unblock it. This can delay local tasks waiting to get their requests serviced. The select facility solves this problem by giving the server task the ability to monitor both the socket and the pipe and service requests as they come in, regardless of the communication stream used. Tasks can block until data becomes available or the device is ready for writing. The select( ) routine returns when one or more file descriptors are ready or a timeout has occurred. Using the select( ) routine, a task specifies the file descriptors on which to wait for activity. Bit fields are used in the select( ) call to specify the read and write file descriptors of interest. When select( ) returns, the bit fields are
301
modified to reflect the file descriptors that have become available. The macros for building and manipulating these bit fields are listed in Table 9-3.
Table 9-3 Select Macros
Macro
Description
Zeroes all bits. Sets the bit corresponding to a specified file descriptor. Clears a specified bit. Returns non-zero if the specified bit is set; otherwise returns 0.
Applications can use select( ) with any character I/O devices that provide support for this facility (for example, pipes, serial devices, and sockets).
Routine
Description
Unlink a file. Link a file. Synchronize a file. Synchronize the data of a file. Change the name of a file. Determine the current value of a configurable limit. Determine the current value of a configurable limit. Determine accessibility of a file. Change the permission mode of a file.
302
Table 9-4
Routine
Description
fcntl( )
For more information, see the API references for fsPxLib and ioLib.
To make this type of I/O more efficient and flexible, the stdio package 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 stdio routines and macros. To access a file with stdio, a file is opened with fopen( ) instead of open( ) (many stdio calls begin with the letter f):
fp = fopen ("/usr/foo", "r");
The returned value, a file pointer is a handle for the opened file and its associated buffers and pointers. A file pointer is actually a pointer to the associated data
303
structure of type FILE (that is, it is declared as FILE *). By contrast, the low-level I/O routines identify a file with a file descriptor, which is a small integer. In fact, the FILE structure pointed to by the file pointer contains the underlying file descriptor of the open file. A file descriptor that is already open can be associated subsequently with a FILE buffer by calling fdopen( ):
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 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 stdio 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. The FILE buffer is deallocated when fclose( ) is called.
304
Asynchronous Input/Output (AIO) is the ability to perform input and output operations concurrently with ordinary internal processing. AIO enables you to de-couple I/O operations from the activities of a particular task when these are logically independent. The VxWorks AIO implementation meets the specification in the POSIX 1003.1b standard. The benefit of AIO is greater processing efficiency: it permits I/O operations to take place whenever resources are available, rather than making them await arbitrary events such as the completion of independent operations. AIO eliminates some of the unnecessary blocking of tasks that is caused by ordinary synchronous I/O; this decreases contention for resources between input/output and internal processing, and expedites throughput. Include AIO in your VxWorks configuration with the INCLUDE_POSIX_AIO and INCLUDE_POSIX_AIO_SYSDRV components. The second configuration constant enables the auxiliary AIO system driver, required for asynchronous I/O on all current VxWorks devices.
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.
305
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 9-5.
Table 9-5 Asynchronous Input/Output Routines
Function
Description
Initiates an asynchronous read operation. Initiates an asynchronous write operation. Initiates a list of up to LIO_MAX asynchronous I/O requests. Retrieves the error status of an AIO operation. Retrieves the return status of a completed AIO operation. Cancels a previously submitted AIO operation. Waits until an AIO operation is done, interrupted, or timed out. Asynchronously forces file synchronization.
306
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 9
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.
307
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.
Device
Driver Description
tty pty pipe mem nfs net null ram scsi romfs
Terminal device Pseudo-terminal device Pipe device Pseudo memory device NFS client device Network device for remote file access Null device RAM device for creating a RAM disk SCSI interface ROMFS device
308
Table 9-6
Device
Driver Description
See the VxWorks Kernel Programmers Guide: I/O System and the VxWorks Device Driver Developers Guide for more detailed information about I/O device drivers.
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 Programmers Guide: I/O System. Table 9-7 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.
309
Table 9-7
Tty Options
Library
Description
Selects line mode. (See Raw Mode and Line Mode, p.310.) Echoes input characters to the output of the same channel. Translates input RETURN characters into NEWLINE (\n); translates output NEWLINE into RETURN-LINEFEED. Responds to software flow control characters CTRL+Q and CTRL+S (XON and XOFF). Strips the most significant bit from all input bytes. default.
OPT_TANDEM
OPT_7_BIT
OPT_MON_TRAP Enables the special ROM monitor trap character, CTRL+X by OPT_ABORT
Enables the special kernel shell abort character, CTRL+C by default. (Only useful if the kernel shell is configured into the system) Sets all of the above option bits. Sets none of the above option bits.
OPT_TERMINAL OPT_RAW
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.309). 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 users read buffer. Input cannot be modified except as directed by other tty option bits. 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 users read buffer. Input can be modified by the special characters CTRL+H (backspace),
310
CTRL+U (line-delete), and CTRL+D (end-of-file), which are discussed in tty Special Characters, p.311.
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.
9
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
1. It will not be possible to restart VxWorks if un-handled external interrupts occur during the boot countdown.
311
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 9-8.
Table 9-8 Tty Special Characters
Character
Description
Modifier
backspace (character delete) line delete EOF (end of file) kernel shell abort trap to boot ROMs output suspend output resume
Creating Pipes
The new pipe can have at most maxMsgs messages queued at a time. Tasks that write to a pipe that already has the maximum number of messages queued are
312
blocked until a message is dequeued. Each message in the pipe can be at most maxLength bytes long; attempts to write longer messages result in an error.
Pipe devices respond to the ioctl( ) functions summarized in Table 9-9. 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.
Table 9-9 I/O Control Functions Supported by pipeDrv
Function
Description
Discards all messages in the pipe. Gets the pipe name of the file descriptor. Gets the number of messages remaining in the pipe. Gets the size in bytes of the first message in the pipe.
The memory device responds to the ioctl( ) functions summarized in Table 9-10. The functions listed are defined in the header file ioLib.h.
313
Table 9-10
Function
Description
FIOSEEK FIOWHERE
Sets the current byte offset in the file. Returns the current byte position in the file.
For more information, see the reference entries for memDrv, ioLib, and ioctl( ).
NFS client devices respond to the ioctl( ) functions summarized in Table 9-11. The functions listed are defined in ioLib.h. For more information, see the reference entries for nfsDrv, ioLib, and ioctl( ).
314
Table 9-11
Function
Description
Gets file status information (directory entry data). Gets the filename of the file descriptor. Gets the number of unread bytes in the file. Reads the next directory entry. Sets the current byte offset in the file. Flushes data to a remote NFS file. Returns the current byte position in the file.
9
315
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( ).
9.8.7 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 Programmers Guide.
316
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 10.5 MS-DOS-Compatible File System: dosFs, p.339.
Configure VxWorks with the INCLUDE_XBD_TRANS component to provide TRFS functionality for your dosFs file system.
317
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 directlyall commands are routed through TRFS, which performs the appropriate translations. If a file system is detected, such as dosFs, the dosFs creation function is called by the monitor and dosFs is instantiated. If not, rawfs is instantiated. For information about how file systems are automatically instantiated, see the VxWorks Kernel Programmers Guide: Local File Systems.
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 systemand 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);
318
9
... /* Perform more file operations here */ ioctl (fd, CBIO_TRANS_COMMIT, 0); close (fd); }
319
320
10
Local File Systems
10.1 Introduction 322 10.2 File System Monitor 325 10.3 Virtual Root File System: VRFS 325 10.4 Highly Reliable File System: HRFS 327 10.5 MS-DOS-Compatible File System: dosFs 339 10.6 Raw File System: rawFs 352 10.7 CD-ROM File System: cdromFs 355 10.8 Read-Only Memory File System: ROMFS 359 10.9 Target Server File System: TSFS 361
321
10.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 10-1. Note that this illustration is relevant for the HRFS, dosFs, rawFs, and cdromFs file systems. The dotted line indicates the elements that must be configured and instantiated to create a specific, functional run-time file system.
322
Figure 10-1
Application
I/O System
File System
HRFS, dosFs, rawFs, cdromFs
10
XBD Facility
Block Device
SCSI, ATA, RAM disk, Floppy, TrueFFS, and so on
Hardware
For information about the XBD facility, see the VxWorks Kernel Programmers Guide: I/O System.
323
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 10.3 Virtual Root File System: VRFS, p.325.
HRFS
A POSIX-compliant transactional file system designed for real-time use of block devices (disks). Can be used on flash memory in conjunction with TrueFFS and the XBD block wrapper component. See 10.4 Highly Reliable File System: HRFS, p.327.
dosFs
An MS-DOS compatible file system designed for real-time use of block devices. Can be used with flash memory in conjunction with the TrueFFS and the XBD block wrapper component. Can also be used with the transaction-based reliable file system (TRFS) facility. See 10.5 MS-DOS-Compatible File System: dosFs, p.339.
rawFS
Provides a simple raw file system that treats an entire disk as a single large file. See 10.6 Raw File System: rawFs, p.352.
cdromFs
Allows applications to read data from CD-ROMs formatted according to the ISO 9660 standard file system. See 10.7 CD-ROM File System: cdromFs, p.355.
ROMFS
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 10.8 Read-Only Memory File System: ROMFS, p.359.
TSFS
Uses the host target server to provide the target with access to files on the host system. See 10.9 Target Server File System: TSFS, p.361. For information about the file system monitor, see the VxWorks Kernel Programmers Guide: Local File Systems. For information about the XBD facility, see the VxWorks Kernel Programmers Guide: I/O System.
324
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 10.5 MS-DOS-Compatible File System: dosFs, p.339 and the VxWorks Kernel Programmers 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 Programmers Guide: Local File Systems.
10
325
To include the VRFS in VxWorks, configure the kernel with the INCLUDE_VRFS component. The VRFS is created and mounted automatically if the component is included in VxWorks. This shell session illustrates the relationship between device names and access to devices and file systems with the VRFS.
-> devs drv name 0 /null 1 /tyCo/0 1 /tyCo/1 2 /aioPipe/0x1817040 6 /romfs 7 / 9 yow-build02-lx: 10 /vio 11 /shm 12 /ram0 value = 25 = 0x19 -> cd "/" value = 0 = -> ll ?--------drwxrwxr-x ?--------drwxrwxrwx drwxrwxrwx value = 0 =
0x0 0 0 0 15179 0 0 1 0 1 0 0x0 0 100 0 0 0 0 20 0 0 2048 Jan 1 00:00 null Jan 23 2098 romfs/ Jan 1 00:00 vio Jan 1 00:00 shm/ Jan 1 00:00 ram0/
Note that /tyCo/0, /tyCo/1, /aioPipe/0x1817040 and yow-build02-lx do not show up in the directory listing of the root directory as they do not follow the naming convention required by the VRFS. The first three include forward slashes in the body of the device name and the fourth does not have a leading forward slash in its name. Also note that the listings of file systems have a trailing forward slash character. Other devices do not, and they have a question mark in the permissions (or attributes) column of the listing because they do not have recognizable file permissions.
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 9.8.6 Null Devices, p.316. For information about POSIX PSE52, see 7.2.1 POSIX PSE52 Support, p.193.
326
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 device name. For example, if the current working directory is hostname, changing directory to /home/panloki will not work it must be named hostname:/home/panloki.
10
Fault tolerance. The file system is never in an inconsistent state, and is therefore able to recover quickly from unexpected loses of power. Configurable commit policies. Hierarchical file and directory system, allowing for efficient organization of files on a volume. Compatibility with a widely available storage devices. POSIX compliance.
For more information about the HRFS libraries see the VxWorks API references for hrfsFormatLib, hrFsLib, and hrfsChkDskLib.
HRFS and Flash Memory
For information about using HRFS with flash memory, see the VxWorks Kernel Programmers Guide: Flash File System Support with TrueFFS.
327
Required Components
Either the INCLUDE_HRFS or the INCLUDE_HRFS_READONLY component is required. As its name indicates, the latter is a read-only version of the main HRFS component. The libraries it provides are smaller as it provides no facilities for disk modifications. The appropriate component for your block device; for example, INCLUDE_ATA. 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 Programmers Guide: I/O System.
The INCLUDE_HRFS_FORMAT (formatter), INCLUDE_HRFS_CHKDSK (consistency checker), and INCLUDE_HRFS_ACCESS_TIMESTAMP (access file stamp) components are optional. Note that the INCLUDE_HRFS_ACCESS_TIMESTAMP component is included in the BUNDLE_RTP_POSIX_PSE52 component bundle.
Optional XBD Components
The INCLUDE_XBD_PART_LIB (disk partitioning) and INCLUDE_XBD_RAMDRV (RAM disk) components are optional. For information about the XBD facility, see the VxWorks Kernel Programmers Guide: I/O System.
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.
328
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.
HRFS_DEFAULT_COMMIT_POLICY
Defines the default commit policy for an HRFS volume, which is FS_COMMIT_AUTO. Commit policies can also be changed at runtime. For more information see 10.4.5 Transactional Operations and Commit Policies, p.330 and 10.4.6 Configuring Transaction Points at Runtime, p.332.
HRFS_DEFAULT_COMMIT_PERIOD
Defines the initial commit period of an HRFS volume if it has been configured for periodic commits. This parameter is measured in milliseconds. The default value is 5000 milliseconds (5 seconds). The commit period can also be changed at runtime. For more information see 10.4.5 Transactional Operations and Commit Policies, p.330 and 10.4.6 Configuring Transaction Points at Runtime, p.332.
10
The INCLUDE_HRFS component for the highly reliable file system. The INCLUDE_HRFS_ACCESS_TIMESTAMP component for updating file and directory access timestamps on an HRFS formatted volume must be included. This component is part of the BUNDLE_RTP_POSIX_PSE52 bundle.
WARNING: The use of the INCLUDE_HRFS_ACCESS_TIMESTAMP component can cause significant degradation in file system performance. For more information, see 10.4.7 File Access Time Stamps, p.333.
The appropriate device driver component (for example INCLUDE_ATA or INCLUDE_XBD_RAMDRV) and INCLUDE_XBD_BLK_DEV if the device driver requires it. See the VxWorks Kernel Programmers Guide: I/O System. The INCLUDE_VRFS component for the VRFS file system, which is included in the BUNDLE_RTP_POSIX_PSE52 component bundle
329
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 10.3 Virtual Root File System: VRFS, p.325 and 7.2.1 POSIX PSE52 Support, p.193.
The HRFS commit policies are as follows: Automatic 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. This is the default policy. There is no need for explicit action on the part of an application to commit a change. The following routines, for example, cause modifications to disk and result in a commit when the automatic policy is in force:
write( )
330
remove( ) delete( ) mkdir( ) rmdir( ) link( ) unlink( ) truncate( ) ftruncate( ) ioctl( ) when used with a control function that requires modifying the disk.
Manual The application decides when a commit is to be performed. The user explicitly sets transaction points. This is the fastest policy in terms of performance but obviously has the potential for greater data loss. The application can, however, decide when critical data has been written and needs to be committed. The commit( ) routine is used with this policy. Periodic Transaction points are set automatically at periodic intervals. This policy is in between automatic and manual in terms of performance and potential data loss.
Mandatory Commits
10
For both manual and periodic commit policies there are circumstances under which a commit is always performed. Mandatory commits occur under the following circumstances:
Creation of a file or directory Deletion of a file or directory. Renaming/moving a file or directory. Space in the inode journal is exhausted. Commit policy is changed at runtime.
Note that mandatory commits are a subset of automatic commitsthey do not, for example, include write( ) and truncate( ).
Rollback
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
331
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 dataalthough the integrity of the file system is preserved.
The commit policy for each volume can be changed using the ioctl( ) function FIOCOMMITPOLICYSETFS as the second parameter. The third parameter then specifies the actual commit policy:
FS_COMMIT_POLICY_AUTO, FS_COMMIT_POLICY_MANUAL, or FS_COMMIT_POLICY_PERIODIC.
If an HRFS volume has been configured for periodic commits, the commit period can be changed with ioctl( ) function FIOCOMMITPERIODSETFS. The third parameter is used to specify the commit period in milliseconds. If 0 is specified then the default commit period is used. The commit( ) routine can be used to commit programmatically. The routine is provided by the INCLUDE_DISK_UTILS component.
332
Example 10-1
Setting an HRFS Commit Policy at Runtime /* open root directory of an HRFS volume */ fd = open ("/hrfs", O_READONLY, 0666); if (fd < 0) return ERROR; /* Set commit policy to manual */ if (ioctl (fd, FIOCOMMITPOLICYSETFS, (void *)FS_COMMIT_POLICY_MANUAL)) == ERROR) return ERROR; /* Policy set to manual. Change policy to periodic */ if (ioctl (fd, FIOCOMMITPOLICYSETFS, (void *)FS_COMMIT_POLICY_PERIODIC)) == ERROR) return ERROR; /* Policy set to periodic. Change commit period to 10 seconds*/ if (ioctl (fd, FIOCOMMITPERIODSETFS, (void *)10000)) == ERROR) return ERROR; /* Period set to 10 seconds. Change commit period back to default */ if (ioctl (fd, FIOCOMMITPERIODSETFS, (void *)0)) == ERROR) return ERROR; /* Period reset. Change policy back to automatic */ if (ioctl (fd, FIOCOMMITPOLICYSETFS, (void *)FS_COMMIT_POLICY_AUTO)) == ERROR) return ERROR;
10
333
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:
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.
334
You can programmatically search directories on HRFS volumes using the opendir( ), readdir( ), rewinddir( ), and closedir( ) routines. To obtain more detailed information about a specific file, use the fstat( ) or stat( ) routine. Along with standard file information, the structure used by these routines also provides the file-attribute byte from a directory entry. For more information, see the API reference for dirLib.
10
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 9.4 Basic I/O, p.292, 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.
335
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.
Function
FIODISKCHANGE FIODISKFORMAT FIODISKINIT FIOFLUSH FIOFSTATGET FIOGETNAME FIOMOVE FIONFREE FIONREAD FIOREADDIR FIORENAME FIORMDIR FIOSEEK FIOSYNC
13 5 6 2 38 18 47 30 1 37 10 32 7 21
Announces a media change. Formats the disk (device driver function). Initializes a file system on a disk volume. Flushes the file output buffer. Gets file status information (directory entry data). Gets the filename of the fd. Moves a file (does not rename the file). Gets the number of free bytes on the volume. Gets the number of unread bytes in a file. Reads the next directory entry. Renames a file or directory. Removes a directory. Sets the current byte offset in a file. Same as FIOFLUSH, but also re-reads buffered file data.
336
Table 10-1
Function
42 39 8 50 51 52 53 54 55
Truncates a file to a specified length. Un-mounts a disk volume. Returns the current byte position in a file. Gets the maximum contiguous disk space into a 64-bit integer. Gets the number of free bytes into a 64-bit integer. Gets the number of unread bytes in a file into a 64-bit integer. Sets the current byte offset in a file from a 64-bit integer. Gets the current byte position in a file into a 64-bit integer. Set the file's size from a 64-bit integer.
10
FIONFREE64 FIONREAD64
FIOSEEK64
FIOWHERE64
FIOTRUNC64
For more information, see the API reference for ioctl( ) in ioLib.
337
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.
338
Hierarchical files and directories, allowing efficient organization and an arbitrary number of files to be created on a volume. A choice of contiguous or non-contiguous files on a per-file basis. Compatible with widely available storage and retrieval media (diskettes, hard drives, and so on). The ability to boot VxWorks from a dosFs file system. Support for VFAT (Microsoft VFAT long file names) Support for FAT12, FAT16, and FAT32 file allocation table types.
10
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.
dosFs and Flash Memory
For information about using dosFs with flash memory, see the VxWorks Kernel Programmers Guide: Flash File System Support with TrueFFS.
dosFs and the Transaction-Based Reliable File System Facility
The dosFs file system can be used with the transaction-based reliable file system (TRFS) facility; see 9.8.8 Transaction-Based Reliable File System Facility: TRFS, p.317.
339
Required Components
In addition, you must include the appropriate component for your block device; for example, INCLUDE_ATA. 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 Programmers Guide: I/O System for more information. Note that you can use INCLUDE_DOSFS to automatically include the following components:
disk cache facility dosFs file system formatting module file system integrity checking standard file system operations, such as ls, cd, mkdir, xcopy, and so on the tar utility
340
For information about the XBD facility, see the VxWorks Kernel Programmers Guide: I/O System.
10
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
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 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:
341
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 directory cache size. The default is 64 KB. Caches can be tuned dynamically for individual instances of the file system using the dosFsCacheInfo( ) and dosFsCacheTune( ) routines. The routines dosFsCacheDelete( ) and dosFsCacheCreate( ) can be used to delete and changes the size of caches. To change the size, first delete, and then create.
The dosFsShow( ) routine can be used to display volume configuration information from the shell. The dosFsVolDescGet( ) routine can be used programmatically obtain or verify a pointer to the DOS_VOLUME_DESC structure. For more information, see the API references for these routines.
342
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( ).
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:
343
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.
You can programmatically search directories on dosFs volumes using the opendir( ), readdir( ), rewinddir( ), and closedir( ) routines. To obtain more detailed information about a specific file, use the fstat( ) or stat( ) routine. Along with standard file information, the structure used by these routines also returns the file-attribute byte from a directory entry. For more information, see the API reference for dirLib.
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 9.4 Basic I/O, p.292, and the ioLib API references.
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 10-2.
344
Table 10-2
Description
read-only file hidden file system file volume label subdirectory file is subject to archiving
10
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
345
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 preserve existing flag settings, determine the current attributes using stat( ) or fstat( ), then change them using bitwise AND and OR operators.
Example 10-2 Setting DosFs File Attributes
This example makes a dosFs file read-only, and leaves other attributes intact.
STATUS changeAttributes ( void ) { int fd; struct stat statStruct; /* open file */ if ((fd = open ("file", O_RDONLY, 0)) == ERROR) return (ERROR); /* get directory entry data */ if (fstat (fd, &statStruct) == ERROR) return (ERROR); /* set read-only flag on file */ if (ioctl (fd, FIOATTRIBSET, (statStruct.st_attrib | DOS_ATTR_RDONLY)) == ERROR) return (ERROR); /* close file */ close (fd); return (OK); }
NOTE: You can also use the attrib( ) routine to change file attributes. For more
346
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 uses adjacent (contiguous) groups of clusters, called extents. Cluster group allocation is nearly contiguous allocation and is the default method used when files are written in units larger than the size of a disks cluster.
10
Absolutely contiguous allocation uses only absolutely contiguous clusters. Because this type of allocation is dependent upon the existence of such space, it is specified under only two conditions: immediately after a new file is 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.
Under most circumstances, cluster group allocation is preferred to absolutely contiguous file access. Because it is nearly contiguous file access, it achieves a nearly optimal access speed. Cluster group allocation also significantly minimizes the risk of fragmentation posed by absolutely contiguous allocation. Absolutely contiguous allocation attains raw disk throughput levels, however this speed is only slightly faster than nearly contiguous file access. Moreover, fragmentation is likely to occur over time. This is because after a disk has been in use for some period of time, it becomes impossible to allocate contiguous space. Thus, there is no guarantee that new data, appended to a file created or opened with absolutely continuous allocation, will be contiguous to the initially written data segment.
347
It is recommended that for a performance-sensitive operation, the application regulate disk space utilization, limiting it to 90% of the total disk space. Fragmentation is unavoidable when filling in the last free space on a disk, which has a serious impact on performance.
The dosFs file system defines the size of a cluster group based on the medias 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.
A contiguous file is made up of a series of consecutive disk sectors. Absolutely contiguous allocation is intended to allocate contiguous space to a specified file (or directory) and, by so doing, optimize access to that file. You can specify absolutely contiguous allocation either when creating a file, or when opening a file previously created in this manner. For more information on the ioctl( ) functions, see 10.5.9 I/O Control Functions Supported by dosFsLib, p.350.
348
To allocate a contiguous area to a newly created file, follow these steps: 1. 2. First, create the file in the normal fashion using open( ) or creat( ). Then, call ioctl( ). Use the file descriptor returned from open( ) or creat( ) as the file descriptor argument. Specify FIOCONTIG as the function code argument and the size of the requested contiguous area, in bytes, as the third argument.
The FAT is then searched for a suitable section of the disk. If found, this space is assigned to the new file. The file can then be closed, or it can be used for further I/O operations. The file descriptor used for calling ioctl( ) should be the only descriptor open to the file. Always perform the ioctl( ) FIOCONTIG operation before writing any data to the file. To request the largest available contiguous space, use CONTIG_MAX for the size of the contiguous area. For example:
status = ioctl (fd, FIOCONTIG, CONTIG_MAX);
10
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.
Opening and Using a Contiguous File
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
349
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( ).
Example 10-3 Finding the Maximum Contiguous Area on a DosFs Device
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).
STATUS contigTest ( void ) { int count; int fd;
/* no argument */
/* open device in raw mode */ if ((fd = open ("/DEV1/", O_RDONLY, 0)) == ERROR) return (ERROR); /* find max contiguous area */ ioctl (fd, FIONCONTIG, &count); /* close device and display size of largest contiguous area */ close (fd); printf ("largest contiguous area = %d\n", count); return (OK); }
350
Table 10-3
Function
FIOATTRIBSET FIOCONTIG FIODISKCHANGE FIODISKFORMAT FIODISKINIT FIOFLUSH FIOFSTATGET FIOGETNAME FIOLABELGET FIOLABELSET FIOMKDIR FIOMOVE FIONCONTIG
35 36 13 5 6 2 38 18 33 34 31 47 41 30 1 37 10 32 7 21 42
Sets the file-attribute byte in the dosFs directory entry. Allocates contiguous disk space for a file or directory. Announces a media change. Formats the disk (device driver function). Initializes a dosFs file system on a disk volume. Flushes the file output buffer. Gets file status information (directory entry data). Gets the filename of the fd. Gets the volume label. Sets the volume label. Creates a new directory. Moves a file (does not rename the file). Gets the size of the maximum contiguous area on a device. Gets the number of free bytes on the volume. Gets the number of unread bytes in a file. Reads the next directory entry. Renames a file or directory. Removes a directory. Sets the current byte offset in a file. Same as FIOFLUSH, but also re-reads buffered file data. Truncates a file to a specified length.
10
FIOTRUNC
351
Table 10-3
Function
39 8 49 50 51 52 53 54 55
Un-mounts a disk volume. Returns the current byte position in a file. Allocates contiguous disk space using a 64-bit size. Gets the maximum contiguous disk space into a 64-bit integer. Gets the number of free bytes into a 64-bit integer. Gets the number of unread bytes in a file into a 64-bit integer. Sets the current byte offset in a file from a 64-bit integer. Gets the current byte position in a file into a 64-bit integer. Set the file's size from a 64-bit integer.
FIONFREE64 FIONREAD64
FIOSEEK64
FIOWHERE64
FIOTRUNC64
For more information, see the API references for dosFsLib and for ioctl( ) in ioLib.
352
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.
10
353
The volume is again mounted automatically on the first disk access following a ready-change operation. !
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.
Function
Decimal Value
Description
13 5 2 18
Announces a media change. Formats the disk (device driver function). Same as FIOSYNC. Gets the device name of the fd.
354
Table 10-4
Function
Decimal Value
Description
1 7 21 39 8
Gets the number of unread bytes on the device. Sets the current byte offset on the device. Writes out all modified file descriptor buffers. Un-mounts a disk volume. Returns the current byte position on the device.
10
Multi-volume sets Record format files CDs with a sector size that is not a power of two1 Multi-session CD-R or CD-RW2
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).
355
After initializing cdromFs and mounting it on a CD-ROM block device, you can access data on that device using the standard POSIX I/O calls: open( ), close( ), read( ), ioctl( ), readdir( ), and stat( ). The write( ) call always returns an error. The cdromFs utility supports multiple drives, multiple open files, and concurrent file access. When you specify a pathname, cdromFS accepts both forward slashes (/) and back slashes (\) as path delimiters. However, the backslash is not recommended because it might not be supported in future releases. The initialization sequence for the cdromFs file system is similar to installing a dosFs file system on a SCSI or ATA device. After you have created the CD file system device (10.7.2 Creating and Using cdromFs, p.357), use ioctl( ) to set file system options. The files system options are described below:
CDROMFS_DIR_MODE_SET/GET
These options set and get the directory mode. The directory mode controls whether a file is opened with the Joliet extensions, or without them. The directory mode can be set to any of the following:
MODE_ISO9660
Try opening the directory first without Joliet, and then with Joliet. !
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.
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.
356
For information on using cdromFs( ), see the API reference for cdromFsLib.
Function Constant
Decimal
Description
Gets the volume descriptor(s) used to open files. Sets the volume descriptor(s) used to open files. Gets the volume descriptor that is currently in use. Sets the readdir( ) strip version number setting.
357
Table 10-5
Function Constant
Decimal
Description
38 18 33 1 52 37 7 53 39 8 54
Gets file status information (directory entry data). Gets the filename of the file descriptor. Gets the volume label. Gets the number of unread bytes in a file. Gets the number of unread bytes in a file (64-bit version). Reads the next directory entry. Sets the current byte offset in a file. Sets the current byte offset in a file (64-bit version). Un-mounts a disk volume. Returns the current byte position in a file. Returns the current byte position in a file (64-bit version).
358
359
For example, adding a process-based application called myVxApp.vxe from the command line would look like this:
cd c:\myInstallDir\vxworks-6.1\target\proj\wrSbc8260_diab mkdir romfs copy c:\allMyVxApps\myVxApp.vxe romfs make TOOL=diab
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 utilitys ROMFS_DIR macro used to identify where it is in the build command. For example:
make TOOL=diab ROMFS_DIR="c:\allMyVxApps"
Note that any files located in the romfs directory are included in the system image, regardless of whether or not they are application executables.
And foo can also be accessed at run-time as /romfs/foo by any applications running on the target.
360
executables or other files), and performance may suffer when they are greater 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 9.8.5 Non-NFS Network Devices, p.315), without requiring any target resourcesexcept 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 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
361
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 */ /* open socket and /* connect to server /* 150.50.50.50 */ */ */
fd = open("/tgtsvr/TCP:150.50.50.50:6164",0,0);
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
362
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.
To use TSFS, configure VxWorks with the INCLUDE_WDB_TSFS component. This creates the /tgtsvr file system on the target. The target server on the host system must also be configured for TSFS. This involves assigning a root directory on your host to TSFS (see the discussion of the target server -R option in Security Considerations, p.363). For example, on a PC host you could set the TSFS root to c:\myTarget\logs. Having done so, opening the file /tgtsvr/logFoo on the target causes c:\myTarget\logs\logFoo to be opened on the host by the target server. A new file descriptor representing that file is returned to the caller on the target.
10
Security Considerations
While TSFS has much in common with netDrv, the security considerations are different (also see 9.8.5 Non-NFS Network Devices, p.315). 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.
363
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 Users Guide: Setting Up Your Hardware and the Wind River Workbench Users Guide: New Target Server Connections.
For information about using the TSFS to boot a targets, see VxWorks Kernel Programmers Guide: Kernel.
364
11
Error Detection and Reporting
11.1 Introduction 365 11.2 Configuring Error Detection and Reporting Facilities 366 11.3 Error Records 368 11.4 Displaying and Clearing Error Records 370 11.5 Fatal Error Handling Options 371 11.6 Other Error Handling Options for Processes 374 11.7 Using Error Reporting APIs in Application Code 374 11.8 Sample Error Record 375
11.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.
365
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 systems response to fatal errors. Macros for implementing error reporting in user code.
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 Programmers Guide: Error Detection and Reporting for information about facilities available only in the kernel. For information about related facilities, see 8.4 Memory Error Detection, p.274.
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 Programmers Guide.
VxWorks must be configured with the appropriate components. A persistent RAM memory region must be configured, and it must be sufficiently large to hold the error records. Optionally, users can change the systems default response to fatal errors.
366
11 Error Detection and Reporting 11.2 Configuring Error Detection and Reporting Facilities
11
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. The persistent-memory area is write-protected when the target system includes an MMU and VxWorks has been configured with MMU support. The size of the persistent memory region is defined by the PM_RESERVED_MEM configuration parameter. By default the size is set to six pages of memory. By default, the error detection and reporting facility uses one-half of whatever persistent memory is available. If no other applications require persistent memory, the component may be configured to use almost all of it. This can be accomplished by defining EDR_ERRLOG_SIZE to be the size of PM_RESERVED_MEM less the size of one page of memory. If you increase the size of the persistent memory region beyond the default, you must create a new boot loader with the same PM_RESERVED_MEM value. The memory area between RAM_HIGH_ADRS and sysMemTop( ) must be big enough to copy the VxWorks boot loader. If it exceeds the sysMemTop( ) limit, the boot loader may corrupt the area of persistent memory reserved for core dump storage when it loads VxWorks. The boot loader, must therefore be rebuilt with a lower RAM_HIGH_ADRS value.
367
WARNING: If the boot loader is not properly configured (as described above), 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 Programmers 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.
368
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 systems responses to the error (see 11.5 Fatal Error Handling Options, p.371). The event types are defined in Table 11-1, and the severity levels in Table 11-2.
Table 11-1 Event Types
Type
Description
System initialization events. System boot events. System reboot (warm boot) events. VxWorks kernel events. Interrupt handler events. Process environment events. Custom events (user defined).
11
Table 11-2
Severity Levels
Severity Level
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
369
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
memory map exception information processor registers disassembly listing (surrounding the faulting address) stack trace
Command
Action
Show all records. Show only FATAL severity level records. Show only INFO severity level records. Show only KERNEL event type records.
370
Table 11-3
Command
Action
Show only RTP (process) event type records. Show only USER event type records. Show only INTERRUPT event type records. Show only INIT event type records. Show only BOOT event type records. Show only REBOOT event type records.
The shells 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:
11
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
debug mode, for lab systems (development) deployed mode, for production systems (field)
The difference between these modes is in their response to fatal errors in processes (RTP events). In debug mode, a fatal error in a process results in the process being
371
stopped. In deployed mode, as fatal error in a process results in the process being terminated. The operative error handling mode is determined by the system debug flag (see 11.5.2 Setting the System Debug Flag, p.373). The default is deployed mode. Table 11-4 describes the responses in each mode for each of the event types. It also lists the routines that are called when fatal records are created. The error handling routines are called response to certain fatal errors. Only fatal errorsand no other event typeshave 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.
Table 11-4 FATAL Error-Handling Options
Event Type
Debug Mode
INIT KERNEL
INTERRUPT RTP
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.
372
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
The modes are identified as Debug, Deployed, or Permanently Deployed. The latter indicates that the INCLUDE_EDR_SYSDBG_FLAG component is not included in the system, which means that the mode is deployed and that it cannot be changed to debug.
The system can be set to either debug mode or deployed mode with the f boot loader parameter when a boot loader is configured and built. The value of 0x000 is used to select deployed mode. The value of 0x400 is used to select debug mode. By default, it is set to deployed mode. For information about configuring and building boot loaders, see the VxWorks Kernel Programmers Guide: Boot Loader.
To change the system debug flag interactively, stop the system when it boots. Then use the c command at the boot-loader command prompt. Change the value of the the f parameter: use 0x000 for deployed mode (the default) or to 0x400 for debug mode.
373
374
11
<<<<<Disassembly>>>>> 0x102261f4 0x102261f8 0x102261fc 0x10226200 0x10226204 0x10226208 0x1022620c 0x10226210 *0x10226214 0x10226218 0x1022621c 0x10226220 0x10226224 0x10226228 0x1022622c 48003559 3be30000 3c601022 38636244 389f0000 4cc63182 48002249 39800000 999f0000 48000014 3c601022 38636278 4cc63182 4800222d 80010014 bl addi lis addi addi crxor bl li stb b lis addi crxor bl lwz 0x1022974c # strtoul r31,r3,0x0 # 0 r3,0x1022 # 4130 r3,r3,0x6244 # 25156 r4,r31,0x0 # 0 crb6,crb6,crb6 0x10228454 # printf r12,0x0 # 0 r12,0(r31) 0x1022622c # 0x1022622c r3,0x1022 # 4130 r3,r3,0x6278 # 25208 crb6,crb6,crb6 0x10228454 # printf r0,20(r1)
375
0x10226230
83e1000c
lwz
r31,12(r1)
376
A
Kernel to RTP Application Migration
A.1 Introduction 377 A.2 Migrating Kernel Applications to Processes 377 A.3 Differences in Kernel and RTP APIs 390
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.
377
To run a 5.5 application in a 6.x real-time process, the software startup code must be changed, and the application must be built with different libraries. Furthermore, certain kernel-only APIs are not available as system calls, which may prevent certain types of software from being migrated out of the kernel. In particular, software that must execute with supervisor privilege (ISRs, drivers, and so on) or software that cannot communicate using standard APIs (interprocess communication, file descriptors, or sockets) cannot be migrated out of the kernel without more substantial changes.
Although real-time processes are designed to isolate and protect applications, many alternatives exist for communication between processes or between processes and kernel applications. If large amounts of data need to be shared, either between applications or between an application and the kernel, consider using a shared-memory or shared-data region. The applications that map a given shared-data region into their memory context can specify different access permissions; for instance, the application that creates and initializes the shared data can open it with read and write permissions, while all the consumer applications may open the shared data region with
378
read-only permissions. This provides some level of protection to the shared data. For more information, see the reference entry for sdLib. If your data needs to be more strictly protected and separated from other applications or from the kernel, use inter-process communication mechanisms to pass data between processes or between a process and the kernel. Common options are public versions of:
semaphores message queues message channels sockets (especially the AF_LOCAL domain sockets) pipes
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.
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 Programmers Guide: Kernel.
WARNING: This is the riskiest option as the possibility exists of breaking the
.init$nn and .fini$nn code sections are replaced by .ctors and .dtors sections.
379
.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 }
Wind River recommends that initialization and finalization functions be specified with an explicit priority. If no priority is specified, functions are assigned the lowest (last) priority by default; this default can be changed with -Xinit-section-default-pri. Unless the default is changed, C++ global class object constructors are also assigned the lowest (last) priority.
Linker command (.dld) files for legacy projects must be modified to define .ctors and .dtors sections. For an example, see bubble.dld and the Wind River Compiler Users Guide: Linker Command Language. Old-style .init$nn and .fini$nn sections are still supported, as are _STI__nn_ and _STD__nn_ function prefixes, through the -Xinit-section=2 option.
Do not call intLock( ) or intUnlock( ). Process tasks must not lock out interrupts. Process tasks can lock out preemption using taskRtpLock( ) and taskRtpUnlock( ), but only for tasks within the same process. In any case, a process cannot preempt another running process. 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 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
380
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 Programmers Guide: Shared Data.
Do not use processor-specific features and instructions in application code. This hampers portability.
POSIX Signals
Signal handling in processes follows POSIX semantics, not VxWorks kernel semantics. If your existing application used VxWorks signals, you must confirm that the new behavior is what the application requires. For more information, see A.2.9 POSIX Signal Differences, p.386.
Watchdogs
The wdLib routines cannot be used in user mode. Replace them with POSIX timers from timerLib as shown in Table A-1.
Table A-1 Corresponding wdLib and timerLib Routines
wdCreate( ) Routines
timer_create( ) Routines
wdCreate( ) wdStart( )
381
Table A-1
wdCreate( ) Routines
timer_create( ) Routines
wdCancel( ) wdDelete( )
timer_cancel( ) timer_delete( )
There are slight differences in the behavior of the two timers, as shown in Table A-2.
Table A-2 Differences Between Watchdogs and POSIX Timers
VxWorks wdLib
POSIX timerLib
A routine executes in an interrupt context when the watchdog timer expires. The handler executes when the timer expires, right in the context of the system clock tick handler.
A signal handler executes as a response to the timer expiring. A signal handler executes in the context of a task; the handler cannot run until 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.380.
382
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.
Example A-1 VxWorks 5.5 Method of I/O Redirection /* temporary data values */ int oldFd0; int oldFd1; int oldFd2; int newFd; /* Get oldFd0 oldFd1 oldFd2 the standard file descriptor numbers */ = ioGlobalStdGet(0); = ioGlobalStdGet(1); = ioGlobalStdGet(2);
A
/* redirect standard IO to new file */ ioGlobalStdSet (0, newFd); ioGlobalStdSet (1, newFd); ioGlobalStdSet (2, newFd); /* Do operations using new standard file for input/output/error */ /* When complete, restore the standard IO to normal */ ioGlobalStdSet (0, oldFd0); ioGlobalStdSet (1, oldFd1); ioGlobalStdSet (2, oldFd2); Example A-2 VxWorks 6.x Method of I/O Redirection
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; /* Get the standard file descriptor numbers */ oldFd0 = dup(0);
383
oldFd1 = dup(1); oldFd2 = dup(2); /* open new file to be stdin/out/err */ newFd = open ("newstandardoutputfile",O_RDWR,0); /* redirect standard IO to new file */ dup2 (newFd, 0); dup2 (newFd, 1); dup2 (newFd, 2); /* Do operations using new standard file for input/output/error */ /* When complete, restore the standard IO to normal */ dup2 (oldFd0, 0); dup2 (oldFd1, 1); dup2 (oldFd2, 2); /* close the dupes */ close (oldFd0); close (oldFd1); close (oldFd2);
Task Naming
Applications running in a process are running in a different environment from the kernel. Some APIs display a different scope in user mode than in kernel mode, typically to match POSIX semantics. exit( ) In user mode, this routine terminates the current process. In kernel mode, exit( ) terminates only the current task. The user-mode behavior of exit( ) matches the POSIX standard. The API taskExit( ) can be used in a process instead of exit( ) if you want to kill only the current task.
384
kill( ) In user mode, this routine sends a signal to a process. In kernel mode, kill( ) sends a signal only to a specific task. The user-mode behavior of kill( ) matches the POSIX standard. The API taskKill( ) can be used in a process instead of kill( ) if you want to send a signal only to a particular task within the process. raise( ) In user mode, this routine sends a signal to the calling process. In kernel mode, raise( ) sends a signal only to the calling task. The user-mode behavior of raise( ) matches the POSIX standard. The API taskRaise( ) can be used in a process instead of raise( ) if you wish to send a signal to the calling task. In addition, if you wish to send a signal to the calling process, the API rtpRaise( ) can be used in a process instead of raise( ). sigqueue( ) In user mode, this routine sends a queued signal to a process. In kernel mode, sigqueue( ) sends a queued signal only to a specific task. The user-mode behavior of sigqueue( ) matches the POSIX standard. The API taskSigqueue( ) can be used in a process instead of sigqueue( ) if you wish to send a queued signal to a particular task within the process. The API rtpSigqueue( ) can be used in a process instead of sigqueue( ) if you wish to send a queued signal to a particular process.
Task locking is available in a process, but is restricted to the tasks of the process where the locking or unlocking calls are made. In other words, you cannot provoke a system-wide task lock from within an application. This also means that, while the process task that disables the task context switching is ensured not to be preempted by other tasks in this same process, it probably will be preempted by other tasks from the kernel or from other applications. The API available for task locking and unlocking in user mode is different from the one available in the kernel. In an application, task locking can be obtained by calling the taskRtpLock( ) API. Task unlocking can be done by calling the taskRtpUnlock( ) API.
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
385
defined as private or public, as well as named. Private objects are visible only to tasks within a process, whereas public objectswhich must be namedare 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 6.9 Inter-Process Communication With Public Objects, p.140.
386
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.385.
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.
A
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. 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).
387
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. For more information, see Semaphores Interruptible by Signals, p.162 and Message Queues Interruptible by Signals, p.166.
Table A-3 shows signal APIs that behave differently in the kernel than in a process.
Table A-3 Differences in Signal API Behavior
API
Kernel Behavior
Process Behavior
sends a signal to the current task sends a signal to the current tasks process sends a queued signal to a task sends a queued signal to a process
388
Socket APIs
In the process of porting network applications to processes, Wind River has exposed both standard socket APIs and routing socket APIs at the process level. If you have an application that limits (or can be made to limit) its interaction with the network stack to standard or routing socket API calls, that application is a good candidate for porting to a process.
routeAdd( )
routeAdd( ) is not supported in user mode. In order to make or monitor changes to the routing table from user mode, routeAdd( ) must be replaced by a routing socket. For more information, see the Wind River Network Stack Programmers Guide.
389
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.
The APIs that make system calls (marked system, system call, or syscall in the reference entry) cannot complete their work without assistance from facilities provided only by the kernel. In processes, the routines use POSIX semantics rather than VxWorks semantics.
While additional changes to APIs have occurred since VxWorks 6.0 as the product has developed, it is helpful to compare the earliest differences to highlight the distinction between running an application in the kernel as opposed to a process.
390
A Kernel to RTP Application Migration A.3 Differences in Kernel and RTP APIs
gives up the CPU by making itself no longer ready. However, tasks in other processes may preempt a task locked with taskRtpLock( ). If exclusion between tasks in different processes is required, use a public semaphore in place of taskLock( ). 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.391.
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.
391
taskCreateHookAdd( ), taskDeleteHookAdd( ) The kernel versions of these routines are unchanged from VxWorks 5.5. However, the user-mode versions are slightly different:
They pass an integer task ID as an argument. They return STATUS instead of void.
For more information, see the reference entries for the user versions of taskCreateHookAdd( ) and taskDeleteHookAdd( ).
Object IDs as Pointers to Memory
In user mode, object IDs such as SEM_ID are no longer 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.
System Call Validation
All user-mode APIs that are system calls have all arguments and memory addresses validated before the call is allowed to complete.
392
A Kernel to RTP Application Migration A.3 Differences in Kernel and RTP APIs
There is no way to get the task list for all tasks in a process. Show routines are not available from user mode.
393
394
Index
Symbols
.ctors 379 .dtors 379 _VXWORKS_COMPATIBILITY_MODE 392 _WRS_DEPRECATED 390
A
access routines (POSIX) 211 aio_cancel( ) 306 aio_error( ) 307 testing completion 308 aio_fsync( ) 306 aio_read( ) 306 aio_return( ) 307 aio_suspend( ) 306 testing completion 308 aio_write( ) 306 aiocb, see control block (AIO) ANSI C function prototypes 43 header files 44 stdio package 303 APIs not present in processes taskSwitchHookAdd( ) 391 taskSwitchHookDelete( ) 391 APIs with different behavior in processes
asctime_r( ) 392 ctime_r( ) 392 gmtime_r( ) 392 kill( ) 388 localtime_r( ) 392 raise( ) 388 sigqueue( ) 388 strerror( ) 392 appliations executing absolutely-linked 37 application libraries 70 applications and overlapped virtual memory 35 APIs 46 building 50 development 40 executing 54 library routines 46 main( ) routine 54 ROMFS, bundling applications with 66 starting with rtpSpawn( ) 54 stripping absolutely-linked 36 structure 42 system calls 46 VxWorks component requirements 49, 54 archive file attribute (dosFs) 345 asctime_r( ) 392 asynchronous I/O (POSIX) 305 see also control block (AIO) see online aioPxLib
395
cancelling operations 307 control block 306 multiple requests, submitting 307 retrieving operation status 307 routines 305 attributes constructor 380 destructor 380 attributes (POSIX) 211 prioceiling attribute 222 specifying 213 attributes(POSIX) protocol attribute 222
B
backspace character, see delete character binary semaphores 149 block devices file systems, and 322364 naming 291 building applications 50
C
C and C++ libraries, Dinkum 48 C library 100 C++ development C and C++, referencing symbols between 102 Run-Time Type Information (RTTI) 104 C++ support 101105 see also iostreams (C++) cancelling threads (POSIX) 218 CD-ROM devices 355 cdromFs file systems 355 see online cdromFsLib character devices naming 291 characters, control (CTRL+x) tty 311 client-server communications 167
CLOCK_MONOTONIC 205 CLOCK_REALTIME 205 clocks see also system clock; clockLib(1) monotonic 205 POSIX 204208 real-time 205 system 130 close( ) using 298 closedir( ) 335, 344 clusters cluster groups 347 disk space, allocating (dosFs) 347 absolutely contiguous 347 methods 347 nearly contiguous 347 single cluster 347 extents 347 code pure 136 shared 134 code examples asynchronous I/O completion, determining signals, using 308 dosFs file systems file attributes, setting 346 maximum contiguous areas, finding 350 message queues attributes, examining (POSIX) 247, 249 POSIX 251 VxWorks 165 mutual exclusion 151 semaphores binary 151 named 243 recursive 157 unnamed (POSIX) 239 tasks deleting safely 129 round-robin time slice (POSIX) 235 synchronization 152 threads creating, with attributes 213214
396
Index
compatibility mode, ANSI string.h 392 timer.h 392 components application requirements 49, 54 configuration event 169 signals 178 configuration and build components 3 tools 3 configuring dosFs file systems 339, 341 HRFS file systems 327 TSFS 363 constructor attribute 380 contexts task 110 CONTIG_MAX 349 control block (AIO) 306 fields 306 control characters (CTRL+x) tty 311 conventions device naming 290 file naming 290 task names 123 counting semaphores 157, 237 creat( ) 298 ctime_r( ) 392 CTRL+C kernel shell abort) 311 CTRL+D (end-of-file) 311 CTRL+H delete character tty 311 CTRL+Q (resume) tty 311 CTRL+S (suspend) tty 311 CTRL+U (delete line) tty 311 CTRL+X (reboot) tty 311
D
data structures, shared 142143 debugging error status values 132133 delayed tasks 112 delayed-suspended tasks 112 delete character (CTRL+H) tty 311 delete-line character (CTRL+U) tty 311 destructor attribute 380 devices see also block devices; character devices; directaccess devices; drivers and specific device types accessing 290 creating pipes 312 default 291 dosFs 291 naming 290 network 314 NFS 314 non-NFS 315 pipes 312 pseudo-memory 313 serial I/O (terminal and pseudo-terminal) 309 sockets 316 working with, in VxWorks 308316 Dinkum C and C++ libraries 48 disks changing dosFs file systems 342 file systems, and 322364 mounting volumes 353 organization (rawFs) 353 synchronizing dosFs file systems 343 displaying information disk volume configuration, about 342 DLL, see plug-ins documentation 2 DOS_ATTR_ARCHIVE 345 DOS_ATTR_DIRECTORY 345
Index
397
DOS_ATTR_HIDDEN 345 DOS_ATTR_RDONLY 345 DOS_ATTR_SYSTEM 345 DOS_ATTR_VOL_LABEL 345 DOS_O_CONTIG 349 dosFs file systems 339 see also block devices; CBIO interface; clusters; FAT tables see online dosFsLib code examples file attributes, setting 346 maximum contiguous area on devices, finding the 350 configuring 339, 341 devices, naming 291 directories, reading 344 disk space, allocating 347 methods 347 disk volume configuration data, displaying 342 disks, changing 342 file attributes 344 ioctl( ) requests, supported 336, 350 open( ), creating files with 297 starting I/O 344 subdirectories creating 343 removing 343 synchronizing volumes 343 dosFsFmtLib 339 dosFsLib 339 dosFsShow( ) 342 drivers 290 see also devices and specific driver types file systems, and 322364 memory 313 NFS 314 non-NFS network 315 pipe 312 pty (pseudo-terminal) 309 tty (terminal) 309 VxWorks, available in 308 dup( ) 382 dup2( ) 382
E
ED&R, see error detection and reporting 365 end-of-file character (CTRL+D) 311 environment variables 15 errno 132133 return values 133 error memory error detection 274 error detection and reporting 365 APIs for application code 374 error records 368 fatal error handling options 371 persistent memory region 367 error handling options 371 error records 368 error status values 132133 errors run-time error checking (RTEC) 281 eventClear( ) 173, 174 eventReceive( ) 173, 174 events 169 accessing event flags 173 and object deletion 172 and show routines 174 and task deletion 172 configuring 169 defined 169 receiving 170 from message queues 170 from semaphores 170 from tasks and ISRs 170 routines 173 sending 171 task events register 174 eventSend( ) 173, 174 exception handling 134 signal handlers 134 executing applications 54 exit( ) 384 exit( ) 128
398
Index
F
fclose( ) 304 fd, see file descriptors FD_CLR 302 FD_ISSET 302 FD_SET 302 FD_ZERO 302 fdopen( ) 304 fdprintf( ) 304, 305 FIFO message queues, VxWorks 164 file descriptor table 293 file descriptors (fd) 292 see also files see online ioLib pending on multiple (select facility) 301 reclaiming 293 redirection 293 standard input/output/error 293 file pointers (fp) 303 file system monitor 325 file systems see also ROMFS file system;dosFs file systems; TRFS file system;rawFs file systems; tapeFs file systems; Target Server File System (TSFS); TrueFFS flash file systems block devices, and 322364 drivers, and 322364 files attributes (dosFs) 344 closing 298 contiguous (dosFs) absolutely 347 nearly 347 creating 298 deleting 299 exporting to remote machines 314 hidden (dosFs) 345 I/O system, and 290 naming 290 opening 296 reading from 299 remote machines, on 314
read-write (dosFs) 345 system (dosFs) 345 truncating 300 write-only (dosFs) 345 writing to 299 -fimplicit-templates compiler option 104 finalization, C++, processes 379 FIOATTRIBSET 346 FIOCONTIG 351 FIODISKCHANGE 354 FIODISKFORMAT 354 FIOFLUSH 336, 351, 354 pipes, using with 313 FIOFSTATGET 336, 351 FTP or RSH, using with 316 NFS client devices, using with 315 FIOGETNAME 336, 351 FTP or RSH, using with 316 NFS client devices, using with 315 pipes, using with 313 FIOLABELGET 351 FIOLABELSET 351 FIOMKDIR 343 FIOMOVE 336, 351 FIONCONTIG 351 FIONFREE 336, 351 FIONMSGS 313 FIONREAD 336, 351 FTP or RSH, using with 316 NFS client devices, using with 315 pipes, using with 313 FIOREADDIR 336, 351 FTP or RSH, using with 316 NFS client devices, using with 315 FIORENAME 336, 351 FIORMDIR 334, 343 FIOSEEK 354 FTP or RSH, using with 316 memory drivers, using with 314 NFS client devices, using with 315 FIOSETOPTIONS tty options, setting 309 FIOSYNC FTP or RSH, using with 316 NFS client devices, using with 315
Index
399
FIOTRUNC 347 FIOWHERE 337, 352 FTP or RSH, using with 316 memory drivers, using with 314 NFS client devices, using with 315 flat memory model 23 floating-point support task options 125 flow-control characters (CTRL+Q and S) tty 311 -fno-implicit-templates compiler option 104 -fno-rtti compiler option (C++) 104 fopen( ) 303 fread( ) 304 fstat( ) 335, 344 FSTAT_DIR 343 FTP (File Transfer Protocol) ioctl functions, and 316 ftruncate( ) 300, 347 fwrite( ) 304
routines callable by 132 HRFS commit policies 330, 332 configuring 328 creating 330 transactional operations 330, 332 HRFS file systems configuring 327 directories, reading 335 starting I/O 335 subdirectories removing 334 HRFS, see Highly Reliable File System 327
I
-I compiler option 45 I/O redirection VxWorks 5.5 383 VxWorks 6.x 383 I/O system see also I/O, asynchronous I/O 305 include files see also header files INCLUDE_ATA configuring dosFs file systems 328, 340 INCLUDE_CDROMFS 357 INCLUDE_DISK_UTIL 340 INCLUDE_DOSFS 339 INCLUDE_DOSFS_CHKDSK 340 INCLUDE_DOSFS_DIR_FIXED 340 INCLUDE_DOSFS_DIR_VFAT 340 INCLUDE_DOSFS_FAT 340 INCLUDE_DOSFS_FMT 340 INCLUDE_DOSFS_MAIN 340 INCLUDE_POSIX_FTRUNCATE 300 INCLUDE_POSIX_MEM 210 INCLUDE_POSIX_SCHED 234 INCLUDE_POSIX_SEM 236 INCLUDE_RAWFS 353 INCLUDE_SIGNALS 178 INCLUDE_TAR 340 INCLUDE_VXEVENTS 169 INCLUDE_WDB_TSFS 363
G
getc( ) 304 global variables 136 gmtime_r( ) 392
H
header files 43 ANSI 44 function prototypes 43 hiding internal details 46 nested 45 private 46 searching for 45 hidden files (dosFs) 345 Highly Reliable File System 327 commit policies 330 transactional operations 330 hook routines 50 hooks, task
400
Index
INCLUDE_XBD 340 INCLUDE_XBD_PARTLIB 328, 341 INCLUDE_XBD_RAMDISK 328, 341 INCLUDE_XBD_TRANS 341 initialization C++, processes 379 instantiation, template (C++) 104 inter-process communication public objects 140 interrupt service routines (ISR) and signals 178 interruptible message queue 166 semaphore 162 intertask communications 139178 network 175 intLock( ) 390 intUnlock( ) 390 I/O system 287 asynchronous I/O 305 basic I/O (ioLib) 292 buffered I/O 303 control functions (ioctl( )) 300 memory, accessing 313 redirection 293 serial devices 309 stdio package (ansiStdio) 303 ioctl( ) 300 dosFs file system support 336, 350 functions FTP, using with 316 memory drivers, using with 313 NFS client devices, using with 314 pipes, using with 313 RSH, using with 316 non-NFS devices 316 raw file system support 354 tty options, setting 309 ioDefPathGet( ) 291 ioDefPathSet( ) 291 IPC, see inter-process communications 140
K
kernel and multitasking 110 POSIX and VxWorks features, comparison of message queues 246 priority levels 115 kernel shell aborting (CTRL+C) tty 311 kernelTimeSlice( ) 120 keyboard shortcuts tty characters 311 kill( ) scope changes in process 385 signal behavior 388 kill( ) 178, 179
L
libc 100 libraries application 70 shared 79 static 78 line mode (tty devices) 310 selecting 310 lio_listio( ) 306 localtime_r( ) 392 locking page (POSIX) 209, 210 semaphores 235 task preemptive locks 116 longjmp( ) 134
Index
M
main( ) 54 memory driver (memDrv) 313 error detection 274 flat model 23
401
locking (POSIX) 209, 210 see also mmanPxLib(1) management, seememory management overlapped virtual memory model 24 paging (POSIX) 209 persistent memory region 367 pool 136 pseudo-I/O devices 313 swapping (POSIX) 209 user regions 26 memory management component requirements 272 error detection 274 heap and partition 272 memory management unit, seeMMU 20 message channels 175 message queue interruptible 166 message queues 162 see also msgQLib(1) and VxWorks events 168 client-server example 167 displaying attributes 166 POSIX 245 see also mqPxLib(1) attributes 247, 249 code examples attributes, examining 247, 249 communicating by message queue 251, 253 notifying tasks 253 unlinking 250 VxWorks facilities, differences from 246 priority setting 165 queuing 166 VxWorks 164 code example 165 creating 164 deleting 164 queueing order 164 receiving messages 164 sending messages 164 timing out 164 waiting tasks 164 migrating
kernel to RTP 377 mlock( ) 210 mlockall( ) 210 mmanPxLib 210 MMU processes without 20 mounting volumes rawFs file systems 353 mq_close( ) 245, 250 mq_getattr( ) 245, 247 mq_notify( ) 245, 253 mq_open( ) 245, 249 mq_receive( ) 245, 250 mq_send( ) 245, 249 mq_setattr( ) 245, 247 mq_unlink( ) 245, 250 mqPxLib 245 msgQCreate( ) 164 msgQDelete( ) 164 msgQEvStart( ) 173 msgQEvStop( ) 173 msgQReceive( ) 164 msgQSend( ) 164 msgQSend( ) 174 multitasking 110, 134 example 138 munlock( ) 210 munlockall( ) 210 mutexes (POSIX) 220 mutual exclusion 143 see also semLib(1) code example 151 counting semaphores 157 and reentrancy 136 VxWorks semaphores 152 binary 151 deletion safety 156 priority inheritance 155 priority inversion 154 recursive use 156
402
Index
N
named semaphores (POSIX) 235 using 241 nanosleep( ) 130 using 208 netDrv compared with TSFS 363 netDrv driver 315 network devices see also FTP; NFS; RSH NFS 314 non-NFS 315 Network File System, see NFS networks intertask communications 175 transparency 314 NFS (Network File System) devices 314 naming 291 open( ), creating files with 297 ioctl functions, and 314 transparency 314 nfsDrv driver 314 null devices 316 NUM_RAWFS_FILES 353
OPT_ECHO 310 OPT_LINE 310 OPT_MON_TRAP 310 OPT_RAW 310 OPT_TANDEM 310 OPT_TERMINAL 310 overlapped virtual memory and applications 35 configuring 29 RTP code region 27 user regions 26 overlapped virtual memory model using 26
24
P
page locking 209 see also mmanPxLib(1) paging 209 pended tasks 112 pended-suspended tasks 112 persistent memory region 367 pipeDevCreate( ) 168 pipes 168 see online pipeDrv ioctl functions, and 313 select( ), using with 168 plug-ins 94 POSIX see also asynchronous I/O asynchronous I/O 305 clocks 204208 see also clockLib(1) file truncation 300 memory-locking interface 209, 210 message queues 245 see also message queues; mqPxLib(1) mutex attributes prioceiling attribute 222 protocol attribute 222 page locking 209 see also mmanPxLib(1) paging 209 priority limits, getting task 234
Index
O
O_CREAT 343 O_NONBLOCK 247 O_CREAT 241 O_EXCL 241 O_NONBLOCK 249 open( ) 296 access flags 296 files asynchronously, accessing 305 files with, creating 297 subdirectories, creating 343 opendir( ) 335, 344 operating system 210 OPT_7_BIT 310 OPT_ABORT 310 OPT_CRMOD 310
403
priority numbering 227 scheduling 234 see also scheduling; schedPxLib(1) semaphores 235 see also semaphores; semPxLib(1) signal functions 179 see also signals; sigLib(1) signals, application behavior 386 swapping 209 thread attributes 211214 specifying 213 threads 210 timers 204208 see also timerLib(1) timers, difference from watchdogs 382 posixPriorityNumbering global variable 227 preemptive locks 116 preemptive priority scheduling 117, 118 printErr( ) 304, 305 prioceiling attribute 222 priority inheritance 155 inversion 154 message queues 165 numbering 227 preemptive, scheduling 117, 118 task, setting VxWorks 115 processes application development 40 configuring VxWorks for 17 default signal handling 387 definition 7 design issues communicating between processes 378 communicating with kernel 379 drivers 382 environment variables 15 flat memory model 23 hardware access 380 header files 389 I/O redirection 382 inheritance and resource reclamation 13 initial task 12
interrupt context 381 life cycle 7 memory and 10 networking issues 389 overlapped virtual memory model 24 POSIX and 16, 226 process APIs 384 real time 6 routines differing from kernel 392 scope changes exit( ) 384 kill( ) 385 raise( ) 385 sigqueue( ) 385 scope of signal handlers 387 semaphores 386 signal handlers 387 signals behavior 388 delivery 387 generation 387 mask 388 sent to blocked tasks 388 socket APIs 389 task APIs 384 task locking/unlocking 385 tasks and 11 virtual memory models 23 watchdogs 381 without MMU support 20 protocol attributes 222 pthread_attr_getdetachstate( ) 216 pthread_attr_getinheritsched( ) 216 pthread_attr_getschedparam( ) 217 pthread_attr_getscope( ) 216 pthread_attr_getstackaddr( ) 216 pthread_attr_getstacksize( ) 216 pthread_attr_setdetachstate( ) 216 pthread_attr_setinheritsched( ) 216 pthread_attr_setschedparam( ) 217 pthread_attr_setscope( ) 216 pthread_attr_setstackaddr( ) 216 pthread_attr_setstacksize( ) 216 pthread_attr_t 212 pthread_cancel( ) 219
404
Index
pthread_cleanup_pop( ) 219 pthread_cleanup_push( ) 219 pthread_getspecific( ) 217 pthread_key_create( ) 217 pthread_key_delete( ) 217 pthread_mutex_getprioceiling( ) 223 pthread_mutex_setprioceiling( ) 223 pthread_mutexattr_getprioceiling( ) 223 pthread_mutexattr_getprotocol( ) 222 pthread_mutexattr_setprioceiling( ) 223 pthread_mutexattr_setprotocol( ) 222 PTHREAD_PRIO_INHERIT 222 PTHREAD_PRIO_NONE 222 PTHREAD_PRIO_PROTECT 222 pthread_setcancelstate( ) 219 pthread_setcanceltype( ) 219 pthread_setspecific( ) 217 pthread_testcancel( ) 219 pty devices 309 see online ptyDrv public object and inter-process communication 140 public objects 385 tasks 122 pure code 136 putc( ) 304
Q
queued signals 179 queues see also message queues ordering (FIFO vs. priority) 161 semaphore wait 161 queuing message queues 166
R
-R option (TSFS) 363 raise( ) scope changes in process 385
signal behavior 388 raise( ) 179 raw mode (tty devices) 310 rawFs file systems 352355 see online rawFsLib disk organization 353 disk volume, mounting 353 ioctl( ) requests, support for 354 starting I/O 354 read( ) 299 read/write semaphores 158 readdir( ) 335, 344 ready tasks 112 real-time processes, see processes 6 reboot character (CTRL+X) tty 311 redirection 293 reentrancy 135138 regions, shared data 51 remove( ) 299 subdirectories, removing 334, 343 resource reclamation processes 13 restart character (CTRL+C) tty 311 resume character (CTRL+Q) tty 311 rewinddir( ) 335, 344 ROM monitor trap (CTRL+X) tty 311 ROMFS bundling applications 66 ROMFS file system 359 round-robin scheduling defined 119 routeAdd( ) 389 routines hook 50 scheduling, for 234 routines not present in user mode intLock( ) 390 intUnlock( ) 390 routeAdd( ) 389 RSH (Remote Shell protocol) ioctl functions, and 316
Index
405
RTEC, seerun-time error checking 281 RTP code region 27 RTP, see processes RTP_CODE_REGION_SIZE 34 RTP_CODE_REGION_START 33 RTP_LINK_ADDR 36 RTP_OVERLAPPED_ADDRESS_SPACE 33 rtpRaise( ) 385 rtpSigqueue( ) 385 rtpSpawn( ) 54 running applications 54 run-time error checking (RTEC) 281 Run-Time Type Information (RTTI) 104 -RW option (TSFS) 364
S
sched_get_priority_max( ) 234 sched_get_priority_max( ) 234 sched_get_priority_min( ) 234 sched_get_priority_min( ) 234 sched_rr_get_interval( ) 234 schedPxLib 227, 234 schedulers 115 scheduling 115 POSIX 234 see also schedPxLib(1) algorithms 226 priority limits 234 priority numbering 227 routines for 234 VxWorks preemptive locks 116 preemptive priority 117, 118 round-robin 119 sections .ctors 379 .dtors 379 security TSFS 363 select facility 301 see online selectLib macros 302
select( ) and pipes 168 select( ) 301 sem_close( ) 236, 242 SEM_DELETE_SAFE 156 sem_destroy( ) 236 sem_getvalue( ) 236 sem_init( ) 236, 238 SEM_INVERSION_SAFE 118, 155 sem_open( ) 236, 241 sem_post( ) 236 sem_trywait( ) 236 sem_unlink( ) 236, 242 sem_wait( ) 236 semaphores 144 and VxWorks events 162 see also semLib(1) counting 237 example 158 deleting 147, 237 giving and taking 149151, 235 in processes 386 interruptible 162 locking 235 POSIX 235 see also semPxLib(1) named 235, 241 code example 243 unnamed 235, 237, 237240 code example 239 posting 235 read/write 158 recursive 156 code example 157 semGive( ) 386 semInfoGet( ) 386 semOpen( ) 386 semTake( ) 386 synchronization 144, 157 code example 152 unlocking 235 VxWorks 144 binary 149 code example 151 control 145
406
Index
counting 157 mutual exclusion 151, 152 queuing 161 synchronization 152 timing out 161 waiting 235 semBCreate( ) 146 semBGive_inline( ) 147 semBGiveScalable( ) 146 semBInitialize( ) 145 semBTake_inline( ) 146 semBTakeScalable( ) 146 semCCreate( ) 146 semCInitialize( ) 145 semDelete( ) 146 semEvStart( ) 173 semEvStop( ) 173 semExchange( ) 147 semFlush( ) 147, 152 semGive( ) 386 semGive( ) 146 semGive( ) 174 semInfoGet( ) 386 semMCreate( ) 146 semMGive_inline( ) 147 semMGiveForce( ) 146 semMGiveScalable( ) 147 semMInitialize( ) 146 semMTake_inline( ) 146 semMTakeScalable( ) 146 semOpen( ) 386 semPxLib 235 semPxLibInit( ) 236 semRGive( ) 146 semRTake( ) 146 semRWCreate( ) 146 semRWGiveForce( ) 146 semRWInitialize( ) 146 semTake( ) 386 semTake( ) 146 semWTake( ) 146 serial drivers 309 setjmp( ) 134 shared code 134 shared data regions 51
shared data structures 142143 shared libraries 79 shared memory see also shared data regions 51 show( ) 166, 241 sigaction( ) 178, 179 sigaddset( ) 179 sigdelset( ) 179 sigemptyset( ) 179 sigfillset( ) 179 sigInit( ) 178 sigismember( ) 179 signal behavior, processes 388 signal handlers 178 signals 176, 176178 see also sigLib(1) configuring 178 and interrupt service routines 178 POSIX 179 queued 179 signal handlers 178 sigpending( ) 179 sigprocmask( ) 178, 179 sigqueue( ) scope changes in process 385 signal behavior 388 sigqueue( ) 179 sigsuspend( ) 179 sigtimedwait( ) 180 sigvec( ) 178 sigwaitinfo( ) 180 socket( ) 316 sockets 389 I/O devices, as 316 TSFS 362 spawning tasks 121122, 138 stacks no fill 124 standard input/output/error basic I/O 293 buffered I/O (ansiStdio) 304 starting applications 54 stat( ) 335, 344 static libraries 78
Index
407
stdio package ANSI C support 303 stopped tasks 112 strerror( ) 392 string.h 392 subdirectories (dosFs) creating 343 file attribute 345 suspended tasks 112 swapping 209 synchronization (task) 144 code example 152 counting semaphores, using semaphores 152 synchronizing media dosFs file systems 343 system calls 46 system clock 130 system files (dosFs) 345
157
T
Target Server File System (TSFS) 361 configuring 363 error handling 362 file access permissions 363 sockets, working with 362 task control blocks (TCB) 110, 131 task options not available in processes VX_SUPERVISOR 391 VX_UNBREAKABLE 391 task variables __thread storage class 137 taskActivate( ) 122 taskCreate( ) 122 taskCreateHookAdd( ) 392 taskCreateHookAdd( ) 131 taskCreateHookDelete( ) 131 taskDelay( ) 130 taskDelete( ) 128 taskDeleteHookAdd( ) 392 taskDeleteHookAdd( ) 131 taskDeleteHookDelete( ) 131 taskExit( ) 384
taskExit( ) 128 taskIdSelf( ) 123 taskIdVerify( ) 123 taskInfoGet( ) 127 taskInit( ) 391 taskIsDelayed( ) 127 taskIsPended( ) 127 taskIsReady( ) 127 taskIsSuspended( ) 127 taskKill( ) 385 taskName( ) 123 taskNameGet( ) 123 taskNameToId( ) 123 taskPriorityGet( ) 127 taskPrioritySet( ) 115, 116 taskRaise( ) 385 taskRestart( ) 130 taskResume( ) 130 taskRtpLock( ) behavior 385 hardware access 380 preemption 390 taskRtpLock( ) 115 taskRtpUnLock( ) 116 taskRtpUnlock( ) behavior 385 hardware access 380 preemption 390 tasks __thread task variables 137 blocked 116 contexts 110 control blocks 110, 131 creating 121122 delayed 112 delayed-suspended 112 delaying 111, 112, 130 deleting safely 128129 code example 129 semaphores, using 156 displaying information about environment variables 15 error status values 132133 see also errnoLib(1) exception handling 134
127
408
Index
see also signals; sigLib(1); excLib(1) executing 129 hooks see also taskHookLib(1) extending with 131132 IDs 122 inital process task 12 names 122 automatic 123 private 122 public 122 option parameters 124 pended 112 pended-suspended 112 priority, setting application tasks 115 VxWorks 115 processes and 11 public 122 ready 112 scheduling POSIX 234 preemptive locks 116 preemptive priority 117, 118 priority limits, getting 234 round-robin 119 VxWorks 115 shared code 134 and signals 134, 176178 spawning 121122, 138 stack allocation 125 stack protection 125 states 111113 stopped 112 suspended 112 suspending and resuming 130 synchronization 144 code example 152 counting semaphores, using 157 task events register 174 API 174 variables 137 taskSafe( ) 128 taskSigqueue( ) 385 taskSpawn( ) 121
taskSuspend( ) 130 taskSwitchHookAdd( ) replaced in processes 391 taskSwitchHookDelete( ) 391 taskUnsafe( ) 128, 129 __ 137 thread-local variable 137 threads (POSIX) 210 attributes 211214 specifying 213 keys 217 private data, accessing 217 terminating 218 time slicing 119 timeout message queues 164 semaphores 161 timeouts semaphores 161 timer.h 392 timer_cancel( ) 382 timer_connect( ) 381 timer_create( ) 381 timer_delete( ) 382 timer_settime( ) replacing watch dog 381 timers see also timerLib(1) message queues, for (VxWorks) 164 POSIX 204208 semaphores, for (VxWorks) 161 tools configuration and build 3 Transaction-Based Reliable File System, see TRFS 317 TRFS file system 317 truncation of files 300 tty devices 309 see online tyLib control characters (CTRL+x) 311 line mode 310 selecting 310 options 309 all, setting 310 none, setting 310
Index
409
raw mode 310 X-on/X-off 310 tyBackspaceSet( ) 312 tyDeleteLineSet( ) 312 tyEOFSet( ) 312 tyMonitorTrapSet( ) 312
VxWorks facilities POSIX, differences from message queues 246 scheduling 115 vxWorks.h 44
U
unnamed semaphores (POSIX) 235, 237, 237240 user regions 26 usrRtpAppInit( ) 64
W
WAIT_FOREVER 161 watchdogs 382 wdCancel( ) 382 wdCreate( ) 381 wdDelete( ) 382 wdStart( ) 381 write( ) 299
V
variables __thread task variables 137 global 136 static data 136 task 137 virtual memory models 23 Virtual Root File System 325 volume labels (dosFs) file attribute 345 VX_ALTIVEC_TASK 124 VX_DSP_TASK 124 VX_FP_TASK 102, 124 VX_FP_TASK option 125 VX_GLOBAL_NO_STACK_FILL 125 VX_NO_STACK_FILL 124 VX_PRIVATE_ENV 124 VX_SUPERVISOR 391 VX_UNBREAKABLE 391 VxWorks components 3 configuration and build 3 header files 43 message queues 164 real-time processes, see processes 6 VxWorks 5.5, I/O redirection 383 VxWorks 6.x, I/O redirection 383 VxWorks events, see events
410