Mengapa SIGPIPE ada?

94

Dari pemahaman saya, SIGPIPEhanya dapat terjadi sebagai hasil dari a write(), yang dapat (dan tidak) mengembalikan -1 dan diatur errnoke EPIPE... Jadi mengapa kita memiliki overhead sinyal ekstra? Setiap kali saya bekerja dengan pipa, saya mengabaikan SIGPIPEdan tidak pernah merasakan sakit apa pun, apakah saya melewatkan sesuatu?

Shea Levy
sumber

Jawaban:

112

Saya tidak membeli jawaban yang diterima sebelumnya. SIGPIPEdihasilkan tepat ketika writegagal dengan EPIPE, bukan sebelumnya - pada kenyataannya satu cara aman untuk menghindari SIGPIPEtanpa mengubah disposisi sinyal global adalah dengan menutupinya sementara dengan pthread_sigmask, melakukan write, kemudian melakukan sigtimedwait(dengan nol batas waktu) untuk mengkonsumsi SIGPIPEsinyal yang tertunda (yang dikirim ke utas panggilan, bukan proses) sebelum membuka kedoknya lagi.

Saya yakin alasan yang SIGPIPEada jauh lebih sederhana: menetapkan perilaku default yang waras untuk program "filter" murni yang terus menerus membaca masukan, mengubahnya, dan menulis keluaran. Tanpa SIGPIPE, kecuali program ini secara eksplisit menangani kesalahan penulisan dan segera keluar (yang mungkin bukan perilaku yang diinginkan untuk semua kesalahan tulis), program ini akan terus berjalan hingga kehabisan masukan bahkan jika pipa keluarannya telah ditutup. Tentu Anda dapat menduplikasi perilaku SIGPIPEdengan secara eksplisit memeriksa EPIPEdan keluar, tetapi tujuan keseluruhan dari SIGPIPEadalah untuk mencapai perilaku ini secara default saat programmer malas.

R .. GitHub BERHENTI ICE BANTUAN
sumber
15
+1. Petunjuknya adalah fakta bahwa SIGPIPE membunuh Anda secara default - ini tidak dirancang untuk mengganggu panggilan sistem, ini dirancang untuk menghentikan program Anda! Jika Anda dapat menangani sinyal dalam penangan sinyal, Anda juga dapat menangani kode pengembalian write.
Nicholas Wilson
2
Anda benar, saya tidak tahu mengapa saya menerimanya sejak awal. Jawaban ini masuk akal, meskipun IMO aneh bahwa misalnya di Linux kemalasan ini dicapai oleh kernel dan bukan libc.
Shea Levy
5
Kedengarannya seperti jawaban ini pada dasarnya bermuara pada: "karena kami tidak memiliki pengecualian". Namun, orang yang mengabaikan kode balik di C adalah masalah yang lebih luas daripada hanya panggilan write (). Apa yang membuat menulis begitu istimewa sehingga membutuhkan sinyalnya sendiri? mungkin program filter murni jauh lebih umum yang saya bayangkan.
Arvid
@Arvid SIGPIPE diciptakan oleh orang-orang Unix, untuk memecahkan masalah yang mereka hadapi di lingkungan mereka di mana program filter sangat umum, Yang harus kita lakukan adalah membaca skrip boot yang membuka sistem.
Kaz
@SheaLevy Sistem Unix mana yang mengimplementasikan SIGPIPE murni di libc mereka?
Kaz
23

Karena program Anda mungkin menunggu I / O atau ditangguhkan. SIGPIPE menginterupsi program Anda secara tidak sinkron, menghentikan panggilan sistem, sehingga dapat ditangani dengan segera.

Memperbarui

Pertimbangkan saluran pipa A | B | C.

Hanya untuk kepastian, kita akan berasumsi bahwa B adalah loop salinan kanonik:

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

Bdiblokir pada panggilan baca (2) menunggu data dari Asaat Cberakhir. Jika Anda menunggu kode pengembalian dari write (2) , kapan B akan melihatnya? Jawabannya, tentu saja, tidak sampai A menulis lebih banyak data (yang bisa jadi menunggu lama - bagaimana jika A diblokir oleh sesuatu yang lain?). Perhatikan, omong-omong, ini juga memungkinkan kita membuat program yang lebih sederhana dan lebih bersih. Jika Anda bergantung pada kode kesalahan dari menulis, Anda memerlukan sesuatu seperti:

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

Pembaruan lainnya

Aha, kamu bingung dengan perilaku penulisnya. Anda lihat, ketika deskriptor file dengan penulisan tertunda ditutup, SIGPIPE terjadi saat itu juga. Meskipun penulisan akan mengembalikan -1 pada akhirnya , inti dari sinyal ini adalah untuk memberi tahu Anda secara asinkron bahwa penulisan tidak lagi memungkinkan. Ini adalah bagian dari apa yang membuat seluruh struktur pipa rutin yang elegan berfungsi di UNIX.

Sekarang, saya dapat mengarahkan Anda ke seluruh diskusi di salah satu dari beberapa buku pemrograman sistem UNIX, tetapi ada jawaban yang lebih baik: Anda dapat memverifikasi ini sendiri. Menulis Bprogram sederhana [1] - Anda sudah punya nyali, yang Anda butuhkan hanyalah a maindan beberapa termasuk - dan tambahkan penangan sinyal untuk SIGPIPE. Jalankan pipa seperti

