What are Foreground and Background Groups?
The controlling terminal keeps track of which process group is the foreground process group. At any moment there is at most one foreground group; all other process groups in the session are background process groups.
The foreground/background distinction controls two things:
- Terminal input: Only the foreground group can freely
read()from the terminal. A background process that tries to read gets SIGTTIN, which stops it by default. - Terminal signals: When you type Ctrl+C, Ctrl+Z, or Ctrl+\, the signal goes to every process in the foreground group only.
It is theoretically possible for a session to have no foreground process group (if all foreground processes terminated). In practice this is rare — the shell typically notices via wait() and moves itself back to the foreground.
| Controlling Terminal | ||
| Foreground PGID = 660 | Session ID = 400 | Driver stores these values |
| Process Group 660 (FOREGROUND) Can read + receives signals |
↔ | Process Group 658 (BACKGROUND) SIGTTIN on read attempt |
Getting the Foreground Group: tcgetpgrp()
tcgetpgrp(fd) returns the PGID of the foreground process group for the terminal referred to by fd. The file descriptor must refer to the calling process’s controlling terminal.
If there is no foreground process group, it returns a value greater than 1 that does not match any existing process group.
#include <unistd.h>
pid_t tcgetpgrp(int fd);
/* Returns PGID of foreground process group, or -1 on error */
int tcsetpgrp(int fd, pid_t pgid);
/* Sets foreground process group to pgid.
* Restrictions:
* - Calling process must have a controlling terminal
* - fd must refer to that controlling terminal
* - pgid must match a process group in the calling process's session
* Returns 0 on success, -1 on error */
tcgetpgrp() and tcsetpgrp() are implemented using two unstandardised ioctl operations: TIOCGPGRP and TIOCSPGRP. The tcgetpgrp/tcsetpgrp functions are the portable, standardised wrappers.How the Shell Uses tcsetpgrp()
When you run a command in the foreground, the shell calls tcsetpgrp() to update the terminal’s foreground PGID to point to the new command’s process group. When the command finishes, the shell calls tcsetpgrp() again to restore itself as the foreground group.
This is the mechanism behind fg and bg commands: the shell simply calls tcsetpgrp() to move the terminal’s foreground PGID pointer.
Code Example 1 — Reading the Foreground Process Group
/* show_fg_pgrp.c
* Shows the foreground process group of the controlling terminal.
* Compile: gcc -o show_fg_pgrp show_fg_pgrp.c
* Run interactively (not with & ) so it is itself the foreground group.
*/
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
int main(void)
{
/* Open the controlling terminal */
int fd = open("/dev/tty", O_RDONLY);
if (fd == -1) {
fprintf(stderr, "open /dev/tty: %s\n", strerror(errno));
return 1;
}
/* Our own identity */
printf("Our PID = %ld\n", (long)getpid());
printf("Our PGID = %ld\n", (long)getpgrp());
printf("Our SID = %ld\n\n", (long)getsid(0));
/* Query the terminal for its foreground process group */
pid_t fg = tcgetpgrp(fd);
if (fg == -1) {
fprintf(stderr, "tcgetpgrp: %s\n", strerror(errno));
} else {
printf("Terminal foreground PGID = %ld\n", (long)fg);
if (fg == getpgrp())
printf(" → We ARE in the foreground group\n");
else
printf(" → We are NOT the foreground group\n");
}
close(fd);
return 0;
}
/* Run in foreground:
* $ ./show_fg_pgrp
* Our PID = 8001
* Our PGID = 8001
* Our SID = 1200
* Terminal foreground PGID = 8001
* → We ARE in the foreground group
*/
Code Example 2 — Simulating Foreground/Background Switch with tcsetpgrp()
/* fg_bg_switch.c
* Demonstrates how a shell-like program uses tcsetpgrp() to
* move a child to the foreground and back.
* Compile: gcc -o fg_bg_switch fg_bg_switch.c
* Must be run as the foreground process.
*/
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
int main(void)
{
int tty_fd = open("/dev/tty", O_RDWR);
if (tty_fd == -1) { perror("open /dev/tty"); return 1; }
pid_t shell_pgid = getpgrp();
printf("Shell PGID = %ld (currently in foreground)\n",
(long)shell_pgid);
printf("Terminal foreground PGID = %ld\n",
(long)tcgetpgrp(tty_fd));
/* Fork a child to represent a "job" */
pid_t child = fork();
if (child == -1) { perror("fork"); return 1; }
if (child == 0) {
/* Child: create new process group */
setpgid(0, 0);
printf("Child PID=%ld PGID=%ld — running as background\n",
(long)getpid(), (long)getpgrp());
/* Simulate some work */
sleep(2);
printf("Child done\n");
return 0;
}
/* Parent (shell): give child its own group */
setpgid(child, child); /* child's PGID = child's PID */
printf("\nBringing child (PGID=%ld) to foreground...\n",
(long)child);
/* Move child to foreground */
tcsetpgrp(tty_fd, child);
printf("Terminal foreground PGID now = %ld\n",
(long)tcgetpgrp(tty_fd));
/* Wait for child */
int status;
waitpid(child, &status, WUNTRACED);
/* Restore shell to foreground */
tcsetpgrp(tty_fd, shell_pgid);
printf("\nRestored shell (PGID=%ld) to foreground\n",
(long)shell_pgid);
printf("Terminal foreground PGID now = %ld\n",
(long)tcgetpgrp(tty_fd));
close(tty_fd);
return 0;
}
/*
* Shell PGID = 8500 (currently in foreground)
* Terminal foreground PGID = 8500
* Child PID=8501 PGID=8501 — running as background
*
* Bringing child (PGID=8501) to foreground...
* Terminal foreground PGID now = 8501
* Child done
*
* Restored shell (PGID=8500) to foreground
* Terminal foreground PGID now = 8500
*/
