Yeniden giriş (bilgi işlem) - Reentrancy (computing)

Olarak işlem , bir bilgisayar programı ya da bir alt yordam çağrılır evresel birden çağrıların güvenli bir şekilde birden fazla işlemci üzerinde eşzamanlı olarak çalışabilir halinde ya da bir evresel prosedür yürütme ortasında kesilebilir ve daha sonra güvenli bir şekilde yeniden çağrılabilir tek bir işlemci sistemi üzerinde (" yeniden girildi") önceki çağrılar yürütmeyi tamamlamadan önce. Kesinti , yeni çağrıların yalnızca dahili çağrıdan kaynaklanabileceği özyinelemeden farklı olarak, atlama veya çağrı gibi dahili bir eylemden veya kesme veya sinyal gibi harici bir eylemden kaynaklanabilir.

Bu tanım, kontrol akışının bir kesme ile kesilebildiği ve bir kesme servis rutini (ISR) veya "işleyici" alt rutinine aktarılabildiği çoklu programlama ortamlarından kaynaklanmaktadır . İşleyici tarafından kullanılan ve kesme tetiklendiğinde potansiyel olarak yürütülüyor olabilecek herhangi bir alt yordam, yeniden girişli olmalıdır. Çoğu zaman, işletim sistemi çekirdeği aracılığıyla erişilebilen alt programlar, yeniden girişli değildir. Bu nedenle, kesme hizmeti rutinleri gerçekleştirebilecekleri eylemlerle sınırlıdır; örneğin, genellikle dosya sistemine erişimleri ve hatta bazen bellek ayırmaları kısıtlanır.

Bu yeniden giriş tanımı, çok iş parçacıklı ortamlarda iş parçacığı güvenliğinden farklıdır . Yeniden girişli bir alt yordam, iş parçacığı güvenliğini sağlayabilir, ancak tek başına yeniden girişli olmak, her durumda iş parçacığı güvenliği için yeterli olmayabilir. Tersine, iş parçacığı güvenli kodun mutlaka yeniden girişli olması gerekmez (örnekler için aşağıya bakın).

Yeniden giriş yapan programlar için kullanılan diğer terimler arasında "paylaşılabilir kod" bulunur. Yeniden giriş alt rutinleri bazen referans materyalinde "sinyal güvenli" olarak işaretlenir. Yeniden giriş programları genellikle "saf prosedürlerdir".

Arka plan

Yeniden giriş, işlevin birden fazla kez çağrılabildiği ancak yalnızca bir kez çağrılmış gibi tam olarak aynı çıktıyı ürettiği idempotence ile aynı şey değildir . Genel olarak konuşursak, bir fonksiyon bazı girdi verilerine dayalı olarak çıktı verileri üretir (genel olarak her ikisi de isteğe bağlıdır). Paylaşılan verilere herhangi bir zamanda herhangi bir işlev tarafından erişilebilir. Veriler herhangi bir işlev tarafından değiştirilebiliyorsa (ve hiçbiri bu değişiklikleri takip etmiyorsa), bir veriyi paylaşanların o verinin önceki herhangi bir zamanda aynı olduğuna dair bir garanti yoktur.

Verilerin bir programda nerede kullanılabileceğini açıklayan kapsam adı verilen bir özelliği vardır . Veri kapsamı ya geneldir ( herhangi bir işlevin kapsamı dışında ve belirsiz bir kapsamda) ya da yereldir (bir işlev her çağrıldığında oluşturulur ve çıkışta yok edilir).

Yerel veriler herhangi bir rutin tarafından paylaşılmaz, yeniden girilir veya girilmez; bu nedenle, yeniden girişi etkilemez. Genel veriler, işlevlerin dışında tanımlanır ve küresel değişkenler (tüm işlevler arasında paylaşılan veriler) veya statik değişkenler (aynı işlevin tüm çağrıları tarafından paylaşılan veriler ) biçiminde birden fazla işlev tarafından erişilebilir . In nesne yönelimli programlama , global veri bir sınıf kapsamında tanımlanır ve yalnızca bu sınıfın fonksiyonları için erişilebilir hale getirmek, özel olabilir. Bir sınıf değişkeninin bir sınıf örneğine bağlı olduğu örnek değişkenler kavramı da vardır . Bu nedenlerle, nesne yönelimli programlamada bu ayrım genellikle sınıf dışından erişilebilen (genel) veriler ve sınıf örneklerinden bağımsız (statik) veriler için ayrılmıştır.