cat | B | more

dan di jendela terminal lain, pasang debugger ke B dan letakkan breakpoint di dalam penangan sinyal B.

Sekarang, bunuh lebih banyak dan B harus istirahat di penangan sinyal Anda. periksa tumpukannya. Anda akan mengetahui bahwa pembacaan masih menunggu keputusan. biarkan penangan sinyal melanjutkan dan kembali, dan melihat hasil yang dikembalikan oleh tulis - yang kemudian akan menjadi -1.

[1] Biasanya, Anda akan menulis program B Anda dalam C. :-)

Charlie Martin
sumber
3
Mengapa B melihat penghentian C lebih cepat dengan SIGPIPE? B akan tetap diblokir pada pembacaan sampai sesuatu ditulis ke STDIN-nya, di mana ia akan memanggil write () dan hanya kemudian SIGPIPE akan dimunculkan / -1 dikembalikan.
Shea Levy
2
Saya sangat menyukai jawabannya: SIGPIPE memungkinkan kematian menyebar kembali dari ujung keluaran pipa secara instan. Tanpa ini, diperlukan satu siklus program penyalinan untuk masing-masing N elemen pipa untuk mematikan pipa, dan menyebabkan sisi masukan menghasilkan garis N yang tidak pernah mencapai akhir.
Yttring
18
Jawaban ini salah. SIGPIPEadalah tidak disampaikan selama membaca, tapi selama write. Anda tidak perlu menulis program C untuk mengujinya, jalankan saja cat | head, dan pkill headdi terminal terpisah. Anda akan melihat bahwa catdengan senang hati hidup dalam menunggu read()—hanya ketika Anda mengetik sesuatu dan menekan enter yang catmati dengan pipa yang rusak, persis karena mencoba menulis keluaran.
pengguna4815162342
5
-1 SIGPIPEtidak dapat dikirim ke Bsaat Bdiblokir readkarena SIGPIPEtidak dibuat hingga Bupaya write. Tidak ada utas yang "menunggu I / O atau ditangguhkan" saat menelepon writepada waktu yang sama.
Dan Moulding
3
Dapatkah Anda memposting program lengkap yang menunjukkan SIGPIPEdibesarkan saat diblokir di read? Saya tidak dapat mereproduksi perilaku itu sama sekali (dan saya tidak benar-benar yakin mengapa saya menerima ini di tempat pertama)
Shea Levy
7

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

Tautan ini mengatakan:

Pipa atau FIFO harus dibuka di kedua ujungnya secara bersamaan. Jika Anda membaca dari pipa atau file FIFO yang tidak memiliki proses menulis padanya (mungkin karena mereka semua telah menutup file, atau keluar), pembacaan mengembalikan akhir file. Menulis ke pipa atau FIFO yang tidak memiliki proses pembacaan dianggap sebagai kondisi kesalahan; itu menghasilkan sinyal SIGPIPE, dan gagal dengan kode kesalahan EPIPE jika sinyal ditangani atau diblokir.

- Makro: int SIGPIPE

Pipa rusak. Jika Anda menggunakan pipa atau FIFO, Anda harus mendesain aplikasi Anda sehingga satu proses membuka pipa untuk dibaca sebelum proses lainnya mulai menulis. Jika proses pembacaan tidak pernah dimulai, atau berhenti tiba-tiba, menulis ke pipa atau FIFO memunculkan sinyal SIGPIPE. Jika SIGPIPE diblokir, ditangani atau diabaikan, panggilan yang melanggar gagal dengan EPIPE sebagai gantinya.

File khusus Pipa dan FIFO dibahas lebih rinci dalam Pipa dan FIFO.

Ankur Agarwal
sumber
5

Saya pikir ini adalah untuk mendapatkan penanganan kesalahan yang benar tanpa memerlukan banyak kode dalam segala hal yang menulis ke pipa.

Beberapa program mengabaikan nilai kembalian write(); tanpa SIGPIPEmereka akan menghasilkan semua keluaran.

Program yang memeriksa nilai pengembalian write()kemungkinan akan mencetak pesan kesalahan jika gagal; hal ini tidak sesuai untuk pipa yang rusak karena sebenarnya ini bukan kesalahan untuk seluruh pipa.

jilles
sumber
2

Info mesin:

Linux 3.2.0-53-generik # 81-Ubuntu SMP Kam 22 Agustus 21:01:03 UTC 2013 x86_64 x86_64 x86_64 GNU / Linux

gcc versi 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5)

Saya menulis kode ini di bawah ini:

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

}

Keluaran:

$ ./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

Anda dapat melihat bahwa dalam setiap contoh SIGPIPEhanya diterima setelah lebih dari 3 karakter (dicoba) ditulis oleh proses penulisan.

Apakah ini tidak membuktikan bahwa SIGPIPEtidak dihasilkan segera setelah proses pembacaan berakhir tetapi setelah upaya untuk menulis lebih banyak data ke pipa tertutup?

Ankur Agarwal
sumber