Inter-process communication Trifon Ruskov ruskov@tu-varna.acad.bg Technical University of Varna - Bulgaria Process Communication and Synchronization The UNIX IPC (Inter-process communications) facilities provide methods for multiple processes to communicate with one another: Signals Half-duplex UNIX pipes Named pipes Message queues Shared memory segments Semaphore sets Trifon Ruskov Technical University - Varna 2 Signals in Unix 9 Signals are software-generated interrupts that are sent to a process when an event happens. 9 Signals can be posted to a process when the system detects a software event. 9 Signals can also come directly from the kernel when a hardware event is encountered. Each signal has a default action: The signal is discarded The process terminate Each signal falls into one of five classes: Hardware condition Software condition Input/output notification Process control Resource control Trifon Ruskov Technical University - Varna 3 Signals in Unix (cont.) Signal definitions are in <signal.h> #define #define #define #define #define #define #define SIGHUP SIGINT SIGQIUT SIGILL SIGTRAP SIGKILL SIGALRM 1 2 3 4 5 9 14 /* /* /* /* /* /* /* hang up */ interrupt */ quit */ illegal instruction */ hardware interrupt */ hard kill */ alarm clock */ etc. Trifon Ruskov Technical University - Varna 4 Sending Signals A system call that sens a signal (signal) to a process (pid) int kill (int pid, int signal) returns: 0 – successful call -1 – otherwise (errorno is set) From the Linux shell: kill –9 37 Trifon Ruskov Technical University - Varna 5 Signal Handling An application program can specify a function, called a signal handler, to be invoked when a specific signal is received. When a signal handler is invoked on receipt of a signal, it is said to catch the signal. A process can deal with a signal in one of the following ways: The process can let the default action happen. The process can block the signal (some signals cannot be ignored). The process can catch the signal with a handler. Receiving signals is straightforward with the function: int (* signal (int sig, void (*func)())) () The function signal() will call the function pointed to by func if the process receives a signal sig. Trifon Ruskov Technical University - Varna 6 Signal Handling (cont.) func can have three values: SIG_DFL – a pointer to the system default function SIG_DFL ( ) SIG_IGN – a pointer to the system ignore function SIG_IGN ( ) function – a user specified function Examples: 1. To ignore a Ctrl/C command from command line signal(SIGINT, SIG_IGN); 2. To reset system to default action signal(SIGINT, SIG_DFL); Trifon Ruskov Technical University - Varna 7 Signal Handling (cont.) 3. To trap a Ctrl/C but not quit on this signal #include <stdio.h> #include <signal.h> void sigproc(void); void quitproc(void); int main(){ signal(SIGINT, sigproc); signal(SIGQUIT, quitproc); for(;;); // infinite loop } void sigproc(){ signal(SIGINT, sigproc); printf(”CTRL/C pressed\n”); } void quitproc(){ exit(0); } Trifon Ruskov Technical University - Varna 8 Signal Handling (cont.) 4. To delete a temporary file after abnormal termination #include <signal.h> void cleanup(void); int main () { signal(SIGINT, cleanup); ... creat(tempfile, 0); ... unlink(tempfile); exit(0); } void cleanup() { unlink(tempfile); exit(1); } Trifon Ruskov Technical University - Varna 9 Signal Handling (cont.) Note: 9 Signals are reset to default when accepted by signal handler. Why? System call pause: pause(); Stop the process until some signal is received. Trifon Ruskov Technical University - Varna 10 Pipes in Unix Pipes provide one-way (half-duplex) communications between related processes. Pipes are implemented based on the kernel I/O buffers. A pipe is a tool for connecting the standard output of one process to the standard input of another process, typically used in UNIX shell pipelines. Example: Counting of files in current directory: ls > tmpfile && wc –w tmpfile && rm tmpfile The same example with a shell pipeline: ls | wc –w The pipeline reduces execution time by: Parallel execution of the commands Avoiding disk operations for temporary saving of intermediate data No temporary files to be deleted Trifon Ruskov Technical University - Varna 11 Pipes in Unix (cont.) Pipes are accessed in a similar way as ordinary files: They have assigned inodes. Reading and writing are through file descriptors. fd1 stdout P1 fd0 kernel I/O buffers stdin P2 Differences between pipes and files: The pipe inode resides in the kernel, not in the physical file system. A pipe can exist only during the existence of its creator process. A pipe has limited size. Trifon Ruskov Technical University - Varna 12 Creating Pipes System call that creates a pipe: int pipe(int fd[2]); Parameters: fd[] – array of file descriptors returns: 0 – successful call -1 – otherwise (errorno is set) This call opens two file descriptors: fd[0] – file descriptor for reading fd[1] – file descriptor for writing Example: Redirecting the standard output to a pipe int fd[2]; pipe(fd); close(1); dup2(1, fd[1]); close(fd[1]); Trifon Ruskov // // // // Create a pipe Close stdout Duplicate stdout to the fd[1] Close fd[1] Technical University - Varna 13 Using Pipes Process, created a pipe, can use it to communicate with a child process. A child process inherits any open file descriptors from the parent, including pipe descriptors. Communicating processes must close unused file descriptor of the pipe. Example: int main() { int fd[2], pid; } pipe(fd); if (pid=fork()) == -1 ) { perror(“fork”); exit(1); } if (pid == 0) close(fd[0]); // Child process closes input side of pipe else close(fd[1]); // Parent process closes output side of pipe Trifon Ruskov Technical University - Varna 14 Example: Implementation of ls | wc –w #include <stdio.h> #include <unistd.h> #include <sys/types.h> pipe int main() { stdout int fd[2], dead, status; if (fork()) wait(&status); else { ls pipe(fd); if (fork()) { close(1); dup2(1, fd[1]); close(fd[1]); close(fd[0]); execl(“/bin/ls”, “ls”, 0); } else { close(0); dup2(0, fd[0]); close(fd[0); close(fd[1]); execl(“/usr/bin/wc”, “wc”, “-w”, 0); } } } Trifon Ruskov Technical University - Varna stdin wc -w 15 Example: Read/Write through a Pipe #include <stdio.h> #include <unistd.h> #include <sys/types.h> int main(void){ int fd[2], nbytes, status; pid_t childpid; char string[] = "Hello!\n"; char readbuffer[20]; } fd[0] read parent fd[1] pipe write child pipe(fd); if(!fork()){ close(fd[0]); // Child process closes input side of pipe //Send "string" through the pipe write(fd[1], string, strlen(string)); exit(0); } else { close(fd[1]); // Parent process closes output side of pipe // Read in a string from the pipe nbytes = read(fd[0], readbuffer, sizeof(readbuffer)); printf("Received string: %s", readbuffer); wait(&status); } return(0); Trifon Ruskov Technical University - Varna 16 Inter-process communication with pipes and signals SIGFPE tty1 P1 tty2 P2 ttyn P0 tty0 main program Pn slave programs Trifon Ruskov pipe SIGTERM Technical University - Varna 17 Inter-process communication with pipes and signals (cont.) Slave program: #include <signal.h> #include <sys/types.h> #include <sys/tty.h> int main(int argc, char *argv[]) { int n; char bufer[TTYHOG]; for (;;) { n = read(0, buffer, TTYHOG]; if (n == 0) { // Reached EOF kill(atoi(argv[1]), SIGFPE); close(1); exit(NULL); } kill(atoi(argv[1])), SIGTERM); write(1, buffer, n); } } Trifon Ruskov Technical University - Varna 18 Inter-process communication with pipes and signals (cont.) Main program: #include #include #include #include <signal.h> <stdio.h> <sys/types.h> <sys/tty.h> int fd[2]; char pids[TTYHOG]; int slave_count; void service() { int n; char buffer[TTYHOG]; // SIGTERM processing signal(SIGTERM, service); n = read(fd[0], buffer, TTYHOG]; write(1, buffer, n); } void slavedrop() { signal(SIGFPE, slavedrop); If (--slave_count = 0) exit(NULL); } Trifon Ruskov // SIGFPE processing Technical University - Varna 19 Inter-process communication with pipes and signals (cont.) void slave(char *tty) { if (fork() == 0) { int tfd; // Slave program initialization // stdio redirection to a terminal close(0); tfd = open(tty, 0); if (tfd != 0) { fprintf(stderr, “bad tty %s\n”, tty); exit(1); } // stdout redirection to a pipe close(1); dup(fd[1]); close(fd[1]); close(fd[0]); // Execute the slave program execl(“./slave”, “slave”, pids, 0); fprintf(stderr, “Can’t exec slave on %s\n”, tty); exit(1); } } Trifon Ruskov Technical University - Varna 20 Inter-process communication with pipes and signals (cont.) // Main program. Started as <program_name> /dev/tty1 /dev/tty2 int main(int argc, char *argv[]) { int i; signal(SIGTERM, service); signal(SIGFPE, slavedrop); sprintf(pids, “%06d”, getpid()); pipe(fd); slave_count = argc – 1; for (i=1; i<argc; i++) slave(argv[i]); close(fd[1]); for (;;) pause(); } Trifon Ruskov Technical University - Varna 21 Named pipes Named pipes are used for inter-process communications. Features: Exist as special files in the physical file system Any unrelated processes can access a named pipe and share data through it Access to named pipes is regulated by the usual file permissions Pipe data is accessed in a FIFO style Once created, a named pipe remains in the file system until explicitly deleted Creation: By UNIX shell commands. Example: mknod <filename> p mkfifo a=rw <filename> By systems calls. Example: mknod(char *pathname, mode_t mode, dev_t dev); mknod(“/tmp/myfifo”, S_IFIFO | 0660, 0); Trifon Ruskov Technical University - Varna 22 Named pipes (cont.) Same I/O operations style on named pipes and regular files – open(), read() and write() calls. Implementation of I/O operations: By system calls. By library functions. Semantics of open() call: Blocking. The process that opens the named pipe for reading, sleeps until another process opens it for writing, and v.v. Non-blocking. Flag O_NONBLOCK, used in open() call disables default blocking. Pipes have size limitations. Trifon Ruskov Technical University - Varna 23 Named pipes. Example Client-server communication through a pipe Server #include <fcntl.h> ... #define PIPE "fifo" int main(){ int fd; char readbuf[20]; server pipe client mknod(PIPE, S_IFIFO | 0660, 0); // create pipe fd = open(PIPE, O_RDONLY, 0); // open pipe for (;;) { if (read(fd, &readbuf, sizeof(readbuf)) < 0){ //read from pipe perror("Error reading pipe"); exit(1); } printf("Received string: %s\n", readbuf); // process data } exit(0); } Trifon Ruskov Technical University - Varna 24 Named pipes. Example (cont.) Client #include <stdio.h> ... #define PIPE “fifo” int main(){ int fd; char writebuf[20] = “Hello”; // open pipe fd = open(PIPE, O_WRONLY, 0); // write to pipe write(fd, writebuf, sizeof(writebuf)); exit(0); } Trifon Ruskov Technical University - Varna 25 Inter-Process Communication (IPC) Unix System V IPC facilities provide a method for multiple processes to communicate with one another. There are several methods of IPC available to UNIX programmers: Messages Shared memory Semaphores Each method is based on a specific object. The objects of various methods have some common features. They: are described in a table have unique kernel identifiers have unique user keys have individual access permissions have status information collected are dynamically created and deleted Trifon Ruskov Technical University - Varna 26 Inter-Process Communication (cont.) To share an object, a process must: be able to identify it (know its key) have appropriate privileges gain access by a “get” system call The particular “get” system call for each method is used for: Creating an object (if it doesn’t exist) Checking process permissions Associating an unique ID to the key Unix shell commands for IPC: ipcs – shows the status of all IPC objects ipcrm msg/shm/sem <object_ID> - deletes a particular object Trifon Ruskov Technical University - Varna 27 IPC Messages Features of the IPC message method: Two or more processes can exchange information through a system message queue. The sending process places a message into the queue. The receiving process reads a message from the queue. Messages have a type, used by the process to select appropriate message. Message have a fixed length. Processes must share a common key to gain access to the message queue. Trifon Ruskov Technical University - Varna 28 IPC Messages Mechanism Internal data structures, maintained by the kernel: queue headers message headers type size type size msgID type size data area message data message data message data Trifon Ruskov Technical University - Varna 29 IPC Messages Mechanism (cont.) Message queue header contains: The queue key The queue permissions Times of the last send and receive operations on queue and pid of their initiators Message data and current queue state Pointers to queues of processes, blocked on send and receive operations Message structure Type (positive number), used for selecting a message from the queue Data with an application-dependent format Trifon Ruskov Technical University - Varna 30 Getting Access to a Message Queue int msgget(key_t key, int flag); Parameters: key – an user defined value, associated with a message queue flag - control flags and settings for the queue permissions returns: msgID – message queue identifier on success -1 – otherwise (errno is set) This system call compares the given key to values that exist within the kernel for other message queues. At that point, the open or access operation is dependent upon the contents of the flag argument: IPC_CREAT - create the queue if it doesn’t already exist in the kernel. IPC_CREAT | IPC_EXCL – function fails if required queue already exists. Trifon Ruskov Technical University - Varna 31 Sending Messages int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); Parameters: msgid – a message queue identifier. msgp – a pointer to a message buffer in the process address space. mgsz - size of the message text in bytes. msgflg – set to 0 or IPC_NOWAIT. returns: 0 – on success -1 – otherwise (errno is set) The function attempts to put the message, pointed by the msgp, into the existing queue with identifier msgid. The message must be previously constructed with type and text (data). Trifon Ruskov Technical University - Varna 32 Sending Messages (cont.) Example of message structure: struct mymsg { long mtype; char mtext[MSGSZ]; } // message type // message data with MSGSZ length The following action of msgsnd are possible: The message is successfully put into the message queue. The calling process is blocked – not enough free space in the queue. Error code is returned. Send operations may be blocking or non-blocking: Blocking is the default action in case of full queue. Blocking can be prevented if IPC_NOWAIT is set as msgflg argument of the msgsnd(). The calling process will return immediately with an error code. Trifon Ruskov Technical University - Varna 33 Receiving Messages int msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); Parameters: msgid – a message queue identifier. msgp – a pointer to a message buffer in the process address space. mgsz - size of the message text in bytes. msgtyp - the received message type as specified by the sending process. msgflg – set to 0 or IPC_NOWAIT. returns: Number of bytes, copied into *msgp – on success -1 – otherwise (errno is set) The function takes a message from the queue with identifier msgid and writes it in process address space, pointed to by msgp. Trifon Ruskov Technical University - Varna 34 Receiving Messages (cont.) The appropriate message is selected according the value of msgtyp: msgtyp = 0 – select the first message in the message queue. msgtyp > 0 – select the first message with type = msgtyp. msgtyp < 0 – select the first message with type <= |msgtyp|. The following actions of msgrcv are possible: The needed message is selected and extracted from the queue. One waiting process is waken up and allowed to perform send operation. The calling process is blocked – no message in the queue. Error code is retuned. Receive operations may be blocking or non-blocking: Blocking is the default action in case of empty queue or requested message is not found. Blocking can be prevented if IPC_NOWAIT is set as msgflg argument of the msgrcv(). The calling process will return immediately with an error code. If the size of the physical message data is greater than msgsz, and flag MSG_ NOERROR is asserted, then the message is truncated, and only msgsz bytes are returned. Trifon Ruskov Technical University - Varna 35 Controlling Message Queues Direct manipulation of internal kernel message structures can be done by: int msgctl(int msqid, int cmd, struct msqid_ds *buf); Parameters: msgid – a message queue identifier. cmd – one of: IPC_STAT – gets information about the status of the queue and stores it in the address pointed to by buf. IPC_SET – set permissions and other information for the queue. IPC_RMID – remove the message queue msgid. buf – pointer to data buffer. returns: 0 – on success -1 – otherwise (errno is set) Trifon Ruskov Technical University - Varna 36 IPC Messages. Example // Message-receiver process #include <sys/ipc.h> #include <sys/msg.h> ... #define KEY 75 #define MSGSIZE 70 #define MSGTYPE 5 typedef struct msgs { long mtype; char mtext[MSGSIZE]; } msg_str; message queue P1 P2 int main (){ int msgid; msg_str msg; msgid = msgget(KEY, IPC_CREAT | 0660); // Create message queue and get its ID ... if (msgrcv(msgid, (struct msgbuf *)&msg, MSGSIZE, MSGTYPE, 0) < 0){ perror("msgrcv"); exit(1); } // msg processing ... } Trifon Ruskov Technical University - Varna 37 IPC Messages. Example (cont.) // Message-sender process #include <sys/ipc.h> #include <sys/msg.h> ... #define KEY 75 #define MSGSIZE 70 #define MSGTYPE 5 typedef struct msgs { long mtype; char mtext[MSGSIZE]; } msg_str; int main (){ int msgid; msg_str msg; char str[6]="HELLO"; } msgid = msgget(KEY, IPC_CREAT | 0660); // Create message queue and get its ID msg.mtype = MSGTYPE; strcpy(msg.mtext, str); if (msgsnd(msgid,(struct msgbuf *)&msg, MSGSIZE, 0) < 0) { perror("msgsnd"); exit(1); } ... Trifon Ruskov Technical University - Varna 38 IPC Shared Memory Shared memory is a mapping of an area (segment) of memory into several process address spaces, so it could be shared by them. This is the fastest form of inter-process communication. Processes access this segment directly by pointers. A segment can be created by one process, and subsequently used by any number of processes. A process creates a shared memory segment using shmget() int shmget(key_t key, size_t size, int shmflg); Parameters: key - an user-defined value, associated with a shared memory segment size - size in bytes of the requested shared memory shmflg - specifies the creation control flags and initial access permissions returns: shmID – shared memory segment identifier on success -1 – otherwise (errno is set) This call is also used to get the ID of an existing shared segment if calling process has enough permissions. Trifon Ruskov Technical University - Varna 39 Attaching a Shared Memory Segment Once a process has a valid IPC identifier for a given segment, its next step is to attach or map the segment into its own address space: void *shmat(int shmid, const void *shmaddr, int shmflg); Parameters: shmid - shared memory segment identifier shmaddr – virtual address in process address space where the shared memory is to be mapped. If the shmaddr argument is zero, the kernel tries to find an unmapped region. This is the recommended method. shmflg - specifies the shared memory access mode (SHM_RDONLY – segment is read-only). returns: address at which segment was attached to the process – on success -1 – otherwise (errno is set) Trifon Ruskov Technical University - Varna 40 Detaching a Shared Memory Segment After a shared memory segment is no longer needed by a process, it should be detached by calling shmdt(). int shmdt(const void *shmaddr ); Parameters: shmaddr – shared memory segment address returned by shmat() returns: 0 – on success -1 – otherwise (errno is set) Trifon Ruskov Technical University - Varna 41 Controlling a Shared Memory Segment Direct manipulation of internal kernel shared memory structures may be done by: int shmctl(int shmid, int cmd, struct shmid_ds *buf); Parameters: shmid – a shared memory segment identifier. cmd – one of: IPC_STAT – retrieves information for a segment, and stores it in the address pointed to by buf. IPC_SET – set the permissions and other information for the shared memory segment. IPC_RMID – marks the segment for removal. The actual removal itself occurs when the last process currently attached to the segment has properly detached it. buf – pointer to data buffer. returns: 0 – on success -1 – otherwise (errno is set) Trifon Ruskov Technical University - Varna 42 IPC Shared Memory. Example // Shared memory clear process Pc #include <sys/ipc.h> #include <sys/shm.h> ... #define SHMKEY 75 shared memory Pc integer variable +1 int main (){ int shmid; int *pint; char *addr; P1 -1 P2 // Create shared memory and get its ID shmid = shmget(SHMKEY, sizeof(int), 0660 | IPC_CREAT); addr = shmat(shmid, 0, 0); // Attach to the shared memory pint = (int *)addr; *pint=0; // Clear shared memory } Trifon Ruskov Technical University - Varna 43 IPC Shared Memory. Example (cont.) // Inter-process communication via shared memory #include <sys/ipc.h> #include <sys/shm.h> ... #define SHMKEY 75 int main (){ int i, shmid, *pint, status; char *addr; // Create shared memory and get its ID shmid = shmget(SHMKEY, sizeof(int), 0660 | IPC_CREAT); addr = shmat(shmid, 0, 0); pint = (int*)addr; } switch (fork()){ // Fork communicating processes case 0: pint = (int *)addr; for (i=0;i<5000000;i++) *pint = *pint + 1; break; default: pint = (int *)addr; for (i=0;i<5000000;i++) *pint = *pint - 1; wait(&status); printf(“Shared memory value is: %d\n”, *pint); } Trifon Ruskov Technical University - Varna 44 IPC Semaphores Semaphores can be operated as individual units or as elements of a set. A semaphore is represented as: A value. A count of processes waiting for its value to increase. A count of processes waiting for its value to become 0. An identifier of the last process completed a semaphore operation. Semaphore sets table Arrays of semaphores 0 1 2 3 0 1 2 3 4 5 6 0 Trifon Ruskov Technical University - Varna 45 Getting Access to Semaphores int semget(key_t key, int nsems, int semflag); Parameters: key – a user defined value, associated with a semaphore set. nsems - specifies the number of semaphores that should be created in a new set. semflag - control flags and settings for the semaphore permissions. returns: semID – semaphore set identifier on success -1 – otherwise (errno is set) This system call compares the given key to values that exist within the kernel for other semaphore sets. At that point, the open or access operation is dependent upon the contents of the semflag argument: IPC_CREAT - create the semaphore set if it doesn’t already exist in the kernel. IPC_CREAT | IPC_EXCL – function fails if the specified semaphore set already exists. Trifon Ruskov Technical University - Varna 46 Semaphore Operations Operations on a semaphore set can be performed by: int semop(int semid,struct sembuf *sops,size_t nsops); Parameters: semid – semaphore set identifier. sops - pointer to an array of structures, each containing the following information about a semaphore operation: the semaphore number. the operation to be performed. control flags, if any. nsops - specifies the length of the array of operations. returns: 0 – on success (all operations performed) -1 – otherwise (errno is set) Trifon Ruskov Technical University - Varna 47 Semaphore Operations (cont.) The sembuf structure specifies a semaphore operation: struct sembuf { ushort_t sem_num; short sem_op; short sem_flg; }; /* semaphore number */ /* semaphore operation */ /* operation flags */ Either all operations, specified in sops, are executed, or none (“all or nothing”). If the execution of some sops operation is not possible, the kernel restores the old values of the semaphores and suspends the process. When the event, on which the process waits, takes place, the semop() system call is restarted and starts its execution from the beginning (i.e. from the first element of sops). Prior to the execution of each operation, the validity of the specified semaphore and its read/write privileges are checked. Trifon Ruskov Technical University - Varna 48 Semaphore Operations (cont.) The semaphore value is changed depending on sem_op: sem_op > 0 – Semaphore value is incremented by the sem_op value (corresponds to releasing multiple resources). All processes, waiting on semaphore increment are awaken. sem_op = 0 – The kernel checks the current semaphore value (sem_val). Two cases are distinguished: sem_val = 0 – The kernel continues with the execution of the remaining operations from sops. sem_val ≠ 0 – Current process is suspended until sem_val becomes 0. sem_op < 0 – (corresponds to obtaining multiple resources). Two cases: If |sem_op| <= sem_val, then sem_val := sem_val + sem_op. If sem_val = 0, the kernel wakes all processes, waiting for zero semaphore value. If |sem_op| > sem_val, then kernel suspends the current process until the semaphore is incremented. Trifon Ruskov Technical University - Varna 49 IPC Semaphores. Example // Inter-process communication via shared memory // synchronized with semaphores #include <sys/ipc.h> shared memory #include <sys/shm.h> #include <sys/sem.h> ... #define SHMKEY 55 integer variable #define SEMKEY 81 int main (){ int i, shmid, *pint, status; char *addr; short init[1]; struct opt { short sem_num; short sem_op; short sem_flg; }; struct sembuf p,v; semaphore +1 P1 -1 P2 // Create shared memory and get its ID shmid = shmget(SHMKEY, sizeof(int), 0660 | IPC_CREAT); addr = shmat(shmid, 0, 0); // Attach to the shared memory pint = (int *)addr; *pint=0; // Clear shared memory Trifon Ruskov Technical University - Varna 50 IPC Semaphores. Example (cont.) // Create and initialize semaphores init[0] = 1; semid = semget(SEMKEY, 1, IPC_CREAT | 0660); p.sem_num = 0; p.sem_op = -1; p.sem_flg = 0; v.sem_num = 0; v.sem_op = 1; v.sem_flg = 0; semctl(semid, 1, SETALL, init); switch (fork()) { // Fork communicating processes case 0: pint = (int *)addr; for (i=0;i<500000;i++){ semop(semid, &p, 1); *pint = *pint + 1; semop(semid, &v, 1); } break; default: pint = (int *)addr; for (i=0;i<500000;i++){ semop(semid, &p, 1); *pint = *pint - 1; semop(semid, &v, 1); } wait(&status); printf(“Shared memory value is: %d\n”, *pint); } } Trifon Ruskov Technical University - Varna 51