25/05/2018, 08:56

Mức yêu cầu Ngắt (Interrupt Request Level )

Windows XP gán một mức yêu cầu ngắt tới mỗi ngắt phần cứng và tới một sự chọn lựa vài sự kiện phần mềm. Mỗi CPU có IRQL của mình. Chúng tôi gắn nhãn những mức IRQL khác nhau với những tên như PASSIVE_LEVEL, APC_LEVEL,…. Hình 4-1 minh họa phạm vi của ...

Windows XP gán một mức yêu cầu ngắt tới mỗi ngắt phần cứng và tới một sự chọn lựa vài sự kiện phần mềm. Mỗi CPU có IRQL của mình. Chúng tôi gắn nhãn những mức IRQL khác nhau với những tên như PASSIVE_LEVEL, APC_LEVEL,…. Hình 4-1 minh họa phạm vi của những giá trị IRQL cho nền tảng x86. (Nói chung, những giá trị số của IRQL phụ thuộc vào nền tảng nào bạn nói về nó). Hầu hết thời gian, máy tính thực hiện trong kiểu người sử dụng ở PASSIVE_LEVEL. Tất cả kiến thức của các bạn về công việc những hệ điều hành đa nhiệm áp dụng ở PASSIVE_LEVEL như thế nào. Điều đó, bộ lập lịch biểu có thể chặn trước một luồng ở chỗ cuối của một phần chia thời gian hay bởi vì một luồng quyền ưu tiên bậc cao đã trở nên xứng đáng để chạy. Những luồng cũng có thể là khối tự ý trong khi họ đợi những sự kiện để xuất hiện.

Một hoạt động trên một CPU đã cho chỉ có thể được ngắt bởi một hoạt động mà thực hiện ở một IRQL bậc cao.

Bạn phải đọc quy tắc này như cách máy tính đã làm. Sự hết hạn của một thời gian ngắn dần dần kéo theo bộ lập lịch biểu luồng ở DISPATCH_LEVEL. Bộ lập lịch biểu có thể làm một luồng khác nhau hiện thời. Khi IRQL trở lại PASSIVE_LEVEL, một luồng khác nhau được chạy. Trừ phi nó vẫn còn đúng mà đầu tiên PASSIVE_LEVEL hoạt động là không ngắt quãng bởi hoạt động PASSIVE_LEVEL thứ hai. Tôi nghĩ rằng sự giải thích này là sự bắt bẻ tỉ mỉ không thể tin được cho đến khi nó được chỉ ra tới tôi mà sự sắp đặt này cho phép một luồng chạy tại APC_LEVEL sẽ được chặn trước bởi một luồng khác nhau chạy ở PASSIVE_LEVEL. Có lẽ một sự phát biểu hữu ích hơn của quy tắc này là:

Một hoạt động trên một CPU đã cho có thể được chỉ ngắt bởi một hoạt động điều đó thực hiện ở một IRQL bậc cao. Tại một hoạtt động hoặc ở trên DISPATCH_LEVEL không thể treo lơ lửng để thực hiện hoạt động khác tại hoặc ở dưới IRQL then-current.

Từ mỗi CPU có IRQL của mình, nó là khả dĩ cho bất kỳ CPU nào trong một máy tính bộ đa xử lý để chạy ở một IRQL điều đó là ít hơn hoặc chuẩn y tới IRQL của mọi CPU khác. Trong mục chính tiếp theo, Tôi sẽ nói với bạn về những sự khóa xoay vòng, mà kết hợp bên trong - một - CPU đồng bộ hóa hành vi của một IRQL với một cơ chế đóng cửa bộ đa xử lý. Tuy nhiên cho thời gian bắt đầu, tôi là câu chuyện gần như gì tình cờ gặp một CPU đơn.

CHÚ Ý:

Thủ tục liên lạc và không may DISPATCH_LEVEL có những tên tương tự. Những thủ tục liên lạc được gọi là bởi vì quản lý vào/ra gửi đi những yêu cầu vào/ra tới chúng. DISPATCH_LEVEL vì thế được gọi là vì nó là IRQL tại kernel thu phát luồng trước đấy chạy khi quyết định chạy luồng nào kế tiếp. (Thu phát luồng chạy ở SYNCH_LEVEL, nếu bạn quan tâm. Đây là cùng như DISPATCH_LEVEL trên một máy bộ xử lý đơn, nếu bạn thật sự quan tâm.)

Giữa DISPATCH_LEVEL và PROFILE_LEVEL là phòng cho những mức ngắt phần cứng khác nhau. Nói chung, mỗi thiết bị mà phát sinh những ngắt có một IRQL mà định nghĩa thứ tự ưu tiên ngắt vis- as – vis của nó đối diện những thiết bị khác. Một driver WDM khám phá IRQL ch) ngắt của nó khi nó nhận được một yêu cầu IRP_MJ_PNP với mã IRP_MN_START_DEVICE chức năng phụ. Mức ngắt của thiết bị là một trong số nhiều tiết mục của thông tin cấu hình được đi qua như một tham số tới yêu cầu này. Chúng tôi thường tham chiếu tới mức này như thiết bị IRQL, hay DIRQL cho ngắn. DIRQL không là một mức yêu cầu đơn. Khá hơn, nó là IRQL cho ngắt có liên hệ với thiết bị nào dưới thảo luận lúc đó.

Tổng kết, những driver được quan tâm với ba yêu cầu ngắt những mức bình thường:

  • PASSIVE_LEVEL, tại nhiều sự liên lạc nào mà những thủ tục và một vài thủ tục đặc biệt thực hiện
  • DISPATCH_LEVEL, tại StartIo và DPC nào mà những thủ tục thực hiện
  • DIRQL, tại đó một ngắt dịch vụ thủ tục thực hiện

IRQL trong Thao tác

Để minh họa sự quan trọng của IRQL, tham chiếu tới Hình 4-2, mà minh họa một trình tự thời gian hợp lý của những sự kiện trên một CPU đơn. Tại sự bắt đầu của chuỗi, CPU thực hiện ở PASSIVE_LEVEL. Ở thời gian t1, một ngắt đến thủ tục dịch vụ thực hiện ở IRQL-1, một trong những mức giữa DISPATCH_LEVEL và PROFILE_LEVEL. Rồi, Ở thời gian t2, ngắt khác đến thủ tục dịch vụ thực hiện ở IRQL-2, mà là ít hơn IRQL-1. Vì quy tắc đã được bàn luận, CPU tiếp tục dịch vụ ngắt đầu tiên. Khi đầu tiên ngắt thủ tục dịch vụ hoàn thành ở thời gian t3, nó có lẽ đã đòi hỏi một DPC. Những thủ tục DPC thực hiện ở DISPATCH_LEVEL. Vậy thì, quyền ưu tiên cao nhất đang xem xét hoạt động là thủ tục dịch vụ cho ngắt thứ 2, mà bởi vậy thực hiện tiếp theo. Khi nó kết thúc ở t4, giả định không có gì khác đã xuất hiện trong khi chờ đợi, DPC sẽ chạy ở DISPATCH_LEVEL. Khi những kết thúc thường lệ DPC ở t5, IRQL có thể bỏ lại PASSIVE_LEVEL.

Hình 4-2. Ưu tiên ngắt trong hành động .

IRQL so sánh với những quyền ưu tiên luồng

Quyền ưu tiên luồng là một khái niệm rất khác nhau từ IRQL. Quyền ưu tiên Luồng kiểm soát những hoạt động của bộ lập lịch biểu trong việc quyết định khi để chặn trước chạy những luồng và cái gì xâu thành chuỗi bắt đầu chạy bước tiếp theo. " Quyền ưu tiên " duy nhất mà có nghĩa rằng bất cứ cái gì ở IRQLs ở trên APC_LEVEL là chính IRQL, và nó kiểm soát những chương trình nào có thể thực hiện hơn là văn cảnh luồng mà bên trong cái đó chúng thực hiện.

IRQL và sự Phân trang

Một hệ quả của việc chạy tại IRQL cao là hệ thống đó được trở nên không có khả năng của việc dịch vụ những lỗi trang. Quy tắc này hàm ý việc là đơn giản được phát biểu:

Mã thực hiện tại hoặc trên DISPATCH_LEVEL không phải là trang nguyên nhân các lỗi.

Một sự liên quan của quy tắc này là bất kỳ thủ tục con trong trình điều khiển của các bạn mà điều đó thực hiện tại hoặc hoặc ở trên các DISPATCH_LEVEL phải có bộ nhớ trong nonpaged. Hơn nữa, tất cả dữ liệu bạn truy nhập trong một thủ tục con như vậy cũng phải có bộ nhớ trong nonpaged. Cuối cùng, trong khi IRQL tăng lên, những thủ tục hỗ trợ kernel-mode ít hơn và ít hơn sẵn sàng cho sự sử dụng của các bạn.

Tài liệu DDK rõ ràng phát biểu những sự hạn chế IRQL trên những thủ tục hỗ trợ. Chẳng hạn, mục vào cho KeWaitForSingleObject chỉ báo hai sự hạn chế:

  • Đối trượng gọi phải được chạy tại hoặc ở dưới DISPATCH_LEVEL.
  • Nếu gọi nonzero một thời gian chờ là khoảng thời gian xác định trong cuộc gọi, đối tượng gọi phải được chạy chính xác ở dưới DISPATCH_LEVEL.

