General TOC
Prev Mach IPC Programming TOC Next

A Little IPC Project

In this first section we are going to learn some basic skills needed to do real IPC, that also will help to understand the bunch of concepts learned in the previous chapter. As a starting point we are going to propose an easy programming project related with IPC; implementing it step by step would allow us to learn gradually how Inter Process Communication is programmed in Mach.

Overview of The Project

The proposed project will be quite simple. In it there is an isolated task, that is going to create a port name with a receive port right. The purpose is that the task send some message to itself and later receive it correctly; this has no practical aplication and is somewhat artificial, but will let us to concentrate only in the fundamental concepts, forgotting everything that is not pure IPC. Also we are no cheating with this example. Since the message are stored in the port's message queue inside the kernel, if we destroy the original sent message ( for example, by sending it in a function; the message would be stored in a local variable and then destroyed when the function returned the control to the main program ), the only way to receive the correct original message's content is to program the IPC correctly.

The project is going to be divided in small, easy parts :

General Setup :

Suppose that, as stated above, there is a Mach task, and this task wants to create a port name with a receive port right. This would also create a port inside the kernel. The only problem is to code this situation, but it's quite easy. The diagram below explains the situation :

TODO : explain mach_port_t, mach_port_allocate and mach_task_self; after the explanations propose the reader to code it himself before continuing.

I suppose that you have already thought the problem. The corresponding code would be :

    mach_port_t rcv_port;

    mach_port_allocate( mach_task_self(), MACH_PORT_RIGHT_RECEIVE, 
                        &rcv_port );

the code above would allocate a port name with a receive port right inside; but there is something that this code lacks : error checking. So better if we write some little error chacking and avoid surprises :

    kern_return_t err; 
    mach_port_t rcv_port;
   
    err = mach_port_allocate( mach_task_self(), MACH_PORT_RIGHT_RECEIVE, 
                              &rcv_port );

    if( err != KERN_SUCCESS ){
	    perror( "error : could not allocate any port\n" );
	    exit(err);
    }

And what could be done with this port right ? The truth is that not much. It is very unlikely that anybody sends a message to this new port. Also, we don't know any way to tell friend processes that a port with receive rights is here, so they could send us some message. Then the only thing that this process can do with our actual knowledge is to pass a message to itself, and that's the reason why I did choose this scheme.

The next thing then is to express, in C language, the mach's message structure for a message containing just an integer. Remember that a mach's message starts with a header ( of type mach_msg_header_t ), and after the header there are type descriptors ( of type mach_msg_type_t or mach_msg_type_long_t ), followed by the data they declare. So the more natural way is group then inside the same C struct ( and hope our compiler has a non-perverse way of representing internally C structs; Just use gcc and you won't have those problems ) :


struct integer_message
{
    mach_msg_header_t head;
    mach_msg_type_t type;

    int inline_integer;
};

For sending the integer_message to the port name inside which the receive right is contained, the idea was to create a send_integer function; this function is explained in the next section.

Writing a Message Sending Function :

As explained above, mach_msg is quite complex; we take this fact as an excuse for writing a function with the only purpose of sending an integer, in a way easy enough that even somebody that knew nothing about mach could use it. A good interface for such a function is the one below :


void 
send_integer( mach_port_t destination, int i );

of course it is unavoidable that inside send_integer there is a mach_msg system call; so let's remember how mach_msg was, and then as another exercise you reader must though how would you invoke this function to send a message.

The mach_msg function had the following syntax :


mach_msg_return_t mach_msg( mach_msg_header_t* msg,
                            mach_msg_option_t option,
			    mach_msg_size_t send_size,
			    mach_msg_size_t rcv_size,
			    mach_port_t rcv_name,
			    mach_msg_timeout_t timeout,
			    mach_port_t notify );

is fairly complex, mainly because there is a large amount of functionality inside it. It serves both for sending and receiving messages, and let you set some other parameters too that are not in the message's header. So let's study it, and learn how to supply correct parameters to mach_msg :

This was an overview with the purpose of understanding the easy case with which we are treating. For further information about mach_msg look at the OSF mach's documents and of course The GNU Mach Reference Manual. Now stop reading, start to think, and write in a paper how would you invoke mach_msg

The code you have though as an exersice would look like this :


kern_return_t err;
struct integer_message message;

/* Some code filling the message */

err = mach_msg( &(message.head), MACH_SEND_MSG, 
                message.head.msgh_size, 0, MACH_PORT_NULL,
                MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL );

if( err != KERN_SUCCESS ){
   /* Some error message... */
}else{
   printf( "success : the message was queued\n" );
}

Let's coninue them with the construction of the send_integer function; as you can appreciate, between the start of this function's code and the sending of the message, there was a comment telling us to fill the message :

/* Some code filling the message */

The next task for the construction of send_integer then is to fill the message's header and the message's type descriptor. Remember first the structures associated with them :


typedef struct {
        mach_msg_bits_t msgh_bits;
	mach_msg_size_t msgh_size;
	mach_port_t msgh_remote_port;
	mach_port_t msgh_local_port;
	mach_port_seqno_t msgh_seqno;
	mach_msg_id_t msgh_id;
} mach_msg_header_t;

and :


typedef struct {
        unsigned int msgt_name:8,
	             msgt_size:8,
		     msgt_number:12,
		     msgt_inline:1,
		     msgt_longform:1,
		     msgt_deallocate:1,
		     msgt_unused:1;
} mach_msg_type_t;

There's is also another message's type descriptor structure for long messages ( in an i386 machine, by a long message we mean a 64bit message ). For a further review about these issues again you can read the OSF mach's documents. Since the objetive is to send an integer with mach_msg_type_t is enough.

So, as with the mach_msg case, we give a brief review of mach_msg_header_t, and later we write the code :

The message's type descriptor is fairly more easy than the message's header; let's see :

The message related code would finally look like this :


    /* (i) Form the message : */

    /* (i.a) Fill the header fields : */
    message.head.msgh_bits = MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_MAKE_SEND);
    message.head.msgh_size = sizeof( struct simple_message );
    message.head.msgh_local_port = MACH_PORT_NULL;
    message.head.msgh_remote_port = destination;
    
    /* (i.b) Explain the message type ( an integer ) */
    message.type.msgt_name = MACH_MSG_TYPE_INTEGER_32;
    message.type.msgt_size = 32;
    message.type.msgt_number = 1;
    message.type.msgt_inline = TRUE;
    message.type.msgt_longform = FALSE;
    message.type.msgt_deallocate = FALSE;
    
    /* (i.c) Fill the message with the given integer : */
    message.inline_integer = i;

