What is a Process Group?
A process group is simply a collection of one or more processes that share the same Process Group ID (PGID). Think of it as a team of processes that were created together to do a job.
- Every process belongs to exactly one process group.
- The process group leader is the process that created the group. Its PID becomes the PGID of the entire group.
- When you fork a child, the child inherits the parent’s PGID automatically.
- A process group has a lifetime: it starts when the leader creates it, and ends when the last member leaves (exits or joins another group).
The PGID of any process is accessible via /proc/PID/stat on Linux.
What is a Session?
A session is a collection of process groups. All the process groups you create during a single login belong to one session.
- Every process has a Session ID (SID) inherited from its parent.
- The session leader is the process that created the session (usually the login shell). Its PID equals the SID.
- All processes in a session share a single controlling terminal.
The Controlling Terminal
When the session leader opens a terminal for the first time, that terminal becomes the controlling terminal for the session, and the session leader becomes the controlling process.
- A terminal can only be the controlling terminal for one session at a time.
- At any moment, one process group in the session is the foreground process group; all others are background process groups.
- Only the foreground group can read from the terminal.
- Typing terminal characters sends signals to the entire foreground group:
| Key | Signal | Default Action |
|---|---|---|
Ctrl+C |
SIGINT | Terminate the process |
Ctrl+\ |
SIGQUIT | Terminate + core dump |
Ctrl+Z |
SIGTSTP | Stop (suspend) the process |
If the controlling process dies and the connection is lost, the kernel sends SIGHUP to the controlling process to notify it.
Visual Diagram: Sessions, Groups & Processes
| SESSION 400 (login shell is session leader) | ||||
| Process Group 400 (Shell) |
←→ | Process Group 658 (Background Job) |
←→ | Process Group 660 (FOREGROUND Job) ★ |
| bash PID=400 |
find PID=658 wc PID=659 |
sort PID=660 uniq PID=661 |
||
| Controlling Terminal Foreground PGID = 660 | Controlling SID = 400 |
||||
Based on Figure 34-1 from TLPI. ★ Only PG 660 can read from the terminal.
Shell Job Control — The Big Picture
Shell job control is the main reason these abstractions exist. Here is what happens during an interactive login session:
- You log in. The login shell becomes the session leader, controlling process, and sole member of its own process group.
- Every command or pipeline you type creates a new process group.
- If you end the command with
&, it becomes a background process group. Otherwise it becomes the foreground process group. - All processes created during the login belong to the same session.
- In a graphical environment, each terminal window has its own separate session.
cat /proc/$$/stat The 5th field is PGID and the 6th field is the session ID.Code Example 1 — Reading PID, PGID, SID of a Process
This program prints the PID, process group ID, and session ID of itself. Run it from a shell to see how a child inherits the parent’s PGID and SID.
/* show_ids.c
* Compile: gcc -o show_ids show_ids.c
* Run: ./show_ids
*/
#include <stdio.h>
#include <unistd.h>
int main(void)
{
printf("=== Process Identity Information ===\n");
printf("PID (Process ID) : %ld\n", (long)getpid());
printf("PPID (Parent PID) : %ld\n", (long)getppid());
printf("PGID (Process Group ID) : %ld\n", (long)getpgrp());
printf("SID (Session ID) : %ld\n", (long)getsid(0));
/* Fork a child — child inherits PGID and SID */
pid_t child = fork();
if (child == 0) {
printf("\n--- Child Process ---\n");
printf("Child PID : %ld\n", (long)getpid());
printf("Child PGID : %ld (same as parent)\n", (long)getpgrp());
printf("Child SID : %ld (same as parent)\n", (long)getsid(0));
return 0;
}
/* Parent waits */
wait(NULL);
return 0;
}
/*
* Expected output (PIDs will differ on your system):
*
* === Process Identity Information ===
* PID (Process ID) : 12345
* PPID (Parent PID) : 1200 <- shell
* PGID (Process Group ID) : 12345 <- process is its own group leader
* SID (Session ID) : 1200 <- shell's PID is session ID
*
* --- Child Process ---
* Child PID : 12346
* Child PGID : 12345 (same as parent)
* Child SID : 1200 (same as parent)
*/
Code Example 2 — Verifying /proc/PID/stat Fields
/* read_proc_stat.c
* Reads process group ID and session ID directly from /proc
* Compile: gcc -o read_proc_stat read_proc_stat.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
char path[64];
FILE *fp;
int pid, ppid, pgrp, session;
char comm[256], state;
/* Build the path to our own stat file */
snprintf(path, sizeof(path), "/proc/%d/stat", (int)getpid());
fp = fopen(path, "r");
if (fp == NULL) {
perror("fopen /proc/PID/stat");
return 1;
}
/* Format: pid (comm) state ppid pgrp session ... */
fscanf(fp, "%d %s %c %d %d %d",
&pid, comm, &state, &ppid, &pgrp, &session);
fclose(fp);
printf("From /proc/%d/stat:\n", pid);
printf(" PID = %d\n", pid);
printf(" Command = %s\n", comm);
printf(" State = %c\n", state);
printf(" PPID = %d\n", ppid);
printf(" PGRP = %d (process group ID)\n", pgrp);
printf(" SESSION = %d (session ID)\n", session);
/* Cross-check with API calls */
printf("\nCross-check with API:\n");
printf(" getpgrp() = %d\n", (int)getpgrp());
printf(" getsid(0) = %d\n", (int)getsid(0));
return 0;
}
/*
* Expected output:
* From /proc/12345/stat:
* PID = 12345
* Command = (read_proc_sta)
* State = S
* PPID = 1200
* PGRP = 12345
* SESSION = 1200
*
* Cross-check with API:
* getpgrp() = 12345
* getsid(0) = 1200
*/
