0% found this document useful (0 votes)
43 views108 pages

UNIX Process Control and IPC Methods

Module 4 of the UNIX Programming course focuses on process control, including the creation, execution, and termination of processes using functions like fork, vfork, exit, and wait. It covers inter-process communication methods, file sharing, and the differences between parent and child processes. The module also discusses the handling of process identifiers and the implications of race conditions in process management.

Uploaded by

ashwinikamath
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
43 views108 pages

UNIX Process Control and IPC Methods

Module 4 of the UNIX Programming course focuses on process control, including the creation, execution, and termination of processes using functions like fork, vfork, exit, and wait. It covers inter-process communication methods, file sharing, and the differences between parent and child processes. The module also discusses the handling of process identifiers and the implications of race conditions in process management.

Uploaded by

ashwinikamath
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

UNIX PROGRAMMING (BCS515C)

Module 4
Process Control (Continued..)
Syllabus Module 4

Process Control: Introduction, Process Identifiers, fork, vfork,


exit, wait, waitpid, wait3, wait4 Functions, Race Conditions, exec
Functions.
Overview of IPC Methods, Pipes, popen, pclose Functions,
Coprocesses, FIFOs, System V IPC, Message Queues,
Semaphores.
Shared Memory, Client-Server Properties, Passing File
Descriptors, An Open Server-Version 1.
Text Book2: Chapter 8, 15,17
INTRODUCTION
• Process control is concerned about creation of new processes,
program execution, and process termination.

PROCESS IDENTIFIERS
#include <unistd.h>
pid_t getpid(void); Returns: process ID of calling process
pid_t getppid(void); Returns: parent process ID of calling process
uid_t getuid(void); Returns: real user ID of calling process
uid_t geteuid(void); Returns: effective user ID of calling process
gid_t getgid(void); Returns: real group ID of calling process
gid_t getegid(void); Returns: effective group ID of calling process

1/24/2023
Ashwini 2
Kamath,
fork FUNCTION
• An existing process can create a new one by calling the fork function.
#include <unistd.h>
pid_t fork(void);
• Returns: 0 in child, process ID of child in parent, -1 on error.
• The new process created by fork is called the child process.
• This function is called once but returns twice.
• The only difference in the returns is that the return value in the child is
0, whereas the return value in the parent is the process ID of the new
child.
• The reason the child's process ID is returned to the parent is that a
process can have more than one child, and there is no function that
allows a process to obtain the process IDs of its children.
• The reason fork returns 0 to the child is that a process can have only a
single parent, and the child can always call getppid to obtain the process
ID of its parent.
1/24/2023
Ashwini 3
Kamath,
• Process ID 0 is reserved for use by the kernel, so it's
not possible for 0 to be the process ID of a child.
• Both the child and the parent continue executing
with the instruction that follows the call to fork.
• The child is a copy of the parent.
• For example, the child gets a copy of the parent's
data space, heap, and stack.
• Note that this is a copy for the child; the parent and
the child do not share these portions of memory.
• The parent and the child share the text segment

1/24/2023
Ashwini 4
Kamath,
/* Program to demonstrate fork function Program name fork1.c */
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
int main( )
{
fork( );
printf(“Hello World\n”);
}
$ cc fork1.c
$ ./[Link]
Hello World
Hello World
Note : The statement Hello World is executed twice as both the child
and parent have executed that instruction.
1/24/2023
Ashwini 5
Kamath,
/* Program name fork2.c */
#include <stdio.h>
#include<unistd.h>
int j=20;
int main() Output:
{ Hello World
int pid, i=10; i=10, j=20
pid=fork( );
if(pid==0)
{
i++; j++;
_exit(0);
}
printf("Hello World\n");
printf("i=%d, j=%d",i,j);
return 0;
}

