0% found this document useful (0 votes)
10 views48 pages

Distributed Computing Manual

The document outlines several experiments involving inter-process communication, remote procedure calls, multicast communication, and clock synchronization using Python and Java. Each experiment includes a theoretical background, algorithms, and sample code for client-server implementations. Key concepts such as socket programming, RPC, and multicast are explained, along with their respective applications in distributed systems.

Uploaded by

221108
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
10 views48 pages

Distributed Computing Manual

The document outlines several experiments involving inter-process communication, remote procedure calls, multicast communication, and clock synchronization using Python and Java. Each experiment includes a theoretical background, algorithms, and sample code for client-server implementations. Key concepts such as socket programming, RPC, and multicast are explained, along with their respective applications in distributed systems.

Uploaded by

221108
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Experiment No - 1

Aim- To implement Inter-Process Communication between client and server using Python
socket programming.

Theory: Inter-Process Communication (IPC) is a mechanism that allows processes to exchange


information. In distributed systems, processes may run on different machines connected
through a network. Communication between these processes is achieved using networking
protocols such as TCP/[Link] programming is a method used for communication between
two systems over a network. A socket acts as an endpoint of communication. The server waits
for connection requests, while the client initiates the communication.
Python provides a socket library that allows easy implementation of network communication.

Fig 1: Inter process Communication Process

Sockets Mechanism
The Sockets are the End Points of Communication between two machines. They provide a way
for processes to communicate with each other, either on the same on machine or over the
Internet also possible. The Sockets enable the communication connection between Server and
the client to transfer data in a bidirectional way.

Fig 2: Client Server Communication using Sockets


Algorithm:

1. Import the socket module.


2. Create a server socket.
3. Bind the socket to host and port.
4. Start listening for client connections.
5. Accept connection requests from clients.
6. Receive message from client.
7. Display the message.
8. Close the connection.

Program:
Client-
import socket

# Create socket object


client_socket = [Link](socket.AF_INET, socket.SOCK_STREAM)

# Server details
host = '[Link]'
port = 12345

# Connect to server
client_socket.connect((host, port))
print("Connected to server!")

while True:
# Send message to server
message = input("Client: ")
client_socket.send([Link]())

if [Link]() == 'exit':
print("Connection closed.")
break

# Receive response from server


data = client_socket.recv(1024).decode()
print(f"Server: {data}")

if [Link]() == 'exit':
print("Server ended the connection.")
break

# Close socket
client_socket.close()
Server-
import socket
# Create a socket object
server_socket = [Link](socket.AF_INET, socket.SOCK_STREAM)
# Define host and port
host = '[Link]' # Localhost
port = 12345
# Bind the socket
server_socket.bind((host, port))
# Listen for connections
server_socket.listen(5)
print("Server is waiting for connection...")
# Accept connection
conn, addr = server_socket.accept()
print(f"Connected to client: {addr}")
while True:
# Receive data from client
data = [Link](1024).decode()
if not data:
break
print(f"Client: {data}")
# Exit condition
if [Link]() == 'exit':
print("Connection closed by client.")
break
# Send response to client
message = input("Server: ")
[Link]([Link]())
if [Link]() == 'exit':
print("Connection closed by server.")
break
# Close connection
[Link]()
server_socket.close()

Result:
Experiment No - 2
Aim: To implement Remote Procedure / RMI Call between client and server.

Theory: Remote Procedure Call (RPC) is a protocol that allows a program to execute a
procedure on another machine without knowing network details. Python provides an XMLRPC
library that allows functions to be called remotely over [Link] simplifies distributed
programming by making remote calls appear like local function calls.

How RPC Works (Step by Step)


1. Client Calls Stub: The client calls a local procedure (stub) as if it were normal.
2. Marshalling: The stub packs (marshals) all input parameters into a message.
3. Send to Server: The message is sent across the network to the server.
4. Server Stub: The server stub unpacks the message and calls the actual server procedure.
5. Execution & Return: The server runs the procedure and returns the result to the stub.
6. Back to Client: The server stub sends the result back, and the client stub unpacks it.
Algorithm
1. Create an RPC server.
2. Define a function to perform computation.
3. Register the function with the server.
4. Start the server.
5. Client connects to server.
6. Client invokes remote function.
7. The server processes requests and returns results.

Program:

Server-
import socket
import json
# Define functions that can be called remotely
def add(a, b):
return a + b
def subtract(a, b):
return a - b
def multiply(a, b):
return a * b
def divide(a, b):
if b == 0:
return "Error: Division by zero"
return a / b

# Function mapping
functions = {
"add": add,
"subtract": subtract,
"multiply": multiply,
"divide": divide
}
# Create socket
server = [Link](socket.AF_INET, socket.SOCK_STREAM)
[Link](('[Link]', 5000))
[Link](5)
print("RPC Server is running...")
while True:
conn, addr = [Link]()
print(f"Connected to {addr}")
data = [Link](1024).decode()
request = [Link](data)
func_name = request["function"]
args = request["args"]

print(f"Client requested: {func_name}{tuple(args)}")


# Execute function
if func_name in functions:
try:
result = functions[func_name](*args)
except Exception as e:
result = str(e)
else:
result = "Error: Function not found"
# Send response
response = [Link]({"result": result})
[Link]([Link]())

[Link]()

Client-
import socket
import json
def call_remote_function(function_name, args):
client = [Link](socket.AF_INET, socket.SOCK_STREAM)
[Link](('[Link]', 5000))

# Create request
request = {
"function": function_name,
"args": args
}
[Link]([Link](request).encode())
# Receive response
response = [Link](1024).decode()
result = [Link](response)
[Link]()
return result["result"]
# User interaction
while True:
print("\nAvailable functions: add, subtract, multiply, divide")
func = input("Enter function name (or 'exit'): ")
if [Link]() == 'exit':
break
a = float(input("Enter first number: "))
b = float(input("Enter second number: "))
result = call_remote_function(func, [a, b])
print("Result from server:", result)

Result:

Remote Method Invocation in Java


Remote Method Invocation (RMI) is an API that allows an object to invoke a method on an
object that exists in another address space, which could be on the same machine or on a remote
machine. Through RMI, an object running in a JVM present on a computer (Client-side) can
invoke methods on an object present in another JVM (Server-side). RMI creates a public
remote server object that enables client and server-side communications through simple
method calls on the server object.
Stub Object: The stub object on the client machine builds an information block and sends this
information to the server.
The block consists of
An identifier of the remote object to be used
Method name which is to be invoked
Parameters to the remote JVM
Skeleton Object: The skeleton object passes the request from the stub object to the remote
object. It performs the following tasks
It calls the desired method on the real object present on the server.
It forwards the parameters received from the stub object to the method.

Working of RMI
The communication between client and server is handled by using two intermediate objects:
Stub object (on client side) and Skeleton object (on server-side) as also can be depicted
from below media as follows:

