Lập trình xử lý ngắt

Một trong những vai trò chính của kernel là tương tác, giao tiếp với các thiết bị ngoại vi như bàn phím, chuột, ổ đĩa … Thông thường tốc độ của vi xử lý nhanh hơn, nên nếu kernel giao tiếp với thiết bị ngoại vi theo cách thông thường: đưa yêu cầu, chờ thiết bị ngoại vi hoàn tất công việc và phản hồi sẽ làm lãng phí hiệu suất làm việc của vi xử lý. Thay vì thế, kernel có thể thực hiện các tác vụ khác, và chỉ làm việc với thiết bị ngoại vi khi thiết bị ngoại vi đã hoàn tất công việc của nó.

Làm cách nào để kernel chỉ làm việc với thiết bị ngoại vi khi nó đã hoàn tất công việc?

Một phương thức phổ biến có thể được dùng là polling. Với phương thức này, kernel sẽ định kì kiểm tra trạng thái của thiết bị, xem thiết bị đã hoàn tất công việc chưa. Khoảng thời gian giữa các lần kiểm tra định kì đó kernel có thể thực thi tác vụ khác. Tuy nhiên, việc định kì kiểm tra trạng thái thiết bị, vẫn đâu đó có sự lãng phí tài nguyên.

Một giải pháp khác hoàn hảo hơn được đưa ra: Thiết bị phần cứng sẽ gửi tín hiệu thông báo tới kernel khi nó hoàn thành công việc, kernel sẽ không cần phải kiểm tra định kì xem thiết bị đã sẵn sàng chưa. Phương pháp này gọi là interrupt – ngắt.

Định nghĩa Interrupts [Ngắt]

Interrupts – ngắt là sự kiện dừng công việc hiện tại của CPU, buộc CPU thực hiện một việc nào đó rồi mới quay trở lại thực hiện tiếp công việc cũ.

Ngắt được chia làm 2 loại chính

  • Ngắt đồng bộ: Ngắt được tạo ra bởi bộ điều khiển trung tâm – Center Processing Unit . Ngắt được gọi là đồng bộ vì trong quá trình CPU thực thi các câu lệnh, bộ điều khiển gọi ngắt sau khi kết thúc thực thi của một lệnh. CPU là thiết bị chủ động tạo ra ngắt.

  • Ngắt không đồng bộ: Ngắt được tạo ra bởi các thiết bị phần cứng khác, và được tạo ra ở bất kì thời điểm nào mà không liên quan đến các câu lệnh hiện tại đang được CPU thực thi. CPU không là thiết bị chủ động tạo ra ngắt, ngắt được tạo ra ở thời điểm CPU không đoán trước được bởi thiết bị khác.

Ví dụ khi bạn đang ăn cơm, có một cuộc điện thoại khẩn cấp gọi đến bất ngờ, bạn phải dừng bữa ăn và nghe xong cuộc điện thoại, sau đó quay lại ăn cơm tiếp. Cuộc điện thoại có thể đến bất kì lúc nào trong thời gian ăn cơm, đây là ngắt không đồng bộ. Ở đây bạn là CPU, còn điện thoại là thiết bị phần cứng ngoài tạo ra ngắt là chuông reo báo cuộc gọi.

Ví dụ về ngắt không đồng bộ: Vẫn là bạn đang ăn cơm, nhưng đang ăn thì thấy một con sâu nằm trong bát canh, bạn cũng phải dừng bữa ăn, gắp con sâu bỏ đi [hoặc bỏ luôn bát canh] rồi quay lại ăn cơm tiếp. Trong ví dụ này, bạn vẫn là CPU, nhưng ngắt được tạo ra bởi chính bạn trong quá trình bạn thực hiện việc uống nước canh và đột nhiên phát hiện ra con sâu.

Trong hai ví dụ trên, điểm khác biệt lớn nhất là việc nguồn tạo ra ngắt khác nhau. Ở ví dụ đầu về ngắt không đồng bộ, nguồn tạo ra ngắt là chiếc điện thoại [vai trò là thiết bị ngoài] còn trong ví dụ tiếp theo, nguồn tạo ra ngắt lại là chính bạn [vai trò là CPU].

