‟overcq”

Z bloga o OUX/C+

Przełączanie zadań w programie OUX / C+

Podsystem OUX zawiera wbudowaną obsługę zadań wewnętrznych programu pisanego w technologii OUX / języku C+. Zadania są podobne do tzw. wątków (threads), ale nie wymagają obsługi (zewnętrznej) przez środowisko systemu operacyjnego. Ponadto zadania podpadają pod inny model użycia oraz są zintegrowane z raportami, cyklerami i impulsatorami.

W każdym czasie podczas wykonywania się programu napisanego w technologii OUX / języku C+ aktywne jest tylko jedno zadanie, tzn. podsystem OUX używa kooperacyjnego przełączania zadań. Zapewnia to szybkość wykonania programu i brak rywalizacji o zasoby.

Zadania

Pierwszym zadaniem, które nie musi być odrębnie definiowane, jest to, które wykonuje się przy starcie programu procedurą main. Inne zadania potrzebują być zdefiniowane jako procedura ogólnie w następujący sposób:

D( moduł, nazwa_zadania )
{
    I_D
    {
    }
}

Gdzie “I_D” to jest pętla główna zadania wykonywana aż do żądania przez system jego zakończenia, gdy jest wyrzucane (czyli zatrzymywane) z innego miejsca programu.

Tak zdefiniowane zadania tworzy się (uruchamia) następującą instrukcją:

D_M( moduł, nazwa_zadania );

A wyrzuca się (zatrzymuje) instrukcją:

D_W( moduł, nazwa_zadania );

W pętli głównej zadania musi znajdować się co najmniej (i zwykle) jedna instrukcja przełączenia do innego zadania (wybranego automatycznie przez podsystem OUX).

Taką instrukcją jest na przykład instrukcja bezwarunkowego przełączenia “I_B” jak w przykładzie:

D( moduł, nazwa_zadania )
{
    I_D
    {   I_B()
            break;
    }
}

Instrukcja ta (podobnie jak każda instrukcja przełączenia) zawiera blok wykonywany wtedy, gdy zadanie ma być wyrzucone. Tutaj następuje zwykłe opuszczenie pętli głównej “I_D” zadania bez wykonywania jakichkolwiek innych działań. Jakkolwiek na żądanie zakończenia zadania zawsze musi nastąpić opuszczenie tej pętli w jakikolwiek sposób.

Instrukcja bezwarunkowego przełączenia powoduje, że zadanie jest wykonywane przez cały czas, chyba że w tym zadaniu znajdzie się jakiekolwiek oczekiwanie na coś. Czyli by nie marnować czasu procesora na bezustanne wykonywanie zadania należy użyć instrukcji warunkowego przełączenia zadania: w oczekiwaniu na raport albo na określony czas cyklera lub impulsatora.

Raporty

Raport jest to taki obiekt, który sygnalizuje zaistnienie czegoś. Zadanie może czekać na nazwany raport. Raport jest tworzony i wyrzucany przez zadanie, które na niego czeka, jak w przykładzie:

D( moduł, nazwa_zadania )
{   X_M( moduł, nazwa_raportu );
    I_D
    {   X_B( moduł, nazwa_raportu, 0 )
            break;
    }
    X_W( moduł, nazwa_raportu );
}

Instrukcja “X_M” tworzy raport, a instrukcja “X_W” wyrzuca go.

“X_B” jest instrukcją warunkowego przełączenia zadania w oczekiwaniu na raport. Jeśli nadejdzie tak nazwany raport, to zadanie będzie wznowione od tej instrukcji.

Instrukcja ta zawiera trzeci parametr, gdzie można podać wskaźnik zmiennej typu N, by zachowana została w niej liczba zgubionych tak nazwanych raportów przed wznowieniem tego zadania.

W procedurze, w której raport będzie emitowany, deklaruje się go instrukcją “X_A” jak w przykładzie:

X_A( moduł, nazwa_raportu );
X_F( moduł, nazwa_raportu );

Instrukcja “X_F” emituje raport.

Znaczniki stanu

Jakkolwiek wygodniej jest deklarować stany programu, które decydują o emisji bądź nieemisji nazwanego raportu.

W trakcie przetwarzania danych pojawiają się stany, które decydują, że raport powinien być wyemitowany bądź nie. Wtedy należy użyć znaczników stanu. Znaczniki stanu są zwykle nazywane tak samo jak raport, o którym decydują. Na przykład:

B U_L( moduł, nazwa_raportu );
if()
    U_F( moduł, nazwa_raportu );
if()
    U_L( moduł, nazwa_raportu );
X_U( moduł, nazwa_raportu );

Instrukcja “U_L” ustawia znacznik stanu na stan wyłączony, a w powyższym przykładzie jest użyta łącznie z deklaracją zmiennej typu B. Instrukcja “U_F” ustawia znacznik na stan włączony. Natomiast instrukcja “X_U” emituje raport, jeśli tak samo nazwany znacznik jest włączony.

Istnieje również instrukcja “U_R”, która czyta stan znacznika, oraz instrukcja “U_E”, która czyta go, a następnie ustawia na wyłączony. Na przykład:

if( U_R( moduł, nazwa_raportu ))
    ;
if( U_E( moduł, nazwa_raportu ))
    ;

Znaczniki stanu można używać nie tylko do warunkowego emitowania raportu, ale również do oznaczania stanów cząstkowych w obiektach, stanów zapisywanych w postaci pól bitowych jak w przykładzie:

struct
{ unsigned U_R( state, visible ) :1;
}q;
U_L( q.state, visible );
U_F( q.state, visible );

Do tego celu wymyślone zostały standardowe nazwy takie jak:

Cyklery

Cykler wylicza czas, z którym został utworzony. Po wyliczeniu całego czasu instrukcja “Y_B” warunkowego przełączenia zadania w oczekiwaniu na czas cyklera wznawia zadanie od tej instrukcji i wylicza czas od nowa. Na przykład:

D( moduł, nazwa_zadania )
{   I timer = Y_M(czas);
    I_D
    {   Y_B( timer, 0 )
            break;
    }
    Y_W(timer);
}

Instrukcja “Y_M” tworzy cykler z czasem podanym milisekundach, a instrukcja “Y_W” wyrzuca go.

Instrukcja “Y_B” zawiera drugi parametr, gdzie można podać wskaźnik zmiennej typu N, by zachowana została w niej liczba zgubionych upływów czasów przed wznowieniem tego zadania.

Cykler służy do synchronizowania czasu pracy zadania, gdy jest to wyraźnie wymagane, na przykład w programie sekundnika zegara, a nie do losowego spowalniania programu.

Impulsatory

Impulsator jest połączeniem raportu z cyklerem: wylicza czas, z którym został wyemitowany, i wtedy wznawia zadanie od instrukcji “Yi_B” warunkowego przełączenia zadania w oczekiwaniu na czas impulsatora. Na przykład:

D( moduł, nazwa_zadania )
{   Yi_M( moduł, nazwa_impulsatora );
    I_D
    {   Yi_B( moduł, nazwa_impulsatora );
            break;
    }
    Yi_W( moduł, nazwa_impulsatora );
}

Instrukcja “Yi_M” tworzy impulsator, a instrukcja “Yi_W” wyrzuca go.

Natomiast przykład emisji impulsatora jest następujący:

Yi_A( moduł, nazwa_impulsatora );
if()
    Yi_F( moduł, nazwa_impulsatora, czas );
if()
    Yi_L( moduł, nazwa_impulsatora );

Instrukcja “Yi_F” emituje impulsator po czasie podanym w milisekundach. Instrukcja “Yi_L” usuwa emisję impulsatora.