You've come to this page because you've asked a question similar to the following:
What is the Windows NT version 6 ("Windows Vista") shutdown process ?
This is the Frequently Given Answer to such questions.
The Windows NT 6 shutdown process is largely based around the
ExitWindowsEx()
Win32 API call. Only at the very end of the
process is the kernel requested to perform the actual task of shutting the
machine down.
ExitWindowsEx()
Unlike many Win32 API calls since Windows NT 4, ExitWindowsEx()
is still implemented as a remote procedure call to the CSRSS ("Client-Server
Runtime Subsystem") process for the
session.
This makes the flow of control in the shutdown procedure less obvious than
it could be, because many actions are performed by CSRSS acting upon a
process' behalf (and hence impersonating the caller's user credentials)
rather than directly by the process itself.
ExitWindowsEx()
has two behaviours. Either it sends a window
message to a window owned by the WINLOGON process for the current
session,
or it broadcasts end-session messages to all top-level windows of a
session.
Both behaviours are implemented by CSRSS, but that is an artifact
of the client-server design of Win32 on Windows NT, rather than an
necessity in its own right. The shutdown process could equally well be
regarded as if the behaviour of ExitWindowsEx()
were
performed directly by the actual process that invoked it.
Shutdown is initiated in one of three ways: an ordinary application process calls
InitiateSystemShutdownEx()
, an application
process calls ExitWindowsEx()
with parameters requesting a system
shutdown, or the interactive user in session 1 triggers the Secure Attention
Sequence. Both of the former two actually end up simulating the third.
When an ordinary application process calls ExitWindowsEx()
to
request a shutdown, a logoff, a power-down, a halt, or a reboot, the first
behaviour of ExitWindowsEx()
is invoked.
ExitWindowsEx()
(i.e. CSRSS acting upon the calling process'
behalf) simply sends a window message to a window owned by the WINLOGON
process for the current session to simulate the interactive user choosing the
equivalent action from the WINLOGON user interface. So in the case of
ExitWindowsEx()
being called to initiate a shutdown, a window
message is sent to the WINLOGON process for that session, requesting a
shutdown, and WINLOGON behaves from that point just as if shutdown had been
requested via its user interface.
This is why, incidentally, ExitWindowsEx()
only has an effect if
the process invoking it is being run in session 1. (Microsoft says "by the
interactive user", which can be misleading. This is what it actually means.)
ExitWindowsEx()
sends a window message to the WINLOGON process
for the session in which the invoking process is running. But only the
WINLOGON process in session 1 does anything in response to the message,
because it is the only session talking to a "local" user; and, indeed,
other sessions do not necessarily have WINLOGON processes at all.
(Session 0 has WININIT, not WINLOGON.) Therefore, only when a process is
runnng in session 1 does ExitWindowsEx()
have any actual
effect.
The behaviour of InitiateSystemShutdownEx()
is similar, albeit
slightly more complicated by the fact that shutdowns triggered in this way can
be deferred. When an ordinary application process calls
InitiateSystemShutdownEx()
it connects to the
\InitShutdown
named pipe and using that pipe performs an RPC
to schedule a shutdown. The server for that named pipe is WINLOGON, which
handles all of the details of deferring the shutdown, and actually triggering
the shutdown at the scheduled time. WINLOGON also allows already scheduled
deferred shutdown requests to be cancelled.
AbortSystemShutdown()
performs an RPC via the same named pipe to
request that a previously scheduled shutdown be cancelled.
The function in WINLOGON that handles the "initiate shutdown" request via the
named pipe is BaseInitiateShutdownEx()
. This checks the client's
privilege level, checks the validity of the request, checks to ensure that a
shutdown is not already scheduled and that a shutdown has not already begun,
checks to see whether WINLOGON is not in the "logged-on and locked" state,
writes a record to the audit log, and (if everything succeeds to this point
— flags in the request allowing some of the aforementioned checks to be
overridden) starts a thread to actually perform the shutdown at the scheduled
time.
The thread runs the not-quite-appropriately-named
LogoffThreadProc()
function. This function waits until the
specified time, displaying a "countdown" dialogue box in the interim, and then
calls ExitWindowsEx()
. This is a normal call to
ExitWindowsEx()
, which sends a message to WINLOGON to simulate
shutdown being requested via its user interface.
WINLOGON has various internal flags and variables that control this deferred
shutdown mechanism. The ShutdownInProgress
flag records the fact
that a shutdown is already scheduled (i.e. the thread has already been
invoked). The ShutdownTime
variable records the time that the
thread will wait until before actually initiating the shutdown. The
ShutdownHasBegun
records the fact that a shutdown has begun (i.e.
the thread has called ExitWindowsEx()
). The
AbortShutdown
flag is set if an "abort shutdown" request comes in
via the named pipe or if abort is chosen from the "countdown" dialogue box.
When WINLOGON receives an interactive (or simulated) shutdown request, it
first logs the current user off. (If ExitWindowsEx()
had been
called requesting a logout, the procedure would end here.) Logging the user
off involves sending end-session messages to all of the message queues in the
session, sending logoff events to all of the consoles in the session,
switching from the user desktop to the login desktop, and effecting an
internal change of state to the "no-one logged on" state.
To send end-session messages and logoff events, WINLOGON itself
calls ExitWindowsEx()
, impersonating the currently logged on
user and using secret undocumented parameters to the call that request the
second behaviour of ExitWindowsEx()
, namely that
that end-session messages be broadcast to all of the top-level windows in
the current session. ExitWindowsEx()
(i.e. CSRSS acting
upon WINLOGON's behalf) enumerates all of the top-level windows in the the
session, first sending the WM_QUERYENDSESSION
window message
and then sending the WM_ENDSESSION
window message to them.
If they are console windows, this in turn causes CSRSS (which is the owner
of all console windows) to send the CTRL_LOGOFF_EVENT
event
to the associated console.
Microsoft's documentation talks about sending messages to threads,
implying that all threads in the session that have message queues are
enumerated. In fact, ExitWindowsEx()
takes the easy option
and enumerates top-level windows instead, which is a lot simpler to do.
Thus if a single-threaded application has two top-level windows, it will
receive the end-session window messages twice, once per window.
Similarly, even if a thread has a message loop, it won't receive
end-session messages unless the program has created a top-level window.
These are both contrary to what Microsoft's documentation implies.
It is CSRSS that implements the timeouts, waiting for a response, and that displays the "end task" dialogue boxes for unresponsive processes.
The shutdown process can potentially become stuck forever at this point,
if the user does not close the dialogue boxes. CSRSS knows that it is in
the middle of processing an ExitWindowsEx()
broadcast, and so
responds with a failure result code if any process in the session
(including WINLOGON) calls ExitWindowsEx()
again. The
current broadcast sequence must be cancelled before CSRSS allows another
one to be triggered. (Note that if ExitWindowsEx()
were
not implemented as a client-server Win32 API call, this behaviour
would be a lot harder to duplicate, since there would be no central
arbitrator to prevent two separate processes from executing
ExitWindowsEx()
simultaneously.)
When all of the end session messages and events have been sent, CSRSS
completes the remote procedure call and WINLOGON's call to
ExitWindowsEx()
returns. WINLOGON then effects an internal
change of state from the "logged-on" state to the "no-one logged on" state.
This involves updating the user's profile, deleting network connections,
playing the "log off" and "system exit" sounds, killing all COM processes
(which has a grace period of 15 minutes), saving and unloading the user's
profile, and deleting RAS connections. Finally WINLOGON informs the LogonIU
of the state change. (On prior versions of Windows NT, WINLOGON would notify
the GINA of an internal change of state by calling the GINA's
WlxLogoff()
function.)
After logging the current user off, WINLOGON then attempts to stop all of the processes running in session 0.
To stop all of the processes running in session 0, WINLOGON calls
ExitWindowsEx()
again, again using secret undocumented
parameters to the call to request an end-session broadcast, but this time
under the aegis of the "local system" user account.
ExitWindowsEx()
(i.e. CSRSS acting upon WINLOGON's behalf)
performs the same enumeration for session 0 as it did for session 1,
sending the same window messages. One difference, however, is that
instead of sending a logoff event to any consoles, it sends a
CTRL_SHUTDOWN_EVENT
instead.
It is here that the Service Controller (SCM) is first informed that the system
is shutting down. It, in turn, attempts to stop all running service
processes. (Its console event handler, its
ScShutdownNotificationRoutine()
function, receives the
CTRL_SHUTDOWN_EVENT
event and in turn calls the SCM's
ScShutdownAllServices()
function.) Because it does so, CSRSS
implements a special case for it. Since it knows which process is the SCM
(because the SCM registers itself with CSRSS at startup) it knows to implement
a different, usually longer, timeout when sending end-session messages to the
SCM.
When all of the shutdown messages and events have been sent, CSRSS completes
the remote procedure call and WINLOGON's call to ExitWindowsEx()
returns. WINLOGON then effects an internal change of state from the "no-one
logged-on" state to the "shutdown" state. WINLOGON informs the LogonIU of the
state change. (On prior versions of Windows NT, WINLOGON would notify the GINA
of an internal change of state by calling the GINA's
WlxShutdown()
function.)
Finally, WINLOGON tells the Session Manager Subsystem (SMSS) to shut the system down. It does this by calling sending a shutdown request to the SMSS's LPC port.
The SMSS is the last stage in the shutdown process. It informs all of the
subsystem processes (e.g. CSRSS, PSXSS, OS2SS, and so forth), in all of
the sessions that it has started, that the system is shutting down, giving
them the opportunity to terminate all of their client process and shut
themselves down. It then calls the kernel's
NtShutdownSystem()
system call.
NtShutdownSystem()
system call
Inside the kernel, all drivers are shut down, the in-memory copies of the
registry hives are flushed to disc, the disc cache is flushed, the paging file
is cleared, and eventually the
NtSetSystemPowerState()
function is called. This causes all
plug-and-play devices to be shut down and the system to be either halted,
rebooted, or powered off.
© Copyright 2006–2006
Jonathan de Boyne Pollard.
"Moral" rights asserted.
Permission is hereby granted to copy and to distribute this web page in its
original, unmodified form as long as its last modification datestamp is preserved.