0% found this document useful (0 votes)
66 views7 pages

Operating Systems Concept Assaignemtn 02

This document discusses the implementation of two communication models in C: pipes and remote procedure calls (RPCs). It provides code examples and explanations of how to create a pipe using the pipe() system call and write data between the pipe's file descriptors. It also explains how to define an RPC protocol using rpcgen, generate client and server stubs, make RPC calls from the client, and register and handle procedures on the server side using svc_run().

Uploaded by

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

Operating Systems Concept Assaignemtn 02

This document discusses the implementation of two communication models in C: pipes and remote procedure calls (RPCs). It provides code examples and explanations of how to create a pipe using the pipe() system call and write data between the pipe's file descriptors. It also explains how to define an RPC protocol using rpcgen, generate client and server stubs, make RPC calls from the client, and register and handle procedures on the server side using svc_run().

Uploaded by

radians042
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 7

OPERATING SYSTEMS CONCEPT

Muhammad Danial Khan


Muhammad Manzar

FA12-BCE-012
FA12-BCE-040

IMPLMENT ANY TWO COMMUNICATION MODELS


Implementation of ordinary pipes in C
To create a simple pipe with C, we make use of the pipe() system
call. It takes a single argument, which is an array of two integers,
and if successful, the array will contain two new file descriptors to
be used for the pipeline. After creating a pipe, the process typically
spawns a new process (remember the child inherits open file
descriptors).
SYSTEM CALL: pipe();
PROTOTYPE: int pipe( int fd[2] );
RETURNS: 0 on success
-1 on error: errno = EMFILE (no free descriptors)
EMFILE (system file table is full)
EFAULT (fd array is not valid)
NOTES: fd[0] is set up for reading, fd[1] is set up for writing

