Copyright (c) Hyperion Entertainment and contributors.

Difference between revisions of "Exec Semaphores"

From AmigaOS Documentation Wiki
Jump to navigation Jump to search
 
(20 intermediate revisions by the same user not shown)
Line 1: Line 1:
{{WIP}}
 
 
== Exec Semaphores ==
 
== Exec Semaphores ==
   
Semaphores are a feature of Exec which provide a general method for tasks to arbitrate for the use of memory or other system resources they may be sharing. This chapter describes the structure of Exec semaphores and the various support functions provided for their use. Since the semaphore system uses Exec lists and signals, some familiarity with these concepts is helpful for understanding semaphores.
+
Semaphores are a feature of Exec which provide a general method for tasks to arbitrate for the use of memory or other system resources they may be sharing. This section describes the structure of Exec semaphores and the various support functions provided for their use. Since the semaphore system uses Exec lists and signals, some familiarity with these concepts is helpful for understanding semaphores.
   
 
In any multitasking or multi-processing system there is a need to share data among independently executing tasks. If the data is static (that is, it never changes), then there is no problem. However, if the data is variable, then there must be some way for a task that is about to make a change to keep other tasks from looking at the data.
 
In any multitasking or multi-processing system there is a need to share data among independently executing tasks. If the data is static (that is, it never changes), then there is no problem. However, if the data is variable, then there must be some way for a task that is about to make a change to keep other tasks from looking at the data.
Line 13: Line 12:
   
 
# All tasks using shared data that is protected by a semaphore must ''always ask for the semaphore first before accessing the data''. If some task accesses the data directly without first going through the semaphore, the data may be corrupted. No task will have safe access to the data.
 
# All tasks using shared data that is protected by a semaphore must ''always ask for the semaphore first before accessing the data''. If some task accesses the data directly without first going through the semaphore, the data may be corrupted. No task will have safe access to the data.
# A deadlock will occur if a task that owns an exclusive semaphore on some data inadvertently calls another task which tries to get an exclusive semaphore on that same data in blocking mode. Deadlocks and other such issues are beyond the scope of this manual. For more details on deadlocks and other problems of shared data in a multitasking system and the methods used to prevent them, refer to a textbook in computer science such as ''Operating Systems'' by Tannenbaum (Prentice-Hall).
+
# A deadlock will occur if a task that owns an exclusive semaphore on some data inadvertently calls another task which tries to get an exclusive semaphore on that same data in blocking mode. Deadlocks and other such issues are beyond the scope of this manual. For more details on deadlocks and other problems of shared data in a multitasking system and the methods used to prevent them, refer to [http://en.wikipedia.org/wiki/Deadlock Wikipedia].
   
  +
[[Exec_Mutexes|Mutexes]] are also provided by the operating system. A mutex is similar to an exclusive mode semaphore. Mutexes may are also optionally recursive (nested). The primary advantage of a mutex over an exclusive semaphore is that it will not use Forbid/Permit locking internally so the overall system will be less affected.
== Semaphore Functions ==
 
   
  +
== The Signal Semaphore ==
Exec provides a variety of useful functions for setting, checking and freeing semaphores. The prototypes for these functions are as follows.
 
 
<pre>VOID AddSemaphore ( struct SignalSemaphore *sigSem );
 
ULONG AttemptSemaphore( struct SignalSemaphore *sigSem );
 
struct SignalSemaphore *FindSemaphore( UBYTE *sigSem );
 
VOID InitSemaphore( struct SignalSemaphore *sigSem );
 
 
VOID ObtainSemaphore( struct SignalSemaphore *sigSem );
 
VOID ObtainSemaphoreList( struct List *sigSem );
 
void ObtainSemaphoreShared( struct SignalSemaphore *sigSem );
 
 
VOID ReleaseSemaphore( struct SignalSemaphore *sigSem );
 
VOID ReleaseSemaphoreList( struct List *sigSem );
 
VOID RemSemaphore( struct SignalSemaphore *sigSem );</pre>
 
=== The Signal Semaphore ===
 
   
 
Exec semaphores are signal based. Using signal semaphores is the easiest way to protect shared, single-access resources in the Amiga. Your task will sleep until the semaphore is available for use. The SignalSemaphore structure is as follows:
 
Exec semaphores are signal based. Using signal semaphores is the easiest way to protect shared, single-access resources in the Amiga. Your task will sleep until the semaphore is available for use. The SignalSemaphore structure is as follows:
Line 73: Line 58:
 
</syntaxhighlight>
 
</syntaxhighlight>
   
==== Creating a SignalSemaphore Structure ====
+
=== Creating a SignalSemaphore Structure ===
   
To initialize a SignalSemaphore structure use the InitSemaphore() function. This function initializes the list structure and the nesting and queue counters. It does not change the semaphore's name or priority fields.
+
To create a SignalSemaphore structure use the AllocSysObject() function with an object type of ASOT_SEMAPHORE. Various tags control the attributes of the SignalSemaphore as explained in the autodoc.
   
  +
