Skip to content
VLSI Mentor

Linux & Bash · Part I · Module 7 · Processes, Jobs and Signals

Parent, Child and Inheritance

Every process on the machine was started by another one, and received a specific, finite list of things from it. Knowing exactly what is on that list — and that nothing travels back up it — settles a surprising number of arguments.

Intermediate18 min readLinuxPart IFundamentalsProcesses

Part I · Module 7 · Chapter 7.4 · Parent, Child and Inheritance

Chapter 7.1 said a child gets a copy of its parent's working directory and environment, and that changes flow downward only. That was asserted and demonstrated once. This chapter makes it precise, because three separate things an engineer runs into — a script that cannot change your directory, a job still running after its shell is gone, and a Z in the ps output — are all the same rule seen from different angles.

1. The Tree

Every process has exactly one parent. Every process except PID 1, which the kernel starts at boot. Follow any chain of parents and it terminates there.

That shape — many things, each with one parent, one root — is a tree.

A process tree. The first process started at boot sits at the top. Below it is the program that handled your login, below that your shell, and below the shell the commands you have started. Every process except the first has exactly one parent.a command yourangrepa long jobrun_sim.shyour shellbashyour loginsessionone per loginthe firstprocessnumber 1, started atboot
Figure 1 — the process tree above a command you have just run. The first process is started by the kernel at boot and has no parent; everything else descends from it. Your shell is a child of whatever handled your login, and the commands you type are children of your shell. The arrangement is strict: every process has exactly one parent, and the chain always terminates at the top.

You can see your own branch of it with the tools from Chapter 7.2:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ echo $$
11286

$ ps -o pid,ppid,comm -p 11286
  PID  PPID COMM
11286 11274 bash

$ ps -o pid,ppid,comm -p 11274
  PID  PPID COMM
11274 24759 /bin/zsh

Two hops and the shape is visible: 24759 started 11274, which started 11286, which is you.

Seeing the tree directly

On Linux, ps can draw it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ ps -ef --forest | head -12
  UID   PID  PPID   C STIME TTY          TIME CMD
    0     1     0   0 Mar28 ?        123:17 /sbin/init
    0   318     1   0 Mar28 ?          0:14  \_ /usr/lib/systemd/systemd-journald
  501   639     1   0 Mar28 ?          8:02  \_ /usr/libexec/session-manager
  501 11274   639   0 13:11 ttys000    0:00      \_ /bin/zsh
  501 11286 11274   0 13:11 ttys000    0:00          \_ bash
  501 12547 11286   0 13:22 ttys000    0:00              \_ sleep 300

The indentation is the tree. pstree does the same thing more compactly if it is installed:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ pstree -p 11286
bash(11286)───sleep(12547)

Walking down instead of up

-o ppid walks up. To walk down — what did this process start? — use pgrep -P:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ pgrep -P $$
12547
12560

Two direct children of your shell. Combine it with -a on Linux to see what they are:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ pgrep -aP $$
12547 sleep 300
12560 /bin/bash ./run_sim.sh

This is the single most useful question to ask about a long-running job: what has it actually started? Chapter 7.2 ended with exactly that diagnosis.

2. What a Child Inherits

When a process starts a child, the child begins as a near-copy of the parent. "Near" is doing some work there, so here is the list.

Inherited — the child starts with a copy:

ThingNote
The working directoryRelative paths in the child resolve from the parent's directory
The environmentEvery exported variable, by value
The user and groupWhich is why permissions behave predictably
Open filesIncluding the three standard streams — Module 9
The umaskChapter 5.6's default-permission mask
The controlling terminalWhich is why your output appears on your screen — and Chapter 7.8's subject
Resource limitsMaximum open files, maximum memory, and so on
Signal dispositionsWhich signals are ignored — Chapter 7.6

Not inherited — the child gets its own:

ThingNote
Its PIDA new number, always
Its parentThe parent is, by definition, the process that started it
Its accumulated CPU timeStarts at zero
Non-exported shell variablesSee the warning below
Shell functions and aliasesThey live inside one shell process, not in the environment

3. One Way Only

Here is the rule stated as sharply as it can be:

Chapter 7.1 demonstrated this with cd. Here it is with the full picture of what the child can and cannot touch:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
#!/bin/bash
echo "  child PID $$, parent $PPID"
echo "  started in $(pwd)"
cd /home/engineer/project/rtl
export MARKER=set-by-child
umask 077
echo "  now in $(pwd), MARKER=$MARKER, umask $(umask)"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ pwd
/home/engineer/project
$ echo "MARKER=[$MARKER]"
MARKER=[]
$ umask
0022

