• No results found

Multitasking .5 Task Creation and Management

Static Libraries, Shared Libraries, and Plug-Ins

INCLUDE_CPLUS_DEMANGLER

6 Multitasking .5 Task Creation and Management

The taskOpen( ) routine provides a POSIX-like API for creating a task (with optional activation) or obtaining a handle on existing task. It also provides for creating a task as a public object, with visibility across all processes and the kernel (see 6.5.3 Inter-Process Communication With Public Tasks, p.110). 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.

6.5.2 Task Names and IDs

A task must be named explicitly (using an ASCII string of any length) if it is created with taskOpen( ). It does not have to be named if it is created with taskSpawn( ), in which case using a null pointer is supplied for the name argument results in automatic assignment of a unique name. The name is of the form tN, where N is a decimal integer that is incremented by one for each task that is created without an explicit name. Regards of how a task is created, a task ID is returned when it is spawned.

To avoid name conflicts, VxWorks uses a convention of prefixing any kernel task name started from the target with the letter t, and any task name started from the host with the letter u. In addition, the name of the initial task of a real-time process is the executable file name (less the extension) prefixed with the letter i.

A task must be created with a name that begins with a back-slash in order to be a public object accessible throughout the system (and not just in the memory context in which it was created—process or kernel). For more information, see

6.5.3 Inter-Process Communication With Public Tasks, p.110.

Most VxWorks task routines take a task ID as the argument specifying a task.

VxWorks uses a convention that a task ID of 0 (zero) always implies the calling task.

Task Naming Rules

The following rules and guidelines should be followed when naming tasks:

The names of public tasks must be unique and must begin with a forward slash (for example, /tMyTask). Note that public tasks are visible throughout the

Table 6-2 Task Creation Routines

Routine Description

taskSpawn( ) Spawns (creates and activates) a new task.

taskCreate( ) Creates, but not activates a new task.

taskOpen( ) Open a task (or optionally create one, if it does not exist).

taskActivate( ) Activates an initialized task.

entire system—all processes and the kernel. For more information about public tasks, see 6.5.3 Inter-Process Communication With Public Tasks, p.110.

The names of private tasks should be unique. VxWorks does not require that private task names be unique, but it is preferable to use unique names to avoid confusing the user. (Note that private tasks are visible only within the entity in which they were created—either the kernel or a process.)

To use the host development tools to their best advantage, task names should not conflict with globally visible routine or variable names.

Task Name and ID Routines

The taskLib routines listed in Table 6-3 manage task IDs and names.

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.

6.5.3 Inter-Process Communication With Public Tasks

VxWorks tasks can be created as private objects, which are accessible only within the memory space in which they were created (kernel or process); or as public objects, which are accessible throughout the system (the kernel and all processes).

Whether tasks are created as private or public objects is determined by how they are named when created. Public tasks must be explicitly named with a

forward-slash prefix (for example, /tMyTask). For more information about naming tasks, see 6.5.2 Task Names and IDs, p.109.

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 detailed information, see 7.8 Inter-Process Communication With Public Objects, p.149 and the taskLib entry in the VxWorks Application API references.

Table 6-3 Task Name and ID Routines

Routine Description

taskName( ) Gets the task name associated with a task ID (restricted to the context—process or kernel—in which it is called).

taskNameGet( ) Gets the task name associated with a task ID anywhere in the entire system (kernel and any processes).

taskNameToId( ) Looks up the task ID associated with a task name.

taskIdSelf( ) Gets the calling task’s ID.

taskIdVerify( ) Verifies the existence of a specified task.

6 Multitasking 6.5 Task Creation and Management

6.5.4 Task Creation Options

When a task is spawned, you can pass in one or more option parameters, which are listed in Table 6-4. The result is determined by performing a logical OR operation on the specified options.

Floating Point Operations

You must include the VX_FP_TASK option when creating a task that does any of the following:

Performs floating-point operations.

Calls any function that returns a floating-point value.

Calls any function that takes a floating-point value as an argument.

For example:

tid = taskSpawn ("tMyTask", 90, VX_FP_TASK, 20000, myFunc, 2387, 0, 0, 0, 0, 0, 0, 0, 0, 0);

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).

Table 6-4 Task Options

Name Description

VX_ALTIVEC_TASK Execute with Altivec coprocessor support.

VX_DSP_TASK Execute with DSP coprocessor support.