1/24/2023
Ashwini 6
Kamath,
/* Program name fork3.c */
#include<sys/types.h>
#include<unistd.h>
int main( )
{
printf(“Before fork: Parent process\n”);
fork( );
printf(“After fork\n”);

$ cc fork1.c
$ ./[Link]
Before fork: Parent process
After fork
After fork
1/24/2023
Ashwini 7
Kamath,
• Note: The statement Before fork: Parent process is
executed only once by the parent because it is called
before fork and statement After fork is executed twice by
child and parent

File Sharing
• Consider a process that has three different files opened
for standard input, standard output, and standard error.
On return from fork, we have the arrangement shown in
Figure 8.2.

1/24/2023
Ashwini 8
Kamath,
1/24/2023
Ashwini 9
Kamath,
• It is important that the parent and the child share the same file
offset.
• Consider a process that forks a child, then waits for the child to
complete.
• Assume that both processes write to standard output as part of
their normal processing.
• If the parent has its standard output redirected (by a shell, perhaps)
it is essential that the parent's file offset be updated by the child
when the child writes to standard output.
• In this case, the child can write to standard output while the parent
is waiting for it; on completion of the child, the parent can continue
writing to standard output, knowing that its output will be
appended to whatever the child wrote.
• If the parent and the child did not share the same file offset, this
type of interaction would be more difficult to accomplish and would
require explicit actions by the parent.

1/24/2023
Ashwini 10
Kamath,
There are two normal cases for handling the descriptors after a fork.
1. The parent waits for the child to complete. In this case, the parent
does not need to do anything with its descriptors. When the child
terminates, any of the shared descriptors that the child read from or
wrote to will have their file offsets updated accordingly.
2. Both the parent and the child go their own ways. Here, after the fork,
the parent closes the descriptors that it doesn't need, and the child
does the same thing. This way, neither interferes with the other's
open descriptors. This scenario is often the case with network
servers
There are numerous other properties of the parent that are inherited
by the child:
• Real user ID, real group ID, effective user ID, effective group ID
• Supplementary group IDs
• Process group ID
• Session ID

1/24/2023
Ashwini 11
Kamath,
• Controlling terminal
• The set-user-ID and set-group-ID flags
• Current working directory
• Root directory
• File mode creation mask
• Signal mask and dispositions
• The close-on-exec flag for any open file descriptors
• Environment
• Attached shared memory segments
• Memory mappings
• Resource limits

1/24/2023
Ashwini 12
Kamath,
The differences between the parent and child are
• The return value from fork
• The process IDs are different
• The two processes have different parent process IDs: the
parent process ID of the child is the parent; the parent
process ID of the parent doesn't change
• The child's tms_utime, tms_stime, tms_cutime, and
tms_cstime values are set to 0
• File locks set by the parent are not inherited by the child
• Pending alarms are cleared for the child
• The set of pending signals for the child is set to the empty set

The two main reasons for fork to fail are


1. if too many processes are already in the system, which
usually means that something else is wrong, or
2. if the total number of processes for this real user ID exceeds
the system's limit.
1/24/2023
Ashwini 13
Kamath,
There are two uses for fork:

1. When a process wants to duplicate itself so that the parent


and child can each execute different sections of code at the
same time. This is common for network servers, the pfrom a
client. When the request arrives, the parent calls fork and lets
the child handle the request. The parent goes back to
waiting for the next service request to arrive.

2. When a process wants to execute a different program. This


is common for shells. In this case, the child does an exec
right after it returns from the fork and execute different
program.

1/24/2023
Ashwini 14
Kamath,
vfork FUNCTION

• The function vfork has the same calling sequence and same return
values as fork.
#include <unistd.h>
pid_t vfork(void);
• The vfork function is intended to create a new process when the
purpose of the new process is to exec a new program.
• The vfork function creates the new process, just like fork, without
copying the address space of the parent into the child, as the child
won't reference that address space; the child simply calls exec (or exit)
right after the vfork.
• Instead, while the child is running and until it calls either exec or
exit, the child runs in the address space of the parent.

1/24/2023
Ashwini 15
Kamath,
• This optimization provides an efficiency gain on some paged virtual-
memory implementations of the UNIX System.
• Another difference between the two functions is that vfork
guarantees that the child runs first, until the child calls exec or exit.
• When the child calls either of these functions, the parent resumes.
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int glob = 6; /* external variable in initialized data */
int main(void)
{
int var; /* automatic variable on the stack */
pid_t pid;
var = 88;
printf("before vfork\n"); /* we don't flush stdio */
1/24/2023
Ashwini 16
Kamath,
if ((pid = vfork()) < 0)
{
err_sys("vfork error");
}
else if (pid == 0)
{ /* child */
glob++; /* modify parent's variables */
var++;
_exit(0); /* child terminates */
}
/* Parent continues here. */
printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);
exit(0);
}

Output:
$ ./[Link]
before vfork
pid = 29039, glob = 7, var = 89
1/24/2023
Ashwini 17
Kamath,
/* Program name vork2.c */
#include <stdio.h>
#include<unistd.h>
int j=20;
int main() Output:
{ Hello World
int pid, i=10; i=11, j=21
pid=fork( );
if(pid==0)
{
i++; j++;
_exit(0);
}
printf("Hello World\n");
printf("i=%d, j=%d",i,j);
return 0;
}

1/24/2023
Ashwini 18
Kamath,
exit FUNCTIONS
There are eight ways for a process to terminate. Normal
termination occurs in five ways:
1. Return from main
2. Calling exit
3. Calling _exit or _Exit
4. Return of the last thread from its start routine
5. Calling pthread_exit from the last thread
Abnormal termination occurs in three ways:
1. Calling abort
2. Receipt of a signal
3. Response of the last thread to a cancellation request
1/24/2023
Ashwini 19
Kamath,
Exit Functions
• Three functions terminate a program normally: _exit and _Exit,
which return to the kernel immediately, and exit, which
performs certain cleanup processing and then returns to the
kernel.

• All three exit functions expect a single integer argument, called


the exit status. Returning an integer value from the main
function is equivalent to calling exit with the same value.
• Thus exit(0); is the same as return(0); from the main function.

1/24/2023
Ashwini 20
Kamath,
wait AND waitpid FUNCTIONS
• When a process terminates, either normally or abnormally, the kernel
notifies the parent by sending the SIGCHLD signal to the parent.
• Because the termination of a child is an asynchronous event - it can
happen at any time while the parent is running - this signal is the
asynchronous notification from the kernel to the parent.
• The parent can choose to ignore this signal, or it can provide a
function that is called when the signal occurs: a signal handler.

A process that calls wait or waitpid can:


1. Block, if all of its children are still running
2. Return immediately with the termination status of a child, if a child
has terminated and is waiting for its termination status to be fetched
3. Return immediately with an error, if it doesn't have any child
processes.

1/24/2023
Ashwini 21
Kamath,
• Both return: process ID if OK, 0 (when using WNOHANG option
if child process status is not available to fetch), or -1 on error.

The differences between these two functions are as follows.


• The wait function can block the caller until a child process
terminates, whereas waitpid has an option that prevents it from
blocking.
• The waitpid function doesn't wait for the child that terminates
first; it has a number of options that control which process it
waits for.
1/24/2023
Ashwini 22
Kamath,
• If a child has already terminated and is a zombie,
wait returns immediately with that child's status.
Otherwise, it blocks the caller until a child
terminates.
• If the caller blocks and has multiple children, wait
returns when one terminates.
• For both functions, the argument statloc is a pointer
to an integer.
• If this argument is not a null pointer, the termination
status of the terminated process is stored in the
location pointed to by the argument.

1/24/2023
Ashwini 23
Kamath,
#include <sys/wait.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
void pr_exit(int status)
{
if (WIFEXITED(status))
printf("normal termination, exit status = %d\n", WEXITSTATUS(status));
else if (WIFSIGNALED(status))
printf("abnormal termination, signal number = %d%s\n", WTERMSIG(status),
#ifdef WCOREDUMP
WCOREDUMP(status) ? " (core file generated)" : "");
#else
"");
#endif
else if (WIFSTOPPED(status))
printf("child stopped, signal number = %d\n“, WSTOPSIG(status));
}
1/24/2023
Ashwini 24
Kamath,
int main(void)
{
pid_t pid;
int status;
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0) /* child */
exit(7);
if (wait(&status) != pid) /* wait for child */
err_sys("wait error");
pr_exit(status); /* and print its status */

