.. _ProcessCycle:

.. raw:: html

   <script>ODSA.SETTINGS.DISP_MOD_COMP = true;ODSA.SETTINGS.MODULE_NAME = "ProcessCycle";ODSA.SETTINGS.MODULE_LONG_NAME = "Process Life Cycle";ODSA.SETTINGS.MODULE_CHAPTER = "Processes and OS Basics"; ODSA.SETTINGS.BUILD_DATE = "2021-06-14 17:15:25"; ODSA.SETTINGS.BUILD_CMAP = false;JSAV_OPTIONS['lang']='en';JSAV_EXERCISE_OPTIONS['code']='java';</script>


.. |--| unicode:: U+2013   .. en dash
.. |---| unicode:: U+2014  .. em dash, trimming surrounding whitespace
   :trim:


.. This file is part of the OpenCSF eTextbook project. It was
.. auto-generated by scripts from the OpenDSA eTextbook project.
.. See https://OpenCSF.org for more details. OpenCSF is distributed
.. under a Creative Commons Attribution-NonCommercial 4.0 International
.. License (see http://creativecommons.org/licenses/by-nc/4.0/),
.. Copyright (c) 2019-2021 by Michael S. Kirkpatrick. OpenDSA is
.. distributed under an MIT open source license, Copyright (c) 2012-2021
.. by the OpenDSA Project Contributors.

.. avmetadata::
   :author: Michael S. Kirkpatrick
   :requires:
   :satisfies: Process Life Cycle
   :topic: Process Creation, Process Switching, Process Destruction

Process Life Cycle
==================

From a very high-level perspective, we can describe the life cycle of a process as a sequence of
:term:`events <event>`. The first event is the creation of the process as a new virtual memory
instance. The CPU will then begin executing the program code according to the
*von Neumann instruction cycle* until it reaches the ``halt`` instruction indicating the
program is complete. At that point, the process is destroyed and any resources (such as physical
memory) associated with it are reclaimed by the kernel.

Process Creation
----------------

There are generally two system calls that are associated with creating a new process: ``fork()`` and
``exec()``. When a program calls ``fork()``, a system call is triggered that asks the kernel to
create a new virtual memory instance. In the immediate moment when this occurs, the contents of the
new virtual memory instance is an exact duplicate of the process that was running. We refer to the
two processes as the *parent* and *child*. While
handling the ``fork()``, the kernel will allocate new internal data structures for the child, assign
it a :term:`process identifier` (PID), and assign a small set of initial resources to it. A PID is
an integer value that acts like a name that can be used to uniquely refer to a process.

.. topic:: C library functions – <unistd.h>

   .. figure:: Images/CSF-Images-Library.png
      :align: left
      :width: 100%
      :alt: Decorative C library image

   ``pid_t fork(void);``
     Creates a new process. Returns the child's PID if successful.

   ``pid_t getpid(void);``
     Gets the current process's PID.

   ``pid_t getppid(void);``
     Gets the PID of the current process's parent.

`Code Listing 2.6 <#cl2-6>`_ demonstrates how to use ``fork()``. Once both processes are running, the code can
distinguish between the parent and child by looking at the value returned from ``fork()``.The kernel
returns 0 to the child, and it returns the child's PID to the parent. If the kernel failed or
refused to create a new process for some reason, it would return a negative value to the parent; the
global variable ``errno`` (declared in the standard library file ``errno.h``) would be set to an
integer value indicating the error that occurred.

.. _cl2-6:

.. codeinclude:: Processes/CodeListing-2.6.c
   :linenos: true

The return values from ``fork()`` make sense when you consider that the C library defines two
functions that allow the process to determine its own PID (``getpid()``) and to find out its
parent's PID (``getppid()``). The child can use these two functions to identify itself and its
parent. However, the parent process may have already created dozens of child processes. The return
value from ``fork()`` informs the parent of the PID for this new child process that was just created.

`Code Listing 2.7 <#cl2-7>`_ shows an example of what happens when the return code from ``fork()`` is not
checked. On line 6, the original process creates a child. For clarity, let the original process of
PID 1000 and this child has PID 1001. Since both processes continue to execute with the same code,
both reach line 9 and create two additional processes (PIDs 1002 and 1003). As a result, there would
be four processes that execute line 12, printing the message four times (with different PIDs each time).

.. _cl2-7:

.. codeinclude:: Processes/CodeListing-2.7.c
   :linenos: true

It may seem odd that ``fork()`` would create a new process running the exact same code as the
parent. However, there are several times where this makes sense. Consider a web browser, such as
Google Chrome. Whenever you open a new tab, Chrome will make a call to ``fork()`` to create a new
process for that tab. Intuitively, all tabs in a web browser work the same way: they get a URL
request from the user and retrieve the corresponding web page or file from a server somewhere.
Consequently, it makes sense that all of the processes for Chrome would start with the same code
segment. Making the tabs run in separate processes provides some security and reliability: If a
plug-in crashes in one tab, only that tab is affected; the other tabs exist in other processes, so
they are isolated from the crash.

.. topic:: Bug Warning

   .. figure:: Images/CSF-Images-BugWarning.png
      :align: left
      :width: 90%
      :alt: Decorative bug warning

   Once a process uses ``fork()`` to create a child process, the timing of the execution becomes
   nondeterministic from the programmer's point of view. The programmer cannot predict or control when
   the child begins executing relative to the parent. In some systems, the child process may begin
   executing immediately before the parent process returns from the call to ``fork()``. In others, the
   child may simply be created and start running at some later time. Consequently, if the order of the
   execution matters, the programmer must take extra steps to ensure correct behavior; one example of
   this is to use ``wait()`` as described below. We will examine more advanced techniques in later chapters.

Switching Program Code
----------------------

In many cases, after making a call to ``fork()``, you want to switch to a different program. For
instance, consider the ``bash`` terminal program. Within ``bash``, some commands are considered
*built-in* and are executed by ``bash`` itself. One example of a built-in is the export command,
which can be used to define or change an :term:`environment variable`, such as ``PATH`` or
``CLASSPATH``. Other commands are used to call a separate compiled program, such as ``ls``, ``gcc``,
or ``vim``. The code to execute these commands does not exist as a part of ``bash``. Rather, these
are distinct programs and the code needs to be loaded into the new process.

.. topic:: C library functions – <unistd.h>

   .. figure:: Images/CSF-Images-Library.png
      :align: left
      :width: 100%
      :alt: Decorative C library image

   ``int execl(const char *path, const char *arg0, ...  /*, (char *)0 */);``
     Executes the program identified by the exact path.

   ``int execle(const char *path, const char *arg0, ...  /*, (char *)0, char* const envp[]*/);``
     Executes the program at path and set environment variables.

   ``int execlp(const char *file, const char *arg0, ...  /*, (char *)0 */);``
     Looks up the program file in the current ``PATH``.

   ``int execv(const char *path, char *const argv[]);``
     Like execl, but command-line arguments are in an array.

   ``int execve(const char *path, char *const argv[], char *const envp[]);``
     Like execle, but command-line arguments are in an array.

   ``int execvp(const char *file, char *const argv[]);``
     Like execlp, but command-line arguments are in an array.

   ``int fexecve(int fd, char *const argv[], char *const envp[]);``
     Executes the program stored in an open file handle fd.

Loading a new program is handled by the ``exec()`` system call, which can be invoked by a family of
C functions. The functions differ by how you pass the parameters. The ``execl()``, ``execle()``, and
``execlp()`` functions take the command line arguments as additional function parameters, whereas
execv(), execve(), and execvp() require you to create a single array that contains all of the
arguments. The ``execl()`` and ``execv()`` functions expect that you provide the exact path to the
executable file, while ``execlp()`` and ``execvp()`` will look up the path for you. The ``execle()``
and ``execve()`` functions allow you to specify a custom set of environment variables for the new
program. Finally, ``fexecve()`` allows you to pass an open file descriptor for the program instead
of the program's name or path. `Code Listings 2.8 <#cl2-8>`_ and `2.9 <#cl2-9>`_ show two different ways to
call the ``ls`` program to list files, one using ``execl()`` and the other using ``execvp()``.

.. _cl2-8:

.. codeinclude:: Processes/CodeListing-2.8.c
   :linenos: true

.. _cl2-9:

.. codeinclude:: Processes/CodeListing-2.9.c
   :linenos: true

There are a couple of important points to note in this code. First, note that both calls to ``ls``
will have the same three arguments (``argv[0] = "ls"``, ``argv[1] = "-l"``, and ``argv[2] = NULL``).
That is, the first parameter passed to ``execl()`` on line 12 of `Code Listing 2.8 <#cl2-8>`_ is not included in
the argument list as the child process receives it. The convention that ``argv[0]`` is the name of
the program arises from how programs like bash specify the remaining parameters to the ``exec()`` functions.

Second, observe that both versions use ``NULL`` to indicate the end of the arguments. In the case of
``execl()``, function takes a variable-length parameter list, so it relies on ``NULL`` to know when
it should stop looking for arguments. In the case of ``execvp()``, the function expects exactly two
arguments, with the second being an array of unknown length.

Third, note that the first parameter of ``execl()`` must be an exact path to the location of the
executable file. If you call ``execl()`` with just ``"ls"`` instead of ``"/bin/ls"``, then it will
look for an executable file in the current directory that is named ``ls``. If the file does not
exist, the call to ``execl()`` will return a negative value to indicate an error. For the call to
``execvp()``, bash will look through the directories specified by the ``PATH`` environment variable
to try to find the executable file for you. If line 12 of `Code Listing 2.8 <#cl2-8>`_ used ``execlp()`` instead
of ``execl()``, ``bash`` would perform a ``PATH`` lookup there, as well.

Finally, note that no code after a successful call to any of the ``exec()`` functions will execute
in the current process. In this case, `Code Listing 2.8 <#cl2-8>`_ line 13 and `Code Listing 2.9 <#cl2-9>`_ line 14 should
never be reached, so neither of the calls to ``exit()`` should happen. The reason is that ``exec()``
**replaces the current process's code, data, stack, and heap segments**. Assuming the call to
``exec()`` is successful, the child process will start over with a clean virtual memory instance;
none of the parent process's information is retained within the child process.


.. topic:: Bug Warning

   .. figure:: Images/CSF-Images-BugWarning.png
      :align: left
      :width: 90%
      :alt: Decorative bug warning

   Always make sure to check the return values for both ``fork()`` and ``exec()``. Most OS enforce a
   limit on the number of processes that a single user can create; once you reach this limit,
   ``fork()`` will fail. Similarly, if you make a mistake in the name of the file that you want to run
   or you do not have permissions, ``exec()`` will fail and the child process will continue to execute
   the current program's code. Failing to check these return values can lead to unexpected behavior.

POSIX Spawn Interface
---------------------

In recent years, the use of ``fork()`` has been criticized for a variety of reasons. For instance,
the implementation requirements of it are considered too slow and require too much power for very
small embedded computing systems (e.g., think of the "computers" in a car that operate the cruise
control or anti-lock breaking). Another problem is that calling ``fork()`` on a process with
multiple threads (see Chapter 7) can lead to inconsistent copies of the parent process; that is, the
thread that calls ``fork()`` might not have a completely accurate memory image, particularly in
:term:`multicore` architectures. Lastly, the semantics of ``fork()`` have
become very complicated, as there are many special cases that must be handled, such as how to deal
with open files, timers, asynchronous I/O buffers, etc. [#f14]_

To address some of these criticisms, POSIX includes a new function interface, ``posix_spawn()``.
This function takes multiple parameters, including the path to the executable and the ``argv`` and
``envp`` arrays that would be passed into ``execv()`` or ``execve()`` functions. In addition,
``posix_spawn()`` takes other parameters that can specify additional operations. These operations
give the programmer explicit control over the special cases mentioned above.

.. topic:: C library functions – <spawn.h>

   .. figure:: Images/CSF-Images-Library.png
      :align: left
      :width: 100%
      :alt: Decorative C library image

   ``int posix_spawn(pid_t *pid, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[]);``
     Create a new process and load the code identified by the path in a single call.

`Code Listing 2.10 <#cl2-10>`_ shows how to use ``posix_spawn()`` to run an external program. In this case, the
``cksum`` program is run on a CSV file, as specified in the ``path`` and ``argv`` arguments. The
child's process ID is returned through the call-by-reference parameter ``&child``. The ``assert()``
is used here to confirm that the process was successfully spawned; ``posix_spawn()`` returns a
non-zero value if an error occurs at any point and the process was not successfully spawned.

.. _cl2-10:

.. codeinclude:: Processes/CodeListing-2.10.c
   :linenos: true

Process Destruction
-------------------

A process can be terminated in a number of ways. Some methods of process termination are voluntary
in nature, such as when the code makes a call to ``exit()``, ``abort()``, or returns from
``main()``. Other methods are involuntary, such as when a process is killed after a segmentation
fault or similar exception. Regardless of what causes the process termination, the same set of
procedures are typically performed: open file handles are closed, the virtual memory space is
destroyed, the associated physical memory is reclaimed by the system, and any relevant data
structures in the kernel are cleaned up.

The preceding example also illustrates a key element about the timing of the destruction of
processes. Once a process calls ``fork()`` and creates a child process, the relative scheduling
between the parent and child is nondeterministic from the programmer's perspective. The two
processes are scheduled independently by the OS running on that particular machine, and the
scheduling will be influenced by other processes running, as well. However, the parent can
temporarily pause its own execution by making a call to ``wait()`` or ``waitpid()``.

.. topic:: C library functions – <sys/wait.h>

   .. figure:: Images/CSF-Images-Library.png
      :align: left
      :width: 100%
      :alt: Decorative C library image

   ``pid_t wait(int *stat_loc);``
     Waits on all children, gets status information in stat_loc.

   ``pid_t waitpid(pid_t pid, int *stat_loc, int options);``
     Waits on a particular child process identified by pid.


The ``wait()`` system call will block the current process until all of its children have been
terminated. A call to ``waitpid()`` is similar, but it only waits for a single, identified child to
terminate. In the preceding example, the first child makes a call to ``execl()`` to run the ``ls``
program. At approximately the same time, the parent process calls ``wait(NULL)``. The
nondeterministic nature of scheduling means it is impossible to determine which happens first, but
the result is the same: the parent process will not proceed to the second call to ``fork()`` until
the first child process runs the ``ls`` program and terminates.

Note that there is no problem even if the child process terminates before the parent gets scheduled
to run. If all children have been terminated by the time a process calls ``wait()``, then the parent
process will simply continue on without pausing. The only requirement for ``wait()`` is that the
parent cannot continue if there is at least one child process still trying to run.

The convention with C programs is to return an integer value from ``main()`` to indicate whether the
program completed successfully. The standard practice is to return 0 to indicate that no error
occurred, whereas a non-zero value typically indicates an error occurred in some way. When working
on the command-line, this value can be retrieved by printing the ``bash`` variable ``$?``. In C
programs, this value can be retrieved through the ``int *`` parameter passed to ``wait()`` as shown
in `Code Listing 2.11 <#cl2-11>`_.

.. topic:: Bug Warning

   .. figure:: Images/CSF-Images-BugWarning.png
      :align: left
      :width: 90%
      :alt: Decorative bug warning

   The return value of the child process is shifted to the left when it is retrieved using ``wait()``,
   as the lower eight bits are used for other purposes. The ``WEXITSTATUS()`` macro corrects this bit
   shifting to receive the actual return code from the child process.

.. [#f14] For a full description of the problems with ``fork()``, see the paper: A. Baumann, J.
   Appavoo, O. Krieger, and T. Roscoe, "A ``fork()`` in the road." In Workshop on Hot Topics in Operating
   Systems (HotOS '19). New York, NY: ACM, 2019.

.. avembed:: Exercises/Processes/ProcessLifeSumm.html ka
   :module: ProcessCycle
   :points: 1.0
   :required: True
   :exer_opts: JXOP-debug=true&amp;JOP-lang=en&amp;JXOP-code=java
   :long_name: Process life cycle questions
   :threshold: 7