Interrupts và Exceptions đều là ngắt, nhưng Interruptslà loại ngắt không đồng bộ, còn Exceptions là ngắt đồng bộ.

Interrupts được tạo ra bởi các thiết bị phần cứng ngoài như bàn phím, chuột, …

Exceptions được tạo ra khi xảy ra lỗi trong quá trình CPU thực thi các câu lệnh, ví dụ như lệnh chia một số cho số 0, tràn bộ đệm …

Phân loại Interrupts và Exceptions

Dựa theo tài liệu của Intel, Interrupts và Exceptions được phân chia như sau

Interrupts – Ngắt: Được chia làm 2 loại

  • Maskable Interrupt [Ngắt có thể che]

Tất cả các ngắt tạo ra bởi thiết bị vào/ra là ngắt có thể che. Một ngắt có thể che có hai trạng thái: trạng thái che và trạng thái không che. CPU chỉ xử lý ngắt khi ngắt ở trạng thái không che, và bỏ qua nếu ngắt đang ở trạng thái che.

  • Nonmaskable Interrupt [Ngắt không thể che]

Một số sự kiện khẩn cấp của hệ thống [ví dụ như phần cứng bị lỗi] là các ngắt không thể che. Ngắt không thể che luôn luôn được CPU xử lý.

Exceptions – Ngoại lệ: Cũng được chia làm 2 loại

  • Processor-detected exceptios – Ngoại lệ phát hiện bởi vi xử lý

Được tạo ra bởi CPU khi CPU phát hiện một điều kiện bất thường khi đang thực hiện một lệnh. Loại ngoại lệ này được chia tiếp thành 3 loại con

- Faults: Ngoại lệ có thể được sửa chữa được. Chương trình gây ra ngoại lệ này có thể tiếp tục thực hiện. Địa chỉ lệnh gây ra ngoại lệ sẽ được lưu lại trong thanh ghi eip.

- Traps: Trap – bẫy được gọi ngay sau khi thực hiện xong lệnh đang được bẫy. Trap thường được sử dụng với mục đích debug, ví dụ như đặt các điểm breakpoint, để thông báo cho công cụ tìm lỗi debugger một câu lệnh vừa được thực hiện xong. Thanh ghi eip trong trường hợp này lưu lại địa chỉ câu lệnh tiếp theo của câu lệnh được bẫy.

- Aborts: Được dùng khi một lỗi nghiêm trọng xảy ra, trường hợp này hệ thống có thể không ghi được chính xác địa chỉ lệnh gây ra ngoại lệ vào thanh ghi eip như trường hợp Faults. Chương trình gây ra ngoại lệ sẽ bị kết thúc.

Programmed exceptions – Ngoại lệ được lập trình

Ngoại lệ được lập trình là ngoại lệ được tạo ra bởi chính câu lệnh [ngược với ngoại lệ phát hiện và tạo ra bởi vi xử lý]. Các lệnh tạo ra ngoại lệ này ví dụ như int, int3, into, bound.

IRQs và Interrupts

IRQ là Interrupt ReQuest – Yêu cầu ngắt. Mỗi một thiết bị phần cứng sẽ được cung cấp một đường yêu cầu ngắt [Interrupt Request Line] riêng. Những đường yêu cầu ngắt này được nối vào chân input của một phần cứng được gọi là PIC – Programmable Interrupts Controller [Bộ điều khiển ngắt lập trình được].

Hình 1: Programmable Interrupts Controller [PIC]

Quá trình một thiết bị phần cứng ngoài tạo ra một ngắt như sau

  • Phần cứng tạo ra một ngắt qua đường yêu cầu ngắt được nối với nó [IRQn].

  • PIC chuyển đổi yêu cầu ngắt thành một vector và ghi chúng vào một cổng điều khiển ngắt của CPU, để CPU có để đọc được thông qua bus dữ liệu.

  • PIC tạo ra một ngắt thông qua đường INTR của CPU.

  • PIC chờ CPU phản hồi lại ngắt. Sau khi CPU phản hồi, PIC xóa ngắt vừa tạo ra trên đường INTR.

  • CPU xử lý ngắt.