if ((pid = fork()) < 0)


err_sys("fork error");
else if (pid == 0) /* child */
abort(); /* generates SIGABRT */
1/24/2023
Ashwini 25
Kamath,
Output:
$./[Link]
normal termination, exit status = 7
if (wait(&status) != pid) /* wait for child */ abnormal termination, signal
err_sys("wait error"); number = 6, core file generated
pr_exit(status); /* and print its status */ child stopped, signal number = 8

if ((pid = fork()) < 0)


err_sys("fork error");
else if (pid == 0) /* child */
status /= 0; /* divide by 0 generates SIGFPE */
if (wait(&status) != pid) /* wait for child */
err_sys("wait error");
pr_exit(status); /* and print its status */
exit(0);
}

1/24/2023
Ashwini 26
Kamath,
• The interpretation of the pid argument for
waitpid depends on its value:

Pid Meaning
pid == 1 Waits for any child process. In this respect, waitpid is
equivalent to wait.

pid > 0 Waits for the child whose process ID equals pid.

pid == 0 Waits for any child whose process group ID equals


that of the calling process.

pid < 1 Waits for any child whose process group ID equals the
absolute value of pid

1/24/2023
Ashwini 27
Kamath,
Macros to examine the termination status returned by wait and waitpid
Macro Description

WIFEXITED(status) True if status was returned for a child that terminated normally. In
this case, we can execute WEXITSTATUS (status)
to fetch the low-order 8 bits of the argument that the child passed to
exit, _exit,or _Exit.
WIFSIGNALED True if status was returned for a child that terminated abnormally, by
(status) receipt of a signal that it didn't catch. In this case, we can execute
WTERMSIG (status) to fetch the signal number that caused the
termination.
Additionally, some implementations (but not the Single UNIX
Specification) define the macro WCOREDUMP (status) that returns
true if a core file of the terminated process was generated.
WIFSTOPPED True if status was returned for a child that is currently stopped. In this
(status) case, we can execute WSTOPSIG (status) to fetch the signal number
that caused the child to stop.
WIFCONTINUED True if status was returned for a child that has been continued after a
(status) job control stop

1/24/2023
Ashwini 28
Kamath,
The options constants for waitpid

Constant Description

WCONTINUED If the implementation supports job control, the status


of any child specified by pid that has been continued
after being stopped, but whose status has not yet
been reported, is returned.
WNOHANG The waitpid function will not block if a child specified
by pid is not immediately available. In this case, the
return value is 0.
WUNTRACED If the implementation supports job control, the status
of any child specified by pid that has stopped, and
whose status has not been reported since it has
stopped, is returned.
The WIFSTOPPED macro determines whether the
return value corresponds to a stopped child process.
1/24/2023
Ashwini 29
Kamath,
• The waitpid function provides three features that
aren't provided by the wait function.
1. The waitpid function lets us wait for one particular
process, whereas the wait function returns the status
of any terminated child. We'll return to this feature
when we discuss the popen function.
2. The waitpid function provides a nonblocking version
of wait. There are times when we want to fetch a
child's status, but we don't want to block.
3. The waitpid function provides support for job control
with the WUNTRACED and WCONTINUED options.

1/24/2023
Ashwini 30
Kamath,
Program to Avoid zombie processes by calling fork twice
#include <sys/wait.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>

int main(void)
{
pid_t pid;
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0) /* first child */
{
if ( ( pid = fork() ) < 0 )
err_sys("fork error");
else if (pid > 0)
exit(0); /* parent from second fork == first child */
/* second child parent process is init after its parent called exit*/
sleep(2);
printf("second child, parent pid = %d\n", getppid());
exit(0);
}
1/24/2023
Ashwini 31
Kamath,
if (waitpid(pid, NULL, 0) != pid) /* wait for first child */
err_sys("waitpid error");
exit(0);
}

Output:
$ ./[Link]
$ second child, parent pid = 1

1/24/2023
Ashwini 32
Kamath,
Waitid FUNCTION
• The waitid function is similar to waitpid, but provides extra
flexibility.
#include<sys/wait.h>
int waitid ( idtype_t idtype, id_t id, siginfo_t *infop, int options);
• Returns: 0 if OK, -1 on error
The idtype constants for waited are as follows:
Constant Description
P_PID Wait for a particular process: id contains the process ID of the
child to wait for.
P_PGID Wait for any child process in a particular process group: id
contains the process group ID of the
children to wait for.
P_ALL Wait for any child process: id is ignored.

1/24/2023
Ashwini 33
Kamath,
• The options argument is a bitwise OR of the flags as shown below:
these flags indicate which state changes the caller is interested in.

Constant Description
WCONTINUED Wait for a process that has previously stopped and has
been continued, and whose status has
Not yet been reported.
WEXITED Wait for processes that have exited.
WNOHANG Return immediately instead of blocking if there is no
child exit status available.
WNOWAIT Don't destroy the child exit status. The child's exit status
can be retrieved by a subsequent call to wait, waitid,or
waitpid
WSTOPPED Wait for a process that has stopped and whose status has
not yet been reported.

1/24/2023
Ashwini 34
Kamath,
wait3 AND wait4 FUNCTIONS
• The only feature provided by these two functions that isn't provided
by the wait, waitid, and waitpid functions is an additional argument
that allows the kernel to return a summary of the resources used by
the terminated process and all its child processes.

• The prototypes of these functions are:


#include<sys/wait.h>
#include<sys/types.h>
#include<sys/times.h>
#include<sys/resource.h>
int wait3 (int *statloc, int options, struct rusage *rusage );
int wait4 (pid_t pid, int *statloc, int options, struct rusage *rusage );
• Both return: process ID if OK,-1 on error

1/24/2023
Ashwini 35
Kamath,
• The resource information includes such statistics as
the amount of user CPU time, the amount of system
CPU time, number of page faults, number of signals
received etc.
• The resource information is available only for
terminated child process not for the process that
were stopped due to job control.

1/24/2023
Ashwini 36
Kamath,
RACE CONDITIONS
• A race condition occurs when multiple processes are trying to do
something with shared data and the final outcome depends on the
order in which the processes run.
• Example: The program below outputs two strings: one from the child
and one from the parent. The program contains a race condition
because the output depends on the order in which the processes are
run by the kernel and for how long each process runs.

#include<sys/wait.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
static void charatatime(char *);
int main(void)
{
pid_t pid;
1/24/2023
Ashwini 37
Kamath,
if ((pid = fork()) < 0)
err_sys("fork error"); Output:
else if (pid == 0) $ ./[Link]
charatatime("output from child\n"); ooutput from child
else utput from parent
charatatime("output from parent\n");
exit(0); $ ./[Link]
} ooutput from child
static void charatatime(char *str) utput from parent
{
char *ptr; $ ./[Link]
int c; output from child
setbuf(stdout, NULL); /* set unbuffered */ output from parent
for (ptr = str; (c = *ptr++) != 0; )
putc(c, stdout);
}

1/24/2023
Ashwini 38
Kamath,
program modification to avoid race condition
#include<sys/wait.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
static void charatatime(char *);
int main(void)
{
pid_t pid;
TELL_WAIT();
if ((pid = fork()) < 0)
err_sys("fork error");

1/24/2023
Ashwini 39
Kamath,
else if (pid == 0)
{
WAIT_PARENT(); /* parent goes first */ When we run this
charatatime("output from child\n"); program, the output is as
} we expect; there is no
else intermixing of output
{ from the two processes.
charatatime("output from parent\n");
TELL_CHILD(pid);
} $ ./[Link]
exit(0); output from parent
} output from child

static void charatatime(char *str)


{
char *ptr; int c;
setbuf(stdout, NULL); /* set unbuffered */
for (ptr = str; (c = *ptr++) != 0; )
putc(c, stdout);
}
1/24/2023
Ashwini 40
Kamath,
exec FUNCTIONS
• When a process calls one of the exec functions, that
process is completely replaced by the new program, and the
new program starts executing at its main function.

• The process ID does not change across an exec,


because a new process is not created; exec merely
replaces the current process - its text, data, heap, and
stack segments with a brand new program from disk.

1/24/2023
Ashwini 41
Kamath,
• There are 6 exec functions:
#include <unistd.h>
int execl (const char *pathname, const char *arg0, ... /* (char *) 0 */ );

int execv (const char *pathname, char *const argv[]);

int execle (const char *pathname, const char *arg0, … /* (char *) 0, char
*const envp[] */ );
int execve (const char *pathname, char *const argv[], char const envp[]);

int execlp (const char *filename, const char *arg0, ... /* (char *) 0 */ );

int execvp (const char *filename, char *const argv[]);

All six return: -1 on error, no return on success.

1/24/2023
Ashwini 42
Kamath,
Difference among six exec functions:
1. The first difference in these functions is that the first four take a
pathname argument, whereas the last two take a filename
argument. When a filename argument is specified
• If filename contains a slash, it is taken as a pathname.
• Otherwise, the executable file is searched for in the directories
specified by the PATH environment variable.
2. The next difference concerns the passing of the argument list (l
stands for list and v stands for vector).
• The functions execl, execlp, and execle require each of the
command-line arguments to the new program to be specified as
separate arguments.
• For the other three functions (execv, execvp, and execve), we have
to build an array of pointers to the arguments, and the address of
this array is the argument to these three functions
1/24/2023
Ashwini 43
Kamath,
3. The final difference is the passing of the environment
list to the new program.
• The two functions whose names end in an e (execle
and execve) allow us to pass a pointer to an array of
pointers to the environment strings.
• The other four functions, however, use the environ
variable in the calling process to copy the existing
environment for the new program.

1/24/2023
Ashwini 44
Kamath,
Function pathname filename Arg list Argv[ ] environ envp[ ]

execl * * *

execlp * * *

execle * * *

execv * * *

execvp * * *

execve * * *

(letter in p l v e
Name)

1/24/2023
Ashwini 45
Kamath,
Relationship of the six exec functions

1/24/2023
Ashwini 46
Kamath,
Process ID does not change after an exec, but the new program
inherits additional properties from the calling process:
• Process ID and parent process ID
• Real user ID and real group ID
• Supplementary group IDs
• Process group ID
• Session ID
• Controlling terminal
• Time left until alarm clock
• Current working directory
• Root directory
• File mode creation mask
• File locks
• Process signal mask
• Pending signals
• Resource limits
• Values for tms_utime, tms_stime, tms_cutime, and tms_cstime.

1/24/2023
Ashwini 47
Kamath,
#include<sys/wait.h>
char *env_init[] = { "USER=unknown", "PATH=/tmp", NULL };
int main(void)
{
pid_t pid;
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0)
if (execle("/home/sar/bin/echoall", "echoall", "myarg1", "MY ARG2", (char *)0,
env_init) <0)
err_sys("execle error");
if (waitpid(pid, NULL, 0) < 0)
err_sys("wait error");
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0)
if(execlp("echoall", "echoall", "only 1 arg", (char *)0) < 0)
err_sys("execlp error");
exit(0);
} Ashwini Kamath, Asst. Prof., ISE, AIT
1/24/2023 48
chikmagalur
/*echoall program that exec function executes in previous code*/
#include<stdio.h>
int main(int argc, char *argv[])
{
int i;
char **ptr;
extern char **environ;
for (i = 0; i < argc; i++) /* echo all command-line args */
printf("argv[%d]: %s\n", i, argv[i]);
for (ptr = environ; *ptr != 0; ptr++) /* and all env strings */
printf("%s\n", *ptr);
exit(0);
}
1/24/2023
Ashwini 49
Kamath,
Output:
$ ./[Link]
argv[0]: echoall
argv[1]: myarg1
argv[2]: MY ARG2
USER=unknown
PATH=/tmp
$ argv[0]: echoall
argv[1]: only 1 arg
USER=sar
LOGNAME=sar
…. 47 more lines that aren't shown
SHELL=/bin/bash
HOME=/home/sar

1/24/2023
Ashwini 50
Kamath,
Overview of IPC Methods
Overview of IPC Methods
• INTRODUCTION
• IPC enables one application to control another application, and for several
applications to share the same data without interfering with one another.
IPC is required in all multiprocessing systems, but it is not generally
supported by single-process operating systems.
• The various forms of IPC that are supported on a UNIX system are as
follows :
1. Half duplex Pipes.
2. Full duplex Pipes.
3. FIFO’s
4. Named full duplex Pipes.
5. Message queues.
6. Shared memory.
7. Semaphores.
8. Sockets.
9. STREAMS.
• The first seven forms of IPC are usually restricted to IPC between processes
on the same host. The final two i.e. Sockets and STREAMS are the only two
that are generally supported for IPC between processes on different hosts.
Ashwini Kamath, Asst. Prof., ISE, AIT
1/20/2023 2
chikmagalur
Pipes
• Pipes are the oldest form of UNIX System IPC. Pipes have two
limitations.
1. Historically, they have been 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.
• A pipe is created by calling the pipe function.
#include <unistd.h>
int pipe(int fd[2]);
Returns: 0 if OK, −1 on error
• Two file descriptors are returned through the fd argument: fd[0] is
open for reading, and fd[1] is open for writing. The output of fd[1] is
the input for fd[0].
Ashwini Kamath, Asst. Prof., ISE, AIT
1/20/2023 3
chikmagalur
• Two ways to picture a half-duplex pipe are shown in Figure 15.2.
The left half of the figure shows the two ends of the pipe connected
in a single process.
• The right half of the figure emphasizes that the data in the pipe
flows through the kernel.

Ashwini Kamath, Asst. Prof., ISE, AIT


1/20/2023 4
chikmagalur
• A pipe in a single process is next to useless.
• Normally, the process that calls pipe then calls fork, creating an IPC
channel from the parent to the child or vice versa. Figure 15.3
shows this scenario.

Ashwini Kamath, Asst. Prof., ISE, AIT


1/20/2023 5
chikmagalur
• What happens after the fork depends on which direction of data
flow we want. For a pipe from the parent to the child, the parent
closes the read end of the pipe (fd[0]), and the child closes the
write end (fd[1]).
• Figure 15.4 shows the resulting arrangement of descriptors.

Ashwini Kamath, Asst. Prof., ISE, AIT


1/20/2023 6
chikmagalur
• For a pipe from the child to the parent, the parent
closes fd[1], and the child closes fd[0].
• When one end of a pipe is closed, the following two
rules apply.
• If we read from a pipe whose write end has been
closed, read returns 0 to indicate an end of file after all
the data has been read.
• If we write to a pipe whose read end has been closed,
the signal SIGPIPE is generated. If we either ignore the
signal or catch it and return from the signal handler,
write returns -1 with errno set to EPIPE.

Ashwini Kamath, Asst. Prof., ISE, AIT


1/20/2023 7
chikmagalur
#include<stdio.h>
else if (pid > 0) /* parent */
#include<stdlib.h> {
#include<sys/types.h> close(fd[0]);
#include<unistd.h> write(fd[1], "hello world\n", 12);
main(void) }
else /* child */
{
{
int n; close(fd[1]);
int fd[2]; n = read(fd[0], line, MAXLINE);
pid_t pid; write(STDOUT_FILENO, line, n);
char line[MAXLINE]; }
exit(0);
if (pipe(fd) < 0)
}
err_sys("pipe error");
if ((pid = fork()) < 0)
err_sys("fork error");

Ashwini Kamath, Asst. Prof., ISE, AIT


1/20/2023 8
chikmagalur
popen AND pclose FUNCTIONS
• Since a common operation is to create a pipe to another process, to
either read its output or send it input, the standard I/O library has
historically provided the popen and pclose functions.
• These two functions handle all the work that we've been doing
ourselves: creating a pipe, forking a child, closing the unused ends
of the pipe, executing a shell to run the command, and waiting for
the command to terminate.
#include <stdio.h>
FILE *popen(const char *cmdstring, const char *type);
Returns: file pointer if OK, NULL on error

int pclose(FILE *fp);


Returns: termination status of cmdstring, or −1 on error
Ashwini Kamath, Asst. Prof., ISE, AIT
1/20/2023 9
chikmagalur
• The function popen does a fork and exec to execute the cmdstring,
and returns a standard I/O file pointer. If type is "r", the file pointer
is connected to the standard output of cmdstring

• If type is "w", the file pointer is connected to the standard


input of cmdstring, as shown:

Ashwini Kamath, Asst. Prof., ISE, AIT


1/20/2023 10
chikmagalur
Figure 15.11 Copy file to pager program using popen
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
#include <sys/wait.h>
#define PAGER "${PAGER:-more}" /* environment variable, or default */
int main(int argc, char *argv[])
{
char line[MAXLINE];
FILE *fpin, *fpout;
if (argc != 2)
err_quit("usage: [Link] <pathname>");
if ((fpin = fopen(argv[1], "r")) == NULL)
err_sys("can’t open %s", argv[1]);
if ((fpout = popen(PAGER, "w")) == NULL)
err_sys("popen error");
/* copy argv[1] to pager */

Ashwini Kamath, Asst. Prof., ISE, AIT


1/20/2023 11
chikmagalur
while (fgets(line, MAXLINE, fpin) != NULL)
{
if (fputs(line, fpout) == EOF)
err_sys("fputs error to pipe");
}
if (ferror(fpin))
err_sys("fgets error");
if (pclose(fpout) == -1)
err_sys("pclose error");
exit(0);
}