VX_FP_TASK Executes with the floating-point coprocessor.

VX_NO_STACK_FILL Does not fill the stack with 0xEE.

VX_NO_STACK_PROTECT Create without stack overflow or underflow guard zones (see 6.5.5 Task Stack, p.112).

VX_PRIVATE_ENV Executes a task with a private environment.

VX_TASK_NOACTIVATE Used with taskOpen( ) so that the task is not activated.

6.5.5 Task Stack

The size of each task’s stack is defined when the task is created (see 6.5.1 Task Creation and Activation, p.108).

It can be difficult, however, to know exactly how much stack space to allocate. To help avoid stack overflow and corruption, you can initially allocated a stack that is much larger than you expect the task to require. Then monitor the stack

periodically from the shell with checkStack( ) or ti( ). When you have determined actual usage, adjust the stack size accordingly for testing and for the deployed system.

In addition to experimenting with task stack size, you can also configure and test systems with guard zone protection for task stacks (for more information, see Task Stack Protection, p.112).

Task Stack Protection

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 task’s stack.

Configuring VxWorks with the INCLUDE_PROTECT_TASK_STACK component adds overflow (but not underflow) protection for user-mode task exception stacks.

! CAUTION: For 64-bit VxWorks, due to the larger size of several data types in the LP64 data model, a task is likely to need a larger stack than what is sufficient for 32-bit VxWorks. Typically an increase of 35% to 50% is enough to most

applications, although there may be cases in which the stack size must be doubled.

Use the checkStack( ) shell command to determine if your tasks’ stacks are too small (that is, the high water mark is too close to the size of the stack).

6 Multitasking 6.5 Task Creation and Management

Note that RTP task guard zones for execution stacks do not use any physical memory—they are virtual memory entities in user space. The guard zones for RTP task exception stacks, however, are in kernel memory space and mapped to physical memory.

Note that the INCLUDE_PROTECT_TASK_STACK component does not provide stack protection for tasks that are created with the VX_NO_STACK_PROTECT task option (see 6.5.4 Task Creation Options, p.111). If a task is created with this option, no guard zones are created for that task.

The size of the guard zones are defined by the following configuration parameters:

TASK_USER_EXEC_STACK_OVERFLOW_SIZE for user task execution stack overflow size.

TASK_USER_EXEC_STACK_UNDERFLOW_SIZE for user task execution stack underflow size.

TASK_USER_EXC_STACK_OVERFLOW_SIZE for user task exception stack overflow size.

The value of these parameters can be modified to increase the size of the guard zones on a system-wide basis. The size of a guard zone is rounded up to a multiple of the CPU MMU page size. The insertion of a guard zone can be prevented by setting the parameter to zero.

Note that for POSIX threads in processes, the size of the execution stack guard zone can be set on an individual basis before the thread is created (using the pthread attributes object, pthread_attr_t). For more information, see 9.13.1 POSIX Thread Stack Guard Zones, p.179.

6.5.6 Task Information

The routines listed in Table 6-5 get information about a task by taking a snapshot of a task’s context when the routine is called. Because the task state is dynamic, the information may not be current unless the task is known to be dormant (that is, suspended).

For information about task-specific variables and their use, see 6.8.3 Task-Specific Variables, p.120.

Table 6-5 Task Information Routines

Routine Description

taskInfoGet( ) Gets information about a task.

taskPriorityGet( ) Examines the priority of a task.

taskIsSuspended( ) Checks whether a task is suspended.

taskIsReady( ) Checks whether a task is ready to run.

taskIsPended( ) Checks whether a task is pended.

taskIsDelayed( ) Checks whether or not a task is delayed.

6.5.7 Task Deletion and Deletion Safety

Tasks can be dynamically deleted from the system. VxWorks includes the routines listed in Table 6-6 to delete tasks and to protect tasks from unexpected deletion.

A process implicitly calls exit( ), thus terminating all tasks within it, if the process’

main( ) routine returns. For more information see 2.2.3 RTP Termination, p.8.

Tasks implicitly call taskExit( ) if the entry routine specified during task creation returns.

When a task is deleted, no other task is notified of this deletion. The routines taskSafe( ) and taskUnsafe( ) address problems that stem from unexpected deletion of tasks. The routine taskSafe( ) protects a task from deletion by other tasks. This protection is often needed when a task executes in a critical region or engages a critical resource.

