Làm thế nào để đường ống giới hạn sử dụng bộ nhớ?


36

Brian Kernighan giải thích trong video này, sự thu hút ban đầu của Bell Labs đối với các ngôn ngữ / chương trình nhỏ dựa trên các giới hạn về bộ nhớ

Một cỗ máy lớn sẽ là 64 k byte - K, không phải M hay G - và điều đó có nghĩa là bất kỳ chương trình riêng lẻ nào cũng không thể lớn, và do đó, có xu hướng tự nhiên là viết các chương trình nhỏ, và sau đó là cơ chế đường ống, về cơ bản chuyển hướng đầu ra, làm cho nó có thể liên kết một chương trình với một chương trình khác.

Nhưng tôi không hiểu làm thế nào điều này có thể hạn chế việc sử dụng bộ nhớ vì thực tế là dữ liệu phải được lưu trữ trong RAM để truyền giữa các chương trình.

Từ Wikipedia :

Trong hầu hết các hệ thống giống Unix, tất cả các quy trình của một đường ống được bắt đầu cùng một lúc [nhấn mạnh của tôi], với các luồng của chúng được kết nối phù hợp và được quản lý bởi bộ lập lịch cùng với tất cả các quy trình khác đang chạy trên máy. Một khía cạnh quan trọng của vấn đề này, đặt các ống Unix khác với các triển khai đường ống khác, là khái niệm về bộ đệm: ví dụ, một chương trình gửi có thể tạo ra 5000 byte mỗi giây và một chương trình nhận chỉ có thể chấp nhận 100 byte mỗi giây, nhưng không dữ liệu bị mất. Thay vào đó, đầu ra của chương trình gửi được giữ trong bộ đệm. Khi chương trình nhận đã sẵn sàng để đọc dữ liệu, thì chương trình tiếp theo trong đường ống sẽ đọc từ bộ đệm. Trong Linux, kích thước của bộ đệm là 65536 byte (64KB). Một bộ lọc bên thứ ba nguồn mở có tên bfr có sẵn để cung cấp bộ đệm lớn hơn nếu được yêu cầu.

Điều này làm tôi bối rối hơn nữa, vì điều này hoàn toàn đánh bại mục đích của các chương trình nhỏ (mặc dù chúng sẽ được mô đun hóa theo một quy mô nhất định).

Điều duy nhất tôi có thể nghĩ là một giải pháp cho câu hỏi đầu tiên của mình (giới hạn bộ nhớ bị phụ thuộc vào dữ liệu kích thước) là các tập dữ liệu lớn đơn giản là không được tính toán trước đó và các đường ống vấn đề thực sự có nghĩa là giải quyết dung lượng bộ nhớ theo yêu cầu của chính các chương trình. Nhưng được đưa ra văn bản in đậm trong trích dẫn Wikipedia, thậm chí điều này làm tôi bối rối: vì một chương trình không được thực hiện tại một thời điểm.

Tất cả điều này sẽ rất có ý nghĩa nếu các tệp tạm thời được sử dụng, nhưng tôi hiểu rằng các đường ống không ghi vào đĩa (trừ khi sử dụng trao đổi).

Thí dụ:

sed 'simplesubstitution' file | sort | uniq > file2

Rõ ràng với tôi rằng sedđang đọc trong tệp và nhổ nó trên từng dòng một. Nhưng sort, như BK nói trong video được liên kết, là một điểm dừng hoàn toàn, do đó, tất cả dữ liệu phải được đọc vào bộ nhớ (hoặc nó?), Sau đó, nó được chuyển sang uniq, mà (theo tôi) sẽ là một chương trình -line-at-a-time. Nhưng giữa ống thứ nhất và thứ hai, tất cả dữ liệu phải nằm trong bộ nhớ, phải không?


1
unless swap is usedtrao đổi luôn được sử dụng khi không có đủ RAM
edc65

Câu trả lời:


44

