0% found this document useful (0 votes)
144 views

Operating System Lab Report

This document is a lab report for an operating systems experiment on inter-process communication. It discusses two methods of IPC: shared memory and pipes. For shared memory, the student implemented a program with a parent and child process that communicate via a shared memory object, with the child writing a message to the shared memory and the parent reading it. For pipes, the student modified an example program so that the parent process reads output from commands executed by the child process in the pipe.

Uploaded by

ajnobi huese
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
144 views

Operating System Lab Report

This document is a lab report for an operating systems experiment on inter-process communication. It discusses two methods of IPC: shared memory and pipes. For shared memory, the student implemented a program with a parent and child process that communicate via a shared memory object, with the child writing a message to the shared memory and the parent reading it. For pipes, the student modified an example program so that the parent process reads output from commands executed by the child process in the pipe.

Uploaded by

ajnobi huese
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 28

Lab 1II Report

Report Subject: OS Experiment - Lab 3

Student ID: 20138380130

Student Name: Khan Md Shahedul Islam


Data:2021/11/6

Computer Operating System Experiment


Laboratory 3

Inter-Process Communication

Objective:

In computer science, Inter-process communication (IPC) is a

mechanism which allows processes to communicate with each other and

synchronize their actions. IPC provides a mechanism to allow the

processes to manage shared data. The objective of this lab is to help you

internalize a couple of important facts about IPC in Linux.

Including:
 Shared memory

 Pipes

Equipment:

VirtualBox with Ubuntu Linux


Methodology:

Program and answer all the questions in this lab sheet.

1 Inter-Process communication

A process is a program in execution, and each process has its own

address space, which comprises the memory locations that the process is

allowed to access. An independent process is not affected by the

execution of other processes while a co-operating process can be affected

by other executing processes. Though one can think that those processes,

which are running independently, will execute very efficiently, in reality,

there are many situations when co-operative nature can be utilized for

increasing computational speed, convenience and modularity. Inter

process communication (IPC) is a mechanism which allows processes to

communicate with each other and synchronize their actions.

Cooperating processes require an inter-process communication (IPC)

mechanism that will allow them to exchange data and information. There

are two fundamental models of inter-process communication: shared

memory and message passing. In the shared-memory model, a region of

memory that is shared by cooperating processes is established. Processes

can then exchange information by reading and writing data to the shared
region. In the message-passing model, communication takes place by

means of messages exchanged between the cooperating processes such as

pipes, message queue, and sockets.

2 Experiments

2.1 Experiment 1: shared memory

communication

Inter-process communication using shared memory requires

communicating processes to establish a region of shared memory.

Typically, a shared-memory region resides in the address space of the

process creating the shared-memory segment. Other processes that wish

to communicate using this shared-memory segment must attach it to their

address space.

These are steps for shared memory communication:

• Create or open a shared memory object with shm_open(). A file

descriptor will be returned if shm_open() creates a shared memory

object successfully.

• Set the shared memory object size with ftruncate().

• Map the shared memory object into the current address space with

mmap() and MAP_SHARED.


• Read/write the shared memory.

• Unmap the shared memory with munmap().

• Close the shared memory object with close().

• Delete the shared memory object with shm_unlink().

• Control operations on the shared memory segment (shmctl())

POSIX provides five entry points to create, map, synchronize, and

undo shared memory segments:


 shm_open(): Creates a shared memory region or attaches to an existing, named region. This
system call returns a file descriptor.
 shm_unlink(): Deletes a shared memory region given a file descriptor (returned
from shm_open()). The region is not actually removed until all processes accessing the
region exit, much like any file in UNIX. However, once shm_unlink() is called (typically
by the originating process), no other processes can access the region.
 mmap(): Maps a shared memory region into the process’s memory. This system call requires
the file descriptor from shm_open() and returns a pointer to memory. (In some cases, you
can also map a file descriptor to a plain file or another device into memory. A discussion of those
options is beyond the scope of this introduction; consult the mmap() documentation for your
operating system for specifics.)
 munmap(): The inverse of mmap().
 msync(): Used to synchronize a shared memory segment with the file system — a technique
useful when mapping a file into memory.

A program shown in text book (page 133) use the producer-

consumer model in implementing shared memory. The producer

establishes a shared memory object and writes to shared memory, and the

consumer reads from shared memory.


Requirements:

You need to write codes that implements a parent and child process

that communicates via a shared memory object.

1) Parent process creates a shared memory

2) Parent process spawn child process

3) Child process runs and write a “greeting to parent!” message to

