Copy Link
Add to Bookmark
Report

Fun Time: Multitasking-Programmierung mit WarpOS

eZine's profile picture
Published in 
Fun Time
 · 11 May 2019

Multitasking-Programmierung mit WarpOS


Multitasking-Programmierung mit WarpOS

Dies ist eine Fortsetzung meines Kurses zur PPC-Programmierung. Diesmal geht es wirklich ins Eingemachte. Direkt in den Kern(el) von WarpOS. Im Normalfall dürften die beschriebenen Methoden nicht benötigt werden, aber in einigen Fällen ist die Multitasking-Programmierung doch recht nützlich.

Bei Rückfragen stehe ich unter MagicSN@Birdland.es.bawue.de oder 07021/51787 oder Steffen Häuser, Limburgstr. 127, 73265 Dettingen/Teck, gerne zur Verfügung. Auch an Software, die ich Portieren kann, bin ich interessiert.

Viele Aufgaben bei einer Software können bekanntlich auf mehrere Tasks aufgeteilt werden. Natürlich ist dies auch bei PPC-Software erwünscht. Dieser Artikel beschreibt, wie man Multitasking-Programmierung auf dem PPC durchführt, unter Verwendung des StormC oder des vbcc-WarpOS Compilers. Ich gehe dabei nicht im Einzelnen auf die Syntax ein, sondern erläutere im Wesentlichen die Verfahren. Syntax kann man in der Dokumentation von WarpOS nachlesen.

1. Die zentrale Funktion

Die zentrale Funktion für das PPC-Multitasking ist die Funktion CreateTaskPPC() der powerpc.library. Sie entspricht gewissermaßen der Funktion CreateTask() der exec.library, nur daß sie PPC-Tasks, und keine 68k-Tasks erzeugt. Dennoch gibt es einige Unterschiede:

- Anstelle einer Task-Struktur wird eine TaskPPC-Struktur erzeugt
- Jede Task-Struktur enthält über task->tp_Task eine "normale" Task
- Struktur - Eigentlich entsprechen PPC-Tasks eher den Prozessen, als den Tasks

Folgende Tags werden beim Erschaffen eines neuen PPC-Tasks eingesetzt:

 
TASKATTR_CODE: Zeigt auf die Funktion, die der Task ausführen soll, wobei
die Funktion mit __saveds deklariert und definiert sein sollte
TASKATTR_EXITCODE: Falls vorhanden: Exitroutine des Tasks
TASKATTR_NAME: Der Name des Tasks, muss angegeben werden
TASKATTR_PRI: Falls vorhanden: Die Priorität des Tasks
TASKATTR_STACKSIZE: Die Größe des Stacks
TASKATTR_R3..._R10: Die Parameter für die Funktion
TASKATTR_R2: LinkerDB


An dieser Stelle möchte ich die Sache mit dem LinkerDB erläutern. Man kann LinkerDB z.B. so deklarieren:

extern ULONG *LinkerDB;

Falls nun ein Task auf globale Variablen zugreifen soll (__saveds allein genügt nicht !!!), so muß man etwa so programmieren:

 
ppctags[0].ti_Data=(ULONG)AudioHandlerTask;
ppctags[2].ti_Data=(ULONG)"MeinTask";
ppctags[3].ti_Data=(ULONG)-128;
ppctags[4].ti_Data=(ULONG)&LinkerDB;
MeinTask=(void *)CreateTaskPPC(ppctags2);


Nun kann der Task auch auf globale Variablen zugreifen.

(Ein DeleteTaskPPC gibt es natürlich auch).

2. Ein Wort zum Multiprocessing

Immer wieder kommt im Usenet - meist von Leuten, die keine PowerPC-Karte besitzen und gerne "theoretisieren" - das Stichwort "Multiprocessoring" hervor. Um dies klarzustellen: PowerUP ist *kein* Multiprozessorsystem, auch wenn es oft als solches bezeichnet wird. Der 68k und der PPC teilen sich einen gemeinsamen Bus, und wenn man versucht, beide gleichzeitig massivst auf diesem Arbeiten zu lassen - z.B. indem man einen Frame auf dem PPC berechnet, und den letzten Frame gleichzeitig mit dem 68k darstellt - so bricht die Busgeschwindigkeit MASSIVST zusammen. Man kann davon ausgehen, daß der Bus auf etwa die halbe Geschwindigkeit gebremst wird. Das Programm läuft also nur noch halb so schnell.

DIES IST KEINE THEORIE, DIES SIND WERTE AUS DER PRAXIS, DIE VON MEHREREN PROGRAMMIERERN UNABH�NGIG VONEINANDER GEMESSEN WURDEN.

Es ist übrigens keine Frage des Kernels. Theoretisch kann man Pseudo-Multiprocessoring sowohl mit WarpOS (man würde das AllocXMsg-System einsetzen) als auch mit ppc.library (das Message-System der ppc.library) programmieren. Man erhält jedoch in beiden Fällen die gleichen miesen Resultate.

ZU EINEM ECHTEN MULTIPROZESSOR GEH�REN ENTWEDER ZWEI BUSSYSTEME ODER LOKALER SPEICHER.

Ich denke, nun können wir das Multiprozessor-Märchen abhaken und uns der Programmierung des Multitaskings weiter widmen.

3. Hilfsfunktionen

Aus der 68k exec.library sind zahllose Hilfsfunktionen für Tasks bekannt, z.B.:

InitSemaphore
ObtainSemaphore
ReleaseSemaphore
Wait
GetMsg
Signal
AllocSignal
CreateMsgPort
...

Alle diese Funktionen werden innerhalb von WarpOS PPC-Native (ohne Kontextswitches) angeboten. Dies sind keine exec.library Funktionen mehr, dies sind Funktionen des WarpOS-Kernels:

InitSemaphorePPC
ObtainSemaphorePPC
ReleaseSemaphorePPC
WaitPPC
GetMsgPPC
SignalPPC
AllocSignalPPC
CreateMsgPortPPC
...

Ausser dem PPC am Ende des Namens ist der einzige Unterschied zu den exec-Funktionen, daß:

- statt eines Task-Parameters ein TaskPPC-Parameter zum Einsatz kommt
- statt eines SignalSemaphore-Parameters ein SignalSemaphorePPC-Parameter zum Einsatz kommt
- statt eines MsgPort-Parameters ein MsgPortPPC-Parameter zum Einsatz kommt ...

Die Includes für all diese neuen Datenstrukturen sind im Includepfad powerpc/ zu finden (z.B. powerpc/tasksPPC.h für die TaskPPC-Struktur).

Gemein haben all diese Strukturen, daß sie jeweils ihr 68k-�quivalent enthalten, so daß man, wenn man bei bestimmten Programmkonstrukten unbedingt die 68k-Struktur oder ein Teil von ihr benötigt, auch auf diese zugreifen kann, z.B.:

task=taskppc->tp_Task;
mp=mp_ppc->mp_Port;
sema=sema_ppc->ssppc_SS;

Man kann z.B. ohne Probleme einem PPC-Task über die 68k-Funktion GetTaskPri eine neue Priorität zuweisen (allerdings ist es sinnvoller, GetTaskPriPPC zu verwenden).

Es sei an dieser Stelle darauf hingewiesen, daß bei WarpOS Semaphoren eine bedeutsame Stellung einnehmen. Man kann bei WarpOS nicht einfach mit einem Forbid() das Multitasking abschalten.

Es bleibt festzustellen, daß WarpOS dem AmigaOS eigentlich sehr ähnlich ist. Die Funktionen sind fast die Selben, zumindest im Bereich des Multitaskings. Achtung, nicht Alle der angegebenen Funktionen sind auch unter WarpUP V7 zugänglich. Im Zweifelsfall WarpOS-Dokumentation konsultieren.

Zusätzlich existieren noch:

- Signal68k: Damit kann ein PPC-Task einem 68k-Task signalisieren
- WaitFor68K: Hiermit kann ein PPC-Task auf einen asynchronen 68k-Task warten, wobei erst nach Ende der Funktion erneut etwas asynchron abgearbeitet werden kann (ist dies nicht genügend => AllocXMsg System ansehen !!!)

4. Was sind eigentlich Semaphoren ?

An dieser Stelle möchte ich noch einmal den Begriff Semaphore wiederholen, da er Neuland für viele Amiga-Programmierer ohne informatische Vorbildung sein dürfte.

Def. Semaphor

Ein Semaphor ist eine Datenstruktur, die von allen Tasks "angetestet" werden kann, ob sie gerade belegt oder frei ist. Ein bestimmter Code kann nur ausgeführt werden, wenn der zuständige Semaphor noch frei ist. Ist er belegt, so wartet der Semaphor, bis er wieder frei ist, und macht dann gleich weiter. Beim gleichzeitigen Zugriff mehrerer Tasks auf einen Semaphor gibt es auf KEINEN FALL Probleme.