$ ./changer.sh
  child PID 12545, parent 12537
  started in /home/engineer/project
  now in /home/engineer/project/rtl, MARKER=set-by-child, umask 0077

$ pwd
/home/engineer/project
$ echo "MARKER=[$MARKER]"
MARKER=[]
$ umask
0022

Every one of the child's three changes succeeded — it printed proof of all three. Every one of them died with the child. Nothing leaked upward.

This is not a limitation to work around. It is the property that makes a shared machine usable: a script somebody else wrote cannot silently change your working directory, your variables or your file-creation defaults, no matter what it does.

The two things that genuinely do affect your shell

Because the one-way rule is absolute, the only way to change your current shell is to not create a child at all.

Run it in the shell you are in — source. source script.sh (or its one-character alias .) reads the file and executes its lines in the current shell process, exactly as if you had typed them:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ ./changer.sh          # a child process: nothing persists
$ pwd
/home/engineer/project

$ source changer.sh     # the SAME process: everything persists
  child PID 11286, parent 11274
$ pwd
/home/engineer/project/rtl
$ echo $MARKER
set-by-child

Look at the PID the script printed the second time: 11286 — your shell's own PID. No child was created, so there was no copy, so the changes are yours. This is why environment setup scripts are always sourced and never executed, and it is why the instructions for an EDA tool invariably say source setup.sh rather than ./setup.sh.

Use a shell function. A function defined in your shell also runs in your shell, with the same consequence. Chapter 18.1 covers functions; the process-level point is that both exceptions work the same way — by not forking.

4. When the Parent Dies First

Processes do not end in a tidy order. A parent can perfectly well exit while its children are still working, and the question of what happens then has a precise answer — one that explains a great deal of real behaviour on shared machines.

Here is the experiment. A script starts a child and then waits:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
#!/bin/bash
sleep 300 &
echo "child PID $!"
sleep 300

Run it in the background, note its PID, find its child, then destroy the parent outright:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ ./parent.sh &
[1] 12298
child PID 12302

$ pgrep -P 12298
12302
12303

$ ps -o pid,ppid,comm -p 12302
  PID  PPID COMM
12302 12298 sleep

$ kill -9 12298
[1]+  Killed                  ./parent.sh

$ ps -o pid,ppid,comm -p 12302,12303
  PID  PPID COMM
12302     1 sleep
12303     1 sleep

Two things happened, and both matter:

  1. The children survived. Destroying the parent as abruptly as it is possible to destroy anything — Chapter 7.7 explains what -9 is — did not touch them at all. They were still sleeping out their 300 seconds.
  2. Their PPID changed from 12298 to 1.

This is the single most practically important fact in the chapter, for two opposite reasons:

  • It is a nuisance when you want things gone. Killing the wrapper script that launched a regression leaves the regression running, now reporting to PID 1 and no longer findable by walking down from the thing you killed. Chapter 7.7 covers killing a whole group of processes rather than one.
  • It is a tool when you want things to persist. A job deliberately orphaned keeps running after the shell that started it is gone. That is most of the mechanism behind Chapter 7.8.

It also explains a ps row you will now recognise. A process showing PPID 1 that is clearly not a system service was orphaned at some point — the thing that started it is gone, and PID 1 picked it up.

5. When the Child Dies First

The other order has its own consequence, and its own piece of vocabulary.

When a process exits, it does not disappear immediately. It leaves behind an exit status — a small number saying how it went — and that status is addressed to its parent. The kernel keeps a minimal record of the dead process alive until the parent collects it.

A process in that state is a zombie, shown as Z in the stat column from Chapter 7.2. It is not running. It uses no memory and no CPU. It is an entry in a table, holding a number, waiting to be read.

Normally this lasts microseconds: the parent collects the status and the entry vanishes. Zombies pile up when a parent is written badly — it starts children and never collects their statuses — and each one occupies a PID-table slot.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ ps -eo pid,ppid,stat,comm | awk '$3 ~ /^Z/'
12901 12880 Z    defunct
12902 12880 Z    defunct

6. Reading a Real Situation

This all comes together the first time something is running that you did not start and cannot immediately explain.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ ps -u $USER -o pid,ppid,etime,stat,cmd --sort=-etime | head -4
  PID  PPID     ELAPSED STAT CMD
 9981     1 02-04:11:32 S    /bin/bash ./nightly_regress.sh
