ทำไม SIGPIPE ถึงมีอยู่?


94

จากความเข้าใจของฉันSIGPIPEสามารถเกิดขึ้นได้ก็ต่อเมื่อ a write()ซึ่งสามารถ (และไม่) คืนค่า -1 และตั้งค่าerrnoเป็นEPIPE... แล้วทำไมเราถึงมีค่าโสหุ้ยพิเศษของสัญญาณ? ทุกครั้งที่ฉันทำงานกับท่อฉันไม่สนใจSIGPIPEและไม่เคยรู้สึกเจ็บปวดเลยด้วยเหตุนี้ฉันพลาดอะไรไปหรือเปล่า?

คำตอบ:


112

ฉันไม่ซื้อคำตอบที่ยอมรับก่อนหน้านี้ SIGPIPEถูกสร้างขึ้นอย่างแน่นอนเมื่อเกิดความwriteล้มเหลวด้วยEPIPEไม่ใช่ล่วงหน้า - ในความเป็นจริงวิธีหนึ่งที่ปลอดภัยในการหลีกเลี่ยงSIGPIPEโดยไม่เปลี่ยนการจัดการสัญญาณส่วนกลางคือการปิดบังชั่วคราวpthread_sigmaskดำเนินการwriteจากนั้นดำเนินการsigtimedwait(โดยไม่มีการหมดเวลาเป็นศูนย์) เพื่อใช้SIGPIPEสัญญาณที่รอดำเนินการ (ซึ่งถูกส่งไปยัง เธรดการโทรไม่ใช่กระบวนการ) ก่อนที่จะเปิดโปงอีกครั้ง

ฉันเชื่อว่าเหตุผลSIGPIPEนั้นง่ายกว่ามาก: การสร้างพฤติกรรมเริ่มต้นที่ดีสำหรับโปรแกรม "ตัวกรอง" บริสุทธิ์ที่อ่านอินพุตอย่างต่อเนื่องแปลงค่าอย่างใดอย่างหนึ่งและเขียนเอาต์พุต หากไม่มีSIGPIPEเว้นแต่โปรแกรมเหล่านี้จะจัดการข้อผิดพลาดในการเขียนอย่างชัดเจนและออกทันที (ซึ่งอาจไม่ใช่ลักษณะการทำงานที่ต้องการสำหรับข้อผิดพลาดในการเขียนทั้งหมดอยู่ดี) โปรแกรมเหล่านี้จะทำงานต่อไปจนกว่าจะหมดอินพุตแม้ว่าจะปิดไปป์เอาต์พุตแล้วก็ตาม แน่ใจว่าคุณสามารถทำซ้ำพฤติกรรมได้SIGPIPEโดยการตรวจสอบEPIPEและออกอย่างชัดเจนแต่จุดประสงค์ทั้งหมดSIGPIPEคือเพื่อให้บรรลุพฤติกรรมนี้โดยค่าเริ่มต้นเมื่อโปรแกรมเมอร์ขี้เกียจ


15
+1. เบาะแสคือความจริงที่ว่า SIGPIPE ฆ่าคุณโดยค่าเริ่มต้น - ไม่ได้ออกแบบมาเพื่อขัดจังหวะการเรียกระบบ แต่ออกแบบมาเพื่อยุติโปรแกรมของคุณ! หากคุณสามารถจัดการสัญญาณในเครื่องจัดการสัญญาณได้คุณก็สามารถจัดการกับรหัสส่งคืนของwrite.
Nicholas Wilson

2
คุณพูดถูกฉันไม่รู้ว่าทำไมฉันถึงยอมรับในตอนแรก คำตอบนี้สมเหตุสมผลแม้ว่า IMO จะแปลกที่เช่นบน Linux ความเกียจคร้านนี้เกิดขึ้นได้จากเคอร์เนลไม่ใช่ libc
Shea Levy

5
ดูเหมือนว่าคำตอบนี้โดยพื้นฐานแล้วคือ: "เพราะเราไม่มีข้อยกเว้น" อย่างไรก็ตามผู้ที่เพิกเฉยต่อรหัสส่งคืนใน C เป็นปัญหาที่กว้างกว่าการเรียกเพียงแค่ write () อะไรที่ทำให้การเขียนพิเศษนั้นต้องการสัญญาณของตัวเอง? บางทีโปรแกรมกรองบริสุทธิ์อาจเป็นเรื่องธรรมดามากกว่าที่ฉันคิด
Arvid

@Arvid SIGPIPE ถูกคิดค้นโดยผู้ใช้ Unix เพื่อแก้ปัญหาที่พวกเขาพบในสภาพแวดล้อมที่โปรแกรมตัวกรองเป็นเรื่องธรรมดามากสิ่งที่เราต้องทำคืออ่านสคริปต์การบูตที่เรียกใช้ระบบ
Kaz