=== Making a SignalSemaphore Available to the Public ===
This fragment creates and initializes a semaphore for a data item such as the SharedList structure above.
 
   
  +
A semaphore should be used internally in your program if it has more than one task operating on shared data structures. There may also be cases when you wish to make a data item public to other applications but still need to restrict its access via semaphores. In that case, you would give your semaphore a unique name and add it to the public SignalSemaphore list maintained by Exec. The ASOSEM_Name tag accomplishes this task for you.
<syntaxhighlight>
 
struct SharedList *slist;
 
 
slist = IExec->AllocVecTags(sizeof(struct SharedList),
 
AVT_Type, MEMF_SHARED,
 
AVT_ClearWithValue, 0,
 
TAG_END);
 
 
if (slist != NULL)
 
{
 
IExec->NewMinList(&slist->sl_List); /* Initialize the MinList */
 
IExec->InitSemaphore((struct SignalSemaphore *)slist); /* And initialize the semaphore */
 
 
/* The semaphore can now be used. */
 
}
 
else IDOS->Printf("Can't allocate structure\n");
 
</syntaxhighlight>
 
 
==== Making a SignalSemaphore Available to the Public ====
 
 
A semaphore should be used internally in your program if it has more than one task operating on shared data structures. There may also be cases when you wish to make a data item public to other applications but still need to restrict its access via semaphores. In that case, you would give your semaphore a unique name and add it to the public SignalSemaphore list maintained by Exec. The AddSemaphore() function does this for you. This works in a manner similar to AddPort() for message ports.
 
   
 
To create and initialize a ''public'' semaphore for a data item and add it to the public semaphore list maintained by Exec, the following function should be used. (This will prevent the semaphore from being added or removed more than once by separate programs that use the semaphore).
 
To create and initialize a ''public'' semaphore for a data item and add it to the public semaphore list maintained by Exec, the following function should be used. (This will prevent the semaphore from being added or removed more than once by separate programs that use the semaphore).
   
  +
<syntaxhighlight>
<pre>UBYTE *name; /* name of semaphore to add */
 
  +
CONST_STRPTR name; // name of semaphore to add
 
struct SignalSemaphore *semaphore;
 
struct SignalSemaphore *semaphore;
   
Forbid();
+
IExec->Forbid();
/* Make sure the semaphore name is unique */
+
// Make sure the semaphore name is unique
if (!FindSemaphore(name)) {
+
if (!IExec->FindSemaphore(name)) {
/* Allocate memory for the structure */
+
// Allocate memory for the structure
  +
// Note that the string 'name' is not copied. If that is needed, add
if (sema=(struct SignalSemaphore *)
 
  +
// ASOSEM_CopyName, TRUE to the tag list.
AllocMem(sizeof(struct SignalSemaphore),MEMF_PUBLIC|MEMF_CLEAR))
 
  +
  +
semaphore = IExec->AllocSysObjectTags(ASOT_SEMAPHORE,
  +
ASOSEM_Name, name,
  +
ASOSEM_Pri, 0, /* Set the priority to zero */
  +
TAG_END);
  +
  +
if (semaphore != NULL)
 
{
 
{
  +
// Use the sempahore.
sema-&gt;ss_Link.ln_Pri=0; /* Set the priority to zero */
 
sema-&gt;ss_Link.ln_Name=name;
 
/* Note that the string 'name' is not copied. If that is needed, allocate memory */
 
/* for it and copy the string. And add the semaphore the the system list */
 
AddSemaphore(semaphore);
 
 
}
 
}
 
}
 
}
Permit();</pre>
+
IExec->Permit();
  +
</syntaxhighlight>
  +
 
A value of NULL for semaphore means that the semaphore already exists or that there was not enough free memory to create it.
 
A value of NULL for semaphore means that the semaphore already exists or that there was not enough free memory to create it.
   
 
Before using the data item or other resource which is protected by a semaphore, you must first obtain the semaphore. Depending on your needs, you can get either ''exclusive'' or ''shared'' access to the semaphore.
 
Before using the data item or other resource which is protected by a semaphore, you must first obtain the semaphore. Depending on your needs, you can get either ''exclusive'' or ''shared'' access to the semaphore.
   
==== Obtaining a SignalSemaphore Exclusively ====
+
=== Obtaining a SignalSemaphore Exclusively ===
   
 
The ObtainSemaphore() function can be used to get an exclusive lock on a semaphore. If another task currently has an exclusive or shared lock(s) on the semaphore, your task will be put to sleep until all locks on the the semaphore are released.
 