12811  9981    01:22:04 R    python3 regress.py --seed 4
12834  9981    01:21:58 R    python3 regress.py --seed 5

Three rows, and the PPID column tells the whole story:

  • 9981 has PPID 1. It is an orphan. The terminal that launched it two days ago is long gone, and PID 1 adopted it. Not a problem — just a fact about its history.
  • 12811 and 12834 have PPID 9981. They are its children. The two-day-old script is not stuck; it is supervising.

And now a consequence worth internalising before Chapter 7.7. If you kill 9981:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ kill 9981

$ ps -o pid,ppid,etime,cmd -p 12811,12834
  PID  PPID     ELAPSED CMD
12811     1    01:24:30 python3 regress.py --seed 4
12834     1    01:23:58 python3 regress.py --seed 5

The supervisor is gone. Both simulations are still running, now orphaned and adopted by PID 1 — and no longer supervised by anything, so nothing will collect their results, start the next seed, or clean up after them.

Killing the parent of a job tree is almost never what you actually want.

Practice

1 — Walk up to PID 1. Start from $$ and look up each PPID in turn until you reach 1. Count the hops.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
p=$$; while [ "$p" -ne 1 ]; do ps -o pid,ppid,comm -p "$p" | tail -1; p=$(ps -o ppid= -p "$p" | tr -d ' '); done

(The while loop is Chapter 17.4's subject — run it now and read it later.)

2 — Walk down. Start two background jobs and list your shell's children with pgrep -P $$. Then start a script that itself starts something, and find the grandchildren by running pgrep -P on the child's PID.

3 — Prove exported and non-exported differ. Set one variable with X=1 and another with export Y=2, then run bash -c 'echo "[$X] [$Y]"'. Explain the result before reading §2 again.

4 — Prove sourcing is different. Write a two-line script that does cd /tmp and echo $$. Run it with ./script.sh, note the PID and your directory. Then run it with source script.sh and note both again. The PID is the tell.

5 — Make an orphan. Reproduce §4's experiment: a script that backgrounds a sleep 300 and then sleeps itself. Kill the script and confirm with ps -o pid,ppid that the child survived and its PPID is now 1.

6 — Find the orphans already on the machine. List every process of yours whose parent is PID 1:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ps -u "$USER" -o pid,ppid,etime,cmd | awk '$2 == 1'

Some will be legitimate services. Anything that looks like your own work is something you detached, deliberately or otherwise.

Check Your Understanding

1. A script contains cd /scratch and export SCRATCH=/scratch. You run ./setup.sh. Afterwards neither has taken effect. What went wrong, and what is the fix?

2. You kill the shell script that launched a regression. The simulations keep going. Is this a bug?

3. ps shows a process of yours with PPID 1, started six hours ago. What can you conclude?

4. ps shows forty processes in state Z, all with the same PPID. What is the problem and where is it?

5. Why does running source on an untrusted script deserve more caution than running it as ./script.sh?

6. You want a script to leave you in a different directory when it finishes. What are your options?

7. Remember

  • Processes form a tree: one parent each, rooted at PID 1, which the kernel starts at boot.
  • ps -o ppid walks up; pgrep -P walks down. ps --forest and pstree draw the whole thing on Linux.
  • A child inherits the working directory, the environment, the user, open files, the umask, the controlling terminal, resource limits and signal dispositions.
  • A child does not inherit its parent's PID, its CPU time, non-exported variables, or shell functions and aliases.
  • Only exported variables cross the boundary. A plain assignment belongs to one shell process.
  • Inheritance is one-way and absolute. No child changes its parent. There is no flag for it.
  • source (or .) runs a file in the current shell, with no child, which is the only reason environment setup scripts work — and the reason sourcing an untrusted script is riskier than running it.
  • Killing a parent does not kill its children. They are orphaned and re-parented to PID 1.
  • PPID 1 on a non-service process means it was orphaned — its original parent is gone.
  • A zombie is a finished process whose exit status nobody collected. It uses nothing and cannot be killed; fix or end the parent, and PID 1 will clean up.
  • Killing the supervisor of a job tree leaves the jobs running, unsupervised. Almost never what you want.

You now know how processes relate to each other. The next chapter is about controlling the ones your own shell started — putting a long command into the background, pulling it back, and juggling several at once.

Where this fits

Part of the Linux & Bash curriculum.