






INTRODUCTION TO MULTITASKING 
AND ASSOCIATED CONCEPTS




Multitasking 

	In the real world, things usually happen in a concurrent manner , and a 
computer that has to cope with a real world environment must behave as if it 
could process several inputs and outputs at the same time.  Since most 
computers can only do one thing at a given time, the software has to simulate 
this kind of concurrent processing by assigning computer (CPU) time 
sequentially to all the external needs.  

	That kind of simulation can be made by application code itself, but this 
produces "spaghetti code", very difficult to understand and maintain. A better 
solution is taking out the simulation to a separate module that hides the 
implementation details. In this case, we  are using multitasking services. 

	Programmers structure their system as a group of   tasks , also called 
processes and threads .The words code and program refer to a static 
sequence of instructions, whereas the words task, process or thread refer to 
the dynamic execution state of that sequence. These tasks can be seen as 
independent programs running concurrently, and each task has its own 
independent control flow.  The module described above, providing multitasking 
and related services is referred to as a multitasking kernel.

	Each time the kernel has control, it has to decide which of the concurrent 
tasks have to get control next time. The selection of a task to get the CPU is 
called scheduling , and the action of giving  control to that task is referred to as 
dispatching. The complete sequence of actions is known as task switch.

	Usually, importance of  each task is different , so the kernel has to 
provide a way to specify which tasks are the most important in order to give 
them CPU time as soon as possible. This is achieved by means of a priority 
mechanism.

	A task may need to pass some data to another task, this is called 
interprocess communication (IPC) . Sometimes, several tasks may need to 
orderly access a shared resource, requiring interprocess synchronization . 
These are also important services of the kernel.

	In order to get its job done, the kernel has to spend some time in its own 
operations. The (kernel execution time)/(total execution time) ratio is called 
kernel overhead, usually expressed as a percentage. Since the time spent by 
the kernel , though necessary, decreases the time available for application 
code, overhead must be kept as short as possible.



Interrupts

	Interrupts are asynchronous hardware signals, that is, signals that enter 
the system at any time, no matter which is the current execution state. 
Interrupts are used to signal hardware events, such as the reception of a byte 
in a serial port. Interrupts are ordered by priority.

	When an interrupt enters the system, the execution jumps to the 
associated Interrupt Service Routine (ISR). For not to disturb the execution of 
the interrupted program, the state of the machine has to be saved before 
servicing the interrupt , and restored before returning from the ISR. These 
actions are referred to as context switching. Interrupts can be disabled , and 
then incoming hardware signals are not serviced until interrupts are re-enabled.

	The time elapsed between the moment an interrupt signal enters the 
system and its corresponding ISR begins its execution is known as interrupt 
latency. Interrupt latency should be as small as possible.

Microkernel-based architecture

	Classic monolithic kernels include every conceivable kernel service 
within a single module, wasting resources if any service is not used. Another 
problem with monolithic kernels is that they disable interrupts for a long time 
while executing kernel calls, increasing interrupt latency.

	Microkernel approach is used to overcome these problems.Basic 
functions, such as scheduling and message passing are performed by a tiny 
module, referred to as microkernel, and other kernel services, such as 
networking, peripheral management, etc., are implemented by kernel  daemons  
(system tasks)  which run on top of that small microkernel, and that can be 
added or not depending on the actual needs. This feature is referred to as 
scalability.

	So, when using APDMX modular approach,  you build  a kernel for your 
application according with your own needs, adding just the modules you really 
need. 



	Both the APDMX  Microkernel and  kernel daemons are intended to be 
embedded into your own applications, that is, you link them with your code, and 
while the underlying operating system sees your application as a single 
program, it internally manages several concurrent tasks. These kind of kernels 
are sometimes referred to as multitasking executives. 

Scheduling  

	Using APDMX  Microkernel, tasks run in a common shared memory 
space.  In technical literature this is called lightweight multitasking  or  
multithreading.

	Depending on when the scheduling decisions are done, kernels can be 
classified in preemptive and non preemptive or cooperative.  In preemptive 
kernels , scheduling decisions are made as result of interrupts entering the 
system,  whereas in  cooperative ones, they are made only in explicit system 
calls.  

	APDMX Microkernel uses cooperative multitasking. This provides  a 
lower kernel overhead and a  shorter interrupt latency, because interrupts are 
enabled most of the time, even when executing kernel code.   Third-party 
libraries can be used  whether they are reentrant or not, and the same is true 
for DOS services. 

	The scheduler policy is very simple: it chooses for execution the ready 
task with the highest priority. There are several queues for ready tasks, one for 
each priority, and  within a given priority, tasks are managed  on a First In First 
Out basis.


Synchronization

	There are situations in which access to shared resources have to be 
carefully controlled in order to avoid race conditions. Let's think, as an 
example, in two tasks of the same priority trying to output each one a page of 
text  to a shared printer.  Each  task could print a single character and then 
relinquish control in order to let other tasks get CPU time.  It is not too difficult 
to see that the two outputs will be mixed, forming  a babble with no sense at all 
. These possibly conflicting sections of code are known as critical sections.

	An option would be for  a task retaining control for the entire printing 