The ObtainSemaphore() function can be used to get an exclusive lock on a semaphore. If another task currently has an exclusive or shared lock(s) on the semaphore, your task will be put to sleep until all locks on the the semaphore are released.
   
  +
{{Note|title=Semaphore Nesting|text=SignalSemaphores have nesting. That is, if your task already owns the semaphore, it will get a second ownership of that semaphore. This simplifies the writing of routines that must own the semaphore but do not know if the caller has obtained it yet.}}
{| class="wikitable"
 
| ''Semaphore Nesting.'' SignalSemaphores have nesting. That is, if your task already owns the semaphore, it will get a second ownership of that semaphore. This simplifies the writing of routines that must own the semaphore but do not know if the caller has obtained it yet.
 
|}
 
   
 
To obtain a semaphore use:
 
To obtain a semaphore use:
   
  +
<syntaxhighlight>
<pre>
 
 
struct SignalSemaphore *semaphore;
 
struct SignalSemaphore *semaphore;
ObtainSemaphore(semaphore);
+
IExec->ObtainSemaphore(semaphore);
  +
</syntaxhighlight>
</pre>
 
   
 
To get an exclusive lock on a ''public'' semaphore, the following code should be used:
 
To get an exclusive lock on a ''public'' semaphore, the following code should be used:
   
  +
<syntaxhighlight>
<pre>
 
UBYTE *name;
+
STRPTR name;
 
struct SignalSemaphore *semaphore;
 
struct SignalSemaphore *semaphore;
   
Forbid(); /* Make sure the semaphore will not go away if found. */
+
IExec->Forbid(); /* Make sure the semaphore will not go away if found. */
if (semaphore=FindSemaphore(name))
+
if (semaphore = IExec->FindSemaphore(name))
ObtainSemaphore(semaphore);
+
IExec->ObtainSemaphore(semaphore);
Permit();
+
IExec->Permit();
  +
</syntaxhighlight>
</pre>
 
   
 
The value of semaphore is NULL if the semaphore does not exist. This is ''only'' needed if the semaphore has a chance of going away at any time (i.e., the semaphore is public and might be removed by some other program). If there is a guarantee that the semaphore will not disappear, the semaphore address could be cached, and all that would be needed is a call to the ObtainSemaphore() function.
 
The value of semaphore is NULL if the semaphore does not exist. This is ''only'' needed if the semaphore has a chance of going away at any time (i.e., the semaphore is public and might be removed by some other program). If there is a guarantee that the semaphore will not disappear, the semaphore address could be cached, and all that would be needed is a call to the ObtainSemaphore() function.
   
==== Obtaining a Shared SignalSemaphore ====
+
=== Obtaining a Shared SignalSemaphore ===
   
 
For read-only purposes, multiple tasks may have a shared lock on a signal semaphore. If a semaphore is already exclusively locked, all attempts to obtain the semaphore shared will be blocked until the exclusive lock is released. At that point, all shared locks will be obtained and the calling tasks will wake up.
 
For read-only purposes, multiple tasks may have a shared lock on a signal semaphore. If a semaphore is already exclusively locked, all attempts to obtain the semaphore shared will be blocked until the exclusive lock is released. At that point, all shared locks will be obtained and the calling tasks will wake up.
Line 160: Line 129:
 
To obtain a shared semaphore, use:
 
To obtain a shared semaphore, use:
   
  +
<syntaxhighlight>
<pre>struct SignalSemaphore *semaphore;
 
ObtainSemaphoreShared(semaphore);</pre>
+
struct SignalSemaphore *semaphore;
  +
IExec->ObtainSemaphoreShared(semaphore);
  +
</syntaxhighlight>
  +
 
To obtain a ''public'' shared semaphore, the following code should be used:
 
To obtain a ''public'' shared semaphore, the following code should be used:
   
  +
<syntaxhighlight>
<pre>UBYTE *name;
 
  +
STRPTR name;
 
struct SignalSemaphore *semaphore;
 
struct SignalSemaphore *semaphore;
   
Forbid();
+
IExec->Forbid();
if (semaphore = FindSemaphore(name))
+
if (semaphore = IExec->FindSemaphore(name))
ObtainSemaphoreShared(semaphore);
+
IExec->ObtainSemaphoreShared(semaphore);
Permit();</pre>
+
IExec->Permit();
  +
</syntaxhighlight>
==== Checking a SignalSemaphore ====
 
 
   
  +
=== Checking a SignalSemaphore ===
   
 
When you attempt to obtain a semaphore with ObtainSemaphore(), your task will be put to sleep if the semaphore is not currently available. If you do not want to wait, you can call AttemptSemaphore() instead. If the semaphore is available for exclusive locking, AttemptSemaphore() obtains it for you and returns TRUE. If it is not available, the function returns FALSE immediately instead of waiting for the semaphore to be released.
 
