Elektroda.pl
Elektroda.pl
X
Elektroda.pl
Proszę, dodaj wyjątek dla www.elektroda.pl do Adblock.
Dzięki temu, że oglądasz reklamy, wspierasz portal i użytkowników.

Jak poradzić sobie z atomowością [C]

06 Sie 2011 16:26 1297 7
  • Poziom 27  
    Witam;

    mam taki problem, otóż w moim sterowniku użyłem jednego z Timerów do pracy jako Timeout. W procedurze obsługi przerwania pochodzącego od przepełnienia mam tylko jedno polecenie _co20ms++;

    Zmienną tą używam w bardzo wielu miejscach programu w stylu:

    Kod: c
    Zaloguj się, aby zobaczyć kod


    I teraz mam pytanie. Jeśli zmienna _co20ms jest typu char, to czy istnieje tu jakaś możlwiość że powstanie przekłamanie od braku atomowości ?
    Co się stanie jeśli zmienna ta będzie 2 bajtowa? Jaki wtedy będzie maksymalny błąd spełnienia tego warunku?
    Jak można w sposób najprostszy zaradzić temu problemowi?

    pozdrawiam
  • Poziom 38  
    Jeśli 1 bajtowa nic się nie stanie. 2 bajtowa wystarcza 2 kolejne odczyty które dadzą ten sam wynik lub zastosowanie atomic block.
  • Poziom 27  
    tadzik85 napisał:
    Jeśli 1 bajtowa nic się nie stanie. 2 bajtowa wystarcza 2 kolejne odczyty które dadzą ten sam wynik lub zastosowanie atomic block.


    to jak zmienić tą konstrukcję :

    while (_co20ms < 1000) { rób coś tam} ?

    Czy tak jest dobrze:

    Kod: c
    Zaloguj się, aby zobaczyć kod



    Atomic Block u mnie w kompilatorze nie ma - używam CodeVision.
  • Pomocny post
    Poziom 38  
    Powinno być ok. Możesz też zastosować metodę drugą. Ale jeśli ta zmienna jest char nie ma sensu kombinować.
  • Poziom 34  
    Jeśli zmienna ta będzie jednobajtowa, to problemów z atomowością generalnie nie będzie, pod warunkiem że nie używasz RMW (_co20ms++) poza przerwaniem.

    W przypadku zmiennej dwubajtowej, wszystko zależy od rdzenia: jeśli jest on w stanie odczytać dwa bajty za pomocą jednej instrukcji (np.arm: ldrh), to problemu też nie będzie. W przypadku procesorów 8-bitowych odczyt na pewno będzie składać się z dwóch elementarnych odczytów (np.avr: 2*ld), tak więc odczyt nie będzie już atomowy. W takim przypisanie zera może działać względnie poprawnie (gdy najpierw zapisywany jest dolny bajt), lub całkowicie źle (gdy najpierw zapisywany jest górny bajt: wtedy przerwanie występujące pomiędzy mogło by odczytać np _co20ms==255, przez zwiększenie doszło by do ustawienia górnego bajtu na 1).

    Jeśli licznika używasz w danym do odmierzania czasu dla co najwyżej jednego zadania, warto się zastanowić nad zmodyfikowaniem kodu: jedna zmienna np uint16_t będąca głównym licznikiem, oraz prosta flaga na jednym bajcie, która informuje czy odliczanie trwa. W ten sposób można bardzo uprościć kod:
    Kod: C
    Zaloguj się, aby zobaczyć kod


    Innym dobrym sposobem, jest ograniczenie się z licznikiem do jednego bajtu zawsze zwiększanego o 1 (nawet gdy występuje przepełnienie), ale ten używać do aktualizowania licznika po stronie wątku:
    Kod: C
    Zaloguj się, aby zobaczyć kod

    Zaleta: licznik po stronie wątku może być dowolnie długi, brak kumulowania się błędów.
    Wada: aktualizacja lokalnego licznika musi być wykonywana częściej niż co 255 jednostek czasu.

    W większości przypadków mimo wszystko najlepsza jest po prostu sekcja krytyczna. Ostatni przykład jest bardzo użyteczny, gdy przez "counter" rozumie się wprost wartość odczytaną z licznika: nie trzeba wtedy nawet stosować przerwań, natomiast rozdzielczość jest bardzo duża.


    Trochę się rozpisałem, ale co mi tam...

    -- edit:
    W przypadku AVR'ów szastanie cli() i sei() często stanowi złą praktykę (gdy nie wiadomo, czy wcześniej przerwania były zablokowane czy nie - powinno się przechowywać poprzednią wartość SREG), jednak w przypadku pętli głównej gdzie wiadomo, że przerwania mają być aktywne - można sobie pozwolić.
  • Poziom 27  
    Dziękuję Boski Dialer za mega wypasioną odpowiedź.
    Używam AVRów.
    Podoba mi się ta pierwsza metoda co mi zaproponowałeś, ale mam pytanie czy mogę to zrobić także tak:


    Kod: c
    Zaloguj się, aby zobaczyć kod
  • Pomocny post
    Poziom 34  
    Teraz zauważyłem, że trochę się machnąłem (dwa odejmowania jedynki). Oczywiście, że możesz tak zrobić - ja wydzieliłem dodatkową zmienną ze względu na optymalizację: aby uniknąć dwóch odczytów zmiennej volatile. Różnica będzie w dosłownie czterech czy pięciu cyklach. Jak chcesz, to możesz nawet napisać tak:
    Kod: C
    Zaloguj się, aby zobaczyć kod

    Konsekwencją zwartości jest to, że kod staje się odrobinę mniej czytelny.
  • Poziom 14  
    Witam,

    A może coś takiego:
    Kod: c
    Zaloguj się, aby zobaczyć kod


    Powyższy przykład obrazuje licznik zapętlony. Resetujemy go przy każdym wystąpieniu.
    Można też tak (krócej):
    Kod: c
    Zaloguj się, aby zobaczyć kod


    Konfiguracja zawarta jest w pliku nagłówka.
    Kod: c
    Zaloguj się, aby zobaczyć kod

    TIMER_MIN - oznacza minimalny czas timera w sekundach (wartość tą należy dobrać tak, żeby zminimalizować błąd, najlepiej nie ruszać).
    TIMER_MIN_I - to samo co wyżej tylko zapisane w milisekundach
    TIMER_NUMBER - numer timera procesora
    TIMER_USE_INT32 - 1 jeżeli używamy długich oczekiwań (zwiększony rozmiar timera)

    Obsługiwane procesory to:
    ATmega128
    ATmega2560
    ATmega32

    Dopisanie kolejnych procków to TYLKO skonfigurowanie definicji w pliku H.

    Kod jest zabezpieczony w każdy sposób przed przerwaniami i wielowątkowością:
    Kod: c
    Zaloguj się, aby zobaczyć kod


    Istnieje jeszcze funkcja timerExpired_within_ms. Pomaga ona w określaniu czy timer wygasł określony czas temu (przydaje się w przypadku kiedy nie wiemy ile zajmie wykonanie całej pętli, a funkcja timera jest krytyczna czasowo).

    Wykorzystanie w celach komercyjnych skutkuje postawieniem mi piwa :)

    Pozdrawiam.
    Załączniki: