The Big Picture: From "Start" to "Running"
You press Start on a service. A moment later it says Running. What really happens in between? This section walks through the chain, step by step. It ties together everything from Sections 2 to 8.
┌────────────────────────────────────────────────────────────┐
│ 0. Someone asks: StartService (boot, services.msc, sc ...) │
└─────────────────────────────┬──────────────────────────────┘
▼
┌────────────────────────────────────────────────────────────┐
│ 1. SCM (services.exe) reads the registry service entry │
│ ImagePath, Type, ObjectName, DependOnService ... │
└─────────────────────────────┬──────────────────────────────┘
▼
┌────────────────────────────────────────────────────────────┐
│ 2. SCM starts dependencies, then the host process │
│ (own .exe, or svchost.exe loading a ServiceDll) │
└─────────────────────────────┬──────────────────────────────┘
▼
┌────────────────────────────────────────────────────────────┐
│ 3. Process calls StartServiceCtrlDispatcher │
│ SCM runs the service's ServiceMain │
└─────────────────────────────┬──────────────────────────────┘
▼
┌────────────────────────────────────────────────────────────┐
│ 4. Service reports START_PENDING, then RUNNING │
│ with SetServiceStatus; SCM updates its records │
└────────────────────────────────────────────────────────────┘
The whole thing must finish in about 30 seconds, or the SCM reports error 1053 (Section 8).
9.1 SCM Reads Registry Service Entry
What it is: The first step. The SCM looks up the service in its database (Section 2.2) and decides how to start it.
Checks before anything starts:
| Check | If it fails |
|---|---|
| Does the service exist? | Error 1060 |
| Is it Disabled? | Error 1058 |
| Is the caller allowed to start it? (Section 6) | Error 5, access denied |
| Is it already running? | Error 1056, "an instance of the service is already running" |
| Are its dependencies running? (Section 4) | SCM starts them first; if one fails, error 1068 |
Values it reads from HKLM\SYSTEM\CurrentControlSet\Services\<name>:
| Value | Used for |
|---|---|
ImagePath | Which program file to launch, and with which options |
Type | Own process (0x10) or shared process (0x20) |
ObjectName | Which account to log on as (Section 5) |
Start, DelayedAutostart, Triggers | Why and when it started (Section 3) |
DependOnService | Start order (Section 4) |
FailureActions | What to do later if it crashes (Section 7) |
Logging on. The SCM then gets the token (a Windows "ID card" listing the account and its powers) for the service's account. For custom accounts, it uses the stored password. If this fails: error 1069 (Section 8).
9.2 Executable/DLL Loaded (svchost.exe Hosting)
What it is: The SCM creates a new process for the service, using the account from step 1. What it launches depends on the service type.
Option A: Own process (Type = 0x10). ImagePath names the service's own .exe, for example C:\Windows\System32\spoolsv.exe for Print Spooler. That program is the service. It runs alone, so a crash affects only itself.
Option B: Shared process (Type = 0x20), hosted by svchost.exe. Many Windows services are not an .exe but a DLL (a code library: a file of functions that other programs load). A generic host program, svchost.exe (Service Host), loads the DLL.
The ImagePath then looks like:
C:\Windows\System32\svchost.exe -k netsvcs -p
| Piece | Meaning |
|---|---|
svchost.exe | The generic host |
-k netsvcs | The service group name. All services in the same group may share one svchost.exe |
-p | An extra protection option used on many current services |
Where is the DLL named? In the service's own Parameters sub-key, the value ServiceDll (for example %SystemRoot%\System32\...dll). svchost reads it and loads that DLL.
The list of which services belong to which group is stored in HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost. Each value there is a group name (like netsvcs) and a list of service names.
Shared hosting Own process
┌──────────────────────────┐ ┌──────────────────────────┐
│ svchost.exe -k netsvcs │ │ spoolsv.exe │
│ ┌────────┐ ┌────────┐ │ │ (Print Spooler's own │
│ │ DLL: A │ │ DLL: B │ │ │ program) │
│ └────────┘ └────────┘ │ └──────────────────────────┘
└──────────────────────────┘
one process, several services one process, one service
Why share? In the past, to save memory: one process instead of 50. The price: if one DLL crashes the host, all services in it die.
Modern change. From Windows 10 version 1703, on PCs with enough RAM (about 3.5 GB or more), Windows runs most services in their own svchost.exe. That is why you see many svchost.exe entries. On low-memory PCs, grouping still happens.
See which service is in which process (read-only):
tasklist /svc /fi "imagename eq svchost.exe"
In Task Manager (Chapter 11), right-click a svchost.exe entry and choose Go to service(s).
9.3 Service Main Function Registered
What it is: Now the new process has to introduce itself to the SCM. A normal program starts at main. A service has an extra step: it must tell the SCM "I am a service. Here are the names of my services and the function that starts each."
The function names (from the Windows programming interface):
| Name | Plain meaning |
|---|---|
StartServiceCtrlDispatcher | The process says "I'm a service host. Here is my table of service names and their start functions." This call does not return until all the services in the process have stopped |
ServiceMain | The start function for one service. The SCM runs it in a new thread |
RegisterServiceCtrlHandlerEx | The service says "here is my control handler: the function to call when you want to stop or pause me" |
| Control handler | A function that receives messages like STOP, PAUSE, CONTINUE, SHUTDOWN |
In the shared-host case, svchost does this work for you: it reads the DLL, finds its ServiceMain entry (the name can be set in the registry, ServiceMain is the default) and puts it in the table.
A service written as an .exe looks like this in simple pseudo-code:
main():
table = [ ("MyService", ServiceMain) ]
StartServiceCtrlDispatcher(table) // blocks here, SCM takes over
ServiceMain():
handler = RegisterServiceCtrlHandlerEx("MyService", ControlHandler)
SetServiceStatus(START_PENDING)
... do start-up work (open files, start threads) ...
SetServiceStatus(RUNNING)
The timing rule. After launching the process, the SCM waits for it to call StartServiceCtrlDispatcher. If this does not happen in about 30 seconds, the SCM gives up, kills the process and writes Event 7009; the user sees error 1053. This is also why an ordinary program turned into a service by a trick fails: it never calls the function, so the SCM thinks it hung.
Behind the scenes. The SCM and the service process talk over a private connection (a named pipe). When you press Stop, the SCM sends the STOP control down that pipe, and the service's control handler runs.
Important: ServiceMain must return quickly. Real work happens on other threads. If ServiceMain blocks forever, the service never reports RUNNING.
9.4 Status Reported Back to SCM
What it is: Throughout its life, the service reports its state to the SCM with SetServiceStatus. The SCM keeps this latest state and shows it to every tool.
The states:
| State | Meaning |
|---|---|
SERVICE_START_PENDING | "I'm starting. Wait for me." |
SERVICE_RUNNING | "I'm ready and working." |
SERVICE_PAUSE_PENDING / SERVICE_PAUSED / SERVICE_CONTINUE_PENDING | Pausing, paused, resuming (only for services that support pause) |
SERVICE_STOP_PENDING | "I'm shutting down." |
SERVICE_STOPPED | "I've stopped." |
┌─────────┐ start ┌───────────────┐ ready ┌─────────┐
│ STOPPED │──────►│ START_PENDING │──────►│ RUNNING │
└─────────┘ └───────────────┘ └────┬────┘
▲ │ stop
│ ┌──────────────┐ ▼
└────────────┤ STOP_PENDING │◄────────────┘
stopped └──────────────┘
Check points. A slow service can send repeated updates while still in a "pending" state, using two numbers: a checkpoint (which should go up each time) and a wait hint (how many more milliseconds it expects to need). This tells the SCM "I'm still working, don't give up." If the checkpoint stops increasing and the wait hint runs out, the SCM treats the service as stuck.
The "accepted controls" flags. In the same status report, the service says which controls it accepts: STOP, PAUSE_CONTINUE, SHUTDOWN, and others. That is why some services show Stop greyed out in services.msc: the service has not said it accepts STOP. Windows uses the flags to decide what buttons are active.
After RUNNING. The SCM now:
- Updates its records (visible via
sc query). - Writes Event 7036 ("entered the running state").
- Starts any dependent services waiting for this one (Section 4).
- Keeps watching the process. If it dies without reporting STOPPED, the SCM runs the recovery actions (Section 7) and logs 7034 or 7031.
See it in your own PC (read-only):
sc query Spooler
It prints STATE, and also WIN32_EXIT_CODE, CHECKPOINT and WAIT_HINT, the same fields the service reported.
Summary
| Step | Who | What |
|---|---|---|
| 1 | SCM | Reads the registry entry, checks permissions, starts dependencies |
| 2 | SCM | Starts the host process under the service account: own .exe or svchost.exe -k group loading ServiceDll |
| 3 | The process | Calls StartServiceCtrlDispatcher; SCM runs ServiceMain; handler registered |
| 4 | The service | SetServiceStatus: START_PENDING, then RUNNING; later STOP_PENDING, STOPPED |
| Time limit | SCM | About 30 seconds to connect, else error 1053 / event 7009 |
| After | SCM | Logs 7036, starts dependents, watches for crashes and applies recovery |