Using the exec() Function in Unix-like Environments

The fork() function creates a new process. Using fork() is simple. Before we call the fork() function, one process, which we term the parent, exists. After we call the fork() function, there are two processes, the parent process and the child process. What can sometimes be confusing is that both processes continue to execute the same program!

However, we can distinguish between the two processes using a decision structure and the return value from the fork() function. The fork() function returns a negative value if it failed. In the child process, the fork() function returns 0. In the parent process, the fork() function returns the PID, which is always a positive integer.

#include <stdio.h>
#include <unistd.h>

int main(void){

    //pid_t is basically an int
    pid_t returnValue;

    if((returnValue = fork()) < 0){
        //error occured
        return -1;
    } 

    if(returnValue  == 0){
        printf("In the new process.\n");
        printf("process ID is %d\n", getpid());
    } 

    if(returnValue > 0){
        printf("In the parent process.\n");
        printf("process ID is %d\n", getpid());
    }

    return 0;

}

Note that PID values are unique; there cannot be two running processes with the same PID.

The exec() function starts a new program that retains the PID of the current process.

While exec() is referred to as being a single function, it is actually a family of functions with slightly different parameter lists and uses.

The simplest function in the exec() family to explain is execve(), since it is the underlying system call. The other functions are in fact wrapper functions.

The execve() function takes three arguments. The first argument is the path argument, this is the path to the executable file we want to execute. The second argument is argv, which is a full list of the arguments we wish to pass the executable file we specified in the first argument. Note that the second argument is an array of pointers, the first element of which should be the path of the program being executed.

#include <stdio.h>
#include <unistd.h>


int main(void){
    
    char *args[] = {"/bin/ls", NULL};

    execve("/bin/ls", args, NULL);


    return 0;

}

Like fork(), the exec() function is declared in the <unistd.h> header.

If exec() succeeds, it does not return to the caller, because exec() completely replaces the caller with the new program.

If exec() fails, it returns -1.

#include <stdio.h>
#include <unistd.h>

int main(void){

    
    char *args[] = {"/bin/ps", NULL};
    
    //check if exec works
    if(execve("/bin/ps", args, NULL) == -1){
        printf("Error with execve");
        return -1;
    }    


    return 0;

}

Remember, a call to exec() should not return, unless there was a problem. Commonly, if there is an error either the program requested doesn’t exist, or it exists but isn’t executable.

In our next program, we will create a child process. In both the parent and  child processes we will run execve() after knowingly passing it bad arguments. We will then examine the error numbers produced using the errno value.

#include <stdio.h>
#include <unistd.h>
#include <errno.h>

int main(void){

    char *args1[] = {"/bin/doesntexist", NULL};
    char *args2[] = {"/home", NULL};

    pid_t forkRVal;
    
    if((forkRVal=fork())<0){
        printf("problem forking");
    }        

    //in child process
    if(forkRVal==0){
        int execReturnVal;
        execReturnVal = execve("/bin/doesntexist", args1, NULL);    
        //execution will only reach this code
        //in the event of failure of execve()
        if(execReturnVal==-1){
            printf("In child process.\t");
            printf("Error no %d\n", errno);
        }
    }

    //in parent process
    if(forkRVal>0){
        int execReturnVal;
        execReturnVal = execve("/home", args2, NULL);
        //check if execve() failed    
        if(execReturnVal==-1){
            printf("In parent process.\t");
            printf("Error no %d\n", errno);
        }
    }

    return 0;

}

The execl() function is a wrapper function for execve(). The execl() function takes as its first argument the pathname of the file to execute. Following this first argument is a variable number of arguments to pass to the program being called. By convention, the second argument should be the name of the program. The list of arguments must be terminated by a NULL value so that execl() can tell where the argument list ends. The program called by execl() inherits the environment specified in the current program’s ENVIRON variable.

#include <stdio.h>
#include <unistd.h>
#include <errno.h>


int main(void){

    int retValue;

    retValue = execl("/bin/date", "date", NULL);
    
    if(retValue==-1){
        printf("Error no %d", errno);
    }

    return 0;

}

If you get the chance, please take a look at my author page on Amazon.com: http://www.amazon.com/Al-Jensen/e/B008MN382O/

Advertisements

Basic POSIX Process Creation

Each process is identified by a number known as the process ID, or pid for short. The pid for the first process is 1, and each subsequent process is assign a new, unique positive integer. It’s a relatively simple matter to get the process ID number for the current process. The getpid() function returns the current function and the getppid() function returns the parent’s ID. Both functions return the pid_t data type, which is an int.

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

int main(void){
    
    pid_t thisPID = getpid();
    pid_t parentPID = getppid();
    
    printf("PID of current process: %d\n", thisPID);
    printf("PID of parent process: %d\n", parentPID);
    
    return 0;
    
}

Note that the pid_t type is defined in the header file <sys/types.h>.

In Linux, processes form a  hierarchy known as the process tree. Traditionally, the first process is known as init; we can confirm this by issuing the bash command ps aux | head in a terminal or pseudo-terminal. New processes are created with the fork() system call; this system call takes no arguments and creates a duplicate of the calling process. The duplicate is called the parent, and the original process is called the child. If fork() fails, it returns -1.

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

int main(void){
    
    fork();
            
    //prints twice
    printf(“Saluton Mundo!\n”);
    
    fork();
    
    //prints four times
    printf(“Greetings, Professor Falken.\n”);
    
    return 0;
    
}

Note that processes execute in parallel. Both the parent and child processes continue executing the remaining statements in the program. If the fork() function returns a value less than 0, we know that the function call has failed.

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

int main(void){
    
    if((fork())<0){
        printf("fork failed.\n");
        exit(1);
    } else {
        printf("Fork succeded!");
        sleep(1);
        exit(0);
    }
    
    return 0;
    
}

The fork() function is unusual in that it returns two different values. The return value in the child process is zero, but the return value in the parent process is the process ID of the new child.

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

int main(void){
    pid_t pid;
    
    if((pid=fork())<0){
        printf("problem forking.\n");
        exit(1);
    }else if (pid==0){
        printf("In the child process.\n");
    } else {
        printf("In the parent process.\n");
    }
    
    return 0;
    
}

Alright, let’s tie it all together in one altogether nifty example.

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

int main(void){
    
    pid_t child;
    
    if((child=fork())<0){
        printf("Problem with fork()\n");
        exit(1);
    } else if (child == 0){
        printf("In child process, parent ID %d\n", getppid());
        printf("In child process, process ID %d\n", getpid());
    } else {
        printf("In parent process, process ID %d\n", getpid());
    }
    
    return 0;
    
}