Tại sao SIGPIPE tồn tại?


93

Theo hiểu biết của tôi, SIGPIPEchỉ có thể xảy ra do kết quả của a write(), có thể (và không) trả về -1 và được đặt errnothành EPIPE... Vậy tại sao chúng ta lại có thêm chi phí của một tín hiệu? Mỗi khi tôi làm việc với đường ống, tôi đều bỏ qua SIGPIPEvà chưa bao giờ cảm thấy đau vì vậy tôi có thiếu thứ gì không?

Câu trả lời:


112

Tôi không mua câu trả lời đã được chấp nhận trước đó. SIGPIPEđược tạo ra chính xác khi writelỗi với EPIPE, chứ không phải trước - trên thực tế, một cách an toàn để tránh SIGPIPEmà không thay đổi vị trí tín hiệu toàn cục là tạm thời che dấu nó bằng pthread_sigmask, thực hiện write, sau đó thực hiện sigtimedwait(không có thời gian chờ) để sử dụng bất kỳ SIGPIPEtín hiệu nào đang chờ xử lý (được gửi đến luồng gọi, không phải quy trình) trước khi hiển thị lại.

Tôi tin rằng lý do SIGPIPEtồn tại đơn giản hơn nhiều: thiết lập hành vi mặc định lành mạnh cho các chương trình "bộ lọc" thuần túy liên tục đọc đầu vào, biến đổi nó bằng cách nào đó và ghi đầu ra. Nếu không SIGPIPE, trừ khi các chương trình này xử lý rõ ràng lỗi ghi và thoát ngay lập tức (dù sao thì đó có thể không phải là hành vi mong muốn đối với tất cả các lỗi ghi), chúng sẽ tiếp tục chạy cho đến khi hết đầu vào ngay cả khi đường dẫn đầu ra của chúng đã bị đóng. Chắc chắn bạn có thể sao chép hành vi của SIGPIPEbằng cách kiểm tra EPIPEvà thoát rõ ràng , nhưng mục đích của toàn bộ SIGPIPElà đạt được hành vi này theo mặc định khi lập trình viên lười biếng.


15
+1. Manh mối là SIGPIPE giết bạn theo mặc định - nó không được thiết kế để làm gián đoạn cuộc gọi hệ thống, nó được thiết kế để chấm dứt chương trình của bạn! Nếu bạn có thể xử lý tín hiệu trong trình xử lý tín hiệu, bạn cũng có thể xử lý mã trả về của write.
Nicholas Wilson,

2
Bạn nói đúng, tôi không biết tại sao tôi chấp nhận điều đó ngay từ đầu. Câu trả lời này có lý, mặc dù IMO thật kỳ lạ khi ví dụ trên Linux, sự lười biếng này đạt được bởi kernel chứ không phải libc.
Shea Levy

5
có vẻ như câu trả lời này về cơ bản tóm gọn lại thành: "bởi vì chúng tôi không có ngoại lệ". Tuy nhiên, mọi người bỏ qua mã trả lại trong C là một vấn đề rộng hơn nhiều so với chỉ gọi write (). Điều gì làm cho việc viết trở nên đặc biệt mà nó cần tín hiệu riêng? có lẽ các chương trình lọc thuần túy phổ biến hơn nhiều mà tôi tưởng tượng.
Arvid

@Arvid SIGPIPE được phát minh bởi những người Unix, để giải quyết một vấn đề mà họ đang gặp phải trong môi trường của họ, trong đó các chương trình lọc cực kỳ phổ biến, Tất cả những gì chúng ta phải làm là đọc các tập lệnh khởi động đưa lên hệ thống.
Kaz

@SheaLevy Hệ thống Unix nào triển khai SIGPIPE hoàn toàn trong libc của họ?
Kaz

23

Vì chương trình của bạn có thể đang đợi I / O hoặc bị tạm ngưng. SIGPIPE làm gián đoạn chương trình của bạn một cách không đồng bộ, chấm dứt cuộc gọi hệ thống và do đó có thể được xử lý ngay lập tức.

Cập nhật

Hãy xem xét một đường ống dẫn A | B | C.

Chỉ để xác định, chúng tôi sẽ giả định rằng B là vòng lặp sao chép chính tắc:

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

Bbị chặn trên cuộc gọi đọc (2) chờ dữ liệu từ Akhi Ckết thúc. Nếu bạn đợi mã trả về từ ghi (2) thì khi nào B sẽ thấy nó? Câu trả lời, tất nhiên, không phải là cho đến khi A ghi thêm dữ liệu (có thể là một thời gian dài chờ đợi - điều gì sẽ xảy ra nếu A bị chặn bởi thứ khác?). Nhân tiện, hãy lưu ý rằng điều này cũng cho phép chúng ta có một chương trình đơn giản hơn, gọn gàng hơn. Nếu bạn phụ thuộc vào mã lỗi khi ghi, bạn cần một cái gì đó như:

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

Cập nhật khác

Aha, bạn đang bối rối về hành vi của văn bản. Bạn thấy đấy, khi đóng bộ mô tả tệp có ghi đang chờ xử lý, SIGPIPE sẽ xảy ra ngay lúc đó. Mặc dù cuối cùng quá trình ghi sẽ trả về -1 , nhưng toàn bộ điểm của tín hiệu là thông báo cho bạn một cách không đồng bộ rằng việc ghi không còn khả thi nữa. Đây là một phần của những gì làm cho toàn bộ cấu trúc đồng quy trình thanh lịch của đường ống hoạt động trong UNIX.

Bây giờ, tôi có thể hướng dẫn bạn đến toàn bộ cuộc thảo luận trong bất kỳ cuốn sách lập trình hệ thống UNIX nào, nhưng có một câu trả lời tốt hơn: bạn có thể tự xác minh điều này. Viết một Bchương trình đơn giản [1] - bạn đã có gan, tất cả những gì bạn cần là một mainvà một số bao gồm - và thêm một trình xử lý tín hiệu cho SIGPIPE. Chạy một đường dẫn như

cat | B | more

và trong một cửa sổ đầu cuối khác, hãy gắn trình gỡ lỗi vào B và đặt một điểm ngắt bên trong trình xử lý tín hiệu B.

Bây giờ, giết nhiều hơn và B sẽ phá vỡ bộ xử lý tín hiệu của bạn. kiểm tra ngăn xếp. Bạn sẽ thấy rằng bài đọc vẫn đang chờ xử lý. hãy để trình xử lý tín hiệu tiến hành và quay lại, và xem kết quả trả về bằng ghi - sau đó sẽ là -1.

[1] Đương nhiên, bạn sẽ viết chương trình B của mình bằng C. :-)


3
Tại sao B lại thấy C chấm dứt sớm hơn với SIGPIPE? B sẽ vẫn bị chặn khi đọc cho đến khi một thứ gì đó được ghi vào STDIN của nó, lúc đó nó sẽ gọi lệnh write () và chỉ khi đó SIGPIPE mới được nâng lên / -1 được trả về.
Shea Levy

2
Tôi thực sự thích câu trả lời: SIGPIPE cho phép cái chết lan truyền trở lại từ đầu ra của đường ống ngay lập tức. Nếu không có điều này, nó cần đến một chu kỳ của chương trình sao chép cho mỗi N phần tử của đường ống để tiêu diệt đường ống và khiến phía đầu vào tạo ra N đường thẳng không bao giờ đạt đến cuối.
Yttrill

18
Câu trả lời này không chính xác. SIGPIPEkhông giao trong thời gian đọc, nhưng trong quá trình write. Bạn không cần phải viết một chương trình C để kiểm tra nó, chỉ cần chạy cat | headpkill headtrong một thiết bị đầu cuối riêng biệt. Bạn sẽ thấy rằng catcuộc sống hạnh phúc khi chờ đợi nó read()— chỉ khi bạn nhập một thứ gì đó vào và nhấn enter thì nó sẽ catchết với một đường ống bị hỏng, chính xác là vì nó đã cố gắng ghi đầu ra.
dùng4815162342

5
-1 SIGPIPEkhông thể được gửi đến Btrong khi Bbị chặn trên readSIGPIPEkhông được tạo cho đến khi Bthử write. Không có luồng nào có thể "đang đợi I / O hoặc bị treo" trong khi gọi writecùng một lúc.
Dan Molding

3
Bạn có thể đăng một chương trình đầy đủ cho thấy SIGPIPEđược nâng lên trong khi bị chặn trên một read? Tôi không thể tái tạo rằng hành vi ở tất cả (và tôi không thực sự chắc chắn lý do tại sao tôi chấp nhận điều này ở nơi đầu tiên)
Shea Levy

7

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

Liên kết này cho biết:

Một đường ống hoặc FIFO phải được mở đồng thời ở cả hai đầu. Nếu bạn đọc từ một đường ống hoặc tệp FIFO mà không có bất kỳ quy trình nào ghi vào nó (có thể vì tất cả chúng đã đóng tệp hoặc thoát), việc đọc sẽ trả về cuối tệp. Việc ghi vào một đường ống hoặc FIFO không có quy trình đọc được coi là một điều kiện lỗi; nó tạo ra tín hiệu SIGPIPE và không thành công với mã lỗi EPIPE nếu tín hiệu được xử lý hoặc bị chặn.

- Macro: int SIGPIPE

Đường ống bị vỡ. Nếu bạn sử dụng đường ống hoặc FIFO, bạn phải thiết kế ứng dụng của mình để một quá trình mở đường ống để đọc trước khi quá trình khác bắt đầu viết. Nếu quá trình đọc không bao giờ bắt đầu hoặc kết thúc đột ngột, việc ghi vào đường dẫn hoặc FIFO sẽ phát ra tín hiệu SIGPIPE. Nếu SIGPIPE bị chặn, bị xử lý hoặc bị bỏ qua, cuộc gọi vi phạm sẽ không thành công với EPIPE.

Các tệp đặc biệt của Pipes và FIFO được thảo luận chi tiết hơn trong Pipes và FIFO.


5

Tôi nghĩ rằng nó là để xử lý lỗi chính xác mà không yêu cầu nhiều mã trong mọi thứ ghi vào đường ống.

Một số chương trình bỏ qua giá trị trả về của write(); nếu không có SIGPIPEchúng sẽ tạo ra tất cả đầu ra một cách vô ích.

Các chương trình kiểm tra giá trị trả về write()có khả năng in ra thông báo lỗi nếu nó không thành công; điều này là không phù hợp cho một đường ống bị vỡ vì nó không thực sự là một lỗi cho toàn bộ đường ống.


2

Thông tin máy móc:

Linux 3.2.0-53-generic # 81-Ubuntu SMP Thứ Năm 22 tháng 8 21:01:03 UTC 2013 x86_64 x86_64 x86_64 GNU / Linux

gcc phiên bản 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5)

Tôi đã viết mã này dưới đây:

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

}

Đầu ra:

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

Bạn có thể thấy rằng trong mọi trường hợp SIGPIPEchỉ được nhận sau khi có hơn 3 ký tự được viết bởi quá trình viết.

Điều này không chứng minh rằng điều đó SIGPIPEkhông được tạo ra ngay sau khi quá trình đọc kết thúc mà là sau khi cố gắng ghi thêm một số dữ liệu vào một đường ống đóng?

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.