Create a communications channel
#include <sys/neutrino.h> int ChannelCreate( unsigned flags ); int ChannelCreate_r( unsigned flags );
For more information, see below.
libc
Use the -l c option to qcc to link against this library. This library is usually included automatically.
The ChannelCreate() and ChannelCreate_r() kernel calls create a channel that can be used to receive messages and pulses. Once created, the channel is owned by the process and isn't bound to the creating thread.
These functions are identical, except in the way they indicate errors. See the Returns section for details.
Threads wishing to communicate with the channel attach to it by calling ConnectAttach(). The threads may be in the same process, or in another process on the same node (or a remote node if the network manager is running).
If a process wants other processes to communicate with it, it typically uses name_attach() to create a channel and associate a name with it, and the sender process uses name_open() to locate that name and create a connection to it. |
Once attached, these threads use MsgSendv() or MsgSendPulse() to enqueue messages and pulses on the channel. Messages and pulses are enqueued in priority order.
To dequeue and read messages and pulses from a channel, use MsgReceivev(). Any number of threads may call MsgReceivev() at the same time, in which case they block and queue (if no messages or pulses are waiting) for a message or pulse to arrive. A multi-threaded I/O manager typically creates multiple threads and has them all RECEIVE-blocked on the channel.
The return value of ChannelCreate() is a channel ID, an int taken from a channel vector on the process. Most managers use a single channel for most, if not all, their communications with clients. Additional channels can be used as special channels for information.
By default, when a message is received from a channel, the thread priority of the receiver is set to match that of the thread that sent the message. This basic priority inheritance prevents priority inversion. If a message arrives at a channel and there's no thread waiting to receive it, the system boosts (if necessary) all threads in the process that have received a message from the channel in the past. This boost prevents a priority inversion of the client in the case where all threads are currently working on behalf of other clients, perhaps at a lower priority. For more information, see “Server boost” in the Interprocess Communication chapter of the System Architecture guide.
Priority inheritance can be disabled by setting _NTO_CHF_FIXED_PRIORITY in the flags argument. In this case a thread's priority isn't affected by messages it receives on a channel.
A manager typically involves the following loop. There may be one or more threads in the loop at a time. Typically your program calls ChannelCreate() only once, and all threads block on that channel.
iov_t iov; ... SETIOV( &iov, &msg, sizeof( msg ) ); ... chid = ChannelCreate(flags); ... for(;;) { /* Here's a one-part message; you could just as easily receive a 20-part message by filling in the iov appropriately. */ rcvid = MsgReceivev(chid, &iov, 1, &info); /* msg is filled in by MsgReceivev() */ switch(msg.type) { ... } /* iov could be filled in again to point to a new message */ MsgReplyv(rcvid, status, iov, 1); }
Some of the channel flags in the flags argument request changes from the default behavior; others request notification pulses from the kernel. The pulses are received by MsgReceivev() on the channel and are described by a _pulse structure.
The channel flags and (where appropriate) associated values for the pulse's code and value are described below.
Pulse code | Pulse value |
---|---|
_PULSE_CODE_COIDDEATH | Connection ID (coid) of a connection that was attached to a destroyed channel. |
If a server exits or closes its channel at more or less the same time that the client closes a connection to the channel, the kernel might or might not send a _PULSE_CODE_COIDDEATH pulse to the client. If the client then opens a new connection to another server before getting the pulse, the pulse will seem to indicate that it's the new server that has died. Your code for handling the _PULSE_CODE_COIDDEATH pulse needs to include something like this:
void got_pulse(struct _pulse *pulse) { if(pulse->type == _PULSE_CODE_COIDDEATH) { int coid = pulse->value.sival_int; if(ConnectServerInfo(0, coid, NULL) != coid) { // server's really gone, so clean up the connection state } else { // stale pulse; probably can ignore it } } }
Pulse code | Pulse value |
---|---|
_PULSE_CODE_DISCONNECT | None |
If a process dies without detaching all its connections, the kernel detaches them from it. When this flag is set, the server must call ConnectDetach( scoid ) where scoid is the server connection ID in the pulse message. Failure to do so leaves an invalid server connection ID that can't be reused. Over time, the server may run out of available IDs. If this flag isn't set, the kernel removes the server connection ID automatically, making it available for reuse.
Pulse code | Pulse value |
---|---|
_PULSE_CODE_THREADDEATH | Thread ID (tid) |
Pulse code | Pulse value |
---|---|
_PULSE_CODE_UNBLOCK | Receive ID (rcvid) |
In most cases, you'll set the _NTO_CHF_UNBLOCK flag. |
If the sending thread unblocks, MsgReplyv() fails. The manager may not be in a position to handle this failure. It's also possible that the client will die because of the signal and never send another message. If the manager is holding onto resources for the client (such as an open file), it may want to receive notification that the client wants to break out of its MsgSendv().
Setting the _NTO_CHF_UNBLOCK bit in flags prevents a thread that's in the REPLY-blocked state from unblocking. Instead, a pulse is sent to the channel, informing the manager that the client wishes to unblock. In the case of a signal, the signal will be pending on the client thread. When the manager replies, the client is unblocked and at that point, any pending signals are acted upon. From the client's point of view, its MsgSendv() will have completed normally and any signal will have arrived on the opcode following the successful kernel call.
When the manager receives the pulse, it can do one of these things:
These calls don't block.
The channel ID of the newly created channel. If an error occurs:
Safety: | |
---|---|
Cancellation point | No |
Interrupt handler | No |
Signal handler | Yes |
Thread | Yes |
ChannelDestroy(), close(), ConnectAttach(), ConnectDetach(), _msg_info, MsgReceivev(), MsgReplyv(), MsgSendv(), MsgSendPulse(), name_attach(), name_close(), name_open(), _pulse
Message Passing chapter of Getting Started with QNX Neutrino