2008-12-12 35 views
9

Tôi quan tâm đến việc viết các mô-đun chương trình riêng biệt chạy dưới dạng các luồng độc lập mà tôi có thể nối với các đường ống. Động lực sẽ là tôi có thể viết và kiểm tra từng mô-đun hoàn toàn độc lập, thậm chí có thể viết chúng bằng các ngôn ngữ khác nhau hoặc chạy các mô-đun khác nhau trên các máy khác nhau. Có rất nhiều khả năng ở đây. Tôi đã sử dụng đường ống trong một thời gian, nhưng tôi không quen với các sắc thái của hành vi của nó.Đường ống như liên lạc liên ngành

  • Có vẻ như đầu nhận sẽ chặn việc chờ đầu vào, điều mà tôi mong đợi, nhưng khối kết thúc gửi đôi khi có thể chờ ai đó đọc từ luồng không?
  • Nếu tôi viết một eof cho luồng, tôi có thể tiếp tục viết cho luồng đó cho đến khi tôi đóng nó không?
  • Có sự khác biệt nào về hành vi được đặt tên và các đường ống chưa đặt tên?
  • Có vấn đề gì ở đầu ống tôi mở đầu tiên với các đường ống có tên không?
  • Hành vi của các đường ống có nhất quán giữa các hệ thống Linux khác nhau không?
  • Hành vi của các đường ống có phụ thuộc vào vỏ tôi đang sử dụng hay cách tôi đã định cấu hình nó?
  • Có bất kỳ câu hỏi nào khác mà tôi nên hỏi hoặc các vấn đề tôi nên biết nếu tôi muốn sử dụng ống theo cách này?

Trả lời

4

Ồ, đó là rất nhiều câu hỏi. Chúng ta hãy xem nếu tôi có thể bao gồm tất cả mọi thứ ...

Nó có vẻ như kết thúc nhận khối sẽ chờ đợi cho đầu vào, mà tôi sẽ mong đợi

Bạn mong đợi một cách chính xác cuộc gọi 'đọc' thực tế sẽ chặn cho đến khi có thứ gì đó ở đó. Tuy nhiên, tôi tin rằng có một số chức năng C sẽ cho phép bạn 'peek' tại những gì (và bao nhiêu) đang chờ đợi trong đường ống. Thật không may, tôi không nhớ nếu điều này khối là tốt.

sẽ khối cuối gửi đôi khi chờ đợi một người nào đó để đọc từ dòng

Không, không bao giờ nên gửi chặn. Hãy suy nghĩ về các nhánh nếu đây là một đường ống trên mạng đến một máy tính khác. Bạn có muốn đợi (thông qua độ trễ có thể cao) cho máy tính khác phản hồi rằng nó đã nhận được nó không? Bây giờ đây là trường hợp khác nếu trình đọc của đích đến đã bị đóng. Trong trường hợp này, bạn nên có một số lỗi kiểm tra để xử lý điều đó.

Nếu tôi viết một eof cho luồng dữ liệu tôi có thể giữ tiếp tục bằng văn bản cho rằng dòng cho đến khi tôi đóng nó

Tôi nghĩ rằng điều này phụ thuộc vào những gì ngôn ngữ mà bạn đang sử dụng và việc thực hiện các ống. Trong C, tôi muốn nói không. Trong một vỏ linux, tôi muốn nói có. Người khác có nhiều kinh nghiệm hơn sẽ phải trả lời điều đó.

Có sự khác biệt nào trong hành vi đường ống có tên và chưa đặt tên? Theo như tôi biết, có. Tuy nhiên, tôi không có nhiều kinh nghiệm với tên vs unnamed.Tôi tin rằng sự khác biệt là:

  • hướng Độc vs hai chiều giao tiếp
  • Reading VÀ văn bản cho "trong" suối "out" của một thread

Liệu nó có vấn đề mà cuối đường ống I mở đầu tiên với các đường ống có tên?

Nói chung là không, nhưng bạn có thể gặp sự cố khi khởi tạo cố gắng tạo và liên kết chủ đề với nhau. Bạn sẽ cần phải có một chủ đề chính để tạo ra tất cả các tiểu chủ đề và đồng bộ đường ống tương ứng của họ với nhau.

Hành vi của các đường ống có phù hợp giữa các hệ thống Linux khác nhau không?

Một lần nữa, điều này tùy thuộc vào ngôn ngữ nào, nhưng nói chung là có. Bạn đã từng nghe đến POSIX chưa? Đó là tiêu chuẩn (ít nhất là cho Linux, Windows thực hiện điều đó).

Liệu các hành vi của các đường ống phụ thuộc trên vỏ Tôi đang sử dụng hoặc cách tôi đã cấu hình nó?

Điều này đang tiến sâu hơn một chút so với khu vực màu xám. Câu trả lời nên là không kể từ khi trình bao về cơ bản nên thực hiện cuộc gọi hệ thống. Tuy nhiên, tất cả mọi thứ cho đến khi đó là điểm cho lấy.

Có bất kỳ câu hỏi khác tôi nên được hỏi

Những câu hỏi bạn đã hỏi cho thấy bạn có một sự hiểu biết phong nha của hệ thống. Tiếp tục nghiên cứu và tập trung vào mức độ bạn sẽ làm việc (shell, C, v.v.). Bạn sẽ học được nhiều hơn nữa bằng cách chỉ cần thử nó mặc dù.

+0

Nhìn trộm nội dung của đường ống có chỉ số stat() không đáng tin cậy trên tất cả các nền tảng. –

+1

Kết thúc bằng văn bản có thể chặn nếu đệm ống đầy - nó không phải là rất lớn. –

+1

Ống chéo máy ... không tồn tại? Cách tiếp cận gần nhất có lẽ là một ổ cắm, nhưng điều đó không giống như một đường ống. –

4

Đây là tất cả dựa trên hệ thống giống UNIX; Tôi không quen với hành vi cụ thể của các phiên bản Windows gần đây.

Dường như kết thúc nhận sẽ chặn việc chờ đầu vào, điều mà tôi mong đợi, nhưng khối kết thúc gửi có khi chờ ai đó đọc từ luồng không?

Có, mặc dù trên máy hiện đại, nó có thể không xảy ra thường xuyên. Đường ống có bộ đệm trung gian mà có thể có khả năng lấp đầy. Nếu có, mặt viết của ống sẽ thực sự bị chặn. Nhưng nếu bạn nghĩ về nó, không có nhiều tệp đủ lớn để mạo hiểm điều này.

Nếu tôi viết eF cho luồng, tôi có thể tiếp tục viết cho luồng đó cho đến khi tôi đóng nó không?

Um, ý bạn là CTRL-D, 0x04? Chắc chắn, miễn là luồng được thiết lập theo cách đó. Viz.

506 # cat | od -c 
abc 
^D 
efg 
0000000 a b c \n 004 \n e f g \n       
0000012 

Có sự khác biệt về hành vi được đặt tên và tên chưa đặt tên?

Có, nhưng chúng phụ thuộc tinh tế và triển khai. Cái lớn nhất là bạn có thể viết vào một đường ống có tên trước khi đầu kia đang chạy; với các đường dẫn chưa đặt tên, các bộ mô tả tập tin được chia sẻ trong quá trình fork/exec, vì vậy không có cách nào để truy cập bộ đệm tạm thời mà không có các tiến trình đang lên.

Có vấn đề gì ở đầu ống tôi mở đầu tiên với các đường ống có tên không?

Không.

Hành vi của các đường ống có nhất quán giữa các hệ thống Linux khác nhau không?

Trong lý do, có. Kích thước bộ đệm vv có thể thay đổi.

Hành vi của các đường ống có phụ thuộc vào vỏ tôi đang sử dụng hoặc cách tôi định cấu hình không?

số Khi bạn tạo một đường ống, dưới tấm chăn những gì xảy ra là quá trình cha mẹ của bạn (vỏ) tạo ra một đường ống trong đó có một cặp mô tả tập tin, sau đó thực hiện một exec ngã ba như giả này:

Chánh:

create pipe, returning two file descriptors, call them fd[0] and fd[1] 
fork write-side process 
fork read-side process 

Write-side:

close fd[0] 
connect fd[1] to stdout 
exec writer program 

đọc-side:

close fd[1] 
connect fd[0] to stdin 
exec reader program 

Có bất kỳ câu hỏi khác tôi nên hỏi hoặc vấn đề mà tôi cần phải nhận thức nếu tôi muốn sử dụng ống theo cách này?

Mọi thứ bạn muốn thực sự sắp xếp thành một hàng như thế này phải không? Nếu không, bạn có thể muốn suy nghĩ về một kiến ​​trúc tổng quát hơn. Nhưng cái nhìn sâu sắc rằng có rất nhiều quy trình riêng biệt tương tác thông qua giao diện "hẹp" của một đường ống là mong muốn là một điều tốt.

[Đã cập nhật: Lúc đầu tôi đã có các chỉ số mô tả tệp được đảo ngược. Giờ chúng đã chính xác, hãy xem man 2 pipe.]

+0

Sự cố 1: Bạn không thể ghi EOF vào tệp hoặc đường ống; bạn đóng tập tin hoặc đường ống để chỉ EOF (hoặc, với một tập tin, bạn có thể cắt ngắn nó). Control-D không phải là EOF ngoại trừ trong bối cảnh của một thiết bị đầu cuối. –

+0

Vấn đề 2: Bạn không thể ghi vào một đường ống có tên trước khi có người nhận, ngay cả khi bạn mở bằng O_NONBLOCK. –

+0

Vấn đề 3: Trên hầu hết các hệ thống, kích thước bộ đệm ống khá nhỏ, như 4096 hoặc 5120 byte. Nó không mất quá nhiều đầu ra để lấp đầy. –

4

Như Dashogun và Charlie Martin lưu ý, đây là một câu hỏi lớn. Một số phần của câu trả lời của họ là không chính xác, vì vậy tôi cũng sẽ trả lời.

Tôi quan tâm đến việc viết các mô-đun chương trình riêng biệt chạy dưới dạng các chuỗi độc lập mà tôi có thể nối với các đường ống.

Hãy cảnh giác với việc cố gắng sử dụng đường ống làm cơ chế giao tiếp giữa các luồng của một quy trình. Bởi vì bạn sẽ có cả đọc và viết kết thúc của đường ống mở trong một quá trình duy nhất, bạn sẽ không bao giờ nhận được chỉ báo EOF (không byte).

Nếu bạn thực sự đề cập đến các quy trình, thì đây là cơ sở của phương pháp Unix cổ điển để xây dựng các công cụ. Nhiều chương trình Unix chuẩn là các bộ lọc đọc từ đầu vào tiêu chuẩn, biến đổi nó bằng cách nào đó và ghi kết quả vào đầu ra tiêu chuẩn. Ví dụ: tr, sort, grepcat là tất cả các bộ lọc, nhưng chỉ một vài bộ lọc. Đây là một mô hình tuyệt vời để làm theo khi dữ liệu bạn đang thao tác cho phép nó. Không phải tất cả các thao tác dữ liệu đều có lợi cho cách tiếp cận này, nhưng có rất nhiều thao tác.

Động lực là tôi có thể viết và kiểm tra từng mô-đun hoàn toàn độc lập, thậm chí có thể viết chúng bằng các ngôn ngữ khác nhau hoặc chạy các mô-đun khác nhau trên các máy khác nhau.

Điểm tốt. Lưu ý rằng không có cơ chế ống thực sự giữa các máy, mặc dù bạn có thể gần với các chương trình như rsh hoặc (tốt hơn) ssh. Tuy nhiên, trong nội bộ, các chương trình như vậy có thể đọc dữ liệu cục bộ từ các đường ống và gửi dữ liệu đó đến các máy từ xa, nhưng chúng giao tiếp giữa các máy trên các ổ cắm, không sử dụng đường ống.

Có nhiều khả năng ở đây. Tôi đã sử dụng đường ống trong một thời gian, nhưng tôi không quen với các sắc thái của hành vi của nó.

OK; đặt câu hỏi là một cách tốt để học. Thí nghiệm là một thử nghiệm khác, tất nhiên.

Có vẻ như đầu nhận sẽ chặn việc chờ đầu vào, điều mà tôi mong đợi, nhưng khối kết thúc gửi đôi khi có thể chờ ai đó đọc từ luồng không?

Có. Có giới hạn về kích thước của bộ đệm ống. Về mặt cổ điển, điều này khá nhỏ - 4096 hoặc 5120 là các giá trị chung. Bạn có thể thấy rằng Linux hiện đại sử dụng một giá trị lớn hơn. Bạn có thể sử dụng fpathconf() và _PC_PIPE_BUF để tìm kích thước của bộ đệm ống. POSIX chỉ yêu cầu bộ đệm là 512 (nghĩa là, _POSIX_PIPE_BUF là 512).

Nếu tôi viết eOF cho luồng, tôi có thể tiếp tục viết cho luồng đó cho đến khi đóng không?

Về mặt kỹ thuật, không có cách nào để viết EOF vào luồng; bạn đóng bộ mô tả đường ống để chỉ EOF. Nếu bạn đang nghĩ đến control-D hoặc control-Z như một ký tự EOF, thì đó chỉ là các ký tự thông thường như các đường ống liên quan - chúng chỉ có hiệu ứng như EOF khi gõ vào một terminal đang chạy ở chế độ chuẩn , hoặc bình thường).

Có sự khác biệt nào về hành vi được đặt tên và đường ống chưa đặt tên?

Có, và không. Sự khác biệt lớn nhất là các đường ống không được đặt tên phải được thiết lập bởi một quá trình và chỉ có thể được sử dụng bởi quá trình đó và trẻ em chia sẻ quy trình đó như một tổ tiên chung. Ngược lại, các ống được đặt tên có thể được sử dụng bởi các quá trình không liên kết trước đó. Sự khác biệt lớn tiếp theo là hậu quả của việc đầu tiên; với một đường ống chưa đặt tên, bạn lấy lại hai bộ mô tả tập tin từ một hàm duy nhất (hệ thống) thành pipe(), nhưng bạn mở một FIFO hoặc một đường ống có tên bằng cách sử dụng hàm open() thông thường.(Một người nào đó phải tạo một FIFO với cuộc gọi mkfifo() trước khi bạn có thể mở nó; các đường ống chưa đặt tên không cần bất kỳ thiết lập nào trước đó.) Tuy nhiên, một khi bạn có một bộ mô tả tập tin mở ra, có sự khác biệt rất nhỏ giữa một đường ống có tên và một đường ống chưa đặt tên .

Có vấn đề gì ở cuối ống tôi mở đầu tiên với các đường ống có tên không?

Không. Quy trình đầu tiên để mở FIFO sẽ (thường) chặn cho đến khi có quá trình với đầu kia mở. Nếu bạn mở nó để đọc và viết (theo cách thông thường nhưng có thể) thì bạn sẽ không bị chặn; nếu bạn sử dụng cờ O_NONBLOCK, bạn sẽ không bị chặn.

Hành vi của các đường ống có nhất quán giữa các hệ thống Linux khác nhau không?

Có. Tôi đã không nghe nói hoặc gặp bất kỳ vấn đề với đường ống trên bất kỳ hệ thống mà tôi đã sử dụng chúng.

Hành vi của các đường ống có phụ thuộc vào vỏ tôi đang sử dụng hoặc cách tôi định cấu hình không?

Không: ống và FIFO độc lập với vỏ mà bạn sử dụng.

Có bất kỳ câu hỏi nào khác mà tôi nên hỏi hoặc các vấn đề tôi cần biết nếu tôi muốn sử dụng ống theo cách này?

Chỉ cần nhớ rằng bạn phải đóng đầu đọc của một đường ống trong quá trình sẽ viết và phần cuối của đường ống trong quá trình sẽ đọc. Nếu bạn muốn giao tiếp hai chiều trên đường ống, hãy sử dụng hai ống riêng biệt. Nếu bạn tạo ra sự sắp xếp phức tạp của hệ thống ống nước, hãy cẩn thận về bế tắc - điều đó là có thể. Tuy nhiên, đường ống tuyến tính không bế tắc (mặc dù nếu quy trình đầu tiên không bao giờ đóng đầu ra của nó, các quy trình hạ nguồn có thể đợi vô thời hạn).


Tôi đã quan sát cả hai bên trên và trong nhận xét cho các câu trả lời khác rằng bộ đệm ống được giới hạn theo kiểu cổ điển với kích thước khá nhỏ. @Charlie Martin phản luận rằng một số phiên bản của Unix có bộ đệm ống động và chúng có thể khá lớn.

Tôi không chắc chắn bạn đang nghĩ gì. Tôi đã sử dụng các chương trình thử nghiệm mà sau trên Solaris, AIX, HP-UX, MacOS X, Linux và Cygwin/Windows XP (kết quả dưới đây):

#include <unistd.h> 
#include <signal.h> 
#include <stdio.h> 
#include <fcntl.h> 
#include <stdlib.h> 
#include <errno.h> 
#include <string.h> 

static const char *arg0; 

static void err_syserr(char *str) 
{ 
    int errnum = errno; 
    fprintf(stderr, "%s: %s - (%d) %s\n", arg0, str, errnum, strerror(errnum)); 
    exit(1); 
} 

int main(int argc, char **argv) 
{ 
    int pd[2]; 
    pid_t kid; 
    size_t i = 0; 
    char buffer[2] = "a"; 
    int flags; 

    arg0 = argv[0]; 

    if (pipe(pd) != 0) 
     err_syserr("pipe() failed"); 
    if ((kid = fork()) < 0) 
     err_syserr("fork() failed"); 
    else if (kid == 0) 
    { 
     close(pd[1]); 
     pause(); 
    } 
    /* else */ 
    close(pd[0]); 
    if (fcntl(pd[1], F_GETFL, &flags) == -1) 
     err_syserr("fcntl(F_GETFL) failed"); 
    flags |= O_NONBLOCK; 
    if (fcntl(pd[1], F_SETFL, &flags) == -1) 
     err_syserr("fcntl(F_SETFL) failed"); 
    while (write(pd[1], buffer, sizeof(buffer)-1) == sizeof(buffer)-1) 
    { 
     putchar('.'); 
     if (++i % 50 == 0) 
      printf("%u\n", (unsigned)i); 
    } 
    if (i % 50 != 0) 
     printf("%u\n", (unsigned)i); 
    kill(kid, SIGINT); 
    return 0; 
} 

Tôi tò mò muốn được có được kết quả thêm từ các nền tảng khác. Dưới đây là các kích thước tôi tìm thấy. Tất cả các kết quả đều lớn hơn tôi mong đợi, tôi phải thú nhận, nhưng Charlie và tôi có thể tranh luận về ý nghĩa của 'khá lớn' khi nói đến kích thước bộ đệm.

  •   8196 - HP-UX 11,23 cho IA-64 (fcntl (F_SETFL) thất bại)
  • 16384 - Solaris 10
  • 16384 - hệ điều hành MacOS X 10.5 (O_NONBLOCK đã không làm việc, mặc dù fcntl (F_SETFL) đã không thất bại)
  • 32768 - AIX 5.3
  • 65536 - Cygwin/Windows XP (O_NONBLOCK đã không làm việc, mặc dù fcntl (F_SETFL) đã không thất bại)
  • 65536 - SuSE Linux 10 (và CentOS) (fcntl (F_SETFL) thất bại)

Một điểm rõ ràng từ các thử nghiệm này là O_NONBLOCK hoạt động với các đường ống trên một số nền tảng chứ không phải trên các nền tảng khác.

Chương trình tạo đường ống và dĩa. Đứa trẻ đóng đầu ghi của đường ống, và sau đó đi ngủ cho đến khi nó nhận được tín hiệu - đó là những gì pause() làm. Sau đó, cha mẹ sẽ đóng đầu đọc của đường ống và đặt các cờ trên bộ mô tả ghi để nó không chặn trên một nỗ lực viết trên một đường ống đầy đủ. Sau đó nó lặp lại, viết một ký tự tại một thời điểm, và in một dấu chấm cho mỗi ký tự được viết, và đếm và dòng mới mỗi 50 ký tự. Khi nó phát hiện một vấn đề viết (bộ đệm đầy đủ, kể từ khi đứa trẻ không đọc một điều), nó dừng vòng lặp, viết số cuối cùng, và giết chết đứa trẻ.

+0

Đây là một câu trả lời tuyệt vời. – jfs

+0

CentOS - fcntl (F_SETFL) không thành công; Ubuntu - chỉ chặn sau ... 65500. – jfs

+0

Cảm ơn, J F Sebastian. Đó là những gì tôi thấy với SuSE; giới hạn 65536 được suy ra. –

Các vấn đề liên quan