W dniu 2023-02-14 o 15:39, Piotr Gałka pisze:
Nie wiem co w arm-ach jest ale w avr-ch jest rejestr SREG dostępny normalnie z C. Wystarczy z and-ować z odpowiednią maską i mamy np przeniesienie czyli if (SREG & 0x01) {}.
W dniu 2023-02-14 o 15:39, Piotr Gałka pisze:
Nie wiem co w arm-ach jest ale w avr-ch jest rejestr SREG dostępny normalnie z C. Wystarczy z and-ować z odpowiednią maską i mamy np przeniesienie czyli if (SREG & 0x01) {}.
W dniu 2023-02-14 o 19:06, Janusz pisze:
Prawdopodobnie to ja tu najwięcej i niepotrzebnie mieszam. Wypowiadam się w kwestiach, o których nie wiem prawie nic. P.G.
Mała uwaga: standard C w ogóle nie okresla konkretnego sposobu zapisu liczb typu integer, dając do wyboru trzy różne.
Dlatego tego typu funkcjonalnośc musiała by najpierw zabetonować jeden z nich (2-complemnet) i określić zachowanie takich bitów w różnych operacjach i sekwencach operacji.
Co prawda ani jeden znany mi kompilator nie potrafi pracować w czymś innym niż 2-c, ale standard nie przejmuje się tym, co jest, ale tym, co możliwe.
Są różne koncepcje aby to zabetonować w standardzie, np:
Co z grubsza oznacza potrzebę napisania tego w asm.
I tak powinno być.
C nie jest od dłubania po bitach na poziomie arytmetyki asemblera, ponieważ stabilność bitu przeniesienia może być związana z optymalizacjami czy kolejnością wykonywania wyrażeń. To nie ten poziom abstrakcji.
W dniu 2023-02-14 o 22:42, heby pisze:
Tak jak kompilator wie, że optymalizując nie może zrobić najpierw dodawania a potem mnożenia tak mógłby wiedzieć w którym momencie wymagane jest wyłuskanie określonego bitu.
Na pewno masz rację, ale na przykład skorzystanie z bitu parzystości uprościło by zapis i przyspieszyło działanie procedury:
int crc16(byte* buf,int n,int crc)// doliczenie n bajtów bufora { // Polynomial = x^16+x^15+x^2+1 crc&=0xFFFF; while(n--) { int d=((*(buf++))^crc)&0xFF; // lower crc part int p=d^(d>>4); p^=p>>2; p^=p>>1; // parity bit crc= (crc>>8) ^ (d<<7) ^ (d<<6) ^ ((p&1) ? 0xC001 : 0); } return crc; }
Kiedyś dawno (w ubiegłym wieku) sprawdzałem, że działa szybciej niż standardowy zapis z wewnętrzną pętlą obracania bajtu bit po bicie mimo konieczności wieloetapowego uzyskiwania bitu parzystości. W assemblerze ma jeszcze większą przewagę nad zapisem z pętlą bo się po prostu korzysta z bitu parzystości.
Wtedy szybkość liczenia crc wydawała nam się ważna. Jak potem doszło szyfrowanie i podpisywanie danych w ramce to czas obliczania crc stracił na ważności :)
Jestem prawie pewien, że brat sprawdzał, czy kompilator kompilując na
8-bitowy procesor się zorientuje, że całą jedną linijkę może pominąć i skorzystać z bitu parzystości i prawie na pewno mimo włączonych wszystkich optymalizacji nie zorientował się. P.G.
Programista może być zaskoczony wieloma rzeczami. Czasami jakiś statement zostanie usunięty, czasami zmieni się kolejność, czasami mnożenie zmieni się na przesuniecie itd itp, w dodatku zależy to od flagi -O, dnia tygodnia czy wersji kompilatora.
Przykładowo: wielu niedzielnych programistów embedded jest zakoczonych, że kompialtor może usunąć im jakiś odczyt z rejestru sprzętowego, jeśli nie oznaczą go volatile. A to generuje sideeffect, na przykąłd jak czytamy bufor typu UART.
I nagle chcesz odczytywać wewnatrzny status CPU, co do którego nie ma nawet pewności, że istnieje na jakiejś architekturze sprzętowej, ani jak działa w konkretnym przypadku. To proszenie się o katastrofę. Od tej pory C musiał by być *tylko* na procesory z flagą C i to w dodatku działającą w jakiś specyficzny sposób. A jak jej nie ma wcale?
Dlaczego nie chcesz jej napisać w asm, skoro uważasz, że będzie szybsza niż wygenerowana przez kompilator?
I dlatego wymagane jest napisanie tego w asm, pod konkretną architekturę, ze wszystkimi możliwymi na niej sztuczkami. Kompilatory C są znakomite, ale:
1) programiści embedded często używają jakiejś padliny dostarczonej przez producentów niszowych, typu KailC, z lat 80, więc kod produkowany ma się nijak do współczesnych sztuczek z optymalizacją, szczególnie z C++. 2) Jesli C wzbogaciłby się o tak niskopoziomowe detale, znacząco ograniczyło by to jego abstrakcje w rozumieniu intencji programisty i dużych optymalizacji. Nie chcemy tego, chcemy mieć abstrakcyjny, uniwersalny język, a nie język do programowania migania LEDem w 3 cyklach zegarowych na AVR i niczego innego. 3) Istnieje wiele rzeczy które w C nie istnieją, na przykład nie ma interfejsu do *fence z x86, co powoduje, że wielu ludzi piszących na systemy wieloprocesorowe (a więc już embedded) jest zaskoczonych jak działa cache w cpu i że to ma to znaczenie. Nie ma supportu *fence, ale jest support dla barier, mutexów, lock-free, atomic. Jest to inny poziom abstrakcji, przez co nie musisz miec nawet architektury z *fence aby pisać kod poprawny, który będzie działać z dowolną architekturą cpu, cache itp.Najzwyczajniej, mylenie C z asm jest nie na miejscu. To nie ten poziom abstrakcji.
W dniu 2023-02-15 o 19:50, heby pisze:
Nigdy nic nie pisałem w asm (nawet drobnych wstawek). Mój związek z asm jest tylko taki, że napisałem (lata 90-te) makroassembler dla 8051, który potem był przez wiele lat używany przez mojego brata (i zapewne przez wszystkich używających naszego systemu edukacyjnego DSM-51).
A to crc16() to jest moja procedura używana w Builderze. Ona powstała z przetłumaczenia na C procedury w asm. Z ciekawości zapisałem ją też wtedy w sposób klasyczny i porównałem (szybkość i czy wyniki faktycznie zawsze wychodzą takie same :) ). Ta wyszła szybsza to tamtą usunąłem.
Oczywiście masz rację. Ja tylko napisałem, że bratu (wychowanemu na asm) brakuje w C czasem pewnych rzeczy, ale nie pisałem, że to znaczy, że C powinno je mieć. P.G.
Czyli konkretnie jaki side effect z tym UARTem?
Na przykład, w wielu uartach odczytanie bajtu z bufora wejściowego możliwe jest dokładnie *raz* na każdy odebrany bajt, hardware traktuje taki odczyt jako procedurę odebrania wiadomości i nastepny odczyt może odczytać coś z dalszej pozycji bufora. W ogólnym wypadku, operacja na rejestrze *może* spowodować zmianę stanu hardware. Rejestr to nie jest pamięć.
Taka ciekawostka: MC68000 i MC68020 rózniły się tym, że pierwszy w niektórych instrukcjach asm zapisywał *dwukrotnie* to samo do pamieci, a drugi racz zapisywał a drugi raz odczytywał i w obu wypadkach jedna z tych operacji była bez sensu algorytmicznego. To cecha magiczna magistrali 6[8|5]00/68000, każdy cykl jest dostępem do pamięci i procesor *coś* musi zrobić w każdym cyklu, nawet jak nie miało to sensu, bo nie miał co wymienić z pamięcią.
Był to spory problem, kiedy okazało się, że jeśli ktoś akurat zapisuje do rejestru hardwareowego, który ma sideefect np. uruchamiania DMA, program działał inaczej w zależności od CPU, mimo że efekt końcowy był identyczny z punktu widzenia ASM.
Ten problem z MC680x0 to sprzętowy odpowiednik "problemów" z C, gdzie też pewne operacje mogą pojawić się i zniknąc w wyniku optymalizacji, co nie przeszkadza w modelu pamięciowym, ale może powodować katastrofy w modelu rejestrów sprzętowych. Dlatego jest słowo volatile. I swoją drogą tylko dlatego ;)
heby snipped-for-privacy@poczta.onet.pl> napisał(a):
Nie tylko. Także na wypadek modyfikacji zmiennej przez procedurę obsługi przerwania. W głównym programie w ramach optymalizacji zmienna może zostać skopiowana do rejestru aby obliczenia wykonywać na nim a nie na pamęci. Gdy w tym momencie pojawi się przerwanie, zmodyfikowana zostanie stara kopia zmiennej.
Wpadłeś w pułapkę.
Nie.
volatile nie służy do tego.
Do tego, co piszesz, służa bariery/fence.
Jeśli potraktujesz przerwania jako wątki preemptive, to tak naprawdę piszesz o zagadnieniu dostępu do zmiennych przez kilka watków. Tego zagadnienia *NIE* należy rozwiązywać za pomocą volatile, ono nie powstało do tego i sie do tego NIE nadaje.
heby snipped-for-privacy@poczta.onet.pl> napisał(a):
Możesz podać przykład na ATmegę?
Wiem. Nic o wątkach nie pisałem.
W dniu 2023-02-15 o 19:50, heby pisze:
Akurat brat przyszedł do mnie z pytaniem, jak coś zrobić w C (o tym dalej bo ja nie znam odpowiedzi ale powiedziałem mu, że zapytam mądrzejszych ode mnie) więc wspomniałem mu o tym co tu napisałem o bicie przeniesienia, czy parzystości.
On ma całkiem odmienne zdanie od Twojego. Nie widzi żadnego problemu, aby w C dla zmiennych całkowitych było na przykład pojęcie bitu parzystości. Jak procesor go ma to kompilator korzysta bezpośrednio, a jak nie ma to wtedy wylicza tak jak ja w tej crc16(). Gdzie tu proszenie się o katastrofę? A.... Nie wspomniałem mu o różnych reprezentacjach zmiennych o których pisałeś z czego on (tak jak i ja) całkowicie nie zdaje sobie sprawy. Ale faktycznie są stosowane jakieś inne reprezentacje?
A teraz pytanie brata na które nie znamy odpowiedzi.
Ogólnie to jest pierwsze podejście do procesorów ARM (nie wiem z którym w tej chwili walczy (Silabs EFM32PG22..., EFM32PG23..., a może EFM32TG11..). Zanim użyje procesor po raz pierwszy musi opanować podstawowe działania no i jest właśnie na tym etapie.
On by potrzebował sizeof(funkcja).
Ale jak próbuje to zrobić to dostaje 1. Zasugerowałem, że może jak wstawi etykietę (przypomnieliśmy sobie, że chyba w C coś takiego jest) na nawiasie zamykającym funkcję to uda się policzyć różnicę między jej adresem a adresem początku funkcji. Właśnie mi krzyknął (jego pokój jest piętro niżej), że z zewnątrz funkcji nie ma dostępu do tej etykiety.
Napiszę do czego mu to potrzebne bo czasem może rozwiązanie głównego problemu robi się inaczej niż on kombinuje.
Wczoraj wieczorem wspólnie tłumaczyliśmy dwa akapity datasheet, czy manuala (nie wiem - on mi po prostu podświetlił akapit na ekranie i chodziło o to jak to rozumiemy).
Tam było, że jak się coś robi z programowaniem flasha z wnętrza programu to ogólnie nie ma gwarancji, że wszystko się uda. I to zdanie było ogólne - czyli nawet jak ruszasz inną stronę niż jesteś to może coś nie zadziałać. Nie napisali co dokładnie, ale skoro może coś się nie udać to my tego nie chcemy. Napisali, żeby przekopiować odpowiednią funkcję do RAMu, wywołać ją i z niej uruchomić proces kasowania, czy programowania flasha.
Już opanował wywoływanie funkcji po jej skopiowaniu do RAMu. Z adresem początku sobie radzi, choć mówi, że wskaźnik na funkcję jest zawsze większy o 1 od prawdziwego adresu i ustalając fragment do kopiowania on musi tę jedynkę odejmować. Wszystko już działa, tylko, że na razie rozmiar funkcji bierze z sufitu na zapas.
No i jedyne co brakuje do odhaczenia kolejnej funkcjonalności to ustalenie w jednej funkcji jaki jest sizeof drugiej funkcji.
Może wiesz (lub ktoś inny) jak to się robi. P.G.
Nie. Bo mowa o C ogólnie. Szczególne dla AVR stosujemy sztuczki asemblerowe, nielegalne w danej sytuacji słowa kluczowe itd itp. Sam fakt użycia "przerwania" jest z definicji nieistniejącym bytem w C i wymaga poza-językowych narzędzi, bo sam język nie dostarcza wsparcia dla przerwań wiec trudno tez, aby dostarczał mechnizmy ich wspierania.
Uwaga o volatile dotyczy *języka* C a nie implementacji tego na AVR.
Jeśli pytałbyś o ogólny C++ to zasugeroeałbym okolice:
Mimo to przerwanie jest czymś identycznym z wątkiem preemptive. Ma te same konsekwencje i dla dużych procesorów, szczególnie wielordzeniowych, niesie z sobą dokładnie te same zagrożenia, co wątki. I nie jest tak, że świat kończy się na 8051. Wielordzeniowe procesory embedded to nic specjalnie dziwnego.
Tam, wszyscy programiści od volatile, wybiją sobie zęby o protokoły synchronizacji cache, out-of-order execution itd itp.
PS. Zaznaczam, że nic nie pisałeś o AVR w poprzednim poście, wiec w ogólnym wypadku, volatile nie może i nie powinno być uzywane w celu synchronizacji zmiannych w przerwaniach. W szczególnym, kiedy znasz konkretną architekturę, być może.
A do czego to potrzebne przeciętnemu suwerenowi? Z tej samej przyczyny w C nie ma gotowej funkcji do wygrywania melodyjek na głośniczku podpiętym pod PB.3. Jest dosłownie 3 osoby na świecie z tą potrzebą.
Dodakowo: jak liczysz parzystość?
Czy -0 i +0 są, czy nie, parzyste? Przypadkowo zakładajmy architekturę wspierajacą signed-magnitude albo 1-complement. Oba dopuszczalne w C. W obu występują dwa zera.
W tym, że standard nie definiuje nawet reprezentacji bitowej liczb. Skoro tak, to niby jak miałby określać znacznie głębsze detale jak parzystość, przeniesienie czy cokolwiek detalicznego i związanego z konkretną architekturą?
Nie są w praktyce, poza małymi wyjątkami.
Ale standard nie przejmuje się, czy są stosowane, czy nie. Są dopuszczalne, wiec nie możesz zakładać, że nie mogą istnieć. Standard nie może ich ignorować.
[...]
Czytaj wątek z przed paru dni:
"MCU - start programu z RAM"
Było to omawiane w tym wątku. Ogólnie, jesli ma to związek z programowaniem flasha, to boje się zapytać, czy jakoś się zabezpieczacie prze utratą zasilania w trakcie.
W standardzie jezyka niby nie ma, ale w praktyce czesto jest. Widac potrzebne, tylko ze realizacja rózna w róznych systemach, to nie wsadzili do jezyka.
Wielozadaniowosc tez była różna w wielu systemach. Stad i "nakładki" na języki.
Przeciez to nie tylko C ma ten problem. Wiec jesli są rozwiązania dobre, to mozna je w i C zaimplementowac. Tylko ... schowac pod volatile, czy osobno.
Bo problemow w zaawansowanych systemach istotnie coraz wiecej.
J.
heby snipped-for-privacy@poczta.onet.pl> napisał(a):
Ale ja nic nie mówiłem o cache procesora ani kolejności operacji. Chodzi o problem jaki wprowadza kompilator optymalizując dostęp do zmiennej poprzez przeniesienie jej z RAM-u do rejestru w jakimś fragmencie kodu. Żadne bariery na to nie pomogą. Powszechnie problem ten rozwiązuje się stosując volatile.
To nie musi być AVR. Wspomniałem o nim, bo on nawet nie ma operacji do barier jak np. ARM (DSB, DMB, ISB).
Kogo to obchodzi? Jak na złość w prawie każdym procesorze są przerwania.
A dlaczego nie powinno i co polecasz w zamian? Nie podałeś żadnego argumentu przeciw volatile. Cały czas chodzi o programy bare metal, bez schedulera. volatile jest powszechnie stosowanym oraz polecanym rozwiązaniem problemu optymalizacji na MCU, o którym wspomniałem poprzednio, a doprecyzowałem na początku tego posta, żeby było jasne o co chodzi. I jakoś w Internecie nie widzę polemiki z tym polecaniem volatile, z wyjątkiem Twoim postów. Pomijam oczywiście przypadki używania volatile do innych problemów, do których się nie nadaje, jak atomowość.
Więc mamy kod:
int z = 0;
int main() { while(!z); return 0; }
isr_handler() { z = 1; }
Przy kompilacji z -O0 nie ma problemu, przerwanie przerwie pętlę while. Przy wyższej optymalizacji pętla może czytać kopię zmiennej i przez to nie zauważyć jej modyfikacji. volatile łatwo i szybko usuwa ten problem. Mam nadzieję, że teraz już jest wszystko jasne i w końcu dowiem się jakie straszne efekty spowoduje tutaj wprowadzenie volatile oraz co jest lepszego.
W dniu 2023-02-16 o 13:54, heby pisze:
Przesłałem bratu (on nie czyta Usenetu) wszystkie wiadomości z tamtego wątku. W czasie jak pisałem dalszą część tej wiadomości wstępnie powiedział, że według niego nie ma tam tego co on szuka. Ale rozmawialiśmy tylko zgrubnie (ja siedzę w swoim pokoju a on piętro niżej). Coś mówił o sekcjach, ale że to... coś tam (nie dosłyszałem). Też rzucę okiem, tylko, że tak właściwie nie mam na to czasu.
Chyba powinien się obrazić :)
Napisałeś tak, jakby przeprogramowywanie flasha było jakąś nadzwyczajną czynnością, a to przecież chyba wszyscy robią. Są jeszcze jakieś urządzenia bez upgrade? Do tej pory używaliśmy AtXmega z EEROMem na pokładzie. O ile się zorientowaliśmy to w ARM (które chyba dużo ludzi używa) EEPROM to jakaś egzotyka. Stosowana jest emulacja EEPROM we flashu więc wszyscy, którzy chcą EEPROMu wewnątrz procka muszą programować Flasha.
Od czasu jak tylko przenieśliśmy się z procesorów OTP na flash (jak pojawiły się pierwsze Atmele) jesteśmy moim zdaniem dobrze zabezpieczeni.
Pierwsza linia obrony: Nasze urządzenia zasilane są z zasilaczy z podtrzymaniem bateryjnym (starczającym na kilka godzin). Dopóki nie ma tak długich przerw w zasilaniu to zanik napięcia zasilania urządzenia praktycznie nie zdarza się przez lata.
Druga linia obrony: Jest czujnik napięcia na elektrolicie przed stabilizatorem VCC. Od przerwania z tego czujnika do choćby drgnięcia w dół napięcia VCC mija więcej czasu niż flash potrzebuje na dokończenie rozpoczętego procesu programowania, czy kasowania strony - czyli (pod warunkiem, że procesor po tym przerwaniu wyłączy wszelkie zbędne obciążenia) każda rozpoczęta operacja na flashu zostanie dokończona w komfortowych warunkach. Oczywiście po tym NMI następna operacja nie zostanie rozpoczęta. To zabezpieczenie występuje tylko w urządzeniach, które przechowują we flashu jakieś dane. Na przykład nie ma go w czytnikach RFID. Tam uważamy, że pozostałe zabezpieczenia wystarczą bo tam chodzi tylko o upgrade, a on się skutecznie broni sam.
Trzecia linia obrony: Brown-out, który zadziała po tym wszystkim zabezpiecza przed zgłupieniem procesora, który mógłby wtedy coś nabruździć we flashu.
Różne inne zabezpieczenia, którym trudno nadać kolejne numery:
- ważne informacje we flashu mają sumy kontrolne. Po resecie procesor je sprawdza. Jak się nie zgadza to przyjmuje bezpieczne wartości default.
- informacja o każdym resecie trafia do rejestracji i serwer może po resecie skontrolować całą zawartość urządzenia.
- produkujemy urządzenia przez 'upgrade' z programu testowego to oznacza, że w każdym urządzeniu są praktycznie dwie kopie programu.
- po resecie bootloader sprawdza crc32 programu i jak jest błędne to sprawdza crc32 upgrade'u i ewentualnie wykonuje upgrade.
- przesyłanie upgrade'u nie niszczy programu (on nadal normalnie działa) więc przerwanie przesyłania upgrade'u w dowolnym momencie nie jest problemem,
- dopiero jak przyjdą wszystkie strony upgrade'u jest sprawdzany jego podpis i upgrade jest wykonywany - zakładamy, że jakby upgrade został przerwany w tym trakcie to po resecie crc32 to wykryje i upgrade rozpocznie się od nowa (nie przesyłanie a wykonywanie).
Dotychczas nie zauważyliśmy problemu, którego źródłem byłoby niedokończenie zapisu flasha. P.G.
I to ma zwrocic rozmiar kodu funkcji, czy rozmiar typu zwracanego przez funkcję?
Nie liczylbym na to, ze to zadziała poprawnie.
Ale jak chcecie miec klopoty ... dodajcie do funkcji kolejny argument, ktory przy wywolaniu zwroci róznice miedzy etykietami. Albo gdzies zapamieta, bo przeciez typ funkcji moze byc inny.
Kopiowanie pod inny adres wymaga relokowalnego kodu - czesto taki jest, ale warto by sie upewnic.
Ciekawe, bardzo ciekawe.
A niech wywola gdzies funkcje ze wskaznika, i zobaczy jak to sie skompiluje - tez bedzie cofnięcie o bajt? Chyba, ze przed punktem startowym funkcji jest cos waznego zapisane.
To chyba nie ma.
Napisalbym to w assemblerze, zeby wiadomo bylo, ze nic sie nie sp*, lub postaral sie skorzystac z jakiejs dynamicznie ladowanej biblioteki lub pozostal przy zapasie :-)
:-(
J.
W dniu 2023-02-16 o 15:37, J.F pisze:
Wiedziałem, że w tym miejscu jest niejasne i ze zaraz dalej się wyjaśni.
On dokładnie wie o co chodzi i próbował mi wyjaśnić ale nie do końca ogarnąłem. To nie jest kwestia błędu kompilatora tylko hardware'u i tak ma być. Na tyle na ile zrozumiałem to przy operacji wywołania funkcji (nie na poziomie C tylko na poziomie assemblera) wymagane jest ustawienie najmłodszego bitu adresu na 1. A że wszystkie funkcje umieszczane są pod adresami modulo (nie wiem, coś więcej niż 1) to ustawienie tego bitu na 1 daje adres o 1 większy.
Dzięki. Wygląda na dokładnie jego pytanie. Na razie nie próbowałem zrozumieć odpowiedzi. Przesłałem mu. Zobaczymy. Na razie chwilowo go nie ma (chyba poszedł na obiad, co i ja zaraz zrobię). P.G.
Istnieje wiele metod, które pozwalają dostać co chcesz bez udziału volatile.
Mutexy, external function, sekcje krytyczne, atomiki, interlocked itd itp.
Nie. Powszechnie rozwiązuje się ten problem używając poprawnych wzorców projektowych dostępnych w języku i czasami na danej platformie kompilatora bądź hardware.
Używanie volatile w tym celu pochodzi z czasów, kiedy nie było innych metod.
Volatile jest np. bezsensowne, bo uniemożliwia optymalizacje w miejscach, gdzie jej nie chcesz i niestety również tam, gdzie chcesz. Albo wszystko albo nic.
Na chwile obecną volatile uzyteczne jest tylko przy komunikacji z hardware *oraz* na wyjątkowo zapuszczonych architekturach, gdzie dokłądnie wiesz, co się stanie po jego użyciu.
Przykładowo, na 8051 przy jednoczesnym dostępie do RAMu bajtowo i bitowo, biedy kompilator może źle wyoptymalizować kawałek kodu. Ale to akuratnie taka popieprzona architektura, do której C pasuje w sposób wymagający wbijania go młotkiem, trudo się więc dziwić, że wymaga również przedziwacznych konstrukcji prostujących ten C do czegoś użytecznego na guano 8051.
Więc wyraźnie wyjaśniam, że jeśli chodzi o ogólne uzycie volatile, to jest ono bezsensowne do rzeczy innych niż pamięć rejetrowa urządzeń. Ale w szczególności, w programach do migania diodą na AVR, może być w jakimś stopniu emulacją brakującej funkcjonalności i tam stosując je rozważnie da się dostać namiastkę poprawnej synchronizacji.
Które obsługiwane są przez skrajnie specyficzny kod, zazwyczaj niezgodny z ABI kompilatora, wymagajacy workaroudów typu pre/post i naked. To, że procedure przerwania można napisać w C nie oznacza, że to "normalne" C. To wyjątkowo wyjątkowe kodowanie, zazwyczaj na poziomie OSa, którego zwykłego kodera C nie dotyczy.
User na górze, nawet jeśli wołany jest z wnętrza przerwania, nie widzi róznicy między nim a wątkiem. Katastrofy z volatile i weak memory ordering sa jak najbardziej możliwe, im wyżej w komplikacji procesora wylądujesz ze swoim kodem.
Atomik? Mutex? Sekcja krytyczna? IPC?
Przecież zlinkowałem arykuł, w którym masz jasno wypisane powody i ostrzeżenie.
Volatile działa inaczej, niż mysli 95% programistów C. I o ile w małych systemach z AVRkiem to akurat nie problem, co najwyżej przyczyna marudzenia "panie, jakie te kompialtory złe robio, nawet zopymalizować nie potrafio", to w dużych systemach prowadzi prosto do wybicia sobie zębów. A te "duże systemy" to obecnie coś, co dobija się z hukiem do drzwi embedowców. Czasy 8051, po raz kolejny, jak co roku od 40 lat, minęły. Za chwile powszechne będą RISC-V z kilkoma rdzeniami, w zależnosci od wymogów mocowych, ze złożonymi superskalarnymi potokami i pokręconym cache. Dalej będziesz w nich stosował volatile?
Przerwania to multitasking, taki sam jak w schedulerze preemptive.
W pierdołowatych małych cpu zapewne tak. W dużych absolutnie nie. Pisząc relatywnie duże programy, o dużej złożoności, z masą wątków i wymianą danych między nimi, nie miałem okazji użyć volatile ani razu. Z ciekawostek: w poważnych firmach słowo volatile jest wyłapywane przez linter kodu i wymaga zgody komisji za zielonym suknem.
Bo jej nie szukasz. Google aż krzyczy "nie uzywaj volatile, to nie działa jak myślisz".
Choćby wiki:
"[...]Operations on volatile variables are not atomic, nor do they establish a proper happens-before relationship for threading. This is specified in the relevant standards (C, C++, POSIX, WIN32),[1] and volatile variables are not threadsafe in the vast majority of current implementations. Thus, the usage of volatile keyword as a portable synchronization mechanism is discouraged by many C/C++ groups[...]".
Niezliczona ilość postów/stron wyjasnia, dlaczego volatile nie jest tym, o czym myślisz, że do czego jest.
Serio, nie zauważyłes?
W małym procesorze tak.
Teraz weź duży procesor. Być może Ci zaskoczy, że jeśli to przerwanie to inny wątek na innym rdzeniu, to mimo, że rdzeń zapisze z = 1, to pętla nigdy się nie zakończy. Bo możesz mieć system ze słabą koherencją cache i bez bariery/fence informacja nigdy nie zostanie zsynchronizowana z lokalnymi cache obu rdzeni. Albo ciekawoski z przestawianiem zapisów, kiedy jeden rdzeń widzi zapis w innej kolejnosci niż wykonany w sąsiednim rdzeniu.
Swoją drogą ten problem jest trudny do zauważenia przez przeciętnego wciskacza klawiszy, bo x86 jest wyjątkowo tolerancyjny dla dziadowskiego kodu. Tam to działa przypadkiem i wiele osób ma podejrzenie, że nie bez powodu takie decyzje projektowe podjęto: łatwiej było zaprojektować tolerancyjny procesor niż liczyć na poprawianie miliardów lini kodu po kiepskich programistach.
Powoduje: zablokowanie optymalizacji *całej* zmiennej, wszędzie oraz nie usuwa innych problemów z wątkowością, takich jak weak memory ordering czy synchronizacja cache.
Ogólnie działa tylko na małych systemach, gdzie nie ma tego typu zagrożeń, co powoduje że volatile jest narzędziem workaroudującym prawidłowe metody, a nie metodą samą w sobie.
Na większych sens jest zerowy, poza dostępem do rejestrów.
Jak już musisz mieć niskopoziomowo to wyjasnione, to może zerknij tutaj:
Have something to add? Share your thoughts — no account required.
Ask the community — no account required