time, but in that case, not just the other  task but every task in the system, is 
prevented to run until the page is finished. This situation is described as 
starvation.

	The best solution is to use a semaphore.  A semaphore is a 
synchronization mechanism that is accessed through two complementary 
operations: wait and signal  (also called p and v).  Any semaphore has an  
integer variable associated to it.  
	
	When a task performs a wait operation on a semaphore, the current 
value of the variable is checked. If it is greater than zero, then the variable is 
decremented by one and execution continues. If not, the calling task is 
suspended until a signal operation is performed on this semaphore. Waiting 
tasks are queued on a FIFO basis.

	When a signal operation is performed, the microkernel checks whether 
there are tasks waiting in the queue. If so, the first task in the queue becomes 
ready, otherwise the variable is incremented.  The variable has an upper 
bound.

	These kind of semaphores are sometimes called Dijkstra semaphores, 
for the name of its creator. A special case is when the maximum value is 1 ; a 
binary semaphore like this, with only two possible values, 0 and 1,  is used to 
implement mutual exclusion and thus it is known as  mutex .



	Using a mutex in our previous example enables  serializing printer 
accesses  without  starving other tasks. The resulting pseudocode  is as 
follows:

Task  A
-------
wait(mutex);
while(page is not finished)
     {
     print a character;
     yield control;
     }
signal(mutex);

Task B
------

wait(mutex);
while(page is not finished)
     {
     print a character;
     yield control;
     }
signal(mutex);


The first task in accessing the semaphore sets its value to 0;  when the other 
task performs its own wait , it encounters this value and is suspended until the 
first task reaches its signal call. 

	In the previous case, the critical sections are time-consuming. However, 
for very short critical sections it may be preferable to simply avoid yielding 
control inside the critical section.
 
	In some cases,  race conditions may appear when a resource (e.g. a 
buffer) is shared by a task and an ISR. In these situations, to avoid yielding 
control is not enough, due to the asynchronous nature of interrupt signals,and 
critical sections within  the task code must be protected by disabling interrupts. 

Intertask communication

	Since APDMX Microkernel has lightweight tasks (also called  threads), 
you can easily share memory between different tasks as long as you guard 
against race conditions. However there are many situations in which you need 
to pass discrete data from a task to another . APDMX Microkernel provides 
convenient message-passing mechanisms to perform intertask communication.



A task can send a message to any other task in two different ways:
*	Tightly coupled : Sending task waits until it receives a reply from 
recipient.
*	Loosely coupled: Sending task continues execution without expecting 
any reply.

	Each task has an associated message queue for incoming messages 
and a mailbox for incoming replies.  The primitives only carry a pointer to the 
message body and the message size. It is up to communicating tasks to agree 
on the message meanings and allocating and freeing memory for the message 
contents.  

	When a task makes a system call to receive a message it can choose 
one of the following options:
*	Not to wait for anything and returning whether a message is in the 
queue or not.
*	To wait  a specified amount of time for a message arrival.
*	Waiting indefinitely for the first message arrival.

Suggested Reading

	We cannot describe here all the topics related to multitasking. If you 
want to learn more, there are several books with an in-depth coverage of that 
topics. Two of them are:

Peterson, J.L, and Silberschatz, A.
Operating System Concepts
(Addison-Wesley)

Tanenbaum, A.S.
Operating Systems: Design and Implementation
(Prentice Hall)


		









ARCHITECTURE AND SERVICES 
OVERVIEW




System Architecture

As prevously stated, APDMX Microkernel is an executive and it is embedded 
within your application. However, we can make a conceptual distinction 
between them.  As nearly all real-mode/protected-mode interactions are 
managed by the DOS Extender ( 32RTM or DOS 4G/W , depending on the 
compiler used) , a simplified layered view of the system would be:

  
Fig. 1    General Architecture

	Although there are some overlaps  not depicted here , it is a good 
approximation to the System Architecture. 

The microkernel is splitted in two parts. The first one is 32-bit code running in 
protected mode. This part is responsible for all the main services of the kernel. 
In addition to the Protected Mode part there is a TSR , the Real Mode Interrupt 
Manager, that hooks some interrupts and acts as a "translator", making it 
possible for the P.M. part to communicate with 16-bit  drivers and TSRs in an 
efficient way.



Your code sees the microkernel as a normal library, with services implemented 
as conventional function calls, though behind the scenes the microkernel is 
switching contexts, juggling with registers and so on.

	One of the most important things we can observe in fig. 1 is that no 
fundamental distinction exists between user tasks and system tasks.  This lack 
of distinction means you can add services whenever they are needed, even if 
the system is already running. Furthermore, you can even write your own 
daemons for non-standard devices, and  the microkernel makes no difference 
between your daemons and standard ones. 



Task states

	A  task can be in one of these states:

READY The task is ready to execute. The scheduler can choose the 
task for CPU allocation. When the task gets control, it goes to RUNNING 
state. 

	RUNNING Task code is executing.

PENDING The task is waiting on a semaphore. When another task 
performs a signal operation on the semaphore, the first waiting task 
becomes READY .

RECEIVING The task is waiting for a message. When a message arrives 
at the reception queue of this task, or an optional timeout expires, the 
task becomes READY.

EXPECTING The task has used a blocking send service and is waiting 
for a reply. When a message arrives at the mailbox, the task becomes 
READY.

FROZEN Task execution is suspended until it is unfrozen by another 
task . The scheduler don't care about frozen tasks. This state, and its 
associated services, only exists in the debugging version in order  to 
make debugging easier.



A transition diagram is shown in fig. 2.  Besides each transition is the 
corresponding  microkernel service or action.
 
Fig.2  Transition diagram

Throughout all the present Manual we use the terms suspended and blocked to 
collectively refer to those states in which the task cannot be scheduled until 
something happens.



APDMX Microkernel  API services list by category

	Some services  make calling task relinquish control if there is a equal or 
higher priority task ready to execute, even if the result of the call itself does not 
suspend the task execution.  If the task is not suspended, then it becomes  
READY.

General

mK_init()
Initializes microkernel's internal data 
structures.


mK_start()
Switches to multitasking mode and 
starts executing the available task 
with the highest priority.


mK_end()
Ends execution, returns control to 
the Operating System command 
interpreter.


mK_yield()
Relinquishes control to the highest 
priority ready task.


Task management

mK_create_task()
Task creation. A task is coded in a C 
function, typically containing a 
while(1) or for(;;) loop.


mK_self_pid()     *

Returns the calling task identifier.

mK_get_task_info()   *
Retrieves actual information about 
the specified task (a subset of the 
Task Control Block).


mK_set_task_info()     *
Sets values for some fields of the 
Task Control Block .


mK_kill_task()       *
Destroys calling task, releasing all 
the resources allocated to that task.




Synchronization

mK_enter_critical()
Execution enters a short critical 
section. Disables interrupts.


mK_exit_critical()
Execution exits from a short critical 
section. Enables interrupts. 


mK_create_semaphore()     *
Creation of a Dijkstra semaphore. It 
allocates memory for the data 
associated with the semaphore, sets 
the initial value and the upper bound 
for the associated counter. 


mK_wait_semaphore()      *
Waits on a semaphore. If 
semaphore state is >0, it 
decrements counter by one and 
continues execution. Otherwise, the 
calling task is suspended.


mK_signal_semaphore()       *
Sets to READY the state of the first 
task waiting on a Dijkstra 
semaphore. If there is no task 
waiting, it increments by one the 
semaphore counter and continues 
execution.

mK_delete_semaphore()
Deletes the semaphore if there are 
no tasks waiting on it.






Timing

mK_time_of_day()
Returns the date and time of day.


mK_get_systicks()

Returns the time (in interrupt tick 
units) elapsed since the system 
started.


Memory management

mK_malloc()        *
Allocates memory blocks from the 
microkernel heap.


mK_mfree()         *
Releases a memory block previously 
allocated by a call to mK_malloc()






Intertask communication

mK_non_blocking_send() *
Transmits a message to another 
task. The calling task remains ready 
for execution.


mK_blocking_send()   *
Transmits a message to another 
task. The calling task is suspended 
until the recipient sends it the  reply. 


mK_receive()   *
Calling task is suspended until it 
receives a message or a specified 
time has elapsed. Alternatively , 
calling task may specify either no 
waiting at all or an indefinite waiting. 

mK_respond() *

The recipient of a message sends a 
reply to the transmitter, allowing it to 
resume its execution. 


Debugging calls

	These calls are available, but have no effect in the production version of 
the microkernel (See the DEBUG VERSION  chapter for additional debug 
features).

mK_freeze()     


Suspends calling task execution. 
The scheduler  does not consider 
that task until it is "unfrozen"


mK_unfreeze()      

Makes a previously frozen task 
schedulable , setting its state to 
READY . 


mK_get_task_snapshot()     
Gets information about the current 
state of a task.


mK_get_semaphore_snapshot() 
Gets information about the current 
state of a semaphore.


mK_profiler_control()    
Controls the task profiler's data 
gathering.


mK_heap_view()  
Provides information about the 
blocks allocated by the memory 
manager.















APPLICATION PROGRAM 
INTERFACE REFERENCE




mK_init()

Description:

This call is used to initialize internal data structures of the microkernel. It 
must be done before any other microkernel service is requested.

Function prototype:
	
void mK_init(void);

Arguments:

	None.

Returned value:

	None.

Notes/Warnings:




mK_start()

Description:

This call switches to multitasking mode and starts executing the 
available task with the highest priority.

Function prototype:
	
	void mK_start(void);

Arguments:

	None.

Returned value:

	None.

Notes/Warnings:




mK_end()

Description:

This call ends multitasking mode. It returns control to the operating 
system command interpreter.

Function prototype:
	
	void mK_end(void);

Arguments:

	None.

Returned value:

	None.

Notes/Warnings:




mK_yield()

Description:

This call explicitly relinquishes control to the ready task with the highest 
priority.

Function prototype:
	
	void mK_yield(void);

Arguments:

	None.

Returned value:

	None.

Notes/Warnings:

If a task makes regular calls to other microkernel services, explicit calls 
to mK_yield() may not be necessary.




mK_create_task()

Description:

It creates a task on the specified code (usually a C function) and sets its 
state to READY.

Function prototype:

	int mK_create_task(TASK_INFO *settings);

Arguments:
settings : address of the structure of TASK_INFO type containing the 
desired values for the task.  TASK_INFO  is defined as follows:
typedef struct
{
void *code;   //address of task code starting point
unsigned stack_size; // size of the stack area
unsigned char default_priority; 
unsigned char current_priority;
char *name; // task symbolic name

}TASK_INFO;

Returned value:

On success, it returns an identifier for the task just created (positive 
value).
mKCREATPROC_NOMEM : Not enough memory available to create the 
task.

Notes/Warnings:



mK_self_pid()

Description:

It returns the identifier of the calling task.

Function prototype:

	int mK_self_pid(void);

Arguments:

	None.

Returned value:

	Caller's PID .

Notes/Warnings:




mK_get_task_info()

Description:

This function retrieves information associated with the specified task. 
Actually, this information is a subset of the Task Control Block inside the 
microkernel.

Function prototype:
int mK_get_task_info(int pid, TASK_INFO *info);

Arguments:

pid : identifier of the task of which information is requested
info : address where the information is to be written [see TASK_INFO 
definition in mK_create_task()]

Returned value:

mKGETINFO_OK :  successful termination
mKGETINFO_BADPID : the pid does not belong to a living task

Notes/Warnings:




mK_set_task_info()


Description:

This function sets some of the values within the Task Control Block .

Function prototype:

	int mK_set_task_info(int pid, TASK_INFO *info);
	
Arguments:

pid : identifier of the task of which information is requested
info : address where the information is to be read [see TASK_INFO 
definition in mK_create_task()]
	
Returned value:

mKSETINFO_OK :  successful termination
mKSETINFO_BADPID : the pid does not belong to a living task
	
Notes/Warnings:

Neither the code start address nor the stack size can be modified by this 
call.



mK_kill_task()

Description:

	This service is used to kill the calling task, freeing all its allocated 
resources.

Function prototype:

	int mK_kill_task(void);

Arguments:

	None.

Returned value:

mKKILL_OK  :  successful execution

Notes/Warnings:




mK_enter_critical()

Description:

This call disables interrupts when entering a short critical section.

Function prototype:

	void mK_enter_critical(void);

Arguments:

	None.

Returned value:

	None.

Notes/Warnings:

This call enables protecting critical sections with a smaller overhead 
than that produced by using semaphores. It is important to note that in 
time-consuming critical sections it may be preferable to use semaphores 
to avoid an excessive interrupt latency.



mK_exit_critical()

Description:

Re-enables interrupts previously disabled by a call to 
mK_enter_critical().

Function prototype:
	
	void mK_exit_critical(void);

Arguments:

	None.

Returned value:

	None.

Notes/Warnings:
	See mK_enter_critical().



mK_create_semaphore()

Description:

This call allocates memory for a Dijkstra semaphore, sets the initial 
value for its counter and the upper limit for the counter value. A mutual 
exclusion semaphore (mutex) can be obtained by setting this upper limit 
to 1.

Function prototype:

	int mK_create_semaphore(short value, short maximum);

Arguments:

	value : value to be assigned to the semaphore's counter

	maximum: upper limit for the semaphore value

Returned value:

On success, the call returns an identifier for the semaphore just created. 
otherwise, it returns one of the following error codes:

mKCREATSM_NOMEM : not enough memory to create semaphore

mKCREATSM_BADVALUE : value not allowed

mKCREATSM_BADCEILING : maximum value is less than 1 .

Notes/Warnings:




mK_wait_semaphore()

Description:

This call performs a WAIT (P) operation on a semaphore. If the value of 
the semaphore is >0 , it is decremented by one and execution continues. 
Otherwise, the calling task is suspended until another one signals that 
semaphore. Suspended tasks are queued on a FIFO basis.

Function prototype:

	int mK_wait_semaphore(int semaphore);

Arguments:

semaphore : semaphore identifier

Returned value:

mKWAITSM_SIGNAL : another task has signaled the semaphore

mKWAITSM_BADNUM : wrong semaphore identifier

Notes/Warnings:




mK_signal_semaphore()

Description:

This call makes a signaling operation on a semaphore. If there are tasks 
waiting on the semaphore, it sets to READY the state of the first one in 
the semaphore queue. Otherwise, it increments by one the semaphore's 
value and execution continues.

Function prototype:

	int mK_signal_semaphore(int semaphore);

Arguments:

	semaphore : semaphore number

Returned value:

mKSIGNALSM_OK : successful termination

mKSIGNALSM_BADNUM : wrong semaphore number

mKSIGNALSM_OVERFLOW : semaphore counter has already its 
maximum value.

Notes/Warnings:

mKSIGNALSM_OVERFLOW may not indicate an error, if we are using 
the semaphore as a mutual exclusion one



mK_delete_semaphore()

Description:
Deletes a semaphore, releasing its associated resources.

Function prototype:
int mK_delete_semaphore(int semaphore);

Arguments:
semaphore : semaphore identifier

Returned value:
mKDELSM_OK : successful termination

mKDELSM_BADNUM : wrong semaphore number

mKDELSM_BUSY : semaphore has tasks waiting on it.

Notes/Warnings:




mK_time_of_day()

Description:

It gives the date and time of the day obtained from the CMOS clock.

