Một quá trình không bị gián đoạn là gì?


155

Đôi khi bất cứ khi nào tôi viết một chương trình trong Linux và nó gặp sự cố do lỗi nào đó, nó sẽ trở thành một quá trình không thể gián đoạn và tiếp tục chạy mãi cho đến khi tôi khởi động lại máy tính của mình (ngay cả khi tôi đăng xuất). Câu hỏi của tôi là:

  • Điều gì gây ra một quá trình để trở nên không bị gián đoạn?
  • Làm thế nào để tôi ngăn chặn điều đó xảy ra?
  • Đây có lẽ là một câu hỏi ngớ ngẩn, nhưng có cách nào để làm gián đoạn nó mà không cần khởi động lại máy tính của tôi không?

Có thể là một chương trình có thể được viết để bắt đầu một quá trình đi vào TASK_UNINTERUPTIBLEtrạng thái bất cứ khi nào hệ thống không ở trạng thái nhàn rỗi, do đó buộc phải thu thập dữ liệu, chờ truyền khi người dùng siêu thoát? Đây sẽ là một mỏ vàng để tin tặc lấy thông tin, trở về trạng thái zombie và truyền thông tin qua mạng khi không hoạt động. Một số người có thể lập luận rằng đây là một cách để tạo ra một Blackdoorquyền hạn, để vào và thoát khỏi bất kỳ hệ thống nào theo ý muốn. Tôi tin tưởng rằng lỗ hổng này có thể được niêm phong vĩnh viễn, bằng cách loại bỏ `TASK_UNINTERUPTIB
Nuuwski

2
xin vui lòng chia sẻ mã?
một lần nữa vào

Câu trả lời:


197

Một quá trình không bị gián đoạn là một quá trình xảy ra trong một cuộc gọi hệ thống (chức năng kernel) không thể bị gián đoạn bởi tín hiệu.

Để hiểu điều đó có nghĩa là gì, bạn cần hiểu khái niệm về một cuộc gọi hệ thống ngắt. Ví dụ kinh điển là read(). Đây là một cuộc gọi hệ thống có thể mất nhiều thời gian (giây) vì nó có khả năng liên quan đến việc quay ổ cứng hoặc di chuyển đầu. Trong hầu hết thời gian này, quá trình sẽ ngủ, chặn trên phần cứng.

Trong khi quá trình đang ngủ trong cuộc gọi hệ thống, nó có thể nhận được tín hiệu không đồng bộ Unix (giả sử SIGTERM), sau đó xảy ra:

  • Hệ thống gọi thoát sớm và được thiết lập để trả về -EINTR cho không gian người dùng.
  • Trình xử lý tín hiệu được thực thi.
  • Nếu quá trình vẫn đang chạy, nó sẽ nhận được giá trị trả về từ cuộc gọi hệ thống và nó có thể thực hiện lại cuộc gọi tương tự.

Trở về sớm từ cuộc gọi hệ thống cho phép mã không gian người dùng thay đổi ngay lập tức hành vi của nó để đáp ứng với tín hiệu. Ví dụ: chấm dứt sạch sẽ trong phản ứng với SIGINT hoặc SIGTERM.

Mặt khác, một số cuộc gọi hệ thống không được phép bị gián đoạn theo cách này. Nếu hệ thống gọi các quầy hàng vì một số lý do, quá trình có thể vẫn vô thời hạn ở trạng thái không thể phá hủy này.

LWN đã chạy một bài viết hay chạm vào chủ đề này vào tháng Bảy.

Để trả lời câu hỏi ban đầu:

  • Làm thế nào để ngăn chặn điều này xảy ra: tìm ra trình điều khiển nào gây rắc rối cho bạn và ngừng sử dụng hoặc trở thành một hacker hạt nhân và khắc phục nó.

  • Làm thế nào để giết một quá trình không bị gián đoạn mà không cần khởi động lại: bằng cách nào đó làm cho cuộc gọi hệ thống chấm dứt. Thường thì cách hiệu quả nhất để làm việc này mà không cần nhấn công tắc nguồn là kéo dây nguồn. Bạn cũng có thể trở thành một hacker hạt nhân và khiến trình điều khiển sử dụng TASK_KILLABLE, như được giải thích trong bài viết của LWN.


30
Tôi rút dây nguồn trên máy tính xách tay của tôi và nó không hoạt động, thật đáng buồn. ;-)
thecarpy

1
Không phải là EINTR thay vì EAGAIN sao? Đồng thời read () trả về -1 và errno được đặt thành lỗi.
lethalman

2
@Dexter: Bạn thực sự đang thiếu điểm. Đọc bài viết của LWN: lwn.net/Articles/288056 . Những vấn đề này là do các lập trình viên trình điều khiển thiết bị lười biếng gây ra và chúng cần được sửa trong mã trình điều khiển thiết bị.
ddaa

4
@ddaa "Truyền thống Unix (và do đó hầu như tất cả các ứng dụng) tin rằng kho lưu trữ tệp ghi là không bị gián đoạn tín hiệu. Sẽ không an toàn hoặc thực tế khi thay đổi bảo đảm đó." -> Đây chính xác là phần sai nhất trong tất cả IMO này. Chỉ cần ngắt yêu cầu đọc / ghi của trình điều khiển và khi thiết bị thực tế (đĩa cứng / card mạng / vv) cung cấp dữ liệu, hãy bỏ qua nó. Một nhân hệ điều hành nên được tạo ra theo cách mà KHÔNG nhà phát triển có thể làm hỏng nó.
Dexter

2
@ddaa Tôi biết Linux không phải là một hạt nhân, mặc dù tôi không chắc phần nào trong nhận xét của tôi liên quan đến nó ... Và sau đó, nhận xét của bạn có nghĩa là một hệ điều hành vi nhân không có vấn đề với các quá trình "không thể gián đoạn" đó? Bởi vì nếu không, có lẽ đã đến lúc tôi trở thành một người hâm mộ hạt nhân ...: D
Dexter

49

Khi một quá trình ở chế độ người dùng, nó có thể bị gián đoạn bất cứ lúc nào (chuyển sang chế độ kernel). Khi kernel trở về chế độ người dùng, nó sẽ kiểm tra xem có bất kỳ tín hiệu nào đang chờ xử lý hay không (bao gồm cả những tín hiệu được sử dụng để giết tiến trình, chẳng hạn như SIGTERMSIGKILL). Điều này có nghĩa là một quá trình chỉ có thể bị giết khi trở về chế độ người dùng.

Lý do một tiến trình không thể bị giết trong chế độ kernel là vì nó có khả năng làm hỏng các cấu trúc kernel được sử dụng bởi tất cả các tiến trình khác trong cùng một máy (cùng cách giết chết một luồng có thể làm hỏng cấu trúc dữ liệu được sử dụng bởi các luồng khác trong cùng tiến trình) .

Khi hạt nhân cần phải làm một việc gì đó có thể mất nhiều thời gian (chờ trên đường ống được viết bởi một quy trình khác hoặc chờ phần cứng làm gì đó), nó sẽ ngủ bằng cách đánh dấu là đang ngủ và gọi trình lập lịch để chuyển sang trình khác quá trình (nếu không có quá trình không ngủ, nó sẽ chuyển sang quá trình "giả" để bảo cpu làm chậm một chút và ngồi trong một vòng lặp - vòng lặp nhàn rỗi).

Nếu một tín hiệu được gửi đến một quá trình ngủ, nó phải được đánh thức trước khi nó trở lại không gian người dùng và do đó xử lý tín hiệu đang chờ xử lý. Ở đây chúng ta có sự khác biệt giữa hai loại giấc ngủ chính:

  • TASK_INTERRUPTIBLE, giấc ngủ bị gián đoạn. Nếu một nhiệm vụ được đánh dấu bằng cờ này, nó đang ngủ, nhưng có thể bị đánh thức bởi các tín hiệu. Điều này có nghĩa là mã đánh dấu nhiệm vụ đang ngủ đang mong đợi một tín hiệu có thể và sau khi nó thức dậy sẽ kiểm tra nó và trả về từ cuộc gọi hệ thống. Sau khi tín hiệu được xử lý, cuộc gọi hệ thống có khả năng có thể được tự động khởi động lại (và tôi sẽ không đi vào chi tiết về cách thức hoạt động của nó).
  • TASK_UNINTERRUPTIBLE, giấc ngủ không ngớt. Nếu một tác vụ được đánh dấu bằng cờ này, nó sẽ không bị đánh thức bởi bất cứ thứ gì khác ngoài bất cứ điều gì nó đang chờ đợi, bởi vì nó không thể dễ dàng được khởi động lại, hoặc bởi vì các chương trình đang mong đợi hệ thống gọi là nguyên tử. Điều này cũng có thể được sử dụng cho giấc ngủ được biết là rất ngắn.

TASK_KILLABLE (được đề cập trong bài viết của LWN được liên kết với câu trả lời của ddaa) là một biến thể mới.

Điều này trả lời câu hỏi đầu tiên của bạn. Đối với câu hỏi thứ hai của bạn: bạn không thể tránh những giấc ngủ không bị gián đoạn, chúng là một điều bình thường (ví dụ, nó xảy ra mỗi khi một quá trình đọc / ghi từ / vào đĩa); tuy nhiên, chúng chỉ nên kéo dài một phần của giây. Nếu chúng tồn tại lâu hơn, điều đó thường có nghĩa là sự cố phần cứng (hoặc sự cố trình điều khiển thiết bị, trông giống với kernel), trong đó trình điều khiển thiết bị đang chờ phần cứng làm điều gì đó sẽ không bao giờ xảy ra. Điều đó cũng có nghĩa là bạn đang sử dụng NFS và máy chủ NFS không hoạt động (nó đang chờ máy chủ khôi phục; bạn cũng có thể sử dụng tùy chọn "xâm nhập" để tránh sự cố).

Cuối cùng, lý do bạn không thể khôi phục là lý do tương tự hạt nhân chờ cho đến khi trở về chế độ người dùng để phát tín hiệu hoặc giết quá trình: nó có khả năng làm hỏng cấu trúc dữ liệu của hạt nhân (mã chờ trong chế độ ngủ bị gián đoạn có thể nhận được lỗi cho biết để trở về không gian người dùng, nơi quá trình có thể bị giết, mã chờ trong một giấc ngủ không bị gián đoạn không mong đợi bất kỳ lỗi nào).


1
Lỗi khóa hệ thống tập tin cũng là một nguyên nhân có thể, IME.
Tobu

3
Tôi không hiểu tất cả những điều này. "Bạn không thể tránh những giấc ngủ không bị gián đoạn" - không thể hệ điều hành được tạo ra theo cách mà giấc ngủ không bị gián đoạn chỉ đơn giản là KHÔNG HIỆN như một trạng thái? Sau đó, phần về tham nhũng - không thể là phần chế độ hạt nhân của chính quá trình (hoặc bất cứ điều gì COULD gây ra tham nhũng) bị chấm dứt hoặc chỉ mã của nó được sửa đổi ngay trong bộ nhớ để trở về? Vui lòng giải thích lý do tại sao điều này quá khó / không thể làm được mà ngay cả Linux cũng không làm được. (Tôi nghĩ vấn đề này chỉ tồn tại trên Windows)
Dexter

Trường hợp duy nhất tôi có thể nghĩ rằng điều đó sẽ khiến (một cách an toàn) giết chết các quá trình đó thực sự là không thể (và không chỉ, giả sử, đặc biệt khó khăn) là nếu chính phần cứng có thể gây ra tham nhũng. Phần cứng không thể được kiểm soát; hạt nhân có thể . Nhưng đó là kernel lấy dữ liệu từ phần cứng và sửa đổi bộ nhớ (đó là lý do tại sao nó không được giải phóng trước khi quá trình quay trở lại chế độ người dùng và tại sao có thể xảy ra tham nhũng) ... thay đổi mã kernel trong bộ nhớ và không gặp vấn đề gì nữa.
Dexter

@Dexter nghĩ về kernel như thể nó là một tiến trình đa luồng đơn, trong đó phần chế độ kernel của mỗi tiến trình là một luồng trong kernel. Đề xuất của bạn sẽ tệ như giết một luồng trong một chương trình đa luồng: nó có thể để lại các khóa bị treo, cấu trúc dữ liệu tạm thời bị sửa đổi hoặc ở giữa bị sửa đổi, v.v.
CesarB

@CesarB, bạn có đúng không khi giết một luồng ... Nhưng không phải luồng "chính" (đó sẽ là nhân hệ điều hành và các luồng khác sẽ là trình điều khiển chẳng hạn) bằng cách nào đó xử lý nó? Mặc dù các cấu trúc "ở giữa bị sửa đổi" dường như là một vấn đề thực sự khó khăn ... có lẽ chúng ta sẽ thực sự không bao giờ thấy một hệ điều hành mà các quá trình không thể gián đoạn sẽ là không thể :(
Dexter

23

Các quy trình không bị gián đoạn là KHÔNG THỂ chờ đợi I / O sau lỗi trang.

Xem xét điều này:

  • Chủ đề cố gắng truy cập một trang không nằm trong lõi (có thể là tệp thực thi được tải theo yêu cầu, một trang của bộ nhớ ẩn danh đã bị tráo đổi hoặc tệp mmap () 'được tải theo yêu cầu, phần lớn là điều tương tự)
  • Hạt nhân hiện đang (cố gắng) tải nó vào
  • Quá trình không thể tiếp tục cho đến khi trang có sẵn.

Quá trình / nhiệm vụ không thể bị gián đoạn trong trạng thái này, vì nó không thể xử lý bất kỳ tín hiệu nào; nếu có, một lỗi trang khác sẽ xảy ra và nó sẽ trở lại đúng vị trí của nó.

Khi tôi nói "process", tôi thực sự có nghĩa là "task", theo Linux (2.6) tạm dịch là "thread" có thể có hoặc không có một mục "nhóm luồng" riêng lẻ trong / Proc

Trong một số trường hợp, nó có thể chờ đợi trong một thời gian dài. Một ví dụ điển hình về điều này sẽ là nơi tệp thực thi hoặc mmap'd nằm trên hệ thống tệp mạng nơi máy chủ bị lỗi. Nếu cuối cùng I / O thành công, nhiệm vụ sẽ tiếp tục. Nếu cuối cùng thất bại, nhiệm vụ thường sẽ nhận được SIGBUS hoặc một cái gì đó.


1
Nếu cuối cùng thất bại, nhiệm vụ thường sẽ nhận được SIGBUS hoặc một cái gì đó. Đợi đã, không thể tạo ra kernel để khi giết các quá trình "không thể gián đoạn" đó, nó chỉ đơn giản là NÓI cho chúng hoạt động I / O không thành công? Sau đó, quá trình sẽ trở lại chế độ người dùng và sẽ biến mất? Có một cách để giết một cách an toàn các quy trình trạng thái 'D' đó. Tôi đoán nó không dễ dàng và đó là lý do tại sao cả Windows và Linux đều không có khả năng đó. Mặt khác, tôi muốn có thể giết chết các quá trình đó ít nhất là không an toàn. Tôi không quan tâm đến sự cố hệ thống có thể xảy ra hoặc bất cứ điều gì ...
Dexter

@Dexter hmm, tôi chưa bao giờ gặp vấn đề này với Windows. Một cách để tái tạo nó ở đó là gì? Ít nhất là theo bài đăng này , tất cả các yêu cầu I / O có thể bị gián đoạn trong Windows.
Ruslan

1

Đối với câu hỏi thứ 3 của bạn: Tôi nghĩ bạn có thể tiêu diệt các quá trình không thể gián đoạn bằng cách chạy sudo kill -HUP 1. Nó sẽ khởi động lại init mà không kết thúc các tiến trình đang chạy và sau khi chạy nó, các tiến trình không bị gián đoạn của tôi đã biến mất.


-3

Nếu bạn đang nói về một quá trình "zombie" (được chỉ định là "zombie" trong đầu ra ps), thì đây là một bản ghi vô hại trong danh sách quy trình đang chờ ai đó thu thập mã trả về của nó và nó có thể được bỏ qua một cách an toàn.

Bạn có thể vui lòng mô tả những gì và "quá trình không thể gián đoạn" là dành cho bạn? Liệu nó có sống sót sau "kill -9" và vui vẻ chu chu cùng không? Nếu đó là trường hợp, thì nó bị kẹt trên một số tòa nhà, bị kẹt trong một số trình điều khiển và bạn bị mắc kẹt với quá trình này cho đến khi khởi động lại (và đôi khi tốt hơn là khởi động lại sớm) hoặc không tải trình điều khiển có liên quan (không có khả năng xảy ra) . Bạn có thể thử sử dụng "strace" để tìm ra nơi quá trình của bạn bị mắc kẹt và tránh nó trong tương lai.


Trình điều khiển không thể được tải mạnh mẽ giống như cách một quá trình có thể bị giết? Tôi biết chế độ kernel có nhiều quyền truy cập hơn chế độ người dùng, nhưng bản thân hệ điều hành không bao giờ có thể có nhiều đặc quyền hơn. Bất cứ điều gì thực thi trong chế độ kernel luôn có thể làm xáo trộn mọi thứ khác đang thực thi trong chế độ kernel - đơn giản là không có điều khiển.
Dexter
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.