
Programming System
Pthread et Exec
Tutorial class by Philippe Décogné
09/11/2002 (dd/mm/yy)
Contrast fork's and pthread_create's behaviour.
Differences between fork's and pthread_create's behaviour
When forking, the child process inherits the parent process as it is at fork time. Thereafter, each process owns a distinct context: address space and data are distinct. Both processes can communicate via pipes or messages queues.
When creating a lightweight process (or thread), address space and data are shared. However you must take care of the synchronization. The secondary thread starts at the function called by pthread_create, the primary thread continues just after pthread_create. Both execution run in pseudo-parallel.
Benefits of lightweight processes:
Caption

A global variable is initialized in the main process. This variable is then changed in the parent process and in the child process (or in the secondary thread) to illustrate resources sharing/non-sharing.
A third example shows what happens if the variable is not global when using threads.
Functions called:
Pthread_create: int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
Creates a new lightweight process, the attributes of which are attr or NULL. Upon creation, the process identifier is stored at address thread. Returns 0 if the new thread is created, an error number otherwise.
The new thread executes the routine start_routine with an argument arg. When the routine terminates an implicit call to pthread_exit occurs. The call uses the return value of the routine start_routine as status value for exit.
Library to be included: pthread.h
Man page: pthread_create(3) - subroutines.
Pthread_join: int pthread_join(pthread-t thread, void **value_ptr)
Suspends the execution of the calling thread until the thread thread terminates. If value_ptr is not null, the value passed to pthread_exit by the terminating lightweight process is store at the address pointed to by value_ptr. Returns 0 if successful, otherwise an error number.
Library to be included: pthread.h
Man page: pthread_join(3) - subroutines.
First program
/* hellopid.c
This program creates a child process which performs an addition based
on a global counter */
/* includes */
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
/* Global variables */
int i; /* a counter */
int main()
{
/* Variables */
pid_t pid;
int status, waitsignal;
/* Initialization */
i = 0;
/* Create a child process */
pid = (int)fork();
if (pid == -1)
{
perror("Unable to create the child process");
}
else if (pid == 0)
{
i += 10;
printf("Hello child: %d\n", i);
i += 20;
printf("Hello child: %d\n", i);
exit(EXIT_SUCCESS);
}
else
{
i += 1000;
printf("Hello parent: %d\n", i);
i += 2000;
printf("Hello parent: %d\n", i);
waitsignal = wait(&status);
if (waitsignal == -1)
{
perror("Wait failed");
exit(EXIT_FAILURE);
}
else
{
printf("Wait succeeded");
}
}
return 0;
}
Outputs
./hellopid
Hello parent: 1000
Hello parent: 3000
Hello child: 10
Hello child: 30
As i is initialized to 0 and the fork occurs before the first addition in the main process, the results in both parent and child processes are independent.
Second program
/* hellothread.c
This program creates a thread which performs an addition based on a counter.
The main thread operates also on the counter. The counter is a global variable.*/
/* includes */
#include <stdio.h>
#include <pthread.h>
/* Functions declarations */
void addition();
/* Global variables */
int i; /* counter for the addition */
int main()
{
/* Variables */
pthread_t thread_id; /* pointer to address of threads ids */
/* Initialization */
i = 0;
/* Create a thread */
if (pthread_create(&thread_id, NULL,(void *(*)())addition, NULL) == -1)
{
perror("Unable to create the thread");
}
/* Addition in the main thread */
i += 1000;
printf("Hello main thread, i is: %d\n", i);
i += 2000;
printf("Hello main thread, i is: %d\n", i);
/* Wait for the secondary thread to terminate */
pthread_join(thread_id, NULL);
return 0;
}
void addition()
{
i += 10;
printf("Hello secondary thread, i is: %d\n", i);
i += 20;
printf("Hello secondary thread, i is: %d\n", i);
}
Outputs
./hellothread
Hello main thread: 1000
Hello main thread: 3000
Hello secondary thread, i is: 3010
Hello secondary thread, i is: 3030
As i is a global variable and the addition in the primary thread occurs before the addition in the secondary thread, the values of i in both threads are the same. The result may be different according to the priority policy.
Third program
/* hellothread1.c
This program creates two threads which perform an addition based on a counter.
The main thread operates also on the counter. The counter is not a global variable.*/
/* includes */
#include <stdio.h>
#include <pthread.h>
/* Fonctions declarations */
void addition();
int main()
{
/* Variables */
pthread_t thread_id; /* pointer to address of threads ids */
int i; /* variable for the addition */
int j; /* counter for the loop */
/* Initialization */
i = 5;
j =0;
for (j = 0; j < 2; j++)
{
/* Create a thread */
if (pthread_create(&thread_id, NULL,(void *(*)())addition, (void *)i) == -1)
{
perror("Unable to create the thread");
}
/* Addition in the main thread */
i += 1000;
printf("Hello main thread, i is: %d\n", i);
i += 2000;
printf("Hello main thread, i is: %d\n", i);
/* Wait for the secondary thread to terminate */
pthread_join(thread_id, NULL);
}
return 0;
}
void addition(i)
{
i += 10;
printf("Hello secondary thread, i is: %d\n", i);
i += 20;
printf("Hello secondary thread, i is: %d\n", i);
}
Outputs
./hellothread1
Hello main thread, i is: 1005
Hello main thread, i is: 3005
Hello secondary thread, i is 15
Hello secondary thread, i is: 35
Hello main thread, i is: 4005
Hello main thread, i is: 6005
Hello secondary thread, i is: 3015
Hello secondary thread, i is: 3035
As i is not a global variable, and as it is initialized to 5, and the secondary thread is created before the addition in the primary thread, there is always a gap between the values of i in both threads.
Format of the primitives
Execl: int execl(const char *path, const char *arg, ..)
Execlp: int execlp(const char *file, const char *arg, ..)
Execle: int execle(const char *path, const char *arg, ..)
Exect: int exect(const char *path, char *const arg[], char *const envp[])
Execv: int execv(const char *path, char *const argv[])
Execvp: int execvp(const char *file, char *const argv[])
Execve: int execve(const char *path, char *const argv[], char *const envp[])
The exec primitives replace the current process by a new process. As a rule, there is no return to the calling process, unless the creation of the new process fails, in which case the return value is -1. File descriptors open in the calling process remain open in the new process, unless the flag close-on-exec has been set.
The first argument is the full path to an executable object file or a file of data for an interpreter (i.e. a shell script). The second argument is made of the parameters sent to the file to be executed either as a list of pointers to character strings or as an array. The first element of the list (or the array) is the name of the file to be executed, the last one is a NULL pointer. The third optional argument is a list (or an array) of environment pointers.
Library to be included: unistd.h
External global variable to be used: extern char **environ in case you want to pass specific environment variables.
Man page: execve(2) - system calls, execl(3) - subroutines.
Decoding the name of the primitives
Format of main in the new process
int main(argc, char *argv[], char *arge[])
where arg is the number of elements of the command (command name + arguments), argv[0] is the command name, argv[1] to argv[n] are the n arguments of the command, and arge are the environment variables of the process.
Basic code for the parent process
...
pid = fork();
if (pid == 0)
{
exec(...); // erase all code after that
code executed by the child process
}
else
{
code executed by the parent process
}
common code
Exec behaviour

