Mutex-Verfahren (TestAndSet, FB_IecCriticalSection) zum Absichern von kritischen Bereichen

Bei der Verwendung von Mutex-Verfahren bzw. bei der Implementierung eines gegenseitigen Ausschlusses werden die Bereiche, in denen konkurrierende Zugriffe stattfinden, als kritische Bereiche (engl. Critical Sections) bezeichnet. Diese Bereiche können mithilfe der Funktion TestAndSet() oder des Funktionsbausteins FB_IecCriticalSection (beide aus der SPS-Bibliothek Tc2_System) synchronisiert werden, sodass die Bereiche unter gegenseitigen Ausschluss gestellt werden und zu einem Zeitpunkt jeweils nur eine Task auf die gemeinsam genutzten Daten zugreifen kann.

Das Betreten eines kritischen Bereichs kann von einer oder mehrerer Bedingungen abhängen. Zudem können verschiedene kritische Bereiche von jeweils unterschiedlichen Bedingungen abhängen.

Beispiele

 
Bei der Funktion TestAndSet() repräsentiert jeweils ein Flag (Boolesche Variable) eine Bedingung, von der das Betreten des kritischen Bereichs abhängt (z. B. bLockData1). Bei FB_IecCriticalSection wird jeweils eine Instanz des Funktionsbausteins als Sperrbedingung verwendet.

Blockierung

VORSICHT

Zykluszeitüberschreitung durch angehaltene Task

Die Dauer der Taskblockierung kann je nach (Auslastung der) Zykluszeit zur Zykluszeitüberschreitung der Task führen.

  • Achten Sie darauf, dass die kritischen Bereiche sehr kurz gehalten werden, damit es bei der wartenden Task keine Zyklusüberschreitungen gibt. Mehrere wartende Tasks werden entsprechend ihrer Priorität in den kritischen Bereich gelassen.

Daraus ergibt sich der Vorteil der Funktion TestAndSet() gegenüber dem Funktionsbaustein FB_IecCriticalSection, dass eine Task in keinem Fall blockiert wird. Der Nachteil ist wiederum, dass für den Fall, dass die Funktion TestAndSet() keinen Zugriff gewährt, eine alternative Implementierung vorhanden sein muss. Dies könnte beispielsweise über eine Zustandsmaschine realisiert werden, um den Zugriff im nächsten Zyklus erneut zu versuchen.

Verklemmung (engl. deadlock)

Beachten Sie, dass es bei einer ungünstigen Verwendung des Funktionsbausteins FB_IecCriticalSection aufgrund der möglichen Blockierung von Tasks zu Verklemmungen kommen kann. An einer Verklemmung sind immer mindestens zwei Tasks beteiligt. Sie entsteht dann, wenn die Tasks während ihrer Blockierung gegenseitig auf die Freigabe einer weiteren Ressource warten, die die jeweils andere Task bereits gesperrt hat.

VORSICHT

Dauerhafter Taskstillstand durch Verklemmung

Wenn eine so beschriebene Verklemmung einmal eingetreten ist, lässt sich diese nicht mehr programmatisch beseitigen. Es kommt zum dauerhaften Stillstand der beteiligten Tasks.

Beispiel:

Auch bei einer falschen Synchronisation muss nicht zwangsläufig eine Verklemmungssituation eintreten. Es kommt nur dann zu einer Verklemmung, wenn sich die Abläufe zufällig in einer ungünstigen Reihenfolge ereignen.

Vermeidung von Verklemmungen

Generell können Sie Verklemmungen vermeiden, wenn jede Task immer nur eine Ressource zu einer Zeit sperren möchte.

Ebenso können Sie Verklemmungen dadurch vermeiden, dass Ressourcen immer nur in einer bestimmten Reihenfolge angefordert und gesperrt werden.

Beispiel:

Beispielprogramm: Zugriffs-Synchronisation mittels TestAndSet()

Dieses Beispiel zeigt, wie aus verschiedenen Taskkontexten sicher auf gemeinsame Daten zugegriffen werden kann. Die Daten sind in einer Strukturinstanz zusammengefasst. Diese beinhaltet zusätzlich eine boolesche Variable bLocked als Test-Flag.

Bevor Sie auf Daten dieser globalen Strukturinstanz lesend oder schreibend zugreifen, müssen sie den Zugriff auf diese erfragen, indem Sie die Funktion TestAndSet() mit dem entsprechenden Test-Flag aufrufen. Wenn der Zugriff nicht gestattet ist, können Sie in diesem Zyklus nicht auf die Daten zugreifen. In diesem Fall muss applikativ eine Alternativbehandlung vorgesehen werden. Bei Bedarf wird der Zugriff im nächsten Zyklus erneut angefragt. Wenn der Zugriff gestattet ist, entsprechend TestAndSet() erfolgreich aufgerufen wurde, können Sie die Daten lesen oder verändern. Sobald Sie die Bearbeitung abgeschlossen haben, geben Sie den Zugriff wieder frei, indem Sie das Test-Flag mit FALSE belegen.

Funktionstest

