Tôi có hạt nhân Linux 3.7.1 nguồn trên tay và do điều này, tôi sẽ cố gắng cung cấp câu trả lời cho câu hỏi của bạn trên cơ sở các nguồn đó. Những gì chúng ta có trong mã. Trong arch\x86\kernel\traps.c
chúng tôi có chức năng early_trap_init()
nơi dòng mã tiếp theo có thể được tìm thấy:
set_intr_gate(X86_TRAP_DE, ÷_error);
Như chúng ta có thể nhìn thấy set_trap_gate()
đã được thay thế bởi set_intr_gate()
. Nếu trong lượt tiếp theo chúng tôi mở rộng cuộc gọi này, chúng ta sẽ đạt được:
_set_gate(X86_TRAP_DE, GATE_INTERRUPT, ÷_error, 0, 0, __KERNEL_CS);
_set_gate
là một thói quen đó là chịu trách nhiệm về hai điều:
- Đầu tư xây dựng các mô tả IDT
Cài đặt của xây dựng bộ mô tả vào ô đích trong mảng mô tả IDT. Bản thứ hai chỉ là sao chép bộ nhớ và không phải là điều thú vị đối với chúng tôi. Nhưng nếu chúng ta sẽ xem xét làm thế nào nó xây dựng mô tả từ các thông số được cung cấp chúng ta sẽ thấy:
struct desc_struct{
unsigned int a;
unsigned int b;
};
desc_struct gate;
gate->a = (__KERNEL_CS << 16) | (÷_error & 0xffff);
gate->b = (÷_error & 0xffff0000) | (((0x80 | GATE_INTERRUPT | (0 << 5)) & 0xff) << 8);
Hoặc cuối cùng
gate->a = (__KERNEL_CS << 16) | (÷_error & 0xffff);
gate->b = (÷_error & 0xffff0000) | (((0x80 | 0xE | (0 << 5)) & 0xff) << 8);
Như chúng ta có thể thấy ở phần cuối của việc xây dựng mô tả chúng tôi sẽ có cấu trúc dữ liệu 8 byte tiếp theo trong bộ nhớ
[0xXXXXYYYY][0xYYYY8E00], where X denotes digits of kernel code segment selector number, and Y denotes digits of address of the divide_error routine.
Các đường dữ liệu 8 byte này ucture là một bộ mô tả ngắt được định nghĩa bởi bộ vi xử lý. Nó được sử dụng bởi bộ vi xử lý để xác định những hành động phải được thực hiện trong trả lời chấp nhận ngắt với vector cụ thể. Bây giờ hãy nhìn vào định dạng của các bộ mô tả ngắt xác định bởi Intel cho x86 xử lý gia đình:
80386 INTERRUPT GATE
31 23 15 7 0
+-----------------+-----------------+---+---+---------+-----+-----------+
| OFFSET 31..16 | P |DPL| TYPE |0 0 0|(NOT USED) |4
|-----------------------------------+---+---+---------+-----+-----------|
| SELECTOR | OFFSET 15..0 |0
+-----------------+-----------------+-----------------+-----------------+
Trong định dạng này cặp SELECTOR: OFFSET xác định địa chỉ của hàm (theo định dạng dài) sẽ kiểm soát để trả lời sự chấp nhận ngắt. Trong trường hợp của chúng tôi, đây là __KERNEL_CS:divide_error
, trong đó divide_error()
là bộ xử lý thực tế của ngoại lệ Division By Zero. P cờ chỉ định là bộ mô tả nên được coi là một bộ mô tả hợp lệ được thiết lập chính xác bởi hệ điều hành và trong trường hợp của chúng tôi nó ở trạng thái nâng lên. DPL - chỉ định các vòng bảo mật mà chức năng divide_error()
có thể được kích hoạt bằng cách sử dụng các ngắt mềm. Một số nền tảng cần thiết để hiểu vai trò của lĩnh vực đó.
Nói chung có ba loại nguồn ngắt:
- thiết bị bên ngoài mà yêu cầu một dịch vụ từ hệ điều hành.
- Bộ vi xử lý, khi phát hiện thấy nó thu nhập vào trạng thái bất thường yêu cầu hệ điều hành giúp nó thoát ra khỏi trạng thái đó.
- Thực hiện chương trình trên bộ xử lý trong điều khiển OS, yêu cầu một số dịch vụ đặc biệt từ hệ điều hành.
Trường hợp cuối cùng có sự hỗ trợ đặc biệt từ bộ xử lý dưới dạng hướng dẫn chuyên dụng int XX. Mỗi khi chương trình muốn dịch vụ hệ điều hành, nó thiết lập các tham số mô tả yêu cầu và đưa ra lệnh int với tham số mô tả vector ngắt, được sử dụng bởi hệ điều hành để cung cấp dịch vụ. Ngắt được tạo ra bằng cách phát hành lệnh int được gọi là ngắt mềm. Vì vậy, ở đây, bộ xử lý chỉ đưa trường DPL vào tài khoản khi nó xử lý các ngắt mềm, hoàn toàn bỏ qua chúng trong trường hợp ngắt do chính bộ xử lý tạo ra hoặc bởi các thiết bị bên ngoài. DPL là một tính năng rất quan trọng, bởi vì nó cấm các ứng dụng mô phỏng các thiết bị, và điều này hàm ý hành vi của hệ thống.
Hãy tưởng tượng ví dụ rằng một số ứng dụng sẽ làm một cái gì đó như thế này:
for(;;){
__asm int 0xFF;
//where 0xFF is vector used by system timer, to notify the kernel that the
another one timer tick was occurred
}
Trong trường hợp thời gian trong máy tính của bạn sẽ nhanh hơn nhiều sau đó trong cuộc sống thực, sau đó bạn mong đợi và hệ thống của bạn mong đợi. Kết quả là hệ thống của bạn sẽ hoạt động rất sai. Như bạn thấy bộ vi xử lý và thiết bị bên ngoài được coi là đáng tin cậy, nhưng nó không phải là một trường hợp cho các ứng dụng chế độ người dùng. Trong trường hợp ngoại lệ của Division By Zero, Linux xác định rằng ngoại lệ này có thể được kích hoạt bằng ngắt mềm chỉ từ vòng 0, hay nói cách khác, chỉ từ hạt nhân. Kết quả là, nếu lệnh int 0 sẽ được thực hiện trong không gian hạt nhân, bộ vi xử lý sẽ chuyển điều khiển đến thường lệ divide_error()
. Nếu cùng một lệnh sẽ được thực hiện trong không gian người dùng, kernel sẽ là vi phạm bảo vệ và sẽ chuyển điều khiển tới trình xử lý ngoại lệ bảo vệ lỗi chung (đây là hành động mặc định cho tất cả các ngắt mềm không hợp lệ). Nhưng nếu ngoại lệ Division By Zero sẽ được tạo ra bởi chính bộ vi xử lý đã cố gắng chia một số giá trị bằng 0, điều khiển sẽ được chuyển đến thường lệ divide error()
bất kể không gian xảy ra sự phân chia không chính xác. Nói chung nó có vẻ như nó sẽ không là một thiệt hại lớn để cho phép ứng dụng để trig Division By Zero ngoại lệ bằng cách gián đoạn mềm. Nhưng lần đầu tiên nó sẽ là một thiết kế xấu xí và cho một số thứ hai logic có thể được đằng sau hiện trường, mà dựa vào thực tế là Division By Zero ngoại lệ có thể được tạo ra chỉ bằng cách thực tế hoạt động phân chia không chính xác.
Trường TYPE chỉ định các tác vụ phụ phải được thực hiện bởi bộ xử lý để trả lời chấp nhận ngắt. Trong thực tế, chỉ có hai kiểu mô tả ngoại lệ được sử dụng: bộ mô tả ngắt và bộ mô tả bẫy. Chúng chỉ khác nhau ở một khía cạnh. Bộ mô tả ngắt buộc bộ xử lý vô hiệu hóa việc chấp nhận ngắt trong tương lai và bộ mô tả bẫy không làm điều này. Nếu thành thật mà nói, tôi không biết, tại sao hạt nhân Linux sử dụng bộ mô tả ngắt cho xử lý ngoại lệ Division By Zero. Đối với tôi, bộ mô tả bẫy phù hợp hơn ở đó.
Và lưu ý cuối cùng liên quan đến đầu ra khó hiểu của chương trình thử nghiệm
Floating point exception (core dumped)
Bởi lý do lịch sử, Linux kernel trả lời cho phép chia cho không ngoại lệ bằng cách gửi SIGFPE (đọc tín hiệu Floating Point Exception) tín hiệu đến quá trình đã cố gắng chia cho số không. Có, không phải SIGDBZ (đọc SIGnal Division By Zero). Tôi biết điều này là đủ khó hiểu.Lý do của hành vi như vậy là Linux bắt chước hành vi gốc UNIX (Tôi nghĩ rằng hành vi này đã bị đóng băng trong POSIX) và UNIX một số lý do tại sao lại xem xét ngoại lệ "Phân chia theo số không" là "Ngoại lệ dấu chấm động". Tôi không biết tại sao.
[POSIX yêu cầu] (https://stackoverflow.com/questions/37262572/on-which-platforms-does-integer-divide-by-zero-trigger-a-floating-point-exceptio) rằng nếu phân chia số nguyên bằng không bẫy, nó làm tăng SIGFPE. Trớ trêu thay, phân chia số nguyên là hoạt động duy nhất có thể dẫn đến SIGFPE trừ khi bạn loại trừ cụ thể các ngoại lệ FP, trên x86 Linux. –