What remains ? Just to glue all those code pieces together. The complete send_integer source here.

Writing a Message Receiving Function

This is very much more easy than the send_integer, since there is only need to supply few fields of mach_msg_header_t, and even none of mach_msg_type_t. A good interface for such function is :


void 
receive_integer( mach_port_t source, int *ip );

If I'm not wrong receive_integer would work with only filling msgh_size ( even there is no apparent need of msgh_local_port ). This makes it more straightforward to write. The other issue that remains is invoke correctly mach_msg for the purpose of receive a message, but with the example above it should be easy. It is left as an exercise to the reader; you can find the solution here

Coding All Together

Only a few things remain to be programmed. The needed work is merging all the parts of the program, and make it work to see whether it works. The conceptual image would be like the one showed in this diagram :

The main program allocates a port name with a receive port right; it also declares two integers, one as argument of send_integer, and the other one as an output argument for receive_integer; later must be checked if both integers coincide ( the message was received correctly ) :


int s, r; /* s -> integer to send; r -> integer to receive */

/* intermediate code */

s= 3; /* or any other number */

send_integer( rcv_port, s );
receive_integer( rcv_port, &r )

/* some error checking code; did the message transmit correctly ? */

Again, since the most difficult parts are already done, is left as an exercise to the to complete the code. Is the most easy part, but anyway you can see the solution here.

Last, nut nor least, here is a copy of the license of these programs.

Playing a Bit More

Before ending this basic introduction, we are going to try some mach's functions that work with ports, and then learn a bit more. This section will grow a little with time when new pedagogic examples about other IPC functionality is thought. For the moment have fun with :

Renaming Ports

Maybe our project's program would be nicer and cleaner in case that when we invoke send_integer and receive_integer differents port's names could be used; for example :


send_integer( destination, s );
/* some code */
receive_integer( source, &r );

This is almost trivial using a mach's system call named mach_port_rename; it's declaration is like this :

kern_return_t mach_port_rename( mach_port_t task,
	                        mach_port_t old_name,
                                mach_port_t new_name ); 

as you can appreciate is quite straightforward; then implement it between a send_integer and a receive_integer. The solution, as usual, here.

Setting the Queue Limit

Another interesting thing about message passing in mach, is that when you send more messages than the queues accepts ( it's qlimit ), then the sender process gets blocked. Associated with this there's another call with the purpose of changing the limit of messages the queues accept. Is this :

kern_return_t mach_port_set_qlimit( mach_port_t task,
                                    mach_port_t name,
	                            mach_port_msgcount_t qlimit );

You can play with this function a little and implement a program that sets the queue limit to some size, and then sends messages to that message queue until it gets blocked ( of course checking that your program blocks at the point is expected ). An example of this kind of programs is here.