Читаем Windows® Internals, Sixth Edition, Part 1 полностью

Note that the contention count, which is extracted from the resource structure, records the number of times threads have tried to acquire the resource and had to wait because it was already owned.

You can examine the details of a specific resource object, including the thread that owns the resource and any threads that are waiting for the resource, by specifying the –v switch and the address of the resource:lkd> !locks -v 0x89929320 Resource @ 0x89929320 Exclusively owned Contention Count = 3913573 Threads: 8952d030-01<*> THREAD 8952d030 Cid 0acc.050c Teb: 7ffdf000 Win32Thread: fe82c4c0 RUNNING on processor 0 Not impersonating DeviceMap 9aa0bdb8 Owning Process 89e1ead8 Image: windbg.exe Wait Start TickCount 24620588 Ticks: 12 (0:00:00:00.187) Context Switch Count 772193 UserTime 00:00:02.293 KernelTime 00:00:09.828 Win32 Start Address windbg (0x006e63b8) Stack Init a7eba000 Current a7eb9c10 Base a7eba000 Limit a7eb7000 Call 0 Priority 10 BasePriority 8 PriorityDecrement 0 IoPriority 2 PagePriority 5 Unable to get context for thread running on processor 1, HRESULT 0x80004001 1 total locks, 1 locks currently held

Pushlocks

Pushlocks are another optimized synchronization mechanism built on gate objects; like guarded mutexes, they wait for a gate object only when there’s contention on the lock. They offer advantages over the guarded mutex in that they can be acquired in shared or exclusive mode. However, their main advantage is their size: a resource object is 56 bytes, but a pushlock is pointer-size. Unfortunately, they are not documented in the WDK and are therefore reserved for use by the operating system (although the APIs are exported, so internal drivers do use them).

There are two types of pushlocks: normal and cache-aware. Normal pushlocks require only the size of a pointer in storage (4 bytes on 32-bit systems, and 8 bytes on 64-bit systems). When a thread acquires a normal pushlock, the pushlock code marks the pushlock as owned if it is not currently owned. If the pushlock is owned exclusively or the thread wants to acquire the thread exclusively and the pushlock is owned on a shared basis, the thread allocates a wait block on the thread’s stack, initializes a gate object in the wait block, and adds the wait block to the wait list associated with the pushlock. When a thread releases a pushlock, the thread wakes a waiter, if any are present, by signaling the event in the waiter’s wait block.

Because a pushlock is only pointer-sized, it actually contains a variety of bits to describe its state. The meaning of those bits changes as the pushlock changes from being contended to noncontended. In its initial state, the pushlock contains the following structure:

One lock bit, set to 1 if the lock is acquired

One waiting bit, set to 1 if the lock is contended and someone is waiting on it

One waking bit, set to 1 if the lock is being granted to a thread and the waiter’s list needs to be optimized

One multiple shared bit, set to 1 if the pushlock is shared and currently acquired by more than one thread

28 (on 32-bit Windows) or 60 (on 64-bit Windows) share count bits, containing the number of threads that have acquired the pushlock

As discussed previously, when a thread acquires a pushlock exclusively while the pushlock is already acquired by either multiple readers or a writer, the kernel allocates a pushlock wait block. The structure of the pushlock value itself changes. The share count bits now become the pointer to the wait block. Because this wait block is allocated on the stack and the header files contain a special alignment directive to force it to be 16-byte aligned, the bottom 4 bits of any pushlock wait-block structure will be all zeros. Therefore, those bits are ignored for the purposes of pointer dereferencing; instead, the 4 bits shown earlier are combined with the pointer value. Because this alignment removes the share count bits, the share count is now stored in the wait block instead.

A cache-aware pushlock adds layers to the normal (basic) pushlock by allocating a pushlock for each processor in the system and associating it with the cache-aware pushlock. When a thread wants to acquire a cache-aware pushlock for shared access, it simply acquires the pushlock allocated for its current processor in shared mode; to acquire a cache-aware pushlock exclusively, the thread acquires the pushlock for each processor in exclusive mode.

Перейти на страницу:

Похожие книги