Dữ liệu không cần phải được lưu trữ trong RAM. Ống chặn nhà văn của họ nếu độc giả không ở đó hoặc không thể theo kịp; trong Linux (và hầu hết các triển khai khác, tôi tưởng tượng) có một số bộ đệm nhưng điều đó không bắt buộc. Như được đề cập bởi mtraceurJdeBP (xem câu trả lời sau), các phiên bản đầu tiên của các bộ đệm Unix vào đĩa và đây là cách chúng giúp hạn chế việc sử dụng bộ nhớ: một đường ống xử lý có thể được chia thành các chương trình nhỏ, mỗi chương trình sẽ xử lý một số dữ liệu, trong giới hạn của bộ đệm đĩa. Các chương trình nhỏ chiếm ít bộ nhớ hơn và việc sử dụng các đường ống có nghĩa là quá trình xử lý có thể được tuần tự hóa: chương trình đầu tiên sẽ chạy, lấp đầy bộ đệm đầu ra, bị đình chỉ, sau đó chương trình thứ hai sẽ được lên lịch, xử lý bộ đệm, v.v. có cường độ lớn hơn các hệ thống Unix ban đầu và có thể chạy song song nhiều đường ống; nhưng với số lượng dữ liệu khổng lồ, bạn vẫn thấy một hiệu ứng tương tự (và các biến thể của loại kỹ thuật này được sử dụng để xử lý dữ liệu lớn trên mạng).

Trong ví dụ của bạn,

sed 'simplesubstitution' file | sort | uniq > file2

sedđọc dữ liệu từ filekhi cần thiết, sau đó viết nó miễn sortlà sẵn sàng để đọc nó; nếu sortchưa sẵn sàng, các khối ghi. Cuối cùng, dữ liệu thực sự sống trong bộ nhớ, nhưng đó là cụ thể sortsortđược chuẩn bị để xử lý mọi vấn đề (nó sẽ sử dụng các tệp tạm thời, lượng dữ liệu cần sắp xếp quá lớn).

Bạn có thể thấy hành vi chặn bằng cách chạy

strace seq 1000000 -1 1 | (sleep 120; sort -n)

Điều này tạo ra một lượng dữ liệu hợp lý và đưa nó vào một quy trình không sẵn sàng để đọc bất cứ điều gì trong hai phút đầu tiên. Bạn sẽ thấy một số writehoạt động được thực hiện, nhưng rất nhanh seqsẽ dừng lại và chờ hai phút trôi qua, bị chặn bởi kernel ( writecuộc gọi hệ thống chờ).


13
Câu trả lời này có thể có lợi từ việc giải thích thêm về lý do tại sao việc chia chương trình thành nhiều phần nhỏ giúp tiết kiệm bộ nhớ sử dụng: Một chương trình phải có khả năng vừa trong bộ nhớ để chạy, nhưng chỉ có chương trình hiện đang chạy . Mọi chương trình khác được hoán đổi vào đĩa trong Unix đầu tiên, chỉ có một chương trình thậm chí được hoán đổi vào RAM thực tế tại một thời điểm. Vì vậy, CPU sẽ chạy một chương trình, ghi vào một đường ống (hồi đó là trên đĩa ), trao đổi chương trình đó và trao đổi trong chương trình đọc từ đường ống. Cách thanh lịch để biến một dây chuyền lắp ráp song song hợp lý thành thực hiện nối tiếp gia tăng.
mtraceur

6
@malan: Nhiều quá trình có thể được bắt đầu và có thể ở trạng thái có thể chạy cùng một lúc. Nhưng nhiều nhất một quy trình có thể được thực thi trên mỗi CPU vật lý tại bất kỳ thời điểm nào và đó là công việc của bộ lập lịch xử lý của kernel để phân bổ "lát" thời gian CPU cho mỗi quy trình có thể chạy được. Trong các hệ thống hiện đại, một quy trình có thể chạy được nhưng hiện không được lên lịch cho thời gian CPU thường tồn tại trong bộ nhớ trong khi chờ lát tiếp theo, nhưng hạt nhân được phép ghi lại bộ nhớ của bất kỳ tiến trình nào ra đĩa và trở lại bộ nhớ như nó tìm thấy thuận tiện. (Xử lý một số chi tiết ở đây.)
Daniel Pryden

5
Các quy trình ở hai bên của một đường ống có thể hoạt động hiệu quả như các đồng quy: một bên viết cho đến khi nó lấp đầy bộ đệm và các khối ghi, tại đó quy trình không thể làm bất cứ điều gì với phần còn lại của thời gian và nó đi vào một Chế độ chờ IO. Sau đó, HĐH đưa phần còn lại của thời gian (hoặc thời gian sắp tới khác) cho phía đọc, đọc cho đến khi không còn gì trong bộ đệm và các khối đọc tiếp theo, tại đó quá trình đọc không thể làm gì với phần còn lại của thời gian của nó và mang lại cho hệ điều hành. Dữ liệu đi qua giá trị một bộ đệm tại một thời điểm.
Daniel Pryden

