You are on page 1of 14

Ques 1 : Study and implement the Dining Philosopher’s problem using C/C++

programming language.

Solution:

The dining philosophers problem is invented by E. W. Dijkstra. Imagine that five


philosophers who spend their lives just thinking and easting. In the middle of the
dining room is a circular table with five chairs. The table has a big plate of spaghetti.
However, there are only five chopsticks available, as shown in the following figure.
Each philosopher thinks. When he gets hungry, he sits down and picks up the two
chopsticks that are closest to him. If a philosopher can pick up both chopsticks, he
eats for a while. After a philosopher finishes eating, he puts down the chopsticks and
starts to think.

Analysis

How do we write a threaded program to simulate philosophers? First, we notice that


these philosophers are in a thinking-picking up chopsticks-eating-putting down
chopsticks cycle as shown below.

The "pick up chopsticks" part is the key point. How does a philosopher pick up
chopsticks? Well, in a program, we simply print out messages such as ``Have left
chopsticks'', which is very easy to do. The problem is each chopstick is shared by two
philosophers and hence a shared resource. We certainly do not want a philosopher
to pick up a chopstick that has already been picked up by his neighbor. This is a
race condition. To address this problem, we may consider each chopstick as a
shared item protected by a mutex lock. Each philosopher, before he can eat, locks
his left chopstick and locks his right chopstick. If the acquisitions of both locks are
successful, this philosopher now owns two locks (hence two chopsticks), and can eat.
After finishes easting, this philosopher releases both chopsticks, and thinks! This
execution flow is shown below.

Because we need to lock and unlock a chopstick, each chopstick is associated with a
mutex lock. Since we have five philosophers who think and eat simultaneously, we
need to create five threads, one for each philosopher. Since each philosopher must
have access to the two mutex locks that are associated with its left and right
chopsticks, these mutex locks are global variables.

Program

Let us see how the above analysis can be convert to a program. Since each
philosopher should be run as a thread, we define a Philosopher class as a derived
class of class Thread.

#include "ThreadClass.h"

#define PHILOSOPHERS 5

class Philosopher: public Thread


{
public:
Philosopher(int Number, int iter);
private:
int No;
int Iterations;
void ThreadFunc();

};

The constructor takes two arguments, Number for the number assigned to this
philosopher thread, and iter for specifying the number of thinking-eating cycles.

The implementation of this class, as shown below, should have all thinking, eating,
locking and unlocking mechanisms implemented. Since each chopstick must be
protected by a mutex lock, we declare an array Chopstick[ ] of pointers to Mutex.
Since the main program allocates these locks, they are declared as global variables
using extern.

Function Filler() generates a char array that contains some spaces. Note that this
function is declared to be static so that is can only be used within this file (i.e.,
Philosopher.cpp).

Let us look at the constructor. It receives two arguments. The first, Number, is
assigned by the main program to indicate which philosopher this thread represents.
The second, iter, gives the number of thinking-eating cycles for each philosopher.
The constructor is very simple. It gives this thread a name. Thus, if the value of
Number is 2 (i.e., philosopher 2), this thread will have the name Philosopher2.

#include <iostream>
#include "Philosopher.h"

extern Mutex *Chopstick[PHILOSOPHERS]; // locks for chopsticks

static strstream *Filler(int n)


{
int i;
strstream *Space;

Space = new strstream;


for (i = 0; i < n; i++)
(*Space) << ' ';
(*Space) << '\0';

return Space;
}

Philosopher::Philosopher(int Number, int iter)


: No(Number), Iterations(iter)
{
ThreadName.seekp(0, ios::beg);
ThreadName << "Philosopher" << Number << '\0';
}

