XC (programozási nyelv) - XC (programming language)
| Paradigma | egyidejű , párhuzamos , elosztott , többmagos , valós idejű , elengedhetetlen |
|---|---|
| Először jelent meg | 2005 |
| Gépelési fegyelem | erős , statikus |
| Fájlnévkiterjesztések | .xc |
| Főbb megvalósítások | |
| xcc | |
| Befolyásolta | |
| C , occam , CSP | |
A számítógépek, XC egy programozási nyelv valós idejű beágyazott párhuzamos processzorok, célzó XMOS XCore processzor .
Az XC elengedhetetlen nyelv, amely az occam párhuzamosságának és kommunikációjának jellemzőin , valamint a C szintaxisán és szekvenciális jellemzőin alapul . Olyan primitív szolgáltatásokat nyújt, amelyek megfelelnek a különféle építészeti erőforrásoknak, nevezetesen: csatorna végek, zárak, portok és időzítők.
Az XCore processzorokkal kombinálva az XC-t olyan beágyazott rendszerek kiépítésére használják, amelyek szintje I / O, valós idejű teljesítmény és számítási képesség, amelyet általában a terepi programozású kaputömbökhöz (FPGA) vagy az alkalmazás-specifikus integrált áramkör (ASIC) eszközökhöz rendelnek.
Bevezetés
Építészeti modell
Egy XC program végrehajtja az XCore csempék gyűjteményét . Minden csempe egy vagy több feldolgozó magot és erőforrást tartalmaz, amelyek megoszthatók a magok között, beleértve az I / O-t és a memóriát. Az összes csempét egy kommunikációs hálózat köti össze, amely lehetővé teszi bármely csempe kommunikálását bármely más csempével. Egy adott célrendszert megadnak a fordítás során, és a fordító biztosítja, hogy elegendő számú csempe, mag és erőforrás álljon rendelkezésre a fordítandó program végrehajtásához.
Az XC jellemzői
A következő szakaszok az XC legfontosabb jellemzőit mutatják be.
Párhuzamosság
Az XC-ben szereplő utasításokat sorrendben hajtják végre (ugyanúgy, mint C-ben), így:
f(); g();
a függvény gcsak akkor kerül végrehajtásra, ha a funkció fvégrehajtása befejeződött. Az utasítások halmaza párhuzamosan végrehajtható egy parutasítás segítségével úgy, hogy
par { f(); g(); }
okokat fés gegyszerre kell végrehajtani. A párhuzamos utasítás végrehajtása csak akkor fejeződik be, amikor az egyes összetevő utasítások elkészültek. Az összetevő utasításokat XC-ben feladatoknak nevezzük .
Mivel a változók megosztása faji viszonyokhoz és nem determinisztikus viselkedéshez vezethet , az XC a párhuzamos diszjunktust érvényesíti . A diszjunktivitás azt jelenti, hogy az a változó, amelyet az egyik komponens utasításában megváltoztattak, parnem használható más utasításban.
Párhuzamos utasítások írhatók egy replikátorral , egy forhurokhoz hasonló módon , így egy feladat sok hasonló példánya létrehozható anélkül, hogy mindegyiket külön kellene írni, így az utasítás:
par (size_t i=0; i<4; ++i)
f(i);
egyenértékű:
par { f(0); f(1); f(2); f(3); }
A párhuzamos utasításban szereplő feladatokat szálak létrehozásával hajtják végre az utasítást végrehajtó processzoron. A feladatok különböző csempékre helyezhetők egy onelőtag használatával. A következő példában:
par {
on tile[0] : f();
par (size_t i=0; i<4; ++i)
on tile[1].core[i] : g();
}
a feladatot fa 0. csempe bármelyik elérhető magjára és a feladat példányait az g1. csempe 0., 1., 2. és 3. magjára helyezzük. A feladat elhelyezése mainegy XC program funkciójára korlátozódik . Fogalmilag ez azért van, mert amikor egy XC programot lefordítanak, akkor a legfelső szinten fel van osztva külön-külön futtatható programokra minden egyes csempéhez.
Kommunikáció
A párhuzamos feladatok képesek egymással kommunikálni interfészek vagy csatornák segítségével .
Interfészek
Az interfész tranzakciótípusokat határoz meg , ahol mindegyik típust paraméter- és visszatérési típusokkal rendelkező függvényként határozzák meg. Ha két feladat kapcsolódik egy interfészen keresztül, az egyik szerverként , a másik pedig kliensként működik . Az ügyfél képes tranzakciót kezdeményezni a megfelelő szerverrel, a szokásos függvényhíváshoz hasonló szintaxissal. Ez az interakció távoli eljáráshívásnak tekinthető . Például a párhuzamos utasításban:
interface I { void f(int x); };
interface I i;
par {
select { // server
i.f(int x):
printf("Received %d\n", x);
break;
}
i.f(42); // client
}
az ügyfél a f42-es paraméterértékkel kezdeményezi a tranzakciót az interfészről i. A szerver megvárja a tranzakciót (esetenként a select utasításban ), és válaszol, amikor az ügyfél kezdeményezi, a kapott paraméterértékű üzenet kinyomtatásával. A tranzakciós függvények kétirányú kommunikációra is használhatók referenciaparaméterek használatával, lehetővé téve az adatok kliensről egy szerverre történő átvitelét, majd vissza.
Az interfészeket csak két feladat használhatja; nem engedik, hogy több kliens kapcsolódjon egy szerverhez. A fajta két végén Interfész kapcsolat típusú T jelentése szerver interfész T és kliens interfész T . Ezért amikor az interfésztípusokat paraméterként adják át, meg kell adni a kapcsolat típusát is, például:
interface T i;
void s(server interface T i) { ... }
void c(client interface T i) { ... }
par {
s(i);
c(i);
}
Az interfész tranzakciós funkciói arra korlátozzák a kiszolgálókat, hogy csak az ügyfél kéréseire reagáljanak, de bizonyos esetekben hasznos, ha a kiszolgáló képes kiváltani az ügyfelek válaszát. Ezt úgy érhetjük el, hogy az interfészben egy paramétert és egy void return típust annotálunk [[notification]] slave. Az ügyfél egy select utasításban várja az értesítési tranzakciót, hogy a szerver kezdeményezze azt. Megjegyezhető egy megfelelő függvény [[clears_notification]], amelyet a rabszolga hív meg az értesítés törléséhez. A következő egyszerű példában:
interface I {
void f(int x);
[[notification]] slave void isReady();
[[clears_notification]] int getValue();
};
interface I i1, i2;
par {
for (size_t i=0; i<2; ++i) { // server
select {
i2.f(int x):
i1.isReady();
break;
i1.getValue() -> int data:
data = 100;
break;
}
}
{ int d; // client 1
select {
i1.isReady():
d = i1.getValue();
break;
}
}
i2.f(42); // client 2
}
amikor a 2. kliens kezdeményezi a tranzakciós funkciót f, a szerver a tranzakciós függvényen keresztül értesíti az 1. ügyfelet isReady. Az 1. ügyfél megvárja a kiszolgáló értesítését, majd elindítja, getValueamikor megkapja.
Annak érdekében, hogy sok klienst könnyebb egy szerverhez csatlakoztatni, az interfészeket tömbként is deklarálhatjuk. A szerver indexváltozó segítségével választhat egy interfész tömbön keresztül.
Az interfészek kibővíthetők, így az alapvető kliens interfészek új funkcionalitással bővíthetők. Különösen az ügyfélfelület-kiterjesztések hívhatnak tranzakciós függvényeket az alapfelületen, hogy további bonyolultságot biztosítsanak.
Csatornák
A kommunikációs csatornák primitívebb módot kínálnak a feladatok közötti kommunikációra, mint az interfészek. Egy csatorna két feladatot kapcsol össze, és lehetővé teszi számukra az adatok küldését és fogadását a <:be- és kimeneti :>operátorok segítségével. Kommunikáció csak akkor történik, ha egy bemenetet egy kimenettel párosítanak, és mivel bármelyik fél megvárja, amíg a másik készen áll, ez a feladatok szinkronizálását is eredményezi. A következőkben:
chan c;
int x;
par {
c <: 42;
c :> x;
}
a 42 értéket a csatornán keresztül küldjük, cés hozzárendeljük a változóhoz x.
Streaming csatornák
Egy streaming csatorna nem igényel minden bemenetet és egyező kimenetet a szinkronizáláshoz, így a kommunikáció aszinkron módon történhet.
Eseménykezelés
A selectnyilatkozat megvárja az események bekövetkezését. Hasonló az occamban zajló váltakozási folyamathoz . A select minden alkotóeleme esemény, például interfész tranzakció, csatorna bemenet vagy port bemenet (lásd: #IO ), és egy társított művelet. A kiválasztás végrehajtásakor megvárja az első esemény engedélyezését , majd végrehajtja az esemény műveletét. A következő példában:
select {
case left :> v:
out <: v;
break;
case right :> v:
out <: v;
break;
}
a select utasítás egyesíti a csatornáról érkező adatokat leftés rightcsatornákat out.
Egy kiválasztott eset védhető , így az eset csak akkor választható ki, ha az őr kifejezés kifejezése igaz, ugyanakkor az esemény engedélyezve van. Például egy őrrel:
case enable => left :> v:
out <: v;
break;
a fenti példa bal oldali csatornája csak akkor adhat be adatokat, ha a változó enableigaz.
Az események kiválasztása tetszőleges, de az esemény prioritása kikényszeríthető a [[ordered]]selects attribútummal. Ennek az a következménye, hogy magasabb prioritású események korábban fordulnak elő a nyilatkozat törzsében.
Az újrafelhasználható összetevők és könyvtárak létrehozásának elősegítése érdekében a select funkciók segítségével a select több esete egyetlen egységbe elvonható. A következő select függvény összefoglalja a fenti select utasítás eseteit:
select merge(chanend left, chanend right, chanend out) {
case left :> v:
out <: v;
break;
case right :> v:
out <: v;
break;
}
hogy a select utasítás megírható legyen:
select {
merge(left, right, out);
}
Időzítés
Minden csempén van egy referencia óra, amelyhez időzítő változókon keresztül lehet hozzáférni . Egy kimeneti művelet végrehajtása egy időzítőn ciklusokban olvassa el az aktuális időt. Például egy függvény eltelt végrehajtási idejének kiszámításához f:
timer t;
uint32_t start, end;
t :> start;
f();
t :> end;
printf("Elapsed time %u s\n", (end-start)/CYCLES_PER_SEC);
ahol a CYCLES_PER_SEC a ciklusok száma másodpercenként.
Az időzítők kiválasztott utasításokban is felhasználhatók események kiváltására. Például a select utasítás:
timer t;
uint32_t time;
...
select {
case t when timerafter(time) :> void:
// Action to be performed after the delay
...
break;
}
megvárja, amíg az időzítő tmeghaladja az értéket, timemielőtt reagálna rá. Az érték a teldobjuk a szintaxis :> void, de hozzá lehet rendelni egy változót xa szintaxis :> int x.
IO
A típusú port változói hozzáférést biztosítanak az XCore eszköz XCore eszközének IO csapjaihoz. A portok két szélességűek lehetnek, lehetővé téve ugyanannyi bit bevitelét vagy kimenetét minden ciklusban. Ugyanaz a csatorna bemeneti és kimeneti szereplők <:és :>rendre használnak erre.
A következő program folyamatosan olvassa az egyik port értékét, és egy másik port adja ki:
#include <xs1.h>
in port p = XS1_PORT_1A;
out port q = XS1_PORT_1B;
int main (void) {
bool b;
while (1) {
p :> b;
q <: b;
}
}
A portok deklarálásának globális hatókörűnek kell lennie, és minden portnak meg kell határoznia, hogy be- vagy kimeneti-e, és rögzített értéket kap hozzá annak megadásához, hogy mely csapoknak felel meg. Ezeket az értékeket makróként definiálják a rendszer fejlécfájljában ( xs1.h).
Alapértelmezés szerint a portokat a csempe referencia-óráján hajtják végre. Az óra blokk erőforrások azonban felhasználhatók különböző órajelek előállítására, akár a referencia óra felosztásával, akár külső jel alapján. A portok tovább konfigurálhatók a pufferelés használatára és a szinkronizálásra más portokkal. Ez a konfiguráció a könyvtár függvényeinek használatával történik.
Port események
A portok eseményeket generálhatnak, amelyek kiválasztott utasításokban kezelhetők. Például az állítás:
select {
case p when pinseq(v) :> void:
printf("Received input %d\n", v);
break;
}
Az állítmányt when pinseqarra használja, hogy megvárja, amíg a port értéke pmegegyezik, vmielőtt elindítja a választ egy értesítés nyomtatásához.
Port időzítése
Annak érdekében, hogy ellenőrizni lehessen, amikor a port kimenetei bekövetkeznek a port órájához képest, a kimeneteket időbélyegzővel vagy időzítéssel lehet ellátni . Az időbélyeggel ellátott állítás:
p <: v @ count;
azt eredményezi, hogy az érték va porton kerül kimenetre, pés counta port számlálójának értéke lesz beállítva (minden referencia-óraciklus egyével növekszik). Időzített kimeneti utasítás:
p @ count <: v;
hatására a port megvárja, amíg számlálója eléri az értéket, countmielőtt az érték vkimenetre kerülne .
Feladatok multiplexelése magokra
Alapértelmezés szerint minden feladat egy csempén található egy maghoz tartozik. Mivel a magok száma korlátozott (nyolc a jelenlegi XCore eszközökön), az XC kétféle módon kínál több feladatot a magokhoz és a rendelkezésre álló magok jobb kihasználásához.
A kiválasztott utasítást tartalmazó véget nem érő ciklusból álló kiszolgálói feladatok az attribútummal kombinálhatóként jelölhetők meg [[combinable]]. Ez lehetővé teszi a fordító számára, hogy két vagy több kombinálható feladatot egyesítsen ugyanazon a magon, futtatva az eseteket egyetlen kijelölésbe.
Az összekapcsolhatóakkal megegyező formájú feladatok, kivéve, hogy a select minden egyes esete kezel egy tranzakciós függvényt, megjelölhető az attribútummal [[distributable]]. Ez lehetővé teszi a fordító számára, hogy a kiválasztott eseteket helyi függvényhívásokká alakítsa át.
Memória hozzáférés
Az XC memóriakapcsolatának két modellje van: biztonságos és nem biztonságos . A biztonságos hozzáférés az alapértelmezett ellenőrzés, amelynek során ellenőrizni kell, hogy:
- a memóriához való hozzáférés nem a határain kívül történik ;
- memória álnevek nem jönnek létre;
- lógó mutatók nem jönnek létre.
Ezeket a garanciákat különböző típusú mutatók kombinálásával (korlátozott, álnevezés, mozgatható), a fordítás közbeni statikus ellenőrzéssel és a futásidejű ellenőrzésekkel érik el .
A nem biztonságos mutatók ugyanúgy viselkednek, mint a C mutatói. A nem biztonságos mutatót a unsafekulcsszóval együtt kell deklarálni , és csak unsafe { ...} régiókban használhatók.
További jellemzők
Hivatkozások
Az XC referenciákat kínál , amelyek hasonlóak a C ++ kódban találhatókhoz, és &a típus után szimbólummal vannak megadva . A hivatkozás egy létező változónak egy másik nevet ad meg, oly módon, hogy annak olvasása és írása megegyezik az eredeti változó beolvasásával és megírásával. A hivatkozások egy tömb vagy struktúra elemeire utalhatnak, és felhasználhatók a reguláris és a tranzakciós függvények paramétereihez.
Kihúzhatatlan típusok
Az erőforrástípusoknak, például az interfészeknek, a csatorna végeinek, a portoknak és az óráknak mindig érvényes értékkel kell rendelkezniük. A nullable minősítő lehetővé teszi, hogy ezek a típusok ne legyenek értékek, amelyeket a ?szimbólum ad meg. Például egy érvénytelen csatornát deklarálunk a következővel:
chan ?c;
A nullázhatatlan erőforrástípusok használhatók a függvények opcionális erőforrás-argumentumainak megvalósítására is. A isnullbeépített függvény segítségével ellenőrizhető, hogy egy erőforrás null.
Többszörös visszatérés
XC-ben a függvények több értéket adhatnak vissza. Például a következő funkció valósítja meg a csere műveletet:
{int, int} swap(int a, int b) {
return {b, a};
}
A függvénycserét többszörös hozzárendeléssel hívják meg:
{x, y} = swap(x, y);
Példa programok
Többmagos Hello világ
#include <stdio.h>
#include <platform.h>
void hello(int id, chanend cin, chanend cout){
if (id > 0) cin :> int;
printf("Hello from core %d!", id);
if (id < 3) cout <: 1;
}
int main(void) {
chan c[3];
par (int i=0; i<4; i++)
on tile[i] : hello(i, c[i], c[(i+1)%4]);
return 0;
}
Történelmi hatások
Az XC tervezését nagymértékben befolyásolta az occam programozási nyelv , amely először a csatornák kommunikációját, váltakozását, portjait és időzítőit vezette be. Az Occamot David May fejlesztette ki, és a Kommunikációs szekvenciális folyamatok formalizmusára épült , amely folyamat algebra Tony Hoare fejlesztette ki .
Lásd még
- occam (programozási nyelv)
- C (programozási nyelv)
- Szekvenciális folyamatok kommunikálása
- Párhuzamos és párhuzamos programozási nyelvek listája