Function prototype:

void mK_time_of_day(mKTIME *buffer);

Arguments:
buffer : address where the results are to be written. The type mKTIME is 
defined as follows:
typedef struct
{
int seconds;
int minutes;
int hours;
int monthday;
int month;
int year;
} mKTIME;

Returned value:
None.

Notes/Warnings:



mK_get_systicks()

Description:

This call gives out the total clock ticks count since system initialization, 
in a 32 bit value.

Function prototype:

	DWORD mK_getsysticks(void);

Arguments:
	None.

Returned value:

	Clock ticks count.

Notes/Warnings:
The hardware is programmed to produce a clock interrupt every 5 
milliseconds, so this is the value for a system tick. However, BIOS 
routines receive "fake" clock ticks every 55 milliseconds, so that BIOS 
internal counters are properly updated.



mK_malloc()

Description:

Requests  a block of memory from the microkernel memory manager.

Function prototype:

void *mK_malloc(DWORD size);

Arguments:

size : size of the memory block being requested.

Returned value:

On success, it returns a pointer to the block just allocated.
On failure, it returns NULL .

Notes/Warnings:




mK_mfree()

Description:

Releases a memory block previously allocated by a call to mK_malloc().

Function prototype:

int mK_mfree(void *block);

Arguments:

block : pointer to the memory block to be released.

Returned value:

mKMFREE_OK : the block has been released

mKMFREE_BADPTR : pointer is not a valid one.

Notes/Warnings:




mK_non_blocking_send()

Description:
Sends a message to another task. Calling task remains ready for 
execution, no matter whether the message is read or not.

Function prototype:

int mK_non_blocking_send(int machine,
				int receiver,
				void *contents,
				int length);
Arguments:

machine : Number of the machine where the recipient resides. It must be 
mKLOCAL_MACHINE if network extensions are not present.
receiver : identifier of the recipient.
contents : address of the message body
length :  size of the message body

Returned value:
mKNBSEND_OK : message was sent (queued)
mKNBSEND_BADPID : the pid does not belong to a living task
mKNBSEND_OVERFLOW : there is no room for the message in the 
input queue of the recipient.



Notes/Warnings:
*	There are no semantics associated with neither the message type 
nor the contents, at the microkernel level.  It is up to the upper layers 
assigning any  meaning to messages. 
*	The reason for including here references to remote machines is to 
unify local and remote comms. Likely, most of the messages are sent 
to another task in the same machine, so it is more efficient to include 
this service within the microkernel.
*	If remote messaging is requested, the microkernel assumes that the 
first task created (the task whose pid == 0) is responsible of network 
communications. Thus, every non-local message is forwarded to task 
0.



mK_blocking_send()

Description:

Sends a message to another task., blocking the calling task until a reply 
is received.

Function prototype:
int mK_blocking_send(int machine,
int receiver,
void *message_contents,
int message_length,
void *reply_contents,
int *reply_length);

Arguments:

machine : Number of the machine where the recipient resides. Unless 
network extensions are installed, it must be mKLOCAL_MACHINE .
receiver : identifier of the recipient.
message_contents : address of the  message body.
message_length : size of the message . 
reply_contents : address of the reply body.
reply_length : size of the reply .

Returned value:

mKBKSEND_OK : A reply has been received.
mKBKSEND_BADPID : the pid does not belong to a living task
mKBKSEND_OVERFLOW : there is no room for the message in the 
input queue of the recipient.
Notes/Warnings:
see  mK_non_blocking_send().



mK_receive()

Description:
The calling task is blocked until a message is received from another task 
or the specified time has elapsed.
Function prototype:
int mK_receive(int *machine
int *pid, 
void *contents, 
int *length,
int *reply_requested, 
DWORD delay);

Arguments:
machine : Number of the machine where the recipient resides. Unless 
network extensions are installed, it must be mKLOCAL_MACHINE .
pid : identifier of the task sending the message.
contents : address where the contents of the message have to be 
written.
length : size of the message body.
reply_requested : If (*reply_requested != 0) then the task that sent the 
message is blocked waiting for a reply. Otherwise, no reply is expected.
delay : timeout for the reception , in clock ticks. There are two special 
values:
mKWAIT_FOREVER : The task waits indefinitely
mKNOWAIT : The call returns inmediately
Returned value:
mKRECEIVE_ARRIVAL : a message is available
mKRECEIVE_ELAPSED : specified time has elapsed
mKRECEIVE_NOTHING : there's no message in the queue. This return 
value can only appear when using mKNOWAIT .
Notes/Warnings:
This call may be used to obtain a timed sleep with a possible wakeup by 
another task.



mK_respond()

Description:
It sends a reply to a task blocked by a call to mK_blocking_send, getting 
it ready to execute.
Function prototype:
	int mK_respond(int machine,
int receiver,  
void *contents,
int length);

Arguments:
pid : task identifier of the expecting task
contents : address of the message body

Returned value:

mKRESPOND_OK : message was sent and the recipient was unblocked.
mKRESPOND_BADPID : wrong task identifier, recipient does not exist.
mKRESPOND_BADSTATE : recipient was not waiting for a reply

Notes/Warnings:

