JMU
Processes
The Basics


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu


Review
Associated with a Process
Working with Process IDs - getpid() getpid
pid_t getpid(void)
Purpose:
Get the process ID of the calling process.
Details:
Return The process ID of the caller
#include <unistd.h> unistd.h
#include <sys/types.h> sys/types.h
Working with Process IDs - getppid() getppid
pid_t getppid(void)
Purpose:
Get the process ID of the parent of the calling process.
Details:
Return The process ID of the caller's parent
#include <unistd.h> unistd.h
#include <sys/types.h> sys/types.h
Shell Info. About Process Status: ps ps
Process Memory
Process Memory (cont.)
Process Memory (cont.)
Process Memory (cont.)
images/process-memory-organization.gif
Process memory (cont.)
unixexamples/processes/memory/memory_layout.c
        #include <stdio.h>
#include <stdlib.h>

// Define an initialized constant with file scope
const int constant_data = 11;

// Define an initialized variable with file scope
int initialized_data = 22;

// Tentatively define a variable with global (i.e., file) scope 
// which is in the Block Started by Symbol (BSS) section (a.k.a., the
// unitialized data block)
int bss_data;

// Remember, argc and argv are copies of the actual parameters
// (since parameters are passed by value)
int main(int argc, char* argv[])
{
  int    local_variable = 99;
  int*   heap_firstcall;
  int*   heap_secondcall;

  // Note that memory allocators often use blocks of different sizes,
  // the smallest of which is often 16 or 32 bytes. So, even though an
  // int requires less space, malloc() will allocate more (i.e., the
  // amount of the smallest block)
  heap_firstcall   = (int*)(malloc(sizeof(int)));
  heap_secondcall  = (int*)(malloc(sizeof(int)));


  // The %p specifier prints a pointer as a hex character sequence 
  printf("Stack        %p\n", &heap_secondcall); // Note the &
  printf(".            %p\n", &heap_firstcall);  // Note the &
  printf(".            %p\n", &local_variable);
  printf(".            %p\n", &argc);
  printf(".            %p\n", &argv);
  printf("\n");
  printf(".            %p\n", heap_secondcall);  // Note the lack of an &
  printf("Heap:        %p\n", heap_firstcall);   // Note the lack of an &
  printf("BSS:         %p\n", &bss_data);
  printf("Initialized: %p\n", &initialized_data);
  printf("Constants:   %p\n", &constant_data);
  printf("Code:        %p\n", &main);


  // Cleanup
  free(heap_firstcall);
  free(heap_secondcall);

  // Done
  return 0;
}
        
Shell Info. About Process Memory: size size
Understanding the Environment
Working with the Environment from a Shell
Get an Environment Variable in a Program - getenv() getenv
char *getenv(const char *name)
Purpose:
Get an environment variable
Details:
name A pointer to the name of the environment variable
Return A pointer to the string containing the value (or NULL if there is no such variable)
#include <stdlib.h> stdlib.h
Add an Environment Variable in a Program - setenv() setenv
int setenv(const char *name, const char* value, int overwrite)
Purpose:
Set (or reset) an environment variable
Details:
name A pointer to the name of the environment variable
value A pointer to the value of the environment variable
ovewrite 0 not to overwrite an existing variable; nonzero to always set/reset
Return 0 on success; -1 on error
#include <stdlib.h> stdlib.h

Note: There is also a putenv() putenv function but you must be much more careful when using it because it does not duplicate the string it just points to it. Hence, the string must not be an automatic variable (i.e., a character on the stack) or an array that might change.

Remove an Environment Variable in a Program - unsetenv() unsetenv
int unsetenv(const char *name)
Purpose:
Remove an environment variable
Details:
name A pointer to the name of the environment variable
Return 0 on success; -1 on error
#include <stdlib.h> stdlib.h
Creating a New Process in a Program
Creating a New Process - fork() fork
pid_t fork(void)
Purpose:
Create a child process
Details:
Return Parent: PID of the child or -1. Child: 0
#include <unistd.h> unistd.h
#include <sys/types.h> sys/types.h

Note: fork() is called from the parent process but creates a duplicate child process (containing the same instructions). So, there is a return in both the parent and the child.

