Jak zamezit, aby byla funkce inline

prezek

  • ***
  • 229
    • Zobrazit profil
Jak zamezit, aby byla funkce inline
« kdy: 23. 09. 2013, 14:10:59 »
U mikrokontroleru potřebuju porovnávat 2 čísla (aktuální čas a čas události), která se mohou měnit v přerušení a problém je, když dojde k přerušení v době porovnávání čísel. Proto jsem chtěl porovnávání oddělit funkcí, která zajistí, že se budou proměnné načítat ve správném pořadí. Představa byla takováto:
1) načte se globální proměnná (čas události) z RAM do registru
2) registr se předá funkci k porovnání
3) funkce přebere parametr
4) načte z RAM do registru druhé číslo (aktuální čas v ms)
5) porovná registry a vrátí výsledek.
Problém je v tom, že funkce se mi překládá jako inline a překladač si vybírá jako optimální opačné načítání, než bych si jí přál a tak může dojít ke kolizi. Dá se to nějak vylepšit, aby se tomu zabránilo? (Napevno stanovit pořadí načítání, nebo zabránit aby se funkce vložila jako inline....)
Aktuálně používaná funkce pro zjištění uplynulého času:
Kód: [Vybrat]
u32 getDelay(u32 timeVal) {
    return cas - timeVal;
}


tttttttttt

Re:Jak zamezit, aby byla funkce inline
« Odpověď #1 kdy: 23. 09. 2013, 14:19:20 »
V gcc jde dát funkci atribut noinline, viz http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html

prezek

  • ***
  • 229
    • Zobrazit profil
Re:Jak zamezit, aby byla funkce inline
« Odpověď #2 kdy: 23. 09. 2013, 14:35:30 »
díky

JSH

Re:Jak zamezit, aby byla funkce inline
« Odpověď #3 kdy: 23. 09. 2013, 18:45:37 »
Z toho popisu nejsem úplně moudrý, ale bojím se, že to není úplně ideální řešení. Pro zajištění pořadí načtení dvou hodnot by to fungovat mohlo, pokud překladač nebude páchat nějaká "zvěrstva". C zaručuje při volání funkce jen to, že vedlejší efekty parametrů se provedou dřív než vedlejší efekty těla funkce. O pořadí čtení a zápisů paměti se nedá předpokládat skoro nic. A i ty vedlejší efekty jsou uspořádané jen částečně, protože pořadí, v jakém se vyhodnocují parametry, není vůbec dané.
Navíc pořadí přístupů do paměti může někdy přehazovat i procesor(nevím na čem to poběží).

Daleko čistší řešení je překladačová a paměťová bariéra. http://en.wikipedia.org/wiki/Memory_barrier To je nicnedělající příkaz(nebo i instrukce procesoru), který ale odděluje to, co je před ním a po něm. Překladač ani procesor přes ni nemůžou přehazovat přistupy do paměti. Je to specifické pro různé překladače a architektury. Pokud používáš procesor který sám nic nepřehazuje, pak stačí čistě překladačová bariéra, která se třeba na gcc udělá jako
Kód: [Vybrat]
asm volatile ("" : : : "memory");Ale pořádně bych se koukl do manuálu. Kolem přerušení si člověk nemůže být jistý vůbec ničím.

V C++11 (a myslím že i v novém C) přibyl popis paměťového modelu, který už multiplatformní bariéry obsahuje.

Ještě jedna otázka pro zamyšlení nad problémem :
Proč vadí, když přerušení změní hodnotu právě během porovnávání? Na tu změněnou hodnotu může zareagovat další průchod. Je to stejné, jako by přerušení přišlo o pár tiků později. Přerušení může vždycky přijít těsně po načtení staré hodnoty.

Ovrscout

Re:Jak zamezit, aby byla funkce inline
« Odpověď #4 kdy: 23. 09. 2013, 18:55:40 »
Většinou by mělo sačit označit proměnné jako volatile. Alespoň tam kde je to jednoprocesor-jednojádro kam mikrokontrolery řadím.
Běžně se to používá tře pro přístup k I/O, skus kouknout na makra (případně funkce) které k tomu používáš.

Pokud je to nějaké větší dělo (vícejádrový ARM atp.) tak zřejmně platí to co napsal JSH (tady už nejsem tak zdatný). - ale skusil bych se podívat co se u překladače/knihoven které používáš píše o synchronizaci vláken atp.

Kód: [Vybrat]
volatile u32 timeVal;


Ovrscout

Re:Jak zamezit, aby byla funkce inline
« Odpověď #5 kdy: 23. 09. 2013, 18:58:23 »
omlouvám se za překlepy, jsem už dneska nějaký utahaný ;)
(sačit -stačit) (tře-třeba)

gamer

Re:Jak zamezit, aby byla funkce inline
« Odpověď #6 kdy: 23. 09. 2013, 19:47:29 »
Správné řešení je:
1) Předat funkci ukazatel na čas události
2) Ve funkci zakázat přerušení
3) Načíst čas události
4) Načíst aktuální čas
5) Povolit přerušení
6) Porovnat
Čas události i aktuální čas by měly být volatile. Většina jiných řešení vede na nějakou race condition.

gamer

