Multitask-Datenzugriffs-Synchronisation
Wenn von mehreren Tasks auf dieselben Daten zugegriffen wird, kann es je nach Task‑/Echtzeitkonfiguration vorkommen, dass die Tasks gleichzeitig auf dieselben Daten zugreifen. Wenn die Daten dabei von mindestens einer der Tasks geschrieben werden, können die Daten während oder nach einer Änderung einen inkonsistenten Zustand haben. Um dies zu verhindern, müssen alle konkurrierenden Zugriffe synchronisiert werden, sodass zu einem Zeitpunkt nur von höchstens einer Task auf die gemeinsam genutzten Daten zugegriffen werden kann.
Wenn also von mehreren Tasks auf dieselben Daten zugegriffen wird und bei mindestens einem dieser Zugriffe die Daten geschrieben werden, müssen alle lesenden und schreibenden Zugriffe synchronisiert werden. Dies gilt unabhängig davon, ob die Tasks auf einem oder mehreren CPU Kernen laufen.
WARNUNG | |
Inkonsistenzen und weitere Gefahren durch ungesicherten Datenzugriff Werden konkurrierende Zugriffe nicht synchronisiert, so besteht die Gefahr eines inkonsistenten oder ungültigen Datensatzes. Je nachdem wie die Daten im weiteren Programmverlauf genutzt werden, kann dies ein Fehlverhalten des Programms, eine ungewünschte Achsbewegung oder auch den plötzlichen Programmstillstand zur Folge haben. Abhängig von der gesteuerten Anlage können Schäden an Anlage und Werkstücken entstehen oder Gesundheit und Leben von Personen gefährdet werden. |
Allgemeine Synchronisationsmöglichkeiten zwischen den TcCOM Modulen sind bereits hier beschrieben. Hierbei wurden die folgenden Mechanismen beschrieben:
- Austausch von Daten über das Prozessabbild (IO Mapping), wobei der Zyklusübergang und damit TwinCAT die Synchronität der Daten zwischen den Quellen und Zielen sicherstellt.
- Methoden-Aufrufe über Schnittstellen, in denen CriticalSections genutzt werden können.
- ADS, welches Daten im Sinne eines Transportmediums transferiert und dadurch die Synchronität der Daten sicherstellt.
- gemeinsamer Datenbereich, der über eine CriticalSection zu schützen ist.
Idealerweise sollte jedoch versucht werden die Notwendigkeit einer Synchronisation zu vermeiden, ansonsten ist der einfachste Weg meistens der Austausch über das Prozessabbild, welche die Daten zwischen den Zyklen von den Ausgangs-Prozessabbildern zu den Eingangs-Prozessabbildern kopieren und dadurch für einen konsistenten Zustand sorgen.
Wenn dieses nicht ausreicht und Daten aus unterschiedlichen Kontexten zugegriffen werden müssen, kann einer der folgenden Möglichkeiten genutzt werden.
Es ist dabei wichtig die Task-Kontexte zu unterscheiden, welche die Daten zugreifen sollen. In der TwinCAT Runtime wird zwischen den Windows Kernel Mode Thread-Kontexten (kurz: Windows Kontext) sowie den TwinCAT Realtime Task-Kontexten (RT-Kontext) unterschieden. Während der Initialisierung von TwinCAT Modulen werden die Transitionen IP, PS, SP, PI im Windows Kontext ausgeführt. Die Transitionen SO und OS werden im RT-Kontext ausgeführt.
Das SDK bietet für solche Szenarien entsprechende Synchronisierungsmöglichkeiten. Trotzdem sollten die TcCOM Module in einer Weise konzipiert werden, dass sie zumindest im Windows-Kontext unabhängig voneinander sind und damit ohne Synchronisierungs-Bedarf auskommen. Die Transitionen im RT-Kontext können bei Bedarf CriticalSections nutzen.
Für die CriticalSection und Semaphoren gilt, dass im Fall einer Sperre die Task auf die Freigabe wartet, in der Zwischenzeit der Core für andere Tasks freigegeben wird. Bei CriticalSections erbt die jeweils aktive Task dabei ggf. die Priorität der wartenden Task („Priority Inheritence“).
CriticalSections
Instanzen von Critical Sections werden durch die TwinCAT Realtime angelegt. Diese Instanz kann sowohl im Windows-Kontext wie auch im RT-Kontext initialisiert werden. Genutzt werden kann die Critical Section Instanz nur im RT-Kontext.
Die TwinCAT Realtime nutzt dabei “priority inheritance” um zu verhindern, dass eine Task mit geringerer Priorität indirekt einen Task mit hoher Priorität blockiert.
CCriticalSectionInstance
Die Klasse CCriticalSectionInstance bietet die Schnittstelle an, um Critical Sections zu handhaben und besitzt den nötigen Speicher. Um eine Critical Section anzulegen benötigt die Klasse die Objekt-ID der TwinCAT Realtime Instanz, welche über OID_TCRTIME_CTRL bereitsteht, sowie eine Referenz zu dem TwinCAT Objekt-Server über einen Pointer auf ITComObjectServer.
Methoden:
- CCriticalSectionInstance(OTCID oid=0, ITComObjectServer* ipSrv=NULL);
Der Default Konstruktor, der die Objekt ID des Critical Section Providers initialisiert. Wenn der Pointer zu dem Objekt-Server gegeben wird, wird die Critical Section auch initialisiert. - ~CCriticalSectionInstance();
Der Destruktor löscht die Cricital Section Instanz. - void SetOidCriticalSection(OTCID oid);
Setzt den CriticalSection Provider, welcher durch die TwinCAT Realtime gegeben ist und dessen Objekt ID über OID_TCRTIME_CTRL verfügbar ist. - bool HasOidCriticalSection();
Gibt TRUE zurück, wenn die Obekt-ID zu einem Wert ungleich 0 gesetzt ist – sonst FALSE. Die Objekt-ID wird dabei nicht überprüft, ob sie zu einem Critical Section Provider gehört. - bool IsInitializedCriticalSection();
Gibt TRUE zurück, wenn die Criticals Section erfolgreich initialisiert wurde. - HRESULT CreateCriticalSection(ITComObjectServer* ipSrv);
Wenn die Critical Section initialisiert ist, wird diese gelöscht und eine neue Critical Section initialisiert. Gibt im Erfolgsfall S_OK zurück. Fehlerfälle werden durch die Rückgabe der Fehlercodes angezeigt:
ADS_E_INVALIDPARM | Ungültiger Critical Section Provider |
ADS_E_NOINTERFACE | Die Objekt ID ist auf eine Referenz gesetzt, die kein Critical Section Provider darstellt, also ITcRTime nicht implementiert. |
E_FAIL | Interner Fehler von dem Ciritical Section Provider. |
- HRESULT CreateCriticalSection(OTCID oid, ITComObjectServer* ipSrv);
Initalisiert die Cristical Section. DeleteCriticalSection() muss aufgerufen werden, wenn diese Methode erneut verwendet wird. Rückgabewerte wie in CreateCriticalSection(ITComObjectServer* ipSrv); - HRESULT DeleteCriticalSection(); Critical Section wird gelöscht. Gibt immer S_OK zurück.
- HRESULT EnterCriticalSection(); Blockiert, bis die Critical Section freigegeben wurde.
- HRESULT LeaveCriticalSection();
Verlässt die Critical Section und gibt diese damit wieder frei.
Es wird MAKE_RTOS_HRESULT(OS_CS_ERR) zurückgegeben, wenn der Aufrufende nicht der Besitzer ist.
EnterCriticalSection() und LeaveCriticalSection() müssen im RT-Kontext aufgerufen werden, ansonsten wird folgender Rückgabewert zurückgegeben:
ADS_E_INVALIDCONTEXT | Rückgabewert, wenn Critical Section außerhalb der RT Kontext betreten wird. Die Critical Section wird nicht betreten. |
Wenn diese Methoden genutzt werden, ohne dass die Critical Section initialisiert wurde, wird S_OK zurückgegeben, ohne dass etwas gemacht wird. Alle anderen Methoden können sowohl im RT-Kontext wie auch im Windows-Kontext benutzt werden.
CriticalSections erlauben verschachtelte („nested“) Aufrufe und verlangen für jeden EnterCriticalSection()-Aufruf einen zugehörigen LeaveCriticalSection()-Aufruf. Die Freigabe der Critical Section ergibt sich dann beim Aufruf des letzten LeaveCritcalSection().
Beispiel:
Das Beispiel zeigt eine Nutzung einer CriticalSection um über einen Interface-Methodenaufruf eine gleichzeitigen Zugriff auf ein Datum zu verhindern.
Critical Section Concurrent
Die Klasse CCriticalSectionInstanceConcurrent erlaubt einen konkurrierenden Zugriff (Concurrent access). Dieser kann genutzt werden, wenn mehrere Tasks lesend auf die zu schützenden Daten zugreifen, aber bei einem schreibenden Zugriff alle anderen Zugriffe unterbunden werden müssen.
Methoden:
- CCriticalSectionInstanceConcurrent(UINT concurrent, OTCID oid=0, ITComObjectServer* ipSrv=NULL); Der Parameter “concurrent” definiert die Anzahl der Tasks, die gleichzeitig die Critical Section via CsEnterCriticalSectionConcurrent() betreten können. Wenn “concurrent” 1 ist, arbeitet die Critical Section Concurrent wie eine Critical Section.
Der Wert 0 ist nicht erlaubt und die Critical Section kann dann nicht initialisiert werden. - ~CCriticalSectionInstanceConcurrent(); Der Destruktor löscht implizit die Critical Section
- void SetOidCriticalSection(OTCID oid); Setzt den CriticalSection Provider, welcher durch die TwinCAT Realtime gegeben ist und dessen Objekt ID über OID_TCRTIME_CTRL verfügbar ist.
- bool HasOidCriticalSection(); Gibt TRUE zurück, wenn die Obect-ID zu einem Wert ungleich 0 gesetzt ist – sonst FALSE. Die Objekt-ID wird dabei nicht überprüft, ob sie zu einem Critical Section Provider gehört.
- bool IsInitializedCriticalSection(); Gibt TRUE zurück, wenn die Criticals Section erfolgreich initialisiert wurde.
- HRESULT CreateCriticalSection(ITComObjectServer* ipSrv); Wenn die Critical Section initialisiert ist, wird diese gelöscht und eine neue Critical Section initialisiert. Gibt im Erfolgsfall S_OK zurück. Fehlerfälle werden durch die Rückgabe der Fehlercodes angezeigt:
ADS_E_INVALIDPARM | Ungültiger Critical Section Provider |
ADS_E_NOINTERFACE | Die Objekt ID ist auf eine Referenz gesetzt, die kein Critical Section Provider darstellt, also ITcRTime nicht implementiert. |
E_FAIL | Interner Fehler von dem Critical Section Provider. |
- HRESULT CreateCriticalSection(OTCID oid, ITComObjectServer* ipSrv);
Initialisiert die Cristical Section. DeleteCriticalSection() muss aufgerufen werden, wenn diese Methode erneut verwendet wird. Rückgabewerte wie in CreateCriticalSection(ITComObjectServer* ipSrv); - HRESULT DeleteCriticalSection(); Critical Section wird gelöscht. Gibt immer S_OK zurück.
- HRESULT EnterCriticalSection(); Blockiert, bis die Critical Section freigegeben wurde und exklusiv betreten werden kann.
- HRESULT LeaveCriticalSection(); Verlässt die Critical Section und gibt diese damit wieder frei. Es wird MAKE_RTOS_HRESULT(OS_CS_ERR) zurückgegeben, wenn der Aufrufende nicht der Besitzer ist.
- HRESULT EnterCriticalSectionConcurrent(); Betritt die Critical Section parallel mit anderen Tasks. Die Anzahl der parallelen Zugriffe ist durch den Parameter “concurrent” im Konstruktor definiert. Wenn diese maximale Anzahl erreicht ist, blockiert der Aufruf, bis einer der parallelen Zugriffe beendet wurde.
EnterCriticalSection(), EnterCriticalSectionConcurrent() und LeaveCriticalSection() müssen im RT-Kontext aufgerufen werden, ansonsten wird folgender Rückgabewert zurückgegeben:
ADS_E_INVALIDCONTEXT | Rückgabewert, wenn Critical Section außerhalb der RT Kontext betreten wird. Die Critical Section wird nicht betreten. |
Wenn diese Methoden genutzt werden, ohne dass die Critical Section initialisiert wurde, wird S_OK zurückgegeben, ohne dass etwas gemacht wird. Alle anderen Methoden können sowohl im RT-Kontext wie auch im Windows-Kontext benutzt werden.
Semaphoren
Semaphoren werden genutzt, um den Zugriff auf limitierte Ressourcen zu synchronisieren. Sie können auch genutzt werden um eine Benachrichtigung (Notification Event) zu realisieren. Semaphoren werden durch die TwinCAT Realtime durch die Klasse CSemaphoreInstance bereitgestellt.
CSemaphoreInstance
Die Klasse CSemaphoreInstance bietet die Schnittstelle an, um Semaphoren zu handhaben. Um eine Semaphore anzulegen, benötigt die Instanz die Objekt-ID der TwinCAT Realtime Instanz, welche über OID_TCRTIME_CTRL bereitsteht, sowie eine Referenz zu dem TwinCAT Objekt-Server über einen Pointer auf ITComObjectServer.
Methoden:
- HRESULT SemCreate(WORD nCntInit, OTCID oid, ITComObjectServer* ipSrv); Erstellt die Semaphore mit nCntInit als initalen Wert der bereitstehenden Resourcen. Es wird S_OK bei Erfolg zurückgegeben und E_FAIL wenn die Semaphore nicht erzeugt werden kann.
- HRESULT SemDelete(); SemDelete() löscht die Semaphore. Liefert S_OK zurück.
- HRESULT SemPost() SemPost() erhöht die Anzahl der verfügbaren Resourcen. Wenn ein Task auf die Semaphore wartet, wird der Task mit der höchsten Priorität freigegeben d.h. sie kann ihre Ausführung fortsetzen. Gibt bei Erfolg S_OK zurück. Mögliche Fehlercodes:
MAKE_RTOS_HRESULT(51) | Semaphoren Overflow. Beispielsweise wenn der interne Speicher nicht ausreicht. |
- HRESULT SemPend(OSTICKS nTimeout); Reduziert die Anzahl der verfügbaren Ressourcen; sollten keine Ressourcen zur Verfügung stehen, blockiert die Task an dieser Stelle.
SemPend() wartet auf eine Semaphore, bis diese zur Verfügung steht. Ein Timeout kann in OSTICKS angegeben werden und ist damit Prozessor-abhängig. Das ITcRTime Interface in kann genutzt werden, um diese zu berechnen. Im Beispiel TcSemaphoreSample ist hierfür eine Methode TimeoutMsToTicks verwendet worden. Als spezielle Werte für nTimeout existieren:
RTIME_NOWAIT (-1) | Methode liefert sofort S_OK, wenn die Semaphore verfügbar ist. |
RTIME_ENDLESSWAIT (0) | Methode wartet unendlich lange auf die Verfügbarkeit der Semamphore |
Die Methode liefert S_OK, wenn den Semaphoren erfolgreich bezogen wurde; ansonsten:
MAKE_RTOS_HRESULT(10) | Zeigt einen Timeout an. Wenn nTimeout als RTIME_NOWAIT gesetzt wurde, ist diese Semaphore nicht verfügbar. |
Beispiel: