<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://changliu.me/feed.xml" rel="self" type="application/atom+xml" /><link href="https://changliu.me/" rel="alternate" type="text/html" /><updated>2026-10-08T13:40:15+00:00</updated><id>https://changliu.me/feed.xml</id><title type="html">Chang Liu</title><subtitle>Software engineer in Tokyo. Notes on code, life, and ideas.</subtitle><entry xml:lang="en"><title type="html">Boids</title><link href="https://changliu.me/2025/06/16/boids.html" rel="alternate" type="text/html" title="Boids" /><published>2025-06-16T00:00:00+00:00</published><updated>2025-06-16T00:00:00+00:00</updated><id>https://changliu.me/2025/06/16/boids</id><content type="html" xml:base="https://changliu.me/2025/06/16/boids.html"><![CDATA[<h1 id="boids">Boids</h1>

<p>Boids algorithm is an artificial life algorithm proposed by Craig Reynolds in 1986, used to simulate the behavior of bird flocks, fish schools, etc. Boids algorithm simulates the interaction between individuals to achieve collective behavior of the group.</p>

<h1 id="terminology">Terminology</h1>
<p>In the Boids algorithm, we have the following terminology:</p>
<ul>
  <li>Boid: An individual in the group</li>
  <li>Perception: The radius of the area around a boid that it can sense</li>
</ul>

<h1 id="rules">Rules</h1>
<p>In the Boids algorithm, we have the following rules:</p>
<ul>
  <li>Separation: A boid should avoid crowding nearby boids</li>
  <li>Cohesion: A boid should move towards the center of mass of nearby boids</li>
  <li>Alignment: A boid should move in the same direction as nearby boids</li>
</ul>

<h1 id="demo">Demo</h1>

<p>Here is a demo of the Boids algorithm. You can adjust the parameters to see how the boids behave.</p>
<ul>
  <li>Count: Number of boids in the simulation</li>
  <li>Perception: Radius around a boid that it can sense</li>
  <li>Separation: Force pushing a boid away from nearby boids</li>
  <li>Cohesion: Force pulling a boid toward the center of nearby boids</li>
  <li>Alignment: Force aligning a boid’s velocity with nearby boids</li>
  <li>Highlight: Toggle to highlight the first boid and show its perception range</li>
</ul>

<link rel="stylesheet" href="/assets/css/boids.css" />

<section class="canvas-container">
  <canvas id="demo-canvas" resize=""></canvas>
  <section class="control">
    <div class="control-label-value">
      <span class="control-label">Count:</span>
      <div class="control-input-group">
        <input type="range" id="boids-count" class="control-input" min="1" max="100" value="1" />
        <span class="control-value" id="number-value">1</span>
      </div>
    </div>
    <div class="control-label-value">
      <span class="control-label">Perception:</span>
      <div class="control-input-group">
        <input type="range" id="perception" class="control-input" min="0" max="100" step="10" value="0" />
        <span class="control-value" id="perception-value">0</span>
      </div>
    </div>
    <div class="control-label-value">
      <span class="control-label">Separation:</span>
      <div class="control-input-group">
        <input type="range" id="separation" class="control-input" min="0" max="5" step="1" value="0" />
        <span class="control-value" id="separation-value">0</span>
      </div>
    </div>
    <div class="control-label-value">
      <span class="control-label">Cohesion:</span>
      <div class="control-input-group">
        <input type="range" id="cohesion" class="control-input" min="0" max="5" step="1" value="0" />
        <span class="control-value" id="cohesion-value">0</span>
      </div>
    </div>
    <div class="control-label-value">
      <span class="control-label">Alignment:</span>
      <div class="control-input-group">
        <input type="range" id="alignment" class="control-input" min="0" max="5" step="1" value="0" />
        <span class="control-value" id="alignment-value">0</span>
      </div>
    </div>
    <div class="control-label-value">
      <span class="control-label">Highlight:</span>
      <div class="control-input-group">
        <input type="checkbox" id="highlight" class="control-input checkbox" />
        <span class="control-value"></span>
      </div>
    </div>
  </section>
  <section class="play">
      <button class="play-button">Play</button>
  </section>
</section>

<script src="/assets/scripts/boids.js"></script>

<h1 id="interesting-things">Interesting things</h1>

<p>Actually, I think these three rules also exist in human society to some extent. So we can run some interesting experiments, such as:</p>

<ul>
  <li>Perception: In earlier societies, before the rise of the internet, people had a much narrower field of view. They could only pay attention to their own small world — their village or tribe. Back then, there was greater diversity, and individuals were less influenced by broader societal norms. After the explosion of information, it’s much easier for a “mainstream” to emerge, and people tend to become more homogeneous in their thinking.</li>
  <li>Separation: This is somewhat similar to the modern concept of boundaries. For example, if someone asks you how much you earn per month — in some societies, that’s totally normal, but in others, people would think there’s something wrong with you. In societies with weak boundaries, people are more likely to form tight-knit communities and feel a stronger sense of belonging. In contrast, in societies with strong boundaries, individuals tend to be more independent, but it also becomes harder to truly connect with others.</li>
  <li>Alignment: I think this has to do with how stable the hierarchy is within a group. When alignment is low, people are not necessarily expected to follow the mainstream path. Yet, the group still tends to move in a cohesive direction, while allowing for shifts in internal positions. On the other hand, when alignment is high, not only does the whole group move in the same direction, but the relative positions of individuals within the group also tend to stay fixed.</li>
