![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Tasking The following topics are covered in this section:
- Example
- Task Creation
- Task Activation
- Task Startup
- Delay Statements
- Task Entry Calls
- Accept and Select Statements
- Task Completion and Termination
- I/O in Tasking Programs
- Library Selection
- Runtime Created Tasks
- Task Names
From the runtime perspective, the semantics of tasks are complicated. They are implemented by a collection of services in the Ada RTS as well as by code generated in the compiler.
ExampleThe following is a tasking example. The concept is taken from the Ada83 LRM 9.12, Ada95 LRM 9.11. Following it are discussions of its runtime interactions, including creation, activation, startup, delay statements, entry calls, accept and select statements, completion, and termination.
1:with text_io; 2:procedure buffer is 3: task buffer is 4: entry read(c: out character); 5: entry write(c: in character); 6: end; 7: task producer is 8: end; 9: task consumer is 10: end; 11: task body producer is 12: begin 13: ... 14: buffer.write(c); 15: ... 16: end; 17: task body consumer is 18: ... 19: begin 20: ... 21: select buffer.read(c); 22: ... 23: else delay(0.001); 24: end select; 25: ... 26: end; 27: task body buffer is 28: begin 29: ... 30: select when count < pool_size => 31: accept write(c: in character) do ... end; 32: ... 33: or when count > 0 => 34: accept read(c: out character) do ... end; 35: ... 36: or terminate; 37: end select; 38: ... 39: end buffer; 40:begin 41: ... 42: while buffer'callable loop 43: delay(0.001); 44: end loop; 45: ... 46:end;
Task CreationTasks come into being in two stages: creation and activation. Tasks are created by the elaboration of object declaration either defined as tasks or containing an instance of a task type. Both tasks and task type instances are treated identically by the runtime system.
In the example, the calls emitted by the compiler for lines 3-6, creating the buffer task, are:
function ts_init_activate_list return a_list_t; function ts_create_master ( fp : address; generic_param : address ) return a_master_t; function ts_create_task_and_link_named_apex2( params: in ts_create_task_t) return a_task_t; where:
A_List_T is a pointer to task activation list head
A_Master_T is a pointer to master structure
A_Task_T is a pointer to task control data structure
The interface to the above and all the Ada tasking routines called by the compiler is provided in the file v_i_taskop.a in the rational.ss subsystem. To see the actual code emitted by the compiler in calling the subprograms, enter the debugger and use the Disassembly button or li (list instruction) command.
The call to Ts_Init_Activate_List initializes a task activation list for this scope (for all the tasks created directly within the declarative part of the test procedure). A pointer to the list head is returned.
The call to Ts_Create_Master initializes a master structure. This structure will keep track of all the tasks that must terminate before this scope can be completed (in this example, before the program can be completed). A pointer to the master structure is returned.
The call to Ts_Create_Task_And_Link_Named_Apex2 causes the creation of a task control data structure. A pointer to this structure is returned.
The return value from this task creation function is the task's descriptor. The compiled code stores this descriptor in the storage class implied by the scope of the object declaration. It uses this descriptor as the task's value. Whenever the task must be named by the generated code to the RTS, this descriptor is passed to the RTS. This descriptor is really the pointer to the task's record, an RTS data structure. The generated code does not take advantage of this fact.
The Apex debugger now knows of this new task, so the debugger command lt elicits the list:
Q# NUM TASK DEBUG STATUS TASK STATUS
* 1 <main program> running executing
2 <debugger task> running waiting to exit
3 buffer.buffer running not yet active
The other tasks are created in a similar fashion, for Producer by the code at line 7 and Consumer by the code at line 9. Now the Tasks window or lt command shows::
Q# NUM TASK DEBUG STATUS TASK STATUS * 1 <main program> running executing 2 <debugger task> running waiting to exit 3 buffer.buffer running not yet active 4 buffer.producer running not yet active 5 buffer.consumer running not yet active
Task ActivationAt the begin that follows a declarative part that contains tasks, the program calls Ts_Activate_List. The activation list is the one being used for the declarative part. The RTS activates the tasks on the list and returns. The compiler calls Ts_Activation_Exceptions immediately following the call to Ts_Activate_List. This makes the activating task wait for the children that were just activated to run and elaborate their declarations. Each child calls Ts_Activation_Complete, which increments a count of successfully activated tasks. The last child to do this wakes up the activating task.
At line 40 the following calls are emitted:
procedure ts_activate_list( activation_list : a_list_id; is_allocator : integer);
function ts_activation_exceptions return act_status_t;
act_ok, act_elab_err, act_except, act_elab_err_act_except);
If Ts_Activation_Exceptions returns Act_Elab_Err, then Tasking_Error exception is raised. If Ts_Activation_Exceptions returns Act_Except or Act_Elab_Err_Act_Except, then Program_Error exception is raised.
The compiler emits the following call for Ts_Activation_Complete:
procedure ts_activation_complete(act_status : act_status_t);
When all the tasks from the above example activate and elaborate correctly, the following output is given by an lt command at an instruction breakpoint at the instruction following the call to Ts_Activate_Exceptions (just after source instruction 40).
Q# NUM TASK DEBUG STATUS TASK STATUS
* 1 <main program> running executing
2 <debugger task> running waiting to exit
D1 3 buffer.producer running suspended at delay
on delay queue, until: 0.048236 secs
D2 4 buffer.consumer running suspended at delay
on delay queue, until: 0.049273 secs
5 buffer.buffer running suspended at select (terminate possible)
open entries: write read
It is possible for the elaboration of the declarative part of a task body to cause an exception. In this case, Ts_Activation_Complete is called by an anonymous exception handler with a parameter indicating abnormal activation. The following calls are emitted for this implicit anonymous handler:
Procedure Ts_Complete_Task;
Procedure Ts_Activation_Complete (Act_Status:Act_Status_T);
Procedure Ts_Exception_Task(Identifier : V_I_Except.Exception.Is;
Occurence : Ada_Exceptions_Rts.Exception_ccurrence_Access);
This wakes up the activating task (which in this case is the main program) even though some of the sibling activated tasks may not yet have called Ts_Activation_Complete. The newly activated tasks are completed and terminated by the Ts_Activation_Exceptions service executing for that activating main program; it returns to the main program with a non-zero value:
0 all is ok during ativation 1 elaboration error, raising Tasking_Error 2 activation error, raising Program_Error
Task StartupThe code for task startup precedes the text for the task body. Consider the generated code for producer (use the debugger Disassembly button or li command).
The task body code has the same procedure startup as other subprograms, getting stack space, checking for stack limits, and setting up the addressing environment for reaching entities in other parts of the program (these are copied from the caller's stack frame).
The only purely tasking activity before elaboration is the call to Ts_Tid to get and record the ID of this task. This value is the task data structure address in the kernel space.
After any elaboration for the task is complete, a call to Ts_Activation_Complete informs the parent task of the success of the activation. Were this the last task on the activation list, the activating task (the main program here) would be placed on the run queue for further execution.
Delay StatementsSimple delay statements (that is, those not in the else part of a select) are implemented as calls to the Ts_Delay service:
procedure ts_delay(delay_val : in duration);
- Delay_Val is the internal representation for duration. Its resolution is 0.1 milliseconds for 32-bit machines and 1 nanosecond for 64-bit machines.The fixed- point number is pushed as the parameter to Ts_Delay. The following lt command output illustrates the main program after it has requested a delay at line 43:
Q# NUM TASK DEBUG STATUS TASK STATUS
D1 1 <main program> running suspended at delay
on delay queue, until: 40.060068 secs
2 <debugger task> running waiting to exit
3 buffer.producer running in rendezvous buffer.buffer[5].write
4 buffer.consumer running terminated
* 5 buffer.buffer running executing
in rendezvous with buffer.producer[3] at entry write
Tasks requesting delays are put on a Delay_Queue. Each timer interrupt checks each task on this queue to see if Current_Time has passed its time to awaken; each such task is then queued on the run queue.
Task Entry CallsThe compiler translates unconditional and untimed entry calls into calls to Ts_Call. Ts_Call takes three parameters: the task descriptor of the task being called, the entry ID of the entry being called (when the compiler sees a task specification it assigns integers, starting at one, to the declared entries), and the address of a parameter block for the call (parameter blocks are made to look like the stack memory for subprogram parameters).
Task Producer contains a simple entry call that provides characters to task Buffer.
The entry call has the following subprogram interface:
procedure ts_call ( called_task : in task_id; called_entry : in integer; param_block : in address);
The Ts_Call routine tries to do an immediate rendezvous if possible. Rendezvous is possible if the called task (Buffer) is suspended at an accept or at a select statement and is waiting at the entry being called. For immediate rendezvous, control is transferred to the called task. This is almost like calling it as a subprogram except that when the rendezvous is completed, both the called task and the calling task are able to execute.
The following is the lt output during a rendezvous:
Q# NUM TASK DEBUG STATUS TASK STATUS
D 1 <main program> running suspended at delay
2 <debugger task> running waiting to exit
3 buffer.producer running doing rendezvous
for buffer.buffer[5].write
D 4 buffer.consumer running suspended at delay
* 5 buffer.buffer running executing
in rendezvous via buffer.producer[3] at entry write
This displays a fast rendezvous, where the accept body is executed by the caller. See Fast Rendezvous Optimization for more information.
If the call cannot be done immediately, it is because the called task is not waiting at an accept or select or because it is not waiting for a call of the entry being called. The called task becomes suspended on the entry queue, waiting for the called task to accept that entry. The following lt command illustrates what happens when Producer suspends waiting for Buffer to accept a write:
Q# NUM TASK DEBUG STATUS TASK STATUS R1 1 <main program> running ready 2 <debugger task> running waiting to exit 3 buffer.producer running suspended at call buffer.buffer[5].write D1 4 buffer.consumer running suspended at delay on delay queue, until: 0.016650 secs * 5 buffer.buffer running executing
In the example, eventually Buffer accepts Producer. An lt output at that time shows a buffer executing the accept body:
Q# NUM TASK DEBUG STATUS TASK STATUS D1 1 <main program> running suspended at delay on delay queue, until: 40.060068 secs 2 <debugger task> running waiting to exit 3 buffer.producer running in rendezvous buffer.buffer[5].write 4 buffer.consumer running terminated * 5 buffer.buffer running executing in rendezvous with buffer.producer[3] at entry write
Conditional entry calls are implemented with the Ts_CondItional_Call service. It is the same as a Ts_Call except that if immediate rendezvous is not possible, it returns False. Otherwise it returns True after the rendezvous.
Timed entry calls are implemented with the Ts_Timed_Call service. Its additional parameter passes a delay duration. If immediate rendezvous is not possible, it suspends after setting up a delay for the requested duration. Whenever a timer interrupt follows, a task still on the Delay_Queue can be awakened as though from a delay.
The conditional and timed entry calls have the following subprogram interfaces:
function ts_conditional_call ( called_task : in task_id; called_entry : in integer; param_block : in address) return boolean; function ts_timed_call ( timeout : in duration; called_task : in task_id; called_entry : in integer; param_block : in address) return boolean;
Accept and Select StatementsThe example has no simple accept statements, but these points are similar to those of the more complex select statement.
Select statement code begins with evaluation of the guards for the accept alternatives. Compiled code builds a list of the open alternatives in a data structure called an entry list. An entry list is an array of integers. The first integer corresponds to the first alternative in the select statement, the second integer corresponds to the second alternative, and so on. If the guard for an entry is closed, a zero is put in the entry list element corresponding to the alternative. If the guard is open, the integer corresponding to the entry being accepted is put in the element corresponding to the alternative.
After the Entry_List is built, a call is generated to one of the Ts_Select routines:
ts_select simple selects (accept alternatives only) ts_select_terminate contains with a terminate alternative ts_select_else an else part (conditional accept) ts_select_delay contains one or more delay alternatives.
In this case, Ts_Select_Terminate is called, with the address and length of Entry_List, a Boolean that is true if the terminate alternative is open and space reserved for two out parameters (the result of the select process and the associated parameter block).
The Ts_Select procedure examines its entry queue to see if any task is waiting for any open entry. If so, immediate rendezvous is possible with the first such task. Otherwise, the task suspends until another task calls an open entry (for all selects) or until its delay expires (for selects with delay alternatives) or until all other dependent tasks are ready to terminate (for selects with open terminate alternatives). Selects with else parts fall to their else part unless immediate rendezvous is possible.
Q# NUM TASK DEBUG STATUS TASK STATUS * 1 <main program> running executing 2 <debugger task> running waiting to exit 3 buffer.producer running terminated 4 buffer.consumer running terminated 5 buffer.buffer running suspended at select (terminate possible) open entries: write read
When the rendezvous takes place, control is returned back to the acceptor task after its call to a Ts_Select subprogram. At the end of the accept body, the Ts_Finish_Accept procedure is called to allow both the acceptor and caller tasks to execute in parallel. If an unhandled exception is raised in the accept body, it is also propagated back to the caller task.
The accept, select, and finish accept calls have the following subprogram interfaces:
function ts_accept(accepting_entry : in integer) return address; procedure ts_select( user_entry_list : in a_entry_record_t; elist_len : in integer; param_block : out address; result : out integer); procedure ts_select_terminate( user_entry_list : in a_entry_record_t; elist_len : in integer; termin_open : in integer; param_block : out address; result : out integer); procedure ts_select_else( user_entry_list : in a_entry_record_t; elist_len : in integer; param_block : out address; result : out integer); procedure ts_select_delay( user_entry_list : in a_entry_record_t; elist_len : in integer; dlist_len : in integer; param_block : out address; result : out integer); procedure ts_finish_accept( exception_occurred : in integer; exception_string : in address);
a_entry_record_t is a pointer to entry list.
Task Completion and TerminationTasks come to an end in two stages: first they complete and then they terminate. Once a task is completed, it does not run again. A completed task becomes terminated when all tasks dependent on it either complete or agree to terminate, and all objects declared by the task are finalized.
The following call is emitted at the end of Producer, a task that has no descendants and simply executes through to completion:
procedure ts_complete_task;
The Ts_Complete_Task service checks and possibly waits for any dependent tasks.
After the call to Ts_Complete_Tasks, the compiler emits code to do any necessary finalization, for example, calling the appropriate finalize procedures for controlled objects declared by the task. Finally, a call to Ts_Terminate_Task is emitted reclaiming resources and stopping the task.
In the example, breakpoints can occur just after all the subtasks have terminated or are waiting at a terminate alternative:
Q# TASK NUM STATUS consumer 4 terminated producer 3 terminated buffer'2 2 suspended at select (terminate possible) open entries: write * <main program> 1 executing
Buffer cannot terminate yet because the main program is still executing; it can still call one of the open entries of Buffer. The code for the main program tries to shut down all the dependent tasks with a call to the Ts_Complete_Master service:
procedure ts_complete_master(master : master_id);
The main program now returns for completion of the whole program, as is described under program exit.
I/O in Tasking ProgramsA task can do I/O in one of the following ways:
- Program-blocking I/O
When a task performs program-blocking I/O, the execution of the entire program (all tasks) is suspended until the I/O completes.
- Task-blocking I/O
When a task performs task-blocking I/O, only the task requesting the I/O is suspended until the I/O completes; all others continue.
- Nonblocking I/O
When a task performs full nonblocking I/O, no blocking occurs. Instead, if the I/O cannot be performed immediately, an exception is raised as described in Table 5, "Nonblocking I/O Summary," on page 116.
The I/O method is determined on a file-by-file basis; that is, a single task can perform program-blocking I/O on one file, task-blocking I/O on another, and full nonblocking I/O on a third.
Specifying the I/O Method
To specify the I/O method, use:
- The Blocking field of the Form parameter on Ada-predefined I/O routines (Text_Io, Sequential_Io, and Direct_Io), described in Files and I/O.
- The Nonblocking_Io argument on pragma Main.
Program-blocking I/O occurs when a task:
- Invokes the blocking form of a UNIX system call (for example, read or write linked with the library libc.1.ada)
- Invokes a subprogram from any of the predefined I/O packages on a file accessed with Blocking = Program
- Is part of a program where Nonblocking_Io is False on pragma Main and invokes a subprogram from any of the predefined I/O packages on a file in the absence of an explicit Blocking mode
- Is part of a program where Nonblocking_Io is True on pragma Main and invokes a subprogram from any of the predefined I/O packages on a non-TTY file in the absence of an explicit Blocking mode
Task-blocking I/O can be invoked only for TTY devices. It occurs when a task invokes a subprogram from any of the predefined I/O packages on a TTY file when:
- The file was accessed with Blocking = Task.
- The task is part of a program where Nonblocking_Io is True on pragma Main in the absence of an explicit Blocking mode.
Nonblocking I/O occurs when a task:
- Invokes the nonblocking form of a UNIX system call (for example, read or write linked with the library nbio.a, or any calls to nbread or nbwrite)
- Invokes a subprogram from any of the predefined I/O packages on a file accessed with Blocking = None
Library SelectionFour runtime libraries offer different levels of support for various tasking constructs to produce smaller executables (and runtime performance gains) for programs not requiring the full generality of Ada tasking. The compiler keeps track of the constructs actually being used by a program and automatically provides whichever of the following runtime libraries is appropriate:
Note: Archive names for Cross systems, those using the mkvar archive tool, use the .var file extension. For example, libada.ts0.var.
There are arguments to the Restrictions pragma (LRM D.7) that can help to ensure that programs can use a particular runtime archive library. To avoid the use of libada.ts3.a, the following pragma can be used:
pragma Restrictions (No_Abort_Statements, No_Dynamic_Priorities, Max_Asynchronous_Select_Nesting => 0, No_Requeue_Statements);
Adding No_Terminate_Alternatives to this list will avoid libada.ts2.a as well:
pragma Restrictions (No_Abort_Statements, No_Dynamic_Priorities, Max_Asynchronous_Select_Nesting => 0, No_Requeue_Statements, No_Terminate_Alternatives);
Adding No_Tasking to this list will avoid libada.ts1.a as well, effectively forcing the selection of libada.ts0.a:
pragma Restrictions (No_Abort_Statements, No_Dynamic_Priorities, Max_Asynchronous_Select_Nesting => 0, No_Requeue_Statements, No_Terminate_Alternatives, No_Tasking);
Note: No_Requeue_Statements and No_Tasking are Rational defined restrictions. See "Configuration Pragmas", for more details on the use of pragma Restrictions under Apex.
Runtime Created TasksThe runtime may create tasks which do not correspond to any Ada task declaration as part of its own operation. This section shows how to recognize these tasks and describes their function.
The Idle Task
The idle task runs at a priority below that of any application task. It is part of the Apex deadlock detection mechanism (see Deadlock Detection) and can also be used to call application code to provide background processing (seeAda Kernel Layer for more information). The idle task is not shown by the debugger lt command.
C Threads
The POSIX Threaded Runtime for Apex is implemented using POSIX threads, and as such is compatible with C code that creates such threads. This runtime system is available for all Apex native platforms and is the only runtime available with Apex Embedded for LynxOS.
An Ada application using the POSIX threaded runtime may be linked with C language code that calls Ada subprograms, and those Ada subprograms may perform tasking operations. This requires interaction with the Ada runtime; many tasking operations (e.g. making an entry call) assume that the caller is an Ada task; in particular, they assume that an Ada Task Control Block (ATCB) is associated with the thread used to implement the task. This will not be true for threads created by C, so when the Ada runtime detects that it is being called from such a thread, it creates an ATCB for it, in effect "promoting" the C thread to an Ada task (see The POSIX Threaded Runtime for more information).
The debugger task window shows these "C tasks". An example demonstrating this feature is shipped with products that include the POSIX Threaded Runtime in the duo_examples.ss subsystem in the threads subdirectory. If this program is run up to a breakpoint in the Make_Entry_Call procedure:
29 procedure Make_Entry_Call is 30 begin 31*= Put_Line (Ada.Task_Identification.Image 32 (Ada.Task_Identification.Current_Task)); 33 T.From_C_Thread; 34 end Make_Entry_Call;
The debugger task window will show a "C task" with the name <non-ada task> (see Figure 2).
Figure 2 Debugger display of C thread, Call Completion Task (LynxOS)
![]()
The Call Completion Task
The call completion task performs tasking operations on behalf of application tasks that cannot do them directly. There are currently three cases where this is necessary:
- Abortion of an task waiting on an entry call to a protected object using the Ceiling_Locking policy. The abortion has to be done by the caller, which means waking it up, which means locking the protected object mutex on which it is waiting. As the aborter is running at an arbitrary priority, this might cause a ceiling violation, so the call completion task is used to do the wakeup at the caller's priority.
- Requeue from a protected entry body executed by an interrupt handler to a task. This occurs when an interrupt handler opens a barrier on which an entry call is waiting, and the entry body contains a requeue to a task. To do the requeue the task must be locked, and this cannot be done from an interrupt handler. The call completion task is used to continue the requeue.
- Reorder of a queue as required by dynamic priority change. As with abortion, the problem is locking a protected object from an arbitrary task while avoiding priority inversion.
The call completion task is displayed by the debugger task window with the name "internal task" (see Figure 2)
Interrupt/Signal Tasks
There are two types of tasks that the runtime may create to support application signal handlers. There are three basic mechanism for handling interrupts (or, in native and LynxOS products, signals) in Apex: protected procedure handlers (LRM C.3), interrupt entries (LRM J.7.1), and the V_Interrupts package (see Signal/Interrupt Handling (Package V_Interrupts)). The POSIX Threaded Runtime handles signals synchronously; if an application handles a signal a task will be created to wait for that signal and to notify the application task when it is delivered. This task will be shown under the debugger with the name <interrupt (#)>, with # replaced by the signal number (see Figure 3).
On all architectures, interrupt entries require the runtime to create an Ada task to rendezvous with the interrupt entry when the interrupt/signal is delivered. With the POSIX Threaded runtime, this task would be in addition to the lower level synchronous signal task. This task will be shown under the debugger with the name <signal (#)>, with # replaced by the signal (or interrupt) number (see Figure 3). The term "signal" in this context describes an Apex runtime service and should not be confused with POSIX signals.
Figure 3 Debugger display of signal and interrupt tasks (LynxOS)
![]()
Task NamesWhen creating a task, the runtime assigns it a unique name. This name is used for the following:
- Task name displayed by the Apex debugger
- Task name retrieved by Ada.Task_Identification.Image
When supported by the underlying OS, the task name is also incorporated into the string associated with the OS thread used to implement the task. This allows identification of Ada tasks in reports generated using OS tools. Currently this is supported for VxWorks and LynxOS products.
In the reports provided by both VxWorks and LynxOS, the thread name display is severely constrained. In the case of VxWorks, only the first 11 characters are displayed. For LynxOS, 32 characters are displayed, but the executable pathname is prepended to the thread name in deriving these 32 characters, so the thread name can be truncated.
For this reason, the first few characters of the task name is a pseudorandom identifier. The runtime calls the configurable V_Shared.V_Map_Task_Name in the usr_conf.ss subsystem to calculate this identifier. This function takes the full task name and a task sequence number as arguments and returns a shorter name that should still useful for identifying Ada tasks. This compressed name should consist of two substrings: one dependent only on the compiler-generated name and the other only on the runtime-generated sequence number. This allows a cross-reference list of the name-dependent substrings to be generated from the executable file which can be used to identify tasks at runtime. The intent is not that this association always be unique, but that it be unique often enough to aid in debugging.
The default algorithm for the V_Map_Task_Name function compresses the task name to 6 characters based on a 32 bit checksum. This is very likely to be unique, but the association between the original and the compressed names is not at all apparent to the eye. For example, here are some compiler generated names and the corresponding compressed names:
If the task sequence number is nonzero (the runtime starts numbering tasks at 1) it is appended to the name as a four character hexadecimal string, resulting in a unique 10 character identifier. The original name is then appended to the result. This is the name that is returned when Ada.Task_Identification.Image is applied to the task (see LRM C.7) and, when supported by the OS, associated with the underlying thread when the task is created.
In VxWorks, thread names are included in the report generated by the i command in the VxWorks shell. LynxOS includes them in the report generated by the ps command with the -t option in a LynxOS shell, as shown in the following example:
> ps -axt pid tid ppid pgrp pri text stk data time dev user S name 0 0 0 0 0 0 0 0 6+16:08 root R nullpr 1 2 1 1 16 24 8 8 0.28 root W /init 46 27 31 31 128 368 12 116 0.02 user W ./dining_philosophers:nCvs5/0001: 46 29+ 31 31 128 368* 4 116* 0.00 user W ./dining_philosophers:Ixg6u$000E: 46 38+ 31 31 128 368* 4 116* 0.00 user W ./dining_philosophers:qxqvm/000D: 46 34+ 31 31 128 368* 4 116* 0.00 user W ./dining_philosophers:qxqvm/000C:Customization
The V_Map_Task_Name function may be modified to provide compression customized to the task names used by your application, or to disable the compression feature altogether (by returning a null string). To do this, you must first create a customized usr_conf.ss view, as described in Configuring the User Library. Once you have created this view, do the following to configure task name compression:
- Modify V_Map_Task_Name in the v_shared.2.ada file in the new usr_conf.ss view to implement the desired mapping.
- Add "with V_Shared;" to the context clause of v_usr_conf.2.ada in the new usr_conf.ss view.
- In the view in which your application is to be linked, import the new usr_conf.ss view as described in Configuring the User Library.
To allow tasks and task types in the application to be identified from compressed names in the OS reports, a cross reference utility, task_xref, is provided. The task_xref utility is unique among Apex tools in that it is configurable; it incorporates the same name compression function used by the runtimes. For this reason, task_xref must be built before use and each time that V_Shared.V_Map_Task_Name is modified. The source code can be found in $APEX_BASE/ada/usr_conf.ss/<UsrConfView>/task_xref. If built from this subdirectory, task_xref will use the default V_Map_Task_Name compression function shipped with Apex. If you have customized V_Map_Task_Name as described above, the task_xref subdirectory in the customized usr_conf.ss view should be used.
To build the task_xref utility:
- 1 . Choose a subdirectory in which you want task_xref to be built.
- 2 . cd to the task_xref subdirectory in the appropriate usr_conf.ss view.
- 3 . Execute: apex_shell create_task_xref <build subdirectory>
For example, to build the default task xref in ~/build, execute:
cd $APEX_BASE/ada/usr_conf.ss/task_xref/<UsrConfView> apex_shell create_task_xref ~/buildThis will create a ~/build/task_xref.ss/<UsrConfView> view, populate it with source code, and link task_xref, which can then be applied to other linked applications:
% ~/build/task_xref.ss/<UsrConfView>/task_xref dining_philosophers | sort 0sgga@ Dining_Philosophers.O D9Na5@ <internal task> DEhv1@ Dining_Philosophers.Dr Dsj6O/ <debugger task> Ixg6u$ Dining_Philosophers.Rand_Delay'Spec Jzgso$ <interrupt ( )> PdVqf$ <signal ( )> ZhQsP@ <timer task> is98X/ Dining_Philosophers.Cutlery (array component) nCvs5/ <main task> oL3FY$ <non-ada task> qxqvm/ Dining_Philosophers.School (array component)Note the use of the UNIX sort utility to provide a list sorted by compressed name. A list sorted by "full" name can be obtained using the -n option of task_xref:
% ~/task_xref.ss/power.lx_ppc.ada95.sandbox.embedded.402.wrk/task_xref -n dining_philosophers | sort <debugger task> Dsj6O/ <internal task> D9Na5@ <interrupt ( )> Jzgso$ <main task> nCvs5/ <non-ada task> oL3FY$ <signal ( )> PdVqf$ <timer task> ZhQsP@ Dining_Philosophers.Cutlery (array component) is98X/ Dining_Philosophers.Dr DEhv1@ Dining_Philosophers.O 0sgga@ Dining_Philosophers.Rand_Delay'Spec Ixg6u$ Dining_Philosophers.School (array component) qxqvm/This cross reference list can be used to identify the LynxOS thread corresponding to the ps -axt output shown above:
46 27 31 31 128 368 12 116 0.02 user W ./dining_philosophers:nCvs5/0001:as implementing the <main task>, that is, the Ada environment task which calls the main program procedure.
Some of the names reported by task_xref are for tasks that do not correspond to tasks in the program source code. These include internal Ada tasks (e.g. <internal task> and <timer task>) and threads created by imported foreign language code (e.g. C) promoted to tasks by the Ada runtime system (e.g. <non-ada task>).
Task Name Comments
The names reported by the cross reference utility are static; they contain no information that would only be available at runtime. It is sometimes helpful to incorporate runtime information into task names when the task is created. Currently, this is only done for tasks which are used in the handling of signals (Apex native products, Apex embedded for LynxOS) or interrupts (Apex embedded products). For example, if a program attaches a task entry to the SIGUSR1 signal in LynxOS using
for Sigusr1_Entry'Address use System.Storage_Elements.To_Address (Psignal.Sigusr1);the runtime will create two tasks, shown here as reported by the LyxnOS "ps -axt" command:
21 32+ 15 15 128 364* 4 116* 0.00 user W ./tn:PdVqf$0010:<signal ( 30)> 21 21+ 15 15 255 364* 4 116* 0.00 user W ./tn:Jzgso$000F:<interrupt ( 30)The task named <interrupt ( 30)> is a low-level synchronous handler; the one named <signal ( 30)> makes the call to the attached entry, which must be done by a task. SIGUSR1 is signal 30 under LynxOS, so the runtime has inserted this into the name.
When the task_xref utility is run on the executable, these two names are reported with the same compressed names seen at runtime:
PdVqf$ <signal ( )> Jzgso$ <interrupt ( )>Note that the compressed names would not in general match if the name as modified by the runtime were passed to V_Map_Task_Name. To prevent the runtime comment from affecting the compressed name, both the runtime and task_xref pass the task name truncated at the first `(` character (e.g. "<signal (") to V_Map_Task_Name. In this way the names listed in the cross reference remain useful at runtime.
Rational Software Corporation http://www.rational.com support@rational.com techpubs@rational.com Copyright © 1993-2002, Rational Software Corporation. All rights reserved. |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |