2012-03-19 29 views
13

Trong ứng dụng của tôi, có một io sợi, đó là dành riêng choLinux select() vs ppoll() vs pselect()

  1. dữ liệu gói nhận được từ các ứng dụng trong một giao thức tùy chỉnh
  2. Gửi gói dữ liệu + giao thức tùy chỉnh trên tcp/ip
  3. Nhận dữ liệu + gói giao thức tùy chỉnh qua tcp/ip
  4. Hủy bỏ giao thức tùy chỉnh và chuyển dữ liệu đến ứng dụng.

Ứng dụng xử lý dữ liệu qua một chuỗi khác. Ngoài ra, các yêu cầu quy định rằng kích thước cửa sổ chưa được trả lời phải là 1, tức là chỉ có một thông báo chưa được giải quyết đang chờ xử lý bất kỳ lúc nào. Điều này ngụ ý rằng nếu io-thread đã gửi một tin nhắn qua socket, nó sẽ không gửi thêm bất kỳ tin nhắn nào, cho đến khi nó nghe thấy một ack từ máy thu. Chủ đề xử lý của ứng dụng liên lạc với io-thread qua đường ống. Ứng dụng cần phải đóng một cách duyên dáng nếu ai đó từ các kiểu CLI Linux ctrl + C. tùy chọn Do đó, đưa ra các yêu cầu này, tôi đã sau

  1. Sử dụng PPoll() trên socket và ống mô tả
  2. Sử dụng Select() câu hỏi
  3. Sử dụng PSelect()

Tôi đã sau

  1. Quyết định giữa select() và poll(). Ứng dụng của tôi chỉ đề cập đến ít hơn 50 bộ mô tả tệp. Có thể giả định rằng sẽ không có sự khác biệt cho dù tôi chọn chọn hay bình chọn?

    1. Quyết định giữa select() và pselect(). Tôi đọc tài liệu linux và nó nói về điều kiện chủng tộc giữa các tín hiệu và chọn(). Tôi không có kinh nghiệm với các tín hiệu, vì vậy ai đó có thể giải thích rõ hơn về điều kiện chủng tộc và chọn()? Liệu nó có liên quan đến việc ai đó đang nhấn ctrl + C trên CLI và ứng dụng không dừng lại không?

    2. Quyết định giữa pselect và ppoll()? Bất kỳ suy nghĩ về một vs người kia

Trả lời

20

Tôi muốn đề xuất bằng cách bắt đầu so sánh với select()poll(). Linux cũng cung cấp cả pselect()ppoll(); và đối số thêm const sigset_t * đối với pselect()ppoll() (so với select()poll()) có cùng tác dụng trên mỗi "biến thể p", giống như vậy. Nếu bạn không sử dụng tín hiệu, bạn không có chủng tộc để bảo vệ chống lại, do đó, câu hỏi cơ sở thực sự là về hiệu quả và dễ lập trình.

Trong khi đó, đã có câu trả lời stackoverflow.com tại đây: what are the differences between poll and select.

Đối với cuộc đua: một khi bạn bắt đầu sử dụng tín hiệu (vì lý do gì), bạn sẽ biết rằng nói chung, bộ xử lý tín hiệu chỉ cần đặt một loại volatile sig_atomic_t để cho biết tín hiệu đã được phát hiện. Lý do cơ bản cho điều này là nhiều cuộc gọi thư viện không phải là re-entrant và tín hiệu có thể được gửi trong khi bạn đang "ở giữa" một thói quen như vậy. Ví dụ, chỉ cần in một thông báo đến một cấu trúc dữ liệu kiểu luồng như stdout (C) hoặc cout (C++) có thể dẫn đến các vấn đề tái nhập.

Giả sử bạn có mã mà sử dụng một biến volatile sig_atomic_t flag, có lẽ để bắt SIGINT, một cái gì đó như thế này (xem thêm http://pubs.opengroup.org/onlinepubs/007904975/functions/sigaction.html):

volatile sig_atomic_t got_interrupted = 0; 
void caught_signal(int unused) { 
    got_interrupted = 1; 
} 
... 
    struct sigaction sa; 
    sa.sa_handler = caught_signal; 
    sigemptyset(&sa.sa_mask); 
    sa.sa_flags = SA_RESTART; 
    if (sigaction(SIGINT, &sa, NULL) == -1) ... handle error ... 
    ... 

Bây giờ, trong phần chính của mã của bạn, bạn có thể muốn để "chạy cho đến khi bị gián đoạn":

while (!got_interrupted) { 
     ... do some work ... 
    } 

Điều này là tốt cho đến khi bạn khởi động cần phải thực hiện cuộc gọi mà chờ đợi đối với một số đầu vào/đầu ra, chẳng hạn như select hoặc poll. Hành động "chờ" cần đợi cho I/O đó, nhưng nó cũng cần phải chờ cho việc ngắt SIGINT. Nếu bạn chỉ cần viết:

while (!got_interrupted) { 
     ... do some work ... 
     result = select(...); /* or result = poll(...) */ 
    } 

sau đó nó có thể là các ngắt sẽ xảy ra ngay trước khi bạn gọi select() hoặc poll(), chứ không phải sau đó. Trong trường hợp này, bạn đã bị gián đoạn — và biến số got_interrupted được đặt — nhưng sau đó, bạn bắt đầu chờ. Bạn nên kiểm tra biến số got_interrupted trước khi bắt đầu chờ đợi, không phải sau đó.

Bạn có thể thử viết:

while (!got_interrupted) { 
     ... do some work ... 
     if (!got_interrupted) 
      result = select(...); /* or result = poll(...) */ 
    } 

này co lại cửa sổ "cuộc đua", bởi vì bây giờ bạn sẽ phát hiện ra sự ngắt nếu nó xảy ra trong khi bạn đang ở trong "làm một số công việc" mã; nhưng vẫn còn một cuộc đua, bởi vì gián đoạn có thể xảy ra ngay sau bạn kiểm tra biến, nhưng ngay trước lựa chọn hoặc bỏ phiếu.

Giải pháp là làm cho "kiểm tra, sau đó chờ đợi" chuỗi "nguyên tử", sử dụng các tính chất tín hiệu chặn của sigprocmask (hoặc, trong POSIX ren mã, pthread_sigmask):

sigset_t mask, omask; 
... 
while (!got_interrupted) { 
    ... do some work ... 
    /* begin critical section, test got_interrupted atomically */ 
    sigemptyset(&mask); 
    sigaddset(&mask, SIGINT); 
    if (sigprocmask(SIG_BLOCK, &mask, &omask)) 
     ... handle error ... 
    if (got_interrupted) { 
     sigprocmask(SIG_SETMASK, &omask, NULL); /* restore old signal mask */ 
     break; 
    } 
    result = pselect(..., &omask); /* or ppoll() etc */ 
    sigprocmask(SIG_SETMASK, &omask, NULL); 
    /* end critical section */ 
} 

(trên mã thực sự không phải là tuyệt vời, nó được cấu trúc để minh họa hơn là hiệu quả - nó hiệu quả hơn để làm thao tác mặt nạ tín hiệu hơi khác một chút, và đặt các bài kiểm tra "bị gián đoạn" một cách khác nhau).

Cho đến khi bạn thực sự bắt đầu cần phải bắt SIGINT, tuy nhiên, bạn chỉ cần so sánh select()poll() (và nếu bạn bắt đầu cần một số lượng lớn mô tả, một số các công cụ dựa trên sự kiện như epoll() là hiệu quả hơn hoặc là một).

+0

"Lý do cơ bản cho điều này là nhiều cuộc gọi thư viện không được tái nhập" Vì vậy, những gì sẽ xảy ra nếu một tín hiệu được giao trong khi chúng tôi đang ở giữa nói "đọc". Điều này có nghĩa là chúng ta không bao giờ có thể sử dụng đọc một lần nữa? – kptlronyttcna

+0

@kptlronyttcna: không, nhưng cũng hơi có: bạn không thể gọi một cách an toàn, ví dụ: 'fread' trên stdin trong trình xử lý tín hiệu. Điều đó không có nghĩa là bạn có thể * không bao giờ * gọi 'fread', chỉ là bạn không thể làm điều đó trên một biến chia sẻ trong một trình xử lý tín hiệu. Các chi tiết chính xác về những gì bạn có thể làm một cách an toàn và những gì bạn không thể thay đổi tùy theo thư viện, hệ thống và các chi tiết khác. (Là một điểm phụ, khi 'đọc' là một cuộc gọi hệ thống, vì nó nằm trên Linux và BSD và Mac, hành vi chính xác của nó phụ thuộc vào đối tượng hệ thống tệp cơ bản." Các thiết bị chậm "có thể trả về lỗi EINTR hoặc đọc ngắn.) – torek

4

giữa (p) lựa chọn và (p) bình chọn là một sự khác biệt khá tinh tế:

Đối với lựa chọn, bạn có thể khởi tạo và cư trú trong bitmap fd_set xấu xí mọi lúc trước khi bạn gọi chọn vì chọn sửa đổi chúng tại chỗ theo kiểu "phá hoại". (cuộc thăm dò phân biệt giữa các thành viên .events.revents trong struct pollfd).

Sau khi chọn, toàn bộ bitmap thường được quét (bởi người/mã) cho các sự kiện ngay cả khi hầu hết các fds đều không được xem. Thứ ba, bitmap chỉ có thể đối phó với fds có số ít hơn một giới hạn nhất định (triển khai hiện đại: ở đâu đó giữa 1024..4096), quy tắc trong các chương trình có fds cao có thể đạt được một cách dễ dàng (mặc dù vậy các chương trình có khả năng đã sử dụng epoll thay thế).

+2

Cảm giác của tôi là 'select' không nên được sử dụng trong các chương trình vừa mới viết, nhưng chỉ có' ​​poll' [hoặc 'ppoll'] (ít nhất trên các hệ thống như không Linux quá cũ, nơi cả 'select' và' poll' đều là các cuộc gọi hệ thống riêng). Tôi tin rằng 'chọn' chỉ dành cho các chương trình cũ ... –

+1

@BasileStarynkevitch Trích dẫn cần thiết ... – paradigmatic

+0

http://www.kegel.com/c10k.html hoặc đơn giản là thực tế là kích thước của' fd_set' đang hạn chế tối đa tập tin mô tả chặt chẽ hơn nhiều so với những gì 'poll' cho phép. –

0

Câu trả lời được chấp nhận không đúng đối với sự khác biệt rõ ràng giữa lựa chọn và chọn lựa. Nó mô tả tốt cách một điều kiện chủng tộc giữa sig-handler và select có thể phát sinh, nhưng nó không chính xác trong cách nó sử dụng pselect để giải quyết vấn đề. Nó bỏ lỡ các điểm chính về pselect đó là nó chờ đợi cho EITHER bộ mô tả tập tin hoặc tín hiệu để sẵn sàng. pselect trả về khi một trong hai tùy chọn này sẵn sàng. Chọn ONLY chờ trên bộ mô tả tệp. Chọn bỏ qua tín hiệu. Xem bài đăng trên blog này để có ví dụ hoạt động tốt: https://www.linuxprogrammingblog.com/code-examples/using-pselect-to-avoid-a-signal-race

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