C CPP & IPC Mechanisms

C CPP & IPC Mechanisms

Share

Learning C C++ & IPC Mechanisms POSIX MultiThreading on Linux

24/07/2016

Guys if u need full document soft copy of Cpp & IPC Mechanism.. I ll Frwd to u...

Photos 19/12/2014

Interprocess Communication(IPC) Ubuntu Linux

Inter-process communication (IPC) is a mechanism that allows the exchange of data between processes. By providing a user with a set of programming interfaces, IPC helps a programmer organize the activities among different processes. IPC allows one application to control another application, thereby enabling data sharing.
Before we going through IPC Mechanisms.. first we need to learn about the Process.
Process
A process is an instance of a computer program that is being executed. It contains the program code and its current activity.
// Program Name is hello.c

int i;
int j=0;
const int k;
static int l;
char *ch;
int main()
{
int m=10;
printf("Hello, world!\n");
return 0;
}
Here hello.c program treated as single process. Not only this program..
If you opened any application like “google chrome”,”notepad”,”editor” for c program all are depends on background process.
Program need to compile(Build) and run.. with command
$gcc programe_name.c //(Compile or Build)
$./a.out //(Run)
When ex*****on started above program it's took some ex*****on steps..
Those steps are mentioned below diagram.



PROCESS MEMORY LAYOUT
1. A running program is called a process and when a program is run, its executable image is loaded into memory area that normally called a process address space in an organized manner.
2. Process address space is organized into four memory areas, called segments:
the stack segment, Heap segment, data segment (bss and data) and text segment,can be illustrated below.

3. The text segment (also called a code segment) is where the compiled code of the program itself resides.
4. The following Table summarizes the segments in the memory address space layout as illustrated in the previous Figure.

Segment
Description
Code - text segment
Often referred to as the text segment, this is the area in which the executable or binary image instructions reside. For example, Linux/Unix arranges things so that multiple running instances of the same program share their code if possible. Only one copy of the instructions for the same program resides in memory at any time. The portion of the executable file containing the text segment is the text section.
Initialized data – data segment
Statically allocated and global data that are initialized with nonzero values live in the data segment. Each process running the same program has its own data segment. The portion of the executable file containing the data segment is the data section.
Uninitialized data – bss segment
BSS stands for ‘Block Started by Symbol’.Global and statically allocated data that initialized to zero by defaultare kept in what is called the BSS area of the process. Each process running the same program has its own BSS area. When running, the BSS, data are placed in the data segment. In the executable file, they are stored in theBSS section. For Linux/Unix the format of an executable, only variables that are initialized to a nonzero value occupy space in the executable’s disk file.
Heap
The heap is where dynamic memory (obtained by malloc(), calloc(), realloc() and new – C++) comes from. Everything on a heap is anonymous, thus you can only access parts of it through a pointer.As memory is allocated on the heap, the process’s address space grows. Although it is possible to give memory back to the system and shrink a process’s address space, this is almost never done because it will be allocated to other process again.Freed memory (free() and delete – C++) goes back to the heap, creating what is called holes. It is typical for the heap to grow upward. This means that successive items that are added to the heap are added at addresses that are numerically greater than previous items. It is also typical for the heap to start immediately after the BSS area of the data segment. The end of the heap is marked by a pointer known as the break. You cannot reference past the break. You can, however, move the break pointer (via brk() and sbrk() system calls) to a new position to increase the amount of heap memory available.
Stack
The stack segment is where local (automatic) variables are allocated. In C program, local variables are all variables declared inside the opening left curly brace of a function's body including the main() or other left curly brace that aren’t defined as static. The data is popped up or pushed into the stack following the Last In First Out (LIFO) rule. The stack holds local variables, temporary information, function parameters, return address and the like. When a function is called, a stack frame (or a procedure activation record) is created and PUSHed onto the top of the stack. This stack frame contains information such as the address from which the function was called and where to jump back to when the function is finished (return address), parameters, local variables, and any other information needed by the invoked function. The order of the information may vary by system and compiler. When a function returns, the stack frame is POPped from the stack. Typically the stack grows downward, meaning that items deeper in the call chain are at numerically lower addresses and toward the heap.

