2013-01-03 35 views
11

Như đã biết, một số cuộc gọi chặn như readwrite sẽ trả lại -1 và đặt errno thành EINTR và chúng tôi cần xử lý việc này.EINTR và các cuộc gọi không chặn

Câu hỏi của tôi là: Điều này có áp dụng cho các cuộc gọi không chặn không, ví dụ: đặt ổ cắm thành O_NONBLOCK?

Vì một số bài viết và nguồn tôi đã đọc cho biết các cuộc gọi không chặn không cần bận tâm với điều này, nhưng tôi đã không tìm thấy tài liệu tham khảo có thẩm quyền về nó. Nếu vậy, nó có áp dụng các cách triển khai khác nhau không?

Trả lời

20

Tôi không thể cung cấp cho bạn câu trả lời chính xác cho câu hỏi này và câu trả lời có thể khác với hệ thống, nhưng tôi mong đợi một ổ cắm không chặn không bao giờ bị lỗi với EINTR. Nếu bạn xem các trang hướng dẫn của các hệ thống khác nhau cho các chức năng ổ cắm sau đây bind(), connect(), send() và , hoặc xem chúng trong tiêu chuẩn POSIX, bạn sẽ thấy một điều thú vị: Tất cả các chức năng này ngoại trừ một hàm có thể trả về -1 và đặt errno thành EINTR. Một chức năng không được ghi lại là không thành công với EINTRbind(). Và bind() cũng là chức năng duy nhất của danh sách đó sẽ không bao giờ chặn theo mặc định. Vì vậy, có vẻ như chỉ có chức năng chặn có thể không thành công vì EINTR, bao gồm read()write(), nhưng nếu các chức năng này không bao giờ chặn, chúng cũng sẽ không bao giờ bị lỗi với EINTR và nếu bạn sử dụng O_NONBLOCK, các chức năng đó sẽ không bao giờ chặn.

Nó cũng sẽ không có ý nghĩa từ góc nhìn logic. Ví dụ. xem xét bạn đang sử dụng chặn I/O và bạn gọi read() và cuộc gọi này phải chặn, nhưng trong khi nó đang chặn, một tín hiệu được gửi đến quá trình của bạn và do đó yêu cầu đọc được ublocked. Hệ thống xử lý tình huống này như thế nào? Tuyên bố rằng read() đã thành công? Đó sẽ là một lời nói dối, nó không thành công vì không có dữ liệu nào được đọc. Tuyên bố nó đã thành công, nhưng dữ liệu byte không được đọc? Điều này sẽ không chính xác, bởi vì "kết quả đọc không" được sử dụng để biểu thị kết thúc luồng (hoặc kết thúc luồng), do đó quy trình của bạn sẽ giả định rằng không có dữ liệu nào được đọc, vì kết thúc của một tập tin đã đạt được (hoặc một ổ cắm/ống đã được đóng ở đầu kia), mà chỉ đơn giản là không phải là trường hợp. Kết thúc tập tin (hoặc cuối luồng) chưa đạt được, nếu bạn gọi lại read(), nó sẽ có thể trả lại nhiều dữ liệu hơn. Vì vậy, đó cũng sẽ là một lời nói dối. Bạn kỳ vọng là cuộc gọi đọc này thành công và đọc dữ liệu hoặc không thành công với lỗi. Vì vậy, cuộc gọi đọc phải thất bại và trả lại -1 trong trường hợp đó, nhưng giá trị errno nào sẽ được hệ thống đặt? Tất cả các giá trị lỗi khác chỉ ra một lỗi nghiêm trọng với bộ mô tả tập tin, nhưng không có lỗi nghiêm trọng và chỉ ra một lỗi như vậy cũng sẽ là một lời nói dối. Đó là lý do tại sao errno được đặt thành EINTR, có nghĩa là: "Không có gì sai với luồng. Cuộc gọi đọc của bạn không thành công, vì nó bị gián đoạn bởi tín hiệu. Nếu nó không bị gián đoạn, nó vẫn có thể thành công, vì vậy nếu bạn vẫn quan tâm đến dữ liệu, vui lòng thử lại. "

