Foreground and Background Process Groups

 

34.5 — Foreground and Background Process Groups
Chapter 34 · The Linux Programming Interface · EmbeddedPathashala
tcgetpgrp(), tcsetpgrp(), and terminal I/O arbitration
Key Terms in This Section

Foreground Process Group Background Process Group tcgetpgrp() tcsetpgrp() TIOCGPGRP ioctl TIOCSPGRP ioctl Terminal I/O arbitration SIGTTIN SIGTTOU

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 */
Implementation detail: On Linux and most UNIX systems, 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
 */

Interview Questions — Section 34.5

Q1. What is the difference between the foreground and background process group?
The foreground process group is the one currently allowed to read from the controlling terminal, and the one that receives terminal-generated signals (SIGINT, SIGTSTP, SIGQUIT). All other process groups in the session are background groups. There can be at most one foreground group at a time per terminal.
Q2. What happens when a background process tries to read from the terminal?
The terminal driver sends SIGTTIN to the background process’s entire process group. The default action of SIGTTIN is to stop (suspend) the process. The user must bring it to the foreground with the fg command so it can complete the read.
Q3. What do tcgetpgrp() and tcsetpgrp() do?
tcgetpgrp(fd) queries the terminal driver for the PGID of the current foreground process group. tcsetpgrp(fd, pgid) tells the terminal driver to change the foreground process group to pgid. These are the functions that job-control shells use to move jobs between foreground and background. Internally they use TIOCGPGRP and TIOCSPGRP ioctls.
Q4. Can a process send tcsetpgrp() to make itself the foreground even if it belongs to a different session?
No. tcsetpgrp() requires that the pgid argument match a process group within the calling process’s own session. Attempting to set a PGID from a different session results in EPERM. This enforces the one-session-per-terminal rule.
Q5. What does SIGTTOU do and when is it generated?
SIGTTOU is generated when a background process tries to write to the controlling terminal and the terminal’s TOSTOP flag is set. Its default action, like SIGTTIN, is to stop the process. If TOSTOP is not set (the default), background processes can write freely. SIGTTOU is also generated if a background process calls terminal control functions like tcsetpgrp() or tcsetattr() on its controlling terminal.

Leave a Reply

Your email address will not be published. Required fields are marked *