When the recipient is not waiting for a reply, the message is discarded.




mK_freeze()

Description:

It freezes specified task, suspending its execution until it is unfrozen by 
another one.

Function prototype:

	int mK_freeze(int pid);

Arguments:

	pid : identifier of the task that is to be frozen

Returned value:

	mKFREEZE_OK : successful execution

	mKFREEZE_BADPID : wrong task identifier

Notes/Warnings:
This call, along with mK_unfreeze(), are intended only for debugging 
purposes. For synchronization, it is far better to use messages or 
semaphores.  These calls are inactive in the production version of the 
kernel.



mK_unfreeze()

Description:

	Unfreezes a previously frozen task .

Function prototype:

	int mK_unfreeze(int pid);

Arguments:

	pid : identifier of the task to be unfrozen

Returned value:

	mKUNFREEZE_OK : succesful termination
	
	mKUNFREEZE_BADPID : wrong task identifier

	mKUNFREEZE_BADSTATE : specified task was not in FROZEN state

Notes/Warnings:
See mK_freeze().



mK_get_task_snapshot()

Description:
Gets information about the current state of a task.
Function prototype:
int mK_get_task_snapshot(int task, mKTASK_INFO *buffer);
Arguments:
task : pid of the task of which information is requested
buffer : address where the task information is to be put. The structure for 
the task  information is as follows:
typedef struct
{
// general information
int how_many; // total number of tasks in the system  
// task profiler info
DWORD times_entered; 
DWORD time_running;
// other status info
int process_state // current state of the task
unsigned stack_size;
unsigned char default_priority;
unsigned char current_priority;
char *symbolic_name;
}mKTASK_INFO;

Returned value:
mKGTASKINFO_OK : Successful completion
mKGTASKINFO_BADPID : Wrong task identifier

Notes/Warnings: 
The following constants are defined for task states: READY, RUNNING, 
PENDING, RECEIVING, EXPECTING, FROZEN .



mK_get_semaphore_snapshot()

Description: 
This call returns information about a semaphore.
Function prototype:
int mK_get_semaphore_snapshot(int semaphore, 
mKSEM_INFO *buffer);
Arguments:
semaphore : semaphore identifier
buffer : address where the information is to be put. The structure for the 
semaphore information is as follows:
typedef struct
{
short value;
short maximum;
int *waiting_tasks; // a large enough space must be 
previously allocated
} mKSEM_INFO;

Returned value:
mKGSEMINFO_OK : Successful completion
mKGSEMINFO_BADPID : Wrong task identifier

Notes/Warnings:
The limit for the number of waiting tasks on a single semaphore is the 
same as the total number of tasks in the whole system.



mK_profiler_control()

Description:
This function controls the task profiler data gathering.
Function prototype:
int mK_profiler_control(int command, int task);
Arguments:
command : action to perform. Must be one of the following constants:
mKPROF_ON  start/restart data collection
mKPROF_OFF  stop data collection
mKPROF_RESET clears profiler data
task : Any valid task identifier .

Returned value:

mKPROFCNT_OK : successful termination
mKPROFCNT_BADCOMMAND : wrong command
mKPROFCNT_BADPID : wrong task identifier

Notes/Warnings:
As default, profiling is active for every task from its first CPU burst.



mK_heap_view()

Description:
Gets information on the memory allocation.
Function prototype:
mKBLOCK_INFO *  mK_heap_view(void);
Arguments:
None.
Returned value:
Address for the memory map. The entries of the memory map are of the 
mKBLOCK_INFO type , defined as follows:
typedef struct
{
void *block_address;
int  block_size;
int owner_task;
}mKBLOCK_INFO;

The end of the memory array is marked by means of a NULL in the field 
block_address of the corresponding structure.

Notes/Warnings:








DEBUG VERSION




Introduction

	If you have purchased the registered version of APDMX Microkernel, 
you have some additional debug features.  You get :

*	State dumps for tasks, semaphores and memory blocks.
*	Parameter checking in kernel calls.
*	A microkernel exception  is raised in case of error, giving you a clue on 
what's wrong.
*	Block stuffing and other actions against memory-related bugs.
*	Specific kernel calls for your own debug code.

As previously stated, the following functions are only active in the debug 
version:

*	mK_freeze()
*	mK_unfreeze()
*	mK_get_task_snapshot()
*	mK_get_semaphore_snapshot()
*	mK_profiler_control()
*	mK_heap_view()




Differences in memory management

	To gain speed, the production version of the memory manager performs 
just the essential actions to allocate and release memory blocks. Since memory 
requests are often one of the main error-prone parts of a program, additional 
measures are taken inside the debug versions in order to make these bugs 
easier to catch:

*	Blocks are initialized, so that if execution jumps into an unused part of a 
block, a breakpoint interrupt is triggered.
*	A checksum is computed for each internal block header, both in 
mK_malloc() and mK_mfree(). This eases catching dangling or 
misplaced pointers.
*	When a block is released by calling mK_mfree(), any further attempt of 
referencing the associated pointer triggers a processor exception.
*	Also, when a block is released, its contents are re-initialized.



Dump Options

	Although you can use debug calls to make your own state displays, 
some special features are provided to see what is happening inside your 
system without any special code. 

	If you press the  PAUSE  key while your system is running, the following 