shared memory

4) Parent process waits child process to terminate, if the exit code == 0,

then read the message from shared memory.

Compile the producer and consumer by:

$gcc -o shm-posix-producer shm-posix-producer.c –lrt

$gcc -o shm-posix-consumer shm-posix-consumer.c –lrt

The producer process calls shm_open to create and open the shared

memory object in O_CREAT|O_RDWR mode. Return its descriptor

shm_fd. After adjusting the size of the shared memory object shm_fd to
the specified size, call mmap to map it to the shared memory area pointed

to by ptr, and the mapping flag Consistent with the flag when the object is

opened, and then use ptr to perform operations on shared memory

objects.

The producer's operation on the shared memory object is to write the

string name and message0 together into the common memory object. The

consumer program can directly access the shared memory object and

output the content written by the producer after calling shm_open and

mmap.

Running the programs:

./shm-posix-producer

./shm-posix-consumer

The producer and consumer processes in the program run sequentially.

From the program structure, the producer first writes data to the shared

memory area, and then the consumer utputs the data in the shared
memory area. If the producer keeps inputting data into the shared

memory while the producer is running, the consumer can automatically

display the content after each input.

Questions:

1. On Linux, all shared memory objects can be found in /dev/shm. Can

you find the shared memory objects that you created?

Solution:

Shared Memory Objects and Linux Kernels

On Linux, all shared memory objects can be found in /dev/shm. You

may list them with ls -l /dev/shm. You may also remove a shared

memory object with rm /dev/shm/[name]. This is handy when you are

debugging your program.


2.2 Experiment 2: ordinary pipe communication

Ordinary pipes allow two processes to communicate in standard

producer– consumer fashion: the producer writes to one end of the pipe

(the write-end) and the consumer reads from the other end (the read-end).

As a result, ordinary pipes are unidirectional, allowing only one-way

communication. If two-way communication is required, two pipes must

be used, with each pipe sending data in a different direction. We next

illustrate constructing ordinary pipes on Linux systems. In the example,

one process writes the message Greetings to the pipe, while the other

process reads this message from the pipe. An ordinary pipe cannot be

accessed from outside the process that created it. Typically, a parent

process creates a pipe and uses it to communicate with a child process


that it creates via fork().

On Linux systems, ordinary pipes are constructed using the function

pipe(int fd[ ])

This function creates a pipe that is accessed through the int fd[] file

descriptors: fd[0] is the read-end of the pipe, and fd[1] is the write-end.

In order to send structured data through a pipe, you have to serialize

it to push it from the sending to receiving process as a stream of bytes.

For instance, imagine you are sending a struct type: you have to turn the

struct into a sequence of bytes in order to send them over the pipe.

When you send two different instances of serialized data structures

over a serial channel such as a Linux pipe, you have to be able to

distinguish where one datum ends and the next one begins. What you use

as the “marker” or “sentinel value” that distinguishes the two instances is

up to you to define.

1) Write an ordinary pipe between parent process and child process.

Copy the pipes-test.c program from the text book (Page 143) to

another file named pipes.c. Now, modify your new program so that the

parent process read to the pipe content from child process when child
process executed commands.

The program first creates a pipe, and then creates a child process of

the current process through fork(). Then each process closes the file

descriptors that are not needed for the read and write pipes. The child

process executes the "ls -a" command under the current path, and writes

the command execution output to the pipe by copying the pipe write

descriptor fd[1] to standard output; the parent process reads the pipe data

and displays it through fd[0] .

Every process in Linux is provided with three standard file

descriptor including standard input, output and error files. By default:

 Standard Input is the keyboard, abstracted as a file to make it

easier to write shell scripts.

 Standard Output is the shell window or the terminal from which

the script runs, abstracted as a file to again make writing scripts &

program easier

 Standard error is the same as standard output: the shell window or

terminal from which the script runs.

A file descriptor is simply a number that refers to an open file. By

default, file descriptor 0 (zero) refers to the standard input & often

abbreviated as stdin. File descriptor 1 refers to standard output (stdout)


and file descriptor 2 refers to standard error (stderr).

You can use dup(2) function to duplicate a file descriptor with the

pipe write descriptor fd[1] by using function dup2 in order to relocate the

standard output.

// dup2() makes newfd be the


copy of oldfd, closing newfd
first if necessary
int dup2(int oldfd, int newfd);

pipes.c

// Khan Md Shahedul Islam


