490 likes | 516 Views
Course code: 10CS62. Interprocess Communication-1. Engineered for Tomorrow. Prepared by : M.Chandana Department of CSE. Contents. Pipes Popen and pclose Functions Coprocesses FIFOs System V IPC Message Queues Semaphores. Engineered for Tomorrow. Pipes: pipe(2). #include <unistd.h>
E N D
Course code: 10CS62 Interprocess Communication-1 Engineered for Tomorrow Prepared by : M.Chandana Department of CSE
Contents • Pipes • Popen and pclose Functions • Coprocesses • FIFOs • System V IPC • Message Queues • Semaphores.
Engineered for Tomorrow Pipes: pipe(2) #include <unistd.h> int pipe(int filedes[2]); Returns: 0 if OK, -1 otherwise oldest and most common form of UNIX IPC half-duplex (on some versions full-duplex)
Engineered for Tomorrow Pipes: pipe(2)
Engineered for Tomorrow Pipes: pipe(2)
Engineered for Tomorrow Pipes: pipe(2)
Engineered for Tomorrow Pipes: pipe(2) • $ cc -Wall pipe1.c; • $ ./a.out • P=> Parent process with pid 23988 (and its ppid 7474). • P=> Sending a message to the child process (pid 23989): • C=> Child process with pid 23989 (and its ppid 23988). • C=> Reading a message from the parent (pid 23988): • Hello child! I’m your parent pid 23988! • $
Engineered for Tomorrow Pipes: pipe(2) • $ cc -Wall pipe1.c; • $ ./a.out • P=> Parent process with pid 23986 (and its ppid 7474). • P=> Sending a message to the child process (pid 23987): • C=> Child process with pid 23987 (and its ppid 23986). • C=> Reading a message from the parent (pid 1): • Hello child! I’m your parent pid 23986! • $
Engineered for Tomorrow Pipes: pipe(2) A more useful example: displaying some content using the user’s preferred pager. (Look, Ma, no temporary files!) $ cat pipe2.c | ${PAGER:-/usr/bin/more} $ cc -Wall pipe2.c $ echo $PAGER $ ./a.out pipe2.c [...] ^Z $ ps -o pid,ppid,command PID PPID COMMAND 22306 26650 ./a.out pipe2.c 22307 22306 more 23198 26650 ps -o pid,ppid,command 26650 26641 -ksh $ fg $ env PAGER=/bin/cat ./a.out pipe2.c
Engineered for Tomorrow Pipes: pipe(2) #include <unistd.h> int pipe(int filedes[2]); Returns: 0 if OK, -1 otherwise • oldest and most common form of UNIX IPC • half-duplex (on some versions full-duplex) • can only be used between processes that have a common ancestor • can have multiple readers/writers (PIPE BUF bytes are guaranteed to • not be interleaved) • Behavior after closing one end: • read(2) from a pipe whose write end has been closed returns 0 after • all data has been read • write(2) to a pipe whose read end has been closed generates • SIGPIPE signal. If caught or ignored, write(2) returns an error and • sets errno to EPIPE.
Engineered for Tomorrow Pipes: popen(3) and pclose(3) #include <stdio.h> FILE *popen(const char *cmd, const char *type); Returns: file pointer if OK, NULL otherwise int pclose(FILE *fp); Returns: termination status cmd or -1 on error • Historically implemented using unidirectional pipe (nowadays frequently implemented using sockets or full-duplex pipes) • Type one of “r” or “w” (or “r+” for bi-directional communication, if available) • cmd passed to /bin/sh -c
Engineered for Tomorrow Pipes: popen(3) and pclose(3) $ cc -Wall popen.c $ echo $PAGER $ ./a.out popen.c [...] $ env PAGER=/bin/cat ./a.out popen.c [...] $
Engineered for Tomorrow Pipes: popen(3) and pclose(3) $ cc -Wall popen.c $ echo $PAGER $ ./a.out popen.c [...] $ env PAGER=/bin/cat ./a.out popen.c [...] $ env PAGER=/bin/cat/foo ./a.out popen.c sh: /bin/cat/foo: Not a directory $ env PAGER="more; touch /tmp/boo" ./a.out popen.c $ env PAGER="more; rm /etc/passwd 2>/dev/null" ./a.out popen.c
Engineered for Tomorrow Coprocesses • A UNIX system filter is a program that reads from standard input and writes to standard output. • Filters are normally connected linearly in shell pipelines. • A filter becomes a coprocess when the same program generates the filter’s input and reads the filter ’s output. • A coprocess normally runs in the background from a shell, and its • standard input and standard output are connected to another program using a pipe.
Engineered for Tomorrow • popen gives us a one-way pipe to the standard input or from the standard output of another process, with a coprocess we have two one-way pipes to the other process: • one to its standard input and one from its standard output. • We want to write to its standard input, let it operate on the data, and then read from its standard output.
Engineered for Tomorrow Simple filter to add two numbers #include "apue.h" int main(void) { int n, int1, int2; char line[MAXLINE]; while ((n = read(STDIN_FILENO, line, MAXLINE)) > 0) { line[n] = 0; /* null terminate */ if (sscanf(line, "%d%d", &int1, &int2) == 2) { sprintf(line, "%d\n", int1 + int2); n = strlen(line); if (write(STDOUT_FILENO, line, n) != n) err_sys("write error"); } else { if (write(STDOUT_FILENO, "invalid args\n", 13) != 13) err_sys("write error"); } } exit(0); }
Engineered for Tomorrow FIFOs: mkfifo(2) #include <sys/stat.h> int mkfifo(const char *path, mode t mode); Returns: 0 if OK, -1 otherwise • Also called as “named pipes” • Allows unrelated processes to communicate • Just a type of file – test for using S ISFIFO(st mode) • Mode same as for open(2) • Use regular I/O operations (ie open(2), read(2), write(2), unlink(2) etc.) • Used by shell commands to pass data from one shell pipeline to • Another without creating intermediate temporary files
Engineered for Tomorrow FIFOs: mkfifo(2) Example: split input into sets
Engineered for Tomorrow FIFOs: mkfifo(2) Example: split input into sets $ mkfifo fifo $ grep pattern fifo > match & $ gzcat file.gz | tee fifo | grep -v pattern > nomatch
Engineered for Tomorrow Client-server communication using FIFOs
Engineered for Tomorrow XSI IPC 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. • The identifier is an internal name for an IPC object. • Cooperating processes need an external naming scheme to be able to rendezvous using the same IPC object. • For this purpose, an IPC object is associated with a key that acts as an external name. • Whenever an IPC structure is being created, a key must be specified. The data type of this key is the primitive system data type key_t, which is often defined as a long integer in the header <sys/types.h>. This key is converted into an identifier by the kernel.
Engineered for Tomorrow There are various ways for a client and a server to rendezvous at the same IPC structure. • The server can create a new IPC structure by specifying a key of IPC_PRIVATE and store the returned identifier somewhere (such as a file) for the client to obtain. The key IPC_PRIVATE guarantees that the server creates a new IPC structure . • The client and the server can agree on a key by defining the key in a common header, for example. The server then creates a new IPC structure specifying this key. • The client and the server can agree on a pathname and project ID (the project ID is a character value between 0 and 255) and call the function ftok to convert these two values into a key. This key is then used in step 2. The only service provided by ftok is a way of generating a key from a pathname and project ID.
Engineered for Tomorrow • #include <sys/ipc.h> key_t ftok(const char *path, int id); Returns: key if OK, (key_t)-1 on error • The path argument must refer to an existing file. • Only the lower 8 bits of id are used when generating the key. • The key created by ftok is usually formed by taking parts of the st_dev and st_ino fields in the stat structure corresponding to the given pathname and combining them with the project ID. If two pathnames refer to two different files, then ftok usually returns two different keys for the two pathnames.
Engineered for Tomorrow Permission Structure • XSI IPC associates an ipc_perm structure with each IPC structure. This structure defines the permissions and owner and includes at least the following members: 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 */ . . . };
Engineered for Tomorrow • A fundamental problem with XSI IPC is that the IPC structures are systemwide 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, by someone executing the ipcrm(1) command, or by the system being rebooted. • Another problem with XSI IPC is that these IPC structures are not known by names in the file system. We can't access them and modify their properties with the functions. • Since these forms of IPC don't use file descriptors, we can't use the multiplexed I/O functions (select and poll) with them.
Engineered for Tomorrow System V IPC • Three types of IPC originating from System V: • Semaphores • Shared Memory • Message Queues • All three use IPC structures, referred to by an identifier and a key; all three are (necessarily) limited to communication between processes on • one and the same host. • Since these structures are not known by name, special system calls (msgget(2), semop(2), shmat(2), etc.) and special userland commands (ipcrm(1), ipcs(1), etc.) are necessary.
Engineered for Tomorrow System V IPC: Semaphores • A semaphore is a counter used to provide access to a shared data object for multiple processes. To obtain a shared resource a process needs to do the following: • 1. Test semaphore that controls the resource. • 2. If value of semaphore > 0, decrement semaphore and use resource; increment semaphore when done • 3. If value == 0 sleep until value > 0 • Semaphores are obtained using semget(2), properties controlled using semctl(2), operations on a semaphore performed using semop(2).
Engineered for Tomorrow • 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 */ . . . };
Engineered for Tomorrow • The kernel maintains a semid_ds structure for each semaphore set: struct semid_ds { struct ipc_perm sem_perm; unsigned short sem_nsems; time_t sem_otime; /* last-semop() time */ time_t sem_ctime; /* last-change time */ . . . };
Engineered for Tomorrow • The first function to call is semget to obtain a semaphore ID. #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. The mode member of this structure is set to the corresponding permission bits of flag. • 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.
Engineered for Tomorrow • The semctl function is the catchall for various semaphore operations. #include <sys/sem.h> int semctl(int semid, int semnum, int cmd,... /* union semun arg */); • The fourth argument is optional, depending on the command requested, and if present, is of type semun, a union of various command-specific arguments: union semun { int val; /* for SETVAL */ struct semid_ds *buf; /* for IPC_STAT and IPC_SET */ unsigned short *array; /* for GETALL and SETALL */ };
Engineered for Tomorrow The function semop atomically performs an array of operations on a semaphore set. #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.
Engineered for Tomorrow • The sem_op element operations are values specifying the amount by which the semaphore value is to be changed. • If sem_op is an integer greater than zero, semop adds the value to the corresponding semaphore element value and awakens all processes that are waiting for the element to increase. • If sem_op is 0 and the semaphore element value is not 0, semop blocks the calling process (waiting for 0) and increments the count of processes waiting for a zero value of that element. • If sem_op is a negative number, semop adds the sem_op value to the corresponding semaphore element value provided that the result would not be negative. If the operation would make the element value negative, semop blocks the process on the event that the semaphore element value increases. If the resulting value is 0, semop wakes the processes waiting for 0.
Engineered for Tomorrow System V IPC: Semaphores $ cc -Wall semdemo.c 1$ ./a.out 2$ ./a.out $ ipcs -s
Engineered for Tomorrow System V IPC: Shared Memory • Fastest form of IPC • Access to shared region of memory often controlled using semaphores • Obtain a shared memory identifier using shmget(2) • Catchall for shared memory operations: shmctl(2) • Attach shared memory segment to a processes address space by calling shmat(2) • Detach it using shmdt(2)
Engineered for Tomorrow System V IPC: Shared Memory $ cc -Wall shmdemo.c $ ./a.out "Cow says: ’Moo!’" $ ./a.out $ ipcs -m
Engineered for Tomorrow System V IPC: Shared Memory • $ cc -Wall memory-layout.c • $ ./a.out • array[] from 804a080 to 8053cc0 • stack around bffff9e4 • malloced from 8053cc8 to 806c368 • shared memory attached from 40162000 to 4017a6a0
Engineered for Tomorrow System V IPC: Message Queues • Linked list of messages stored in the kernel • Create or open existing queue using msgget(2) • Add message at end of queue using msgsnd(2) • Control queue properties using msgctl(2) • Receive messages from queue using msgrcv(2) The message itself is contained in a user-defined structure such as Struct mymsg { Long mtype; /* message type */ Char mtext[512]; /* body of message */ };
Engineered for Tomorrow • Each queue has the following msqid_ds structure associated with it: struct msqid_ds { struct ipc_perm msg_perm; /* see Section 15.6.2 */ msgqnum_t msg_qnum; /* # of messages on queue */ msglen_t msg_qbytes; /* max # of bytes on queue */ pid_t msg_lspid; /* pid of last msgsnd() */ pid_t msg_lrpid; /* pid of last msgrcv() */ time_t msg_stime; /* last-msgsnd() time */ time_t msg_rtime; /*last-msgrcv() time */ time_t msg_ctime; /* last-change time */ . . . };
Engineered for Tomorrow System V IPC: Message Queues $ cc -Wall msgsend.c -o msgsend $ cc -Wall msgrecv.c -o msgrecv $ ipcs -q $ ./msgsend 1 $ ipcs -q $ ./msgsend 1 $ ipcs -q $ ./msgrecv 1 $ ipcs -q $ ./msgrecv 1 $ ipcs -q $ ./msgrecv 1 ^C $ ipcs -q $ ./msgsend 2 $ ipcrm -q <msqid>
Engineered for Tomorrow • This structure defines the current status of the queue. The first function normally called is msgget to either open an existing queue or create a new queue . #include <sys/msg.h> int msgget(key_t key, int flag); Returns: message queue ID if OK, 1 on error. • When a new queue is created, the following members of the msqid_ds structure are initialized: • The ipc_perm structure is initialized. The mode member of this structure is set to the corresponding permission bits of flag. • 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. This value is then used with the other three message queue functions.
Engineered for Tomorrow • The msgctl function performs various operations on a queue. #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.
Engineered for Tomorrow • Data is placed onto a message queue by calling msgsnd. #include <sys/msg.h> int msgsnd(int msqid, const void *ptr, size_t nbytes, int flag); Returns: 0 if OK, 1 on error. • Each message is composed of a positive long integer type field, a non-negative length (nbytes), and the actual data bytes (corresponding to the length). Messages are always placed at the end of the queue.
Engineered for Tomorrow • Messages are retrieved from a queue by msgrcv • . #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. • The type argument lets us specify which message we want.