Bir sinyal yakalandığında sistemin kesilmesi


29

Adam sayfalarını okumasını read()ve write()çağrılar çağrılar olursa olsun engellemek veya olmasın zorunda olup sinyalleri tarafından kesintiye olsun anlaşılmaktadır.

Özellikle, varsayalım

  • Bir işlem, bazı sinyaller için bir işleyici kurar.
  • bir cihazın (diyelim ki bir terminal) açılır O_NONBLOCK olmayan set (yani engelleme modunda işletme)
  • işlem daha sonra read()cihazdan okumak için bir sistem çağrısı yapar ve sonuç olarak çekirdek uzayında bir çekirdek kontrol yolu yürütür.
  • Öncelik read()çekirdek alanında yürütülürken işleyicinin daha önce kurulduğu sinyal bu işleme iletilir ve sinyal işleyicisi çağrılır.

SUSv3 'Sistem Arayüzleri hacmi (XSH)' içindeki man sayfalarını ve uygun bölümleri okumak , şunu bulur:

ben. A read()herhangi bir veriyi okumadan önce bir sinyal tarafından kesilirse (yani, veri mevcut olmadığından bloke etmek zorunda kaldıysa ), errno[EINTR] olarak ayarlanmış olarak -1 değerini döndürür .

ii. A read()bazı verileri başarıyla okuduktan sonra bir sinyal tarafından kesilirse (yani isteği hemen yerine getirmek mümkün oldu), okunan bayt sayısını döndürür.

Soru A): Her iki durumda da (blok yok / blok yok) sinyalin teslimatı ve kullanımının tamamen şeffaf olmadığı varsayımı doğru muyum read()?

Dava i. bloke etme read()işlemi normalde duruma yerleştirir, TASK_INTERRUPTIBLEçünkü bir sinyal iletildiğinde, çekirdeği işlemi bu TASK_RUNNINGduruma getirir .

Bununla birlikte, read()blokaj gerekmediğinde (talep ii.) Ve talebi çekirdek uzayda işlerken, bir sinyalin gelmesi ve bunun ele alınmasının bir HW'nin gelmesi ve doğru şekilde ele alınması gibi şeffaf olacağını düşünürdüm. kesme olur. Özellikle, sinyalin gönderilmesi üzerine, işlemin , kesilen (çekirdek boşluğu içinde) kesilmesi işleminin bitmesi için eninde sonunda geri döneceği sinyal işleyicisini yürütmek için işlemin geçici olarak kullanıcı moduna geçirileceğini varsayıyordum . İşlem tamamlandıktan sonra işlem çağrının hemen sonrasında (kullanıcı alanında), sonuçta okunan baytların tümü geri döndüğü noktaya geri döner .read()read()read()

Fakat ii. read()Veriler derhal mevcut olduğu için kesintiye uğradığını ima ediyor gibi görünüyor , ancak geri döndürüyor, verilerin yalnızca bir kısmını döndürüyor (hepsinden ziyade).

Bu beni ikinci (ve son) soruma getiriyor:

Soru B): A) altındaki varsayımım doğruysa, read()talebi derhal yerine getirebilecek veriler bulunduğundan engellenmesine gerek olmasa bile neden kesintiye uğruyor? Başka bir deyişle, read()sinyal işleyiciyi yürüttükten sonra neden devam edilemiyor, sonuçta tüm mevcut verilerin (sonuçta mevcuttu) döndürülmesine neden oluyor?

Yanıtlar:


29

Özet: Bir sinyalin alınmasının şeffaf olmadığına, ne i (herhangi bir şey okumadan yarıda kesilirse) veya ii (ii) (kısmi bir okumanın ardından yarıda kesilirse) karşı haklısınız. Aksi takdirde, hem işletim sisteminin mimarisinde hem de uygulama mimarisinde temel değişiklikler yapmam gerekir.

İşletim sistemi uygulama görünümü

Bir sistem çağrısı bir sinyal tarafından kesilirse ne olacağını düşünün. Sinyal işleyici, kullanıcı modu kodunu yürütecektir. Ancak, sistem çağrısı işleyicisi çekirdek kodudur ve herhangi bir kullanıcı modu koduna güvenmez. Öyleyse, sistem çağrısı işleyicisinin seçeneklerini inceleyelim:

  • Sistem çağrısını sonlandırın; kullanıcı koduna ne kadar yapıldığını bildiriniz. İsterseniz, sistem çağrısını bir şekilde yeniden başlatmak uygulama koduna kalmıştır. Unix böyle çalışır.
  • Sistem çağrısının durumunu kaydedin ve kullanıcı kodunun aramayı sürdürmesine izin verin. Bu birkaç nedenden dolayı sorunlu:
    • Kullanıcı kodu çalışırken, kaydedilmiş durumu geçersiz kılacak bir şey olabilir. Örneğin, bir dosyadan okunuyorsa, dosya kesilebilir. Bu nedenle, çekirdek kodunun bu durumları ele almak için çok fazla mantığa ihtiyacı olacaktır.
    • Kaydedilen durumun herhangi bir kilidi tutmasına izin verilemez, çünkü kullanıcı kodunun sistemi aramaya devam edeceği garantisi yoktur ve daha sonra kilit sonsuza kadar tutulur.
    • Çekirdek, bir çağrı başlatmak için normal arabirime ek olarak, devam eden sistem çağrılarını devam ettirmek veya iptal etmek için yeni arabirimler göstermelidir. Bu nadir bir vaka için çok fazla komplikasyondur.
    • Kaydedilen durumun kaynakları kullanması gerekir (en azından hafıza); bu kaynakların çekirdek tarafından tahsis edilmesi ve tutulması gerekir, ancak sürecin paylaştırılmasına karşı sayılır. Bu aşılmaz bir şey değil, ama bir komplikasyon.
      • Sinyal işleyicinin, kendilerinin kesintiye uğradığı sistem çağrıları yapabileceğini unutmayın; bu nedenle, tüm olası sistem çağrılarını kapsayan statik bir kaynak tahsisine sahip olamazsınız.
      • Peki ya kaynaklar tahsis edilemezse? O zaman sistem çağrısı yine de başarısız olacaktı. Bu, uygulamanın bu durumu ele almak için kodunun olması gerektiği anlamına gelir; bu nedenle bu tasarım uygulama kodunu basitleştirmez.
  • Devam etmekte (ancak askıya alınmış) kalın, sinyal işleyici için yeni bir iş parçacığı oluşturun. Bu, yine sorunlu:
    • Erken unix uygulamalarında işlem başına tek bir iş parçacığı vardı.
    • Sinyal işleyicisi, sistem çağrısının ayakkabılarını aşma riskini taşır. Bu yine de bir konudur, ancak mevcut unix tasarımında, bunun içinde yer almaktadır.
    • Yeni konu için kaynakların tahsis edilmesi gerekir; yukarıyı görmek.