For example, a task might take a semaphore for exclusive access to some data structure. While executing inside the critical region, the task might be deleted by another task. Because the task is unable to complete the critical region, the data structure might be left in a corrupt or inconsistent state. Furthermore, because the semaphore can never be released by the task, the critical resource is now

unavailable for use by any other task and is essentially frozen.

Using taskSafe( ) to protect the task that took the semaphore prevents such an outcome. Any task that tries to delete a task protected with taskSafe( ) is blocked.

When finished with its critical resource, the protected task can make itself available for deletion by calling taskUnsafe( ), which readies any deleting task. To support nested deletion-safe regions, a count is kept of the number of times taskSafe( ) and

Table 6-6 Task-Deletion Routines

Routine Description

exit( ) Terminates the specified process (and therefore all tasks in it) and frees the process’ memory resources.

taskExit( ) Terminates the calling task (in a process) and frees the stack and any other memory resources, including the task control block.a taskDelete( ) Terminates a specified task and frees memory (task stacks and task control blocks only).b The calling task may terminate itself with this routine.

taskSafe( ) Protects the calling task from deletion by any other task in the same process. A task in a different process can still delete that task by terminating the process itself with kill( ).

taskUnsafe( ) Undoes a taskSafe( ), which makes calling task available for deletion.

a. Memory that is allocated by the task during its execution is not freed when the task is terminated.

b. 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.

6 Multitasking 6.5 Task Creation and Management

taskUnsafe( ) are called. Deletion is allowed only when the count is zero, that is, there are as many unsafes as safes. Only the calling task is protected. A task cannot make another task safe or unsafe from deletion.

The following code fragment shows how to use taskSafe( ) and taskUnsafe( ) to protect a critical region of code:

taskSafe ();

semTake (semId, WAIT_FOREVER); /* Block until semaphore available */

.

. /* critical region code */

.

semGive (semId); /* Release semaphore */

taskUnsafe ();

Deletion safety is often coupled closely with mutual exclusion, as in this example.

For convenience and efficiency, a special kind of semaphore, the mutual-exclusion semaphore, offers an option for deletion safety. For more information, see

7.4.4 Mutual-Exclusion Semaphores, p.130.

6.5.8 Task Execution Control

The routines listed in Table 6-7 provide direct control over a task’s execution.

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 9.9 POSIX Clocks and Timers, p.173.

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 */

Table 6-7 Task Execution Control Routines

Routine Description taskSuspend( ) Suspends a task.

taskResume( ) Resumes a task.

taskRestart( ) Restarts a task.

taskDelay( ) Delays a task; delay units are ticks, resolution in ticks.

nanosleep( ) Delays a task; delay units are nanoseconds, resolution in ticks.

A delay of zero duration is only possible with taskDelay( ). The nanosleep( ) routine treats zero as an error. For information about the ready queue, see Scheduling and the Ready Queue, p.106.

System clock resolution is typically 60Hz (60 times per second). This is a relatively long time for one clock tick, and would be even at 100Hz or 120Hz. Thus, since periodic delaying is effectively polling, you may want to consider using

event-driven techniques as an alternative.

6.5.9 Task Scheduling Control

Tasks are assigned a priority when they are created (see 6.5.1 Task Creation and Activation, p.108). You can change a task’s priority level while it is executing by calling taskPrioritySet( ). The ability to change task priorities dynamically allows applications to track precedence changes in the real world. Be aware, however, that when a task’s priority is changed with taskPrioritySet( ), it is placed at the end of the ready queue priority list for its new priority. For information about the ready queue, see Scheduling and the Ready Queue, p.106.

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.195.

6.5.10 Tasking Extensions: Hook Routines

To allow additional task-related facilities to be added to the system, VxWorks provides hook routines that allow additional routines to be invoked whenever a task is created, a task context switch occurs, or a task is deleted. There are spare fields in the task control block (TCB) available for application extension of a task’s context. These hook routines are listed in Table 6-8; for more information, see the VxWorks API reference for taskHookLib.

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 NOTE: ANSI and POSIX APIs are similar.

Table 6-8 Task Hook Routines

Routine Description

taskCreateHookAdd( ) Adds a routine to be called at every task create.

taskCreateHookDelete( ) Deletes a previously added task create routine.

taskDeleteHookAdd( ) Adds a routine to be called at every task delete.

taskDeleteHookDelete( ) Deletes a previously added task delete routine.

6 Multitasking