</ul>]]></content><author><name></name></author><summary type="html"><![CDATA[Boids]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://changliu.me/assets/images/social-card.png" /><media:content medium="image" url="https://changliu.me/assets/images/social-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="zh"><title type="html">Boids</title><link href="https://changliu.me/zh/2025/06/16/boids.html" rel="alternate" type="text/html" title="Boids" /><published>2025-06-16T00:00:00+00:00</published><updated>2025-06-16T00:00:00+00:00</updated><id>https://changliu.me/zh/2025/06/16/boids</id><content type="html" xml:base="https://changliu.me/zh/2025/06/16/boids.html"><![CDATA[<h1 id="boids">Boids</h1>

<p>Boids算法是Craig Reynolds在1986年提出的一种人工生命算法，用来模拟鸟群、鱼群等生物群体的行为。Boids算法通过模拟个体之间的互动，让整个群体表现出集体行为。</p>

<h1 id="术语">术语</h1>
<p>在Boids算法中，我们有以下术语：</p>
<ul>
  <li>Boid：群体中的一个个体</li>
  <li>Perception（感知）：一个boid能感知到的周围区域的半径</li>
</ul>

<h1 id="规则">规则</h1>
<p>在Boids算法中，我们有以下规则：</p>
<ul>
  <li>Separation（分离）：boid应避免与附近的boid挤在一起</li>
  <li>Cohesion（聚合）：boid应朝附近boid的重心移动</li>
  <li>Alignment（对齐）：boid应与附近的boid朝同一方向移动</li>
</ul>

<h1 id="演示">演示</h1>

<p>下面是Boids算法的演示。你可以调整参数，观察boid的行为变化。</p>
<ul>
  <li>Count：模拟中boid的数量</li>
  <li>Perception：boid能感知的半径</li>
  <li>Separation：把boid推离附近boid的力</li>
  <li>Cohesion：把boid拉向附近boid重心的力</li>
  <li>Alignment：使boid的速度与附近boid趋于一致的力</li>
  <li>Highlight：开关，高亮第一个boid并显示它的感知范围</li>
</ul>

<link rel="stylesheet" href="/assets/css/boids.css" />

<section class="canvas-container">
  <canvas id="demo-canvas" resize=""></canvas>
  <section class="control">
    <div class="control-label-value">
      <span class="control-label">Count:</span>
      <div class="control-input-group">
        <input type="range" id="boids-count" class="control-input" min="1" max="100" value="1" />
        <span class="control-value" id="number-value">1</span>
      </div>
    </div>
    <div class="control-label-value">
      <span class="control-label">Perception:</span>
      <div class="control-input-group">
        <input type="range" id="perception" class="control-input" min="0" max="100" step="10" value="0" />
        <span class="control-value" id="perception-value">0</span>
      </div>
    </div>
    <div class="control-label-value">
      <span class="control-label">Separation:</span>
      <div class="control-input-group">
        <input type="range" id="separation" class="control-input" min="0" max="5" step="1" value="0" />
        <span class="control-value" id="separation-value">0</span>
      </div>
    </div>
    <div class="control-label-value">
      <span class="control-label">Cohesion:</span>
      <div class="control-input-group">
        <input type="range" id="cohesion" class="control-input" min="0" max="5" step="1" value="0" />
        <span class="control-value" id="cohesion-value">0</span>
      </div>
    </div>
    <div class="control-label-value">
      <span class="control-label">Alignment:</span>
      <div class="control-input-group">
        <input type="range" id="alignment" class="control-input" min="0" max="5" step="1" value="0" />
        <span class="control-value" id="alignment-value">0</span>
      </div>
    </div>
    <div class="control-label-value">
      <span class="control-label">Highlight:</span>
      <div class="control-input-group">
        <input type="checkbox" id="highlight" class="control-input checkbox" />
        <span class="control-value"></span>
      </div>
    </div>
  </section>
  <section class="play">
      <button class="play-button">Play</button>
  </section>
</section>

<script src="/assets/scripts/boids.js"></script>

<h1 id="有趣的事">有趣的事</h1>

<p>我觉得这三条规则在一定程度上也存在于人类社会中，所以我们可以做一些有趣的思想实验，比如：</p>

<ul>
  <li>Perception（感知）：在更早的社会里，互联网出现之前，人们的视野要窄得多。他们只能关注自己的小世界，比如自己的村庄或部落。那时多样性更强，个体受更大范围社会规范的影响也更小。信息爆炸之后，“主流”更容易形成，人们的想法也更容易趋同。</li>
  <li>Separation（分离）：这有点像现代人说的“边界感”。比如有人问你每个月赚多少钱，在有些社会里这很正常，在另一些社会里，人们会觉得你有问题。在边界感弱的社会里，人们更容易形成紧密的社群，归属感也更强。相反，在边界感强的社会里，个体更独立，但也更难真正与他人建立联系。</li>
  <li>Alignment（对齐）：我觉得这和群体内部等级是否稳定有关。对齐程度低时，人们不一定要走主流的路，但群体整体仍会朝着一致的方向前进，同时允许内部位置发生变化。反过来，对齐程度高时，不仅整个群体朝同一方向移动，个体在群体中的相对位置也倾向于固定不变。</li>
</ul>]]></content><author><name></name></author><summary type="html"><![CDATA[Boids]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://changliu.me/assets/images/social-card.png" /><media:content medium="image" url="https://changliu.me/assets/images/social-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="zh"><title type="html">东京府中换驾照路线图</title><link href="https://changliu.me/2025/04/02/fuchu-drivers-license-guide.html" rel="alternate" type="text/html" title="东京府中换驾照路线图" /><published>2025-04-02T00:00:00+00:00</published><updated>2025-04-02T00:00:00+00:00</updated><id>https://changliu.me/2025/04/02/fuchu-drivers-license-guide</id><content type="html" xml:base="https://changliu.me/2025/04/02/fuchu-drivers-license-guide.html"><![CDATA[<h1 id="东京府中换驾照路线图">东京府中换驾照路线图</h1>

<p>有很多小伙伴在小红书私信要大图，在这里分享出来。</p>

<p><img src="/assets/images/fuchu_route_a.jpg" alt="路线A" width="100%" />
<img src="/assets/images/fuchu_route_b.jpg" alt="路线B" width="100%" />
<img src="/assets/images/fuchu_route_c.jpg" alt="路线C" width="100%" />
<img src="/assets/images/fuchu_route_d.jpg" alt="路线D" width="100%" /></p>

<p>如果觉得有帮助，请支持一下！</p>

<p><img src="/assets/images/wechat_pay_qr_code.jpg" alt="微信支持" width="50%" /></p>]]></content><author><name></name></author><summary type="html"><![CDATA[东京府中换驾照路线图]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://changliu.me/assets/images/social-card.png" /><media:content medium="image" url="https://changliu.me/assets/images/social-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="en"><title type="html">Time, Clocks, and the Ordering of Events in a Distributed System, Lamport, 1978</title><link href="https://changliu.me/2020/10/06/time-clocks-and-the-ordering-of-events.html" rel="alternate" type="text/html" title="Time, Clocks, and the Ordering of Events in a Distributed System, Lamport, 1978" /><published>2020-10-06T00:00:00+00:00</published><updated>2020-10-06T00:00:00+00:00</updated><id>https://changliu.me/2020/10/06/time-clocks-and-the-ordering-of-events</id><content type="html" xml:base="https://changliu.me/2020/10/06/time-clocks-and-the-ordering-of-events.html"><![CDATA[<h1 id="time-clocks-and-the-ordering-of-events-in-a-distributed-system-lamport-1978">Time, Clocks, and the Ordering of Events in a Distributed System, Lamport, 1978</h1>

<p>In this post, I summarize what I learned from the paper <a href="https://lamport.azurewebsites.net/pubs/time-clocks.pdf">Time, Clocks, and the Ordering of Events in a Distributed System</a>. Lamport published it in <em>Communications of the ACM</em> in 1978. The paper discusses time and clocks in distributed systems in depth. It proposes important concepts and algorithms such as “Happened Before”, logical clock, physical clock, and “State Machine”. It is a classic that every distributed systems reader must read.</p>

<h2 id="1-problem">1. Problem</h2>

<p>Before we start, let’s consider the following problem:</p>

<ul>
  <li>Someone did something \(A\) and looked at his watch. It read “2020/10/06 13:00”. He claims that \(A\) happened at “2020/10/06 13:00”.</li>
  <li>Another person did something \(B\) and looked at his watch. It read “2020/10/06 13:05”. He claims that \(B\) happened at “2020/10/06 13:05”.</li>
</ul>

<p>Assume both are honest. Can we say that \(A\) happened before \(B\)? Clearly, we cannot tell which came first, because we don’t know whether their watches are “accurate”. What if the problem changes to the following?</p>

<ul>
  <li>Someone did something \(A\) and looked at his watch. It read “2020/10/06 13:00”. He claims that \(A\) happened at “2020/10/06 13:00”.</li>
  <li>He made a phone call to another person.</li>
  <li>After the call, the second person did something \(B\) and looked at his watch. It read “2020/10/06 12:55”. He claims that \(B\) happened at “2020/10/06 12:55”.</li>
</ul>

<p>We can see that clock readings and timestamps cannot describe the order of events accurately. Yet in a distributed system, the order of events often plays a “key role” in many algorithms. So how do we describe this order accurately, or design our clocks to avoid the problem in the example above?</p>

<h2 id="2-what-is-time">2. What is time</h2>

<blockquote>
  <p>The concept of time is fundamental to our way of thinking. It is derived from the more basic concept of the order in which events occur.</p>
</blockquote>

<p>The definition of time is essential for understanding “the order of events” and “concurrency” in distributed systems. Lamport points out that time is derived from a more basic concept: the order in which events occur. For example, when we say something happened at 13:00, we mean it happened after we read 13:00 on the clock and before 13:01. So a clock is a way of discretizing continuous time by “numbering” it.</p>

<h2 id="3-distributed-system">3. Distributed system</h2>

<p>The distributed system in this post consists of several processes that are spatially separated. Events in the same process happen sequentially. Processes communicate by sending and receiving messages. A process can be an independent computer, an independent operating system process, or an independent hardware module inside one computer. From here on, I call a process a “node”. In particular, the communication delay between nodes is not negligible compared with the frequency of events inside a single node.</p>

<h2 id="4-happened-before-a-partial-ordering">4. Happened Before: a partial ordering</h2>

<p>For events in a distributed system, we define the “happened before” relation, written as “\(\rightarrow\)”. It satisfies the following three conditions.</p>

<ul>
  <li>(1) If \(a\) and \(b\) are two events on the same node, and \(a\) happens before \(b\), then \(a \rightarrow b\) .</li>
  <li>(2) If event \(a\) is a node sending a message, and \(b\) is another node receiving this message, then \(a \rightarrow b\) .</li>
  <li>(3) If \(a \rightarrow b\) and \(b \rightarrow c\) , then \(a \rightarrow c\) .</li>
</ul>

<p>Two events are <strong>concurrent</strong> if and only if \(a \nrightarrow b, b \nrightarrow a\).</p>

<p>We also require \(\rightarrow\) to be irreflexive, that is, \(a \nrightarrow a\) . Clearly, it makes no sense to say that an event happened “before” itself.</p>

<p>To describe this relation intuitively, Lamport introduces the “space-time diagram” shown below. In the diagram, time runs upward along the vertical direction, and the horizontal direction shows different nodes in space. The black dots are events. The wavy arrows are messages.</p>

<p>Recall the “happened before” relation above. We can easily find event pairs in the diagram that satisfy it. For example, \(p_1 \rightarrow r_4\), which is derived from \(p_1 \rightarrow q_2 \rightarrow q_4 \rightarrow r_3 \rightarrow r_4\).</p>

<p>The diagram also has concurrent events, such as \(p_3\) and \(q_3\). In the diagram, we can see that \(p_3\) happens later than \(q_3\) in physical time. But the nodes in the system do not know which one came first.</p>

<p><img src="/assets/images/lamport-time-clocks/Fig1.jpg" alt="Figure 1. space-time diagram" width="70%" />
<em>Figure 1. space-time diagram</em></p>

<h2 id="5-logical-clock">5. Logical clock</h2>

<blockquote>
  <p>a clock is just a way of assigning a number to an event.</p>
</blockquote>

<p><strong>A clock is just a way of assigning a number to an event.</strong> More precisely, for each node \(P_i\), we define a clock \(C_i\) as a function. It assigns the number \(C_i \langle a \rangle\) to any event \(a\). For the whole system, the time of any event \(b\) is \(C \langle b \rangle\). If \(b\) happens on node \(P_j\), then \(C \langle b \rangle =  C_j \langle b \rangle\). Here we treat the clock as a logical clock inside the system, not a physical clock. Its labels and counting method do not need to agree with physical time. To satisfy the “happened before” partial ordering above, our logical clock must satisfy the following Clock Condition.</p>

<p><strong>Clock Condition.</strong> For any events \(a, b\) in the system: if \(a \rightarrow b\), then \(C \langle a \rangle &lt; C \langle b \rangle\).</p>

<ul>
  <li>C1. If \(a\) and \(b\) are two events on the same node \(P_i\), and \(a\) happens before \(b\), then \(C_i \langle a \rangle &lt; C_i \langle b \rangle\).</li>
  <li>C2. If event \(a\) is node \(P_i\) sending a message, and \(b\) is node \(P_j\) receiving this message, then \(C_i \langle a \rangle &lt; C_j \langle b \rangle\).</li>
</ul>

<p><strong>In particular, the converse of the Clock Condition, “if \(C \langle a \rangle &lt; C \langle b \rangle\), then \(a \rightarrow b\)”, does not hold.</strong> It would require concurrent events to have the same logical time. For example, in Figure 1, both \(p_2\) and \(p_3\) are concurrent with \(q_3\). But by C1, \(C \langle p_2 \rangle &lt; C \langle p_3 \rangle\). So we must have \(C \langle q_3 \rangle \neq C \langle p_2 \rangle\) or \(C \langle q_3 \rangle \neq C \langle p_3 \rangle\). This contradicts the concurrent relation.</p>

<p>For a logical clock, we can imagine “tick” events constantly happening inside a single node. For example, take two consecutive events \(a, b\) on the same node \(P_i\), with \(C_i \langle a \rangle = 4, C_i \langle b \rangle = 7\). Then tick events numbered \(5,6,7\) happen between them. So we can add “tick lines” to the space-time diagram, as the dashed lines in the figure below. By C1, there must be at least one tick line between two consecutive events on the same node. By C2, every message must cross at least one tick line.</p>

<p><img src="/assets/images/lamport-time-clocks/Fig2.png" alt="Figure 2" width="70%" />
<em>Figure 2</em></p>

<p>To make this easier to understand, we can redraw the tick lines as equivalent horizontal lines, while keeping the partial ordering of events and messages, as in the figure below.</p>

<p><img src="/assets/images/lamport-time-clocks/Fig3.png" alt="Figure 3" width="70%" />
<em>Figure 3</em></p>

<p>For the logical clock algorithm on a single node, we have the following Implementation Rules.</p>

<ul>
  <li>IR1. Each node \(P_i\) increments \(C_i\) between any two consecutive events.</li>
  <li>IR2. (a) If event \(a\) is node \(P_i\) sending message \(m\), then \(m\) contains a timestamp \(T_m=C_i \langle a \rangle\). (b) When it receives message \(m\), process \(P_j\) sets its current time \(C_j\) to \(C_j'\), such that \(C_j' \geq C_j\) and \(C_j' &gt; T_m\) .</li>
</ul>

<p>In practice, when we receive a message, we should run IR2 to update the time first. Then we run the actual event. This guarantees the <strong>Clock Condition</strong>.</p>

<h2 id="6-total-ordering">6. Total ordering</h2>

<p>With the logical clock, we can sort all events in the system into a total ordering. We first sort events by their time. For events with the same time, we use a priority \(\prec\) predefined on all nodes. The priority can be any rule, such as sorting by id.</p>

<p>More precisely, we define the total ordering \(\Rightarrow\). For event \(a\) on node \(P_i\) and event \(b\) on node \(P_j\), \(a \Rightarrow b\) if and only if (i) \(C_i \langle a \rangle &lt; C_j \langle b \rangle\) or (ii) \(C_i \langle a \rangle = C_j \langle b \rangle\) and \(P_i \prec P_j\).</p>

<p>By the <strong>Clock Condition</strong>, anything that satisfies the partial ordering \(\rightarrow\) also satisfies the total ordering \(\Rightarrow\).</p>

<p><img src="/assets/images/lamport-time-clocks/partial-total.png" alt="Figure 4. partial ordering and total ordering" width="40%" />
<em>Figure 4. partial ordering and total ordering</em></p>

<h2 id="7-physical-clock">7. Physical clock</h2>

<h3 id="71-outside-the-system">7.1 Outside the system</h3>

<p>Under the total ordering, events outside the system sometimes cause abnormal behavior.</p>

<p>Consider this case. Someone triggers event A on node A, then calls another person. After the call, that person triggers event B on node B. The system knows nothing about the outside event “phone call”. So it is possible to get \(B \Rightarrow A\).</p>

<p>We define the set of all events in the system as \(\varphi\). The set of system events together with external events is \(\underline{\varphi}\). \(\underline{\rightarrow}\) is the happened before relation on \(\underline{\varphi}\). In the example above, we have \(A \underline{\rightarrow} B\), but \(A \nrightarrow B\).</p>

<p>Clearly, no algorithm can guarantee the \(\underline{\rightarrow}\) relation from \(\varphi\) alone, without outside information. To ensure \(A \rightarrow B\), we have two options.</p>

<ol>
  <li>Introduce the outside information explicitly. For example, event \(A\) happens at logical time \(T_A\). After the call, we tell the system explicitly that the time of \(B\) must be greater than \(T_A\).</li>
  <li>Build a system that satisfies the following <strong>Strong Clock Condition</strong>.</li>
</ol>

<p><strong>Strong Clock Condition.</strong> For any events \(a, b\) in \(\varphi\): if \(a \underline{\rightarrow} b\) then \(C \langle a \rangle &lt; C \langle b \rangle\).</p>

<p>Clearly, compared with option 1, the <strong>Strong Clock Condition</strong> is the option we want. Next, I explain how to implement a physical clock that satisfies the <strong>Strong Clock Condition</strong>.</p>

<h3 id="72-physical-clock-implementation">7.2 Physical clock implementation</h3>

<p>Let \(C_i(t)\) be the reading of clock \(C_i\) at physical time \(t\). For mathematical convenience, we assume \(C_i\) is continuously differentiable in \(t\). \(dC_i(t)/dt\) is the rate at which the clock runs at time \(t\).</p>

<p>For \(C_i\) to run at a rate close to real physical time, we need \(dC_i(t)/dt \approx 1\) for all \(t\). More precisely, we need the following condition.</p>

<ul>
  <li>PC1. There exists a constant \(\kappa \ll 1\) such that for all \(i\) : \(\vert  dC_i(t)/dt - 1 \vert  &lt; \kappa\). For typical crystal controlled clocks, \(\kappa \leq 10^{-6}\).</li>
</ul>

<p>Besides keeping each clock accurate, the clocks must also stay synchronized with each other. That is, \(C_i(t) \approx C_j(t)\) for all \(i,j,t\).</p>

<ul>
  <li>PC2. For all \(i, j\): \(\vert C_i(t) - C_j(t)\vert  &lt; \epsilon\). Intuitively, the height difference of a single tick line in Figure 2 cannot be too large.</li>
</ul>

<p>For PC2, because of accumulated error, two clocks that run completely independently will drift further and further apart. So we need an algorithm to synchronize the clocks on different nodes.</p>

<p>First, we assume our clocks satisfy the <strong>Clock Condition</strong>. Then we only need to consider the case \(a \nrightarrow b\) in \(\underline{\varphi}\). It is easy to see that \(a\) and \(b\) must happen on different nodes.</p>

<p>Let \(\mu\) be smaller than the minimum communication delay between nodes. That is, event \(a\) happens at physical time \(t\), and event \(b\) happens on another node. If \(a\underline{\rightarrow} b\), then \(b\) happens at \(t + \mu\) at the earliest. Usually, we can set \(\mu\) to the minimum distance between nodes divided by the speed of light.</p>

<p>To avoid the abnormal case above, we must make sure that \(C_i(t + \mu) - C_j(t) &gt; 0\) for any \(i, j\) and \(t\) .</p>

<p>Combining with PC1, we have \(C_i(t + \mu) - C_i(t) &gt; (1- \kappa)\mu\). See Appendix 8.2 for the derivation.</p>

<p>Combining with PC2, we need \(-\epsilon \geq -\mu(1 - \kappa)\), so we need \(\epsilon/(1 - \kappa) \leq \mu\). See Appendix 8.3 for the derivation.</p>

<h4 id="721-physical-clock-algorithm">7.2.1 Physical clock algorithm</h4>

<p>Next, I describe the algorithm that makes the formulas above, PC1 and PC2 hold.</p>

<p>For a message \(m\) sent at physical time \(t\) and received at physical time \(t'\), we define the total delay of the message as \(v_m = t' - t\). The receiving node does not know the value of \(v_m\). But it can know the minimum delay of the message \(\mu_m\), where \(\mu_m \geq 0\) and \(\mu_m \leq v_m\). We call \(\xi_m = v_m - \mu_m\) the unpredictable delay.</p>

<p>For the physical clock algorithm on a single node, we have the following Implementation Rules.</p>

<ul>
  <li>IR1’. If node \(P_i\) receives no message at physical time \(t\), then \(C_i\) is differentiable at \(t\), and \(dC_i(t)/dt &gt; 0\).</li>
  <li>IR2’. (a) If \(P_i\) sends message \(m\) at physical time \(t\), then \(m\) contains a timestamp \(T_m=C_i(t)\). (b) When it receives message \(m\) at physical time \(t'\), process \(P_j\) sets its current time to \(C_j(t') = \max(C_j(t' - 0), T_m + \mu_m)\). Here \(C_j(t' - 0) = \underset{\delta \rightarrow 0}{\lim}C_j(t'-\vert \delta\vert )\).</li>
</ul>

<h4 id="722-proof-of-the-physical-clock-algorithm">7.2.2 Proof of the physical clock algorithm</h4>

<p>Now we prove that the implementation rules above ensure PC2.</p>

<p>We view the whole system as a directed graph. The vertices are the nodes. A directed edge from \(P_i\) to \(P_j\) is a message link.</p>

<p>Let \(d\) be the diameter of the directed graph (longest shortest path). Let \(\tau\) be the minimum communication interval between two nodes. That is, between any time \(t\) and \(t + \tau\), \(P_i\) sends at least one message to \(P_j\). The theorem below tells us how long after system start-up the system reaches a time synchronization that satisfies PC2.</p>

<p><strong>Theorem</strong>: Assume the system is a strongly connected graph with diameter \(d\) that follows IR1’ and IR2’. For any message \(m\), \(\mu_m \leq \mu\), where \(\mu\) is a specific constant, for all \(t \geq t_0\). (a) PC1 always holds. (b) There are constants \(\tau\) and \(\xi\) such that, on every edge of the system, a message with an unpredictable delay of at most \(\xi\) is sent every \(\tau\) seconds. Then PC2 is satisfied for all \(t\gtrapprox t_0 + \tau d\), with \(\epsilon \approx d(2\kappa\tau + \xi)\) and \(\mu + \xi \ll \tau\). The proof is in Appendix 8.4.</p>

<h2 id="8-appendix">8. Appendix</h2>

<h3 id="81-total-ordering-application-mutual-exclusion-problem">8.1 Total Ordering Application: Mutual Exclusion Problem</h3>

<h3 id="82-derivation-of-pc1">8.2 Derivation of PC1</h3>

<p>By PC1, \(\left \vert  \frac{C_i(t+\mu) - C_i(t)}{\mu}  &lt; \kappa \right \vert\). So \((1 - \kappa)\mu &lt; C_i(t+\mu) - C_i(t) &lt; (1 + \kappa)\mu\) .</p>

<h3 id="83-derivation-of-pc2">8.3 Derivation of PC2</h3>

<p>Continuing from 8.2, \(C_i(t) + \mu(1-\kappa) &lt; C_i(t+\mu)\).</p>

<p>So for \(C_i(t + \mu) - C_j(t) &gt; 0\), we need \(C_i(t) - C_j(t) &gt; -\mu(1 - \kappa)\).</p>

<p>By PC2, \(C_i(t) - C_j(t) &lt; -\epsilon\).</p>

<p>So we get \(\epsilon \leq \mu(1 - \kappa)\).</p>

<h3 id="84-proof-of-the-theorem">8.4 Proof of the theorem</h3>

<p>For any \(i\) and \(t\), we define \(C_i^t\) as a clock that is set to \(C_i\) at time \(t\), runs at the same rate as \(C_i\), and is never reset. That is,</p>

\[C_i^t = C_i(t) + \int_{t}^{t'}[dC_i(t)/dt]dt \tag{1}\]

<p>For all \(t' \geq t\), because \(C_i\) can be reset, we note that</p>

\[C_i(t') \geq C_i^t(t') \tag{2}\]

<p>Suppose \(P_1\) sends a message to \(P_2\) at time \(t_1\). \(P_2\) receives it at time \(t_2\). The unpredictable delay is \(\leq \xi\), and \(t_0 \leq t_1 \leq t_2\). Then for all \(t \geq t_2\), we have</p>

\[\begin{aligned}
&amp; C_2^{t_2}(t) \geq C_2^{t_2}(t_2) + (1 - \kappa)(t - t_2) &amp; \qquad [by\ (1)\ and\ PC1] \\
&amp; \geq C_1(t_1) + \mu_m + (1 - \kappa)(t - t_2) &amp; \qquad [by\ IR2'(b)] \\
&amp; = C_1(t_1) + (1 - \kappa)(t - t_1) - [(t_2 - t_1) - \mu_m] + \kappa(t_2 - t_1) \\
&amp; \geq C_1(t_1) + (1 - \kappa)(t - t_1) - \xi
\end{aligned}\]

<p>So, using these assumptions, for all \(t \geq t_2\) we get</p>

\[C_2^{t_2}(t) \geq C_1(t_1) + (1 - \kappa)(t - t_1) - \xi \tag{3}\]

<p>Now suppose that for \(i =1,...,n\), we have \(t_i \leq t_i' &lt; t_{i+1}, t_0 \leq t_1\). At \(t_i'\), \(P_i\) sends a message to \(P_{i+1}\). The message is received at \(t_{i+1}\), and its unpredictable delay is less than \(\xi\). Applying equation (3) repeatedly, for \(t \geq t_{n+1}\) we get</p>

\[C_{n+1}^{t_{n+1}}(t) \geq C_1(t_1') + (1 - \kappa)(t - t_1') - n\xi \tag{4}\]

<p>From PC1, IR1’, and IR2’, we can derive</p>

\[C_1(t_1') \geq C_1(t_1) + (1 - \kappa)(t_1' - t_1)\]

<p>Combining this with (4) and (2), for \(t \geq t_{n+1}\) we get</p>

\[C_{n+1}(t) \geq C_1(t_1) + (1 - \kappa)(t - t_1) - n\xi \tag{5}\]

<p>For any two nodes \(P\) and \(P'\), we can find a sequence of nodes \(P = P_0, P_1, ..., P_{n+1} = P', n \leq d\). By assumption (b), we can find times \(t_i, t_i'\) such that \(t_i' - t_i \leq \tau\) and \(t_{i+1} - t_i' \leq v\), where \(v=\mu + \xi\). So inequality (5) holds for any \(t \geq t_1 + d(\tau + v)\), with \(n \leq d\). For any \(i, j, t, t_1\) with \(t_1 \geq t_0\) and \(t \geq t_1 + d(\tau + v)\), we therefore have</p>

\[C_i(t) \geq C_j(t_1) + (1 - \kappa)(t - t_1) - d\xi \tag{6}\]]]></content><author><name></name></author><summary type="html"><![CDATA[Time, Clocks, and the Ordering of Events in a Distributed System, Lamport, 1978]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://changliu.me/assets/images/social-card.png" /><media:content medium="image" url="https://changliu.me/assets/images/social-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="zh"><title type="html">Time, Clocks, and the Ordering of Events in a Distributed System, Lamport, 1978</title><link href="https://changliu.me/post/time-clocks-and-the-ordering-of-events-in-a-distributed-system/" rel="alternate" type="text/html" title="Time, Clocks, and the Ordering of Events in a Distributed System, Lamport, 1978" /><published>2020-10-06T00:00:00+00:00</published><updated>2020-10-06T00:00:00+00:00</updated><id>https://changliu.me/post/time-clocks-and-the-ordering-of-events</id><content type="html" xml:base="https://changliu.me/post/time-clocks-and-the-ordering-of-events-in-a-distributed-system/"><![CDATA[<h1 id="time-clocks-and-the-ordering-of-events-in-a-distributed-system-lamport-1978">Time, Clocks, and the Ordering of Events in a Distributed System, Lamport, 1978</h1>

<p>本篇文章总结学习了Lamport于1978年发表在 <em>Communications of the ACM</em> 上的论文 <a href="https://lamport.azurewebsites.net/pubs/time-clocks.pdf">Time, Clocks, and the Ordering of Events in a Distributed System</a>，文章对分布式系统中的时间、时钟等概念做了深入的讨论，提出了“Happened Before”、“逻辑时钟”、“物理时钟”、“State Machine”等重要概念与算法，是分布式领域不能不读的经典论文。</p>

<h2 id="1-问题">1. 问题</h2>

<p>在进入正题之前，首先让我们考虑如下问题：</p>

<ul>
  <li>某人做了某事 \(A\) 并看了其手表，其读数为“2020/10/06 13:00”。他声称 \(A\) 发生于“2020/10/06 13:00”。</li>
  <li>另一人做了某事 \(B\) 并看了其手表，其读数为“2020/10/06 13:05”。他声称 \(B\) 发生于“2020/10/06 13:05”。</li>
</ul>

<p>假设两人都是诚实的，我们能说 \(A\) 发生在 \(B\) 之前吗？显然，由于我们并不知道两人的手表时间是否“准确”，所以不能准确地判断出孰先孰后。那么如果问题变成下面这样，结论又是如何呢？</p>

<ul>
  <li>某人做了某事 \(A\) ，并看了其手表，读数为“2020/10/06 13:00”。他声称 \(A\) 发生于“2020/10/06 13:00”。</li>
  <li>此人打了一通电话给另一人。</li>
  <li>第二个人在通话完毕后，做了某事 \(B\) 并看了其手表，其读数为“2020/10/06 12:55”。他声称 \(B\) 发生于“2020/10/06 12:55”。</li>
</ul>

<p>由此可见，我们通常使用的时钟读数、时间戳等概念并不能准确地刻画出事件发生的先后顺序。而在分布式系统中，事件发生的先后常常扮演着各种算法的“关键角色”。那么我们如何准确地刻画这种顺序，或是设计我们的时钟，从而避免上面例子中的问题呢？</p>

<h2 id="2-时间是什么">2. 时间是什么</h2>

<blockquote>
  <p>The concept of time is fundamental to our way of thinking. It is derived from the more basic concept of the order in which events occur.</p>
</blockquote>

<p>时间的定义对于分布式领域关于“事件发生先后”、“并发”等概念的认识至关重要。Lamport在文中指出，时间是由更加基本的概念“事件发生的顺序”衍生出来的。例如我们说某件事在13:00发生，其实是在说这件事发生在我们读到时钟上的读数为13:00之后，13:01之前。由此可见，时钟其实是对连续的时间进行了离散化的“编号”。</p>

<h2 id="3-分布式系统">3. 分布式系统</h2>

<p>本文中讨论的分布式系统，是由若干空间上分离的process组成。同一process上的事件顺序串行发生，process之间通过收发消息进行通信。这里的process可以是若干独立的计算机、独立的进程，或是一台计算机内独立的硬件模块。在后文中我们统称process为“节点”。特别地，各个节点之间的通信延迟与单个节点内部事件发生的频率相比，是不可忽略的。</p>

<h2 id="4-happened-before-偏序关系">4. Happened Before 偏序关系</h2>

<p>对于一个分布式系统中的若干事件，我们定义“happened before”关系，用“\(\rightarrow\)”标识。其满足如下三个条件，</p>

<ul>
  <li>(1) 如果 \(a\) 和 \(b\) 是在相同节点上的两个事件，\(a\) 在 \(b\) 之前发生，则有 \(a \rightarrow b\) 。</li>
  <li>(2) 如果事件 \(a\) 表示某个节点发送某条消息，\(b\) 是另一个节点接收这条消息，则有 \(a \rightarrow b\) 。</li>
  <li>(3) 如果有 \(a \rightarrow b\) 且 \(b \rightarrow c\) ，则有 \(a \rightarrow c\) 。</li>
</ul>

<p>当且仅当 \(a \nrightarrow b, b \nrightarrow a\) 时，我们称两个事件为<strong>并发的(concurrent)</strong>。</p>

<p>此外，我们规定 \(\rightarrow\) 为非自反关系，即 \(a \nrightarrow a\) 。显然，说一件事发生在自己“之前”并无任何意义。</p>

<p>为了直观地描述这一关系，Lamport引入了如下图所示的“时空图”，图中垂直方向自下而上为时间先后顺序，水平方向为空间上的不同节点。图中的黑色圆点表示事件，波浪线箭头表示通信消息。</p>

<p>回顾上面的“happened before”关系，我们不难在图中找到若干满足条件的事件对，例如 \(p_1 \rightarrow r_4\)，其由 \(p_1 \rightarrow q_2 \rightarrow q_4 \rightarrow r_3 \rightarrow r_4\) 推导而来。</p>

<p>图中亦有若干并发的事件，例如 \(p_3\) 和 \(q_3\)，虽然在图中我们能看到 \(p_3\) 发生的物理时间(physical time)晚于 \(q_3\)，但对于系统中的节点来说，他们并不知道谁先谁后。</p>

<p><img src="/assets/images/lamport-time-clocks/Fig1.jpg" alt="图1. space-time diagram" width="70%" />
<em>图1. space-time diagram</em></p>

<h2 id="5-逻辑时钟">5. 逻辑时钟</h2>

<blockquote>
  <p>a clock is just a way of assigning a number to an event.</p>
</blockquote>

<p><strong>时钟仅仅是对事件的发生予以编号而已。</strong> 更加准确地讲，对于每一个节点 \(P_i\) 我们定义时钟 \(C_i\) 为一个函数，它为任意的事件 \(a\) 赋值编号为 \(C_i \langle a \rangle\)。对整个系统时钟来讲，任意事件 \(b\) 的发生时间标记为 \(C \langle b \rangle\)，如果其发生在节点 \(P_j\) 上，则 \(C \langle b \rangle =  C_j \langle b \rangle\)。这里的时钟我们看做是系统内部的逻辑时钟，而非物理时钟，其标识与计数方法无需与物理时间一致。为了满足上文的”happened before”偏序关系，我们设计的逻辑时钟需要满足如下的Clock Condition。</p>

<p><strong>Clock Condition.</strong> 对于系统中的任意事件 \(a, b\)：如果 \(a \rightarrow b\)，则 \(C \langle a \rangle &lt; C \langle b \rangle\)。</p>

<ul>
  <li>C1. 如果 \(a\) 和 \(b\) 是在相同节点 \(P_i\) 上的两个事件，\(a\) 在 \(b\) 之前发生，则有 \(C_i \langle a \rangle &lt; C_i \langle b \rangle\)。</li>
  <li>C2. 如果事件 \(a\) 表示节点 \(P_i\) 发送某条消息，\(b\) 表示节点 \(P_j\) 接收这条消息，则有\(C_i \langle a \rangle &lt; C_j \langle b \rangle\)。</li>
</ul>

<p><strong>特别地，Clock Condition的逆命题“如果 \(C \langle a \rangle &lt; C \langle b \rangle\)，则 \(a \rightarrow b\)”并不成立。</strong> 因为它要求并发的事件必须具有相同的逻辑时间。例如图1中的 \(p_2,p_3\) 都与 \(q_3\) 为并发关系，但由 C1 有 \(C \langle p_2 \rangle &lt; C \langle p_3 \rangle\)，则必然有 \(C \langle q_3 \rangle \neq C \langle p_2 \rangle\) 或 \(C \langle q_3 \rangle \neq C \langle p_3 \rangle\)，与并发关系矛盾。</p>

<p>对于逻辑时钟，我们可以想象单个节点内不断发生着“tick”事件，例如在同一节点 \(P_i\) 内连续发生的 \(a, b\) 两个事件，有 \(C_i \langle a \rangle = 4, C_i \langle b \rangle = 7\)，那么在这两个事件之间发生了编号为 \(5,6,7\) 的 tick 事件。于是我们可以在时空图中加入类似下图虚线所示的”tick line”。根据 C1 我们可以得到，在同一节点内的连续两个事件之间，至少要有一条 tick line。根据 C2 我们可以得到，每一条消息必须穿过至少一条 tick line。</p>

<p><img src="/assets/images/lamport-time-clocks/Fig2.png" alt="图2" width="70%" />
<em>图2</em></p>

<p>为了更方便理解，我们也可以在保证事件和消息的偏序关系下，将 tick line 绘制成如下图中等价的水平线的形式。</p>

<p><img src="/assets/images/lamport-time-clocks/Fig3.png" alt="图3" width="70%" />
<em>图3</em></p>

<p>对于单个节点上的逻辑时钟算法的实现，我们有如下的实现规则（Implementation Rule）：</p>

<ul>
  <li>IR1. 每个节点 \(P_i\) 在任意连续的两个事件之间都要增加 \(C_i\) 。</li>
  <li>IR2. (a) 如果事件 \(a\) 表示节点 \(P_i\) 发送消息 \(m\) ，那么 \(m\) 中包含时间戳 \(T_m=C_i \langle a \rangle\)。(b) 当收到消息 \(m\) 时，进程 \(P_j\) 设置当前时间 \(C_j\) 为 \(C_j'\)，使得 \(C_j' \geq C_j\) 且 \(C_j' &gt; T_m\) 。</li>
</ul>

<p>在实践中，当我们收到某条消息后，应当先执行 IR2 修改时间，再执行具体事件，从而保证 <strong>Clock Condition</strong>。</p>

<h2 id="6-全序关系">6. 全序关系</h2>

<p>利用逻辑时钟，我们可以对整个系统中的事件进行全序(total order)排序。我们首先根据事件发生的时间对其排序。对于发生时间相同的事件，我们引入对所有节点预先定义的优先级 \(\prec\)，这里的优先级可以是根据 id 排序等任意规则。</p>

<p>更加严谨地说，我们定义全序关系 \(\Rightarrow\)。对于发生在节点 \(P_i\) 的事件 \(a\) 和发生在节点 \(P_j\) 的事件 \(b\)，有 \(a \Rightarrow b\) 当且仅当 (i) \(C_i \langle a \rangle &lt; C_j \langle b \rangle\) 或 (ii) \(C_i \langle a \rangle = C_j \langle b \rangle\) 且 \(P_i \prec P_j\)。</p>

<p>这里由 <strong>Clock Condition</strong> 我们可以看到，凡是满足偏序关系 \(\rightarrow\) 的，一定也满足全序关系 \(\Rightarrow\)。</p>

<p><img src="/assets/images/lamport-time-clocks/partial-total.png" alt="图4. 偏序与全序关系" width="40%" />
<em>图4. 偏序与全序关系</em></p>

<h2 id="7-物理时钟">7. 物理时钟</h2>

<h3 id="71-系统之外">7.1 系统之外</h3>

<p>在全序关系下，由于系统之外的一些事件，使得我们有时会遇到一些反常行为。</p>

<p>考虑下面这种情况，某人在节点A上触发了事件A，随后打电话给另一个人。此人接到电话后在节点B上触发事件B。由于整个系统对于系统之外的事件“打电话”毫不知情，则有可能出现 \(B \Rightarrow A\) 的情况。</p>

<p>我们可以定义系统中的所有事件集合为 \(\varphi\)。系统中的事件与外部事件的合集为 \(\underline{\varphi}\)。\(\underline{\rightarrow}\) 为 \(\underline{\varphi}\) 上的 happened before 关系。在上面的例子中，我们有 \(A \underline{\rightarrow} B\)，但 \(A \nrightarrow B\)。</p>

<p>显然没有任何算法能够不利用外部信息，仅凭 \(\varphi\) 就能保证 \(\underline{\rightarrow}\) 关系。在此，为了能够确保\(A \rightarrow B\)，有如下两种方案，</p>

<ol>
  <li>显式地引入外部信息。例如 \(A\) 事件发生的逻辑时间为 \(T_A\)，在接到电话后，显式地告知系统 \(B\) 的发生时间应大于 \(T_A\)。</li>
  <li>构建满足如下<strong>Strong Clock Condition</strong> 的系统。</li>
</ol>

<p><strong>Strong Clock Condition.</strong> 对于 \(\varphi\) 中的任意事件 \(a, b\)：如果\(a \underline{\rightarrow} b\) 则 \(C \langle a \rangle &lt; C \langle b \rangle\)。</p>

<p>显然相较于方案1，<strong>Strong Clock Condition</strong>才是我们希望的方案。下面具体介绍如何实现满足<strong>Strong Clock Condition</strong>的物理时钟。</p>

<h3 id="72-物理时钟实现">7.2 物理时钟实现</h3>

<p>令 \(C_i(t)\) 表示时钟 \(C_i\) 在物理时间 \(t\) 读到的读数。为了数学上的方便，在此我们认为 \(C_i\) 对于 \(t\) 是连续可微的，\(dC_i(t)/dt\) 表示时钟在时间 \(t\) 运行的速率。</p>

<p>为了使 \(C_i\) 的运行速率与真实物理时钟相近，对于所有的 \(t\)，我们必须使得 \(dC_i(t)/dt \approx 1\)。更严谨地讲，我们需要满足如下条件，</p>

<ul>
  <li>PC1. 存在一个常数 \(\kappa \ll 1\)，对于所有的 \(i\) ，有 \(\vert  dC_i(t)/dt - 1 \vert  &lt; \kappa\)。对于典型的晶控时钟(crystal controlled clock)，\(\kappa \leq 10^{-6}\)。</li>
</ul>

<p>除了保证单个时钟运行准确之外，各个时钟之间也需要保持同步，即对所有的 \(i,j,t\)，有 \(C_i(t) \approx C_j(t)\)。</p>

<ul>
  <li>PC2. 对于所有的 \(i, j\)：\(\vert C_i(t) - C_j(t)\vert  &lt; \epsilon\)。直观来讲即图2中的单条 tick line 高度差不能太大。</li>
</ul>

<p>对于 PC2，由于累计误差（accumulated error）的存在，两个完全独立运行的时钟必然会误差越来越大。因此我们需要某种算法对不同节点上的时钟进行对时。</p>

<p>首先我们假设我们的时钟满足<strong>Clock Condition</strong>，这样我们只需考虑在 \(\underline{\varphi}\) 中 \(a \nrightarrow b\) 的情况。不难发现，此时 \(a\) 与 \(b\) 必然发生在不同的节点上。</p>

<p>令 \(\mu\) 小于节点间的最小通信时延。即事件 \(a\) 发生于物理时间 \(t\)，事件 \(b\) 发生于另一节点，若 \(a\underline{\rightarrow} b\)，则 \(b\) 最早发生于 \(t + \mu\)。通常我们可以设定 \(\mu\) 为节点间的最小距离除以光速。</p>

<p>为了避免上文中的反常情况，我们必须保证对于任意的 \(i, j\) 和 \(t\) ，有 \(C_i(t + \mu) - C_j(t) &gt; 0\)。</p>

<p>结合PC1，有 \(C_i(t + \mu) - C_i(t) &gt; (1- \kappa)\mu\)，详细推导见附录8.2。</p>

<p>结合PC2，需保证 \(-\epsilon \geq -\mu(1 - \kappa)\)，则需有 \(\epsilon/(1 - \kappa) \leq \mu\)，详细推导见附录8.3。</p>

<h4 id="721-物理时钟算法">7.2.1 物理时钟算法</h4>

<p>下面介绍具体的算法实现，从而保证上面的公式与PC1，PC2成立。</p>

<p>对于一条发送于物理时间 \(t\) ，接收于物理时间 \(t'\) 的消息 \(m\)，我们定义消息的总延迟（total delay） \(v_m = t' - t\)。接收消息的节点当然不知道 \(v_m\) 的值，但是它可以知道这条消息的最小延迟（minimum delay） \(\mu_m\)， \(\mu_m \geq 0\) 且 \(\mu_m \leq v_m\)。我们称 \(\xi_m = v_m - \mu_m\) 为不可预测延迟（unpredictable delay）。</p>

<p>对于单个节点上的物理时钟算法的实现，我们有如下的实现规则（Implementation Rule）：</p>

<ul>
  <li>IR1’. 每个节点 \(P_i\) 在物理时间 \(t\) 没有收到任何消息，那么 \(C_i\) 在 \(t\) 时刻可微，且 \(dC_i(t)/dt &gt; 0\)。</li>
  <li>IR2’. (a) 如果 \(P_i\) 在物理时间 \(t\) 发送消息 \(m\) ，那么 \(m\) 中包含时间戳 \(T_m=C_i(t)\)。(b) 当在物理时间 \(t'\) 收到消息 \(m\) 时，进程 \(P_j\) 设置当前时间 \(C_j(t') = \max(C_j(t' - 0), T_m + \mu_m)\)。其中\(C_j(t' - 0) = \underset{\delta \rightarrow 0}{\lim}C_j(t'-\vert \delta\vert )\)。</li>
</ul>

<h4 id="722-物理时钟算法证明">7.2.2 物理时钟算法证明</h4>

<p>现在我们证明上述的实现规则可以确保满足PC2。</p>

<p>将整个系统视为一个有向图，图中的点为各个节点，\(P_i\) 到 \(P_j\) 的有向边视为其消息链路。</p>

<p>令 \(d\) 为有向图的直径(longest shortest path)。\(\tau\) 为两个节点之间的最低通信间隔，即任意时间 \(t\) 到 \(t + \tau\)之间，\(P_i\) 至少应该发送一条消息给 \(P_j\)。下面的定理给出系统启动后，至多多久系统会达成满足PC2的时间同步。</p>

<p><strong>定理</strong>：假设系统为一个遵循IR1’和IR2’，且直径为 \(d\) 的强连通图。对于任意的消息 \(m\)，\(\mu_m \leq \mu\)，其中\(\mu\) 为某个特定常数，且对于所有 \(t \geq t_0\)。 (a) PC1 总是成立。 (b) 存在常数 \(\tau\) 和 \(\xi\)，在系统中每条边上，每\(\tau\)秒会转发一条不可预测延迟最大为 \(\xi\) 的消息。则PC2 满足于，对于所有的 \(t\gtrapprox t_0 + \tau d\)，\(\epsilon \approx d(2\kappa\tau + \xi)\)，\(\mu + \xi \ll \tau\)。其证明见于附录8.4。</p>

<h2 id="8-附录">8. 附录</h2>

<h3 id="81-全序关系的应用互斥访问">8.1 全序关系的应用：互斥访问</h3>

<h3 id="82-pc1-推导">8.2 PC1 推导</h3>

<p>由PC1有 \(\left \vert  \frac{C_i(t+\mu) - C_i(t)}{\mu}  &lt; \kappa \right \vert\)，则有 \((1 - \kappa)\mu &lt; C_i(t+\mu) - C_i(t) &lt; (1 + \kappa)\mu\) 。</p>

<h3 id="83-pc2-推导">8.3 PC2 推导</h3>

<p>继8.2有，\(C_i(t) + \mu(1-\kappa) &lt; C_i(t+\mu)\)。</p>

<p>故要使得 \(C_i(t + \mu) - C_j(t) &gt; 0\)，则需有 \(C_i(t) - C_j(t) &gt; -\mu(1 - \kappa)\)。</p>

<p>由PC2有，\(C_i(t) - C_j(t) &lt; -\epsilon\)。</p>

<p>故而得，\(\epsilon \leq \mu(1 - \kappa)\)。</p>

<h3 id="84-定理证明">8.4 定理证明</h3>

<p>对于任意的 \(i\) 和 \(t\)，我们定义 \(C_i^t\) 为一个时钟，在时刻 \(t\) 设定为 \(C_i\) 并与 \(C_i\) 运行速率相同，且永不被修正(reset)。即，</p>

\[C_i^t = C_i(t) + \int_{t}^{t'}[dC_i(t)/dt]dt \tag{1}\]

<p>对于所有的 \(t' \geq t\)，我们注意到由于 \(C_i\) 会被修正，有</p>

\[C_i(t') \geq C_i^t(t') \tag{2}\]

<p>假设 \(P_1\) 在时刻 \(t_1\) 发送消息给 \(P_2\)，接收于时刻 \(t_2\)，不可预测延迟 \(\leq \xi\)，\(t_0 \leq t_1 \leq t_2\)。则对于所有的 \(t \geq t_2\)，我们有</p>

\[\begin{aligned}
&amp; C_2^{t_2}(t) \geq C_2^{t_2}(t_2) + (1 - \kappa)(t - t_2) &amp; \qquad [by\ (1)\ and\ PC1] \\
&amp; \geq C_1(t_1) + \mu_m + (1 - \kappa)(t - t_2) &amp; \qquad [by\ IR2'(b)] \\
&amp; = C_1(t_1) + (1 - \kappa)(t - t_1) - [(t_2 - t_1) - \mu_m] + \kappa(t_2 - t_1) \\
&amp; \geq C_1(t_1) + (1 - \kappa)(t - t_1) - \xi
\end{aligned}\]

<p>因此，利用这些假设，我们可以得到，对于所有的\(t \geq t_2\)，有</p>

\[C_2^{t_2}(t) \geq C_1(t_1) + (1 - \kappa)(t - t_1) - \xi \tag{3}\]

<p>现在假设对于 \(i =1,...,n\)，我们有\(t_i \leq t_i' &lt; t_{i+1}, t_0 \leq t_1\)。在 \(t_i'\)时，\(P_i\) 发送一条消息给 \(P_{i+1}\)，该消息接收于 \(t_{i+1}\)，其不可预测延迟小于 \(\xi\)。反复应用等式(3)可以得到，对于 \(t \geq t_{n+1}\)，</p>

\[C_{n+1}^{t_{n+1}}(t) \geq C_1(t_1') + (1 - \kappa)(t - t_1') - n\xi \tag{4}\]

<p>通过 PC1, IR1’, IR2’我们可以推导出，</p>

\[C_1(t_1') \geq C_1(t_1) + (1 - \kappa)(t_1' - t_1)\]

<p>再结合(4)与(2)，我们可以得到对于 \(t \geq t_{n+1}\)，有</p>

\[C_{n+1}(t) \geq C_1(t_1) + (1 - \kappa)(t - t_1) - n\xi \tag{5}\]

<p>对于任意的两个节点 \(P\) 和 \(P'\)，我们能找到一个节点序列 \(P = P_0, P_1, ..., P_{n+1} = P', n \leq d\)。通过假设 (b)，我们能找到时间 \(t_i, t_i'\)，有 \(t_i' - t_i \leq \tau\) 且 \(t_{i+1} - t_i' \leq v\)，其中 \(v=\mu + \xi\)。因此对于任何的 \(t \geq t_1 + d(\tau + v)\)，不等式 (5) 成立，且有 \(n \leq d\)。对于任意的\(i, j, t, t_1\)，其中 \(t_1 \geq t_0\) 且 \(t \geq t_1 + d(\tau + v)\)，我们因此有</p>

\[C_i(t) \geq C_j(t_1) + (1 - \kappa)(t - t_1) - d\xi \tag{6}\]]]></content><author><name></name></author><summary type="html"><![CDATA[Time, Clocks, and the Ordering of Events in a Distributed System, Lamport, 1978]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://changliu.me/assets/images/social-card.png" /><media:content medium="image" url="https://changliu.me/assets/images/social-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="en"><title type="html">libco Source Notes (3): Automatic Switching</title><link href="https://changliu.me/2020/09/23/libco-auto.html" rel="alternate" type="text/html" title="libco Source Notes (3): Automatic Switching" /><published>2020-09-23T00:00:00+00:00</published><updated>2020-09-23T00:00:00+00:00</updated><id>https://changliu.me/2020/09/23/libco-auto</id><content type="html" xml:base="https://changliu.me/2020/09/23/libco-auto.html"><![CDATA[<h1 id="libco-source-notes-3-automatic-switching">libco Source Notes (3): Automatic Switching</h1>

<p>In the previous post, libco Source Notes (2): Explicit Switching, we covered the explicit coroutine switching interfaces that libco provides, and discussed how to use a coroutine pool. This post covers the interfaces libco provides for automatic switching. I suggest reading it together with my <a href="https://github.com/changliu0828/libco">annotated version</a>.</p>

<h2 id="background-of-automatic-switching">Background of Automatic Switching</h2>

<p>In Li Fangyuan’s libco talk$^{[2]}$, he explains that before libco, most network communication in WeChat used synchronous IO interfaces. To quickly adapt the existing business code, libco hooks system calls and provides coroutine-based primitives such as <code class="language-plaintext highlighter-rouge">poll</code>, <code class="language-plaintext highlighter-rouge">read</code> and <code class="language-plaintext highlighter-rouge">write</code>. Because of how coroutines work, a system call that used to block now behaves as if it were non-blocking.</p>

<h2 id="timeout-management">Timeout Management</h2>

<p>libco needs to manage events that require timeouts, such as network IO and condition variables, in one place. For this, it implements a timeout manager based on a timing wheel. Before I cover how libco hooks system calls, let’s lay some groundwork on how this timeout manager is implemented.</p>

<p>As Figure 1 shows, the timing wheel is the dark red wheel-shaped array in the figure. we call each cell of the array a slot. A single slot stores the list of events registered within a certain period of time (the yellow linked lists in the figure). In libco, one slot has a precision of 1 millisecond. The whole wheel has 60000 slots, so it covers 60 seconds. The timing wheel has two main interface functions in libco, shown below.</p>

<p><code class="language-plaintext highlighter-rouge">AddTimeout</code> computes the difference between the current time <code class="language-plaintext highlighter-rouge">allNow</code> and the wheel start time <code class="language-plaintext highlighter-rouge">ullStart</code>, and inserts the item into the matching slot. Note that when the timeout is longer than the wheel length of 60 seconds, libco inserts the event into the “last” slot.</p>

<p><code class="language-plaintext highlighter-rouge">TakeAllTimeout</code> computes the difference between the current time <code class="language-plaintext highlighter-rouge">allNow</code> and the wheel start time <code class="language-plaintext highlighter-rouge">ullStart</code> to find the matching slot. It then walks all slots from the slot at the start index <code class="language-plaintext highlighter-rouge">ullStartIdx</code> up to that slot, and moves the timed-out items into the result list <code class="language-plaintext highlighter-rouge">apResult</code>.</p>

<p>With this, we can see that libco uses the timing wheel to manage timeout events efficiently.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* Insert a new item into the timing wheel
 * @param 
 * apTimeout :timing wheel structure
 * apItem    :new timeout item
 * allNow    :current time (timestamp in ms)
 * @return   :0 on success, else the failure line number
 */</span>
<span class="kt">int</span> <span class="nf">AddTimeout</span><span class="p">(</span> <span class="n">stTimeout_t</span> <span class="o">*</span><span class="n">apTimeout</span><span class="p">,</span><span class="n">stTimeoutItem_t</span> <span class="o">*</span><span class="n">apItem</span> <span class="p">,</span><span class="kt">unsigned</span> <span class="kt">long</span> <span class="kt">long</span> <span class="n">allNow</span> <span class="p">);</span>
<span class="cm">/* Take all timeout items out of the timing wheel
 * @param 
 * apTimeout:timing wheel structure
 * allNow   :current time (timestamp in ms)
 * apResult :result list of timeout events
 */</span>
<span class="kr">inline</span> <span class="kt">void</span> <span class="nf">TakeAllTimeout</span><span class="p">(</span> <span class="n">stTimeout_t</span> <span class="o">*</span><span class="n">apTimeout</span><span class="p">,</span><span class="kt">unsigned</span> <span class="kt">long</span> <span class="kt">long</span> <span class="n">allNow</span><span class="p">,</span><span class="n">stTimeoutItemLink_t</span> <span class="o">*</span><span class="n">apResult</span> <span class="p">);</span>
</code></pre></div></div>

<p><img src="/assets/images/libco-auto/timing-wheel.png" alt="Figure 1. Timeout management" width="100%" />
<em>Figure 1. Timeout management</em></p>

<h2 id="event-loop">Event Loop</h2>

<p>libco manages IO events with epoll. <code class="language-plaintext highlighter-rouge">co_eventloop</code> triggers the IO events and switches to the matching coroutine to run it. Recall the thread-private global variable <code class="language-plaintext highlighter-rouge">stCoRoutineEnv_t</code> mentioned earlier, which represents the coroutine runtime environment. It holds the epoll structure handle <code class="language-plaintext highlighter-rouge">pEpoll</code>:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">stCoRoutineEnv_t</span>
<span class="p">{</span>
  <span class="n">stCoRoutine_t</span> <span class="o">*</span><span class="n">pCallStack</span><span class="p">[</span> <span class="mi">128</span> <span class="p">];</span>   <span class="c1">//call stack of all coroutines</span>
  <span class="kt">int</span> <span class="n">iCallStackSize</span><span class="p">;</span>                 <span class="c1">//index of the top of pCallStack</span>
  <span class="n">stCoEpoll_t</span> <span class="o">*</span><span class="n">pEpoll</span><span class="p">;</span>                <span class="c1">//epoll wrapper</span>

  <span class="c1">//for copy stack log lastco and nextco</span>
  <span class="n">stCoRoutine_t</span><span class="o">*</span> <span class="n">pending_co</span><span class="p">;</span>           
  <span class="n">stCoRoutine_t</span><span class="o">*</span> <span class="n">occupy_co</span><span class="p">;</span>           <span class="c1">//current coroutine</span>
<span class="p">};</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">stCoEpoll_t</code> is defined as follows:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">stCoEpoll_t</span>
<span class="p">{</span>
  <span class="kt">int</span> <span class="n">iEpollFd</span><span class="p">;</span>                                   <span class="c1">//EpollFd</span>
  <span class="k">static</span> <span class="k">const</span> <span class="kt">int</span> <span class="n">_EPOLL_SIZE</span> <span class="o">=</span> <span class="mi">1024</span> <span class="o">*</span> <span class="mi">10</span><span class="p">;</span>       <span class="c1">//max number of events returned by one epoll_wait call</span>
  <span class="k">struct</span> <span class="nc">stTimeout_t</span> <span class="o">*</span><span class="n">pTimeout</span><span class="p">;</span>                   <span class="c1">//timing wheel, timeout management</span>
  <span class="k">struct</span> <span class="nc">stTimeoutItemLink_t</span> <span class="o">*</span><span class="n">pstTimeoutList</span><span class="p">;</span>     <span class="c1">//list of timed-out items</span>
  <span class="k">struct</span> <span class="nc">stTimeoutItemLink_t</span> <span class="o">*</span><span class="n">pstActiveList</span><span class="p">;</span>      <span class="c1">//list of ready items  </span>
  <span class="n">co_epoll_res</span> <span class="o">*</span><span class="n">result</span><span class="p">;</span>                           <span class="c1">//epoll_wait result</span>
<span class="p">};</span>
</code></pre></div></div>

<p>The main event loop code is as follows,</p>

<p>$L11$ blocks on <code class="language-plaintext highlighter-rouge">epoll_wait</code> with a timeout of 1 millisecond. This <code class="language-plaintext highlighter-rouge">epoll_wait</code> is not hooked. It is the native system call.</p>

<p>$L13-L29$ takes out all events in <code class="language-plaintext highlighter-rouge">result-&gt;events</code>, runs the prepare function <code class="language-plaintext highlighter-rouge">pfnPrepare</code>, and adds them to the <code class="language-plaintext highlighter-rouge">active</code> list.</p>

<p>$L32-L42$ takes out all timeout events and adds them to the <code class="language-plaintext highlighter-rouge">active</code> list.</p>

<p>$L59$ calls the process function <code class="language-plaintext highlighter-rouge">pfnProcess</code> for every event in the <code class="language-plaintext highlighter-rouge">active</code> list.</p>

<p>$L66$ checks whether the event loop needs to exit:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* Event loop
 * @param 
 * ctx:epoll handle
 * pfn:function that checks whether to exit the event loop
 * arg:argument of pfn
 */</span>
<span class="kt">void</span> <span class="nf">co_eventloop</span><span class="p">(</span> <span class="n">stCoEpoll_t</span> <span class="o">*</span><span class="n">ctx</span><span class="p">,</span><span class="n">pfn_co_eventloop_t</span> <span class="n">pfn</span><span class="p">,</span><span class="kt">void</span> <span class="o">*</span><span class="n">arg</span> <span class="p">)</span>
<span class="p">{</span>
  <span class="k">if</span><span class="p">(</span> <span class="o">!</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">result</span> <span class="p">)</span>
  <span class="p">{</span>
    <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">result</span> <span class="o">=</span>  <span class="n">co_epoll_res_alloc</span><span class="p">(</span> <span class="n">stCoEpoll_t</span><span class="o">::</span><span class="n">_EPOLL_SIZE</span> <span class="p">);</span>
  <span class="p">}</span>
  <span class="n">co_epoll_res</span> <span class="o">*</span><span class="n">result</span> <span class="o">=</span> <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">result</span><span class="p">;</span> 

  <span class="k">for</span><span class="p">(;;)</span>
  <span class="p">{</span>
    <span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">co_epoll_wait</span><span class="p">(</span> <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">iEpollFd</span><span class="p">,</span><span class="n">result</span><span class="p">,</span><span class="n">stCoEpoll_t</span><span class="o">::</span><span class="n">_EPOLL_SIZE</span><span class="p">,</span> <span class="mi">1</span> <span class="p">);</span>

    <span class="n">stTimeoutItemLink_t</span> <span class="o">*</span><span class="n">active</span> <span class="o">=</span> <span class="p">(</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">pstActiveList</span><span class="p">);</span>
    <span class="n">stTimeoutItemLink_t</span> <span class="o">*</span><span class="n">timeout</span> <span class="o">=</span> <span class="p">(</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">pstTimeoutList</span><span class="p">);</span>

    <span class="n">memset</span><span class="p">(</span> <span class="n">timeout</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="k">sizeof</span><span class="p">(</span><span class="n">stTimeoutItemLink_t</span><span class="p">)</span> <span class="p">);</span>  <span class="c1">//clear the timeout queue</span>

    <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o">&lt;</span><span class="n">ret</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span>  <span class="c1">//walk the fds that have events</span>
    <span class="p">{</span>
      <span class="n">stTimeoutItem_t</span> <span class="o">*</span><span class="n">item</span> <span class="o">=</span> <span class="p">(</span><span class="n">stTimeoutItem_t</span><span class="o">*</span><span class="p">)</span><span class="n">result</span><span class="o">-&gt;</span><span class="n">events</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">data</span><span class="p">.</span><span class="n">ptr</span><span class="p">;</span> <span class="c1">//get the stTimeoutItem_t that the event data points to</span>
      <span class="k">if</span><span class="p">(</span> <span class="n">item</span><span class="o">-&gt;</span><span class="n">pfnPrepare</span> <span class="p">)</span>  <span class="c1">//if there is a prepare function, run it; it adds the item to the ready list</span>
      <span class="p">{</span>
        <span class="n">item</span><span class="o">-&gt;</span><span class="n">pfnPrepare</span><span class="p">(</span> <span class="n">item</span><span class="p">,</span><span class="n">result</span><span class="o">-&gt;</span><span class="n">events</span><span class="p">[</span><span class="n">i</span><span class="p">],</span><span class="n">active</span> <span class="p">);</span>
      <span class="p">}</span>
      <span class="k">else</span>  <span class="c1">//add to the ready list manually</span>
      <span class="p">{</span>
        <span class="n">AddTail</span><span class="p">(</span> <span class="n">active</span><span class="p">,</span><span class="n">item</span> <span class="p">);</span>
      <span class="p">}</span>
    <span class="p">}</span>

    <span class="kt">unsigned</span> <span class="kt">long</span> <span class="kt">long</span> <span class="n">now</span> <span class="o">=</span> <span class="n">GetTickMS</span><span class="p">();</span>
    <span class="n">TakeAllTimeout</span><span class="p">(</span> <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">pTimeout</span><span class="p">,</span><span class="n">now</span><span class="p">,</span><span class="n">timeout</span> <span class="p">);</span>  <span class="c1">//insert the timed-out items into the timeout list</span>

    <span class="n">stTimeoutItem_t</span> <span class="o">*</span><span class="n">lp</span> <span class="o">=</span> <span class="n">timeout</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">;</span>
    <span class="k">while</span><span class="p">(</span> <span class="n">lp</span> <span class="p">)</span>
    <span class="p">{</span>
      <span class="c1">//printf("raise timeout %p\n",lp);</span>
      <span class="n">lp</span><span class="o">-&gt;</span><span class="n">bTimeout</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>  <span class="c1">//mark as timed out</span>
      <span class="n">lp</span> <span class="o">=</span> <span class="n">lp</span><span class="o">-&gt;</span><span class="n">pNext</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">Join</span><span class="o">&lt;</span><span class="n">stTimeoutItem_t</span><span class="p">,</span><span class="n">stTimeoutItemLink_t</span><span class="o">&gt;</span><span class="p">(</span> <span class="n">active</span><span class="p">,</span><span class="n">timeout</span> <span class="p">);</span>  <span class="c1">//merge the timeout list into the ready list</span>

    <span class="n">lp</span> <span class="o">=</span> <span class="n">active</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">;</span>
    <span class="k">while</span><span class="p">(</span> <span class="n">lp</span> <span class="p">)</span>
    <span class="p">{</span>

      <span class="n">PopHead</span><span class="o">&lt;</span><span class="n">stTimeoutItem_t</span><span class="p">,</span><span class="n">stTimeoutItemLink_t</span><span class="o">&gt;</span><span class="p">(</span> <span class="n">active</span> <span class="p">);</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">lp</span><span class="o">-&gt;</span><span class="n">bTimeout</span> <span class="o">&amp;&amp;</span> <span class="n">now</span> <span class="o">&lt;</span> <span class="n">lp</span><span class="o">-&gt;</span><span class="n">ullExpireTime</span><span class="p">)</span>  <span class="c1">//marked as timed out but the expire time is not reached yet, add it back to the timing wheel </span>
      <span class="p">{</span>
        <span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">AddTimeout</span><span class="p">(</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">pTimeout</span><span class="p">,</span> <span class="n">lp</span><span class="p">,</span> <span class="n">now</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ret</span><span class="p">)</span> 
        <span class="p">{</span>
          <span class="n">lp</span><span class="o">-&gt;</span><span class="n">bTimeout</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
          <span class="n">lp</span> <span class="o">=</span> <span class="n">active</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">;</span>
          <span class="k">continue</span><span class="p">;</span>
        <span class="p">}</span>
      <span class="p">}</span>
      <span class="k">if</span><span class="p">(</span> <span class="n">lp</span><span class="o">-&gt;</span><span class="n">pfnProcess</span> <span class="p">)</span>  <span class="c1">//call the process function of the stTimeoutItem_t item</span>
      <span class="p">{</span>
        <span class="n">lp</span><span class="o">-&gt;</span><span class="n">pfnProcess</span><span class="p">(</span> <span class="n">lp</span> <span class="p">);</span>
      <span class="p">}</span>

      <span class="n">lp</span> <span class="o">=</span> <span class="n">active</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if</span><span class="p">(</span> <span class="n">pfn</span> <span class="p">)</span>  <span class="c1">//lets the user break out of the event loop</span>
    <span class="p">{</span>
      <span class="k">if</span><span class="p">(</span> <span class="o">-</span><span class="mi">1</span> <span class="o">==</span> <span class="n">pfn</span><span class="p">(</span> <span class="n">arg</span> <span class="p">)</span> <span class="p">)</span>
      <span class="p">{</span>
        <span class="k">break</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="the-hooked-poll">The Hooked <code class="language-plaintext highlighter-rouge">poll</code></h2>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">pollfd</span> <span class="p">{</span>
  <span class="kt">int</span>   <span class="n">fd</span><span class="p">;</span>         <span class="cm">/* file descriptor */</span>
  <span class="kt">short</span> <span class="n">events</span><span class="p">;</span>     <span class="cm">/* requested events */</span>
  <span class="kt">short</span> <span class="n">revents</span><span class="p">;</span>    <span class="cm">/* returned events */</span>
<span class="p">};</span>
<span class="kt">int</span> <span class="n">poll</span><span class="p">(</span><span class="k">struct</span> <span class="nc">pollfd</span> <span class="o">*</span><span class="n">fds</span><span class="p">,</span> <span class="n">nfds_t</span> <span class="n">nfds</span><span class="p">,</span> <span class="kt">int</span> <span class="n">timeout</span><span class="p">);</span>
</code></pre></div></div>

<p>The original <code class="language-plaintext highlighter-rouge">poll</code> function takes the fds and the events of interest <code class="language-plaintext highlighter-rouge">events</code> as an array of <code class="language-plaintext highlighter-rouge">pollfd</code>. It returns the events that happened in <code class="language-plaintext highlighter-rouge">revents</code>. <code class="language-plaintext highlighter-rouge">poll</code> also supports a timeout in milliseconds. When <code class="language-plaintext highlighter-rouge">timeout</code> is set to a non-zero value (a negative value means forever), the thread blocks in poll until a matching event happens or the timeout expires.</p>

<p>The hooked poll in libco can give up the context when an IO coroutine blocks, and switch to the main coroutine. Most of its code merges and restores the events of identical fds in the <code class="language-plaintext highlighter-rouge">pollfd</code> array passed in. The core part of the code is <code class="language-plaintext highlighter-rouge">co_poll_inner</code>, shown below:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* poll core
 * @param 
 * ctx:epoll handle
 * fds:fd array
 * nfds:length of the fd array
 * timeout:timeout in ms
 * pollfunc:default poll 
 */</span>
<span class="kt">int</span> <span class="nf">co_poll_inner</span><span class="p">(</span> <span class="n">stCoEpoll_t</span> <span class="o">*</span><span class="n">ctx</span><span class="p">,</span><span class="k">struct</span> <span class="nc">pollfd</span> <span class="n">fds</span><span class="p">[],</span> <span class="n">nfds_t</span> <span class="n">nfds</span><span class="p">,</span> <span class="kt">int</span> <span class="n">timeout</span><span class="p">,</span> <span class="n">poll_pfn_t</span> <span class="n">pollfunc</span><span class="p">)</span>
<span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">timeout</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="c1">//poll: Specifying a timeout of zero causes poll() to return immediately, even if no file descriptors are ready.</span>
  <span class="p">{</span>
    <span class="k">return</span> <span class="n">pollfunc</span><span class="p">(</span><span class="n">fds</span><span class="p">,</span> <span class="n">nfds</span><span class="p">,</span> <span class="n">timeout</span><span class="p">);</span>  <span class="c1">//call the native system poll (the upper-level poll has already checked this, so it is not needed here)</span>
  <span class="p">}</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">timeout</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span>  <span class="c1">//poll: Specifying a negative value in timeout means an infinite timeout.</span>
  <span class="p">{</span>
    <span class="n">timeout</span> <span class="o">=</span> <span class="n">INT_MAX</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="kt">int</span> <span class="n">epfd</span> <span class="o">=</span> <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">iEpollFd</span><span class="p">;</span>
  <span class="n">stCoRoutine_t</span><span class="o">*</span> <span class="n">self</span> <span class="o">=</span> <span class="n">co_self</span><span class="p">();</span>

  <span class="c1">//1.struct change</span>
  <span class="n">stPoll_t</span><span class="o">&amp;</span> <span class="n">arg</span> <span class="o">=</span> <span class="o">*</span><span class="p">((</span><span class="n">stPoll_t</span><span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="n">stPoll_t</span><span class="p">)));</span> <span class="c1">//allocate a stPoll_t</span>
  <span class="n">memset</span><span class="p">(</span> <span class="o">&amp;</span><span class="n">arg</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="k">sizeof</span><span class="p">(</span><span class="n">arg</span><span class="p">)</span> <span class="p">);</span>

  <span class="n">arg</span><span class="p">.</span><span class="n">iEpollFd</span> <span class="o">=</span> <span class="n">epfd</span><span class="p">;</span>  <span class="c1">//link stPoll_t with stCoEpoll_t here</span>
  <span class="n">arg</span><span class="p">.</span><span class="n">fds</span> <span class="o">=</span> <span class="p">(</span><span class="n">pollfd</span><span class="o">*</span><span class="p">)</span><span class="n">calloc</span><span class="p">(</span><span class="n">nfds</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">pollfd</span><span class="p">));</span>  <span class="c1">//allocate nfds pollfd</span>
  <span class="n">arg</span><span class="p">.</span><span class="n">nfds</span> <span class="o">=</span> <span class="n">nfds</span><span class="p">;</span>

  <span class="n">stPollItem_t</span> <span class="n">arr</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span>
  <span class="k">if</span><span class="p">(</span> <span class="n">nfds</span> <span class="o">&lt;</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">arr</span><span class="p">)</span> <span class="o">/</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">arr</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">self</span><span class="o">-&gt;</span><span class="n">cIsShareStack</span><span class="p">)</span>  <span class="c1">//when nfds is less than 2 and the shared stack is not used</span>
  <span class="p">{</span>
    <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span> <span class="o">=</span> <span class="n">arr</span><span class="p">;</span>
  <span class="p">}</span> 
  <span class="k">else</span>
  <span class="p">{</span>
    <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span> <span class="o">=</span> <span class="p">(</span><span class="n">stPollItem_t</span><span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span> <span class="n">nfds</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span> <span class="n">stPollItem_t</span> <span class="p">)</span> <span class="p">);</span>
  <span class="p">}</span>
  <span class="n">memset</span><span class="p">(</span> <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">nfds</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">stPollItem_t</span><span class="p">)</span> <span class="p">);</span>

  <span class="n">arg</span><span class="p">.</span><span class="n">pfnProcess</span> <span class="o">=</span> <span class="n">OnPollProcessEvent</span><span class="p">;</span>  <span class="c1">//process function, calls co_resume(arg.pArg) to wake up the coroutine that arg.pArg points to</span>
  <span class="n">arg</span><span class="p">.</span><span class="n">pArg</span> <span class="o">=</span> <span class="n">GetCurrCo</span><span class="p">(</span> <span class="n">co_get_curr_thread_env</span><span class="p">()</span> <span class="p">);</span> <span class="c1">//argument of the process function, i.e. the current coroutine</span>
  
  
  <span class="c1">//2. add epoll</span>
  <span class="k">for</span><span class="p">(</span><span class="n">nfds_t</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o">&lt;</span><span class="n">nfds</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span>
  <span class="p">{</span>
    <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">pSelf</span> <span class="o">=</span> <span class="n">arg</span><span class="p">.</span><span class="n">fds</span> <span class="o">+</span> <span class="n">i</span><span class="p">;</span>  <span class="c1">//link stPollItem_t with pollfd</span>
    <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">pPoll</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">arg</span><span class="p">;</span> <span class="c1">//point to the stPoll_t it belongs to</span>

    <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">pfnPrepare</span> <span class="o">=</span> <span class="n">OnPollPreparePfn</span><span class="p">;</span>  <span class="c1">//set the prepare function</span>
    <span class="k">struct</span> <span class="nc">epoll_event</span> <span class="o">&amp;</span><span class="n">ev</span> <span class="o">=</span> <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">stEvent</span><span class="p">;</span>

    <span class="k">if</span><span class="p">(</span> <span class="n">fds</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">fd</span> <span class="o">&gt;</span> <span class="o">-</span><span class="mi">1</span> <span class="p">)</span>  <span class="c1">//fd is valid</span>
    <span class="p">{</span>
      <span class="n">ev</span><span class="p">.</span><span class="n">data</span><span class="p">.</span><span class="n">ptr</span> <span class="o">=</span> <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span> <span class="o">+</span> <span class="n">i</span><span class="p">;</span> <span class="c1">//make stPollItem_t.stEvent.data.ptr point to stPollItem_t</span>
      <span class="n">ev</span><span class="p">.</span><span class="n">events</span> <span class="o">=</span> <span class="n">PollEvent2Epoll</span><span class="p">(</span> <span class="n">fds</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">events</span> <span class="p">);</span> <span class="c1">//set stPollItem_t.stEvent.data.events</span>

      <span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">co_epoll_ctl</span><span class="p">(</span> <span class="n">epfd</span><span class="p">,</span><span class="n">EPOLL_CTL_ADD</span><span class="p">,</span> <span class="n">fds</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">fd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ev</span> <span class="p">);</span> <span class="c1">//add stPollItem_t.stEvent to stCoEpoll_t.iEpollFd</span>
      <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">errno</span> <span class="o">==</span> <span class="n">EPERM</span> <span class="o">&amp;&amp;</span> <span class="n">nfds</span> <span class="o">==</span> <span class="mi">1</span> <span class="o">&amp;&amp;</span> <span class="n">pollfunc</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="c1">//when nfds is 1 and adding to epoll fails, free the temporary stPoll_t</span>
      <span class="p">{</span>
        <span class="k">if</span><span class="p">(</span> <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span> <span class="o">!=</span> <span class="n">arr</span> <span class="p">)</span>
        <span class="p">{</span>
          <span class="n">free</span><span class="p">(</span> <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span> <span class="p">);</span>
          <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="n">free</span><span class="p">(</span><span class="n">arg</span><span class="p">.</span><span class="n">fds</span><span class="p">);</span>
        <span class="n">free</span><span class="p">(</span><span class="o">&amp;</span><span class="n">arg</span><span class="p">);</span>
        <span class="k">return</span> <span class="n">pollfunc</span><span class="p">(</span><span class="n">fds</span><span class="p">,</span> <span class="n">nfds</span><span class="p">,</span> <span class="n">timeout</span><span class="p">);</span>  <span class="c1">//run the native poll</span>
      <span class="p">}</span>
    <span class="p">}</span>
    <span class="c1">//if fail,the timeout would work</span>
  <span class="p">}</span>

  <span class="c1">//3.add timeout</span>

  <span class="kt">unsigned</span> <span class="kt">long</span> <span class="kt">long</span> <span class="n">now</span> <span class="o">=</span> <span class="n">GetTickMS</span><span class="p">();</span>
  <span class="n">arg</span><span class="p">.</span><span class="n">ullExpireTime</span> <span class="o">=</span> <span class="n">now</span> <span class="o">+</span> <span class="n">timeout</span><span class="p">;</span>
  <span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">AddTimeout</span><span class="p">(</span> <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">pTimeout</span><span class="p">,</span><span class="o">&amp;</span><span class="n">arg</span><span class="p">,</span><span class="n">now</span> <span class="p">);</span> <span class="c1">//add stPoll_t to the timing wheel of stCoEpoll_t</span>
  <span class="kt">int</span> <span class="n">iRaiseCnt</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
  <span class="k">if</span><span class="p">(</span> <span class="n">ret</span> <span class="o">!=</span> <span class="mi">0</span> <span class="p">)</span>
  <span class="p">{</span>
    <span class="n">co_log_err</span><span class="p">(</span><span class="s">"CO_ERR: AddTimeout ret %d now %lld timeout %d arg.ullExpireTime %lld"</span><span class="p">,</span>
        <span class="n">ret</span><span class="p">,</span><span class="n">now</span><span class="p">,</span><span class="n">timeout</span><span class="p">,</span><span class="n">arg</span><span class="p">.</span><span class="n">ullExpireTime</span><span class="p">);</span>
    <span class="n">errno</span> <span class="o">=</span> <span class="n">EINVAL</span><span class="p">;</span>
    <span class="n">iRaiseCnt</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>

  <span class="p">}</span>
    <span class="k">else</span>
  <span class="p">{</span>
    <span class="n">co_yield_env</span><span class="p">(</span> <span class="n">co_get_curr_thread_env</span><span class="p">()</span> <span class="p">);</span> <span class="c1">//give up the CPU and wait for an event in epoll or a timeout</span>
    <span class="n">iRaiseCnt</span> <span class="o">=</span> <span class="n">arg</span><span class="p">.</span><span class="n">iRaiseCnt</span><span class="p">;</span>  <span class="c1">//we are back; before coming back, OnPollPreparePfn has already set iRaiseCnt and revents on stPoll_t, and removed it from the timing wheel </span>
  <span class="p">}</span>

    <span class="p">{</span>
    <span class="c1">//clear epoll status and memory</span>
    <span class="n">RemoveFromLink</span><span class="o">&lt;</span><span class="n">stTimeoutItem_t</span><span class="p">,</span><span class="n">stTimeoutItemLink_t</span><span class="o">&gt;</span><span class="p">(</span> <span class="o">&amp;</span><span class="n">arg</span> <span class="p">);</span>  <span class="c1">//remove from the timing wheel</span>
    <span class="k">for</span><span class="p">(</span><span class="n">nfds_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span><span class="n">i</span> <span class="o">&lt;</span> <span class="n">nfds</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span>
    <span class="p">{</span>
      <span class="kt">int</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">fds</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">fd</span><span class="p">;</span>
      <span class="k">if</span><span class="p">(</span> <span class="n">fd</span> <span class="o">&gt;</span> <span class="o">-</span><span class="mi">1</span> <span class="p">)</span>
      <span class="p">{</span>
        <span class="n">co_epoll_ctl</span><span class="p">(</span> <span class="n">epfd</span><span class="p">,</span><span class="n">EPOLL_CTL_DEL</span><span class="p">,</span><span class="n">fd</span><span class="p">,</span><span class="o">&amp;</span><span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">stEvent</span> <span class="p">);</span> <span class="c1">//remove from epoll</span>
      <span class="p">}</span>
      <span class="n">fds</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">revents</span> <span class="o">=</span> <span class="n">arg</span><span class="p">.</span><span class="n">fds</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">revents</span><span class="p">;</span>  <span class="c1">//return the events that have fired</span>
    <span class="p">}</span>


    <span class="k">if</span><span class="p">(</span> <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span> <span class="o">!=</span> <span class="n">arr</span> <span class="p">)</span> <span class="c1">//free stPoll_t</span>
    <span class="p">{</span>
      <span class="n">free</span><span class="p">(</span> <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span> <span class="p">);</span>
      <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">free</span><span class="p">(</span><span class="n">arg</span><span class="p">.</span><span class="n">fds</span><span class="p">);</span>
    <span class="n">free</span><span class="p">(</span><span class="o">&amp;</span><span class="n">arg</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="k">return</span> <span class="n">iRaiseCnt</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>As the figure below shows, the first half of the code, $L1-L90$, wraps the fds in <code class="language-plaintext highlighter-rouge">stPoll_t</code> and adds it to the timing wheel timeout manager.</p>

<p>$L91$ gives up the CPU and switches out of the current coroutine.</p>

<p>After $L92$, once the event loop fires, it calls the <code class="language-plaintext highlighter-rouge">OnPollProcessEvent</code> set at <code class="language-plaintext highlighter-rouge">L41</code> to switch to the matching coroutine, and then does the cleanup:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">OnPollProcessEvent</span><span class="p">(</span> <span class="n">stTimeoutItem_t</span> <span class="o">*</span> <span class="n">ap</span> <span class="p">)</span>
<span class="p">{</span>
  <span class="n">stCoRoutine_t</span> <span class="o">*</span><span class="n">co</span> <span class="o">=</span> <span class="p">(</span><span class="n">stCoRoutine_t</span><span class="o">*</span><span class="p">)</span><span class="n">ap</span><span class="o">-&gt;</span><span class="n">pArg</span><span class="p">;</span>
  <span class="n">co_resume</span><span class="p">(</span> <span class="n">co</span> <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><img src="/assets/images/libco-auto/stCoEpoll_t.png" alt="Figure 2. Structures related to poll" width="90%" />
<em>Figure 2. Structures related to poll</em></p>

<h2 id="conclusion">Conclusion</h2>

<p>This post covered the parts of the libco source code that handle automatic switching. Thank you for reading. If you have any questions or thoughts, or find any mistake in this post, please <a href="mailto:changliu0828@gmail.com">let me know</a>.</p>

<h2 id="references">References</h2>

<ol>
  <li><a href="https://blog.csdn.net/weixin_43705457/article/details/106863859">libco source code analysis, csdn (in Chinese)</a></li>
  <li><a href="http://purecpp.org/purecpp/static/64a819e99584452aab70a7f9c307717f.pdf">libco talk, Li Fangyuan (in Chinese)</a></li>
</ol>]]></content><author><name></name></author><summary type="html"><![CDATA[libco Source Notes (3): Automatic Switching]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://changliu.me/assets/images/social-card.png" /><media:content medium="image" url="https://changliu.me/assets/images/social-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="zh"><title type="html">libco源码笔记(3)自动切换</title><link href="https://changliu.me/post/libco-auto/" rel="alternate" type="text/html" title="libco源码笔记(3)自动切换" /><published>2020-09-23T00:00:00+00:00</published><updated>2020-09-23T00:00:00+00:00</updated><id>https://changliu.me/post/libco-auto</id><content type="html" xml:base="https://changliu.me/post/libco-auto/"><![CDATA[<h1 id="libco源码笔记3自动切换">libco源码笔记(3)自动切换</h1>

<p>在之前的文章libco源码笔记(2)显式切换中，我们介绍了libco提供的显式协程切换接口，并讨论了协程池的使用。本文讨论libco提供的自动切换相关函数接口。建议配合我自己的<a href="https://github.com/changliu0828/libco">注释版本</a>阅读本文。</p>

<h2 id="自动切换的背景">自动切换的背景</h2>

<p>李方源的libco分享$^{[2]}$中讲到，使用libco之前，微信大多数的网络通信使用同步IO接口。为了快速改造现有业务代码，libco以hook系统调用的形式，提供了协程基础上的<code class="language-plaintext highlighter-rouge">poll</code>，<code class="language-plaintext highlighter-rouge">read</code>，<code class="language-plaintext highlighter-rouge">write</code>等原语。利用协程的特性，原来阻塞的系统调用可以达到非阻塞的效果。</p>

<h2 id="超时管理">超时管理</h2>

<p>libco为了统一管理网络IO、条件变量等需要超时管理的事件，实现了基于时间轮(timing wheel)的超时管理器。在介绍其对系统调用的hook前，让我们先铺垫一些关于这个超时管理器的实现。</p>

<p>如下图1所示，时间轮为图中深红色的轮状数组，数组的每一个单元我们称为一个槽(slot)。单个slot里存储一定时间内注册的事件列表（图中黄色链表）。在libco中，单个slot的精度为1毫秒，整个时间轮由60000个slot组成，对应的整个时间轮覆盖60秒的时间。libco中关于时间轮的接口函数主要是下面两个。</p>

<p><code class="language-plaintext highlighter-rouge">AddTimeout</code>通过计算当前时间<code class="language-plaintext highlighter-rouge">allNow</code>与时间轮起始时间<code class="language-plaintext highlighter-rouge">ullStart</code>的差，插入对应slot。特别注意的是当超时事件大于轮长60秒时，libco将这种事件插入到“最后一个”slot。</p>

<p><code class="language-plaintext highlighter-rouge">TakeAllTimeout</code>通过计算当前时间<code class="language-plaintext highlighter-rouge">allNow</code>与时间轮起始时间<code class="language-plaintext highlighter-rouge">ullStart</code>的差，得出对应slot，遍历从起始索引<code class="language-plaintext highlighter-rouge">ullStartIdx</code>所指slot到该slot之间的所有slot中的超时项并移动到结果链表<code class="language-plaintext highlighter-rouge">apResult</code>中。</p>

<p>至此，我们看到通过时间轮，libco得以高效地完成对超时事件的管理。</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* 在时间轮中插入新项
 * @param 
 * apTimeout :时间轮结构
 * apItem    :新的超时项
 * allNow    :当前时间(timestamp in ms)
 * @return   :0成功, else失败行数
 */</span>
<span class="kt">int</span> <span class="nf">AddTimeout</span><span class="p">(</span> <span class="n">stTimeout_t</span> <span class="o">*</span><span class="n">apTimeout</span><span class="p">,</span><span class="n">stTimeoutItem_t</span> <span class="o">*</span><span class="n">apItem</span> <span class="p">,</span><span class="kt">unsigned</span> <span class="kt">long</span> <span class="kt">long</span> <span class="n">allNow</span> <span class="p">);</span>
<span class="cm">/* 在时间轮中取出所有超时项
 * @param 
 * apTimeout:时间轮结构
 * allNow   :当前时间(timestamp in ms)
 * apResult :超时事件结果链表
 */</span>
<span class="kr">inline</span> <span class="kt">void</span> <span class="nf">TakeAllTimeout</span><span class="p">(</span> <span class="n">stTimeout_t</span> <span class="o">*</span><span class="n">apTimeout</span><span class="p">,</span><span class="kt">unsigned</span> <span class="kt">long</span> <span class="kt">long</span> <span class="n">allNow</span><span class="p">,</span><span class="n">stTimeoutItemLink_t</span> <span class="o">*</span><span class="n">apResult</span> <span class="p">);</span>
</code></pre></div></div>

<p><img src="/assets/images/libco-auto/timing-wheel.png" alt="图1. 超时管理" width="100%" />
<em>图1. 超时管理</em></p>

<h2 id="事件循环">事件循环</h2>

<p>libco通过epoll管理IO事件，通过<code class="language-plaintext highlighter-rouge">co_eventloop</code>触发IO事件，并切换至对应协程执行。我们回顾之前提到过的、表示协程运行环境的线程私有全局变量<code class="language-plaintext highlighter-rouge">stCoRoutineEnv_t</code>，其中持有epoll的结构体句柄<code class="language-plaintext highlighter-rouge">pEpoll</code>。</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">stCoRoutineEnv_t</span>
<span class="p">{</span>
  <span class="n">stCoRoutine_t</span> <span class="o">*</span><span class="n">pCallStack</span><span class="p">[</span> <span class="mi">128</span> <span class="p">];</span>   <span class="c1">//所有协程的调用栈</span>
  <span class="kt">int</span> <span class="n">iCallStackSize</span><span class="p">;</span>                 <span class="c1">//pCallStack栈顶索引</span>
  <span class="n">stCoEpoll_t</span> <span class="o">*</span><span class="n">pEpoll</span><span class="p">;</span>                <span class="c1">//epoll封装</span>

  <span class="c1">//for copy stack log lastco and nextco</span>
  <span class="n">stCoRoutine_t</span><span class="o">*</span> <span class="n">pending_co</span><span class="p">;</span>           
  <span class="n">stCoRoutine_t</span><span class="o">*</span> <span class="n">occupy_co</span><span class="p">;</span>           <span class="c1">//当前协程</span>
<span class="p">};</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">stCoEpoll_t</code>的定义如下，</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">stCoEpoll_t</span>
<span class="p">{</span>
  <span class="kt">int</span> <span class="n">iEpollFd</span><span class="p">;</span>                                   <span class="c1">//EpollFd</span>
  <span class="k">static</span> <span class="k">const</span> <span class="kt">int</span> <span class="n">_EPOLL_SIZE</span> <span class="o">=</span> <span class="mi">1024</span> <span class="o">*</span> <span class="mi">10</span><span class="p">;</span>       <span class="c1">//epoll_wait单次最大返回事件数量</span>
  <span class="k">struct</span> <span class="nc">stTimeout_t</span> <span class="o">*</span><span class="n">pTimeout</span><span class="p">;</span>                   <span class="c1">//时间轮, 超时管理</span>
  <span class="k">struct</span> <span class="nc">stTimeoutItemLink_t</span> <span class="o">*</span><span class="n">pstTimeoutList</span><span class="p">;</span>     <span class="c1">//已超时项链表</span>
  <span class="k">struct</span> <span class="nc">stTimeoutItemLink_t</span> <span class="o">*</span><span class="n">pstActiveList</span><span class="p">;</span>      <span class="c1">//已就绪项链表  </span>
  <span class="n">co_epoll_res</span> <span class="o">*</span><span class="n">result</span><span class="p">;</span>                           <span class="c1">//epoll_wait结果</span>
<span class="p">};</span>
</code></pre></div></div>

<p>主事件循环代码如下，</p>

<p>$L11$阻塞在<code class="language-plaintext highlighter-rouge">epoll_wait</code>上，并设置超时时间为1毫秒。这里的<code class="language-plaintext highlighter-rouge">epoll_wait</code>并没有经过hook，是系统原生的。</p>

<p>$L13-L29$取出所有的<code class="language-plaintext highlighter-rouge">result-&gt;events</code>的事件，执行预处理函数<code class="language-plaintext highlighter-rouge">pfnPrepare</code>，并加入<code class="language-plaintext highlighter-rouge">active</code>链表。</p>

<p>$L32-L42$取出所有超时事件并加入<code class="language-plaintext highlighter-rouge">active</code>链表。</p>

<p>$L59$对所有的<code class="language-plaintext highlighter-rouge">active</code>链表中事件调用处理函数<code class="language-plaintext highlighter-rouge">pfnProcess</code>。</p>

<p>$L66$检查是否需要退出事件循环。</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* 事件循环
 * @param 
 * ctx:epoll句柄
 * pfn:退出事件循环检查函数
 * arg:pfn参数
 */</span>
<span class="kt">void</span> <span class="nf">co_eventloop</span><span class="p">(</span> <span class="n">stCoEpoll_t</span> <span class="o">*</span><span class="n">ctx</span><span class="p">,</span><span class="n">pfn_co_eventloop_t</span> <span class="n">pfn</span><span class="p">,</span><span class="kt">void</span> <span class="o">*</span><span class="n">arg</span> <span class="p">)</span>
<span class="p">{</span>
  <span class="k">if</span><span class="p">(</span> <span class="o">!</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">result</span> <span class="p">)</span>
  <span class="p">{</span>
    <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">result</span> <span class="o">=</span>  <span class="n">co_epoll_res_alloc</span><span class="p">(</span> <span class="n">stCoEpoll_t</span><span class="o">::</span><span class="n">_EPOLL_SIZE</span> <span class="p">);</span>
  <span class="p">}</span>
  <span class="n">co_epoll_res</span> <span class="o">*</span><span class="n">result</span> <span class="o">=</span> <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">result</span><span class="p">;</span> 

  <span class="k">for</span><span class="p">(;;)</span>
  <span class="p">{</span>
    <span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">co_epoll_wait</span><span class="p">(</span> <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">iEpollFd</span><span class="p">,</span><span class="n">result</span><span class="p">,</span><span class="n">stCoEpoll_t</span><span class="o">::</span><span class="n">_EPOLL_SIZE</span><span class="p">,</span> <span class="mi">1</span> <span class="p">);</span>

    <span class="n">stTimeoutItemLink_t</span> <span class="o">*</span><span class="n">active</span> <span class="o">=</span> <span class="p">(</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">pstActiveList</span><span class="p">);</span>
    <span class="n">stTimeoutItemLink_t</span> <span class="o">*</span><span class="n">timeout</span> <span class="o">=</span> <span class="p">(</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">pstTimeoutList</span><span class="p">);</span>

    <span class="n">memset</span><span class="p">(</span> <span class="n">timeout</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="k">sizeof</span><span class="p">(</span><span class="n">stTimeoutItemLink_t</span><span class="p">)</span> <span class="p">);</span>  <span class="c1">//清空超时队列</span>

    <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o">&lt;</span><span class="n">ret</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span>  <span class="c1">//遍历有事件的fd</span>
    <span class="p">{</span>
      <span class="n">stTimeoutItem_t</span> <span class="o">*</span><span class="n">item</span> <span class="o">=</span> <span class="p">(</span><span class="n">stTimeoutItem_t</span><span class="o">*</span><span class="p">)</span><span class="n">result</span><span class="o">-&gt;</span><span class="n">events</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">data</span><span class="p">.</span><span class="n">ptr</span><span class="p">;</span> <span class="c1">//获取event里数据指向的stTimeoutItem_t</span>
      <span class="k">if</span><span class="p">(</span> <span class="n">item</span><span class="o">-&gt;</span><span class="n">pfnPrepare</span> <span class="p">)</span>  <span class="c1">//如果有预处理函数，执行，由其加入就绪列表</span>
      <span class="p">{</span>
        <span class="n">item</span><span class="o">-&gt;</span><span class="n">pfnPrepare</span><span class="p">(</span> <span class="n">item</span><span class="p">,</span><span class="n">result</span><span class="o">-&gt;</span><span class="n">events</span><span class="p">[</span><span class="n">i</span><span class="p">],</span><span class="n">active</span> <span class="p">);</span>
      <span class="p">}</span>
      <span class="k">else</span>  <span class="c1">//手动加入就绪列表</span>
      <span class="p">{</span>
        <span class="n">AddTail</span><span class="p">(</span> <span class="n">active</span><span class="p">,</span><span class="n">item</span> <span class="p">);</span>
      <span class="p">}</span>
    <span class="p">}</span>

    <span class="kt">unsigned</span> <span class="kt">long</span> <span class="kt">long</span> <span class="n">now</span> <span class="o">=</span> <span class="n">GetTickMS</span><span class="p">();</span>
    <span class="n">TakeAllTimeout</span><span class="p">(</span> <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">pTimeout</span><span class="p">,</span><span class="n">now</span><span class="p">,</span><span class="n">timeout</span> <span class="p">);</span>  <span class="c1">//将超时项插入超时列表</span>

    <span class="n">stTimeoutItem_t</span> <span class="o">*</span><span class="n">lp</span> <span class="o">=</span> <span class="n">timeout</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">;</span>
    <span class="k">while</span><span class="p">(</span> <span class="n">lp</span> <span class="p">)</span>
    <span class="p">{</span>
      <span class="c1">//printf("raise timeout %p\n",lp);</span>
      <span class="n">lp</span><span class="o">-&gt;</span><span class="n">bTimeout</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>  <span class="c1">//设置为超时</span>
      <span class="n">lp</span> <span class="o">=</span> <span class="n">lp</span><span class="o">-&gt;</span><span class="n">pNext</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">Join</span><span class="o">&lt;</span><span class="n">stTimeoutItem_t</span><span class="p">,</span><span class="n">stTimeoutItemLink_t</span><span class="o">&gt;</span><span class="p">(</span> <span class="n">active</span><span class="p">,</span><span class="n">timeout</span> <span class="p">);</span>  <span class="c1">//将超时列表合并入就绪列表</span>

    <span class="n">lp</span> <span class="o">=</span> <span class="n">active</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">;</span>
    <span class="k">while</span><span class="p">(</span> <span class="n">lp</span> <span class="p">)</span>
    <span class="p">{</span>

      <span class="n">PopHead</span><span class="o">&lt;</span><span class="n">stTimeoutItem_t</span><span class="p">,</span><span class="n">stTimeoutItemLink_t</span><span class="o">&gt;</span><span class="p">(</span> <span class="n">active</span> <span class="p">);</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">lp</span><span class="o">-&gt;</span><span class="n">bTimeout</span> <span class="o">&amp;&amp;</span> <span class="n">now</span> <span class="o">&lt;</span> <span class="n">lp</span><span class="o">-&gt;</span><span class="n">ullExpireTime</span><span class="p">)</span>  <span class="c1">//还未达到超时时间但已经标记为超时的，加回时间轮 </span>
      <span class="p">{</span>
        <span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">AddTimeout</span><span class="p">(</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">pTimeout</span><span class="p">,</span> <span class="n">lp</span><span class="p">,</span> <span class="n">now</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ret</span><span class="p">)</span> 
        <span class="p">{</span>
          <span class="n">lp</span><span class="o">-&gt;</span><span class="n">bTimeout</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
          <span class="n">lp</span> <span class="o">=</span> <span class="n">active</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">;</span>
          <span class="k">continue</span><span class="p">;</span>
        <span class="p">}</span>
      <span class="p">}</span>
      <span class="k">if</span><span class="p">(</span> <span class="n">lp</span><span class="o">-&gt;</span><span class="n">pfnProcess</span> <span class="p">)</span>  <span class="c1">//调用stTimeoutItem_t项的执行函数</span>
      <span class="p">{</span>
        <span class="n">lp</span><span class="o">-&gt;</span><span class="n">pfnProcess</span><span class="p">(</span> <span class="n">lp</span> <span class="p">);</span>
      <span class="p">}</span>

      <span class="n">lp</span> <span class="o">=</span> <span class="n">active</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if</span><span class="p">(</span> <span class="n">pfn</span> <span class="p">)</span>  <span class="c1">//用于用户控制跳出事件循环</span>
    <span class="p">{</span>
      <span class="k">if</span><span class="p">(</span> <span class="o">-</span><span class="mi">1</span> <span class="o">==</span> <span class="n">pfn</span><span class="p">(</span> <span class="n">arg</span> <span class="p">)</span> <span class="p">)</span>
      <span class="p">{</span>
        <span class="k">break</span><span class="p">;</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="hook后的poll">hook后的<code class="language-plaintext highlighter-rouge">poll</code></h2>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">pollfd</span> <span class="p">{</span>
  <span class="kt">int</span>   <span class="n">fd</span><span class="p">;</span>         <span class="cm">/* file descriptor */</span>
  <span class="kt">short</span> <span class="n">events</span><span class="p">;</span>     <span class="cm">/* requested events */</span>
  <span class="kt">short</span> <span class="n">revents</span><span class="p">;</span>    <span class="cm">/* returned events */</span>
<span class="p">};</span>
<span class="kt">int</span> <span class="n">poll</span><span class="p">(</span><span class="k">struct</span> <span class="nc">pollfd</span> <span class="o">*</span><span class="n">fds</span><span class="p">,</span> <span class="n">nfds_t</span> <span class="n">nfds</span><span class="p">,</span> <span class="kt">int</span> <span class="n">timeout</span><span class="p">);</span>
</code></pre></div></div>

<p>原始的<code class="language-plaintext highlighter-rouge">poll</code>函数以<code class="language-plaintext highlighter-rouge">pollfd</code>数组的形式传入fd以及关注的事件<code class="language-plaintext highlighter-rouge">events</code>，并将发生的事件返回到<code class="language-plaintext highlighter-rouge">revents</code>中。此外<code class="language-plaintext highlighter-rouge">poll</code>支持毫秒级别的超时设置，当<code class="language-plaintext highlighter-rouge">timeout</code>设置为非0值时(负数为永久)，线程会阻塞于poll直到有对应事件发生或超时。</p>

<p>libco hook后的poll可以在IO协程阻塞时让出上下文，切换至主协程。其中，大部分代码是处理传入<code class="language-plaintext highlighter-rouge">pollfd</code>数组中相同fd的事件合并和还原。代码中的核心部分为<code class="language-plaintext highlighter-rouge">co_poll_inner</code>，其代码如下，</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* poll内核
 * @param 
 * ctx:epoll句柄
 * fds:fd数组
 * nfds:fd数组长度
 * timeout:超时时间ms
 * pollfunc:默认poll 
 */</span>
<span class="kt">int</span> <span class="nf">co_poll_inner</span><span class="p">(</span> <span class="n">stCoEpoll_t</span> <span class="o">*</span><span class="n">ctx</span><span class="p">,</span><span class="k">struct</span> <span class="nc">pollfd</span> <span class="n">fds</span><span class="p">[],</span> <span class="n">nfds_t</span> <span class="n">nfds</span><span class="p">,</span> <span class="kt">int</span> <span class="n">timeout</span><span class="p">,</span> <span class="n">poll_pfn_t</span> <span class="n">pollfunc</span><span class="p">)</span>
<span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">timeout</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="c1">//poll: Specifying a timeout of zero causes poll() to return immediately, even if no file descriptors are ready.</span>
  <span class="p">{</span>
    <span class="k">return</span> <span class="n">pollfunc</span><span class="p">(</span><span class="n">fds</span><span class="p">,</span> <span class="n">nfds</span><span class="p">,</span> <span class="n">timeout</span><span class="p">);</span>  <span class="c1">//调用系统原生poll(其实上层poll已经做过检查了，此处无需再做)</span>
  <span class="p">}</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">timeout</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span>  <span class="c1">//poll: Specifying a negative value in timeout means an infinite timeout.</span>
  <span class="p">{</span>
    <span class="n">timeout</span> <span class="o">=</span> <span class="n">INT_MAX</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="kt">int</span> <span class="n">epfd</span> <span class="o">=</span> <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">iEpollFd</span><span class="p">;</span>
  <span class="n">stCoRoutine_t</span><span class="o">*</span> <span class="n">self</span> <span class="o">=</span> <span class="n">co_self</span><span class="p">();</span>

  <span class="c1">//1.struct change</span>
  <span class="n">stPoll_t</span><span class="o">&amp;</span> <span class="n">arg</span> <span class="o">=</span> <span class="o">*</span><span class="p">((</span><span class="n">stPoll_t</span><span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="n">stPoll_t</span><span class="p">)));</span> <span class="c1">//分配一个stPoll_t</span>
  <span class="n">memset</span><span class="p">(</span> <span class="o">&amp;</span><span class="n">arg</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="k">sizeof</span><span class="p">(</span><span class="n">arg</span><span class="p">)</span> <span class="p">);</span>

  <span class="n">arg</span><span class="p">.</span><span class="n">iEpollFd</span> <span class="o">=</span> <span class="n">epfd</span><span class="p">;</span>  <span class="c1">//此处stPoll_t与stCoEpoll_t进行关联</span>
  <span class="n">arg</span><span class="p">.</span><span class="n">fds</span> <span class="o">=</span> <span class="p">(</span><span class="n">pollfd</span><span class="o">*</span><span class="p">)</span><span class="n">calloc</span><span class="p">(</span><span class="n">nfds</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">pollfd</span><span class="p">));</span>  <span class="c1">//分配nfds个pollfd</span>
  <span class="n">arg</span><span class="p">.</span><span class="n">nfds</span> <span class="o">=</span> <span class="n">nfds</span><span class="p">;</span>

  <span class="n">stPollItem_t</span> <span class="n">arr</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span>
  <span class="k">if</span><span class="p">(</span> <span class="n">nfds</span> <span class="o">&lt;</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">arr</span><span class="p">)</span> <span class="o">/</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">arr</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">self</span><span class="o">-&gt;</span><span class="n">cIsShareStack</span><span class="p">)</span>  <span class="c1">//nfds少于2且未使用共享栈的情况下</span>
  <span class="p">{</span>
    <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span> <span class="o">=</span> <span class="n">arr</span><span class="p">;</span>
  <span class="p">}</span> 
  <span class="k">else</span>
  <span class="p">{</span>
    <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span> <span class="o">=</span> <span class="p">(</span><span class="n">stPollItem_t</span><span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span> <span class="n">nfds</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span> <span class="n">stPollItem_t</span> <span class="p">)</span> <span class="p">);</span>
  <span class="p">}</span>
  <span class="n">memset</span><span class="p">(</span> <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">nfds</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">stPollItem_t</span><span class="p">)</span> <span class="p">);</span>

  <span class="n">arg</span><span class="p">.</span><span class="n">pfnProcess</span> <span class="o">=</span> <span class="n">OnPollProcessEvent</span><span class="p">;</span>  <span class="c1">//处理函数, 调用co_resume(arg.pArg), 唤醒参数arg.pArg所指协程</span>
  <span class="n">arg</span><span class="p">.</span><span class="n">pArg</span> <span class="o">=</span> <span class="n">GetCurrCo</span><span class="p">(</span> <span class="n">co_get_curr_thread_env</span><span class="p">()</span> <span class="p">);</span> <span class="c1">//处理函数参数, 即当前协程</span>
  
  
  <span class="c1">//2. add epoll</span>
  <span class="k">for</span><span class="p">(</span><span class="n">nfds_t</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o">&lt;</span><span class="n">nfds</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span>
  <span class="p">{</span>
    <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">pSelf</span> <span class="o">=</span> <span class="n">arg</span><span class="p">.</span><span class="n">fds</span> <span class="o">+</span> <span class="n">i</span><span class="p">;</span>  <span class="c1">//关联stPollItem_t与pollfd</span>
    <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">pPoll</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">arg</span><span class="p">;</span> <span class="c1">//指向所属stPoll_t</span>

    <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">pfnPrepare</span> <span class="o">=</span> <span class="n">OnPollPreparePfn</span><span class="p">;</span>  <span class="c1">//设置预处理</span>
    <span class="k">struct</span> <span class="nc">epoll_event</span> <span class="o">&amp;</span><span class="n">ev</span> <span class="o">=</span> <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">stEvent</span><span class="p">;</span>

    <span class="k">if</span><span class="p">(</span> <span class="n">fds</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">fd</span> <span class="o">&gt;</span> <span class="o">-</span><span class="mi">1</span> <span class="p">)</span>  <span class="c1">//fd有效</span>
    <span class="p">{</span>
      <span class="n">ev</span><span class="p">.</span><span class="n">data</span><span class="p">.</span><span class="n">ptr</span> <span class="o">=</span> <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span> <span class="o">+</span> <span class="n">i</span><span class="p">;</span> <span class="c1">//设置stPollItem_t.stEvent.data.ptr指向stPollItem_t</span>
      <span class="n">ev</span><span class="p">.</span><span class="n">events</span> <span class="o">=</span> <span class="n">PollEvent2Epoll</span><span class="p">(</span> <span class="n">fds</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">events</span> <span class="p">);</span> <span class="c1">//设置stPollItem_t.stEvent.data.events</span>

      <span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">co_epoll_ctl</span><span class="p">(</span> <span class="n">epfd</span><span class="p">,</span><span class="n">EPOLL_CTL_ADD</span><span class="p">,</span> <span class="n">fds</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">fd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ev</span> <span class="p">);</span> <span class="c1">//将stPollItem_t.stEvent加入stCoEpoll_t.iEpollFd中</span>
      <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">&lt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">errno</span> <span class="o">==</span> <span class="n">EPERM</span> <span class="o">&amp;&amp;</span> <span class="n">nfds</span> <span class="o">==</span> <span class="mi">1</span> <span class="o">&amp;&amp;</span> <span class="n">pollfunc</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="c1">//nfds只有一个时，插入epoll失败, 释放掉临时的stPoll_t</span>
      <span class="p">{</span>
        <span class="k">if</span><span class="p">(</span> <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span> <span class="o">!=</span> <span class="n">arr</span> <span class="p">)</span>
        <span class="p">{</span>
          <span class="n">free</span><span class="p">(</span> <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span> <span class="p">);</span>
          <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="n">free</span><span class="p">(</span><span class="n">arg</span><span class="p">.</span><span class="n">fds</span><span class="p">);</span>
        <span class="n">free</span><span class="p">(</span><span class="o">&amp;</span><span class="n">arg</span><span class="p">);</span>
        <span class="k">return</span> <span class="n">pollfunc</span><span class="p">(</span><span class="n">fds</span><span class="p">,</span> <span class="n">nfds</span><span class="p">,</span> <span class="n">timeout</span><span class="p">);</span>  <span class="c1">//执行原生poll</span>
      <span class="p">}</span>
    <span class="p">}</span>
    <span class="c1">//if fail,the timeout would work</span>
  <span class="p">}</span>

  <span class="c1">//3.add timeout</span>

  <span class="kt">unsigned</span> <span class="kt">long</span> <span class="kt">long</span> <span class="n">now</span> <span class="o">=</span> <span class="n">GetTickMS</span><span class="p">();</span>
  <span class="n">arg</span><span class="p">.</span><span class="n">ullExpireTime</span> <span class="o">=</span> <span class="n">now</span> <span class="o">+</span> <span class="n">timeout</span><span class="p">;</span>
  <span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">AddTimeout</span><span class="p">(</span> <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">pTimeout</span><span class="p">,</span><span class="o">&amp;</span><span class="n">arg</span><span class="p">,</span><span class="n">now</span> <span class="p">);</span> <span class="c1">//将stPoll_t加入stCoEpoll_t的时间轮</span>
  <span class="kt">int</span> <span class="n">iRaiseCnt</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
  <span class="k">if</span><span class="p">(</span> <span class="n">ret</span> <span class="o">!=</span> <span class="mi">0</span> <span class="p">)</span>
  <span class="p">{</span>
    <span class="n">co_log_err</span><span class="p">(</span><span class="s">"CO_ERR: AddTimeout ret %d now %lld timeout %d arg.ullExpireTime %lld"</span><span class="p">,</span>
        <span class="n">ret</span><span class="p">,</span><span class="n">now</span><span class="p">,</span><span class="n">timeout</span><span class="p">,</span><span class="n">arg</span><span class="p">.</span><span class="n">ullExpireTime</span><span class="p">);</span>
    <span class="n">errno</span> <span class="o">=</span> <span class="n">EINVAL</span><span class="p">;</span>
    <span class="n">iRaiseCnt</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>

  <span class="p">}</span>
    <span class="k">else</span>
  <span class="p">{</span>
    <span class="n">co_yield_env</span><span class="p">(</span> <span class="n">co_get_curr_thread_env</span><span class="p">()</span> <span class="p">);</span> <span class="c1">//让出CPU, 等待epoll中的事件发生或超时</span>
    <span class="n">iRaiseCnt</span> <span class="o">=</span> <span class="n">arg</span><span class="p">.</span><span class="n">iRaiseCnt</span><span class="p">;</span>  <span class="c1">//再次回来, 回来前会执行OnPollPreparePfn, 已经将stPoll_t设置好iRaiseCnt, revents, 并从时间轮中删除 </span>
  <span class="p">}</span>

    <span class="p">{</span>
    <span class="c1">//clear epoll status and memory</span>
    <span class="n">RemoveFromLink</span><span class="o">&lt;</span><span class="n">stTimeoutItem_t</span><span class="p">,</span><span class="n">stTimeoutItemLink_t</span><span class="o">&gt;</span><span class="p">(</span> <span class="o">&amp;</span><span class="n">arg</span> <span class="p">);</span>  <span class="c1">//从时间轮中删除</span>
    <span class="k">for</span><span class="p">(</span><span class="n">nfds_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span><span class="n">i</span> <span class="o">&lt;</span> <span class="n">nfds</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span>
    <span class="p">{</span>
      <span class="kt">int</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">fds</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">fd</span><span class="p">;</span>
      <span class="k">if</span><span class="p">(</span> <span class="n">fd</span> <span class="o">&gt;</span> <span class="o">-</span><span class="mi">1</span> <span class="p">)</span>
      <span class="p">{</span>
        <span class="n">co_epoll_ctl</span><span class="p">(</span> <span class="n">epfd</span><span class="p">,</span><span class="n">EPOLL_CTL_DEL</span><span class="p">,</span><span class="n">fd</span><span class="p">,</span><span class="o">&amp;</span><span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">stEvent</span> <span class="p">);</span> <span class="c1">//从epoll中删除</span>
      <span class="p">}</span>
      <span class="n">fds</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">revents</span> <span class="o">=</span> <span class="n">arg</span><span class="p">.</span><span class="n">fds</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">revents</span><span class="p">;</span>  <span class="c1">//返回已经触发的事件</span>
    <span class="p">}</span>


    <span class="k">if</span><span class="p">(</span> <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span> <span class="o">!=</span> <span class="n">arr</span> <span class="p">)</span> <span class="c1">//释放stPoll_t</span>
    <span class="p">{</span>
      <span class="n">free</span><span class="p">(</span> <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span> <span class="p">);</span>
      <span class="n">arg</span><span class="p">.</span><span class="n">pPollItems</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">free</span><span class="p">(</span><span class="n">arg</span><span class="p">.</span><span class="n">fds</span><span class="p">);</span>
    <span class="n">free</span><span class="p">(</span><span class="o">&amp;</span><span class="n">arg</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="k">return</span> <span class="n">iRaiseCnt</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>如下图所示，代码中前半部分$L1-L90$将fd封装为<code class="language-plaintext highlighter-rouge">stPoll_t</code>，并将其加入到时间轮超时管理器中。</p>

<p>$L91$让出CPU，切出当前协程。</p>

<p>$L92$之后是在事件循环触发后，调用<code class="language-plaintext highlighter-rouge">L41</code>中设置的<code class="language-plaintext highlighter-rouge">OnPollProcessEvent</code>切换至对应协程，然后进行对应的清理工作。</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">OnPollProcessEvent</span><span class="p">(</span> <span class="n">stTimeoutItem_t</span> <span class="o">*</span> <span class="n">ap</span> <span class="p">)</span>
<span class="p">{</span>
  <span class="n">stCoRoutine_t</span> <span class="o">*</span><span class="n">co</span> <span class="o">=</span> <span class="p">(</span><span class="n">stCoRoutine_t</span><span class="o">*</span><span class="p">)</span><span class="n">ap</span><span class="o">-&gt;</span><span class="n">pArg</span><span class="p">;</span>
  <span class="n">co_resume</span><span class="p">(</span> <span class="n">co</span> <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><img src="/assets/images/libco-auto/stCoEpoll_t.png" alt="图2. poll相关结构" width="90%" />
<em>图2. poll相关结构</em></p>

<h2 id="最后">最后</h2>

<p>至此，我们介绍了libco源码中自动切换的部分代码。感谢你的阅读。如果你有任何疑问和感想，或发现本文有任何错误，请一定<a href="mailto:changliu0828@gmail.com">让我知道</a>。</p>

<h2 id="参考">参考</h2>

<ol>
  <li><a href="https://blog.csdn.net/weixin_43705457/article/details/106863859">libco源码分析，csdn</a></li>
  <li><a href="http://purecpp.org/purecpp/static/64a819e99584452aab70a7f9c307717f.pdf">libco分享，李方源</a></li>
</ol>]]></content><author><name></name></author><summary type="html"><![CDATA[libco源码笔记(3)自动切换]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://changliu.me/assets/images/social-card.png" /><media:content medium="image" url="https://changliu.me/assets/images/social-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="en"><title type="html">libco Source Notes (2): Main Structures and Functions</title><link href="https://changliu.me/2020/09/22/libco-api.html" rel="alternate" type="text/html" title="libco Source Notes (2): Main Structures and Functions" /><published>2020-09-22T00:00:00+00:00</published><updated>2020-09-22T00:00:00+00:00</updated><id>https://changliu.me/2020/09/22/libco-api</id><content type="html" xml:base="https://changliu.me/2020/09/22/libco-api.html"><![CDATA[<h1 id="libco-source-notes-2-main-structures-and-functions">libco Source Notes (2): Main Structures and Functions</h1>

<p>In the previous post, <a href="/2020/09/11/libco-coroutine.html">libco Source Notes (1): Coroutines and Context Switching</a>, we covered the basic concept of coroutines and the core context-switching code in libco. This post uses an example to introduce several important function interfaces that libco provides. I suggest reading it together with my <a href="https://github.com/changliu0828/libco">annotated version</a>.</p>

<h2 id="main-libco-structures">Main libco Structures</h2>

<p>First, we look at the three core structures in libco. Figure 1 below shows how they relate to each other.</p>

<h3 id="coctx_t"><code class="language-plaintext highlighter-rouge">coctx_t</code></h3>

<p>It saves the context information needed when switching coroutines. For details, see <a href="/2020/09/11/libco-coroutine.html">libco Source Notes (1): Coroutines and Context Switching</a>. I do not repeat them here.</p>

<h3 id="stcoroutine_t"><code class="language-plaintext highlighter-rouge">stCoRoutine_t</code></h3>

<p>This is the main coroutine structure. It holds all the information about a single coroutine, such as its start/stop state, its function, its context, and its shared stack.</p>

<h3 id="stcoroutineenv_t"><code class="language-plaintext highlighter-rouge">stCoRoutineEnv_t</code></h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kr">__thread</span> <span class="n">stCoRoutineEnv_t</span><span class="o">*</span> <span class="n">gCoEnvPerThread</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>   <span class="c1">//Coroutine runtime environment. __thread: thread-private</span>
</code></pre></div></div>

<p>This is a thread-private global static variable. It holds the global coroutine environment, such as the coroutine call stack and the epoll handle. <code class="language-plaintext highlighter-rouge">pCallStack</code> is the coroutine call stack of the current thread. Because libco uses asymmetric coroutines</p>

<p><img src="/assets/images/libco-api/co-core-struct.svg" alt="Figure 1. libco core structures" width="100%" />
<em>Figure 1. libco core structures</em></p>

<h2 id="main-libco-interface-functions">Main libco Interface Functions</h2>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*  Coroutine creation interface
 *  @param
 *  co          :double pointer to the main coroutine structure
 *  attr        :configurable coroutine attributes, including stack size and shared stack address
 *  pfn         :function the coroutine calls
 *  arg         :argument of the function the coroutine calls
 *  @return     :0
 */</span>
<span class="kt">int</span> <span class="nf">co_create</span><span class="p">(</span> <span class="n">stCoRoutine_t</span> <span class="o">**</span><span class="n">ppco</span><span class="p">,</span><span class="k">const</span> <span class="n">stCoRoutineAttr_t</span> <span class="o">*</span><span class="n">attr</span><span class="p">,</span><span class="n">pfn_co_routine_t</span> <span class="n">pfn</span><span class="p">,</span><span class="kt">void</span> <span class="o">*</span><span class="n">arg</span> <span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span><span class="p">(</span> <span class="o">!</span><span class="n">co_get_curr_thread_env</span><span class="p">()</span> <span class="p">)</span> 
    <span class="p">{</span>
        <span class="n">co_init_curr_thread_env</span><span class="p">();</span>      <span class="c1">//Initialize the environment of this thread. Only the main coroutine calls this</span>
    <span class="p">}</span>
    <span class="n">stCoRoutine_t</span> <span class="o">*</span><span class="n">co</span> <span class="o">=</span> <span class="n">co_create_env</span><span class="p">(</span> <span class="n">co_get_curr_thread_env</span><span class="p">(),</span> <span class="n">attr</span><span class="p">,</span> <span class="n">pfn</span><span class="p">,</span><span class="n">arg</span> <span class="p">);</span>   <span class="c1">//Create the coroutine runtime environment and initialize the coroutine data</span>
    <span class="o">*</span><span class="n">ppco</span> <span class="o">=</span> <span class="n">co</span><span class="p">;</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">co_create</code> does two things. First, if the current thread has not initialized its runtime environment <code class="language-plaintext highlighter-rouge">stCoRoutineEnv_t</code>, it initializes it. This includes initializing the coroutine call stack, creating the main coroutine, and pushing it onto the stack. Second, it creates a coroutine from the <code class="language-plaintext highlighter-rouge">attr</code> parameter, allocates a private stack (or sets up the shared stack), and returns the handle <code class="language-plaintext highlighter-rouge">co</code>:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*  Coroutine switch-in interface
 *  @param
 *  co          :pointer to the main coroutine structure
 */</span>
<span class="kt">void</span> <span class="nf">co_resume</span><span class="p">(</span> <span class="n">stCoRoutine_t</span> <span class="o">*</span><span class="n">co</span> <span class="p">)</span>
<span class="p">{</span>
    <span class="n">stCoRoutineEnv_t</span> <span class="o">*</span><span class="n">env</span> <span class="o">=</span> <span class="n">co</span><span class="o">-&gt;</span><span class="n">env</span><span class="p">;</span>
    <span class="n">stCoRoutine_t</span> <span class="o">*</span><span class="n">lpCurrRoutine</span> <span class="o">=</span> <span class="n">env</span><span class="o">-&gt;</span><span class="n">pCallStack</span><span class="p">[</span> <span class="n">env</span><span class="o">-&gt;</span><span class="n">iCallStackSize</span> <span class="o">-</span> <span class="mi">1</span> <span class="p">];</span>  <span class="c1">//The coroutine that is currently running</span>
    <span class="k">if</span><span class="p">(</span> <span class="o">!</span><span class="n">co</span><span class="o">-&gt;</span><span class="n">cStart</span> <span class="p">)</span>                                                           <span class="c1">//First time entering</span>
    <span class="p">{</span>
        <span class="n">coctx_make</span><span class="p">(</span> <span class="o">&amp;</span><span class="n">co</span><span class="o">-&gt;</span><span class="n">ctx</span><span class="p">,(</span><span class="n">coctx_pfn_t</span><span class="p">)</span><span class="n">CoRoutineFunc</span><span class="p">,</span><span class="n">co</span><span class="p">,</span><span class="mi">0</span> <span class="p">);</span>                 <span class="c1">//Save the context (current registers) in co-&gt;ctx</span>
        <span class="n">co</span><span class="o">-&gt;</span><span class="n">cStart</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>                                                         <span class="c1">//Mark as started</span>
    <span class="p">}</span>
    <span class="n">env</span><span class="o">-&gt;</span><span class="n">pCallStack</span><span class="p">[</span> <span class="n">env</span><span class="o">-&gt;</span><span class="n">iCallStackSize</span><span class="o">++</span> <span class="p">]</span> <span class="o">=</span> <span class="n">co</span><span class="p">;</span>                              <span class="c1">//Push onto the coroutine call stack</span>
    <span class="n">co_swap</span><span class="p">(</span> <span class="n">lpCurrRoutine</span><span class="p">,</span> <span class="n">co</span> <span class="p">);</span>                                               <span class="c1">//Switch</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">co_resume</code> switches to a given coroutine. If <code class="language-plaintext highlighter-rouge">co</code> has not started yet, it initializes the coroutine stack with <code class="language-plaintext highlighter-rouge">coctx_make</code>. Then it pushes the coroutine onto the call stack and switches context with the current coroutine:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">co_yield_env</span><span class="p">(</span> <span class="n">stCoRoutineEnv_t</span> <span class="o">*</span><span class="n">env</span> <span class="p">)</span>
<span class="p">{</span>
    
    <span class="n">stCoRoutine_t</span> <span class="o">*</span><span class="n">last</span> <span class="o">=</span> <span class="n">env</span><span class="o">-&gt;</span><span class="n">pCallStack</span><span class="p">[</span> <span class="n">env</span><span class="o">-&gt;</span><span class="n">iCallStackSize</span> <span class="o">-</span> <span class="mi">2</span> <span class="p">];</span>
    <span class="n">stCoRoutine_t</span> <span class="o">*</span><span class="n">curr</span> <span class="o">=</span> <span class="n">env</span><span class="o">-&gt;</span><span class="n">pCallStack</span><span class="p">[</span> <span class="n">env</span><span class="o">-&gt;</span><span class="n">iCallStackSize</span> <span class="o">-</span> <span class="mi">1</span> <span class="p">];</span>

    <span class="n">env</span><span class="o">-&gt;</span><span class="n">iCallStackSize</span><span class="o">--</span><span class="p">;</span>

    <span class="n">co_swap</span><span class="p">(</span> <span class="n">curr</span><span class="p">,</span> <span class="n">last</span><span class="p">);</span>
<span class="p">}</span>
<span class="cm">/* Interface to switch out the current coroutine
 */</span>
<span class="kt">void</span> <span class="n">co_yield_ct</span><span class="p">()</span>
<span class="p">{</span>
    <span class="n">co_yield_env</span><span class="p">(</span> <span class="n">co_get_curr_thread_env</span><span class="p">()</span> <span class="p">);</span>
<span class="p">}</span>
<span class="cm">/*  Coroutine switch-out interface
 *  @param
 *  co          :pointer to the main coroutine structure
 */</span>
<span class="kt">void</span> <span class="k">co_yield</span><span class="p">(</span> <span class="n">stCoRoutine_t</span> <span class="o">*</span><span class="n">co</span> <span class="p">)</span>
<span class="p">{</span>
    <span class="n">co_yield_env</span><span class="p">(</span> <span class="n">co</span><span class="o">-&gt;</span><span class="n">env</span> <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">co_yield</code> family of functions makes the current coroutine give up the CPU. They pop it off the call stack and switch context with the previous coroutine on the stack.</p>

<h2 id="example">Example</h2>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//example_test.cpp</span>
<span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stdlib.h&gt;</span><span class="cp">
#include</span> <span class="cpf">"co_routine.h"</span><span class="cp">
</span>
<span class="kt">void</span><span class="o">*</span> <span class="nf">f</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">args</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"f</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
        <span class="n">co_yield_ct</span><span class="p">();</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">void</span><span class="o">*</span> <span class="n">g</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">args</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"g</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
        <span class="n">co_yield_ct</span><span class="p">();</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">stCoRoutine_t</span><span class="o">*</span> <span class="n">co_f</span><span class="p">;</span>
    <span class="n">stCoRoutine_t</span><span class="o">*</span> <span class="n">co_g</span><span class="p">;</span>
    <span class="n">co_create</span><span class="p">(</span><span class="o">&amp;</span><span class="n">co_f</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">f</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span> 
    <span class="n">co_create</span><span class="p">(</span><span class="o">&amp;</span><span class="n">co_g</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">g</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span> 
    <span class="k">while</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">co_resume</span><span class="p">(</span><span class="n">co_f</span><span class="p">);</span>
        <span class="n">co_resume</span><span class="p">(</span><span class="n">co_g</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Using the three basic functions above, I wrote a small example here. The program creates two coroutines, <code class="language-plaintext highlighter-rouge">f</code> and <code class="language-plaintext highlighter-rouge">g</code>. Each coroutine prints its own function name and then gives up the CPU. The main coroutine calls <code class="language-plaintext highlighter-rouge">co_resume</code> in a loop to wake the two coroutines in turn. The program prints this in a loop:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./example_test
f
g
f
...
</code></pre></div></div>

<h2 id="conclusion">Conclusion</h2>

<p>We have now covered the core functions of libco and how they execute. Thank you for reading. If you have any questions or thoughts, or find any mistake in this post, please <a href="mailto:changliu0828@gmail.com">let me know</a>.</p>

<h2 id="references">References</h2>

<ol>
  <li><a href="https://blog.csdn.net/weixin_43705457/article/details/106863859">libco source code analysis, csdn (in Chinese)</a></li>
  <li><a href="http://purecpp.org/purecpp/static/64a819e99584452aab70a7f9c307717f.pdf">libco talk, Li Fangyuan (in Chinese)</a></li>
</ol>]]></content><author><name></name></author><summary type="html"><![CDATA[libco Source Notes (2): Main Structures and Functions]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://changliu.me/assets/images/social-card.png" /><media:content medium="image" url="https://changliu.me/assets/images/social-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="zh"><title type="html">libco源码笔记(2)主要结构与函数</title><link href="https://changliu.me/post/libco-api/" rel="alternate" type="text/html" title="libco源码笔记(2)主要结构与函数" /><published>2020-09-22T00:00:00+00:00</published><updated>2020-09-22T00:00:00+00:00</updated><id>https://changliu.me/post/libco-api</id><content type="html" xml:base="https://changliu.me/post/libco-api/"><![CDATA[<h1 id="libco源码笔记2主要结构与函数">libco源码笔记(2)主要结构与函数</h1>

<p>在之前的文章<a href="/post/libco-coroutine/">libco源码笔记(1)协程与上下文切换</a>中，我们介绍了协程的基本概念以及libco中的上下文切换核心代码。本文结合一个示例，介绍libco提供的几个重要函数接口。建议配合我自己的<a href="https://github.com/changliu0828/libco">注释版本</a>阅读。</p>

<h2 id="libco主要结构体">libco主要结构体</h2>

<p>首先我们介绍libco中的三个核心结构体，下图1描述了三者的关系。</p>

<h3 id="coctx_t"><code class="language-plaintext highlighter-rouge">coctx_t</code></h3>

<p>保存协程切换时所需的上下文信息，详尽的说明请参考<a href="/post/libco-coroutine/">libco源码笔记(1)协程与上下文切换</a>，此处不再赘述。</p>

<h3 id="stcoroutine_t"><code class="language-plaintext highlighter-rouge">stCoRoutine_t</code></h3>

<p>协程主要结构体，包含单个协程的全部信息，如协程启停状态，执行函数，上下文信息，共享栈信息等。</p>

<h3 id="stcoroutineenv_t"><code class="language-plaintext highlighter-rouge">stCoRoutineEnv_t</code></h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kr">__thread</span> <span class="n">stCoRoutineEnv_t</span><span class="o">*</span> <span class="n">gCoEnvPerThread</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>   <span class="c1">//协程运行环境 __thread:线程私有</span>
</code></pre></div></div>

<p>线程私有全局静态变量，包含全局协程环境信息，如协程调用栈，epoll句柄等。其中<code class="language-plaintext highlighter-rouge">pCallStack</code>为当前线程中的协程调用栈，由于libco为非对称协程</p>

<p><img src="/assets/images/libco-api/co-core-struct.svg" alt="图1. libco核心结构" width="100%" />
<em>图1. libco核心结构</em></p>

<h2 id="libco主要接口函数">libco主要接口函数</h2>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*  协程创建接口
 *  @param
 *  co          :协程主结构体二级指针
 *  attr        :协程可配置属性, 包括栈大小、共享栈地址
 *  pfn         :协程调用函数
 *  arg         :协程调用函数参数
 *  @return     :0
 */</span>
<span class="kt">int</span> <span class="nf">co_create</span><span class="p">(</span> <span class="n">stCoRoutine_t</span> <span class="o">**</span><span class="n">ppco</span><span class="p">,</span><span class="k">const</span> <span class="n">stCoRoutineAttr_t</span> <span class="o">*</span><span class="n">attr</span><span class="p">,</span><span class="n">pfn_co_routine_t</span> <span class="n">pfn</span><span class="p">,</span><span class="kt">void</span> <span class="o">*</span><span class="n">arg</span> <span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span><span class="p">(</span> <span class="o">!</span><span class="n">co_get_curr_thread_env</span><span class="p">()</span> <span class="p">)</span> 
    <span class="p">{</span>
        <span class="n">co_init_curr_thread_env</span><span class="p">();</span>      <span class="c1">//初始化本线程环境，主协程才会调用</span>
    <span class="p">}</span>
    <span class="n">stCoRoutine_t</span> <span class="o">*</span><span class="n">co</span> <span class="o">=</span> <span class="n">co_create_env</span><span class="p">(</span> <span class="n">co_get_curr_thread_env</span><span class="p">(),</span> <span class="n">attr</span><span class="p">,</span> <span class="n">pfn</span><span class="p">,</span><span class="n">arg</span> <span class="p">);</span>   <span class="c1">//创建协程运行环境, 初始化协程数据</span>
    <span class="o">*</span><span class="n">ppco</span> <span class="o">=</span> <span class="n">co</span><span class="p">;</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">co_create</code>主要负责两件事情。首先，在当前线程没有初始化运行环境<code class="language-plaintext highlighter-rouge">stCoRoutineEnv_t</code>时，对其进行初始化，包括初始化协程调用栈，创建主协程并压栈等。其次，根据传入的配置参数<code class="language-plaintext highlighter-rouge">attr</code>创建协程，分配私有栈（设置共享栈）并返回句柄<code class="language-plaintext highlighter-rouge">co</code>。</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*  协程切回接口
 *  @param
 *  co          :协程主结构体指针
 */</span>
<span class="kt">void</span> <span class="nf">co_resume</span><span class="p">(</span> <span class="n">stCoRoutine_t</span> <span class="o">*</span><span class="n">co</span> <span class="p">)</span>
<span class="p">{</span>
    <span class="n">stCoRoutineEnv_t</span> <span class="o">*</span><span class="n">env</span> <span class="o">=</span> <span class="n">co</span><span class="o">-&gt;</span><span class="n">env</span><span class="p">;</span>
    <span class="n">stCoRoutine_t</span> <span class="o">*</span><span class="n">lpCurrRoutine</span> <span class="o">=</span> <span class="n">env</span><span class="o">-&gt;</span><span class="n">pCallStack</span><span class="p">[</span> <span class="n">env</span><span class="o">-&gt;</span><span class="n">iCallStackSize</span> <span class="o">-</span> <span class="mi">1</span> <span class="p">];</span>  <span class="c1">//当前正在运行的协程</span>
    <span class="k">if</span><span class="p">(</span> <span class="o">!</span><span class="n">co</span><span class="o">-&gt;</span><span class="n">cStart</span> <span class="p">)</span>                                                           <span class="c1">//第一次进入</span>
    <span class="p">{</span>
        <span class="n">coctx_make</span><span class="p">(</span> <span class="o">&amp;</span><span class="n">co</span><span class="o">-&gt;</span><span class="n">ctx</span><span class="p">,(</span><span class="n">coctx_pfn_t</span><span class="p">)</span><span class="n">CoRoutineFunc</span><span class="p">,</span><span class="n">co</span><span class="p">,</span><span class="mi">0</span> <span class="p">);</span>                 <span class="c1">//在co-&gt;ctx中保存上下文(当前寄存器)</span>
        <span class="n">co</span><span class="o">-&gt;</span><span class="n">cStart</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>                                                         <span class="c1">//标记为已开始</span>
    <span class="p">}</span>
    <span class="n">env</span><span class="o">-&gt;</span><span class="n">pCallStack</span><span class="p">[</span> <span class="n">env</span><span class="o">-&gt;</span><span class="n">iCallStackSize</span><span class="o">++</span> <span class="p">]</span> <span class="o">=</span> <span class="n">co</span><span class="p">;</span>                              <span class="c1">//压入协程调用栈</span>
    <span class="n">co_swap</span><span class="p">(</span> <span class="n">lpCurrRoutine</span><span class="p">,</span> <span class="n">co</span> <span class="p">);</span>                                               <span class="c1">//切换</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">co_resume</code>负责切换至某一协程。在<code class="language-plaintext highlighter-rouge">co</code>尚未启动时，通过<code class="language-plaintext highlighter-rouge">coctx_make</code>初始化协程栈，然后将协程压栈，并与当前协程进行上下文切换。</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">co_yield_env</span><span class="p">(</span> <span class="n">stCoRoutineEnv_t</span> <span class="o">*</span><span class="n">env</span> <span class="p">)</span>
<span class="p">{</span>
    
    <span class="n">stCoRoutine_t</span> <span class="o">*</span><span class="n">last</span> <span class="o">=</span> <span class="n">env</span><span class="o">-&gt;</span><span class="n">pCallStack</span><span class="p">[</span> <span class="n">env</span><span class="o">-&gt;</span><span class="n">iCallStackSize</span> <span class="o">-</span> <span class="mi">2</span> <span class="p">];</span>
    <span class="n">stCoRoutine_t</span> <span class="o">*</span><span class="n">curr</span> <span class="o">=</span> <span class="n">env</span><span class="o">-&gt;</span><span class="n">pCallStack</span><span class="p">[</span> <span class="n">env</span><span class="o">-&gt;</span><span class="n">iCallStackSize</span> <span class="o">-</span> <span class="mi">1</span> <span class="p">];</span>

    <span class="n">env</span><span class="o">-&gt;</span><span class="n">iCallStackSize</span><span class="o">--</span><span class="p">;</span>

    <span class="n">co_swap</span><span class="p">(</span> <span class="n">curr</span><span class="p">,</span> <span class="n">last</span><span class="p">);</span>
<span class="p">}</span>
<span class="cm">/* 当前协程切出接口
 */</span>
<span class="kt">void</span> <span class="n">co_yield_ct</span><span class="p">()</span>
<span class="p">{</span>
    <span class="n">co_yield_env</span><span class="p">(</span> <span class="n">co_get_curr_thread_env</span><span class="p">()</span> <span class="p">);</span>
<span class="p">}</span>
<span class="cm">/*  协程切出接口
 *  @param
 *  co          :协程主结构体指针
 */</span>
<span class="kt">void</span> <span class="k">co_yield</span><span class="p">(</span> <span class="n">stCoRoutine_t</span> <span class="o">*</span><span class="n">co</span> <span class="p">)</span>
<span class="p">{</span>
    <span class="n">co_yield_env</span><span class="p">(</span> <span class="n">co</span><span class="o">-&gt;</span><span class="n">env</span> <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">co_yield</code>系列函数负责让当前协程让出CPU，将其出栈，并与栈上的前一个协程进行上下文切换。</p>

<h2 id="示例">示例</h2>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//example_test.cpp</span>
<span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stdlib.h&gt;</span><span class="cp">
#include</span> <span class="cpf">"co_routine.h"</span><span class="cp">
</span>
<span class="kt">void</span><span class="o">*</span> <span class="nf">f</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">args</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"f</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
        <span class="n">co_yield_ct</span><span class="p">();</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">void</span><span class="o">*</span> <span class="n">g</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">args</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"g</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
        <span class="n">co_yield_ct</span><span class="p">();</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">stCoRoutine_t</span><span class="o">*</span> <span class="n">co_f</span><span class="p">;</span>
    <span class="n">stCoRoutine_t</span><span class="o">*</span> <span class="n">co_g</span><span class="p">;</span>
    <span class="n">co_create</span><span class="p">(</span><span class="o">&amp;</span><span class="n">co_f</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">f</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span> 
    <span class="n">co_create</span><span class="p">(</span><span class="o">&amp;</span><span class="n">co_g</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">g</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span> 
    <span class="k">while</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">co_resume</span><span class="p">(</span><span class="n">co_f</span><span class="p">);</span>
        <span class="n">co_resume</span><span class="p">(</span><span class="n">co_g</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>使用上面提到的三个基本函数，我在这里写了一个小例子，程序创建了两个协程<code class="language-plaintext highlighter-rouge">f</code>和<code class="language-plaintext highlighter-rouge">g</code>，每个协程在打印自己的函数名后，让出CPU。主协程循环调用<code class="language-plaintext highlighter-rouge">co_resume</code>依次唤起两个协程，程序的运行输出为循环打印：</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./example_test
f
g
f
...
</code></pre></div></div>

<h2 id="最后">最后</h2>

<p>至此，我们了解了libco核心部分的相关函数与执行过程。感谢你的阅读。如果你有任何疑问和感想，或发现本文有任何错误，请一定<a href="mailto:changliu0828@gmail.com">让我知道</a>。</p>

<h2 id="参考">参考</h2>

<ol>
  <li><a href="https://blog.csdn.net/weixin_43705457/article/details/106863859">libco源码分析，csdn</a></li>
  <li><a href="http://purecpp.org/purecpp/static/64a819e99584452aab70a7f9c307717f.pdf">libco分享，李方源</a></li>
</ol>]]></content><author><name></name></author><summary type="html"><![CDATA[libco源码笔记(2)主要结构与函数]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://changliu.me/assets/images/social-card.png" /><media:content medium="image" url="https://changliu.me/assets/images/social-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry xml:lang="en"><title type="html">libco Source Notes (1): Coroutines and Context Switching</title><link href="https://changliu.me/2020/09/11/libco-coroutine.html" rel="alternate" type="text/html" title="libco Source Notes (1): Coroutines and Context Switching" /><published>2020-09-11T00:00:00+00:00</published><updated>2020-09-11T00:00:00+00:00</updated><id>https://changliu.me/2020/09/11/libco-coroutine</id><content type="html" xml:base="https://changliu.me/2020/09/11/libco-coroutine.html"><![CDATA[<h1 id="libco-source-notes-1-coroutines-and-context-switching">libco Source Notes (1): Coroutines and Context Switching</h1>

<p>This post uses WeChat’s high-performance open-source coroutine library <a href="https://github.com/Tencent/libco">libco</a> to summarize the problems coroutines address and how they solve them. libco has few source comments, so here is <a href="https://github.com/changliu0828/libco">my own annotated version</a>. I suggest reading this post together with it. All code and explanations here run on x86 32-bit. The 64-bit case differs slightly. I skip it for space.</p>

<h2 id="callback-hell">Callback Hell</h2>

<p>Before we start, let’s briefly recall why coroutines exist.</p>

<p>At first, as in Figure 1(a) below, our system has a steady stream of tasks (task in the figure) to process. We write a server program for it. The program runs as a single process (process in the figure) and keeps fetching tasks (loop in the figure). For each task it fetches, it calls the handler <code class="language-plaintext highlighter-rouge">f()</code> to run the actual logic. In <code class="language-plaintext highlighter-rouge">f()</code>, the code segment <code class="language-plaintext highlighter-rouge">g()</code> takes a long time. Even so, tasks arrive less often than <code class="language-plaintext highlighter-rouge">f()</code> takes to run. The system consumes tasks faster than they are produced, so the service runs fine.</p>

<p>As the business grows, we receive more tasks per unit of time. The single-process model in (a) can no longer consume tasks in time. So, as in Figure 1(b), we can split <code class="language-plaintext highlighter-rouge">g()</code> out into its own process, since it is fairly independent and uses many resources. The original process calls <code class="language-plaintext highlighter-rouge">g()</code> with an asynchronous remote call <code class="language-plaintext highlighter-rouge">call_g()</code>. It registers a callback <code class="language-plaintext highlighter-rouge">g_callback()</code> to handle the result of <code class="language-plaintext highlighter-rouge">g()</code>. When coding, we have to change from sequential programming to programming with a calling part and a callback part.</p>

<p><img src="/assets/images/libco-coroutine/server-model.png" alt="Figure 1" width="100%" />
<em>Figure 1</em></p>

<p>The asynchronous style raises system throughput and lowers coupling. But as the figure below shows, one sequential piece of code is split into several pieces. When the code is complex and needs many remote calls, maintainability drops sharply. We call this <strong>callback hell</strong>.</p>

<p><img src="/assets/images/libco-coroutine/callback-hell.png" alt="Figure 2. Code segments under synchronous and asynchronous programming" width="50%" />
<em>Figure 2. Code segments under synchronous and asynchronous programming</em></p>

<h2 id="what-is-a-coroutine">What Is a Coroutine</h2>

<p>How do we solve callback hell? How do we keep execution asynchronous, yet turn the broken-up code back into the sequential flow we know? When C/C++ code runs, almost all of the runtime state is held in stack frames and registers. Suppose a remote call blocks. If we save the execution context ourselves, give up the CPU, and load the context again when the remote call returns, we can finish the asynchronous process inside one function stack. We call this mechanism a <strong>coroutine</strong>. It is like the process/thread switching we know, but the user triggers the context switch and manages it. So it is also often called a “user-space thread”.</p>

<p><img src="/assets/images/libco-coroutine/co-lib.png" alt="Figure 3. Responsibilities of a coroutine library" width="90%" />
<em>Figure 3. Responsibilities of a coroutine library</em></p>

<h2 id="coroutine-context-and-switching">Coroutine Context and Switching</h2>

<p>What exactly is in the runtime “context” that we must save and load by hand? Take the following <code class="language-plaintext highlighter-rouge">main</code> function calling the <code class="language-plaintext highlighter-rouge">sum</code> function as an example:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">sum</span><span class="p">(</span><span class="kt">int</span> <span class="n">x</span><span class="p">,</span> <span class="kt">int</span> <span class="n">y</span><span class="p">)</span> <span class="p">{</span>
  <span class="kt">int</span> <span class="n">z</span> <span class="o">=</span> <span class="n">x</span> <span class="o">+</span> <span class="n">y</span><span class="p">;</span>
  <span class="k">return</span> <span class="n">z</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
  <span class="kt">int</span> <span class="n">b</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span>
  <span class="kt">int</span> <span class="n">c</span> <span class="o">=</span> <span class="n">sum</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">);</span>
  <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>After compiling with <code class="language-plaintext highlighter-rouge">g++ -m32 -S sum.cpp</code>, the assembly is as follows:</p>

<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">_Z3sumii:</span>
<span class="nf">pushl</span>   <span class="o">%</span><span class="nb">ebp</span>
<span class="nf">movl</span>    <span class="o">%</span><span class="nb">esp</span><span class="p">,</span> <span class="o">%</span><span class="nb">ebp</span>
<span class="nf">subl</span>    <span class="kc">$</span><span class="mi">16</span><span class="p">,</span> <span class="o">%</span><span class="nb">esp</span>         <span class="c1">;make space for stack</span>
<span class="nf">movl</span>    <span class="mi">12</span><span class="p">(</span><span class="o">%</span><span class="nb">ebp</span><span class="p">),</span> <span class="o">%</span><span class="nb">eax</span>    <span class="c1">;%eax = y</span>
<span class="nf">movl</span>    <span class="mi">8</span><span class="p">(</span><span class="o">%</span><span class="nb">ebp</span><span class="p">),</span> <span class="o">%</span><span class="nb">edx</span>     <span class="c1">;%edx = x</span>
<span class="nf">addl</span>    <span class="o">%</span><span class="nb">edx</span><span class="p">,</span> <span class="o">%</span><span class="nb">eax</span>        <span class="c1">;%eax = %eax + %edx</span>
<span class="nf">movl</span>    <span class="o">%</span><span class="nb">eax</span><span class="p">,</span> <span class="o">-</span><span class="mi">4</span><span class="p">(</span><span class="o">%</span><span class="nb">ebp</span><span class="p">)</span>    <span class="c1">;z = %eax</span>
<span class="nf">movl</span>    <span class="o">-</span><span class="mi">4</span><span class="p">(</span><span class="o">%</span><span class="nb">ebp</span><span class="p">),</span> <span class="o">%</span><span class="nb">eax</span>    <span class="c1">;%eax = z</span>
<span class="nf">leave</span>                     <span class="c1">;%esp = %ebp; pop %ebp</span>
<span class="nf">ret</span>                       <span class="c1">;pop %eip; jump(%eip)</span>

<span class="nl">main:</span>
<span class="nf">pushl</span>   <span class="o">%</span><span class="nb">ebp</span>
<span class="nf">movl</span>    <span class="o">%</span><span class="nb">esp</span><span class="p">,</span> <span class="o">%</span><span class="nb">ebp</span>
<span class="nf">subl</span>    <span class="kc">$</span><span class="mi">24</span><span class="p">,</span> <span class="o">%</span><span class="nb">esp</span>         <span class="c1">;make space for stack</span>
<span class="nf">movl</span>    <span class="kc">$</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">4</span><span class="p">(</span><span class="o">%</span><span class="nb">ebp</span><span class="p">)</span>      <span class="c1">;int a = 1;</span>
<span class="nf">movl</span>    <span class="kc">$</span><span class="mi">10</span><span class="p">,</span> <span class="o">-</span><span class="mi">8</span><span class="p">(</span><span class="o">%</span><span class="nb">ebp</span><span class="p">)</span>     <span class="c1">;int b = 10;</span>
<span class="nf">movl</span>    <span class="o">-</span><span class="mi">8</span><span class="p">(</span><span class="o">%</span><span class="nb">ebp</span><span class="p">),</span> <span class="o">%</span><span class="nb">eax</span>
<span class="nf">movl</span>    <span class="o">%</span><span class="nb">eax</span><span class="p">,</span> <span class="mi">4</span><span class="p">(</span><span class="o">%</span><span class="nb">esp</span><span class="p">)</span>     <span class="c1">;y = b;</span>
<span class="nf">movl</span>    <span class="o">-</span><span class="mi">4</span><span class="p">(</span><span class="o">%</span><span class="nb">ebp</span><span class="p">),</span> <span class="o">%</span><span class="nb">eax</span>
<span class="nf">movl</span>    <span class="o">%</span><span class="nb">eax</span><span class="p">,</span> <span class="p">(</span><span class="o">%</span><span class="nb">esp</span><span class="p">)</span>      <span class="c1">;x = a;</span>
<span class="nf">call</span>    <span class="nv">_Z3sumii</span>          <span class="c1">;push(eip); jump(sum);</span>
<span class="nf">movl</span>    <span class="o">%</span><span class="nb">eax</span><span class="p">,</span> <span class="o">-</span><span class="mi">12</span><span class="p">(</span><span class="o">%</span><span class="nb">ebp</span><span class="p">)</span>
<span class="nf">movl</span>    <span class="kc">$</span><span class="mi">0</span><span class="p">,</span> <span class="o">%</span><span class="nb">eax</span>          <span class="c1">;return 0;</span>
</code></pre></div></div>

<p>As the figure below shows, the code mainly works on the stack frames of the two functions, shown in yellow and green. The base pointer register <code class="language-plaintext highlighter-rouge">ebp</code> and the stack pointer register <code class="language-plaintext highlighter-rouge">esp</code> mark the bottom and the top of the stack.</p>

<p>$L14$ first pushes the current <code class="language-plaintext highlighter-rouge">ebp</code>. Since <code class="language-plaintext highlighter-rouge">main</code> is the function that runs right after the process starts, <code class="language-plaintext highlighter-rouge">ebp</code> is 0 here.</p>

<p>$L15$ sets the <code class="language-plaintext highlighter-rouge">ebp</code> position for <code class="language-plaintext highlighter-rouge">main</code>.</p>

<p>$L16$ moves the <code class="language-plaintext highlighter-rouge">esp</code> address down by 16. This makes enough room for the local variables and the arguments of the <code class="language-plaintext highlighter-rouge">sum</code> call.</p>

<p>$L17-L22$ assign values to the variables <code class="language-plaintext highlighter-rouge">a,b</code> and to the arguments <code class="language-plaintext highlighter-rouge">x,y</code> of <code class="language-plaintext highlighter-rouge">sum</code>.</p>

<p>$L23$ runs the <code class="language-plaintext highlighter-rouge">call</code> instruction. It pushes the current instruction register <code class="language-plaintext highlighter-rouge">eip</code> and jumps to <code class="language-plaintext highlighter-rouge">sum</code> (<code class="language-plaintext highlighter-rouge">eip</code> points to the first instruction of <code class="language-plaintext highlighter-rouge">sum</code>).</p>

<p>$L2$ pushes the current <code class="language-plaintext highlighter-rouge">ebp</code>, which is <code class="language-plaintext highlighter-rouge">ebp_main</code> in the figure.</p>

<p>$L3$ sets the <code class="language-plaintext highlighter-rouge">ebp</code> position for <code class="language-plaintext highlighter-rouge">sum</code>, pointing at the current <code class="language-plaintext highlighter-rouge">esp</code>.</p>

<p>$L4$ makes room on the stack.</p>

<p>$L5, L6$ use <code class="language-plaintext highlighter-rouge">ebp_sum + 8, ebp_sum + 12</code> to get the values of the arguments <code class="language-plaintext highlighter-rouge">x, y</code>.</p>

<p>$L7-L9$ do the addition and put the result in <code class="language-plaintext highlighter-rouge">eax</code>.</p>

<p>$L10$ calls the <code class="language-plaintext highlighter-rouge">leave</code> instruction. It moves <code class="language-plaintext highlighter-rouge">esp</code> back to the <code class="language-plaintext highlighter-rouge">ebp</code> position, pops <code class="language-plaintext highlighter-rouge">ebp_main</code>, and assigns it to <code class="language-plaintext highlighter-rouge">ebp</code>.</p>

<p>$L11$ calls the <code class="language-plaintext highlighter-rouge">ret</code> instruction. It pops the instruction address <code class="language-plaintext highlighter-rouge">eip</code> saved before the call to <code class="language-plaintext highlighter-rouge">sum</code> and assigns it to <code class="language-plaintext highlighter-rouge">eip</code>. Now the stack of <code class="language-plaintext highlighter-rouge">main</code>, the yellow part in the figure, is restored.</p>

<p>Line $L24$ assigns the result of <code class="language-plaintext highlighter-rouge">sum</code> in <code class="language-plaintext highlighter-rouge">eax</code> to <code class="language-plaintext highlighter-rouge">c</code>.</p>

<p>Line $L25$ assigns the return value <code class="language-plaintext highlighter-rouge">0</code> to <code class="language-plaintext highlighter-rouge">eax</code>. This finishes the whole process.</p>

<p><img src="/assets/images/libco-coroutine/function-call-example.png" alt="Figure 4. Function stack of sum.cpp" width="60%" />
<em>Figure 4. Function stack of sum.cpp</em></p>

<p>From this analysis, we can see that for a running function, <strong>arguments, return address, function stack, and registers</strong> make up all of its runtime information. With them, we can restore the execution state of any function. We call this the <strong>coroutine context</strong>.</p>

<h3 id="coctx_t-context-information"><code class="language-plaintext highlighter-rouge">coctx_t</code> Context Information</h3>

<p>libco describes the coroutine context with the structure <code class="language-plaintext highlighter-rouge">coctx_t</code> defined below. <code class="language-plaintext highlighter-rouge">ss_sp</code> and <code class="language-plaintext highlighter-rouge">ss_size</code> hold the arguments, the return address, and the function stack. These are the red-box part of Figure 4. <code class="language-plaintext highlighter-rouge">regs</code> holds the registers for 32-bit/64-bit:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">coctx_t</span>
<span class="p">{</span>
<span class="cp">#if defined(__i386__)
</span>  <span class="kt">void</span> <span class="o">*</span><span class="n">regs</span><span class="p">[</span> <span class="mi">8</span> <span class="p">];</span>    <span class="c1">//see coctx.cpp for details</span>
<span class="cp">#else
</span>  <span class="kt">void</span> <span class="o">*</span><span class="n">regs</span><span class="p">[</span> <span class="mi">14</span> <span class="p">];</span>   <span class="c1">//see coctx.cpp for details. R10, R11 are callee saved registers, saved by the called function</span>
<span class="cp">#endif
</span>  <span class="kt">size_t</span> <span class="n">ss_size</span><span class="p">;</span>     <span class="c1">//remaining size of the coroutine stack</span>
  <span class="kt">char</span> <span class="o">*</span><span class="n">ss_sp</span><span class="p">;</span>        <span class="c1">//bottom address of the coroutine stack</span>
<span class="p">};</span>
</code></pre></div></div>

<h3 id="co_make-context-initialization"><code class="language-plaintext highlighter-rouge">co_make</code> Context Initialization</h3>

<p>libco uses the <code class="language-plaintext highlighter-rouge">coctx_make</code> below to fill in the initial content of the coroutine context on the first call (<code class="language-plaintext highlighter-rouge">co_resume</code>):</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*
 * @param
 * ctx  :pointer to the context struct
 * pfn  :pointer to the function to call
 * s    :argument
 * s1   :argument
 */</span>
<span class="kt">int</span> <span class="nf">coctx_make</span><span class="p">(</span><span class="n">coctx_t</span><span class="o">*</span> <span class="n">ctx</span><span class="p">,</span> <span class="n">coctx_pfn_t</span> <span class="n">pfn</span><span class="p">,</span> <span class="k">const</span> <span class="kt">void</span><span class="o">*</span> <span class="n">s</span><span class="p">,</span> <span class="k">const</span> <span class="kt">void</span><span class="o">*</span> <span class="n">s1</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// make room for coctx_param</span>
  <span class="kt">char</span><span class="o">*</span> <span class="n">sp</span> <span class="o">=</span> <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">ss_sp</span> <span class="o">+</span> <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">ss_size</span> <span class="o">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">coctx_param_t</span><span class="p">);</span> <span class="c1">//ss_sp is heap memory, move sp to the high address</span>
  <span class="n">sp</span> <span class="o">=</span> <span class="p">(</span><span class="kt">char</span><span class="o">*</span><span class="p">)((</span><span class="kt">unsigned</span> <span class="kt">long</span><span class="p">)</span><span class="n">sp</span> <span class="o">&amp;</span> <span class="o">-</span><span class="mi">16L</span><span class="p">);</span>                       <span class="c1">//i386 requires the stack start address to be 16-byte aligned</span>

  <span class="n">coctx_param_t</span><span class="o">*</span> <span class="n">param</span> <span class="o">=</span> <span class="p">(</span><span class="n">coctx_param_t</span><span class="o">*</span><span class="p">)</span><span class="n">sp</span><span class="p">;</span>
  <span class="kt">void</span><span class="o">**</span> <span class="n">ret_addr</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span><span class="o">**</span><span class="p">)(</span><span class="n">sp</span> <span class="o">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span> <span class="o">*</span> <span class="mi">2</span><span class="p">);</span>           <span class="c1">//return address</span>
  <span class="o">*</span><span class="n">ret_addr</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span><span class="n">pfn</span><span class="p">;</span>
  <span class="n">param</span><span class="o">-&gt;</span><span class="n">s1</span> <span class="o">=</span> <span class="n">s</span><span class="p">;</span>
  <span class="n">param</span><span class="o">-&gt;</span><span class="n">s2</span> <span class="o">=</span> <span class="n">s1</span><span class="p">;</span>

  <span class="n">memset</span><span class="p">(</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">regs</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">regs</span><span class="p">));</span>

  <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">regs</span><span class="p">[</span><span class="n">kESP</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="kt">char</span><span class="o">*</span><span class="p">)(</span><span class="n">sp</span><span class="p">)</span> <span class="o">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span> <span class="o">*</span> <span class="mi">2</span><span class="p">;</span>
  <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Figure 5 below shows the coroutine stack after <code class="language-plaintext highlighter-rouge">co_make</code> fills it. It differs from the function call stack above. Before the arguments and the return address, 4 bytes are left empty (NULL in the figure). This prepares for the later context switch.</p>

<p><img src="/assets/images/libco-coroutine/co_make.png" alt="Figure 5. co_make initializes the coroutine stack" width="60%" />
<em>Figure 5. co_make initializes the coroutine stack</em></p>

<h3 id="coctx_swap-context-switching"><code class="language-plaintext highlighter-rouge">coctx_swap</code> Context Switching</h3>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">extern</span> <span class="s">"C"</span>
<span class="p">{</span>
  <span class="k">extern</span> <span class="kt">void</span> <span class="n">coctx_swap</span><span class="p">(</span> <span class="n">coctx_t</span> <span class="o">*</span><span class="p">,</span><span class="n">coctx_t</span><span class="o">*</span> <span class="p">)</span> <span class="k">asm</span><span class="p">(</span><span class="s">"coctx_swap"</span><span class="p">);</span>
<span class="p">};</span>
</code></pre></div></div>

<p>libco switches coroutine contexts with the <code class="language-plaintext highlighter-rouge">coctx_swap</code> function. It takes two <code class="language-plaintext highlighter-rouge">coctx_t *</code> arguments. The first points to where the current coroutine context is saved. The second points to the context to switch in:</p>

<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">.globl</span> <span class="nv">coctx_swap</span>
<span class="nl">coctx_swap:</span>
    <span class="nf">movl</span> <span class="mi">4</span><span class="p">(</span><span class="o">%</span><span class="nb">esp</span><span class="p">),</span> <span class="o">%</span><span class="nb">eax</span>              <span class="c1">;eax = *(esp+4) get the first argument coctx_t</span>
    <span class="nf">movl</span> <span class="o">%</span><span class="nb">esp</span><span class="p">,</span> <span class="mi">28</span><span class="p">(</span><span class="o">%</span><span class="nb">eax</span><span class="p">)</span>             <span class="c1">;coctx_t.regs[7] = esp </span>
    <span class="nf">movl</span> <span class="o">%</span><span class="nb">ebp</span><span class="p">,</span> <span class="mi">24</span><span class="p">(</span><span class="o">%</span><span class="nb">eax</span><span class="p">)</span>             <span class="c1">;coctx_t.regs[6] = ebp</span>
    <span class="nf">movl</span> <span class="o">%</span><span class="nb">esi</span><span class="p">,</span> <span class="mi">20</span><span class="p">(</span><span class="o">%</span><span class="nb">eax</span><span class="p">)</span>             <span class="c1">;coctx_t.regs[5] = esi</span>
    <span class="nf">movl</span> <span class="o">%</span><span class="nb">edi</span><span class="p">,</span> <span class="mi">16</span><span class="p">(</span><span class="o">%</span><span class="nb">eax</span><span class="p">)</span>             <span class="c1">;coctx_t.regs[4] = edi</span>
    <span class="nf">movl</span> <span class="o">%</span><span class="nb">edx</span><span class="p">,</span> <span class="mi">12</span><span class="p">(</span><span class="o">%</span><span class="nb">eax</span><span class="p">)</span>             <span class="c1">;coctx_t.regs[3] = edx</span>
    <span class="nf">movl</span> <span class="o">%</span><span class="nb">ecx</span><span class="p">,</span> <span class="mi">8</span><span class="p">(</span><span class="o">%</span><span class="nb">eax</span><span class="p">)</span>              <span class="c1">;coctx_t.regs[2] = ecx</span>
    <span class="nf">movl</span> <span class="o">%</span><span class="nb">ebx</span><span class="p">,</span> <span class="mi">4</span><span class="p">(</span><span class="o">%</span><span class="nb">eax</span><span class="p">)</span>              <span class="c1">;coctx_t.regs[1] = ebx</span>

    <span class="nf">movl</span> <span class="mi">8</span><span class="p">(</span><span class="o">%</span><span class="nb">esp</span><span class="p">),</span> <span class="o">%</span><span class="nb">eax</span>              <span class="c1">;eax = *(esp+8) get the second argument coctx_t</span>
    <span class="nf">movl</span> <span class="mi">4</span><span class="p">(</span><span class="o">%</span><span class="nb">eax</span><span class="p">),</span> <span class="o">%</span><span class="nb">ebx</span>              <span class="c1">;ebx = coctx_t.regs[1] </span>
    <span class="nf">movl</span> <span class="mi">8</span><span class="p">(</span><span class="o">%</span><span class="nb">eax</span><span class="p">),</span> <span class="o">%</span><span class="nb">ecx</span>              <span class="c1">;ecx = coctx_t.regs[2] </span>
    <span class="nf">movl</span> <span class="mi">12</span><span class="p">(</span><span class="o">%</span><span class="nb">eax</span><span class="p">),</span> <span class="o">%</span><span class="nb">edx</span>             <span class="c1">;edx = coctx_t.regs[3]            </span>
    <span class="nf">movl</span> <span class="mi">16</span><span class="p">(</span><span class="o">%</span><span class="nb">eax</span><span class="p">),</span> <span class="o">%</span><span class="nb">edi</span>             <span class="c1">;edi = coctx_t.regs[4] </span>
    <span class="nf">movl</span> <span class="mi">20</span><span class="p">(</span><span class="o">%</span><span class="nb">eax</span><span class="p">),</span> <span class="o">%</span><span class="nb">esi</span>             <span class="c1">;esi = coctx_t.regs[5] </span>
    <span class="nf">movl</span> <span class="mi">24</span><span class="p">(</span><span class="o">%</span><span class="nb">eax</span><span class="p">),</span> <span class="o">%</span><span class="nb">ebp</span>             <span class="c1">;ebp = coctx_t.regs[6] </span>
    <span class="nf">movl</span> <span class="mi">28</span><span class="p">(</span><span class="o">%</span><span class="nb">eax</span><span class="p">),</span> <span class="o">%</span><span class="nb">esp</span>             <span class="c1">;esp = coctx_t.regs[7] </span>

  <span class="nf">ret</span>
</code></pre></div></div>

<p>Use the figure below as a reference. The stack before calling <code class="language-plaintext highlighter-rouge">coctx_swap</code> is the green part. The <code class="language-plaintext highlighter-rouge">call</code> instruction pushes the return address.</p>

<p>At $L3$, <code class="language-plaintext highlighter-rouge">esp</code> is at the position shown in the figure, and we enter the <code class="language-plaintext highlighter-rouge">coctx_swap</code> function.</p>

<p>$L3-L10$ save the register values into the <code class="language-plaintext highlighter-rouge">coctx_t</code> pointed to by the first argument.</p>

<p>$L12-L19$ read the information in the <code class="language-plaintext highlighter-rouge">coctx_t</code> pointed to by the second argument into the registers. This restores the context.</p>

<p>The <code class="language-plaintext highlighter-rouge">ret</code> instruction at $L21$ pops <code class="language-plaintext highlighter-rouge">eip</code>, the entry of the function <code class="language-plaintext highlighter-rouge">pfn</code>, and jumps to <code class="language-plaintext highlighter-rouge">pfn</code>. Now the yellow stack is built as the stack space before calling pfn (compare with the yellow part in the red box of Figure 4). The <code class="language-plaintext highlighter-rouge">NULL</code> reserved earlier is exactly what the <code class="language-plaintext highlighter-rouge">ret</code> instruction needs here.</p>

<p><img src="/assets/images/libco-coroutine/coctx_swap.png" alt="Figure 6. coctx_swap context switching" width="90%" />
<em>Figure 6. coctx_swap context switching</em></p>

<h2 id="symmetric-and-asymmetric-coroutines">Symmetric and Asymmetric Coroutines</h2>

<p>Above, we saw how two coroutines switch contexts. For scheduling the coroutines, there are two main ways, as the figure below shows: symmetric coroutines and asymmetric coroutines.</p>

<p>In symmetric coroutines, all coroutines run as equals. They call <code class="language-plaintext highlighter-rouge">transfer</code> to jump freely between each other.</p>

<p>Asymmetric coroutines work like a coroutine call stack. At the start, only the main coroutine is on the stack. Calling <code class="language-plaintext highlighter-rouge">resume</code> pushes another coroutine onto the stack and switches to its context. When a coroutine finishes, or calls <code class="language-plaintext highlighter-rouge">yield</code> explicitly, it is popped and we switch back to the previous coroutine context. Usually the coroutine call stack is not deep. In practice, most switching is between the main coroutine (IO) and logic coroutines.</p>

<p>In real applications, symmetric coroutines cost more to maintain, and it is hard to keep track of the call chain. So asymmetric coroutines are more common. The libco described here is an asymmetric coroutine library.</p>

<p><img src="/assets/images/libco-coroutine/symmetric-asymmetric-co.png" alt="Figure 7. Symmetric/asymmetric coroutines" width="100%" />
<em>Figure 7. Symmetric/asymmetric coroutines</em></p>

<h2 id="private-stack-and-shared-stack">Private Stack and Shared Stack</h2>

<p>From the <code class="language-plaintext highlighter-rouge">coctx_make</code> code above, we can see that the coroutine stack in libco is about <code class="language-plaintext highlighter-rouge">ss_size</code> in size. By default, when you call <code class="language-plaintext highlighter-rouge">co_create</code> to create a new coroutine in libco, it allocates <code class="language-plaintext highlighter-rouge">ss_size</code> of 128K on the heap and points <code class="language-plaintext highlighter-rouge">ss_sp</code> at it. Each coroutine then has its own stack space. This is called the “private stack” mode, also called stackful mode. In private stack mode, a context switch only needs to save and load registers, so the cost is low. But each coroutine’s stack size is fixed, so a lot of stack space is wasted.</p>

<p>In contrast to the private stack, libco offers a shared stack mode, also called stackless mode. In a shared stack, all coroutines use one fixed-size block of stack space (128K by default in libco). When a coroutine switches out, libco uses <code class="language-plaintext highlighter-rouge">malloc</code> to get a block of memory sized to the stack currently in use, and copies the shared stack content out to it. This uses memory more sensibly, but a context switch costs more.</p>

<h2 id="conclusion">Conclusion</h2>

<p>So far, we have used the libco source to cover context switching, the core part of coroutines. Thank you for reading. If you have any questions or thoughts, or find any mistake in this post, please <a href="mailto:changliu0828@gmail.com">let me know</a>.</p>

<h2 id="references">References</h2>

<ol>
  <li><a href="https://blog.csdn.net/weixin_43705457/article/details/106863859">libco source code analysis, csdn (in Chinese)</a></li>
  <li><a href="http://purecpp.org/purecpp/static/64a819e99584452aab70a7f9c307717f.pdf">libco talk, Li Fangyuan (in Chinese)</a></li>
</ol>]]></content><author><name></name></author><summary type="html"><![CDATA[libco Source Notes (1): Coroutines and Context Switching]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://changliu.me/assets/images/social-card.png" /><media:content medium="image" url="https://changliu.me/assets/images/social-card.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>