Using Job Control Within the Shell

 

34.7.1 — Using Job Control Within the Shell
Chapter 34 · The Linux Programming Interface · EmbeddedPathashala
fg, bg, jobs, Ctrl+Z, SIGTTIN, SIGTTOU, and the TOSTOP flag
Key Terms in This Section

Job number %n fg command bg command jobs command Ctrl+Z → SIGTSTP SIGCONT (resume) SIGTTIN (background read) SIGTTOU (background write) TOSTOP flag stty tostop kill -STOP

What is Job Control?

Job control lets you manage multiple commands from a single shell. You can run some commands in the background (they run invisibly), bring them to the foreground, stop them, and restart them.

A job is a command or pipeline started by the shell. Each job gets a job number (shown in square brackets) and is placed in its own process group.

Shell Job Control Commands Quick Reference

Command / Key Signal Sent Effect
command & none Start job in background
fg [%n] SIGCONT Bring job to foreground (and resume if stopped)
bg [%n] SIGCONT Resume a stopped job in background
jobs none List all background jobs and their states
Ctrl+Z SIGTSTP → foreground group Stop the foreground job
Ctrl+C SIGINT → foreground group Terminate the foreground job
kill -STOP %n SIGSTOP → job Stop a background job
kill %n SIGTERM → job Terminate a background job

Job State Machine

From State Command/Signal To State
command & Running in Background
command (no &) Running in Foreground
Running in Foreground Ctrl+Z (SIGTSTP) Stopped in Background
Stopped in Background fg (SIGCONT) Running in Foreground
Stopped in Background bg (SIGCONT) Running in Background
Running in Background fg Running in Foreground
Running in Background Terminal read attempt Stopped (SIGTTIN)
Running in Foreground Ctrl+C (SIGINT) Terminated
Running in Background kill -STOP Stopped in Background

SIGTTIN: Background Process Reading from Terminal

Only the foreground job can read from the terminal. If a background job tries to read() from the terminal:

  1. The terminal driver sends SIGTTIN to the background process’s entire group.
  2. The default action stops the job.
  3. The user brings it to the foreground with fg, allows the read to complete.

Special cases: If the process is blocking or ignoring SIGTTIN, the read() fails with EIO instead.

SIGTTOU: Background Process Writing to Terminal (TOSTOP)

By default, background jobs CAN write to the terminal. But if you enable the TOSTOP flag (stty tostop), then background writes also stop the job with SIGTTOU.

SIGTTOU is also generated when a background process calls terminal control functions like tcsetpgrp(), tcsetattr(), etc. — regardless of TOSTOP.

Code Example 1 — Program That Responds to SIGTTIN and SIGTTOU

/* sigttin_demo.c
 * Demonstrates SIGTTIN when a background process tries to read.
 * Run this in the background: ./sigttin_demo &
 * Then wait — it gets stopped by SIGTTIN.
 * Bring to foreground with: fg
 * Compile: gcc -o sigttin_demo sigttin_demo.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>

static void sig_handler(int sig)
{
    char msg[80];
    snprintf(msg, sizeof(msg),
             "\n[PID %ld] Caught signal %d (%s)\n",
             (long)getpid(), sig, strsignal(sig));
    write(STDERR_FILENO, msg, strlen(msg));
}

int main(void)
{
    /* Catch SIGTTIN and SIGTTOU so we can observe them */
    struct sigaction sa;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    sa.sa_handler = sig_handler;
    sigaction(SIGTTIN, &sa, NULL);
    sigaction(SIGTTOU, &sa, NULL);
    sigaction(SIGCONT, &sa, NULL);

    printf("PID=%ld  PGID=%ld\n",
           (long)getpid(), (long)getpgrp());
    printf("Checking if I am in foreground...\n");
    fflush(stdout);

    /* Open controlling terminal */
    int tty = open("/dev/tty", O_RDWR | O_NOCTTY);

    /* Try to read — this will cause SIGTTIN if we are in background */
    printf("Attempting read from /dev/tty...\n");
    fflush(stdout);

    char buf[64];
    ssize_t n = read(tty == -1 ? STDIN_FILENO : tty,
                     buf, sizeof(buf)-1);
    if (n > 0) {
        buf[n] = '\0';
        printf("Read: %s\n", buf);
    } else if (n == 0) {
        printf("EOF from terminal\n");
    } else {
        perror("read");
    }

    if (tty != -1) close(tty);
    return 0;
}
/*
 * $ ./sigttin_demo &
 * [1] 9100
 * PID=9100  PGID=9100
 * Attempting read from /dev/tty...
 * [1]+ Stopped   ./sigttin_demo      ← stopped by SIGTTIN
 *
 * $ fg
 * ./sigttin_demo
 * hello                               ← you type this
 * Read: hello
 */

