Operating Systems 2017F Lecture 3

From Soma-notes

Video

Video from the lecture given on September 14, 2017 is now available.

Code

Code and files from the lecture (captured as they were at the end) are available here.


Notes

Commands

  • -wall: show all warning
  • less: allow screen navigation, hit q to quit

What is fork?

  • Fork creates a new program but contains the same code (call the clone system call) and only argv[] and envp[] passed on to the new born program
- More on creating a process using fork().
 #include <stdio.h>
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/wait.h>


 int main(int argc, char* argv[], char* envp[]) {
  int child;
  int status;
  pid_t retval;
        
  child = fork();

  printf("Hello!  I am %d, my parent is %d.\n",
          getpid(), getppid());

  if (child == 0) {
  /* We're the child */
    sleep(3);
    printf("Child all done!\n");
  } else {
  /* We're the parent */
    retval = wait(&status);
    printf("Parent all done!  Child %d returned %d.\n",
            (int) retval, WEXITSTATUS(status));
  }
        
  return 42;
}
- sleep(int)
  - causes the current process to do nothing for the specified number of seconds.
- pid_t = fork(void)
  - fork creates a clone of the parent process
  - pid is 0 when the child process is running and >0 when the parent process is running.  Both processes will return from the system call to fork().
- pid_t = wait(int * status)
  - this system call is equivalent to pid_t = waitpid(-1, &status, 0)
  - the wait system call suspends the current process until one of its children has terminated.
  - pid_t specifies which child to wait for.  Various options are available that define how to wait.
  - int* status holds the status of the wait system call.  There are various macros that define what the status can be.
- What happens when the newline character is removed from the printf statement?
  - printf holds the output in a buffer until it receives a newline character.  The result is the output does not print when expected. 
  - The kernel is doing things as it is told, so the c library is at fault.
  - We could get the expected output if we bypass the c library using a system call: "write"
  - running an "strace" on the executable will show us the order in which the processes were run to help debug the problem.
- What happens in the conditional?
  - When the parent process calls fork(), a child process is created and begins executing at the fork() statement.  The OS returns a value from fork() which indicates which process 
    is currently running.
  - If fork() returns zero then the child sleeps for 3 seconds before printing.  The sleep system call forces the child process to do nothing for 3 seconds.
  - if fork() returns anything else, it is assumed to be the parent process.  In this case the wait() system call is run by the parent which causes the parent process to wait for its 
    child to finish; then the printf executes and prints the child process id and its return value from main.  The macro WEXITSTATUS retrieves 42.

What Happen to the child process after the parent process died?

  • Every child process must have a currently running parent. When the parent process die, kernel collect the dead body(return value) and re-parent the process
  • In the demo showed in class, “system d user” became its new parent
    • Use command "Pstree" to show process hierarchy
  • Every process must have a valid process parent. Use "pstree" to see the tree structure
  • When a parent dies before a child "init" will adopt every orphan on the system

What is zombie process?

  • When a process die it become 'zombie' (s = sleep, z = zombie)
  • We can use wait to be a 'good' parent

How to load a new binary manually

  • Use the execve command.
    • We can fork child process to handle dangerous task (“Suicide Squad”), or handle all incoming request

Manipulating environment variables

  • argc tells the number of argument in command line, while argv[] contains the actual command obtained in the command line

Adding the execve() system call to the mix

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>


int main(int argc, char* argv[], char* envp[]) {
  int child;
  int status;
  pid_t retval;
  int i;
  printf("You called me with %d arguments.\n", argc);
       
  for (i=0; i<argc; i++) {
    printf("Arg %d: %s\n", i, argv[i]);
  }
       
  child = fork();
  printf("Hello!  I am %d, my parent is %d.\n",
         getpid(), getppid());
  if (child == 0) {
  /* We're the child */
               
    execve("/bin/ls", argv, envp);
    sleep(3);
    printf("Child all done!\n");
  } else {
  /* We're the parent */
     retval = wait(&status);
     printf("Parent all done!  Child %d returned %d.\n",
            (int) retval, WEXITSTATUS(status));
  }
       
  return 42;
}
- int execve(const char *filename, char *const argv[],  char *const envp[])
  - this system call executes the program pointed to by filename
  - The child process becomes independent from its parent -- a new program
  - argv[] is an array of strings passed to the new program.  The values are loaded into the array by execve not the kernel.
  - envp[] is an array of strings of the form key=value which are passed as environment to the new program
- What happens when the if statement executes
  - execve creates a new program -- an independent process and passes arguments to the "ls" program.
  - execve stops the new process after it has completed.
- Some interesting points about main(int argc, char* argv[], char* envp[])
  - Notice that the size of the arrays are not defined.  We have to use argc to get this information.  Not good programming practice but is 
    accepted because it is grandfathered from the early days of the c language.
  - arg[0] is always the name of the program.