Mi a gyűrű puffer?

A gyűrűs puffer várólistaként vagy ciklikus pufferként is ismert, és a várólista gyakori formája. Ez egy népszerű, könnyen megvalósítható szabvány, és bár körként van ábrázolva, az alapkódban lineáris. A gyűrűsor rögzített hosszúságú tömbként létezik, két mutatóval: az egyik a sor elejét, a másik pedig a farokot képviseli. A módszer hátránya a rögzített méret. Azoknál a várólistáknál, ahol az elemeket nem csak a puffer elején és végén kell hozzáadni és eltávolítani, a csatolt listaként történő megvalósítás az előnyben részesített megközelítés.

A puffer elméleti alapjai

A puffer elméleti alapjai

A felhasználó számára könnyebb választani egy hatékony tömbszerkezetet, miután megértette az alapul szolgáló elméletet. A ciklikus puffer olyan adatstruktúra, ahol egy tömböt ciklusként dolgoznak fel és jelenítenek meg, vagyis az indexek visszatérnek 0-ra, miután elérték a tömb hosszát. Ez a tömb két mutatójával történik: "fej" és "farok". Amikor adatokat ad hozzá a pufferhez, a fejlécmutató felfelé mozog. Hasonlóképpen, amikor eltávolítják őket, a farok is felfelé mozog. A fej, a farok, a mozgás iránya, az írás és az olvasás helye a rendszer végrehajtásától függ.

A körkörös puffereket túlságosan hatékonyan használják megoldani fogyasztói problémák. Vagyis az egyik végrehajtási szál felelős az adatok előállításáért, a másik pedig a fogyasztásért. A nagyon alacsony és közepes szintű beágyazott eszközökben a gyártó az ISR formátumban (az érzékelőktől kapott információk), a fogyasztó pedig a fő eseményciklus formájában jelenik meg.

A ciklikus pufferek egyik jellemzője, hogy egy gyártó és egy fogyasztó környezetében zárak nélkül kerülnek megvalósításra. Ez ideális információs struktúrává teszi őket a beágyazott programok számára. A következő különbség az, hogy nincs pontos módszer a kitöltött szektor megkülönböztetésére az ürestől. Ez azért van, mert mindkét esetben a fej összeolvad a farokkal. Ennek kezelésére számos módszer és megoldás létezik, de a legtöbbjük sok zavart okoz, és megnehezíti az olvasást.

Egy másik kérdés, amely a ciklikus pufferrel kapcsolatban merül fel. Új adatokat kell kiírnom, vagy felül kell írnom a meglévőket, ha azok megteltek? A szakértők szerint nincs egyértelmű előnye a másiknak, végrehajtása pedig az adott helyzettől függ. Ha ez utóbbinak nagyobb jelentősége van az alkalmazás számára, használja az átírási módszert. Másrészt, ha az érkezési sorrendben kerülnek feldolgozásra, akkor az újakat eldobják, amikor a gyűrűs puffer megtelt.

Ciklikus sor végrehajtása

Az implementáció megkezdése, az adattípusok meghatározása, majd a módszerek: core, push és pop. A "push" és a" pop "eljárásokban a" next " eltolási pontokat arra a helyre számítják ki, ahol az aktuális írás és olvasás megtörténik. Ha a következő hely a farokra mutat, akkor a puffer megtelt, és az adatok már nem íródnak. Hasonlóképpen, ha a" fej "egyenlő a "farokkal", akkor üres, és semmi sem olvasható ki belőle.

Ciklikus sor végrehajtása

Normál használati eset

A kiegészítő eljárást az alkalmazási folyamat hívja meg az adatok kinyerésére a Java gyűrűpufferből. A kritikus szakaszokban szerepelnie kell, ha egynél több szál olvassa a tartályt. A farok az információ olvasása előtt a következő eltolásra mozog, mivel minden blokk egy bájt, és hasonló mennyiség van fenntartva a pufferben, amikor a kötet teljesen betöltődik. De a ciklikus meghajtó fejlettebb megvalósításaiban az egyes partícióknak nem feltétlenül kell azonos méretűnek lenniük. Ilyen esetekben még az utolsó bájtot is megpróbálják menteni további ellenőrzések és határok hozzáadásával.

Ilyen sémákban, ha a farok olvasás előtt mozog, az olvasandó információkat az újonnan kibővített adatok felülírhatják. Általában ajánlott először elolvasni, majd mozgatni a farokmutatót. Először határozza meg a puffer hosszát, majd hozzon létre egy "circ_bbuf_t" példányt, majd rendeljen egy mutatót a "maxlen" - hez. Ebben az esetben a tárolónak globálisnak kell lennie, vagy a veremben kell lennie. Tehát például, ha 32 bájtos gyűrűs pufferre van szüksége, hajtsa végre a következőket az alkalmazásban (lásd. az alábbi kép).

Normál használati eset

A funkcionális követelmények meghatározása

A "ring_t" adattípus olyan adattípus lesz, amely tartalmaz egy mutatót a pufferre, annak méretére, a fejléc és a farok indexére, az adatszámlálóra.

A "ring_init()" inicializáló függvény inicializálja a puffert annak alapján, hogy mutatót kap a tároló szerkezetére, amelyet a hívó függvény előre meghatározott méretű.

A " ring_add ()" Hívás hozzáadása függvény hozzáad egy bájtot a puffer következő rendelkezésre álló helyéhez.

a "ring_remove ()" gyűrűeltávolító funkció eltávolít egy bájtot a tartály legrégebbi érvényes helyéről.

Ring peek a " ring_peek ()" függvény beolvassa a bájtok száma "uint8_t `count `" a gyűrű puffer az új megadott paraméterként, törlése nélkül értékeket olvasni a tárolóból. Visszaadja a ténylegesen olvasott bájtok számát.

A "ring_clear ()" Ring cleanup függvény A "Tail" - t "Head" - re állítja, és a "0" - t betölti az összes pufferpozícióba.

Puffer létrehozása C/C-ben ++

A beágyazott rendszerek korlátozott erőforrásai miatt a ciklikus pufferrel rendelkező adatstruktúrák megtalálhatók a legtöbb rögzített méretű projektben, amelyek úgy működnek, mintha a memória eredendően folytonos és ciklikus lenne. Az adatokat nem kell átrendezni, mivel a memóriát generálják és használják, és a fej/farok mutatókat beállítják. Ciklikus pufferkönyvtár létrehozásakor a felhasználóknak a könyvtár API-jaival kell dolgozniuk, nem pedig közvetlenül a struktúrát kell megváltoztatniuk. Ezért a gyűrűs puffer kapszulázása "C használt". Ily módon a fejlesztő megtartja a könyvtár megvalósítását, szükség szerint megváltoztatja, anélkül, hogy a végfelhasználóknak is frissíteniük kellene.

A felhasználók nem tudnak dolgozni a "circular_but_t" mutatóval, egy leíró típus jön létre, amely helyette használható. Ez kiküszöböli annak szükségességét, hogy egy mutatót dobjon a megvalósításba.a" typedefcbuf_handle_t " függvény. A fejlesztőknek API-t kell készíteniük a könyvtár számára. Az inicializálás során létrehozott átlátszatlan leíró Típus segítségével kölcsönhatásba lépnek a "C" gyűrűs pufferkönyvtárral. Általában válassza az "uint8_t" lehetőséget alapadattípusként. De bármilyen konkrét típust használhat, ügyelve arra, hogy megfelelően kezelje az alappuffert és a bájtok számát. A felhasználók a kötelező eljárások végrehajtásával lépnek kapcsolatba a tárolóval:

  1. Inicializálja a tartályt és annak méretét.
  2. Állítsa vissza a kör alakú tartályt.
  3. Adatok hozzáadása a gyűrűs pufferhez "Si".
  4. Szerezze be a következő értéket a tárolóból.
  5. Információ kérése az elemek aktuális számáról és a maximális kapacitásról.
Állítsa vissza a kör alakú tartályt

Mind a "teljes", mind az "üres" esetek ugyanúgy néznek ki: "fej" és "farok", a mutatók egyenlőek. Két megközelítés van, amely különbséget tesz a teljes és az üres között:

  1. Teljes állam farok + 1 = = fej.
  2. Üres állam fej = = farok.

Könyvtári funkciók megvalósítása

Kör alakú tároló létrehozásához használja annak szerkezetét az állapot kezeléséhez. A kapszulázás megőrzése érdekében a struktúrát a könyvtáron belül határozzák meg ".c " fájl, nem a fejlécben. A telepítés során figyelnie kell:

  1. A base data buffer.
  2. Maximális méret.
  3. A fej jelenlegi helyzete, hozzáadva növekszik.
  4. A jelenlegi farok, eltávolítással növekszik.
  5. Zászló, amely jelzi, hogy a tartály megtelt-e vagy sem.

Most, hogy a tárolót megtervezték, a könyvtári funkciók megvalósulnak. Az API-k mindegyikéhez inicializált pufferleíró szükséges. Ahelyett, hogy eltömítené a kódot feltételes utasításokkal, alkalmazzon utasításokat annak biztosítására, hogy az API követelmények teljesüljenek a.

API Stílus követelmények

A megvalósítás nem lesz szálorientált, ha a zárakat nem adták hozzá a ciklikus tárolók alapkönyvtárához. A tároló inicializálásához az API olyan kliensekkel rendelkezik, amelyek alapvető pufferméretet biztosítanak, így a könyvtár oldalán hozzák létre, például az egyszerűség kedvéért "malloc". A dinamikus memóriát nem használó rendszereknek meg kell változtatniuk az "init" funkciót egy másik módszer használatához, például egy statikus tárolókészletből történő allokációhoz.

Egy másik megközelítés a kapszulázás megszakítása, amely lehetővé teszi a felhasználók számára, hogy statikusan deklarálják a konténerszerkezeteket. Ebben az esetben a "circular_buf_init" - et frissíteni kell, hogy egy mutatót vagy "init" - et készítsen, hozzon létre egy veremszerkezetet, majd adja vissza. Mivel azonban a kapszulázás megszakadt, a felhasználók könyvtári eljárások nélkül megváltoztathatják. A tároló létrehozása után töltse ki az értékeket, majd hívja "Visszaállítás". Mielőtt visszatérne az "init" - ből, a rendszer biztosítja, hogy a tároló üres állapotban legyen.

A tároló üres állapotban jött létre

Adatok hozzáadása és törlése

Az adatok hozzáadása és törlése a pufferből "fej"- és "farok"-mutatókkal történő manipulációt igényel. Amikor hozzáadódik a tárolóhoz, egy új érték kerül beillesztésre az áramba "fej"-a hely és népszerűsítése. Törléskor kapja meg az aktuális értéket "farok"-mutató és elősegíti a "farok". Ha elő kell mozdítanod "farok"-a mutatót, valamint a "fejet" ellenőrizni kell, hogy az érték beillesztése okozza-e "teljes". Amikor a puffer már megtelt, mozgassa a "farokot" egy lépéssel a "fej"előtt.

Adatok hozzáadása és törlése

A mutató előléptetése után töltse ki "teljes"-zászló, egyenlőség ellenőrzése "fej = = farok". Az operátor moduláris használata miatt a" head "és a" tail " értékek visszaállnak "0", a maximális méret elérésekor. Ez biztosítja, hogy a" fej "és a" farok " mindig az alapul szolgáló adattároló érvényes indexei legyenek: "statikus Void advance_pointer (cbuf_handle_t cbuf)". Létrehozhat egy hasonló kiegészítő funkciót, amelyet akkor hív meg, amikor egy értéket töröl a pufferből.

Sablon Osztály Interfész

Annak érdekében, hogy a C++ implementáció bármilyen adattípust támogasson, hajtsa végre a sablont:

  1. A puffer visszaállítása a tisztításhoz.
  2. Adatok hozzáadása és törlése.
  3. A teljes / üres állapot ellenőrzése.
  4. Az aktuális elemek számának ellenőrzése.
  5. A konténer teljes kapacitásának ellenőrzése.
  6. Annak érdekében, hogy a puffer megsemmisítése után ne hagyjon adatokat, használjon C++ intelligens mutatókat annak biztosítására, hogy a felhasználók kezelhessék az adatokat.
Sablon Osztály Interfész

Ebben a példában a C++ puffer A C implementáció logikájának nagy részét utánozza, de az eredmény sokkal tisztább és újrafelhasználható kialakítás. Ezenkívül a C++ tároló a következőket használja "std:: mutex" szál-orientált megvalósítás biztosítása. Egy osztály létrehozásakor adatokat osztanak ki a fő pufferhez, és beállítják annak méretét. Ez kiküszöböli a C megvalósításhoz szükséges általános költségeket. Ezzel szemben a C++ konstruktor nem hívja "Visszaállítás", mivel a tagváltozók kezdeti értékei meg vannak adva, a kör alakú tároló a megfelelő állapotban indul el. A visszaállítási viselkedés a puffert üres állapotba állítja vissza. A C++ ciklikus konténer implementációban a " méret "és a" kapacitás " a sorban lévő elemek számát jelenti, nem pedig a bájtban megadott méretet.

UART STM32 illesztőprogram

A puffer elindítása után be kell építeni az UART illesztőprogramba. Először globális elemként a fájlban, ezért deklarálnia kell:

  • "descriptor_rbd" puffer memória "_rbmem: statikus rbd_t _rbd";
  • "statikus karakter _rbmem [8]".

Mivel ez egy UART illesztőprogram, ahol minden karakternek 8 bitesnek kell lennie, a karakterek tömbjének létrehozása elfogadható. Ha 9 vagy 10 bites módot használ, akkor minden elemnek "u16_t". A tárolót úgy számítják ki, hogy elkerülhető legyen az adatvesztés.

A várólista modulok gyakran statisztikai információkat tartalmaznak, amelyek lehetővé teszik a maximális használat nyomon követését. Az "uart_init" inicializálási függvényben a puffert úgy kell inicializálni, hogy meghívjuk a "ring_buffer_init" - et, és átadunk egy attribútumstruktúrát minden tagnak, amelyhez a tárgyalt értékeket hozzárendeljük. Ha sikeresen inicializálódik, az UART modul a reset-ből származik, a vétel megszakítása megengedett az IFG2-ben.

UART STM32 illesztőprogram

A második funkció, amelyet meg kell változtatni "uart_getchar". A fogadott szimbólum olvasása az UART perifériáról a várólistáról történő olvasás helyébe lép. Ha a sor üres, a függvénynek vissza kell térnie -1. Ezután végre kell hajtania az UART-t, hogy megkapja az ISR-t. Nyissa meg a fejlécfájlt "msp430g2553.h", görgessen le a megszakítási vektor szakaszhoz, ahol találnak egy usciab0rx nevű vektort. Az elnevezés azt jelenti, hogy az A0 és B0 usci modulok használják. Az USCI A0 vétel megszakításának állapota az IFG2-ről olvasható. Ha be van állítva, akkor a zászlót törölni kell, és a fogadó rekesz adatait pufferelni kell a"ring_buffer_put".

UART adattár

Ez az adattár információt nyújt az UART-adatok DMA használatával történő olvasásáról, ha a fogadandó bájtok száma előre nem ismert. A mikrokontrollerek családjában az STM32 gyűrűs puffer különböző módokban működhet:

  1. Lekérdezési mód (nincs DMA, nincs IRQ) - az alkalmazásnak le kell kérdeznie az állapotbiteket, hogy ellenőrizze, elfogadták-e az új karaktert, majd elég gyorsan el kell olvasnia, hogy megkapja az összes bájtot. Nagyon egyszerű megvalósítás, de senki sem használja a való életben. Hátrányok-könnyű kihagyni a fogadott karaktereket az adatcsomagokban, csak alacsony átviteli sebesség esetén működik.
  2. Megszakítási mód (DMA nélkül) - az UART gyűrűs puffer megszakítást vált ki, a CPU pedig az adatfogadás feldolgozására szolgáló segédprogramra vált. A leggyakoribb megközelítés minden alkalmazásban ma, jól működik a közepes sebességtartományban. Hátrányok - a megszakítási feldolgozási eljárást minden fogadott szimbólumra elvégzik, más feladatokat leállíthat nagy teljesítményű mikrokontrollerekben, nagyszámú megszakítással, egyidejűleg az operációs rendszerrel, amikor adatcsomagot kap.
  3. , A DMA mód az adatok átvitelére szolgál az USART RX regiszterből a felhasználói memóriába hardver szinten. Ebben a szakaszban nincs szükség az alkalmazással való interakcióra, kivéve az alkalmazás által kapott adatok feldolgozásának szükségességét. Nagyon könnyen dolgozhat operációs rendszerek. Optimalizált nagy adatátviteli sebesség > 1Mbps és alacsony fogyasztású alkalmazások, nagy adatcsomagok esetén a puffer méretének növelése javíthatja a funkcionalitást.

Végrehajtás ARDUINO-ban

Az Arduino gyűrűs puffer mind a táblák tervezésére, mind a használt programozási környezetre vonatkozik dolgozni. Az Arduino magja az Atmel AVR sorozatú mikrokontroller. A legtöbb munkát az AVR végzi, és sok szempontból az AVR körüli Arduino tábla képviseli a funkcionalitást-könnyen csatlakoztatható érintkezők, USB-soros interfész a programozáshoz és a kommunikációhoz.

Sok a közös Arduino táblák jelenleg használja a ATmega 328 gyűrű puffer, régebbi táblák használt ATmega168 ATmega8. Az olyan táblák, mint a Mega, összetettebb lehetőségeket választanak, mint például az 1280 és hasonló. Minél gyorsabb az esedékesség és a nulla, annál jobb a kar használata. Körülbelül egy tucat különböző Arduino tábla van nevekkel. Különböző mennyiségű flash memóriával, RAM-mal és I/O portokkal rendelkezhetnek AVR gyűrűs pufferrel.

AVR gyűrű puffer

A" roundBufferIndex " változót használjuk tárolásra az aktuális pozíció, és amikor hozzáadódik a pufferhez.

, a tömböt a tömb korlátai korlátozzák

Ezek a kódfuttatás eredményei. A számok egy pufferben vannak tárolva, és amikor megteltek, elkezdik felülírni őket. Így megkaphatja az utolsó N számot.

Utolsó N számok

Az előző példában egy indexet használtunk az aktuális pufferpozíció eléréséhez, mert ez elegendő a művelet magyarázatához. De általában normális, hogy egy mutatót használnak. Ez egy módosított kód, amely index helyett mutatót használ. Valójában a művelet ugyanaz, mint az előző, és az eredmények hasonlóak.

Nagy teljesítményű CAS műveletek

Nagy teljesítményű CAS műveletek

A Disruptor egy nagy teljesítményű könyvtár az üzenetek szálak közötti átvitelére, amelyet az LMAX Exchange fejlesztett ki és nyitott meg néhány évvel ezelőtt. Ők hozták létre ezt a szoftvert kezelni hatalmas forgalom (több mint 6 millió TPS) a lakossági pénzügyi kereskedési platform. 2010-ben mindenkit megleptek azzal, hogy milyen gyors lehet a rendszerük, ha az összes üzleti logikát egy szálon futtatják. Bár az egyetlen szál fontos koncepció volt a megoldásukban, a Disruptor többszálú környezetben működik, és egy gyűrűs pufferen alapul - egy olyan adatfolyamon, amelyben már nincs szükség elavult adatokra, mert újabb és relevánsabb adatok érkeznek.

Ebben az esetben vagy hamis logikai értéket ad vissza, vagy a blokkolás működik. Ha ezen megoldások egyike sem elégíti ki a felhasználókat, akkor lehetséges egy változó méretű puffer megvalósítása, de csak akkor, ha meg van töltve, és nem csak akkor, ha a gyártó eléri a tömb végét. Az átméretezéshez minden elemet át kell helyezni egy újonnan kiosztott nagyobb tömbre, ha azt alapul szolgáló adatstruktúraként használják, ami természetesen drága művelet. Sok van egyéb dolgok, amelyek gyorsabbá teszik a Disruptor-t, például az üzenetek fogyasztása kötegelt módban.

Gyűrű puffer "qtserialport" (soros port) a QIODevice-től örökölt, különféle soros információk fogadására használható, és tartalmazza az összes rendelkezésre álló soros eszközt. A soros port mindig nyitva van exkluzív hozzáféréssel, ami azt jelenti, hogy más folyamatok vagy szálak nem férhetnek hozzá a nyitott soros porthoz.

A gyűrűs pufferek nagyon hasznosak "C programozás", , például értékelheti az UART-on keresztül érkező bájtok áramlását.

Cikkek a témában