Code Example 2 — Simulating fg/bg Job State Transitions

/* job_state_demo.c
 * Shows job states by printing what signals a process receives.
 * Run: ./job_state_demo &
 * Then try: fg, Ctrl+Z, bg, kill -STOP %1, bg
 * Compile: gcc -D_GNU_SOURCE -o job_state_demo job_state_demo.c
 */
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>

static void handler(int sig)
{
    char msg[80];
    int n = snprintf(msg, sizeof(msg),
                     "[PID %ld] Signal: %s\n",
                     (long)getpid(), strsignal(sig));
    write(STDOUT_FILENO, msg, n);
}

int main(void)
{
    struct sigaction sa;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    sa.sa_handler = handler;

    /* Install handlers for all job-control signals */
    int sigs[] = { SIGTSTP, SIGSTOP, SIGCONT, SIGTTIN, SIGTTOU,
                   SIGHUP, SIGINT, SIGTERM, 0 };
    for (int i = 0; sigs[i]; i++) {
        /* Note: SIGSTOP and SIGKILL cannot be caught */
        if (sigs[i] != SIGSTOP)
            sigaction(sigs[i], &sa, NULL);
    }

    printf("Job state demo started.\n");
    printf("PID=%ld  PGID=%ld\n", (long)getpid(), (long)getpgrp());
    printf("Try: fg, Ctrl+Z, bg, kill -STOP %d, bg\n", (int)getpid());
    fflush(stdout);

    /* Loop printing heartbeat every 3 seconds */
    for (int tick = 1; ; tick++) {
        sleep(3);
        printf("  tick %d (still running, PID=%ld)\n",
               tick, (long)getpid());
        fflush(stdout);
    }
    return 0;
}
/*
 * Run in background, then interact:
 * $ ./job_state_demo &
 * $ fg                      → [PID 9200] Signal: Continued
 * Ctrl+Z                    → [PID 9200] Signal: Stopped
 * $ bg                      → [PID 9200] Signal: Continued
 * $ kill -STOP 9200         → job stops again
 * $ bg %1                   → [PID 9200] Signal: Continued
 */

Interview Questions — Section 34.7.1

Q1. What signal does Ctrl+Z generate and what does it do?
Ctrl+Z generates SIGTSTP (Terminal Stop). It is sent to all processes in the foreground process group. The default action is to stop (suspend) the processes. The job can later be resumed with fg (to foreground) or bg (to background), both of which send SIGCONT.
Q2. What is the difference between SIGTSTP and SIGSTOP?
Both stop a process, but SIGTSTP can be caught, blocked, or ignored — a process can install a handler for it. SIGSTOP cannot be caught, blocked, or ignored; it always stops the process. This is why programs like vi handle SIGTSTP (to save terminal state before stopping) but cannot handle SIGSTOP. The SIGTSTP handler pattern involves raising SIGSTOP to actually stop the process after doing cleanup.
Q3. What is SIGTTIN and what triggers it?
SIGTTIN is sent to a background process group when any member tries to read() from the controlling terminal. Since only the foreground job can read the terminal, this prevents background jobs from stealing terminal input. The default action stops the job. Special cases: if SIGTTIN is blocked or ignored, read() fails with EIO instead.
Q4. What is the TOSTOP terminal flag and how do you set it?
TOSTOP (Terminal Output Stop) is a terminal flag that, when set, causes SIGTTOU to be sent to background jobs that try to write to the controlling terminal. Without TOSTOP (the default), background jobs can write freely. You enable it with stty tostop. It can also be set programmatically with tcsetattr().
Q5. What does the %% notation mean in shell job control?
%% (or %+) refers to the “current job” — the job that was most recently stopped in the foreground with Ctrl+Z, or if no such job exists, the most recently started background job. %- refers to the previous current job. These shorthands allow fg %% and bg %% to act on the most relevant job without needing to type its number.
Q6. After a background job is stopped with Ctrl+Z, how do you resume it in the background?
Use the bg command: bg %1 (or just bg for the current job). The shell sends SIGCONT to the process group, which resumes all stopped processes in that group. The job then continues running in the background.

Leave a Reply

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