Die Echtzeit- und Multitasking-Programmiersprache PEARL

Der Name PEARL steht für Process and Experiment Automation Realtime Language und darf nicht mit Perl (Practical Extraction and Report Language) verwechselt werden.

PEARL ist eine höhere Programmiersprache, die eine komfortable, sichere und weitgehend rechnerunabhängige Programmierung von Multitasking- und Echtzeit-Aufgaben erlaubt, und wurde seit 1977 in verschiedenen Entwicklungsstufen genormt, zuletzt 1998 als PEARL-90 (DIN 66253-2 1998, Berlin, Beuth-Verlag, 1998).

Wichtiger Grundsatz bei der Entwicklung von PEARL war, neben einer möglichst leichten Abbildbarkeit der prozessrechentechnischen Probleme, die einfache Erlernbarkeit für den Programmierer. Jeder, der schon eine prozedurale Programmiersprache kennt, wird sich in sehr kurzer Zeit mit PEARL anfreunden können.

Alle grundlegenden Datentypen und Sprachstrukturen anderer prozeduraler Programmiersprachen sind in PEARL vorhanden (>). PEARL bietet darüber hinaus komfortable Sprachelemente zur Bearbeitung von Multitasking- und Echtzeitaufgaben (>).

Grundlegende Datentypen und Sprachstrukturen

Datentypen:

  • Grunddatentypen
    • Festkomma, Gleitkomma
    • Zeichen, Zeichenketten
    • Bitvariablen, Bitketten
  • Zusammengesetzte Datentypen
    • Strukturen
    • Felder beliebiger Dimension mit vorgebbaren Unter- und Obergrenzen bei den einzelnen Dimensionen.
  • Funktionen, Prozeduren (Parameterübergabe per Value, per IDENT und als Zeiger)
  • typbehaftete Zeiger auf alle Objekte, auch auf Funktionen und Prozeduren
  • Typfreie Zeiger und Type-Casting

Blockstruktur, Gültigkeit von Objekten:

  • Objektdeklarationen innerhalb eines BEGIN-END-Blockes
  • Prozedur- und funktionsweite Objekte
  • Modulweite Objekte
  • Zugriff auf Objekte anderer Module durch globale Spezifikation von Daten
  • Bekanntmachung von Objekten für andere Module durch globale Deklaration

Kontrollstrukturen:

  • Bedingte Anweisungen
    • IF-THEN-ELSE-FIN
    • CASE-ALT-...-ALT-OUT-FIN
  • Wiederholungen
    • FOR-REPEAT-END
    • WHILE-REPEAT-END

Genaue Informationen über den Aufbau von PEARL sind in

  • DIN 66253-2 1998, Berlin, Beuth-Verlag, 1998
  • PEARL 90 - Language Report, Version 2.2., GI-Fachgruppe 4.4.2, Bonn, GI Gesellschaft für Informatik e.V., 1998

enthalten.

up

Bessere Hardwareunabhängigkeit

Um eine Entkopplung von hardwareabhängigen Komponenten, wie z.B. Ein-/ und Ausgabeschnittstellen, zu dem hardwareunabhängigen Programm zu erreichen, wird ein PEARL-Modul in zwei Sektionen unterteilt:

  • Im sogenannten Systemteil, der ab dem Schlüsselwort SYSTEM beginnt, werden die Namen für die hardwareabhängigen I/O-Schnittstellen bekannt gemacht, und ihre Eigenschaften definiert. Auch Interruptquellen sind hier zu definieren.
  • Der sogenannte Problemteil, welcher mit PROBLEM beginnt, enthält beispielsweise Variablen, Konstanten, Tasks und Prozeduren. Tasks und Prozeduren können auf die im SYSTEM-Teil definierten Schnittstellen zugreifen.
up

Multitasking-Anweisungen

Dem Betriebssystem bekannte Tasks können aus einem PEARL-Programm beliebig in ihrem Zustand verändert werden.

  • ACTIVATE Taskname
    Startet die Task mit dem Namen Taskname sofort.
  • TERMINATE Taskname
    Die Task mit dem Namen Taskname wird abgebrochen.
  • SUSPEND Taskname
    Die Task mit dem Namen Taskname wird angehalten.
  • CONTINUE Taskname
    Die angehaltene Task mit dem Namen Taskname wird fortgesetzt.
  • PREVENT Taskname
    Die Task mit dem Namen Taskname, deren Aktivierung mit einem Ereignis gekoppelt wurde (siehe (>) Beispiele), wird wieder ausgeplant. Sie wird also nicht mehr von diesem Ereignis aktiviert bzw. fortgesetzt.
