Processes executing concurrently may be either independent processes or cooperating processes.
A process is independent if it does not share data with any other processes.
A process is cooperating if it can affect or be affected by the other processes. Clearly, any processes that shares data with other processes is a cooperating process.
Cooperating processes require an IPC mechanism that will allow them to exchange data that is, send data to and receive data from each other.
Two fundamental models of IPC:
shared memory
message passing
Consider the Producer-Consumer Problem to illustrate the concept of cooperating processes. Producer-Consumer Problem is a common paradigm for cooperating processes.
A producer produces information that is consumed by a consumer.
For example
a compiler produces assembly code, and a assembler consumes it.
a web server produces an HTML, file, and a browser consums it.
A solution using shared-memory to allow producer and consumer to run concurrently.
Let a buffer of items be available
a producer can fill the buffer
a consumer can empty the buffer
A shared memory is a region of memory shared by the producer and consumer processes.
Define a shared buffer
#define BUFFER_SIZE 10
typedef struct {
...
} item;
item buffer[BUFFER_SIZE];
int in = 0;
int out = 0;
// The producer process using shared memory
item next_produced;
while (true) {
/* produce an item in next_produced */
while (((in + 1) % BUFFER_SIZE) == out)
; /* do nothing */
// waiting
buffer[in] = next_produced;
in = (in + 1) % BUFFER_SIZE;
}
// The consumer process using shared memory
while (true) {
while (in == out)
; /* do nothing */
next_consumed = buffer[out];
out = (out + 1) % BUFFER_SIZE;
/* consume the item in next_consumed */
}
The scheme of using shared-memory
requires that these processes share a region of memory and that
the code for accessing and manipulating the shared memory be written explicitly by the application programmer.
O/S provides the means for cooperating processes to communicate with each other via a message-passing facility.
Two operations of the message-passing facility
send(message)
receive(message)
// The producer process using message passing
message next_produced;
while (true) {
/* produce an item in next_produced */
send(next_produced);
}
// The consumer process using message passing
message next_consumed;
while (true) {
receive(next_consumed);
/* consume the item in next_consumed */
}
if two processes &&P&& and &&Q&& want to communicate, they must send to and receive messages from each other.
This communication link can be implemented in a variety of ways
direct or indirect communication
synchronous and asynchronous communication
automatic or explicit buffering
each process that wants to communicate must explicitly name the recipient or sender of the communication.
The primitives of this scheme:
send(, message) - send a message to process .
receive(, message) - receive a message from process .
The properties of communication links in this scheme
Links are established automatically.
A link is associcated with exactly two processes.
There exists exactly one link between each pair of processes.
the messages are sent to and received from ports(, or mailboxes.)
A mailbox (also referred to as ports)
can be viewed abstractly as an object
into wich messages can be placed by processes, and
from which messages can be removed.
The primitives of this scheme:
send(, message) - send a message to mailbox .
receive(, message) - receive a message from mailbox .
The properties of communication links in this scheme
Links are established between a pair of processes only if both members of the pair have shared mailbox.
A link may be associcated with more than two processes.
A number of different links may exist, between each pair of processes with each link corresponding to one mailbox.
OS provides a mechanism that allows a process to do
Create a new mailbox.
Send and Receive messages through the mailbox.
Delete a mailbox.
blocking : non-blocking = synchronous : asynchronous
Blocking send: the sender is blocked until the message is received.
Non-blocking send: the sender is sends the message and
Blocking receive: the receiver blocks until a message is
Non-blocking receive: the receiver retrieves either a valid message or
a null message.
Shared Memory: POSIX Shared Memory
POSIX: Portable Operating System Interface (for UNIX)
tried to standardization (but failed)
Message Passing: Pipes
is organized using memory mapped files, which associate the region of shared memory with a file. (for speed)
First, create a shared memory object
fd = shm_open(name, O_CREAT | ORDWR, 0666);
Configure the size of the object in bytes
ftruncate(fd, 4096);
※ 4096
: unit of reading & writing
Finally, establish a memory mapped file
mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
Producer process illustrating POSIX shared-memory API
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/mman.h>
int main()
{
const int SIZE = 4096; // the size of shared memory
const char *name = "OS"; // the name of shared memory
const char *message_0 = "Hello, ";
const char *message_1 = "Shared Memory! \n";
int shm_fd; // the file descriptor of shared memory
char *ptr; // pointer to shared memory
/* create the shared memory object */
shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666);
/* configure the size of the shared memory */
ftruncate (shm_fd, SIZE);
/* map the shared memory object */
ptr = (char *)mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
/* write to the shared memory */
sprintf(ptr, "%s", message_0);
ptr += strlen(message_0); // moving pointer
sprintf(ptr, "%s", message_1);
ptr += strlen(message_1); // moving pointer
return 0;
}
Compile
$ gcc 3.16_shm_producer.c -lrt
Consumer process illustrating POSIX shared-memory API
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/mman.h>
int main()
{
const int SIZE = 4096; // the size of shared memory
const char *name = "OS"; // the name of shared memory
int shm_fd; // the file descriptor of shared memory
char *ptr; // pointer to shared memory
/* create the shared memory object */
shm_fd = shm_open(name, O_RDONLY, 0666);
/* map the shared memory object */
ptr = (char *)mmap(0, SIZE, PROT_READ, MAP_SHARED, shm_fd, 0);
/* read from the shared memory object */
printf("%s", (char *)ptr);
/* remove the shared memory */
shm_unlink(name);
return 0;
}
Compile
$ gcc 3.16_shm_consumer.c -lrt
were one of the first IPC mechanisms in early UNIX systems.
A pipe acts as a conduit allowing two processes to communicate.
Four issues of pipe implementation:
Does the pipe allow unidirectional or bidirectional communication?
In the case of two way comm., is it half-duplex or full-duplex?
Must a relationship exist between the communicating process, such as parent child?
Can the pipes communicate over a network?
Two common types of pipes
1. Ordinary pipes
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 created.
2. Named pipes
can be accessed without a parent-child relationship.
allow two processes to communicate in producer-consumer fashion.
the producer writes to one end of the pipe (write end)
the consumger reads from the other end (read end)
unidirectional: only one-way communication is possible.
On UNIX systems
ordinary pipes are constructed using the function
pipe(int fd[])
fd[0]: the read end of the pipe
fd[1]: the write end
Ordinary pipe in UNIX
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#define BUFFER_SIZE 25
#define READ_END 0
#define WRITE_END 1
int main()
{
char write_msg[BUFFER_SIZE] = "Greetings";
char read_msg[BUFFER_SIZE];
int fd[2];
pid_t pid;
/* create the pipe */
pipe(fd);
pid = fork(); // fork a new process
if (pid > 0) { // parent process
close(fd[READ_END]);
/* write to the pipe */
write(fd[WRITE_END], write_msg, strlen(write_msg) + 1);
close(fd[WRITE_END]);
}
else if (pid == 0) { // child process
close(fd[WRITE_END]);
/* read to the pipe */
read(fd[READ_END], read_msg, BUFFER_SIZE);
printf("read %s\n", read_msg);
close(fd [READ_END]);
}
return 0;
}
Two other strategies in client-server (not locally) systems
Sockets are defined as endpoints for communication.
RPCs(Remote Procedure Calls) abstracts procedure calls between processes on networked systems.
identified by and IP address concatenated with a port number.
provides a much easier interface to sockets
Java provides three different types of sockets
1. Socket class: connection-oriented (TCP)
2. DatagramSocket class: connectionless (UDP)broadcasting to everyone
- MulticastSocket class: multiple recipients
broadcasting to someone
Date server in Java
Date client in Java
is one of the most common forms of remote service.
designed as a way to abstract the procedure call mechanism for use between systems with network connections.
A client invokes a procedure on a remote host as it would invoke a procedure locally.
hides the details that allow communication to take place by providing a stub on the client side.
The stub of client-side locates the server and marshals the parameters.
The stub of server-side received this message,
unpacks the marshalled parameters, and
performs the procedure on the server.