Bir kesinti ile temel fark, kesinti kodunun güvenilir ve son derece kısıtlı olmasıdır. Genellikle kaynak tahsis etmek, sonsuza dek koşmak, kilitlemek, serbest bırakmak veya başka türden kötü şeyler yapmak yasaktır; Kesme işleyicisi işletim sistemi uygulayıcısı tarafından yazıldığından, kötü bir şey yapmayacağını biliyor. Öte yandan, uygulama kodu her şeyi yapabilir.

Uygulama tasarım görünümü

Bir sistem çağrısının ortasında bir uygulama kesintiye uğradığında, sistem çağrısı tamamlamaya devam etmeli mi? Her zaman değil. Örneğin, terminalden bir satır okuyan bir kabuk gibi bir program düşünün ve kullanıcı Ctrl+Cbastırıp SIGINT'i tetikler. Okuma tamamlanmamalı, sinyal bununla ilgili. Bu örnekte, readhenüz bir bayt okunmamış olsa bile , sistem çağrısının kesilebilir olması gerektiğini gösterdiğini unutmayın .

Bu nedenle, uygulamanın çekirdeğe sistem çağrısını iptal etmesini söylemesinin bir yolu olmalıdır. Unix tasarımında, otomatik olarak gerçekleşir: sinyal, sistem çağrısını geri döndürür. Diğer tasarımlar, uygulamanın serbest bırakılmasından sonra sistemi çağırmaya devam etmesini veya iptal etmesini gerektirir.

readSistem çağrısı işletim sisteminin genel tasarımı göz önüne alındığında, mantıklı ilkel çünkü öyle bir yoldur. Bunun anlamı, kabaca “olabildiğince fazla, bir sınıra kadar (arabellek boyutu) oku, ancak başka bir şey olursa dur”. Aslında tam bir tamponu okumak read, mümkün olduğu kadar çok bayt okunana kadar bir döngüde çalışmayı içerir ; bu daha üst seviye bir fonksiyondur fread(3). read(2)Hangisinin bir sistem çağrısı olduğundan farklı olarak, freadüstüne kullanıcı alanında uygulanan bir kütüphane işlevidir read. Bir dosyayı okuyan veya deneyen bir uygulama için uygundur; komut satırı yorumlayıcısı veya bağlantıları temiz bir şekilde kısması gereken ağ bağlantılı bir program için veya eşzamanlı bağlantıları olan ve iş parçacığı kullanmayan ağ bağlantılı bir program için uygun değildir.

Bir döngüde okuma örneği, Robert Love'ın Linux Sistem Programlamasında verilmiştir:

ssize_t ret;
while (len != 0 && (ret = read (fd, buf, len)) != 0) {
  if (ret == -1) {
    if (errno == EINTR)
      continue;
    perror ("read");
    break;
  }
  len -= ret;
  buf += ret;
}

Bu ilgilenir case ive case iive birkaç tane daha.


UNIX tasarım felsefesi ile ilgili bir makalede öne sürülen benzer görüşleri destekleyen çok özlü ve net bir cevap için Gilles'a çok teşekkür ederim.
Sistem çağrı

@darbehdar Üçü: unix tasarım felsefesi (burada esas olarak süreçler çekirdekten daha az güvenilirdir ve keyfi kod çalıştırabilir, ayrıca süreçler ve iş parçacıkları dolaylı olarak oluşturulmaz), teknik kısıtlamalar (kaynak tahsisinde) ve uygulama tasarımı (burada sinyalin çağrıyı iptal etmesi gerektiğinde durumlardır).
Gilles 'SO- kötü olmayı bırak'

2

Soru A'ya cevap vermek için :

Evet, sinyalin teslimatı ve kullanımı tamamen şeffaf değildir read().

read()O sinyal ile kesintiye esnada çalışan yarıya bazı kaynakları işgal olabilir. Ve sinyalin sinyal işleyicisi bir tane daha read()(veya başka herhangi bir asenkron sinyal güvenli sistem çağrısı) çağırabilir . Bu nedenle, read()kullandığı kaynakları serbest bırakmak için ilk önce sinyalin kesilmesi gerekir, aksi takdirde read()sinyal işleyiciden aranan aynı kaynaklara erişir ve tekrarlanan sorunlara neden olur.

Çünkü sistem çağrıları read()sinyal işleyiciden çağrılabilenin dışında bir durumdaydı ve aynı özdeş kaynakları işgal edebilirler read(). Yukarıdaki sorunlu durumlardan kaçınmak için, en basit ve en güvenli tasarım, read()çalışma sırasında bir sinyal geldiğinde kesintileri durdurmaktır .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.