Trên các hệ thống Unix, tại sao chúng ta phải rõ ràng các tệp `open ()` và `close ()` để có thể `read ()` hoặc `write ()` chúng?


50

Tại sao làm open()close()tồn tại trong thiết kế hệ thống tập tin Unix?

Hệ điều hành không thể phát hiện lần đầu tiên read()hoặc write()được gọi và làm bất cứ điều gì open()bình thường sẽ làm?


22
Điều đáng chú ý là mô hình này không phải là một phần của hệ thống tệp mà là API Unix . Hệ thống tập tin chỉ quan tâm đến việc các byte trên đĩa đi đâu và đặt tên tệp ở đâu, v.v ... Hoàn toàn có thể có mô hình thay thế mà bạn mô tả trên đầu một hệ thống tệp Unix như UFS hoặc ext4, nó sẽ tùy thuộc vào kernel để dịch các cuộc gọi đó thành các bản cập nhật thích hợp cho hệ thống tập tin (giống như bây giờ).
marcelm

18
Theo phrased, tôi nghĩ rằng đây là nhiều hơn về lý do tại sao open()tồn tại. "Không phải HĐH chỉ phát hiện lần đầu tiên đọc () hoặc write () và làm bất cứ điều gì mở () thường sẽ làm?" Có một đề nghị tương ứng cho khi đóng cửa sẽ xảy ra?
Joshua Taylor

7
Làm thế nào bạn sẽ nói read()hoặc write()tập tin nào để truy cập? Có lẽ bằng cách vượt qua con đường. Điều gì xảy ra nếu đường dẫn của tệp thay đổi trong khi bạn truy cập vào nó (giữa hai read()hoặc write()cuộc gọi)?
dùng253751

2
Ngoài ra, bạn thường không kiểm soát truy cập read()write(), chỉ trên open().
Pavel imerda

6
@ John: Có lẽ bạn đang quên mất phần cứng bị giới hạn trong những ngày đó. PDP-7 mà Unix được triển khai lần đầu tiên (trên Google) có tối đa 64K RAM và xung nhịp 0,333 MHz - thay vì một bộ vi điều khiển đơn giản ngày nay. Thực hiện việc thu gom rác như vậy, hoặc sử dụng mã hệ thống để giám sát truy cập tệp, sẽ khiến hệ thống phải quỳ xuống.
jamesqf

Câu trả lời:


60

Dennis Ritchie đề cập trong «The Evolution của Unix Time chia sẻ hệ thống» đó openclosecùng với read, writecreatđã có mặt trong hệ thống ngay từ đầu.

Tôi đoán một hệ thống không có openclosesẽ không thể tưởng tượng được, tuy nhiên tôi tin rằng nó sẽ làm phức tạp thiết kế. Nói chung, bạn muốn thực hiện nhiều cuộc gọi đọc và viết, không chỉ một cuộc gọi và điều đó có lẽ đặc biệt đúng trên những máy tính cũ có RAM rất hạn chế mà UNIX bắt nguồn. Có một tay cầm duy trì vị trí tệp hiện tại của bạn đơn giản hóa việc này. Nếu readhoặcwritelà để trả lại tay cầm, họ sẽ phải trả lại một cặp - một tay cầm và trạng thái trả lại của chính họ. Phần xử lý của cặp sẽ vô dụng đối với tất cả các cuộc gọi khác, điều này sẽ khiến cho sự sắp xếp đó trở nên khó xử. Để trạng thái của con trỏ vào kernel cho phép nó cải thiện hiệu quả không chỉ bằng cách đệm. Cũng có một số chi phí liên quan đến tra cứu đường dẫn - có một tay cầm cho phép bạn chỉ trả một lần. Hơn nữa, một số tệp trong thế giới quan UNIX thậm chí không có đường dẫn hệ thống tệp (hoặc không - bây giờ chúng làm với những thứ như /proc/self/fd).


7
Chi phí tra cứu đường dẫn và kiểm tra giấy phép, v.v. là rất đáng kể. Nếu bạn muốn tạo một hệ thống không có open/ close, bạn chắc chắn sẽ triển khai các công cụ như /dev/stdoutcho phép đường ống.
Peter Cordes

5
Tôi nghĩ một khía cạnh khác của vấn đề này là bạn có thể giữ tay cầm đó vào cùng một tệp khi sử dụng nhiều lần đọc khi bạn giữ tệp mở. Mặt khác, bạn có thể có trường hợp một quá trình khác hủy liên kết và tạo lại một tệp có cùng tên và việc đọc một tệp trong các khối có thể hoàn toàn không nhất quán. (Một số điều này cũng có thể phụ thuộc vào hệ thống tệp.)
Bruno

2
Tôi đã thiết kế một cái mà không đóng (); bạn chuyển số inode và offset để đọc () và write (). Tôi không thể làm mà không mở () rất dễ dàng vì đó là nơi giải quyết tên.
Joshua

3
@Joshua: Một hệ thống như vậy có ngữ nghĩa cơ bản khác nhau vì các mô tả tệp unix không đề cập đến các tệp (inodes) mà để mở các mô tả tệp , trong đó có thể có nhiều tệp cho một tệp nhất định (inode).
R ..

@Joshua, bạn chỉ cần đổi tên open()để get_inode()và làm cho toàn bộ hệ thống hơn và cứng (không thể đọc / ghi cùng một tập tin tại một số vị trí cùng một lúc).
vonbrand

53

Sau đó, tất cả các cuộc gọi readwritesẽ phải chuyển thông tin này trên mỗi thao tác:

  • tên của tập tin
  • quyền của tập tin
  • người gọi đang nối thêm hay đang tạo
  • liệu người gọi đã hoàn thành công việc với tệp hay chưa (để loại bỏ bộ đệm đọc không sử dụng và đảm bảo bộ đệm ghi thực sự viết xong)

Cho dù bạn xem xét độc lập cuộc gọi open , read, writecloseđể đơn giản hơn một đơn mục đích I / O thông điệp dựa trên triết lý thiết kế của bạn. Các nhà phát triển Unix đã chọn sử dụng các hoạt động và chương trình đơn giản có thể được kết hợp theo nhiều cách, thay vì một hoạt động (hoặc chương trình) duy nhất thực hiện mọi thứ.


Người gọi cũng trong hầu hết các trường hợp phải chỉ định phần bù mong muốn trong một tệp. Có một số tình huống (ví dụ: giao thức UDP cho phép truy cập dữ liệu) trong đó mỗi yêu cầu xác định độc lập một tệp và bù có thể hữu ích vì nó giúp loại bỏ nhu cầu duy trì trạng thái của máy chủ, nhưng nói chung sẽ thuận tiện hơn khi có máy chủ theo dõi vị trí tập tin. Hơn nữa, như đã lưu ý ở nơi khác, mã sẽ ghi các tệp thường cần khóa chúng trước và khóa chúng sau đó; kết hợp các hoạt động với mở / đóng là rất thuận tiện.
supercat

5
"Tập tin" có thể không có tên hoặc quyền ở vị trí đầu tiên; readwritekhông bị giới hạn đối với các tệp nằm trên một hệ thống tệp và đó là quyết định thiết kế cơ bản trong Unix, như pjc50 giải thích.
rebierpost

1
Ngoài ra , trong tệp để đọc / ghi nó - bắt đầu, kết thúc hoặc một vị trí tùy ý (thường là ngay sau khi kết thúc lần đọc / ghi cuối cùng) - hạt nhân theo dõi điều này cho bạn (với chế độ để hướng tất cả ghi vào cuối tệp hoặc nếu không các tệp được mở với vị trí ở đầu và nâng cao với mỗi lần đọc / ghi và có thể được di chuyển với lseek)
Random832