When you attempt to obtain a semaphore with ObtainSemaphore(), your task will be put to sleep if the semaphore is not currently available. If you do not want to wait, you can call AttemptSemaphore() instead. If the semaphore is available for exclusive locking, AttemptSemaphore() obtains it for you and returns TRUE. If it is not available, the function returns FALSE immediately instead of waiting for the semaphore to be released.
Line 179: Line 152:
 
To attempt to obtain a semaphore, use the following:
 
To attempt to obtain a semaphore, use the following:
   
  +
<syntaxhighlight>
<pre>struct SignalSemaphore *semaphore;
 
AttemptSemaphore(semaphore);</pre>
+
struct SignalSemaphore *semaphore;
  +
IExec->AttemptSemaphore(semaphore);
  +
</syntaxhighlight>
  +
 
To make an attempt to obtain a ''public'' semaphore, the following code should be used:
 
To make an attempt to obtain a ''public'' semaphore, the following code should be used:
   
  +
<syntaxhighlight>
<pre>UBYTE *name;
 
  +
UBYTE *name;
 
struct SignalSemaphore *semaphore;
 
struct SignalSemaphore *semaphore;
   
Forbid();
+
IExec->Forbid();
if (semaphore = FindSemaphore(name)) AttemptSemaphore(semaphore);
+
if (semaphore = IExec->FindSemaphore(name)) IExec->AttemptSemaphore(semaphore);
Permit();</pre>
+
IExec->Permit();
  +
</syntaxhighlight>
==== Releasing a SignalSemaphore ====
 
 
   
  +
=== Releasing a SignalSemaphore ===
   
 
Once you have obtained the semaphore and completed any operations on the semaphore protected object, you should release the semaphore. The ReleaseSemaphore() function does this. For each successful ObtainSemaphore(), ObtainSemaphoreShared() and AttemptSemaphore() call you make, you ''must'' have a matching ReleaseSemaphore() call.
 
Once you have obtained the semaphore and completed any operations on the semaphore protected object, you should release the semaphore. The ReleaseSemaphore() function does this. For each successful ObtainSemaphore(), ObtainSemaphoreShared() and AttemptSemaphore() call you make, you ''must'' have a matching ReleaseSemaphore() call.
   
==== Removing a SignalSemaphore Structure ====
+
=== Removing a SignalSemaphore Structure ===
   
 
Semaphore resources can only be freed if the semaphore is not locked. A public semaphore should first be removed from the system semaphore list with the RemSemaphore() function. This prevents other tasks from finding the semaphore and trying to lock it. Once the semaphore is removed from the system list, the semaphore should be locked exclusively so no other task can lock it. Once the lock is obtained, it can be released again, and the resources can be deallocated.
 
Semaphore resources can only be freed if the semaphore is not locked. A public semaphore should first be removed from the system semaphore list with the RemSemaphore() function. This prevents other tasks from finding the semaphore and trying to lock it. Once the semaphore is removed from the system list, the semaphore should be locked exclusively so no other task can lock it. Once the lock is obtained, it can be released again, and the resources can be deallocated.
   
  +
All of this can be accomplished with a single call to FreeSysObject():
The following code should be used to remove a public semaphore:
 
   
  +
<syntaxhighlight>
<pre>UBYTE *name;
 
  +
IExec->FreeSysObject(ASOT_SEMAPHORE, semaphore);
struct SignalSemaphore *semaphore;
 
  +
</syntaxhighlight>
   
  +
== Multiple Semaphores ==
Forbid();
 
if (semaphore=FindSemaphore(name))
 
{
 
RemSemaphore(semaphore); /* So no one else can find it... */
 
ObtainSemaphore(semaphore); /* Wait for us to be last user...*/
 
ReleaseSemaphore(semaphore); /* Ready for cleanup... */
 
}
 
FreeMem(semaphore, sizeof(struct SignalSemaphore));
 
Permit();</pre>
 
 
=== Multiple Semaphores ===
 
   
 
The semaphore system has the ability to ask for ownership of a complete list of semaphores. This can help prevent deadlocks when there are two or more tasks trying to get the same set of semaphores. If ''task A'' gets ''semaphore 1'' and tries to obtain ''semaphore 2'' '''after''' ''task B'' has obtained ''semaphore 2'' but '''before''' ''task B'' tries to obtain ''semaphore 1'' then both tasks will hang. Exec provides ObtainSemaphoreList() and ReleaseSemaphoreList() to prevent this problem.
 
The semaphore system has the ability to ask for ownership of a complete list of semaphores. This can help prevent deadlocks when there are two or more tasks trying to get the same set of semaphores. If ''task A'' gets ''semaphore 1'' and tries to obtain ''semaphore 2'' '''after''' ''task B'' has obtained ''semaphore 2'' but '''before''' ''task B'' tries to obtain ''semaphore 1'' then both tasks will hang. Exec provides ObtainSemaphoreList() and ReleaseSemaphoreList() to prevent this problem.
Line 224: Line 192:
 
For example:
 
For example:
   
  +
