Code 1

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 81

SUDUKU

#include <stdio.h>

#include <stdlib.h>

#include <pthread.h>

#define SIZE 9

#define NUM_THREADS 27

int sudoku[SIZE][SIZE];

// Structure to hold thread arguments

typedef struct {

int row;

int col;

} ThreadArgs;

// Function to check if a given row is valid

void *isRowValid(void *param) {

ThreadArgs *args = (ThreadArgs *)param;

int row = args->row;

int frequency[SIZE] = {0};

for (int col = 0; col < SIZE; col++) {

int num = sudoku[row][col];

if (frequency[num - 1] == 1 || num < 1 || num > 9) {

pthread_exit(NULL);

frequency[num - 1] = 1;
}

pthread_exit((void *)1);

// Function to check if a given column is valid

void *isColValid(void *param) {

ThreadArgs *args = (ThreadArgs *)param;

int col = args->col;

int frequency[SIZE] = {0};

for (int row = 0; row < SIZE; row++) {

int num = sudoku[row][col];

if (frequency[num - 1] == 1 || num < 1 || num > 9) {

pthread_exit(NULL);

frequency[num - 1] = 1;

pthread_exit((void *)1);

// Function to check if a given 3x3 subgrid is valid

void *isSubgridValid(void *param) {

ThreadArgs *args = (ThreadArgs *)param;

int startRow = args->row;

int startCol = args->col;

int frequency[SIZE] = {0};

for (int row = startRow; row < startRow + 3; row++) {

for (int col = startCol; col < startCol + 3; col++) {


int num = sudoku[row][col];

if (frequency[num - 1] == 1 || num < 1 || num > 9) {

pthread_exit(NULL);

frequency[num - 1] = 1;

pthread_exit((void *)1);

int main() {

// Sudoku grid

int inputSudoku[9][9] = {

{6, 2, 4, 5, 3, 9, 1, 8, 7},

{5, 1, 9, 7, 2, 8, 6, 3, 4},

{8, 3, 7, 6, 1, 4, 2, 9, 5},

{1, 4, 3, 8, 6, 5, 7, 2, 9},

{9, 5, 8, 2, 4, 7, 3, 6, 1},

{7, 6, 2, 3, 9, 1, 4, 5, 8},

{3, 7, 1, 9, 5, 6, 8, 4, 2},

{4, 9, 6, 1, 8, 2, 5, 7, 3},

{2, 8, 5, 4, 7, 3, 9, 1, 6}

};

// Copy Sudoku to sudoku array

for (int i = 0; i < SIZE; i++) {

for (int j = 0; j < SIZE; j++) {

sudoku[i][j] = inputSudoku[i][j];

}
}

pthread_t threads[NUM_THREADS];

int threadCount = 0;

// Create threads to check each row

for (int i = 0; i < SIZE; i++) {

ThreadArgs *args = (ThreadArgs *)malloc(sizeof(ThreadArgs));

args->row = i;

pthread_create(&threads[threadCount++], NULL, isRowValid, (void *)args);

// Create threads to check each column

for (int i = 0; i < SIZE; i++) {

ThreadArgs *args = (ThreadArgs *)malloc(sizeof(ThreadArgs));

args->col = i;

pthread_create(&threads[threadCount++], NULL, isColValid, (void *)args);

// Create threads to check each subgrid

for (int i = 0; i < SIZE; i += 3) {

for (int j = 0; j < SIZE; j += 3) {

ThreadArgs *args = (ThreadArgs *)malloc(sizeof(ThreadArgs));

args->row = i;

args->col = j;

pthread_create(&threads[threadCount++], NULL, isSubgridValid, (void *)args);

}
// Join all threads

for (int i = 0; i < NUM_THREADS; i++) {

void *status;

pthread_join(threads[i], &status);

if (status == NULL) {

printf("Sudoku solution is invalid.\n");

return 1;

printf("Sudoku solution is valid.\n");

return 0;

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/time.h>

#include <sys/wait.h>

int main() {

struct timeval start_time, end_time, before_switch, after_switch;

int num_context_switches = 0;

double waiting_time = 0.0;

gettimeofday(&start_time, NULL); // Record submission time

pid_t pid = fork();

if (pid < 0) {

printf("Fork failed\n");
return 1;

} else if (pid == 0) {

gettimeofday(&before_switch, NULL); // Record time before context switch

sched_yield(); // Yield to let the parent continue

gettimeofday(&after_switch, NULL); // Record time after context switch

exit(0);

} else {

gettimeofday(&before_switch, NULL); // Record time before waiting for child

wait(NULL); // Wait for child process to finish

gettimeofday(&after_switch, NULL); // Record time after child process finished

gettimeofday(&end_time, NULL); // Record completion time

// Calculate waiting time in milliseconds

waiting_time = (double)(after_switch.tv_sec - before_switch.tv_sec) * 1000.0 +

(double)(after_switch.tv_usec - before_switch.tv_usec) / 1000.0;

// Calculate number of context switches

num_context_switches = (after_switch.tv_sec - before_switch.tv_sec) * 1000000 +

(after_switch.tv_usec - before_switch.tv_usec);

// Print results

printf("Submission Time: %ld seconds, %ld milliseconds\n", start_time.tv_sec/1000000000,


start_time.tv_usec / 1000);

printf("Completion Time: %ld seconds, %ld milliseconds\n", end_time.tv_sec/1000000000,


end_time.tv_usec / 1000);

printf("Number of Context Switches: %d\n", num_context_switches);

printf("Waiting Time: %f milliseconds\n", waiting_time);


return 0;

IV. Assignment This assignment will involve designing two kernel modules:
1. Design a kernel module that creates a /proc file named /proc/jiffies that reports the
current value of jiffies when the /proc/jiffies file is read, such as with the command cat
/proc/jiffies Be sure to remove /proc/jiffies when the module is removed.
2. Design a kernel module that creates a proc file named /proc/seconds that reports the
number of elapsed seconds since the kernel module was loaded. This will involve using the
value of jiffies as well as the HZ rate. When a user enters the command cat /proc/seconds
your kernel module will report the number of seconds that have elapsed since the kernel
module was first loaded. Be sure to remove /proc/seconds when the module is removed
To implement these two kernel modules, you'll need to write code in C that interacts with the
Linux kernel's interface. Below are the basic steps for each module:

1. /proc/jiffies Module:

- Include necessary headers.


- Implement a function to read the current value of jiffies.
- Create a function to handle file operations (read) for the /proc/jiffies file.
- Implement module initialization and cleanup functions to create/remove the /proc/jiffies file.

2. /proc/seconds Module:

- Include necessary headers.


- Implement a function to calculate the number of seconds elapsed since the module was
loaded using jiffies and HZ rate.
- Create a function to handle file operations (read) for the /proc/seconds file.
- Implement module initialization and cleanup functions to create/remove the /proc/seconds
file.
Here's a basic structure for each module:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/jiffies.h>

#define PROC_JIFFIES_FILENAME "jiffies"


#define PROC_SECONDS_FILENAME "seconds"

static struct proc_dir_entry *proc_jiffies_entry;


static struct proc_dir_entry *proc_seconds_entry;
static unsigned long long module_load_jiffies;

// Function to read jiffies


static int proc_jiffies_read(char *page, char **start, off_t off, int count, int *eof, void *data) {
int len;
len = snprintf(page, count, "%lu\n", jiffies);
return len;
}

// Function to read seconds


static int proc_seconds_read(char *page, char **start, off_t off, int count, int *eof, void *data) {
unsigned long elapsed_seconds = (jiffies - module_load_jiffies) / HZ;
int len;
len = snprintf(page, count, "%lu\n", elapsed_seconds);
return len;
}
// Module initialization
static int __init init_proc_module(void) {
module_load_jiffies = jiffies;
proc_jiffies_entry = proc_create(PROC_JIFFIES_FILENAME, 0, NULL,
&proc_jiffies_fops);
if (!proc_jiffies_entry)
return -ENOMEM;

proc_seconds_entry = proc_create(PROC_SECONDS_FILENAME, 0, NULL,


&proc_seconds_fops);
if (!proc_seconds_entry) {
remove_proc_entry(PROC_JIFFIES_FILENAME, NULL);
return -ENOMEM;
}

return 0;
}

// Module cleanup
static void __exit cleanup_proc_module(void) {
remove_proc_entry(PROC_JIFFIES_FILENAME, NULL);
remove_proc_entry(PROC_SECONDS_FILENAME, NULL);
}

module_init(init_proc_module);
module_exit(cleanup_proc_module);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
```

This is just a basic structure. You need to fill in the implementation details for reading jiffies,
calculating elapsed seconds, and handling file operations correctly. Also, ensure proper error
handling throughout the code.

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/jiffies.h>

#define PROC_JIFFIES_FILENAME "jiffies"


#define PROC_SECONDS_FILENAME "seconds"

static struct proc_dir_entry *proc_jiffies_entry;


static struct proc_dir_entry *proc_seconds_entry;
static unsigned long long module_load_jiffies;

// Function to read jiffies


static int proc_jiffies_read(char *page, char **start, off_t off, int count, int *eof, void *data) {
int len;
len = snprintf(page, count, "%lu\n", jiffies);
return len;
}

// Function to read seconds


static int proc_seconds_read(char *page, char **start, off_t off, int count, int *eof, void *data) {
unsigned long elapsed_seconds = (jiffies - module_load_jiffies) / HZ;
int len;
len = snprintf(page, count, "%lu\n", elapsed_seconds);
return len;
}

// Module initialization
static int __init init_proc_module(void) {
module_load_jiffies = jiffies;
proc_jiffies_entry = proc_create(PROC_JIFFIES_FILENAME, 0, NULL,
&proc_jiffies_fops);
if (!proc_jiffies_entry)
return -ENOMEM;

proc_seconds_entry = proc_create(PROC_SECONDS_FILENAME, 0, NULL,


&proc_seconds_fops);
if (!proc_seconds_entry) {
remove_proc_entry(PROC_JIFFIES_FILENAME, NULL);
return -ENOMEM;
}

return 0;
}

// Module cleanup
static void __exit cleanup_proc_module(void) {
remove_proc_entry(PROC_JIFFIES_FILENAME, NULL);
remove_proc_entry(PROC_SECONDS_FILENAME, NULL);
}
module_init(init_proc_module);
module_exit(cleanup_proc_module);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");

Chapter No 3
Progr
ammi
ng
Exerc
ise
Producer - consumer problem implement in circular queue?
1- Include necessary headers:

#
i
n
c
l
u
d
e
<
s
t
d
i
o
.
h
>
#
i
n
c
l
u
d
e
<
s
t
d
l
i
b
.
h
>
#
i
n
c
l
u
d
e
<
p
t
h
r
e
a
d
.
h
>
#
i
n
c
l
u
d
e
<
s
e
m
a
p
h
o
r
e
.
h
>
2- Define the buffer size and declare

global variables for the circular


queue: #define BUFFER_SIZE 5
int buffer [BUFFER_SIZE];
int front = 0, rear = -1, count = 0;
3- Declare semaphores for synchronization:

sem_t mutex, empty, full;


4- Define the producer and consumer functions:

Producer: Generates a
random item and adds it to
the buffer. Consumer:
Removes an item from the
buffer and prints it. void
*producer(void *param) {
int item;
while (1) {
item = rand() % 100; //
Generate a random item
sem_wait(&empty); // Wait
if the buffer is full
sem_wait(&mutex); //
Acquire the mutex to access
the buffer rear = (rear + 1) %
BUFFER_SIZE;
buffer [rear] =
item; count ++;
printf("Produced: %d\n", item);
sem_post (&mutex); // Release the mutex
sem_post (&full); // Signal that the buffer is no longer empty
} } void *consumer (void *param) {
int item;
while (1) {
sem_wait (&full); // Wait if the buffer is empty
sem_wait (&mutex); // Acquire the mutex to access the
buffer item = buffer [front];
front = (front + 1) %
BUFFER_SIZE; count--;
printf ("Consumed: %d\n", item);
sem_post (&mutex); // Release the mutex
sem_post (&empty); } } // Signal that the buffer is no
longer full 5-Define the main function:
Initialize semaphores.
Create producer and consumer
threads. Wait for threads to finish.
Clean up
semaphores. int
main() {
pthread_t tid_producer, tid_consumer;
sem_init (&mutex, 0, 1); // Initialize the mutex to 1
sem_init (&empty, 0, BUFFER_SIZE); // Initialize empty to
BUFFER_SIZE sem_init (&full, 0, 0); // Initialize full to 0
// Create producer and consumer threads
pthread_create (&tid_producer, NULL, producer,
NULL); pthread_create (&tid_consumer, NULL,
consumer, NULL);
// Wait for the threads to finish
pthread_join (tid_producer,
NULL); pthread_join
(tid_consumer, NULL);
// Clean up
sem_destroy
(&mutex);
sem_destroy
(&empty);
sem_destroy (&full);
return 0;}
In this implementation, the mutex semaphore ensures that only one thread can access the buffer
at a time to avoid race conditions. The empty semaphore tracks the number of empty slots in the
buffer, and the full semaphore tracks the number of full slots. Producers wait on empty before
adding items to the buffer, and consumers wait on full before consuming items. After adding or
removing an item, the semaphores are updated accordingly.

 3.1 Using the program, explain what output will be at LINE A?


#include <stdio.h>
#include
<sys/types.h>
#include <unistd.h>
#include
<sys/wait.h> int
main() {
int value =
5; pid_t
pid; pid =
fork(); if
(pid == 0) {
value += 15;
return 0; // Return from child process
} else if (pid > 0) {
wait(NULL); // Wait for child process to
finish printf("PARENT: value = %d\n", value);
}
else { // Fork
failed
perror("fork");
return 1;
} return 0;}
At LINE A, the value of value will be 5 in the parent process and 20 in the child process. Here's
a step-by- step explanation of what happens:

 The program starts by declaring an int variable value and setting it to 5.


 It then forks a new process using fork (). This creates a copy of the current process. In
the parent process, fork () returns the process ID (pid) of the child process. In the child
process, it returns 0.
 In the child process (pid == 0), the value of value is increased by 15, making it 20. Then,
the child process returns from main, exiting the child process.
 In the parent process (pid > 0), the parent waits for the child process to finish using wait
(NULL). This ensures that the parent process waits until the child process has
completed before continuing.
 After the child process has finished, the parent process prints the value of value, which
is still 5 because the child process's modifications to value do not affect the parent's
copy of value.
So, the output of the program will be: PARENT:
value = 5

#include
<stdio.h>
#include
<unistd.h> int
main(){
/* fork a child process
*/ fork();
/* fork another child process
*/ fork();
/* and fork another*/
fork();
return 0;}
3.11 Including the initial parent process, how many processes are created by the
program?
Let's analyze the code to understand how many processes are created:
• Initially, there is one process, the parent process.
• After the first fork(), there are two processes (the original parent process and a child process).
• After the second fork (), each of the two existing processes creates a child process,
resulting in a total of four processes.
• After the third fork (), each of the four existing processes creates another child process,
resulting in a total of eight processes.
• So, including the initial parent process, the program creates a total of 8 processes.

How?
Let's break down the process:
• Initially, there is one process, the parent process.
• After the first fork(), the parent process creates a child process. Now we have two
processes: one is the parent, and the other is the child created by the first fork().
• After the second fork(), both the parent and the child created by the first fork() will
execute the fork() statement. This means that each of these two processes will create a
child process. So, from the parent, we get one more child, and from the child created by
the first fork(), we get another child. This results in a total of four processes: the original
parent, the child from the first fork(), and two children from the second fork().
• After the third fork(), each of the four existing processes (original parent, child from the
first fork(), and two children from the second fork()) will execute the fork() statement,
resulting in each process creating a child. This gives us an additional four children. So,
now we have a total of eight processes.

Here's the breakdown:


• Original parent (1)
• Child from the first fork() (2)
• Children from the second fork() (2 x 2 = 4)
• Children from the third fork() (4 x 2 = 8)
• Total processes: 1 (original parent) + 1 (child from the first fork ()) + 2 (children from
the second fork()) + 4 (children from the third fork()) = 8 processes.

#include <stdio.h>
#include <unistd.h>
int
main(){
int i;
for (i = 0; i < 4; i++)
fork();
return 0; }
We need to determine the number of processes created by the program shown in the
given code?

This program will create a total of 2^4 = 16 processes, including the initial parent process.
Here's how it works:
• The initial process (let's call it process A) starts and enters the loop.
• In the first iteration of the loop, process A forks, creating a new process (let's call it
process B). Now we have two processes: A and B.
• In the second iteration of the loop, both processes A and B fork. Process A creates
process C, and process B creates process D. Now we have four processes: A, B, C, and
D.
• In the third iteration of the loop, processes A, B, C, and D each fork again. Process
A creates process E, process B creates process F, process C creates process G, and
process D creates process H. Now we have eight processes: A, B, C, D, E, F, G,
and H.
• In the fourth and final iteration of the loop, processes A, B, C, D, E, F, G, and H each
fork one more time. This results in a total of 16 processes being created: A, B, C, D, E,
F, G, H, I, J, K, L, M, N, O, and P.
Therefore, the program will create 16 processes.
How?
Let's break it down step by step:
• Initially, there is one process (let's call it Process 1).
• In the first iteration of the loop, Process 1 forks. Now we have two processes: Process 1 (which we'll
call Process 1a after the fork) and a new process (which we'll call Process 2). So far, we have Process 1a
and Process 2.
• In the second iteration of the loop, both Process 1a and Process 2 fork.
o Process 1a forks into Process 1a1 and Process 1a2.
o Process 2 forks into Process 2a and Process 2b.
• Now we have six processes: Process 1a1, Process 1a2, Process 2a, Process 2b, and the original
Process 1 (which we'll now call Process 1b to differentiate it from its child processes).
• In the third iteration of the loop, each of the six processes forks again:
1. Process 1a1 forks into Process 1a1a and Process 1a1b.
2. Process 1a2 forks into Process 1a2a and Process 1a2b.
3. Process 2a forks into Process 2a1 and Process 2a2.
4. Process 2b forks into Process 2b1 and Process 2b2.
5. Process 1b forks into Process 1b1 and Process 1b2.

Now we have 16 processes: Process 1a1a, Process 1a1b, Process 1a2a, Process 1a2b, Process
2a1, Process 2a2, Process 2b1, Process 2b2, Process 1b1, Process 1b2, and the processes from
the previous iteration.
In the fourth iteration, each of the 16 processes forks again, resulting in a total of 32 processes.
However, the loop condition in the program (i < 4) prevents the loop from executing a fifth time,
so we end up with 16 processes in total.
Therefore, the program will create 16 processes.

3.12 Explain the circumstances under which the line of code marked printf
("LINE J") in the given code will be reached.
#include
<sys/types.h>
#include <stdio.h>
#include <unistd.h>
int main(){
pid_t pid;
/* fork a child process
*/ pid = fork();
if (pid < 0) { /* error occurred
*/ fprintf(stderr, "Fork Failed");
return 1; }
else if (pid == 0) { /* child process
*/ execlp("/bin/ls", "ls", NULL);
printf("LINE J"); }
else { /* parent process */
/* parent will wait for the child to complete */
wait(NULL);
printf("Child Complete");

} return 0;}
We need to explain the circumstances under which the line of code marked printf
("LINE J") in the given code will be reached.
The line of code printf("LINE J") will only be reached in the child process if the execlp function
call fails. This function call replaces the current process image with a new process image, in this
case, the ls command. If execlp fails, the child process will continue executing from the next
line of code after the execlp call, which is printf("LINE J").
However, in this specific case, execlp is used to execute the ls command, which is a standard
Unix command to list directory contents. Unless there's an issue with the ls command itself
(such as the command not being found or executable permissions not being set), execlp is
unlikely to fail, and therefore, the line printf("LINE J") is unlikely to be reached in normal
circumstances.
Explain How?
 The execlp function replaces the current process image with a new process image. In this
case, it is used to execute the ls command, which lists the contents of a directory. When
execlp is successful, the ls command replaces the child process entirely, and the code
that follows execlp in the child process is not executed.
 If execlp fails, it returns -1 and sets errno to indicate the error. Possible reasons for failure
include the executable file not being found, insufficient permissions to execute the file,
or the file not being in the correct format for execution.
 If execlp fails for any reason, the child process continues executing from the next line of
code after the execlp call. In this case, that line is printf("LINE J"). Therefore,
printf("LINE J") will only be reached in the child process if the execlp call fails.

3.15 Identify the values of pid at lines A, B, C, & D.

#include
<sys/types.h>
#include <stdio.h>
#include <unistd.h>
int main(){

pid_t pid, pid1;


/* fork a child process */
pid = fork();
if (pid < 0) { /* error occurred */
fprintf(stderr, "Fork Failed");
return 1; }
else if (pid == 0) { /* child process */
pid1 = getpid();
printf("child: pid = %d", pid); /* A */
printf("child: pid1 = %d", pid1); /* B */

}
else { /* parent process */
pid1 = getpid();
printf("parent: pid = %d", pid); /* C */
printf("parent: pid1 = %d", pid1); /* D */
wait(NULL); }
return 0; }

Actual pid of the parent process: pid=2600. Actual

pid of the child process pid=2603.

We need to identify the values of pid at lines A, B, C, D in the given code.


In the given code, we have a parent process that forks a child process. The child process
prints its own PID (process ID) and the parent process prints its own PID. Let's analyze the
values of pid and pid1 at lines A, B, C, and D:

• Line A: pid in the child process is the return value of fork(), which is the PID of
the child process. So, pid at line A in the child process will be the PID of the
child process itself.
• Line B: pid1 in the child process is the result of getpid(), which also gives the
PID of the child process. So, pid1 at line B in the child process will be the PID
of the child process itself.
• Line C: In the parent process, pid is the PID of the child process returned by
fork(), so pid at line C in the parent process will be the PID of the child process.
• Line D: In the parent process, pid1 is the result of getpid(), which gives the
PID of the parent process. So, pid1 at line D in the parent process will be the
PID of the parent process.
Therefore, the values of pid and pid1 at lines A, B, C, and D are as follows:

1. A: pid in child process = 0 (PID of the child process)


2. B: pid1 in child process = 2603 (PID of the child process)
3. C: pid in parent process = 2603 (PID of the child process)
4. D: pid1 in parent process = 2600 (PID of the parent process)

Explain How it Work?


This program demonstrates the use of the fork() system call to create a new process
(child process) that is a copy of the current process (parent process). Here's a step-by-
step explanation of how the program works:

o Include Header Files: The program includes necessary header files for
input-output operations (stdio.h), process identifiers (sys/types.h), and
process-related functions (unistd.h).
o Declare Variables: It declares two variables of type pid_t to store the
process IDs (pid) of the parent and child processes (pid1).
o Forking a Child Process: The fork() system call is used to create a new
process. After calling fork(), the program splits into two separate
processes: the parent process and the child process.
 In the parent process, fork() returns the PID of the child process.
 In the child process, fork() returns 0, indicating that it is the child process.
o Checking Fork Result: The program checks the return value of
fork() to determine whether an error occurred or which process is
executing.

1. If fork() returns a negative value, an error occurred, and an error


message is printed.
2. If fork() returns 0, it means the process is the child process, and it prints
its PID (pid) and its parent's PID (pid1).
3. If fork() returns a positive value, it means the process is the parent process,
and it prints its child's PID (pid) and its own PID (pid1).
• Waiting for Child Process (Parent Only): After printing its information, the parent
process waits for the child process to complete using the wait() system call. This
ensures that the parent does not terminate before the child process.
• Printing Process Information: The program prints the process IDs (pid and pid1)
of the child and parent processes to the standard output.
• Return Statement: Finally, the program returns 0 to indicate successful completion.

When you run this program, it will create a new child process, and both the parent and
child processes will print their respective process IDs. The output will show that the child
process has a PID of 0 (indicating it is the child process) and a different PID for the
parent process.

3.16 Explain what the output will be at lines X and Y in the given code?

#include
<sys/types.h>
#include <stdio.h>
#include <unistd.h>
#define SIZE 5
int nums[SIZE] = {0, 1, 2, 3, 4};
int
main(){
int i;
pid_t pid;
pid = fork();
if (pid == 0)
{
for (i = 0; i < SIZE; i++)
{ nums[i] *= -i;
printf("CHILD: %d ", nums[i]); } }/* LINE X

*/ else if (pid > 0) {


wait(NULL);
for (i = 0; i < SIZE; i++)
printf("PARENT: %d ", nums[i]); } /*
LINE Y */ return 0;}
We need to explain what the output will be at lines X and Y in the given code?
In the given code, the fork() function is used to create a new process. After the fork()
call, there are two processes running: the parent process and the child process. Both
processes will continue executing from the point of the fork() call, but they will have
separate memory spaces.

Line X (inside the child process):

 In the child process, pid is 0, so it enters the if block.


 The child process then modifies the nums array by multiplying each element by -i
(where i is the index).
 The printf statement in the child process prints the modified nums array
elements with the prefix "CHILD: ".
Output at Line X (inside the child process):CHILD: 0 CHILD: -1 CHILD: -4
CHILD: - 9 CHILD: -16

Line Y (inside the parent process):

 In the parent process, pid is greater than 0, so it enters the else if block.
 The parent process waits for the child process to finish executing using wait(NULL).
 After the child process completes, the parent process continues executing
from this point.
 The printf statement in the parent process prints the original nums array
elements without modification, with the prefix "PARENT: ".
Output at Line Y (inside the parent process):PARENT: 0 PARENT: 1 PARENT: 2
PARENT: 3 PARENT: 4

Therefore, the final output of the program will be:


CHILD: 0 CHILD: -1 CHILD: -4 CHILD: -9 CHILD: -16 PARENT: 0 PARENT: 1
PARENT: 2 PARENT: 3 PARENT: 4

Explain?
This program demonstrates the use of the fork() system call to create a new process,
effectively splitting the program into two separate processes: the parent process and the
child process.
Here's how it works:
 Initialization: The program defines an array nums of size SIZE and initializes it with values
{0, 1, 2, 3, 4}. It also declares variables i for the loop and pid for storing the
process ID returned by fork().
 Forking: When fork() is called, a new process (child process) is created as a copy
of the calling process (parent process). The pid variable will contain 0 in the child
process and the child's process ID in the parent process.

 Child Process (pid == 0): In the child process, pid is 0, so it enters the if block.
It then iterates over the nums array, multiplying each element by -i and printing
the result with the prefix "CHILD: ".

Parent Process (pid > 0): In the parent process, pid is greater than 0, so it enters the else if
block. It waits for the child process to finish executing using wait(NULL), ensuring that
it waits for the child to complete before continuing. After the child process completes,
the parent process continues executing from this point. It then iterates over the original
nums array, printing each element with the prefix "PARENT: ".

Output: Since the child and parent processes execute concurrently after the fork() call,
their outputs may be interleaved in the terminal. The child process modifies and prints
the nums array with the "CHILD: " prefix, while the parent process prints the original
nums array with the "PARENT: " prefix after the child has finished.

End: Both processes return 0 at the end, indicating successful completion.

Overall, the program demonstrates how fork() can be used to create a new process and
how the child and parent processes can execute independently but share the same code
and data (in this case, the nums array)

3.18:
On Unix and Unix-like computer operating systems, a zombie is a process that has
completed execution through the exit() system call but still has an entry in the process
table. The child only becomes a zombie if it ends and the parent doesn’t call wait() as
long as the parent continues living.
#include This C program demonstrates the use of the
<stdio.h> fork system call to create a child process.
#include Let's break it down step by step:
<unistd.h>
#include
<stdlib.h>
#include <sys/types.h>
 #include <stdio.h>: Includes the
int main() standard input-output header file,
{ which contains functions like printf.
pid_t pid;
// creating child process and storing  #include <unistd.h>: Includes the header
the returned PID file for POSIX operating system API,
pid = fork(); which contains the fork function
//if pid greater than 0 then the code inside if declaration.
statement is being executed by parent process
if(pid > 0)  #include <stdlib.h>: Includes the
{ standard library header file, which
printf("This is the parent contains the exit function declaration.
process"); sleep(10);
}  #include <sys/types.h>: Includes the
else if (pid == 0) header file for data types used in
// this code is being executed by child process system calls, including pid_t.
{
 int main(): Defines the main function,
printf("This is the child process"); the entry point of the program.
// terminating the child process
exit(0);  pid_t pid; Declares a variable pid of
} type pid_t, which is used to store the
return 0; process ID returned by fork.
}
 pid = fork();: Calls the fork function to
create a new process. After this line,
there are two processes: the parent
process and the child process. The fork
function returns different values in each
process: 0 in the child process and the
process ID of the child in the parent
process.

 if(pid > 0) { ... }: Checks if the pid is


greater than 0, indicating that this code is
running in the parent process. It prints
"This is the parent process" and then
sleeps for 10 seconds using sleep(10);.

 else if (pid == 0) { ... }: Checks if the


pid is equal to 0, indicating that this
code is running in the child process. It
prints "This is the child process" and
then exits the child process using
exit(0);.

 return 0;: Returns 0 to indicate


successful execution of the program.
So, when you run this program, it will create a child process, and both the parent and
child processes will print their respective messages. The parent process will then sleep
for 10 seconds, while the child process will immediately exit.
In the parent’s code block described above the parent sleeps for 10 seconds while the
child finishes its execution using exit() system call. However there is no call for wait()
and the child becomes a zombie.

3.19-
Command line to run the program: ./time <command>

Example output:
. /time ls
Times .c
Time

Elapsed time: 0.25422

We need to write a program in C called time.c that will determine the necessary time
to run a command from the command line. This program should have 2 versions, each
representing a different method of IPC. The first version should use shared memory,
and the second version should use a pipe.

2-
In this problem, we are required to write a program in C called time.c that can determine
the required time to run a command from the command line. We need to write 2 versions
of this program, each representing a different method of IPC. The first version should
use shared memory, and the second version should use a pipe. These programs should
involve the use of fork(),exec(), and gettimeofday() functions.
For the first version of the program, we will use shared memory to share the starting time
between the parent and child processes. The child process will write the starting time to a
shared memory region before it calls exec() After the child process terminates, the parent
process will read the starting time from shared memory. The parent process will then
record the current timestamp for the ending time. The difference between the starting and
ending times represents the elapsed time to execute the command.
The following code illustrates how this can be done:

// We first import the necessary


libraries #include <stdio.h> //
Standard I/O #include <unistd.h> //
POSIX API #include <sys/time.h> //
gettimeofday() #include <sys/wait.h>
#include <sys/shm.h> // Shared memory

int main(int argc, char *argv[]) printf("Elapsed time: %.5f\n", elapsed);


{
// Error handling for the command line
arguments if (argc < 2) {
printf("Usage: ./time
<command>\n"); return 1;
}
// Declaring a pointer to a timeval
struct struct timeval *start_time;

// Creating a shared memory segment


int shmid = shmget(IPC_PRIVATE, sizeof(struct
timeval), IPC_CREAT | 0666);
start_time = (struct timeval *)shmat(shmid, NULL, 0);

// Creating a child process using fork() system


call pid_t pid = fork();
// Error handling for fork() system
call if (pid < 0)
{
perror("Fork
failed"); return 1;
}
// Child process (executes the
command) if (pid == 0) {
// Setting the start time
gettimeofday(start_time,
NULL);
// Executing the command
execvp(argv[1], &argv[1]);
// Error handling for execvp() system call
// These lines will be executed only if execvp() fails
perror("Exec failed");
return 1; }
// Parent process (waits for the child to
finish) else {
// Waiting for the child process to finish
wait(NULL);
// Declaring a pointer to a timeval struct
struct timeval end_time;
// Setting the end time
gettimeofday(&end_time,
NULL);
// Calculating and printing the elapsed time
float elapsed = (end_time.tv_sec - start_time-
>tv_sec) + (end_time.tv_usec - start_time-
>tv_usec) / 1e6;
The program starts by defining the main function, which
takes command line arguments. It forks a new process. After this line, there are two
processes running concurrently - the parent and the
child.
It checks if the program is called with at least one
argument (the command to be executed). If not, it prints the
correct usage and exits with an error code. If fork() fails, it prints an error message and exits with
an error code.

It declares a pointer to a timeval structure (used for


storing time) and sets up a shared memory segment to If fork() returns 0, it means this code is running in the
store the start time child process.

It records the start time using gettimeofday().


}
// Detaching and removing the shared memory segment
// to avoid memory leaks
shmdt(start_time);
shmctl(shmid, IPC_RMID,
NULL); return 0;}

3-
For the second version of the program, we will use a pipe to share the starting time
between the parent and child processes. The child process will write the starting time to
the pipe before it calls exec(). After the child process terminates, the parent process will
read the starting time from the pipe. The parent process will then record the current
timestamp for the ending time. The difference between the starting and ending times
represents the elapsed time to execute the command.
// We first import the necessary  #include <stdio.h>: This line
libraries #include <stdio.h> // includes the standard input/output
Standard I/O #include <unistd.h> // library, which provides functions
POSIX API like printf and perror for input/output
#include <sys/time.h> // operations.
gettimeofday() #include <sys/wait.h>
// wait()  #include <unistd.h>: This line includes
int main(int argc, char *argv[]) the POSIX API library, which provides
{ access to various POSIX (Portable
// Error handling for the command line Operating System Interface) functions,
arguments if (argc < 2) { including fork, pipe, and exec.
printf("Usage: ./time
<command>\n"); return 1; }  #include <sys/time.h>: This line
// Creating a pipe to communicate between the includes the gettimeofday function,
// parent and child processes, and error which is used to get the current time
handling int fd[2]; with microsecond precision.
if (pipe(fd) == -1)
{  #include <sys/wait.h>: This line
perror("Pipe includes the wait function, which is
failed"); return 1; used to wait for a child process to
} finish.
// Creating a child process using fork() system
call pid_t pid = fork();  int main(int argc, char *argv[]): This
// Error handling for fork() system is the main function definition, which
call if (pid < 0) takes command-line arguments argc
{ (argument count) and argv (argument
perror("Fork vector).
failed"); return 1;
}  if (argc < 2) { printf("Usage: ./time
// Child process (executes the <command>\n"); return 1; }: This line
command) if (pid == 0) checks if the user has provided the
{ command to be executed. If not, it
// Closing the read end of the prints a usage message and returns an
pipe close(fd[0]); error code.
// Getting the start time of the command  int fd[2]; if (pipe(fd) == -1) {
struct timeval start_time; perror("Pipe failed"); return 1; }: This
gettimeofday(&start_time, NULL); line creates a pipe using the pipe
// Writing the start time to the pipe system call, which is used for
write(fd[1], &start_time, sizeof(struct timeval)); interprocess communication. It stores
// Closing the write end of the the file descriptors for the read and
pipe close(fd[1]); write ends of the pipe in the array fd.
// Executing the command
execvp(argv[1], &argv[1]);  pid_t pid = fork();: This line creates a
// Error handling for execvp() system call child process using the fork system
// These lines will be executed only if execvp() fails call. The pid variable stores the process
perror("Exec failed"); ID (PID) of the child process.
return 1; }
// Parent process (waits for the child to  if (pid < 0) { perror("Fork failed");
finish) else { return 1;
// Closing the write end of the }: This line checks if fork failed to
pipe close(fd[1]); create a child process. If so, it prints an
// Waiting for the child process to finish error message and returns an error code.
wait(NULL);
// Reading the start time from the pipe  if (pid == 0) { ... } else { ... }: This
struct timeval start_time, end_time; line uses the pid variable to determine
read(fd[0], &start_time, sizeof(struct timeval)); whether the current process is the
// Getting the end time of the command parent or the child.
gettimeofday(&end_time, NULL);
// Calculating and printing the elapsed time  close(fd[0]);: This line closes the read
float elapsed = (end_time.tv_sec - start_time.tv_sec) + end of the pipe in the child process to
(end_time.tv_usec - start_time.tv_usec) / 1e6; prevent it from reading.
printf("Elapsed time: %.5f seconds\n", elapsed); }
// Closing the read end of the pipe  gettimeofday(&start_time, NULL);:
close(fd[0]); This line gets the current time and
return 0; } stores it in the start_time variable,
which will be used to calculate the
elapsed time.

 write(fd[1], &start_time, sizeof(struct


timeval));: This line writes the
start_time to the write end of the pipe,
which the parent process will read later.

 close(fd[1]);: This line closes the write


end of the pipe in the child process.

 execvp(argv[1], &argv[1]);: This line


replaces the child process with the
command provided by the user. It uses
the execvp system call, which searches
for the command in the PATH
environment variable.

 perror("Exec failed"); return 1;: These


lines are executed only if execvp fails.
They print
an error message and return an error
code.

 close(fd[1]);: This line closes the write


end of the pipe in the parent process to
prevent it from writing.

 wait(NULL);: This line waits for the


child process to finish.

 read(fd[0], &start_time, sizeof(struct


timeval));: This line reads the
start_time from the read end of the
pipe, which was written by the child
process.

 gettimeofday(&end_time, NULL);:
This line gets the current time, which
will be used to calculate the elapsed
time.

 float elapsed = (end_time.tv_sec -


start_time.tv_sec) + (end_time.tv_usec
- start_time.tv_usec) / 1e6;: This line
calculates the elapsed time in seconds
by subtracting the start time from the
end time and converting the result to
seconds.

 printf("Elapsed time: %.5f seconds\n",


elapsed);: This line prints the elapsed
time with five decimal places.

 close(fd[0]);: This line closes the read


end of the pipe in the parent process.

 return 0;: This line indicates that the program


has executed successfully and returns a
success code

3.20-
#include<stdio.h> 1- This section includes the necessary header files
#include<stdlib.h> (stdio.h for standard input/output functions and stdlib.h
#define MIN_PID for memory allocation) and defines constants MIN_PID
300 and MAX_PID to specify the range of process IDs that
#define MAX_PID 5000 can be allocated.

/* We will use a bitmap in which a value of 0 at position i


indicates that 2- This defines a structure PID_tab to hold a process ID and
a process id of value i is available and a value of 1 a boolean value (boolean) indicating whether the PID is in
indicates that the use. pId
process id is currently in use*/ is a pointer to this structure and will be used as a
using namespace std; bitmap to manage PID allocation.
//Creating and initializes a data structure
//for representing pids
// returns—1 if unsuccessful, 1 if successful 3- allocate_map() initializes the PID bitmap. It allocates
struct PID_tab{ memory for the bitmap based on the range of PIDs, sets
int PID; the first PID in use (MIN_PID), and sets all other PIDs as
bool boolean; available.
}*pId;
//allocating bitmap values to the data
structure int allocate_map(){ 4- allocate_pid() searches for an available PID in the
int i; bitmap and marks it as in use (boolean = 0). It returns the
//void* calloc (size_t num, size_t size); allocated PID or -1 if no PID is available.
pId=(struct PID_tab
*)calloc((MAX_PID-
MIN_PID+1),sizeof(struct
PID_tab)); if(pId==NULL){
5- release_pid(int pid) releases the specified PID,
return -1; }
marking it as available (boolean = 1) in the bitmap.
pId[0].PID=MIN_PID;
pId[0].boolean=1;
for( i=1;i<MAX_PID-
MIN_PID+1;i++){
pId[i].PID=pId[i-1].PID+1; 6- The main() function demonstrates the use of the PID
pId[i].boolean=1; manager. It initializes the PID bitmap (allocate_map()) and
then repeatedly allocates PIDs using allocate_pid() and
} return 1; } prints them out.
////allocating pid to the new
process int allocate_pid(){
int i ;
for( i=0;i<MAX_PID-MIN_PID+1;i++){
if(pId[i].boolean==
1){
pId[i].boolean=
0;
return pId[i].PID; }
} if(i==MAX_PID-MIN_PID+1)
return -1; }
//releasing pid
void release_pid(int pid){
pId[pid-
MIN_PID].boolean=1; } int
main(){
int pid;
allocate_map();
for(int i=1; i<=6;
i++){
/* This statement would be executed
* repeatedly until the condition
* i<=6 returns false. All the pids printed will have
different values
*/
if((pid=allocate_pid())!=-1);
printf("Pid= %d \n",pid);
}
return 0;}
3.21
#include <stdio.h>
#include <unistd.h>
#include
<sys/types.h>
#include
<sys/types.h>
#include
<sys/wait.h>
int main() { //we declare a var of type int to hold the positive int and initalize the
value to 0 int i=0;
int status; //declaring the parent
id pid_t pid;
//the exercise asks to perform necessary error checking to ensure that a positive
//integer is passed on the command line so we will implement a so while loop
//the statement inside the do segment will be repeated until the number entered is
positive do{ //the C library function int printf(const char *format, ...) sends formatted
output to stdout printf("Enter a positive integer: ");
//the C library function int scanf(const char *format, ...) reads formatted input from
stdin scanf("%d", &i);
}while (i<=0); //forking the oarent function
pid = fork(); //fork returns a positive value to the
parent if (pid>0){ //we don't want a zombie
child
wait(&status); } //so we call the wait() function at the parent
//fork returns 0 to the newly created child
// we want to generate the sequence in the child function
else if (pid== 0) {
//when the algorithm is continually applied all positive integers will eventually reach 1
//so we want to print all values until they reach 1
// we will use a while loop
// we will loop until the value inputed will
reach 1 while (i!=1){
//if inputed number is even the division with 2 will return a
remainder of 0
//so if remainder is 0 number is even, if it's 1 number is odd

if (i%2 == 0){ // if number is even divide it by 2


i = i/2; }
else if (i%2 == // if number is odd then caculate
1){ (3*number)+1
i = 3 * (i) + }
1;
//finally print the number
printf("%d\n",i); } //when 1 is reached break the loop
}

return 0; }

Chap
ter
no 4
Programming Exercise

Question # 4.1
Provide three programming examples in which multithreading provides
better performance than a single-threaded solution.

Answer:
Certainly! Here are three scenarios where multithreading can provide better performance than a
single- threaded solution:

1. Parallelizing CPU-bound tasks:

 Imagine you have a task that involves heavy computation, such as image processing,
video encoding, or mathematical calculations. By dividing the workload among multiple
threads, each running on a separate CPU core, you can potentially achieve significant
speedup. Each thread can work on a portion of the data concurrently, thus reducing the
overall execution time.

import threading

def process_data(data):
# Perform heavy computation on data

pass

def main():

data = [...] # Your data to process

num_threads = 4

chunk_size = len(data) // num_threads

threads = []

for i in range(num_threads):

start = i * chunk_size
end = (i + 1) * chunk_size if i < num_threads - 1 else len(data)

thread = threading.Thread(target=process_data, args=(data[start:end],))

threads.append(thread)

thread.start()

for thread in threads:

thread.join()

if name == " main ":

main()

I/O-bound tasks:
 When dealing with tasks that spend a significant amount of time waiting for I/O operations (such
as disk I/O, network I/O, or user input), multithreading can be beneficial. While one thread is
waiting for an I/O operation to complete, other threads can continue executing, thus maximizing
CPU utilization and overall throughput.

import

threading

import requests

def fetch_url(url):

response = requests.get(url)

# Process response data

def main():

urls = [...] # List of URLs to fetch

threads = []

for url in urls:


thread = threading.Thread(target=fetch_url, args=(url,))

threads.append(thread)
thread.start()

for thread in threads:

thread.join()

if name == " main ":

main()

GUI applications:
 In graphical user interface (GUI) applications, responsiveness is key. Performing time-consuming
tasks on the main (UI) thread can lead to unresponsive or "frozen" interfaces. By offloading these
tasks to separate threads, you can keep the UI responsive while the background threads handle the
heavy lifting.

import threading

from tkinter import Tk, Button

def time_consuming_task():

# Simulate a time-consuming task

import time

time.sleep(5)

def start_task():

thread = threading.Thread(target=time_consuming_task)

thread.start()

def main():

root = Tk()

root.geometry("200x100")
button = Button(root, text="Start Task", command=start_task)
button.pack()

root.mainloop()

if name == " main ":

main()

In each of these examples, multithreading allows you to leverage parallelism to improve performance by
utilizing multiple CPU cores or by keeping the application responsive while handling time-consuming
tasks.

Question# 4.6

Provide two programming examples in which multithreading does not provide better performance
than a single-threaded solution.

Answer:

Certainly! Multithreading doesn't always lead to better performance. Here are two scenarios where
multithreading may not provide significant performance improvements compared to a single-threaded
solution:

I/O-bound Task with Limited Resources:

import

requests

import time

def fetch_url(url):

response = requests.get(url)

print(f"Fetched {url}")

def main():

urls = ["https://example.com"] * 10

start_time = time.time()
for url in urls:

fetch_url(url)

end_time = time.time()

print(f"Time taken: {end_time - start_time} seconds")

if name == " main ":

main()

In this example, the program fetches multiple URLs sequentially. However, the fetching process involves
waiting for network I/O, during which the CPU remains idle. Introducing multithreading here won't
provide significant performance improvement because the bottleneck is the network I/O, and threads
will spend most of their time waiting for responses rather than utilizing CPU resources effectively.

CPU-bound Task with Global Interpreter Lock (GIL) (in Python): import

time

import threading

def

compute():

result = 0

for _ in range(10**7):

result += 1

print("Computed result")

def main():

start_time = time.time()

threads = []
for _ in range(4):

thread = threading.Thread(target=compute)

thread.start()
threads.append(thread

) for thread in threads:

thread.join()

end_time = time.time()

print(f"Time taken: {end_time - start_time} seconds")

if name == " main ":

main()In Python, the Global Interpreter Lock (GIL) prevents multiple native threads from executing
Python bytecodes simultaneously in the same process. Therefore, for CPU-bound tasks, multithreading
may not provide significant performance improvements due to contention for the GIL. In this example,
although multiple threads are created to perform CPU-bound computations, they cannot effectively
utilize multiple CPU cores simultaneously due to the GIL.

In these scenarios, alternative approaches such as asynchronous I/O (for I/O-bound tasks) or
multiprocessing (for CPU-bound tasks) may be more effective in improving performance compared to
multithreading.

Question # 4.17:

Consider the following code segment:

pid t pid;

pid = fork();

if (pid == 0) { /* child process */

fork();

thread create( . . .);

fork();

a. How many unique processes are created?

b. How many unique threads are created?

Answer:
Let's analyze the code segment to determine the number of unique processes and threads

created: pid_t pid;


pid = fork();

if (pid == 0) { /* child process */

fork();

thread_create(...);

fork();

a. Number of Unique Processes:

The initial process calls fork() once, creating one child process. This child process then calls fork() again,
creating another child process. So far, we have three processes: the initial parent process, the first child
process, and the second child process.

In the first child process, thread_create(...) is called. This creates a new thread within the child process,
not a new process. Therefore, the number of processes remains three.

Finally, after the if block, the parent process (the initial process) calls fork() again, creating another child
process. So, we add one more process.

Therefore, the total number of unique processes created is 4.

b. Number of Unique Threads:

Threads are created only within the child process where thread_create(...) is called. So, only one thread is
created in the child process that executes the thread_create(...) function.

Therefore, the total number of unique threads created is 1.

Question# 4.20

Consider a multicore system and a multithreaded program written

using the many-to-many threading model. Let the number of user-

level threads in the program be greater than the number of

processing cores in the system. Discuss the performance implications

of the following scenarios.

a. The number of kernel threads allocated to the program is less

than the number of processing cores.

b. The number of kernel threads allocated to the program is equal to


the number of processing cores.

c. The number of kernel threads allocated to the program is

greater than the number of processing cores but less than the

number of user-level threads.

Answer:

Let's analyze the provided code snippet and discuss its behavior and potential performance implications in
the context of the scenarios you mentioned:

#include <pthread.h>

#include <stdio.h>

int value = 0;

void *runner(void *param); /* the thread */

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

pid_t pid; pthread_t

tid; pthread_attr_t

attr;

pid = fork();

if (pid == 0) { /* child process */

pthread_attr_init(&attr); pthread_create(&tid,

&attr, runner, NULL); pthread_join(tid, NULL);


printf("CHILD: value = %d", value); /* LINE C */

} else if (pid > 0) { /* parent process */ wait(NULL);

printf("PARENT: value = %d", value); /* LINE P */

void *runner(void *param) {

value = 5; pthread_exit(0);

Now, let's discuss the performance implications of each scenario:

a. Number of kernel threads allocated to the program is less than the number of processing cores:

In this scenario, if there are fewer kernel threads than processing cores, the system may not be able to
fully utilize all available CPU cores.

When the child process is created using fork(), it creates a separate memory space, including a copy of the
value variable. However, the pthread_create() call in the child process creates a new thread that updates
the value variable, but this change won't be reflected in the parent process's memory space.

Therefore, when the child process completes and the parent process continues execution, the value
variable in the parent process will remain unchanged (still 0).

The performance implication here is that the parent process may not see the updated value set by the child
thread, leading to potential inconsistencies or incorrect behavior in the program.

b. Number of kernel threads allocated to the program is equal to the number of processing cores:

In this scenario, if there is one kernel thread per processing core, the system can potentially fully utilize all
available CPU cores.

The behavior of the program remains the same as in scenario (a), with the potential issue of the parent
process not seeing the updated value set by the child thread.
c. Number of kernel threads allocated to the program is greater than the number of
processing cores but less than the number of user-level threads:

In this scenario, if there are more kernel threads than processing cores, the system may
experience increased context switching overhead.

The behavior of the program remains the same as in scenarios (a) and (b), with the potential issue of the
parent process not seeing the updated value set by the child thread.

Overall, regardless of the number of kernel threads allocated to the program, the parent process may not
see the updated value set by the child thread due to separate memory spaces created by the fork()
system call, which can lead to inconsistencies or incorrect behavior in the program. To address this issue,
inter-process communication mechanisms such as pipes or shared memory could be used to synchronize
data between the parent and child processes.
Question#4.21
Pthreads provides an API for managing thread cancellation. The pthread setcancelstate() function is used
to set the cancellation state. Its prototype appears as follows: pthread setcancelstate(int state, int
*oldstate) The two possible values for the state are PTHREAD CANCEL ENABLE and PTHREAD CANCEL
DISABLE. Using the code segment shown in Figure 4.24, provide examples of two operations that would be
suitable to perform between the calls to disable and enable thread cancellation.int oldstate; pthread
setcancelstate(PTHREAD CANCEL DISABLE, &oldstate); /* What operations would be performed here? */
pthread setcancelstate(PTHREAD CANCEL ENABLE, &oldstate);

Answer:

Between the calls to disable and enable thread cancellation using pthread_setcancelstate(), it's common to
perform operations that are considered critical sections or regions where thread cancellation should be
temporarily disabled to ensure that these operations complete without interruption. Here are two
examples of such operations:

Updating Shared Data Structures:

If multiple threads are accessing and modifying shared data structures concurrently, it's essential to
disable thread cancellation during critical sections where the integrity of the data needs to be
maintained.

For example, between the calls to disable and enable thread cancellation, operations such as updating a
linked list, modifying a shared queue, or manipulating a shared data structure can be performed safely
without the risk of cancellation interrupting the operation midway and leaving the data structure in an
inconsistent state.
By disabling thread cancellation, we ensure that the critical operation completes without interruption,
preserving the integrity of the shared data structure.

pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);

// Critical section - updating shared data structure

// Perform operations to update shared data structures here

pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate); File I/O

Operations:

File I/O operations, especially when dealing with critical files or resources, may require thread
cancellation to be temporarily disabled to prevent potential data corruption or loss if a thread is canceled
while performing the operation.

For example, between the calls to disable and enable thread cancellation, operations such as opening,
writing to, or closing a critical file can be performed safely without the risk of cancellation interrupting the
operation midway and leaving the file in an inconsistent state.

By disabling thread cancellation, we ensure that the file I/O operation completes without interruption,
maintaininpthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);

// Critical section - file I/O operation

// Perform file I/O operations here

pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate); g the

integrity of the file or resource being accessed.

In both examples, by disabling thread cancellation during critical sections, we ensure that the operations
are completed atomically and without interruption, thereby maintaining data integrity and preventing
potential issues that could arise from cancellation occurring at an inopportune momen

Question#4.22
Write a multithreaded program that calculates various statistical values for a

list of numbers. This program will be passed a series of numbers on the

command line and will then create three separate worker threads. One thread

will determine the average of the numbers, the second will determine the

maximum value, and the third will determine the mini mum value. For

example, suppose your program is passed the integers 90 81 78 95 79 72 85

The program will report The

average value is 82 The

minimum value is 72 The

maximum value is 95

The variables representing the average, minimum, and maximum values will

be stored globally. The worker threads will set these values, and the parent

thread will output the values once the workers have exited.

(We could obviously expand this program by creating additional threads that

determine other statistical values, such as median and standard deviation.)

Answer:

Here's a multithreaded program in C that calculates the average, minimum, and maximum values of a list
of numbers passed as command-line arguments:

#include <stdio.h>

#include <stdlib.h>

#include <pthread.h>

#define NUM_THREADS 3
int numbers[] = {90, 81, 78, 95, 79, 72, 85};

int num_count = sizeof(numbers) / sizeof(numbers[0]); double

average;

int minimum;

int maximum;

void *calculate_average(void *arg) {

double sum = 0;

for (int i = 0; i < num_count; i++) { sum

+= numbers[i];

average = sum / num_count;

pthread_exit(NULL);

void *calculate_minimum(void *arg) {

minimum = numbers[0];

for (int i = 1; i < num_count; i++) { if

(numbers[i] < minimum) {

minimum = numbers[i];

pthread_exit(NULL);

}
void *calculate_maximum(void *arg) { maximum =

numbers[0];

for (int i = 1; i < num_count; i++) { if

(numbers[i] > maximum) { maximum =

numbers[i];

pthread_exit(NULL);

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

threads[NUM_THREADS];

// Create threads

pthread_create(&threads[0], NULL, calculate_average, NULL);

pthread_create(&threads[1], NULL, calculate_minimum, NULL);

pthread_create(&threads[2], NULL, calculate_maximum, NULL);

// Wait for threads to finish

for (int i = 0; i < NUM_THREADS; i++) { pthread_join(threads[i],

NULL);

// Output results
printf("The average value is %.2f\n", average); printf("The

minimum value is %d\n", minimum); printf("The maximum

value is %d\n", maximum);

return 0;

This program creates three worker threads, each responsible for calculating one of the statistical values:
average, minimum, and maximum. The main thread waits for all worker threads to finish their calculations
using pthread_join(), and then outputs the results. The variables representing the average, minimum, and
maximum values are stored globally so that the worker threads can set these values, and the main thread
can access them to output the results.
Question#4.23
Write a multithreaded program that outputs prime numbers. This pro gram

should work as follows: The user will run the program and will enter a number

on the command line. The program will then create a separate thread that

outputs all the prime numbers less than or equal to the number entered by

the user.

Answer:

Here's a multithreaded program in C that outputs prime numbers up to a given number entered by the
user:

#include <stdio.h>

#include <stdlib.h>

#include <pthread.h>

// Function to check if a number is prime int

is_prime(int n) {
if (n <= 1) return 0; // 0 and 1 are not prime for

(int i = 2; i * i <= n; i++) {

if (n % i == 0) return 0; // Not prime if divisible by any number up to its square root

return 1; // Prime otherwise

// Function executed by the thread to output prime numbers void

*output_primes(void *arg) {

int n = *((int *) arg);

printf("Prime numbers up to %d:\n", n); for

(int i = 2; i <= n; i++) {

if (is_prime(i)) {

printf("%d ", i);

printf("\n");

pthread_exit(NULL);

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

(argc != 2) {

printf("Usage: %s <number>\n", argv[0]); return

1;

}
int n = atoi(argv[1]); if

(n <= 0) {

printf("Please enter a positive integer.\n");

return 1;

pthread_t tid;

pthread_create(&tid, NULL, output_primes, &n); pthread_join(tid,

NULL);

return 0;

This program takes a number as a command-line argument and creates a separate thread to
output all prime numbers up to that number. The is_prime() function checks if a given number is
prime, and the output_primes() function is executed by the thread to output prime numbers. The
main thread waits for the thread to finish using pthread_join() before exiting.

Question#4.27

The Fibonacci sequence is the series of numbers 0, 1, 1, 2, 3, 5, 8, For

mally, it can be expressed as:

fib0 = 0

fib1 = 1

fibn = fibn−1 + fibn−2

Write a multithreaded program that generates the Fibonacci sequence. This

program should work as follows: On the command line, the user will enter

the number of Fibonacci numbers that the program is to gen


erate. The program will then create a separate thread that will generate the

Fibonacci numbers, placing the sequence in data that can be shared by the

threads (an array is probably the most convenient data struc ture). When the

thread finishes execution, the parent thread will output the sequence

generated by the child thread. Because the parent thread cannot begin

outputting the Fibonacci sequence until the child thread

finishes, the parent thread will have to wait for the child thread to finish. Use the

techniques described in Section 4.4 to meet this requirement.

Answer:

Here's a multithreaded program in C that generates the Fibonacci sequence up to a specified number of
terms entered by the user:

#include <stdio.h>

#include <stdlib.h>

#include <pthread.h>

// Structure to pass arguments to the thread typedef

struct {

int *fib_sequence;

int num_terms;

} ThreadArgs;

// Function executed by the thread to generate Fibonacci sequence void

*generate_fibonacci(void *arg) {

ThreadArgs *args = (ThreadArgs *) arg; int

num_terms = args->num_terms;
int *fib_sequence = args->fib_sequence;

fib_sequence[0] = 0;

fib_sequence[1] = 1;

for (int i = 2; i < num_terms; i++) {

fib_sequence[i] = fib_sequence[i - 1] + fib_sequence[i - 2];

pthread_exit(NULL);

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

(argc != 2) {

printf("Usage: %s <number of Fibonacci terms>\n", argv[0]); return 1;

int num_terms = atoi(argv[1]); if

(num_terms <= 0) {

printf("Please enter a positive integer.\n");

return 1;

int *fib_sequence = (int *) malloc(num_terms * sizeof(int));


if (fib_sequence == NULL) { printf("Memory

allocation failed.\n"); return 1;

pthread_t tid;

ThreadArgs args = {fib_sequence, num_terms};

pthread_create(&tid, NULL, generate_fibonacci, &args);

pthread_join(tid, NULL);

printf("Fibonacci sequence up to %d terms:\n", num_terms); for (int

i = 0; i < num_terms; i++) {

printf("%d ", fib_sequence[i]);

printf("\n");

free(fib_sequence);

return 0;

This program takes the number of Fibonacci terms as a command-line argument and creates a separate
thread to generate the Fibonacci sequence up to that number of terms. The generate_fibonacci()
function is executed by the thread to generate the sequence and store it in an array. The main thread
waits for the thread to finish using pthread_join() before outputting the generated Fibonacci sequence.

Question#4.28
Modify programming problem Exercise 3.20 from Chapter 3, which asks you to design a pid manager.
This modification will consist of writing a multithreaded program that tests your solution to Exercise
3.20. You will create a number of threads— for example, 100—and each thread will request a pid, sleep
for a random period of time, and then release the pid. (Sleeping for a random period of time
approximates the typical pid usage in which a pid is assigned to a new process, the process executes
and then terminates, and the pid is released on the process’s termination.) On UNIX and Linux systems,
sleeping is accomplished through the sleep() function, which is passed an integer value representing the
number of seconds to sleep. This problem will be modified in Chapter 7.

Answer

To modify Exercise 3.20 to a multithreaded program, we need to implement a pid manager that supports
concurrent access from multiple threads. Each thread will request a pid, sleep for a random period of time,
and then release the pid. We'll use a mutex to ensure thread safety when accessing the pid manager.

Here's the modified program: c

#include <stdio.h>

#include <stdlib.h>

#include <pthread.h>

#include <unistd.h>

#include <time.h>

#define MIN_PID 300

#define MAX_PID 5000

#define NUM_THREADS 100

int *pid_map;

pthread_mutex_t mutex;
int allocate_map() {

int size = MAX_PID - MIN_PID + 1; pid_map =

(int *)malloc(size * sizeof(int)); if (pid_map ==

NULL) return -1;

for (int i = 0; i < size; i++) {

pid_map[i] = 0; // 0 means the pid is available

pthread_mutex_init(&mutex, NULL);

return 0;

int allocate_pid() {

pthread_mutex_lock(&mutex);

for (int i = 0; i < MAX_PID - MIN_PID + 1; i++) { if

(pid_map[i] == 0) {

pid_map[i] = 1; // 1 means the pid is allocated

pthread_mutex_unlock(&mutex);

return i + MIN_PID;

pthread_mutex_unlock(&mutex);

return -1; // No available pid

void release_pid(int pid) {


pthread_mutex_lock(&mutex);

pid_map[pid - MIN_PID] = 0; // Release the pid

pthread_mutex_unlock(&mutex);

void *thread_function(void *arg) { int

pid;

pid = allocate_pid(); if

(pid != -1) {

printf("Thread %ld got pid %d\n", pthread_self(), pid);

srand(time(NULL));

int sleep_time = rand() % 5 + 1; // Sleep for a random period of time (1 to 5 seconds)

sleep(sleep_time);

release_pid(pid);

printf("Thread %ld released pid %d\n", pthread_self(), pid);

} else {

printf("Thread %ld failed to get pid\n", pthread_self());

pthread_exit(NULL);

int main() {

if (allocate_map() == -1) { printf("Failed to

allocate pid map\n"); return 1;


}

pthread_t threads[NUM_THREADS];

for (int i = 0; i < NUM_THREADS; i++) { pthread_create(&threads[i], NULL,

thread_function, NULL);

for (int i = 0; i < NUM_THREADS; i++) { pthread_join(threads[i],

NULL);

pthread_mutex_destroy(&mutex);

free(pid_map);

return 0;

In this modified program:

allocate_map() initializes the pid map array and the mutex.

allocate_pid() searches for an available pid in the pid map array and marks it as allocated. release_pid()

releases a pid by marking it as available in the pid map array.

The thread_function() is the function executed by each thread. It allocates a pid, sleeps for a random
period of time, and then releases the pid.

In main(), multiple threads are created, and each thread executes the thread_function().

After all threads finish execution, the pid map array and mutex are destroyed, and memory is
deallocated.
This program ensures that multiple threads can concurrently request and release pids safely using mutex locking and
unlocking.
Question no 5.15

#include <stdatomic.h>

// Define the structure for the mutex lock typedef

struct {

int available;

} Lock;

// Function to initialize the mutex lock void

initLock(Lock *lock) {

lock->available = 1; // 1 indicates lock is available

// Function to acquire the mutex lock void

acquireLock(Lock *lock) {

while (1) {

// Attempt to acquire the lock atomically

if (atomic_compare_exchange_weak(&lock->available, &lock->available, 0)) {

// Lock acquired return;

// If failed to acquire, retry

}
// Function to release the mutex lock void

releaseLock (Lock *lock) {

// Release the lock by setting it back to available

atomic_store(&lock->available, 1);

Explanation: Explanation:

 The Lock structure contains a single integer field available, which indicates whether
the lock is available or not. Here, 1 represents that the lock is available.
 initLock () function initializes the lock to be available.
 acquireLock() function attempts to acquire the lock using an atomic operation. It
continuously retries until the lock is successfully acquired. The atomic operation used
here is atomic_compare_exchange_weak(), which atomically compares the value of
lock->available with 0 (indicating lock is available) and swaps it with 0 if it matches. If
the exchange fails (i.e., the lock is not available), it retries.
 releaseLock() function releases the lock by setting lock->available back to 1,
indicating that the lock is available again.
 The Lock structure contains a single integer field available, which indicates whether

the lock is available or not. Here, 1 represents that the lock is available.
 initLock () function initializes the lock to be available.
 acquireLock() function attempts to acquire the lock using an atomic operation. It
continuously retries until the lock is successfully acquired. The atomic operation used
here is atomic_compare_exchange_weak () , which atomically compares the value of
(indicating lock is available) and swaps it with If the
lock->available with 0 0 if it matches.
exchange fails (i.e., the lock is not available), it retries.

 releaseLock() function releases the lock by setting lock->available back to 1,


indicating that the lock is available again.

Question no: 5.16


#include <pthread.h>

// Define the structure for the mutex lock typedef struct {

int available;

pthread_cond_t cond; // Condition variable for waiting queue pthread_mutex_t mutex; //


Mutex for protecting the condition variable
} Lock;
// Function to initialize the mutex lock void
initLock(Lock *lock) {
lock->available = 1; // 1 indicates lock is available pthread_cond_init(&lock->cond, NULL); //
Initialize condition variable pthread_mutex_init(&lock->mutex, NULL); // Initialize mutex

// Function to acquire the mutex lock void


acquireLock(Lock *lock) {
pthread_mutex_lock(&lock->mutex); // Lock the mutex

// While the lock is not available, wait on the condition variable while (lock->available
== 0) {

pthread_cond_wait(&lock->cond, &lock->mutex);

// Set the lock to unavailable lock-


>available = 0;

pthread_mutex_unlock(&lock->mutex); // Unlock the mutex

// Function to release the mutex lock void


releaseLock(Lock *lock) {

pthread_mutex_lock(&lock->mutex); // Lock the mutex

// Set the lock to available lock-


>available = 1;
// Signal the condition variable to wake up a waiting thread
pthread_cond_signal(&lock->cond);

pthread_mutex_unlock(&lock->mutex); // Unlock the mutex

}
Explanation:

 We have introduced a waiting queue mechanism using a condition variable (cond)


and a mutex ( mutex ) to protect access to the condition variable. itialize the
 In initLock() , we in condition variable and the mutex.
 In acquireLock() , a thread locks the mutex, checks if the lock is available. If not, it

waits on the condition variable using pthread_cond_wait(). This blocks the thread and atomically releases
the mutex, allowing other threads to proceed until the condition variable is signaled.
 When the lock becomes available ( lock->available == 1 ), the thread proceeds to
acquire the lock and sets lock->available to 0 .
to 1, and
 In releaseLock(), the thread locks the mutex, sets lock->available

signals the condition variable to wake up a waiting thread.


 Using this implementation, threads waiting to acquire the lock will be blocked and
placed into a waiting queue (cond), avoiding busy waiting and ensuring efficient
resource utilization.
Question 5.2

 Initialization : Initialize a mutex lock


 Increment Operation: When a thread wants to increment the hits variable, it
first acquires the lock using the mutex. This prevents other threads from
accessing or modifying hits concurrently.
 Update Operation : The thread increments the hits variable.
 Release Lock : After updating hits, the thread releases the lock, allowing other
threads to acquire it.
# Initialize a mutex lock mutex_lock =
Mutex()

# Function to increment hits def


increment_hits():
# Acquire the lock
mutex_lock.acquire()
# Increment hits hits
+= 1

# Release the lock


mutex_lock.release()

Question no 5.34
Monitor ReaderWriter int
readers_waiting = 0 int
writers_waiting = 0 int
readers_active = 0

bool writer_active = false

procedure StartRead()

await(!writer_active) // Wait until no writer is active readers active++

procedure End Read() readers


active--

procedure StartWrite() writers_waiting++


await(!writer_active && readers_active == 0) // Wait until no readers or writers are active

writers_waiting-- writer_active
= true

procedure EndWrite()
writer_active = false

procedure Reader() StartRead()


// Read data EndRead()

procedure Writer () StartWrite ()


// Write data EndWrite ()

Explanation:
 are procedures for readers to start and end reading,
StartRead() and
respectively. EndRead()
are procedures for writers to start and end writing,
 EndWrite()
StartWrite() and
respectively.
in StartRead() ensures that readers wait until no writer is active
await(!writer_active)

before starting to read. 


await(!writer_active && readers_active == 0) in ensures that writers
StartWrite()
wait until no readers or writers are active before starting to write.

 readers_active counts the number of active readers.


 writer_active is a Boolean flag indicating whether a writer is active.
 readers waiting and writers_waiting are counters for the number of readers and writers
waiting, respectively.

Question:5.35 Monitor
Alarm Clock

int current ticks = 0

Condition variable tick condition

// Function invoked by the hardware clock at regular intervals procedure tick()


current ticks++

signal(tick condition) // Signal waiting threads

// Function to delay a calling program for a specified number of ticks procedure


delay(int ticks_to_wait)
int target ticks = current ticks + ticks_to_wait
while current ticks < target ticks

wait(tick condition) // Wait for next tick

Explanation:

 The monitor Alarm Clock maintains an internal variable current ticks to keep track of
the number of ticks passed.
tick condition
 A condition variable is used to synchronize threads waiting for ticks.
tick() current
 The hardware clock invokes the function at regular intervals, incrementing
ticks
and signaling waiting threads.
delay()
 The function allows a calling program to delay itself for a specified number of ticks.
It calculates the target number of ticks based on the current time and the desired delay. If
tick condition
the current number of ticks is less than the target, the thread waits on the
variable until the next tick occurs.

You might also like