Re: Problem using queued spinlocks on single cpu comp uter

> class CSpinLock

{
private:
KLOCK_QUEUE_HANDLE *m_psLockQueue;
PKSPIN_LOCK m_oSpinLock;
CString *m_name;
public:
CSpinLock (name)
{
KeInitializeSpinLock(&m_oSpinLock);
m_psLockQueue = new KLOCK_QUEUE_HANDLE;
m_name = new CString(name);
}

Why are these pointers, and why are you allocating with new? Why not
just make them non-pointer members? Am I missing something?

class CSpinLock
{
private:
KLOCK_QUEUE_HANDLE m_LockQueue;
PKSPIN_LOCK m_oSpinLock;
CString m_name;
public:
CSpinLock (const CString & name)
: m_name(name)
{
KeInitializeSpinLock(&m_oSpinLock);
}

Chuck

CSpinLock will be the lock itself, and “acquisitor” is a helper object used
like:

CSpinLock SomeGlobalLock;

f()
{
// The locked code path
{
CSpinLockAcquisitor(&SomeGlobalLock);

// End of locked path
}
}

The destructor will release the spinlock.

Maxim Shatskih, Windows DDK MVP
StorageCraft Corporation
xxxxx@storagecraft.com
http://www.storagecraft.com

----- Original Message -----
From: “Moreira, Alberto”
To: “Windows System Software Devs Interest List”
Sent: Thursday, September 25, 2003 7:36 PM
Subject: [ntdev] Re: Problem using queued spinlocks on single cpu comp uter

> That’s an interesting statement, Chuck, what advantages do you see in
> separating these two ?
>
> Alberto.
>
>
> -----Original Message-----
> From: Chuck Batson [mailto:xxxxx@cbatson.com]
> Sent: Thursday, September 25, 2003 11:09 AM
> To: Windows System Software Devs Interest List
> Subject: [ntdev] Re: Problem using queued spinlocks on single cpu computer
>
>
> > CSpinLock::CSpinLock()
>
> By the way, a better design is to use two separate classes: one for the
> spin lock object itself, and another for acquisition/release.
>
> Chuck
>
>
> —
> Questions? First check the Kernel Driver FAQ at
> http://www.osronline.com/article.cfm?id=256
>
> You are currently subscribed to ntdev as: xxxxx@compuware.com
> To unsubscribe send a blank email to xxxxx@lists.osr.com
>
>
>
> The contents of this e-mail are intended for the named addressee only. It
> contains information that may be confidential. Unless you are the named
> addressee or an authorized designee, you may not copy or use it, or disclose
> it to anyone else. If you received it in error please notify us immediately
> and then destroy it.
>
>
> —
> Questions? First check the Kernel Driver FAQ at
http://www.osronline.com/article.cfm?id=256
>
> You are currently subscribed to ntdev as: xxxxx@storagecraft.com
> To unsubscribe send a blank email to xxxxx@lists.osr.com

> class CSpinLock

{
private:
KLOCK_QUEUE_HANDLE m_LockQueue;
PKSPIN_LOCK m_oSpinLock;
CString m_name;
public:
CSpinLock (const CString & name)
: m_name(name)
{
KeInitializeSpinLock(&m_oSpinLock);
}

This will not work. The correct way is:

class CSpinLock
{
friend class CSpinLockAcquisitor;
public:
CSpinLock() { KeInitializeSpinLock(&m_Lock); }
private:
KSPIN_LOCK m_Lock;
};

class CSpinLockAcquisitor
{
public:
CSpinLockAcquisitor(CSpinLock* Lock) {
KeAcquireInStackQueuedSpinLock(Lock->m_Lock, &m_Locker); }
~CSpinLockAcquisitor { KeReleaseInStackQueuedSpinLock(&m_Locker); }
private:
KLOCK_QUEUE_HANDLE m_Locker;
};

Maxim Shatskih, Windows DDK MVP
StorageCraft Corporation
xxxxx@storagecraft.com
http://www.storagecraft.com

> That’s an interesting statement, Chuck, what advantages do you see in

separating these two ?

The conceptual separation of a resource from its use. We can use the
current discussion as the basis for an example:

class CSpinLockAcquire;

class CSpinLock
{
public:
CSpinLock() { KeInitializeSpinLock(&SpinLock); }
private:
Lock(KLOCK_QUEUE_HANDLE & h) {
KeAcquireInStackQueuedSpinLock(&SpinLock, &h); }
Unlock(KLOCK_QUEUE_HANDLE & h) {
KeReleaseInStackQueuedSpinLock(&SpinLock, &h); }
KSPIN_LOCK SpinLock;
friend class CSpinLockAcquire;
};

class CSpinLockAcquire
{
public:
CSpinLockAcquire(CSpinLock & s) : SpinLock(s) {
s.Lock(Handle); }
~CSpinLockAcquire() { s.Unlock(Handle); }
private:
CSpinLock & SpinLock;
KLOCK_QUEUE_HANDLE Handle;
};

This conceptual separation is useful any time you have a resource which
must be acquired/released, locked/unlocked, etc. There are a few
concrete benefits:

  1. Exception safety. Since locking and unlocking is handled by the
    constructor/destructor of CSpinLockAcquire, you can never have a
    situation of a lock not being matched with a corresponding unlock, even
    in the presence of exceptions.

  2. Human error and maintainability. For the same reason, it’s
    impossible for the programmer to “forget” to unlock a resource.
    Granted, if you forget to release a spin lock, you’re likely to know
    pretty quickly, but it still wastes time and effort diagnosing the
    problem. The same general principle applies to other types of resources
    (e.g. not necessarily spin locks) where usage may be more involved
    and/or subtle and hence more prone to difficult-to-diagnose “forgetting
    to unlock” errors.

  3. Acquisition/release data is separate from resource data. This is
    good for two reasons:

a. Memory storage for the resource object itself may be precious.
Consider the “single class” implementation where the spin lock and its
queue handle are stored together. The spin lock has to go into shared
memory which is accessible to all those who might potentially acquire
it. There’s no reason for the queue handle to be shared, since it’s
only used by one acquirer at a time. So separating them is beneficial
when use of shared memory has performance implications.

b. Resource acquisition data sometimes must be separate from the
resource, especially when it’s possible for more that one entity to
acquire a given resource at the same time. This is less useful in this
case, when only one processor can acquire a spinlock at any given time.
(Though it’s still a bit aesthetically displeasing to me, when you think
about two different processors calling KeAcquireInStackQueuedSpinLock()
with a pointer to the same queue handle; “luckily” only one of them will
succeed in acquiring the spin lock, so there will never be more than one
user of the queue handle… but still makes me shudder.) But there are
situations in which this is necessary. For example, a resource which
has both read and write acquires, where multiple readers are allowed but
only a single writer (with no readers) is allowed. Since you can have
multiple readers, obviously it won’t do to put the acquisition handle
(or whatever relevant acquisition-related information) in the resource
object itself.

Chuck