Tất cả các CPU hiện đại có khả năng làm gián đoạn hướng dẫn máy hiện đang thực thi. Họ lưu đủ trạng thái (thông thường, nhưng không phải luôn luôn, trên ngăn xếp) để có thể tiếp tục thực hiện sau đó, như thể không có gì xảy ra (thông thường lệnh bị gián đoạn sẽ được khởi động lại từ đầu). Sau đó, họ bắt đầu thực hiện một trình xử lý ngắt , chỉ là mã máy nhiều hơn, nhưng được đặt tại một vị trí đặc biệt để CPU biết vị trí của nó trước. Trình xử lý ngắt luôn là một phần của hạt nhân của hệ điều hành: thành phần chạy với đặc quyền lớn nhất và chịu trách nhiệm giám sát việc thực hiện tất cả các thành phần khác. 1,2
Các ngắt có thể là đồng bộ , có nghĩa là chúng được kích hoạt bởi chính CPU như là một phản ứng trực tiếp với một thứ mà lệnh hiện đang thực hiện, hoặc không đồng bộ , nghĩa là chúng xảy ra vào thời điểm không thể đoán trước do sự kiện bên ngoài, như dữ liệu đến trên mạng Hải cảng. Một số người bảo lưu thuật ngữ "ngắt" cho các ngắt không đồng bộ và gọi các ngắt đồng bộ là "bẫy", "lỗi" hoặc "ngoại lệ", nhưng những từ đó đều có nghĩa khác nên tôi sẽ gắn với "ngắt đồng bộ".
Bây giờ, hầu hết các hệ điều hành hiện đại đều có khái niệm về các quy trình . Về cơ bản nhất, đây là một cơ chế trong đó máy tính có thể chạy nhiều chương trình cùng một lúc, nhưng nó cũng là một khía cạnh quan trọng trong cách các hệ điều hành cấu hình bảo vệ bộ nhớ , là một tính năng của hầu hết (nhưng, than ôi, vẫn chưa phải là tất cả ) CPU hiện đại. Nó đi cùng với bộ nhớ ảo, đó là khả năng thay đổi ánh xạ giữa các địa chỉ bộ nhớ và vị trí thực tế trong RAM. Bảo vệ bộ nhớ cho phép hệ điều hành cung cấp cho mỗi tiến trình một đoạn RAM riêng, chỉ có nó mới có thể truy cập. Nó cũng cho phép hệ điều hành (thay mặt một số tiến trình) chỉ định các vùng RAM là chỉ đọc, thực thi, được chia sẻ giữa một nhóm các quy trình hợp tác, v.v. Cũng sẽ có một đoạn bộ nhớ chỉ có thể truy cập được bởi nhân. 3
Miễn là mỗi tiến trình chỉ truy cập bộ nhớ theo cách mà CPU được cấu hình để cho phép, bảo vệ bộ nhớ là vô hình. Khi một quá trình phá vỡ các quy tắc, CPU sẽ tạo ra một ngắt đồng bộ, yêu cầu kernel sắp xếp mọi thứ. Nó thường xảy ra rằng quá trình không thực sự phá vỡ các quy tắc, chỉ có kernel cần thực hiện một số công việc trước khi quá trình có thể được phép tiếp tục. Chẳng hạn, nếu một trang trong bộ nhớ của một tiến trình cần được "đuổi" sang tệp hoán đổi để giải phóng không gian trong RAM cho một thứ khác, thì kernel sẽ đánh dấu trang đó không thể truy cập được. Lần tiếp theo quá trình cố gắng sử dụng nó, CPU sẽ tạo ra một ngắt bảo vệ bộ nhớ; hạt nhân sẽ lấy lại trang từ trao đổi, đặt nó trở lại vị trí cũ, đánh dấu nó có thể truy cập lại và tiếp tục thực hiện.
Nhưng giả sử rằng quá trình thực sự đã phá vỡ các quy tắc. Nó đã cố gắng truy cập một trang chưa bao giờ có bất kỳ RAM nào được ánh xạ tới nó hoặc nó đã cố thực thi một trang được đánh dấu là không chứa mã máy hoặc bất cứ thứ gì. Họ các hệ điều hành thường được gọi là "Unix" đều sử dụng tín hiệu để xử lý tình huống này. 4 Tín hiệu tương tự như các ngắt, nhưng chúng được tạo bởi kernel và được xử lý bởi các tiến trình, thay vì được tạo bởi phần cứng và được tạo bởi kernel. Quá trình có thể xác định xử lý tín hiệutrong mã riêng của họ và cho biết kernel đang ở đâu. Những bộ xử lý tín hiệu sau đó sẽ thực thi, làm gián đoạn dòng điều khiển thông thường, khi cần thiết. Tất cả các tín hiệu đều có một số và hai tên, một trong số đó là từ viết tắt khó hiểu và tên còn lại là một cụm từ ít khó hiểu hơn. Tín hiệu được tạo ra khi một quá trình phá vỡ các quy tắc bảo vệ bộ nhớ là (theo quy ước) số 11, và tên của nó là SIGSEGV
và "Lỗi phân đoạn". 5,6
Một sự khác biệt quan trọng giữa tín hiệu và ngắt là có một hành vi mặc định cho mọi tín hiệu. Nếu hệ điều hành không xác định trình xử lý cho tất cả các ngắt, đó là lỗi trong HĐH và toàn bộ máy tính sẽ gặp sự cố khi CPU cố gắng gọi trình xử lý bị thiếu. Nhưng các quy trình không có nghĩa vụ xác định các bộ xử lý tín hiệu cho tất cả các tín hiệu. Nếu kernel tạo tín hiệu cho một tiến trình và tín hiệu đó bị bỏ lại ở hành vi mặc định của nó, kernel sẽ tiếp tục và làm bất cứ điều gì mặc định và không làm phiền quá trình. Hầu hết các hành vi mặc định của tín hiệu là "không làm gì" hoặc "chấm dứt quá trình này và cũng có thể tạo ra kết xuất lõi". SIGSEGV
là một trong những cái sau
Vì vậy, để tóm tắt lại, chúng ta có một quy trình phá vỡ các quy tắc bảo vệ bộ nhớ. CPU đã đình chỉ quá trình và tạo ra một ngắt đồng bộ. Hạt nhân đã ngắt trường và tạo SIGSEGV
tín hiệu cho quá trình. Giả sử quy trình không thiết lập trình xử lý tín hiệu SIGSEGV
, vì vậy kernel thực hiện hành vi mặc định, đó là chấm dứt quá trình. Điều này có tất cả các hiệu ứng giống như _exit
cuộc gọi hệ thống: các tệp đang mở bị đóng, bộ nhớ bị giải phóng, v.v.
Cho đến thời điểm này, không có gì có thể in ra bất kỳ thông điệp nào mà con người có thể nhìn thấy, và vỏ (hay nói chung hơn là quá trình cha mẹ của quá trình vừa bị chấm dứt) hoàn toàn không được tham gia. SIGSEGV
đi đến quá trình phá vỡ các quy tắc, không phải cha mẹ của nó. Tuy nhiên, bước tiếp theo trong chuỗi là thông báo cho quá trình cha mẹ rằng con của nó đã bị chấm dứt. Điều này có thể xảy ra theo nhiều cách khác nhau, trong đó đơn giản nhất là khi phụ huynh đang đợi thông báo này, bằng một trong những wait
cuộc gọi hệ thống ( wait
, waitpid
, wait4
, vv). Trong trường hợp đó, kernel sẽ chỉ khiến cuộc gọi hệ thống đó trả về và cung cấp cho tiến trình cha một số mã được gọi là trạng thái thoát. 7 Trạng thái thoát thông báo cho cha mẹ tại sao quá trình con bị chấm dứt; trong trường hợp này, nó sẽ biết rằng đứa trẻ đã bị chấm dứt do hành vi mặc định của SIGSEGV
tín hiệu.
Quá trình cha mẹ sau đó có thể báo cáo sự kiện cho con người bằng cách in một tin nhắn; chương trình shell hầu như luôn luôn làm điều này. Mã của bạn crsh
không bao gồm mã để làm điều đó, nhưng dù sao thì nó cũng xảy ra, bởi vì thói quen của thư viện C system
chạy một trình bao đầy đủ tính năng /bin/sh
, "dưới mui xe". crsh
là ông bà trong kịch bản này; thông báo tiến trình cha mẹ được điền vào /bin/sh
, trong đó in thông báo thông thường của nó. Sau đó, /bin/sh
nó tự thoát, vì nó không còn gì để làm và việc triển khai thư viện C system
nhận được thông báo thoát đó . Bạn có thể thấy thông báo thoát đó trong mã của mình, bằng cách kiểm tra giá trị trả về củasystem
; nhưng nó sẽ không cho bạn biết rằng quá trình cháu đã chết trên một segfault, bởi vì nó được tiêu thụ bởi quá trình vỏ trung gian.
Chú thích
Một số hệ điều hành không triển khai trình điều khiển thiết bị như một phần của kernel; tuy nhiên, tất cả các trình xử lý ngắt vẫn phải là một phần của kernel và mã cũng cấu hình bảo vệ bộ nhớ, vì phần cứng không cho phép bất cứ thứ gì ngoại trừ kernel làm những việc này.
Có thể có một chương trình gọi là "trình ảo hóa" hoặc "trình quản lý máy ảo" thậm chí còn đặc quyền hơn kernel, nhưng với mục đích của câu trả lời này, nó có thể được coi là một phần của phần cứng .
Nhân là một chương trình , nhưng nó không phải là một quá trình; nó giống như một thư viện Tất cả các quy trình thực hiện các phần của mã của hạt nhân, theo thời gian, ngoài mã riêng của họ. Có thể có một số "chuỗi nhân" chỉ thực thi mã hạt nhân, nhưng chúng không liên quan đến chúng tôi ở đây.
Hệ điều hành một và duy nhất mà bạn có khả năng sẽ phải đối phó nữa mà không thể coi là một triển khai của Unix, tất nhiên, là Windows. Nó không sử dụng tín hiệu trong tình huống này. (Thật vậy, nó không có tín hiệu; trên Windows <signal.h>
giao diện hoàn toàn bị giả mạo bởi thư viện C.) Thay vào đó, nó sử dụng một cái gì đó gọi là " xử lý ngoại lệ có cấu trúc ".
Một số vi phạm bảo vệ bộ nhớ tạo ra SIGBUS
("Lỗi xe buýt") thay vì SIGSEGV
. Dòng giữa hai là không xác định và thay đổi từ hệ thống để hệ thống. Nếu bạn đã viết một chương trình xác định trình xử lý cho SIGSEGV
thì có lẽ nên xác định trình xử lý tương tự SIGBUS
.
"Lỗi phân đoạn" là tên của ngắt được tạo ra do vi phạm bảo vệ bộ nhớ bởi một trong những máy tính chạy Unix gốc , có thể là PDP-11 . " Phân đoạn " là một loại bảo vệ bộ nhớ, nhưng ngày nay thuật ngữ " lỗi phân đoạn " nói chung cho bất kỳ loại vi phạm bảo vệ bộ nhớ nào.
Tất cả các cách khác mà quá trình cha mẹ có thể được thông báo về một đứa trẻ đã chấm dứt, kết thúc bằng việc cha mẹ gọi wait
và nhận được trạng thái thoát. Chỉ là một cái gì đó khác xảy ra đầu tiên.
crsh
là một ý tưởng tuyệt vời cho loại thử nghiệm này. Cảm ơn đã cho tất cả chúng ta biết về nó và ý tưởng đằng sau nó.