In a previous post, I explained how the fork() and exec() system calls are used to create processes on Unix. In this post, I will guide you through a step-by-step practical example of how you can create a process using these two system calls in Linux — the most popular Unix-like OS on the market today.
Let’s get started with the first system call, fork(). Here is the declaration of this function:
int fork();
As I mentioned in the last post, fork() creates a copy of the process that invoked it. After fork() returns, there are two processes in the system. Both of these processes have the same image — i.e., their memory segments have identical content. Note that the fork() system call returns an int, this return value can be used to differentiate between the old process and the newly forked process. The return value of fork() is the PID (process ID) of the newly created process in the old process, and zero in the newly created process. OK, enough theory, lets get our hands dirty with the first code example. Create a new .c file, lets assume that it is called test.c, using your favorite text editor and copy the code below into it.
#include <stdio.h> int main(){ int val=0; printf("This is before I make a call to fork()\n"); val=fork(); printf("The value of val is %d\n",val); return 0; }
The code is pretty simple. Up-to line 4, the program is no different from any C program on Linux. A variable, val, is defined in line 3, and the program prints out a message in line 4 stating that the fork() function is yet to be invoked. Note that up-to line 4, there is only one process executing. The interesting stuff begins to occur starting at line 5.
Line 5 contains a call to the fork() function. When this function finishes executing, there will be two processes in the system. Therefore, the fork() function returns in two processes; the original process, and the newly created process.
Go ahead and compile and run this code. Here is the output I got when I ran the program on my system.
This is before I make a call to fork() The value of val is 3713 The value of val is 0
Notice that the value of val is printed out two times, even though the printf statement is called once. This happens because after the fork() system call in line 5, all the instructions are executed twice — once in the main process and once in the newly forked process. Also, note that val takes on the values 0 and 3713. As mentioned before, zero is printed out in the newly created process and 3713 is printed out in the old process.
Let’s take this one step further, let’s check the return value of fork() and explicitly print out whether we are in the old process or the new process. The code below does this.
#include <stdio.h> int main(){ int val=0; printf("This is before I make a call to fork()\n"); val=fork(); if (val==0) printf("We are in the new process\n"); else printf("We are in the old process\n"); return 0; }
As you can see, we now check the value of val to see whether it is zero or not. If it is zero, we are in the new process, otherwise we are in the old process.
Now that we can differentiate between the two processes, let us see how we can load a new image into the newly forked process. Remember that we do this using the exec() family of system calls in Unix. There are many versions of exec() in the standard C libraries provided on Linux, do a man exec to check out the semantics of the different version available. I will use the execl() function in this post. Here is the declaration of execl().
int execl(const char *path, const char *arg, ...);
The first parameter, path, is the path containing the binary file we would like to load (including it’s name). The second parameter, arg, is the name of the executable, other arguments, all of type char *, represent the command line parameters you want to send to the new executable. For this example, I chose to create a new process representing the command ls -l. Therefore, I fork a new process and then load the ls binary into this new process. Here is the code I used.
#include <stdio.h> #include <unistd.h> int main(){ int res=0; int status=0; char *path = "/bin/ls"; char *arg0 = "ls"; char *arg1 = "-l"; printf("This is before call to fork()\n"); res=fork(); if (res==0){ printf("I am now in the child process\n"); execl(path,arg0,arg1,NULL); }else printf("I am now in the parent process\n"); wait(&status); return 0; }
Let’s zoom in on the new parts of this program. First, examine lines 8-10. In these lines, we setup the strings that will be passed to the execl function. The first string is the path to the executable (including its name), the second is its name and the third is the parameter we wish to send to the executable.
Next, notice that the execl call occurs within the if statement on lines 14-17. This if statement identifies the new process, since it checks if the return value of fork() is equal to zero. This is done because we want to overlay the image of the ls binary on the address space of the newly created process. The final piece of the puzzle is the wait() system call on line 19. I will not go into the details of this call, but suffice it to say that the wait call is necessary because there are now two processes executing and we want the main process to wait until the child it has forked terminates before exiting, this will ensure that the program terminates correctly. Here is the output I received when I ran this program:
This is before call to fork() I am now in the parent process I am now in the child process total 24 -rwxr-xr-x 1 sherif sherif 7239 2011-08-04 18:30 process -rwxr-xr-x 1 sherif sherif 7166 2011-08-04 21:03 test2 -rw-r--r-- 1 sherif sherif 234 2011-08-04 21:03 test2.c -rw-r--r-- 1 sherif sherif 394 2011-08-04 21:37 test.c
That’s it folks, happy forking! 😀
11 Comments