Re:Jak zamezit, aby byla funkce inline
« Odpověď #7 kdy: 23. 09. 2013, 19:55:48 »
Respektive pokud se čas události nemění asynchronně, tak ho není potřeba do funkce předávat jako ukazatel, stačí hodnotou. Přerušení bych každopádně zakázal.

prezek

  • ***
  • 229
    • Zobrazit profil
Re:Jak zamezit, aby byla funkce inline
« Odpověď #8 kdy: 23. 09. 2013, 20:53:01 »
Na několika různých procesorech (ATmega, ARM Cortex-M3, ARM7, PIC32 ... všechno jsou to jednojádra bez operačního systému) potřebuju vědět, zda už uplynul určitý čas (např. definovaná doba svícení LED, pískání, doba do přechodu do úsporného režimu, samovolný restart po ztrátě komunikace...). Některé jevy vznikají v přerušení (takže ne v hlavní smyčce), ale většinou uplynutí času vyhodnocuju v hlavní smyčce. Pokud vyhodnocuju vypršení jednoduše rozdílem if((casZmacknuti-getCas())>30)...., tak se stává toto:
1) načte se aktuální čas (třeba funkcí getCas, nebo z globální proměnné, která obsahuje aktuální čas) do registru r2 (např. hodnota 127)
2) v přerušení se nastaví hodnota casZmacknuti na aktuální čas (např. hodnota 128), který je větší než čas v registru r2
3) po návratu z přerušení se čas události načte do registru r3
4) odečtou se hodnoty a vyjde 0xffff (při unsigned short), což je větší, než 30 a tím pádem se chybně vyhodnotí vypršení času.
Řešil jsem to několika způsoby: ukládáni časů do pomocných proměnných, dvojitá kontrola vypršení času, kontrola na předběhnutí času události před časem hodin, ...
Zatím nejpřehlednější mi přijde použití funkce na výpočet rozdílu času události a aktuálního času:
Kód: [Vybrat]
u32 getDelay(u32 timeVal) __attribute__((noinline));
u32 getDelay(u32 timeVal) {
    return cas - timeVal;
}

...
if(getDelay(udalostTime)>30)cas Vyprsel();
...
Aktuální čas se u jednotlivých mikroprocesorů a programů liší rozlišením i počtem bitů podle potřeby (někdy potřebuju čas s přeností na jednotlivé takty nejrychlejších hodin mikroprocesoru, jindy stačí milisekundy), ale vyhodnocování by mělo být stejné. Pokud znáte lepší způsob (musí to být hooooodně rychlé a spolehlivé), tak sem s ním.

gamer: Vypínání přerušení jsem se chtěl vyhnout, tak jak to mám, by to snad nemělo být potřeba.

gamer

Re:Jak zamezit, aby byla funkce inline
« Odpověď #9 kdy: 23. 09. 2013, 21:12:39 »
Potřebuješ provést dvě operace:
1) načtení aktuálního času (asynchronně se měnícího)
2) načtení času události (která vznikla taky asynchronně)
Celé tohle dohromady musí být atomická operace. Nestačí mít atomický bod 1 a bod 2, atomicky musí proběhnout obojí dohromady. Takže mi z toho vychází jako nejjednodušší zakázat přerušení...

(Pokud tedy nechceš zauvažovat nad změnou architektury aplikace, většinou totiž stačí při vzniku události v přerušení jen někam poznamenat, že se to stalo a zpracovat to synchronně v hlavní smyčce, až na to přijde řada. Málokdy se jedná o něco tak urgentního, aby to nemohlo pár ms počkat).

JSH

Re:Jak zamezit, aby byla funkce inline
« Odpověď #10 kdy: 24. 09. 2013, 14:32:00 »
(Pokud tedy nechceš zauvažovat nad změnou architektury aplikace, většinou totiž stačí při vzniku události v přerušení jen někam poznamenat, že se to stalo a zpracovat to synchronně v hlavní smyčce, až na to přijde řada. Málokdy se jedná o něco tak urgentního, aby to nemohlo pár ms počkat).
Jenže ta hlavní smyčka bude taky potřebovat vstupy v konzistentním stavu. Takže během jejich načítání musí být taky přerušení zakázané. Pokud teda přerušení neskládají požadavky do nějaké atomické fronty. Pak tu synchronizaci řeší ta fronta a ne zakázaná přerušení.

gamer

Re:Jak zamezit, aby byla funkce inline
« Odpověď #11 kdy: 24. 09. 2013, 16:45:16 »
Jenže ta hlavní smyčka bude taky potřebovat vstupy v konzistentním stavu. Takže během jejich načítání musí být taky přerušení zakázané. Pokud teda přerušení neskládají požadavky do nějaké atomické fronty. Pak tu synchronizaci řeší ta fronta a ne zakázaná přerušení.
V embedded se nějaké synchronizované atomické fronty událostí moc nenosí, většinou si člověk vystačí s atomickým zápisem/čtení/inkrementem/dekrementem integeru nějak takhle:
Kód: [Vybrat]
volatile int event_counter = 0;

void interrupt_handler()
{
    ++event_counter; // toto musi byt atomicky inkrement
}

void hlavni_smycka()
{
    while (event_counter) // toto musi byt atomicke cteni
    {
        --event_counter; // toto musi byt atomicky dekrement
        zpracuj_udalost();
    }
}
Nemusím zakazovat přerušení a nemám žádnou složitou synchronizaci mezi přerušením a hlavní smyčkou.