<syntaxhighlight>
<pre>
 
ObtainSemaphore((struct SignalSemaphore *)SemaphoreList);
+
IExec->ObtainSemaphore((struct SignalSemaphore *)SemaphoreList);
ObtainSemaphoreList(SemaphoreList-&gt;sl_List);
+
IExec->ObtainSemaphoreList(SemaphoreList->sl_List);
   
 
/* At this point the objects are protected, and can be manipulated */
 
/* At this point the objects are protected, and can be manipulated */
   
ReleaseSemaphoreList(SemaphoreList-&gt;sl_List);
+
IExec->ReleaseSemaphoreList(SemaphoreList->sl_List);
ReleaseSemaphore((struct SignalSemaphore *)SemaphoreList);
+
IExec->ReleaseSemaphore((struct SignalSemaphore *)SemaphoreList);
  +
</syntaxhighlight>
</pre>
 
   
 
See the SharedList structure above for an example of a semaphore structure with a list header.
 
See the SharedList structure above for an example of a semaphore structure with a list header.
   
=== Semaphore Example ===
+
== Semaphore Example ==
   
 
A simple "do nothing" example of Exec signal semaphore use is shown below. When the semaphore is owned by a task, attempted access by other tasks will block. A nesting count is maintained, so the current task can safely call ObtainSemaphore() on the same semaphore.
 
A simple "do nothing" example of Exec signal semaphore use is shown below. When the semaphore is owned by a task, attempted access by other tasks will block. A nesting count is maintained, so the current task can safely call ObtainSemaphore() on the same semaphore.
   
  +
<syntaxhighlight>
<pre>
 
/* semaphore.c - Exec semaphore example - compile with lc -L semaphore.c */
+
// semaphore.c - Exec semaphore example - compile with gcc -o semaphore semaphore.c
#include &lt;exec/types.h&gt;
+
#include <exec/types.h>
#include &lt;exec/semaphores.h&gt;
+
#include <exec/semaphores.h>
#include &lt;clib/exec_protos.h&gt;
+
#include <proto/exec.h>
#include &lt;stdio.h&gt;
 
   
  +
struct SignalSemaphore LockSemaphore;
#ifdef LATTICE
 
int CXBRK(void) { return(0); } /* Disable Lattice CTRL/C handling */
 
void chkabort(void) { return; } /* really */
 
#endif
 
   
  +
int main(int argc,char *argv[])
struct SignalSemaphore LockSemaphore = {0};
 
 
VOID main(int argc,char *argv[])
 
 
{
 
{
InitSemaphore(&amp;LockSemaphore);
+
LockSemaphore = IExec->AllocSysObjectTags(ASOT_SEMAPHORE, TAG_END);
  +
ObtainSemaphore(&amp;LockSemaphore); /* Task now owns the semaphore. */
 
...
+
if (LockSemaphore != NULL)
  +
{
ReleaseSemaphore(&amp;LockSemaphore); /* Task has released the semaphore. */
 
  +
IExec->ObtainSemaphore(LockSemaphore); // Task now owns the semaphore.
  +
// ...
  +
IExec->ReleaseSemaphore(LockSemaphore); // Task has released the semaphore.
  +
  +
IExec->FreeSysObject(ASOT_SEMPAHORE, LockSemaphore);
  +
}
  +
  +
return 0;
 
}
 
}
  +
</syntaxhighlight>
</pre>
 
   
 
== Function Reference ==
 
== Function Reference ==
Line 267: Line 237:
 
The following charts give a brief description of the Exec semaphore functions. See the SDK for details about each call.
 
