<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang=""> <head> <meta charset="utf-8" /> <meta name="generator" content="pandoc" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" /> <title>Blog Article on Message Passing in VExec</title> <style>

code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa;  padding-left: 4px; }
div.sourceCode
{   }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */

</style> </head> <body> <p>Message exchange using AmigaOS-style message ports and message headers works only between threads. Messages are identified by and queued using direct pointers to the message headers, which are intrusively prepended to the intended message payload; additionally, message payloads frequently include pointers to ancillary data structures, basically rendering the <code>msg_length</code> field basically useless.</p> <p>By making messages a proper kernel-managed resource, we can treat messages similarly to virtual files, exposing a precisely managed window into the sender’s address space. Messages encapsulated this way retain much of the desirable developer experience that the original AmigaOS is well known for. Although message payloads must now be copied, this approach reduces the number of copies to a minimum of just one.</p> <p>NOTE. The text that follows is hypothetical and forward looking. The design contained herein is preliminary. It’s also written in C, even though the system call interfaces will be designed around the RISC-V assembly language.</p> <h1 id="using-message-channels-to-receive-messages">Using Message Channels To Receive Messages</h1> <p>To receive messages, a task must first create a message channel. The recommended process for creating a new message channel is to first allocate a signal (see <code>AllocSignal()</code>) for it to use when receiving a new message. Next, create the channel proper (see <code>NewChannel()</code>), setting optional attributes as desired by the application.</p> <p>The following code illustrates how to create a new channel with a custom-assigned signal bit:</p>

<pre class="sourceCode c"><code class="sourceCode c"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true"></a><span class="dt">int</span> errcode;</span> <span id="cb1-2"><a href="#cb1-2" aria-hidden="true"></a><span class="dt">uint8_t</span> my_sig_bit = AllocSignal(&amp;errcode);</span> <span id="cb1-3"><a href="#cb1-3" aria-hidden="true"></a><span class="cf">if</span>(errcode != <span class="dv">0</span>) {</span> <span id="cb1-4"><a href="#cb1-4" aria-hidden="true"></a> <span class="co">// No more signal bits</span></span> <span id="cb1-5"><a href="#cb1-5" aria-hidden="true"></a>}</span> <span id="cb1-6"><a href="#cb1-6" aria-hidden="true"></a></span> <span id="cb1-7"><a href="#cb1-7" aria-hidden="true"></a>ChannelAttrs chan_attrs = {</span> <span id="cb1-8"><a href="#cb1-8" aria-hidden="true"></a> .sig_bit = my_sig_bit,</span> <span id="cb1-9"><a href="#cb1-9" aria-hidden="true"></a>};</span> <span id="cb1-10"><a href="#cb1-10" aria-hidden="true"></a><span class="dt">uint64_t</span> chanID = NewChannel(&amp;chan_attrs, CHATTR_SIGBIT, &amp;errcode);</span> <span id="cb1-11"><a href="#cb1-11" aria-hidden="true"></a><span class="cf">if</span>(errcode != <span class="dv">0</span>) {</span> <span id="cb1-12"><a href="#cb1-12" aria-hidden="true"></a> <span class="co">// Channel creation failed for some reason</span></span> <span id="cb1-13"><a href="#cb1-13" aria-hidden="true"></a>}</span></code></pre>
<p>If successful, this will return a number uniquely identifying the channel in the VM/OS environment. The channel will be configured to signal the task that created it with the signal bit specified.</p> <p>NOTE. You could create the channel and leave CHATTR_SIGBIT clear, at which time VExec will try to allocate a sigbit on your behalf. However, to find out what bit it allocated, you’ll later need to query the channel’s attributes. You might as well just allocate the signal bit yourself and set it manually upon new channel creation. This also allows several different channels to share a common signal bit.</p> <p>Once a channel is created, it can be published so that other tasks can now locate it by name.</p> <pre><code>chan_attrs.name = &quot;ServerChannel&quot;; chan_attrs.is_public = 1; SetChanAttrs(chanID, &amp;chan_attrs, CHATTR_NAME | CHATTR_ISPUBLIC, &amp;errcode); if(errcode != 0) {

// Channel cannot be made public for some reason.

}</code></pre> <p>At this point, the server code can wait on the channel in the normal way.</p>