Starten Sie das Beispielprogramm. Innerhalb MAIN1 und MAIN2 befindet sich jeweils eine Zählervariable (nLocalBlockedCounter), die bei fehlgeschlagenem TestAndSet()-Aufruf hochzählt. Wenn diese 0 ist, konnte der Datenzugriff auf die globalen Variablen bei jedem Versuch erfolgreich ausgeführt werden.

Um ein Gefühl für die Notwendigkeit der Zugriffs-Synchronisation zu erhalten, können Sie die zwei Tasks von unterschiedlichen CPU-Cores ausführen lassen. Wenn Sie nun das Beispielprogramm starten, dann sehen Sie, wie die Zählervariablen unregelmäßig hochzählen und somit anzeigen, dass der Datenzugriff ab und zu gesperrt war.

Die Zugriffs-Synchronisation ist jedoch nicht nur bei Multi‑Core‑Verwendung notwendig, sondern auch wenn die zwei Tasks auf einer CPU laufen. Durch gegenseitige Taskunterbrechungen kann es hier ebenso zu kritischen Inkonsistenzen bei ungesichertem Datenzugriff kommen.

Download: TC3_PlcSample_MultiTaskSync_TestAndSet.zip

Beispielprogramm: Zugriffs-Synchronisation mittels FB_IecCriticalSection

Mutex-Verfahren (TestAndSet, FB_IecCriticalSection) zum Absichern von kritischen Bereichen 1:

Windows CE

Die Funktionalität vom FB_IecCriticalSection wird unter Windows-CE-Betriebssystemen ab TwinCAT v3.1.4022.29 unterstützt.

Das Beispiel zeigt die Verwendung von Critical Sections in der SPS anhand von Geldtransfers bei Geldkonten. Ein Konto wird durch einen Funktionsbaustein FB_Account repräsentiert. Vier Konten sind beteiligt. Alle vier Funktionsbausteininstanzen sind in einer globalen Variablenliste deklariert, um den Zugriff (hier: Geldtransfer) aus verschiedenen Taskkontexten zu ermöglichen.

Jedes Konto hat zu Beginn einen Kontostand von 1000. Der Kontostand von jedem Konto kann mit den Methoden Get() und Set() ausgelesen und neu gesetzt werden. Die folgenden Geldtransfers sind in vier unterschiedlichen Taskkontexten implementiert.

Task 1: A->B 500

Task 2: B->C 250, B->D 250

Task 3: C->A 250

Task 4: D->A 250

Nach Abschluss dieser Geldtransfers sollte jedes Konto wieder über einen Betrag von 1000 verfügen.

Es muss sichergestellt werden, dass der Zugriff auf ein Konto niemals aus zwei Taskkontexten zugleich geschieht.

Im FB_Account ist der Funktionsbaustein FB_IecCriticalSection verwendet. Die Methode FB_Account.Lock() führt ein Enter() der Critical Section aus und die Methode FB_Account.Unlock() führt ein Leave() der Critical Section aus. Bevor auf den Kontostand eines Kontos zugegriffen werden darf, muss die Methode Lock() erfolgreich ausgeführt werden. Der Zugriff auf dieses Konto ist dann für andere gesperrt.

Um eine Verklemmung (engl. deadlock) zu vermeiden, wird eine Sperrreihenfolge festgelegt. Diese soll folgendermaßen definiert sein:

Sperrreihenfolge: A vor B vor C vor D

Damit ergibt sich als Entsperrreihenfolge: D vor C vor B vor A

Implementierung vom Geldtransfer innerhalb der Task 3:

(* Task 3: C->A 250*)
IF GVL.fbDepotA.Lock() THEN
    IF GVL.fbDepotC.Lock () THEN
        GVL.fbDepotC.Set (GVL.fbDepotC.Get() - 250);
        GVL.fbDepotA.Set (GVL.fbDepotA.Get() + 250);
        GVL.fbDepotC.Unlock();
    END_IF
    GVL.fbDepotA.Unlock();
END_IF

Funktionstest

Starten Sie das Beispielprogramm. Innerhalb Main1 können Sie die Summe aller Kontostände sehen, welche im SPS-Online-Ansicht immer 4000 betragen muss.

Um ein Gefühl für die Notwendigkeit der Verwendung von Critical Sections in diesem Beispiel zu erhalten, können Sie die vier Tasks von unterschiedlichen CPUs ausführen lassen und die globale Variable bIgnoreLock auf TRUE setzen. Wenn Sie nun das Beispielprogramm starten, dann sehen Sie, wie die Kontostände fehlerhafte Werte annehmen und die Summe aller Kontostände ebenfalls eine Fehlfunktion der Geldtransfers belegt.

Die Zugriffs-Synchronisation ist jedoch nicht nur bei Multi-Core-Verwendung notwendig, sondern auch wenn die vier Tasks auf einem CPU-Core laufen. Durch gegenseitige Taskunterbrechungen kann es hier ebenso zu kritischen Inkonsistenzen bei ungesichertem Datenzugriff kommen.

Download: TC3_PlcSample_MultiTaskSync_IecCriticalSection.zip