Memory in the Parent and Child
Execution of the Parent and Child
Files in the Parent and Child
Files in the Parent and Child (cont.)
images/file-descriptors_fork.gif
The Process Lifecycle
images/process-lifecycle.gif
Terminating a Process: Returning from main()
Terminating a Process: _exit() _exit
void _exit(int status)
Purpose:
Terminate the calling process normally
Details:
status 0 for a successful termination; postivie otherwise
Return Does not return
#include <unistd.h> unistd.h

Note: Normally, the library call exit() is used rather than the system call _exit()

Terminating a Process (cont.): exit() exit
void exit(int status)
Purpose:
"Cleanup" and terminate the calling process normally
Details:
status 0 for a successful termination; positive otherwise
Return Does not return
#include <stdlib.h> stdlib.h

Note: exit() calls exit handlers (in reverse order of registration), flushes the standard files, and calls _exit().

Details of Termination (Normal and Abnormal)
  1. Open file descriptors are closed (and file locks are released)
  2. Other descriptors are closed (e.g., message catalog descriptors, conversion descriptors)
  3. Semaphores are closed
  4. Message queues are closed
  5. Memory locks are removed
  6. Memory mappings are unmapped
Exit Handlers: atexit() atexit
int atexit(void (*func)(void));
Purpose:
Add a function to a list of functions to be called when a process terminates.
Details:
Return 0 on success; non-zero on error
#include <stdlib.h> stdlib.h
Doing the "Same Thing" in Two Processes
unixexamples/processes/fork/messy/grads.c
        #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int
main(int argc, char *argv[])
{
  const char* fname;
  FILE *input_file;
  int   arguments, grads, total;
  pid_t pid;

  pid = fork(); // The assignment will be performed in the parent and the child

  switch (pid)
    {
    case -1:
      exit(1);

    case 0:
      fname = "college_1990.dat";
      break;

    default:
      fname = "college_2000.dat";
      break;
    }

  total = 0;
  input_file = fopen(fname, "r");

  while (!feof(input_file))
    {
      arguments = fscanf(input_file, "%d\n", &grads);
      if (arguments == 1) total += grads;
    }

  fclose(input_file);
  
  printf("Total College Grads in %s: %d\n", fname, total);
  
  
  exit(0);
}
        
Shortcomings of the Previous Implementation
Doing "Different Things" in Two Processes
unixexamples/processes/fork/messy/summary.c
        #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int
main(int argc, char *argv[])
{
  const char* fname;
  FILE *input_file;
  float commute, total_commute;
  int   arguments, grads, n, total_grads;
  pid_t pid;

  pid = fork(); // The assignment will be performed in the parent and the child

  switch (pid)
    {
    case -1:
      exit(1);

    case 0:
      fname = "college_2000.dat";
      total_grads = 0;
      input_file = fopen(fname, "r");

      while (!feof(input_file))
        {
          arguments = fscanf(input_file, "%d\n", &grads);
          if (arguments == 1) total_grads += grads;
        }
      
      fclose(input_file);
      printf("Total College Grads in %s: %d\n", fname, total_grads);
      break;

    default:
      fname = "commute_2000.dat";
      total_commute = 0.0;
      n     = 0;
      input_file = fopen(fname, "r");
      while (!feof(input_file))
        {
          arguments = fscanf(input_file, "%f\n", &commute);
          if (arguments == 1)
            {
              total_commute += commute;
              ++n;
            }
        }

      fclose(input_file);
      printf("Average Commute in %s: %f\n", fname, total_commute/n);
      break;
    }
  
  exit(0);
}
        
Shortcomings of the Previous Implementation
The Examples Revisited - The Library
unixexamples/processes/fork/functions/stats_lib.c
        #include "stats_lib.h"
#include <stdio.h>

float
mean(const char *fname)
{
  FILE   *input_file;
  float  datum, total;
  int    arguments, n;

  total = 0.0;
  n     = 0;
  input_file = fopen(fname, "r");
  while (!feof(input_file))
    {
      arguments = fscanf(input_file, "%f\n", &datum);
      if (arguments == 1)
        {
          total += datum;
          ++n;
        }
    }

  fclose(input_file);

  return total / (double)n;
}


int
total(const char *fname)
{
  FILE *input_file;
  int   arguments, datum, total;

  total = 0;
  input_file = fopen(fname, "r");
  while (!feof(input_file))
    {
      arguments = fscanf(input_file, "%d\n", &datum);
      if (arguments == 1) total += datum;
    }

  fclose(input_file);

  return total;
}
        
