Cách xử lý EINTR thích hợp trong các thư viện


10

Các nghi thức được đề nghị khi nói đến EINTRtrong các thư viện là gì?

Tôi hiện đang viết một chức năng thực hiện một số tác vụ hệ thống tệp với API POSIX, nhưng rất nhiều cuộc gọi tôi sử dụng có thể có khả năng trả về EINTR. Ngoài ra, chức năng có thể chặn trong một số trường hợp. (Đối với những người quan tâm, nó thực hiện một cơ chế khóa.)

Vì lợi ích của việc làm điều này càng chung chung càng tốt, tôi muốn biết đâu là cách thích hợp để đối phó với một cuộc gọi hệ thống bị gián đoạn.

  • Từ hầu hết các nguồn tôi đã đọc, mọi người thường thử lại cuộc gọi và tiếp tục công việc của họ. Tuy nhiên, tôi không chắc đó là điều đúng đắn phải làm ở đây, vì có thể có những lý do chính đáng để làm gián đoạn chức năng của tôi vì nó có thể mất một lượng thời gian đáng kể. Hơn nữa, nó có nghĩa là EINTRđơn giản sẽ bị nuốt bởi chức năng và người gọi sẽ mất bất kỳ dấu hiệu nào cho thấy nó xảy ra.

  • Chiến lược hiện tại của tôi là hủy bỏ ngay lập tức thao tác nếu tôi nhận được EINTRvà thông báo cho người gọi về nó. Bằng cách này, người gọi có thể quyết định xem họ có muốn thử lại chức năng của tôi không? (Hoặc có lẽ sự hiểu biết của tôi về các tín hiệu là thiếu sót?)


1
Thư viện của bạn đang làm gì Tôi có xu hướng phê duyệt chiến lược hiện tại của bạn (hủy bỏ hoạt động EINTRvà báo cáo điều đó cho người gọi), nhưng nó có thể phụ thuộc vào thư viện của bạn đang làm gì ...
Basile Starynkevitch

1
Trong trường hợp này, nó thực hiện thao tác khóa.
Rufflewind

Câu trả lời:


2

Luôn luôn để lại quyết định về cách xử lý EINTRcho người dùng và giúp dễ dàng tiếp tục hoạt động khi thích hợp.

Thông thường cách tốt nhất để làm điều đó là trả lại từ chức năng thư viện của bạn cho người gọi EINTR, nhưng trong một số trường hợp, một cuộc gọi lại hoặc một số triển khai khác có thể tốt hơn - cách nào tốt nhất phụ thuộc vào các yếu tố khác, nhưng luôn để người dùng kiểm soát thử lại và sơ yếu lý lịch.

Điều này có nghĩa là nếu mã thư viện của bạn có thể thành công một phần trước khi nhận được EINTR, thì bạn nên suy nghĩ cẩn thận những gì người dùng có thể cần biết về thành công một phần đó hoặc nếu người dùng có thể cần tiếp tục hoạt động từ nơi thất bại. Bạn có thể cần phải trả lại thông tin bổ sung hoặc cung cấp giao diện để tiếp tục từ bất kỳ nơi nào có thể phù hợp.

Đây là lý do tại sao các cuộc gọi hệ thống như thế readwritengày nay trả lại thành công một phần - bởi vì nó rất bực bội khi người dùng được thông báo:

Bạn đã cố gắng viết "foo"và chúng tôi đã viết thành công hoặc không có gì, hoặc "f", hoặc "fo", bạn không biết điều gì . Chúc vui vẻ! Hy vọng hệ thống của bạn có thể xử lý khởi động lại toàn bộ ghi sau bất kỳ trong số đó!

Tất nhiên, trong một số trường hợp, chúng ta nên viết các hệ thống để xử lý các tình huống chính xác như vậy - ví dụ, có lẽ sau khi ghi một phần, bạn luôn tạo lại tệp hoặc mở lại kết nối mạng hoặc bạn sử dụng một số byte để có nghĩa là "bắt đầu lại" - vì vậy nó phụ thuộc vào trường hợp sử dụng thư viện của bạn nhắm mục tiêu.

Nếu một chức năng thư viện thực hiện một số hoạt động và không có cách nào để biết được chúng đã thất bại ở đâu và các hoạt động đó không phải là tất cả một cách an toàn và hiệu quả, điều đó về cơ bản làm cho một thư viện không thể sử dụng được mã cần mạnh mẽ.

Nếu tất cả các bước trong chức năng thư viện idempotent an toàn và hiệu quả, hoặc toàn bộ là nguyên tử - như có được một khóa - thì chỉ cần cho người dùng biết rằng một sự EINTRkiện đã xảy ra là đủ.

