Process Groups in Linux System Programming: Complete Guide
Process Groups in Linux System Programming: Complete Guide
The Linux Programming Interface · EmbeddedPathashala
Key Terms in This Section
Getting the Process Group ID: getpgrp()
Every process can find its own process group ID using getpgrp(). This function takes no arguments and always succeeds.
There is also getpgid(pid) which returns the process group ID of any specified process (pass 0 for the calling process, same as getpgrp()).
#include <unistd.h>
pid_t getpgrp(void); /* Returns PGID of calling process — always succeeds */
pid_t getpgid(pid_t pid); /* Returns PGID of process 'pid'; 0 means calling process */
Changing the Process Group ID: setpgid()
setpgid(pid, pgid) changes the process group of a process.
| Argument | Meaning |
|---|---|
pid == 0 |
Apply to the calling process |
pgid == 0 |
Set PGID to equal the process’s own PID (creates a new group) |
pid == pgid |
Create a new group with this process as leader |
pid != pgid |
Move a process from one group to another (within same session) |
So these three calls are all equivalent — each creates a new group with the calling process as leader:
setpgid(0, 0);
setpgid(getpid(), 0);
setpgid(getpid(), getpid());
Rules and Restrictions on setpgid()
4 restrictions you must know:
- ESRCH —
pidmust be the calling process or one of its children. You cannot move another unrelated process. - EPERM — When moving between groups, all parties (calling process, target process, destination group) must be in the same session.
- EPERM —
pidcannot be a session leader. Session leaders are locked to their own group. - EACCES — A parent cannot change a child’s PGID after the child has called
exec(). This protects the newly exec’d program from surprise PGID changes.
The Race Condition Problem in Job-Control Shells
Job-control shells need to put all processes in a pipeline into the same new process group before any process starts running. But after fork(), the kernel can schedule either the parent or child first — there is no guaranteed order.
This creates a race condition:
- If the parent calls
setpgid(child, pgid)but the child already called exec(), the call fails with EACCES. - If the child calls
setpgid(0, pgid)but the parent already sent job-control signals, the child might miss them.
setpgid() immediately after fork(). The parent ignores EACCES errors (meaning the child already exec’d and changed its own group first). This way, one of the two calls will always succeed in time./* job_control_shell_pattern.c
* Demonstrates how a job-control shell sets child's PGID safely
* Compile: gcc -o jcs job_control_shell_pattern.c
*/
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
int main(void)
{
pid_t childPid;
/*
* pipelinePgid: the PGID all processes in this pipeline should share.
* For the FIRST process in a pipeline, this is set to 0 so setpgid()
* will use the child's own PID as the PGID (making it a group leader).
* For later processes, it is set to the PID of the first process.
*/
pid_t pipelinePgid = 0; /* 0 means "use child's own PID" */
childPid = fork();
switch (childPid) {
case -1:
perror("fork");
return 1;
case 0: /* ---- Child ---- */
/*
* Child sets its OWN PGID immediately after fork.
* If pipelinePgid==0, the child becomes the group leader.
*/
if (setpgid(0, pipelinePgid) == -1) {
perror("child setpgid");
/* In a real shell: handle error */
}
printf("Child PID=%ld PGID=%ld\n",
(long)getpid(), (long)getpgrp());
/* ... exec the actual command here ... */
return 0;
default: /* ---- Parent (shell) ---- */
/*
* Parent ALSO sets the child's PGID.
* One of the two setpgid() calls will win the race.
* If child already exec'd, we get EACCES — ignore it.
*/
if (setpgid(childPid, pipelinePgid) == -1) {
if (errno != EACCES) { /* EACCES is fine — child won the race */
perror("parent setpgid");
}
}
printf("Parent: child %ld assigned to PGID %ld\n",
(long)childPid, (long)pipelinePgid);
wait(NULL);
break;
}
return 0;
}
/*
* The key insight: both parent and child call setpgid().
* One will succeed, one may fail with EACCES — that is OK.
* This eliminates the race condition.
*/
Code Example 2 — Moving a Process to a Different Group
/* change_pgroup.c
* Demonstrates creating and moving between process groups
* Compile: gcc -o change_pgroup change_pgroup.c
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
static void print_ids(const char *label)
{
printf("[%s] PID=%ld PGID=%ld SID=%ld\n",
label,
(long)getpid(),
(long)getpgrp(),
(long)getsid(0));
}
int main(void)
{
print_ids("parent before fork");
pid_t child = fork();
if (child == -1) { perror("fork"); return 1; }
if (child == 0) {
/* --- CHILD --- */
print_ids("child after fork (inherits parent PGID)");
/* Create a NEW process group — child becomes group leader */
if (setpgid(0, 0) == -1) {
perror("setpgid");
return 1;
}
print_ids("child after setpgid(0,0) — new group leader");
/*
* Now create a grandchild. It will inherit this new PGID.
*/
pid_t gc = fork();
if (gc == 0) {
print_ids("grandchild — inherits child's PGID");
return 0;
}
wait(NULL);
return 0;
}
/* --- PARENT --- */
wait(NULL);
print_ids("parent after children finish");
return 0;
}
/*
* Sample output:
* [parent before fork] PID=500 PGID=500 SID=400
* [child after fork ...] PID=501 PGID=500 SID=400 <- inherits
* [child after setpgid(0,0)] PID=501 PGID=501 SID=400 <- new leader
* [grandchild ...] PID=502 PGID=501 SID=400 <- inherits child's PGID
* [parent after children] PID=500 PGID=500 SID=400
*/