Beispiel:

 
1. Task:

extern int a;
struct SignalSemaphore sema;
InitSemaphorePPC(&sema);
while(1)
{
ObtainSemaphorePPC(&sema);
a=1;
ReleaseSemaphorePPC(&sema);
}

2. Task:

extern int a;
struct SignalSemaphore sema2;
InitSemaphorePPC(&sema2);
while(1)
{
ObtainSemaphorePPC(&sema2);
a=2;
ReleaseSemaphorePPC(&sema2);
}


Der Wert von a ist zu jedem Zeitpunkt exakt definiert. Die beiden Tasks greifen niemals gleichzeitig darauf zu.

Aufpassen sollte man, wenn man mehrere Semaphoren verschachtelt. Eine Situation, in der jeder Task auf das Freiwerden der Resource wartet, die gerade der andere Task belegt, nennt man einen DEADLOCK.

Aber das soll hier genügen. Weitere Informationen über Semaphoren können jedem guten Buch über Betriebssysteme entnommen werden. Semaphoren sollten verwendet werden, wann immer eine Resource nicht gleichzeitig von zwei Tasks verwendet werden kann.

Dabei werden Semaphoren erst initialisiert, dann "obtained", dann "released". Man sollte jeden "obtainten" Semaphore auch wieder "releasen", damit die Resource wieder frei wird.

Semaphore sind ein sehr geschicktes Mittel, um Multitasking in einer Weise zu programmieren, daß Deadlock-Situationen oder auch Situationen, in denen zu oft gewartet wird, vermieden werden.

5. Das AllocXMsg-System

Was nun noch fehlt, ist ein System, Nachrichten zwischen 68k und PPC hin und her zu schicken. Man beachte jedoch die Warnung von oben, dass ein solches Programm, wenn man nicht GENAU weiß, was man tut, zu großer Ineffizienz führen kann.

a) Anlegen der Message-Ports

Für den 68k wird ein MsgPort angelegt, für den PPC ein MsgPortPPC

b) Anlegen der Tasks

wie üblich

c) Anlegen der Messages

Der 68k verwendet die Funktion AllocXMsg. Hierbei muß eine Message-Größe angegeben werden, sowie der Reply-Port des 68k-Tasks. Für den PPC existiert eine analoge Funktion AllocXMsgPPC. FreeXMsg/FreeXMsgPPC existieren natürlich ebenfalls.

d) �bertragen der Messages

Hierzu werden die Funktionen PutXMsg (vom 68k zum PPC) und PutXMsgPPC (vom PPC zum 68k) eingesetzt. Als Parameter werden ein MsgPort(PPC) und die in c) gewonnene Message benötigt (in die zuvor die Nachricht eingetragen wird).

e) Empfangen und Beantworten der Messages

Auf 68k-Seite werden GetMsg, WaitPort und ReplyMsg eingesetzt, auf PPC-Seite GetMsgPPC, WaitPortPPC und ReplyMsgPPC. Reply-Messages erhalten hierbei den Nodetype NT_REPLYMSG.

Nachdem die Message verschickt wurde, verliert der entsprechende Prozessor SOFORT den Besitz über die Message. Erst wenn sie Replied wurde, darf wieder auf die Message zugegriffen werden. Falls es keinen Replyport gibt, darf die Message - nachdem sie von der anderen Seite gelesen wurde - freigegeben werden. Nachdem die Message beantwortet wurde, kann sie weiterverwendet werden.

Achtung: ReplyMsg sollte nur aufgerufen werden, wenn auch ein ReplyPort existiert.

Achtung: Der empfangende Task darf nur Daten zugreifen, die direkt im Message-Körper enthalten sind. Eine Ausnahme ist nur möglich, falls sich die beiden Tasks selber um die Cache-Kohärenz kümmern. Nur am Messagekörper selbst führt das System ßushing/Invalidation durch.

Achtung: Der empfangende Task hat auch Schreibzugriff auf den Message-Körper.

Beim Anwenden des AllocXMsg-Systems sind also im wesentlichen zwei Dinge zu beachten:

1) Effizienz ("Bus-Hits")
2) Cache-Kohärenz (entweder alles, was übergeben werden soll, in die Message packen, d.h. auch keine globalen Variablenzugriffe, oder aber sich selbst um die Cache-Koheränz kümmern).

