1

Does the Linux shell do a fork/exec and then waitpid() to get the return code to populate the $? variable, each time it executes something?

Scooter
  • 6,802
  • 8
  • 41
  • 64

1 Answers1

3

Yes, that's exactly what it does.

You can see this for yourself, if you are interested, by running the shell under strace (a tool that intercepts and prints all system calls a program makes).

strace bash -c '/usr/bin/ls > /dev/null; echo $?'

This gives the following output, much trimmed:

execve("/usr/bin/bash", ["bash", "-c", "/usr/bin/ls > /dev/null; echo $?"], [/* 58 vars */]) = 0
[....]
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7ff16c3d69d0) = 1189
[....]
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 1189
[....]
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=1189, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
wait4(-1, 0x7ffd3096b290, WNOHANG, NULL) = -1 ECHILD (No child processes)
[....]
exit_group(0)                           = ?
+++ exited with 0 +++

I used a bash script with two commands because otherwise bash doesn't fork but just execs the command, replacing itself (a useful optimization I just discovered it does!)

In the output, clone is the system call behind the fork function, and likewise wait4 is called by waitpid. You don't see the exec because that happens in the child process, which we didn't ask strace to trace. If you add -f then it will do so, but will also trace the contents of the ls process.

ams
  • 24,923
  • 4
  • 54
  • 75
  • Thanks for the answer. Do you know how the value gets passed between processes - is it passed via a register or is there a set memory location used? – Scooter Nov 18 '15 at 12:45
  • When a process exits it calls `_exit(n)` (unless killed by a signal), and the kernel stores the exit code, `n`. The parent process can then retrieve the value using some variant of the `wait` system call. – ams Nov 18 '15 at 12:48