Những đường yêu cầu ngắt IRQn có thể được tạm thời vô hiệu hóa, hoặc cho phép hoạt động trở lại bằng cách điều khiển PIC, nên PIC được gọi là bộ điều khiển ngắt lập trình được. Vô hiệu hóa một đường yêu cầu ngắt IRQn không làm mất ngắt đó, mà PIC sẽ gửi lại ngắt cho CPU ngay khi cho phép đường yêu cầu ngắt hoạt động trở lại. Tính năng này thường được sử dụng bởi interrupt handler – xử lý ngắt.

Ở trên hình 1, còn một đường nữa là NMI. Đường này là Non-Maskable Interrupt, dành cho những ngắt không thể bị che được đề cập ở phần phân loại ngắt và ngoại lệ.

PIC được sử dụng với các hệ thống có một CPU, cổng ra của PIC được nối thẳng tới chân INTR của CPU. Với những hệ thống có hai hoặc nhiều CPUs, việc dùng PIC để đưa các ngắt tới mỗi CPU sao cho tận dụng được khả ngăn xử lý song song của hệ thống là việc rất phức tạp.

Trên cơ sở đó, I/O Advanced Programmable Interrupts Controller -  I/O APIC được ra đời để dùng cho những hệ thống chạy nhiều CPUs. Do sự khác biệt giữa I/O APIC và PIC nằm nhiều ở phần cứng và không liên quan nhiều tới kiến thức về ngắt nên trong chương này sẽ không đề cập sâu hơn về I/O APIC.

Exceptions

Như đã đề cập ở chương trước, ngoại lệ là một loại ngắt sinh ra bởi chính CPU trong quá trình thực thi câu lệnh. Vi xử lý 80x86 định nghĩa 20 loại exceptions khác nhau. Bảng dưới đây chỉ liệt kê một vài exceptions chúng ta thường gặp trong quá trình lập trình cùng hàm xử lý ngoại lệ và signal được tạo ra.

Số

Tên/Loại

Mô tả

Hàm xử lý

Signal tạo ra

0

Divide Error [fault]

Được tạo ra khi chương trình chia một số nguyên cho 0

divide_error[ ]

SIGFPE

3

Break

[trap]

Tạo ra bởi lệnh int3, sử dụng bởi các chương trình tìm lỗi.

int3[ ]

SIGTRAP

14

Page Fault

[fault]

Địa chỉ page không tồn tại trong bộ nhớ, Page Entry tương ứng bằng NULL hoặc đang có sự vi phạm cơ chế bảo vệ page.

page_fault[ ]

SIGSEGV

16

Floating-point error

[fault]

Bộ số thực dấu phảy động tạo ra exception khi xảy ra lỗi ví dụ như chia cho 0 hoặc tràn số.

coprocessor_error[ ]

SIGFPE

18

Machine check

[abort]

Cơ chế kiểm tra phát hiện ra CPU hoặc đường bus bị lỗi sẽ tạo ra exception này

machine_check[]

Không tạo ra signal

Bảng 1 Exceptions trong vi xử lý 80x86

Ví dụ như loại ngoại lệ 0 “Divide Error” trong bảng 1. Khi chương trình thực hiện chia một số cho số 0 [trong chương trình có đoạn mã nguồn 3/0 chẳng hạn], CPU khi thực thi đoạn mã nguồn đó sẽ tạo ra ngoại lệ “Divide Error”, gọi tới hàm xử lý ngoại lệ tương ứng divide_error[ ]. Hàm xử lý ngoại lệ này gửi signal SIGFPE tới tiến trình gây ra lỗi.

Kết luận

Bài viết này đã trình bày các khái niệm, cách phân loại ngắt và ngoại lệ trong Linux. Trong bài tiếp theo, chúng ta sẽ tiếp tục tìm hiểu cụ thể cách CPU xử lý ngắt và ngoại lệ.

Chủ Đề