These are the steps to be followed sequentially to implement Interface as defined below
as follows:

1. Defining a remote interface


2. Implementing the remote interface
3. Creating Stub and Skeleton objects from the implementation class
using rmic (RMI compiler)
4. Start the rmi registry
5. Create and execute the server application program
6. Create and execute the client application program.
Program:
import [Link].*;
import [Link].*;
import [Link].*;

/* =======================
Remote Interface (RPC)
======================= */
interface HelloService extends Remote {
String sayHello(String name) throws RemoteException;
}

/* =======================
Remote Implementation
======================= */
class HelloServiceImpl extends UnicastRemoteObject
implements HelloService {

protected HelloServiceImpl() throws RemoteException {


super();
}

@Override
public String sayHello(String name) throws RemoteException {
return "Hello " + name + ", this message is from RMI Server!";
}
}
/* =======================
Main Class (Server + Client)
====================== */
public class RMIRPC {

// -------- SERVER --------


static void startServer() {
try {
[Link](1099);
HelloService service = new HelloServiceImpl();
[Link]("rmi://localhost/HelloService", service);
[Link]("✅ RMI Server started...");
} catch (Exception e) {
[Link]();
}
}
// -------- CLIENT --------
static void startClient() {
try {
HelloService service =
(HelloService) [Link]("rmi://localhost/HelloService");

String response = [Link]("Student");


[Link]("📩 Server Response: " + response);
} catch (Exception e) {
[Link]();
}
}

// -------- MAIN --------


public static void main(String[] args) {
if ([Link] == 0) {
[Link]("Usage:");
[Link](" java RMIRPC server");
[Link](" java RMIRPC client");
return;
}

if (args[0].equalsIgnoreCase("server")) {
startServer();
} else if (args[0].equalsIgnoreCase("client")) {
startClient();
}
}
}

Result:
Experiment No -3
Aim: To implement group communication using multicast.

Theory: Group communication allows one sender to transmit messages to multiple receivers
simultaneously. Multicast networking enables efficient communication with multiple nodes
without sending individual messages.
Types of Group Communication in a Distributed System

Below are the three types of group communication in distributed systems:


1. Unicast Communication

Fig: Unicast Communication


• Unicast communication is the point-to-point transmission of data between two nodes
in a network. In the context of distributed systems:
• Unicast is when a sender sends a message to a specific recipient, using their unique
network address.
• Each message targets one recipient, creating a direct connection between the sender
and the receiver.
• You commonly see unicast in client-server setups, where a client makes requests and
receives responses, as well as in direct connections between peers.
• This method makes good use of network resources, is easy to implement, and keeps
latency low because messages go straight to the right person.
• Unicast isn’t efficient for sending messages to many recipients at once, as it requires
separate messages for each one, leading to more work.
2. Multicast Communication

Fig: Multicast Communication


Multicast communication involves sending a single message from one sender to multiple
receivers simultaneously within a network. It is particularly useful in distributed systems
where broadcasting information to a group of nodes is necessary:
• Multicast lets a sender share a message with a specific group of people who want it.
• This way, the sender can reach many people at once, which is more efficient than
sending separate messages.
• This approach is often used to send updates to subscribers or in collaborative
applications where real-time sharing of changes is needed.
• By sending data just once to a group, multicast saves bandwidth, simplifies
communication, and can easily handle a larger number of recipients.
• Managing group membership is necessary to ensure reliable message delivery, and
multicast can run into issues if there are network problems that affect everyone in the
group.

3. Broadcast Communication

4. Fig: Broadcast Communication


Broadcast communication involves sending a message from one sender to all nodes in the
network, ensuring that every node receives the message:

• Broadcast is when a sender sends a message to every node in the network without
targeting specific recipients.
• Messages are delivered to all nodes at once using a special address designed for this
purpose.
• It’s often used for network management tasks, like sending status updates, or for
emergency alerts that need to reach everyone quickly.
• Broadcast ensures that every node receives the message without needing to specify
who the recipients are, making it efficient for sharing information widely.
• It can cause network congestion in larger networks and raises security concerns since
anyone on the network can access the broadcast message, which might lead to
unauthorized access.

Algorithm:
1. Create a sender program.
2. Define multicast IP address and port.
3. Send a message to the multicast group.
4. Create a receiver program.
5. Join a multicast group.
6. Receive and display messages.

Program:

Sender-
import socket
import time
MCAST_GRP = '[Link]'
MCAST_PORT = 5007
# Create UDP socket
sock = [Link](socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)

# Set TTL (time-to-live)


[Link](socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)

print("Multicast Sender Started...")

while True:
message = input("Enter message to send (or 'exit'): ")

if [Link]() == 'exit':
break

[Link]([Link](), (MCAST_GRP, MCAST_PORT))