//2018380130

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#define BUFFER_SIZE 25
#define READ_END 0
#define WRITE_END 1
int main(void) {
char write_msg[BUFFER_SIZE] = "Greetings";
char read_msg[BUFFER_SIZE];
int fd[2];
pid_t pid;
char EoF = '\0';

if (pipe(fd) == -1) {
fprintf(stderr, "Pipe failed");
return 1;
}
pid = fork();
//Pipe(fd);
if (pid < 0) {
fprintf(stderr, "Fork failed");
return 1;
}
if (pid > 0) {
//parent process
close(fd[READ_END]);
//printf("%s", write_msg);
for (int i = 0; i < strlen(write_msg); i++) {
write(fd[WRITE_END], &write_msg[i], 1);
}
write(fd[WRITE_END], &EoF, 1);
close(fd[WRITE_END]);
}
else {
//child process
close(fd[WRITE_END]);
printf("read \n");
for (int j = 0; j < strlen(write_msg); j++) {
if (read(fd[READ_END], &read_msg[j], BUFFER_SIZE) >= 0){
read(fd[READ_END], &read_msg[j], BUFFER_SIZE);
printf("%c", read_msg[j]);
}
}
close(fd[READ_END]);
}
return 0;
}

2) Write a two-way ordinary pipe


Copy your pipes-test.c program to another file named upper.c.

Modify your program so that it defines two pipes: one for communication

from parent to child and another for communication from child to parent.

Make sure to close the correct ends of each pipe. Ultimately, your goal is

to have two pipes, one in each direction, so that you have bi-directional

communication between the two processes. It helps a lot to use names for

the pipes file descriptors that indicate their direction. For instance: p-to-c

and c-to-p tell us the processes that the pipes interconnect and the

direction of the flow of information.

This new version of the program must behave as follows.

 The parent sends a message to the child (byte-by-byte or line) and

when it is done, it enters a loop to read characters from the pipe

coming from the child (also byte-by-byte), terminating the loop on

the receipt of EOF.

 The child receives the message (also byte-by-byte or line),

printing each character to standard out as it arrives. For each

character received, the child converts it to uppercase (using

toupper(3) function) and sends it back to the parent using your

second pipe. As the parent reads the characters received from the
child, it prints them to standard out.

Make sure to reason carefully about when the write ends of the two

pipes should be closed, so that your processes can terminate their loops

gracefully and reach their termination state when appropriate. Also, make

sure to use the wrappers defined previously (Fork, Pipe, Read, and

Write).

upper.c

//Khan Md Shahedul Islam


//2018380130

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <ctype.h>
#define BUFFER_SIZE 25
#define READ_END 0
#define WRITE_END 1
int main(void) {
char write_msg[BUFFER_SIZE] = "Greetings";
char read_msg[BUFFER_SIZE];
int p2c[2];
int c2p[2];
pid_t pid;
char EoF = '\0';
int status;
if (pipe(p2c) == -1) {
fprintf(stderr, "Pipe failed");
return 1;
}
if (pipe(c2p) == -1) {
fprintf(stderr, "Pipe failed");
return 1;
}
pid = fork();
//Pipe(fd);
if (pid < 0) {
fprintf(stderr, "Fork failed");
return 1;
}
if (pid > 0) {
//parent process
close(p2c[READ_END]);
close(c2p[WRITE_END]);
//printf("%s", write_msg);
for (int i = 0; i < strlen(write_msg); i++) {
write(p2c[WRITE_END], &write_msg[i], BUFFER_SIZE);
}
write(p2c[WRITE_END], &EoF, BUFFER_SIZE);
close(p2c[WRITE_END]);
wait(&status);
//printf("we made it here\n");
for (int c = 0; c < strlen(write_msg); c++) {
//printf("c");
read(c2p[READ_END], &read_msg[c], BUFFER_SIZE);
printf("%c", read_msg[c]);
}
printf("\n");
close(c2p[READ_END]);
}
else {
//child process
close(p2c[WRITE_END]);
close(c2p[READ_END]);
const char *filler;
printf("read\n");
for (int j = 0; j < strlen(write_msg); j++) {
//printf("a");

read(p2c[READ_END], &read_msg[j], BUFFER_SIZE);


filler = toupper(read_msg[j]);
printf("%c", read_msg[j]);
write(c2p[WRITE_END], &filler, BUFFER_SIZE);
}
close(p2c[READ_END]);
printf("\n");
//write(c2p[WRITE_END], &EoF, 1);
close(c2p[WRITE_END]);
}
return 0;
}

Questions:

1 Does the pipe allow bidirectional communication, or is

