critical region is one of the most confusing and poorly documented concepts in windows kernel dev
critical region is not the same as critical section
they’re even not related
your code can enter critical region by calling keentercriticalregion or fsrtlenterfilesystem, it is just a wrapper for keentercriticalregion
leave critical region by calling kelevecriticalregion or fsrtlexitfilesystem
the first important thing to not about critical region is that they’re a thread specific construct
therefor, when you enter a critical region, you’re entering for the current thread only, because of this, critical regions are most definitely not a sync mechanism
CRITICAL REGION IS NOT A SYNCHRONIZATION MECHANISM
then what is it then?
the answer lies in an insightful comment provided with the fsrtlenterfilesystem macro
//++
//
// VOID
// FsRtlEnterFileSystem (
// );
//
// Routine Description:
//
// This routine is used when entering a file system (e.g., through its
// Fsd entry point). It ensures that the file system cannot be suspended
// while running and thus block other file I/O requests. Upon exit
// the file system must call FsRtlExitFileSystem.
//
// Arguments:
//
// Return Value:
//
// None.
//
//--
#define FsRtlEnterFileSystem() { \
KeEnterCriticalRegion(); \
}
what this comment is trying to say that entering a Critical Region prevents the current thread from being suspended, thread suspension is performed by a normal kernel apc, which is simply a kernel mode callback directed to a particular thread,
by entering a critical region, your code prevents normal kapc from execution on the current thread
not that other things are done via normal kapc as well, such as hard error popups
but for out discussion we just care that critical regions prevent thread suspension
given this, we can make two statements:
- threads in a critical region can not be suspended
- thread not in a critical region can be suspend as long as the thread is running at irql passive_level
at first this sounds pretty hideous, I mean, have you ever given serious consideration to what would happen if a thread running in your driver was suspended?
most driver devs don’t think of this and if they do, they assume that they’re immune to suspension simply by running in kernel mode
no such luck, any code in your driver that runs at passive level and not in a critical region can be suspended
we’re doomed then?
what prevents this from being a complete mess is that in order to forcibly suspend a thread, a caller must have sufficient access to hte thread in question
thus if a process has sufficient rights and wants to puse a running thread, they’re allowed to do that
if this causes the process owning the thread to hang or otherwise malfunction, oh well! we can just document that under “Don’t Do That”
why bither having critical region at all then, well, the problem comes when there’s a knock on effect that causees problems in other threads or processes
waht if suspending a thread in process a caues threads in process B to hang?
just because I have the authority to suspend threads in process A doesn’t mean I have the sam autority with Process B
though I effectiely achieved the same result
even worse, imagine an unprivileged application that suspends own of its own threads, causing other privileged applications to hang or crash, this would be very bad indeed
thus, as you’re writing your code you need to ask yourself, if the current thread was suspended, would that affect any other threads in the system? Most problem cases will occur when acquiring lock; what happens to other potential users of a lock if you acquire that lock and then your thread is suspended?
take the file system for example, the fs uses a significat amount of file and voulme level locking while processing IO requests from user mode
what if a thread is currently holding one of these locks when it is suspended? this could potentially result in a system-wide deadlock as other threads come along and try to acquire the same lock to perform io operations of the file or volume
we solve this problem in one of two ways, the first way is to acquire a lock that also implicitly enters a critical region
examples of these types of locks are:
- spinlocks
- kernel mutexes
- fast mutexes
- guarded mutexes
however, if you acquire a lock that does not implicitly enter a critical region then you must explicitly call keentercriticalregion before acquiring the lock and keleavecriticalregion after releasing it
examples of locks that do not enter a critical region are:
- executive resources
- unsafe fast mutex
- kevent
- ksemaphore
if you’re using one of the above as a synchronization mechanism and therefor acquire it from multiple different thread contexts, you really need a critical region to keep yourself safe from a DoS
Conclusion
in a nutshell, the critical region can be thought of simply as a way to prevent thread suspension
threads executing in kernel mode are subject to being suspended, which is fine unless your driver creates cross thread dependencies such as a lock
in that case, it’s up to you to properly enter and leae a critical region at the right times