1.5 Service Manager Boundaries

These design notes explain the boundaries behind the operator workflows. The implementation chapters specify each mechanism in detail.

1.5.1 peinit in one sentence #

peinit is the sole service manager and PID 1 — it reads service definitions from the registry, starts them in dependency order under per-service identities, supervises them through a defined state machine, and tears them down in reverse at shutdown.

Everything in this topic is an elaboration of that sentence: what a service definition is, what dependency order means, what the state machine looks like, how identity is materialised, and how you drive the whole thing from a shell.

1.5.2 Three kinds of object #

peinit is easiest to understand through the three first-class objects it works with. Keeping them straight is most of the battle when you read a status output or an event log.

ObjectWhat it isLifetime
ServiceA definition — a named unit of execution with identity, policy, and configuration, stored in the registry. It has a runtime state (the state machine) and, when running, a process.Long-lived. Persists across restarts and reboots.
JobA single process execution. Every time peinit forks — a service's main process, a hook, a health check, a submitted job — that is one job, with its own GUID and exit result.Short-lived. A restart creates a new job.
OperationA requested action on a service — start, stop, restart, reload, reset. Control commands create operations that are validated, queued, and executed.Short-lived. Resolves to a terminal state, then is dropped.

A service is the what; a job is what actually ran; an operation is what someone asked for. A single restart command, for example, is one operation, which stops the current job and starts a new one, all against one service. Jobs and operations both carry GUIDs and are emitted to eventd as they complete, which is how the history of a service is reconstructed after the fact. They get their own page: Jobs and operations.

Underneath all three sits the fourth thing peinit is always handling — the token, the identity a service runs under. Tokens are not peinit's invention; they are the system-wide identity object. peinit's role is to obtain the right token for each service and install it before the process runs. That is the subject of Service identity and privileges.

1.5.3 What peinit is not #

peinit borrows vocabulary from init systems you may already know, and the familiarity is a trap. Clearing three wrong mental models is most of understanding what peinit actually is.

It is not systemd. There are no unit files. peinit does not read, parse, or translate .service files, and there is no migration shim. A service definition is a key in the registry under Machine\System\Services\, made of typed registry values — not a text file under /etc. Some interfaces are deliberately compatible where that helps: peinit speaks the sd_notify readiness protocol, and timer schedules use systemd's OnCalendar calendar-expression format. The compatibility stops at those wire formats; the model underneath is different.

It is not Windows SCM. The influence is real — services as securable objects with per-service descriptors, token-based service identity, an access-controlled control interface, a structured state machine — but it is architectural, not interface-level. peinit does not implement the SCM RPC protocol, Windows service types, or Windows control codes.

It is not a logging system. peinit holds a service's stdout/stderr pipes at birth, so it controls where output goes — but storage, indexing, and queries are eventd's job. peinit forwards; eventd is the historian. The same split applies to job and operation history: peinit emits structured events and forgets them; eventd keeps them. See Service output and logging.

A fourth, smaller correction: peinit does not supervise forking daemons. A service that double-forks to "daemonise" is solving a problem that does not exist when the manager tracks the process it spawned. peinit tracks every process by pidfd; a legacy binary that insists on double-forking must be wrapped at the package layer.

1.5.4 The one operational invariant #

peinit is single-threaded PID 1, and that one fact explains a surprising amount of its design. In a single-threaded PID 1, a blocking system call blocks everything — child reaping, watchdog expiry, shutdown signals, every other event stops while the call is stuck. So peinit's hard rule is that its event loop never blocks on a userspace service.

Two consequences you will meet repeatedly:

  • peinit operates on an in-memory snapshot of the registry, not a live view. It reads service definitions at boot and on configuration reload, and thereafter works from an in-memory model that it updates from change notifications. Supervision never waits on the registry being available. This is why a configuration change does not always take effect immediately — see Defining a service.
  • Anything that could block is pushed off the main loop. Filesystem condition checks run in a short-lived forked helper rather than a stat() on the event loop; token requests to authd are driven by a timed state machine, not a blocking call; log delivery is best-effort and never exerts back-pressure on peinit.

You do not need to think about the event loop to operate peinit, but it is the reason behind several behaviours that would otherwise look arbitrary.

1.5.5 Where peinit sits in the system #

peinit is part of the Trusted Computing Base: a compromise of peinit is a compromise of the whole machine. It runs as SYSTEM with all privileges, and it never drops that identity. It depends on, and is depended on by, the other TCB components:

peinit relies onFor
KACSService identity (installing tokens on children), control-interface authentication (peer token + AccessCheck), and per-service access control. See Access decisions.
LCS / the registryService definitions, boot configuration, and its own parameters. See The registry.
registrydThe registry source daemon — the first service peinit starts. peinit treats it as an opaque dependency. (registryd is an interface; the default implementation is loregd — see Boot and boot modes.)
authdMinting tokens for non-platform services. peinit requests; authd mints.
eventdService logs (forwarded over a socket) and job/operation/audit events (emitted through KMES).
KACS, againConveying a job identity: when a process submits a job over peinit's jobs socket and attaches a token, the kernel gates the attach — so peinit can run a program as a service's client without ever judging the identity itself. See Jobs and operations.

The bootstrapping puzzle — authd needs the registry, the registry needs to be started by something, and that something is peinit running before any of them exist — is resolved by the boot model in Boot and boot modes.

1.5.6 How a token is materialised #

The Identity field decides where the token comes from:

Identity valueToken source
SYSTEMMinted by peinit from its own SYSTEM identity.
Any other principal name or SIDRequested from authd.
Absent or emptyauthd, defaulting to LocalService.

1.5.6.1 The SYSTEM path #

For a SYSTEM service, peinit mints an independent SYSTEM token using its own token as the template: it reads its own identity (user SID S-1-5-18, the group list, the privilege set) and mints a fresh primary token with the same identity, adding the service's per-service SID. The minted token is fully independent — the privilege trimming that follows affects only this token, never peinit's.

This path exists because of a bootstrapping problem: the platform services that make the normal token flow possible cannot use it, because they have to start before it exists. registryd and authd use this bootstrap path during boot. Standard eventd and lpsd deployments now use Identity=Service, take the authd path, and start after authd (§4.2).

1.5.6.2 The authd path #

For any other identity, peinit asks authd for a token: it sends the Identity value verbatim, and authd routes it to the right source —

  • Well-known principals (LocalService, NetworkService, …) → a built-in identity with a predefined minimal privilege set;
  • Local service accounts → lpsd, the local principal store;
  • Domain accounts → the directory connector;

— resolves the principal's SIDs, mints a token, creates a logon session, and hands the token back to peinit to install. peinit neither knows nor cares whether the identity is local or domain; routing is authd's job.

authd records each request as an authd.service.attested event: the service (object.service.name), the identity it got (object.token.sid), its session and the privileges policy granted it. A refused request is recorded too, with the reason, such as account-restricted for an identity that may not be used for a service logon. The event is standard, so the emission policy can switch it off on a machine that restarts services often.

1.5.7 The per-service SID #

Every service token — minted or authd-issued — carries a per-service SID in its group list. It uses authority S-1-5-80 and is derived deterministically from the service name: the SHA-1 of the uppercased name (as UTF-16LE), with the digest split into five sub-authorities. authd adds it automatically for the tokens it mints; peinit computes and adds it itself for SYSTEM tokens, no authd involvement needed.

The point of the per-service SID is fine-grained access control even when services share an identity. A dozen services might all run as LocalService, but each has a unique service SID, so an ACL can grant access to exactly one of them without inventing a dedicated account. It also keeps services configured as SYSTEM distinguishable to AccessCheck. See Well-known principals for the SID landscape this fits into.

1.5.8 A clean context #

A service inherits only what peinit explicitly hands it — its standard streams and any stored file descriptors. Every other descriptor peinit holds — the control socket, the notify socket, the event-loop fd, the registry and authd and eventd connections — is opened close-on-exec, so it closes automatically at exec and can never leak into a service. peinit also resets the child's signal state to defaults (it runs with all signals blocked for its own signalfd; a service must not inherit that). The guarantee is that a service starts from a clean slate, not from peinit's privileged one.

1.5.9 The cgroup tree #

Every service runs in its own cgroup tree under /sys/fs/cgroup/peinit/, with separate sub-cgroups for the main process, hooks, and health checks:

/sys/fs/cgroup/peinit/<service>/
                       ├── main/     the main process
                       ├── hooks/    pre/post hooks
                       └── health/   health checks

peinit uses cgroups for two things only: tracking every process a service spawns (so a child that forks its own children is still accounted to the service), and clean kill (terminating the entire tree at once, so nothing is orphaned). It does not use cgroups for resource accounting or limits.

The split into sub-cgroups serves a real purpose: it satisfies cgroup v2's "no internal processes" rule, and it lets peinit kill a service's hooks or health checks without touching the main process.

When an old tree cannot be removed — because a process survived SIGKILL in D-state and leaked it — peinit creates a fresh generational tree (<service>.gen<N>) for the next start, so a stuck old instance never blocks a new one. Leaks are surfaced in the service's warnings; they are a sign of an underlying I/O fault, covered in Keeping services running.

Edit this page