Doing the "Same Thing" Revisited
unixexamples/processes/fork/functions/grads.c
        #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "stats_lib.h"


int
main(int argc, char *argv[])
{
  const char* fname;
  pid_t pid;

  pid = fork(); // The assignment will be performed in the parent and the child

  switch (pid)
    {
    case -1:
      exit(1);

    case 0:
      fname = "college_1990.dat";
      printf("Total College Grads in %s: %d\n", fname, total(fname));
      break;

    default:
      fname = "college_2000.dat";
      printf("Total College Grads in %s: %d\n", fname, total(fname));
      break;
    }

  exit(0);
}
        
Doing "Different Things" Revisited
unixexamples/processes/fork/functions/summary.c
        #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "stats_lib.h"


int
main(int argc, char *argv[])
{
  const char* fname;
  pid_t pid;

  pid = fork(); // The assignment will be performed in the parent and the child

  switch (pid)
    {
    case -1:
      exit(1);

    case 0:
      fname = "college_2000.dat";
      printf("Total College Grads in %s: %d\n", fname, total(fname));
      break;

    default:
      fname = "commute_2000.dat";
      printf("Average Commute in %s: %f\n", fname, mean(fname));
      break;
    }

  exit(0);
}
        
Doing More Than Two Things
unixexamples/processes/fork/functions/summary_multiple.c
        #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "stats_lib.h"


int
main(int argc, char *argv[])
{
  const char* fname;
  pid_t pid, qid;

  pid = fork(); // fork once
  switch (pid)
    {
    case -1:
      exit(1);

    case 0:
      fname = "college_1990.dat";
      printf("Total College Grads in %s: %d\n", fname, total(fname));
      break;

    default:
      fname = "commute_1990.dat";
      printf("Average Commute in %s: %f\n", fname, mean(fname));
      break;
    }

  qid = fork(); // fork again
  switch (qid)
    {
    case -1:
      exit(1);

    case 0:
      fname = "college_2000.dat";
      printf("Total College Grads in %s: %d\n", fname, total(fname));
      break;

    default:
      fname = "commute_2000.dat";
      printf("Average Commute in %s: %f\n", fname, mean(fname));
      break;
    }

  exit(0);
}
        
Doing More Than Two Things (cont.)
Doing More Than Two Things (cont.)
A Real World Complication
Executing a Program: execve() execve
int execve(const char *path, char *const argv[], char *const envp[]);
Purpose:
Load a new program into an existing process's memory (discarding the existing text/code, data, stack, heap, etc...)
Details:
path The path name of the new program
argv The NULL-terminated (so the length can be determined) array of arguments to be passed to the new program
envp The NULL-terminated (so the length can be determined) array of name-value pairs specifying the environment list
Return Does not return on success; returns -1 on error
#include <unistd.h> unistd.h

Note: The process ID remains the same and the file descriptors are left open.

Executing a Program: errno
ENOENT The file doesn't exist
ENOEXEC The file isn't in a recognizable format
ETXTBSY The file is open for writing
E2BIG The argument list and/or environment space are too big
An Example of execve()
The Programs
unixexamples/processes/exec/college.c
        #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "stats_lib.h"


int
main(int argc, char *argv[])
{
  const char* fname;
  
  fname = argv[1];
  printf("Total College Grads in %s: %d\n", fname, total(fname));

  exit(0);
}
        
unixexamples/processes/exec/commute.c
        #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "stats_lib.h"


int
main(int argc, char *argv[])
{
  const char* fname;

  fname = argv[1];
  printf("Average Commute in %s: %f\n", fname, mean(fname));

  exit(0);
}
        
An Example of execve() (cont.)
unixexamples/processes/exec/census.c
        #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

// Note: There is no reason to #include the headers for grads and
//       commute because neither the compiler nor linker uses them.


int
main(int argc, char *argv[])
{
  char *argvec[3];
  char *envvec[] = {NULL};
  pid_t pid;

  argvec[2] = NULL;
  pid = fork(); // The assignment will be performed in the parent and the child

  switch (pid)
    {
    case -1:
      exit(1);

    case 0:
      argvec[0] = (char*)"college";
      argvec[1] = (char*)"college_2000.dat";
      break;

    default:
      argvec[0] = (char*)"commute";
      argvec[1] = (char*)"commute_2000.dat";
      break;
    }
  execve(argvec[0], argvec, envvec);
}
        
Doing More Than Two Things Revisited
unixexamples/processes/exec/census_notmultiple.c
        #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


int
main(int argc, char *argv[])
{
  char *argvec[3];
  char *envvec[] = {NULL};
  pid_t pid;

  argvec[2] = NULL;

  pid = fork();  
  switch (pid)
    {
    case -1:
      exit(1);

    case 0:
      argvec[0] = (char*)"college";
      argvec[1] = (char*)"college_2000.dat";
      break;

    default:
      argvec[0] = (char*)"commute";
      argvec[1] = (char*)"commute_2000.dat";
      break;
    }
  execve(argvec[0], argvec, envvec);



  pid = fork();  
  switch (pid)
    {
    case -1:
      exit(1);

    case 0:
      argvec[0] = (char*)"college";
      argvec[1] = (char*)"college_1990.dat";
      break;

    default:
      argvec[0] = (char*)"commute";
      argvec[1] = (char*)"commute_1990.dat";
      break;
    }
  execve(argvec[0], argvec, envvec);


  
  exit(0);
}
        
Doing More Than Two Things Revisited (cont.)
Doing More Than Two Things Revisited Again
unixexamples/processes/exec/census_multiple.c
        #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


int
main(int argc, char *argv[])
{
  char *argvec[3];
  char *envvec[] = {NULL};
  pid_t pid;

  argvec[2] = NULL;

  pid = fork();
  if      (pid == -1) exit(1);
  else if (pid ==  0) // First child
    {
      argvec[0] = (char*)"commute";
      argvec[1] = (char*)"commute_1990.dat";
      execve(argvec[0], argvec, envvec);
    }

  pid = fork();
  if      (pid == -1) exit(1);
  else if (pid ==  0) // Second child
    {
      argvec[0] = (char*)"commute";
      argvec[1] = (char*)"commute_2000.dat";
      execve(argvec[0], argvec, envvec);
    }

  pid = fork();
  if      (pid == -1) exit(1);
  else if (pid ==  0) // Third child
    {
      argvec[0] = (char*)"college";
      argvec[1] = (char*)"college_1990.dat";
      execve(argvec[0], argvec, envvec);
    }

  // Parent
  argvec[0] = (char*)"college";
  argvec[1] = (char*)"college_2000.dat";
  execve(argvec[0], argvec, envvec);

  
  exit(0);
}
        
Doing More Than Two Things Revisited Again (cont.)
The exec() Family
Monitoring Child Processes
Waiting for a Child to Terminate: waitpid() waitpid
pid_t waitpid(pid_t pid, int *status, int options);
Purpose:
Wait for a particular child process to terminate.
Details:
pid The process to wait for (see below)
status An outbound parameter used to indicate how the child terminated
options A bit mask
Return The PID of the child or 0; -1 on error
#include <sys/types.h> sys/types.h
#include <sys/wait.h> sys/wait.h

Note: If pid is greater than 0 then this call returns when the specific process ID terminates. If pid is 0 then this call returns when any child in the same process as the caller group terminates. If pid is less than -1 then this call returns when any child in the group of the absolute value of the PID terminates. If pid is -1 then it returns when any child terminates.

Waiting for a Child to Terminate: Options
WNOHANG Return immediately (with a value of 0) if no member of the wait set has already terminated.
WUNTRACED Suspend execution of the calling process until a running member of the wait set is terminated or stopped.
WCONTINUED Suspend execution of the calling process until a cunning member of the wait set is terminated or a stopped member has been resumed.
Waiting for a Child to Terminate: wait() wait
pid_t wait(int *status);
Purpose:
Wait for any child child process to terminate.
Details:
status An outbound parameter used to indicate how the child terminated
Return The PID of the child or 0; -1 on error
#include <sys/types.h> sys/types.h
#include <sys/wait.h> sys/wait.h

Note: Calling wait(&status) is equivalent to calling waitpid(-1, &status; 0).

Macros for "Dissecting" the Status
WIFEXITED(status) Returns true if the child exited normally.
WIFSIGNALED(status) Returns true if the child was killed by a signal (and WTERMSIG(status) returns the signal number).
WIFSTOPPED(status) Returns true if the child that caused the return is stopped (and WSTOPSIG(status) then returns the signal number).
WIFCONTINUED(status) Returns true if the child that caused the return was resumed.
Orphans and Zombies
Details of Zombies
An Example with Zombies
unixexamples/processes/wait/zombies.c
        #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define N 5