communication unidirectional in an ordinary pipe?

Solution:

Ordinary pipes are unidirectional, allowing only one-way

communication.

Ordinary pipes allow two processes to communicate in

standard producer– consumer fashion: the producer writes to one end

of the pipe (the write-end) and the consumer reads from the other end

(the read-end). 

If bidirectional communications are needed, then a second pipe is

required.

2 If two-way communication is allowed, is it half duplex (data can


travel only one way at a time) or full duplex (data can travel in both

directions at the same time)?

Solution:

If two-way communication is required, two pipes must be used, with

each pipe sending data in a different direction. Pipes are half duplex

Half-duplex and Full-duplex Transmission

Although FIFOs allow bidirectional communication, only half-duplex

transmission is permitted.

If data must travel in both directions, two FIFOs are typically used. 

3 Must a relationship (such as parent–child) exist between the

communicating processes in an ordinary pipe?

Solution:

yes

2.3 Experiment 3: Named pipe communication

We used one pipe for one-way communication and two pipes for bi-

directional communication. We can use single named pipe that can be


used for two-way communication (communication between the server

and the client, plus the client and the server at the same time) as Named

Pipe supports bi-directional communication. Named pipes provide a

much more powerful communication tool. Communication can be

bidirectional, and no parent–child relationship is required.

A named pipe, however, can last as long as the system is up, beyond

the life of the process. It can be deleted if no longer used. Usually a

named pipe appears as a file and generally processes attach to it for inter-

process communication. A FIFO file is a special kind of file on the local

storage which allows two or more processes to communicate with each

other by reading/writing to/from this file.

A FIFO special file is entered into the filesystem by calling mkfifo()

in C. Once we have created a FIFO special file in this way, any process

can open it for reading or writing, in the same way as an ordinary file.

However, it has to be open at both ends simultaneously before you can

proceed to do any input or output operations on it.

The following steps outline how to use a named pipe from Linux

programs:

 Create a named pipe using the mkfifo() function. Only one of the
processes that use the named pipe needs to do this.

 Access the named pipe using the appropriate I/O method such as

open().

 Communicate through the pipe with another process using file I/O

functions:

 Write data to the named pipe.

 Read data from the named pipe.

 Close the named pipe.

Creating a FIFO file: In order to create a FIFO file, a function calls i.e.

mkfifo is used.
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char
*pathname, mode_t mode);

mkfifo() makes a FIFO


special file with name
pathname. mode specifies the
FIFO's permissions.
Pathname specifies the name
of the new FIFO.
mode specifies which access
modes the FIFO has when it is
created. Refer to the
documentation for stat() for a
discussion of access mode
flags. You can use 0666 (read
and write allowed for
everyone)

Requriments:

You need to write two programmers which follow rules below.


Step 1: Create two processes, one is client and another one is server.

Step 2: Server process performs the following:

 Creates a named pipe (using library function mkfifo()) with

name “fifo” in /tmp directory, if not created.

 Opens the named pipe for read and write purposes.

 Here, created FIFO with permissions of read and write for

Owner.

 Waits infinitely for a message from the client.

 If the message received from the client is not “end”, prints the

message and reverses the string. The reversed string is sent

back to the client. If the message is “end”, closes the fifo and

ends the process.

Step 3: Client process performs the following:

 Opens the named pipe for read and write purposes.

 Accepts string from the user.

 Checks, if the user enters “end” or other than “end”. Either

way, it sends a message to the server. However, if the string is

“end”, this closes the FIFO and also ends the process.

 If the message is sent as not “end”, it waits for the message

(reversed string) from the client and prints the reversed string.

 Repeats infinitely until the user enters the string “end”


Now, let's take a look at the sample code of the FIFO
server.
// 2018380130

// Khan Md Shahedul Islam

// fifo_Server_2way.c

#include <stdio.h>

#include <sys/stat.h>

#include <sys/types.h>

#include <fcntl.h>

#include <unistd.h>

#include <string.h>

#define FIFO_FILE "/tmp/fifo_twoway"

void reverse_string(char *);