@SheaLevy ระบบ Unix ใดที่ใช้ SIGPIPE ใน libc ของพวกเขาอย่างหมดจด
Kaz

23

เนื่องจากโปรแกรมของคุณอาจกำลังรอ I / O หรือถูกระงับ SIGPIPE ขัดจังหวะโปรแกรมของคุณแบบอะซิงโครนัสยุติการเรียกระบบและสามารถจัดการได้ทันที

อัปเดต

A | B | Cพิจารณาท่อ

เพื่อความชัดเจนเราจะถือว่า B คือลูปสำเนามาตรฐาน:

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

Bถูกบล็อกในการรอสายอ่าน (2)สำหรับข้อมูลAเมื่อCสิ้นสุด หากคุณรอรหัสส่งคืนจากการเขียน (2) B จะเห็นเมื่อใด แน่นอนว่าคำตอบคือไม่ใช่จนกว่า A จะเขียนข้อมูลเพิ่มเติม (ซึ่งอาจต้องรอนาน - จะเกิดอะไรขึ้นถ้า A ถูกบล็อกโดยสิ่งอื่น?) สังเกตว่าสิ่งนี้ยังช่วยให้เรามีโปรแกรมที่ง่ายและสะอาดขึ้น หากคุณขึ้นอยู่กับรหัสข้อผิดพลาดจากการเขียนคุณจะต้องมีสิ่งต่อไปนี้:

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

การปรับปรุงอื่น ๆ

อ๊ะคุณสับสนเกี่ยวกับพฤติกรรมของการเขียน คุณจะเห็นเมื่อตัวอธิบายไฟล์ที่มีการเขียนที่รอดำเนินการถูกปิด SIGPIPE จะเกิดขึ้นทันที แม้ว่าการเขียนจะส่งกลับ -1 ในที่สุดจุดรวมของสัญญาณคือการแจ้งให้คุณทราบแบบอะซิงโครนัสว่าไม่สามารถเขียนได้อีกต่อไป นี่เป็นส่วนหนึ่งของสิ่งที่ทำให้โครงสร้าง Co-routine ที่สวยงามทั้งหมดของท่อทำงานใน UNIX

ตอนนี้ฉันสามารถชี้ให้คุณเห็นการอภิปรายทั้งหมดในหนังสือการเขียนโปรแกรมระบบ UNIX หลายเล่ม แต่มีคำตอบที่ดีกว่า: คุณสามารถยืนยันได้ด้วยตัวเอง เขียนง่ายBโปรแกรม [1] - คุณได้มีความกล้าที่มีอยู่แล้วสิ่งที่คุณต้องเป็นmainและบางส่วนรวมถึง - SIGPIPEและเพิ่มตัวจัดการสัญญาณ เรียกใช้ไปป์ไลน์เช่น

cat | B | more

และในหน้าต่างเทอร์มินัลอื่นแนบดีบักเกอร์กับ B และวางเบรกพอยต์ไว้ในตัวจัดการสัญญาณ B

ตอนนี้ฆ่าได้มากขึ้นและ B ควรทำลายตัวจัดการสัญญาณของคุณ ตรวจสอบสแต็ก คุณจะพบว่าการอ่านยังคงค้างอยู่ ให้สัญญาณจัดการดำเนินการและการกลับมาและดูที่ผลที่ส่งกลับโดยเขียน - ซึ่งจะแล้วจะ -1

[1] โดยปกติคุณจะเขียนโปรแกรม B ของคุณในภาษา C :-)


3
ทำไม B ถึงเห็นว่า C เลิกกับ SIGPIPE เร็วกว่านี้? B จะยังคงถูกบล็อกในการอ่านจนกว่าจะมีการเขียนบางสิ่งลงใน STDIN ซึ่ง ณ จุดนั้นจะเรียกการเขียน () จากนั้น SIGPIPE จะถูกยกขึ้น / -1 จะถูกส่งกลับ
Shea Levy

2
ฉันชอบคำตอบมาก: SIGPIPE ช่วยให้ความตายแพร่กระจายกลับจากปลายขาออกของไปป์ไลน์ได้ทันที หากไม่มีสิ่งนี้จะใช้เวลาถึงหนึ่งรอบของโปรแกรมคัดลอกสำหรับแต่ละองค์ประกอบ N ของไปป์เพื่อฆ่าไปป์ไลน์และทำให้ด้านอินพุตสร้าง N บรรทัดที่ไม่มีวันสิ้นสุด
Yttrill