Receiver-
import socket
import struct
# Multicast group details
MCAST_GRP = '[Link]'
MCAST_PORT = 5007
# Create socket
sock = [Link](socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
# Allow multiple clients to use same port
[Link](socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# Bind to the server address
[Link](('', MCAST_PORT))
# Join multicast group
mreq = [Link]("4sl", socket.inet_aton(MCAST_GRP), socket.INADDR_ANY)
[Link](socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)

print("Receiver joined multicast group...")


while True:
data, addr = [Link](1024)
print(f"Message from {addr}: {[Link]()}")

Result:
Experiment 4
Clock Synchronization (Cristian’s Algorithm Simulation)
Aim: To simulate clock synchronization in distributed systems.

Theory: In distributed systems, each node has its own clock which may differ from others.
Clock synchronization ensures all systems maintain consistent time. Cristian’s algorithm
synchronizes a client clock with a time server.

Cristian's Algorithm is a clock synchronization algorithm used to synchronize time with a


time server by client processes. This algorithm works well with low-latency networks
where Round Trip Time is short as compared to accuracy while redundancy-prone distributed
systems/applications do not go hand in hand with this algorithm. Here Round Trip Time
refers to the time duration between the start of a Request and the end of the corresponding
Response.
Below is an illustration imitating the working of Cristian's algorithm:
Program:

Server-
import socket
import time
HOST = '[Link]'
PORT = 5000
server_socket = [Link](socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((HOST, PORT))
server_socket.listen(5)
print("Time Server is running...")
while True:
conn, addr = server_socket.accept()
print(f"Connected by {addr}")
# Receive request
data = [Link](1024)

if not data:
break
# Get server time
server_time = [Link]()
# Send server time
[Link](str(server_time).encode())
[Link]()

Client-
import socket
import time

HOST = '[Link]'
PORT = 5000
client_socket = [Link](socket.AF_INET, socket.SOCK_STREAM)

# Record request time (T0)


T0 = [Link]()

client_socket.connect((HOST, PORT))
client_socket.send(b"Request Time")

# Receive server time (Ts)


data = client_socket.recv(1024)
T1 = [Link]()

Ts = float([Link]())

client_socket.close()

# Calculate Round Trip Time (RTT)


RTT = T1 - T0

# Cristian’s Algorithm adjustment


client_time = Ts + (RTT / 2)

print("\n--- Clock Synchronization ---")


print(f"Client Request Time (T0): {T0}")
print(f"Server Time (Ts): {Ts}")
print(f"Client Receive Time (T1): {T1}")
print(f"Round Trip Time (RTT): {RTT}")
print(f"Synchronized Client Time: {client_time}")
print(f"Local System Time: {[Link]()}")

Result:
Experiment 5
Aim: To simulate leader election using Bully Algorithm.

Theory: Election algorithms are used to choose a coordinator among distributed processes
when the existing coordinator fails. In the Bully algorithm, the process with the highest ID
becomes the new coordinator.
In distributed systems, leader election algorithms are used to select a coordinator among
multiple processes when the current coordinator fails. The Ring Algorithm is a distributed
election algorithm where processes are arranged in a logical ring structure.
In this algorithm, each process has a unique ID and knows the ID of its neighbor in the ring.
When a process detects that the coordinator has failed, it initiates an election message
containing its own ID and passes it to the next process in the ring.
Each process that receives the election message compares its own ID with the ID in the
message. If its ID is higher, it replaces the ID in the message with its own and forwards it to
the next process. This process continues until the message returns to the initiator.
The process with the highest ID becomes the new coordinator and announces itself to all
other processes in the ring.

Algorithm:
1. Define a list of processes with unique IDs.
2. Arrange the processes logically in a ring structure.
3. Select a process to initiate the election.
4. Pass the election message to the next process in the ring.
5. Each process compares its ID with the received ID.
6. The highest ID is selected as the coordinator.
7. Display the elected coordinator.

Program:
import time
class Process:
def __init__(self, pid):
[Link] = pid
[Link] = True
class BullyAlgorithm:
def __init__(self, num_processes):
[Link] = [Process(i) for i in range(1, num_processes + 1)]
[Link] = max([Link], key=lambda p: [Link])
def display_processes(self):
print("\nProcess Status:")
for p in [Link]:
status = "Alive" if [Link] else "Down"
print(f"Process {[Link]}: {status}")
print(f"Current Coordinator: P{[Link]}")
def crash_process(self, pid):
for p in [Link]:
if [Link] == pid:
[Link] = False
print(f"\n Process {pid} crashed!")

if [Link] == pid:
print("Coordinator crashed! Starting election...")
self.start_election(self.get_highest_alive())
return
def get_highest_alive(self):
alive = [p for p in [Link] if [Link]]
return max(alive, key=lambda p: [Link])
def start_election(self, initiator):
print(f"\n Process {[Link]} starts election")
higher_processes = [
p for p in [Link] if [Link] > [Link] and [Link]
]
if not higher_processes:
[Link] = initiator
print(f"\n Process {[Link]} becomes the NEW COORDINATOR")
return
for p in higher_processes:
print(f"Process {[Link]} → ELECTION → Process {[Link]}")
[Link](1)
# Higher process responds and takes over
next_initiator = max(higher_processes, key=lambda p: [Link])
print(f"\nProcess {next_initiator.pid} responds OK")
self.start_election(next_initiator)
def recover_process(self, pid):
for p in [Link]:
if [Link] == pid:
[Link] = True
print(f"\n Process {pid} recovered!")
print("Recovered process starts election...")
self.start_election(p)
return
# ---- Simulation ----
if __name__ == "__main__":
system = BullyAlgorithm(5)
system.display_processes()
# Crash current leader
system.crash_process(5)
[Link](2)
system.display_processes()
# Recover highest process
system.recover_process(5)
[Link](2)
system.display_processes()

Result:
Experiment 6
Aim: To implement mutual exclusion in distributed systems.

Theory: Mutual exclusion ensures that only one process can access a shared resource at a
time to prevent conflicts.
Mutual exclusion is a concurrency control property which is introduced to prevent race
conditions. It is the requirement that a process can’t enter its critical section while another
concurrent process is currently present or executing in its critical section i.e only one process
is allowed to execute the critical section at any given instance of time.

Mutual exclusion in single computer system Vs. distributed system: In single computer
system, memory and other resources are shared between different processes. The status of
shared resources and the status of users is easily available in the shared memory so with the
help of shared variable (For example: Semaphores) mutual exclusion problem can be easily
solved. In Distributed systems, we neither have shared memory nor a common physical clock
and therefore we can’t solve mutual exclusion problem using shared variables. To eliminate
the mutual exclusion problem in distributed system approach based on message passing is
used. A site in distributed system do not have complete information of state of the system due
to lack of shared memory and a common physical clock.

Requirements of Mutual exclusion Algorithm:


• No Deadlock: Two or more sites should not endlessly wait for any message that will
never arrive.
• No Starvation: Every site who wants to execute critical section should get an
opportunity to execute it in finite time. Any site should not wait indefinitely to
execute critical section while other site are repeatedly executing critical section
• Fairness: Each site should get a fair chance to execute critical section. Any request to
execute critical section must be executed in the order they are made i.e Critical section
execution requests should be executed in the order of their arrival in the system.
• Fault Tolerance: In case of failure, it should be able to recognize it by itself in order
to continue functioning without any disruption.

Some points are need to be taken in consideration to understand mutual exclusion fully:
1) It is an issue/problem which frequently arises when concurrent access to shared
resources by several sites is involved. For example, directory management where
updates and reads to a directory must be done atomically to ensure correctness.
2) It is a fundamental issue in the design of distributed systems.
3) Mutual exclusion for a single computer is not applicable for the shared resources since
it involves resource distribution, transmission delays, and lack of global information.

Solution to distributed mutual exclusion: As we know shared variables or a local kernel


can’t be used to implement mutual exclusion in distributed systems. Message passing is a
way to implement mutual exclusion. Below are the three approaches based on message
passing to implement mutual exclusion in distributed systems:
1. Token Based Algorithm:
• A unique token is shared among all the sites.
• If a site possesses the unique token, it is allowed to enter its critical section
• This approach uses sequence number to order requests for the critical section.
• Each request for critical section contains a sequence number. This sequence number is
used to distinguish old and current requests.
• This approach ensures Mutual exclusion as the token is unique

Example : Suzuki–Kasami Algorithm

2. Non-token based approach:


• A site communicates with other sites in order to determine which sites should execute
critical section next. This requires exchange of two or more successive round of
messages among sites.
• This approach use timestamps instead of sequence number to order requests for the
critical section.
• When ever a site make request for critical section, it gets a timestamp. Timestamp is
also used to resolve any conflict between critical section requests.
• All algorithm which follows non-token-based approach maintains a logical clock.
Logical clocks get updated according to Lamport's scheme
Example : Ricart–Agrawala Algorithm

3. Quorum based approach:


• Instead of requesting permission to execute the critical section from all other sites,
each site requests only a subset of sites which is called a quorum.
• Any two subsets of sites or Quorum contain a common site.
• This common site is responsible to ensure mutual exclusion

Example : Maekawa’s Algorithm

Program:
import threading
import time
class Process:
def __init__(self, pid, total):
[Link] = pid
[Link] = 0
[Link] = False
[Link] = 0
[Link] = 0
[Link] = total
[Link] = []
def request_cs(self, processes):
[Link] += 1
[Link] = [Link]
[Link] = True
[Link] = 0
print(f"\nProcess {[Link]} requesting CS")
for p in processes:
if [Link] != [Link]:
p.receive_request(self, [Link])

while [Link] < [Link] - 1:


[Link](0.1)
self.enter_cs()
def receive_request(self, sender, ts):
[Link] = max([Link], ts) + 1
if (not [Link] or
(ts, [Link]) < ([Link], [Link])):
sender.receive_reply()
print(f"{[Link]} → REPLY → {[Link]}")
else:
[Link](sender)
def receive_reply(self):
[Link] += 1
def enter_cs(self):
print(f"🟢 {[Link]} ENTER CS")
[Link](1)
print(f"🔵 {[Link]} EXIT CS")
[Link] = False
for p in [Link]:
p.receive_reply()
print(f"{[Link]} (deferred) → REPLY → {[Link]}")
[Link]()
if __name__ == "__main__":
n = 3
processes = [Process(i, n) for i in range(n)]
threads = []
for p in processes:
t = [Link](target=p.request_cs, args=(processes,))
[Link](t)
for t in threads:
[Link]()
[Link](0.5)
for t in threads:
[Link]()

Result:
Experiment 7
Aim: To detect deadlock using a wait-for graph.

Theory: Deadlock occurs when processes wait indefinitely for resources held by other
processes. A wait-for graph helps identify circular dependencies.
Deadlocks are a fundamental problem in distributed systems. A process may request
resources in any order and a process can request resources while holding others. A Deadlock
is a situation where a set of processes are blocked as each process in a Distributed system is
holding some resources and that acquired resources are needed by some other processes.

Example: If there are three processes p1,p2 and p1 are acquiring r1 resource and that r1 is
needed by p2 which is acquiring another resource r2 and that is needed by p1. Here cycle
occurs. It is called a deadlock.

Here in the above graph, we found a cycle from P1 to P2 and again to P1. So we can say that
the system is in a deadlock state. The Problem of deadlocks has been generally studied in
distributed systems under the following models:
• The system has only reusable resources.
• Processes are allowed only exclusive access to resources.
• There is only one copy of each resource.

Deadlock Detection
The deadlock Detection Algorithm is of two types:
• Wait-for-Graph Algorithm (Single Instance)
• Banker's Algorithm (Multiple Instance)

Wait-for-Graph Algorithm

It is a variant of the Resource Allocation graph. In this algorithm, we only have processes as
vertices in the graph. If the Wait-for-Graph contains a cycle then we can say the system is in
a Deadlock state. We need to remove resources while converting from Resource Allocation
Graph to Wait-for-Graph.

• Resource Allocation Graph: Contains Processes and Resources.


• Wait-for-Graph: Contains only Processes after removing the Resources while
conversion from Resource Allocation Graph.

Algorithm:
Below are the steps to follow:
• Step 1: Take the first process (Pi) from the resource allocation graph and check the
path in which it is acquiring resource (Ri), and start a
• wait-for-graph with that particular process.
• Step 2: Make a path for the Wait-for-Graph in which there will be no Resource
included from the current process (Pi) to next process (Pj), from that next process (Pj)
find a resource (Rj) that will be acquired by next Process (Pk) which is released from
Process (Pj).
• Step 3: Repeat Step 2 for all the processes.
• Step 4: After completion of all processes, if we find a closed-loop cycle then the
system is in a deadlock state, and deadlock is detected.

Example 1:
Consider a Resource Allocation Graph with 4 Processes P1, P2, P3, P4, and 4 Resources R1,
R2, R3, R4. Find if there is a deadlock in the Graph using the Wait for Graph-based deadlock
detection algorithm.

Step 1
First take Process P1 which is waiting for Resource R1, resource R1 is acquired by Process
P2, Start a Wait-for-Graph for the above Resource Allocation Graph.
Step 2
Now we can observe that there is a path from P1 to P2 as P1 is waiting for R1 which is been
acquired by P2. Now the Graph would be after removing resource R1 looks like.

Step 3
From P2 we can observe a path from P2 to P3 as P2 is waiting for R4 which is acquired by
P3. So make a path from P2 to P3 after removing resource R4 looks like.

Step 4
From P3 we find a path to P4 as it is waiting for P3 which is acquired by P4. After removing
R3 the graph looks like this.
Step 5
Here we can find Process P4 is waiting for R2 which is acquired by P1. So finally, the Wait-
for-Graph is as follows:

Step 6

Note: Finally In this Graph, we found a cycle as the Process P4 again came back to the
Process P1 which is the starting point (i.e., it's a closed-loop). So, According to the
Algorithm if we found a closed loop, then the system is in a deadlock state. So here we can
say the system is in a deadlock state.

Example 2:

Now consider another Resource Allocation Graph with 4 Processes P1, P2, P3, P4, and 3
Resources R1, R2, R3. Find if there is a deadlock in the Graph using the Wait for Graph-
based deadlock detection algorithm.

Step 1
First take Process P1 which is waiting for Resource R1, resource R1 is acquired by Process
P2, Start a Wait-for-Graph for the above Resource Allocation Graph.

Step 2
Now we can observe that there is a path from P1 to P2 and also from P1 to P4 as P1 is
waiting for R1 which is been acquired by P2 and P1 is also waiting for R2 which is acquired
by P4. Now the Graph would be after removing resources R1 and R2 looks like.

Step 3
From P2 we can observe a path from P2 to P3 as P2 is waiting for R3 which is acquired by
P3. So make a path from P2 to P3 after removing resource R3 looks like.

Step 4
Here we can find Process P4 is waiting for R3 which is acquired by P3. So finally the Wait-
for-Graph looks like after removing Resource R3 looks like.

Step 5
In this Graph, we don't find a cycle as no process came back to the starting point (i.e., there is
no closed loop). So, According to the Algorithm if we found a closed loop, then the system is
in a deadlock state. But here we didn't find any closed loop so the system is not in a deadlock
state. The system is in a safe state.

Note: In Example 2, even though it looks like a loop but there is no process that has reached
the first process, or starting point again. So there is no closed loop.

Issues in Wait-for-Graph based Deadlock Detection Algorithm