<pre class="sourceCode c"><code class="sourceCode c"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true"></a><span class="dt">uint64_t</span> channel_sigmask = <span class="dv">1</span> &lt;&lt; my_sig_bit;</span> <span id="cb3-2"><a href="#cb3-2" aria-hidden="true"></a><span class="co">// Assume this_mask and that_mask have already been initialized elsewhere.</span></span> <span id="cb3-3"><a href="#cb3-3" aria-hidden="true"></a></span> <span id="cb3-4"><a href="#cb3-4" aria-hidden="true"></a>sigs = Wait(channel_sigmask | this_mask | that_mask);</span> <span id="cb3-5"><a href="#cb3-5" aria-hidden="true"></a></span> <span id="cb3-6"><a href="#cb3-6" aria-hidden="true"></a><span class="cf">if</span>(sigs &amp; my_channel_event) {</span> <span id="cb3-7"><a href="#cb3-7" aria-hidden="true"></a> handle_my_channel(chanID);</span> <span id="cb3-8"><a href="#cb3-8" aria-hidden="true"></a>}</span> <span id="cb3-9"><a href="#cb3-9" aria-hidden="true"></a></span> <span id="cb3-10"><a href="#cb3-10" aria-hidden="true"></a><span class="cf">if</span>(sigs &amp; this_mask) {</span> <span id="cb3-11"><a href="#cb3-11" aria-hidden="true"></a> handle_this_thing(...etc...);</span> <span id="cb3-12"><a href="#cb3-12" aria-hidden="true"></a>}</span> <span id="cb3-13"><a href="#cb3-13" aria-hidden="true"></a></span> <span id="cb3-14"><a href="#cb3-14" aria-hidden="true"></a><span class="cf">if</span>(sigs &amp; that_mask) {</span> <span id="cb3-15"><a href="#cb3-15" aria-hidden="true"></a> handle_that_thing(...etc...);</span> <span id="cb3-16"><a href="#cb3-16" aria-hidden="true"></a>}</span></code></pre>
<p>Once we know that the channel has received an event, we know at least one message has arrived since we last checked its status. We can then grab and process messages off of it, one by one, until the channel has been drained.</p>
<pre class="sourceCode c"><code class="sourceCode c"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true"></a><span class="dt">void</span> handle_my_channel(<span class="dt">uint64_t</span> chanID) {</span> <span id="cb4-2"><a href="#cb4-2" aria-hidden="true"></a> MsgID msg;</span> <span id="cb4-3"><a href="#cb4-3" aria-hidden="true"></a> <span class="dt">int</span> errcode;</span> <span id="cb4-4"><a href="#cb4-4" aria-hidden="true"></a></span> <span id="cb4-5"><a href="#cb4-5" aria-hidden="true"></a> <span class="cf">for</span>(;;) {</span> <span id="cb4-6"><a href="#cb4-6" aria-hidden="true"></a> msg = GetChannelMsg(chanID, &amp;errcode);</span> <span id="cb4-7"><a href="#cb4-7" aria-hidden="true"></a> <span class="cf">if</span>(errcode == <span class="dv">0</span>) {</span> <span id="cb4-8"><a href="#cb4-8" aria-hidden="true"></a> handle_nonlocal_message(msg);</span> <span id="cb4-9"><a href="#cb4-9" aria-hidden="true"></a> } <span class="cf">else</span> {</span> <span id="cb4-10"><a href="#cb4-10" aria-hidden="true"></a> <span class="co">// Most likely, we just emptied the channel of messages.</span></span> <span id="cb4-11"><a href="#cb4-11" aria-hidden="true"></a> <span class="cf">break</span>;</span> <span id="cb4-12"><a href="#cb4-12" aria-hidden="true"></a> }</span> <span id="cb4-13"><a href="#cb4-13" aria-hidden="true"></a> }</span> <span id="cb4-14"><a href="#cb4-14" aria-hidden="true"></a>}</span></code></pre>
<p>We’ll discuss handling of messages in the next section.</p> <p>You can dispose of a channel, assuming it has been completely emptied.</p>
<pre class="sourceCode c"><code class="sourceCode c"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true"></a>errcode = DisposeChannel(chanID);</span> <span id="cb5-2"><a href="#cb5-2" aria-hidden="true"></a><span class="cf">if</span>(errcode != <span class="dv">0</span>) {</span> <span id="cb5-3"><a href="#cb5-3" aria-hidden="true"></a> <span class="co">// Most likely, the channel isn&#39;t empty.</span></span> <span id="cb5-4"><a href="#cb5-4" aria-hidden="true"></a>}</span></code></pre>
<p>So far, the basic work-flow is pretty much identical to in-process message ports and traditional messages, except that instead of pointers, we’re now dealing with opaque handles for the various data objects. The biggest difference, however, will come from processing messages; recall that, so far, we still don’t have access to the message’s content yet.</p> <h1 id="receiving-messages">Receiving Messages</h1> <p>To recover the content of a message, you must read the message. Unlike classical AmigaOS, where you can just reference a message’s structure elements directly, we cannot do that in VM/OS. The message data still resides in the sender’s address space at this point.</p> <p>To read a message, you can use <code>ReadChanMsg()</code>. This function is responsible for copying the message data out of the sender’s address space and directly into your own address space. Note that the message itself maintains a cursor, indicating from where it should start reading. When you first receive the message, the cursor is reset to offset zero.</p>
<pre class="sourceCode c"><code class="sourceCode c"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true"></a><span class="dt">void</span> handle_nonlocal_message(<span class="dt">uint64_t</span> msg) {</span> <span id="cb6-2"><a href="#cb6-2" aria-hidden="true"></a> <span class="kw">struct</span> MyMessageHeader header;</span> <span id="cb6-3"><a href="#cb6-3" aria-hidden="true"></a> <span class="kw">struct</span> Body1 b1;</span> <span id="cb6-4"><a href="#cb6-4" aria-hidden="true"></a> <span class="kw">struct</span> Body2 b2;</span> <span id="cb6-5"><a href="#cb6-5" aria-hidden="true"></a> <span class="dt">int</span> errcode;</span> <span id="cb6-6"><a href="#cb6-6" aria-hidden="true"></a></span> <span id="cb6-7"><a href="#cb6-7" aria-hidden="true"></a> <span class="dt">int64_t</span> actual = ReadChanMsg(msg, &amp;header, <span class="kw">sizeof</span>(header));</span> <span id="cb6-8"><a href="#cb6-8" aria-hidden="true"></a> <span class="cf">if</span>(actual != <span class="kw">sizeof</span>(header)) {</span> <span id="cb6-9"><a href="#cb6-9" aria-hidden="true"></a> <span class="co">// Message that was sent is too small even for a proper header.</span></span> <span id="cb6-10"><a href="#cb6-10" aria-hidden="true"></a> <span class="cf">goto</span> msg_doone;</span> <span id="cb6-11"><a href="#cb6-11" aria-hidden="true"></a> }</span> <span id="cb6-12"><a href="#cb6-12" aria-hidden="true"></a></span> <span id="cb6-13"><a href="#cb6-13" aria-hidden="true"></a> <span class="cf">switch</span>(header.request_code) {</span> <span id="cb6-14"><a href="#cb6-14" aria-hidden="true"></a> <span class="cf">case</span> DO_REQUEST_1:</span> <span id="cb6-15"><a href="#cb6-15" aria-hidden="true"></a> actual = ReadChanMsg(msg, &amp;b1, <span class="kw">sizeof</span>(b1));</span> <span id="cb6-16"><a href="#cb6-16" aria-hidden="true"></a> <span class="cf">if</span>(actual == <span class="kw">sizeof</span>(b1)) {</span> <span id="cb6-17"><a href="#cb6-17" aria-hidden="true"></a> <span class="co">// Process request 1 here.</span></span> <span id="cb6-18"><a href="#cb6-18" aria-hidden="true"></a> }</span> <span id="cb6-19"><a href="#cb6-19" aria-hidden="true"></a> <span class="cf">goto</span> msg_done;</span> <span id="cb6-20"><a href="#cb6-20" aria-hidden="true"></a></span> <span id="cb6-21"><a href="#cb6-21" aria-hidden="true"></a> <span class="cf">case</span> DO_REQUEST_2:</span> <span id="cb6-22"><a href="#cb6-22" aria-hidden="true"></a> actual = ReadChanMsg(msg, &amp;b2, <span class="kw">sizeof</span>(b2));</span> <span id="cb6-23"><a href="#cb6-23" aria-hidden="true"></a> <span class="cf">if</span>(actual == <span class="kw">sizeof</span>(b2)) {</span> <span id="cb6-24"><a href="#cb6-24" aria-hidden="true"></a> <span class="co">// Process request 1 here.</span></span> <span id="cb6-25"><a href="#cb6-25" aria-hidden="true"></a> }</span> <span id="cb6-26"><a href="#cb6-26" aria-hidden="true"></a> <span class="cf">goto</span> msg_done;</span> <span id="cb6-27"><a href="#cb6-27" aria-hidden="true"></a></span> <span id="cb6-28"><a href="#cb6-28" aria-hidden="true"></a> <span class="cf">default</span>:</span> <span id="cb6-29"><a href="#cb6-29" aria-hidden="true"></a> <span class="co">// Unknown message type; just reply to it.</span></span> <span id="cb6-30"><a href="#cb6-30" aria-hidden="true"></a> }</span> <span id="cb6-31"><a href="#cb6-31" aria-hidden="true"></a>msg_done:</span> <span id="cb6-32"><a href="#cb6-32" aria-hidden="true"></a> errcode = ReplyChanMsg(msg);</span> <span id="cb6-33"><a href="#cb6-33" aria-hidden="true"></a> <span class="cf">if</span>(errcode != <span class="dv">0</span>) {</span> <span id="cb6-34"><a href="#cb6-34" aria-hidden="true"></a> <span class="co">// Message reply failed; even so, it is no longer eligible for use</span></span> <span id="cb6-35"><a href="#cb6-35" aria-hidden="true"></a> <span class="co">// by this task anymore.</span></span> <span id="cb6-36"><a href="#cb6-36" aria-hidden="true"></a> }</span> <span id="cb6-37"><a href="#cb6-37" aria-hidden="true"></a>}</span></code></pre>
<p>Of course, <code>ReadChanMsg()</code> assumes you can read the message sequentially, from start to end. That’s usually what you want to do; however, if you need to seek elsewhere and read, you can do so with <code>ReadChanMsgAt(msg, buffer, size, offset)</code>. This sets the cursor to <code>offset</code>, and then falls into <code>ReadChanMsg()</code>.</p> <h1 id="replying-to-messages">Replying to Messages</h1> <p>If you have data you wish to send back to the sender, which is frequently the case, you can do so with <code>WriteChanMsg()</code> and <code>WriteChanMsgAt()</code>, which behave similarly to their read-counterparts, except that it copies data back into the sender’s address space.</p> <p><code>ReplyChanMsg()</code> sends the message back to its sender, by way of its reply channel. NOTE: Once a message has been replied, it can no longer be read from or written to (unless the sender re-sends the message, of course).</p> <h1 id="sending-messages">Sending Messages</h1> <p>Creating a message is somewhat similar to receiving one. You have a (set of) data structure(s), and then you must create a new message to represent the memory those structures occupy.</p> <p>The <code>NewMsgQuick()</code> function is usually sufficient; it takes a single buffer and a reply channel ID to create a new message. However, if you have more complicated requirements, you can represent the structure using an I/O vector, which is used for scatter/gather operations.</p> <p>For example, if we represent a Unix-style filesystem call as a message to a file server somewhere, we might see logic like so:</p>
<pre class="sourceCode c"><code class="sourceCode c"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true"></a><span class="co">// Example implementation of write().</span></span> <span id="cb7-2"><a href="#cb7-2" aria-hidden="true"></a><span class="co">// I am going to skip error handling for brevity&#39;s sake.</span></span> <span id="cb7-3"><a href="#cb7-3" aria-hidden="true"></a><span class="dt">size_t</span> write(<span class="dt">int</span> fd, <span class="dt">void</span> *buffer, <span class="dt">size_t</span> length) {</span> <span id="cb7-4"><a href="#cb7-4" aria-hidden="true"></a> <span class="kw">struct</span> WriteRequest wr = {</span> <span id="cb7-5"><a href="#cb7-5" aria-hidden="true"></a> .fd = fd,</span> <span id="cb7-6"><a href="#cb7-6" aria-hidden="true"></a> .req_length = length,</span> <span id="cb7-7"><a href="#cb7-7" aria-hidden="true"></a> .actual_length = <span class="dv">0</span>, <span class="co">// will be filled in later</span></span> <span id="cb7-8"><a href="#cb7-8" aria-hidden="true"></a> .errcode = <span class="dv">0</span>, <span class="co">// will be filled in later</span></span> <span id="cb7-9"><a href="#cb7-9" aria-hidden="true"></a> };</span> <span id="cb7-10"><a href="#cb7-10" aria-hidden="true"></a></span> <span id="cb7-11"><a href="#cb7-11" aria-hidden="true"></a> <span class="co">// Because no data copies happen until the last possible moment,</span></span> <span id="cb7-12"><a href="#cb7-12" aria-hidden="true"></a> <span class="co">// we can directly reference the supplied memory buffer in our IOVec.</span></span> <span id="cb7-13"><a href="#cb7-13" aria-hidden="true"></a> <span class="co">// The file server will &quot;see&quot; this message as a single WriteRequest-</span></span> <span id="cb7-14"><a href="#cb7-14" aria-hidden="true"></a> <span class="co">// prefixed buffer of length sizeof(wr)+length bytes.</span></span> <span id="cb7-15"><a href="#cb7-15" aria-hidden="true"></a> <span class="kw">struct</span> io_vec iov[<span class="dv">2</span>] = {</span> <span id="cb7-16"><a href="#cb7-16" aria-hidden="true"></a> [<span class="dv">0</span>] {.io_addr = &amp;wr, .io_len = <span class="kw">sizeof</span>(wr) },</span> <span id="cb7-17"><a href="#cb7-17" aria-hidden="true"></a> [<span class="dv">1</span>] {.io_addr = buffer, .io_len = length },</span> <span id="cb7-18"><a href="#cb7-18" aria-hidden="true"></a> };</span> <span id="cb7-19"><a href="#cb7-19" aria-hidden="true"></a></span> <span id="cb7-20"><a href="#cb7-20" aria-hidden="true"></a> msg = NewMsg(our_replay_chan_id, &amp;iov, <span class="dv">2</span>, &amp;errcode);</span> <span id="cb7-21"><a href="#cb7-21" aria-hidden="true"></a> SendChanMsg(our_fileserver_id, msg);</span> <span id="cb7-22"><a href="#cb7-22" aria-hidden="true"></a> <span class="co">// arm I/O response timeout timer here...</span></span> <span id="cb7-23"><a href="#cb7-23" aria-hidden="true"></a> <span class="dt">uint64_t</span> sigs = Wait(our_fileserver_mask | timer_mask);</span> <span id="cb7-24"><a href="#cb7-24" aria-hidden="true"></a> <span class="cf">if</span>(sigs &amp; our_fileserver_mask) {</span> <span id="cb7-25"><a href="#cb7-25" aria-hidden="true"></a> GetChanMsg(my_reply_id, &amp;errcode); <span class="co">// we know it&#39;s our msg.</span></span> <span id="cb7-26"><a href="#cb7-26" aria-hidden="true"></a> DisposeMsg(msg);</span> <span id="cb7-27"><a href="#cb7-27" aria-hidden="true"></a> errno = wr.errcode;</span> <span id="cb7-28"><a href="#cb7-28" aria-hidden="true"></a> <span class="cf">return</span> wr.actual_length;</span> <span id="cb7-29"><a href="#cb7-29" aria-hidden="true"></a> }</span> <span id="cb7-30"><a href="#cb7-30" aria-hidden="true"></a></span> <span id="cb7-31"><a href="#cb7-31" aria-hidden="true"></a> <span class="cf">if</span>(sigs &amp; timer_mask) {</span> <span id="cb7-32"><a href="#cb7-32" aria-hidden="true"></a> <span class="co">// I/O timeout happened; clean up and &quot;cancel&quot; the message, etc.</span></span> <span id="cb7-33"><a href="#cb7-33" aria-hidden="true"></a> <span class="co">// Return EAGAIN or EINTR something.</span></span> <span id="cb7-34"><a href="#cb7-34" aria-hidden="true"></a> }</span> <span id="cb7-35"><a href="#cb7-35" aria-hidden="true"></a>}</span></code></pre>
<h1 id="example-tally-server">Example: Tally Server</h1> <p>The following code is a more-or-less complete server implementation. Some details have been elided, both for brevity and because a C environment for VM/OS doesn’t yet exist. This code is preliminary and subject to change once concrete implementations are written.</p>
<pre class="sourceCode c"><code class="sourceCode c"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true"></a><span class="kw">enum</span> Ops {</span> <span id="cb8-2"><a href="#cb8-2" aria-hidden="true"></a> op_add, op_subtract, op_shutdown</span> <span id="cb8-3"><a href="#cb8-3" aria-hidden="true"></a>};</span> <span id="cb8-4"><a href="#cb8-4" aria-hidden="true"></a></span> <span id="cb8-5"><a href="#cb8-5" aria-hidden="true"></a><span class="kw">struct</span> MsgReq {</span> <span id="cb8-6"><a href="#cb8-6" aria-hidden="true"></a> <span class="kw">enum</span> Ops function;</span> <span id="cb8-7"><a href="#cb8-7" aria-hidden="true"></a> <span class="dt">int64_t</span> accumulator;</span> <span id="cb8-8"><a href="#cb8-8" aria-hidden="true"></a> <span class="dt">int64_t</span> operand;</span> <span id="cb8-9"><a href="#cb8-9" aria-hidden="true"></a>};</span> <span id="cb8-10"><a href="#cb8-10" aria-hidden="true"></a></span> <span id="cb8-11"><a href="#cb8-11" aria-hidden="true"></a><span class="dt">void</span></span> <span id="cb8-12"><a href="#cb8-12" aria-hidden="true"></a>main(<span class="dt">void</span>) {</span> <span id="cb8-13"><a href="#cb8-13" aria-hidden="true"></a> <span class="dt">int</span> errcode;</span> <span id="cb8-14"><a href="#cb8-14" aria-hidden="true"></a></span> <span id="cb8-15"><a href="#cb8-15" aria-hidden="true"></a> <span class="co">// Allocate a signal, so that we can be notified when</span></span> <span id="cb8-16"><a href="#cb8-16" aria-hidden="true"></a> <span class="co">// a message arrives on our channel.</span></span> <span id="cb8-17"><a href="#cb8-17" aria-hidden="true"></a> <span class="dt">uint8_t</span> server_sigbit = AllocSignal(&amp;errcode);</span> <span id="cb8-18"><a href="#cb8-18" aria-hidden="true"></a> <span class="cf">if</span>(errcode != <span class="dv">0</span>) {</span> <span id="cb8-19"><a href="#cb8-19" aria-hidden="true"></a> printf(<span class="st">&quot;Unable to allocate signal.</span><span class="sc">\n</span><span class="st">&quot;</span>);</span> <span id="cb8-20"><a href="#cb8-20" aria-hidden="true"></a> <span class="cf">goto</span> end;</span> <span id="cb8-21"><a href="#cb8-21" aria-hidden="true"></a> }</span> <span id="cb8-22"><a href="#cb8-22" aria-hidden="true"></a> <span class="dt">uint64_t</span> server_channel_mask = <span class="dv">1</span> &lt;&lt; server_sigbit;</span> <span id="cb8-23"><a href="#cb8-23" aria-hidden="true"></a></span> <span id="cb8-24"><a href="#cb8-24" aria-hidden="true"></a> <span class="co">// Create a public channel named TallyServer. Configure</span></span> <span id="cb8-25"><a href="#cb8-25" aria-hidden="true"></a> <span class="co">// it so that, when a message arrives, we (this task) is</span></span> <span id="cb8-26"><a href="#cb8-26" aria-hidden="true"></a> <span class="co">// notified via the signal we allocated above.</span></span> <span id="cb8-27"><a href="#cb8-27" aria-hidden="true"></a> ChannelAttrs chattrs = { <span class="dv">0</span>, };</span> <span id="cb8-28"><a href="#cb8-28" aria-hidden="true"></a> chattrs.sigbit = server_sigbit;</span> <span id="cb8-29"><a href="#cb8-29" aria-hidden="true"></a> chattrs.name = <span class="st">&quot;TallyServer&quot;</span>;</span> <span id="cb8-30"><a href="#cb8-30" aria-hidden="true"></a> chattrs.is_public = true;</span> <span id="cb8-31"><a href="#cb8-31" aria-hidden="true"></a> <span class="dt">uint64_t</span> chanID = NewChannel(</span> <span id="cb8-32"><a href="#cb8-32" aria-hidden="true"></a> &amp;chattrs,</span> <span id="cb8-33"><a href="#cb8-33" aria-hidden="true"></a> <span class="co">// Set only these attrs; ignore or assume defaults for the rest.</span></span> <span id="cb8-34"><a href="#cb8-34" aria-hidden="true"></a> CHATTRF_SIGBIT | CHATTRF_NAME | CHATTRF_ISPUBLIC,</span> <span id="cb8-35"><a href="#cb8-35" aria-hidden="true"></a> &amp;errcode</span> <span id="cb8-36"><a href="#cb8-36" aria-hidden="true"></a> );</span> <span id="cb8-37"><a href="#cb8-37" aria-hidden="true"></a> <span class="cf">if</span>(errcode != <span class="dv">0</span>) {</span> <span id="cb8-38"><a href="#cb8-38" aria-hidden="true"></a> printf(<span class="st">&quot;Unable to create public channel&quot;</span>);</span> <span id="cb8-39"><a href="#cb8-39" aria-hidden="true"></a> <span class="cf">goto</span> no_channel;</span> <span id="cb8-40"><a href="#cb8-40" aria-hidden="true"></a> }</span> <span id="cb8-41"><a href="#cb8-41" aria-hidden="true"></a></span> <span id="cb8-42"><a href="#cb8-42" aria-hidden="true"></a> printf(<span class="st">&quot;Welcome to Tally Server. Now entering event loop.</span><span class="sc">\n</span><span class="st">&quot;</span>);</span> <span id="cb8-43"><a href="#cb8-43" aria-hidden="true"></a></span> <span id="cb8-44"><a href="#cb8-44" aria-hidden="true"></a> <span class="cf">for</span>(<span class="dt">bool</span> still_running = true; still_true; ) {</span> <span id="cb8-45"><a href="#cb8-45" aria-hidden="true"></a> <span class="co">// We only need to wait on a single event source, so we ignore</span></span> <span id="cb8-46"><a href="#cb8-46" aria-hidden="true"></a> <span class="co">// return code (it&#39;ll either be 0 or server_channel_mask).</span></span> <span id="cb8-47"><a href="#cb8-47" aria-hidden="true"></a> Wait(server_channel_mask);</span> <span id="cb8-48"><a href="#cb8-48" aria-hidden="true"></a></span> <span id="cb8-49"><a href="#cb8-49" aria-hidden="true"></a> <span class="co">// If we&#39;re here, we know that at least one message is awaiting</span></span> <span id="cb8-50"><a href="#cb8-50" aria-hidden="true"></a> <span class="co">// us on the server channel.</span></span> <span id="cb8-51"><a href="#cb8-51" aria-hidden="true"></a> <span class="cf">for</span>(;;) {</span> <span id="cb8-52"><a href="#cb8-52" aria-hidden="true"></a> msg = GetChanMsg(chanID, &amp;errcode);</span> <span id="cb8-53"><a href="#cb8-53" aria-hidden="true"></a> <span class="cf">if</span>(errcode == GCME_CHAN_EMPTY)</span> <span id="cb8-54"><a href="#cb8-54" aria-hidden="true"></a> <span class="cf">break</span>;</span> <span id="cb8-55"><a href="#cb8-55" aria-hidden="true"></a></span> <span id="cb8-56"><a href="#cb8-56" aria-hidden="true"></a> <span class="cf">if</span>(errcode != <span class="dv">0</span>) {</span> <span id="cb8-57"><a href="#cb8-57" aria-hidden="true"></a> printf(<span class="st">&quot;Unable to GetChanMsg.&quot;</span>);</span> <span id="cb8-58"><a href="#cb8-58" aria-hidden="true"></a> <span class="cf">goto</span> no_chan_msg;</span> <span id="cb8-59"><a href="#cb8-59" aria-hidden="true"></a> }</span> <span id="cb8-60"><a href="#cb8-60" aria-hidden="true"></a></span> <span id="cb8-61"><a href="#cb8-61" aria-hidden="true"></a> <span class="kw">struct</span> MsgReq mr;</span> <span id="cb8-62"><a href="#cb8-62" aria-hidden="true"></a> <span class="dt">uint64_t</span> actual = ReadChanMsg(msg, &amp;mr, <span class="kw">sizeof</span>(mr));</span> <span id="cb8-63"><a href="#cb8-63" aria-hidden="true"></a> <span class="cf">switch</span>(mr.function) {</span> <span id="cb8-64"><a href="#cb8-64" aria-hidden="true"></a> <span class="cf">case</span> op_shutdown:</span> <span id="cb8-65"><a href="#cb8-65" aria-hidden="true"></a> still_running = false;</span> <span id="cb8-66"><a href="#cb8-66" aria-hidden="true"></a> prinf(<span class="st">&quot;Someone is asking us to shutdown.</span><span class="sc">\n</span><span class="st">&quot;</span>);</span> <span id="cb8-67"><a href="#cb8-67" aria-hidden="true"></a> <span class="cf">break</span>;</span> <span id="cb8-68"><a href="#cb8-68" aria-hidden="true"></a> <span class="cf">case</span> op_add:</span> <span id="cb8-69"><a href="#cb8-69" aria-hidden="true"></a> mr.accumulator += mr.operand;</span> <span id="cb8-70"><a href="#cb8-70" aria-hidden="true"></a> <span class="cf">break</span>;</span> <span id="cb8-71"><a href="#cb8-71" aria-hidden="true"></a> <span class="cf">case</span> op_subtract:</span> <span id="cb8-72"><a href="#cb8-72" aria-hidden="true"></a> mr.accumulator -= mr.operand;</span> <span id="cb8-73"><a href="#cb8-73" aria-hidden="true"></a> <span class="cf">break</span>;</span> <span id="cb8-74"><a href="#cb8-74" aria-hidden="true"></a> }</span> <span id="cb8-75"><a href="#cb8-75" aria-hidden="true"></a> <span class="co">// After calculating the results, return it to the sender.</span></span> <span id="cb8-76"><a href="#cb8-76" aria-hidden="true"></a> <span class="co">// Note that we use WriteChanMsgAt() here because the message</span></span> <span id="cb8-77"><a href="#cb8-77" aria-hidden="true"></a> <span class="co">// cursor is already set at the end of the message, thanks to</span></span> <span id="cb8-78"><a href="#cb8-78" aria-hidden="true"></a> <span class="co">// the prior read.</span></span> <span id="cb8-79"><a href="#cb8-79" aria-hidden="true"></a> actual = WriteChanMsgAt(msg, &amp;mr, <span class="kw">sizeof</span>(mr), <span class="dv">0</span>);</span> <span id="cb8-80"><a href="#cb8-80" aria-hidden="true"></a> ReplyChanMsg(msg);</span> <span id="cb8-81"><a href="#cb8-81" aria-hidden="true"></a> }</span> <span id="cb8-82"><a href="#cb8-82" aria-hidden="true"></a> }</span> <span id="cb8-83"><a href="#cb8-83" aria-hidden="true"></a></span> <span id="cb8-84"><a href="#cb8-84" aria-hidden="true"></a>no_chan_msg:</span> <span id="cb8-85"><a href="#cb8-85" aria-hidden="true"></a> DisposeChannel(chanID);</span> <span id="cb8-86"><a href="#cb8-86" aria-hidden="true"></a>no_channel:</span> <span id="cb8-87"><a href="#cb8-87" aria-hidden="true"></a> FreeSignal(server_sigbit);</span> <span id="cb8-88"><a href="#cb8-88" aria-hidden="true"></a>end:</span> <span id="cb8-89"><a href="#cb8-89" aria-hidden="true"></a>}</span></code></pre>
<h1 id="example-tally-client">Example: Tally Client</h1>
<pre class="sourceCode c"><code class="sourceCode c"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true"></a><span class="kw">enum</span> Ops {</span> <span id="cb9-2"><a href="#cb9-2" aria-hidden="true"></a> op_add, op_subtract, op_shutdown</span> <span id="cb9-3"><a href="#cb9-3" aria-hidden="true"></a>};</span> <span id="cb9-4"><a href="#cb9-4" aria-hidden="true"></a></span> <span id="cb9-5"><a href="#cb9-5" aria-hidden="true"></a><span class="kw">struct</span> MsgReq {</span> <span id="cb9-6"><a href="#cb9-6" aria-hidden="true"></a> <span class="kw">enum</span> Ops function;</span> <span id="cb9-7"><a href="#cb9-7" aria-hidden="true"></a> <span class="dt">int64_t</span> accumulator;</span> <span id="cb9-8"><a href="#cb9-8" aria-hidden="true"></a> <span class="dt">int64_t</span> operand;</span> <span id="cb9-9"><a href="#cb9-9" aria-hidden="true"></a>};</span> <span id="cb9-10"><a href="#cb9-10" aria-hidden="true"></a></span> <span id="cb9-11"><a href="#cb9-11" aria-hidden="true"></a><span class="dt">void</span></span> <span id="cb9-12"><a href="#cb9-12" aria-hidden="true"></a>main(<span class="dt">int</span> argc, <span class="dt">char</span>* argv[]) {</span> <span id="cb9-13"><a href="#cb9-13" aria-hidden="true"></a> <span class="co">// Create our reply channel. After we send a message,</span></span> <span id="cb9-14"><a href="#cb9-14" aria-hidden="true"></a> <span class="co">// it will eventually appear on this channel.</span></span> <span id="cb9-15"><a href="#cb9-15" aria-hidden="true"></a> <span class="dt">uint8_t</span> reply_sigbit = AllocSignal(&amp;errcode);</span> <span id="cb9-16"><a href="#cb9-16" aria-hidden="true"></a> <span class="cf">if</span>(errcode != <span class="dv">0</span>) {</span> <span id="cb9-17"><a href="#cb9-17" aria-hidden="true"></a> printf(<span class="st">&quot;Cannot allocate a signal.</span><span class="sc">\n</span><span class="st">&quot;</span>);</span> <span id="cb9-18"><a href="#cb9-18" aria-hidden="true"></a> <span class="cf">goto</span> end;</span> <span id="cb9-19"><a href="#cb9-19" aria-hidden="true"></a> }</span> <span id="cb9-20"><a href="#cb9-20" aria-hidden="true"></a> <span class="dt">uint64_t</span> reply_mask = <span class="dv">1</span> &lt;&lt; reply_sigbit;</span> <span id="cb9-21"><a href="#cb9-21" aria-hidden="true"></a></span> <span id="cb9-22"><a href="#cb9-22" aria-hidden="true"></a> <span class="kw">struct</span> ChannelAttrs chatters = { <span class="dv">0</span>, };</span> <span id="cb9-23"><a href="#cb9-23" aria-hidden="true"></a> chattrs.sig_bit = reply_sigbit;</span> <span id="cb9-24"><a href="#cb9-24" aria-hidden="true"></a> <span class="dt">uint64_t</span> replyID = NewChannel(&amp;chattrs, CHATTRF_SIGBIT, &amp;errcode);</span> <span id="cb9-25"><a href="#cb9-25" aria-hidden="true"></a> <span class="cf">if</span>(errcode != <span class="dv">0</span>) {</span> <span id="cb9-26"><a href="#cb9-26" aria-hidden="true"></a> printf(<span class="st">&quot;Unable to create reply channel.</span><span class="sc">\n</span><span class="st">&quot;</span>);</span> <span id="cb9-27"><a href="#cb9-27" aria-hidden="true"></a> <span class="cf">goto</span> no_reply_channel;</span> <span id="cb9-28"><a href="#cb9-28" aria-hidden="true"></a> }</span> <span id="cb9-29"><a href="#cb9-29" aria-hidden="true"></a></span> <span id="cb9-30"><a href="#cb9-30" aria-hidden="true"></a> <span class="co">// Locate the server; this is the program who will perform our</span></span> <span id="cb9-31"><a href="#cb9-31" aria-hidden="true"></a> <span class="co">// arithmetic for us, since C just isn&#39;t up to the task. ;)</span></span> <span id="cb9-32"><a href="#cb9-32" aria-hidden="true"></a> <span class="dt">uint64_t</span> serverID = FindChannel(<span class="st">&quot;TallyServer&quot;</span>, &amp;errcode);</span> <span id="cb9-33"><a href="#cb9-33" aria-hidden="true"></a> <span class="cf">if</span>(errcode != <span class="dv">0</span>) {</span> <span id="cb9-34"><a href="#cb9-34" aria-hidden="true"></a> printf(<span class="st">&quot;Cannot find Tally Server channel</span><span class="sc">\n</span><span class="st">&quot;</span>);</span> <span id="cb9-35"><a href="#cb9-35" aria-hidden="true"></a> <span class="cf">goto</span> bad_operation;</span> <span id="cb9-36"><a href="#cb9-36" aria-hidden="true"></a> }</span> <span id="cb9-37"><a href="#cb9-37" aria-hidden="true"></a></span> <span id="cb9-38"><a href="#cb9-38" aria-hidden="true"></a> <span class="co">// Build the message we want to send.</span></span> <span id="cb9-39"><a href="#cb9-39" aria-hidden="true"></a> <span class="kw">struct</span> MsgReq mr;</span> <span id="cb9-40"><a href="#cb9-40" aria-hidden="true"></a></span> <span id="cb9-41"><a href="#cb9-41" aria-hidden="true"></a> <span class="cf">if</span>(argc &lt; <span class="dv">4</span>) {</span> <span id="cb9-42"><a href="#cb9-42" aria-hidden="true"></a> printf(<span class="st">&quot;Syntax: %s accumulator/N function/K operand/N</span><span class="sc">\n</span><span class="st">&quot;</span>, argv[<span class="dv">0</span>]);</span> <span id="cb9-43"><a href="#cb9-43" aria-hidden="true"></a> <span class="cf">goto</span> bad_operation;</span> <span id="cb9-44"><a href="#cb9-44" aria-hidden="true"></a> }</span> <span id="cb9-45"><a href="#cb9-45" aria-hidden="true"></a></span> <span id="cb9-46"><a href="#cb9-46" aria-hidden="true"></a> <span class="cf">switch</span>(argv[<span class="dv">2</span>][<span class="dv">0</span>]) {</span> <span id="cb9-47"><a href="#cb9-47" aria-hidden="true"></a> <span class="cf">case</span> <span class="ch">&#39;+&#39;</span>: mr.function = op_add; <span class="cf">break</span>;</span> <span id="cb9-48"><a href="#cb9-48" aria-hidden="true"></a> <span class="cf">case</span> <span class="ch">&#39;-&#39;</span>: mr.function = op_subtract; <span class="cf">break</span>;</span> <span id="cb9-49"><a href="#cb9-49" aria-hidden="true"></a> <span class="cf">case</span> <span class="ch">&#39;Q&#39;</span>: mr.function = op_shutdown; <span class="cf">break</span>;</span> <span id="cb9-50"><a href="#cb9-50" aria-hidden="true"></a> <span class="cf">default</span>:</span> <span id="cb9-51"><a href="#cb9-51" aria-hidden="true"></a> printf(<span class="st">&quot;I don&#39;t know what operation you asked for.</span><span class="sc">\n</span><span class="st">&quot;</span>);</span> <span id="cb9-52"><a href="#cb9-52" aria-hidden="true"></a> <span class="cf">goto</span> bad_operation;</span> <span id="cb9-53"><a href="#cb9-53" aria-hidden="true"></a> }</span> <span id="cb9-54"><a href="#cb9-54" aria-hidden="true"></a> mr.accumulator = atoi(argv[<span class="dv">1</span>]);</span> <span id="cb9-55"><a href="#cb9-55" aria-hidden="true"></a> mr.operand = atoi(argv[<span class="dv">3</span>]);</span> <span id="cb9-56"><a href="#cb9-56" aria-hidden="true"></a></span> <span id="cb9-57"><a href="#cb9-57" aria-hidden="true"></a> <span class="co">// Send the message. When building the message, be sure to set the</span></span> <span id="cb9-58"><a href="#cb9-58" aria-hidden="true"></a> <span class="co">// reply channel ID, so it knows where to go when it comes home!</span></span> <span id="cb9-59"><a href="#cb9-59" aria-hidden="true"></a> <span class="dt">uint64_t</span> msg = NewMsgQuick(replyID, &amp;mr, <span class="kw">sizeof</span>(mr), &amp;errcode);</span> <span id="cb9-60"><a href="#cb9-60" aria-hidden="true"></a> <span class="cf">if</span>(errcode != <span class="dv">0</span>) {</span> <span id="cb9-61"><a href="#cb9-61" aria-hidden="true"></a> printf(<span class="st">&quot;Cannot create message from simple buffer</span><span class="sc">\n</span><span class="st">&quot;</span>);</span> <span id="cb9-62"><a href="#cb9-62" aria-hidden="true"></a> <span class="cf">goto</span> bad_operation;</span> <span id="cb9-63"><a href="#cb9-63" aria-hidden="true"></a> }</span> <span id="cb9-64"><a href="#cb9-64" aria-hidden="true"></a></span> <span id="cb9-65"><a href="#cb9-65" aria-hidden="true"></a> SendChanMsg(serverID, msg);</span> <span id="cb9-66"><a href="#cb9-66" aria-hidden="true"></a></span> <span id="cb9-67"><a href="#cb9-67" aria-hidden="true"></a> <span class="co">// At this point, the message is away and queued onto the server&#39;s channel.</span></span> <span id="cb9-68"><a href="#cb9-68" aria-hidden="true"></a> <span class="co">// NO DATA HAS BEEN COPIED YET. Thus, if you need to send more messages,</span></span> <span id="cb9-69"><a href="#cb9-69" aria-hidden="true"></a> <span class="co">// you must create more buffers as they&#39;re needed. The memory this</span></span> <span id="cb9-70"><a href="#cb9-70" aria-hidden="true"></a> <span class="co">// message refers to must remain stable until the receiver has copied the</span></span> <span id="cb9-71"><a href="#cb9-71" aria-hidden="true"></a> <span class="co">// data for itself.</span></span> <span id="cb9-72"><a href="#cb9-72" aria-hidden="true"></a> <span class="co">//</span></span> <span id="cb9-73"><a href="#cb9-73" aria-hidden="true"></a> <span class="co">// We can now do whatever we want. For this simple example, we choose to</span></span> <span id="cb9-74"><a href="#cb9-74" aria-hidden="true"></a> <span class="co">// just wait for a response by waiting on the reply port.</span></span> <span id="cb9-75"><a href="#cb9-75" aria-hidden="true"></a></span> <span id="cb9-76"><a href="#cb9-76" aria-hidden="true"></a> Wait(reply_mask);</span> <span id="cb9-77"><a href="#cb9-77" aria-hidden="true"></a></span> <span id="cb9-78"><a href="#cb9-78" aria-hidden="true"></a> <span class="co">// We complete the handshake by retrieving the sent message.</span></span> <span id="cb9-79"><a href="#cb9-79" aria-hidden="true"></a> <span class="dt">uint64_t</span> reply_msg_id = GetChanMsg(replyID, &amp;errcode);</span> <span id="cb9-80"><a href="#cb9-80" aria-hidden="true"></a> <span class="cf">if</span>(errcode != <span class="dv">0</span>) {</span> <span id="cb9-81"><a href="#cb9-81" aria-hidden="true"></a> printf(<span class="st">&quot;Uh oh; something went wrong when collecting our reply.</span><span class="sc">\n</span><span class="st">&quot;</span>);</span> <span id="cb9-82"><a href="#cb9-82" aria-hidden="true"></a> <span class="cf">goto</span> no_reply;</span> <span id="cb9-83"><a href="#cb9-83" aria-hidden="true"></a> }</span> <span id="cb9-84"><a href="#cb9-84" aria-hidden="true"></a></span> <span id="cb9-85"><a href="#cb9-85" aria-hidden="true"></a> <span class="co">// Print out the results from our server.</span></span> <span id="cb9-86"><a href="#cb9-86" aria-hidden="true"></a></span> <span id="cb9-87"><a href="#cb9-87" aria-hidden="true"></a> printf(<span class="st">&quot;The result is: %lld</span><span class="sc">\n</span><span class="st">&quot;</span>, mr.accumulator);</span> <span id="cb9-88"><a href="#cb9-88" aria-hidden="true"></a></span> <span id="cb9-89"><a href="#cb9-89" aria-hidden="true"></a> <span class="co">// At this point, we could re-use the buffer/msg pair.</span></span> <span id="cb9-90"><a href="#cb9-90" aria-hidden="true"></a></span> <span id="cb9-91"><a href="#cb9-91" aria-hidden="true"></a>no_reply:</span> <span id="cb9-92"><a href="#cb9-92" aria-hidden="true"></a> DisposeMsg(msg);</span> <span id="cb9-93"><a href="#cb9-93" aria-hidden="true"></a>bad_operation:</span> <span id="cb9-94"><a href="#cb9-94" aria-hidden="true"></a> DisposeChannel(replyID);</span> <span id="cb9-95"><a href="#cb9-95" aria-hidden="true"></a>no_reply_channel:</span> <span id="cb9-96"><a href="#cb9-96" aria-hidden="true"></a> FreeSignal(reply_sigbit);</span> <span id="cb9-97"><a href="#cb9-97" aria-hidden="true"></a>end:</span> <span id="cb9-98"><a href="#cb9-98" aria-hidden="true"></a>}</span></code></pre>
</body> </html>