Kiểm soát hoàn toàn IRQL(Implicitly Controlling IRQL)

Hầu hết thời gian, hệ thống gọi những thủ tục trong trình điều khiển của các bạn tại IRQL đúng cho những hoạt động bạn giả định để thực hiện. Mặc dù tôi không bàn luận chi tiết nhiều những thủ tục này, tôi muốn đưa cho bạn một ví dụ của điều mà tôi thấy có nghĩa. Bắt gặp đầu tiên của các bạn với một yêu cầu vào/ra mới xuất hiện khi quản lý vào/ra gọi là một trong số những thủ tục liên lạc của các bạn để xử lý một IRP. Sự gọi thông thường xuất hiện tại PASSIVE_LEVEL bởi vì bạn có lẽ đã cần đển việc ngăn chặn luồng gọi hướng tới và bạn có lẽ đã cần để gọi là bất kỳ thủ tục hỗ trợ nào. Bạn có thể không ngăn chặn một luồng ở một IRQL bậc cao, tất nhiên, và PASSIVE_LEVEL là mức mà có những sự hạn chế ít nhất trên những thủ tục hỗ trợ bạn có thể gọi.

Mặc dù nó là khả dĩ cho bạn để rõ ràng kiểm soát IRQL (và tôi sẽ giải thích như thế nào trong mục tiếp theo), hiếm khi có bất cứ lý do gì để làm như thế, vì trong thư giữa các nhu cầu của bạn và mức mà các hệ thống các cuộc gọi bạn. Vậy thì, bạn không cần phải nhận được treo lên trên IRQL mà bạn đang thi hành tại thời điểm từ khoảng thời gian rất ngắn để: điều đó gần như chắc chắn mức độ chính xác cho công việc bạn đang vụ phải làm ngay sau đó.

Kiểm soát rõ ràng IRQL (Explicitly Controlling IRQL )

Khi cần thiết, bạn có thể tăng và sau này hạ thấp xuống IRQL trên bộ xử lý hiện thời bằng việc gọi là KeRaiseIrql và KeLowerIrql. Chẳng hạn, từ việc bên trong một thủ tục chạy ở PASSIVE_LEVEL:

KIRQL oldirql;

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

KeRaiseIrql(DISPATCH_LEVEL, &oldirql);

KeLowerIrql(oldirql);

  1. KIRQL là tên typedef cho một số nguyên mà giữ một giá trị IRQL. Chúng tôi sẽ cần một biến để giữ IRQL hiện thời, vì thế chúng tôi khai báo nó cách này.
  2. Điều này thể hiện khẳng định một điều kiện cần thiết để gọi KeRaiseIrql: IRQL mới phải lớn hơn hoặc bằng mức hiện tại. Nếu quan hệ là không đúng, KeRaiseIrql sẽ bugcheck (điều đó, báo cáo một lỗi thông qua một màn hình màu xanh của sự chết).
  3. KeRaiseIrql nâng IRQL hiện thời tới mức được chỉ rõ bởi đối số đầu tiên. Nó cũng cất giữ IRQL hiện thời tại vị trí được trỏ vào bởi đối số thứ hai. Trong ví dụ này, chúng tôi là nâng IRQL tới DISPATCH_LEVEL và cất giữ mức hiện thời ở oldirql.
  4. Sau khi thực hiện dù mã nào chúng tôi ước muốn thực hiện tại IRQL cao, chúng tôi hạ thấp xuống mức yêu cầu quay trở lại giá trị trước đây của nó bằng việc gọi là KeLowerIrql và chỉ rõ giá trị oldirql trước đó được trả lại bởi KeRaiseIrql.

Sau khi nâng IRQL, bạn cần phải dần dần khôi phục nó tới giá trị ban đầu. Cách khác, những sự giả thiết khác nhau làm bởi mã bạn gọi sau đó hoặc bằng cách gọi là mã mà bạn có thể bật ra để sau này được không chính xác. Tài liệu DDK nói rằng bạn phải luôn luôn gọi cho KeLowerIrqlwith cùng giá trị như mà trở lại bởi việc gọi ngay lập tức KeRaiseIrql, trừ phi thông tin này không thật sự chính xác. Quy tắc duy nhất mà KeLowerIrql thật sự áp dụng là IRQL mới phải là ít hơn hoặc bằng với một IRQL hiện hành. Bạn có thể hạ thấp xuống IRQL trong những bước nếu bạn muốn.

0