CS 111: Operating Systems

Lecture 5 Scribe Notes (Winter 2013)


By Yongchun Li and Wei Dai (1/23/13)


Orthogonality

"In mathematics, orthogonality is the relation of two lines at right angles to one another (perpendicularity), and the generalization of this relation into n dimensions; and to a variety of mathematical relations thought of as describing non-overlapping, uncorrelated, or independent objects of some kind." -- Wikipedia

"In computer science, orthogonality is the ability to use various language features in arbitrary combinations with consistent results." -- Wikipedia

Goal:

Tool:

What is a process:

How do we specify how process work?

            typedef long pid_t;     //this must be a signed integer type
                                    //pid_t in <sys/types.h> header
            pid_t e = 97;
            pid_t fork();           //handle for a process

            pid_t c = fork();       //clones current process,
                                    //yields the pid of the child if parent process
                                    //0 if you are the child
                                    //-1 if not enough resources
            if(c == 0) {
                do what the child should do
            } else {
                in parent
            }

            while(!fork())
                continue;
            exit(1);

            while(1)
                fork();
        
            //print time of day using 'fork'
            bool printdate(void) {
                pid_t p = fork();
                if(p < 0)
                    return 0;
                if(p == 0) {
                    execvp("/usr/bin/date", (char **){"date", "-u", 0});
                    exit(126);
                }
                
                //back in parent
                int status;
                while(waitpid(p, &status, 0) < 0)
                    continue;
                return (WIFEXITED(status) && WEXITSTATUS(status) == 0);
            }
        

Important System Call

System Call: fork()

In computing, when a process forks, it creates a copy of itself. Under Unix-like operating systems, this is created with the fork() system call.The original process that calls fork() is the parent process, and the newly created process is the child process. Both processes return from the system call and execute the next instruction.

A typical declaration for fork() will be

  
            pid_t fork(void);
                // returns 0 if the fork succeeded and is now running in the child process
                // returns -1 if the fork failed
                // returns a value > 0 if the fork succeeded and is now running in the parent process
        

Main differences between child and parent process:

Example of testing fork() in C:

                
            int var_glb; /* A global variable*/

            int main(void)
            {
                pid_t childPID;
                int var_lcl = 0;

                childPID = fork();

                if(childPID >= 0) // fork was successful
                {
                    if(childPID == 0) // child process
                    {
                        var_lcl++;
                        var_glb++;
                        printf("\n Child Process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
                    }
                    else //Parent process
                    {
                        var_lcl = 10;
                        var_glb = 20;
                        printf("\n Parent process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
                    }
                }
                else // fork failed
                {
                    printf("\n Fork failed, quitting!!!!!!\n");
                    return 1;
                }

                return 0;
            }
           
        

System Call: waitpid()

All of these system calls are used to wait for state changes in a child of the calling process, and obtain information about the child whose state has changed. A state change is considered to be: the child terminated; the child was stopped by a signal; or the child was resumed by a signal. In the case of a terminated child, performing a wait allows the system to release the resources associated with the child; if a wait is not performed, then the terminated child remains in a "zombie" state

A typical declaration for waitpid() will be

  
            pid_t waitpid(pid_t pid, int *status, int options);
                //If waitpid() was invoked with WNOHANG set in options, and there are children specified by pid for which status is not available, waitpid() returns 0. 
                //If WNOHANG was not set, waitpid() returns the process ID of a child when the status of that child is available. 
                //Otherwise, it returns -1 and sets errno to one of the following values:
        

Example of testing waitpid() in C:

            int main(int argc, char *argv[])
            {
                pid_t cpid, w;
                int status;

                cpid = fork();
                if (cpid == -1) {
                perror("fork");
                exit(EXIT_FAILURE);
                }

                if (cpid == 0) {            /* Code executed by child */
                    printf("Child PID is %ld\n", (long) getpid());
                    if (argc == 1)
                    pause();                /* Wait for signals */
                    _exit(atoi(argv[1]));
                } else {                    /* Code executed by parent */
                    do {
                        w = waitpid(cpid, &status, WUNTRACED | WCONTINUED);
                        if (w == -1) {
                        perror("waitpid");
                        exit(EXIT_FAILURE);
                        }

                        if (WIFEXITED(status)) {
                            printf("exited, status=%d\n", WEXITSTATUS(status));
                        } else if (WIFSIGNALED(status)) {
                            printf("killed by signal %d\n", WTERMSIG(status));
                        } else if (WIFSTOPPED(status)) {
                            printf("stopped by signal %d\n", WSTOPSIG(status));
                        } else if (WIFCONTINUED(status)) {
                            printf("continued\n");
                        }
                    } while (!WIFEXITED(status) && !WIFSIGNALED(status));
                    exit(EXIT_SUCCESS);
                }
            }  
        

Question for waitpid

  1. if process doesn't exist?
  2. A waits for B, B waits for A?
  3. child exits before parent waits for it
            //records exit status
            while((p = fork()) >= 0)
                if(!p)
                    exit();
        
            //Reap the zombies
            while(waitpid(-1, ♣))     //-1 means any process
                continue;
        

System Call: exec*()

The exec collection of functions of Unix-like operating systems cause the running process to be completely replaced by the program passed as an argument to the function. As a new process is not created, the process identifier (PID) does not change, but the data, heap and stack of the original process are replaced by those of the new process. In the execl, execlp, execv, and execvp calls, the new process image inherits the current environment variables.

Typical declaration for exec*() will be

            int execl(char const *path, char const *arg0, ...);
            int execle(char const *path, char const *arg0, ..., char const * const *envp);
            int execlp(char const *file, char const *arg0, ...);
            int execv(char const *path, char const * const * argv);
            int execve(char const *path, char const * const *argv, char const * const *envp);
            int execvp(char const *file, char const * const *argv);
                //The base of each is exec (execute), followed by one or more letters:
                //e - An array of pointers to environment variables is explicitly passed to the new process image.
                //l - Command-line arguments are passed individually to the function.
                //p - Uses the PATH environment variable to find the file named in the path argument to be executed.
                //v - Command-line arguments are passed to the function as an array of pointers.
        

Example of using execl() in C:

            main()
            {
                execl("/bin/ls", "/bin/ls", "-r", "-t", "-l", (char *) 0);
            }
        

Suppose 'date' loops?

want to work even then

            void donothing(int sig) {}
            
            signal(SIGALRM; donothing);
            alarm(5);       //please wake me up in 5 seconds, deliver a signal
                            //set 5-second time limit
            
            if(waitpid(p, &status, 0) < 0 && errno == EINTR) {
                //alarm went off
                kill(p, SIGINT);
                waitpid(p, &status, 0);
            }
        

Data structure to implement this

process table

pid exit status zombie ppid start location of RAM size of RAM register uid gid
         
         
9712131     
         

in kernel: to resume pid 97


Files

fork : exit :: open :: close

            int open(char const *, int, ...);       //create an file descriptor
             ↑           ↑          ↑    ↑
    file descriptor  file name   flags extra info
             ↑
            from user app's point of view, just an nonnegative integer a handle
            

            int close(int);
                       ↑
                    file descriptor


            int dup(int);       //clones a file descriptor