25/05/2018, 00:34

Các đối tượng Kernel Dispatcher (Kernel Dispatcher Objects )

Kernel cung cấp năm kiểu của những đối tượng đồng bộ hóa mà bạn có thể sử dụng để kiểm soát luồng của những luồng không chuyên quyền. Nhìn thấy Bảng 4-1 cho một tóm lược của những kiểu đối tượng thu phát nhân này và những sự sử dụng của họ. Vào một vài ...

Kernel cung cấp năm kiểu của những đối tượng đồng bộ hóa mà bạn có thể sử dụng để kiểm soát luồng của những luồng không chuyên quyền. Nhìn thấy Bảng 4-1 cho một tóm lược của những kiểu đối tượng thu phát nhân này và những sự sử dụng của họ. Vào một vài khoảnh khắc, một trong số đối tượng này trong một của hai trạng thái: được báo hiệu hay không phải - báo hiệu. Tại những lần khi nó thừa nhận được cho bạn để ngăn chặn một luồng trong văn cảnh của bạn đang chạy, bạn có thể đợi một hoặc nhiều đối tượng để đạt đến báo hiệu trạng thái bằng việc gọi là KeWaitForSingleObject hay KeWaitForMultipleObjects. Kernel cũng cung cấp những thủ tục cho kKhởi tạo và kiểm soát tình trạng của một trong các đối trượng này.

Bảng 4-1. Những đối tượng thu phát Kernel
Đối tượng Kiểu dữ liệu Mô tả
Event KEVENT Ngăn chặn một luồng cho đến khi luồng khác nào đó phát hiện một sự kiện đã xuất hiện
Semaphore KSEMAPHORE Được sử dụng thay vì một sự kiện khi những một sự gọi số lượng sự chờ đợi chuyên quyền có thể được thỏa mãn
Mutex KMUTEX Loại trừ những luồng khác từ việc thực hiện một mục đặc biệt của mã
Timer KTIMER Thực hiện những trì hoãn của một luồng trong một thời gian đã cho
Thread KTHREAD Ngăn chặn một luồng cho đến khi luồng khác hoàn thành

Trong vài mục tiếp theo, Tôi sẽ mô tả làm sao để sử dụng những đối tượng thu phát kernel. Tôi sẽ bắt đầu bằng việc giải thích khi bạn có thể ngăn chặn một luồng bằng việc gọi một của những nguyên thủy chờ đợi, và sau đó Tôi sẽ bàn luận những thủ tục hỗ trợ mà bạn sử dụng với toàn bộ các kiểu đối tượng. Tôi sẽ kết thúc mục này bằng việc bàn luận những khái niệm liên quan của những sự báo động luồng và sự giao hàng gọi thủ tục không đồng bộ.

Để hiểu khi nào và như thế nào nó thừa nhận được một trình điều khiển WDM để ngăn chặn một luồng trên một đối tượng thu phát kernel. Nói chung, dù luồng nào đang thực hiện lúc đó của một phần mềm hay ngắt phần cứng tiếp tục là luồng hiện thời trong khi những xử lý kernel ngắt. Chúng tôi nói của việc thực hiện mã kernel-mode trong văn cảnh của luồng hiện thời này. Trong sự đáp lại tới những ngắt của những cách thức khác nhau, bộ lập lịch biểu có lẽ đã quyết định chuyển những luồng, tất nhiên, tại trường hợp nào một luồng mới trở nên " hiện hành."

Thông thường, chỉ một trình điều khiển mức- cao nhất có thể biết cho chắc chắn điều đó nó là sự vận dụng trong một văn cảnh luồng không chuyên quyền. Giả sử cho phép bạn là một sự liên lạc thường lệ trong một trình điều khiển mức- thấp hơn, và bạn ngạc nhiên được hay không bạn được gọi là trong một luồng chuyên quyền. Nếu trình điều khiển mức- cao nhất trực tiếp chỉ gửi bạn một IRP từ sự liên lạc thường lệ của nó, bạn đã làm trong bản gốc, không chuyên quyền, luồng. Nhưng giả thiết rằng trình điều khiển được có mang một IRP vào một hàng đợi và sau đó trả lại tới ứng dụng. Trình điều khiển đó đã loại bỏ IRP từ hàng đợi trong một luồng chuyên quyền và sau đó được gửi nó hay IRP khác đến bạn. Trừ phi bạn biết điều đó là không xảy ra, bạn cần phải giả thiết bạn trong một luồng chuyên quyền nếu bạn không phải là trình điều khiển mức- cao nhất.

Mặc dù điều mà Tôi đã nói, trong nhiều hoàn cảnh bạn có thể chắc chắn của văn cảnh luồng. Các thủ tục DriverEntry và AddDevice của các bạn được gọi là trong một luồng hệ thống mà bạn có thể ngăn chặn nếu bạn cần tới. Bạn chiến không thắng thường cần tới khối rõ ràng bên trong những thủ tục này, trừ phi bạn đã có thể nếu bạn muốn tới. Bạn cũng nhận được những yêu cầu IRP_MJ_PNP trong một luồng hệ thống. Trong nhiều trường hợp, bạn phải ngăn chặn luồng đó để xử lý chính xác yêu cầu. Cuối cùng, bạn sẽ đôi khi nhận những yêu cầu vào/ra trực tiếp từ một ứng dụng, trong trường hợp nào bạn sẽ biết bạn trong một luồng thuộc về ứng dụng.

Đợi trên một Đối tượng Thu phát Đơn

Bạn gọi là KeWaitForSingleObject như được minh họa trong ví dụ sau đây:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

LARGE_INTEGER timeout;

NTSTATUS status = KeWaitForSingleObject(object, WaitReason,

  WaitMode, Alertable, &timeout);

Như được gợi ý bởi ASSERT, bạn phải là đang thực hiện tại hoặc ở dưới DISPATCH_LEVEL thậm chí gọi là thủ tục dịch vụ này.

