SIGPIPE neden var?


94

Benim anlayış, SIGPIPEsadece sonucu olarak ortaya çıkabilir write(), hangi can (ve) dönüş -1 ve seti errnoiçin EPIPE... bir sinyalin ek yük var mı neden So? Borularla her çalıştığımda görmezden geldiğim SIGPIPEve sonuç olarak hiç acı hissetmedim, bir şeyi mi kaçırıyorum?

Yanıtlar:


112

Önceden kabul edilmiş cevabı satın almıyorum. önceden değil, SIGPIPEtam olarak writebaşarısız olduğunda üretilir EPIPE- aslında SIGPIPEglobal sinyal düzenlerini değiştirmeden kaçınmanın güvenli bir yolu , onu geçici olarak maskelemek pthread_sigmask, gerçekleştirmek ve writeardından sigtimedwaitherhangi bir bekleyen SIGPIPEsinyali (gönderilen ) tüketmek için (sıfır zaman aşımı ile) gerçekleştirmektir . tekrar maskesini çıkarmadan önce işlem değil, çağıran iş parçacığı.

Varoluş sebebinin SIGPIPEçok daha basit olduğuna inanıyorum : girdiyi sürekli okuyan, onu bir şekilde dönüştüren ve çıktıyı yazan saf "filtre" programları için mantıklı varsayılan davranış oluşturmak. Olmazsa SIGPIPE, bu programlar açıkça yazma hatalarını işlemedikçe ve hemen çıkmadıkça (her halükarda bu tüm yazma hataları için istenen davranış olmayabilir), çıkış boruları kapatılsa bile girdileri bitene kadar çalışmaya devam edecekler. Elbette, davranışını SIGPIPEaçıkça kontrol ederek EPIPEve çıkarak çoğaltabilirsiniz , ancak tüm amacı SIGPIPE, programcı tembel olduğunda varsayılan olarak bu davranışı elde etmekti.


15
+1. İpucu, SIGPIPE'nin varsayılan olarak sizi öldürmesidir - bir sistem çağrısını kesmek için tasarlanmamıştır, programınızı sonlandırmak için tasarlanmıştır! Sinyali bir sinyal işleyicide işleyebiliyorsanız, dönüş kodunu da işleyebilirsiniz write.
Nicholas Wilson

2
Haklısın, bunu ilk başta neden kabul ettiğimi bilmiyorum. Bu cevap mantıklıdır, ancak IMO'da bu tembelliğin libc tarafından değil, çekirdek tarafından elde edilmesi tuhaftır.
Shea Levy

5
bu cevap temelde şu şekilde özetlenebilir: "çünkü istisnalarımız yoktu". Bununla birlikte, insanların C'deki dönüş kodlarını görmezden gelmesi, sadece yazma () çağrıları yapmaktan çok daha geniş bir konudur. Yazmayı bu kadar özel kılan, kendi sinyaline ihtiyaç duyan nedir? belki de saf filtre programları hayal ettiğimden çok daha yaygındır.
Arvid

@Arvid SIGPIPE, filtre programlarının son derece yaygın olduğu ortamlarında yaşadıkları bir sorunu çözmek için Unix çalışanları tarafından icat edildi. Tek yapmamız gereken sistemi ortaya çıkaran önyükleme betiklerini okumak.
Kaz

@SheaLevy Hangi Unix sistemleri SIGPIPE'i tamamen kendi libc'lerinde uyguluyor?
Kaz

23

Çünkü programınız G / Ç bekliyor olabilir veya başka bir şekilde askıya alınabilir. Bir SIGPIPE, programınızı eşzamansız olarak kesintiye uğratır, sistem çağrısını sonlandırır ve böylece hemen işlenebilir.

Güncelleme

Bir boru hattı düşünün A | B | C.

Kesinlik için, B'nin kanonik kopya döngüsü olduğunu varsayacağız:

while((sz = read(STDIN,bufr,BUFSIZE))>=0)
    write(STDOUT,bufr,sz);

