How to Start a Linux Process in Suspended State (Like Ctrl+Z) with PID Capture


2 views

When debugging or managing processes, we often need to launch applications in a suspended state - exactly like hitting Ctrl+Z immediately after execution. This technique proves invaluable for:

  • Debugging initialization routines
  • Process orchestration in shell scripts
  • Race condition testing
  • Delayed service startups

kill -STOP /h2>

The most reliable method uses the process substitution technique combined with signals:


#!/bin/bash
# Launch process and immediately suspend
your_command &
PID=$!
kill -STOP $PID
echo "Process $PID suspended. Resume with: kill -CONT $PID"

gdbserver /h2>

For more control (especially with multi-process applications):


gdbserver --attach :1234 pidof your_program &
gdb -ex "set detach-on-fork off" -ex "catch exec" -ex run

When working with containers or namespaces:


unshare -fp --mount-proc bash -c "your_command & export PID=\$!; kill -STOP \$PID; echo \$PID" > pidfile

Here's a robust function for shell scripts:


launch_suspended() {
    local cmd=$1
    local outfile=$(mktemp)
    
    ( $cmd & echo $! > $outfile; kill -STOP $! ) &
    
    while [ ! -s $outfile ]; do
        sleep 0.1
    done
    
    PID=$(cat $outfile)
    rm -f $outfile
    echo $PID
}

Usage example:


PID=$(launch_suspended "node server.js")
# ... perform pre-resume actions ...
kill -CONT $PID

Be aware of:

  • Signal timing races (use synchronization files as shown above)
  • TTY handling differences between foreground/background
  • Child process inheritance of signal states
  • Namespace propagation of signals in containerized environments

When automating process management in Linux, there are scenarios where you need to start a process but immediately suspend it – exactly like hitting Ctrl+Z manually. This is particularly useful when:

  • Building process supervision systems
  • Implementing custom job control
  • Debugging initialization sequences
  • Creating process templates for quick spawning

The most reliable method uses the shell's built-in job control combined with process signals:


# Basic approach
(sleep 10 &) 
kill -STOP $!
echo "PID $! is now suspended"

For robust scripting, we need to handle process groups and avoid shell job control interference:


#!/bin/bash

# Start process in background with new process group
setsid bash -c 'exec your_command_here' &> /dev/null &

# Immediately suspend it
kill -STOP $!

# Verify process state
if ps -o state= -p $! | grep -q 'T'; then
    echo "Process $! successfully suspended (Stopped state)"
    # Store PID for later resumption
    echo "$!" > suspended.pid
else
    echo "Failed to suspend process" >&2
    exit 1
fi

When debugging complex process startups, you might want to examine memory maps or file descriptors before execution continues:


# Suspend nginx worker at launch
(setsid /usr/sbin/nginx -g "daemon off;" &> /dev/null &)
kill -STOP $!
NGINX_PID=$!

# Inspect before continuation
ls -l /proc/$NGINX_PID/fd
pmap $NGINX_PID

# When ready to continue
kill -CONT $NGINX_PID

Other approaches with different trade-offs:

Using gdb


gdb -ex "set breakpoint pending on" \
    -ex "break main" \
    -ex "run" \
    -ex "detach" \
    --args your_program args

Using strace


strace -e inject=STOP:signal=STOP your_program
  • Processes started in suspended state won't respond to normal job control commands
  • Some applications may have signal handlers that interfere with SIGSTOP
  • Child processes spawned after suspension won't inherit the stopped state
  • Always verify process state with ps -o state= -p PID (look for 'T')