![]() |
![]() |
![]() |
Create a communications channel
#include <sys/neutrino.h> int ChannelCreate( unsigned flags ); int ChannelCreate_r( unsigned flags );
libc
The ChannelCreate() and ChannelCreate_r() functions are identical except in the way they indicate errors. See the Returns section for details.
These 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.
Threads wishing to communicate with the channel attach to it using the ConnectAttach() call. The threads may be in the same process, another process on the same node or a remote node if the network manager is running. 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.
When a message is received from a channel, the thread priority of the receiver is set to match that of the thread which 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 which 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. When a thread is first created, it isn't associated with a channel until it does a MsgReceivev() on it. In the case of multiple channels a thread is associated with the last channel it received from.
After receiving a message, a thread can dissociate itself from the channel by calling MsgReceivev() with a -1 for the channel ID. Priority inheritance can be disabled by setting _NTO_CHF_FIXED_PRIORITY in the flags argument. In this case a thread's priority won't be 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. Note that ChannelCreate() should be called only once and not by each thread.
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, iov, 1); }
The flags argument contains a number of flags that can be used to request notification pulses from the kernel. The pulses are received by MsgReceivev() on the channel and are described by a struct _pulse that contains at least the following members:
Member | Description |
---|---|
short unsigned type | 0 (indicates it's a pulse) |
short unsigned subtype | 0 (indicates it's a pulse) |
char code | A pulse code defined below |
union sigval value | Information relevant to the code |
long scoid | Server connection ID |
The channel notification flags and associated values for code and value (if any) are:
A pulse is delivered to this channel for each connection that belongs to the calling process when the channel that the connection is attached to is destroyed. Setting this flag also results in the equivalent behaviour of setting the _NTO_CHF_THREAD_DEATH flag.
A pulse is delivered when all connections from a process are detached (e.g. close(), ConnectDetach(), name_close()). 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 which 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.
Suppress priority inheritance when receiving messages.
Reserved for the io_net resource manager.
The dstmsglen member of the struct _msg_info structure passed as an argument to MsgReceivev() must be valid.
The srcmsglen member of the struct _msg_info structure passed as an argument to MsgReceivev() must be valid.
A pulse is delivered on the death of any thread in the process which owns the channel. Setting the _NTO_CHF_COID_DISCONNECT flag also results in the equivalent behaviour of setting this flag.
![]() |
In most cases, you'll set the _NTO_CHF_UNBLOCK flag. |
A pulse is delivered when a thread that's REPLY-blocked on a channel attempts to unblock before its message is replied to. This occurs between the time of a MsgReceivev() and a MsgReplyv() by the server. The sending thread may be unblocked because of a signal or a kernel timeout.
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 (like 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 two things. If it believes that it'll be replying shortly it can discard the pulse resulting in a small latency in the unblocking or signal to the client. A short blocking request to a filesystem often takes this approach. If the reply is going to take some time or an unknown amount of time, the manager should cancel the current operation and reply back with an error or whatever data is available at this time in the reply message to the client thread. A request to a device manager waiting for input would take this approach.
These calls don't block.
The only difference between these functions is the way they indicate errors:
Safety: | |
---|---|
Cancellation point | No |
Interrupt handler | No |
Signal handler | Yes |
Thread | Yes |
ChannelDestroy(), MsgReceivev()
![]() |
![]() |
![]() |