Trong sự gọi này, đối tượng trỏ vào đối tượng bạn muốn đợi trên. Mặc dù đối số này được đánh máy như một PVOID, nó cần phải là một con trỏ tới một trong những đối tượng thu phát được liệt kê trong Bảng 4-1. Đối tượng phải ở trong được đánh số trang kí ức-chẳng hạn, trong một cấu trúc mở rộng thiết bị hay vùng dữ liệu khác được cấp phát từ đánh số trang pool. Cho đa số những mục đích, ngăn xếp thực hiện có thể được xem xét được đánh số trang.

Đợi trên nhiều đối tượng Thu phát(Waiting on Multiple Dispatcher Objects )

KeWaitForMultipleObjects là một sổ tay chức năng tới KeWaitForSingleObjectthat ở đó bạn sử dụng khi bạn muốn đợi cho một hay tất cả vài đối tượng thu phát đồng thời. Gọi là chức năng này như trong ví dụ này:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

LARGE_INTEGER timeout;

NTSTATUS status = KeWaitForMultipleObjects(count, objects, 

  WaitType, WaitReason, WaitMode, Alertable, &timeout, waitblocks);

Ở đây những đối tượng là địa chỉ của một mảng của những con trỏ tới những đối tượng thu phát, và sự đếm là số của những con trỏ trong mảng. Sự đếm phải là ít hơn như chuẩn y tới giá trị MAXIMUM_WAIT_OBJECTS, mà hiện thời cân bằng với 64. Mảng, cũng như toàn bộ những đối tượng tới những phần tử nào (của) mảng chỉ, phải là trong đánh số trang kí ức. WaitType là một trong những giá trị liệt kê WaitAll hay WaitAny và chỉ rõ liệu có phải bạn muốn đợi cho đến tất cả các đối tượng đồng thời báo hiệu trạng thái hay liệu có phải, thay vào đó, bạn muốn đợi cho đến khi bất kỳ những đối tượng nào được báo hiệu.

Đối số waitblocks trỏ vào một mảng của các cấu trúc KWAIT_BLOCK mà kernel sẽ sử dụng để điều hành thao tác chờ đợi. Bạn không cần khởi tạo những cấu trúc này trong bất kỳ cách nào- kernel chỉ cần biết kho ở đâu cho nhóm của những khối chờ đợi mà nó sẽ thường ghi tình trạng của toàn bộ những đối tượng trong thời gian chờ đợi. Nếu bạn đợi một số nhỏ của những đối tượng (đặc biệt, một số không có lớn hơn THREAD_WAIT_OBJECTS, mà hiện thời cân bằng với 3), bạn có thể cung cấp NULL cho tham số này. Nếu bạn cung cấp NULL, KeWaitForMultipleObjects sử dụng một mảng preallocated của khối chờ đợi mà tồn tại trong đối tượng luồng. Nếu bạn đợi nhiều những đối tượng hơn điều này, bạn phải cung cấp đánh số trang kí ức điều đó là ít nhất count * sizeof(KWAIT_BLOCK) những byte trong độ dài.

Những lý lẽ còn lại tới KeWaitForMultipleObjects là giống như tương ứng những lý lẽ tới KeWaitForSingleObject, và đa số trở lại những mã có cùng ý nghĩa.

Nếu bạn chỉ rõ WaitAll, giá trị STATUS_SUCCESS trở lại được chỉ báo rằng tất cả các đối tượng quản lý để đạt đến báo hiệu trạng thái đồng thời. Nếu bạn chỉ rõ WaitAny, giá trị trở lại bằng số thì bằng chỉ số mảng những đối tượng của đối tượng đơn mà thỏa mãn sự chờ đợi. Nếu hơn một trong những đối tượng tình cờ được báo hiệu, bạn sẽ được nói về một trong số chúng— có thể lowest- numbered của tất cả thứ mà được báo hiệu vào khoảnh khắc đó, trừ phi có thể một vài cách khác. Bạn có thể nghĩ về phép cộng giá trị STATUS_WAIT_0 này cộng với chỉ số mảng. Bạn không có thể đơn giản thực hiện sự thử NT_SUCCESS thông thường của trạng thái trở về trước khi việc rút chỉ số mảng từ mã tình trạng, tuy nhiên, bởi vì các mã trả về khả dĩ khác (bao gồm STATUS_TIMEOUT, STATUS_ALERTED, và STATUS_USER_APC) cũng đi qua sự thử. Mã sử dụng thích điều này:

NTSTATUS status = KeWaitForMultipleObjects(...);

if ((ULONG) status < count)

  {

  ULONG iSignaled = (ULONG) status - (ULONG) STATUS_WAIT_0;

  }

Khi KeWaitForMultipleObjects trả lại một mã trạng thái bằng một đối tượng chỉ số mảng trong một trường hợp WaitAny, nó cũng thực hiện những thao tác được yêu cầu bởi đối tượng đó. Nếu nhiều đối tượng được báo hiệu và bạn chỉ rõ WaitAny, những thao tác chỉ được thực hiện cho một đối tượng, điều đó cho rằng để thỏa mãn sự chờ đợi và chỉ số được trả về. Đối tượng là không nhất thiết đầu tiên một trong mảng của các bạn mà tình cờ được báo hiệu.

Những sự kiện Nhân Kernel

Bạn sử dụng những chức năng dịch vụ được liệt kê trong Bảng 4-2 để làm việc với những đối tượng sự kiện kernel. Để khởi tạo một đối tượng sự kiện, dự trữ đầu tiên nonpaged cho một đối tượng của kiểu KEVENT và sau đó gọi là KeInitializeEvent:

ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);

KeInitializeEvent(event, EventType, initialstate);