6
@malan Các chương trình được khởi động "cùng một lúc" về mặt khái niệm trên tất cả các hệ thống Unix, chỉ trên các hệ thống đa bộ xử lý hiện đại có đủ RAM để giữ chúng, nghĩa là tất cả chúng đều được giữ trong RAM cùng một lúc, trong khi trên một hệ thống có thể Không giữ tất cả chúng trong RAM cùng một lúc, một số bị tráo đổi ra đĩa. Cũng lưu ý rằng "bộ nhớ" trong nhiều ngữ cảnh có nghĩa là bộ nhớ ảo là tổng của cả không gian RAM và dung lượng trao đổi trên đĩa. Wikipedia đang tập trung vào khái niệm hơn là vào các chi tiết triển khai, đặc biệt là vì cách Unix thực sự cũ đã làm mọi thứ ít liên quan hơn bây giờ.
mtraceur

2
@malan Ngoài ra, mâu thuẫn bạn đang thấy xuất phát từ hai ý nghĩa khác nhau của "bộ nhớ" (RAM so với RAM + trao đổi). Tôi chỉ nói về RAM phần cứng và trong bối cảnh đó, chỉ có mã hiện đang được CPU thực thi cần phải phù hợp với RAM (đó là điều đã ảnh hưởng đến các quyết định mà Kernighan đang nói đến), trong khi trong bối cảnh tất cả các chương trình được thực thi một cách hợp lý bởi HĐH tại một thời điểm nhất định (ở mức trừu tượng được cung cấp vào đầu thời gian), một chương trình chỉ cần nằm gọn trong toàn bộ bộ nhớ ảo có sẵn cho HĐH, bao gồm không gian hoán đổi trên đĩa.
mtraceur

34

Nhưng tôi không hiểu làm thế nào điều này có thể hạn chế việc sử dụng bộ nhớ vì thực tế là dữ liệu phải được lưu trữ trong RAM để truyền giữa các chương trình.

Đây là lỗi cơ bản của bạn. Các phiên bản đầu tiên của Unix không chứa dữ liệu đường ống trong RAM. Họ lưu trữ chúng trên đĩa. Ống có i-nút; trên một thiết bị đĩa được ký hiệu là thiết bị đường ống . Quản trị viên hệ thống đã chạy một chương trình có tên /etc/configđể chỉ định (trong số những thứ khác) âm lượng trên đĩa nào là thiết bị đường ống, âm lượng nào là thiết bị gốcthiết bị kết xuất nào .

Lượng dữ liệu đang chờ xử lý bị hạn chế bởi thực tế là chỉ các khối trực tiếp của nút i trên đĩa được sử dụng để lưu trữ. Cơ chế này làm cho mã đơn giản hơn, bởi vì nhiều thuật toán tương tự được sử dụng để đọc từ một đường ống như được sử dụng để đọc cho một tệp thông thường, với một số điều chỉnh gây ra bởi thực tế là các ống không thể tìm kiếm được và bộ đệm là hình tròn.

Cơ chế này đã được thay thế bởi những người khác vào giữa đến cuối những năm 1980. SCO XENIX đã đạt được "Hệ thống ống hiệu suất cao", thay thế các nút i bằng bộ đệm trong lõi. 4BSD thực hiện các đường ống không tên vào ổ cắm. AT & T thực hiện lại các đường ống bằng cơ chế STREAM.

Và tất nhiên, sortchương trình đã thực hiện một loại đầu vào 32KiB nội bộ có giới hạn (hoặc bất kỳ dung lượng bộ nhớ nhỏ hơn nào mà nó có thể phân bổ nếu không có sẵn 32KiB), viết kết quả được sắp xếp vào các stmX??tệp trung gian trong /usr/tmp/đó nó được hợp nhất bên ngoài để sắp xếp cuối cùng đầu ra.

đọc thêm

  • Steve D. Pate (1996). "Giao tiếp giữa các quá trình". UNIX Internals: Một cách tiếp cận thực tế . Addison-Wesley. Số Nether YAM201877212.
  • Maurice J. Bach (1987). "Hệ thống gọi cho hệ thống tập tin". Thiết kế của hệ điều hành Unix . Hội trường-Prentice. SỐ 0132017571.
  • Steven V. Earhart (1986). " config(1 triệu)". Hướng dẫn lập trình viên Unix: 3. Cơ sở quản trị hệ thống . Holt, Rinehart và Winston. ISBN 0030093139. trang 23 Từ28.

1

