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')