Sự kiện là địa chỉ của đối tượng sự kiện. EventType là một trong những giá trị liệt kê NotificationEvent và SynchronizationEvent. Một sự kiện thông báo có đặc điểm đó, khi đó là sự thiết lập tới trạng thái báo hiệu, nó ở lại báo hiệu cho đến khi nó là rõ ràng lập lại tới trạng thái không báo hiệu. Hơn nữa, tất cả các luồng mà đợi trên một sự kiện thông báo được giải phóng khi sự kiện được báo hiệu. Cái này cũng như một sự kiện được đặt lại bởi tài liệu trong kiểu người sử dụng. Một sự kiện đồng bộ hóa, mặt khác, được đặt lại tới trạng thái không - báo hiệu ngay khi một luồng đơn được giải phóng. Đây là cái gì xảy ra trong kiểu người sử dụng khi người nào đó gọi là SetEvent trên một đối tượng sự kiện tự khởi động lại. Thao tác duy nhất được thực hiện trên một đối tượng sự kiện bởi KeWaitXxx sẽ đặt lại một sự kiện đồng bộ hóa tới không phải - báo hiệu. Cuối cùng, initialstate TRUE chỉ rõ rằng trạng thái ban đầu của sự kiện sẽ được báo hiệu và FALSE chỉ rõ trạng thái ban đầu đó không phải - báo hiệu.

Bảng 4-2. Các chức năng dịch vụ cho sự sử dụng với những đối tượng sự kiện Kernel
Chức năng dịch vụ Mô tả
KeClearEvent Sự kiện những thiết lập không-báo hiệu; không báo cáo trạng thái liền trước
KeInitializeEvent Khởi tạo đối tượng sự kiện
KeReadStateEvent Xác định trạng thái hiện tại của sự kiện (Windows XP và chỉ Windows 2000)
KeResetEvent Sự kiện những thiết lập không-báo hiệu; trạng thái liền trước trả lại
KeSetEvent Sự kiện những thiết lập được báo hiệu; trạng thái liền trước trả lại

CHÚ Ý:

Trong dãy này của những mục trên những nguyên thủy đồng bộ hóa, tôi lặp lại những sự hạn chế IRQL mà tài liệu DDK đó mô tả. Trong phiên bản hiện thời của Microsoft Windows XP, DDK đôi khi hạn chế hơn hệ điều hành thật sự. Chẳng hạn, KeClearEvent có thể được gọi là tại bất kỳ IRQL nào, không phải chỉ tại hoặc ở dưới DISPATCH_LEVEL. KeInitializeEvent có thể được gọi ở bất kỳ IRQL nào, không phải chỉ ở PASSIVE_LEVEL. Tuy nhiên, bạn cần phải lưu tâm tới những sự phát biểu trong DDK như tương đương tới việc nói mà Microsoft có lẽ đã một ngày nào đó áp đặt lấy sự hạn chế tài liệu, nào phải không có những cố gắng để báo cáo trạng thái thật sự của những vấn đề.

Bạn có thể gọi KeSetEvent để đặt một sự kiện trạng thái báo hiệu:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

LONG wassignaled = KeSetEvent(event, boost, wait);

Như bao hàm bởi ASSERT, bạn phải đang là chạy tại hoặc ở dưới DISPATCH_LEVEL để gọi chức năng này. Đối số sự kiện là một con trỏ tới đối tượng sự kiện trong câu hỏi, và sự tăng là một giá trị để được thêm vào một luồng đợi quyền ưu tiên nếu việc đặt sự kiện dẫn tới thỏa mãn người nào đó đợi. Nhìn thấy bên cạnh (" mà Đối số thứ ba làm phiền tới KeSetEvent ") cho một giải thích của đại số Boolean đợi đối số, một trình điều khiển WDM nào mà hầu như chưa bao giờ chỉ định TRUE. Giá trị trở lại khác không nếu sự kiện đã báo hiệu trạng thái trước khi gọi và 0 nếu sự kiện là không không - Báo hiệu trạng thái.

Một bộ lập lịch biểu đa nhiệm cần thúc đẩy giả tạo quyền ưu tiên của một luồng mà đợi những thao tác vào/ra hay những đối tượng đồng bộ hóa để tránh làm chết đói những luồng mà tiêu phí nhiều thời gian đợi. Đây là bởi vì một luồng mà những khối cho lý do nào đó nói chung từ bỏ thời gian ít ỏi của nó và không thắng trở lại CPU cho đến khi nó có một quyền ưu tiên một cách tương đối cao hơn so với luồng xứng đáng khác hay luồng khác mà có cùng quyền ưu tiên kết thúc những thời gian ít ỏi của chúng. Một luồng những khối không bao giờ là điều đó, tuy nhiên, hiểu được để hoàn thành những lát thời gian của nó. Trừ phi một sự tăng được áp dụng tới luồng mà các khối lặp đi lặp lại nhiều lần, bởi vậy, nó sẽ tiêu phí nhiều thời gian đợi những luồng CPU- bound để kết thúc thời gian ít ỏi của chúng.

Bạn và tôi luôn luôn không chiến thắng có một ý tưởng tốt của giá trị nào để sử dụng cho một sự tăng quyền ưu tiên. Một kinh nghiệm tốt để đi theo sau sẽ chỉ rõ IO_NO_INCREMENT trừ phi bạn có một lý do tốt không phải tới. Nếu việc đặt sự kiện sẽ thức giấc trên một luồng điều đó quan hệ với một chảy tràn dữ liệu time-sensitive (như một trình điều khiển âm thanh), cung cấp sự tăng điều đó thích hợp tới hồ như thiết bị đó ( như IO_SOUND_INCREMENT). Thứ quan trọng sẽ không phải thúc đẩy người trông đợi cho một lý do ngu ngốc. Chẳng hạn, nếu bạn thử xử lý một yêu cầu đồng bộ IRP_MJ_PNP —xem Chương 6— bạn sẽ đang đợi những trình điều khiển lower-level để xử lý IRP trước khi bạn theo đuổi, và sự thủ tục hoàn thành của các bạn sẽ gọi là KeSetEvent. Từ những yêu cầu Plug and Play không có tuyên bố đặc biệt trên bộ xử lý và chỉ xuất hiện hiếm khi xảy ra, chỉ rõ IO_NO_INCREMENT, thậm chí cho một card âm thanh.

Sử dụng một Sự kiện Đồng bộ hóa cho sự loại trừ lẫn nhau (Using a Synchronization Event for Mutual Exclusion)

Tôi sẽ nói với những bạn sau đó trong chương này về hai kiểu đối tượng loại trừ lẫn nhau— một kernel mutex và một mutex nhanh thực hiện — bạn có thể sử dụng tới giới hạn sự truy nhập để chia sẻ dữ liệu trong những hoàn cảnh trong đó một khóa quay tròn không thích hợp cho lý do nào đó. Đôi khi bạn có thể đơn giản sử dụng một sự kiện đồng bộ hóa cho mục đích này. Đầu tiên định nghĩa sự kiện đánh số trang kí ức, như sau:

typedef struct _DEVICE_EXTENSION {

  KEVENT lock;

  } DEVICE_EXTENSION, *PDEVICE_EXTENSION;

Initialize it as a synchronization event in the signaled state:

KeInitializeEvent(&pdx->lock, SynchronizationEvent, TRUE);

Vào tiết diện tới hạn lightweight của các bạn bằng việc đợi trên sự kiện. Để lại bởi sự thiết đặt sự kiện.

KeWaitForSingleObject(&pdx->lock, Executive, KernelMode,

  FALSE, NULL);

KeSetEvent(&pdx->lock, EVENT_INCREMENT, FALSE);

Chỉ sử dụng mánh khóe này trong một luồng hệ thống, Tuy nhiên, cản trở một sự gọi user-mode tới NtSuspendThread tạo ra một sự bế tắc. (Sự bế tắc này có thể dễ dàng xảy ra nếu một trình gỡ rối user-mode đang chạy trên cùng quá trình.) Nếu bạn chạy trong một luồng người sử dụng, bạn cần phải thích sử dụng một mutex nhanh thực hiện. Không sử dụng mánh khóe này chút nào cho mã, điều đó thực hiện trong đường dẫn phân trang, như cách được giải thích sau đó liên quan đến " không an toàn " của việc thu nhận một thực hiện nhanh mutex.

Kernel đánh tín hiệu (Kernel Semaphores )

Một đèn hiệu kernel là một máy đếm số nguyên với ngữ nghĩa học đồng bộ hóa có kết hợp. Xem xét đèn báo hiệu khi máy đếm khẳng định và không phải - báo hiệu khi máy đếm là 0. Máy đếm không thể đảm nhiệm một giá trị tiêu cực. Việc giải phóng một đèn hiệu tăng máy đếm, trong khi mà một cách thành công đợi trên một sự giảm bớt đèn hiệu máy đếm. Nếu sự giảm bớt làm cho sự đếm là 0, đèn hiệu được xem xét không phải- báo hiệu, với hệ quả những đối tượng gọi KeWaitXxx khác đó mà nài nỉ tìm thấy nó báo hiệu khối bắt buộc. Ghi chú mà nếu nhiều luồng hơn đang đợi một đèn hiệu so với giá trị của máy đếm, không phải tất cả luồng đợi sẽ được giải tỏa.

kernel cung cấp ba chức năng dịch vụ để kiểm soát trạng thái của một đối tượng đèn hiệu. (Xem bảng 4-3.) Bạn khởi tạo một đèn hiệu bằng việc gọi chức năng PASSIVE_LEVEL sau đây:

ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);

KeInitializeSemaphore(semaphore, count, limit);

Trong sự gọi này, đèn hiệu trỏ vào một đối tượng KSEMAPHORE đánh số trang kí ức. Biến đếm là giá trị ban đầu của máy đếm, và giới hạn của trị số cực đại mà máy đếm sẽ được cho phép để đảm nhiệm, mà phải lớn như sự đếm ban đầu.

Bảng 4-3. Những chức năng dịch vụ cho sự sử dụng với những đối tượng đánh tín hiệu Kernel
Service Function Mô tả
KeInitializeSemaphore Khởi tạo đối tượng đèn hiệu
KeReadStateSemaphore Xác định trạng thái hiện thời của đèn hiệu
KeReleaseSemaphore Những sự đông cứng đánh tín hiệu đối tượng tới trạng thái báo hiệu

Nếu bạn tạo ra một đèn hiệu với một giới hạn của 1, đối tượng có phần tương tự như một mutex trong điều đó duy nhất một luồng tại một thời điểm sẽ có khả năng để đòi hỏi nó. Một kernel mutex có một số đặc tính mà một sự thiếu đèn hiệu, tuy nhiên, để giúp đỡ ngăn ngừa những sự bế tắc. Tương ứng, ở đó gần như không có điểm nào trong việc tạo ra một đèn hiệu với một giới hạn của 1.

Nếu bạn tạo ra một đèn hiệu với một giới hạn lớn hơn 1, bạn có một đối tượng mà cho phép nhiều luồng truy nhập một tài nguyên đã cho. Một định lý quen thuộc trong những mệnh lệnh lý thuyết hàng đợi mà cung cấp một hàng đợi đơn cho nhiều người phục vụ rõ ràng hơn ( điều đó, dẫn đến ít sự biến đổi hơn trong những thời gian đợi) so với việc cung cấp một ngăn cách hàng đợi cho toàn bộ các máy chủ riêng. Thời gian đợi trung bình giống như trong cả hai trường hợp, nhưng sự biến đổi trong những thời gian đợi là nhỏ hơn với hàng đợi đơn. (Đây tại sao là những hàng đợi trong những kho ngày càng tăng được tổ chức sao cho những khách hàng đợi trong một dòng đơn cho thư ký sẵn có tiếp theo.) Loại đèn hiệu này cho phép bạn tổ chức một sự đông cứng của phần mềm hay phần cứng những máy chủ để tận dụng định lý đó.

Chủ nhân (hay một trong những chủ nhân) của một đèn hiệu giải phóng tuyên bố của nó tới đèn hiệu bằng việc gọi là KeReleaseSemaphore:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

LONG wassignaled = KeReleaseSemaphore(semaphore, boost,

  delta, wait);

Thao tác này thêm delta, mà phải được tích cực, tới máy đếm được liên quan đến đánh tín hiệu , do đó mang đèn hiệu vào báo hiệu trạng thái và cho phép những luồng khác sẽ được giải phóng. Trong đa số những trường hợp, bạn sẽ chỉ rõ 1 tham số này để chỉ báo rằng một người khiếu nại của đèn hiệu đang giải phóng tuyên bố của nó. Sự tăng và những tham số đợi có cùng nhập khẩu với những tham số tương ứng tới KeSetEvent, được bàn luận trước đó. Giá trị trở lại là 0 nếu trạng thái trước đây của đèn hiệu không được báo hiệu và khác không nếu trạng thái trước đây được báo hiệu.

KeReleaseSemaphore không cho phép bạn tăng máy đếm bên ngoài giới hạn được chỉ rõ khi bạn khởi tạo đèn hiệu. Nếu bạn thử, nó không điều chỉnh máy đếm chút nào, và nó nâng một ngoại lệ với mã STATUS_SEMAPHORE_LIMIT_EXCEEDED. Trừ phi người nào đó có một chương trình xử lý đặc biệt có cấu trúc để bẫy ngoại lệ, một kiểm tra lỗi sẽ dẫn đến kết quả.

Bạn có thể cũng thẩm vấn trạng thái hiện thời của một đèn hiệu với điều gọi này:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

LONG signaled = KeReadStateSemaphore(semaphore);

Giá trị trở lại khác không nếu đèn hiệu được báo hiệu và 0 nếu đèn hiệu không báo hiệu. Bạn không nên giả định rằng giá trị trả lại là hiện nay giá trị của máy đếm— nó đã có thể là bất kỳ giá trị khác không nào nếu máy đếm xác thực.

Có được nói bạn tất cả điều này về sử dụng kernel như thế nào để đánh tín hiệu, Tôi cảm thấy Tôi nên nói bạn điều đó Tôi chưa bao giờ nhìn thấy một trình điều khiển mà sử dụng một trong số chúng.

Kernel Timers

Kernel cung cấp một đối tượng timer mà một số chức năng giống như một sự kiện mà tự động báo hiệu tại một thời gian tuyệt đối xác định hay sau một khoảng xác định. Nó cũng khả dĩ để tạo ra một thiết bị bấm giờ mà báo hiệu nhiều lần và thu xếp cho một DPC phản hồi đi theo sau sự hết hạn của thiết bị bấm giờ. Bảng 4-5 liệt kê những chức năng dịch vụ bạn sử dụng với các đối tượng thiết bị bấm giờ.

Bảng 4-5. Các chức năng dịch vụ cho việc sử dụng với những đối tượng Thiết bị bấm giờ Kernel
Service Function Mô tả
KeCancelTimer Hủy bỏ một thiết bị bấm giờ tích cực
KeInitializeTimer Khởi tạo một thiết bị bấm giờ thông báo trước kia
KeInitializeTimerEx Khởi tạo một thông báo hay thông báo có đặc trưng lặp đi lặp lại hay thiết bị bấm giờ đồng bộ hóa
KeReadStateTimer Xác định trạng thái hiện thời của một thiết bị bấm giờ
KeSetTimer Chỉ rõ thời gian hết hạn cho một thông báo thiết bị bấm giờ
KeSetTimerEx Chỉ rõ sự hết hạn thời gian và những thuộc tính khác của một thiết bị bấm giờ

Có vài kịch bản cách dùng khác nhau cho những thiết bị bấm giờ, tôi mô tả trong trong vài mục tiếp theo:

  • Thiết bị bấm giờ được dùng như một sự kiện tự báo hiệu
  • Thiết bị bấm giờ với một DPC thường lệ sẽ được gọi là khi một thiết bị bấm giờ hết hạn
  • Thiết bị bấm giờ tuần hoàn thường gọi trên một DPC thường lệ và trên một lần nữa

Những thiết bị bấm giờ thông báo được dùng như những sự kiện(Notification Timers Used like Events):

Trong kịch bản này, chúng tôi sẽ tạo ra một đối tượng thiết bị bấm giờ thông báo và chờ đợi cho đến khi nó hết hạn. Đầu tiên cấp phát một đối tượng KTIMER trong việc đánh số trang kí ức. Rồi, chạy tại hoặc ở dưới DISPATCH_LEVEL, khởi tạo đối tượng thiết bị bấm giờ, như được cho thấy ở đây:

PKTIMER timer;      // <== someone gives you this

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

KeInitializeTimer(timer);

Tại điểm này, thiết bị bấm giờ không - báo hiệu trạng thái và không đếm xuống-một sự chờ đợi trên thiết bị bấm giờ chưa bao giờ được thỏa mãn. Để bắt đầu sự đếm thiết bị bấm giờ, gọi là KeSetTimer như sau:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

LARGE_INTEGER duetime;

BOOLEAN wascounting = KeSetTimer(timer, duetime, NULL);

Giá trị duetime là một giá trị thời gian truyền 64- bit được biểu thị ở đơn vị 100-nanosecond. Nếu giá trị là đại lượng dương, nó là một thời gian tuyệt đối tương đối tới cùng tháng giêng 1, 1601, kỷ nguyên được dùng cho thiết bị bấm giờ hệ thống. Nếu giá trị là phủ định, nó là một khoảng tương đối đối với thời gian hiện tại. Nếu bạn chỉ rõ một thời gian tuyệt đối, một sự thay đổi kế tiếp tới đồng hồ hệ thống thay đổi khoảng thời gian của timeout bạn kinh nghiệm. Điều đó, thiết bị bấm giờ không hết hạn cho đến khi đồng hồ hệ thống cân bằng với hoặc vượt hơn dù giá trị tuyệt đối nào bạn chỉ định. Trong sự tương phản, nếu bạn chỉ rõ một dạng tương quan timeout, khoảng thời gian của timeout bạn kinh nghiệm thì không bị ảnh hưởng bởi những sự thay đổi trong đồng hồ hệ thống. Đây là giống như những quy tắc mà ứng dụng vào tham số timeout tới KeWaitXxx.

Giá trị trở lại từ KeSetTimer, nếu TRUE , chỉ báo rằng thiết bị bấm giờ đã là đếm xuống (trong trường hợp nào, sự gọi của chúng ta tới KeSetTimer đã hủy bỏ nó và bắt đầu sự đếm khắp nơi lần nữa).

Vào bất kỳ thời gian nào bạn có thể xác định trạng thái hiện thời của một thời gianr:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

BOOLEAN counting = KeReadStateTimer(timer);

KeInitializeTimer và những chức năng dịch vụ thật sự cũ hơn KeSetTimerare mà đã được bỏ bởi những chức năng mới hơn. Chúng tôi có thể đã khởi tạo thiết bị bấm giờ với sự gọi này:

ASSERT(KeGetCurrentIqrl() <= DISPATCH_LEVEL);

KeInitializeTimerEx(timer, NotificationTimer);

Chúng tôi có thể cũng đã sử dụng phiên bản mở rộng của chức năng thiết bị bấm giờ tập hợp,KeSetTimerEx:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

LARGE_INTEGER duetime;

BOOLEAN wascounting = KeSetTimerEx(timer, duetime, 0, NULL);

Tôi sẽ giải thích xa hơn nữa trong chương này mục đích của những tham số thêm trong những phiên bản mở rộng này của công tác vận hành.

Một lần thiết bị bấm giờ là sự đếm xuống, nó là sự yên tĩnh được xem xét not-signaled cho đến khi thời gian đến hạn xác định đến. Tại điểm đó, đối tượng trở nên được báo hiệu, và tất cả các luồng chờ đợi đều được giải phóng. Những bảo đảm hệ thống duy nhất sự hết hạn của thiết bị bấm giờ sẽ được chú ý không có sớm hơn thời gian đến hạn bạn chỉ định. Phải chăng bạn chỉ rõ một thời gian đến hạn với một lò tinh luyện chính xác so với độ hạt của thiết bị bấm giờ hệ thống ( Bạn không có thể điều khiển), timeout sẽ được chú ý sau đó chốc lát chính xác bạn chỉ rõ. Bạn có thể gọi KeQueryTimeIncrement để xác định độ hạt của đồng hồ hệ thống.

Những thiết bị bấm giờ Thông báo được sử dụng với một DPC (Notification Timers Used with a DPC):

Trong kịch bản này, chúng tôi muốn sự hết hạn của thiết bị bấm giờ thúc đẩy một DPC. Bạn chọn phương pháp này của thao tác nếu bạn muốn được chắc chắn rằng bạn có thể giúp timeout không vấn đề mức ưu tiên gì có được luồng của các bạn. (Từ bạn có thể chỉ đợi ở dưới DISPATCH_LEVEL, việc chiếm lại điều khiển của CPU sau thiết bị bấm giờ hết hạn tùy thuộc vào những tính bất bình thường của sự lập lịch luồng. Tuy nhiên, DPC thực hiện tại IRQL cao và do đó có hiệu quả chặn trước tất cả các luồng.)

Chúng tôi khởi tạo đối tượng thiết bị bấm giờ trong cùng cách. Chúng tôi cũng đã khởi tạo một đối tượng KDPC mà cái đó chúng tôi cấp phát đánh số trang kí ức. Chẳng hạn:

PKDPC dpc;  // <== points to KDPC you've allocated

ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);

KeInitializeTimer(timer);

KeInitializeDpc(dpc, DpcRoutine, context);

Bạn có thể khởi tạo đối tượng thiết bị bấm giờ bằng cách sử dụng hoặc KeInitializeTimer hoặc KeInitializeTimerEx , đến khi bạn vui lòng. DpcRoutine là địa chỉ của một sự gọi thủ tục được hoãn lại thường lệ, điều này bắt buộc ở đánh số trang kí ức. Tham số văn cảnh là một giá trị 32-bit tùy ý ( kiểu như một PVOID ) mà sẽ được đi qua như một đối số tới thủ tục DPC. Đối số dpc là một con trỏ tới một đối tượng KDPC mà cho cái đó bạn cung cấp được đánh số trang lưu trữ. (Nó có lẽ đã trong mở rộng thiết bị của các bạn, chẳng hạn.)

Khi chúng tôi muốn bắt đầu sự đếm thiết bị bấm giờ xuống, chúng tôi chỉ rõ đối tượng DPC như một trong những đối số tới KeSetTimer HayKeSetTimerEx:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

LARGE_INTEGER duetime;

BOOLEAN wascounting = KeSetTimer(timer, duetime, dpc);

Bạn đã có thể cũng sử dụng mẫu KeSetTimerEx mở rộng nếu bạn muốn. Sự khác nhau duy nhất giữa sự gọi này và cái chúng tôi khảo sát trong mục có trước là điều đó chúng tôi có chỉ rõ địa chỉ đối tượng DPC như một đối số. Khi thiết bị bấm giờ hết hạn, hệ thống sẽ xếp hàng DPC cho sự thực hiện sớm như cho phép những điều kiện. Điều này ít nhất sớm như là bạn có khả năng để tỉnh dậy từ một sự chờ đợi. Thủ tục DPC của các bạn có hình thức khung sau đây:

VOID DpcRoutine(PKDPC dpc, PVOID context, PVOID junk1,

  PVOID junk2)

  {

  }

Cho cái gì đáng giá của nó, thậm chí khi bạn cung cấp một đối số DPC tới KeSetTimer hay KeSetTimerEx , bạn có thể vẫn còn gọi là KeWaitXxx để đợi ở PASSIVE_LEVEL hay APC_LEVEL nếu bạn muốn. Trên một CPU hệ thống- đơn, DPC xuất hiện trước khi sự chờ đợi đã có thể kết thúc vì nó thực hiện ở một IRQL bậc cao.

Những thiết bị bấm giờ Đồng bộ hóa (Synchronization Timers):

Cũng như những đối tượng sự kiện, những đối tượng thiết bị bấm giờ gửi tới cả thông báo lẫn những flavor đồng bộ hóa. Một thiết bị bấm giờ thông báo cho phép bất kỳ số nào của việc đợi những luồng để tiến hành một khi nó hết hạn. Một thiết bị bấm giờ đồng bộ hóa, bởi sự tương phản, chỉ cho phép một luồng đơn để theo đuổi. Một lần một sự chờ đợi của luồng được thỏa mãn, những sự chuyển đổi thiết bị bấm giờ tới trạng thái not-signaled. Để tạo ra một thiết bị bấm giờ đồng bộ hóa, bạn phải sử dụng dạng mở rộng của chức năng khởi tạo dịch vụ:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

KeInitializeTimerEx(timer, SynchronizationTimer);

SynchronizationTimer là một trong những giá trị của sự liệt kê TIMER_TYPE. Giá trị khác là NotificationTimer.

Nếu bạn sử dụng một DPC với một thiết bị bấm giờ đồng bộ hóa, nghĩ rằng việc xếp hàng DPC như việc là một thứ thêm mà xảy ra khi thiết bị bấm giờ hết hạn. Nghĩa là, sự hết hạn mang thiết bị bấm giờ vào báo hiệu trạng thái và những hàng đợi một DPC. Một luồng có thể được giải phóng khi một kết quả của hiện thân thiết bị bấm giờ báo hiệu.

Sự sử dụng duy nhất tôi có bao giờ tìm thấy cho một thiết bị bấm giờ đồng bộ hóa khi bạn muốn một thiết bị bấm giờ tuần hoàn (xem ở mục tiếp theo).

Những thiết bị bấm giờ Tuần hoàn(Periodic Timers):

Cho đến lúc này, tôi có bàn luận những thiết bị bấm giờ duy nhất mà hết hạn chính xác một lần. Bằng cách sử dụng chức năng thiết bị bấm giờ tập hợp mở rộng bạn có thể cũng đòi hỏi một timeout tuần hoàn:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

LARGE_INTEGER duetime;

BOOLEAN wascounting = KeSetTimerEx(timer, duetime,

  period, dpc);

Giai đoạn ở đây là theo một chu kỳ timeout, biểu thị trong những mili-giây (ms), và dpc là một con trỏ để chọn tới một đối tượng KDPC. Một thiết bị bấm giờ của cách thức này hết hạn một lần vào thời gian đến hạn và định kỳ sau đó. Để đạt được sự hết hạn tuần hoàn chính xác, chỉ rõ cùng dạng tương quan thời gian với sự tạm ngưng hoạt động. Việc chỉ rõ một chữ số không vào thời gian gây ra thiết bị bấm giờ để ngay lập tức hết hạn, về cái đó hành vi tuần hoàn bắt qua. Nó thường có ý nghĩa để bắt đầu một thiết bị bấm giờ tuần hoàn phối hợp với một đối tượng DPC, bởi cách, bởi vì việc cho phép bạn được thông báo mà không phải nhiều lần đợi timeout.

Chắc chắn để gọi là KeCancelTimer để hủy bỏ một thiết bị bấm giờ tuần hoàn trước khi đối tượng KTIMER hay thủ tục DPC biến mất từ kí ức. Nó là hoàn toàn bối rối để cho hệ thống chuyển đi trình điều khiển của các bạn và, 10 nanô-giây sau đó, gọi thủ tục DPC không tồn tại của các bạn. Không phải chỉ điều đó, nhưng nó gây ra một sự kiểm tra lỗi. Những vấn đề này là không đổi để gỡ lỗi đến các cấu tạo Driver Verifier làm một sự kiểm tra đặc biệt cho kí ức trả lại mà chứa đựng một KTIMER tích cực.

Một Ví dụ:

Một sử dụng cho những thiết bị bấm giờ nhân được chỉ đạo một vòng kiểm tra tuần tự trong một luồng hệ thống dành cho nhiệm vụ của việc nhiều lần kiểm tra một thiết bị cho hoạt động. Không phải nhiều thiết bị ngày nay cần được phục vụ bởi một vòng kiểm tra tuần tự, nhưng của bạn có thể một của vài ngoại lệ. Tôi sẽ bàn luận đề tài này trong Chương 14, và nội dung bạn bao gồm một trình điều khiển mẫu (POLLING) mà minh họa tất cả các khái niệm liên quan. Một phần mà lấy mẫu là vòng sau đây mà thu được thiết bị tại những khoảng cố định. Lôgic của trình điều khiển là vòng đó như vậy có thể bị đứt quãng bằng việc đặt một sự kiện làm chết. Vậy thì, trình điều khiển sử dụng KeWaitForMultipleObjects. Mã thật sự phức tạp hơn đoạn sau đây, tôi đã biên tập soạn thảo tập trung vào phần liên quan đến thiết bị bấm giờ:

VOID PollingThreadRoutine(PDEVICE_EXTENSION pdx)

  {

  NTSTATUS status;

  KTIMER timer;

  KeInitializeTimerEx(&timer, SynchronizationTimer);

  PVOID pollevents[] = {

    (PVOID) &pdx->evKill,

    (PVOID) &timer,

    };

  C_ASSERT(arraysize(pollevents) <= THREAD_WAIT_OBJECTS);

  

  LARGE_INTEGER duetime = {0};

  #define POLLING_INTERVAL 500

  KeSetTimerEx(&timer, duetime, POLLING_INTERVAL, NULL);

  while (TRUE)

    {

    status = KeWaitForMultipleObjects(arraysize(pollevents),

      pollevents, WaitAny, Executive, KernelMode, FALSE,

      NULL, NULL);

    if (status == STATUS_WAIT_0)

      break;

    if (<device needs attention>)

      <do something>;

    }

  KeCancelTimer(&timer);

  PsTerminateSystemThread(STATUS_SUCCESS);

  }

  1. Ở đây chúng tôi khởi tạo một thiết bị bấm giờ kernel. Bạn phải chỉ rõ một SynchronizationTimer ở đây, bởi vì một NotificationTimer ở lại báo hiệu trạng thái sau sự hết hạn đầu tiên.
  2. Chúng tôi sẽ cần cung cấp một mảng của những con trỏ đối tượng thu phát như một trong những đối số tới KeWaitForMultipleObjects , và đây là chúng tôi thiết lập điều đó. Phần tử đầu tiên của mảng là sự kiện làm chết mà phần khác nào đó của trình điều khiển được có lẽ đã đặt khi thời gian của nó cho luồng hệ thống này để thoát ra. Phần tử thứ hai là đối tượng thiết bị bấm giờ. Sự phát biểu C_ASSERT mà đi theo sau mảng này kiểm tra rằng chúng tôi có đủ vài đối tượng trong mảng của chúng tôi mà chúng tôi có thể tuyệt đối sử dụng mảng mặc định của những khối chờ đợi trong đối tượng luồng của chúng tôi.
  3. Sự phát biểu KeSetTimerEx bắt đầu một thiết bị bấm giờ tuần hoàn đang chạy. Duetimeis là 0, vì vậy thiết bị bấm giờ đi ngay lập tức vào trong báo hiệu trạng thái. Nó sẽ hết hạn mỗi 500 ms sau đó.
  4. Trong khoảng thời gian kiểm tra tuần tự của chúng tôi kiểm soát thành vòng, chúng tôi đợi thiết bị bấm giờ hết hạn hay cho sự kiện làm chết sẽ được đặt. Nếu sự chờ đợi hoàn thành bởi vì sự kiện làm chết, chúng tôi rời bỏ vòng, dọn sạch sẽ lên trên, và ra khỏi luồng hệ thống này. Nếu sự chờ đợi hoàn thành vì thiết bị bấm giờ đã hết hạn, chúng tôi tiếp tục bước tiếp theo.
  5. Đây là trình điều khiển thiết bị của chúng tôi làm cái gì đó liên quan đến phần cứng của chúng tôi.

Những giải pháp tới những thiết bị bấm giờ Kernel ( Alternatives to Kernel Timers )

Hơn là sử dụng một đối tượng thiết bị bấm giờ kernel, bạn có thể sử dụng hai chức năng tính toán thời gian khác mà có lẽ đã thích hợp hơn. Đầu tiên của mọi thứ bạn có thể gọi KeDelayExecutionThread để đợi ở PASSIVE_LEVEL cho một khoảng đã cho. Chức năng này rõ ràng ít cồng kềnh hơn so với tạo ra, khởi tạo, đặt, và đợi một thiết bị bấm giờ bằng cách sử dụng gọi chức năng tách rời.

ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);

LARGE_INTEGER duetime;

NSTATUS status = KeDelayExecutionThread(WaitMode,

  Alertable, &duetime);

ở đây là WaitMode, sự báo động , và mã tình trạng trở về lại có cùng ý nghĩa với những tham số tương ứng tới KeWaitXxx, và duetime là cùng loại timestamp mà tôi tranh luận trước đó liên quan đến kernel những thiết bị bấm giờ. Chú ý rằng chức năng này yêu cầu một con trỏ tới một số nguyên lớn cho tham số timeout, trong khi mà những chức năng khác liên quan đến những thiết bị bấm giờ yêu cầu chính số nguyên lớn.

Nếu yêu cầu của các bạn sẽ chậm trễ trong một thời gian rất ngắn gọn (ít hơn 50 micrô-giây), bạn có thể gọi cho KeStallExecutionProcessorat bất kỳ IRQL nào:

KeStallExecutionProcessor(nMicroSeconds);

Mục đích của sự trì hoãn này sẽ cho phép thời gian phần cứng của các bạn chuẩn bị cho thao tác tiếp theo của nó trước khi chương trình của các bạn tiếp tục thực hiện. Sự trì hoãn có lẽ đã kết luận một cách đáng kể dài hơn so với bạn đòi hỏi bởi vì KeStallExecutionProcessorcan được chặn trước bởi những hoạt động mà xuất hiện tại một IRQL bậc cao so với đối tượng gọi nào đó đang sử dụng.

Những luồng Nhân (Kernel Thread):

Đôi khi là bạn sẽ tạo ra luồng kernel-mode của riêng mình-khi thiết bị của các bạn cần được bị xén ngọn định kỳ, chẳng hạn. Trong kịch bản này, bất kỳ sự chờ đợi nào được thực hiện sẽ ở trong kernel mode bởi vì luồng chạy chỉ riêng trong kernel mode.

Xử lý các yêu cầu Plug and Play (Handling Plug and Play Requests):

Tôi sẽ cho bạn thấy trong Chương 6 làm sao để xử lý những yêu cầu vào/ra để quản lý PnP gửi cách của các bạn. Vài yêu cầu như vậy yêu cầu sự dùng đồng bộ trên phần của các bạn. Nói cách khác, bạn đi qua chúng xuống ngăn xếp trình điều khiển để hạ thấp xuống những mức và đợi chúng để hoàn thành. Bạn sẽ là đợi KeWaitForSingleObjectto gọi trong kernel mode bởi vì quản lý PnP gọi bạn bên trong văn cảnh của một luồng kernel mode. Ngoài ra, nếu bạn cần thực hiện những yêu cầu phụ thuộc như bộ phận yêu cầu xử lý của một PnP-ví dụ, nói về một thiết bị USB-bạn sẽ đang đợi trong kernel mode

Xử lý những yêu cầu vào/ra khác(Handling Other I/O Request):

Khi bạn xử lý những loại khác của những yêu cầu vào/ra và bạn biết mà bạn chạy trong văn cảnh của một luồng không chuyên quyền mà phải đưa những kết quả của những cân nhắc của các bạn trước khi cách tiến hành nó có lẽ đã dễ hiểu thích hợp ngăn chặn luồng đó bằng việc gọi một nguyên thủy chờ đợi. Trong một trường hợp như vậy, bạn muốn đợi trong cùng kiểu bộ xử lý với thực thể mà gọi bạn. Hầu hết thời gian, bạn có thể đơn giản tin cậy RequestorMode trong IRP bạn hiện thời xử lý. Nếu bạn thu thập cho điều khiển bởi những cách thức khác với một IRP, bạn đã có thể gọi ExGetPreviousMode xác định kiểu bộ xử lý trước đây. Nếu bạn đi để đợi một thời gian dài, nó tốt hơn để sử dụng kết quả của những sự thử này như đối số WaitMode trong KeWaitXxxcall của bạn, và nó cũng tốt để chỉ rõ TRUE cho đối số cảnh báo.

Ghi chú:

Hàng cuối: Thực hiện những sự chờ đợi không nhanh nhẹn trừ phi bạn biết bạn không quyết tâm.

0