======== END of PROCESS ========

Now we learn about IPC Mechanism.
IPC techniques are divided into several concepts:-
1.Pipe
2.FIFO (Named Pipe)
3.Message Queue
4.Shared Memory
5.Semaphore
6.Scokets
7.POSIX Multi-threading
PIPE

A pipe is an in-memory data channel between processes.When one process writes into the pipe the other process can read from it.
So one side process1 treated as parent process. And other side process2 treated as child process.
Pipe uni-directional means that data only travels in one direction at any time, from parent to child or vice versa. And pipe doesn't have any name. It's also called un-named pipes.
First we need to create pipe in parent process. Main() program treated as parent process.then
A pipe is created by the "pipe()" system call:

befor pipe it's neccessary to create integer array for storing “file descriptors” for read and write. A file descriptor is an indicator that points to an input/output device. Stdin-0, stdout-1, stderr-2.(0,1,2 all are integers).

int pfd[2];
int pipe(pfd);
Pipe has one argument: the address of an integer array.

A successful call to pipe creates the pipe and creates two file descriptors for it. The file descriptors are stored in the integer array. pfd[0] is a descriptor representing the read end of the pipe; pfd[1] is for the write end of the pipe.

A single process would not use a pipe. They are used when two processes wish to communicate in a one-way fashion.
Pipe used between two process. But both process are related process(parent & child(child inherited by parent).
Pipes doesn't work out between unrelated process.
Because Other processes cannot open the pipe, because the pipe does not have a name in the file system. However, if the owner of the pipe spawns any children, those children will inherit the pipe descriptors in their file descriptor tables. So, the basic technique used is to create a pipe, then with fork() system call we created one or more children. Thease child processes can now use the pipe for communication.
So parent process(main program) and pipe created. Now we need to create child process.. with fork() system call.
Fork()

System call fork() is used to create processes. It takes no arguments and returns a process ID. The purpose of fork() is to create a new process, which becomes the child process of the caller. After a new child process is created, both processes will execute the next instruction following the fork() system call.
If fork() returns a negative value, the creation of a child process was unsuccessful.
Fork() returns a zero to the newly created child process.
Fork() returns a positive value, the process ID of the child process, to the parent. The returned process ID is of type pid_t defined in sys/types.h. Normally, the process ID is an integer. Moreover, a process can use function getpid() to retrieve the process ID assigned to this process.
Therefore, after the system call to fork(), a simple test can tell which process is the child.
Please note that Unix will make an exact copy of the parent's address space and give it to the child. Therefore, the parent and child processes have separate address spaces.
If the call to fork() is executed successfully, Unix will
make two identical copies of address spaces, one for the parent and the other for the child.
Both processes will start their ex*****on at the next statement following the fork() call. In this case, both processes will start their ex*****on at the assignment statement as shown below:

After creating child , both process(parent & child) contain two read ends and two write ends.
so ne of the processes must close its read end, and the other must close its write end. Then it will become a simple pipeline again.
Suppose the parent wants to write down a pipeline to a child. The parent closes its read end, and writes into the other end. The child closes its write end and reads from the other end.

Example of PIPE Program:-



int main()
{
printf("This is the parent process. My parent_pid is %d \n", getpid());
printf("This is the child process. My child_pid is %d \n", fork());

return 0;
}
output:-
This is the parent process. My parent_pid is 4072
This is the child process. My child_pid is 4073
This is the child process. My child_pid is 0

Explanation:-

fork() executes before the printf. So when its done, you have two processes with the same instructions to execute. Therefore, printf will execute twice. The call to fork() will return 0 to the child process, and the pid of the child process to the parent process.

/******************************************/







int main(void)
{
int fd[2];
pid_t childpid;
char string[] = "Hello, world!\n";
char readbuffer[80];

pipe(fd);
if((childpid = fork()) == -1)
{
perror("fork");
exit(1);
}
if(childpid == 0)
{
/* Child process closes up input side of pipe */
close(fd[0]);
/* Send "string" through the output side of pipe */
write(fd[1], string, (strlen(string)+1));
exit(0);
}
else
{
/* Parent process closes up output side of pipe */
close(fd[1]);
/* Read in a string from the pipe */
read(fd[0], readbuffer, sizeof(readbuffer));
printf("Parent Received string: %s", readbuffer);
}
return(0);
}
Another Example:-






void ChildProcess(void); /* child process prototype */
void ParentProcess(void); /* parent process prototype */
int fd[2];

int main(void)
{
pid_t pid;
pipe(fd);
pid = fork();

if (pid == 0)
ChildProcess();
else
ParentProcess();
return 0;
}
void ChildProcess(void)
{
char readbuffer[80];
printf("This line is from child\n");
close(fd[1]);
/* Read in a string from the pipe */
read(fd[0], readbuffer, sizeof(readbuffer));
printf("child Received string: %s\n", readbuffer);
printf("*** Child process is done ***\n");
}
void ParentProcess(void)
{
printf("This line is from parent\n");
char string[]="Hell, world!";
close(fd[0]);
/* Send "string" through the output side of pipe */
write(fd[1], string, (strlen(string)+1));
printf("*** Parent is done ******\n");
}
output:-
This line is from parent
*** Parent is done ******
This line is from child
child Received string: Hell, world!
*** Child process is done ***

Properties of Pipe: -

1. Pipes do not have a name. For this reason, the processes must share a parent process. This is the main drawback to pipes. However, pipes are treated as file descriptors, so the pipes remain open even after fork and exec.
2. Pipes do not distinguish between messages; they just read a fixed number of bytes. Newline (\n) can be used to separate messages. A structure with a length field can be used for message containing binary data.
3. Pipes can also be used to get the output of a command or to provide input to a command.
4. Pipes used only b/w parent and child process. And unidirectional.
/******************************************************************************/
FIFO – Named pipes: mkfifo, mknod

Pipes are commonly used for interprocess communication. But the major disadvantage of pipes is that they can be used only by one process (there are readers and writers within the same process) or the processes which share the same file descriptor table (normally the processes and the child processes or threads created by them). Thus pipes have this big limitation: they cannot pass information between unrelated processes. This is because they do not share the same file descriptor table. But if names are given to the pipes, then one would be able to read or write data to them just like a normal file. The processes need not even share anything with each otherFIFO (First In First Out) are also called named pipes.

A FIFO works like a pipe, but its interface looks like a file. It has a filename and permissions, and it's in a directory. Once you make the FIFO, one process can write to it and another process can read from it.
They have "names" and exist as special files within a file system. You can use it over and over again, Meaning They exist even if no one is using it and until they are removed with rm or unlink() They can be used with unrelated process.
The main features of FIFO are
1. It implements FIFO feature of the pipes
2. They can be opened just like normal files using their names
3. Data can be read from or written to the fifo
4. FIFO file does not contain any user information. Instead, it allows two or
more processes to communicate with each other by reading/writing to/from this
file.
5. That is, all systems should guarantee write(...,length) or read(...,length)
correctly executable when length 0 indicates to only receive messages of this type. In this case, the call will only return messages with a matching msg_to_receive type --- more about this on the msgsend primitive.
Specifies the type of message requested as follows:
If msgtype is 0, the first message on the queue is received.
If msgtype is greater than 0, the first message of type msgtyp is received.
If msgtype is less than 0, the first message of the lowest type that is less than or equal to the absolute value of msgtype is received.
4. If the flags are zero, then the receiver will block if there is nothing to receive in the message queue. All other options are OR'ed in as masking flags:
5. IPC_NOWAIT indicates that this operation should return an error if there is no packet to receive. In this case we should get a ENOMSG error return code.
6. MSG_NOERROR indicates that the message should be truncated if necessary to fit the receiving buffer. The error E2BIG is returned when this option is used and the message cannot fit into the buffer.
msgctl(msgid, IPC_RMID, 0);
Normally, your program should free up any message queue that it allocates. However, if your program crashes, your message queue doesn't automatically get freed up. It stays around and can be seen with the ipcs command.
1. To get rid of the message queue resource from your program, code the following:
msgctl(msgid, IPC_RMID, 0);
2. If your program crashes or doesn't free up the message queue, you can free up your message queue with a command like:
ipcrm msg id
ex:- $ipcrm shm 32768
3. How do you know if your message queue is filling up. A regular ipcs can tell you the current number of messages and the total number of bytes you have in the message queue. But if you are in your program, you can also find out with an msgctl call.
4. The following call will return information about your message queue:
struct msqid_ds info;
msgctl(msgid, IPC_STAT, &info);
5. The info datastructure has a number of useful fields. Some of the more useful are:
msg_cbytes - current # of bytes on Q
msg_qnum - current # of messages on Q
msg_qbytes - max # of bytes on Q
6. On some systems (Linux only permits this to superusers) you can change msg_qbytes, by filling in the msqid_ds structure and issuing a command like:
msgctl(msgid, IPC_SET, &info);

EXAMPLES:-
A general scheme of using Message Queue is the following Sender:
1. For a sender, it should be started before any receiver. The sender should perform the following tasks:
2. Ask for a Message Queue with a key and memorize the returned Message Queue ID. This is performed by system call msgget().
3. Create message structure and send message through with system call msgsnd().
4. Do something and wait for all receiver process completion. That means First we need conform Receiver receive all messages from MessageQueue. Then we remove Message Queue from system resources.. through msgctl();
5. Control , status or Remove all Message Queue oprations performed with system call msgctl().

For the receiver part, the procedure is almost the same:
1. Ask for a Message Queue with the same (receiver Message Queue key) key and memorize the returned Message Queue ID.
2. Create message structure for stored received messages.and receive message through with system call msgrcv().
3. Control , status or Remove all Message Queue oprations performed with system call msgctl().Exit.

Sender.c








struct my_msgbuf
{
long mtype;
char mtext[200];
};

int main(void)
{
struct my_msgbuf buf;
int msqid;
key_t key=getuid();

if ((msqid = msgget(key, 0644 | IPC_CREAT)) == -1)
{
perror("msgget");
exit(1);
}
printf("Enter lines of text, ^D to quit:\n");
buf.mtype = 1; /* we don't really care in this case */

while(fgets(buf.mtext, sizeof buf.mtext, stdin) != NULL)
{
int len = strlen(buf.mtext);
/* ditch newline at end, if it exists */
if (buf.mtext[len-1] == '\n')
buf.mtext[len-1] = '\0';

if (msgsnd(msqid, &buf, len+1, 0) == -1) /* +1 for '\0' */
perror("msgsnd");
}
if (msgctl(msqid, IPC_RMID, NULL) == -1)
{
perror("msgctl");
exit(1);
}
return 0;
}

Receiver.c








struct my_msgbuf
{
long mtype;
char mtext[200];
};

int main(void)
{
struct my_msgbuf buf;
int msqid;
key_t key=getuid();

if ((msqid = msgget(key, 0644)) == -1)
{ /* connect to the queue */
perror("msgget");
exit(1);
}
printf("spock: ready to receive messages, captain.\n");
for(;;)
{ /* Spock never quits! */
if (msgrcv(msqid, &buf, sizeof(buf.mtext), 0, 0) == -1)
{
perror("msgrcv");
exit(1);
}
printf("receive: \"%s\"\n", buf.mtext);
}
return 0;
}
Notice that in receiver.c, in the call to msgget(), doesn't include the IPC_CREAT option.B'coz we already created Message queue in sender.c.. and we just used existing Message Queue which create by sender.c.

First run this command... “ipcs -q”
teja@B3537:~$ ipcs -q

------ Message Queues --------
key msqid owner perms used-bytes messages

output:-

Sender.c
Receiver.c
teja@B3537:~$ gcc sender.c
teja@B3537:~$ ./a.out
Enter lines of text, ^D to quit:

Enter Message : hi
Enter Message : baskar
Enter Message : how
Enter Message : are
Enter Message : you?

Ctrl +d enter
teja@B3537:~$

teja@B3537:~$ gcc receiver.c
teja@B3537:~$ ./a.out
receiver: ready to receive messages, captain.

Message : hi
Message : baskar
Message : how
Message : are
Message : you?
teja@B3537:~$

Incase if you run sendr.c prog only and entered few message and terminate.
Then chek message queue status with command “ipcs -q”.
teja@B3537:~$ ipcs -q

------ Message Queues --------
key msqid owner perms used-bytes messages
0x000003e8 32768 teja 644 15 5

/******************************************************************************/

After entering messages from sender side Message Queue allow one or more processes to write messages, which will be read by one or more reading processes. Linux maintains a list of message queues, the msgque vector; each element of which points to a msqid_ds data structure that fully describes the message queue. When message queues are created a new msqid_ds data structure is allocated from system memory and inserted into the vector.

Each msqid_ds
data structure contains an ipc_perm data structure and pointers to the messages entered onto this queue. In addition, Linux keeps queue modification times such as the last time that this queue was written to and so on. The msqid_ds also contains two wait queues; one for the writers to the queue and one for the readers of the message queue.
Each time a process attempts to write a message to the write queue its effective user and group identifiers are compared with the mode in this queue's ipc_perm data structure. If the process can write to the queue then the message may be copied from the process's address space into a msg
data structure and put at the end of this message queue. Each message is tagged with an application specific type, agreed between the cooperating processes. However, there may be no room for the message as Linux restricts the number and length of messages that can be written. In this case the process will be added to this message queue's write wait queue and the scheduler will be called to select a new process to run. It will be woken up when one or more messages have been read from this message queue.
Reading from the queue is a similar process. Again, the processes access rights to the write queue are checked. A reading process may choose to either get the first message in the queue regardless of its type or select messages with particular types. If no messages match this criteria the reading process will be added to the message queue's read wait queue and the scheduler run. When a new message is written to the queue this process will be woken up and run again.
Shared Memory
What is Shared Memory?
In the discussion of the fork() system call, we mentioned that a parent and its children have separate address spaces. While this would provide a more secured way of executing parent and children processes (because they will not interfere each other), they shared nothing and have no way to communicate with each other. A shared memory is an extra piece of memory that is attached to some address spaces for their owners to use. As a result, all of these processes share the same memory segment and have access to it. Consequently, race conditions may occur if memory accesses are not handled properly. The following figure shows two processes and their address spaces. The yellow rectangle is a shared memory attached to both address spaces and both process 1 and process 2 can have access to this shared memory as if the shared memory is part of its own address space. In some sense, the original address spaces is "extended" by attaching this shared memory.

Shared memory is a feature supported by UNIX System V, including Linux, SunOS and Solaris. One process must explicitly ask for an area, using a key, to be shared by other processes. This process will be called the server. All other processes, the clients, that know the shared area can access it. However, there is no protection to a shared memory and any process that knows it can access it freely. To protect a shared memory from being accessed at the same time by several processes, a synchronization protocol must be setup.
A shared memory segment is identified by a unique integer, the shared memory ID. The shared memory itself is described by a structure of type shmid_ds in header file sys/shm.h. To use this file, files sys/types.h and sys/ipc.h must be included. Therefore, your program should start with the following lines:



A general scheme of using shared memory is the following:
For a server, it should be started before any client. The server should perform the following tasks:
1. Ask for a shared memory with a memory key and memorize the returned shared memory ID. This is performed by system call shmget().
2. Attach this shared memory to the server's address space with system call shmat().
3. Initialize the shared memory, if necessary.
4. Do something and wait for all clients' completion.
5. Detach the shared memory with system call shmdt().
6. Remove the shared memory with system call shmctl().
For the client part, the procedure is almost the same:
1. Ask for a shared memory with the same memory key and memorize the returned shared memory ID.
2. Attach this shared memory to the client's address space.
3. Use the memory.
4. Detach all shared memory segments, if necessary.
5. Exit.
On the next, we shall describe these system calls and their uses.
Keys

Unix requires a key of type key_t defined in file sys/types.h for requesting resources such as shared memory segments, message queues and semaphores. A key is simply an integer of type key_t; however, you should not use int or long, since the length of a key is system dependent.
There are three different ways of using keys, namely:
1. a specific integer value (e.g., 123456)
2. a key generated with function ftok()
3. a uniquely generated key using IPC_PRIVATE (i.e., a private key).
The first way is the easiest one; however, its use may be very risky since a process can access your resource as long as it uses the same key value to request that resource. The following example assigns 1234 to a key:
key_t SomeKey;
SomeKey = 1234;
The ftok() function has the following prototype:
key_t ftok(const char *path, /* a path string */
int id ); /* an integer value */
Function ftok() takes a character string that identifies a path and an integer (usually a character) value, and generates an integer of type key_t based on the first argument with the value of id in the most significant position. For example, if the generated integer is 35028A5D16 and the value of id is 'a' (ASCII value = 6116), then ftok() returns 61028A5D16. That is, 6116 replaces the first byte of 35028A5D16, generating 61028A5D16.
Thus, as long as processes use the same arguments to call ftok(), the returned key value will always be the same. The most commonly used value for the first argument is ".", the current directory. If all related processes are stored in the same directory, the following call to ftok() will generate the same key value:



key_t SomeKey;
SomeKey = ftok(".", 'x');
After obtaining a key value, it can be used in any place where a key is required. Moreover, the place where a key is required accepts a special parameter, IPC_PRIVATE. In this case, the system will generate a unique key and guarantee that no other process will have the same key. If a resource is requested with IPC_PRIVATE in a place where a key is required, that process will receive a unique key for that resource. Since that resource is identified with a unique key unknown to the outsiders, other processes will not be able to share that resource and, as a result, the requesting process is guaranteed that it owns and accesses that resource exclusively.

Asking for a Shared Memory Segment - shmget()

The system call that requests a shared memory segment is shmget(). It is defined as follows:
shm_id = shmget(key_t k, /* the key for the segment */
int size, /* the size of the segment */
int flag); /* create/use flag */
In the above definition, k is of type key_t or IPC_PRIVATE. It is the numeric key to be assigned to the returned shared memory segment. Size is the size of the requested shared memory. The purpose of flag is to specify the way that the shared memory will be used. For our purpose, only the following two values are important:
1. IPC_CREAT | 0666 for a server (i.e., creating and granting read and write access to the server)
2. 0666 for any client (i.e., granting read and write access to the client)
Note that due to Unix's tradition, IPC_CREAT is correct and IPC_CREATE is not!!!
If shmget() can successfully get the requested shared memory, its function value is a non-negative integer, the shared memory ID; otherwise, the function value is negative. The following is a server example of requesting a private shared memory of four integers:




.....
int shm_id; /* shared memory ID */
.....
shm_id = shmget(IPC_PRIVATE, 4*sizeof(int), IPC_CREAT | 0666);
if (shm_id < 0) {
printf("shmget error\n");
exit(1);
}
/* now the shared memory ID is stored in shm_id */
If a client wants to use a shared memory created with IPC_PRIVATE, it must be a child process of the server, created after the parent has obtained the shared memory, so that the private key value can be passed to the child when it is created. For a client, changing IPC_CREAT | 0666 to 0666 works fine. A warning to novice C programmers: don't change 0666 to 666. The leading 0 of an integer indicates that the integer is an octal number. Thus, 0666 is 110110110 in binary. If the leading zero is removed, the integer becomes six hundred sixty six with a binary representation 1111011010.
Server and clients can have a parent/client relationship or run as separate and unrelated processes. In the former case, if a shared memory is requested and attached prior to forking the child client process, then the server may want to use IPC_PRIVATE since the child receives an identical copy of the server's address space which includes the attached shared memory. However, if the server and clients are separate processes, using IPC_PRIVATE is unwise since the clients will not be able to request the same shared memory segment with a unique and unknown key.
Suppose process 1, a server, uses shmget() to request a shared memory segment successfully. That shared memory segment exists somewhere in the memory, but is not yet part of the address space of process 1 (shown with dashed line below). Similarly, if process 2 requests the same shared memory segment with the same key value, process 2 will be granted the right to use the shared memory segment; but it is not yet part of the address space of process 2. To make a requested shared memory segment part of the address space of a process, use shmat().
Attaching a Shared Memory Segment to an Address Space - shmat()

After a shared memory ID is returned, the next step is to attach it to the address space of a process. This is done with system call shmat(). The use of shmat() is as follows:
shm_ptr = shmat(int shm_id, /* shared memory ID */
char *ptr, /* a character pointer */
int flag); /* access flag */
System call shmat() accepts a shared memory ID, shm_id, and attaches the indicated shared memory to the program's address space. The returned value is a pointer of type (void *) to the attached shared memory. Thus, casting is usually necessary. If this call is unsuccessful, the return value is -1. Normally, the second parameter is NULL. If the flag is SHM_RDONLY, this shared memory is attached as a read-only memory; otherwise, it is readable and writable.
In the following server's program, it asks for and attaches a shared memory of four integers.





int shm_id;
key_t mem_key;
int *shm_ptr;

mem_key = ftok(".", 'a');
shm_id = shmget(mem_key, 4*sizeof(int), IPC_CREAT | 0666);
if (shm_id < 0) {
printf("*** shmget error (server) ***\n");
exit(1);
}

shm_ptr = (int *) shmat(shm_id, NULL, 0); /* attach */
if ((int) shm_ptr == -1) {
printf("*** shmat error (server) ***\n");
exit(1);
}
The following is the counterpart of a client.





int shm_id;
key_t mem_key;
int *shm_ptr;

mem_key = ftok(".", 'a');
shm_id = shmget(mem_key, 4*sizeof(int), 0666);
if (shm_id < 0) {
printf("*** shmget error (client) ***\n");
exit(1);
}

shm_ptr = (int *) shmat(shm_id, NULL, 0);
if ((int) shm_ptr == -1) { /* attach */
printf("*** shmat error (client) ***\n");
exit(1);
}
Note that the above code assumes the server and client programs are in the current directory. In order for the client to run correctly, the server must be started first and the client can only be started after the server has successfully obtained the shared memory.
Suppose process 1 and process 2 have successfully attached the shared memory segment. This shared memory segment will be part of their address space, although the actual address could be different (i.e., the starting address of this shared memory segment in the address space of process 1 may be different from the starting address in the address space of process 2).

Detaching and Removing a Shared Memory Segment - shmdt() and shmctl()
System call shmdt() is used to detach a shared memory. After a shared memory is detached, it cannot be used. However, it is still there and can be re-attached back to a process's address space, perhaps at a different address. To remove a shared memory, useshmctl().
The only argument of the call to shmdt() is the shared memory address returned by shmat(). Thus, the following code detaches the shared memory from a program:
shmdt(shm_ptr);
where shm_ptr is the pointer to the shared memory. This pointer is returned by shmat() when the shared memory is attached. If the detach operation fails, the returned function value is non-zero.
To remove a shared memory segment, use the following code:
shmctl(shm_id, IPC_RMID, NULL);
where shm_id is the shared memory ID. IPC_RMID indicates this is a remove operation. Note that after the removal of a shared memory segment, if you want to use it again, you should use shmget()followed by shmat().

Examples:-

Communicating Between Parent and Child

The following main function runs as a server. It uses IPC_PRIVATE to request a private shared memory. Since the client is the server's child process created after the shared memory has been created and attached, the child client process will receive the shared memory in its address space and as a result no shared memory operations are required.






void ClientProcess(int []);

void main(int argc, char *argv[])
{
int ShmID;
int *ShmPTR;
pid_t pid;
int status;

if (argc != 5) {
printf("Use: %s #1 #2 #3 #4\n", argv[0]);
exit(1);
}

ShmID = shmget(IPC_PRIVATE, 4*sizeof(int), IPC_CREAT | 0666);
if (ShmID < 0) {
printf("*** shmget error (server) ***\n");
exit(1);
}
printf("Server has received a shared memory of four integers...\n");

ShmPTR = (int *) shmat(ShmID, NULL, 0);
if ((int) ShmPTR == -1) {
printf("*** shmat error (server) ***\n");
exit(1);
}
printf("Server has attached the shared memory...\n");

ShmPTR[0] = atoi(argv[1]);
ShmPTR[1] = atoi(argv[2]);
ShmPTR[2] = atoi(argv[3]);
ShmPTR[3] = atoi(argv[4]);
printf("Server has filled %d %d %d %d in shared memory...\n",
ShmPTR[0], ShmPTR[1], ShmPTR[2], ShmPTR[3]);

printf("Server is about to fork a child process...\n");
pid = fork();
if (pid < 0) {
printf("*** fork error (server) ***\n");
exit(1);
}
else if (pid == 0) {
ClientProcess(ShmPTR);
exit(0);
}

wait(&status);
printf("Server has detected the completion of its child...\n");
shmdt((void *) ShmPTR);
printf("Server has detached its shared memory...\n");
shmctl(ShmID, IPC_RMID, NULL);
printf("Server has removed its shared memory...\n");
printf("Server exits...\n");
exit(0);
}

void ClientProcess(int SharedMem[])
{
printf(" Client process started\n");
printf(" Client found %d %d %d %d in shared memory\n",
SharedMem[0], SharedMem[1], SharedMem[2], SharedMem[3]);
printf(" Client is about to exit\n");
}

This program asks for a shared memory of four integers and attaches this shared memory segment to its address space. Pointer ShmPTR points to the shared memory segment. After this is done, we have the following:

Then, this program forks a child process to run function ClientProcess(). Thus, two identical copies of address spaces are created, each of which has a variable ShmPTR whose value is a pointer to the shared memory. As a result, the child process has already known the location of the shared memory segment and does not have to use shmget() and shmat(). This is shown below:

The parent waits for the completion of the child. For the child, it just retrieves the four integers, which were stored there by the parent before forking the child, prints them and exits. The wait() system call in the parent will detect this. Finally, the parent exits.

Communicating Between Two Separate Processes

In this example, the server and client are separate processes. First, a naive communication scheme through a shared memory is established. The shared memory consists of one status variable status and an array of four integers. Variable status has value NOT_READY if the data area has not yet been filled with data, FILLED if the server has filled data in the shared memory, and TAKEN if the client has taken the data in the shared memory. The definitions are shown below.

Create header file as “shm1.h” with below data..
NOT_READY -1
FILLED 0
TAKEN 1

struct Memory {
int status;
int data[4];
};
Assume that the server and client are in the current directory. The server uses ftok() to generate a key and uses it for requesting a shared memory. Before the shared memory is filled with data, status is set to NOT_READY. After the shared memory is filled, the server sets status to FILLED. Then, the server waits until status becomes TAKEN, meaning that the client has taken the data.

The following is the server program.







"shm1.h"

void main(int argc, char *argv[])
{
key_t ShmKEY;
int ShmID;
struct Memory *ShmPTR;

if (argc != 5) {
printf("Use: %s

Want your school to be the top-listed School/college in Bangalore?

Click here to claim your Sponsored Listing.

Location

Telephone

Website

Address


Bangalore
560037