Ashwini Kamath, Asst. Prof., ISE, AIT


1/20/2023 12
chikmagalur
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.
• The process creates two pipes: one is the standard input of the
coprocess, and the other is the standard output of the coprocess.
Figure 15.16 shows this arrangement.

Ashwini Kamath, Asst. Prof., ISE, AIT


1/20/2023 13
chikmagalur
Figure 15.17 Simple filter to add two numbers
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
#include <sys/wait.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");
}
Ashwini Kamath, Asst. Prof., ISE, AIT
1/20/2023 14
chikmagalur
else
{
if (write(STDOUT_FILENO, "invalid args\n", 13) != 13)
err_sys("write error");
}
}
exit(0);
}

FIFOs
• FIFOs are sometimes called named pipes. Pipes can be used
only between related processes when a common ancestor has
created the pipe.

Ashwini Kamath, Asst. Prof., ISE, AIT


1/20/2023 15
chikmagalur
#include <sys/stat.h>
int mkfifo(const char *path, mode_t mode);
int mkfifoat(int fd, const char *path, mode_t mode);
Both return: 0 if OK, −1 on error
• Once we have used mkfifo to create a FIFO, we open it using open.
When we open a FIFO, the nonblocking flag (O_NONBLOCK) affects
what happens.
• In the normal case (O_NONBLOCK not specified), an open for read-
only blocks until some other process opens the FIFO for writing.
Similarly, an open for write-only blocks until some other process
opens the FIFO for reading.
• If O_NONBLOCK is specified, an open for read-only returns
immediately. But an open for write-only returns 1 with errno set to
ENXIO if no process has the FIFO open for reading.

Ashwini Kamath, Asst. Prof., ISE, AIT


1/20/2023 16
chikmagalur
• There are two uses for FIFOs.
1. FIFOs are used by shell commands to pass data from one shell
pipeline to another without creating intermediate temporary files.
2. FIFOs are used as rendezvous points in client-server applications to
pass data between the clients and the servers.
• 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.
Consider a procedure that
needs to process a filtered
input stream twice.
Figure 15.20 shows this
arrangement.

Ashwini Kamath, Asst. Prof., ISE, AIT


1/20/2023 17
chikmagalur
• With a FIFO and the UNIX program tee(1), 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 in the background, reading
from the FIFO. We then start prog1 and use tee to send its input to
both the FIFO and prog2. Figure 15.21 shows the process
arrangement.

Ashwini Kamath, Asst. Prof., ISE, AIT


1/20/2023 18
chikmagalur
• FIFOs cant be used to send data.
For example Client-Server
Communication Using a FIFO
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 that the
server creates. (Fig 15.22)
• Since there are multiple writers
for the FIFO, the requests sent
by the clients to the server need
to be less than PIPE_BUF bytes
in size.
• This prevents any interleaving
of the client writes.

Ashwini Kamath, Asst. Prof., ISE, AIT


1/20/2023 19
chikmagalur
• 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.
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.
• For example, the server can create a FIFO with the name
/tmp/[Link], where XXXXX is replaced with the client’s
process ID. This arrangement is shown in Figure 15.23.

Ashwini Kamath, Asst. Prof., ISE, AIT


1/20/2023 20
chikmagalur
Ashwini Kamath, Asst. Prof., ISE, AIT
1/20/2023 21
chikmagalur
The UNIX System V IPC Methods
• The IPC methods supported by UNIX System V are:
• Messages: allow processes on the same machine to exchange
formatted data Semaphores: provide a set of system-wide variables
that can be modified and used by processes on the same machine
to synchronize their execution. Semaphores are commonly used
with a shared memory to control the access of data in each shared
memory region
• Shared memory: allows multiple processes on the same machine to
share a common region of virtual memory, such that data written to
a shared memory can be directly read and modified by other
processes
• Transport Level Interface: allows two processes on different
machines to set up a direct, two-way communication channel. This
method uses STREAMS as the underlying data transport interface.

Ashwini Kamath, Asst. Prof., ISE, AIT


1/20/2023 22
chikmagalur
MESSAGE QUEUES
• A message queue is a linked list of messages stored within the
kernel and identified by a message queue identifier. We'll call the
message queue just a queue and its identifier a queue ID.
• A new queue is created or an existing queue opened by msgget.
New messages are added to the end of a queue by msgsnd.
• Every message has a positive long integer type field, a non-negative
length, and the actual data bytes (corresponding to the length), all
of which are specified to msgsnd when the message is added to a
queue.
• Messages are fetched from a queue by msgrcv.
• We don't have to fetch the messages in a first-in, first-out order.
Instead, we can fetch messages based on their type field.
• Each queue has the following msqid_ds structure associated with it:

Ashwini Kamath, Asst. Prof., ISE, AIT


1/20/2023 23
chikmagalur
struct msqid_ds
{
struct ipc_perm msg_perm;
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 */
...
};
Ashwini Kamath, Asst. Prof., ISE, AIT
1/20/2023 24
chikmagalur
• 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.
1. The ipc_perm structure is initialized. The mode member of this
structure is set to the corresponding permission bits of flag.
2. msg_qnum, msg_lspid, msg_lrpid, msg_stime, and msg_rtime are
all set to 0.
3. msg_ctime is set to the current time.
4. 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.
Ashwini Kamath, Asst. Prof., ISE, AIT
1/20/2023 25
chikmagalur
• 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.
• The cmd argument specifies the command to be performed on the
queue specified by msqid.
msqid Description
value
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.
IPC_RMID Remove the message queue from the system and any data still on the
queue. This removal is immediate. Any other process still using the
message queue will get an error of EIDRM on its next attempted
operation on the queue.
Ashwini Kamath, Asst. Prof., ISE, AIT
1/20/2023 26
chikmagalur
• 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.
• The ptr argument points to a long integer that contains the positive
integer message type, and it is immediately followed by the
message data. (There is no message data if nbytes is 0.)
• If the largest message we send is 512 bytes, we can define the
following structure:
struct mymesg
{
long mtype; /* positive message type */
char mtext[512]; /* message data, of length nbytes */
};
Ashwini Kamath, Asst. Prof., ISE, AIT
1/20/2023 27
chikmagalur
• The ptr argument is then a pointer to a mymesg structure.
The message type can be used by the receiver to fetch
messages in an order other than first in, first out.
• 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.
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.