int
main(void)
{
  pid_t   pid;

  printf("\nWhile the parent is sleeping, execute the following command");
  printf("(and note the <defunct> indicator).\n");
  printf("ps -l --ppid %ld\n\n", (long)getpid());
  
  for (int i=0; i<N; i++)
    {
      pid = fork();
      if (pid == 0) // This is a child process
        {
          printf("I'm child %d with a PID of %ld\n", i, (long)getpid());
          exit(0); // Terminate (and become a zombie)
        }
    }
  
  // Executed in the parent only
  sleep(20);

  printf("\nAfter the parent terminates, execute the command again.\n");
  printf("ps -l --ppid %ld\n\n", (long)getpid());

  exit(0);
}

        
An Example with an Orphan
unixexamples/processes/wait/orphan.c
        #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define N 5



int
main(void)
{
  pid_t   pid;

  printf("\nWhile the parent is sleeping, execute the command:\n");
  printf("ps --ppid %ld\n\n", (long)getpid());

  
  pid = fork();
  if (pid == 0) // This is a child process
    {
      // When the parent terminates, the child becomes an orphan
      // and is inherited by init (which has a PID of 1)
      printf("\nThe child has a PID of %ld\n", (long)getpid());
      printf("After the parent terminates, execute the command:\n");
      printf("ps -l --pid %ld\n", (long)getpid()); 
      sleep(30);

      // When the child also terminates it becomes a zombie
      // and will be reaped by init
      printf("\nThe child has now terminated.\n");
      printf("Execute the command again:\n");
      printf("ps -l --pid %ld\n", (long)getpid()); 

      exit(0);
    }

  // Executed in the parent only
  sleep(20);
  printf("\nThe parent has now terminated.\n");
  exit(0);
}
        
An Example of Reaping
unixexamples/processes/wait/reap_one.c
        #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#define N 5



int
main(void)
{
  pid_t   pid;

  printf("While the parent is sleeping, execute the following command.\n");
  printf("ps -l --ppid %ld\n\n", (long)getpid());

  
  for (int i=0; i<N; i++)
    {
      pid = fork();
      if (pid == 0) // This is a child process
        {
          printf("I'm child %d with a PID of %ld\n", i, (long)getpid());
          sleep(1);
          exit(0); // Terminate (and become a zombie)
        }
    }

  // Executed in the parent only

  
  // Reap a single child ("at random")
  //
  // -1 indicates the wait set is all child processes
  //  0 indicates execution should be suspended until one element terminates
  // This call returns immediately if the child is a zombie, otherwise
  // it waits.
  // 
  int   status;
  pid_t reaped;

  reaped = waitpid(-1, &status, 0); // Equivalent to wait(&status)
  printf("Reaped child with PID %ld\n", (long)reaped);

  sleep(20);

  exit(0);
}
        
Another Example of Reaping
unixexamples/processes/wait/reap_all.c
        #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#define N 5



int
main(void)
{
  pid_t   pid;

  printf("While the parent is sleeping, execute the command:\n");
  printf("ps -l --ppid %ld\n\n", (long)getpid());

  
  for (int i=0; i<N; i++)
    {
      pid = fork();
      if (pid == 0) // This is a child process
        {
          printf("I'm child %d with a PID of %ld\n", i, (long)getpid());
          sleep(1);
          exit(0); // Terminate (and become a zombie)
        }
    }

  // Executed in the parent only

  
  // Reap all children ("in random order")
  //
  int   status;
  pid_t reaped;
  while ((reaped = waitpid(-1, &status, 0)) > 0)
    {
      printf("Reaped child with PID %ld\n", (long)reaped);
    }

  sleep(20);

  exit(0);
}
        
Process Groups
Setting the Process Group: setpgid() setpgid
int setpgid(pid_t pid, pid_t pgid)
Purpose:
Set the process group ID
Details:
pid The ID of the process (or 0 for the current process)
pgid The ID of the group (or 0 to use the process ID)
Return 0 on success; -1 on error
#include <sys/types.h> sys/types.h
#include <unistd.h> unistd.h
Getting the Process Group: getpgrp() getpgrp
pid_t getpgrp(void)
Purpose:
Get the process group ID of the calling process
Details:
Return Process group ID of the calling process
#include <unistd.h> unistd.h