2011-12-16 18 views
7

Trong kernel 2.6.11.5, chia zero xử lý ngoại lệ được thiết lập như:Tại sao nhân Linux sử dụng cổng bẫy để xử lý ngoại lệ divid_error?

set_trap_gate(0,&divide_error); 

Theo "Những hiểu biết Linux Kernel", cửa bẫy Intel không thể được truy cập bởi một quá trình Chế độ sử dụng. Nhưng nó hoàn toàn có thể là một quá trình chế độ người dùng cũng tạo ra một divide_error. Vậy tại sao Linux thực hiện nó theo cách này?

[Chỉnh sửa] Tôi nghĩ câu hỏi vẫn mở, vì set_trap_gate() đặt giá trị DPL của mục nhập IDT thành 0, có nghĩa là mã CPL = 0 (đọc kernel) mới có thể thực thi nó, vì vậy không rõ trình xử lý có thể được gọi từ chế độ người dùng:

#include<stdio.h> 

int main(void) 
{ 
    int a = 0; 
    int b = 1; 

    b = b/a; 

    return b; 
} 

được biên dịch với gcc div0.c. Và đầu ra của ./a.out là:

Floating point ngoại lệ (core dumped)

Vì vậy, nó không giống như thế này đã được xử lý bởi các bộ phận bằng 0 bẫy mã.

+0

[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. –

Trả lời

2

Hạt nhân không chạy dưới chế độ người dùng. Nó phải xử lý bẫy được tạo bởi các chương trình chế độ người dùng (ví dụ: các quy trình linux trong vùng người dùng). Mã hạt nhân không được dự kiến ​​sẽ chia cho số không.

Tôi không hiểu rõ câu hỏi của bạn. Làm thế nào bạn sẽ thực hiện nó nếu không?

+0

Thực tế set_trap_gate (0, & divid_error); có nghĩa là mã * hạt nhân * duy nhất có thể được xử lý bởi bẫy này ... –

3

Mã chế độ người dùng không có bảng hệ thống truy cập kinh doanh như phân đoạn và bảng mô tả ngắt, chúng không có ý định được thao tác bên ngoài hạt nhân OS và không cần. Các trình xử lý Linux cho các ngoại lệ như phân chia bằng không, ngoại lệ bảo vệ chung, lỗi trang và các trường hợp khác chặn các ngoại lệ bắt nguồn từ cả chế độ người dùng và mã chế độ lõi. Chúng có thể xử lý chúng khác nhau dựa trên nguồn gốc, nhưng bảng mô tả ngắt chứa địa chỉ của một trình xử lý cho mọi loại ngoại lệ (ví dụ như ở trên). Và mọi người xử lý đều biết cách xử lý ngoại lệ của nó.

4

bit DPL trong IDT chỉ được xem khi phần mềm gián đoạn được gọi với lệnh int. Phân chia bằng không là phần mềm gián đoạn được kích hoạt bởi CPU và do đó có DPL không có hiệu lực trong trường hợp này

1

Câu trả lời cho câu hỏi của bạn có thể được tìm thấy tại mục 6.12.1.1 của "Intel (R) 64 và IA- 32 Kiến trúc và hướng dẫn sử dụng phần mềm phát triển của, Tập 3A"

Bộ xử lý kiểm tra DPL của ngắt hoặc bẫy cổng chỉ khi một ngoại lệ hoặc ngắt được tạo ra với một n INT, INT 3, hoặc VÀO hướng dẫn. Ở đây, CPL phải nhỏ hơn hoặc bằng DPL của cổng. Hạn chế này ngăn cản chương trình hoặc thủ tục ứng dụng chạy ở cấp đặc quyền 3 khi sử dụng phần mềm ngắt để truy cập trình xử lý ngoại lệ quan trọng, chẳng hạn như xử lý lỗi trang , với điều kiện các trình xử lý đó được đặt trong mã đặc quyền hơn phân đoạn cấp độ). Đối với các ngắt do phần cứng tạo ra và các ngoại lệ được phát hiện bởi bộ xử lý, bộ xử lý bỏ qua DPL bị gián đoạn và các cổng bẫy.

Đó là những gì Alex Kreimer trả lời

Về vào tin nhắn. Tôi không hoàn toàn chắc chắn, nhưng có vẻ như hệ điều hành gửi tín hiệu SIGFPE cho quá trình.

5

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, &divide_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, &divide_error, 0, 0, __KERNEL_CS); 

_set_gate là một thói quen đó là chịu trách nhiệm về hai điều:

  1. Đầu tư xây dựng các mô tả IDT
  2. 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) | (&divide_error & 0xffff); 
    gate->b = (&divide_error & 0xffff0000) | (((0x80 | GATE_INTERRUPT | (0 << 5)) & 0xff) << 8); 
    

Hoặc cuối cùng

gate->a = (__KERNEL_CS << 16) | (&divide_error & 0xffff); 
gate->b = (&divide_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:

  1. thiết bị bên ngoài mà yêu cầu một dịch vụ từ hệ điều hành.
  2. 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 đó.
  3. 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.