Ashwini Kamath, Asst. Prof., ISE, AIT


1/20/2023 28
chikmagalur
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 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. Otherwise, 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.
Ashwini Kamath, Asst. Prof., ISE, AIT
1/20/2023 29
chikmagalur
• 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.
• A common form of semaphore is called a binary semaphore. It
controls a single resource, and its value is initialized to 1.
• In general, however, a semaphore can be initialized to any positive
value, with the value indicating how many units of the shared
resource are available for sharing.
• The kernel maintains a semid_ds structure for each semaphore set:
struct semid_ds
{
struct ipc_perm sem_perm; /* see Section 15.6.2 */
unsigned short sem_nsems; /* # of semaphores in set */
time_t sem_otime; /* last-semop() time */
time_t sem_ctime; /* last-change time */
...
};
Ashwini Kamath, Asst. Prof., ISE, AIT
1/20/2023 30
chikmagalur
• The Single UNIX Specification defines the fields shown, but
implementations can define additional members in the semid_ds
structure.
• 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 */
...
};

• 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

Ashwini Kamath, Asst. Prof., ISE, AIT


1/20/2023 31
chikmagalur
• 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.

Ashwini Kamath, Asst. Prof., ISE, AIT


1/20/2023 32
chikmagalur
• 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 */ );
• Returns: (see following)
• 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 */
};
Ashwini Kamath, Asst. Prof., ISE, AIT
1/20/2023 33
chikmagalur
cmd Description
IPC_STAT Fetch the semid_ds structure for this set, storing it in the structure
pointed to by [Link].
IPC_SET Set the sem_perm.uid, sem_perm.gid, and sem_perm.mode fields
from the structure pointed to by [Link] in the semid_ds structure
associated with this set.
IPC_RMID Remove the semaphore set from the system. This removal is
immediate. Any other process still using the semaphore will get an
error of EIDRM on its next attempted operation on the semaphore.
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 [Link].
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 [Link].
SETALL Set all the semaphore values in the set to the values pointed to by
[Link]
Ashwini Kamath, Asst. Prof., ISE, AIT
1/20/2023 34
chikmagalur
• 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 */
};

Ashwini Kamath, Asst. Prof., ISE, AIT


1/20/2023 35
chikmagalur
• The nops argument specifies the number of operations (elements)
in the array.
• 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.
Ashwini Kamath, Asst. Prof., ISE, AIT
1/20/2023 36
chikmagalur
Shared Memory
• Shared memory is one of the fastest forms of Interprocess
Communication (IPC).
• It allows multiple processes to access the same region of
memory.
• Data is exchanged directly through memory, avoiding the
overhead of kernel involvement for each transfer (unlike
pipes or message queues).
Advantage
• Efficiency: No need to copy data between kernel and user
space multiple times.
• Speed: All processes can read/write the same data region
simultaneously.
• Use Case: Common in producer–consumer systems,
databases, or applications sharing large data sets.
Steps to Use Shared Memory
• Create or get a shared memory segment → shmget()
• Attach the segment to your address space → shmat()
• Access (read/write) the data
• Detach the segment → shmdt()
• Control or remove the segment → shmctl()
• shmget() — Create or Access a Shared Memory Segment
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
Parameters:
• key → unique key (use ftok() or IPC_PRIVATE)
• size → size in bytes
• shmflg → flags (e.g., IPC_CREAT | 0666)
Return:
• On success → shared memory identifier (shmid)
• On error → -1
• shmat() — Attach Shared Memory Segment
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);

Parameters:
• shmid → ID from shmget()
• shmaddr → desired address (usually NULL)
• shmflg → 0 for read/write, or SHM_RDONLY

Return:
• On success → pointer to attached memory segment
• On error → (void *) -1
shmdt() — Detach Shared Memory Segment
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
Parameter: shmaddr → address returned by shmat()
Return: 0 on success, −1 on error

shmctl() — Control Shared Memory Segment


#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
Parameters:
shmid → shared memory ID
cmd → operation (IPC_STAT, IPC_SET, IPC_RMID)
buf → pointer to struct shmid_ds
Return: 0 on success, −1 on error
Structure: struct shmid_ds
Stores control and status information about a shared memory
segment.
struct shmid_ds {
struct ipc_perm shm_perm; /* ownership and permissions */
size_t shm_segsz; /* size of segment (bytes) */
pid_t shm_lpid; /* PID of last shm operation */
pid_t shm_cpid; /* PID of creator */
shmatt_t shm_nattch; /* number of current attachments */
time_t shm_atime; /* last attach time */
time_t shm_dtime; /* last detach time */
time_t shm_ctime; /* last change time */
};
Shared Memory Writer (Producer)
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <string.h>
#define SHM_KEY 0x1234
int main(void)
{
int shmid;
char *shmaddr;
shmid = shmget(SHM_KEY, 1024, IPC_CREAT | 0666);
shmaddr = shmat(shmid, NULL, 0);
strcpy(shmaddr, "Hello from shared memory!");
printf("Data written successfully.\n");
shmdt(shmaddr);
return 0;
}
Shared Memory Reader (Consumer)
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#define SHM_KEY 0x1234
int main(void)
{
int shmid;
char *shmaddr;
shmid = shmget(SHM_KEY, 1024, 0666);
shmaddr = shmat(shmid, NULL, 0);
printf("Data read: %s\n", shmaddr);
shmdt(shmaddr);
shmctl(shmid, IPC_RMID, NULL); // remove shared segment
return 0;
}
Constant Description
IPC_CREAT Create the segment if it does not exist
IPC_EXCL Fail if segment already exists
IPC_RMID Remove the segment
SHM_RDONLY Attach for read-only access
IPC_PRIVATE Create a private shared memory key