The following charts give a brief description of the Exec semaphore functions. See the SDK for details about each call.
   
  +
{| class="wikitable"
<table>
 
  +
! Exec Semaphore Function
<tr class="header">
 
  +
! Description
<th align="left">'''Exec Semaphore Function'''</th>
 
  +
|-
<th align="left">'''Description'''</th>
 
  +
| AddSemaphore()
</tr>
 
  +
| Initialize and add a signal semaphore to the system.
<tr class="odd">
 
  +
|-
<td align="left">AddSemaphore()</td>
 
  +
| AllocSysObject(ASOT_SEMAPHORE)
<td align="left">Initialize and add a signal semaphore to the system.</td>
 
  +
| Allocate and initialize a new semaphore.
</tr>
 
  +
|-
<tr class="even">
 
<td align="left">AttemptSemaphore()</td>
+
| AttemptSemaphore()
<td align="left">Try to get an exclusive lock on a signal</td>
+
| Try to get an exclusive lock on a signal semaphore without blocking.
  +
|-
</tr>
 
  +
| FindSemaphore()
<tr class="odd">
 
  +
| Find a given system signal semaphore.
<td align="left"></td>
 
  +
|-
<td align="left">semaphore without blocking.</td>
 
  +
| FreeSysObject(ASOT_SEMAPHORE)
</tr>
 
  +
| Free a semaphore.
<tr class="even">
 
  +
|-
<td align="left">FindSemaphore()</td>
 
  +
| InitSemaphore()
<td align="left">Find a given system signal semaphore.</td>
 
  +
| Initialize a signal semaphore.
</tr>
 
  +
|-
<tr class="odd">
 
  +
| ObtainSemaphore()
<td align="left">InitSemaphore()</td>
 
<td align="left">Initialize a signal semaphore.</td>
+
| Try to get exclusive access to a signal semaphore.
  +
|-
</tr>
 
  +
| ObtainSemaphoreList()
<tr class="even">
 
  +
| Try to get exclusive access to a list of signal semaphores.
<td align="left">ObtainSemaphore()</td>
 
  +
|-
<td align="left">Try to get exclusive access to a signal semaphore.</td>
 
  +
| ObtainSemaphoreShared()
</tr>
 
  +
| Try to get shared access to a signal semaphore.
<tr class="odd">
 
  +
|-
<td align="left">ObtainSemaphoreList()</td>
 
  +
| ReleaseSemaphore()
<td align="left">Try to get exclusive access to a list of signal semaphores.</td>
 
  +
| Release the lock on a signal semaphore.
</tr>
 
  +
|-
<tr class="even">
 
  +
| ReleaseSemaphoreList()
<td align="left">ObtainSemaphoreShared()</td>
 
  +
| Release the locks on a list of signal semaphores.
<td align="left">Try to get shared access to a signal semaphore (V36).</td>
 
  +
|-
</tr>
 
  +
| RemSemaphore()
<tr class="odd">
 
  +
| Remove a signal semaphore from the system.
<td align="left">ReleaseSemaphore()</td>
 
  +
|}
<td align="left">Release the lock on a signal semaphore.</td>
 
</tr>
 
<tr class="even">
 
<td align="left">ReleaseSemaphoreList()</td>
 
<td align="left">Release the locks on a list of signal semaphores.</td>
 
</tr>
 
<tr class="odd">
 
<td align="left">RemSemaphore()</td>
 
<td align="left">Remove a signal semaphore from the system.</td>
 
</tr>
 
</table>
 

Latest revision as of 22:02, 12 July 2012

Exec Semaphores

Semaphores are a feature of Exec which provide a general method for tasks to arbitrate for the use of memory or other system resources they may be sharing. This section describes the structure of Exec semaphores and the various support functions provided for their use. Since the semaphore system uses Exec lists and signals, some familiarity with these concepts is helpful for understanding semaphores.

In any multitasking or multi-processing system there is a need to share data among independently executing tasks. If the data is static (that is, it never changes), then there is no problem. However, if the data is variable, then there must be some way for a task that is about to make a change to keep other tasks from looking at the data.

For example, to add a node to a linked list of data, a task would normally just add the node. However, if the list is shared with other tasks, this could be dangerous. Another task could be walking down the list while the change is being made and pick up an incorrect pointer. The problem is worse if two tasks attempt to add an item to the list at the same time. Exec semaphores provide a way to prevent such problems.

A semaphore is much like getting a key to a locked data item. When you have the key (semaphore), you can access the data item without worrying about other tasks causing problems. Any other tasks that try to obtain the semaphore will be put to sleep until the semaphore becomes available. When you have completed your work with the data, you return the semaphore.

For semaphores to work correctly, there are two restrictions that must be observed at all times:

  1. All tasks using shared data that is protected by a semaphore must always ask for the semaphore first before accessing the data. If some task accesses the data directly without first going through the semaphore, the data may be corrupted. No task will have safe access to the data.
  2. A deadlock will occur if a task that owns an exclusive semaphore on some data inadvertently calls another task which tries to get an exclusive semaphore on that same data in blocking mode. Deadlocks and other such issues are beyond the scope of this manual. For more details on deadlocks and other problems of shared data in a multitasking system and the methods used to prevent them, refer to Wikipedia.

Mutexes are also provided by the operating system. A mutex is similar to an exclusive mode semaphore. Mutexes may are also optionally recursive (nested). The primary advantage of a mutex over an exclusive semaphore is that it will not use Forbid/Permit locking internally so the overall system will be less affected.

The Signal Semaphore

Exec semaphores are signal based. Using signal semaphores is the easiest way to protect shared, single-access resources in the Amiga. Your task will sleep until the semaphore is available for use. The SignalSemaphore structure is as follows:

struct SignalSemaphore {
    struct  Node ss_Link;
    SHORT   ss_NestCount;
    struct  MinList ss_WaitQueue;
    struct  SemaphoreRequest ss_MultipleLink;
    struct  Task *ss_Owner;
    SHORT   ss_QueueCount;
};
ss_Link
is the node structure used to link semaphores together. The ln_Pri and ln_Name fields are used to set the priority of the semaphore in a list and to name the semaphore for public access. If a semaphore is not public the ln_Name and ln_Pri fields may be left NULL.
ss_NestCount
is the count of number of locks the current owner has on the semaphore.
ss_WaitQueue
is the List header for the list of other tasks waiting for this semaphore.
ss_MultipleLink
is the SemaphoreRequest used by ObtainSemaphoreList().
ss_Owner
is the pointer to the current owning task.
ss_QueueCount
is the number of other tasks waiting for the semaphore.

A practical application of a SignalSemaphore would be to use it as the base of a shared data structure. For example:

struct SharedList {
    struct SignalSemaphore sl_Semaphore;
    struct MinList         sl_List;
};

Creating a SignalSemaphore Structure

To create a SignalSemaphore structure use the AllocSysObject() function with an object type of ASOT_SEMAPHORE. Various tags control the attributes of the SignalSemaphore as explained in the autodoc.

Making a SignalSemaphore Available to the Public

A semaphore should be used internally in your program if it has more than one task operating on shared data structures. There may also be cases when you wish to make a data item public to other applications but still need to restrict its access via semaphores. In that case, you would give your semaphore a unique name and add it to the public SignalSemaphore list maintained by Exec. The ASOSEM_Name tag accomplishes this task for you.

To create and initialize a public semaphore for a data item and add it to the public semaphore list maintained by Exec, the following function should be used. (This will prevent the semaphore from being added or removed more than once by separate programs that use the semaphore).

CONST_STRPTR name;   // name of semaphore to add
struct SignalSemaphore *semaphore;
 
IExec->Forbid();
// Make sure the semaphore name is unique
if (!IExec->FindSemaphore(name)) {
    // Allocate memory for the structure
    // Note that the string 'name' is not copied. If that is needed, add
    // ASOSEM_CopyName, TRUE to the tag list.
 
    semaphore = IExec->AllocSysObjectTags(ASOT_SEMAPHORE,
      ASOSEM_Name, name,
      ASOSEM_Pri, 0,    /* Set the priority to zero */
      TAG_END);
 
    if (semaphore != NULL)
    {
      // Use the sempahore.
    }
}
IExec->Permit();

A value of NULL for semaphore means that the semaphore already exists or that there was not enough free memory to create it.

Before using the data item or other resource which is protected by a semaphore, you must first obtain the semaphore. Depending on your needs, you can get either exclusive or shared access to the semaphore.

Obtaining a SignalSemaphore Exclusively

The ObtainSemaphore() function can be used to get an exclusive lock on a semaphore. If another task currently has an exclusive or shared lock(s) on the semaphore, your task will be put to sleep until all locks on the the semaphore are released.

Semaphore Nesting
SignalSemaphores have nesting. That is, if your task already owns the semaphore, it will get a second ownership of that semaphore. This simplifies the writing of routines that must own the semaphore but do not know if the caller has obtained it yet.

To obtain a semaphore use:

struct SignalSemaphore *semaphore;
IExec->ObtainSemaphore(semaphore);

To get an exclusive lock on a public semaphore, the following code should be used:

STRPTR name;
struct SignalSemaphore *semaphore;
 
IExec->Forbid();     /* Make sure the semaphore will not go away if found. */
if (semaphore = IExec->FindSemaphore(name))
    IExec->ObtainSemaphore(semaphore);
IExec->Permit();

The value of semaphore is NULL if the semaphore does not exist. This is only needed if the semaphore has a chance of going away at any time (i.e., the semaphore is public and might be removed by some other program). If there is a guarantee that the semaphore will not disappear, the semaphore address could be cached, and all that would be needed is a call to the ObtainSemaphore() function.

Obtaining a Shared SignalSemaphore

For read-only purposes, multiple tasks may have a shared lock on a signal semaphore. If a semaphore is already exclusively locked, all attempts to obtain the semaphore shared will be blocked until the exclusive lock is released. At that point, all shared locks will be obtained and the calling tasks will wake up.

To obtain a shared semaphore, use:

struct SignalSemaphore *semaphore;
IExec->ObtainSemaphoreShared(semaphore);

To obtain a public shared semaphore, the following code should be used:

STRPTR name;
struct SignalSemaphore *semaphore;
 
IExec->Forbid();
if (semaphore = IExec->FindSemaphore(name))
    IExec->ObtainSemaphoreShared(semaphore);
IExec->Permit();

Checking a SignalSemaphore

When you attempt to obtain a semaphore with ObtainSemaphore(), your task will be put to sleep if the semaphore is not currently available. If you do not want to wait, you can call AttemptSemaphore() instead. If the semaphore is available for exclusive locking, AttemptSemaphore() obtains it for you and returns TRUE. If it is not available, the function returns FALSE immediately instead of waiting for the semaphore to be released.

To attempt to obtain a semaphore, use the following:

struct SignalSemaphore *semaphore;
IExec->AttemptSemaphore(semaphore);

To make an attempt to obtain a public semaphore, the following code should be used:

UBYTE *name;
struct SignalSemaphore *semaphore;
 
IExec->Forbid();
if (semaphore = IExec->FindSemaphore(name)) IExec->AttemptSemaphore(semaphore);
IExec->Permit();

Releasing a SignalSemaphore

Once you have obtained the semaphore and completed any operations on the semaphore protected object, you should release the semaphore. The ReleaseSemaphore() function does this. For each successful ObtainSemaphore(), ObtainSemaphoreShared() and AttemptSemaphore() call you make, you must have a matching ReleaseSemaphore() call.

Removing a SignalSemaphore Structure

Semaphore resources can only be freed if the semaphore is not locked. A public semaphore should first be removed from the system semaphore list with the RemSemaphore() function. This prevents other tasks from finding the semaphore and trying to lock it. Once the semaphore is removed from the system list, the semaphore should be locked exclusively so no other task can lock it. Once the lock is obtained, it can be released again, and the resources can be deallocated.

All of this can be accomplished with a single call to FreeSysObject():

IExec->FreeSysObject(ASOT_SEMAPHORE, semaphore);

Multiple Semaphores

The semaphore system has the ability to ask for ownership of a complete list of semaphores. This can help prevent deadlocks when there are two or more tasks trying to get the same set of semaphores. If task A gets semaphore 1 and tries to obtain semaphore 2 after task B has obtained semaphore 2 but before task B tries to obtain semaphore 1 then both tasks will hang. Exec provides ObtainSemaphoreList() and ReleaseSemaphoreList() to prevent this problem.

A semaphore list is a list header to a list that contains SignalSemaphore structures. The semaphore list must not contain any public semaphores. This is because the semaphore list functions use the standard node structures in the semaphore.

To arbitrate access to a semaphore list use another semaphore. Create a public semaphore and use it to arbitrate access to the list header of the semaphore list. This also gives you a locking semaphore, protecting the ObtainSemaphoreList() call. Once you have gained access to the list with ObtainSemaphore(), you may obtain all the semaphores on the list via ObtainSemaphoreList() (or get individual semaphores with ObtainSemaphore()). When you are finished with the protected objects, release the semaphores on the list with ReleaseSemaphoreList(), and then release the list semaphore via ReleaseSemaphore().

For example:

IExec->ObtainSemaphore((struct SignalSemaphore *)SemaphoreList);
IExec->ObtainSemaphoreList(SemaphoreList->sl_List);
 
/* At this point the objects are protected, and can be manipulated */
 
IExec->ReleaseSemaphoreList(SemaphoreList->sl_List);
IExec->ReleaseSemaphore((struct SignalSemaphore *)SemaphoreList);

See the SharedList structure above for an example of a semaphore structure with a list header.

Semaphore Example

A simple "do nothing" example of Exec signal semaphore use is shown below. When the semaphore is owned by a task, attempted access by other tasks will block. A nesting count is maintained, so the current task can safely call ObtainSemaphore() on the same semaphore.

// semaphore.c - Exec semaphore example - compile with gcc -o semaphore semaphore.c
#include <exec/types.h>
#include <exec/semaphores.h>
#include <proto/exec.h>
 
struct SignalSemaphore LockSemaphore;
 
int main(int argc,char *argv[])
{
    LockSemaphore = IExec->AllocSysObjectTags(ASOT_SEMAPHORE, TAG_END);
 
    if (LockSemaphore != NULL)
    {
        IExec->ObtainSemaphore(LockSemaphore);  // Task now owns the semaphore.
        // ...
        IExec->ReleaseSemaphore(LockSemaphore); // Task has released the semaphore.
 
        IExec->FreeSysObject(ASOT_SEMPAHORE, LockSemaphore);
    }
 
    return 0;
}

Function Reference

The following charts give a brief description of the Exec semaphore functions. See the SDK for details about each call.

Exec Semaphore Function Description
AddSemaphore() Initialize and add a signal semaphore to the system.
AllocSysObject(ASOT_SEMAPHORE) Allocate and initialize a new semaphore.
AttemptSemaphore() Try to get an exclusive lock on a signal semaphore without blocking.
FindSemaphore() Find a given system signal semaphore.
FreeSysObject(ASOT_SEMAPHORE) Free a semaphore.
InitSemaphore() Initialize a signal semaphore.
ObtainSemaphore() Try to get exclusive access to a signal semaphore.
ObtainSemaphoreList() Try to get exclusive access to a list of signal semaphores.
ObtainSemaphoreShared() Try to get shared access to a signal semaphore.
ReleaseSemaphore() Release the lock on a signal semaphore.
ReleaseSemaphoreList() Release the locks on a list of signal semaphores.
RemSemaphore() Remove a signal semaphore from the system.