Mutex procedure (TestAndSet, FB_IecCriticalSection) for securing critical sections

When using mutex procedures or implementing a mutual exclusion, the sections in which competing accesses occur are defined as critical sections. These sections can be synchronized using the function TestAndSet() or the function block FB_IecCriticalSection (both from the PLC library Tc2_System), so that the sections are placed under mutual exclusion and only one task can access the shared data at a time.

Entering a critical section may depend on one or more conditions. In addition, different critical sections can depend on different conditions.

Examples

 
With the TestAndSet() function, a flag (Boolean variable) represents a condition on which entering the critical section depends (e.g. bLockData1). With FB_IecCriticalSection, one instance of the function block is used as a lock condition.

Blockage

CAUTION

Cycle timeout due to stopped task

The duration of the task blocking can lead to the task exceeding the cycle time, depending on the (utilization of the) cycle time.

  • Ensure that the critical sections are kept short, in order to avoid cycle overruns in the waiting task. If several tasks are waiting to enter the critical section, access is granted based on their priority.

This results in the advantage of the TestAndSet() function, compared to the function block FB_IecCriticalSection, that a task is not blocked under any circumstances. The disadvantage is that, in the event that the function TestAndSet() does not grant access, an alternative implementation must be available. This could, for example, be realized via a state machine in order to try access again in the next cycle.

Deadlock

Note that if the function block FB_IecCriticalSection is used unfavorably, the tasks may become locked, deadlocks may occur. A deadlock always involves at least two tasks. It occurs when the tasks are waiting for each other to release another resource that the other task has already locked.

CAUTION

Permanent task standstill due to deadlock

Once a deadlock has occurred as described, it can no longer be eliminated programmatically. The tasks involved come to a permanent standstill.

Example:

Even with incorrect synchronization, a deadlock situation is not inevitable. A deadlock only occurs if the processes occur randomly in an unfavorable sequence.

Avoiding deadlocks

In general, you can avoid deadlocks if each task only wants to lock one resource at a time.

You can also avoid deadlocks by only ever requesting and locking resources in a certain sequence.

Example:

Sample program: Access synchronization using TestAndSet()

This example shows how shared data can be accessed securely from different task contexts. The data is combined in a structure instance. This also contains a Boolean variable bLocked as a test flag.

Before you read or write to data of this global structure instance, you must request access to it by calling the function TestAndSet() with the corresponding test flag. If access is not granted, you cannot access the data in this cycle. In this case, an alternative treatment must be provided for. If necessary, access is requested again in the next cycle. If access is granted and TestAndSet() was called successfully, you can read or modify the data. As soon as you have finished editing, release the access again by setting the test flag to FALSE.

Function test

Start the sample program. Within MAIN1 and MAIN2 there is a counter variable (nLocalBlockedCounter), which increments if the TestAndSet() call fails. If the counter is 0, the data access to the global variables was successfully executed on each attempt.

To get a feel for the need for access synchronization, you can run the two tasks from different CPU cores. If you now start the sample program, you will see how the counter variables are incremented irregularly, indicating that data access was occasionally blocked.

Access synchronization is not only necessary for multi-core use, but also when the two tasks run on the same CPU. Mutual task interruptions can also lead to critical inconsistencies in unsaved data access.

Download:TC3_PlcSample_MultiTaskSync_TestAndSet.zip.

Sample program: Access synchronization using FB_IecCriticalSection

Mutex procedure (TestAndSet, FB_IecCriticalSection) for securing critical sections 1:

Windows CE

The functionality of the FB_IecCriticalSection is supported under Windows CE operating systems from TwinCAT v3.1.4022.29 onwards.

The sample shows the use of Critical Sections in the PLC using money transfers for cash accounts. An account is represented by a function block FB_Account. Four accounts are involved. All four function block instances are declared in a global variable list to enable access (here: money transfer) from different task contexts.

Each account has an initial balance of 1000. The balance of each account can be read and reset using the Get() and Set() methods. The following money transfers are implemented in four different task contexts.

Task 1: A->B 500

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

Task 3: C->A 250

Task 4: E->A 250

Once these money transfers have been completed, each account should have a balance of 1000.

It must be ensured that access to an account never occurs from two task contexts at the same time.

The function block FB_IecCriticalSection is used in the FB_Account. The method FB_Account.Lock() executes Enter() on the Critical Section, the method FB_Account.Unlock() executes Leave() on the Critical Section. Before an account balance can be accessed, the Lock() method must be executed successfully. Access to this account is then blocked for others.

To avoid a deadlock, a locking sequence is defined. This should be defined as follows:

Locking sequence: A before B before C before D

This results in the following unlocking sequence: D before C before B before A

Implementation of money transfer within 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

Function test

Start the sample program. Within Main1 you can see the sum of all account balances, which must always be 4000 in PLC online view.

To get a feel for the need to use Critical Sections in this example, you can run the four tasks from different CPUs and set the global variable bIgnoreLock to TRUE. If you now start the sample program, you will see how the account balances assume incorrect values and the sum of all account balances also indicates a money transfer malfunction.

Access synchronization is not only necessary for multi-core use, but also when the four tasks run on the same CPU core. Mutual task interruptions can also lead to critical inconsistencies in unsaved data access.

Download: TC3_PlcSample_MultiTaskSync_IecCriticalSection.zip