The first integer in the array (element 0) is set up and opened for reading, while the
second integer (element 1) is set up and opened for writing. Visually speaking, the
output of fd1 becomes the input for fd0. Once again, all data traveling through the
pipe moves through the kernel.
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
main()
{

int

fd[2];

pipe(fd);
.
.

Remember that an array name in C decays into a pointer to its first member.
Above, fd is equivalent to &fd[0]. Once we have established the pipeline, we then
fork our new child process:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
main()
{
int
pid_t

fd[2];
childpid;

pipe(fd);

if((childpid = fork()) == -1)


{
perror("fork");
exit(1);
}
.
.

If the parent wants to receive data from the child, it should close fd1, and the child
should close fd0. If the parent wants to send data to the child, it should close fd0, and
the child should close fd1. Since descriptors are shared between the parent and child,
we should always be sure to close the end of pipe we aren't concerned with. On a
technical note, the EOF will never be returned if the unnecessary ends of the pipe are
not explicitly closed.
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
main()
{
int
pid_t

fd[2];
childpid;

pipe(fd);
if((childpid = fork()) == -1)
{
perror("fork");
exit(1);
}
if(childpid == 0)
{
/* Child process closes up input side of pipe */
close(fd[0]);

}
else
{
}
.
.

/* Parent process closes up output side of pipe */


close(fd[1]);

As mentioned previously, once the pipeline has been established, the file descriptors
may be treated like descriptors to normal files.
/*****************************************************************************
Excerpt from "Linux Programmer's Guide - Chapter 6"
(C)opyright 1994-1995, Scott Burkett
*****************************************************************************
MODULE: pipe.c
*****************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(void)
{
int
pid_t
char
char

fd[2], nbytes;
childpid;
string[] = "Hello, world!\n";
readbuffer[80];

pipe(fd);
if((childpid = fork()) == -1)
{
perror("fork");
exit(1);
}
if(childpid == 0)
{
/* Child process closes up input side of pipe */
close(fd[0]);
/* Send "string" through the output side of pipe */
write(fd[1], string, (strlen(string)+1));
exit(0);
}
else
{

/* Parent process closes up output side of pipe */


close(fd[1]);
/* Read in a string from the pipe */

nbytes = read(fd[0], readbuffer, sizeof(readbuffer));


printf("Received string: %s", readbuffer);
}
return(0);
}

Implementation of Remote procedure calls


Defining the Protocol
The easiest way to define and generate the protocol is to use a protocol complier such
as rpcgen
For the protocol you must identify the name of the service procedures, and data types
of parameters and return arguments.
The protocol compiler reads a definitio and automatically generates client and server
stubs.
uses its own language (RPC language or RPCL) which looks very similar to
preprocessor directives.
rpcgen

exists as a standalone executable compiler that reads special files denoted by


a .x prefix.
rpcgen

So to compile a RPCL file you simply do


rpcgen rpcprog.x

This will generate possibly four files:

rpcprog_clnt.c

rpcprog_svc.c

-- the server stub

rpcprog_xdr.c

-- If necessary XDR (external data representation) filters

rpcprog.h

The Client side

-- the client stub

-- the hearder file needed for any XDR filter

There is just one function on the client side of the simplified interface

rpc_call().

It has nine parameters:


int
rpc_call (char *host /* Name of server host */,
u_long prognum /* Server program number */,
u_long versnum /* Server version number */,
xdrproc_t inproc /* XDR filter to encode arg */,
char *in /* Pointer to argument */,
xdr_proc_t outproc /* Filter to decode result */,
char *out /* Address to store result */,
char *nettype /* For transport selection */);

This function calls the procedure specified by prognum, versum, and procnum on the
host. The argument to be passed to the remote procedure is pointed to by
the in parameter, and inproc is the XDR filter to encode this argument.
The out parameter is an address where the result from the remote procedure is to be
placed. outproc is an XDR filter which will decode the result and place it at this
address.
The client blocks on rpc_call() until it receives a reply from the server. If the server
accepts, it returns RPC_SUCCESS with the value of zero. It will return a non-zero value if
the call was unsuccessful. This value can be cast to the type clnt_stat, an enumerated
type defined in the RPC include files (<rpc/rpc.h>) and interpreted by
the clnt_sperrno() function. This function returns a pointer to a standard RPC error
message corresponding to the error code. In the example, all "visible" transports listed
in /etc/netconfig are tried. Adjusting the number of retries requires use of the lower
levels of the RPC library. Multiple arguments and results are handled by collecting
them in structures.
The example changed to use the simplified interface, looks like
#include
#include
#include
#include

<stdio.h>
<utmp.h>
<rpc/rpc.h>
<rpcsvc/rusers.h>

/* a program that calls the RUSERSPROG


* RPC program
*/
main(int argc, char **argv)
{

unsigned long nusers;


enum clnt_stat cs;
if (argc != 2) {
fprintf(stderr, "usage: rusers hostname\n");

exit(1);

if( cs = rpc_call(argv[1], RUSERSPROG,


RUSERSVERS, RUSERSPROC_NUM, xdr_void,
(char *)0, xdr_u_long, (char *)&nusers,
"visible") != RPC_SUCCESS ) {
clnt_perrno(cs);
exit(1);
}

fprintf(stderr, "%d users on %s\n", nusers, argv[1] );


exit(0);

Since data types may be represented differently on different


machines, rpc_call() needs both the type of, and a pointer to, the RPC argument
(similarly for the result). For RUSERSPROC_NUM, the return value is an unsigned long, so
the first return parameter of rpc_call() is xdr_u_long (which is for an unsigned long)
and the second is &nusers (which points to unsigned long storage).
Because RUSERSPROC_NUM has no argument, the XDR encoding function
of rpc_call() is xdr_void() and its argument is NULL.

The server side


The server program using the simplified interface is very straightforward. It simply
calls rpc_reg() to register the procedure to be called, and then it calls svc_run(), the
RPC library's remote procedure dispatcher, to wait for requests to come in.
rpc_reg()

has the following prototype:

rpc_reg(u_long prognum /* Server program number */,


u_long versnum /* Server version number */,
u_long procnum /* server procedure number */,
char *procname /* Name of remote function */,
xdrproc_t inproc /* Filter to encode arg */,
xdrproc_t outproc /* Filter to decode result */,
char *nettype /* For transport selection */);

invokes service procedures in response to RPC call messages. The


dispatcher in rpc_reg() takes care of decoding remote procedure arguments and
encoding results, using the XDR filters specified when the remote procedure was
registered. Some notes about the server program:
svc_run()

Most RPC applications follow the naming convention of appending a _1 to the


function name. The sequence _n is added to the procedure names to indicate the
version number n of the service.


The argument and result are passed as addresses. This is true for all functions
that are called remotely. If you pass NULL as a result of a function, then no reply is sent
to the client. It is assumed that there is no reply to send.

The result must exist in static data space because its value is accessed after the
actual procedure has exited. The RPC library function that builds the RPC reply
message accesses the result and sends the value back to the client.

Only a single argument is allowed. If there are multiple elements of data, they
should be wrapped inside a structure which can then be passed as a single entity.

The procedure is registered for each transport of the specified type. If the type
parameter is (char *)NULL, the procedure is registered for all transports specified
in NETPATH.
You can sometimes implement faster or more compact code than
can rpcgen. rpcgen handles the generic code-generation cases. The following program
is an example of a hand-coded registration routine. It registers a single procedure and
enters svc_run() to service requests.
#include <stdio.h>
#include <rpc/rpc.h>
#include <rpcsvc/rusers.h>
void *rusers();
main()
{
if(rpc_reg(RUSERSPROG, RUSERSVERS,
RUSERSPROC_NUM, rusers,
xdr_void, xdr_u_long,
"visible") == -1) {
fprintf(stderr, "Couldn't Register\n");
exit(1);
}
svc_run(); /* Never returns */
fprintf(stderr, "Error: svc_run returned!\n");
exit(1);
}

can be called as many times as is needed to register different programs,


versions, and procedures.
rpc_reg()

You might also like