![]() |
Faculty of Engineering and Information Technology (FEIT)
Department of Computer Science
|
Laboratory 9Signals and UNIX Semaphores
You are expected to use the man pages! Remember that man pages with the same title may appear in multiple sections. If you want to search all sections use man -a. SignalsA signal is like a message that when delivered to a process may cause it to halt what it is currently doing and execute some alternative set of instructions. We say "may" because a process can elect to ignore certain signals. Each signal is characterized by an integer value, with some values pre-assigned. For instance you may be familiar with the signal used to kill a running process. This signal is known as SIGKILL, has a value of 9, and can be sent to a particular process ID (PID) by issuing the command kill -9 PID. Details of other predefined signals are available from the man pages by typing man 7 signal. A process may control the effects of signals by installing a signal handler function. In this sense, signals are similar to the traps that you used with PeANUt in COMP2300 (however, signals are not to be confused with traps or interrupts, as they are purely implemented by the software and OS). In the following code we create two signal handlers. Using the function signal(), we assign these to be executed when our executing process receives either SIGUSR1 or SIGUSR2. We then put our program into an infinite number of 1 second sleeps.
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void handler1(int sig);
void handler2(int sig);
int main(int argc, char* argv[]) {
/* Print out process ID and signal values */
printf("Hello from Process ID %d \n", getpid());
printf("Value of SIGUSR1 %d \n", SIGUSR1);
printf("Value of SIGUSR2 %d \n", SIGUSR2);
/* Assign the signal handlers*/
signal(SIGUSR1, handler1);
signal(SIGUSR2, handler2);
while (1) sleep(1);
return 0;
}
void handler1(int sig) {
printf("Handler 1 caught signal %d\n", sig);
return;
}
void handler2(int sig) {
printf("Handler 2 caught signal %d\n", sig);
return;
}
ACTIONS
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
int main(int argc, char* argv[]) {
double value;
long int iseed;
struct timeval tv;
/* Use time of day to generate seed for random number generator*/
gettimeofday(&tv, NULL);
iseed = tv.tv_usec;
srand48(iseed);
value = drand48();
if (value < 0.8) {
/* 80% of time code terminates normally */
printf("Process %d will terminate \n", getpid());
sleep(1);
} else {
/* 20% of time code runs for ever */
printf("Process %d will run for ever \n", getpid());
while (1)
sleep(1);
}
return 0;
}
ACTIONS
Unix SemaphoresJust to fully convince you that introducing concurrent processing with Ada95 is much easier than using Unix we will now look briefly at using Unix semaphores (or maybe we will look at Unix semaphores because they are important in their own right, especially since they can be shared by completely separate programs). To obtain a Unix semaphore we use the semget() function:
int semget(key_t key, int nsems, int semflg);
This returns an identifier that is used to manipulate the semaphore,
however rather than obtaining just a single semaphore this function
allows the user to allocate an array of nsems semaphores. For
this reason, the return value is referred to as the semaphore set
id, and therefore to identify a specific semaphore we need to specify
both the semaphore set id AND the number of the semaphore within this
semaphore set. (You should also be aware that there are system dependent
limits on the number of semaphores in a semaphore array. E.g. on Linux
look in /proc/sys/kernel/sem or
/usr/include/linux/sem.h. On Solaris look in
/etc/system. You should also consider why such semaphores
areallocated in sets, rather than individually)
Of the other parameters of this system call, the first is a key that we will brush over for the moment, while the third is used to determine whether, for example, we want to create a new semaphore set and if so what access permissions to assign it. Having created a semaphore set it can be manipulated using calls to semctl():
int semctl(int semid, int semnum, int cmd, ...);
We will use this function to initialize the value of each semaphore. (do
man semctl; for more info). While to do the traditional
"P" and "V" semaphore operations we use:
int semop(int semid, struct sembuf *sops, unsigned nsops);
...pretty easy really!!
The following code puts this all together. First it creates a semaphore set with 2 elements. It then initializes the first semaphore within this set a value of 1 while gives the second a value of 0. It then does a double check to verify that these values are set, before forking a second process. Since the semaphore array is created before the call to fork both the parent and the child have access to it. Each process then enters a loop that selectively decrements or increments the first semaphore (ignoring the second semaphore for the time being).
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/sem.h>
#include <sys/types.h>
#include <sys/ipc.h>
union semun {
int val;
struct semid_ds *buf;
ushort *array;
};
int main(int argc, char* argv[]) {
key_t sem_key; int nsems, semflg; // used to set up semaphore array
int semid; // OS-assigned id of semaphore array
union semun semctl_arg; // used to set elements of array
struct sembuf sem0_P, sem0_V, sem1_P, sem1_V;// specify P & V ops for each
int irpt; pid_t fork_pid;
/* SEMAPHORE CREATION */
/* create a new semaphore with PRIVATE key */
sem_key = IPC_PRIVATE;
/* number of semaphores in the array */
nsems = 2;
/* set flag to creation with user read/write access permissions */
semflg = IPC_CREAT | 0600;
/* Now create the semaphore array */
semid = semget(sem_key, nsems, semflg);
if (semid < 0) {
perror("Semaphore creation error");
exit(-1);
}
printf("Semaphore created - ID = %d\n", semid);
/* SEMAPHORE INITIALISATION */
/* initialize element 0 of the semaphore array to 1 */
semctl_arg.val = 1;
if (semctl(semid, 0, SETVAL, semctl_arg) < 0) {
perror("Semaphore 0 initialisation error ");
exit(-1);
}
/* initialize element 1 of the semaphore array to 0 */
semctl_arg.val = 0;
if (semctl(semid, 1, SETVAL, semctl_arg) < 0) {
perror("Semaphore 1 initialisation error ");
exit(-1);
}
/*CHECK VALUE OF SEMAPHORES */
printf("Value of semaphore array 0 %d\n", semctl(semid, 0, GETVAL));
printf("Value of semaphore array 1 %d\n", semctl(semid, 1, GETVAL));
/* setup semaphore op buffers for P (decrement) and V (increment) */
sem0_P.sem_num = 0; sem0_P.sem_op = -1; sem0_P.sem_flg = 0;
sem0_V.sem_num = 0; sem0_V.sem_op = +1; sem0_V.sem_flg = 0;
/*FORK A SECOND PROCESS - THEY WILL SHARE THE SEMAPHORE ARRAY */
fork_pid = fork();
/* loop 10 times and manipulate the semaphore */
for (irpt=0; irpt<10; irpt++) {
/* SEMAPHORE GIVES MUTUAL EXCLUSION OF PARENT/CHILD IN FOLLOWING REGION*/
/* Semaphore op - waits until semaphore > 0 then proceed */
if (semop(semid, &sem0_P, 1)) {
perror("semaphore decrement error");
exit(-1);
}
printf("%s %d\n", fork_pid? "Parent": "Child", irpt);
fflush(stdout);
/* Semaphore op - increment semaphore - no waiting */
if (semop(semid, &sem0_V, 1)) {
perror("semaphore increment error");
exit(-1);
}
/* END MUTUAL EXCLUSION */
} // for(...)
/* Have parent wait for child to terminate for tidy output! */
if (fork_pid)
wait(NULL);
return 0;
}
ACTIONS
#!/bin/csh -f
set us="$user"
foreach sh (` ipcs -s | grep $us | awk '{print $2}' `)
ipcrm -s $sh
end
Save this code to a file, make sure it has execute permissions, and then
execute this file.
Semaphore created - ID = 393219 Value of semaphore array 0 1 Value of semaphore array 1 99 Child 0 Child 1 Child 2 Child 3 Child 4 Child 5 Child 6 Child 7 Child 8 Child 9 Parent 0 Parent 1 Parent 2 Parent 3 Parent 4 Parent 5 Parent 6 Parent 7 Parent 8 Parent 9This is fine, but the intention was actually to use the semaphore to cause the Parent/Child to execute the region of mutual exclusion in an alternating fashion. So that we get output that looks like: Semaphore created - ID = 98307 Value of semaphore array 0 1 Value of semaphore array 1 99 Parent 0 Child 0 Parent 1 Child 1 Parent 2 Child 2 Parent 3 Child 3 Parent 4 Child 4Your task is to modify the above code to achieve this. Do this by using both of the elements of the semaphore array. To ensure that you do this correctly augment the printout to include the current wall-clock time on the parent or child process (use gettimeofday() to obtain the wall-clock time). Hint: the operative word is alternating; another approach is to recognize that a semaphore can be used to implement a shared variable. Next LabShared memory segments and requeue implementations ...... just joking!If you do want to learn about shared memory, read the man pages for shmget, shmat, shmctl and shmdt, and/or take COMP4300 in the future. If you want to do more Ada take COMP4330, while if you want to learn more computer architecture, build a cluster and run some applications on it do COMP3320. PS. it is not just software engineers who write code that runs forever - just mainly software engineers! |