Consider an interactive shell running a pipeline:
The pipeline contains three processes. When the user presses Ctrl+C, the terminal must interrupt the pipeline as one unit without terminating the shell that launched it.
A parent-child relationship is not enough to express that requirement. The processes might have different parents, and a shell may manage several unrelated pipelines at once.
Unix solves the problem with three layers:
These structures explain foreground and background execution, Ctrl+C, Ctrl+Z, jobs, fg, bg, terminal access, and the way a shell manages an entire pipeline.
Every process has several identifiers with different purposes.
| Identifier | Meaning | Common query |
|---|---|---|
| PID | Identifies one process | getpid() |
| PPID | Identifies its parent | getppid() |
| PGID | Identifies its process group | getpgrp() or getpgid() |
| SID | Identifies its session | getsid() |
The hierarchy has strict shape:
Every process belongs to exactly one process group. Every process group belongs to exactly one session. A process group cannot contain processes from different sessions.
These relationships survive program replacement. Executing a new program changes the process's code and memory image, but it does not change the PID, PGID, or SID.
A child created by fork() initially inherits its parent's PGID and SID. The shell can then move that child into the group selected for its job before the child executes the requested program.
A process group is a set of processes that the kernel can address as one unit.
The group is identified by a process group ID, or PGID. One member is initially used as the process group leader, meaning its PID equals the PGID.
If a pipeline's first child has PID 8201, a shell commonly uses 8201 as the PGID and places the rest of the pipeline in that group:
For the job grep ... | sort | less:
| PID | Command | PGID |
|---|---|---|
| 8201 | grep | 8201 |
| 8202 | sort | 8201 |
| 8203 | less | 8201 |
The group takes its ID from the first process in the pipeline.
The leader is not a manager with special scheduling power. Its PID simply supplies the group's identifier. The process group can continue to exist after that leader terminates as long as other members remain.
Process groups provide a stable way to:
They are unrelated to Unix file-permission groups and user group IDs.
On Linux, a process group is also different from a thread group. Threads in one process share a thread-group ID used by Linux's threading model; PGID belongs to terminal job control and can group several separate processes.
The POSIX interface is:
The first argument selects the process whose group will change. The second selects the destination process group.
Two zero values provide useful shorthand:
Here, the first 0 means the calling process and the second means that process's own PID. The caller therefore creates a new process group and becomes its leader.
To join another group in the same session:
A process cannot use this interface to join a group in another session. A session leader's process group also cannot be changed, because doing so would break the process-group and session hierarchy.
The query interfaces are:
setpgid() RaceA job-control shell must establish the child's process group after fork() but before two things happen:
The parent and child run independently, so either may execute first.
A robust shell handles both schedules by attempting the placement from both sides:
| Side | What it does after the fork |
|---|---|
| Child | Calls setpgid(0, job_pgid), then execs the requested program |
| Parent | Calls setpgid(child_pid, job_pgid), then continues constructing the job |
Whichever call happens first establishes the intended relationship. If the child has already performed the placement and then completed exec(), the parent's late attempt can report EACCES; a shell must distinguish that expected race outcome from a genuinely incorrect group.
The child's call must occur before exec(). The parent is not allowed to change a child's group through setpgid() after that child has successfully executed a new program.
For a pipeline, the first child normally creates a group whose PGID equals its PID. Each later child joins that existing group. The parent shell repeats the appropriate setpgid() call for every child so it can safely manage the group without relying on child scheduling order.
This is a concrete example of defensive concurrency: both processes perform an idempotent setup action because neither controls which one runs first.
A session is a collection of one or more process groups.
The process that creates a session becomes its session leader, meaning its PID equals the new session ID.
Sessions define a boundary for terminal job control. A session may have one controlling terminal, and a terminal can control at most one session.
An interactive login commonly produces a structure like this:
All three process groups belong to one session, but only group 8201 currently owns foreground terminal access.
Not every session has a controlling terminal. Services launched without an interactive terminal commonly operate in sessions with no controlling terminal at all.
setsid()The interface for creating a session is:
On success, the caller:
Its PID, PGID, and SID therefore have the same numeric value immediately after the call.
setsid() fails if the caller is already a process group leader. This restriction preserves the rule that an entire process group belongs to one session. Otherwise, a leader could leave while the rest of its group stayed behind.
A common reliable sequence is:
The child is guaranteed not to be a process group leader at creation time because its PID differs from the process group it inherited from its parent. It can therefore call setsid().
Creating a new session is one ingredient in traditional daemonization. It detaches the process from the old controlling-terminal relationship. It does not close inherited terminal file descriptors, choose new standard streams, change the working directory, or define a service lifecycle by itself.
Modern service managers generally expect a service to remain in the foreground and let the manager provide isolation, logging, restart policy, and supervision. Self-daemonizing is useful only when the surrounding deployment model requires it.
A controlling terminal is not merely a file used for input and output. The kernel associates it with a session and records one process group in that session as the terminal's foreground process group.
The relevant interfaces are:
tcsetpgrp() can select only a nonempty process group in the same session as the caller and controlling terminal.
Foreground ownership is a property of the terminal, not a state independently stored as “foreground” on each process. A process is in the foreground when its PGID matches the terminal's recorded foreground PGID; otherwise, it is in a background process group.
This comparison lets the terminal driver enforce job-control behavior without understanding shell commands or pipeline syntax.
Suppose an interactive shell currently controls the terminal and launches a pipeline.
The shell performs a sequence conceptually like this:
Ownership returns to where it started, which is why a shell can run one job after another without the terminal ending up owned by a process that has exited.
The shell itself waits in a different process group while the job is foreground. This separation is why terminal-generated job-control signals can affect the job without affecting the shell.
The exact implementation must also coordinate signal masks, terminal modes, file descriptors, and errors. The sequence above isolates the structural role of process groups and the terminal.
When a command ends with &:
the shell still creates and tracks the job's process group, but it does not transfer foreground terminal ownership to that group. The shell remains foreground and immediately reads another command.
Background execution does not mean:
It means the job's process group is not the controlling terminal's foreground process group.
This distinction explains why appending & is not a complete method for running a reliable long-lived service.
Certain terminal control characters cause the terminal driver to send a signal to every member of its foreground process group.
Common defaults include:
SIGINTSIGQUITSIGTSTPIf the foreground group contains a three-process pipeline, all three processes are targeted. This is exactly why the shell grouped them.
The shell is normally in a separate background process group during this time, so it does not receive that terminal-generated signal. After the job exits or stops, the shell makes itself foreground again.
Applications can change their signal dispositions, so the result is not always termination or stopping. Process groups determine the targets; each target's signal policy determines its response.
Loading simulation...
Only the foreground process group may normally read from its controlling terminal.
If a background group attempts a terminal read, the terminal driver sends SIGTTIN to that process group. The default action stops the group.
This behavior prevents a background job from silently competing with the shell for keyboard input. A common interaction looks like:
The user can bring the job to the foreground and allow it to read:
Background terminal output is normally allowed. If the terminal's TOSTOP setting is enabled, however, a background write generates SIGTTOU, whose default action also stops the group.
A background process that ignores or blocks the applicable terminal-stop signal may receive an error or behave differently according to the terminal rule involved. Terminal job control is coordination, not an access-security boundary; normal file permissions still govern whether the terminal can be opened.
The kernel knows about processes, process groups, sessions, terminals, and state changes. It does not maintain Bash-style job numbers such as %1 or %2.
An interactive shell builds a job table that maps its own job identifiers to process groups and member processes.
For example:
Here, [1] is a shell-local job identifier. 9120 is a process ID and commonly also the new job's PGID. %1 is meaningful to that shell; it is not a kernel PID.
Typical job-control commands operate on the stored process group:
jobs displays jobs known to the current shell.fg %1 continues job 1 and gives its group the terminal.bg %1 continues job 1 while leaving the shell in the foreground.Job tables differ between shells and are not shared automatically between separate shell processes.
Noninteractive shells and environments without a controlling terminal may disable job control. Code should not assume that every launched pipeline receives interactive foreground/background behavior.
waitpid() can select children by process group. Passing a value less than -1 selects any child whose PGID is the absolute value:
The options allow a shell to observe more than termination:
WUNTRACED reports a child that has stopped.WCONTINUED reports a stopped child that has resumed.One successful call reports one child state change. A pipeline contains several children, so the shell must keep per-process state and continue collecting changes.
A shell considers a job completed only when all relevant members have terminated. It can report a job as stopped when its member state indicates that the job is no longer executing, then reclaim the terminal and return a prompt.
This is why job control needs both kernel grouping and user-space bookkeeping. A PGID identifies the unit, while the shell's job table records the state of every member.
The kill() interface can target a process group by using the negative PGID:
killpg(job_pgid, SIGTERM) expresses the same group-oriented intent through a dedicated function.
At a shell prompt, -- prevents a negative PGID from being parsed as a command option:
This sends SIGTERM to every process the caller is permitted to signal in process group 8201.
Group signaling is critical for supervision. Terminating only the first process in a pipeline can leave its partners running, blocked on pipes, or holding resources. The supervisor should target the ownership unit it actually created.
Negative and zero PID forms have broad meaning in kill(). Production code must validate the PGID and avoid using an uninitialized or unresolved value as a group target.
This program creates two children in one process group, then sends SIGTERM to the group rather than to either child individually.
Compile and run it:
The two child PIDs differ, but their printed PGIDs match. The parent remains outside that group, so the negative-PGID kill() targets both children without terminating the parent.
The calls in both parent and child mirror the race-safe setup used by shells. The child establishes its own group before continuing, while the parent ensures the group exists before performing group operations.
The following program prints the identifiers relevant to job control:
Compile it and compare foreground and background launches from an interactive shell:
TPGID is the terminal's foreground process group. In the foreground run, it should match the program's PGID. In the background run, it should normally match the shell's PGID instead.
Linux ps can display the same hierarchy:
For a broader view:
Processes belonging to the same pipeline should share a PGID. Processes managed by the same interactive login shell normally share a SID. A TPGID of -1 commonly indicates that no controlling-terminal foreground group applies.
Stopping a process preserves its execution state without terminating it.
The terminal's Ctrl+Z character normally sends SIGTSTP to the foreground process group. Once the shell observes that the job stopped, it reclaims the terminal and reports the job.
To resume in the background, bg %1 makes the shell send SIGCONT to the group while keeping its own PGID in the terminal foreground.
To resume in the foreground, fg %1 first makes the job's PGID the terminal foreground group, then sends SIGCONT and waits for the job.
The ordering matters. A resumed program that immediately reads from the terminal must already be foreground, or the terminal can stop it again with SIGTTIN.
An orphaned process group is not simply a group containing an orphan process.
A process group is considered orphaned when none of its members has a parent in a different process group within the same session. In practical terms, there is no longer an external parent in that session positioned to perform ordinary job control for the group.
This matters when the group contains stopped processes. If the shell or other supervising parent disappears, those processes could remain stopped forever with nobody available to continue them.
On Linux, when termination causes a process group containing stopped members to become orphaned, the kernel sends SIGHUP followed by SIGCONT to each member. The first signal reports the lost supervisory relationship; the second ensures that stopped members resume and can respond or terminate.
This rule belongs to process-group job control. It is distinct from the reparenting of an individual child whose parent exits.
A background job still belongs to the shell's session and may still have the shell's terminal open. Closing the terminal or ending the login can therefore affect it.
Several mechanisms can contribute:
SIGHUP to jobs it manages when it exits.Shell features such as nohup and disown adjust parts of this behavior, but they are not equivalent to creating a new session. nohup primarily changes hangup-signal handling and redirects terminal-oriented output; disown changes the shell's job bookkeeping according to that shell's rules.
A reliable service should have an explicit supervisor and deliberate standard-input, standard-output, session, and shutdown policies. Merely adding & addresses only foreground ownership while the shell remains alive.
SIGINT and SIGTSTP to that entire group.SIGTTIN.setpgid() in both parent and child to avoid creation races, tcsetpgrp() to transfer terminal ownership, and group-aware waits to track job state.setsid() creates a session and removes the caller from its previous controlling-terminal relationship, but it is only one part of service detachment.& changes foreground ownership; it does not create an independent, supervised service.5 quizzes