Bengellenir okuma (2) verilerine için çağrı bekletme Ane zaman Csona erdiği. Eğer (2) yazısından dönüş kodunu beklerseniz , B onu ne zaman görecek? Elbette cevap, A daha fazla veri yazana kadar değil (ki bu uzun bir bekleme olabilir - ya A başka bir şey tarafından engellenirse?). Bu arada, bunun bize daha basit, daha temiz bir program sağladığına dikkat edin. Yazmadan kaynaklanan hata koduna bağlıysanız, aşağıdaki gibi bir şeye ihtiyacınız olacak:

while((sz = read(STDIN,bufr,BUFSIZE))>=0)
    if(write(STDOUT,bufr,sz)<0)
        break;

Başka bir güncelleme

Aha, yazının davranışı konusunda kafan karıştı. Gördüğünüz gibi, beklemedeki yazma ile dosya tanımlayıcı kapatıldığında, SIGPIPE hemen o anda gerçekleşir. Yazma sonunda -1 döndürürken , sinyalin tüm amacı, yazma işleminin artık mümkün olmadığını size eşzamansız olarak bildirmektir. Bu, boruların tüm zarif ortak rutin yapısının UNIX'te çalışmasını sağlayan şeyin bir parçasıdır.

Şimdi, sizi birkaç UNIX sistem programlama kitabının herhangi birindeki bütün bir tartışmaya yönlendirebilirim, ancak daha iyi bir cevap var: Bunu kendiniz doğrulayabilirsiniz. Basit bir Bprogram [1] yazın - cesaretiniz var, tek ihtiyacınız olan a mainve bazılarını içerir - ve bir sinyal işleyici ekleyin SIGPIPE. Gibi bir boru hattı çalıştırın

cat | B | more

ve başka bir terminal penceresinde, B'ye bir hata ayıklayıcı iliştirin ve B sinyal işleyicisinin içine bir kesme noktası koyun.

Şimdi, daha fazlasını öldürün ve B sinyal işleyicinizi kırsın. yığını inceleyin. Sen olduğunu göreceksiniz okuma hala beklemede. sinyal işleyicisinin ilerlemesine ve geri dönmesine izin verin ve write ile döndürülen sonuca bakın - bu daha sonra -1 olacaktır .

[1] Doğal olarak, B programınızı C'de yazacaksınız. :-)


3
B neden C'nin SIGPIPE ile feshedildiğini görsün? B, STDIN'sine bir şey yazılıncaya kadar okumada bloke kalacak, bu noktada write () çağıracak ve ancak o zaman SIGPIPE yükseltilecek / -1 döndürülecektir.
Shea Levy

2
Cevabı gerçekten beğendim: SIGPIPE, ölümün boru hattının çıktı ucundan anında geri yayılmasına izin veriyor. Bu olmadan boru hattını öldürmek için borunun her N elemanı için kopyalama programının bir döngüsünü alır ve giriş tarafının asla sona ulaşmayan N hat oluşturmasına neden olur.
Yttrill

18
Bu cevap yanlıştır. SIGPIPEolduğu değil fakat sırasında, okuma sırasında teslim write. Test etmek için bir C programı yazmanıza gerek yok, sadece çalıştırın cat | headve pkill headayrı bir terminalde. catMutlu bir şekilde kendi içinde beklemeye devam ettiğini göreceksiniz - read()yalnızca bir şey yazıp enter tuşuna bastığınızda cat, tam olarak çıktı yazmaya çalıştığı için kırık bir boru ile ölür.
user4815162342

5
-1 SIGPIPEteslim edilemez Bise Bile bloke edilir read, çünkü SIGPIPEkadar oluşturulmaz Bgirişimi write. writeAynı anda arama sırasında hiçbir iş parçacığı "G / Ç için bekliyor veya başka bir şekilde askıya alınamaz" .
Dan Molding

3
A'da SIGPIPEengellendiğinde ortaya çıkarılan tam bir program yayınlayabilir misiniz read? Bu davranışı hiç yeniden oluşturamıyorum (ve ilk başta bunu neden kabul ettiğimi bilmiyorum)
Shea Levy

7

https://www.gnu.org/software/libc/manual/html_mono/libc.html