Nếu bây giờ bạn chuyển sang I/O không chặn, tình huống trên không bao giờ phát sinh. Cuộc gọi đọc sẽ không bao giờ chặn và nếu nó không thể đọc dữ liệu ngay lập tức, nó sẽ không thành công với lỗi EAGAIN (POSIX) hoặc EWOULDBLOCK (không chính thức, trên Linux đều là lỗi tương tự, chỉ thay thế tên cho nó), có nghĩa là: "Có không có sẵn dữ liệu ngay bây giờ và do đó cuộc gọi đọc của bạn sẽ phải chặn và chờ dữ liệu đến, nhưng việc chặn không được phép, do đó, nó không thành công. " Vì vậy, có một lỗi cho mọi tình huống có thể phát sinh.

Tất nhiên, ngay cả với I/O không chặn, cuộc gọi đọc có thể tạm thời bị gián đoạn bởi tín hiệu nhưng tại sao hệ thống phải chỉ ra điều đó? Mỗi chức năng gọi, cho dù đây là một chức năng hệ thống hoặc một bằng văn bản của người sử dụng, có thể tạm thời bị gián đoạn bởi một tín hiệu, thực sự mọi người duy nhất, không có ngoại lệ. Nếu hệ thống sẽ phải thông báo cho người dùng bất cứ khi nào điều đó xảy ra, tất cả các chức năng hệ thống có thể bị lỗi vì EINTR. Tuy nhiên, ngay cả khi có sự gián đoạn tín hiệu, các chức năng thường thực hiện nhiệm vụ của mình từ đầu đến cuối, đó là lý do tại sao gián đoạn này không liên quan. Lỗi EINTR được sử dụng để thông báo cho người gọi rằng hành động mà anh ta yêu cầu không được thực hiện do gián đoạn tín hiệu, nhưng trong trường hợp I/O không chặn, không có lý do nào khiến chức năng không thực hiện việc đọc hoặc ghi yêu cầu, trừ khi nó không thể được thực hiện ngay bây giờ, nhưng sau đó điều này có thể được chỉ định bởi một lỗi thích hợp.

Để xác nhận lý thuyết của mình, tôi đã xem hạt nhân của MacOS (10.8), phần lớn vẫn dựa trên hạt nhân FreeBSD và dường như xác nhận sự nghi ngờ. Nếu một cuộc gọi đọc hiện không khả thi, vì không có sẵn dữ liệu, hạt nhân sẽ kiểm tra cờ O_NONBLOCK trong cờ mô tả tệp. Nếu cờ này được đặt, nó sẽ thất bại ngay lập tức với EAGAIN. Nếu nó không được thiết lập, nó sẽ đặt luồng hiện hành vào chế độ ngủ bằng cách gọi một hàm có tên là msleep(). Chức năng là documented here (như tôi đã nói, OS X sử dụng nhiều mã FreeBSD trong hạt nhân của nó). Chức năng này làm cho luồng hiện tại ngủ cho đến khi nó được đánh thức rõ ràng (trường hợp nếu dữ liệu sẵn sàng để đọc) hoặc thời gian chờ đã được nhấn (ví dụ: bạn có thể đặt thời gian chờ nhận trên ổ cắm). Tuy nhiên, thread cũng được đánh thức, nếu một tín hiệu được gửi đi, trong trường hợp đó, msleep() sẽ trả về EINTR và lớp cao hơn kế tiếp chỉ vượt qua lỗi này. Vì vậy, nó là msleep() sản xuất lỗi EINTR, nhưng nếu cờ O_NONBLOCK được đặt, msleep() không bao giờ được gọi ở vị trí đầu tiên, do đó lỗi này không thể được trả lại. Tất nhiên đó là MacOS/FreeBSD, các hệ thống khác có thể khác nhau, nhưng vì hầu hết các hệ thống cố gắng giữ ít nhất một mức nhất quán nhất quán giữa các API này, nếu hệ thống phá vỡ giả định, I/O không bị chặn cuộc gọi có thể không bao giờ thất bại vì EINTR, điều này có thể không phải theo ý định và thậm chí có thể được khắc phục nếu báo cáo của bạn.

+0

Cảm ơn câu trả lời chi tiết của bạn. – haohaolee

+0

Giải thích hay. Cảm ơn bạn. –

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