18
คำตอบนี้ไม่ถูกต้อง SIGPIPEจะไม่ถูกส่งระหว่างการอ่าน แต่ในช่วงไฟล์write. คุณไม่จำเป็นต้องเขียนโปรแกรม C เพื่อทดสอบเพียงแค่เรียกใช้cat | headและpkill headในเทอร์มินัลแยกต่างหาก คุณจะเห็นว่าการcatรอคอยอย่างมีความสุขread()- เฉพาะเมื่อคุณพิมพ์บางสิ่งลงไปแล้วกด Enter จะcatทำให้ท่อแตกเพราะพยายามเขียนเอาต์พุต
user4815162342

5
-1 SIGPIPEไม่สามารถถูกส่งไปBในขณะที่Bถูกบล็อกในreadเพราะSIGPIPEจะไม่สร้างจนความพยายามB writeไม่มีเธรดใดสามารถ "กำลังรอ I / O หรือถูกระงับ" ในขณะที่โทรwriteในเวลาเดียวกัน
แดนปั้น

3
คุณสามารถโพสต์โปรแกรมเต็มรูปแบบที่แสดงSIGPIPEการเพิ่มขึ้นในขณะที่ถูกบล็อกบน a read? ฉันไม่สามารถทำซ้ำพฤติกรรมนั้นได้เลย (และฉันไม่แน่ใจว่าทำไมฉันถึงยอมรับสิ่งนี้ตั้งแต่แรก)
Shea Levy

7

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

ลิงค์นี้ระบุว่า:

ท่อหรือ FIFO ต้องเปิดที่ปลายทั้งสองข้างพร้อมกัน หากคุณอ่านจากไฟล์ไปป์หรือไฟล์ FIFO ที่ไม่มีกระบวนการใด ๆ เขียนถึงมัน (อาจเป็นเพราะพวกเขาปิดไฟล์ทั้งหมดหรือออก) การอ่านจะส่งกลับจุดสิ้นสุดของไฟล์ การเขียนไปป์หรือ FIFO ที่ไม่มีกระบวนการอ่านถือว่าเป็นเงื่อนไขข้อผิดพลาด มันสร้างสัญญาณ SIGPIPEและล้มเหลวด้วยรหัสข้อผิดพลาด EPIPE หากสัญญาณถูกจัดการหรือบล็อก

- มาโคร: int SIGPIPE

ท่อแตก หากคุณใช้ไปป์หรือ FIFO คุณต้องออกแบบแอปพลิเคชันของคุณเพื่อให้กระบวนการหนึ่งเปิดไปป์เพื่ออ่านก่อนที่อีกกระบวนการหนึ่งจะเริ่มเขียน หากกระบวนการอ่านไม่เริ่มต้นหรือยุติโดยไม่คาดคิดการเขียนไปป์หรือ FIFO จะทำให้เกิดสัญญาณ SIGPIPE หาก SIGPIPE ถูกบล็อกจัดการหรือละเว้นการโทรที่ละเมิดจะล้มเหลวด้วย EPIPE แทน

Pipes และไฟล์พิเศษ FIFO จะกล่าวถึงในรายละเอียดเพิ่มเติมใน Pipes และ FIFO


5

ฉันคิดว่ามันคือการทำให้การจัดการข้อผิดพลาดถูกต้องโดยไม่ต้องใช้รหัสจำนวนมากในทุกสิ่งที่เขียนลงในไพพ์

บางโปรแกรมไม่สนใจค่าส่งคืนของwrite(); โดยที่SIGPIPEพวกเขาจะสร้างผลลัพธ์ทั้งหมดอย่างไร้ประโยชน์

โปรแกรมที่ตรวจสอบค่าส่งคืนของwrite()แนวโน้มที่จะพิมพ์ข้อความแสดงข้อผิดพลาดหากล้มเหลว สิ่งนี้ไม่เหมาะสมสำหรับท่อที่แตกเนื่องจากไม่ใช่ข้อผิดพลาดสำหรับท่อทั้งหมด


2

ข้อมูลเครื่อง:

Linux 3.2.0-53-generic # 81-Ubuntu SMP พฤหัสบดี 22 สิงหาคม 21:01:03 UTC 2013 x86_64 x86_64 x86_64 GNU / Linux

gcc เวอร์ชัน 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5)

ฉันเขียนโค้ดด้านล่างนี้:

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

}

เอาท์พุต:

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

คุณจะเห็นได้ว่าในทุกๆอินสแตนซ์SIGPIPEจะได้รับหลังจากกระบวนการเขียนมากกว่า 3 ตัวอักษรเท่านั้น

สิ่งนี้ไม่ได้พิสูจน์SIGPIPEว่าไม่ได้สร้างขึ้นทันทีหลังจากสิ้นสุดกระบวนการอ่าน แต่หลังจากพยายามเขียนข้อมูลเพิ่มเติมไปยังท่อปิด?

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.