options are displayed:

F1.- Task information.
First option (F1) produces a listing of the tasks present in the system at this 
moment, giving for each task:
*	pid (task identifier)
*	name (symbolic name)
*	whether profiling is active (represented by an asterisk)  or not, for 
that task
*	total CPU time used by that task
*	total number of CPU bursts 
*	default priority and current priority (DP/CP)
*	current task state ( READY, RUNNING, etc.)

You can scroll this and the following listings by pressing PGUP and PGDN keys.
In the bottom of the screen, the total number of living tasks is shown.

F2.- Semaphore information
For each semaphore the following information is displayed:
*	semaphore identifier (num.)
*	current value of the associated variable
*	maximum value
*	list of tasks waiting within the semaphore queue. Up to 20 pids 
are shown	



F3.- Memory blocks.
	For each block requested through mK_malloc(), the following information 
is displayed:
*	address of the block
*	size in bytes
*	owner of that block (pid); if a block is owned by the system itself,  this 
field is  -1
 
	In the last line of the screen, the global heap state is shown. Two 
different algorithms are used inside the memory manager. The tail is the size of 
the memory  available for the primary algorithm and never allocated.  The "hits" 
field  shows the percentage of block requests satisfied using the primary 
algorithm.

F12.- Terminate execution.

Pressing this key ends execution, returning to the DOS prompt.

Microkernel exceptions

	When an internal assertion fails, a microkernel exception is raised. If you 
are executing your application on a debugger, such as Turbo-debugger, a 
breakpoint interrupt is triggered, so that you can examine the call nesting, 
variable contents and so on.  This breakpoint has no effect if the code is 
running directly from the operating system.  Whether you are using a debugger 
or not, the following information appears on your screen:

*	Error code.
*	Error message, including between square brackets the kernel call 
where the error is detected.
*	Task identifier (pid) . If the error appears before starting multitasking,  
a message is displayed instead of the pid.
*	Task  symbolic name.
*	Address where task code starts.
*	Default and current priorities of the task.
*	Address and size of the stack zone for that task.
*	Total number of CPU bursts for that task.



Exception Messages by service

mK_create_task()

*	Not enough memory for task name
*	No free entry for task 
*	Not enough memory for stack space
*	No room in queue for task pid
*	Incorrect default priority
*	Incorrect current priority
*	Incorrect stack size

mK_get_task_info()

*	Task pid out of range
*	Task pid doesn't correspond to any living task

mK_set_task_info()

*	Task pid out of range
*	Task pid doesn't correspond to any living task 
*	Incorrect current priority

mK_start()

*	Second call not allowed
*	No tasks created


mK_malloc()

*	Allocation failure
*	Incorrect block size

mK_mfree()

*	Corrupted header detected
*	Attempt to free a NULL pointer

mK_create_semaphore()

*	Bad limit value
*	Bad initial value
*	No free entry
*	Not enough memory

mK_wait_semaphore()

*	Semaphore number out of range
*	Number doesn't correspond to any existing semaphore

mK_signal_semaphore()

*	Semaphore number out of range
*	Number doesn't correspond to any existing semaphore

mK_delete_semaphore()

*	Semaphore number out of range
*	Number doesn't correspond to any existing semaphore


mK_non_blocking_send()

*	Recipient pid out of range
*	Recipient doesn't exist
*	No room in queue for message
*	Incorrect machine number

mK_blocking_send()

*	Recipient pid out of range
*	Recipient doesn't exist 
*	No room in queue for message
*	Incorrect machine number

mK_respond()

*	Recipient pid out of range
*	Recipient doesn't exist in
*	Recipient was not expecting a response

mK_freeze()

*	Task pid out of range
*	Task pid doesn't correspond to any living task

mK_unfreeze()

*	Task pid out of range
*	Task pid doesn't correspond to any living task
*	Referenced task was not frozen


mK_get_task_snapshot()

*	Task pid out of range
*	Task pid doesn't correspond to any living task
*	Semaphore number out of range

mK_get_semaphore_snapshot()

*	Number doesn't correspond to any existing semaphore 

mK_profiler_control()

*	Task pid out of range
*	Task pid doesn't correspond to any living task
*	Incorrect command issued 



	











GLOSSARY






Aperiodic task
A task that is activated on demand. [= asynchronous task]

Asynchronous Device I/O task
A task that interfaces to an I/O device and is activated by interrupts from 
that device.

Asynchronous I/O device
An I/O device that generates an interrupt when it has produced some 
input or when it has finished processing an output operation.

Asynchronous task
A task that is activated on demand.

Binary semaphore
A boolean variable that is only accessed by means of two atomic 
operations, wait and signal, usually used to enforce mutual exclusion.

Clock tick 
Time unit, it is the time elapsed between two consecutive clock 
interrupts.

Cohesion
A criterion for identifying the strength or unity within a module.

Component
In analysis, an object or function. In design, a task or information hiding 
module.



Concurrent task
[See Task]

Context Switch
Action of saving the processor state (the contents of its registers) in 
memory and then restoring another set of register contents to either start 
or resume another line of execution. A context switch may be part of a 
task switch. [See task switch].