For every resource we call WFG Detection Algorithm, the computation time would be very
high for the CPU if there are many resources. The solution for this is, rather than calling this
algorithm for every resource request that cannot be granted immediately. Just invoke the
algorithm after a definite interval.
• Scalability: Graph maintenance becomes costly in large systems with many processes
and resources.
• Overhead: Continuous updating of edges (waits-for relations) increases runtime
overhead.
• False Deadlock Detection: Temporary waits may be mistaken for deadlocks if the
graph is checked too frequently.
• Delayed Detection: If checked infrequently, real deadlocks may persist for long
before detection.
• Complexity: Cycle detection in dynamic graphs requires extra computation.
• Distributed Systems Issue: Constructing a global wait-for-graph is difficult due to
partial knowledge and message delays.

Program:
class WaitForGraph:
def __init__(self, processes):
[Link] = {p: [] for p in processes}
def add_edge(self, p1, p2):
# p1 is waiting for p2
[Link][p1].append(p2)
def detect_deadlock(self):
visited = set()
rec_stack = set()
def dfs(node):
[Link](node)
rec_stack.add(node)
for neighbor in [Link][node]:
if neighbor not in visited:
if dfs(neighbor):
return True
elif neighbor in rec_stack:
return True
rec_stack.remove(node)
return False
for process in [Link]:
if process not in visited:
if dfs(process):
return True
return False
# ---- Simulation ----
if __name__ == "__main__":
processes = ["P1", "P2", "P3", "P4"]

wfg = WaitForGraph(processes)

# Add dependencies (Wait relationships)


wfg.add_edge("P1", "P2")
wfg.add_edge("P2", "P3")
wfg.add_edge("P3", "P1") # This creates a cycle (deadlock)
wfg.add_edge("P4", "P2")

print("\nWait-For Graph:")
for p in [Link]:
print(f"{p} → {[Link][p]}")

if wfg.detect_deadlock():
print("\n🔴 Deadlock Detected!")
else:
print("\n🟢 No Deadlock.")

Result:
Experiment 8
Aim: To simulate load balancing in distributed systems.

Theory:
Load balancing distributes workloads across multiple processors to improve system
performance and avoid overloading.

A load balancer is a device that acts as a reverse proxy and distributes network or application
traffic across a number of servers. Load adjusting is the approach to conveying load units
(i.e., occupations/assignments) across the organization which is associated with the
distributed system. Load adjusting should be possible by the load balancer. The load balancer
is a framework that can deal with the load and is utilized to disperse the assignments to the
servers. The load balancers allocates the primary undertaking to the main server and the
second assignment to the second server.

Purpose of Load Balancing in Distributed Systems:

• Security: A load balancer provide safety to your site with practically no progressions
to your application.
• Protect applications from emerging threats: The Web Application Firewall (WAF)
in the load balancer shields your site.
• Authenticate User Access: The load balancer can demand a username and secret key
prior to conceding admittance to your site to safeguard against unapproved access.
• Protect against DDoS attacks: The load balancer can distinguish and drop conveyed
refusal of administration (DDoS) traffic before it gets to your site.
• Performance: Load balancers can decrease the load on your web servers and advance
traffic for a superior client experience.
• SSL Offload: Protecting traffic with SSL (Secure Sockets Layer) on the load balancer
eliminates the upward from web servers bringing about additional assets being
accessible for your web application.
• Traffic Compression: A load balancer can pack site traffic giving your clients a
vastly improved encounter with your site.
Load Balancing Approaches:

• Round Robin
• Least Connections
• Least Time
• Hash
• IP Hash

Classes of Load Adjusting Calculations:

Following are a portion of the various classes of the load adjusting calculations.
• Static: In this model assuming any hub/node is found with a heavy load, an
assignment can be taken arbitrarily and move the undertaking to some other arbitrary
system.
• Dynamic: It involves the present status data for load adjusting. These are better
calculations than static calculations.
• Deterministic: These calculations utilize processor and cycle attributes to apportion
cycles to the hubs.
• Centralized: The framework states data is gathered by a single hub.

Advantages of Load Balancing:

Load balancers minimize server response time and maximize throughput.


Load balancer ensures high availability and reliability by sending requests only to
online servers
Load balancers do continuous health checks to monitor the server’s capability of
handling the request.

Migration:

Another important policy to be used by a distributed operating system that supports process
migration is to decide about the total number of times a process should be allowed to migrate.
Migration Models:

• Code section
• Resource section
• Execution section

Code section: It contains the real code.


Resource fragment: It contains a reference to outer resources required by the interaction.
Execution section: It stores the ongoing execution condition of interaction, comprising
private information, the stack, and the program counter.
Powerless movement: In the powerless relocation just the code section will be moved.
Solid relocation: In this movement, both the code fragment and the execution portion will be
moved. The relocation additionally can be started by the source.
Program:
import random
import time
class Server:
def __init__(self, sid):
[Link] = sid
[Link] = 0

def assign_task(self, task):


[Link] += 1
print(f"Task {task} → Server {[Link]} (Load: {[Link]})")
[Link](0.3)

def finish_task(self):
if [Link] > 0:
[Link] -= 1

class LoadBalancer:
def __init__(self, num_servers):
[Link] = [Server(i) for i in range(num_servers)]
[Link] = 0 # for round robin

# Round Robin
def round_robin(self, task):
server = [Link][[Link]]
server.assign_task(task)
[Link] = ([Link] + 1) % len([Link])

# Least Loaded
def least_loaded(self, task):
server = min([Link], key=lambda s: [Link])
server.assign_task(task)

# Random
def random_assign(self, task):
server = [Link]([Link])
server.assign_task(task)

def show_load(self):
print("\nCurrent Server Loads:")
for s in [Link]:
print(f"Server {[Link]}: {[Link]}")
# ---- Simulation ----
if __name__ == "__main__":
lb = LoadBalancer(3)

tasks = list(range(1, 11))


print("\n--- Round Robin ---")
for t in tasks:
lb.round_robin(t)
lb.show_load()
[Link](1)
print("\n--- Least Loaded ---")
for t in tasks:
lb.least_loaded(t)
lb.show_load()
[Link](1)
print("\n--- Random Assignment ---")
for t in tasks:
lb.random_assign(t)
lb.show_load()

Result:
Experiment 9
Aim: To simulate distributed shared memory.

Theory: Distributed Shared Memory (DSM) allows multiple systems to access shared data as
if they share the same memory.
Distributed shared memory can be achieved via both software and hardware. Hardware
examples include cache coherence circuits and network interface controllers. In contrast,
software DSM systems implemented at the library or language level are not transparent and
developers usually have to program them differently.

What is Distributed Shared Memory?


It is a mechanism that manages memory across multiple nodes and makes inter-process
communications transparent to end-users. The applications will think that they are running on
shared memory. DSM is a mechanism of allowing user processes to access shared data
without using inter-process communications. In DSM every node has its own memory and
provides memory read and write services and it provides consistency protocols. The
distributed shared memory (DSM) implements the shared memory model in distributed
systems but it doesn't have physical shared memory. All the nodes share the virtual address
space provided by the shared memory model. The Data moves between the main memories of
different nodes.

