Reentrancy (výpočetní technika) - Reentrancy (computing)

Ve výpočetní technice se počítačový program nebo podprogram nazývá reentrant, pokud lze více vyvolání bezpečně spustit souběžně na více procesorech nebo v systému s jedním procesorem, kde lze proceduru reentrant přerušit uprostřed jeho provádění a poté bezpečně znovu zavolat (“ znovu zadán “), než jeho předchozí vyvolání dokončí provedení. Přerušení může být způsobeno interní akcí, jako je skok nebo volání, nebo externí akcí, jako je přerušení nebo signál , na rozdíl od rekurze , kde nové vyvolání může být způsobeno pouze interním voláním.

Tato definice pochází z prostředí multiprogramování, kde by tok řízení mohl být přerušen přerušením a přenesen do rutiny služby přerušení (ISR) nebo podprogramu „handler“. Jakýkoli podprogram použitý obsluhou, který by se potenciálně mohl spouštět při spuštění přerušení, by měl být znovu zadán. Podprogramy přístupné přes jádro operačního systému často nejsou reentrantní. Rutiny služby přerušení jsou tedy omezeny v akcích, které mohou provádět; například jim je obvykle omezen přístup k systému souborů a někdy dokonce i k přidělování paměti.

Tato definice reentrancy se liší od bezpečnosti vláken v prostředích s více vlákny. Podprogram reentrantu může dosáhnout bezpečnosti vláken, ale být sám reentrantem nemusí být dostačující k tomu, aby byl bezpečný pro vlákna ve všech situacích. Naopak kód bezpečný pro vlákna nemusí být nutně reentrantní (příklady viz níže).

Mezi další výrazy používané pro programy reentrantů patří „kód pro sdílení“. Reentrantní podprogramy jsou někdy v referenčním materiálu označeny jako „bezpečné pro signál“. Reentrantní programy jsou často „čisté procedury“.

Pozadí

Reentrancy není totéž jako idempotence , ve které může být funkce volána více než jednou, ale přesto generuje přesně stejný výstup, jako kdyby byla volána pouze jednou. Obecně řečeno, funkce produkuje výstupní data na základě některých vstupních dat (i když obě jsou obecně volitelné). Ke sdíleným datům lze kdykoli přistupovat pomocí jakékoli funkce. Pokud lze data změnit jakoukoli funkcí (a nikdo tyto změny nezaznamenává), neexistuje žádná záruka pro ty, kteří sdílejí datum, že tento datum je stejný jako kdykoli předtím.

Data mají charakteristiku zvanou rozsah , která popisuje, kde v programu mohou být data použita. Rozsah dat je buď globální (mimo rozsah jakékoli funkce a s neurčitým rozsahem), nebo místní (vytvořený při každém volání funkce a zničení při ukončení).

Místní data nejsou sdílena žádnými rutinami, ať už zadává nebo ne; proto nemá vliv na opětovný vstup. Globální data jsou definována mimo funkce a lze k nim přistupovat více než jednou funkcí, a to buď ve formě globálních proměnných (data sdílená mezi všemi funkcemi), nebo jako statické proměnné (data sdílená všemi vyvoláními stejné funkce). V objektově orientovaném programování jsou globální data definována v rozsahu třídy a mohou být soukromá, takže jsou přístupná pouze pro funkce této třídy. Existuje také koncept proměnných instance , kde je proměnná třídy vázána na instanci třídy. Z těchto důvodů je v objektově orientovaném programování toto rozlišení obvykle vyhrazeno pro data přístupná mimo třídu (veřejná) a pro data nezávislá na instancích tříd (statická).

Reentrancy se liší od, ale úzce souvisí s bezpečností vláken . Funkce může být bezpečná pro vlákno a přesto se nemusí znovu zadat. Například funkce by mohla být zabalena všude kolem s mutexem (který se vyhýbá problémům ve vícevláknových prostředích), ale pokud by byla tato funkce použita v rutině služby přerušení, mohla by vyhladovět čekáním na první spuštění k uvolnění mutexu. Klíčem k zamezení zmatku je, že reentrant se týká pouze jednoho vlákna, které se spouští. Je to koncept z doby, kdy neexistovaly žádné víceúlohové operační systémy.

Pravidla pro reentrancy

Reentrantní kód nesmí obsahovat žádná statická ani globální nekonstantní data bez serializace .
Funkce reentrantu mohou pracovat s globálními daty. Například rutina služby reentrantního přerušení by mohla zachytit kus hardwarového stavu, s nímž lze pracovat (např. Vyrovnávací paměť pro čtení sériového portu), který je nejen globální, ale také nestálý. Přesto se typické použití statických proměnných a globálních dat nedoporučuje, v tom smyslu, že s výjimkou částí kódu, které nejsou serializovány , by v těchto proměnných měly být použity pouze atomické pokyny pro čtení, úpravy a zápis (nemělo by být možné pro přerušení nebo signál, který má přijít během provádění takové instrukce). Všimněte si, že v C není zaručeno, že i čtení nebo zápis bude atomový; může být rozděleno do několika čtení nebo zápisů. Standard C a SUSv3 sig_atomic_tk tomuto účelu slouží, i když se zárukami pouze pro jednoduché čtení a zápis, nikoli pro zvýšení nebo snížení. Složitější atomové operace jsou k dispozici v C11 , který poskytuje stdatomic.h.
Reentrantní kód se nesmí sám upravovat bez serializace.
Operační systém může procesu umožnit úpravu jeho kódu. K tomu existují různé důvody (např. Rychlé vyblbnutí grafiky), ale toto obecně vyžaduje serializaci, aby se předešlo problémům s reentrancí.

Může se však sám změnit, pokud se nachází ve vlastní jedinečné paměti. To znamená, že pokud každé nové vyvolání používá jiné umístění fyzického strojového kódu, kde je vytvořena kopie původního kódu, neovlivní to ostatní vyvolání, i když se během provádění daného vyvolání (vlákna) samo upraví.

Kód reentrantu nesmí volat nereentrantní počítačové programy nebo rutiny .
Více úrovní priority uživatele, objektu nebo procesu nebo vícenásobného zpracování obvykle komplikuje řízení kódu reentrant. Je důležité sledovat jakýkoli přístup nebo vedlejší účinky, které se provádějí v rámci rutiny navržené tak, aby byla reentrantní.

Reentrancy podprogramu, který pracuje se zdroji operačního systému nebo nelokálními daty, závisí na atomičnosti příslušných operací. Pokud například podprogram upravuje 64bitovou globální proměnnou na 32bitovém počítači, může být operace rozdělena na dvě 32bitové operace, a tedy pokud je podprogram při provádění přerušen a znovu vyvolán z obsluhy přerušení , globální proměnná může být ve stavu, kdy bylo aktualizováno pouze 32 bitů. Programovací jazyk může poskytovat záruky atomičnosti pro přerušení způsobené interní akcí, jako je skok nebo volání. Pak by funkce fve výrazu jako (global:=1) + (f()), kde pořadí vyhodnocení podvýrazů může být v programovacím jazyce libovolné, viděla globální proměnnou buď nastavenou na 1 nebo na předchozí hodnotu, ale ne v přechodném stavu, kde byla pouze část aktualizováno. (K poslednímu může dojít v C , protože výraz nemá žádný sekvenční bod .) Operační systém může poskytovat záruky atomičnosti signálů , jako je systémové volání přerušené signálem, který nemá částečný účinek. Hardware procesoru může poskytovat záruky atomičnosti pro přerušení , jako jsou přerušené instrukce procesoru, které nemají částečné efekty.

Příklady

Tento článek pro ilustraci reentrancy používá jako příklad pomocnou funkci Cswap() , která přebírá dva ukazatele a transponuje jejich hodnoty, a rutinu zpracování přerušení, která také volá funkci swap.

Ani reentrantní, ani bezpečné pro vlákna

Toto je příklad funkce swapu, která se nezdařila být reentrantní nebo bezpečná pro vlákna. Protože je tmpproměnná globálně sdílena, bez synchronizace, mezi všemi souběžnými instancemi funkce, může jedna instance interferovat s daty, na která se spoléhá jiná. Jako takový by neměl být používán v rutině služby přerušení isr():

int tmp;

void swap(int* x, int* y)
{
    tmp = *x;
    *x = *y;
    /* Hardware interrupt might invoke isr() here. */
    *y = tmp;    
}

void isr()
{
    int x = 1, y = 2;
    swap(&x, &y);
}

Bezpečné pro vlákna, ale ne reentrantní

Funkci swap()v předchozím příkladu lze nastavit jako bezpečnou pro tmp vlákna vytvořením místního vlákna . Stále se nedaří znovu zadat, a to bude i nadále způsobovat problémy, pokud isr()je voláno ve stejném kontextu jako vlákno, které již probíhá swap():

_Thread_local int tmp;

void swap(int* x, int* y)
{
    tmp = *x;
    *x = *y;
    /* Hardware interrupt might invoke isr() here. */
    *y = tmp;    
}

void isr()
{
    int x = 1, y = 2;
    swap(&x, &y);
}

Reentrantní, ale ne bezpečné pro vlákna

Následující (poněkud vykonstruovaná) modifikace swapové funkce, která si dává pozor, aby globální data zůstala v konzistentním stavu v okamžiku, kdy končí, je reentrantní; není však bezpečný pro vlákna, protože nejsou použity žádné zámky, lze jej kdykoli přerušit:

int tmp;

void swap(int* x, int* y)
{
    /* Save global variable. */
    int s;
    s = tmp;

    tmp = *x;
    *x = *y;      /*If hardware interrupt occurs here then it will fail to keep the value of tmp. So this is also not a reentrant example*/
    *y = tmp;     /* Hardware interrupt might invoke isr() here. */

    /* Restore global variable. */
    tmp = s;
}

void isr()
{
    int x = 1, y = 2;
    swap(&x, &y);
}

Reentrantní a bezpečné pro vlákna

Implementace, swap()která se alokuje tmpv zásobníku místo globálně a která se nazývá pouze s nesdílenými proměnnými, protože parametry jsou bezpečné pro vlákna i reentrant. Bezpečné pro vlákna, protože zásobník je lokální pro vlákno a funkce působící pouze na místní data vždy vytvoří očekávaný výsledek. Neexistuje žádný přístup ke sdíleným datům, proto neexistuje žádný datový závod.

void swap(int* x, int* y)
{
    int tmp;
    tmp = *x;
    *x = *y;
    *y = tmp;    /* Hardware interrupt might invoke isr() here. */
}

void isr()
{
    int x = 1, y = 2;
    swap(&x, &y);
}

Obsluha přerušení Reentrant

Obsluha přerušení reentrantu je obsluha přerušení, která znovu povolí přerušení na začátku obsluhy přerušení. To může snížit latenci přerušení . Obecně platí, že při programování rutin služby přerušení se doporučuje povolit přerušení co nejdříve v obsluze přerušení. Tato praxe pomáhá vyhnout se ztrátě přerušení.

Další příklady

V následujícím kódu se nereentrantují fani gfunkce.

int v = 1;

int f()
{
    v += 2;
    return v;
}

int g()
{
    return f() + 2;
}

Ve výše uvedeném f()závisí na nekonstantní globální proměnné v; pokud f()je tedy během provádění přerušen ISR, který upravuje v, pak reentry into f()vrátí nesprávnou hodnotu v. Hodnotu va tedy návratovou hodnotu fnelze s jistotou předpovědět: budou se lišit v závislosti na tom, zda se vběhem fprovádění změnilo přerušení . Z tohoto důvodu fnení reentrantní. Ani není g, protože volá f, což není reentrant.

Tyto mírně pozměněné verze jsou reentrantní:

int f(int i)
{
    return i + 2;
}

int g(int i)
{
    return f(i) + 2;
}

V následujícím je funkce bezpečná pro vlákna, ale ne (nutně) reentrant:

int function()
{
    mutex_lock();

    // ...
    // function body
    // ...

    mutex_unlock();
}

Ve výše uvedeném function()lze bez problémů volat různými vlákny. Pokud je však funkce použita v obsluze opětovného přerušení a uvnitř funkce dojde k druhému přerušení, bude druhá rutina viset navždy. Protože služba přerušení může deaktivovat další přerušení, celý systém by mohl utrpět.

Poznámky

Viz také

Reference

Citované práce

Další čtení