51

Khái niệm về xử lý tệp rất quan trọng vì lựa chọn thiết kế của UNIX rằng "mọi thứ đều là tệp", bao gồm cả những thứ không thuộc hệ thống tệp. Chẳng hạn như ổ đĩa băng, bàn phím và màn hình (hoặc teletype!), Đầu đọc thẻ / băng đục lỗ, kết nối nối tiếp, kết nối mạng và (phát minh UNIX chính) kết nối trực tiếp với các chương trình khác gọi là "ống dẫn".

Nếu bạn xem xét nhiều tiện ích UNIX tiêu chuẩn đơn giản như grep, đặc biệt là trong các phiên bản gốc của chúng, bạn sẽ nhận thấy rằng chúng không bao gồm các cuộc gọi đến open()close()chỉ readwrite. Các thẻ điều khiển được thiết lập bên ngoài chương trình bởi trình bao và được truyền vào khi nó được khởi động. Vì vậy, chương trình không phải quan tâm liệu nó ghi vào một tập tin hay một chương trình khác.

Cũng như open, những cách khác để nhận file descriptor là socket, listen, pipe, dup, và rất Heath Robinson cơ chế cho việc gửi file descriptor qua ống: https://stackoverflow.com/questions/28003921/sending-file-descriptor-by-linux -ổ cắm

Chỉnh sửa: một số ghi chú bài giảng mô tả các lớp của cảm ứng và cách điều này cho phép O_APPEND hoạt động hợp lý. Lưu ý rằng việc giữ dữ liệu inode trong bộ nhớ đảm bảo hệ thống sẽ không phải đi và tìm nạp lại chúng cho hoạt động ghi tiếp theo.


1
Ngoài ra creat, và listenkhông tạo ra một fd, nhưng khi (và nếu) một yêu cầu xuất hiện trong khi lắng nghe acceptsẽ tạo và trả về một fd cho ổ cắm (đã kết nối) mới.
dave_thndry_085

18
Đây là câu trả lời chính xác. Tập hợp hoạt động (nhỏ) nổi tiếng trên các bộ mô tả tệp là một API thống nhất cho tất cả các loại tài nguyên sản xuất hoặc tiêu thụ dữ liệu. Khái niệm này là thành công. Một chuỗi có thể hiểu một cú pháp xác định loại tài nguyên cùng với vị trí thực tế (URL có ai không?), Nhưng để sao chép các chuỗi xung quanh chiếm vài phần trăm RAM có sẵn (PDP 7? 16 kB?) Có vẻ quá mức .
Peter - Tái lập Monica

Có lẽ nó sẽ được, nếu các cuộc gọi cấp thấp và vỏ được phát triển cùng một lúc. Nhưng pipeđã được giới thiệu một vài năm sau khi phát triển trên Unix bắt đầu.
Thomas Dickey

1
@Thomas Dickey: Điều đó chỉ cho thấy thiết kế ban đầu tốt như thế nào, vì nó cho phép mở rộng đơn giản cho các đường ống & c :-)
jamesqf 27/2/2016

Nhưng theo dòng lập luận đó, câu trả lời này không cung cấp gì mới.
Thomas Dickey

10

Câu trả lời là không, bởi vì open () và close () tạo và hủy một tay cầm tương ứng. Đôi khi, thực sự có lúc bạn thực sự muốn đảm bảo rằng bạn là người gọi duy nhất có cấp độ truy cập cụ thể, vì một người gọi khác (ví dụ) viết vào một tệp mà bạn đang phân tích cú pháp bất ngờ có thể rời khỏi một ứng dụng trong một trạng thái không xác định hoặc dẫn đến một sự sống động hoặc bế tắc, ví dụ bổ đề của Nhà triết học ăn uống.