Types of Distributed Shared Memory


1. On-Chip Memory
• The data is present in the CPU portion of the chip.
• Memory is directly connected to address lines.
• On-Chip Memory DSM is expensive and complex.

2. Bus-Based Multiprocessors
• A set of parallel wires called a bus acts as a connection between CPU and memory.
• Accessing of same memory simultaneously by multiple CPUs is prevented by using
some algorithms.
• Cache memory is used to reduce network traffic.

3. Ring-Based Multiprocessors
• There is no global centralized memory present in Ring-based DSM.
• All nodes are connected via a token passing ring.
• In ring-bases DSM a single address line is divided into the shared area.

Advantages of Distributed Shared Memory


• Simpler Abstraction: Programmer need not concern about data movement, as the
address space is the same it is easier to implement than RPC.
• Easier Portability: The access protocols used in DSM allow for a natural transition
from sequential to distributed systems. DSM programs are portable as they use a
common programming interface.
• Locality of Data: Data moved in large blocks i.e. data near to the current memory
location that is being fetched, may be needed future so it will be also fetched.
• On-Demand Data Movement: It provided by DSM will eliminate the data exchange
phase.
• Larger Memory Space: It provides large virtual memory space, the total memory size
is the sum of the memory size of all the nodes, paging activities are reduced.
• Better Performance: DSM improve performance and efficiency by speeding up access
to data.
• Flexible Communication Environment: They can join and leave DSM system
without affecting the others as there is no need for sender and receiver to existing,
• Process Migration Simplified: They all share the address space so one process can
easily be moved to a different machine.

Disadvantages of Distributed Shared Memory


• Accessibility: The data access is slow in DSM as compare to non-distributed.
• Consistency: When programming is done in DSM systems, programmers need to
maintain consistency.
• Message Passing: DSM use asynchronous message passing and is not efficient as per
other message passing implementation.
• Data Redundancy: DSM allows simultaneous access to data, consistency and data
redundancy is common disadvantage.
• Lower Performance: CPU gets slowed down, even cache memory does not aid the
situation.

Program:

import threading
import time

