fg, bg, jobs, ctrl+z
Context
Process: A kernel level abstraction representing an executing program. It has a PID, PPID, GID, address space (with code, static data, stack, heap), a table of file descriptors etc.
Job: A shell level abstraction representing one or more processes grouped together into a single process group.
In Unix, processes can be paused by sending the SIGTSTP. The process is suspended and sent into the background
➜ sleep 100 && cat "Hello world" | echo
^Z # ctrl + z sends SIGTSTP to the process hence suspending it
[1] + 78159 suspended sleep 100
fg can be used to resume the paused process.
fg -> foreground
➜ fg
[1] + 78159 continued sleep 100
In case there are multiple suspended jobs. fg brings the most recently suspended job to the foreground.
➜ jobs
[1] suspended sleep 100
[2] - suspended sleep 100 # - means previous suspended job
[3] + suspended sleep 100 # + means current suspended job.
# invoking fg without arg will resume the current suspended job [3] and bring its process to the foreground
➜ fg
[3] 26648 continued sleep 100
#invoking fg with job ID resumes that job and brings its process to the foreground.
➜ jobs
[1] - suspended sleep 100
[2] + suspended sleep 100
➜ fg %1
[1] - 94189 continued sleep 100
jobs shows all background jobs. These are processes suspended or sent to the background.
Adding & after a command executes the command in a background process.
# executes the process in the background
➜ (sleep 100 && echo 'Will be sent to background') &
[3] 94611 # 3 is the job id, 94611 is the process PID
➜ jobs
[1] - suspended sleep 100
[2] + suspended sleep 100 # the most recently suspended job.
[3] running ( sleep 100 && echo 'Will be sent to background'; ) # the running background process
More details on job vs process
Let’s walk through how the shell (zsh for example) creates a job and use it to manage processes that should be managed as one unit of execution.
This command below requires the shell to create two processes
One to run wc |. Another to run cat
➜ wc | cat
The shell will create two processes and place them in a single process group. The ID of the process group is the ID of the first process in the group (The leader process).
➜ ps -o pid,ppid,pgid,tpgid,comm | grep -iE 'PPID|50626'
PID PPID PGID TPGID COMM
50626 23401 50626 51212 wc
50627 23401 50626 51212 cat
Hitting ctrl + z suspends all processes in the process group. The shell then persists a job record of the process group. Creating a job records helps the shell track process groups that have been suspended or are running in the background.
# Both processes in the group are suspended.
➜ wc | cat
^Z
[1] + 50626 suspended wc |
50627 suspended cat
# Job record created after suspension
➜ jobs
[1] + suspended wc | cat
Using fg or bg, the job can be resumed to run in the foreground or background. These will resume the processes (by sending SIGCONT to the process group) in the process group and bring one of them to the foreground in the case of fg.
➜ ~ fg
[1] + 50626 continued wc |
50627 continued cat
wc: stdin: read: Interrupted system call # wc is not robost enough to resume when stdin read is interruped by a syscall. It should have retry the read :(
fg vs bg
# start and suspend a foreground process
➜ ~ sleep 100
^Z
[1] + 59312 suspended sleep 100
➜ ~ jobs
[1] + suspended sleep 100
# Resume the suspended process and run in background
➜ ~ bg
[1] + 59312 continued sleep 100
➜ ~ jobs
[1] + running sleep 100
# Bring background process to foreground then suspend again
➜ ~ fg
[1] + 59312 running sleep 100
^Z
[1] + 59312 suspended sleep 100
# Resume suspended process and bring to foreground then suspend
➜ ~ fg
[1] + 59312 continued sleep 100
^Z
[1] + 59312 suspended sleep 100
➜ ~ jobs
[1] + suspended sleep 100
# Kill suspended job. Running background jobs can also be terminated by providing the job ID.
➜ ~ kill %1
[1] + 59312 terminated sleep 100
➜ ~ jobs
➜ ~