Bạn đúng một phần, nhưng chỉ là tình cờ .

Trong ví dụ của bạn, tất cả dữ liệu thực sự phải được đọc "ở giữa" các đường ống, nhưng nó không cần phải nằm trong bộ nhớ (bao gồm cả bộ nhớ ảo). Việc triển khai thông thường sortcó thể sắp xếp các bộ dữ liệu không phù hợp với RAM bằng cách thực hiện các phần sắp xếp theo tempfiles và hợp nhất. Tuy nhiên, có một thực tế nhất định là bạn không thể xuất ra một chuỗi được sắp xếp trước khi đọc từng phần tử. Điều đó khá rõ ràng. Vì vậy, có, sortchỉ có thể bắt đầu xuất ra ống thứ hai sau khi đã đọc (và làm bất cứ điều gì, có thể sắp xếp một phần tempfiles) mọi thứ từ ống thứ nhất. Nhưng nó không nhất thiết phải giữ tất cả trong RAM.

Tuy nhiên, điều này không liên quan gì đến cách thức hoạt động của đường ống. Các đường ống có thể được đặt tên (theo truyền thống chúng đều được đặt tên) có nghĩa là không có gì hơn và không có gì khác hơn là chúng có một vị trí trong hệ thống tệp, giống như các tệp. Và đó chỉ là những gì các đường ống đã từng có, các tệp (với việc ghi kết hợp nhiều như khả năng sẵn có của bộ nhớ vật lý sẽ cho phép, như là một tối ưu hóa).

Ngày nay, các đường ống là một bộ đệm hạt nhân nhỏ, có kích thước hữu hạn mà dữ liệu được sao chép, ít nhất đó là những gì về mặt khái niệm xảy ra. Nếu hạt nhân có thể giúp nó, các bản sao được tách ra bằng cách chơi các thủ thuật VM (ví dụ: đường ống từ một tệp thường chỉ cung cấp cùng một trang cho quá trình khác để đọc, do đó cuối cùng chỉ có một thao tác đọc, không phải hai bản sao và không bộ nhớ bổ sung đã được sử dụng bởi bộ đệm bộ đệm dù sao cũng cần thiết. Trong một số trường hợp, bạn cũng có thể nhận được 100% không sao chép. Hoặc, một cái gì đó rất gần.

Nếu các đường ống có kích thước nhỏ và hữu hạn, thì làm thế nào điều này có thể hoạt động đối với bất kỳ lượng dữ liệu không xác định (có thể lớn) nào? Điều đó thật đơn giản: Khi không có gì phù hợp hơn, các khối ghi cho đến khi có chỗ một lần nữa.

Triết lý của nhiều chương trình đơn giản là hữu ích nhất từng có khi bộ nhớ rất khan hiếm. Bởi vì, tốt, bạn có thể làm việc theo từng bước nhỏ, từng bước một. Ngày nay, những lợi thế là, ngoài một số tính linh hoạt bổ sung, tôi dám khẳng định, không còn tuyệt vời nữa.
Tuy nhiên, các đường ống được triển khai rất hiệu quả (chúng phải như vậy!), Vì vậy cũng không có bất lợi nào, và đó là một thứ đã được thiết lập đang hoạt động tốt và mọi người đã quen, vì vậy không cần phải thay đổi mô hình.


Khi bạn nói 'ống được đặt tên' (dường như JdeBP nói rằng có một 'thiết bị đường ống'), điều đó có nghĩa là có giới hạn số lượng ống có thể được sử dụng tại một thời điểm nhất định (nghĩa là có giới hạn đối với bạn có thể sử dụng bao nhiêu lần |trong một lệnh)?
malan

2
Tôi chưa bao giờ thấy một giới hạn như vậy, và tôi không nghĩ rằng trên lý thuyết đã từng có một. Trong thực tế, bất cứ thứ gì có tên tệp đều cần một nút, và số lượng các nút là, tất nhiên, là hữu hạn. Số lượng trang vật lý trên một hệ thống, nếu không có gì khác. Hệ thống hiện đại đảm bảo 4k viết nguyên tử, vì vậy mỗi ống phải ít nhất của chính mình hoàn toàn 4k trang, trong đó đặt một giới hạn cứng về số lượng các ống bạn có thể có. Nhưng hãy cân nhắc việc có một vài GB RAM ... thực tế, đó là giới hạn bạn sẽ không bao giờ gặp phải. Hãy thử và gõ một vài triệu ống trên thiết bị đầu cuối ... :)
Damon
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.