class DSM:
def __init__(self, num_nodes):
[Link] = {"x": 0} # shared variable
[Link] = [Node(i, self) for i in range(num_nodes)]
def broadcast_update(self, key, value, source_id):
print(f"\n Broadcasting update: {key} = {value} from Node
{source_id}")
for node in [Link]:
if node.node_id != source_id:
node.update_local_copy(key, value)

class Node:
def __init__(self, node_id, dsm):
self.node_id = node_id
[Link] = dsm
self.local_memory = dict([Link])

def read(self, key):


value = self.local_memory.get(key, None)
print(f"Node {self.node_id} READ {key} = {value}")
return value

def write(self, key, value):


print(f"\n Node {self.node_id} WRITE {key} = {value}")
self.local_memory[key] = value

# Update global memory


[Link][key] = value

# Broadcast change to other nodes


[Link].broadcast_update(key, value, self.node_id)

def update_local_copy(self, key, value):


print(f"Node {self.node_id} updating {key} = {value}")
self.local_memory[key] = value

# ---- Simulation ----


def node_work(node):
[Link](node.node_id)

[Link]("x")
new_value = node.node_id * 10
[Link]("x", new_value)
[Link](1)
[Link]("x")

if __name__ == "__main__":
dsm = DSM(3)
threads = []
for node in [Link]:
t = [Link](target=node_work, args=(node,))
[Link](t)

for t in threads:
[Link]()

for t in threads:
[Link]()

Result:
Experiment 10
Aim: Study of Distributed File System (AFS/CODA)

Theory: A distributed file system (DFS) is a networked architecture that allows multiple
users and applications to access and manage files across various machines as if they were on a
local storage device. Instead of storing data on a single server, a DFS spreads files across
multiple locations, enhancing redundancy and reliability.
• This setup not only improves performance by enabling parallel access but also
simplifies data sharing and collaboration among users.
• By abstracting the complexities of the underlying hardware, a distributed file system
provides a seamless experience for file operations, making it easier to manage large
volumes of data in a scalable manner.

Components of DFS
• Location Transparency: Location Transparency achieves through the namespace
component.
• Redundancy: Redundancy is done through a file replication component.

In the case of failure and heavy load, these components together improve data availability by
allowing the sharing of data in different locations to be logically grouped under one folder,
which is known as the "DFS root". It is not necessary to use both the two components of DFS
together, it is possible to use the namespace component without using the file replication
component and it is perfectly possible to use the file replication component without using the
namespace component between servers.

Distributed File System Replication


Early iterations of DFS made use of Microsoft's File Replication Service (FRS), which
allowed for straightforward file replication between servers. The most recent iterations of the
whole file are distributed to all servers by FRS, which recognises new or updated files. "DFS
Replication" was developed by Windows Server 2003 R2 (DFSR). By only copying the
portions of files that have changed and minimising network traffic with data compression, it
helps to improve FRS. Also,
it provides users with flexible configuration options to manage network traffic on a
configurable schedule.
Features of DFS
1. Transparency
• Structure transparency: There is no need for the client to know about the number or
locations of file servers and the storage devices. Multiple file servers should be
provided for performance, adaptability, and dependability.
• Access transparency: Both local and remote files should be accessible in the same
manner. The file system should be automatically located on the accessed file and send
it to the client’s side.
• Naming transparency: There should not be any hint in the name of the file to the
location of the file. Once a name is given to the file, it should not be changed during
transferring from one node to another.
• Replication transparency: If a file is copied on multiple nodes, both the copies of the
file and their locations should be hidden from one node to another.
2. User mobility: It will automatically bring the user's home directory to the node where the
user logs in.
3. Performance: Performance is based on the average amount of time needed to convince
the client requests. This time covers the CPU time + time taken to access secondary
storage + network access time. It is advisable that the performance of the Distributed File
System be similar to that of a centralized file system.
4. Simplicity and ease of use: The user interface of a file system should be simple and the
number of commands in the file should be small.
5. High availability: A Distributed File System should be able to continue in case of any
partial failures like a link failure, a node failure, or a storage drive crash. A high authentic
and adaptable distributed file system should have different and independent file servers for
controlling different and independent storage devices.
6. Scalability: Since growing the network by adding new machines or joining two networks
together is routine, the distributed system will inevitably grow over time. As a result, a good
distributed file system should be built to scale quickly as the number of nodes and users in
the system grows. Service should not be substantially disrupted as the number of nodes and
users grows.
7. Data integrity: Multiple users frequently share a file system. The integrity of data saved in
a shared file must be guaranteed by the file system. That is, concurrent access requests from
many users who are competing for access to the same file must be correctly synchronized
using a concurrency control method. Atomic transactions are a high-level concurrency
management mechanism for data integrity that is frequently offered to users by a file
system.
8. Security: A distributed file system should be secure so that its users may trust that their data
will be kept private. To safeguard the information contained in the file system from
unwanted & unauthorized access, security mechanisms must be implemented.

History of DFS
The server component of the Distributed File System was initially introduced as an add-on
feature. It was added to Windows NT 4.0 Server and was known as "DFS
4.1". Then later on it was included as a standard component for all editions of Windows 2000
Server. Client-side support has been included in Windows NT 4.0 and also in later on version
of Windows. Linux kernels 2.6.14 and versions after it come with an SMB client VFS
known as "cifs" which supports DFS. Mac OS X 10.7 (lion) and onwards supports Mac OS
X DFS.

Working of DFS
There are two ways in which DFS can be implemented:

• Standalone DFS namespace allows only for those DFS roots that exist on the local
computer and are not using Active Directory. A Standalone DFS can only be acquired
on those computers on which it is created. It does not provide any fault liberation and
cannot be linked to any other DFS. Standalone DFS roots are rarely come across
because of their limited advantage.
• Domain-based DFS namespace stores the configuration of DFS in Active Directory,
creating the DFS namespace root accessible at \\<domainname>\<dfsroot> or
\\<FQDN>\<dfsroot>
Fig: Distributed File System (DFS)
Applications of DFS:
• NFS: NFS stands for Network File System. It is a client-server architecture that
allows a computer user to view, store, and update files remotely. The protocol of NFS
is one of the several distributed file system standards for Network-Attached Storage
(NAS).
• CIFS stands for Common Internet File System. CIFS is an accent of SMB. That is,
CIFS is an application of SIMB protocol, designed by Microsoft.
• SMB stands for Server Message Block. It is a protocol for sharing a file and was
invented by IBM. The SMB protocol was created to allow computers to perform read
and write operations on files to a remote host over a Local Area Network (LAN). The
directories present in the remote host can be accessed via SMB and are called as
"shares".
• Hadoop is a group of open-source software services. It gives a software framework
for distributed storage and operating of big data using the MapReduce programming
model. The core of Hadoop contains a storage part, known as Hadoop Distributed File
System (HDFS), and an operating part which is a MapReduce programming model.
• NetWare is an abandon computer network operating system developed by Novell, Inc.
It primarily used combined multitasking to run different services on a personal
computer, using the IPX network protocol.

Andrew File System (AFS)


Andrew File System (AFS) is a distributed network file system developed at Carnegie Mellon
University. It is designed to support a large number of clients and provide efficient file
sharing across distributed environments.

Features of AFS
• Client-side caching improves performance.
• Location transparency, users access files without knowing their physical location.
• Scalability, supports thousands of clients.
• Security using authentication mechanisms.
• Volume-based file management.
Working of AFS
1. Clients request files from the server.
2. Files are cached on the client machine.
3. Updates are synchronized with the server.
4. Multiple clients can access files efficiently.

CODA File System


CODA is an advanced distributed file system derived from AFS. It was also developed at
Carnegie Mellon University and focuses on high availability and fault tolerance.

Features of CODA
• Disconnected operation, clients can work even when the network is unavailable.
• Server replication for high availability.
• Fault tolerance in case of server failures.
• Automatic conflict resolution.

Working of CODA
1. Files are replicated on multiple servers.
2. Clients access cached copies of files.
3. If the network fails, the client can continue working locally.
4. Once the connection is restored, changes are synchronized with servers.

Feature AFS CODA

Developed at Carnegie Mellon Carnegie Mellon University


University

Caching Client-side caching Advanced caching

Fault Tolerance Limited High

Disconnected Operation Not supported Supported

Replication Limited Multiple server replication


Case Study: CORBA

The Common Object Request Broker Architecture (CORBA), standardized by the Object
Management Group, was developed to address one of the fundamental challenges in distributed
computing: enabling seamless communication between heterogeneous systems. In large-scale
distributed environments, applications are often built using different programming languages,
run on diverse operating systems, and are deployed across geographically dispersed networks.
CORBA solves this problem by introducing a middleware layer that abstracts communication
complexities and provides a uniform interface for interaction. This allows developers to build
scalable and interoperable systems without worrying about underlying implementation details
such as network protocols or data representation formats.

The central component of CORBA is the Object Request Broker (ORB), which acts as a
mediator between client requests and server responses. When a client invokes a method on a
remote object, the ORB intercepts the request, identifies the object’s location, and forwards the
request to the appropriate server. The response is then sent back to the client in a transparent
manner. This mechanism ensures location transparency, meaning that the client does not need
to know whether the object is local or remote. The ORB also handles essential tasks such as
parameter marshalling and unmarshalling, connection management, and error handling,
thereby simplifying the development of distributed applications.

Another key aspect of CORBA is the Interface Definition Language (IDL), which serves as a
contract between clients and servers. IDL allows developers to define object interfaces in a
language-neutral way, specifying the methods, parameters, and data types that can be used.
These definitions are then compiled into language-specific stubs and skeletons, enabling
communication between components written in different programming languages. For
example, a server implemented in C++ can provide services to a client written in Java without
requiring any changes to either application’s core logic. This language independence is one of
CORBA’s most powerful features and has made it suitable for enterprise environments with
diverse technology stacks.

CORBA also includes a wide range of services that enhance the functionality of distributed
systems. These include naming services, which allow objects to be registered and discovered
dynamically; transaction services, which ensure data consistency across multiple operations;
and security services, which provide authentication, authorization, and encryption
mechanisms. Additionally, CORBA supports event and notification services, enabling
asynchronous communication between distributed components. This is particularly useful in
real-time systems such as stock trading platforms or telecommunications networks, where
timely data updates are critical.

A real-world application of CORBA can be found in telecommunications systems, where it has


been used to integrate network management components developed by different vendors. In
such systems, CORBA enables interoperability by allowing various subsystems—such as
billing, customer management, and network monitoring—to communicate effectively.
Similarly, in healthcare systems, CORBA has been used to integrate patient records, diagnostic
tools, and administrative services across multiple hospitals and departments. These
implementations highlight CORBA’s ability to handle complex, distributed workflows in
mission-critical environments.

Despite its strengths, CORBA has certain limitations that have affected its adoption in modern
systems. One of the primary challenges is its complexity, as setting up and maintaining
CORBA-based systems requires significant expertise. The use of IDL, along with the need for
ORB configuration and management, can make development time-consuming. Additionally,
CORBA systems tend to be heavyweight, requiring substantial computational resources
compared to newer lightweight communication frameworks. As a result, many organizations
have transitioned to alternative technologies such as RESTful APIs and microservices, which
offer simpler and more flexible solutions for distributed communication.

Another limitation is the difficulty in debugging and maintaining CORBA-based applications.


Since communication is handled through multiple layers of abstraction, identifying and
resolving issues can be challenging. Furthermore, firewall and network configuration issues
can arise due to the protocols used by CORBA, making deployment more complex in certain
environments. These challenges have contributed to a gradual decline in CORBA’s popularity,
especially with the rise of cloud computing and container-based architectures.

However, CORBA still holds significant academic and historical value in the field of
distributed computing. It introduced several important concepts such as middleware
abstraction, object-based communication, and interface standardization, which have influenced
modern distributed system designs. Many of the ideas pioneered by CORBA are still relevant
today and can be seen in technologies like web services, gRPC, and distributed object
frameworks. Understanding CORBA provides valuable insights into the evolution of
distributed computing and helps students grasp the fundamental principles that underpin
modern systems.

Conclusion:
In conclusion, CORBA represents a foundational technology in distributed computing that has
played a crucial role in enabling interoperability across heterogeneous systems. While it has
largely been replaced by more lightweight and flexible solutions, its contributions to the field
remain significant. By studying CORBA, one can gain a deeper understanding of distributed
system design, including challenges such as scalability, reliability, and cross-platform
communication. As such, CORBA continues to be an important case study for both academic
learning and historical reference in the evolution of distributed computing technologies.
Case Study: Android Stack
The Android Stack refers to the layered architecture of the Android Operating System,
designed to support the development and execution of mobile applications efficiently. This
architecture follows a modular approach, where each layer provides specific functionality and
interacts with the layers above and below it. The Android Stack is typically divided into five
main layers: Linux Kernel, Hardware Abstraction Layer (HAL), Native Libraries, Android
Runtime, Application Framework, and Applications. This structured design ensures scalability,
flexibility, and efficient resource management across a wide range of devices.

At the base of the Android Stack lies the Linux Kernel, which acts as the core of the system. It
is responsible for managing hardware resources such as memory, process scheduling, power
management, and device drivers. The Linux Kernel provides a secure and stable environment
by enforcing permissions and isolating processes. It also includes drivers for hardware
components like display, camera, Bluetooth, and Wi-Fi, enabling communication between the
hardware and software layers. This layer is crucial because it ensures that Android can run on
diverse hardware platforms while maintaining consistency.

Above the kernel is the Hardware Abstraction Layer (HAL), which acts as an interface between
hardware components and higher-level software. HAL allows developers to interact with
hardware devices without needing to understand the low-level implementation details. For
example, whether a device uses a specific camera chip or sensor, the HAL provides a consistent
interface to access its functionality. This abstraction improves portability and simplifies the
development of applications across different devices and manufacturers.

The next layer consists of Native Libraries and the Android Runtime. Native libraries are
written in C and C++ and provide core functionalities such as graphics rendering, database
management, and media playback. Examples include OpenGL for graphics, SQLite for
databases, and WebKit for web browsing. The Android Runtime includes the Android Runtime
(ART), which replaced the older Dalvik Virtual Machine. ART uses ahead-of-time (AOT)
compilation to improve application performance and reduce startup time. It also includes core
Java libraries that developers use to build Android applications. Together, these components
form the backbone of application execution and performance optimization.
At the top of the Android Stack is the Applications layer, which includes all user-facing
applications such as contacts, messaging, browser, and third-party apps installed from app
stores. These applications are built using the APIs provided by the Application Framework and
run within their own sandboxed environment for security. This isolation ensures that one
application cannot interfere with another, thereby enhancing system stability and user privacy.
From a distributed computing perspective, the Android Stack supports communication and data
exchange through various mechanisms such as web services, REST APIs, and inter-process
communication (IPC). Android applications can interact with remote servers, cloud services,
and other devices, making it a powerful platform for distributed applications. Features such as
background services, broadcast receivers, and content providers enable efficient data sharing
and synchronization across distributed systems.
Figure [Link] Stack

The Android Operating System follows a layered architecture known as the Android Stack,
which is designed to manage hardware resources and provide a platform for application
development. This architecture is divided into multiple layers, each responsible for specific
functionalities. The layered approach ensures that developers can build applications
efficiently without dealing directly with hardware complexities, making the system scalable,
flexible, and easy to maintain.
1. Applications Layer
The Applications layer is the topmost layer of the Android architecture and consists of all
user-facing applications. These include both pre-installed system applications and third-party
applications downloaded by users. Each application runs in its own isolated environment
(sandbox), which enhances security and prevents interference between apps. This layer
directly interacts with the user and provides the actual functionality of the device.
Key features:
• Includes apps like Phone, Browser, Games
• Developed using Java or Kotlin
• Runs in isolated processes for security

2. Application Framework
Below the Applications layer is the Application Framework, which provides a set of APIs
and system services that developers use to create applications. This layer acts as a bridge
between applications and the lower-level system components. It simplifies development by
offering reusable components and managing essential functions like UI design and
application lifecycle.
Important services provided by this layer include Activity Manager (controls app lifecycle),
Window Manager (manages screen layout), Content Providers (enable data sharing), and
Notification Manager (handles alerts). These services allow applications to function smoothly
without directly interacting with the system hardware.
Main components:
• Activity Manager
• Window Manager
• Content Providers
• Notification Manager

3. Android Runtime & Native Libraries


This layer is responsible for executing applications and providing core functionalities. The
Android Runtime (ART) plays a crucial role by running application code efficiently. It uses
ahead-of-time compilation, which improves performance and reduces app startup time.
Alongside ART, native libraries written in C and C++ provide essential features such as
graphics rendering, database management, and multimedia processing.
These libraries act as building blocks for higher-level operations, ensuring that applications
can perform complex tasks efficiently without requiring developers to implement everything
from scratch.
Includes:
• ART (Android Runtime)
• Core Java Libraries
• Native Libraries like:
o OpenGL (graphics)
o SQLite (database)
o WebKit (browser engine)

4. Hardware Abstraction Layer (HAL)


The Hardware Abstraction Layer (HAL) serves as an interface between the hardware and the
software layers of Android. It hides the complexity of hardware components and provides a
consistent interface for higher-level layers. This ensures that applications can run on different
devices without modification, even if the hardware differs.
In simpler terms, HAL acts like a translator between software and hardware, making the
system more portable and device-independent.
Examples:
• Camera HAL
• Audio HAL
• Sensor HAL

5. Linux Kernel
The Linux Kernel forms the foundation of the Android Stack and is responsible for low-level
system operations. It manages hardware resources, including memory, processes, and device
drivers. The kernel also ensures system security by enforcing access control and isolating
processes.
It plays a critical role in maintaining system stability and performance, acting as a bridge
between hardware and software components.
Functions:
• Memory management
• Process scheduling
• Device drivers (camera, Wi-Fi, display)
• Power management

Conclusion:
The Android Stack is a well-structured architecture that ensures smooth interaction between
applications and hardware. Its layered design simplifies development, improves performance,
and enhances security. By understanding this architecture, developers can create efficient and
scalable mobile applications while leveraging the full capabilities of the Android system.

You might also like