Im üblichen Fall zahlt sich die Verwendung von "Multiprocessoring" bei PowerUP Boards nicht aus. Aber unter Umständen kann man das AllocXMsg-System schon verwenden, um mal eine kleine Message zwischen den Prozessoren hin und her zu schicken. Zumindest Support dafür ist vorhanden. Aber wie gesagt: Wer nicht genau weiß, was er tut => Finger weg !!!

6. Andere Elemente der powerpc.library

Des weiteren enthält die powerpc.library noch:

  • Hilfsfunktionen PPC-Native (z.B. Listen-Handling)
  • Kontextswitch-Funktionen (StormC macht das aber meistens vollautomatisch, braucht man höchstens zum aufrufen von 68k Assembler-Funktionen, die der automatische Kontextswitch nicht durchführt, oder um Mixed Binaries mit vbcc-WarpOS zu erzeugen, der diese (noch ?) nicht direkt unterstützt).
  • Speichermanagement, inklusive fakultatives Memory-Protection (Es sei darauf hingewiesen: "AllocMem considered harmful". Immer AllocVecPPC verwenden, oder malloc. Und immer schön auf 8 Byte alignen.
  • Lowlevel-Funktionen für MMU, Supervisormodus u.ä. (wichtig etwa für Leute, die einen Mac-Emulator programmieren wollen)
  • PPC Native Timerfunktionen, die direkt die Timerbase-Register des PPC verwenden, dabei aber den Funktionen des timer.device nachempfunden sind (z.B. GetSysTimePPC). - Funktionen, um Informationen über das System anzufordern
  • Funktionen, um das Multitasking zu beeinßussen (z.B. die Nice-Values, die bei einem dynamischen Scheduler wie WarpOS die Rechenzeit für die Tasks beeinßussen
  • Funktionen, die bei der Programmierung eines Debuggers helfen

7. Hooks

Ein weiterer Abschnitt sei den "Hooks" gewidmet. Ein Hook ist eine nützliche Konstruktion, in der eine Funktion eine andere Funktion als Parameter erhält. Systeme wie AHI nützen diese recht extensiv. Leider geht das schief, falls z.B. die Funktion als Parameter PPC ist, die Funktion der Library/des Devices aber 68k. Es klappt einfach nicht, keine Chance. Ein Beispiel wäre AHI_AllocAudioA(), selbst wenn man den Hook-Parameter nicht angibt, geht das schief.

Ein weiterer beliebter Befehl, der dieses Problem hat, ist RawDoFmt(). In Form von SPrintF/SPrintF68k bietet WarpOS Ersatzcode an.

Lösung:

68k und PPC Code zusammenlinken, das komplette AHI-Handling im 68k-Part erledigen. Es wird stark empfohlen, ein MixedBinary zu verwenden, da StormC innerhalb eines MixedBinary die Handhabung für solche Dinge stark erleichtert. vbcc-WarpOS kann das leider noch nicht automatisch, hier muß der Kontextswitch zwischen den beiden Teilen noch manuell programmiert werden. Prinzipiell gilt jedoch das Selbe.

8. Empfehlungen

Es sei im Allgemeinen empfohlen:

  • Möglichst viel (auch möglichst viele Tasks PPC-Native machen)
  • Einen Task nur dann zu einem 68k-Task machen, wenn er als PPC-Task wirklich massivst Kontextswitches enthielte
  • stets synchron arbeiten, asynchrones Arbeiten, wenn immer möglich, vermeiden (aufgrund der Einschränkungen der PowerUP-Hardware). 68k/PPC parallel an einer Aufgabe arbeiten zu lassen, bremst beide Prozessoren aufgrund von "Bushits" massivst runter.
  • Es lohnt sich nicht, den Video-Refresh von einem 68k-Task erledigen zu lassen. Hier am Besten 100% PPC-Native vorgehen
  • Netzwerk-Support könnte sich als 68k-Task lohnen
  • Keyboard/Audio bringen evtl. minimale Gewinne, üblicherweise lohnt es sich jedoch nicht
  • Falls 68k-Tasks vorkommen, am Besten ein MixedBinary verwenden
  • Bei Zugriffen auf globale Variablen LinkerDB nicht vergessen
  • Die Verwendung von Funktionen des ahi.device muß in einem MixedBinary erfolgen (braucht kein Extra Task sein, kann aber... aber es muß in jedem Fall 68k erfolgen)

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT