25/05/2018, 08:22

Khóa xoay vòng (Spin Locks )

Để giúp đỡ bạn đồng bộ hóa truy nhập tới dữ liệu dùng chung trong thế giới đa xử lý cân đối của Windows XP, kernel để cho bạn định nghĩa bất kỳ số của các đối tượng khóa xoay vòng nào. Để thu nhận một khóa xoay vòng, mã trên một CPU thực hiện một trong ...

Để giúp đỡ bạn đồng bộ hóa truy nhập tới dữ liệu dùng chung trong thế giới đa xử lý cân đối của Windows XP, kernel để cho bạn định nghĩa bất kỳ số của các đối tượng khóa xoay vòng nào. Để thu nhận một khóa xoay vòng, mã trên một CPU thực hiện một trong những thao tác nguyên tử mà những sự thử và sau đó được đặt một biến kí ức theo một cách mà không có CPU khác có thể truy nhập biến cho đến thao tác hoàn thành. Nếu sự thử chỉ ra sự khóa đó trước đó tự do, chương trình tiếp tục. Nếu sự thử chỉ ra sự khóa đó thì trước đó được giữ, chương trình lặp lại test-and-set trong một vòng lặp chặt: nó là “spins.” Dần dần chủ nhân giải phóng sự khóa bằng việc đặt lại biến, về cái đó là một trong số sự chờ đợi quá trình hoạt động test-and-set của CPU sẽ báo cáo sự khóa như tự do.

Hình 4-3 minh họa khái niệm của việc sử dụng một sự khóa xoay vòng. Giả thiết chúng tôi có " tài nguyên " nào đó mà có lẽ đã được sử dụng đồng thời trên hai CPU khác nhau. Để làm ví dụ cụ thể, hình dung rằng tài nguyên là tế bào LIST_ENTRY mà neo một bản kê liên kết của IRPs. Danh sách có lẽ đã được truy nhập bởi một hoặc nhiều thủ tục gởi đi, một sự hủy bỏ thường lệ, một DPC thường lệ,.... Bất kỳ số nào của những thủ tục này có lẽ đã là đang thực hiện đồng thời trên các CPU khác nhau và thử để sửa đổi danh sách kết hợp. Để ngăn ngừa sự hỗn loạn, chúng tôi liên tưởng một khóa xoay vòng với điều này " tài nguyên."

Hình 4-3. Sử dụng một khóa xoay vòng để bảo vệ một tài nguyên dùng chung.

Giả thiết bây giờ mà mã thực hiện trên CPU-A muốn truy nhập tài nguyên dùng chung ở thời gian t1. Nó thu nhận sự khóa xoay vòng và bắt đầu sự truy nhập của nó. Không lâu nữa về sau, ở thời gian t2, mã thực hiện trên CPU-B cũng muốn truy nhập cùng tài nguyên. CPU- B chương trình thử thu nhận sự khóa xoay vòng. Từ CPU một hiện thời sở hữu sự khóa xoay vòng, CPU B những sự xoay vòng trong một vòng lặp chặt, liên tục kiểm tra và kiểm tra lại khóa xoay vòng để nhìn thấy liệu có phải nó đã trở nên tự do. Khi nào CPU-A phiên bản khóa tại thời gian t3, CPU-B tìm thấy khóa tự do và những quyền đòi nó. CPU-B đã giải phóng sự truy nhập tới tài nguyên. Cuối cùng, ở thời gian t4, CPU-B kết thúc sự truy nhập của nó và giải phóng khóa CPU-B.

Việc thu nhận một khóa xoay vòng nâng IRQL tới DISPATCH_LEVEL tự động. Vậy thì, mã mà thu nhận một khóa phải được đánh số trang bộ nhớ và không phải ngăn chặn luồng mà trong đó nó chạy. (Có một ngoại lệ trong Windows XP và những hệ thống sau. KeAcquireInterruptSpinLock nâng IRQL tới DIRQL cho một ngắt và đòi hỏi khóa xoay vòng liên quan đến ngắt.)

