XC (programozási nyelv) - XC (programming language)

XC
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 &lt;:és :&gt;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:

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

Hivatkozások

További irodalom

  • Az XMOS programozási útmutató ( HTML , PDF )
  • Az XC specifikáció ( HTML , PDF )

Külső linkek