void Philosopher::ThreadFunc()
{
Thread::ThreadFunc();
strstream *Space;
int i;

Space = Filler(No*2);

for (i = 0; i < Iterations; i++) {


Delay(); // think for a while
Chopstick[No]->Lock(); // get left chopstick
Chopstick[(No+1) % PHILOSOPHERS]->Lock(); // gets right chopstick
cout << Space->str() << ThreadName.str()
<< " begin eating." << endl;
Delay(); // eat for a while
cout << Space->str() << ThreadName.str()
<< " finish eating." << endl;
Chopstick[No]->Unlock(); // release left chopstick
Chopstick[(No+1) % PHILOSOPHERS]->Unlock(); // release right chopstick
}
Exit();

The function ThreadFunc() implements the executable code of a philosopher thread.


First of all, it creates a char array of No*2 spaces so that this thread's output would
be indented properly. After this, this thread iterates Iteration times. In each cycle,
this thread simulates thinking and eating. To this end, we use a method of class
Thread: Delay(). The purpose of Delay() is simply delaying the execution of the
thread for a random number of times. This simulates ``think for a while'' and ``eat
for a while.''

Let us look at how locking and unlocking of chopsticks is carried out. Suppose the
chopsticks are numbered counter clockwise. For philosopher i, his left chopstick is i
and his right chopstick is i+1. Of course, we cannot use i+1 directly, because when
i=4, the right chopstick of philosopher is 0 rather than 5. This can easily be done
with the remainder operator: (i+1) % PHILOSOPHERS, where PHILOSOPHERS is the
number of philosophers. In the above code, philosopher No thinks for a while, locks
his left chopstick by calling the method Chopstick[No]->Lock(), locks his right
chopstick by calling the method Chopstick[(No+1) % PHILOSOPHERS]->Lock(), eats
for a while, unlocks his left chopstick by calling the method Chopstick[No]-
>Unlock(), and unlocks his right chopstick by calling the method Chopstick[(No+1)
% PHILOSOPHERS]->Unlock(). This completes one thinking-eating cycle. This cycle
repeats for Iteration number of times.

Note that in the above code each philosopher picks up, or locks, his left
chopstick first followed by the right one.

The main program, as usual, is easy as shown below. The number of thinking-eating
cycles a philosopher must perform is the only command line argument. Since
mutex locks must be created before their uses, the main program allocates
the mutex locks before the creation of threads. In the following, each mutex
lock is created with a name like ChopStick0, ChopStick1, ..., ChopStick4. After all
chopstick locks are created, the main thread continues to create philosopher threads
and joins with all of its child threads. When all philosopher threads terminate, the
main thread returns (i.e., terminates).

#include <iostream>
#include <stdlib.h>

#include "Philosopher.h"

Mutex *Chopstick[PHILOSOPHERS]; // locks for chopsticks

int main(int argc, char *argv[])


{
Philosopher *Philosophers[PHILOSOPHERS];
int i, iter;
strstream name;

if (argc != 2) {
cout << "Use " << argv[0] << " #-of-iterations." << endl;
exit(0);
}
else
iter = abs(atoi(argv[1]));

for (i=0; i < PHILOSOPHERS; i++) { // initialize chopstick mutex locks


name.seekp(0, ios::beg);
name << "ChopStick" << i << '\0';
Chopstick[i] = new Mutex(name.str());
}

for (i=0; i < PHILOSOPHERS; i++) { // initialize and run philosopher threads
Philosophers[i] = new Philosopher(i, iter);
Philosophers[i]->Begin();
}

for (i=0; i < PHILOSOPHERS; i++)


Philosophers[i]->Join();

Exit();

return 0;

Discussion

Here are some very important facts about this program:

1. If you read the program carefully, we implicitly assign philosopher No to use


chopstick ChopStick[No] and chopstick ChopStick[(No+1) %
PHILOSOPHERS]. In other word, each philosopher is assigned to a fixed
chair. Is it necessary? It is certainly not. For example, when a philosopher is
hungry, we can generate a random integer i in the range of 0 and 4. If that
chair is occupied, generate another random integer. In this way, we simulate
the activity of finding an un-occupied chair. Once an un-occupied chair, say i,
is found, this philosopher uses chopstick i and (i + 1) % PHILOSOPHERS. In
doing so, our program may be very complex and blur our original focus. After
you understand the above program, you can certainly try to make it more
realistic.
2. The above program forces each philosopher to pick up and put down his left
chopstick, followed by his right one. This is also for the purpose of simplicity.
In fact, it is easy to see that the order of putting down the chopsticks is
irrelevant. Try to reasoning about this yourself.
3. The most serious problem of this program is that deadlock could
occur!
What if every philosopher sits down about the same time and picks up his left
chopstick as shown in the following figure? In this case, all chopsticks are
locked and none of the philosophers can successfully lock his right chopstick.
As a result, we have a circular waiting (i.e., every philosopher waits for his
right chopstick that is currently being locked by his right neighbor), and hence
a deadlock occurs.

4. Starvation is also a problem!


Imagine that two philosophers are fast thinkers and fast eaters. They think
fast and get hungry fast. Then, they sit down in opposite chairs as shown
below. Because they are so fast, it is possible that they can lock their
chopsticks and eat. After finish eating and before their neighbors can lock the
chopsticks and eat, they come back again and lock the chopsticks and eat. In
this case, the other three philosophers, even though they have been sitting
for a long time, they have no chance to eat. This is a starvation. Note that it
is not a deadlock because there is no circular waiting, and every one has a
chance to eat!
The above shows a simple example of starvation. You can find more
complicated thinking-eating sequence that also generate starvation. Please
try.

Ques 2: Study and implement the Dekker’s solution to mutual exlusion using
C/C++ programming language.

Solution: Dekker’s Solution to Mutual Exclusion:

Mythical Mutex

Preventing race conditions requires the ability to protect critical regions of code -
areas of code performing updates of a variable that is shared between threads must
allow only one thread to enter one of these regions at a time. We need an operator
that will allow only one thread to enter any one of regions at a time, suspending any
others that try until the first has completed its task, and left the critical region.

A critical region is a code segment that, with respect to similar critical regions is (or
should be) executed atomically. Variables that are shared amongst the processes are
manipulated in the critical region, and no process must be allowed to change them
while any other process is manipulating these variables.

Simply put, a critical region is one in which shared variables can be changed.

If two threads try to write different values to the same memory word at the same
time, the net result will be one of the two values, not some combination of the
values. Similarly, if one CPU tries to read a memory word at the same time another
modifies it, the read will return either the old or new value--it will not see a ``half-
changed'' memory location.

We assume the existence of Mutex class than contains two methods:

Lock:

which consists of all the steps required before the critical region is safe to enter.

UnLock:

which consists of the actions require after the critical region, to allow other processes
to gain their own lock on the region.

Mutual exclusion then becomes associated with critical regions. Each critical region
needs to be surrounded by a lock-unlock pair, and one common mutex operator
must be used by all processes desiring to enter the critical region. The specifics of
the lock and unlock operators are unimportant in terms of the abstract functionality
provided by the mutex.

Mutex operator may exist as part of the library of functions provided by the
operating system, or can be created using atomic primitives supported by the
microprocessor. Mutexes can also be created by careful manipulation of shared
variables, or by using other synchronization operators provided by the operating
system. Mutexes can also be built into more complex synchronization operators that
offer additional functionality or ease of use in some situations.

Dekker’s Solution to Mutual Exclusion

This is the first correct solution proposed for the two-process case. Originally
developed by Dekker in a different context, it was applied to the critical section
problem by Dijkstra.

Both the turn variable and the status flags are combined in a careful way. The entry
protocol begins as in Solution 3; we (the requesting process) set our flag and then
check our neighbor's flag. If that flag is also set, the turn variable is used. There is
no progress problem now because we know the other process is in, or before its
critical region. If the turn is ours we wait for the flag of the other process to clear. No
process will wait indefinitely with its flag set. If the turn belongs to the other process
we wait, but we clear our flag before waiting to avoid blocking the other process.
When the turn is given to us we reset our flag and proceed.

Global boolean array variable flag with two components both initialized to false;

Global integer turn = 0;


Process Pi , where i is 0 or 1

integer variable i with value 0 for P0 and 1 for P1 ;

flag[i] = true;

while (flags[1-i] == true)

if (turn != i)

flags[i] = false;

while (turn != i);

flags[i] = true;

critical region

turn = 1 - i;

flags[i] = false;
ANALYSIS: The mutual exclusion requirement is assured. No process will enter its
critical region without setting its flag. Every process checks the other flag after
setting its own. If both are set, the turn variable is used to allow only one process to
proceed. .

The turn variable is only considered when both processes are using, or trying to use,
the resource. Deadlock is not possible. No process waits with its flag continuously
set, and one process always has the turn. The process with the turn will (eventually)
discover the other flag free and will proceed.

Finally, bounded waiting is assured. Suppose Pj exits its critical region and re-enters
immediately while Pi is waiting. Then the turn has been given to Pi , and the flag of
Pi is set. Pj will clear its flag and wait, and Pi will proceed.

The mutex class constructed from this successful solution would look like:

class Mutex
{
boolean flag[2]
int turn;

Mutex ()
{
flag[0] = false;
flag[1] = false;
turn = 0;
}

void Lock (int i) // supply id of process as parameter.


{
flag[i] = true;
while (flags[1-i] == true)
{
if (turn != i)
{
flags[i] = false;
while (turn != i);
flags[i] = true;
}
}
}

void UnLock (int i) // supply id of process as parameter.


{
turn = 1 - i;
flags[i] = false;
}
}
Ques no.3 explain the operation and give examples of routing in multistage switch
based system?
Solution
Before starting the disussion on the multistage switching networks. Let us first try
and understand the principle involved in the construction of a crossbar switch.
Consider the Figure 1 , a 2X2 crossbar switch. The switch shown has 2 inputs,
labeled A and B and 2 outputs labeled 0 and 1 . This switch has the capability to
connect its inputs A and B to either outputs 0 and 1, depending on the value of some
control bit attached to the switch. Say, if the control bit is zero, the input A Is
connected to the output ), and when the control bit is equal to1, the input A is
ocnencted to output 1. Because there is no buffers in the switch, process of resetting
up the switch after a request rejection is very slow. To speedup the process, some
buffers are inserted within the switch ( please refer to figure 2)

Figure

Using such type of multiple 2X2 switches as building blocks. It is possible to build a
multistage network to control the communication between multiple sources and
destination . These can be connected to each other, either in the form of a binary
tree (Figure 7) or omega network ( figure 8). To understand the working of such
type of systems , consider the binary tree of figure 7. The two processors P1 and P2
are connected through switches to eight memory modules marked in binary from
000 to 111. The path from the source to the destination is determined by the binary
bits of destination . The first bit of destination number determines the switch output
in the first level. The second bit specifies the output of the switch in the second level
and so on. For example, to connect P1 to memory 101, it is necessary to form a path
from P1 to output 1 in the first level switch, output 0 in the second level switch and
output 1 in the third level switch p1 and P2 can be connected to any of the eight
memory modules. Certain request patterns cannot be satisfied simultaneously. For
example, if P1 is connected to one of the destination 000 through 011 then P2 can
be connected to only of the destination from 100 to 111.

Figure

The processor and the memory can be interconnected in number of ways. One such
topology is the omega switching network( fig 8) In this there is exactly one path
from any one processor to any memory module. However two sources cannot be
connected simultaneously to destination 000 and 001 and 011 and so on. The path
from source to the destination is determined is determinded in the same way as in
previous case. The source send a 3 bit pattern representing the destination number.
This binary pattern moves through network, and is examined by the junction boxes
one by one. Level 1 examines the first bit of the pattern and depending on whether it
is 0 or 1 , routes it to upper or lower output. Through one of the outputs pattern now
moves to seond level. The seond level examines second bit of the pattern and
accordingly moves pattern through appropriate out put to third level, which repeats
process with third bit of pattern.

Figure
The configuration can be used for both loosely coupled and tightly coupled system.
For tightly coupled systems, the source and the destination are the processing
elements.
Ques 4. Develop a procedural design (algorithm nearer to C-language code) for nay
encryption / decryption of your choice given in the book. Define all appropriate data
structure in the design representation.

Solution:
Rivest-Shamir-Adelman (RSa) public-key algorithm

The invention of public key encryption in the 70s has greatly improved the practical
usability of encryption, it allows for the secure exchange of information between two
parties with out need to exhange secret keys whih has always been a problem with
conventional encryption scheme.

Public keys schemes solves this shortfall by having two keys , one to encrypt, and
another to decrypt. Messages encryted with public key an only be decryted by
corresponding private key. This removes need for secure key does is allow someone
to encrypt a message for owner of the key pair.

The RSA public key algorihm was published in ACM in 1979, by researchers at MIT,
Adi shamir has sine returned to Israel , where he continues topursue his research in
cryptography The security of the RSA crypto system is based on the presumption
that fatorizing algo since RSA paper was published , these advances have been easily
compensated for by increasing key sizes. RSA key sizes of 2048 or more bits are
considered to be extremely secure at present Part of the RSA systems strength is the
difficulty of factorizing RSA modulus increases exponentially with increases in key
sizes.

This algorithm a clever design , but math involved is elegantly simple. The
encryption / decryptiom algorithm is very simple, and the key generation moderately
easy to follow. RSA key generation produces three numbers: n ( the modulus), e
(encryption key ) and d (decryption key ).

The public key is the pair of number n and e.


To encrypt, we take message M represented as a number, and calculate the cipher
text C:
e
C = M mod n

It’s that simple, there are various optimizations to speed up the calculation that this
is essentially what is going on in RSA encryption. To decrypt the message , you
reverse the process with cipher text C and using the derytion key d as exponent.

d
M= C mod n

In fact whole process is so simple, and so widely published , that to attempt to


restrict export of software implementation of this is completely ineffectual. To
highlight the bizarre nature of export restrictions,we present here a program written
in the perl scripting language which when typed in on a Unix or IBM PC running DOS
( with the perl and dc utilities installed), allows you to encrypt and decrypt messages
using RSA algorithm.

Desription of the RSA Crytosystem.


The RSA system is a symmetric public key cryptosystem in the terms of previous
section. Recall that this mean that there are any number of pairs of algo ( E, D) both
defined on the same set of values. E is public encryption algorithm and D is the
private decryption algorithm. These satisfy:

a. Encryption/ decryptionworks: If c = E (m) is the ciphertext corresponding to


some plaintext m, then m = D( c ). ( In other words: m = D (E (m)) , for any
message m)
b. Sign / verify works : If s = D(m) is the signature orresponding to some
plaintext m, then m = E (s) (In other words : m=D(e(m)), for any message
m)
c. Can decrypt efficiently: For any message or ciphertext x, there is an efficient
algo to calculate D(x)
d. Public/ private keys stay the way : From knowledge of E , here is no efficient
way to discover D.

Users A, B … can create their own pairs (Ea, Da) , (Eb,Db) … of RSA key pairs.
Encryption algo are published or made available ona secure public key server, while
the decryption algorithm are kept secret from everyone except the originator. The
previous section has gone over how these can be used.

In RSA , the plaintext and ciphertext are just large positive integers, up to certain
size depending on specific key pair. The underlying algorithm are not secret but only
certain information used in them.

The RSA the plaintext and ciphertext are just large positive integers , up to certain
size depending on specific key pair. The underlying algorithm are not secret but only
certain information used in them.

The RSA system itself is constructed as follows:

1. Choose random “large” prime integers p and q of roughly the same size , but
not too close together.
2. Calculate the product n = p*q (ordinary integer multiplication)
3. Choose a random encryption exponent e less than n that has no fators in
common with either p-1 or q-1
4. Calculate the (unique) decryption exponent satisfying e*d mod (p-1)*(q-1) =
1
5. The encryption function is
e
E(m) = M mod n, for any message m.
d
6. The decryption function is D(c ) =c mod n, for any ciphertext c.
7. The public key (published): This is the pair of integers ( n, e)
8. The private key ( kept secret ) : This is the triple of intgers (p, q, d)

There is more to story about each of above items:


1. At present , “large” means at least 512 bits. For better security each prime
should be at least 1024 bits long. There are efficient algo for generating
random numbers of a given size that are almost certainly prime (see below) .
2. n is then either 1024 or 2048 bits long.
3. The encryption exponent e can be just 3. If one is using this exponent, then
the primes must be such that p-1 and q-1 are not divisible by 3.
4. The decryption exponent must be calculated , and thee are efficient a;gorithm
to do this , but they require a knowledge of p and q The modulus for division,
(p-1)*(q-1), is Euler phi function of n=p*q, where this is a function studied in
number theory.

One of the function properties is important in proving that RSA works.


1. There are efficient algorithms for carrying out the modular exponentiation
needed here.
2. Ditto.
3. If it isknown that 3 is the encryption exponent then only n needs to be
published.
4. Only d needs to be kept as the secret data decryption (along with public n
and e). However , p and q can be efficiently calculated from other numbers
and they are needed anyway for most efficient form of modular exponention.

Some people are surprised that RSA just deals with large integers . So how does
it represent data . Suppose the value of n is at least 1024 bits long. This is the
same as 128 bytes In priniciple , one can just run encrypted or signed In
practice, the protocols will demand additional data besides just raw message ,
such as timestamps , but there is room for a lot of data in single RSA encryption.

A note on RSA algorithm.

It is interesting to note that RSA algorithm is symmetric As a result encryption


and decryption are mutual inverses and commutative.This new algorithm for key
exchange was now known as RSA the initials of its three inventor and satisfies
the original Diffe-Hellman description of ‘ multi-user cryptography’ because It
does not require two active participant when performing both encryption and
decryption.
A=47 and b=71

For now Now david ould calculate the value of c.


C=aXb=47X71=3337

David also must make sure that the encryption key must be relatively
prime, or has nocommonfactor with the numbers.
(a-b)(b-1) = 46 X 70 = 3220
David then chooses79 for his encryption key, using theextended Eulid
algo. And prime numbers (which are known only to David) , he calculates
the decryption key.
D = 79 ^-1(mod(3220) = 1019
David then publishes his public keyonhis web site for people to encrypt
messages with to send to him.
David’s RSA
key
C =
3337
Encryption key= 79
The next day Antonia wants to encrypt the number 688 and raises it to
the 79th power and takes the modulo 3337 of the result
(688^79)mod (3337) = 1570
David then gets the number 1570, and tries to decrypt it by replacing the
encryption with the decryption key.
(1570^1019)mod (3337) = 688
Rivest , Shamir abd Adelman wrote a paper descirbing the invention and published it
as MIT artificial Intelligence Laboratory Technical Report . A challenge was placed in
Martin Gardner column in Scientific American in which readers were invited to crack.

C=114, 381, 625, 757, 888,867, 669, 235, 779 , 976, 146, 612, 010, 218, 296, 721,
242
,362, 562,561,842,935,706,935,245,733,897,830,597,123,563,958,705,058,989,
075,147,599,290,026,879,543,541
Encryption key = 9007

The message “first solver wins one hundred dollar” appeared in an A=01, B=02,
C=03… format. This was solved in April 26, 1994, cracked by an international
effort vis the internet with the use of 1600 workstations, mainframes and
supercomputer attacked the number for eight months before finding its factors.
Of course, RSa algorithm is safe, as it would be incredibly difficult to gather up
such international participation to commit malicious acts.

RSA patent filed on December 14, 1977 approved as #4,405,829 titled +


cryptographic communication System and Method” to MIT, Rivest , Shamir and
Adelman. However , since it was published before the patent application , it could
not be patented under European and Japanesse law. Rivest tried to put RSA onto
chip in order to help with computing problem, but MIT at the time lacked the
technical expertise to make clips. And no outside companies would do it either. A
company was soon formed, called RSA data security , and was granted an
exclusive license on the RSA patent as was and is standard practice of MIT.

Despite the brilliant idea , RSA didn’t sell very well , this is where PGP comes in,
people needed a practical , affordable demonstration that they could try
themselves.

You might also like