Unit V Inter process communication Introduction 1. Pipes 2. FIFOs System V IPC 3. Message Queues 4 .Semaphores 5. Shared Memory What is IPC? Sharing of information between processes. IPC is a mechanism to transfer data between processes Why IPC? To share resources For client/server applications For modularity For convenience In unix we have three ways to share the information between processes P1 P2 1 P3 P4 2 P5 Shared memory P6 3 Shared information Filesystem 1) Two processes p1 and p2 sharing some information that resides in a file in filesystem. To access this data each process must go through the kernel (read, write). 2) Two process p3 and p4 are sharing information that resides with in the kernel. Each operation to access the shared information involves a system call to the kernel Ex:pipe,message queue, semaphores 3). Two process p5 and p6 have a region of shared memory that each process can reference. Once shared memory is set up by the each process, then the process can access the data in the shared memory without involving the kernel. We can implement the IPC using five techniques 1. Pipes 2. FIFOs 3. Message Queue 4. Shared Memory 5. Semaphores 1. Pipes: Pipes are the oldest form of unix ipc and are provided by all unix systems. Pipes are used to share the information between parent and child process. Parent process pipe (write) child process (read) Figure 1.1 Pipes have two limitations: 1. Pipes are half duplex (i.e data flows in only one direction). 2. Pipes can be used only between processes that have a common ancestor. Normally a pipe is created by a process, that process calls fork, and the pipe is used between the parent and the child. 1.1 Creation of pipe: pipe () function: pipe() function is used to create a pipe between parent and child. Syntax: # include<unistd.h> int pipe (int filedes [2]); Returns:0 if ok, -1 on error Two file descriptors are returned through the filedes argument. file des[0] is open for reading file des [1] is open for writing The output of filedes [1] is the input for filedes [0]. Two ways to view a half-duplex pipe user process fd[0] user process fd[0] fd[1] Figure 1.2 fd[1] pipe kernel Figure1.3 Figure 1 shows the two ends of the pipe connected in a single process. Figure 2 shows that the data in the pipe flows through the kernel. An IPC channel is created between parent and child when the parent process calls pipe and then calls fork Pipe from parent to child: Parent child . fd[1] fd[0] pipe Kernel Figure 1.4 For a pipe from the parent to child , the parent closes the read end of the pipe(fd[0]) and the child closes the write end (fd[1]). Pipe from child to parent: Parent child . fd[0] fd[1] pipe Kernel Figure 1.5 For a pipe from the child to parent, the parent closes fd[1] and the child closes the fd[0]. Ex: Write a c program to provide IPC using pipes? #include<stdio.h> #include<fcntl.h> #include<unistd.h> #include<sys/stat.h> int main() { char str[20]; pid_t pid; int fd[2],n; pipe(fd); pid=fork(); if(pid>0) { close(fd[0]); write(fd[1],"IPC using pipes",14); } else { close(fd[1]); n=read(fd[0],str,2); write(STDOUT_FILENO,str,n); } } Drawbacks of pipes: Pipes are used only between related processes. When a common ancestor has created the pipe. Pipes are not permanent, a process creates the pipe and the termination of that process leads to their destruction. 2. FIFOs FIFOs are also called named pipes. By using FIFOs, unrelated processes also exchange the data. FIFOs are permanent. FIFO is a file. Creating a FIFO is similar to creating a file. 2.1 Creation of FIFO: mkfifo() function: mkfifo function is used to create a new FIFO. Syntax: #include<sys/types.h> #include<sys/stat.h> int mkfifo(const char *pathname, mode_t mode); Returns:0 if OK , -1 on error Here pathname is the name of FIFO Specification of the mode is same as open() function. Open() function is used to open an existing FIFO. Normal I/O functions all work with FIFOs. 2.2 Uses of FIFOs: FIFOs are used by shell commands to pass data from one shell pipeline to another without creating intermediate temporary files. FIFOs are used in client-server applications to pass data between thye clients and the servers. 2.2.1 Example Using FIFOs to Duplicate Output Streams FIFOs can be used to duplicate an output stream in a series of shell commands. This prevents writing the data to an intermediate disk file (similar to using pipes to avoid intermediate disk files). But whereas pipes can be used only for linear connections between processes, a FIFO has a name, so it can be used for nonlinear connections. Consider a procedure that needs to process a filtered input stream twice. Figure 2.1 shows this arrangement. With a FIFO and the UNIX program tee we can accomplish this procedure without using a temporary file. (The tee program copies its standard input to both its standard output and to the file named on its command line.) mkfifo fifo1 prog3 < fifo1 & prog1 < infile | tee fifo1 | prog2 We create the FIFO and then start prog3, reading from the FIFO. We then start Prog1 and use tee to send its input to both the FIFO and prog2. Figure 2 shows the process arrangement. Procedure that processes a filtered input stream twice Prog3 input file Prog1 Prog2 Figure 2.2.1 Using a FIFO and tee to send a stream to two different processes FIFO input file Prog1 Prog3 tee Prog2 Figure 2.2 2.2.2 Example Client-Server Communication Using a FIFO Here FIFOs are used to send data between a client and a server. If we have a server that is contacted by numerous clients, each client can write its request to a well-known FIFO. Well-known FIFO is created by server. Well-known FIFO means that the pathname of the FIFO is known to all the clients that need to contact the server. Server read request Well-known FIFo Write request write request client client Figure 2.2.2.1 In this each client writes the request into a well-known FIFO. Server reads the each client request from the well-known FIFO. Problem: The problem in using FIFOs for this type of client-server communication is how to send replies back from the server to each client. A single FIFO can't be used, as the clients would never know when to read their response versus responses for other clients. Solution: One solution is for each client to send its process ID with the request. The server then creates a unique FIFO for each client, using a pathname based on the client's process ID Server read request write replies write replies Client-specific FIFO read replies Well-known FIFO write request Client-specific FIFO write requests client client figure 2.2.2.2 read replies System V IPC: Message queue, shared memory and semaphores are called System V IPC because these three have many similarities. Identifiers and Keys Each IPC structure (message queue, semaphore, or shared memory segment) in the kernel is referred to by a non-negative integer identifier. To send or fetch a message to or from a message queue, we need the identifier for the queue. The identifier is an internal name for an IPC object. Permission Structure Each System V IPC techniques having the following permission structure. struct ipc_perm { uid_t uid; /* owner's effective user id */ gid_t gid; /* owner's effective group id */ uid_t cuid; /* creator's effective user id */ gid_t cgid; /* creator's effective group id */ mode_t mode; /* access modes */ . . . }; Configuration Limits All three forms of System V IPC have built-in limits that we may encounter. Most of these limits can be changed by reconfiguring the kernel Advantages and Disadvantages A fundamental problem with System V IPC is that the IPC structures are system wide and do not have a reference count. For example, if we create a message queue, place some messages on the queue, and then terminate, the message queue and its contents are not deleted. They remain in the system until specifically read or deleted by some process calling msgrcv or msgctl. Another problem with XSI IPC is that these IPC structures are not known by names in the file system. 3. Message QUEUE: Queue is a data structure Message is a small group of data Messages are passed between processes through a message queue. Message queue is a queue on to which the messages are placed. A message queue is a linked list of messages stored within the kernel and identified by a message queue identifier. Message with in a queue are of different types. Data structures related to Message queue: Struct msqid_ds { struct ipc_perm msg_perm; struct msg *msg_first; /*Pointer to 1st msg on queue*/ struct msg *msg_last; /*Pointer to last msg on queue*/ ulong msg_cbytes;/*current number of bytes on queue*/ ulong msg_qnum;/* number of messages on queue*/ ulong msg_qbytes;/*max number of bytes on queue*/ pid_t msg_lspid;/*pid of last msgsnd()*/ pid_t msg_lrpid;/*pid of last msgrcv()*/ time_t stime;/*last msgsnd()*/ time_t rtime;/*last msgrcv()*/ time_t ctime;/*last change time*/ .} 3.1 Creating a message queue: msgget()function: used to create a new message queue or to open an existing message queue. Syntax: #include <sys/msg.h> int msgget(key_t key, int flag); Returns: message queue ID if OK, 1 on error In either of the following cases a new IPC structure is created. key is IPC-PRIVATE flag is IPC-CREAT When a new queue is created the following members of the msqid_ds structure are initialized ipc-perm structure is initialized msg_qnum, msg_lspid, msg_lrpid, msg_stime, and msg_rtime are all set to 0. msg_ctime is set to the current time. msg_qbytes is set to the system limit. On success, msgget returns the non-negative queue ID. Eg: /*Program to create a message queue*/ #include<sys/types.h> #include<sys/msg.h> #include<sys/ipc.h> int main() { key-t key=100; Int msqid; msqid=msgget(key,IPC-CREAT/0666); pf(“message queue id is%d”,msqid); } Message queue structure in the kernel: Ipc_perm[] NULL NULL NULL Msg_first type type type Msg_last length length length . data data data . . Msg.ctime Figure 3.1 3.2 Write/send a message into a message queue: msg snd()function: This function is used to place data on a message queue. Data is placed onto a message queue by calling msgsnd. Syntax: #include <sys/msg.h> int msgsnd(int msqid, const void *ptr, size_t nbytes, int flag); Returns: 0 if OK, 1 on error Ptr argument is the pointer to a message structure. Messages are placed at the end of the queue When msgsnd()returns successfully, then msqid_ds structure associated with the message queue is updated. struct message { long m type,/*message type*/ char mtext[512],/*message data*/ }; 3.3 Fetch/Receive a message from message queue: msgrcv() function: This function is used to retrieve data from a message queue Messages are retrieved from a queue by msgrcv. Syntax: #include <sys/msg.h> ssize_t msgrcv(int msqid, void *ptr, size_t nbytes , long type, int flag); Returns: size of data portion of message if OK, 1 on error Ptr argument points to a message structure. N bytes specify the size of the data The type argument lets us specify which message we want. type == 0 The first message on the queue is returned. type > 0 The first message on the queue whose message type equals type is returned. type < 0 The first message on the queue whose message type is the lowest value less than or equal to the absolute value of type is returned. We can specify the flag value as IPC_NOWAIT to make the operation non-blocking. 3.4 msgctl() function: The msgctl function performs various operations on a queue Used to know the status of a message queue and to delete a message queue from the system. Syntax: #include <sys/msg.h> int msgctl(int msqid, int cmd, struct msqid_ds *buf ); Returns: 0 if OK, 1 on error The cmd argument specifies the command to be performed on the queue specified by msqid. IPC_STAT Fetch the msqid_ds structure for this queue, storing it in the structure pointed to by buf. IPC_SET Copy the following fields from the structure pointed to by buf to the msqid_ds structure associated with this queue: msg_perm.uid,msg_perm.gid,msg_perm.mode, and msg_qbytes. msg_perm.cuid or msg_perm.uid IPC_RMID Remove the message queue from the system and any data still on the queue. This removal is immediate. Any other process using the message queue will get an error . a) Write a ‘c’ program to create a message queue and send a message into the queue. #include<stdio.h> #include<sys/ipc.h> #include<sys/msg.h> #include<sys/types.h> struct mesg { long type; char mtext[252]; } mesg; main () { int msqid,len; if((msqid=msgget((key_t)10,IPC_CREAT|0666))<0) { printf("not"); } printf("qid is=%d",msqid); mesg.type=6; strcpy(mesg.mtext,"example for mq"); len=strlen(mesg.mtext); if(msgsnd(msqid,&mesg,len,0)==-1) printf("write error"); printf("data is placed successfully"); } O/P: mesg que id is =0. Data is placed into the queue=example of mq. b) Read the message in the message queue written in the previous program. #include<stdio.h> #include<sys/msg.h> #include<sys/ipc.h> #include<sys/types.h> struct mesg { long type; char mtext[255]; }mesg; main() { int msqid; if((msqid=msgget((key_t)10,IPC_CREAT|0666))<0) printf("error"); printf("received mq id is=%d",msqid); if((msgrcv(msqid,&mesg,255,6,IPC_NOWAIT))<0) printf("ERRROR"); printf("%s",mesg.mtext); } 4. Shared memory Shared memory is region of memory that is shared by two or more processes. Shared memory allows two or more processes to share a given region of memory. This is the fastest form of IPC, because the data does not need to be copied between the client and the server. The only trick in using shared memory is synchronizing access to a given region among multiple processes. If the server is placing data into a shared memory region, the client shouldn't try to access the data until the server is done. Often, semaphores are used to synchronize shared memory access. Address space for process A Address space for Physical memory Shared memory region Figure 4.1 Process B Data structures for Shared memory: The kernel maintains a structure for each shared memory segment struct shmid_ds { struct ipc_perm shm_perm; size_t shm_segsz; /* size of segment in bytes */ pid_t shm_lpid; /* pid of last shmop () */ pid_t shm_cpid; /* pid of creator */ shmatt_t shm_nattch; /* number of current attaches */ time_t shm_atime; /* last-attach time */ time_t shm_dtime; /* last-detach time */ time_t shm_ctime; /* last-change time */ . . . }; 4.1Creation of shared memory segment: shmget() function: This function is used to create or obtain a shared memory segment. Syntax: #include <sys/shm.h> int shmget(key_t key, size_t size, int flag); Returns: shared memory ID if OK, 1 on error Key is the address of shared memory segment. When a new segment is created, the following members of the shmid_ds structure are initialized. The ipc_perm structure is initialized shm_lpid, shm_nattach, shm_atime, and shm_dtime are all set to 0. shm_ctime is set to the current time. shm_segsz is set to the size requested. The size parameter is the size of the shared memory segment in bytes 4.2 Attaching a shared memory segment to the process address space: shmat() function This function is used to attach a shared memory segment to a process address space. Once a shared memory segment has been created, a process attaches it to its address space by calling shmat. Syntax: #include <sys/shm.h> void *shmat(int shmid, const void *addr, int flag); Returns: pointer to shared memory segment if OK, 1 on error Based on addr value shared memory segment is attach to process address space If addr is 0, the segment is attached at the first available address selected by the kernel. This is the recommended technique. If addr is nonzero and SHM_RND is not specified, the segment is attached at the address given by addr. If addr is nonzero and SHM_RND is specified, the segment is attached at the address given by addr 4.3 Detaching shared memory segment from the process address space: shmdt() function This function is used to detach a shared memory segment from a process address space Syntax: #include <sys/shm.h> int shmdt(void *addr); Returns: 0 if OK, 1 on error The addr argument is the value that was returned by a previous call to shmat. If successful, shmdt will decrement the shm_nattch counter in the associated hmid_ds structure. shmctl() function: The shmctl function is the catchall for various shared memory operations. This function is used to remove a shared memory segment from a system and also used to know the status of a shared memory segment Syntax : #include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf); Returns: 0 if OK, 1 on error The cmd argument specifies one of the following commands to be performed, on the segment specified by shmid. If cmd= IPC_STAT Fetch the shmid_ds structure for this segment, storing it in the structure pointed to by buf if cmd=IPC_RMID Remove the shared memory segment set from the system. if cmd=IPC_SET Set the following three fields from the structure pointed to by buf in the shmid_ds structure associated with this shared memory segment: shm_perm.uid,shm_perm.gid, and shm_perm.mode Ex: Write a c program that illustrates the inter process communication using shared memory Process A: #include<stdio.h> #include<sys/shm.h> #include<sys/types.h> #include<sys/ipc.h> #include<string.h> main() { int shmid,flag; key_t key=0x1000; char *msg; shmid=shmget(key,10,IPC_CREAT|0666); if(shmid<0) { printf("error"); } printf("%d\n",shmid); msg=shmat(shmid,0,0); //printf("%u",msg); strcpy(msg,"example for sharedmemory"); //printf("%s",msg); //write(1,msg,strlen(msg)); } Process B #include<stdio.h> #include<sys/shm.h> #include<sys/types.h> #include<sys/ipc.h> main() { int shmid; key_t key=0x1000; char *msg; shmid=shmget(key,10,IPC_CREAT|0666); if(shmid<0) { printf("error"); } printf("id is%d",shmid); msg=shmat(shmid,0,0); //read(shmid,msg,strlen(msg)); printf("%s",msg); } 5. Semaphores: A semaphore is not a form of IPC similar to the pipes, FIFOs, and message queues. A semaphore is a counter used to provide access to a shared data object for multiple processes. Semaphore is an integer. Semaphore is used to control shared resource. Semaphore is a synchronization tool. 5.1 Types of semaphores Binary semaphore: It controls a single resource, and its value is initialized to 1. In general If the semaphore value is zero means resource is in use. .If the semaphore value is one means resource is available. Counting semaphore: a semaphore whose value is between zero or and some limit. A semaphore can be initialized to any positive value, with the value indicating how many units of the shared resource are available for sharing To obtain a shared resource, a process needs to do the following: 1. Test the semaphore that controls the resource. 2. If the value of the semaphore is positive, the process can use the resource. In this case, the process decrements the semaphore value by 1, indicating that it has used one unit of the resource. 3.If the value of the semaphore is 0, the process goes to sleep until the semaphore value is greater than 0. When the process wakes up, it returns to step 1. 4. When a process is done with a shared resource that is controlled by a semaphore, the semaphore value is incremented by 1. If any other processes are asleep, waiting for the semaphore, they are awakened. Data structures for semaphore: The kernel maintains a semid_ds structure for each semaphore set: Struct semid_ds { struct ipc_perm sem_perm; unsigned short sem_nsems; /* # of semaphores in set */ time_t sem_otime; /* last-semop() time */ time_t sem_ctime; /* last-change time */ . . . }; Each semaphore is represented by an anonymous structure containing at least the following members: struct { unsigned short semval; /* semaphore value, always >= 0 */ pid_t sempid; /* pid for last operation */ unsigned short semncnt; /* # processes awaiting semval>curval */ unsigned short semzcnt; /* # processes awaiting semval==0 */ . . . }; 5.2 Creation of semaphore: semget() function This function is used to create a new semaphore or obtain a semaphore ID. Syntax: #include <sys/sem.h> int semget(key_t key, int nsems, int flag); Returns: semaphore ID if OK, 1 on error When a new set is created, the following members of the semid_ds structure are initialized. The ipc_perm structure is initialized sem_otime is set to 0. sem_ctime is set to the current time. sem_nsems is set to nsems. The number of semaphores in the set is nsems. If a new set is being created (typically in the server),we must specify nsems. If we are referencing an existing set (a client), we can specify nsems as 0. Ex:Write a c program to create a new semaphore? #include<stdio.h> #include<sys/types.h> #include<sys/ipc.h> #include<sys/sem.h> int main() { int semid; key=(key_t)0x20; semid=semget(key,1,IPC_creat|0666); printf(“semid=%d”,semid); } 5.3 Operations on semaphore: semop() function The function semop atomically performs an array of operations on a semaphore set. Syntax: #include <sys/sem.h> int semop(int semid, struct sembuf semoparray[],size_t nops); Returns: 0 if OK, 1 on error The semoparray argument is a pointer to an array of semaphore operations, represented by sembuf structures: struct sembuf { unsigned short sem_num; /* member # in set (0, 1, ..., nsems-1) */ short sem_op; /* operation (negative, 0, or positive) */ short sem_flg; /* IPC_NOWAIT, SEM_UNDO */ }; The nops argument specifies the number of operations (elements) in the array. The operation on each member of the set is specified by the corresponding sem_op value. This value can be negative, 0, or positive 5.4 semctl function The semctl function is the catchall for various semaphore operations. This function is used for the following 1 .To remove the semaphore set from the system. 2. To know the status of semaphore set. 3. To set the values to semaphore. 4. To get the semaphore values. Syntax : #include <sys/sem.h> int semctl(int semid, int semnum, int cmd, ... /* union semun arg */); The cmd argument specifies one of the following ten commands to be performed on the set specified by semid. IPC_STAT Fetch the semid_ds structure for this set, storing it in the structure pointed to by arg.buf. IPC_SET Set the sem_perm.uid, sem_perm.gid, and sem_perm.mode fields from the structure pointed to by arg.buf in the semid_ds structure associated with this set. IPC_RMID Remove the semaphore set from the system. Whose effective user ID equals sem_perm.cuid or sem_perm.uid GETVAL Return the value of semval for the member semnum. SETVAL Set the value of semval for the member semnum. The value is specified by arg.val. GETPID Return the value of sempid for the member semnum. GETNCNT Return the value of semncnt for the member semnum. GETZCNT Return the value of semzcnt for the member semnum. GETALL Fetch all the semaphore values in the set. These values are stored in the array pointed to by arg.array. SETALL Set all the semaphore values in the set to the values pointed to by arg.array.