Programozási tippek beágyazott rendszerekhez pic24
Gyakorlatilag minden beágyazott alkalmazás megszakításokat és sok - multitasking és multithreading alkalmazást használ. Az ilyen alkalmazásokat úgy kell végrehajtani, hogy figyelembe veszik, hogy a végrehajtási környezet bármikor megváltozhat. Például egy megszakítás, ami bekövetkezik, az ütemezőt a kontextust magasabb prioritású feladatra váltja. Mi történik, ha a feladatok és a függvények közös változókat használnak? Nagy valószínűséggel feltételezhetjük, hogy lesz katasztrófa - az egyik feladat elriasztja egy másik feladat adatait. Kb. Természetesen ugyanez mondható el a hagyományos megszakítási változókról és a fő hurokról a hagyományos, egy feladatú programokban.
A beágyazott rendszerek világában a visszatérő funkciónak a következő szabályoknak kell megfelelnie:
1. szabály. Ez a függvény atomos hozzáférést biztosít a globális változókhoz, vagy minden egyes függvényhíváshoz egy változót hoz létre
2. szabály. A funkció nem okoz nem-visszatérő funkciókat
3. szabály. A függvény atomos hozzáférést biztosít a perifériás modulokhoz
Az 1. és a 3. szabályban megtalálható az "atomi" szó, amely a "oszthatatlan" görög szóra épül. A programozás során az "atomi" kifejezés olyan műveletekre szolgál, amelyek nem szakíthatók meg. Tekintsük az utasításokat:
Ez atomikus, hiszen a visszaállítás nem más, mint megszakíthatja az utasítás végrehajtását - az utasítás végrehajtása függetlenül más feladatoktól vagy megszakításoktól.
Az 1. szabály első része megköveteli a globális változók atomfunkcióját. Hagyja, hogy a két függvény használja a globális globális változó foobar. Ha az A funkció tartalmazza a kódot
akkor nem reentrant, hiszen a változó foobárhoz való hozzáférés nem atomi, mert a foobár megváltoztatásához három helyett akciót kell használni. Ez a kód megszakíthatja a megszakítást, amelyben a B függvényt hívják meg, amely szintén végrehajt valamilyen műveletet a foobárral. Miután visszatér a megszakításból, a temp érték nem felel meg a foobár aktuális értékének.
Van egy konfliktus, a gép leesik és több száz ember kiabál: "Miért senki sem tanította ezt a kibaszott programozót, hogy újrahasznosító funkciókat használjon?" Azonban feltételezzük, hogy az A függvény másképp néz ki:
Most a művelet atomikus, a megszakítás nem állítja le a műtétet a középső foobáron, a konfliktus nem merül fel, akkor ez a művelet újra megnyugszik.
Várjon ... Tényleg tudjátok, mit fog generálni a C fordítója ehhez a művelethez? Egy x86-os processzoron ez a kód így néz ki:
ami persze nem atomos, tehát a foobar + = 1 kifejezés; nem reentrant. Az atomi változat így fog kinézni:
Az erkölcs: ez nagyon óvatos az előírásban az atomkód generálásával kapcsolatban. Ellenkező esetben a "Maximum" program újságírói a te ajtód alatt találod meg: "Ki bűnös a Phopop Kirkorov moped fékének meghibásodásáért?". Általánosságban elmondható, hogy mindenképpen abból kell kiindulnia, hogy az ilyen kifejezésekhez a fordító mindig nem atomi kódot generál
Az 1. szabály második része azt mondja, hogy ha a függvény nem atomos hozzáférést használ, akkor minden egyes függvényhíváshoz létre kell hozni az adatokat. A "hívás" alatt egy funkció azonnali végrehajtását értjük. A multitasking alkalmazásoknál a funkciót több feladat is egyszerre hívhatja meg.
Tekintsük a következő kódot:
A foo egy globális változó, amelynek hatóköre kívül esik a some_function () függvényen. Még akkor sem, ha nincs más funkció. az adatok sérülhetnek, ha néhány_funkciót () hívnak különböző feladatokból (és persze egyszerre hívhatóak le).
A C és a C ++ védelmet nyújt ebből a veszélyből. Egy helyi változót használunk. Ie definiálja a foo funkciót. Ebben az esetben minden hívás some_function () fogja használni a változó osztottak ki a verem (azaz természetesen nem vonatkozik a PIC16 és PIC18. Az utóbbi igaz, akkor a fordítóprogram Microchip C18, ami végre egy szoftvercsomagot, és így beugró. De ha szükséges, ő ott van?).
Egy másik lehetőség a memória dinamikus elosztása. Igaz, meg kell győződnie arról, hogy a dinamikus elosztás könyvtári funkciói visszatértek. Általában nem. Mint a helyi változók esetében, mindegyik hívásra minden memóriahívást generálnak, így megoldva az újbóli beilleszkedés alapvető problémáját.
A 2. szabály szerint a hívófüggvény örökli a visszahívó hiányát a hívásban. Vagyis, ha az "A" funkcióból meghívott B függvény elrontja az adatokat, értelmetlen az A visszatéréséről beszélni.
A magas szintű nyelvekről származó fordítók használatával bonyolult probléma merül fel. Biztos vagy benne, hogy a fordítókönyvtárak visszatérnek? Nyilvánvaló, hogy a karakterlánc (a C-hez nem érvényes) és más összetett műveletek hívási funkciókat tartalmaznak a fordítókönyvtárból. A legtöbb fordító létrehoz egy könyvtári függvényt a matematikai műveletekhez, még a triviális sokszorosításhoz és az egészek osztásához is.
Ha a könyvtári funkciókat használó kódnak újra be kell érnie - konzultáljon ezekkel. támogatja a fordító gyártóját, és olvassa el a kézikönyvet - általában arról írnak. Külön figyelmet kell fordítani a külső könyvtárak - protokollkészletek, fájlrendszerek stb.
A 3. szabály csak a beágyazott rendszerekre vonatkozik. A perifériát globális változóként lehet tekinteni, és ha több műveletre van szükség a perifériás modul fenntartásához, megosztási problémát kaphat.
Mi a legjobb módja annak, hogy a funkció újra belépjen? Természetesen ne használjon globális változókat.
Általában a globális változók nagymértékben bonyolítják a kód hibakeresését, és finom hibákat okoznak. Próbáljon helyi változókat használni vagy dinamikusan felosztani a memóriát.
A globális változók azonban a leggyorsabb módja az adatok átvitelének. Általánosságban elmondható, hogy lehetetlen teljesen megszabadulni a globális változók beépített valós idejű rendszerekben. Ezért a megosztott erőforrások (globális változók vagy perifériák) használata során különböző módszereket kell használnunk az atomicitás biztosítására.
A leggyakoribb mód az, ha a megszakításokat letiltja, miközben végrehajtja a nem visszatérő kódot. Ha a megszakítások le vannak tiltva, akkor a multitask rendszer egyetlen feladatra vált. Valójában ez nem teljesen igaz - ki akadályozza az interrupt RTOS szolgáltatáskapcsolási kontextus megszakítását? Kapcsolja ki a megszakításokat, hajtsa végre a kód nem reentrant részét, engedélyezze a megszakításokat. Gyakran ez így néz ki:
Ez a kód azonban veszélyes! Ha a do_something () egy olyan globális függvény, amelyet különböző helyekről hívnak le, akkor bizonyos pontokon tiltott megszakításokkal hívható. A visszatérés előtt a megszakítások engedélyezettek, a kontextus megváltozik. Egy nagyon veszélyes módszer, amely súlyos problémákhoz vezethet.
És ne használja a régi kifogást: "Igen, de amikor írom a kódot, nagyon óvatos vagyok, csak akkor hívom ezt a funkciót, ha biztos vagyok benne, hogy a megszakítások megengedettek." A programozó, aki kicseréli Önt (és ilyen kifogásokkal nagyon hamar megtörténhet) nem ismerheti az ilyen súlyos korlátozásokat (különösen azért, mert alig tettétek a dokumentációba).
A következő funkció sokkal jobbnak tűnik:
A megszakítások tilalma növeli a rendszer válaszidejét a külső eseményekre (ami gyakran egyszerűen elfogadhatatlan). Egy lágyabb módja annak, hogy biztosítsuk a visszaemelkedõ résztvevõt, hogy szemaforákat használjon. jelezve az erőforrás alkalmazását. A szemafor a bináris indikátor a "on-off" típusú, amelyhez hozzáférést atomikusan végeznek el. A semaphore-t olyan engedélyjelzőként használják, amely a feladatot üresjáratban tartja, míg a szükséges erőforrás foglalt.
Szinte minden kereskedelmi RTOS-nak van egy "szemafor" típusú szinkronizációs objektuma (gyakran egy szimultán hozzáférést biztosító semafor, mutexnek nevezik, és a mutexnek további tulajdonságai vannak). Ha az RTOS-t használja, akkor a szemaforák az utat nyitják vissza. Ne használja az RTOS-t? Gyakran látok olyan programokat, amelyek egy változójelzőt használnak egy erőforrás védelme érdekében:
Nézd egyszerű és elegáns, de ez a kód veszélyes!