Bu bağlantı şöyle diyor:

Bir boru veya FIFO her iki uçta aynı anda açılmalıdır. Üzerine yazma işlemi olmayan bir boru veya FIFO dosyasından okursanız (belki de tümü dosyayı kapattıkları veya çıktıkları için), okuma dosyanın sonunu döndürür. Okuma işlemi olmayan bir boruya veya FIFO'ya yazmak bir hata durumu olarak değerlendirilir; bir SIGPIPE sinyali üretir ve eğer sinyal işlenirse veya bloke edilirse EPIPE hata koduyla başarısız olur.

- Makro: int SIGPIPE

Kırık boru. Borular veya FIFO'lar kullanıyorsanız, uygulamanızı, bir işlemin başka bir işlem yazmaya başlamadan önce boruyu okumak için açacağı şekilde tasarlamanız gerekir. Okuma işlemi hiç başlamazsa veya beklenmedik bir şekilde sona ererse, boruya veya FIFO'ya yazmak SIGPIPE sinyalini yükseltir. SIGPIPE engellenirse, ele alınırsa veya yok sayılırsa, sorun teşkil eden arama bunun yerine EPIPE ile başarısız olur.

Borular ve FIFO özel dosyaları, Borular ve FIFO'larda daha ayrıntılı olarak ele alınmaktadır.


5

Bir boruya yazılan her şeyde çok fazla kod gerektirmeden hata işlemeyi doğru yapmak olduğunu düşünüyorum.

Bazı programlar write(); SIGPIPEonlar olmadan tüm çıktıları gereksiz bir şekilde üretmezler.

write()Muhtemelen dönüş değerini kontrol eden programlar, başarısız olursa bir hata mesajı yazdırır; Bu, tüm boru hattı için gerçekten bir hata olmadığından, kırık bir boru için uygun değildir.


2

Makine bilgisi:

Linux 3.2.0-53-generic # 81-Ubuntu SMP Per Ağustos 22 21:01:03 UTC 2013 x86_64 x86_64 x86_64 GNU / Linux

gcc sürüm 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5)

Bu kodu aşağıya yazdım:

// Writes characters to stdout in an infinite loop, also counts 
// the number of characters generated and prints them in sighandler
// writestdout.c

# include <unistd.h>
# include <stdio.h>
# include <signal.h>
# include <string.h>

int writeCount = 0;    
void sighandler(int sig) {
    char buf1[30] ;
    sprintf(buf1,"signal %d writeCount %d\n", sig, writeCount);
    ssize_t leng = strlen(buf1);
    write(2, buf1, leng);
    _exit(1);

}

int main() {

    int i = 0;
    char buf[2] = "a";

    struct sigaction ss;
    ss.sa_handler = sighandler;

    sigaction(13, &ss, NULL);

    while(1) {

        /* if (writeCount == 4) {

            write(2, "4th char\n", 10);

        } */

        ssize_t c = write(1, buf, 1);
        writeCount++;

    }

}

// Reads only 3 characters from stdin and exits
// readstdin.c

# include <unistd.h>
# include <stdio.h>

int main() {

    ssize_t n ;        
    char a[5];        
    n = read(0, a, 3);
    printf("read %zd bytes\n", n);
    return(0);

}

Çıktı:

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 11486

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 429

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 281

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 490

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 433

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 318

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 468

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 11866

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 496

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 284

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 271

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 416

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 11268

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 427

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 8812

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 394

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 10937

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 10931

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 3554

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 499

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 283

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 11133

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 451

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 493

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 233

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 11397

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 492

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 547

$ ./writestdout | ./readstdin 
read 3 bytes
signal 13 writeCount 441

Bunu her durumda görebilirsin SIGPIPE , yalnızca yazma sürecinde 3 karakterden fazla yazıldıktan (yapılmaya çalışıldıktan) sonra alındığını .

Bu SIGPIPE, okuma işlemi sona erdikten hemen sonra değil, kapalı bir boruya biraz daha fazla veri yazma girişiminden sonra oluşturulduğunu kanıtlamaz mı ?

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.