Ngoài ra, nếu chúng ta thử lại EINTR, thì chúng ta có thể ngắt xử lý tín hiệu . Ở mức thấp, trình xử lý tín hiệu chỉ có thể sử dụng một cách an toàn một bộ tính năng hạn chế và do đó, trong nhiều trường hợp, trình xử lý tín hiệu sẽ chỉ đặt boolean cho biết tín hiệu đã được nhận, sau đó quay lại, hy vọng rằng khi mã được nối lại, nó sẽ thoát ra khỏi bất cứ điều gì nó đang làm. Nếu chúng tôi nhận được EINTRvà sau đó chúng tôi thử lại thay vì trả lại quyền kiểm soát cho người dùng, chúng tôi có thể sẽ giữ mã không làm điều đó.

Phải làm gì sau EINTRlà một quyết định toàn bộ chương trình - câu trả lời đúng không thể được biết đến mà không biết những gì chương trình đang làm và làm thế nào chương trình có nghĩa là để đáp ứng với một tín hiệu, và nó có ảnh hưởng đến phần còn lại của chương trình.

Biết làm thế nào hoặc nếu người dùng có thể cần tiếp tục và giúp người dùng làm như vậy nếu cần, là trách nhiệm của thư viện - không thể biết câu trả lời đúng mà không biết thư viện đang làm gì.


4

Tín hiệu trong Unix

khi một số quy trình khác gửi tín hiệu quy trình của bạn, chương trình của bạn sẽ dừng những gì nó đang làm ...

1) Chạy mã xử lý bạn đã viết. Bạn không biết chương trình của bạn có thể làm gì khi tín hiệu đến. Đó là ý tưởng với các tín hiệu, chúng có thể hoàn toàn không đồng bộ.

2) Khi xử lý tín hiệu hoàn tất, nó thường chỉ quay trở lại và chương trình của bạn tiếp tục ở nơi nó dừng lại, như thể không có gì xảy ra.

Tìm thấy một số thông tin hữu ích trong Richard Stevens

Một đặc điểm của các hệ thống UNIX trước đó là nếu một quy trình bắt được tín hiệu trong khi quy trình bị chặn trong một cuộc gọi hệ thống "chậm", cuộc gọi hệ thống đã bị gián đoạn. Cuộc gọi hệ thống trả về lỗi và errno được đặt thành EINTR. Điều này được thực hiện theo giả định rằng kể từ khi tín hiệu xảy ra và quá trình bắt được nó, có nhiều khả năng đã xảy ra sự cố sẽ đánh thức cuộc gọi hệ thống bị chặn.

Ngữ nghĩa POSIX.1 cho việc đọc và ghi bị gián đoạn đã thay đổi với phiên bản 2001 của tiêu chuẩn. Các phiên bản trước đó đã cho triển khai lựa chọn cách xử lý việc đọc và ghi đã xử lý một phần dữ liệu. Nếu đọc đã nhận và chuyển dữ liệu vào bộ đệm của ứng dụng, nhưng chưa nhận được tất cả những gì ứng dụng yêu cầu và sau đó bị gián đoạn, hệ điều hành có thể thất bại trong cuộc gọi hệ thống với errno được đặt thành EINTR hoặc cho phép cuộc gọi hệ thống thành công, trả về một phần dữ liệu nhận được. Tương tự, nếu việc ghi bị gián đoạn sau khi truyền một số dữ liệu trong bộ đệm của ứng dụng, hệ thống vận hành có thể thất bại trong cuộc gọi hệ thống với errno được đặt thành EINTR hoặc cho phép cuộc gọi hệ thống thành công, trả lại một phần dữ liệu được ghi. Trong lịch sử, các triển khai có nguồn gốc từ Hệ thống V không thực hiện được cuộc gọi hệ thống, trong khi các triển khai có nguồn gốc BSD trả lại thành công một phần. Với phiên bản 2001 của tiêu chuẩn POSIX.1, ngữ nghĩa theo kiểu BSD là bắt buộc.

Vấn đề với các cuộc gọi hệ thống bị gián đoạn là bây giờ chúng ta phải xử lý trả lại lỗi một cách rõ ràng. Chuỗi mã điển hình (giả sử thao tác đọc và giả sử rằng chúng tôi muốn khởi động lại việc đọc ngay cả khi nó bị gián đoạn) sẽ là

again:
    if ((n = read(fd, buf, BUFFSIZE)) < 0) {
        if (errno == EINTR)
            goto again;     /* just an interrupted system call */
        /* handle other errors */
    }

Để ngăn các ứng dụng phải xử lý các cuộc gọi hệ thống bị gián đoạn, 4.2BSD đã giới thiệu tự động khởi động lại các cuộc gọi hệ thống bị gián đoạn nhất định.


3
Điều này không trả lời câu hỏi. đó là cách các thư viện nên tiếp cận vấn đề. Nếu tôi biết rằng tôi muốn bỏ qua EINTR trong một số thao tác nhất định, tôi có thể thực hiện với cuộc gọi này thử lại ( goto again). Nhưng là một nhà phát triển thư viện, tôi không biết liệu đây có phải là những gì người dùng thư viện của tôi muốn.
Messa
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.