up

Einplanung auf Ereignisse und Zeitpunkte

Die Aktivierung und die Fortsetzung von Tasks lässt sich auch an externe Ereignisse oder Zeitpunkte bedingt ausführen. Einplanungen zu einer festen Uhrzeit, aber auch Einplanungen beim Auftreten äußerer Ereignisse (Interrupts), sind möglich.

Beispiele:

  • ALL 0.00005 SEC ACTIVATE Highspeedregler;
    Zyklische Aktivierung eines Reglers mit einer Frequenz von 20 kHz
  • AT 12:00 ALL 4 SEC UNTIL 12:30 ACTIVATE Mittagspause PRIO 1;
    Zyklische Einplanung, alle 4 Sekunden zwischen 12:00 Uhr und 12:30 mit einer hohen Priorität
  • WHEN Feuer ACTIVATE Loesch;
    Aktivierung der Task Loesch, wenn Interrupt Feuer eintrifft.
up

Tasksynchronisation zur Verhinderung von Inkonsistenzen

Eine Synchronisation von Tasks ist immer dann notwendig, wenn diese Daten gemeinsam nutzen. Die drei folgenden Beispiele zeigen, wie sich mit Hilfe von Semaphor- (>) und Boltvariablen (>) Tasks synchronisieren lassen. Beabsichtigt eine Task A den Zugriff auf einen Datensatz, der gerade von einer TASK B bearbeitet wird, so blockiert das Betriebssystem die Task A solange, bis B die Daten freigibt. Bei Semaphoren erlaubt PEARL an Stelle einer Blockade auch das Testen mit Eintritt bei Freiheit (Schlüsselwort TRY in Beispiel (>)).

Kritische Pfade

Kritische Pfade entstehen, wenn 2 Tasks gleichzeitig auf gemeinsame Objekte zugreifen wollen. Mit Hilfe von Semaphor-Variablen (>) kann ein begonnener Zugriff ungehindert beendet werden, ohne von anderen Tasks unterbrochen zu werden, die ebenfalls zugreifen wollen.

PROBLEM;
  DCL A      CHAR(255);        ! String: 255 Bytes
  DCL SEMVAR SEMA  PRESET(1);  ! Init: Zugriff erlaubt
  DCL FAILED FIXED INIT(0);

T1: TASK;
  DCL B1 CHAR(255);
  REQUEST SEMVAR;    A=B1;  RELEASE SEMVAR;
END;

T2: TASK;
  DCL B2 CHAR(255);
  REQUEST SEMVAR;    A=B2;  RELEASE SEMVAR;
END;

TEST: TASK;
  IF TRY SEMVAR THEN
    RELEASE SEMVAR;  !Semaphor wieder freigeben
  ELSE
    FAILED=FAILED+1;
  FIN;
  ! Selbstaktivierung nach 10 Sekunden
  AFTER 10 SEC ACTIVATE TEST;
END;
up
Producer-Consumer-Schemata

Produziert eine Task A Daten, die zur Weiterverarbeitung durch eine oder mehrere andere Tasks vorgesehen sind, so spricht man von einem Producer-Consumer-Schema. Hier ein Beispiel eines Ringspeichers mit 1024 Zeichen, von denen die lesende Task immer 2 gleichzeitig braucht.

PROBLEM;
  DCL PLATZDA   SEMA PRESET(1024);  ! Init: 1024 frei
  DCL ZEICHENDA SEMA PRESET(0);     ! Init: Am Anfang leer

HOLDATEN: TASK;
  REQUEST ZEICHENDA; REQUEST ZEICHENDA;
  HOLE_ZWEI_ZEICHEN_AUS_DEM_RINGPUFFER;
  RELEASE PLATZDA; RELEASE PLATZDA;
END;

SCHREIBDATEN: TASK;
  REQUEST PLATZDA;
  SCHREIB_EIN_ZEICHEN_IN_RINGPUFFER;
  RELEASE ZEICHENDA;
END;

Producer-Consumer-Schemata entstehen auch bei der Messwerterfassung und der Automatisierung von Fertigungsprozessen.

up
Datenbanken

Möchte der Programmierer erlauben, dass mehrere Tasks gleichzeitig einen Datensatz lesen dürfen (im Beispiel die Tasks LESER1 und LESER2), aber jeweils nur eine schreiben kann, und dies auch nur, falls keine andere liest, sind BOLT-Variablen das Mittel der Wahl.