First program
/* lsessai.c
Create a process which executes the command ls -l. */
/* Includes */
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
/* Global variable */
extern int errno;
int main(void)
{
pid_t child_pid;
pid_t wait_pid;
int wait_status;
/*Create the child process */
child_pid = fork();
if ((int)child_pid == -1)
{
printf("Error in creation of child process: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
else if (child_pid == 0)
{
printf("The pid of the parent process is: %d\n", (int)getppid());
execlp("ls", "ls", "-l", NULL);
printf("Error in execution of command line: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
else
{
printf("The pid of the child process is: %d\n", (int)child_pid);
wait_pid = wait(&wait_status);
if ((int)wait_pid == -1)
{
printf("Error in wait call: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
if (WIFEXITED(wait_status))
{
printf("End of child process, status is: %d\n", WEXITSTATUS(wait_status));
}
else if (WIFSIGNALED(wait_status))
{
printf("Child process exits with signal: %d %s\n", WTERMSIG(wait_status), WCOREDUMP(wait_status) ? " with core file.\n": "");
}
else
{
printf("The child process has stopped.\n");
}
}
return 0;
}
Second program
/* finkessai.c
Create one process which executes finkselfupdate-cvs, then, if successful,
create another process which executes finkupdate-all.
Use the primitives execve and excle, just for the purpose of demo */
/* Includes */
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
/* Define */
/* Define the path for fink command - Change it you don't use the default path */
#define FINK_PATH "/sw/bin/fink"
/* Global variables */
extern int errno;
/* used to pass the current environment to the newly created process instead of the array envp[] */
extern char **environ;
/* Function to execute while entering the first child process */
static void exec_finkcvs_cmd(void)
{
/* Define the arguments to pass to the function execve */
static char *argv[] = {"fink", "selfupdate-cvs", NULL};
/* Just to trace the execution */
printf("Entering the exec_finkcvs_cmd\n");
/* Execute the execve function */
execve(FINK_PATH, argv, environ);
/* If the program goes here, call to execve has failed */
printf("Call to execve has failed: %s\n", strerror(errno));
/* Pass the status to parent process */
exit(EXIT_FAILURE);
}
/* Function to execute while entering the second child process */
static void exec_finkupdate_cmd(void)
{
/* Just to trace the execution */
printf("Entering the exec_finkupdate_cmd\n");
/* Execute the execle function */
execle(FINK_PATH, "fink", "update-all", NULL, environ);
/* If the program goes here, call to execve has failed */
printf("Call to execle has failed: %s\n", strerror(errno));
/* Pass the status to parent process */
exit(EXIT_FAILURE);
}
int main(void)
{
pid_t child1_pid;
pid_t child2_pid;
pid_t wait_pid1;
pid_t wait_pid2;
int wait_status1;
int wait_status2;
/*Create the first child process */
child1_pid = fork();
if ((int)child1_pid == -1)
{
printf("Error in creation of first child process: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
else if (child1_pid == 0)
{
printf("The pid of the parent process is: %d\n", (int)getppid());
exec_finkcvs_cmd();
}
else
{
printf("The pid of the first child process is: %d\n", (int)child1_pid);
wait_pid1 = waitpid(child1_pid, &wait_status1, WUNTRACED);
if ((int)wait_pid1 == -1)
{
printf("Error in wait call after first child process: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
if (WIFEXITED(wait_status1))
{
printf("End of first child process, status is: %d\n", WEXITSTATUS(wait_status1));
/*Create the second child process */
child2_pid = fork();
if ((int)child2_pid == -1)
{
printf("Error in creation of second child process: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
else if (child2_pid == 0)
{
printf("The pid of the parent process is: %d\n", (int)getppid());
exec_finkupdate_cmd();
}
else
{
printf("The pid of the second child process is: %d\n", (int)child2_pid);
wait_pid2 = waitpid(child2_pid, &wait_status2, WUNTRACED);
if ((int)wait_pid2 == -1)
{
printf("Error in wait call after second child process: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
if (WIFEXITED(wait_status2))
{
printf("End of second child process, status is: %d\n", WEXITSTATUS(wait_status2));
}
else if (WIFSIGNALED(wait_status2))
{
printf("Second child process exits with signal: %d %s\n", WTERMSIG(wait_status2), WCOREDUMP(wait_status2) ? " with core file.\n": "");
}
else
{
printf("The second child process has stopped.\n");
}
}
}
else if (WIFSIGNALED(wait_status1))
{
printf("First child process exits with signal: %d %s\n", WTERMSIG(wait_status1), WCOREDUMP(wait_status1) ? " with core file.\n": "");
}
else
{
printf("The first child process has stopped.\n");
}
}
return 0;
}