int main() {

int fd;

char readbuf[80];

char end[10];

int to_end;

int read_bytes;

/* Create the FIFO if it does not exist */

mkfifo(FIFO_FILE, S_IFIFO|0640);
strcpy(end, "end");

fd = open(FIFO_FILE, O_RDWR);

while(1) {

read_bytes = read(fd, readbuf, sizeof(readbuf));

readbuf[read_bytes] = '\0';

printf("FIFOSERVER: Received string: \"%s\" and length is %d\n",

readbuf, (int)strlen(readbuf));

to_end = strcmp(readbuf, end);

if (to_end == 0) {

close(fd);

break;

reverse_string(readbuf);

printf("FIFOSERVER: Sending Reversed String: \"%s\" and length is

%d\n", readbuf, (int) strlen(readbuf));

write(fd, readbuf, strlen(readbuf));

/*

sleep - This is to make sure other process reads this, otherwise this

process would retrieve the message

*/

sleep(2);

}
return 0;

void reverse_string(char *str) {

int last, limit, first;

char temp;

last = strlen(str) - 1;

limit = last/2;

first = 0;

while (first < last) {

temp = str[first];

str[first] = str[last];

str[last] = temp;

first++;

last--;

return;

Compilation and Execution Results

FIFOSERVER: Received string: "LINUX IPCs" and length

is 10
FIFOSERVER: Sending Reversed String: "sCPI XUNIL" and

length is 10

FIFOSERVER: Received string: "Inter Process

Communication" and length is 27

FIFOSERVER: Sending Reversed String: "noitacinummoC

ssecorP retnI" and length is 27

FIFOSERVER: Received string: "end" and length is 3

Let's have a look at the sample code of the FIFO client


below:-
// 2018380130
//2018380130
// fifo_Client_2way.c

#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FIFO_FILE "/tmp/fifo_twoway"
int main() {
int fd;
int end_process;
int stringlen;
int read_bytes;
char readbuf[80];
char end_str[5];
printf("FIFO_CLIENT: Send messages, infinitely, to end enter \"end\"\n");
fd = open(FIFO_FILE, O_CREAT|O_RDWR);
strcpy(end_str, "end");
while (1) {
printf("Enter string: ");
fgets(readbuf, sizeof(readbuf), stdin);
stringlen = strlen(readbuf);
readbuf[stringlen - 1] = '\0';
end_process = strcmp(readbuf, end_str);
//printf("end_process is %d\n", end_process);
if (end_process != 0) {
write(fd, readbuf, strlen(readbuf));
printf("FIFOCLIENT: Sent string: \"%s\" and string length is %d\n",
readbuf, (int)strlen(readbuf));
read_bytes = read(fd, readbuf, sizeof(readbuf));
readbuf[read_bytes] = '\0';
printf("FIFOCLIENT: Received string: \"%s\" and length is %d\n", readbuf,
(int)strlen(readbuf));
} else {
write(fd, readbuf, strlen(readbuf));
printf("FIFOCLIENT: Sent string: \"%s\" and string length is %d\n",
readbuf, (int)strlen(readbuf));
close(fd);
break;
}
}
return 0;
}

Compilation and Execution Results

FIFO_CLIENT: Send messages, infinitely, to end enter

"end"

Enter string: LINUX IPCs

FIFOCLIENT: Sent string: "LINUX IPCs" and string

length is 10

FIFOCLIENT: Received string: "sCPI XUNIL" and length

is 10

Enter string: Inter Process Communication


FIFOCLIENT: Sent string: "Inter Process

Communication" and string length is 27

FIFOCLIENT: Received string: "noitacinummoC ssecorP

retnI" and length is 27

Enter string: end

FIFOCLIENT: Sent string: "end" and string length is 3

Questions:

1 What are the advantages of using named pipe?

Solution:

 A major advantage of using named pipes is that they provide a

useful way to send one-line requests to an OpenEdge background

session running a message handler procedure. Multiple users can

send requests through the same named pipe and each request is

removed from the pipe as it is received. In addition, the message

handler procedure can loop indefinitely looking for input because it

blocks (waits) until there is something to read. Finally, output

through named pipes is more efficient than writing a complete

response to an ordinary file, closing the file, and then informing the

recipient that the results are available. The receiving process can

read the result through a named pipe as soon as it is written.


 There is another advantage of named pipes: you can use them

across different systems. Suppose you want real-time

communication of two process running on different machines.

Then share a folder between the two, put your FIFO onto the

folder, and off you go. It is considerably easier than transforming

an application designed to work on files into a service listening on

a port.

 A disadvantage of named pipes is that multiple processes cannot

use a single named pipe to send or receive multi-line messages,

unless you define a more complex protocol to control message

interleaving. Also, although synchronizing named pipe input and

output is helpful in some situations, it is a problem in others. For

example, if the message handler procedure running in the

background OpenEdge session starts returning results to an output

named pipe, and for some reason the requestor is not ready to read

the results, the message handler cannot move on to read the next

request.

You might also like