Operating System Concepts: Alternative Strategies, Command Line Interface, And System Monitoring Tools
Task 1: Apply New Knowledge of Operating System Concepts
The producer was implemented by use of a few includes and functions. The main functions used were
- ftok; used to generate a unique key for the shared memory segment.
- shmget : used to create the memory segment and obtain the shared memory ID
- shmctl used in destroying the shared memory segment.
The producer was tested and confirmed to be running okay, and destroyed the shared memory segment successfully after the 10 seconds sleep.
Task 2: Send a single message from producer to consumer
Stage 1: Modification of the Producer
The producer made in task 1 was modified to attach a the shared memory segment. This was done by use of the shmat function as follows: char *sharedMemoryPTR = (char*) shmat(shared_memory_id,(void*)0,0);
A for loop was then used to set a null on all the memory locations of the shared memory segment. The MAX_SIZE constant has a value of 32, which is the maximum size of the shared memory.
for (int c = 0; c <= MAX_SIZE; c++)
*s++ = ‘ ‘;
A message was then obtained from the user and set to the memory segment. To obtain the message the code used was; fgets(message, sizeof message, stdin);
The message was written into the memory segment using the code; strncpy(sharedMemoryPTR , message,MAX_SIZE );
To detach the shared memory segment, the function shmdt as follows;
shmdt(sharedMemoryPTR);
Finally,, the shared memory segment was destroyed using the function shmctl ; shmctl(shared_memory_id, IPC_RMID, NULL);
Stage 2: consumer.c
A consumer was created ; to enable the consumer connect to the shared memory segment, a single key was used for both the producer and the consumer. The key was therefore statically embedded in the code with a definite value. The consumer connected to the shared memory, read the message in the memory then detached from the segment. The screenshots below shows both the producer and consumer running;
Task 3: Send a series of messages between producer and consumer
Stage 1: Modification of the producer to handle a series of messages
The strategy employed in allowing the producer to handle a series of messages is the use of a while loop, that runs infinitely until an EOF is encountered. For this case, the inputs are entered by the user manually, so to send the EOF on Linux, one has to type Ctrl+D which sends an EOF to the program. Since the program uses fgets to obtain the user inputs from stdin, the EOF is interpreted as NULL. The code snippet for the loop is as shown below.
while (fgets(message, sizeof message, stdin) != NULL){
//Write the message to the memory segment
strncpy(sharedMemoryPTR , message,MAX_SIZE );
printf(“Memory segment contains the message: %s n”, sharedMemoryPTR);
printf(“-Sleeping for 10 seconds!!…n”);
sleep(10);
printf( “Enter a message of up to 20 characters or Ctrl+D to exit:”);}
The screen shot below shows successful testing of the code; the program runs until Ctrl + D is pressed which indicates EOF.
Stage 2 : Consumer modification to read a series of messages
To facilitate detection of new messages, the available memory address space after address 20 was used. A flag was set at position 26 of the memory. The value of this position was set to 5; whenever the consumer checks the memory and finds that the value is 5, it knows that no new messages have been written by the producer. This is because the producer destroys this value before setting the message on the first 20 locations. This means that whenever the consumer checks for the value at position 25 and finds that it is not the value of the flag [5], it immediately indicates that the server has written into the shared memory. The consumer therefore reads the message.
Task 2: Send a Single Message from Producer to Consumer
The consumer also maintains a counter which is incremented by 5 whenever the consumer finds that the producer has not posted new messengers; If the producer places new messages the counter is reset to 0, however if no new messages are posted, the counter continues to increment and if the value of the counter reaches 410 – which is nearly a wait time of 20 seconds, the consumer assumes that the producer is no longer active. It therefore detaches from the shared memory segment and exits.
char *s = shared_mem;
//the counter is to be used to determine if the producer is no longer available
int counter = 0 ;
for(;;){
if(shared_mem[25] == 5){
counter += 5;
//Bussy Waiting using for loop
for(int i =0 ; i< 100000000; i++);
if(counter > 410 ){
printf(“Seems the server is not active n”);
shmdt(shared_mem);
printf(“-Detached from Shared Memory n”);
exit(0);
}else{
//reset counter to show that the producer is still active
counter = 0;
printf(“-Message Read from Shared Memory : %sn”,shared_mem);
//set all the memory location to NULL
for (int c = 0; c <= MAX_SIZE; c++)
*s++ = ‘ ‘;
//Set flag that will tell the consumer when the segment is changed
shared_mem[25] = 5;
Producer
When the producer and consumer were passing messages between one another, it was observed that the consumer had the highest CPU usage of both processes. The high CPU usage is attributed to the use of busy wait algorithm by the consumer. For this consumer, a for loop was used to implement the busy waiting which, although it doesn’t do anything useful , consumes a lot of computing resources. On the other hand, the producer sleeps ; relieving the CPU to perform other tasks.
At this stage, the producer was modified to allow the user to control the sleep interval by the message content. The user’s input message in the form of $P:n, where n is a series of digits representing sleep time in milliseconds, was taken and passed through a number of filtrations and checks to ascertain its validity.
Nested IF/ELSE structures were used to check if the input was in the valid format and the value of n was within a range of 800 – 20000. A char array was first used to store the values of n, which were extracted by use of a for loop.
To start with, the control character was checked and format of the first 3 characters ascertained using the criteria; message[0] == ‘$’ && message[1] == ‘P’ && message[2] == ‘:’
If this condition was met, the value of n was then extracted into an array by use of a for loop; as shown in the code snippet below;
//Check for control char AND Format
if(message[0] == ‘$’ && message[1] == ‘P’ && message[2] == ‘:’){
Task 3: Send a Series of Messages Between Producer and Consumer
for(int x = 3; x < sizeof(message);x++){
if (isdigit(message[x]) != 0){
validDigits[x-3] = message[x];
count++;
}else{
break; //the loop breaks at the first instance of a non digit
}//END ELSE
}//END FOR
The array used to store the value of n is then filtered to only extract values that were entered through message. The value obtained is the sleep time, which has to be converted to seconds and nano seconds. The code below was used in the conversion.
sleepTime = atoi(filteredValues);
struct timespec req;
/*Since nano sleep takes both seconds
* and nano seconds, the following block
* converts milliseconds to nano seconds
* based on the value of the sleep time
**/
if( sleepTime > 999){
req.tv_sec = sleepTime / 1000;
req.tv_nsec = (sleepTime – ((long)req.tv_sec * 1000)) * 1000000;
}else{
req.tv_sec = 0;
req.tv_nsec = (sleepTime*1000000) * 1000000;
The sleep was then initiated using nano sleep as show below;
printf(“-Sleeping for %i seconds!!…n”,sleepTime/1000);
nanosleep(&req , NULL);
Stage 2 : Testing with various sleep intervals
Testing was done with a number of sleep intervals; it was observed that the CPU usage for the producer was more erratic with the use of variable intervals. In some instances the CPU usage reduced significantly while in some instance – especially when short sleep intervals were used – the usage increased as compared to when a constant sleep value was used.
Starting the consumer after the producer had been running for a while did not have any major effect; the consumer and producer were synchronized , this is mainly due to the fact that there was room for only one message. However, when shorter sleep time were used. The consumer fell out of sync with the producer and showed signs of missing out some messages.
Replace Busy waiting with semaphores
To replace the busy wait with a semaphore, modification were done on both the producer and the consumer. Since the shared location has to be accessed by both the producer and consumer; the semaphore was created by the producer using a key that was then stored in a file. The producer accessed the file and obtained the key, allowing it to connect to the existing semaphore. This approach made it possible to synchronize access to the shared resource.
The implementation of the semaphores had a significant positive effect in terms of CPU usage. By comparison to the busy wait; the semaphore resulted in minimal use of the CPU.
The producer was modified to first check if the value at position 30 is set to 5; which indicates that the consumer has accessed the shared memory.
Stage 2
Top command was used to observed CPU utilisation by the producer and consumer. It was observed that with the use of the semaphore, the consumer used less CPU time than when using the busy wait. By comparison to results obtained from the other tasks, the use of semaphore.
Stage 1 : Circular buffer
The producer was modified to write character by character to the buffer instead of the complete line as it was in task 5. To write the data to the buffer, a for loop was used, which iterated over the char array picking each character and pushing it to the end of the buffer. When a new value was written into the buffer by the producer, every existing value was pushed ahead; effectively creating a FIFO structure where new values at the end of the buffer pushes out the older values.
Stage 2: Character-based sleep control message processing
To implement character-based control on sleep time of both the producer and the consumer, the first three characters of any messages were read and then each character tested. The first task was to identify the occurrence of the control character $, then determining if the message is for the producer or consumer; finally the characters after the colon were tested if they are digits using the isDigit function. The default sleep interval was set at 100ms for both the producer and the consumer.
Stage 3 Testing
Testing was done using a specially designed test scrip which tried to check if the variation of sleep time between the producer and the consumer affected CPU usage and accuracy of the read and write into the shared memory. Test showed that the program performed as expected. The use of semaphore ensured that no message could be overwritten before the consumer read the message.