Synchronization
Shared memory provides no synchronization.
To avoid race conditions: Use semaphores (semget, semop)
Client–Server Properties
Basic Client–Server Relationship
• Simplest form: Client forks and execs the server.
• Two half-duplex pipes are created before fork() for two-way
communication.
• The server may be a set-user-ID program, giving it special
privileges (e.g., root).
• The server can determine the client’s real user ID since exec does
not change real IDs.
Open Server Concept
• The server can perform open() calls on behalf of the client.
• Allows extra permission checks beyond normal UNIX file
permissions.
• The server (with elevated privileges) decides if the client can
access a file based on the client’s real UID.
• Works fine for regular files, but cannot pass file descriptors back
to parent processes easily.
Daemon-Type Servers
• The server runs independently (daemon process) and
communicates via IPC mechanisms.
• Pipes cannot be used; must use named IPC like:
• FIFOs
• Message Queues
• Shared Memory

Using FIFOs
• For one-way communication (client → server), one well-
known FIFO is enough.
• For two-way communication, each client needs its own FIFO.
• Example: System V printer spooler (lp command → lpsched
daemon).
Using Message Queues
Two techniques:
1. Single Queue (shared by all clients):
• Clients send requests with type = 1 and include their PID.
• Server replies using type = client’s PID.
2. Separate Queue per Client:
• Each client creates a private queue (IPC_PRIVATE).
• Server has a well-known queue.
• Client includes its queue ID in the first message.
• Drawbacks:
• Waste of limited system-wide queues.
• Server must manage multiple queues — select() and
poll() don’t work with message queues.
Shared Memory and Synchronization
• Message queues can be replaced with shared memory segments
and semaphores/record locking for coordination.
Client Identification & Authentication
• Important when the server has special privileges (set-user-ID).
• Kernel IPC mechanisms don’t automatically identify the sender.
• msg_lspid gives the client’s PID, but not its effective UID — not
portable to obtain UID from PID.

Identifying Clients Securely


Using FIFOs:
• Client creates its own FIFO with user-read/write only
permissions.
• Server uses stat()/fstat() to:
• Verify owner UID = client’s effective UID.
• Check permissions (user-read/write only).
• Check timestamps (recent — ≤ 30 seconds).
Using XSI IPC (msg, sem, shm):
Use the ipc_perm structure:
cuid, cgid → creator’s user/group IDs.
Server ensures the IPC object:
Has user-read/write only permissions.
Has recent timestamps.

Improved Method (Sockets)


In socket-based IPC, the kernel automatically provides:
Client’s effective user ID (EUID)
Client’s effective group ID (EGID)
Provides secure and reliable authentication
Passing File Descriptors
• Some inter-process-communication (IPC) mechanisms (pipes,
FIFOs, message queues, shared memory) allow only data transfer.
• UNIX domain sockets also allow transfer of open file descriptors
between processes.
• This lets one process (usually a privileged server) open a file and
send the open descriptor to another process (the client).

#include <sys/socket.h>
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
struct msghdr {
void *msg_name; /* optional address */
socklen_t msg_namelen; /* size of address */
struct iovec *msg_iov; /* scatter/gather array */
int msg_iovlen; /* elements in msg_iov[] */
void *msg_control; /* ancillary data */
socklen_t msg_controllen; /* length of ancillary data */
int msg_flags; /* flags returned by recvmsg() */
};

struct cmsghdr {
socklen_t cmsg_len; /* data length including header */
int cmsg_level; /* originating protocol (SOL_SOCKET) */
int cmsg_type; /* type (SCM_RIGHTS for fd passing) */
/* followed by unsigned char cmsg_data[] */
};
Steps

1. Prepare a msghdr structure.


2. Set up a cmsghdr whose data area contains the integer file
descriptor.
3. Call sendmsg() to transmit it.
4. The receiving process calls recvmsg(), retrieves the descriptor
from the control data, and uses it as a normal open file
descriptor.
/* send_fd: send one open file descriptor */
int send_fd(int sockfd, int fd_to_send);

/* recv_fd: receive one open file descriptor */


int recv_fd(int sockfd);

Example
• Client connects to server via UNIX-domain socket.
• Server opens a file (possibly with elevated privileges).
• Server calls send_fd() to transmit the open descriptor.
• Client receives it with recv_fd() and then can read()/write()
normally.
An Open Server – Version 1
• Build a server that performs privileged open() operations on
behalf of clients and passes back the open descriptor using
the mechanism above.

Server
• Runs with suitable privileges (e.g., set-user-ID root).
• Waits on a UNIX-domain socket for client requests.
• Each request contains the filename and open() flags.
• Calls open(pathname, oflag, mode) to obtain a descriptor.
• Returns the result:
• If success → uses send_fd() to pass descriptor.
• If failure → sends an error code.
Client
• Connects to the server socket.
• Sends filename and desired flags.
• Waits for either an error indication or a received descriptor
• On success, uses the descriptor for normal I/O.

/* open_server: main loop example */


int main(void)
{
int fd;
char buf[MAXLINE];
ssize_t n;

for ( ; ; ) {
/* read request from client */
if ( (n = read(STDIN_FILENO, buf, MAXLINE)) <= 0 )
break;
buf[n-1] = 0; /* strip newline */
if ( (fd = open(buf, O_RDONLY)) < 0 )
send_err(STDOUT_FILENO, errno, "cannot open");
else
send_fd(STDOUT_FILENO, fd);
close(fd);
}
exit(0);
}
int open_client(const char *pathname, int oflag)
{
int sockfd, fd;
sockfd = cli_conn(SERVER_PATH); /* connect to server */
write(sockfd, pathname, strlen(pathname)+1);
fd = recv_fd(sockfd); /* get descriptor */
close(sockfd);
return fd;
}

You might also like