Ngay cả khi không có sự xem xét đó, vẫn có ý nghĩa về hiệu suất được xem xét; close () cho phép hệ thống tập tin (nếu nó phù hợp hoặc nếu bạn gọi cho nó) xóa bộ đệm mà bạn đang chiếm, một hoạt động đắt tiền. Một số chỉnh sửa liên tiếp cho luồng trong bộ nhớ hiệu quả hơn nhiều so với một số chu trình đọc-ghi-sửa đổi cơ bản không liên quan đến một hệ thống tệp, mà đối với tất cả các bạn biết, tồn tại một nửa thế giới nằm rải rác trên một trung tâm lưu trữ số lượng lớn có độ trễ cao. Ngay cả với lưu trữ cục bộ, bộ nhớ thường nhanh hơn nhiều đơn hàng so với lưu trữ số lượng lớn.


7

Open () cung cấp một cách để khóa các tệp trong khi chúng đang được sử dụng. Nếu các tệp được tự động mở, đọc / ghi và sau đó được đóng lại bởi HĐH, sẽ không có gì ngăn các ứng dụng khác thay đổi các tệp đó giữa các hoạt động.

Mặc dù điều này có thể quản lý được (nhiều hệ thống hỗ trợ truy cập tệp không độc quyền) vì đơn giản, hầu hết các ứng dụng đều cho rằng các tệp mà chúng mở không thay đổi.


5

Bởi vì đường dẫn của tệp có thể di chuyển trong khi bạn cho rằng nó sẽ giữ nguyên.


4

Đọc và ghi vào hệ thống tập tin có thể bao gồm rất nhiều chương trình đệm, quản lý hệ điều hành, quản lý đĩa cấp thấp và một loạt các hành động tiềm năng khác. Vì vậy, các hành động open()close()phục vụ như là thiết lập cho các loại hoạt động này. Việc triển khai khác nhau của một hệ thống tập tin có thể được tùy chỉnh cao khi cần và vẫn còn trong suốt đối với chương trình gọi.

Nếu HĐH không mở / đóng, thì với readhoặc write, các hành động tệp đó vẫn sẽ phải thực hiện bất kỳ khởi tạo, xóa / quản lý bộ đệm, v.v. Đó là rất nhiều chi phí để áp đặt cho việc đọc và viết lặp đi lặp lại.


Đừng quên rằng open () và close () cũng giữ vị trí trong tệp (cho lần đọc tiếp theo hoặc lần ghi tiếp theo). Vì vậy, ở cuối hoặc read () và write () sẽ cần một cấu trúc để xử lý tất cả các tham số hoặc nó cần các đối số cho mỗi tham số. Tạo một cấu trúc tương đương (trang lập trình viên) với một mở, vì vậy nếu HĐH cũng biết về mở, chúng ta chỉ có nhiều lợi thế hơn.
Giacomo Catenazzi

1

Câu thần chú Unix là "đưa ra một cách làm", có nghĩa là "bao thanh toán" thành các phần (có thể tái sử dụng) để được kết hợp theo ý muốn. Tức là, trong trường hợp này tách biệt việc tạo và hủy xử lý tệp khỏi việc sử dụng chúng. Những lợi ích quan trọng đến sau, với các đường ống và kết nối mạng (chúng cũng được thao tác thông qua xử lý tệp, nhưng chúng được tạo theo các cách khác). Có thể gửi xử lý tệp xung quanh (ví dụ: chuyển chúng đến các quy trình con dưới dạng "tệp mở" tồn tại exec(2)và thậm chí đến các quy trình không liên quan thông qua một đường ống) chỉ có thể theo cách này. Đặc biệt nếu bạn muốn cung cấp quyền truy cập được kiểm soát vào một tệp được bảo vệ. Vì vậy, bạn có thể ví dụ mở/etc/passwd để viết và chuyển nó cho một quy trình con không được phép mở tệp đó để viết (vâng, tôi biết đây là một ví dụ lố bịch, hãy thoải mái chỉnh sửa với một cái gì đó thực tế hơn).

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.