Như một hệ quả hiển nhiên thực tế trước đây, bạn có thể đòi hỏi một khóa xoay vòng chỉ khi bạn đang chạy tại hoặc ở dưới DISPATCH_LEVEL. Nội tại, kernel thì có thể thu nhận những sự khóa quay tròn ở một IRQL cao hơn DISPATCH_LEVEL, nhưng bạn và tôi không có khả năng hoàn thành chiến công đó.

Thực tế khác về những khóa xoay vòng là rất ít công có ích xuất hiện trên một CPU điều đó đợi một khóa xoay vòng. Sự xoay vòng xảy ra ở DISPATCH_LEVEL với những ngắt được cho phép, vì vậy một CPU đó là việc đợi một khóa xoay vòng có thể dịch vụ những ngắt phần cứng. Nhưng để tránh làm hại sự thực hiện, bạn cần tối giản số lượng của công việc bạn trong khi giữ một quay tròn khóa CPU khác nào đó thích hợp để muốn.

Nhân tiện, hai CPUs có thể đồng thời giữ hai khóa quay tròn khác nhau. Sự sắp đặt này có ý nghĩa: bạn liên tưởng một sự khóa quay tròn với một tài nguyên dùng chung nhất định, hay tập hợp nào đó của những tài nguyên dùng chung. Ở đó là không có lập luận để đưa lên xử lý liên quan đến những tài nguyên khác nhau bảo vệ bởi những khóa quay tròn khác nhau.

Làm việc với những sự khóa Quay tròn

Để sử dụng một sự khóa quay tròn rõ ràng, định phần lưu trữ cho một đối tượng KSPIN_LOCK trong đánh số trang kí ức. Rồi gọi là KeInitializeSpinLock để khởi tạo đối tượng. Muộn hơn, trong lúc việc chạy tại hoặc ở dưới DISPATCH_LEVEL, thu nhận khóa, thực hiện công việc mà cần được bảo vệ từ sự giao thoa, và sau đó giải phóng khóa. Chẳng hạn, giả thiết mở rộng thiết bị của các bạn chứa đựng một QLock có tên khóa quay tròn mà bạn sử dụng để bảo vệ sự truy nhập tới một hàng đợi IRP đặc biệt bạn có thiết lập. Bạn sẽ khởi tạo sự khóa này trong chức năng AddDevice của các bạn:

typedef struct _DEVICE_EXTENSION {

  KSPIN_LOCK QLock;

  } DEVICE_EXTENSION, *PDEVICE_EXTENSION;

NTSTATUS AddDevice(...)

  {

  PDEVICE_EXTENSION pdx = ...;

  KeInitializeSpinLock(&pdx->QLock);

  }

Nơi khác trong trình điều khiển của các bạn, lời nói trong chức năng liên lạc cho kiểu IRP nào đó, bạn có thể đòi hỏi ( và nhanh là phiên bản) sự khóa xung quanh sự thao tác hàng đợi nào đó mà bạn cần thực hiện. Chú ý rằng chức năng phải ở trong đánh số trang kí ức vì nó thực hiện trong một thời gian tại một IRQL cao.

NTSTATUS DispatchSomething(...)

  {

  KIRQL oldirql;

  PDEVICE_EXTENSION pdx = ...;

  KeAcquireSpinLock(&pdx->QLock, &oldirql);

  KeReleaseSpinLock(&pdx->QLock, oldirql);

  }

  1. Khi KeAcquireSpinLock thu nhận sự khóa quay tròn, nó cũng nâng IRQL tới DISPATCH_LEVEL và quay trở lại mức hiện hành trong biến tới những điểm đối số thứ hai.
  2. Khi KeReleaseSpinLock giải phóng sự khóa quay tròn, nó cũng hạ thấp xuống IRQL quay trở lại giá trị được chỉ rõ trong đối số thứ hai.

Nếu bạn biết bạn đã thực hiện ở DISPATCH_LEVEL, bạn có thể cất giữ một ít thời gian bằng việc gọi là hai thủ tục đặc biệt. Kỹ thuật này là thích hợp, chẳng hạn, trong DPC, StartIo, và những thủ tục trình điều khiển khác thực hiện ở DISPATCH_LEVEL:

KeAcquireSpinLockAtDpcLevel(&pdx->QLock);

KeReleaseSpinLockFromDpcLevel(&pdx->QLock);

0