Trong Linux, điều gì xảy ra với trạng thái của một tiến trình khi nó cần đọc các khối từ đĩa? Nó có bị chặn không? Nếu vậy, quy trình khác được chọn để thực thi như thế nào?
Câu trả lời:
Trong khi chờ đợi read()
hoặc write()
đến / từ một bộ mô tả tệp trả về, quá trình sẽ được đặt trong một kiểu ngủ đặc biệt, được gọi là "D" hoặc "Disk Sleep". Điều này là đặc biệt, bởi vì quá trình không thể bị giết hoặc bị gián đoạn trong trạng thái như vậy. Quá trình chờ trả về từ ioctl () cũng sẽ được chuyển sang trạng thái ngủ theo cách này.
Một ngoại lệ cho điều này là khi một tệp (chẳng hạn như thiết bị đầu cuối hoặc thiết bị ký tự khác) được mở ở O_NONBLOCK
chế độ, được chuyển khi nó giả định rằng một thiết bị (chẳng hạn như modem) sẽ cần thời gian để khởi tạo. Tuy nhiên, bạn đã chỉ ra các thiết bị chặn trong câu hỏi của mình. Ngoài ra, tôi chưa bao giờ thử một ứng dụng ioctl()
có khả năng chặn trên fd được mở ở chế độ không chặn (ít nhất là không cố ý).
Cách chọn một quy trình khác hoàn toàn phụ thuộc vào bộ lập lịch mà bạn đang sử dụng, cũng như những gì các quá trình khác có thể đã thực hiện để sửa đổi trọng số của chúng trong bộ lập lịch đó.
Một số chương trình không gian người dùng trong những trường hợp nhất định đã được biết là vẫn ở trạng thái này mãi mãi, cho đến khi khởi động lại. Chúng thường được nhóm chung với các "thây ma" khác, nhưng thuật ngữ này sẽ không chính xác vì chúng không tồn tại về mặt kỹ thuật.
Khi một quy trình cần tìm nạp dữ liệu từ đĩa, quy trình đó sẽ dừng chạy trên CPU một cách hiệu quả để cho các quy trình khác chạy vì hoạt động có thể mất nhiều thời gian để hoàn thành - thời gian tìm kiếm đĩa ít nhất là 5 mili giây và 5 mili giây là 10 triệu Chu kỳ CPU, một sự vĩnh cửu theo quan điểm của chương trình!
Theo quan điểm của lập trình viên (cũng được cho là "trong không gian người dùng"), đây được gọi là lệnh gọi hệ thống chặn . Nếu bạn gọi write(2)
(là một trình bao bọc libc mỏng xung quanh lệnh gọi hệ thống cùng tên), quá trình của bạn không chính xác dừng lại ở ranh giới đó; nó tiếp tục, trong nhân, chạy mã lệnh gọi hệ thống. Hầu hết thời gian nó đi đến một trình điều khiển bộ điều khiển đĩa cụ thể (tên tệp → hệ thống tệp / VFS → thiết bị khối → trình điều khiển thiết bị), trong đó lệnh để tìm nạp một khối trên đĩa được gửi đến phần cứng thích hợp, đó là rất hoạt động nhanh chóng trong hầu hết thời gian.
SAU ĐÓ tiến trình được đặt ở trạng thái ngủ (trong không gian hạt nhân, việc chặn được gọi là ngủ - không có gì bị 'chặn' theo quan điểm của hạt nhân). Nó sẽ được đánh thức sau khi phần cứng cuối cùng đã tìm nạp dữ liệu thích hợp, sau đó quá trình sẽ được đánh dấu là có thể chạy được và sẽ được lên lịch. Cuối cùng, bộ lập lịch sẽ chạy quá trình.
Cuối cùng, trong không gian người dùng, lệnh gọi hệ thống chặn trả về với trạng thái và dữ liệu phù hợp, và dòng chương trình tiếp tục.
Có thể gọi hầu hết các lệnh gọi hệ thống I / O ở chế độ không chặn (xem O_NONBLOCK
trong open(2)
và fcntl(2)
). Trong trường hợp này, các lệnh gọi hệ thống trả về ngay lập tức và chỉ báo cáo việc gửi hoạt động của đĩa. Lập trình viên sẽ phải kiểm tra rõ ràng sau đó xem thao tác đã hoàn thành, thành công hay chưa và tìm nạp kết quả của nó (ví dụ: với select(2)
). Đây được gọi là lập trình không đồng bộ hoặc dựa trên sự kiện.
Hầu hết các câu trả lời ở đây đề cập đến trạng thái D (được gọi TASK_UNINTERRUPTIBLE
trong tên trạng thái Linux) là không chính xác. Các D nhà nước là một chế độ đặc biệt giấc ngủ mà chỉ kích hoạt trong một đường dẫn mã không gian hạt nhân, khi mà đường dẫn mã không thể bị gián đoạn (vì nó sẽ là quá phức tạp để chương trình), với kỳ vọng rằng nó sẽ chặn chỉ cho một rất thời gian ngắn. Tôi tin rằng hầu hết các "trạng thái D" thực sự là vô hình; chúng tồn tại rất ngắn và không thể quan sát được bằng các công cụ lấy mẫu như 'top'.
Bạn có thể gặp phải các quy trình không thể xử lý được ở trạng thái D trong một vài tình huống. NFS nổi tiếng vì điều đó, và tôi đã gặp nó nhiều lần. Tôi nghĩ rằng có một cuộc xung đột ngữ nghĩa giữa một số đường dẫn mã VFS, giả sử luôn đến được đĩa cục bộ và phát hiện lỗi nhanh (trên SATA, thời gian chờ lỗi sẽ khoảng vài 100 mili giây) và NFS, thực sự lấy dữ liệu từ mạng. linh hoạt hơn và phục hồi chậm (thời gian chờ TCP 300 giây là phổ biến). Đọc bài viết này để biết giải pháp tuyệt vời được giới thiệu trong Linux 2.6.25 với TASK_KILLABLE
trạng thái. Trước thời đại này, đã có một vụ hack nơi bạn thực sự có thể gửi tín hiệu đến các máy khách xử lý NFS bằng cách gửi một SIGKILL tới chuỗi hạt nhân rpciod
, nhưng hãy quên đi thủ thuật xấu xí đó.…
/proc/stat
?
Quá trình thực hiện I / O sẽ được đặt ở trạng thái D (trạng thái ngủ không gián đoạn) , giải phóng CPU cho đến khi có một ngắt phần cứng để CPU quay trở lại thực thi chương trình. Xem man ps
các trạng thái quy trình khác.
Tùy thuộc vào hạt nhân của bạn, có một bộ lập lịch trình theo dõi một hàng loạt các quá trình sẵn sàng thực thi. Nó, cùng với một thuật toán lập lịch, cho kernel biết tiến trình nào cần gán cho CPU nào. Có các quy trình nhân và quy trình người dùng cần xem xét. Mỗi tiến trình được phân bổ một lát thời gian, là một phần thời gian CPU được phép sử dụng. Khi quá trình sử dụng tất cả thời gian của nó, nó được đánh dấu là đã hết hạn và được ưu tiên thấp hơn trong thuật toán lập lịch.
Trong kernel 2.6 , có một bộ lập lịch độ phức tạp thời gian O (1) , vì vậy bất kể bạn có bao nhiêu tiến trình đang chạy, nó sẽ chỉ định các CPU trong thời gian không đổi. Tuy nhiên, nó phức tạp hơn, vì quyền ưu tiên được giới thiệu 2.6 và cân bằng tải CPU không phải là một thuật toán dễ dàng. Trong mọi trường hợp, nó hoạt động hiệu quả và CPU sẽ không hoạt động trong khi bạn đợi I / O.
Như những người khác đã giải thích, các tiến trình ở trạng thái "D" (ngủ liên tục) chịu trách nhiệm cho quá trình ps bị treo. Đối với tôi, điều đó đã xảy ra nhiều lần với RedHat 6.x và các thư mục nhà NFS đã tự động đếm.
Để liệt kê các tiến trình ở trạng thái D, bạn có thể sử dụng các lệnh sau:
cd /proc
for i in [0-9]*;do echo -n "$i :";cat $i/status |grep ^State;done|grep D
Để biết thư mục hiện tại của quá trình và có thể là đĩa NFS được gắn có vấn đề, bạn có thể sử dụng lệnh tương tự như ví dụ sau (thay 31134 bằng số quá trình đang ngủ):
# ls -l /proc/31134/cwd
lrwxrwxrwx 1 pippo users 0 Aug 2 16:25 /proc/31134/cwd -> /auto/pippo
Tôi nhận thấy rằng việc đưa ra lệnh umount bằng nút chuyển -f (force) cho hệ thống tệp nfs được gắn kết có liên quan, có thể đánh thức quá trình ngủ:
umount -f /auto/pippo
hệ thống tệp không được ngắt kết nối, vì nó đang bận, nhưng quá trình liên quan đã đánh thức và tôi có thể giải quyết vấn đề mà không cần khởi động lại.
Giả sử quy trình của bạn là một chuỗi duy nhất và bạn đang sử dụng chặn I / O, quy trình của bạn sẽ chặn chờ I / O hoàn tất. Kernel sẽ chọn một tiến trình khác để chạy trong thời gian chờ đợi dựa trên độ đẹp, mức độ ưu tiên, thời gian chạy cuối cùng, v.v. Nếu không có tiến trình có thể chạy nào khác, thì hạt nhân sẽ không chạy được nữa; thay vào đó, nó sẽ cho phần cứng biết rằng máy đang ở chế độ nhàn rỗi (điều này sẽ dẫn đến mức tiêu thụ điện năng thấp hơn).
Các quy trình đang chờ I / O hoàn tất thường hiển thị ở trạng thái D, ví dụ: ps
và top
.
Có, tác vụ bị chặn trong lệnh gọi hệ thống read (). Một tác vụ khác đã sẵn sàng chạy hoặc nếu không có tác vụ nào khác sẵn sàng, tác vụ nhàn rỗi (cho CPU đó) sẽ chạy.
Việc đọc đĩa thông thường, bị chặn khiến tác vụ chuyển sang trạng thái "D" (như những người khác đã lưu ý). Các tác vụ như vậy góp phần vào mức trung bình tải, mặc dù chúng không tiêu tốn CPU.
Một số loại IO khác, đặc biệt là tty và mạng, không hoạt động hoàn toàn giống nhau - quá trình kết thúc ở trạng thái "S" và có thể bị gián đoạn và không được tính vào mức trung bình tải.
Có, các tác vụ chờ IO bị chặn và các tác vụ khác sẽ được thực thi. Việc chọn tác vụ tiếp theo được thực hiện bởi bộ lập lịch Linux .
Nói chung quá trình sẽ chặn. Nếu thao tác đọc trên bộ mô tả tệp được đánh dấu là không chặn hoặc nếu quá trình đang sử dụng IO không đồng bộ, nó sẽ không chặn. Ngoài ra, nếu quá trình có các chuỗi khác không bị chặn, chúng có thể tiếp tục chạy.
Quyết định về quá trình chạy tiếp theo là tùy thuộc vào bộ lập lịch trong nhân.