TASK_$INTRO Domain/OS TASK_$INTRO
NAME
intro - the Domain/OS task library
SYNOPSIS (C)
#include <apollo/base.h>
#include <apollo/task.h>
SYNOPSIS (Pascal)
%include "/sys/ins/base.ins.pas"
%include "/sys/ins/task.ins.pas"
DESCRIPTION
The task_$ calls create and manage a multitasking environment within a
single process. In a multitasking environment, multiple threads of exe-
cution (tasks) run within a single address space. Because task creation
and switching are less expensive than process creation and switching,
tasking is a useful way of breaking down any complex operation into
separate pieces that run concurrently. It is particularly useful in
Remote Procedure Call (RPC) servers that manage multiple remote requests
in parallel.
The following is a list of the task_$ calls.
task_$blast kill a task without cleanup
task_$create create a task
task_$exit exit a task
task_$get_ec get a completion eventcount
task_$get_handle return the task handle
task_$get_info get information about a task
task_$release release a task and report
task_$set_name name a task
task_$set_result change the completion status and output value
task_$signal signal a task
task_$tasking_enabled determine whether tasking is enabled
task_$yield yield the processor
Creating a Task
A process creates a task by calling task_$create, which returns a task
handle to identify the task. You can obtain the handle of the currently
running task by calling task_$get_handle. (You can also optionally
assign to the task an ASCII-string task name, up to 32 characters, by
calling task_$set_name.)
The first task in a process, the one that first calls task_$create, is
known as the Distinguished Task (DT). After the first call to
task_$create, tasking is enabled in the process, and any task can create
a new task.
Time Slicing and Priority Levels
Tasks are able to run concurrently because of a scheduling scheme based
on time slicing and priority levels. An initial priority level (from 1
to 9, where 9 is the highest) is assigned to a task when it is created.
The Task Scheduler uses the priority level to determine which task (of
the tasks that are ready) to run when one of the following conditions
occurs:
⊕ A time-slice interval ends.
⊕ A task voluntarily yields the processor by calling task_$yield.
⊕ A task blocks in user space by calling one of the following:
ec2_$wait, ec2_$wait_svc, time_$wait, msg_$wait, or mutex_$lock.
(When a task blocks in the operating system the entire process is
blocked, and another task is not started until the process returns to
user space.)
As a task runs without blocking, its priority level is gradually lowered
until it reaches the lowest priority level. It then runs at the lowest
priority level until it blocks. Each time a task wakes after being
blocked, the Task Scheduler reassigns the initial priority level to the
task. The Task Scheduler algorithm favors tasks that run a short time
between blocking, but ensures that all tasks eventually run: no task can
lock out other tasks forever.
A task exists until one of the following events occurs:
⊕ The task routine returns.
⊕ The task routine calls task_$exit.
⊕ An unhandled fault occurs.
⊕ The task's lifetime, as defined by task_$create, expires.
Completion Eventcount
If a task is created with the task_$intend_to_wait option, the system
maintains an eventcount that advances when the task completes. A task
can wait for the completion of another by calling task_$get_ec to get the
eventcount, and then using ec2_$wait to wait for it to advance. If a
task is created with a completion eventcount, then some other task must
call task_$release to release the task after it completes. The
task_$release call will also supply the completion status and output
value of the completed task to the caller.
Fault Inhibiting
Inhibiting faults in a tasking process inhibits all asynchronous signals,
including those used for task switching. To inhibit all signals, includ-
ing those that cause task switching, use pfm_$inhibit and pfm_$enable.
For example, a tasking program could use pfm_$inhibit and pfm_$enable
pairs to prevent other tasks from running during critical sections of
code.
Fault inhibiting is always per task: one task can inhibit faults while
another doesn't. If a fault is pending when a non-inhibited task begins
to run, the fault is signaled then. Also, pfm_$inhibit does not prevent
a task switch from occurring when a task blocks or voluntarily yields the
processor.
Fault Handlers
Because fault handlers are established on a per-process basis, the same
fault handler executes regardless of the task that established it or the
task that is running when the fault occurs. A handled fault is not pro-
pagated to the DT.
Clean-Up Handlers
Clean-up handlers are established per task; that is, a clean-up handler
is invoked only in the task that established it. A synchronous fault is
delivered to the clean-up handler in the task in which the synchronous
fault occurs. An asynchronous fault is delivered to the DT the next time
it runs. The Task Manager sets up a default clean-up handler at the base
of a task's stack when the task is created.
A task can exit with cleanup either by returning or by calling
task_$exit. A task can also cause another task to exit cleanly by cal-
ling task_$signal. As a last resort, a task can destroy another task
without cleanup by calling task_$blast.
Programming Restrictions
Because tasks are time-sliced, an application using tasking must ensure
that all code it uses is "re-entrant"; that is, designed so that multiple
tasks can execute it concurrently.
For an example of code that is not re-entrant, suppose an application
(written in C) uses two tasks, T1 and T2. Each task calls a function
add_to_table that adds an integer to a table (array) of integers. The
procedure uses two global variables, num_of_entries and table.
int num_of_entries = 0;
int table[100];
void
add_to_table(int x)
{
table[num_of_entries] = x;
num_of_entries = num_of_entries + 1;
}
Task T1 assigns x to table[num_of_entries] but is then suspended.
Meanwhile, task T2 starts running and calls add_to_table. Since task T1
has not yet updated num_of_entries, task T2 overwrites the entry that
task T1 has made. Presumably, the application intends that the table and
entry count should reflect the calls made by both tasks; consequently,
add_to_table is not re-entrant.
It is often possible to safely use code that is not re-entrant. For
example, if a system library that is not re-entrant is called solely from
one task, then it does not matter that the library is not re-entrant.
Also, some libraries return handles to static data structures, where the
handle is the only pathway to that storage. Consequently, if only one
task ever uses a particular handle when it makes calls that are not re-
entrant, no problems will occur. For example, ios_$open is not re-
entrant, but a single task may safely use it.
If you create a library and you intend it to run with tasking applica-
tions, you should make the manager re-entrant by using Mutual Exclusion
(mutex) locks. Mutex locks ensure that only one task at a time has
access to the manager's data structures. In order to use a data struc-
ture, the task must first request and receive the lock associated with
it. For example, to make the add_to_table procedure re-entrant, add
mutex locks to it as follows:
int num_of_entries = 0;
int table[100];
mutex_$lock_rec_t table_lock;
void
add_to_table(int x)
{
mutex_$lock(table_lock, mutex_$wait_forever);
table[num_of_entries] = x;
num_of_entries = num_of_entries + 1;
mutex_$unlock(table_lock);
}
Note that the application must call mutex_$init before it makes the first
call to the add_to_table procedure.
Not all Domain/OS libraries are re-entrant. The following table categor-
izes Domain/OS calls into those that are probably re-entrant (that is,
they are most likely re-entrant but have not been thoroughly tested for
this feature), partially re-entrant (they return handles as described
above), not re-entrant, and those that are definitely re-entrant.
___________________________________________________
| Re-entrant Properties of Domain/OS Calls |
|__________________________________________________|
| Probably | Partially | Not | |
|Re-entrant | Re-entrant | Re-entrant | Re-entrant |
|___________|____________|____________|____________|
|ec2 | cal | error | ctm |
|ms | fpp | gmf | ev |
|mutex | pbu | ios | gmr |
|name | proc2 | ipc | gmr3d |
|pfm | sio | pad | gpr |
|proc1 | smd | stream | mbx |
|rws | time | vfmt | pbufs |
|sfcb | tpad | | pgm |
|task | vec | | prf |
| | | | trait |
|___________|____________|____________|____________|
Domain/OS calls without side effects (time_$clock for example) are gen-
erally re-entrant. Calls that open, create, or close objects (ios_$open
and ipc_$delete for example) are not re-entrant.
The following additional restrictions apply to tasking:
⊕ UNIX system and library calls are not supported. They may work, but
they are not guaranteed to.
⊕ Asynchronous fault handlers must not make any task_$ calls.
⊕ Ec2_$wait_svc behaves exactly like ec2_$wait when tasking is enabled.
The reason is that the Task Manager does not know which task to notify
when an asynchronous fault handler has returned to the point at which
the fault occurred.
Constants
task_$max_priority
The highest task priority.
task_$name_max_len
The maximum number of bytes in a task name.
task_$dt_handle
The task handle for the Distinguished Task (DT).
Data Types
task_$handle_t
A task handle.
task_$info_pt
A pointer to type task_$info_t.
task_$info_t
A record type for passing task information. The diagram below
illustrates the task_$info_t data type.
15 0
______________________________________________________________
| routine_to_run_in_task |
|_____________________________________________________________|
| routine_to_run_in_task |
|_____________________________________________________________|
| arg_ptr |
|_____________________________________________________________|
| arg_ptr |
|_____________________________________________________________|
| arg_len |
|_____________________________________________________________|
| arg_len |
|_____________________________________________________________|
| stack_size |
|_____________________________________________________________|
| stack_size |
|_____________________________________________________________|
| initial_priority |
|_____________________________________________________________|
| initial_priority |
|_____________________________________________________________|
| current_priority |
|_____________________________________________________________|
| current_priority |
|_____________________________________________________________|
| lifetime |
|_____________________________________________________________|
| lifetime |
|_____________________________________________________________|
| task_name_ptr |
|_____________________________________________________________|
| task_name_ptr |
|_____________________________________________________________|
| task_name_len |
|_____________________________________________________________|
| task_name_len |
|_____________________________________________________________|
| state |
|_____________________________________________________________|
| level_created |
______________________________________________________________
15 0
routine_to_run_in_task
A pointer to the task routine.
arg_ptr
Pointer to the task's argument vector.
arg_len
The number of bytes in the task's argument vector.
stack_size
The number of bytes in the task's stack.
initial_priority
The initial priority of the task.
current_priority
The priority at which the task is currently running.
lifetime
The lifetime of the task. It is a value of type
task_$lifetime_t.
task_name_ptr
A pointer to the name of the task.
task_name_len
The number of bytes in the task name.
state
The run state of the task. It is a value of type
task_$run_state_t.
level_created
The program level at which the task was created.
task_$lifetime_t
An enumerated type for specifying the lifetime of a task. (In Pas-
cal, the values of type task_$lifetime_t are constants.) It takes
one of the following values:
task_$forever
Create a task that lives independent of the process that
created it.
task_$until_level_exit
The task lives until the level at which it was created is
released.
task_$until_exec_or_level_exit
The task lives until the level at which it was created is
released or overlaid by a UNIX exec(2) call.
task_$name_pt
A pointer to a task name.
task_$option_set_t
A small set type for specifying options to task_$create. There is
currently only one value:
task_$intend_to_wait
Create a task completion eventcount that advances when the new
task terminates. When created with the task_$intend_to_wait
option, a task is suspended when it completes pending a
task_$release call.
When created without the task_$intend_to_wait option, a task is not
suspended when it completes; instead it terminates immediately on
completion, and the completion status and output value of the task
will not be accessible.
task_$routine_pt
A pointer to a task routine. A task routine must not return a value
and must take two arguments: a pointer to an argument vector, fol-
lowed by the number of bytes in the argument vector. The argument
vector may contain anything.
In C, a valid task routine declaration is
void task_routine(
void *arg_pointer,
int arg_length)
In Pascal, a valid task routine declaration is
procedure task_routine(
in arg_ptr: univ_ptr;
in arg_len: integer32);
options(val_param);
task_$run_state_t
An enumerated type for describing the run state of a task. It takes
one of the following values:
task_$ready
The task is ready to run.
task_$waiting
The task is waiting.
Errors
task_$bad_$ec_wait
Internal error: bad status from ec2_$wait.
task_$bad_handle
Invalid task handle.
task_$cant_blast_dt
Attempted to blast the distinguished task.
task_$cant_blast_yourself
Task attempted to blast itself.
task_$exit_fault
Task exited.
task_$invalid_lifetime
Invalid task lifetime.
task_$invalid_name
Task name is too long.
task_$invalid_priority
Invalid task priority.
task_$lib_init_failed
Internal error: library initialization failed.
task_$no_backgrnd_from_foregrnd
A foreground task attempted to create a background task.
task_$no_completion_ec
Task does not have a completion eventcount.
task_$no_room
Not enough virtual memory left to create task.
task_$not_completed
Task not yet completed.
task_$not_found
Task does not exist.
task_$not_init
Task Manager has not been initialized.
task_$stack_corrupted
A task stack was corrupted.
task_$stack_overflow
A task stack overflowed.
task_$too_many_tasks
Attempted to exceed the maximum allowable number of tasks in a pro-
cess.
SEE ALSO
ec2_$intro, fault_$intro, mutex_$intro, pfm_$intro.