Control Specification
A specification that describes the behavior of the system in terms of 
decision tables, state transition tables, state transition diagrams and/or 
process activation tables.

Control task
A task that executes a state transition diagram (mapped to a state 
transition table).

Coupling
A criterion for determining the degree of connectivity between modules.

CPU Burst

Time elapsed between the moment when a task gets control and the 
moment when control is given to a different task.

Critical Section
A section of code that may lead to race conditions if not protected by 
means of intertask synchronization.

Daemon
Word borrowed from UNIX terminology. A daemon is a task which 
provides an operating system service.



Deadlock
A case where two or more tasks are suspended indefinitely because 
each task is waiting for a resource acquired by another task.

Device Interface Module (DIM)
An information hiding module that hides the characteristics of an I/O 
device and presents a virtual device interface to its users.

Discrete Data
Data that arrives at specific time points.

Distributed Processing Environment
A system configuration where several geographically dispersed nodes 
are interconnected by means of a local area or wide area network.

Environmental Model
A model that defines the external entities that the system has to interface 
to, and the inputs to and outputs from the system.

Finite State Machine (FSM)
A conceptual machine with a given number of states and transitions, 
which are caused by input signals. A FSM is usually represented by a 
state transition diagram or state transition table.

FIFO
Acronym for First In First Out [see Queue].

FSM
Finite State Machine

Identifier
An unique number used to specify an individual entity (task,  semaphore, 
etc).



IPC
[See Interprocess Communication]

Interprocess Communication
Action of passing data and/or control signals between concurrent tasks.

ISR
Acronym for Interrupt Service Routine.

Loosely-coupled Message Communication
A producer task sends a message to a consumer task and does not wait 
for a response; a message queue could potentially build up between the 
tasks.

Message
An item of data transmitted using explicit addressing.

Microkernel
Component of  some operating systems that performs the lowest level 
operations, such as context switching, dispatching and interrupt 
management. Usually, the higher level operation are performed by 
daemons. The microkernel approach is the opposite to the monolithic 
approach in operating system design.

Modularity
The extent to which software is composed of discrete components so 
that a change to one component has minimal impact on the other 
components.

Multitasking
The technique of concurrently executing a number of related tasks in the 
same computer.



Mutual exclusion
Only allowing one task to have access to shared data at a time, often 
enforced by means of binary semaphores.

Overhead
Relative cost imposed to a system by a service or a component. Usually 
it is measured as the relation between the amount of a resource used by 
that service and the total amount of that resource. 

Passive I/O device
A passive (synchronous) I/O device does not generate an interrupt on 
completion of an input or output operation. The input from a passive 
input device needs to be read on a polled basis.

Periodic Function
A function that is activated at regular intervals to perform some action.

Periodic I/O task
A task that interfaces to a passive I/O device and polls the device on a 
regular basis.
PID
A unique number identifying a task within a machine.
Polling
Action of sequentially testing each possible source of  input until one is 
found with some data ready to send.

Preemption
The kernel take away control from the running task in order to give it to a 
higher priority one. Running task does not voluntarily relinquish control 
but the kernel uses an interrupt to make the task switch.

Priority
An assigned level of importance given to various entities that determines 
the order in which they will be serviced.



Process
Same as task.

Profiling
Measuring the relative amount of CPU time used by different software 
components (tasks, functions, etc.). Usually it is used to determine which 
components have to be optimized.

Queue
A list that allows insertions at one end and deletions at the opposite end.

Real-time
Pertaining to the processing of data by a computer in connection with 
another process outside the computer according to time requirements 
imposed by the outside process. 

Scheduler
Software component that chooses which is the next task to run. Usually 
it is embedded in a kernel.

Semaphore
Synchronization mechanism used to avoid race conditions and data 
corruption when two processes are trying to access a shared resource.

Starvation
Situation where one or more tasks never get control, even if they are 
ready for execution.

State Transition Diagram (STD)
A graphical representation of a Finite State Machine in which the nodes 
represent states and the arcs represent transitions between them.



State Transition Table (STT)
A tabular representation of a Finite State Machine.

Symbolic name
Human-understandable name for an entity (process, queue, etc.), 
represented as a character string. Usually, software uses also a numeric 
identifier for that entity. 

Task [also concurrent task, process]
A task represents the execution of a sequential program or a sequential 
component of a concurrent program.  Each task deals with a sequential 
thread of execution; there is no concurrence within a task.

Task Control Block

Data structure internally used by the kernel to store all the data 
concerning a specific task.

Task Priority
Number associated with each task that indicates to the scheduler the 
order in which the tasks must be chosen to run.

Task Switch
Action of saving the state of a task, selecting another task to run, and 
restoring the corresponding state. Usually involves at least two context 
switches.

Throughput
The amount of processing performed in a given amount of time by an 
entity in a computer system.

Tightly-coupled message communication with reply
The case when a producer task sends a message to a consumer task 
and then waits for a reply.

 Contact APD for the availability of such extensions.
 To gain speed, Protected Mode part sometimes accesses low memory directly.
 Those marked with *
 not to be confused with microprocessor exceptions, which are managed by the DOS Extender

Moonlight Microkernel - Programmer's  Manual

*1995 APD S.A.           Page 65