PROBLEM;
  DCL A CHAR(255);  ! String: 255 Bytes
  DCL BOLTVAR BOLT; ! Default: frei

LESER1: TASK;
  DCL L1 CHAR(255);
  ! ENTER erlaubt, dass alle weiteren Tasks mit ENTER
  ! ebenfalls weiterlaufen  koennen
  ENTER BOLTVAR;    L1=A;   LEAVE BOLTVAR;
END;

LESER2: TASK;
  DCL L2 CHAR(255);
  ENTER BOLTVAR;    L2=A;   LEAVE BOLTVAR;
END;

SCHREIBER1: TASK;
  DCL S1 CHAR(255);
  ! Nach einem RESERVE laeuft eine Task nur weiter,
  ! wenn kein anderer Leser oder Schreiber einen
  ! ENTER/RESERVE durchgefuehrt hat.
  RESERVE BOLTVAR;  A=S1;   FREE  BOLTVAR;
END;

SCHREIBER2: TASK;
  DCL S2 CHAR(255);
  RESERVE BOLTVAR;  A=S2;   FREE  BOLTVAR;
END;
up

Ein- und Ausgabe

PEARL sieht für die verschiedenen Ein- und Ausgabeformen jeweils eigene Schlüsselworte vor. In den folgenden Beispielen steht eine alphic_dation z.B. für eine Festplatte, Terminal, LCD-Display und serielle oder parallele Schnittstelle, eine basic_dation z.B. für eine analoge oder digitale Prozeß-Ein-/Ausgabe.

  • Ausgabe
    • Formatierte Ausgabe
      PUT objekt1, ... TO alphic_dation BY formatliste;
    • Binäre Ausgabe
      WRITE objekt1, ... TO alphic_dation;
    • Ausgabe an Prozeßperipherie
      SEND objekt1, ... TO basic_dation;
  • Eingabe
    • Formatiertes Einlesen
      GET objekt1, ... FROM alphic_dation BY formatliste;
    • Binäres Einlesen
      READ objekt1, ... FROM alphic_dation;
    • Einlesen aus der Prozessperipherie
      TAKE objekt1, ... FROM basic_dation;

Die Größenangaben in einzelnen Formatanweisungen können neben Konstanten auch durch Variablen und Funktionsaufrufe festgelegt werden und müssen nicht schon zur Compilezeit bekannt sein.

up

Spezielle Datentypen

  • CLOCK, DURATION
    Diese beiden Datentypen beschreiben Zeitpunkte und -räume. Bei den zeitlichen Einplanungen treten diese Datentypen ebenfalls auf. In den angegebenen Beispielen (>) ist 12:00 eine Konstante vom Typ CLOCK und 0.00005 SEC eine Konstante vom Typ DURATION. Auch Berechnungen zwischen diesen Datentypen sind möglich, wie die beiden folgenden Beispiele zeigen.
    • durationvar=clockvar-clockvar
    • clockvar=clockvar+durationvar
  • INTERRUPT
    Diese Datentypen müssen im Systemteil eines Modules mit einem Hardware- Interrupteingang verbunden werden. Im Problemteil können sie dann mit den oben beschriebenen Mitteln (>) (WHEN ..) zum Ändern eines Taskzustandes verwendet werden.
  • SEMA
    Semaphor-Variablen dienen zur Synchronisation zwischen verschiedenen Tasks (>). Hauptsächlich dienen sie zum Schutz von Verbunddatenobjekten, die von mehreren Tasks gleichzeitig genutzt werden. Benutzt bei kritischen Pfaden (>) oder Producer-Consumer-Schemata (>). Eine Semaphor-Nachbildung lässt sich ohne Betriebssystem-Unterstützung nicht erreichen, da die gewöhnlichen Hochsprach-Operationen belegen, ggf. blockieren bzw. freigeben oder blockierte Task fortsetzen in einem Multitasking-System nicht unteilbar sind.
  • BOLT
    Boltvariablen erlauben im Gegensatz zu Semaphoren einen gleichzeitigen Lesezugriff auf Daten von mehreren Tasks und blockieren schreibende Tasks, wenn eine andere einen Lese- oder Schreibzugriff begonnen hat (>). Bezüglich der Nachbildung von diesen Variablen ohne Betriebssystemunterstützung gelten die Ausführung zu Semaphoren (>) entsprechend.
up

Basierend auf den ehemaligen Webseiten des IRT Hannover unter Prof. Gerth
Autoren: Thomas Probol, Stefan Eilers, Thorsten Lilge