Yeniden giriş, iş parçacığı güvenliğinden farklıdır, ancak bununla yakından ilişkilidir . Bir işlev iş parçacığı için güvenli olabilir ve yine de yeniden girişli olmayabilir. Örneğin, bir işlev bir muteks ile her tarafa sarılabilir (bu, çok iş parçacıklı ortamlarda sorunları önler), ancak bu işlev bir kesme hizmeti rutininde kullanılmışsa, ilk yürütmenin muteks'i serbest bırakmasını beklemekten ölebilir. Karışıklığı önlemenin anahtarı, yeniden girenin yalnızca bir iş parçacığı yürütmesine atıfta bulunmasıdır . Çoklu görev işletim sistemlerinin olmadığı zamanlardan kalma bir kavramdır.

Yeniden giriş kuralları

Yeniden giriş kodu, serileştirme olmadan herhangi bir statik veya küresel sabit olmayan veriyi tutamaz .
Yeniden giriş işlevleri küresel verilerle çalışabilir. Örneğin, bir yeniden giriş kesintisi hizmet rutini, çalışmak için (örneğin, seri bağlantı noktası okuma arabelleği) bir donanım durumu parçası alabilir ve bu yalnızca küresel değil, aynı zamanda geçicidir. Yine de, statik değişkenlerin ve genel verilerin tipik kullanımı tavsiye edilmez, çünkü serileştirilmemiş kod bölümleri dışında , bu değişkenlerde sadece atomik okuma-değiştirme-yazma talimatları kullanılmalıdır (bu mümkün olmamalıdır). böyle bir talimatın yürütülmesi sırasında gelecek bir kesinti veya sinyal). C'de, bir okuma veya yazmanın bile atomik olduğu garanti edilmediğine dikkat edin; birkaç okuma veya yazma işlemine bölünebilir. C standardı ve SUSv3 sig_atomic_t, artırma veya azaltma için değil, yalnızca basit okuma ve yazma için garantiler verse de bu amacı sağlar. Daha karmaşık atomik işlemler sağlayan C11'de mevcuttur stdatomic.h.
Yeniden giriş kodu, serileştirme olmadan kendini değiştiremez .
İşletim sistemi, bir işlemin kodunu değiştirmesine izin verebilir. Bunun çeşitli nedenleri vardır (örneğin, grafikleri hızlı bir şekilde blitting ), ancak bu , yeniden girişle ilgili sorunlardan kaçınmak için genellikle serileştirme gerektirir.

Ancak, kendi benzersiz belleğinde bulunuyorsa, kendini değiştirebilir. Yani, her yeni çağrı, orijinal kodun bir kopyasının yapıldığı farklı bir fiziksel makine kodu konumu kullanıyorsa, o belirli çağrının (iş parçacığının) yürütülmesi sırasında kendini değiştirse bile diğer çağrıları etkilemeyecektir.

Yeniden giriş kodu, yeniden giriş yapmayan bilgisayar programlarını veya rutinlerini çağıramaz .
Birden çok kullanıcı, nesne veya işlem önceliği veya çoklu işlem düzeyi, genellikle yeniden giriş kodunun kontrolünü karmaşıklaştırır. Reentrant olarak tasarlanmış bir rutin içinde yapılan herhangi bir erişim veya yan etkinin kaydını tutmak önemlidir.

İşletim sistemi kaynakları veya yerel olmayan veriler üzerinde çalışan bir alt yordamın yeniden girişi , ilgili işlemlerin atomitesine bağlıdır . Örneğin, alt program 32 bitlik bir makinede 64 bitlik bir global değişkeni değiştirirse, işlem iki 32 bitlik işleme bölünebilir ve böylece alt program yürütülürken kesintiye uğrar ve kesme işleyicisinden yeniden çağrılırsa , global değişken yalnızca 32 bitin güncellendiği bir durumda olabilir. Programlama dili, atlama veya çağrı gibi dahili bir eylemin neden olduğu kesintiler için atomite garantileri sağlayabilir. Daha sonra, bir programlama dilinde alt ifadelerin değerlendirme sırasının keyfi olabileceği fgibi bir ifadedeki işlev (global:=1) + (f()), global değişkenin ya 1'e ya da önceki değerine ayarlandığını görecek, ancak yalnızca bir kısmının olduğu bir ara durumda değil. güncellenmiş. (Sonuncusu C'de olabilir , çünkü ifadenin sıra noktası yoktur .) İşletim sistemi , kısmi bir etkiye sahip olmayan bir sinyal tarafından kesilen bir sistem çağrısı gibi, sinyaller için atomik garantiler sağlayabilir . İşlemci donanım için bölünmezlik garanti sağlayabilir kesme gibi kısmi etkilere sahip olmayan kesintiye işlemci talimatları gibi.

Örnekler

Yeniden girişi göstermek için, bu makale örnek olarak iki işaretçi alıp değerlerini değiştiren bir C yardımcı program işlevini swap()ve takas işlevini de çağıran bir kesme işleme yordamını kullanır.

Ne reentrant ne de thread güvenli

Bu, yeniden girişli veya iş parçacığı için güvenli olamayan örnek bir takas işlevidir. Yana tmpdeğişken küresel paylaşılır ve senkronizasyon gerekmeden, fonksiyonun herhangi eşzamanlı örnekleri arasında, bir örneği verileri başka dayanmadığınızı engel olabilir. Bu nedenle, kesme hizmeti rutininde kullanılmamış olmalıdır 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);
}

İplik güvenli ancak reentrant değil

swap()Önceki örnekteki işlev , tmp thread-local yapılarak iş parçacığı için güvenli hale getirilebilir . Hâlâ reentrant olmayı başaramıyor ve bu, isr()halihazırda yürütülmekte olan bir iş parçacığıyla aynı bağlamda çağrılırsa sorunlara neden olmaya devam edecek 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);
}

Reentrant ancak iş parçacığı için güvenli değil

Genel verileri çıkarken tutarlı bir durumda bırakmaya özen gösteren takas işlevinin aşağıdaki (biraz yapmacık) değişikliği yeniden girişlidir; ancak, kullanılan hiçbir kilit olmadığından iş parçacığı için güvenli değildir, herhangi bir zamanda kesilebilir:

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);
}

Reentrant ve iş parçacığı güvenli

Bir uygulanması swap()o ayırır tmpüzerinde yığının yerine küresel ve bu sadece parametre olarak paylaşılmamış değişkenlerle denir hem iş parçacığı güvenli ve yeniden yapılandırılmıştır. Yığın bir iş parçacığı için yerel olduğundan ve yalnızca yerel verilere etki eden bir işlev her zaman beklenen sonucu üreteceğinden iş parçacığı güvenlidir. Paylaşılan verilere erişim yoktur, bu nedenle veri yarışı yoktur.

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);
}

Reentrant kesme işleyicisi

Yeniden giren bir kesme işleyicisi, kesme işleyicisinde erken kesmeleri yeniden etkinleştiren bir kesme işleyicisidir. Bu, kesme gecikmesini azaltabilir . Genel olarak, kesme servis rutinlerini programlarken, kesme işleyicisinde mümkün olan en kısa sürede kesmelerin yeniden etkinleştirilmesi önerilir. Bu uygulama, kesintilerin kaybolmasını önlemeye yardımcı olur.

Diğer örnekler

Aşağıdaki kodda, ne işlevler ne fde gişlevler reentrant değildir.

int v = 1;

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

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

Yukarıda, f()sabit olmayan bir global değişkene bağlıdır v; bu nedenle, f()yürütme sırasında değiştiren bir ISR tarafından kesintiye uğrarsa v, içine yeniden giriş f()yanlış değerini döndürür v. değeri vve dolayısıyla dönüş değeri fgüvenle tahmin edilemez: 'in yürütülmesi vsırasında bir kesmenin değiştirilip değiştirilmediğine bağlı olarak değişir f. Dolayısıyla freentrant değildir. İkisi de değildir g, çünkü çağırır f, ki bu reentrant değildir.

Bunlar biraz değişmiş versiyonu olan evresel:

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

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

Aşağıda, işlev iş parçacığı için güvenlidir, ancak (zorunlu olarak) yeniden girişli değildir:

int function()
{
    mutex_lock();

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

    mutex_unlock();
}

Yukarıda, function()herhangi bir sorun olmadan farklı iş parçacıkları tarafından çağrılabilir. Ancak, işlev bir yeniden giriş kesme işleyicisinde kullanılıyorsa ve işlev içinde ikinci bir kesinti ortaya çıkarsa, ikinci rutin sonsuza kadar askıda kalır. Kesinti hizmeti diğer kesintileri devre dışı bırakabileceğinden, tüm sistem bundan zarar görebilir.

Notlar

Ayrıca bakınız

Referanslar

Atıfta bulunulan eserler

daha fazla okuma