<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

 <title>菜菜博士 - 博士在网络的家</title>
 <link href="https://microcai.org/feed.xml" rel="self"/>
 <link href="https://microcai.org/"/>
 <updated>2026-04-22T15:20:54+00:00</updated>
 <id>https://microcai.org</id>
 <author>
   <name></name>
   <email></email>
 </author>

 
 <entry>
   <title>编译期构造只读容器</title>
   <link href="https://microcai.org/2026/04/22/compile-time-container.html"/>
   <updated>2026-04-22T00:00:00+00:00</updated>
   <id>https://microcai.org/2026/04/22/compile-time-container</id>
   <content type="html">&lt;p&gt;我在设计变频器的红外遥控处理的时候，定义了一个 map 容器。
这个容器很简单，就是一个 int 对 int 的映射。&lt;/p&gt;

&lt;p&gt;把 红外遥控器的按键ID, 映射到我内部定义的一个 枚举命令id 上。&lt;/p&gt;

&lt;p&gt;这样只要修改这个 map 容器，就可以适配多种红外遥控。&lt;/p&gt;

&lt;p&gt;但是，我发现，这个 map 容器哪怕定义为 const 变量也没用，他依旧是分配在内存里的。而且是分配在宝贵的堆内存里。&lt;/p&gt;

&lt;p&gt;不仅仅如此，这个 map 容器的构造还会在运行时分配大量的细碎内存块。&lt;/p&gt;

&lt;p&gt;除了占用运行时内存，他对单片机flash的占用并不消失。因为构造这个map所需的原始数据还是需要躺在 .rodata 段里。&lt;/p&gt;

&lt;p&gt;既然这个 map 容器是只读的，那么为啥我需要在运行时分配这么多的内存？
直接在编译期就构造好一个map，然后在运行时直接使用，岂不更好？&lt;/p&gt;

&lt;p&gt;我试着给 std::map 前面添加一个 constexpr 修饰符，结果编译失败。
看来 std.map 并不能简单的通过 constexpr 变成编译期构造。&lt;/p&gt;

&lt;p&gt;由于 单片机的内存有 8KB 之巨，很长一段时间里我都没有遇到内存不足问题。直到最近为了缩减代码体积，把一些本该无栈协程实现的代码替换成了有栈协程。编译后能缩减数个KB的体积。
结果遇到莫名其妙的硬件错误。调试了许久才发现原来是内存分配失败导致的。&lt;/p&gt;

&lt;p&gt;于是想在其他地方扣点内存出来，于是重新盯上了这个 map 容器。&lt;/p&gt;

&lt;p&gt;我最初的做法本来是想自己手搓一个。
但是，如此需求，难道真只有我有吗？&lt;/p&gt;

&lt;p&gt;如果并不是我独有的需求，应该已经有前人已经实现过了。不能动不动就重复发明轮子呀！&lt;/p&gt;

&lt;p&gt;于是我找啊找，找到了这个 &lt;a href=&quot;https://github.com/serge-sans-paille/frozen&quot;&gt;frozen&lt;/a&gt;  库。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp++&quot;&gt;
#include &amp;lt;map&amp;gt;

static const std::map&amp;lt;ir_command, UserCommand&amp;gt; ir_command_map = {
	{0x0002000D, UserCommand::speed_0},
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样的代码，被替换成了&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-cpp++&quot;&gt;#include &amp;lt;frozen/map.hpp&amp;gt;

static constexpr frozen::map&amp;lt;ir_command, UserCommand, 41&amp;gt; ir_command_map = {
	{0x0002000D, UserCommand::speed_0},
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后重新编译，代码体积略微膨胀。但是运行时的堆内存占用一下减小千余字节。
代码膨胀，是因为原来编译器会把这 41 个 KV 对，一共 82 个数字存在 .rodata 段里, 然后在 启动的时候调用构造函数构造 map。如今编译器直接在 .rodata 段里构造出一个 map 容器的二叉树。在运行时直接在这个只读的，放在 FLASH 而不是 RAM 里的 二叉树进行查找。不需要构造了。因此减少了 RAM 的使用。但是存在 .rodata 段的二叉树，比原始的82个整数要多占一些空间。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>搞懂环路补偿</title>
   <link href="https://microcai.org/2026/02/13/loop-compensation-explain.html"/>
   <updated>2026-02-13T00:00:00+00:00</updated>
   <id>https://microcai.org/2026/02/13/loop-compensation-explain</id>
   <content type="html">&lt;p&gt;经常设计模拟电路的朋友会发现，运放的输出端通常需要串入一个补偿网络到反向输入端。&lt;/p&gt;

&lt;p&gt;如下图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/op_loop_compensation.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;R1 C1 C2 构成了一个补偿网络。&lt;/p&gt;

&lt;p&gt;这是为何呢？&lt;/p&gt;

&lt;h1 id=&quot;闭环系统的稳定性&quot;&gt;闭环系统的稳定性&lt;/h1&gt;

&lt;p&gt;闭环系统，就是消灭跟踪误差。
消灭的方式，就是对误差进行放大，然后进行反向调节——“负反馈”。&lt;/p&gt;

&lt;p&gt;理想的运放，有无穷大的放大倍数，和 0 延迟。&lt;/p&gt;

&lt;p&gt;然而现实世界不是理想世界，显示中的运放是有延迟的，放大倍数也不是无穷大。&lt;/p&gt;

&lt;p&gt;放大倍数对运放的稳定性其实关系不大，主要是放大延迟：即输入信号发生改变的时候，输出信号能多快的反应过来并跟踪输入的变化。&lt;/p&gt;

&lt;p&gt;为什么这个反应速度很重要呢？&lt;/p&gt;

&lt;p&gt;因为反应慢了，会导致“运放振荡”。&lt;/p&gt;

&lt;p&gt;为什么反应慢了会振荡呢？&lt;/p&gt;

&lt;p&gt;如上图的运放，在稳态时，其2个输入端的电压是相同的。当输入信号发生变化的时候，比如，增加。
则运放需要提高输出电压，以抬高其反相输入端的电压，最终使得2个输入端的电压相同。&lt;/p&gt;

&lt;p&gt;如果运放抬高输出端的电压不够快，有延迟，此时同相输入的电压就已经跌落。那么经过延迟后，运放的反相输入端电压就会超过同相输入端。&lt;/p&gt;

&lt;p&gt;此时运放不得不降低输出电压，而他每次进行调节的时候，都总是反应慢一拍，导致每次都会“过调”。过调后又要反向调节，但是因为延迟的缘故，始终无法稳定下来。
于是运放就进入了振荡。&lt;/p&gt;

&lt;p&gt;也就是，输入的一个扰动，会导致运放的输出产生非常多的来回振荡。而这个来回振荡又反过来引起跟多的来回振荡。&lt;/p&gt;

&lt;p&gt;那么如何从数学上精确的描述运放的这种“来回振荡”的行为呢？&lt;/p&gt;

&lt;p&gt;专门用来描述闭环系统的稳定性的数学工具，就是 频率响曲线，包括 增益曲线和相位延迟两个子图。&lt;/p&gt;

&lt;p&gt;也就是，运放对每一个频率的输入，他对这个频率会产生多大幅度的“增益”（放大倍数），以及对这个频率他会有多少延迟（转化为相位延迟表示）。&lt;/p&gt;

&lt;p&gt;如下面这个：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/loop_gain_phase_curve.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;一条增益曲线，一条相位延迟曲线，就完整的描述了一个系统的行为逻辑了。
那么，对于一个闭环系统，什么样的曲线，算是“稳定”的呢？（稳定，即发生扰动后不会发生来回振荡）&lt;/p&gt;

&lt;p&gt;答案是，在整个增益 &amp;gt;0db 的部分，其相位延迟全部都 &amp;lt; 180度。在工程上为了留有一定的余量，通常会要求延迟 &amp;lt;150度，乃至 120度。增益 = 0db 的频率，姑且起个名字叫穿越频率。&lt;/p&gt;

&lt;p&gt;增益 &amp;lt;1 的部分，哪怕延迟 &amp;gt; 180，导致的振荡也是“阻尼振荡”。因此系统还是稳定的，只不过不是很优秀。也就是说，我们希望在延迟 =180度的那个频率，他离穿越频率越远越好，系统在扰动下的恢复时间就会越短。&lt;/p&gt;

&lt;p&gt;于是就有了相位裕度。相位裕度指的是在穿越频率上，距离180度的不稳定极限，还剩下多少。相位裕度越大，说明系统就越稳定。&lt;/p&gt;

&lt;p&gt;为什么延迟要 &amp;lt; 180度 呢？ 因为运放是一个“负反馈”系统。如果运放的输出和输入之间有了 180度的延迟，则运放实质上会变成一个正反馈。正反馈就使得运放产生自激振荡。&lt;/p&gt;

&lt;h1 id=&quot;如何修正反馈的稳定性&quot;&gt;如何修正反馈的稳定性&lt;/h1&gt;

&lt;p&gt;修正它，有一个很简单的办法：将超过穿越频率的信号给过滤掉。
只要输入给运放的信号，其频率成分总是小于穿越频率，运放就没有机会不稳定了。&lt;/p&gt;

&lt;p&gt;那么为什么一般是在输出端和反向输入端之间，连接高通滤波器而不是在输入端使用低通滤波器呢？
因为补偿网络还有一个重要功能，是调节穿越频率本身！&lt;/p&gt;

&lt;p&gt;这个功能特点，对于“电源类应用”是非常必要的。&lt;/p&gt;

&lt;p&gt;补偿网络，通过前馈调节，提前补偿运放的反馈链，从而实质上从输出去除掉部分信号。所以补偿网络，从形式上看，通常是一个高通滤波器。不响应低频信号，从而不调节低频增益。对于高频信号，让运放的输入输出直通从而压制高频增益。
因此，补偿网络的截止频率，就变成了此时整个环路的穿越频率。而在输入端使用低通滤波器则起不到这个作用。同时补偿网络对低频的相位延迟影响微乎其微，而输入低通滤波器则会引入更多的相位延迟，并不利于环路的稳定。&lt;/p&gt;

&lt;h1 id=&quot;使用何种类型补偿网络&quot;&gt;使用何种类型补偿网络&lt;/h1&gt;

&lt;p&gt;使用何种类型的补偿网络，关键在于系统未进行补偿的情况下，其环路的频率响应情况。理想的补偿网络，是系统使用了补偿网络后，其频响曲线变成一条直线。
然而这是不可能的。因此，退而求其次，选择补偿网络，就是压制高频增益，降低穿越频率，提高相位裕度。对于一些出现二次穿越的情况，还需要使用带通滤波器设计的环路补偿器。&lt;/p&gt;

&lt;p&gt;因此，系统原有的频响特性，决定了补偿的类型。&lt;/p&gt;

&lt;p&gt;为什么说电流模式 PWM 控制好？好就好在他的补偿器容易设计。电流模式pwm控制器，其补偿网络简单到，只需要将穿越频率压低到开关频率的1/10就能使环路稳定。而电压型pwm控制，补偿网络还要引入微分积分器，对占空比的变化将要引起的电压变化进行预测—— 也就是不能简单的压制穿越频率。或者说，电压信pwm控制器，不进行补偿的情况下，其极点频率太低，使得只是降低穿越频率变得不可能。需要降低穿越频率的同时提高极点频率——简单的来说，就是补偿器自身的频响应要实现相位提前。&lt;/p&gt;

&lt;h1 id=&quot;测量频响曲线&quot;&gt;测量频响曲线&lt;/h1&gt;

&lt;p&gt;所以，要设计补偿网络，前提是需要先知道原来系统的频响曲线。&lt;/p&gt;

&lt;p&gt;有2个办法可以获得它：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;使用电路仿真软件。&lt;/li&gt;
  &lt;li&gt;使用频谱仪。&lt;/li&gt;
  &lt;li&gt;用传递函数算&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;我个人是更倾向于推荐使用仿真软件。因为它成本更低。但是如果清晰的知道每个步骤的传递函数，也可以算（不用手算，可以编程算）。编程算的最简单办法，其实是使用 matlab (笑)。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>极坐标变换中的负电流</title>
   <link href="https://microcai.org/2026/01/16/negative-current-in-polar-foc.html"/>
   <updated>2026-01-16T00:00:00+00:00</updated>
   <id>https://microcai.org/2026/01/16/negative-current-in-polar-foc</id>
   <content type="html">&lt;p&gt;三相电流进行采样后，采集到的电流是有正有负的。因为采集的是相电流，而且相电流是交流电，因此必然会出现有正有负的情况。&lt;/p&gt;

&lt;p&gt;相电流经过矢量合成，必然可以表现为在极坐标上的一条向量。&lt;/p&gt;

&lt;p&gt;此时，这个向量，只有幅值和角度。&lt;/p&gt;

&lt;p&gt;这个幅值，是正数。&lt;/p&gt;

&lt;p&gt;我们都知道，电机在运行过程中，会产生反电动势，反电动势高于输入电压的情况下，电机会表现为发电机，对外输出能量。在机械上就是表现为，产生拖拽，也就是俗称电机输出了负扭矩。&lt;/p&gt;

&lt;p&gt;在极坐标上看，就是电流的矢量和转子产生了负夹角。&lt;/p&gt;

&lt;p&gt;这个负夹角，对控制算法来说，实际上是不利的。&lt;/p&gt;

&lt;p&gt;为何这么说呢？&lt;/p&gt;

&lt;p&gt;这意味着控制算法不仅仅要考虑电流的大小，还要考虑电流的大小（扭矩大小）还得考虑和转子的夹角是正（定子磁场带着转子走）还是负（转子被定子磁场拖拽）。&lt;/p&gt;

&lt;p&gt;因此，在三相电流采样后转电流矢量这个坐标转化算法里，必须同时输入转子角度，然后把负夹角处理为负电流，正夹角。&lt;/p&gt;

&lt;p&gt;这样，电流采样模块输出的就是负电流，而负电流，就可以被 电流调节PID模块 正确的处理，产生更高的输出电压来抵消反电动势，以便扭转负电流为正电流。&lt;/p&gt;

&lt;p&gt;同时， SVPWM 调制模块，也必须接受 “负电压” 这样的输入，以简化 电流调节PID模块 的控制算法。&lt;/p&gt;

&lt;p&gt;所谓负电压，其实就是电压矢量转180度。&lt;/p&gt;

&lt;p&gt;经过这样负电流负电压的处理后，正反转控制，就简单的变成了正电流和负电流的控制，同时避免了 “夹角PID模块” 对反转的考虑。 注意，负电流！=发电。只有 电流和电压的乘积为负，才是发电。&lt;/p&gt;

&lt;p&gt;反转的时候，电流采集到的是负电流，电流环输出的是负电压。功率一样是正的。夹角一样是正 90度。也就是说，负责调节电流和转子夹角的PID算法，不需要根据正反转决定是正90度还是负90度夹角，统统处理成90度。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Clarke-park 变换毫无意义</title>
   <link href="https://microcai.org/2026/01/01/clarke-park-is-useless.html"/>
   <updated>2026-01-01T00:00:00+00:00</updated>
   <id>https://microcai.org/2026/01/01/clarke-park-is-useless</id>
   <content type="html">&lt;p&gt;电机领域有个很神奇的现象：人云亦云，不会思考。&lt;/p&gt;

&lt;p&gt;比如，早期有个教授写教材的时候说反电动势为梯形波的叫无刷，正弦波的叫永磁同步。
然后大家就跟着他这么说了，完全不会思考。&lt;/p&gt;

&lt;p&gt;而国内做电机的，基本上教程来自德州仪器的电机库。几十年时间过去了，还没有跳出德州仪器当年给国内的人画的地牢。完全不会思考。
离了 Clarke-park 变换，就不会做 foc 了。
更有人写 foc 教程的时候，大言不惭的说，FOC离不开Clarke-park 变换，还说没有这个变换就没有 foc。&lt;/p&gt;

&lt;p&gt;笑掉大牙，可是从业者都是这样的。不会思考，人云亦云。
长江大学的唐老师说的好，国内搞硬件的人，水平很差，喜欢对国外顶礼膜拜， 有问题都是自己的问题，但是如果国产的东西他自己水平次用出问题了，他又要怪国产芯片不行了，不找自己的问题了。
这种心态放到电机研究上，那就是永远不能跳出当年德州仪器规划出来的电机控制框架。
特别是，当德州仪器和 Matlab 合作搞出 simulink 电机控制开发套餐，国人就更不敢挑战了。
因为硬件领域是有一条鄙视链的，算法研究 》底层代码编写》搞应用落地的。
而 simulink 正好占据了电机算法研究领域的绝对统治地位。
搞研究的都用 simulink，他还处在鄙视链最上层，那么最下层的人就绝对不敢挑战权威，跳出 simulink 里的那套框架了。
特别是，中国搞电机研究的人，主要集中在高校里的 simulink算法党，和各大血汗工厂里的牛马应用党，最缺的是中间写底层代码的。因为这些活，被德州仪器等芯片工厂的“FAE”包了。
二十多年前，阿莫电子论坛上，有一个牛人在8051上手搓无刷电机驱动，被一些牛马党膜拜的同时，被另一批人嘲讽，有“原厂电机库” 好用吗？
他们的论据是，原厂电机库有很多不公开的高级算法，肯定是汇编写的，调用芯片底层资源的，你 C 写的肯定不行。
那么这个和 clarke -park 变换有什么关系呢？
因为德州仪器最早公开电机控制算法，“大公无私”的向下游公司提供电机控制库。还手把手的教他们怎么用。（大公无私打个引号，因为芯片是卖钱的。但是也算大公无私，因为他毕竟教了，而不是像日本公司那样只收钱，不教你）。
还做一系列的开发板，和 simulink 无缝集成。
无缝集成的意思是，在 simulink里动动鼠标，改改参数，就能让 simulink 自动生成一套基于 C2000 架构的电机控制代码。然后 simulink 自动调用德州仪器的编译器，下载到他的开发板上运行，一气呵成，只需要点  simulink 里的 “RUN”。。。。一行代码都不用写。
而这套体系，就是基于  Clarke -park 变换构建的。
可以说，这套 simulink 电机开发套件，就是国内一切电机开发的源头。
国内所有的论文，都是基于这套架构上做的开发。研究各种控制理论，也必须得先在这套架构上跑过才能发论文。&lt;/p&gt;

&lt;p&gt;这个就是 唯“clarke-park变换”是尊的源头。&lt;/p&gt;

&lt;p&gt;那么这个变换好不好呢？当然好，不好就不会被德州仪器选中。
但是，唯“clarke-park变换”是尊，正说明了国内的人不思进取，人云亦云。&lt;/p&gt;

&lt;p&gt;因为，实际上，还有一个更好的。只不过，这套更好的方案，不太适合。。。 simulink 这套以“信号控制论”为基础设计的框架进行算法设计。只适合手搓 代码的情况下使用。&lt;/p&gt;

&lt;p&gt;手搓 代码 可就要了高校那群算法党老鼻子命了。他们自然不用。&lt;/p&gt;

&lt;p&gt;而处于鄙视链上层的人不用，下层的人自然就不敢用了。&lt;/p&gt;

&lt;p&gt;那说了半天，我要搞个什么变换呢？&lt;/p&gt;

&lt;p&gt;答案就是没有变换，只需要改下使用的坐标系。&lt;/p&gt;

&lt;p&gt;Clarke-park 变换解决的问题，其实是直角坐标系下描述三相电麻烦的问题。
那我不使用直角坐标系不就完了？&lt;/p&gt;

&lt;p&gt;其实，如果你去问电工，他如何描述三相电，你问他会不会给你进行 Clarke 变换。
答案就是不会。电工描述三相电，用的就是一个特殊的坐标系，“极坐标”。
电工对三相电的大小，用的是 “电压， 频率，相位” 三要素。而不是给你  Clarke-park 变换一下再用 Alpha Beta 坐标。
等等，交流电的瞬时电压时刻在变，怎么能用“电压”去描述呢？
在直角坐标系下，当然不能。&lt;/p&gt;

&lt;p&gt;在直角坐标下，电压作为一个坐标，是会时刻变化的。这也是为何使用直角坐标系下，无法对交流电进行“直流控制”的原因。&lt;/p&gt;

&lt;p&gt;但是，如果使用 极坐标，问题就迎刃而解。在极坐标里，描述一个三相电流矢量，用的是三相电的 （幅值，相位），对，哪怕是有3条电流，只需要2个维度的描述信息。
而且这个描述信息里的“幅值”是一个直流量。我们要对交流电机进行直流控制的时候，就只需要控制“幅值”这个直流量就可以了。
在极坐标系里，描述一个 foc 向量，用不到复杂的变换，就是简单粗暴的 幅值+角度。
幅值+角度作为一个整体描述的是三相电压的矢量，如何对应三相各自的电压，其实就是简单的  Ua = 幅值&lt;em&gt;sin(角度）， Ub = 幅值&lt;/em&gt;sin(角度+120），Uc = 幅值*sin(角度+240）
而“角度”这个东西，我们可以从编码器直接获取。
于是，foc的扭矩控制，就是简单的 PID 算法控制 幅值这个直流量。不涉及到任何变换和反变换操作。
也就是说，如果使用极坐标系，电机控制根本用不到 clarke 变换。因为没有这些变换，于是就没有了晦涩难懂的什么 D 坐标轴，Q坐标轴。
一切都是非常直观的，高中生都能理解的，交流电的本质定义，幅值和相位。
好了，一些在 DQ坐标系下很晦涩难懂的东西，在极坐标系你们会豁然开朗。&lt;/p&gt;

&lt;p&gt;比如 D轴电流，Q轴电流，到底是什么意思。&lt;/p&gt;

&lt;p&gt;其实，电工早就有了更通俗易懂的语言：无功，有功。&lt;/p&gt;

&lt;p&gt;洋人最擅长的东西，就是语言上制造行业壁垒。pig meet 不能说，要说 pork。买涨不能说，要说买多。
无功，顾名思义，就是没有做工。为啥没有做工？因为电压和电流有相位差，用有效电压*有效电流算出来的功率会比实际功率大。这个比实际功率大的就叫“视在功率”。视在功率=无功+有功。有功占视在功率的比值呢，就是所谓的功率因素了。
然后功率因素正好，恰恰等于电压电流的相位差的 余玄值。于是一些教科书会定义功率因素=cos（电压电流相位差）
为啥会有相位差？因为电机的线圈有电感啊！&lt;/p&gt;

&lt;p&gt;电感会导致电流滞后电压。于是就有了相位差。&lt;/p&gt;

&lt;p&gt;foc控制，目的是控制磁场的矢量，而不是控制电压的矢量。必须得把这个相位差考虑进去。
因此，直接将转子位置的角度 + 90度，来生成定子的电压矢量，是不合适的。因为磁场是电流产生的，而不是电压。因此 foc控制，本质上是控制 “电流”的矢量角度，比转子磁场提前90度。要做到这点，电压实际上要提前更多。
至于提前多少？
在老式的有刷电机里，靠的是改变电刷的位置实现电压提前。但是这么做实际上很不精确。
因为电机的功率因素是会不断变化的，也就是说电压和电流的相位差并不固定。
因此，在foc控制里，需要精确的测量电压和电流的相位差，然后把这个相位差补偿进去。&lt;/p&gt;

&lt;p&gt;在 clarke-park 变换里，这个补偿的算法，靠的是 把 IQ=0 做为控制目标。
为啥呢？因为  clarke-park 变换里，以转子的电角度为标准进行变换，如果电流矢量和转子的电角度差90度，则正好在它的视角里，无功=0。
如果把 IQ！=0 作为控制目标，就进入了所谓的“弱磁控制”。
本质上，就是让电流矢量比转子的电角度提前超过90度。这样因为磁场夹角超过90度，导致扭矩下降。。。。 但是，带来的另一个好处是反电动势下降。于是可以降低电机的 KV值，提高最高转速。缺点就是电压和电流的相位差进一步加大，也就是功率因素下降。功率因素下降，自然整套体系的效率也跟着下降。对续航是不利的，更好的做法是物理弱磁，也就是变KV电机=变磁通电机。
经过  clarke-park 变换后，很多人就失去了弱磁控制的本质理解了。因为变换来变换去，把你绕晕了。但是如果直接使用极坐标，不进行变换，你一眼就能看出来弱磁控制的本质。
所以，我反对 clarke-park 变换，就是因为这种变换遮掩了电机控制的本质，把简单问题复杂化。
包括 IQ=0 控制，也因为 clarke-park 变换 导致很多人云里雾里不知道为啥要这么干。只知道这么干扭矩最大。
但是在极坐标系下，你一眼就能看出来，这是为了对抗定子线圈电感的电流滞后效应。&lt;/p&gt;

&lt;p&gt;另外在异步电机控制里，因为没有了“转子角度”这个东西，导致 park 变换输入的角度就回归回使用电压的相位角了。于是在异步电机控制里，clarke-park 变换后的 DQ坐标，本质上就是有功和无功。
但是，它们又要起一个新名词来糊弄你，叫 励磁电流，和做工电流。这是上坟烧报纸呢。
真正的励磁电流，是转子转差率导致转子切割磁感应线产生的电流。这个和 park变换后看到的 IQ 远的很呢。&lt;/p&gt;

&lt;p&gt;那么，在抛弃了 clarke-park 变换后，到底要怎么进行foc控制呢？&lt;/p&gt;

&lt;p&gt;首先，我们写这么一个底层函数， SetOutputVoltage(U, angle);
2个参数，一个是幅值，一个是角度。
SetOutputVoltage 最简单的做法是 Ua = 幅值&lt;em&gt;sin(角度）， Ub = 幅值&lt;/em&gt;sin(角度+120），Uc = 幅值*sin(角度+240）获得三相的输出电压，然后根据三相的输出电压计算占空比，配置到硬件 PWM 输出上。
当然，这种计算出来的波形叫 SPWM, 电压利用率不高。可以再进行一次三角波注入，计算出来的波形就叫 SVPWM 了（笑）。&lt;/p&gt;

&lt;p&gt;接着，我们要写2个采样函数，一个叫 GetRotorAngle 一个叫 GetCurrent。GetRotorAngle 返回转子的角度（有传感器就直接读传感器，没传感器就用软件去估算，怎么估算就是另外的话题了）。GetCurrent返回三相电流的 “幅值和相位”。没错，不是直接返回三个相电流，而是返回计算后合成的 幅值和相位。
然后写一个控制环在 pwm中断里运行。这个控制环的计算很简单：&lt;/p&gt;

&lt;p&gt;rotorAngle = GetRotorAngle(); // 获取转子角度
auto [current, currentAnagle] = GetCurrent(); // 获取电流幅值和相位
PID_caculated_U = toqueControl(current); // 根据电流计算电压
auto anglediff = last_VoltageAngle - currentAnagle ; // 计算电流电压的相位差
last_VoltageAngle = rotorAngle + 90 + anglediff; // 把相位差补进去
SetOutputVoltage(PID_caculated_U,   last_VoltageAngle)；&lt;/p&gt;

&lt;p&gt;输出电压具体多少，在 toqueControl 里计算。要根据当前的电流值和目标进行反馈控制。内环是扭矩控制，是基础。如果是速度/位置模式，要再套一个外环。外环的输出是扭矩，再交给内环控制。&lt;/p&gt;

&lt;p&gt;GetCurrent 如何返回电流的幅值和相位呢？因为硬件采集到的，实际上是独立的 ABC 三相的相电流，里面没有幅值信息，也没有相位信息。
实际上很简单，就是矢量合成。合成后的矢量，长度就是幅值，角度就是相位。
因此，可以套用矢量合成公式进行。也就是，输入的是 Ia Ib Ic 三相电流，
其长度是&lt;/p&gt;

&lt;p&gt;$
\sqrt (2/3*(Ia^2 + Ib^2 + Ic^2))
$&lt;/p&gt;

&lt;p&gt;而相位，则是  asin(Ia/幅值)&lt;/p&gt;

&lt;p&gt;在极坐标体系下，会发现不需要 atan 运算。而这个运算在 clarke-park 体系下是必须的。为了避免 atan 在 90度下的无穷大问题，一般还要使用 atan2 这个函数。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>运算放大器运算了什么</title>
   <link href="https://microcai.org/2025/12/29/opap-how-do-it-do.html"/>
   <updated>2025-12-29T00:00:00+00:00</updated>
   <id>https://microcai.org/2025/12/29/opap-how-do-it-do</id>
   <content type="html">&lt;h1 id=&quot;什么是运放&quot;&gt;什么是运放&lt;/h1&gt;

&lt;p&gt;模拟电路的基础元件，有 二极管，三极管，电容，电阻，电感，还有…….运放。&lt;/p&gt;

&lt;p&gt;运放，虽然这个基础结构实际上内部是由多个三极管构成的，但是，他依然是基础元件。&lt;/p&gt;

&lt;p&gt;而且这个基础元件，就好像电容电感一样，有一个“理论最优”的模型，但是实际上做不出来。&lt;/p&gt;

&lt;p&gt;这个理论模型，就是放大倍数无穷大。&lt;/p&gt;

&lt;p&gt;如果，只是放大倍数无穷大，运放是无法成为基础元件的。&lt;/p&gt;

&lt;p&gt;运放能成为基础元件，最关键的原因，是运放是一个图灵部件。&lt;/p&gt;

&lt;p&gt;运放在电路中最关键的作用，是执行“死循环”，使得模拟电路图灵完备。&lt;/p&gt;

&lt;h1 id=&quot;死循环&quot;&gt;死循环&lt;/h1&gt;

&lt;p&gt;模拟电路也是计算机。模拟计算也是计算。&lt;/p&gt;

&lt;p&gt;运放在模拟电路里最重要的功能，就是实现死循环，完成“闭环控制”。&lt;/p&gt;

&lt;p&gt;例如在一个开关电源里，运放在其中扮演的，就是精细调控占空比的角色。&lt;/p&gt;

&lt;p&gt;通过分压电阻采集目标电压（反馈输入），然后和基准电压（控制目标）进行比较，产生新的占空比电压（控制输出），最后由三角波比较器输出为占空比调制信号。&lt;/p&gt;

&lt;p&gt;这样一个 反馈输入-》比较控制目标-》输出控制信号 要变成一个死循环，快速的不断循环。
这个循环，就是由运放执行的。&lt;/p&gt;

&lt;p&gt;而且运放执行这个控制循环的速度，是数字电路望尘莫及的。运放执行这个控制循环的速度，就是运放的带宽。&lt;/p&gt;

&lt;p&gt;控制速度，不能高于开关速度。而运放的带宽远高于电力电子器件的开关速度。因此绝大多数情况下，我们要降低控制带宽。&lt;/p&gt;

&lt;p&gt;降低带宽的办法，不是让运放慢一点，而是修改反馈给运放的信号，让反馈信号慢下来。&lt;/p&gt;

&lt;p&gt;但是，运放的带宽实际上并没有下降。如果输入的信号有“延迟”。运放动作太快了，就会振荡。&lt;/p&gt;

&lt;p&gt;比如反馈电压低了，运放增大占空比，但是占空比增大后，反馈的电压不能立即反应，需要滞后一段时间。
此时运放就会因为反馈的电压一直很低，而不断增大 占空比。直到最初增大占空比的效果呈现，然后反馈回来的电压就会一下冲过头。此时运放就会反过来立即减小占空比。然后延迟达成效果后，再来个电压过低。&lt;/p&gt;

&lt;p&gt;然后运放又增大占空比。&lt;/p&gt;

&lt;p&gt;这样来回拉锯，始终不能稳定。这个就是反馈信号有延迟导致的运放振荡。&lt;/p&gt;

&lt;h1 id=&quot;运放的偿&quot;&gt;运放的偿&lt;/h1&gt;

&lt;p&gt;这个时候，就要进行一次前馈补偿。
所谓的前馈补偿，就是提前把运放 “增大占空比” 的效果给反馈给运放。免得运放认为反馈回来的信号一直不够而继续增大占空比，然后过冲。。。。&lt;/p&gt;

&lt;p&gt;在真实的占空比增大带来的反馈电压抬高之前，就提前把“反馈电压”给抬高。使得运放“以为”反馈电压已经正常了，就停止调节。&lt;/p&gt;

&lt;p&gt;这个就是运放必须要做前馈调节，而数字控制不需要的原因。因为数字控制可以直接修改算法实现。
但是运放的反馈调节算法是死的，是通过在 “反馈” 这个地方进行劫持来达成所谓的修改控制算法的目的。&lt;/p&gt;

&lt;p&gt;运放的补偿，实际上就是个预测器。&lt;/p&gt;

&lt;p&gt;提前把运放的输出结果进行预测，然后将预测值叠加在真实的反馈值之上，再送给运放。
这样运放就“认为”条件已经满足，从而停止调节。实现了运放的稳定运行，避免反复调节导致的震荡。&lt;/p&gt;

&lt;p&gt;运放的补偿通常由电容和电阻串联构成。&lt;/p&gt;

&lt;p&gt;电容的大小，决定了预测多久，电阻的大小，决定预测的结果叠多少回真实值。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>无传感器感应电机驱动</title>
   <link href="https://microcai.org/2025/12/13/sensorless-induction-motor.html"/>
   <updated>2025-12-13T00:00:00+00:00</updated>
   <id>https://microcai.org/2025/12/13/sensorless-induction-motor</id>
   <content type="html">&lt;h1 id=&quot;前言&quot;&gt;前言&lt;/h1&gt;

&lt;p&gt;我很喜欢异步电机。当然特指三相异步电机。如果是单相的，那是一点喜欢不起来。&lt;/p&gt;

&lt;p&gt;三相异步电机，如果需要精确的控制，总是需要转速传感器。有了转速传感器，就可以配合滑差率进行闭环控制。&lt;/p&gt;

&lt;p&gt;但是，传感器并不总是有的。很多时候，传感器安装不便，有的情况下，也是为了懒惰：没传感器的情况下只需要接3根线。&lt;/p&gt;

&lt;p&gt;没有传感器，如何获得转速呢？&lt;/p&gt;

&lt;p&gt;在无刷电机里，转子有磁场。有转速就可以发电。于是可以用反电动势大法获得转子的位置——连实时位置都有了，转速更有了。&lt;/p&gt;

&lt;p&gt;但是，在异步电机里，如何在无传感器的情况下获取转子速度呢？&lt;/p&gt;

&lt;h1 id=&quot;电流幅值和瞬时值&quot;&gt;电流幅值和瞬时值&lt;/h1&gt;

&lt;p&gt;异步电机说到底还是交流电驱动的。交流电过去后，就可以测量出电流的幅值和相对电压的相位差了。&lt;/p&gt;

&lt;p&gt;电流相对电压的相位差，也就是无功功率的由来。&lt;/p&gt;

&lt;p&gt;我们通过测量手段测量出来的，实际上是三条相线的相电流。Ia Ib Ic。在古代电机控制算法里，需要经过一个变换将他变成 Iq 和 Id。但是，这一个步骤，对无刷电机来说，首先需要知道电机的转子电角度，对异步电机来说，首先需要知道转差率。&lt;/p&gt;

&lt;p&gt;不如换一种变换方式，首先将他转换为通常意义上的 “电流”，也就是不带矢量的电流。这个电流，就是电功率 P=IU 里的那个电流。三相电虽然电流和电压的大小时刻在变化，但是它的功率是保持恒定的。因此他实际上有一个固定 I 和一个固定的 U. 这个固定的 I 和固定的 U 就是三相交流电的有效电压和有效电流。 这个等效电压，可以通过 $占空比*母线电压 / \sqrt{2}$计算出来，而这个等效电流，如何快速的通过 Ia Ib Ic 计算出来呢？&lt;/p&gt;

&lt;p&gt;答案是，通过矢量合成。由于 Ia Ib Ic 是在空间上错开120度的矢量，经过合成，就知道了这个合成矢量的模。&lt;/p&gt;

&lt;p&gt;于是
$
I = \sqrt{2/3*(Ia^2 + Ib^2 + Ic^2 ) }
$&lt;/p&gt;

&lt;p&gt;这样计算获得的就是每相电的有效值。&lt;/p&gt;

&lt;p&gt;因此，在任意时刻，程序手头能立刻获得的实时数据有：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1：输出电压幅值
2：输出频率
3：相电流有效值
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;而此时要求得的是，滑差率，从而确定下一个周期的 输出频率。&lt;/p&gt;

&lt;p&gt;凭借这4个参数，能否求得滑差率呢？&lt;/p&gt;

&lt;p&gt;答案是，不能。&lt;/p&gt;

&lt;p&gt;因此，还需要添加额外的参数：也就是所谓的电机参数。&lt;/p&gt;

&lt;p&gt;这个额外的电机参数是：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1：定子电阻
2：鼠笼绕组的等效电阻
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;有了这额外的2个参数，能否计算出滑差率呢？&lt;/p&gt;

&lt;p&gt;那么，我们来看，滑差率如何影响电流的幅值和相位。&lt;/p&gt;

&lt;p&gt;首先我们知道，定子输入的功率P一部分被定子的电阻消耗，而另一部分不会凭空消失，肯定是转移给了转子。&lt;/p&gt;

&lt;p&gt;因此，转子消耗的功率&lt;/p&gt;

&lt;p&gt;$
Protor = Ptotal - Psator
$&lt;/p&gt;

&lt;p&gt;转子消耗的功率，一部分变成的机械能输出，而另一部分，则变成了转子的励磁电流，经过转子的电阻消耗掉了。&lt;/p&gt;

&lt;p&gt;这个比值，恰好等于转差率！&lt;/p&gt;

&lt;p&gt;也就是&lt;/p&gt;

&lt;p&gt;$
转差率= （Irotor^2*Rrotor） \div (Ptotal-Pstator)
$&lt;/p&gt;

&lt;p&gt;而转子电流的大小，恰好等于定子电流*定子匝数。这里，定子电流就是上面计算出来的相电流有效值。
于是，我们把转子的电阻和电流都换算到初级侧，就可以去掉定子匝数，只需要转子的等效电阻 Rrotor’&lt;/p&gt;

&lt;p&gt;$
转差率= （I^2*Rrotor’） \div (Ptotal-Pstator)
$&lt;/p&gt;

&lt;p&gt;这里，转子的等效电阻这个东西，是可以通过 “参数辨识” 功能获取的，不需要用户提前编码到控制器里。&lt;/p&gt;

&lt;h1 id=&quot;参数辨识过程&quot;&gt;参数辨识过程&lt;/h1&gt;

&lt;p&gt;但是，电机的转子的等效电阻，定子电阻，这个对变频器来说极其重要的参数，是不会标在电机铭牌上的。&lt;/p&gt;

&lt;p&gt;于是，变频器必须自己获得这个参数。&lt;/p&gt;

&lt;p&gt;要获得这个参数，需要变频器进行2个测试：直流测试和堵转测试。&lt;/p&gt;

&lt;p&gt;分别获取 直流测试电流和堵转电流。以及测试的时候分别测得的功率。&lt;/p&gt;

&lt;p&gt;那么，&lt;/p&gt;

&lt;p&gt;$
定子电阻 = 直流测试功率/直流测试电流
$&lt;/p&gt;

&lt;p&gt;然后，转子等效电阻有一个工程近似计算公式&lt;/p&gt;

&lt;p&gt;$
转子等效电阻=堵转功率/（堵转电流^2*3） - 定子电阻
$&lt;/p&gt;

&lt;p&gt;于是，这2个对后续计算极其重要的参数就这么简单的 获得了。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>反激电源里 tl431+pc817 组合下对比 Boost 是怎么实现稳压的</title>
   <link href="https://microcai.org/2025/10/21/tl431-pc817.html"/>
   <updated>2025-10-21T00:00:00+00:00</updated>
   <id>https://microcai.org/2025/10/21/tl431-pc817</id>
   <content type="html">&lt;p&gt;以前说过，反激就是隔离的 Boost。&lt;/p&gt;

&lt;p&gt;在 Boost 升压电路里，稳压靠的是 一个分压网络和芯片的 FB 引脚。
芯片自动调节占空比，使得 FB 引脚电压被稳定在内部参考电压。从而间接的将输出电压稳定。&lt;/p&gt;

&lt;p&gt;在反激电源里，同样也是靠芯片的 FB 引脚反馈。但是此时反馈信号来自 PC817 而不是分压电阻。
pc817 则受 tl431 驱动。&lt;/p&gt;

&lt;p&gt;实际上，这里和 boost 拓扑里是一样的。就是比较分压和参考电压。
在 Boost 拓扑里，参考电压是芯片内置的。而在 pc817+tl431 的组合里，这个参考电压，就是 tl431。&lt;/p&gt;

&lt;p&gt;tl431 通过控制 pc817 的导通，来间接调控反激控制器的占空比。tl431 流过电流越大，则 pc817 的内部发光二极管
流过的电流也越大。于是 pc817 的输出三极管的电流也越大。&lt;/p&gt;

&lt;p&gt;pc817 通常用于将反激芯片的 FB 引脚（芯片会内部上拉）拉低。也就是 pc817 的二极管流过电流越大，反激芯片的 FB 引脚电压就被拉的越低。&lt;/p&gt;

&lt;p&gt;反激芯片的 FB 引脚电压越低，反激芯片的 pwm 占空比就越低。&lt;/p&gt;

&lt;p&gt;以DK1203为例子，他的 FB 引脚电压在 2.3V-2.5V 之间可以线性的调控占空比 0%-100%。高于 2.6V 芯片进入短路保护，低于 2.3V 芯片进入过压保护。
因此， tl431 可以通过 pc817 直接调控反激芯片的占空比。从而稳定输出电压。&lt;/p&gt;

&lt;p&gt;如果将 boost 芯片用于反激，则 pc817 的输出要改成对 FB 进行上拉。&lt;/p&gt;

&lt;p&gt;有一些boost控制器的 FB 引脚对应的内部比较器的 输出端也会引出。通常标记为 COMP 引脚。这种情况下，只需要将 FB 接地，然后用 pc817 拉低 COMP 引脚。
因为这种设计下，COMP 引脚的电压直接决定了占空比。FB 接地会使得 COMP 引脚事实上处于被运放持续拉高的状态。由于运放的输出电流有限，他拗不过 pc817，
因此 pc817 可以将 COMP 的电压拉低。&lt;/p&gt;

&lt;p&gt;接下来讲下 tl431 的几个电阻的阻值如何确定。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/tl431+pc817.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这里， R24 的作用，是给 tl431 一个偏置电流。
为何呢？因为 tl431 要工作，肯定是要吃电流的。如果 pc817 直接串入 tl431， 意味着在任何情况下， pc817 的发光二极管都处于导通状态。这是我们要避免的。
因此， 12V 供电通过 R24 后再流入 tl431 . 然后 pc817 使用 R24 上的分压供电。&lt;/p&gt;

&lt;p&gt;当 tl431 上流过的电流超过 1.2mA 的时候， R24 上产生 1.2V 的压降，于是 pc817 才会开始导通。&lt;/p&gt;

&lt;p&gt;这样可以确保， tl431 有一个安全的 1.2mA 工作电流，在这个电流以下，pc817 绝对不会工作。&lt;/p&gt;

&lt;p&gt;根据手册， tl431 要求 1mA 静态工作电流。因此 R24 这里取值 1.2K 也是没问题的。&lt;/p&gt;

&lt;p&gt;当 tl431 的 2脚电压高于 2.5V ，tl431 就开始吃电流。他吃多少电流，就取决于2脚电压比 2.5V 高多少。最大能吃到 100mA 电流。&lt;/p&gt;

&lt;p&gt;如果他吃 100mA 电流，则 R24 上会产生 100V 电压——这当然是不可能的。所以这个电路里， tl431吃不了那么大电流。但是吃 10mA 电流还是可以的。此时 R24 上会产生 10V 的分压 —— 如果不加以限制，10V 的电压足以烧毁 pc817 的发光二极管。因此 R27 的作用，就是限制流过 pc817 的电流，保护 pc817 的发光二极管。1k 的阻值，足够确保 pc817 的发光二极管。上的电流不会超过最大值值。&lt;/p&gt;

&lt;p&gt;R23 和 R26 是一个典型的分压网络。只要记得 R26 不要超过 10k 就可以。因为 tl431 的 2脚会吃掉一个微弱的电流。这导致 R23 不能太大，导致 R26 实际上分不到那么多电压。一般我们要让 R26 上流过的电流十倍于 tl431 的 2脚吃掉的电流。而 R26 上流过的电流就是 2.5V/R26 。&lt;/p&gt;

&lt;p&gt;接下来，确定 C12 C13 R25 的值。
这是一个2型补偿器。&lt;/p&gt;

&lt;p&gt;因此，他的目的，就是防止 tl431 振荡。aka，这个带通滤波器的目的，就是将 tl431的 1脚调控路径 —— tl431 + pc817 + 反击芯片 + 变压器 这样一条长反射弧构成的变化。给提前反馈给 2脚。从而避免反馈延迟导致的 PID 振荡。
如果 1脚的反馈，总是提前补到2脚——则 占空比永远不可能发生真正的变化。
因此，我们要将 tl431 里，速度慢的反馈给过滤掉，速度快的反馈给提前补偿到2脚。&lt;/p&gt;

&lt;p&gt;所谓 速度慢的反馈，其实就是  “ tl431 + pc817 + 反击芯片 + 变压器 这样一条长反射弧” 的动态响应频率。
低于这个频率的，就反馈给芯片，高于这个频率的，就给直接补到2脚。这样就避免了 PID 震荡。&lt;/p&gt;

&lt;p&gt;这就是这个带通滤波器的存在意义。
因此，需要实际进行调试后，确定你设计的电路的响应频率，然后确定带通滤波器的截止频率。然后由截止频率调整滤波器参数。&lt;/p&gt;

&lt;p&gt;不过，也可以根据经验进行。一般  tl431 + pc817 + 反击芯片 + 变压器 这样一条长反射弧的响应频率在 1khz 左右。&lt;/p&gt;

&lt;p&gt;因此，高于 1khz 的调整信号应该过滤掉不反馈给 pc817 避免震荡。&lt;/p&gt;

&lt;p&gt;因此设计截止频率 1khz 的滤波器放置在 tl431 的 1 2 脚之间。只有低于 1khz 的信号传给 pc817 。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>电机类型优劣对比</title>
   <link href="https://microcai.org/2025/09/30/motor-type-compare.html"/>
   <updated>2025-09-30T00:00:00+00:00</updated>
   <id>https://microcai.org/2025/09/30/motor-type-compare</id>
   <content type="html">&lt;p&gt;不同电机的优劣对比整理归纳。&lt;/p&gt;

&lt;p&gt;先说单相异步电机：&lt;/p&gt;

&lt;p&gt;优点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;只需要单相交流供电，供电容易&lt;/li&gt;
  &lt;li&gt;无需变频器驱动，成本低廉&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;缺点&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;无法调速&lt;/li&gt;
  &lt;li&gt;效率低&lt;/li&gt;
  &lt;li&gt;有 100hz 的工频振动&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;再说三相异步电机：&lt;/p&gt;

&lt;p&gt;优点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;功率密度大于单相异步电机&lt;/li&gt;
  &lt;li&gt;同功率下成本低于单相电机（有三相供电的情况下）&lt;/li&gt;
  &lt;li&gt;转矩恒定，无齿槽转矩波动，无 100hz 工频振动&lt;/li&gt;
  &lt;li&gt;便于开环控制&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;缺点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;无三相电源的情况下，比单相电机要多一个变频器的成本&lt;/li&gt;
  &lt;li&gt;效率略低于无刷电机&lt;/li&gt;
  &lt;li&gt;功率密度低于无刷电机&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;再说无刷电机：&lt;/p&gt;

&lt;p&gt;优点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;效率高&lt;/li&gt;
  &lt;li&gt;功率密度大&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;缺点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;必须搭配无刷电机驱动器使用，成本高&lt;/li&gt;
  &lt;li&gt;无刷电机驱动器成本略高于变频器&lt;/li&gt;
  &lt;li&gt;电机有永磁铁，害怕高温&lt;/li&gt;
  &lt;li&gt;稀土永磁铁转子成本于鼠笼式异步电机转子&lt;/li&gt;
  &lt;li&gt;永磁体带来齿槽转矩波动&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;
&lt;p&gt;接着说永磁同步电机：&lt;/p&gt;

&lt;p&gt;优点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;相比无刷电机，齿槽转矩波动小（三相异步电机理论上无转矩波动，实际有一些）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;缺点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;效率略低于无刷电机&lt;/li&gt;
  &lt;li&gt;正弦波控制器成本高于无刷电机的方波控制&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;p&gt;我把无刷电机和永磁同步电机单列出来，因为这俩东西看起来差不多，实际也差不多，遵循的也是同样的原理。
但是，还是有较大的差异，主要体现在绕组上。&lt;/p&gt;

&lt;p&gt;无刷电机的绕组一般使用集中式绕组。所谓集中式绕组，指绕组的跨距为1。
集中式绕组跨距小，大大的节约绕组的铜线成本。因为只有嵌入定子槽的铜线才能产生有用的磁场。而在定子槽外面的那部分端线，其磁场是不产生输出的。集中式绕组的端线少，因此成本低。节约的端部铜线成本可以抵消永磁体带来的成本增加。因此无刷电机成本不会大幅高于异步电机，某些情况下可能还低于异步电机。&lt;/p&gt;

&lt;p&gt;但是缺点也是集中式绕组带来的：转矩波动大。&lt;/p&gt;

&lt;p&gt;因此，改用和异步电机一样的长跨距绕组，则可以大大减小转矩波动。于是就是所谓的永磁同步电机了。&lt;/p&gt;

&lt;p&gt;因此永磁同步电机是所有电机类型里成本最高的。&lt;/p&gt;

&lt;hr /&gt;
&lt;p&gt;接着说控制器。&lt;/p&gt;

&lt;p&gt;只有 异步电机和有刷电机是可以无控制器运行的。无刷电机和永磁同步电机，必须依靠控制器运行。&lt;/p&gt;

&lt;p&gt;单相异步电机如果使用变频器控制，则实际成本（相同的轴功率的情况下）会远高于三相异步电机，因此，如果要搭配控制器，则一定不能考虑单相异步电机。&lt;/p&gt;

&lt;p&gt;异步电机变频器，无刷电机控制器，永磁同步电机控制器，三者都可以运行在无传感器模式和有传感器模式。下面分别列出优缺点。&lt;/p&gt;

&lt;p&gt;其中，算法成本指的就是需要的控制器的运算能力。
控制电路成本指的是为了搭配控制算法而设计的电路的成本。主要是电压电流采样。所有驱动器的功率输出级的成本是一样的。&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;开环异步&lt;/th&gt;
      &lt;th&gt;无感闭环异步&lt;/th&gt;
      &lt;th&gt;有感闭环异步&lt;/th&gt;
      &lt;th&gt;无感无刷电机&lt;/th&gt;
      &lt;th&gt;有感无刷电机&lt;/th&gt;
      &lt;th&gt;无感永磁同步&lt;/th&gt;
      &lt;th&gt;有传感器永磁同步&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;需要的传感器&lt;/td&gt;
      &lt;td&gt;无&lt;/td&gt;
      &lt;td&gt;无&lt;/td&gt;
      &lt;td&gt;脉冲输出转速计&lt;/td&gt;
      &lt;td&gt;无&lt;/td&gt;
      &lt;td&gt;霍尔传感器&lt;/td&gt;
      &lt;td&gt;无&lt;/td&gt;
      &lt;td&gt;单圈绝对值编码器&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;传感器成本&lt;/td&gt;
      &lt;td&gt;无&lt;/td&gt;
      &lt;td&gt;无&lt;/td&gt;
      &lt;td&gt;低&lt;/td&gt;
      &lt;td&gt;无&lt;/td&gt;
      &lt;td&gt;低&lt;/td&gt;
      &lt;td&gt;无&lt;/td&gt;
      &lt;td&gt;高&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;控制算法&lt;/td&gt;
      &lt;td&gt;简单&lt;/td&gt;
      &lt;td&gt;极复杂&lt;/td&gt;
      &lt;td&gt;简单&lt;/td&gt;
      &lt;td&gt;简单&lt;/td&gt;
      &lt;td&gt;简单&lt;/td&gt;
      &lt;td&gt;复杂&lt;/td&gt;
      &lt;td&gt;简单&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;算法成本&lt;/td&gt;
      &lt;td&gt;低&lt;/td&gt;
      &lt;td&gt;高&lt;/td&gt;
      &lt;td&gt;低&lt;/td&gt;
      &lt;td&gt;低&lt;/td&gt;
      &lt;td&gt;低&lt;/td&gt;
      &lt;td&gt;高&lt;/td&gt;
      &lt;td&gt;低&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;控制电路成本&lt;/td&gt;
      &lt;td&gt;低&lt;/td&gt;
      &lt;td&gt;高&lt;/td&gt;
      &lt;td&gt;低&lt;/td&gt;
      &lt;td&gt;中&lt;/td&gt;
      &lt;td&gt;中&lt;/td&gt;
      &lt;td&gt;高&lt;/td&gt;
      &lt;td&gt;中&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;控制效果&lt;/td&gt;
      &lt;td&gt;够用&lt;/td&gt;
      &lt;td&gt;好&lt;/td&gt;
      &lt;td&gt;极好&lt;/td&gt;
      &lt;td&gt;低速不行&lt;/td&gt;
      &lt;td&gt;极好&lt;/td&gt;
      &lt;td&gt;好&lt;/td&gt;
      &lt;td&gt;好极了&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;转矩波动&lt;/td&gt;
      &lt;td&gt;几乎没有&lt;/td&gt;
      &lt;td&gt;几乎没有&lt;/td&gt;
      &lt;td&gt;几乎没有&lt;/td&gt;
      &lt;td&gt;中&lt;/td&gt;
      &lt;td&gt;中&lt;/td&gt;
      &lt;td&gt;低&lt;/td&gt;
      &lt;td&gt;低&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;其中，无感闭环异步和无感永磁同步，硬件电路成本是最高的。
因为为了达到较好的算法估算效果，需要极高精度的电流和电压采样，并且搭配强悍的 DSP 芯片进行解算。因此实际上是成本大于有感的方案的。只不过某些应用场景下，电机无法安装传感器。例如压缩机内部的驱动电机，其高温高压的环境无法安装传感器。&lt;/p&gt;

&lt;p&gt;低成本的情况下使用无感电机，一般使用的是控制成本适中的无刷电机。
无刷电机基于反电动势获得转子位置估算，存在低速无法估算的问题。因此一般会避开低速段，只进行中高速控制。 低速避开的方法是控制器强拖电机到中速再切换到闭环控制。因此只适合无低速工况的场景。例如风机，驱动螺旋桨的电机。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>中国人要反对洋奴哲学</title>
   <link href="https://microcai.org/2025/09/09/anti-yangnu.html"/>
   <updated>2025-09-09T00:00:00+00:00</updated>
   <id>https://microcai.org/2025/09/09/anti-yangnu</id>
   <content type="html">&lt;p&gt;作者：张春桥。&lt;/p&gt;

&lt;p&gt;发表于 1976年《红旗》第四期.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;在社会主义革命和建设中，是坚持独立自主、自力更生，还是推行洋奴哲学、爬行主义，这是马克思主义和修正主义两条路线的原则分歧，是社会主义历史阶段两个阶级、两条道路斗争的一个重要方面。 去年夏季前后，党内那个不肯改悔的走资派在“一切为了现代化”的幌子下，又大肆贩卖洋奴哲学。他公开主张把发展生产、发展科学技术的希望寄托在外国，叫嚷“要拿出多的东西换取外国最新最好的设备”，还说什么“这是最可靠的”，而且是“一个大政策”。&lt;/p&gt;

&lt;p&gt;一时，祟洋迷外之风又刮起来了。&lt;/p&gt;

&lt;p&gt;国内劳动群众的创造受到议论讥笑，坚持自力更生的革命精神受到非难攻击，似乎坚持独立自主、自力更生不对了，推行洋奴哲学、爬行主义反而有理了。&lt;/p&gt;

&lt;p&gt;伟大领袖毛主席指出：“自力更生为主，争取外援为辅，破除迷信，独立自主地干工业、干农业，干技术革命和文化革命，打倒奴隶思想，埋葬教条主义，认真学习外国的好经验，也一定研究外国的坏经验——引以为戒，这就是我们的路线。”&lt;/p&gt;

&lt;p&gt;社会的财富是工人、农民和劳动知识分子自己创造的。&lt;/p&gt;

&lt;p&gt;在社会主义制度的基础上，我国人民能够发挥出无穷无尽的力量。只要充分发挥人民群众的聪明智慧和创造才能，完全能够依靠我们自己的力量，建设起一个具有现代农业、现代工业、现代国防和现代科学技术的社会主义强国。&lt;/p&gt;

&lt;p&gt;与此相反，党内机会主义路线的头子总是鼓吹洋奴哲学、爬行主义，推行一条同独立自主、自力更生完全对立的修正主义路线。他们看不见人民群众的力量，根本否认人民群众创造历史这一马克思主义的真理。他们拜倒在西方资产阶级的面前，甘心跟在别人后面一步一步地爬行，甚至主张卖国投降，靠向外国乞讨过活。&lt;/p&gt;

&lt;p&gt;在无产阶级文化大革命运动中，全国人民扬眉吐气，发奋图强，在社会主义建设事业中取得了许多重大的成就。&lt;/p&gt;

&lt;p&gt;人造地球卫星按预定计划返回地面，新的石油勘探开采技术的采用，大型内燃机车、大容量双水内冷汽轮发电机组、百万次电子计算机、电子扫描显微镜、各种类型数控机床等新产品试制成功，都表明我们在赶上和超过世界先进水平的道路上正凯歌行进。&lt;/p&gt;

&lt;p&gt;我们同党内那个不肯改悔的走资派的分歧，并不在于要不要“四个现代化”，而在于走什么道路，执行什么路线，究竟把社会主义建设的基点放在那里。&lt;/p&gt;

&lt;p&gt;毛主席早就指出：“我们的方针要放在什么基点上？放在自己力量的基点上叫做自力更生。”把立足点放在自力更生上，是我们进行革命和建设的一条根本原则，是战胜一切困难夺取胜利的可靠保证。&lt;/p&gt;

&lt;p&gt;在革命战争年代，我们依靠自己的力量，依靠全体军民的创造力，打败了日本帝国主义和国民党反动派。&lt;/p&gt;

&lt;p&gt;在革命战争胜利以后，我们又在物质条件十分困难的情况下，依靠自力更生，打破了帝国主义的封锁禁运，顶住了社会帝国主义的刁难破坏，独立自主地发展社会主义经济，把一个贫穷落后的旧中国，建设成为初步繁荣昌盛的社会主义新中国。&lt;/p&gt;

&lt;p&gt;但是，我们面临的任务仍然是很艰巨的。完成艰巨的任务靠什么？基本的一条就是靠充分动员和依靠广大革命群众。离开这一条，我们就不可能多快好省地建设社会主义。&lt;/p&gt;

&lt;p&gt;因此，我们更要自觉地坚持独立自主、自力更生的方针，绝对不能离开这个基点。&lt;/p&gt;

&lt;p&gt;我们是社会主义国家，又是一个人口众多的大国，我们既不能掠夺别国人民的财富，也不能依赖任何外国的力量来搞建设。吃现成的要受气，依赖别人是建设不成社会主义的。&lt;/p&gt;

&lt;p&gt;只有从本国的实际情况出发，依靠本国人民群众的创造力，充分利用和挖掘本国的资源和潜力，才能在不太长的时间里，建立起独立完整的工业体系和国民经济体系，使我们的国家更能经受风险，立于不败之地。&lt;/p&gt;

&lt;p&gt;党内那个不肯改悔的走资派大唱反调，到处叫嚷要千方百计出口，去“换回好多好东西回来”。据说这样就能加快资源的开发，加快工业的技术改造，加快科研的步伐，真是妙不可言。&lt;/p&gt;

&lt;p&gt;世上难道真有这样的事吗？&lt;/p&gt;

&lt;p&gt;国际上存在阶级斗争，这是不以人们的意志为转移的，工人群众从长期的斗争实践中很懂得这一点，他们说：“我们决不能把社会主义建设的命运系在别人的腰带上”。这句话尖锐地指出要注意被别人卡住脖子，牵着鼻子走的危险。&lt;/p&gt;

&lt;p&gt;如果不把立足点放在自力更生上，样样靠引进，为了引进，甚至把发展经济主要立足于国内市场的社会主义原则丢在一边，无原则地以出口换进口，势必造成那么一种状况：自己能生产的无限制地进口；国内很需要的又无限制地出口；买人家先进的，自己造落后的，甚至把矿山资源的开采主权也让给人家。&lt;/p&gt;

&lt;p&gt;这样下去，岂不是要把我国变成帝国主义国家倾销商品的市场、原料基地、修配车间和投资场所吗？那里还有什么工业化的速度，那里还谈得上独立自主地发展社会主义经济！这只能作帝国主义的经济附庸。&lt;/p&gt;

&lt;p&gt;经济上丧失独立，政治上也就不可能自主。&lt;/p&gt;

&lt;p&gt;中国人民在历史上遭受过的创痛是很深的。&lt;/p&gt;

&lt;p&gt;一百多年前，清朝洋务派头子李鸿章、曾国藩，不就是鼓吹“中国欲自强，则莫如学习外国利器。欲学习外国利器，则莫如觅制器之器”吗？这伙洋奴汉奸，一味想买外国的“制器之器”，搞所谓“自强”。&lt;/p&gt;

&lt;p&gt;结果呢，中国非但没有因此强盛起来，反而越来越深地陷入了殖民地、半殖民地的深渊。&lt;/p&gt;

&lt;p&gt;在社会主义建设中，只有把立足点放在自力更生上，充分发挥人民群众的创造力，才能真正赢得高速度。&lt;/p&gt;

&lt;p&gt;这已经为二十多年来，特别是文化大革命以来的无数事实所证明。我们的石油工业近十五年平均每年增长百分之二十以上，靠的是自力更生，其速度之快，连我们的敌人也无法否认。&lt;/p&gt;

&lt;p&gt;无产阶级文化大革命以来，我们的造船工业的发展速度也很快。&lt;/p&gt;

&lt;p&gt;文化大革命以前，刘少奇宣扬“造船不如买船，买船不如租船”，机引进，船买进，眼睛盯着外国的一点技术专利，国产的货轮和船用柴油机长期得不到发展。&lt;/p&gt;

&lt;p&gt;在文化大革命中，广大工人群众、干部批判了洋奴哲学、爬行主义的修正主义路线，才改变了面貌。&lt;/p&gt;

&lt;p&gt;一九七〇年，上海工人开始打造船工业翻身仗，到一九七五年的六年中，船舶的吨位和柴油机的马力都超过了文化大革命前十七年的总和。&lt;/p&gt;

&lt;p&gt;文化大革命前只造了一艘万吨轮，而这六年中万吨级以上的船舶就造了四十四艘。到底是自力更生快，还是搞洋奴哲学快，不是很清楚吗？&lt;/p&gt;

&lt;p&gt;党内那个不肯改悔的走资派口口声声说要把国民经济搞上去，却偏偏闭住眼睛不看事实，真是偏见比无知离真理更远！&lt;/p&gt;

&lt;p&gt;搞社会主义，首先要坚持正确的方向和道路。&lt;/p&gt;

&lt;p&gt;党内那个不肯改悔的走资派，打着把国民经济搞上去的旗号，到处鼓吹只要拿到先进的技术、设备，不管走什么路，用什么方法都可以。&lt;/p&gt;

&lt;p&gt;毛主席最近指出：“他这个人是不抓阶级斗争的，历来不提这个纲。还是‘白猫、黑猫’啊，不管是帝国主义还是马克思主义。”&lt;/p&gt;

&lt;p&gt;按照党内那个不肯改悔的走资派的一套办，必然把我国经济引向资本主义道路。其实，即使是资本主义国家，完全依靠外国，也不可能真正发展自己的独立经济。&lt;/p&gt;

&lt;p&gt;我们是社会主义国家，要有自己的独立的经济体系，只能走自己工业发展的道路。离开独立自主、自力更生，不但搞不成社会主义的现代化，而且会使我们的社会主义国家蜕化变质，复辟资本主义，靠向外国乞讨过活。&lt;/p&gt;

&lt;p&gt;苏修就是一面镜子。苏联全面复辟资本主义之后，官僚垄断资本向西方垄断资本买专利权，借贷款，甚至不惜把未开发的资源拿去作抵押。自称有强大工业基础的超级大国，外债却越背越重，从一九六四年到一九七五年上半年，向西方国家乞求贷款竟达一百六十叁亿美元。一手向人家掠夺，一手又向人家乞讨，这是苏修叛徒集团搞假共产主义的一大特色。&lt;/p&gt;

&lt;p&gt;我们提倡自力更生，并不是拒绝学习和研究外国的经验，包括好的经验和坏的经验。我们也不是反对引进某些确实有用的外国技术、设备。但是，对待外国的经验以及技术、设备，都要具体分析，加以鉴别，“排泄其糟粕，吸收其精华”，使其为我所用。学习要和独创结合，立足予超。决不能生吞活剥地照抄照搬，不管好的和坏的、成功的和失败的，适合我国需要的和不适合我国需要的，一古脑儿统统搬来。&lt;/p&gt;

&lt;p&gt;毛主席历来号召我们，要破除迷信，解放思想，埋葬教条主义，打倒奴隶思想，批判那种认为“外国月亮比中国的圆”的洋奴哲学。因为这些东西是窒息人民群众的革新创造精神、束缚人民群众手脚的精神枷锁。洋教条、洋偶像不扫荡，生动活泼的革命精神就焕发不出来。&lt;/p&gt;

&lt;p&gt;那些盲目崇拜外国的人，看不起本国人民群众的智慧和力量，在“洋”人面前矮半截，以为事事不如人，连某些资产阶级思想家都不如。&lt;/p&gt;

&lt;p&gt;清代的学者严复就很赞赏“学我者病，来者方多”的说法，不主张生搬硬套，懂得后来可以居上的道理。&lt;/p&gt;

&lt;p&gt;我们不能走世界各国技术发展的老路，跟在别人后面一步一步地爬行。&lt;/p&gt;

&lt;p&gt;我们要敢于走前人没有走过的道路，敢于攀登前人没有攀登过的高峰。&lt;/p&gt;

&lt;p&gt;外国有的，我们要有；外国没有的，我们也要有。&lt;/p&gt;

&lt;p&gt;文化大革命以来，许多工厂试制成功的新产品、新材料，有不少就是工人群众发奋图强、土法上马搞出来的。工人们豪迈地说：西方资产阶级能办到的，东方无产阶级也一定能办到。而且办得更好；西方资产阶级办不到的，我们东方的无产阶级也能办到。&lt;/p&gt;

&lt;p&gt;党内那个不肯改悔的走资派却把中外资产阶级的东西奉若神物，对于我国人民群众的创造从来看不上眼，只许永远跟着别人爬行。&lt;/p&gt;

&lt;p&gt;洋奴哲学、爬行主义有着深刻的阶级根源和思想根源。中国的买办资产阶级从来是帝国主义的附庸，历来奉行洋奴哲学。民族资产阶级先天就有软弱性，既怕民众，也怕帝国主义。它同帝国主义有矛盾的一面，在一个时期里有可能与民众结成统一战线去反对帝国主义，但它又有依附于帝国主义经济的一面，常常屈服于帝国主义的压力，崇洋迷外。&lt;/p&gt;

&lt;p&gt;党内走资本主义道路的当权派是党内的资产阶级。 在民主革命阶段，他们就是带着资产阶级的这种劣根性跑进党内来的。进入社会主义革命时期，没有把立足点移过来，仍然代表资产阶级。他们害怕群众、害怕帝国主义的劣根性愈来愈发作，就不能不同广大人民群众处于尖锐对立的地位。&lt;/p&gt;

&lt;p&gt;阶级投降和民族投降是一对孪生兄弟。对内搞阶级投降，对外必然要搞民族投降，鼓吹洋奴哲学。 洋奴哲学，是帝国主义长期侵略我国的精神产物。只要阶级和阶级斗争还存在，只要帝国主义还存在，洋奴哲学的幽灵总会在一部分人的头脑中徘徊。&lt;/p&gt;

&lt;p&gt;因此，批判洋奴哲学，是个长期的斗争任务，必须反复地进行下去。我们一定要遵照毛主席的教导，以阶级斗争为纲，深入批判洋奴哲学，更加自觉地坚持独立自主、自力更生的方针，加速我国社会主义建设的步伐，把无产阶级专政下的继续革命进行到底。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>了解反激，驯服反激</title>
   <link href="https://microcai.org/2025/05/24/flyback-howto.html"/>
   <updated>2025-05-24T00:00:00+00:00</updated>
   <id>https://microcai.org/2025/05/24/flyback-howto</id>
   <content type="html">&lt;h1 id=&quot;序&quot;&gt;序&lt;/h1&gt;

&lt;p&gt;反激，这恐怕是当今世上使用最广泛的开关电源拓扑了。&lt;/p&gt;

&lt;p&gt;学习电力电子技术，就从反激开始。&lt;/p&gt;

&lt;h1 id=&quot;何谓反激&quot;&gt;何谓反激&lt;/h1&gt;

&lt;p&gt;反激反激，顾名思义，是反过来激发。啥叫反过来呢？意思就是，开关管关闭的时候，变压器才开始干活。开关管关闭了，变压器主绕组上都没电流了，变压器才开始干活。
这没电流了才想起来该干活了，所以是反着的。故得名反激。有反必有正，故而必有一个正激来对应。&lt;/p&gt;

&lt;h2 id=&quot;为何要反过来干活&quot;&gt;为何要反过来干活&lt;/h2&gt;

&lt;p&gt;其实开关电源最先诞生的拓扑绝对不是反激。可能是正激，也可能是推挽。&lt;/p&gt;

&lt;p&gt;正激变压器其实更容易理解，因为他就是按照变压器的定义工作的。正激变换器拓扑可以看成是隔离版的 Buck 降压电路。&lt;/p&gt;

&lt;p&gt;所以，反激也可以看成是隔离版的Boost升压电路。&lt;/p&gt;

&lt;p&gt;因为，反激和 Boost 升压的一个共同点是，都是在开关管关断的时候，才能对外输出能量。&lt;/p&gt;

&lt;p&gt;因此，反激之所以要反过来干活，其实他脱胎自 boost 升压电路。&lt;/p&gt;

&lt;p&gt;在 Boost 变换器里，开关管打开的时候，输出二极管截止。输入电压全部施加在电感上，对电感进行充能。随着时间推移，电感上的电流越来越大。
如果电流大到突破电感的饱和值，电感就会突然失去电感特性，变成一条导线，于是短路炸机。好在，控制器不会允许电感电流突破饱和值，而是适时的关闭 开关管。
因为电感电流不能突变，因此电感的电流需要寻找突破口继续流动。于是就会击穿 输出二极管，对输出电容进行充电。直到开关管下一次打开。下一次打开前，电感电流要么已经归0,要么还有剩余电流。&lt;/p&gt;

&lt;p&gt;而在 反激变换器里，其实这个过程由变压器的两个绕组分别在不同的时间不同的空间完成。
开关管打开的时候，次级的二极管截止，次级线圈没有电流。输入电压全部施加在初级线圈上，对变压器进行充能。随着时间推移，初级线圈上的电流越来越大。
如果电流大到突破变压器磁芯的饱和值，变压器就会突然失去感抗，变成一条导线，于是短路炸机。好在，控制器不会允许初级线圈电流突破饱和值，而是适时的关闭 开关管。&lt;/p&gt;

&lt;p&gt;但是此时，电感电流不能突变的定律就不再适用了。其实电感电流不能突变不是铁律，真正的铁律是磁通量不能突变。为了维持磁通量不变，电感的电流才不能突变。
如果铁芯上绕有2个绕组，那么另一个绕组立即产生电流，也可以维持磁通量不变。&lt;/p&gt;

&lt;p&gt;而次级绕组上的二极管，恰恰会在初级的开关管关闭的时候，允许电流通过了。于是，为了维持磁通量不变，次级立即会产生 N倍于初级关管时刻电流的电流。这个 N 取决于变压器线圈的匝比。
这就是所谓的 安匝平衡。&lt;/p&gt;

&lt;p&gt;所以，反激和 Boost 升压的一个改进之处，是它输出有了一个N倍的电流放大，这导致了输出有了 N 倍的电压下降。于是反激相比 boost 升压电路，反而可以因为这个变比实现降压。
然后是输出又另一个线圈完成，于是输入输出就电气隔离了。&lt;/p&gt;

&lt;h2 id=&quot;相比正激的优势&quot;&gt;相比正激的优势&lt;/h2&gt;

&lt;p&gt;任何变压电路，都需要电感的参与。正激是隔离版的 Buck 。但是，他的变压器不是电感。他的变压器只是实现了电流的比例变换。变换后，还需要电感“续流”才能实现完整的回路。&lt;/p&gt;

&lt;p&gt;也就是说，在正激变换器里，变压器+开关管，只是实现了非隔离 Buck 电路里的 “开关管” 的功能。另一个电感，还需要存在以维持开关管关闭的时候的输出。&lt;/p&gt;

&lt;p&gt;所以，在正激变换器里，需要一个变压器变压，同时输出还需要有一个电感。&lt;/p&gt;

&lt;p&gt;而反激变换器里，变压器同时承担电流放大和电感的功能。因此，反激变换器相比正激最大的意义，就是省去了一个输出侧的电感。&lt;/p&gt;

&lt;p&gt;从而极大的降低了成本。&lt;/p&gt;

&lt;h2 id=&quot;反激的劣势&quot;&gt;反激的劣势&lt;/h2&gt;

&lt;p&gt;反激的优势是便宜，而劣势则是漏感带来的能量损耗。理想的变压器当然没有漏感。然而现实中的变压器有。
意味着初级的开关管关断的时候，并不能100%的将能量转移到次级。&lt;/p&gt;

&lt;p&gt;这部分不能转移到次级的能量，继续推动初级线圈的电流运动。然而此时开关管已经关断，于是电感的电流没有续流路径，反而会形成一个尖峰电压加在开关管上。
为了吸收这部分能量保护开关管，就需要一个由电容电阻构成的吸收电路，通过发热消耗掉漏感能量。&lt;/p&gt;

&lt;p&gt;因此，反激的第一个劣势，就是变压器漏感一定会产生能量损耗。这部分损耗和初级线圈电流的平方成正比，和初级的漏感成正比。&lt;/p&gt;

&lt;p&gt;电流越大，漏感导致的能量损失就越大。&lt;/p&gt;

&lt;p&gt;因此反激一定要限制初级的峰值电流。也就限制了整个电源的功率不能太大。&lt;/p&gt;

&lt;h1 id=&quot;元件参数的计算&quot;&gt;元件参数的计算&lt;/h1&gt;

&lt;p&gt;在反激变换器里，最关键和最难的是变压器参数的计算。&lt;/p&gt;

&lt;p&gt;反激的变压器，不单单是变压器，他还是电感。因此，反激的变压器，不能只看匝数。还得看电感。&lt;/p&gt;

&lt;p&gt;因此，反激变换器里，变压器的几个关键参数需要设计阶段进行抉择：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;变压器磁芯&lt;/li&gt;
  &lt;li&gt;初级匝数&lt;/li&gt;
  &lt;li&gt;次级匝数&lt;/li&gt;
  &lt;li&gt;气隙&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;诶？气隙是怎么回事？为啥要磨呢？&lt;/p&gt;

&lt;h2 id=&quot;匝数计算&quot;&gt;匝数计算&lt;/h2&gt;

&lt;p&gt;不是只要保证匝比一样，就可以随意决定初级的匝数的。匝数的多寡，主要是为了最大化的利用磁芯的饱和磁通。匝数多了，那么只要较小的电流即可产生巨大的磁通量，容易饱和。
匝数少了，则磁通量的变化量就不够大，不能有效利用磁芯。&lt;/p&gt;

&lt;p&gt;因此，需要利用设计初期的时候就定下来的&lt;strong&gt;目标&lt;/strong&gt;来决定匝数。磁芯确定后，匝数就直接和峰值电流绑定了。磁通量变化量和峰值电流除就得到了所需的初级匝数。&lt;/p&gt;

&lt;h2 id=&quot;气隙&quot;&gt;气隙&lt;/h2&gt;

&lt;p&gt;那么，为啥要磨呢？因为反激变压器，他不单单是变压器。变压器只要确定匝数，而反激还是个电感。因此，它还需要确定电感量！&lt;/p&gt;

&lt;p&gt;如果你按计算的匝数绕变压器，会发现一个结果：测量后发现初级的电感量非常大。&lt;/p&gt;

&lt;p&gt;为啥反激需要给定的初级电感量呢？&lt;/p&gt;

&lt;p&gt;因为反激是 ”储能-释放“ 这样的结构。以 60W 的反激电源为例子。假设开关频率为 60khz,意味着每次开关管打开的时候，变压器磁芯里都要存储60/60000 = 1mW 的能量。&lt;/p&gt;

&lt;p&gt;而我们知道，反激变压器乃 boost 变换器升级而来。他的初次级要满足安匝守恒。根据 安匝守恒 可以很快计算出来，开关管关闭的瞬间，初级线圈所必须具有的电流。
没错，这个电流是个确定值，不可更改。 &lt;em&gt;正是如此，大名鼎鼎的3842 才是个电流模式 pwm 控制器。其控制的，就是关管时刻的初级绕组电流。&lt;/em&gt;
然后，因为反激的变压器是个电感，要存储能量。而电感存储的能力，正好正比于电流的平方和电感的乘积。&lt;/p&gt;

&lt;p&gt;如此一来，就直接确定了初级线圈所必须的电感值。&lt;/p&gt;

&lt;p&gt;也就是说，初级线圈的 匝数和电感值，都是根据设计目标直接确定的。&lt;/p&gt;

&lt;p&gt;然而，直接在磁芯上绕这么多匝数，测量后几乎100% 会发现，实际制作出来的变压器，初级电感值会大于设计值。&lt;/p&gt;

&lt;p&gt;那么，如何让电感值能和设计值一样呢？ 调整匝比？ 不能。因为匝数也是计算获得的。
调整磁芯？ 不能，因为匝数的计算就是依据选定的磁芯。&lt;/p&gt;

&lt;p&gt;那还能怎么办？&lt;/p&gt;

&lt;p&gt;答案就剩下了唯一一个：开气隙。&lt;/p&gt;

&lt;p&gt;变压器的铁芯如果不紧密贴合，就会有漏磁。有漏磁就会产生漏感。那么开气隙是否和这个要求背道而驰呢？&lt;/p&gt;

&lt;p&gt;其实并不。因为只有边缘的磁芯不贴合才会有漏磁。中柱有间隙，那么磁场会从中间的空气穿过，而不会泄漏到线圈的外部。&lt;/p&gt;

&lt;p&gt;因此，反激变压器，100% 需要磨气隙，缩小电感值到设计的电感值。&lt;/p&gt;

&lt;p&gt;所谓磨气隙，是针对自制变压器来说的。因为设计阶段自制变压器的时候，磁芯是标准规格的货架产品。量产的时候，是直接计算获得中柱的间隙。直接生产出中柱有间隙的磁芯即可。&lt;/p&gt;

&lt;h1 id=&quot;尖峰吸收&quot;&gt;尖峰吸收&lt;/h1&gt;

&lt;p&gt;其实任何感性负载，都要考虑 MOS 管关闭后，因电感电流不能突变而来的续流问题。&lt;/p&gt;

&lt;p&gt;这个电流有3种办法解决， 其一是强行遏制，就需要使用阻容元件进行吸收了。其二是将其回收回母线电容。其三是让他来回震荡，利用它产生谐振。&lt;/p&gt;

&lt;p&gt;反激变换器通常使用的是第一种办法。使用阻容元件吸收，就得计算需要使用多大的电容和电阻来吸收。这就需要知道变压器的漏感和峰值电流，还有  MOS 的耐压。&lt;/p&gt;

&lt;p&gt;计算方式如下：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;漏感记为 L, mos管关闭的时候，绕组的电流记 I . 那么无法传递给次级的能量，就是 I*I*L/2。
这个能量由电容先行吸收，吸收后，电容电压抬升, 抬升幅度为 U .抬升所吸收的能量，为漏感能量。
也就是 U*U*C/2 = I*I*L/2 。那么这个 抬升幅度 U ，要小于 mos 的耐压 - 电源电压。
通常取 200V。这样就算出来需要多大的 C 来吸收了。&lt;/p&gt;

  &lt;p&gt;每次开关，都会产生泄漏，这个泄漏功率，就和开关频率有关，开关频率记为 fs, 那么泄漏功率就是 I*I*L/2* fs。
这个功率要靠电容给消耗。电容的热功率 = U*U\/R。令 U = 200V, 就得出了 吸收电阻的大小。&lt;/p&gt;
&lt;/blockquote&gt;

</content>
 </entry>
 
 <entry>
   <title>记录使用 GP8891CAS 时踩的一个天坑</title>
   <link href="https://microcai.org/2025/05/13/a-trap-by-gp8891cas.html"/>
   <updated>2025-05-13T00:00:00+00:00</updated>
   <id>https://microcai.org/2025/05/13/a-trap-by-gp8891cas</id>
   <content type="html">&lt;h1 id=&quot;序&quot;&gt;序&lt;/h1&gt;

&lt;p&gt;任何一个家电，必须得有一个低压供电电源。&lt;/p&gt;

&lt;p&gt;要获得一个低压供电，通常的做法是接一个开关电源。&lt;/p&gt;

&lt;p&gt;但是，外置的电源徒增成本。而且显得很不专业。最好的办法就是在 PCB 上集成一个供电电源。&lt;/p&gt;

&lt;p&gt;想起来前年我刚刚入门硬件设计。做了一个 低压变频器。
到去年年初的时候，开始尝试制作 220V 市电版的变频器。事情见 &lt;a href=&quot;/2024/02/05/220v-vfd-success.html&quot;&gt;这片文章&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;那时候，我是不懂 ACDC 变换的。因此电路板上低压供电，是由电源模块实现的。&lt;/p&gt;

&lt;p&gt;比如当时的照片&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/photo_2024-02-08_13-22-33.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;可以看到 画面中有2个 电源模块的位置。&lt;/p&gt;

&lt;p&gt;没错，还得用俩电源模块。分别供应高压侧的栅极驱动，和低压侧的单片机。&lt;/p&gt;

&lt;p&gt;而且还分别是 220V转 12V 和 220V 转5V 。虽然两个模块并不贵。三块多一个。但是，加起来也有七块钱了。&lt;/p&gt;

&lt;p&gt;而且还让我的设计不紧凑，使用了外挂电源模块。显得很不高级。&lt;/p&gt;

&lt;p&gt;后来，我改进了设计。因为我绘制低压的时候，经常就使用 32V转5V的 DCDC 电路。&lt;/p&gt;

&lt;p&gt;因此，我使用了 DK501 这款能直接从 DC310V 经过 Buck变换降压到 12V 的芯片。&lt;/p&gt;

&lt;p&gt;还是熟悉的 Buck 电路。&lt;/p&gt;

&lt;p&gt;但是， Buck 降压是非隔离的。为了给单片机供一个隔离的电，我还是使用了一个 12V转5V的隔离电源模块。&lt;/p&gt;

&lt;p&gt;这样让板子的外挂电源从2个变成了1个。。。。&lt;/p&gt;

&lt;h1 id=&quot;flybuck-方案&quot;&gt;FlyBuck 方案&lt;/h1&gt;

&lt;p&gt;我发现，要设计好电路，一定要先学会电源。没有电源任何事情都办不成。
于是我开始在看 B站上长江大学的唐老师的视频。唐老师是长江大学的老师，专门教电源电路的。他出了几百期视频，大部分就是在讲解 Buck/Boost/SEPIC 这类 DCDC 电路。&lt;/p&gt;

&lt;p&gt;看了几百期视频，我才了解到，有一种廉价的隔离电源的方案，叫 FlyBuck 。 就是把 buck 电路的电感换成2个绕组的共模电感。&lt;/p&gt;

&lt;p&gt;这样在共模电感的另一个线圈，就能直接获得一个同样电压的隔离供电。&lt;/p&gt;

&lt;p&gt;于是，这就是我后来改进的方案了。后来这个方案被用在了 ACBuck 电路里。&lt;/p&gt;

&lt;p&gt;ACBuck 使用 DK501 从 整流后的 310V 直流电直接 BUCK 降压到 12V, 但是，使用的 flybuck, 偷偷的把电感换成了共模电感。于是在主回路获得非隔离的 12V, 在另一个绕组获得隔离的 12V。
然后 12V 经过降压获得 3.3v 给单片机使用。&lt;/p&gt;

&lt;h1 id=&quot;进入反激的世界&quot;&gt;进入反激的世界&lt;/h1&gt;

&lt;p&gt;正当我觉得 高压 Buck直降 + FlyBuck 能同时获得一个隔离电源的方案满足了我的需求后。我固步自封了。因为其他的方案难度都太大了。既然简单的方案能满足，我就懒得学习复杂的电源拓扑了。&lt;/p&gt;

&lt;p&gt;直到数月前。一个华强北人士找到了我。说要我帮他设计一个变频器。&lt;/p&gt;

&lt;p&gt;我以为他是看到我的 10元变频器 方案动心了呢。结果他要我做一个 三相380V输入，三相380V输出的变频器。&lt;/p&gt;

&lt;p&gt;不过都答应给他做一个了，于是我花了几天功夫给他设计了一份。核心电路（单片机+逆变桥）几个小时就绘制完毕了。&lt;/p&gt;

&lt;p&gt;但是，供电部分我犯愁了。&lt;/p&gt;

&lt;p&gt;因为我熟悉的 高压 buck 直降，并不能用在 380V 输入的场合。&lt;/p&gt;

&lt;p&gt;类似 DK501 这样的 高压输入 Buck 降压芯片，也不过是耐压到 400V 直流电。
而 380V 交流，整流后的电压是 540V, 还得考虑电源的电压波动，需要留有余量，最低也得支持到 700V 输入的  buck 芯片。&lt;/p&gt;

&lt;p&gt;然而，市面上并不存在如此高压的 buck 降压芯片。&lt;/p&gt;

&lt;p&gt;因此，我只能使用反激电源。&lt;/p&gt;

&lt;p&gt;好在，几百期唐老师的视频不是白看的。我马上到德州仪器的网站找设计案例。唐老师特别推崇德州仪器的网站，说这个网站是大宝库。&lt;/p&gt;

&lt;p&gt;果不其然，我抄到了反激电源的设计。&lt;/p&gt;

&lt;p&gt;于是，我抄了一份 德州仪器的设计，然后给华强北老板交差了。&lt;/p&gt;

&lt;p&gt;这也是我第一次使用反激电源，结果很奇迹的，一次成功。只有个别需要飞线修正的，更新到原理图后，就可以直接量产了。不需要来回修正打样。。。。&lt;/p&gt;

&lt;p&gt;老板手下员工惊呼，居然只需打样一次！&lt;/p&gt;

&lt;p&gt;呵呵，没办法，我以前设计的时候是各种花冤枉钱，打样一堆失败品才能最终获得一个能用的。
最后痛定思痛，改掉了程序员的陋习。尽量让设计可以一次打样，下次就可量产。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;为什么说是程序员的陋习呢？因为程序员打样一个新版本，只要 Ctrl+S, 然后按 F7 编译。
失败了也不要紧，改了可以马上重新编译。于是程序员养成了随时编译（换到硬件领域就是随时随地打样一个错误的不能工作的电路）的陋习。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这次使用反激电源的经历，使得我对“开关电源”的理解更透彻了。&lt;/p&gt;

&lt;p&gt;反思了以前的设计，突然发现，其实 FlyBuck 并不比反激便宜。&lt;/p&gt;

&lt;h1 id=&quot;史上最低价的-acdc-芯片&quot;&gt;史上最低价的 ACDC 芯片&lt;/h1&gt;

&lt;p&gt;为啥呢？因为反激的控制器芯片非常的便宜。
得益于开关电源的大量使用。反激控制器芯片大概也许是这个星球上，产量最大，型号最多，价钱最低的芯片类型了。&lt;/p&gt;

&lt;p&gt;DCDC 降压和升压芯片，分别只能降压或者升压，还都比反激芯片贵。而反激即能降压，也能升压。&lt;/p&gt;

&lt;p&gt;Buck 降压只是从原理上来说简单，但是实际产品因为竞争没那么充分，没那么卷。所以 buck 芯片的价格还不够低。&lt;/p&gt;

&lt;p&gt;反激芯片，则卷出天际了。于是内部逻辑更复杂，需要更多晶体管实现的反激芯片，卖到了比 buck 更低的地步。&lt;/p&gt;

&lt;p&gt;GP8891CAS 就是这个卷出天际的市场里被我发现的一个胜利者。&lt;/p&gt;

&lt;p&gt;哪怕是以高价著称的立创商场，他的售价也不足2毛钱。&lt;/p&gt;

&lt;p&gt;于是，我将原先为宁波老板设计的一个廉价变频器，改进为使用反激电源供电。
原始的设计，是使用非隔离供电来降低成本。单片机直接使用 DK501 降压出来的5V非隔离电源。&lt;/p&gt;

&lt;p&gt;而使用了反激电源的设计，在只略微增加一元钱成本的情况下，就用上了隔离的设计。其实增加的一元成本，主要是因为需要使用一个2元的隔离器。没错，电源本身，还降低了1元的成本！&lt;/p&gt;

&lt;p&gt;是的，使用了最低价的 ACDC 反激芯片后，反激变换器隔离供电的成本，低于高压Buck降压的非隔离方案。&lt;/p&gt;

&lt;p&gt;为啥呢？因为高压 Buck 降压，使用的 DK501 芯片要6毛钱。芯片贵4毛。同时，高压 Buck 降压方案，需要使用 2mH/1A 的电感。这样的电感就要一元多了。&lt;/p&gt;

&lt;p&gt;而反激方案，芯片只要1毛6，同时，EE10 变压器也只要5毛钱。&lt;/p&gt;

&lt;h1 id=&quot;踩坑&quot;&gt;踩坑&lt;/h1&gt;

&lt;p&gt;我满心欢喜的完成了设计，然后打样。结果上电就炸芯片。炸的就是 GP8891CAS 。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;还好我测试的时候，那个板子只焊接了供电部分的元件。不会顺带报销掉单片机 集成功率模块 等其他高价值元件。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果我从淘宝买的 GP8891CAS，我大概 会怀疑自己买到假芯片了 (这其实也是淘宝假芯片多的谣言由来)。&lt;/p&gt;

&lt;p&gt;但是因为 GP8891CAS 恰恰淘宝上无货，我从立创商城买的。所以肯定是自己用错了，而不是假货的缘故。&lt;/p&gt;

&lt;p&gt;但是，仔细翻阅手册，发现就是照着手册的 “典型应用电路” 做的。&lt;/p&gt;

&lt;p&gt;到底哪出问题了呢？&lt;/p&gt;

&lt;h2 id=&quot;寻找-gp8891cas-的抄袭对象&quot;&gt;寻找 GP8891CAS 的抄袭对象&lt;/h2&gt;

&lt;p&gt;所以，问题就在于，抄袭芯片的手册通常是不能看的。
得看 原版被抄袭对象的手册。&lt;/p&gt;

&lt;p&gt;结果不搜不知，好家伙， GP8891CAS 已经不知道是几手抄袭了。
原来 GP8891CAS 是替代品的替代品的替代品。难怪价格卷出天际了。&lt;/p&gt;

&lt;p&gt;于是找到了他的替代对象，MT2513B。研读了它的手册。其实 MT2513B 的直接替代品是 3667B 。GP8891CAS 应该是替代3667B 的。&lt;/p&gt;

&lt;p&gt;然而 MT2513B 本身也是另一个芯片的替代品。。。。&lt;/p&gt;

&lt;p&gt;最后在各种 “相似芯片” 的海洋里，扒到了真正的源头，CR52177SC。&lt;/p&gt;

&lt;p&gt;为啥能确认这个是真正的源头呢？ 因为芯片原厂给了 “设计案例”。。而不单单是一个数据手册。&lt;/p&gt;

&lt;p&gt;正如 UC3842 在 德州仪器的官网能找到 设计案例 一样。&lt;/p&gt;

&lt;p&gt;然而，这个 CR52177SC 的设计案例，却不免费提供。免费提供的，只有这个设计案例的 “照片展示”。
原理图得联系经理，不能官网直接看。&lt;/p&gt;

&lt;p&gt;照片如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://9982006.s21i.faiusr.com/4/ABUIABAEGAAg6O7HlwYooKupuQQw3AU40wE.png.webp&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这就不大方了，不如 德州仪器啊。你不给，1毛6的替代品照样造出来抢你市场。&lt;/p&gt;

&lt;p&gt;不过，综合了好几款同功能的相互替代的芯片的数据手册后，我终于找到了炸芯片的根源。&lt;/p&gt;

&lt;p&gt;然后飞线修正之。&lt;/p&gt;

&lt;p&gt;最后跑起来了。&lt;/p&gt;

&lt;p&gt;演示视频看 &lt;a href=&quot;https://www.bilibili.com/video/BV1gkE5zdEs4&quot;&gt;这里&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;成品效果图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/25yuan_vvvf.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;总结&quot;&gt;总结&lt;/h2&gt;

&lt;p&gt;使用 反激控制器后，只要增加一个绕组，就能多获得一个隔离电源。因此，如果需要多路不同的电压供电，反激就特别适合。以几乎免费的方式获得隔离电源。&lt;/p&gt;

&lt;p&gt;相比去年我用2个独立的 220v转5v 的电源模块，板子上直接集成一个反激电源的方案，不仅仅大幅缩小了体积，也大幅降低了成本。&lt;/p&gt;

&lt;p&gt;经此次成功的反激电源设计经历，以后我可以是认为自己总算入门了硬件电路设计。&lt;/p&gt;

&lt;p&gt;然后，要多相信芯片的 原理框图，然后根据这个理解来设计外围电路，而不是抄 “典型应用电路图”。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>设计一个 3V - 100V 超宽压输入，15V 输出的电源</title>
   <link href="https://microcai.org/2025/05/02/boost-as-flyback.html"/>
   <updated>2025-05-02T00:00:00+00:00</updated>
   <id>https://microcai.org/2025/05/02/boost-as-flyback</id>
   <content type="html">&lt;h1 id=&quot;dcdc-的困境&quot;&gt;DCDC 的困境&lt;/h1&gt;

&lt;p&gt;在设计一个电机控制器的时候，遇到了一个多电压的问题。&lt;/p&gt;

&lt;p&gt;首先是单片机，需要 3.3V 供电。然后是传感器，需要 5V 供电，最后是栅极驱动，需要 15V （其实是 10-18V 都行 ）供电。&lt;/p&gt;

&lt;p&gt;如果使用降压 DCDC 的方式获得这些电压，意味着这个电机控制器，最低需要 15V 供电。&lt;/p&gt;

&lt;p&gt;这样，就不能适应广泛存在的电压标准：12V。因此，使用降压的方式获得这些电压，通常意味着设计输入电压为 24V 和以上。&lt;/p&gt;

&lt;p&gt;如果是 12V供电，则需要升压到 15V。如果想实现同时支持 12V 和  24V 供电，做法有两个。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- 使用 升降压 方案。需要4个MOS管，成本略高。

- 输入先降压到 5V, 然后升压到 15V。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;降压后升压或者升降压的方案，意味着板子可以支持 6V 12V 24V 三个电压等级的应用。&lt;/p&gt;

&lt;p&gt;但是，如果是单锂电池供电呢？则需要支持到 3V 输入。如果先降压到 3V, 则3V 到 15V 的升压倍数较大。Boost 变换会较为吃力。
可以先选择升压。3V 先升压到 16V。 然后 从 16V 降压到哦 15V 和 5V。如果输入超过 16V ， 则前级的 Boost 芯片停止工作，直通到后面的 降压 15V 的芯片。&lt;/p&gt;

&lt;p&gt;这意味着，不管是 3V 升压16V的芯片，还是 16V 降压 15V 的芯片，都要承受输入的最大电压。&lt;/p&gt;

&lt;p&gt;如果设计为要求 3V - 100V 输入呢？又当如何？&lt;/p&gt;

&lt;h1 id=&quot;反激方案&quot;&gt;反激方案&lt;/h1&gt;

&lt;p&gt;通常来说，反激是用于 AC220V 输入的方案。但是，实际上反激最低也可以支持 3V 的低压。&lt;/p&gt;

&lt;p&gt;反激的方案有个优势，就是输入的耐压是由 MOS 管决定的，和芯片自身无关。因为反激的芯片是双供电的。首先是用一个启动电阻供电，等第一个 pwm波发出去后，就由辅助绕组供电了。芯片本身一直在低压环境下工作，只有 MOS 管需要承受百伏高压。反激的另一个优势是另一路 5V 电压可以从变压器直接获得，不需要再经过一次 DCDC 变压。&lt;/p&gt;

&lt;p&gt;当然，现成的反激芯片，其实都不支持 3V 这么低的电压下启动。最少也要 10V 的电压下启动。&lt;/p&gt;

&lt;p&gt;然而，反激其实就是 Boost 升压。因此，任意 Boost 升压 DCDC 芯片，其实都支持当成反激控制器用。&lt;/p&gt;

&lt;p&gt;而且，反激比 Boost 的一大优势是，反激能降压。而 Boost 只能升压。这意味着，如果使用反激的方式，输入 不管大于 15V 还是小于 15V ， 都可以从容的获得 15V 的输出。&lt;/p&gt;

&lt;p&gt;而且反激获得的输出还是隔离的。虽然在低压电机控制器上，并不太需要这个隔离能力就是了。&lt;/p&gt;

&lt;p&gt;为了支持从 3V 的低压下启动，我需要寻找2满足2个要求的 Boost 升压芯片：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1. 最低支持到 3V 输入。
2. 使用外置 MOS 管
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果 100V 是刚需，则第二个要求是必须的。如果只需要 3V - 30V 这样的常规低压，则 2 是非必须的。&lt;/p&gt;

&lt;p&gt;如果是使用内置 MOS 的 boost 升压芯片，要记得，反激是有 反射电压 的。因此如果要支持 30V 输入， 则需要使用  60V 的 boost 芯片。
选外置 mos 管也是一样的道理。要支持到 100V 输入，需要选择 150V - 200V 耐压的 MOS，当然升压控制器自身的输入耐压就是无关紧要的了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/SCH_3-75V输出15V_2025-05-02.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;变压器有4个次级绕组。其中一个是给控制器供电的辅助绕组。剩下3个，分别是5V 输出，防浪涌MOS专用绕组，和 15V 输出。&lt;/p&gt;

&lt;p&gt;防浪涌本来使用一个继电器即可，但是继电器有静态工作损耗。所以改成了 NMOS. 这个 NMOS 需要浮地驱动，因此需要隔离电源。反正反激的隔离电源获得成本低。&lt;/p&gt;

&lt;p&gt;5V 输出实际上是 6V 输出。需要接 LDO 降压为  5V 和 3.3V 使用。因为 5V 和 3.3V 都是对电压纹波比较敏感的元件。因此后面再跟 LDO 降压并进一步降低纹波。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>直流 UPS 功能的电源</title>
   <link href="https://microcai.org/2025/04/24/ups-flyback.html"/>
   <updated>2025-04-24T00:00:00+00:00</updated>
   <id>https://microcai.org/2025/04/24/ups-flyback</id>
   <content type="html">&lt;h1 id=&quot;恒流恒压模式和电池的充电原理&quot;&gt;恒流恒压模式和电池的充电原理&lt;/h1&gt;

&lt;p&gt;开关电源分恒压模式，和恒流恒压模式两种。区别就在于，负载消耗的电流超过了电源的供电能力时电源的应对策略。 恒压电源会立即切断输出进入保护模式。可能可以经过一段时间的等待后重新打开输出。即 “打嗝模式”。而恒流恒压模式下，电源会减小输出电压，直到负载消耗的电流小于设定值。适用于负载电流会随着电压的降低而降低的情况使用。例如，充电。&lt;/p&gt;

&lt;p&gt;电池是一个电压源，它自身的内阻极小。因此，如果外部电压低于电池，电池就会放电，以稳定电压。如果外界电压高于电池，电池则会充电，以稳定电压。
充电电流的大小，可以用 （电源电压-电池电压）/电池内阻 计算。而电池的内阻极小，电压会随着充电进行逐步提升，因此充电器需要有“恒压恒流”模式。&lt;/p&gt;

&lt;p&gt;首先输出电池的满电电压，然后设定一个最大的电流。和电池并联后，开关电源的电压大于电池电压（除非是满电状态）再加上电池内阻很小，充电器会输出一个很大的电流。此时充电器立即进入恒流模式。降低输出电压，直到电流等于设定的电流就不再降低电压。&lt;/p&gt;

&lt;h1 id=&quot;同时并联运行负载和对电池充电&quot;&gt;同时并联运行负载和对电池充电&lt;/h1&gt;

&lt;p&gt;如果负载消耗的是 直流电，那么将电池逆变成 220v 然后再由负载的电源转换为直流电，这对电池存储的能量无疑是巨大的浪费。而且电池的容量非常昂贵。因此，在低压系统里，最佳的 UPS 实践方法，是直接并联一组 12V 电池进行供电。当开关电源的输出消失，就是电池立即填补输出的时候。&lt;/p&gt;

&lt;p&gt;但是，在 直接并联电池当 UPS 的12V 低压供电系统里，如果使用 12v 恒流恒压电源直接对负载供电并且并联一个电池，则无法控制电池的充电电流。
只要电池未进入涓流充电模式，那么电池总是倾向于吃完电源的输出电流。迫使电源进入恒流模式。
因此，充电电流总是会被动的设置为 充电器的输出电流 - 负载消耗电流。
如果负载消耗的电流是一个定数，倒是可以设定 $充电器的输出电流 = 负载消耗的定数电流 + 期望的充电电流。$&lt;/p&gt;

&lt;p&gt;然而，现实中，负载的电流消耗总是波动的。而如果负载消耗电流波动较大，例如负载最大可消耗 10A 电流的情况下，就需要设置恒流 10A输出。这意味着电池最大必须接受 10A 的充电电流。必须假定负载有可能不消耗任何电流。而如果不配置 10A 的充电器，那么可能会在极端情况下，即使没有停电也会导致电池越用越没电。&lt;/p&gt;

&lt;p&gt;因此，我们需要设计一种特殊的充电器，它只有一路 12V 输出，电池和负载并联到一起。但是却可以同时监控负载和电池的电流。总输出进行恒压恒流模式的同时，对冲入电池的电也加恒压恒流模式。
它的输出电压会维持在恰好满足负载的供电，并且对电池进行恒流恒压模式充电。也就是说，如果设定充电电流为 1A 。那么充电器会恰好输出电压使得电流 = 1A + 负载消耗电流。如果电池已经进入恒压充电阶段，则正好输出电池满电电压。即便负载的电流从 0A 到 10A 变化，也不会用更大的电流对电池充电。
这样的 UPS ，在电池亏电的情况下，就不会盲目的以最大电流模式输出，以期电池能吸纳剩余电流。也不需要特意配置一个小功率充电器限制电池充电电流又导致极端下电池边充边掉电。
而是可以恰到好处的按设定电流充电，而不管充电器本身的电流输出能力是多少。&lt;/p&gt;

&lt;h1 id=&quot;实现原理&quot;&gt;实现原理&lt;/h1&gt;

&lt;p&gt;UPS 的原理是：
充电器为两路并联的 12V 输出。2个输出在内部是并联的，由同一个变压器绕组供电。
电池和输出实际上是并联的，因此停电后，电池会立即放电为负载供电。中间没有任何切换延迟。&lt;/p&gt;

&lt;p&gt;同时进行电池限流的原理是：&lt;/p&gt;

&lt;p&gt;输出虽然是并联的，但是使用了2个采样电阻分别采样两路输出的电流大小。
整个电源的输出部分，就有 恒压反馈，负载恒流反馈，电池恒流反馈，这三个负反馈控制环路。&lt;/p&gt;

&lt;p&gt;输出电压由 PWM 的占空比控制，而 PWM 占空比则由单个 负反馈控制环进行 三环竞争。任意一个环路要求降低 pwm 占空比，则 pwm 占空比就要降低。而 pwm 控制器内部，又有提高 pwm 占空比的内禀冲动。当三路反馈都没有要求降低占空比，则占空比缓慢增加。&lt;/p&gt;

&lt;p&gt;电池充电过程中，电池的恒流控制环路起决定性作用。
电池充满后，稳压控制环路则起主导作用。
负载的恒流环，则正常来说几乎不起作用。主要起一个限制最大功率，保护电源的作用。&lt;/p&gt;

&lt;h1 id=&quot;应用限制&quot;&gt;应用限制&lt;/h1&gt;

&lt;p&gt;负载需要能接受锂电池直接供电。因此需要适应锂电池满电到空电的电压变化（12.6v - 10v ），不能需要绝对稳定的12V供应。&lt;/p&gt;

&lt;h1 id=&quot;星火计划&quot;&gt;星火计划&lt;/h1&gt;

&lt;p&gt;这样一个电源，想明白工作原理后，我就准备自己制作一个。但是做硬件是一件烧钱的事情。于是恰好看到了 嘉立创 搞的 “星火计划”。就报名参加了。主要是参加计划，可以报销在立创商城买耗材和在嘉立创打PCB的钱。当然，如果运气好能拿个千八百的安慰奖也很好。&lt;/p&gt;

&lt;h2 id=&quot;进度&quot;&gt;进度&lt;/h2&gt;

&lt;p&gt;4月22日，UPS开关电源的计划审核通过。创建工程，将前期思考绘制到一半完成度的工作导入星火计划的专用工程并继续。这个工程源码会在计划完成后由嘉立创自动公开。（嗯，项目是必须开源的，方便后来者复刻。）
4月24日，原理图和PCB初稿绘制完毕。由于立创商城没有变压器的外壳和合适的高频变压器，需要在淘宝上单独购买。&lt;/p&gt;

&lt;p&gt;– 接下来的工作：&lt;/p&gt;

&lt;p&gt;1：严格复核原理图，确保一次成功。星火计划只能报销一次打样的钱。如果有错误需修正，第二次打样可得自费了。得悠着点。
2：等外壳到货后，还得拿游标卡尺测量并对 pcb 进行微调，确保 打样回来的 pcb 能严丝合缝的装入外壳。并且不会发生 pcb上的元件和外壳干涩的事情。
3：打样并进行耗材经费申请。方法是先下单，然后申请经费。申请到经费后PCB订单就会变成0元支付。:）
4：等待PCB送达，然后编写单片机代码调试。
5：寄一台样机到嘉立创总部验收。&lt;/p&gt;

&lt;h2 id=&quot;目前渲染的草图&quot;&gt;目前渲染的草图&lt;/h2&gt;

&lt;p&gt;正面：
&lt;img src=&quot;/images/ups_flyback1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;背面：
&lt;img src=&quot;/images/ups_flyback2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;用户交互的设计&quot;&gt;用户交互的设计：&lt;/h2&gt;

&lt;p&gt;屏幕的左边有2个电位器。左边的电位器用于微调输出电压。输出电压的调节范围是 11v - 15v。
右边的电位器用于设定充电电流。充电电流的调节范围是 0-5A。&lt;/p&gt;

&lt;p&gt;调节电压需要断开负载。拧动旋钮，屏幕上会实时显示当前的输出电压。
调节充电电流的旋钮，屏幕上也会实时显示设定的充电电流。&lt;/p&gt;

&lt;p&gt;屏幕的显示应该是这样的：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Output: 12.6V/5A
Load: ?A
Bat: ?A
CurMODE: CC or CV
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;停电后的动作&quot;&gt;停电后的动作&lt;/h2&gt;

&lt;p&gt;为了节约电池电量。交流停电后，要关闭屏幕，单片机进入节能模式。除非用户按下唤醒按钮来点亮屏幕。&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;更新&quot;&gt;更新：&lt;/h1&gt;

&lt;p&gt;PQ2620变压器无货。是说现成的已经绕好的样品无货。我不想花大价钱打样变压器，也不想自己绕制。太累了。所以转而买了  PQ3230 规格的。有现货，适合 12V9A 的开关电源的。&lt;/p&gt;

&lt;p&gt;于是紧急修改设计。好在 PQ3230 也没有大多少，微调下元件布局就布的下了。&lt;/p&gt;

&lt;p&gt;电源外壳到了，用游标卡尺测量了尺寸后，重新微调了一下 PCB 大小。对pcb边缘的元件还得诺腾一番，因为有干涩。这下算是定稿了。&lt;/p&gt;

&lt;p&gt;准备在 4月28日，也就是周一这天，向嘉立创申请耗材。顺利的话喜欢5月份能打样回来一个完成品吧。&lt;/p&gt;

&lt;p&gt;有了实际的外壳掂量在手里，又修改了下接口，把调整电位器放到最右侧。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/flyback_ups_v2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;更新2&quot;&gt;更新2：&lt;/h1&gt;

&lt;p&gt;PQ2620 后来到了。PQ3230 和 PQ2620 都到手后，又拿外壳比了一下，发现 PQ3220太高了，会完全顶到外壳顶部，略微高了 0.5mm 。靠钢制外壳的弹性勉强是可以塞进去的。。
但是考虑到 PQ2620 还是到了，于是PCB设计由改回了 PQ2620 。&lt;/p&gt;

&lt;h1 id=&quot;更新3&quot;&gt;更新3：&lt;/h1&gt;

&lt;p&gt;4月29号终于定稿，提交了打样订单。然后申请耗材。等待耗材申请通过中。PCB+SMT+元件费用一共 1160 。不知道嘉立创要多久审核。估计要51以后了。&lt;/p&gt;

&lt;h1 id=&quot;更新3-1&quot;&gt;更新3&lt;/h1&gt;
&lt;p&gt;过了个 五丶一，耗材审核通过了。随后去 smt 订单页面，发现待支付的订单已经变成 0元了。马上点确认，立即支付，一气呵成。&lt;/p&gt;

&lt;p&gt;估计 5月14日应该能拿到2块PCBA了。&lt;/p&gt;

&lt;h1 id=&quot;更新4&quot;&gt;更新4&lt;/h1&gt;

&lt;p&gt;5月16日，收到了 嘉立创 寄来的，打好的 PCBA。把变压器焊接上后，完美启动。&lt;/p&gt;

&lt;p&gt;开始编写单片机代码。单片机代码编写完成后，项目即算大功告成……大概。&lt;/p&gt;

&lt;h1 id=&quot;更新5&quot;&gt;更新5&lt;/h1&gt;

&lt;p&gt;5月 19 日，代码初步编写完成。电池也寄到了。&lt;/p&gt;

&lt;p&gt;装上电池后，成功！能限制电池充电电流！输出电压会恰到好处的停在刚好让电池以设定电流充电。&lt;/p&gt;

&lt;p&gt;拔掉 220V 后，电池立即进入放电模式。 UPS 模式成功！&lt;/p&gt;

&lt;p&gt;接下来，就是完善代码。然后打包寄到嘉立创总部。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>变频器原理和设计指导</title>
   <link href="https://microcai.org/2025/04/19/vvvf-explained.html"/>
   <updated>2025-04-19T00:00:00+00:00</updated>
   <id>https://microcai.org/2025/04/19/vvvf-explained</id>
   <content type="html">&lt;h1 id=&quot;从-buck-降压电路说起&quot;&gt;从 Buck 降压电路说起&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/images/basic_buck.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;上图为一个基本的 Buck 降压电路。 Q1 和 Q2 是互补的两个 MOS/IGBT 管。&lt;/p&gt;

&lt;p&gt;所谓互补，是两个同时只有一个会导通。要么是 Q1 打开，要么是 Q2 打开。&lt;/p&gt;

&lt;p&gt;Q1 关闭的时候， Q2 必须打开。Q1 打开的时候，Q2 必须关闭。&lt;/p&gt;

&lt;p&gt;Q1 和 Q2 可以使用一个 PWM 信号控制。&lt;/p&gt;

&lt;p&gt;那么 pwm 信号的占空比，就决定了 Q1 打开的时间占比。也就决定了 OUTPUT 输出端的电压占 总 电压的比。&lt;/p&gt;

&lt;p&gt;OUTPUT 处的电压 = VCC * pwm 占空比。&lt;/p&gt;

&lt;p&gt;记住这个。&lt;/p&gt;

&lt;h1 id=&quot;两路-buck-降压电路&quot;&gt;两路 Buck 降压电路&lt;/h1&gt;

&lt;p&gt;将上述降压电路复制成2份，就构成了一个逆变器。&lt;/p&gt;

&lt;p&gt;为何呢？&lt;/p&gt;

&lt;p&gt;看图&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/buck_as_H_bridge.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;两个 Buck 电路，输出 OUTPUTA 和 OUTPUTB 。&lt;/p&gt;

&lt;p&gt;如果 OUTPUTA 的电压 &amp;gt; OUTPUTB 的电压。那么对于接到 OUTPUTA和OUTPUTB的负载来说，电流就会从 OUTPUTA流出，经过负载后，流入 OUTPUTB.&lt;/p&gt;

&lt;p&gt;如果 OUTPUTA 的电压 &amp;lt; OUTPUTB 的电压。那么对于接到 OUTPUTA和OUTPUTB的负载来说，电流就会从 OUTPUTB流出，经过负载后，流入 OUTPUTA.&lt;/p&gt;

&lt;p&gt;也就是说，对负载来说，OUTPUTA + OUTPUTB 就相当于是一个交流电源。&lt;/p&gt;

&lt;p&gt;这种2个 buck 构成的就是所谓的 “逆变桥” 了。这2个 buck 降压电路，则称为 左半桥和右半桥。&lt;/p&gt;

&lt;p&gt;一般来说，如果要输出正弦波交流电，交替的让其中一桥输出 0 电压。另一个桥输出半个 sin 波。&lt;/p&gt;

&lt;p&gt;所谓输出 0 电压，指的是打开 下管，关闭上管。
所谓输出半个 sin波，是指 只输出 sin 正弦波为正的那半个周期。&lt;/p&gt;

&lt;p&gt;而输出 sin波的方法就是，在每个 pwm 周期里，调节 pwm 的占空比。 pwm 占空比的调节方法就是计算 sin 。假设 pwm 周期是 18khz ，输出 50hz 交流电，那么正好，每个 pwm 周期，为 1 度。
每个完整的 sin 波形，由 360 个 pwm 脉冲构成。 于是 直接一个 自增的 i 变量。 pwm = sin( i 度)。 i=i+1; 以上2行代码，每个 pwm 周期执行一次。每次执行用于设定本次 pwm 的占空比。&lt;/p&gt;

&lt;p&gt;这样就可以输出按 sin 规律变化的 pwm 占空比了。&lt;/p&gt;

&lt;h1 id=&quot;三路-buck-降压电路&quot;&gt;三路 Buck 降压电路&lt;/h1&gt;

&lt;p&gt;如果将上文提到的 Buck 降压电路复制成3分呢？
那就得到了一种叫 “三相逆变桥” 的电路。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/3pharse_full_bridge.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这三个 Buck 电路，就可以输出三相交流电了。&lt;/p&gt;

&lt;p&gt;当然，需要对 3个半桥的 PWM 占空比进行控制才能实现。&lt;/p&gt;

&lt;p&gt;以上就是变频器的硬件原理了。 至于如何输出 3路 pwm 波形使得 6 个 IGBT 能实现 三相逆变，可以看我往期的文章。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>防启动浪涌电流设计</title>
   <link href="https://microcai.org/2025/04/19/surge-avoid-startup.html"/>
   <updated>2025-04-19T00:00:00+00:00</updated>
   <id>https://microcai.org/2025/04/19/surge-avoid-startup</id>
   <content type="html">&lt;h1 id=&quot;启动浪涌&quot;&gt;启动浪涌&lt;/h1&gt;

&lt;p&gt;在许多用电设备里，都要配置一个巨大的储能稳压电容。&lt;/p&gt;

&lt;p&gt;这个大电容会在设备启动的瞬间，吸收巨大的充电电流。形成浪涌。&lt;/p&gt;

&lt;p&gt;为了避免浪涌电流，需要限制充电电流。一个典型的做法是使用 NTC 电阻。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/ntc_inruash_avoid.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;NTC 电阻初始电阻较高。工作一段时间后电阻值下降。这样就以较低的成本解决了开机浪涌电流。&lt;/p&gt;

&lt;p&gt;但是 NTC 毕竟不是导线，高温下电阻仍然有一些。对效率不利。
而且关机后立马插电开机，则 NTC 温度较高，当时电容却是空的。此时防止不了浪涌。&lt;/p&gt;

&lt;p&gt;因此，通常的做法是使用继电器防浪涌。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/inrush_current_avoid_relay.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;插入插头的时候， 外部电源通过 R3 电阻 对 C1 充电。&lt;/p&gt;

&lt;p&gt;等 C1 充满后，Q1 打开，继电器吸合，旁路掉启动电阻。&lt;/p&gt;

&lt;p&gt;不过，都用上继电器了，使用 单刀双掷的继电器其实并不增加成本，反而还可以多一个功能：下电自动进行电容泄放。&lt;/p&gt;

&lt;p&gt;如图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/inrush_current_avoid_relay2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;一开始，继电器3 5 脚常闭。 2 5 脚常开。&lt;/p&gt;

&lt;p&gt;于是，上电后，电源通过 R3 对 C1 充电。&lt;/p&gt;

&lt;p&gt;C1 充满后，控制信号控制 MOS 导通，导致继电器吸合。 继电器的 3 5 脚断开， 2 5 脚闭合。&lt;/p&gt;

&lt;p&gt;这样 R3 就被继电器旁路了。&lt;/p&gt;

&lt;p&gt;断电后，继电器由于停电自动断开。 2 5 脚断开，3 5 脚自动闭合。&lt;/p&gt;

&lt;p&gt;此时 C1 电容通过 R4 电阻进行释放。&lt;/p&gt;

&lt;p&gt;也就是，实现了掉电自动对 C1 进行释放的机制。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>DMA 大幅降低 SVPWM/SPWM 调制的 CPU 占用</title>
   <link href="https://microcai.org/2025/04/14/use-dma-to-reduce-spwm-overhead.html"/>
   <updated>2025-04-14T00:00:00+00:00</updated>
   <id>https://microcai.org/2025/04/14/use-dma-to-reduce-spwm-overhead</id>
   <content type="html">&lt;h1 id=&quot;svpwm-和-dma&quot;&gt;S(V)PWM 和 DMA&lt;/h1&gt;

&lt;p&gt;在变频器里，FOC 通常使用 定时器 输出 PWM 实现。&lt;/p&gt;

&lt;p&gt;通常来说，需要在每个 PWM 的益出中断里，重新计算三路输出的 PWM 占空比。&lt;/p&gt;

&lt;p&gt;如果使用非常高的pwm频率，那么 pwm 的中断将非常频繁。这对于 cpu 时钟并不高的单片机来说，会极大的降低 cpu 处理其他业务逻辑的能力。&lt;/p&gt;

&lt;p&gt;而这个问题，在使用 “同步调制” 的方式下，有一个很好解决的办法。&lt;/p&gt;

&lt;p&gt;在同步调制下，载波频率随着输出的频率变化而变化。这意味着，pwm 的占空比实际上是周期性发生的。&lt;/p&gt;

&lt;p&gt;比如使用 1:360 的同步调制下，每个 sin基波由 360个pwm方波构成。意味着，每隔 360 次，pwm 占空比就会重复。&lt;/p&gt;

&lt;p&gt;因此，如果预先分配一个 360 份的 pwm占空比。然后使用 DMA 搬运 pwm占空比到 TIM 定时器。则整个 spwm 过程中， cpu 都不再需要参与。&lt;/p&gt;

&lt;p&gt;如果需要改变输出频率，则 cpu 只须调节 pwm 的频率，甚至不需要更新 DMA 的数据。&lt;/p&gt;

&lt;p&gt;直到 pwm 频率已经不适应输出频率，直到发生“换挡” — 切换调制比。&lt;/p&gt;

&lt;p&gt;每次切换调制比的时候，就需要重新生成 pwm占空比序列，然后重新设置 DMA 控制器。&lt;/p&gt;

&lt;p&gt;而且，三个 pwm 通道可以使用同一份 pwm 占空比循环数据。只要初始运行的时候，使用不同的起始地址。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>简谐运动 和 CNC 运动插补</title>
   <link href="https://microcai.org/2025/03/07/sin-in-cnc.html"/>
   <updated>2025-03-07T00:00:00+00:00</updated>
   <id>https://microcai.org/2025/03/07/sin-in-cnc</id>
   <content type="html">&lt;h1 id=&quot;数控机床的基础知识&quot;&gt;数控机床的基础知识&lt;/h1&gt;

&lt;p&gt;机床每个轴的伺服电机，期望输入的是步进信号：一个脉冲信号和一个方向信号。&lt;/p&gt;

&lt;p&gt;因此，机床的运动控制器，需要将程序中的移动指令，转换为多个轴的步进信号。&lt;/p&gt;

&lt;p&gt;例如一个分辨率为 0.001mm 的机床，控制器每发送一个脉冲信号，电机就旋转一个小角度，通过传动系统后，体现为主轴移动了 0.001mm。&lt;/p&gt;

&lt;p&gt;对多轴联动机床来说，程序中的移动指令，要被控制器分解为多个轴的移动。然后控制器需要精确的控制每个轴的步进信号，使得多个轴可以协调运动，完成所谓的多轴联动。&lt;/p&gt;

&lt;p&gt;例如程序指令是将主轴从 (0,0,0) 坐标移动到 （0.1,0.2,0.3）。那么控制器需要在 X 轴发送 100 个脉冲，Y 轴发送 200 个脉冲，Z 轴发送 300 个脉冲。&lt;/p&gt;

&lt;p&gt;不仅仅是要发送这几个脉冲，而且要确保，这几个脉冲的间隔时间正好是 3:2:1 , 于是 X 轴发送 100 个脉冲，Y 轴发送 200 个脉冲，Z 轴发送 300 个脉冲 花费的时间恰好相等，这样，机床主轴在伺服电机的驱动下，就可以让主轴沿着直线从  (0,0,0) 坐标移动到 （0.1,0.2,0.3）。&lt;/p&gt;

&lt;p&gt;过去，数控机床只能实现两轴联动。例如 将主轴从 (0,0,0) 坐标移动到 （0.1,0.2,0.3），机床需要分解成2个步骤执行。
首先抬起主轴，也就是 将主轴从 (0,0,0) 坐标移动到 （0,0,0.3）。 接着移动主轴到 （0.1,0.2,0.3）。无法实现三个轴同时运动。&lt;/p&gt;

&lt;p&gt;这种机床，现在已经非常罕见了。人们形象的称呼其为  2.5轴机床。因为物理上有三个轴，但是只能2个轴实现联动。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;不过控制器限制下的 2.5 轴机床虽然少见，但是将3轴机床当 2.5轴使用却还是主流。比如 3D 打印，就几乎只需要两轴联动。虽然实际上它的控制器是完全支持3轴联动的。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;为了加工复杂曲面，机床会额外引入 3个旋转轴中的2个。三个旋转轴记为是 A B C 轴，分别对应在 X Y Z 轴上的旋转。由于主轴的刀具自身就在旋转，因此通常 主轴所在的轴是无意义。因此只需要2个旋转轴。通常主轴是在 Z 轴上，因此一般而言立式加工是 A B 两个旋转轴，而卧式加工的主轴是水平放置的，因此通常会使用 A C 轴 或者 B C 轴。&lt;/p&gt;

&lt;p&gt;旋转轴的目的，就是让 刀具的转轴，不再平行于机床的 Z 轴承(对于立式机床而言) 或者 X 轴承（对于卧式机床而言）。&lt;/p&gt;

&lt;p&gt;凡引入了2个旋转轴而构成的5轴机床，必然，一定，是要实现五轴联动的。&lt;/p&gt;

&lt;p&gt;前面说过，联动的意思，就是在 程序中给出 的多个 坐标，要控制器给出的多个轴的脉冲步进信号相互同步，实现多个轴 &lt;strong&gt;同时到达&lt;/strong&gt;。不仅仅是要同时到达，还得匀速同时到达。这样起点到终点之间，才是一条直线。因为程序给出的，就是直线运动指令。&lt;/p&gt;

&lt;h2 id=&quot;关于旋转刀具中心-rtcp&quot;&gt;关于旋转刀具中心 （RTCP）&lt;/h2&gt;

&lt;p&gt;传统上，机床的控制程序，是直接针对5个轴的坐标的。所谓五轴联动，是程序里会直接给出每段运动轨迹的 X Y Z A B C 六个坐标轴 （ ABC 三个坐标里有一个会永远为  0 ，具体看机床类型）。控制器协调5个轴同时到达程序给定位置，即可实现 五轴联动。&lt;/p&gt;

&lt;p&gt;但是，这意味着加工程序必须知道&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;旋转轴是主轴旋转，还是工件转。&lt;/li&gt;
  &lt;li&gt;机床的旋转轴的位置（2个旋转轴的在 XYZ 上的位置）。&lt;/li&gt;
  &lt;li&gt;旋转轴中心点的位置和夹持的工件原点的偏移&lt;/li&gt;
  &lt;li&gt;刀具的具体长度&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这无疑会增加加工程序和特定机床和刀具的耦合程度。&lt;/p&gt;

&lt;p&gt;因此，就有了所谓的 RTCP 功能的机床。
RTCP 功能的机床，实质上是一种虚拟机床。&lt;/p&gt;

&lt;p&gt;程序给定的位置，不再直接表达机床的轴的坐标，而是表达 &lt;strong&gt;刀尖&lt;/strong&gt; 的坐标。因此，在五轴联动+RTCP的组合下，所有的机床就都是一种机床：XYZAB 立式机床。并且旋转的不是主轴，而是刀具。程序给出 A 轴旋转，不是让 机床的 主轴直接绕 A 轴旋转，而是在 绕 A 轴旋转的时候，同时按圆形轨迹移动 YZ 轴。也就是说，程序是直接对 刀尖进行轨迹编程，而不是对机床的5个轴进行编程。&lt;/p&gt;

&lt;p&gt;至于刀具轨迹，如何转换为机床5个轴的运动，就是机床的控制器需要操心的事情了。&lt;/p&gt;

&lt;p&gt;这样，程序就通用化了。在编程的时候，程序只需要考虑 刀路和刀具的半径，不再需要考虑机床的具体结构。连刀具长度都不需要考虑了。&lt;/p&gt;

&lt;p&gt;这样在加工的时候，一线操作人员可以更换长度不同的刀具，也可以使用原来的加工程序（其实在加工的时候，是刀具磨损后长度有轻微差异，可以在夹刀具的时候输入刀具准确的长度而无需修改加工程序，而不是真的换不同类型刀）。&lt;/p&gt;

&lt;p&gt;由于五轴机床必须支持五轴联动，因此俗称的真五轴和假五轴，其实就是指是否支持 RTCP 功能。&lt;/p&gt;

&lt;h1 id=&quot;加工指令和插补&quot;&gt;加工指令和插补&lt;/h1&gt;

&lt;p&gt;在机床的控制中，运动指令主要分直线运动，和圆形运动。
由于加工程序中，只给出了刀具路径的位置参数，而没有给出中间的每个点的位置（无数个点啊，也给不出来）。所谓位置参数，指直线的时候，给出的是目的地址。机床要从当前位置以直线运动到给定位置。如果是圆形，则给的是圆心位置和圆弧角度。机床要以当前位置和给出的圆心位置做圆周运动。&lt;/p&gt;

&lt;p&gt;对每段轨迹来说，中间的位置，就需要机床的控制器进行“插补”。&lt;/p&gt;

&lt;p&gt;因此，机床的插补，主要就分为直线插补 \&amp;amp; 圆形插补。&lt;/p&gt;

&lt;p&gt;直线插补的方式很好理解。就是计算每轴需要插补多少个脉冲，然后控制器输出多个轴在相同的时间里正好输出这么多个脉冲。&lt;/p&gt;

&lt;p&gt;那么圆形的插补呢？&lt;/p&gt;

&lt;h2 id=&quot;简谐运动&quot;&gt;简谐运动&lt;/h2&gt;

&lt;p&gt;弹簧的震动，钟摆的摆动，在大自然都被称为“简谐运动”。因为他们的运动位移和运动速度，在时间上看，都是 正弦波。&lt;/p&gt;

&lt;p&gt;而圆周运动，恰好就是简谐运动在多个轴上的叠加。&lt;/p&gt;

&lt;p&gt;一个圆周运动，在三维坐标系上，分解成3个坐标轴上的运动。我们会发现，每个轴上的运动，都是正弦的。&lt;/p&gt;

&lt;p&gt;因此，对机床进行圆周的联动插补，本质上就是让三轴进入同周期的简谐运动。&lt;/p&gt;

&lt;p&gt;因此，机床的控制代码，在读取加工程序的时候，可以将多轴运动插补，首先分解为多个轴上自身的  位移-时间 曲线。
而这条曲线，只需要 直线和三角函数 两种方程即可描述。&lt;/p&gt;

&lt;p&gt;而且 RTCP 是可以转换的。RTCP 让直线运动被补偿为圆周运动，让圆周运动被补偿为另一个圆周运动或者直线运动。&lt;/p&gt;

&lt;h2 id=&quot;运动插补的中间描述&quot;&gt;运动插补的中间描述&lt;/h2&gt;

&lt;p&gt;因此，最终，送入插补器的代码，就是 5 条或者3条，同步的 位置-时间 曲线。 几条曲线，又各以相同的 “时间” 分片进行分段描述。&lt;/p&gt;

&lt;p&gt;在每个时间段内，要么是直线，要么是正弦曲线。&lt;/p&gt;

&lt;p&gt;而后插补器，就可以根据这个数据生成 脉冲信号。&lt;/p&gt;

&lt;p&gt;为啥要这么设计呢？ 因为插补器需要输出非常高精度的脉冲信号。&lt;/p&gt;

&lt;p&gt;而插补器进行插补的时候，就只需要进行最简单的直线或者正弦的插补。比如步进驱动器一般使用 500khz 的信号。意味着插补器最快每 2us 输出一个脉冲。
但是插补器却不能工作在 500khz .因为如果需要输出一个 300khz 的脉冲，让主轴以 300mm/s 的速度移动起来，那么工作在 500khz 的插补器，只能要么输出 500khz 的脉冲要么输出 250khz 的脉冲。二者交替出现。这会导致机床抖动。所以插补器会以 5Mhz 的频率工作，但是限制每 2us 最多输出一个脉冲。只有简单的直线和正弦，才可以满足这个计算时间要求。
同时，插补器必然，也只能是一个独立的硬件。最好是 FPGA 或者 ASIC。
因为高阶的伺服驱动，可以接受 5Mhz 的步进信号。这意味着插补器需要以 50Mhz 工作。也就是每 20ns 就要完成一次计算。而且是雷打不动的每20ns完成一次计算。这样的时间要求，只能使用独立的硬件才能完成。&lt;/p&gt;

&lt;h2 id=&quot;廉价插补器的设计&quot;&gt;廉价插补器的设计&lt;/h2&gt;

&lt;p&gt;当然，如果对精度要求放宽，也是可以使用单片机而不是FPGA输出步进脉冲的。例如很多廉价3D打印机，其机床主控就是一个单片机。&lt;/p&gt;

&lt;p&gt;如果使用单片机，则需要双核单片机。独立划一个核用来跑插补，生成高精度脉冲，另一个核用来进行运动解算。通常单片机运行插补，其插补频率很难突破 100khz。这导致其输出分辨率很难突破 1万步/s。对于一个桌面机床来说，其每步步进通常不会低到 1um ，而是在 0.05mm 。因此 1万步/s 的最大脉冲输出，基本上也能满足 500mm/s 的运动速度。其实机床的加工基本上是以 3000mm-6000mm每分钟进行的。主要这里的时间单位秒和分的区别。每分钟 6米其实也就每秒10厘米。&lt;/p&gt;

&lt;p&gt;需要更高的插补速度，是因为有很多高端机床，每脉冲的分辨率是 100纳米。然后还要能做到每分钟 6米的进给速度。&lt;/p&gt;

&lt;p&gt;当然，另一个廉价做法是，将插补曲线序列化为 PCM 编码的脉冲。然后使用 DMA 传输给 GPIO 硬件。由于 DMA 的速度是恒定的，因此可以以极高的速度输出一个非常稳定的步进信号。这样使用单核单片机即可完成工作。&lt;/p&gt;

&lt;p&gt;例如，在 72Mhz 总线频率下，DMA 能以 72M/s 的速度向 GPIO 写入数据。&lt;/p&gt;

&lt;p&gt;那么就把待输出的信号，看成是  72Mhz 采样率的 PCM 声音。每秒就是 72MB 的数据。以 20us 为时间分片，每个分片为 1440字节。每次 DMA 中断，就生成 1440 个新的数据交给 DMA 引擎。&lt;/p&gt;

&lt;p&gt;因为DMA传输一个1再传输一个0才能完成一个脉冲输出。实际上驱动器对脉冲宽度是又要求的。因此需要生成连续多个 1 满足驱动器的要求。&lt;/p&gt;

&lt;p&gt;但是这就等于意味着，插补器能在 1/72000000 s 为时间分辨率上控制脉冲的上升沿和下降沿时间。这几乎就等同于插补器以 72Mhz 运行。&lt;/p&gt;

&lt;p&gt;从这个逻辑上来说。其实要输出高精度的脉冲，其实就只需要一个高采样率的6声道声卡。声卡能支持的采样率上限，就是插补器的时间精度上限。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>极坐标在三相电力系统中的计算优势</title>
   <link href="https://microcai.org/2025/02/26/polar-coordinate-system-for-3phase-power.html"/>
   <updated>2025-02-26T00:00:00+00:00</updated>
   <id>https://microcai.org/2025/02/26/polar-coordinate-system-for-3phase-power</id>
   <content type="html">&lt;p&gt;在三相电力系统里，我们有三个相位差 120° 的火线。于是，也就有了三个相位差120°的电压和电流矢量。&lt;/p&gt;

&lt;p&gt;三相平衡的前提下。对这个三相的电压电流矢量进行计算的时候，为了方便计算，会用到 park 和 clark 变换。目的是转成2个互相垂直的矢量。需要考虑的矢量个数，就从3个降到了2个。&lt;/p&gt;

&lt;p&gt;但是，如果引入 极坐标系统，那么三相平衡的前提下，这3个电压矢量，就可以在极坐标下，合成为一个电压矢量。3个电流矢量，在极坐标系下就可以合成为一个电流矢量。&lt;/p&gt;

&lt;p&gt;并且二者可以使用同一个坐标系。不需要专门引入一个旋转的坐标系。当然，就算要引入旋转坐标系，在极坐标系体系里，在原点旋转只需要一个加法。因为在三相平衡的条件下，电压和电流矢量都是从原点开始的。&lt;/p&gt;

&lt;p&gt;在极坐标下，三相电压可以描述为  （ Vmax ，θv）。其中 Vmax 是相电压最大值，而 θv 为相位。
同理，三相对称电流也可以描述为 （Imax, θi）。其中 Imax 是相电流的最大值。而 θi 为相位。&lt;/p&gt;

&lt;p&gt;虽然最终用于描述的变量都是2个。在 直角坐标系下，经过 clark 变换后， 电压也是描述为 (Ualpha , Ubeta)。
但是极坐标体系对计算过程更友好了。&lt;/p&gt;

&lt;p&gt;比如，判定功率因素，只需要将 电压和电流的 相位相减，然后求余弦即可。
比如，计算有功功率，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vmax*Imax*cos(θv - θi)。&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;在 foc 的控制中，我们主要的控制目标，就是 让 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;θi - θe = 90°&lt;/code&gt;。其中， θe 为转子的电角度。控制方式为调整 θv 相对 θe 的提前量。范围是 (θv - θe) ∈ [90°， 180°)。 本环应该采取慢速环。控制带宽应该低于电机的转动惯量。以免电流测量带来的 θi 高频波动影响 θv。&lt;/p&gt;

&lt;p&gt;其次对扭矩的控制，我们主要控制 Imax = Iref 即可。而电流属于间接控制。其内环是 电压环。
电压环的控制目标，就是 Vmax。控制 Vmax = Vref 即可。&lt;/p&gt;

&lt;p&gt;功率控制，就是 控制 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Vmax*Imax*cos(θv - θi) = Pref&lt;/code&gt;。也是属于间接控制，内环还是电压环。&lt;/p&gt;

&lt;p&gt;电压环应该采取快速环策略。使用逐周期控制。并且对 上层的 功率控制环和电流控制环，采取 双环竞争的策略。&lt;/p&gt;

&lt;p&gt;由于电压是输出的，因此需要设计在 极坐标系下的 svpwm 调制算法。其实根据 θv 可以直接判断扇区，非常简单。&lt;/p&gt;

&lt;p&gt;而主要的变换，在于测量获得三相电流后，如何反向构建为（Imax, θi）。 其实方法也很简单，就是在极坐标系下的 (Ia, 0°)，(Ib, 120°)，(Ib, 240°) 三个矢量合成。为一个 （Imax, θi）。&lt;/p&gt;

&lt;p&gt;而且计算过程并不复杂。首先根据 Ia Ib Ic 的符号，判定扇区。和svpwm是一样的。&lt;/p&gt;

&lt;p&gt;接下来就是根据扇区，计算 I1 I2 . 然后根据  I1 I2 反推出矢量的夹角。计算过程本质上是 svpwm 的逆运算。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>三相220v供电可行性再论</title>
   <link href="https://microcai.org/2025/02/11/220v-3phase-again.html"/>
   <updated>2025-02-11T00:00:00+00:00</updated>
   <id>https://microcai.org/2025/02/11/220v-3phase-again</id>
   <content type="html">&lt;p&gt;经过深思熟虑，我提出了一种新的居民用电供电模式： 即三相220伏4线制供电。以适应未来更大的入户功率需求。&lt;/p&gt;

&lt;p&gt;特点为：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- 入户线路为3根火线，一条地线，没有零线。
- 火线和火线之间的电压为220v。火线对地电压 130v。
- 入户电表设计三相不平衡保护开关
- 使用任意2条火线来兼容老的单相220v设备，继续提供 220v 的 2孔和3孔插座。
- 三相设备使用4脚插座。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;不同供电制度下的对比如下：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;220v三相&lt;/th&gt;
      &lt;th&gt;220v 单相&lt;/th&gt;
      &lt;th&gt;380v三相&lt;/th&gt;
      &lt;th&gt;240v 直流&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;电压危险性&lt;/td&gt;
      &lt;td&gt;中&lt;/td&gt;
      &lt;td&gt;中&lt;/td&gt;
      &lt;td&gt;高&lt;/td&gt;
      &lt;td&gt;中&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;每1kw功率电流&lt;/td&gt;
      &lt;td&gt;2.63A&lt;/td&gt;
      &lt;td&gt;4.55A&lt;/td&gt;
      &lt;td&gt;1.52A&lt;/td&gt;
      &lt;td&gt;4.17A&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;线材成本系数&lt;/td&gt;
      &lt;td&gt;2.63 * 3+1&lt;/td&gt;
      &lt;td&gt;4.55 * 2+1&lt;/td&gt;
      &lt;td&gt;1.52 * 4+1&lt;/td&gt;
      &lt;td&gt;4.17 * 2&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;电能质量&lt;/td&gt;
      &lt;td&gt;高&lt;/td&gt;
      &lt;td&gt;低 （100hz功率波动）&lt;/td&gt;
      &lt;td&gt;高&lt;/td&gt;
      &lt;td&gt;高&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;异步电机转矩&lt;/td&gt;
      &lt;td&gt;恒转矩&lt;/td&gt;
      &lt;td&gt;100hz 转矩波动&lt;/td&gt;
      &lt;td&gt;恒转矩&lt;/td&gt;
      &lt;td&gt;无法使用&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;故障保护成本&lt;/td&gt;
      &lt;td&gt;中&lt;/td&gt;
      &lt;td&gt;低&lt;/td&gt;
      &lt;td&gt;中&lt;/td&gt;
      &lt;td&gt;高&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;三相失横危险性&lt;/td&gt;
      &lt;td&gt;低&lt;/td&gt;
      &lt;td&gt;高&lt;/td&gt;
      &lt;td&gt;高&lt;/td&gt;
      &lt;td&gt;无&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;三相失横表现&lt;/td&gt;
      &lt;td&gt;欠压 （罢工）&lt;/td&gt;
      &lt;td&gt;过压 （烧毁） 或 欠压（罢工）&lt;/td&gt;
      &lt;td&gt;过压（烧毁） + 失压&lt;/td&gt;
      &lt;td&gt;无&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;380v 三相异步电机兼容性&lt;/td&gt;
      &lt;td&gt;星接改三角&lt;/td&gt;
      &lt;td&gt;无法使用&lt;/td&gt;
      &lt;td&gt;直接使用&lt;/td&gt;
      &lt;td&gt;无法使用&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;220v 三相异步电机兼容性&lt;/td&gt;
      &lt;td&gt;直接使用&lt;/td&gt;
      &lt;td&gt;无法使用&lt;/td&gt;
      &lt;td&gt;三角改星接&lt;/td&gt;
      &lt;td&gt;无法使用&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;220v 单相电机兼容性&lt;/td&gt;
      &lt;td&gt;接任意2火线 （三相不平衡）&lt;/td&gt;
      &lt;td&gt;直接使用&lt;/td&gt;
      &lt;td&gt;接任一火线 （三相不平衡）&lt;/td&gt;
      &lt;td&gt;无法使用&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;开关电源兼容性&lt;/td&gt;
      &lt;td&gt;接任意2火线 （三相不平衡） /  整流后输入 （三相平衡）&lt;/td&gt;
      &lt;td&gt;直接使用&lt;/td&gt;
      &lt;td&gt;接任一火线（三相不平衡）&lt;/td&gt;
      &lt;td&gt;直接使用&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;全桥整流后电压&lt;/td&gt;
      &lt;td&gt;310v&lt;/td&gt;
      &lt;td&gt;310v&lt;/td&gt;
      &lt;td&gt;540v&lt;/td&gt;
      &lt;td&gt;240v&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;电子器件耐压等级&lt;/td&gt;
      &lt;td&gt;650v&lt;/td&gt;
      &lt;td&gt;650v&lt;/td&gt;
      &lt;td&gt;1200v&lt;/td&gt;
      &lt;td&gt;450v&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;输入整流成本&lt;/td&gt;
      &lt;td&gt;650v耐压二极管*6 + 小电容&lt;/td&gt;
      &lt;td&gt;650v耐压二极管*4 + 大电容 + NTC&lt;/td&gt;
      &lt;td&gt;1200v耐压二极管*6 + 小电容&lt;/td&gt;
      &lt;td&gt;小电容&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;逆变方式&lt;/td&gt;
      &lt;td&gt;310v 直流母线  + 6管全桥逆变&lt;/td&gt;
      &lt;td&gt;310v 直流母线+4管全桥逆&lt;/td&gt;
      &lt;td&gt;540v直流母线  + 6管全桥逆变&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;在三种供电制度下，各自有最适合的电机，而相同功率下的不同电机的成本对比：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;220v三相异步电机&lt;/th&gt;
      &lt;th&gt;220v 单相异步电机&lt;/th&gt;
      &lt;th&gt;380v三相异步电机&lt;/th&gt;
      &lt;th&gt;无刷电机&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;电机效率&lt;/td&gt;
      &lt;td&gt;80% - 90%&lt;/td&gt;
      &lt;td&gt;50% - 70%&lt;/td&gt;
      &lt;td&gt;80% - 90%&lt;/td&gt;
      &lt;td&gt;90% - 98%&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;电机成本&lt;/td&gt;
      &lt;td&gt;低&lt;/td&gt;
      &lt;td&gt;中&lt;/td&gt;
      &lt;td&gt;低&lt;/td&gt;
      &lt;td&gt;高（永磁）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;扭矩特性&lt;/td&gt;
      &lt;td&gt;恒扭矩&lt;/td&gt;
      &lt;td&gt;脉动扭矩&lt;/td&gt;
      &lt;td&gt;恒扭矩&lt;/td&gt;
      &lt;td&gt;恒扭矩 （正弦波驱动） / 脉动扭矩 (方波驱动)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;调速方式&lt;/td&gt;
      &lt;td&gt;变频变压调速&lt;/td&gt;
      &lt;td&gt;无法调速&lt;/td&gt;
      &lt;td&gt;变频变压调速&lt;/td&gt;
      &lt;td&gt;变频变压调速&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;变频器需求&lt;/td&gt;
      &lt;td&gt;可选&lt;/td&gt;
      &lt;td&gt;无法使用&lt;/td&gt;
      &lt;td&gt;可选&lt;/td&gt;
      &lt;td&gt;必须使用&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;电机功率适用范围&lt;/td&gt;
      &lt;td&gt;百瓦 - 万瓦&lt;/td&gt;
      &lt;td&gt;百瓦 - 千瓦&lt;/td&gt;
      &lt;td&gt;百瓦 - 十万瓦&lt;/td&gt;
      &lt;td&gt;毫瓦 - 百万瓦特&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;如果考虑到现行的220v单相供电进行升级，则升级的优缺点如下：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;220v 单相升级220v三相&lt;/th&gt;
      &lt;th&gt;220v 单相 升级 到380v三相&lt;/th&gt;
      &lt;th&gt;220v 单相 升级 到240v 直流&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;电压危险性&lt;/td&gt;
      &lt;td&gt;没变&lt;/td&gt;
      &lt;td&gt;提高了&lt;/td&gt;
      &lt;td&gt;略有下降&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;电流&lt;/td&gt;
      &lt;td&gt;下降&lt;/td&gt;
      &lt;td&gt;下降&lt;/td&gt;
      &lt;td&gt;几乎不变&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;室内布线设计&lt;/td&gt;
      &lt;td&gt;2.5平足够使用到 15kw，无需4平布线&lt;/td&gt;
      &lt;td&gt;零线断线非常危险，要处处考虑&lt;/td&gt;
      &lt;td&gt;几乎无变化&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;保护器设计&lt;/td&gt;
      &lt;td&gt;2P保护器升级为3P保护&lt;/td&gt;
      &lt;td&gt;2P保护器升级为3P保护&lt;/td&gt;
      &lt;td&gt;直流断路器特难&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;老设备无脑兼容方式&lt;/td&gt;
      &lt;td&gt;抽2相&lt;/td&gt;
      &lt;td&gt;抽 1 相，注意零线电流&lt;/td&gt;
      &lt;td&gt;有条件下兼容（使用开关电源的设备可直接兼容）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;（带开关电源的）新设备设计思路&lt;/td&gt;
      &lt;td&gt;修改整流器即可 （小改）&lt;/td&gt;
      &lt;td&gt;修改整流器并提高器件耐压（重新设计）&lt;/td&gt;
      &lt;td&gt;去掉整流器 （降本增效）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;使用感应电机的设备升级思路&lt;/td&gt;
      &lt;td&gt;改为三相电机/改为无刷电机&lt;/td&gt;
      &lt;td&gt;改为三相电机/改为无刷电机&lt;/td&gt;
      &lt;td&gt;改为无刷电机&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;配电网的改造（老设施）&lt;/td&gt;
      &lt;td&gt;更换变压器，入户改三相&lt;/td&gt;
      &lt;td&gt;入户改三相&lt;/td&gt;
      &lt;td&gt;配电网需要使用交-直整流器&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;配电网的建设（新建住宅）&lt;/td&gt;
      &lt;td&gt;新买变压器&lt;/td&gt;
      &lt;td&gt;新买变压器&lt;/td&gt;
      &lt;td&gt;新买整流器 （贵）&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

</content>
 </entry>
 
 <entry>
   <title>LLC 的调频控制</title>
   <link href="https://microcai.org/2025/02/06/LLC-explained.html"/>
   <updated>2025-02-06T00:00:00+00:00</updated>
   <id>https://microcai.org/2025/02/06/LLC-explained</id>
   <content type="html">&lt;h1 id=&quot;llc-调压的的物理原理&quot;&gt;LLC 调压的的物理原理&lt;/h1&gt;

&lt;p&gt;LLC 之所以叫 LLC ， 是因为电路等效为 两个 L 和一个 C。但那是分析用的，实际上电路就一个 L 和一个 C。 当然故意接2个 L 也可以的。只不过会增加成本。&lt;/p&gt;

&lt;p&gt;LLC 谐振的原理呢，其实就是LC振荡器。但是不是自激振荡，而是他激。根据激发他的外部输入电路的不同，分为全桥LLC和半桥LLC。&lt;/p&gt;

&lt;p&gt;LLC 为何通过调节频率，能调节输出电压呢？&lt;/p&gt;

&lt;p&gt;看电路：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/llc.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;LLC 尽管实际电路上就一个 变压器和一个电容，但是因为变压器有漏感，所以 LLC 可以看成是 一个 LC 滤波器接上一个变压器。&lt;/p&gt;

&lt;p&gt;是的，没错，就是一个 LC 滤波器。&lt;/p&gt;

&lt;p&gt;这个 LC 滤波器呢，就有一个谐振频率，高于它的和低于它的频率，能量都会被削弱。&lt;/p&gt;

&lt;p&gt;因此，当外部输入的频率，高于或者低于谐振频率的时候，等于这个 LC 滤波器对输入的交流电进行了滤波。也就是被衰减。那么衰减后施加在变压器上的电压就不再是原始电压了。
于是次级绕组输出电压就降低。&lt;/p&gt;

&lt;p&gt;只有工作在谐振频率的时候，滤波器增益为 1, 也就是能量完全通过滤波器。于是次级绕组输出电压最大。&lt;/p&gt;

&lt;p&gt;但是，我要说但是了。为啥 LLC 一定要有漏感呢？ 而且漏感还不能小。其实主要原因是：这个滤波器的谐振频率是会随着负载变化的。
漏感越大，则谐振频率随负载变化越小。&lt;/p&gt;

&lt;p&gt;为啥谐振频率会变化呢？ 这就要说到变压器的特点了。变压器的初级绕组能量完全传递给次级，那么铁芯就不会有磁链。也就是说，这个线圈不会产生电感。
如果次级没有能量消耗，那么电流的能量就会转换成磁链储存在铁芯里。也就是说，这个线圈会有个最大的电感。&lt;/p&gt;

&lt;p&gt;因为现实变压器不是理想变压器，因此，初级的能量无法完全传递给次级。所以这个无法完全传递过去的能量就会驻留在铁芯里，形成 “漏感”。在传统的反激或者正激变换器里，漏感是个坏东西。要尽量消除。
而在 LLC 架构里，这个漏感反而是必须的。因为如果没有漏感，则次级完全消纳初级能量后，LC谐振电路因为没有电感就被破坏掉了。
在 LLC 架构里，漏感不仅仅需要，而且还得很大。因为变压器哦绕组的电感，会随着次级消耗的能量多寡而变动，因此，这个 LC 滤波器，电路中串入的 L 的数值就会变动。这导致滤波器的谐振频率会有一个很大的变动。为了降低谐振频率的变动范围，就需要变压器有较大的漏感。或者，外部再串入一个电感。&lt;/p&gt;

&lt;h1 id=&quot;调频不是为了调压&quot;&gt;调频不是为了调压&lt;/h1&gt;

&lt;p&gt;很多对 LLC 的理解不深刻的人，很容易把 LLC 看成是一种通过调节 pwm 频率来调节输出电压的拓扑结构。其实这种做法是严重错误的。&lt;/p&gt;

&lt;p&gt;LLC 拓扑下，控制器之所以要调节pwm频率，并不是为了通过调节 pwm 频率实现调压。而是通过观察输出电压的反馈来控制pwm频率以求让 LLC 变换器始终工作在谐振点。&lt;/p&gt;

&lt;p&gt;因为每个变压器制造出来都是有一些差异的。每个电容也是有容值差异。这导致成品电路每个 LLC 的谐振频率都不同。因此控制器需要智能寻找谐振点。而成本最低的做法就是用次级电压反馈。因为变压器只是说绕组的电感是有个体偏差，但是匝数比是不会有差异的。只要初级电压是稳定的，那么工作在谐振点的LLC次级输出电压就是稳定的。通过次级电压反馈，就可以控制初级工作频率为谐振频率。&lt;/p&gt;

&lt;p&gt;因此 LLC 变换器有一个隐藏的要求：初级电压必须稳定。&lt;/p&gt;

&lt;p&gt;这就是为何 LLC 变换器总是和主动式PFC搭配。因为 PFC 整流器可以输出一个稳定的直流电压。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>动能回收的实现方式</title>
   <link href="https://microcai.org/2025/01/27/regenerative-explained.html"/>
   <updated>2025-01-27T00:00:00+00:00</updated>
   <id>https://microcai.org/2025/01/27/regenerative-explained</id>
   <content type="html">&lt;p&gt;电动汽车进行动能回收的原理就是电动机可以作为发电机使用。&lt;/p&gt;

&lt;p&gt;但是具体是怎么实习的呢？&lt;/p&gt;

&lt;h1 id=&quot;方法1当发电机使用&quot;&gt;方法1，当发电机使用&lt;/h1&gt;

&lt;p&gt;发电机发的电，如何转换成直流电？ 答案是 整流，然后经过 Boost 升压。为何要升压呢？因为感应电动势小于电池电压。&lt;/p&gt;

&lt;p&gt;也就是说动能回收的时候，电机输出电压要小于电池电压。因此整流后，还要经过一个升压，然后就能冲入电池了。&lt;/p&gt;

&lt;p&gt;调节升压电路的输出电压，就可以调节充电电流，也就是调节了动能回收的功率。&lt;/p&gt;

&lt;p&gt;因此动能回收可以是一条独立的电路。其电路就是 6个二极管构成的三相整流桥 + 1或2个 mos管构成的非同步或同步Boost升压电路。&lt;/p&gt;

&lt;h1 id=&quot;方法2使用4象限逆变器&quot;&gt;方法2，使用4象限逆变器&lt;/h1&gt;

&lt;p&gt;前一个方法要增加一套电路，成本大大的。其实完全可以复用逆变器的电路。&lt;/p&gt;

&lt;p&gt;那么具体的来说，就是让逆变器输出的三相电压的相位，完全跟随电动机旋转时产生的3个反电动势电压的相位。但是，逆变器输出的电压，略低于电动机的反电动势。&lt;/p&gt;

&lt;p&gt;于是，电流就会逆向流动。从电动机反向流入逆变器的直流母线。
导致直流母线电压抬升。&lt;/p&gt;

&lt;p&gt;而逆变器的直流母线又是和电池并联的，于是电流逆向流入电池。完成充电。&lt;/p&gt;

&lt;p&gt;而控制逆变器的输出电压和电机反电动势的电压差，就可以控制充电电流的大小。&lt;/p&gt;

&lt;p&gt;进而控制动能回收功率。&lt;/p&gt;

&lt;p&gt;其实做过三相功率因素矫正电路的人都知道，其电路也是6个MOS管。实际上三相带功率因素矫正的整流电路，本质上就是个工作在动能回收模式的逆变器。只是和电机的逆变器直接使用角度传感器获得转子位置（进而获得各相电压相位）不同，功率因素矫正电路使用锁相环电路获得市电的相位。&lt;/p&gt;

&lt;p&gt;而且，使用此方法，也不需要对电控的代码进行修改。&lt;/p&gt;

&lt;p&gt;哈哈，这是为啥呢？&lt;/p&gt;

&lt;p&gt;因为现有的电控代码，都是使用 DQ 坐标系进行控制的。在控制代码里，其实只要简单的将 闭环控制目标设定为输出负 Q轴电流，剩下的代码，自动的就会将逆变器的工作模式切换为上述流程。
不需要额外增加代码。&lt;/p&gt;

&lt;p&gt;既然正向输出的 “功率限制” 功能都是现成的，那么动能回收控制功率就是换个正负号的事情。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>电机控制的几个思索</title>
   <link href="https://microcai.org/2025/01/26/motor-and-regenerative.html"/>
   <updated>2025-01-26T00:00:00+00:00</updated>
   <id>https://microcai.org/2025/01/26/motor-and-regenerative</id>
   <content type="html">&lt;h1 id=&quot;关于能量转化上的思考&quot;&gt;关于能量转化上的思考&lt;/h1&gt;

&lt;p&gt;电动机，供电即转。转即发电。&lt;/p&gt;

&lt;p&gt;无负载的情况下，电机无功率输出，（理论上）无功率消耗。&lt;/p&gt;

&lt;p&gt;通电即转，和无功率消耗的矛盾是如何化解的呢？那就是“反电动势”。
在无负载的情况下，电机的转速会达到反电动势等于输入电压。&lt;/p&gt;

&lt;p&gt;此时输入电压完全和反电动势抵消。于是绕组实际上无电流通过。因此不产生扭矩。故 电压*0电流 = 0 功率。&lt;/p&gt;

&lt;p&gt;只要有负载，转速就会低于空载转速，于是反电动势低于输入电压。绕组产生电流，建立磁场，电机产生扭矩输出。&lt;/p&gt;

&lt;p&gt;因此电机消耗的电流，大致相当于 （输入电压 - 反电动势）/绕组阻抗。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;注：绕组阻抗是由电阻和电抗叠加而来的。因此超导做的绕组依然不会有无穷大电流。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1 id=&quot;关于空挡滑行再启动&quot;&gt;关于空挡滑行再启动&lt;/h1&gt;

&lt;p&gt;在控制策略上，空挡滑行有2种控制策略&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;切断供电回路&lt;/p&gt;

    &lt;p&gt;控制器停止工作并切断回路（6个MOS管全部关闭）。切断电路后，电机空挡旋转产生的反电动势没有回路。因此不产生电流。电机进入空挡滑行状态。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;控制器输出电压跟随反电动势，确保绕组无电流&lt;/p&gt;

    &lt;p&gt;此模式下，控制器继续积极工作。但是始终保证控制器输出的电压等于电机反电动势。确保相电流为0。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;第二种模式下，电机空挡滑行的时候，控制器仍然在积极工作，因此存在不大不小的损耗。&lt;/p&gt;

&lt;p&gt;从节能减排的角度而言，应该优先选择第一种策略。&lt;/p&gt;

&lt;p&gt;那么第二种策略是为何产生的呢？&lt;/p&gt;

&lt;p&gt;答案就是在空挡滑行状态再加速（松油门滑行后，再踩油门加速）的情况下。二者会产生不同的响应特点。&lt;/p&gt;

&lt;h3 id=&quot;切断回路模式的再加速工作特点&quot;&gt;切断回路模式的再加速工作特点&lt;/h3&gt;

&lt;p&gt;由于控制器停止工作，因此控制器输出电压为 0。
因此，再加速的时候，控制器输出电压就需要“重新建立”。控制器的输出电压会经历一次从 0 到超过反电动势电压的一个“攀升”的过程。值得一提的是，在控制器输出0电压的时候，因为6管全关，因此起到了切断电路的效果，因此只有控制器 开始工作，电压输出开始不为0的时候，电路才接通。&lt;/p&gt;

&lt;p&gt;这个过程虽然很短暂（一般而言是几十个毫秒），但是在攀升的过程中，存在短暂的控制器输出电压低于电机反电动势的时间。这个时间电机就会输出负扭矩。如果这个负扭矩较大，就会在体感上形成一个类似燃油车挂档不合适导致的“顿挫” —— 松离合器的时候，发动机被车速强行带上更高转速，从而导致一段时间内发动机输出负扭矩。&lt;/p&gt;

&lt;p&gt;故为了消除这个顿挫，才有了控制器持续工作的空挡滑行模式。&lt;/p&gt;

&lt;h3 id=&quot;相电流闭环控制为0的再加速特点&quot;&gt;相电流闭环控制为0的再加速特点&lt;/h3&gt;

&lt;p&gt;由于使用了闭环控制。因此空挡后再加速的时候，只要将闭环控制的电流目标调节为 &amp;gt; 0 即可立即输出扭矩。
不会经历一个短暂的 “顿挫” 过程。&lt;/p&gt;

&lt;h1 id=&quot;强制动能回收&quot;&gt;强制动能回收&lt;/h1&gt;

&lt;p&gt;但是，在空挡滑行的时候，还让控制器持续工作，意味着不能实现6个 mos管全关的节能模式。&lt;/p&gt;

&lt;p&gt;舒适了，但是并不节能。&lt;/p&gt;

&lt;p&gt;如果强制动能回收。则可以强迫驾驶员用自己的脚避免进入 “松油门滑行” 状态。等于完全取消了这个工况。&lt;/p&gt;

&lt;p&gt;因此即避免了关控制器模式的顿挫，也避免了闭环0电流滑行的损耗。&lt;/p&gt;

&lt;p&gt;与此同时，还不需要设计 机械刹车和动能回收的协调工作。&lt;/p&gt;

&lt;p&gt;三赢。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>福特和福建的弹射技术对比</title>
   <link href="https://microcai.org/2025/01/15/ac-electromagnetic-launch.html"/>
   <updated>2025-01-15T00:00:00+00:00</updated>
   <id>https://microcai.org/2025/01/15/ac-electromagnetic-launch</id>
   <content type="html">&lt;p&gt;电磁弹射，被加速的舰载机总重，30吨，即3万公斤；加速长度100米，加速时间2.2秒，舰载机末端速度，85米每秒；总耗电能38度；瞬间功率6万千瓦。&lt;/p&gt;

&lt;p&gt;福特航母在上实际90年代立项。 上世纪90年代，哪家公司能拿出功率达到 6万千瓦的变频器和储能40度电的超级电容呢？&lt;/p&gt;

&lt;p&gt;这就是有时候，先驱变成先烈的问题。&lt;/p&gt;

&lt;p&gt;因为当年没有大功率的逆变器技术，
因为超大功率的逆变器是随着特高压直流输电才研发出来的。
所以福特就只能上不需要逆变器的异步电机。由发电机直接供电。&lt;/p&gt;

&lt;p&gt;发电机直接供电，瞬间大负载下，发电机的转速（也就是发电频率）就会掉。而没有任何原动机能实现这么快速的调频。
防止掉频率的方法就是加大质量的飞轮。作用就和拖拉机上的那个大飞轮一样。&lt;/p&gt;

&lt;p&gt;依靠飞轮的巨大惯性，防止发电机转子转速下降。&lt;/p&gt;

&lt;p&gt;而且动能回收也非常简单，异步电机只要转速超过同步转速就自动进入发电状态。
因此只要给回收缆绳上接一个电机，飞机降落的时候以极高的速度拉扯缆绳。缆绳带动电机发电，这个电直接并入航母电网。由于缆绳带动的是异步电机，
因此不需要同步就可以并网。电能输入航母电网。然后整个航母电网电压提升，带着飞轮的发电机由于电网不再从发电机汲取能源，导致发电机变为电动机，从电网提取能量。
从而实现储能。&lt;/p&gt;

&lt;p&gt;所以，这个交流技术其实是很成熟很稳定的。&lt;/p&gt;

&lt;p&gt;由于整个系统的交流频率和飞轮转速挂钩。因此是运行在在某个区间内“变动”的频率上的。和陆地上固定频率的电网不同。
但是频率的范围也不能太大。猜测系统频率在 350hz ~ 450hz 之间。
这样有效的频率范围就只有 100hz 。 也就是说， 0-350hz 这个区间对应的飞轮转速是 “无用转速”。&lt;/p&gt;

&lt;p&gt;因此飞轮质量必须非常大，转速必须非常高。这样才能在狭窄的可用转速区间内，输出足够的储能。&lt;/p&gt;

&lt;p&gt;由于4条弹射器不可能同时运作。其1是放飞跑道有交叠，其二是为了飞机起飞后的涡流干扰考虑。&lt;/p&gt;

&lt;p&gt;因此，基于控制成本的考虑，福特使用的是 4台发电机+4个飞轮+4个弹射器 这样的组合。但是每台发电机只有1万千瓦功率。这样可以大幅降低总成本和总质量。
这样，运作的时候，实际上就是4台发电机并联发电，但是每次只给一台弹射器供电。&lt;/p&gt;

&lt;p&gt;为了避免4台发电机之间复杂的相位同步并网问题，福特使用的每条弹射器，是4条直线电机并联输出动力的。
这意味着，4台发电机少一台，弹射器就无法工作。&lt;/p&gt;

&lt;p&gt;下面是我画的示意图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/fute_tansheqi.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;从结构图里可以很清晰的推断出来，福特航母弹射器坏一条就4条不能用。&lt;/p&gt;

&lt;p&gt;但是，只要每个零件的可靠性做的很高，那么整个系统的可靠性也不是说不能用。&lt;/p&gt;

&lt;p&gt;因此，福特的技术路线，并不是错误的，不可取的。
而是着眼于当年的技术水平限制做的设计。&lt;/p&gt;

&lt;p&gt;而一旦掌握了特高压直流输电，就一定会掌握一个重要技术：超大功率直流/交流逆变器。&lt;/p&gt;

&lt;p&gt;这个逆变器是弹射器能使用“直流供电”的前置技术。&lt;/p&gt;

&lt;p&gt;而一旦有了这个逆变器，则整个设计可以“直流化”。&lt;/p&gt;

&lt;p&gt;整个系统架构如下图:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/fujian_tansheqi.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;注意这个储能电池和电网之间，实际上并不需要任何“控制器”。而是直接并联。&lt;/p&gt;

&lt;p&gt;比如这个电池使用 3700v 电池组。（一千节锂电池串联）。&lt;/p&gt;

&lt;p&gt;那么只要是输出 4200v 电压的发电机，都可以并到这个直流电网里。&lt;/p&gt;

&lt;p&gt;只要用电侧使用功率低于发电功率，电池就会自动“吸纳”多余电流。
而只要用电侧使用功率高于发电功率，电池就会自动放电维持电网电压。&lt;/p&gt;

&lt;p&gt;这个是电池的工作性质决定的。&lt;/p&gt;

&lt;p&gt;实际上每台汽车，都有这么一个直流电网。这个电网就是靠12v蓄电池进行稳压储能的。
12v蓄电池满电电压是 14.4v，因此汽车发电机输出一个14.4v电压。并且和电池直接并联。&lt;/p&gt;

&lt;p&gt;电池未充满的情况下，发电机输出电压被电池拉低。发电机输出最大功率。电池充电功率 = 发电机功率-用电功率。 发电机工作在恒流模式。电池充满达到14.4v 后。发电机在内部稳压系统的作用下，会减小励磁电流，进而减少发电功率。发电机工作在恒压模式。如果有个瞬时的大功率用电设备开启，则整个系统电压就会偏离 14.4v 。 电池自动进入放电模式来弥补发电机的功率缺口。&lt;/p&gt;

&lt;p&gt;这就是直流电网的巨大“储能优势”。并不需要复杂的控制器，仅仅是将电池并联到电网上就可以全自动实现储能和放能。&lt;/p&gt;

&lt;p&gt;对于只能以恒功率模式运行的发电机，还可以监控电池 soc ，在电池 soc 达到 80% 的时候，直接关闭发电机。因为接下来电池就得进入恒压变流模式充电了。如果发电机不支持这种变功率模式，关掉就可以了。&lt;/p&gt;

&lt;p&gt;因此，电池的配置，以 50% soc 下电池的放电能完全支持一条弹射器工作来选择即可。&lt;/p&gt;

&lt;p&gt;比如动力电池一般能支持 10C 放电。在 3700v 额定电压下，支持弹射器工作大概要输出 1.8万安电流。那么只要使用一个  3700v/1800Ah 的电池，即可完全承担弹射器的工作。
电池成本大概就特么是 100辆新能源汽车的电池。在航母上就是洒洒水，和没成本一样。&lt;/p&gt;

&lt;p&gt;起码比飞轮要便宜的多的多。&lt;/p&gt;

&lt;p&gt;而新能源产业的蓬勃发展，使得动力电池的可靠性非常高。而且航母有充足的地方，储备多个动力电池组。一个有问题就切断一个。&lt;/p&gt;

&lt;p&gt;更进一步，连航母的推进器都可以从这个直流电网取电。进而实现“全电推进”。&lt;/p&gt;

&lt;p&gt;这样，航母的动力形式反而变的无关紧要了。只要能经得起海上颠簸，足够小型化的发电方式，都可以上舰。&lt;/p&gt;

&lt;p&gt;整套架构下来，在技术上唯一要攻克的地方，就是那个大功率逆变器。其他组件都是成熟的货架商品。而且也不存在坏一条整舰瘫痪的问题。&lt;/p&gt;

&lt;p&gt;哪怕那个大功率逆变器，说大，也就比动车组使用的大，但是和直流输电的逆变器相比，那都是弟中弟了。&lt;/p&gt;

&lt;p&gt;但是，要在90年代完成这个架构，那是不可能的。&lt;/p&gt;

&lt;p&gt;90年代如果使用这套设计。首先电池无法使用锂电池。
而是使用当年成熟的货架电池——镍氢电池。想想使用镍氢电池的丰田混动那孱弱的动力。
然后，90年代没有这么大功率的 IGBT 器件。如果使用多逆变器并联方案，则故障率和成本都会飙升。不见得比交流设计更强。&lt;/p&gt;

&lt;p&gt;因此福特设计上固然有些许缺陷，但是这个缺陷是基于当年的技术水平做的妥协。&lt;/p&gt;

&lt;p&gt;如果实现的时候好好的提升各部件的质量，并非不能使用。&lt;/p&gt;

&lt;p&gt;顺带一提，关于福特弹射器是4个直线电机并联出力可不是我瞎想的，这个是福特维修中的弹射器的一张照片:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/fute_tansheqi2.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;可以清晰的看出4个定子和12条供电线。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>资本主义绝户计</title>
   <link href="https://microcai.org/2025/01/08/capitalism-and-self-emasculation.html"/>
   <updated>2025-01-08T00:00:00+00:00</updated>
   <id>https://microcai.org/2025/01/08/capitalism-and-self-emasculation</id>
   <content type="html">&lt;p&gt;全世界的文明，都陷入了一个困境：经济越是发达，生育率越低。&lt;/p&gt;

&lt;p&gt;很多人说，这是因为经济发达了，妇女受教育程度高了。女性不愿意生孩子了。&lt;/p&gt;

&lt;p&gt;这就是典型的胡说八道。但是这种错误认知居然还是当下的主流认知。&lt;/p&gt;

&lt;p&gt;为什么说这个认知是错误的呢？女性接受教育就会自我阉割放弃生育权？
这个错误的离谱之处在于，否认了男性在抚养后代上的承担。仿佛孩子只要生下来就可以自动长大。&lt;/p&gt;

&lt;p&gt;生孩子只要幸苦10个月，养孩子则要幸苦一辈子。
只要这个社会进步到了，孩子不能管生不管养，生育率就会断崖式下跌。和女性是否接受教育没有关系。&lt;/p&gt;

&lt;p&gt;让生育率下跌的，从来不是所谓的女权觉醒，而是养育孩子的成本越来越高。高到6个钱包都无力供养的情况下，自然整个社会就开始自我绝育了。&lt;/p&gt;

&lt;p&gt;在进行进一步论述前，首先要在基本观念上取得共识：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;人，首先要养活自己，然后才谈的上养育后代。&lt;/li&gt;
  &lt;li&gt;人，都希望自己的下一代活的比自己好。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;一个族群人口是否扩张，取决于这2个先决条件。&lt;/p&gt;

&lt;p&gt;一个人人都无法养活自己的族群，必然人口萎缩。就算孩子出生率很高，也必然面临大量的夭折儿童。
一个人人只能缩衣节食，然后一代过的不如一代的族群，必然也是人口萎缩的。就算勉强为了所谓的传宗接代而维持出生率。面对一代过的不如一代的事实，也会让人心生绝望。直到某一代，连养活自己都无法维持。&lt;/p&gt;

&lt;p&gt;好了，有了以上的共识，为啥说资本主义是绝户计呢？&lt;/p&gt;

&lt;h1 id=&quot;造不如买买不如租&quot;&gt;造不如买，买不如租&lt;/h1&gt;

&lt;p&gt;我们知道，造不如买，买不如租。比如过去大户人家，他们虽然会有很多“家生子”，但是府中奴仆的主力来源依然是 “买”。（所谓“家生子”是指家中奴仆生育的后代，由主人完全出资养大。）根本原因就是因为养大一个家生子的开销，远大于从外部购买一个已经成年的劳动力。&lt;/p&gt;

&lt;p&gt;而买不如租，就是资本主义的逻辑。买来的奴仆，主人还是要负责他的生老病死。
而租来的奴仆，主人完全享受他“健康壮年期”的劳动，而不需要承担其他任何开销。&lt;/p&gt;

&lt;p&gt;20岁-60岁，资本家只需要花费 400万 就可以买断一个人一辈子的劳动。
这 400万，资本家获得的是一个 “上过大学”，接受过优质教育的奴仆。而且400万是上限。期间奴仆如果生病了，不能履行职责了，都可以换一个。换一个人是没有额外成本的。甚至还可以要求租来的奴仆在无法履职的期间反向赔偿主人的损失。&lt;/p&gt;

&lt;p&gt;如果资本家不使用“租”的方式。他要支付多少钱呢？ 首先，从0岁养到大学毕业，资本家要支出大约200万。
而且，还有一定的“废品率”。也就是说有一部分买来的奴仆，将永久性无法承担高级工作。&lt;/p&gt;

&lt;p&gt;其次，20岁到60岁，资本家虽然不需要支付工资，但是期间的衣食住行一样要大约支付200-300万。
最后，60岁开始，奴仆无力劳动，但是还是要养他到老。期间的衣食住行差不多还得 100万。&lt;/p&gt;

&lt;p&gt;看到没？使用“雇佣制”下，资本家节约了大量的“养育”成本。同时不承担“废品率”。资本家唯一多支出的期间，是壮年期，给奴仆发放的工资，要高于“养一个奴仆”的衣食住行开支。&lt;/p&gt;

&lt;p&gt;而这部分多列的开支，已经用“不承担养育和养老成本”给节约回来了。而且还免去了“养废“的风险。&lt;/p&gt;

&lt;p&gt;而且还有一个重大优势：不需要等待二十几年才能获得一个高级奴仆。而是可以立即获得。&lt;/p&gt;

&lt;p&gt;与此同时，租来的壮年期的奴仆工作效率也远远高于买来的奴仆。哪怕不考虑节约的养育和养老开销，租相比买都是赚了的。&lt;/p&gt;

&lt;p&gt;好了，这是雇佣制替代奴隶制背后的经济逻辑。&lt;/p&gt;

&lt;p&gt;那么这个经济逻辑为何会影响生育率呢？&lt;/p&gt;

&lt;h1 id=&quot;劳动力再生产&quot;&gt;劳动力再生产&lt;/h1&gt;

&lt;p&gt;在资本主义下，人是被 “物化”的。因此我这里也使用物化的逻辑看待人口。
劳动力，其实也是一种”生产资料”。由氨基酸构成，葡萄糖驱动的机器，和钢铁构成，烃烷驱动的机器，在资本家的眼里是没有区别的。都是生产线的组成部分。&lt;/p&gt;

&lt;p&gt;一条生产线，任何一个零件老化，都需要生产一个新的零件换上。&lt;/p&gt;

&lt;p&gt;对资本家来说，这叫“产线更新”。&lt;/p&gt;

&lt;p&gt;资本家需要将商品利润的一部分，投入“生产线的更新”。&lt;/p&gt;

&lt;p&gt;但是很不巧的是，在资本主义制度下，氨基酸构成的机器，其“生产线的更新”成本是由机器自己承担的。
也就是说，资本家将人力资源的使用方式，等同于“石油天然气”一样的“不可再生矿产”。
采取的做法是用完就废弃。然后去别的地方挖新的。&lt;/p&gt;

&lt;p&gt;因此，资本主义才会不断的谋求“产业链转移”。将钢铁构成的生产线不断的移动到有新鲜的氨基酸零件的地方。
一旦将这个地方的氨基酸机器挥霍的差不多了，就转移到另一个地方。&lt;/p&gt;

&lt;p&gt;资本家并不谋求这些氨基酸构成的产线零件的 “可持续更新”。&lt;/p&gt;

&lt;p&gt;至于这些零件如果还有祖传的责任，于是以极大的压缩自身需求为代价，努力的为资本家培养下一代耗材，则资本家不过是减缓了产业链转移的速度。&lt;/p&gt;

&lt;p&gt;因为，这样做，必然导致 ”活的越来越差“。而这是反人性的。&lt;/p&gt;

&lt;h1 id=&quot;生育率&quot;&gt;生育率&lt;/h1&gt;

&lt;p&gt;所以生育率的降低的根本原因已经呼之欲出了。就是资本主义下，对人力资源是涸泽而渔的。&lt;/p&gt;

&lt;p&gt;除非人力资源自己能放弃自己身为人的尊严，只为“产子”这一个目的而活。&lt;/p&gt;

&lt;p&gt;而耗材一旦不放弃人的尊严，就被扣上了 “知识越多越不生” 的帽子。&lt;/p&gt;

&lt;p&gt;然后用所谓的“生育率下降是经济发展的必然结果”来忽悠&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>调用约定和上下文切换</title>
   <link href="https://microcai.org/2025/01/03/calling-convertion-and-context-swap.html"/>
   <updated>2025-01-03T00:00:00+00:00</updated>
   <id>https://microcai.org/2025/01/03/calling-convertion-and-context-swap</id>
   <content type="html">&lt;p&gt;编写协程库的时候，我常常在想，上下文切换的速度，受&lt;strong&gt;调用约定&lt;/strong&gt;的影响非常大。&lt;/p&gt;

&lt;p&gt;因为用户协程的上下文切换，本质上是调用一个 函数。而这个函数需要 “切换” 非易失性寄存器。&lt;/p&gt;

&lt;p&gt;因此，在给定的调用约定下，非易失性寄存器越多，则上下文切换时需要保留和恢复的寄存器数量就越多。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;而由内核进行上下文切换(也就是线程)，所有寄存器都得保留。开销是最大的。何况还有执行特权级切换的巨大开销。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;在知乎上发表的批评 云风的 coroutine 库的文章的评论区里，有个人提到了 &lt;a href=&quot;https://photonlibos.github.io/blog/stackful-coroutine-made-fast&quot;&gt;这篇文章&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;好家伙，他们对内联汇编玩出花来了。使用内联汇编的 clobbered registers 列表来自动生成寄存器的保存和恢复代码。&lt;/p&gt;

&lt;p&gt;然后，他们给 clang 提了一个补丁，增加了一个叫 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;preserve_none&lt;/code&gt; 的调用约定。&lt;/p&gt;

&lt;p&gt;所谓 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;preserve_none&lt;/code&gt;，顾名思义，就是这个调用约定下，尽可能啥寄存器都不保留，全是易失性寄存器。&lt;/p&gt;

&lt;p&gt;于是，如果使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;preserve_none&lt;/code&gt; 的调用约定实现 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zcontext_swap&lt;/code&gt;, 则代码里并不需要保留上下文。只需要切换 rsp 寄存器，然后调用下 hook 即可返回了。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;zcontext_swap_preserve_none:
    // 保存frame寄存器
    push %rbp

    // 切换栈指针
    mov %rsp, (%r12)
    mov (%r13), %rsp

    //  恢复 frame 寄存器
    pop %rbp
    // if(hook_function) hook_function(argument);
    test %r14, %r14
    jne 1f
    // 返回 argument
    mov %r15, %rax
    // 返回
    ret
1:
    // 如果有 hook ，则调用 hook 后返回
    mov %r15, ARG1_REG
    mov %r15, %r12 // 同时兼容 preserve_none 调用约定 的 hook
    jmp * %r14 // 开启尾调用优化.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在 无 hook 的情况下，只需 8 条指令即完成上下文切换。&lt;/p&gt;

&lt;p&gt;快，非常的快。&lt;/p&gt;

&lt;p&gt;然后有人说了，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zcontext_swap&lt;/code&gt; 是快了，但是调用它的地方，得保持寄存器，工作量并没有减少。只是换了个地方。&lt;/p&gt;

&lt;p&gt;是的。没错。如果寄存器无论如何需要保留的话，确实保持寄存器的工作量是没有任何变化的。
但是，如果在某些情况下，调用方的寄存器，实际上也没有那么多需要保留呢？&lt;/p&gt;

&lt;p&gt;也就是说，使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;preserve_none&lt;/code&gt; 调用约定。在最次的情况下，是把寄存器的保留工作交给调用方。这个和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zcontext_swap&lt;/code&gt; 自己进行保留工作，是没有工作量的区别的。因此，没有任何性能损失。
而在一些情况下，调用方的代码用不到那么多寄存器的情况下，反而可以省去寄存器的保留工作。&lt;/p&gt;

&lt;p&gt;也就是说，存在可以少恢复寄存器的情况，从而减少了 cpu 的开销。&lt;/p&gt;

&lt;p&gt;至于到底要保留多少寄存器，则取决于进行协程调度的上下文环境。这就是阿里的那篇文章说的 “上下文感知协程切换”。
切换协程的时候，通过感知上下文来自动决定切换的工作量。&lt;/p&gt;

&lt;p&gt;这样这个协程上下文切换的开销，就和无栈协程一样小了。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>在Linux上交叉编译windows版 Qt 和 qt应用</title>
   <link href="https://microcai.org/2024/12/31/cross-compile-qt-app-by-clang.html"/>
   <updated>2024-12-31T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/12/31/cross-compile-qt-app-by-clang</id>
   <content type="html">&lt;h1 id=&quot;准备工具链&quot;&gt;准备工具链&lt;/h1&gt;

&lt;p&gt;一个半月前，我研究了一下 &lt;a href=&quot;https://microcai.org/2024/11/16/cross-compile-by-clang.html&quot;&gt;使用本机 clang 进行交叉编译&lt;/a&gt;。那时候，是在本机 x86 上交叉编译 arm 和 mips 架构的软件。目标系统还是 linux 的。&lt;/p&gt;

&lt;p&gt;交叉编译，需要使用交叉工具链。而使用本机clang进行交叉编译，只是将交叉工具链里的“编译器”换成了本机本就安装的 clang, 而 “头文件和库文件” 则是使用 archlinux 和 mips debian 的 rootfs 提供。
缺啥库，qemu-user + chroot 进去后直接安装即可。&lt;/p&gt;

&lt;p&gt;关于如何进行异构chroot, 我在 &lt;a href=&quot;https://microcai.org/2011/04/26/chrootarm.html&quot;&gt;chroot to arm on an x86 Gentoo
&lt;/a&gt; 和 &lt;a href=&quot;https://microcai.org/2022/04/26/hybrid-container.html&quot;&gt;异构容器&lt;/a&gt; 里分别讲述过。&lt;/p&gt;

&lt;p&gt;但是，一个合格的开发者，怎么可能只写 Linux 程序呢？&lt;/p&gt;

&lt;p&gt;非常自然的，交叉编译到 windows 上也是一个正经需求。&lt;/p&gt;

&lt;p&gt;要交叉到 windows 上，通常的做法是使用 MinGW。MinGW 不仅仅是一套移植到 Windows 上的 gcc 工具链，还是一套在 Linux 下产生exe文件的交叉编译器。&lt;/p&gt;

&lt;p&gt;但是正如标签里的 clang 所言，本篇介绍的，可不是 MinGW ， 而是继续使用本机的 clang 进行交叉编译 exe。&lt;/p&gt;

&lt;p&gt;编译器本机早就有了，头文件和库呢？&lt;/p&gt;

&lt;p&gt;答案是，还得从 mingw 里拿。&lt;/p&gt;

&lt;p&gt;通常来说， mingw 里是一套 “自由” 的 sdk. 意味着它使用 动态链接的 MSVCRT.DLL 作为 C 库, 然后辅之以  libmingw32-1.dll 作为 posix 兼容层，然后 libgcc-1.dll 作为 编译器运行时。
然后是 libwinpthread-1.dll 提供 pthread ，然后使用 libstdc++-1.dll 提供 c++ 标准库。&lt;/p&gt;

&lt;p&gt;链接出来的 exe 带上一堆 dll 可不是我喜欢的做法。我希望使用静态链接，最多动态链接到标准系统DLL。显然 mingw32 并不能满足我的要求。使用它的库，就非得带上一堆 DLL。&lt;/p&gt;

&lt;p&gt;既然之前用 clang 进行交叉，那么我也希望继续使用 clang 交叉编译 exe。本来计划从 VS2022 里抠头文件，毕竟 VS2022 的头文件肯定是兼容 clang 的。&lt;/p&gt;

&lt;p&gt;结果大喜过望的是，&lt;a href=&quot;https://github.com/mstorsjo/llvm-mingw/releases&quot;&gt;LLVM-MINGW32&lt;/a&gt; 恰好满足了我的需求。 LLVM-MINGW32 是将 mingw32 里的 gcc 换成了 clang 。同时被替换掉的还有运行时。应该说，只是使用了 mingw32 开发的 windows SDK。剩下的是 llvm 项目的。c++ 标准库使用的是 libc++。编译器运行时是llvm开发的 libcompiler-rt，而 C 库，则使用的微软新做的 UCRT。UCRT 从 win10 开始就是系统标准库了。win10 以前的系统还提供了 “安装程序”。&lt;/p&gt;

&lt;p&gt;因此，在不进行任何设置的情况下， llvm-mingw32 编译出来的程序，会依赖  ucrtbase.dll, libc++.dll, libunwind.dll  libwinpthread-1.dll。&lt;/p&gt;

&lt;p&gt;其中 ucrtbase.dll 是 标准系统库。因此，需要将剩下的库进行“静态”即可。&lt;/p&gt;

&lt;p&gt;我的做法仍是，修改 clang 编译器的配置文件 x86_64-w64-windows-gnu.cfg 为如下内容。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-target x86_64-w64-mingw32
-rtlib=compiler-rt
-stdlib=libc++
-fuse-ld=lld
-flto=thin
-Wl,--start-group -Wl,-Bstatic -lc++ -lunwind -lwinpthread -Wl,-Bdynamic -Wl,--end-group -lucrtbase
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样就不必在每个项目里都改 cmakelists.txt 添加静态链接参数了。&lt;/p&gt;

&lt;p&gt;有了，llvm-mingw ，接下来就是准备一个 llvm-mingw 编译的 静态 QT 库了。&lt;/p&gt;

&lt;h1 id=&quot;准备-qt-静态库&quot;&gt;准备 qt 静态库&lt;/h1&gt;

&lt;p&gt;从 git 拉 qt 源码：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://code.qt.io/qt/qt5.git qt-source &lt;span class=&quot;nt&quot;&gt;-b&lt;/span&gt; v6.8.1
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;qt-source
./init-repository.pl
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这是非常漫长的一个过程。如果可能的话，挂上 VPN。&lt;/p&gt;

&lt;p&gt;交叉编译 QT 啊，需要一种叫 Qt6HostInfoConfig.cmake 的文件。这个文件告诉了 qt ，到哪里去找“本机 qt 工具”。&lt;/p&gt;

&lt;p&gt;因为 qt 编译的过程中，需要使用 moc, uic 之类的工具对源码进行处理。而这些工具需要在本机运行。因此不能编译，而必须使用本机已有的。不然在 linux 上如何运行 moc.exe 呢？&lt;/p&gt;

&lt;p&gt;本机 qt 工具，可以直接使用系统自带的 qt. d但是系统自带的 qt 是没有 Qt6HostInfoConfig.cmake 的。
因此需要自己编写一个。内容如下：&lt;/p&gt;

&lt;div class=&quot;language-cmake highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;Qt6CoreTools_DIR /usr/lib64/cmake/Qt6CoreTools&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;Qt6DBusTools_DIR /usr/lib64/cmake/Qt6DBusTools&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;Qt6GuiTools_DIR /usr/lib64/cmake/Qt6GuiTools&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;Qt6LinguistTools_DIR /usr/lib64/cmake/Qt6LinguistTools&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;Qt6QmlTools_DIR /usr/lib64/cmake/Qt6QmlTools&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;Qt6Quick3DTools_DIR /usr/lib64/cmake/Qt6Quick3DTools&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;Qt6QuickTools_DIR /usr/lib64/cmake/Qt6QuickTools&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;Qt6ScxmlTools_DIR /usr/lib64/cmake/Qt6ScxmlTools&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;Qt6ShaderTools_DIR /usr/lib64/cmake/Qt6ShaderTools&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;Qt6ShaderToolsTools_DIR /usr/lib64/cmake/Qt6ShaderToolsTools&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;Qt6Tools_DIR /usr/lib64/cmake/Qt6Tools&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;Qt6ToolsTools_DIR /usr/lib64/cmake/Qt6ToolsTools&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;Qt6UiTools_DIR /usr/lib64/cmake/Qt6UiTools&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;Qt6WaylandScannerTools_DIR /usr/lib64/cmake/Qt6WaylandScannerTools&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;Qt6WebEngineCoreTools_DIR /usr/lib64/cmake/Qt6WebEngineCoreTools&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;Qt6WidgetsTools_DIR /usr/lib64/cmake/Qt6WidgetsTools&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;把这个文件丢到 mingw 的文件夹里备用。比如 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/x86_64-w64-mingw32/share/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;然后开始编译 qt 。命令如下：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
git clone https://code.qt.io/qt/qt5.git qt-source &lt;span class=&quot;nt&quot;&gt;-b&lt;/span&gt; v6.8.1
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;qt-source
./init-repository.pl
&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ..

&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/bin:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PATH&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;mkdir &lt;/span&gt;qt-build
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;qt-build

../qt-source/configure &lt;span class=&quot;nt&quot;&gt;-optimize-size&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-debug-and-release&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-static&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-static-runtime&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-xplatform&lt;/span&gt; win32-clang-g++ &lt;span class=&quot;nt&quot;&gt;-platform&lt;/span&gt; win32-clang-g++ &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-qt-host-path&lt;/span&gt; ~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/x86_64-w64-mingw32/share/ &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-prefix&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/x86_64-w64-mingw32/qt-release&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-confirm-license&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-no-feature-accessibility&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-no-feature-valgrind&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-no-feature-appstore-compliant&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-no-feature-assistant&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-no-feature-example-hwr&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-no-feature-windeployqt&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-no-feature-macdeployqt&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-no-feature-androiddeployqt&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-no-feature-designer&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-no-feature-qdbus&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-no-feature-qtdiag&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-no-feature-qtplugininfo&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-no-feature-qtattributionsscanner&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-skip&lt;/span&gt; qtopcua,qtgrpc,qt3d,qtcanvas3d,qtdatavis3d,qtgamepad,qtcharts,qtconnectivity,qtmqtt,qtcoap,qtqa,qtdbus,qtremoteobjects,qtpim,qtspeech,qtfeedback,qtactiveqt,qtserialbus,qtserialport,tests &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-DFEATURE_cxx20&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;ON &lt;span class=&quot;nt&quot;&gt;-DCMAKE_TOOLCHAIN_FILE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/x86_64-w64-mingw32/share/cross_llvm_mingw.cmake


cmake &lt;span class=&quot;nt&quot;&gt;--build&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
cmake &lt;span class=&quot;nt&quot;&gt;--install&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;需要注意的是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-qt-host-path&lt;/code&gt; 参数。还有 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-DCMAKE_TOOLCHAIN_FILE&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/x86_64-w64-mingw32/share/cross_llvm_mingw.cmake 的内容如下&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_VERSION 10.0)

set(CMAKE_SYSTEM_PROCESSOR x86_64)
set(WIN32 TRUE)
set(WIN64 TRUE)
set(MINGW TRUE)
set(MSVC FALSE)

set(CMAKE_SIZEOF_VOID_P 8)

# specify the cross compiler
set(ENV{PATH} &quot;~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/bin:$ENV{PATH}&quot;)
set(CMAKE_C_COMPILER &quot;~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/bin/x86_64-w64-mingw32-clang&quot;)
set(CMAKE_CXX_COMPILER &quot;~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/bin/x86_64-w64-mingw32-clang++&quot;)

# where is the target environment
set(CMAKE_FIND_ROOT_PATH &quot;~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/&quot;)
set(CMAKE_SYSROOT &quot;~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/&quot;)

# search for programs in the build host directories
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# for libraries and headers in the target directories
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;期间会 100% 遇到编译错误。主要是 Qt 开发人员不怎么使用 CI 进行测试。因此本机调试通过就急着提交源码。根本不管源码能不能在别的环境下编译通过。&lt;/p&gt;

&lt;p&gt;所有如果遇到编译错误，请自行修正源码中的错误。^_^。&lt;/p&gt;

&lt;p&gt;目前 在 v6.8.1 分支中，遇到的编译错误来自 qtquick3d 带的三方库 embree。
有些是因为 使用了 Windows.h 这样的头文件名字导致失败。这个可以不修改源码，自己跑到 mingw 的头文件夹里，把windows.h 做各软连接 Windows.h 搞定。还有什么 Winsock2.h 和 winsock2.h 之类的区别。
凡是遇到这种大小写问题导致的头文件没找到，优先改 mingw 的头文件做软连接。这样不需要修改源码。
这样，就只有 embree 的代码是需要真的修改的了。embree 的错误其实上游是修正了的，可以参考上游的修正 &lt;a href=&quot;https://github.com/RenderKit/embree/commit/cda4cf191&quot;&gt;fix issue #486&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;当然，如果是只编译 qtbase 是不需要操心这些的。&lt;/p&gt;

&lt;p&gt;编译完成后，就把 qt安装到了 mingw 的文件夹里。当然安装到别的文件夹里也可以。我倾向于安装到 mingw 的文件夹里。主要是因为 cmake工具链使用了 –sysroot=mingw安装目录 参数。
对没在 sysroot 里的库可能会被 “无视“。&lt;/p&gt;

&lt;h1 id=&quot;使用-qt-静态库&quot;&gt;使用 qt 静态库&lt;/h1&gt;

&lt;p&gt;接下来，就是对使用 qt 的项目进行交叉了。同样的，需要写一份 toolchain 文件。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_VERSION 10.0)

set(CMAKE_SYSTEM_PROCESSOR x86_64)
set(WIN32 TRUE)
set(WIN64 TRUE)
set(MINGW TRUE)
set(MSVC FALSE)

set(CMAKE_SIZEOF_VOID_P 8)

# specify the cross compiler
set(CMAKE_C_COMPILER &quot;~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/bin/x86_64-w64-mingw32-clang&quot;)
set(CMAKE_CXX_COMPILER &quot;~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/bin/x86_64-w64-mingw32-clang++&quot;)

# where is the target environment
set(CMAKE_FIND_ROOT_PATH &quot;~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/&quot;)
set(CMAKE_SYSROOT &quot;~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/&quot;)
set(ENV{PATH} &quot;~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/bin:$ENV{PATH}&quot;)

# search for programs in the build host directories
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# for libraries and headers in the target directories
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH)

set(CMAKE_CXX_FLAGS_RELEASE &quot;-flto=thin&quot;)

set(CMAKE_PREFIX_PATH &quot;~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/qt-release/lib/cmake/Qt6&quot;)
set(QT_HOST_PATH &quot;~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/x86_64-w64-mingw32/share/&quot;)
set(Qt6HostInfo_DIR &quot;~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/x86_64-w64-mingw32/share/&quot;)
set(QT_FORCE_FIND_TOOLS  ON)

set(Qt6BundledZLIB_DIR ~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/qt-release/lib/cmake/Qt6BundledZLIB)
set(Qt6BundledPcre2_DIR ~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/qt-release/lib/cmake/Qt6BundledPcre2)
set(Qt6BundledLibpng_DIR ~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/qt-release/lib/cmake/Qt6BundledLibpng)
set(Qt6BundledHarfbuzz_DIR ~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/qt-release/lib/cmake/Qt6BundledHarfbuzz)
set(Qt6BundledFreetype_DIR ~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/qt-release/lib/cmake/Qt6BundledFreetype)
set(Qt6BundledLibjpeg_DIR ~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/qt-release/lib/cmake/Qt6BundledLibjpeg)
set(Qt6BundledOpenXR_DIR ~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/qt-release/lib/cmake/Qt6BundledOpenXR)
set(Qt6Qml_DIR ~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/qt-release/lib/cmake/Qt6Qml)
set(Qt6_DIR ~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/qt-release/lib/cmake/Qt6)

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;保存为 ~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/x86_64-w64-mingw32/share/cross_llvm_mingw.cmake&lt;/p&gt;

&lt;p&gt;然后，就可以使用&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cmake &lt;span class=&quot;nt&quot;&gt;--toolchain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;~/llvm-mingw-20241217-ucrt-ubuntu-20.04-x86_64/x86_64-w64-mingw32/share/cross_llvm_mingw.cmake  path_to_source
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;进行交叉编译了。&lt;/p&gt;

&lt;p&gt;使用方式可以看下我这个终端录屏 &lt;a href=&quot;https://asciinema.org/a/pOzr0D9u9WMxTdy7yDeEFIgxW&quot;&gt;asciinema录制回放&lt;/a&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>协程切换为什么需要 一个 hook_function 参数.</title>
   <link href="https://microcai.org/2024/12/28/context-switch-need-a-hook.html"/>
   <updated>2024-12-28T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/12/28/context-switch-need-a-hook</id>
   <content type="html">&lt;h1 id=&quot;前言&quot;&gt;前言&lt;/h1&gt;

&lt;p&gt;刚刚，我将 zcontext 的 API 进行了以下修正。&lt;/p&gt;

&lt;p&gt;从&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;zcontext_swap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;zcontext_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;zcontext_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;改成了&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;zcontext_swap_hook_function_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;zcontext_swap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;zcontext_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;zcontext_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;zcontext_swap_hook_function_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hook_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;增加了一个 hook_function。这是为何呢？这个 hook_function 又是怎么使用的呢？在什么情况下运行呢？&lt;/p&gt;

&lt;p&gt;别着急，我一一道来。&lt;/p&gt;

&lt;h2 id=&quot;需求1自动进行栈回收&quot;&gt;需求1：自动进行栈回收&lt;/h2&gt;

&lt;p&gt;协程需要分配一个新栈。而协程执行完毕的时候，需要对分配的栈内存进行回收。
而协程自身是无法执行这个任务的。协程只有不在运行状态，他的栈才能释放。而他在运行状态下，又如何调用 free 释放自己呢？
free 本身运行也依赖栈。free 不能把自己正在使用的栈给释放了。&lt;/p&gt;

&lt;p&gt;因此，这个需求我用了一些比较绕的方法实现了。其实就是让调度器负责回收。这就限制了调度器只能跑在主线程上。
并且调度器（主事件循环）需要和协程的代码进行一定程度的耦合。&lt;/p&gt;

&lt;p&gt;但是为了解决内存泄漏，这个耦合也是不得不为之。其实应该有更好的解决方法的。&lt;/p&gt;

&lt;h2 id=&quot;需求2线程安全的协程切换&quot;&gt;需求2：线程安全的协程切换&lt;/h2&gt;

&lt;p&gt;在多线程运行调度器的情况下，可能发生协程切换的竞争。具体情况如下：&lt;/p&gt;

&lt;p&gt;假设运行2个线程执行 GetQueuedCompletionStatus。姑且称为 主线程A, 主线程B.
为了更好的性能，通常的做法是让线程A负责 accept, 而线程B负责处理客户端连接。如果处理客户端有瓶颈，还可以创建更多的线程处理。
这里先简化为2个线程。&lt;/p&gt;

&lt;p&gt;系统运行若干个 Accept 协程。并且在接受一个新连接后，创建一个新的处理协程来处理新的连接。
姑且称是协程A吧。某个协程 A 拿到了一个新连接后，创建一个新的处理协程来处理新的连接。
姑且称这个刚刚创建的新协程为 协程C，这个新接受的连接对应 socket C。&lt;/p&gt;

&lt;p&gt;好了，协程A, 接受了一个新连接，然后创建了 协程 C。此时协程 C 和协程 A ，都是在主线程 A 上运行的。&lt;/p&gt;

&lt;p&gt;这时，我们要往socket C 上投递 WSARecv 了。此时投递是在 主线程A上进行的。由于 socket C 绑定了线程B的 IO完成端口，因此稍后
会在主线程 B 上获得完成事件。此时协程 C 是在 线程 A 上运行的。调用 WSARecv 后，紧接着就要进行“协程挂起”操作。
正常来说，线程B获得socket C 的完成事件后，就会恢复协程C。于是，协程 C 后续就在 线程 B 上运行了。&lt;/p&gt;

&lt;p&gt;这里就发生了一个竞争事件1：就是在线程A上刚刚投递读操作。然后 B 线程马上就获得了完成事件。此时 协程C都还没来得及进行挂起。
这个竞争事件的处理很简单，在线程B上忽略它。因为协程C在进行挂起操作的时候，会先检查 overlapped 是不是已完成。如果是，就不会挂起。因此，如果IO事件完成的太快，
其实还是会在发起的线程里处理后续。&lt;/p&gt;

&lt;p&gt;我们主要考虑的是竞争事件2：就是协程C,检查发现事件没有完成，于是就使用 zcontext_swap 将自己换出。
后面线程B获得完成事件通知后，就可以将 overlapped 上存储的 zcontext_t 对象进行唤醒。也就是调用 zcontext_swap 切入协程C。
在这里，如果 线程A实际上尚处于 zcontext_swap 将协程C换出的过程中，线程B就调用 zcontext_swap 要切入协程C。&lt;/p&gt;

&lt;p&gt;于是竞争发生了，发生了永远无法预测的行为。&lt;/p&gt;

&lt;h3 id=&quot;解决竞争事件2&quot;&gt;解决竞争事件2&lt;/h3&gt;

&lt;p&gt;解决的办法是，在 overlapped 事件上设置2个 标志。一为 IO是否完成，二为 协程是否已挂起。&lt;/p&gt;

&lt;p&gt;在获取 IO 结果的代码里，先检查 IO 是否完成。如果完成，则直接返回。如果未完成。则挂起自己。并设置 协程是否已挂起 标志。&lt;/p&gt;

&lt;p&gt;而在事件循环里，获取到 OVERLAPPED 对象后，先设置 IO 完成标识。然后检查 协程是否挂起，是则调用 zcontext_swap 唤醒协程。&lt;/p&gt;

&lt;p&gt;那么问题就在于，如果任务协程是先设置为 已挂起标志，后调用 zcontext_swap 挂起自身。则可能会存在另一个线程里运行的事件循环会在
协程挂起到一半的情况下要恢复它。&lt;/p&gt;

&lt;p&gt;因此，只能是 zcontext_swap 完成后，再设置这个标志。&lt;/p&gt;

&lt;p&gt;问题在于，zcontext_swap 完成后，当前线程就运行了另一个协程了，另一个协程也是在 zcontext_swap 这个地方返回的。
协程 A 调用 zcontext_swap 切换到了 协程B。那么协程B 还得沟通下，去把协程A的活给干一下？&lt;/p&gt;

&lt;p&gt;这种做法太抽象了。本来不同的协程干的活就是不相干的。现在却要让协程和协程之间还紧密耦合。
那对协程B来说，他 zcontext_swap 返回的时候，上一个运行的协程，可不一定是 协程A, 那协程B是不是要在zcontext_swap的后面，紧跟着写上百八十号代码，
分别处理不同前任的工作？&lt;/p&gt;

&lt;p&gt;好吧，也只能这样。好在 zcontext_swap 有一个参数，协程A 调用 zcontext_swap 切换到协程B, 它带了一个参数。这个参数就会变成 协程B 调用zcontext_swap 的返回值。
也就是说，zcontext_swap 确实做到了，获取前任的信息。&lt;/p&gt;

&lt;p&gt;因此，在每个 zcontext_swap 的后面，实际上我都会跟上这样的代码：&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pre_task_info&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;zcontext_swap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pre_task_info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;free&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pre_task_info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;是的，这就是之前，我 解决 需求1 的做法。就是让后任处理前任。平时 zcontext_swap 调用传的都是 0. 唯独在协程死亡前，它会把自己的栈地址传给 zcontext_swap。
于是后任，就顺利的清理掉了前任。&lt;/p&gt;

&lt;p&gt;这个机制还可以继续扩展，增加更多复杂的代码，实现后任为前任处理更多的事情。。。&lt;/p&gt;

&lt;p&gt;但是，这意味着每个 zcontext_swap 调用的地方，都要 重复处理前任的代码。&lt;/p&gt;

&lt;p&gt;要是这个工作，在 zcontext_swap 内部就处理掉就好了！&lt;/p&gt;

&lt;h1 id=&quot;解决&quot;&gt;解决&lt;/h1&gt;

&lt;p&gt;对啊，zcontext_swap 内部要是能在新协程的上下文里，执行一段代码后再返回到目标协程。那么就可以把解决问题的代码，给保留到本协程的代码里。而不是泄漏的到处都是。&lt;/p&gt;

&lt;p&gt;于是，对 需求1 的解决方案变成了这样：&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;coroutine_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coroutine_task&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 调用用户的协程代码.&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_coroutine_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// 转到 next 协程，并释放  task 对象. task 对象被释放的同时也会释放掉本协程的栈.&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;zcontext_swap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;free_task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// 这里不会被运行到, 加个 terminiate 调用&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;terminate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样， free_task 这样的工作，就不会泄漏到别地方。&lt;/p&gt;

&lt;p&gt;而对需求2 的解决方案变成了这样&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;wait_overlapped&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FiberOVERLAPPED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ov&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// FiberOVERLAPPED 派生自 OVERLAPPED&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FiberOVERLAPPED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resume_state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ov&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ready&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ov&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytes_transfered&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;update_overlapped_resume_state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[](&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FiberOVERLAPPED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resume_state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;zcontext_swap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ov&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coro_ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;g_main_loop_coro&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;update_overlapped_resume_state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ov&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ov&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bytes_transfered&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;而主事件循环，则变得不会带上一堆代码处理“前任”了。&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main_loop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(;;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;GetQueuedCompletionStatus&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(...)&lt;/span&gt;

        &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FiberOVERLAPPED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lpOverlapped&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ready&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FiberOVERLAPPED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resume_state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FiberOVERLAPPED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lpOverlapped&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resume_state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;

            &lt;span class=&quot;n&quot;&gt;zcontext_swap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;g_main_loop_coro&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FiberOVERLAPPED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lpOverlapped&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coro_ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如此一来，各自的代码都干净起来了。&lt;/p&gt;

&lt;h1 id=&quot;总结&quot;&gt;总结&lt;/h1&gt;

&lt;p&gt;由此可见，如果一个 swap 函数，可以多接受一个参数，用来在刚刚切换完栈后干一些扫尾工作，则可以极大的简化使用者的代码结构。&lt;/p&gt;

&lt;p&gt;微软的 SwitchToFiber 看起来就一个参数，非常简单。实则让协程相互之间缺乏关联。于是很多东西得靠全局变量+协程本地存储来做。
ucontext 和 微软的Fiber没有特别差异。&lt;/p&gt;

&lt;p&gt;Boost.Context 里带的名为 fcontext_t 的接口，则多了一个切换的时候相互传的参数。最初 zcontext 就是模仿的 fcontext。
准确的来说，是我先实现了 zcontext ，然后去研究 fcontext, 发现英雄所见略同的和 fcontext 在设计理念上撞车了。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;在十天前我在 &lt;a href=&quot;https://microcai.org/2024/12/18/why-boost-fcontext-is-fast.html&quot;&gt;这篇&lt;/a&gt; 文章里，就提到我先写了
个 zcontext 然后发现和 fcontext 如出一辙。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;现在，我更进一步，把 zcontext 升级了，zcontext 将是最好的 有栈协程上下文切换API.&lt;/p&gt;

&lt;p&gt;注意，我说，这是“有栈协程上下文切换API”，而不是有栈协程本身。&lt;/p&gt;

&lt;p&gt;因为，一个&lt;strong&gt;有栈协程&lt;/strong&gt;，必须得和 异步 IO 搭配起来。
也就是说，一个&lt;strong&gt;有栈协程上下文切换API&lt;/strong&gt; + 利用这个API封装的一个异步IO库，才等于&lt;strong&gt;协程库&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;事实上 iocp 这个库里带的 universal_fiber.hpp，就是个协程库。而 zcontext 是它支持的 众多 上下文切换API 中的一个。使用不同的上下文切换API, 自然有不同的一些做法配合。&lt;/p&gt;

&lt;p&gt;在众多的切换api里，最好用的是 zcontext，其次是 fcontext ，然后是 Fiber 。 最差的是 ucontext。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>从0开始写上下文切换</title>
   <link href="https://microcai.org/2024/12/26/context-switch-explained.html"/>
   <updated>2024-12-26T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/12/26/context-switch-explained</id>
   <content type="html">&lt;h1 id=&quot;有栈协程的核心是执行上下文执行上下文的核心是栈&quot;&gt;有栈协程的核心是执行上下文。执行上下文的核心是栈。&lt;/h1&gt;

&lt;p&gt;因此，切换栈就等于切换了上下文。&lt;/p&gt;

&lt;h1 id=&quot;栈在协程切换上的核心地位&quot;&gt;栈在协程切换上的核心地位&lt;/h1&gt;

&lt;p&gt;栈，存储了一个协程/线程 的“调用链”，以及依附于这条链上的“变量”。&lt;/p&gt;

&lt;p&gt;没有栈，ret 指令将无所适从。&lt;/p&gt;

&lt;p&gt;虽说栈是核心，但是栈本质是堆叠存储“历史 寄存器状态”。而当前没有入栈的寄存器状态，才是真正的当前上下文。&lt;/p&gt;

&lt;p&gt;因此，很多古代协程库的做法，是栈协程“句柄” 上，开辟一个数百字节的空间。用于存放当下的CPU寄存器的数值。
上下文切换的时候，就是把当前 cpu 寄存器拷到句柄空间，然后从新协程的句柄空间里把寄存器的值一个一个读回来。&lt;/p&gt;

&lt;p&gt;这种做法，是认可 “CPU寄存器数值的集合” 是当前上下文。栈不过是其中一个寄存器指向的一块内存区域，没什么特别的。&lt;/p&gt;

&lt;p&gt;但是，boost.fcontext 另辟蹊径。他唯一指定 栈为协程的正统核心。当前的 cpu 寄存器状态，可以通过“调用 上下文切换” 函数，变成 上一层的 “历史状态”。&lt;/p&gt;

&lt;p&gt;于是本层所要做的，就只是切换栈。在切换栈前，也只须将本层寄存器状态入栈。切换后，再将寄存器状态出栈。&lt;/p&gt;

&lt;p&gt;因此，在 boost.fcontext 眼里，代表协程正统的，不是那个存储了几百个字节的寄存器数值的句柄空间，而是 栈顶指针。&lt;/p&gt;

&lt;p&gt;而用于引用协程的那个句柄， fcontext_t 结构，不过就是存储了一个栈顶指针。
而不像 ucontext_t 那样，好家伙洋洋洒洒数百字节体积。&lt;/p&gt;

&lt;p&gt;这就是对“何谓上下文”的理解理念不同带来的设计差异。&lt;/p&gt;

&lt;p&gt;这点设计差异，造成了数据结构的不同。数据结构都不同了，自然上下文切换的代码也不尽相同了。正是所谓的，数据结构决定了算法。&lt;/p&gt;

&lt;p&gt;来个上下文切换代码看看&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/context_switch_code.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;作为对比，看下 ucontext 系是怎么做上下文切换的：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/ucontext_code_swapcontext.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这就是不同的设计理念带来的差异。&lt;/p&gt;

&lt;p&gt;ucontext 认为 cpu寄存器是上下文，栈只是其中一个寄存器所引用的附属上下文。
而 boost.fcontext 则认为，栈就是上下文的全部。包括了所有的寄存器历史。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;所谓寄存器的历史，是说进行函数调用的时候，编译器会生成入栈指令保留寄存器的数值，以便函数返回后能恢复这些寄存器的值。于是在调用链上，每一层调用帧上面都保留了一份寄存器的“快照‘&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;既然寄存器的快照就在栈上留着，那只要把当前的状态再“压栈”一次。那么所有的上下文，不就都用一个栈顶表达了吗？&lt;/p&gt;

&lt;p&gt;显然我们会发现，将栈视为协程的核心，能极大的简化代码。&lt;/p&gt;

&lt;p&gt;这点差异，还影响了 make_context 的设计。&lt;/p&gt;

&lt;h1 id=&quot;新建协程&quot;&gt;新建协程&lt;/h1&gt;

&lt;p&gt;一个协程上下文切换库，只需要编写3个函数&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;上下文切换&lt;/li&gt;
  &lt;li&gt;新上下文创建&lt;/li&gt;
  &lt;li&gt;新上下文的出生点&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;其中，新建上下文的出生点代码，是不需要暴露给用户的内部使用代码。另外两个，是作为库接口的形式存在的。&lt;/p&gt;

&lt;p&gt;上下文的切换，在上节已经赘述过了。就是当前状态 push 入栈。然后切换栈顶，然后 pop 出栈。大功告成。&lt;/p&gt;

&lt;p&gt;傻瓜式的代码。&lt;/p&gt;

&lt;p&gt;接下来讲解下，如何创建新上下文。&lt;/p&gt;

&lt;p&gt;通常来说，创建新协程，需要3个参数：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;新协程的栈&lt;/li&gt;
  &lt;li&gt;新协程的用户代码入口（传一个函数指针）&lt;/li&gt;
  &lt;li&gt;新协程函数的参数 &lt;em&gt;用万恶的void*吧 ：）&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;有“创建后即刻运行”，和创建后不运行，调用 上下文切换 切过去后方可运行 两种工作模式。&lt;/p&gt;

&lt;p&gt;一般我们选择 创建后不运行，需要主动调用 上下文切换 才开始运行。&lt;/p&gt;

&lt;p&gt;那么我们仔细思考前面的上下文切换的代码。
这个代码在切换了栈之后，恢复了一系列寄存器后，执行了 ret 指令。&lt;/p&gt;

&lt;p&gt;这个 ret指令就是返回到了新的栈上的调用方。&lt;/p&gt;

&lt;p&gt;对于一个新创建的协程，我们可以 &lt;strong&gt;直接构造&lt;/strong&gt; 它处于&lt;strong&gt;调用了上下文切换，等待返回&lt;/strong&gt;这么一个状态。&lt;/p&gt;

&lt;p&gt;也就是说，假设有这么一个启动函数：&lt;/p&gt;

&lt;div class=&quot;language-llvm highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nl&quot;&gt;__coroutine_entry:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;; same as C code&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;; arg = swap_context(from, to, 0);&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;; user_function(arg);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;swap_context&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;; get user_function from stack&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;real_entry:&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;pop&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;rbx&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;; pass arguments to user function&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;mov&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;rdi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;rax&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;; call user function&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;call&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;rbx&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我们创建新协程的时候，就是让这个协程，处于 call swap_context 已经调用，然后控制权切走，因此还没返回的状态。等这个新协程创建好了，然后调用 swap_context 它就立马从 swap_context 返回，然后接着 调用 用户函数完成启动。&lt;/p&gt;

&lt;p&gt;实际上，在标签 real_entry 之前的代码，是不需要存在的。因为它从创建起来，就已经处于等待返回到real_entry的地步了。&lt;/p&gt;

&lt;p&gt;对于这么一个状态，其栈是这样的：&lt;/p&gt;

&lt;p&gt;note：栈从右向左增长，地址从左到右增加。每个格子表示 8 个字节。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/new_coro_stack_status.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;那么，只要构造好这么一个栈，返回地址填real_entry: 那行的地址。用户函数地址填 make_context 的时候传入的地址。&lt;/p&gt;

&lt;p&gt;接下来 swap_context 的 ret 就会到 real_entry: 那行。然后一个 pop 就把传入的用户函数地址存入  rbx 寄存器。接着 mov rdi, rax 将 rax 存入 rdi寄存器。rdi 也是函数第一个参数。
然后 call rbx 就正式跳入 用户代码了。&lt;/p&gt;

&lt;p&gt;协程就这样启动了。&lt;/p&gt;

&lt;p&gt;实际上，对于初始协程来说，他初始寄存器的数值是啥根本没有关心的意义。&lt;/p&gt;

&lt;p&gt;因此，make_context 实际上要坐的，就只是填好返回地址和用户函数地址。&lt;/p&gt;

&lt;p&gt;也就是说，将传入的栈顶地址减少 96 字节存入 context_t 里。
然后按图中所示在指定的偏移出写入 real_entry 的地址，和用户传入的函数指针。&lt;/p&gt;

&lt;p&gt;因此，编写一个上下文切换库所需的3个函数。 只有2个需要写汇编。一个可以用 C 语言完成。
需要写汇编的那两个，其中一个还特别段，三行代码的事情。&lt;/p&gt;

&lt;h1 id=&quot;跨平台&quot;&gt;跨平台&lt;/h1&gt;

&lt;p&gt;其实，一个上下文切换库，最大的工作量在跨平台。因为别以为3个函数有一个是 C 写的就跨平台了。
其实并不。
三个都需要针对平台重写。&lt;/p&gt;

&lt;p&gt;这个 “平台”，不单单是不同的 CPU 上要写一组，还有不同的“调用约定”下也要写一组。&lt;/p&gt;

&lt;p&gt;因此要编写的数量是  CPU架构数*操作系统*调用约定*汇编器数目。&lt;/p&gt;

&lt;p&gt;这也难怪 boost.fcontext  里有近百份汇编源码文件了。这膨胀的相当的厉害。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>链接到 ucrtbase.dll</title>
   <link href="https://microcai.org/2024/12/23/link-to-ucrtbase-dll.html"/>
   <updated>2024-12-23T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/12/23/link-to-ucrtbase-dll</id>
   <content type="html">&lt;p&gt;使用 vc6.0 以后的 VS 编译器，最恼火的一点就是生成的 exe 依赖 msvcrXXX.dll。
其中 XXX 是个版本号。而且 msvcrXXX.dll 系统是不自带的。&lt;/p&gt;

&lt;p&gt;于是每个 exe 都得带上 vcredist.exe 安装程序。&lt;/p&gt;

&lt;p&gt;过于蛋疼。&lt;/p&gt;

&lt;p&gt;正统的解决方法是改使用静态C库。&lt;/p&gt;

&lt;p&gt;但是会带来二进制体积暴涨的问题。&lt;/p&gt;

&lt;p&gt;十分的怀念 VC6.0 可以生成exe 依赖 msvcrt.dll， 不带版本号那种。而不带版本号的那个，每个 windows 都会自带。&lt;/p&gt;

&lt;p&gt;有比较多的偏门方式，可以在 vc6 以后的版本继续链接到 msvcrt.dll 。但是一来是偏方，而来在 vs2017 以后这些偏方也没用了。&lt;/p&gt;

&lt;p&gt;后来对此也不甚钻研了，直接无脑静态链接了。&lt;/p&gt;

&lt;p&gt;最近，突然又关心起 msvcrt 问题，因为用了下 LLVM MinGW。然后在 readme 里看到了 UCRT 介绍。&lt;/p&gt;

&lt;p&gt;UCRT？ UCRT 是啥？&lt;/p&gt;

&lt;p&gt;原来微软也发现大家不爽带版本号的 msvcrXXX.dll。于是用 C++ 以 extern “C” 的方式重写了它的 C 运行时。
这个重写的新C运行时就是 UCRT。每个 win 都会带上。名为 ucrtbase.dll 和 Debug 版的 ucrtbased.dll。&lt;/p&gt;

&lt;p&gt;连这个UCRT的，还有新的 vcruntimeXXX.dll。而过去 C++ 运行时是 msvcpXXX.dll 。&lt;/p&gt;

&lt;p&gt;这个 UCRT 和过去的 msvcrt.dll 相比一个最显著的区别，是他可以让静态版的 c++ 运行时用上动态版的 C 运行时。&lt;/p&gt;

&lt;p&gt;而之前的老 C库，是 动态C++库必须使用动态 C 库。静态 C++库必须使用静态 C库。&lt;/p&gt;

&lt;p&gt;于是，使用 UCRT 则可以实现虽然c++运行时库还是静态的但是至少把标准C运行时给它动态链接了。起码能缩小几百KB的二进制体积。
而且大家都用动态C运行时库，可以解决跨DLL的接口兼容问题。&lt;/p&gt;

&lt;p&gt;如果是 vs2022 则默认已经是使用 ucrt 了。&lt;/p&gt;

&lt;p&gt;只不过还是老一套的 静态搭静态的规矩。也就是 静态 c++ 库搭配静态 C 库。&lt;/p&gt;

&lt;p&gt;在 cmake 里，启用方式就是&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;if (MSVC)
    add_link_options(&quot;/NODEFAULTLIB:libucrt.lib;libucrtd.lib&quot;)
    link_libraries(ucrt$&amp;lt;$&amp;lt;CONFIG:Debug&amp;gt;:d&amp;gt;.lib)
endif()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;libucrt.lib 是静态 C 运行时，而 ucrt.lib 是动态的。去掉 libucrt.lib 改用 ucrt.lib 就可以了。&lt;/p&gt;

&lt;p&gt;不过很诡异的是， ucrtd.lib 会连接到 ucrtbased.dll ，而 ucrt.lib 则看起来还是静态的。导入表里没有 ucrtbase.dll&lt;/p&gt;

&lt;p&gt;如果使用 llvm-mingw 项目的 clang 编译器，则可以实现 release 版本确实连接 ucrtbase.dll&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/ucrtbase.dll.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;然后导入表里没有 api-ms-*.dll 了。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>boost.fcontext 为何快？</title>
   <link href="https://microcai.org/2024/12/18/why-boost-fcontext-is-fast.html"/>
   <updated>2024-12-18T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/12/18/why-boost-fcontext-is-fast</id>
   <content type="html">&lt;p&gt;在 &lt;a href=&quot;https://microcai.org/2024/12/17/dig-into-libco.html&quot;&gt;批评 libco&lt;/a&gt; 的 上下文切换代码的时候，我曾经说，协程的上下文切换代码，并不需要恢复所有的寄存器。
只需要恢复调用约定里规定的 “非易失性寄存器”。&lt;/p&gt;

&lt;p&gt;只有操作系统进行抢占式调度的时候，才需要恢复所有的寄存器。因为抢占式调度，可以在任何时间任何地方中断线程。如果不恢复所有寄存器，则在线程的角度看来就是寄存器的值可以任何时间任何地方突然“自主改变”。&lt;/p&gt;

&lt;p&gt;而由用户执行的协程调度则不然。上下文切换，一定是代码里“主动”调用某个控制权转移接口实现的。&lt;/p&gt;

&lt;p&gt;而这个控制权转移接口，在编译器看来，不过是一次再正常不过的外部函数调用。只要按照约定，准备好寄存器。。 并且在它返回的时候，自主恢复易失性寄存器。&lt;/p&gt;

&lt;p&gt;是的没错，调用它的地方，本来就假定易失性寄存器的数值会改变。&lt;/p&gt;

&lt;p&gt;因此，在协程的上下文切换函数里，根本没有任何必要恢复易失性寄存器的内容。只需要恢复非易失性寄存器即可。&lt;/p&gt;

&lt;p&gt;为了验证我的设想，我写了一个 zcontext 进行了验证。
这个 zcontext 已提交进 uasync 代码仓库，详情看 &lt;a href=&quot;https://github.com/microcai/iocp/commit/e2d6da2a7cb208a8ed1fcdda6d835ccef193b3c4&quot;&gt;commit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;不过我这上下文切换，只恢复了8个非易失性通用寄存器。对16 个 MMX 寄存器，还有 AVX 寄存器，都没有进行恢复。
不知道会不会出问题。于是就仔细的研读了 boost.context 的汇编代码。希望研究下怎么恢复 SSE 和 AVX 寄存器。&lt;/p&gt;

&lt;p&gt;注：boost.context 里负责上下文切换的 汇编写的 api 叫 fcontext 。&lt;/p&gt;

&lt;p&gt;结果发现 fcontext 的做法和我的不能说完全一样，简直是一模一样。&lt;/p&gt;

&lt;p&gt;无非是我用了 一排 push + 一排 pop 做的切换，而它的代码使用了 mov 指令。但都是等价的。&lt;/p&gt;

&lt;p&gt;也只恢复了非易失性通用寄存器和 2个 80387状态寄存器。&lt;/p&gt;

&lt;p&gt;这下总算完全理解为啥 boost.context 称自己的性能天下无敌了。&lt;/p&gt;

&lt;p&gt;因为确实没有比这更快的上下文切换方式了。&lt;/p&gt;

&lt;p&gt;由此可见，libco 的作者还真是水货一个。他引以为傲的那段汇编代码，就是一坨羊粪。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>点评 腾讯 的 协程库 libco</title>
   <link href="https://microcai.org/2024/12/17/dig-into-libco.html"/>
   <updated>2024-12-17T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/12/17/dig-into-libco</id>
   <content type="html">&lt;h1 id=&quot;序&quot;&gt;序&lt;/h1&gt;

&lt;p&gt;libco 是 腾讯开源的一个协程库。噱头很大，动不动就是承载了微信后台亿万并发。
敢说个不马上被喷成狗头。但是我偏就是要对这些光有名头没啥技术的东西祛魅。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://microcai.org/2024/12/16/dig-into-coroutine-lib.html&quot;&gt;上一片文章&lt;/a&gt; 里，点评了云风的 coroutine库，其中提到了 libco 犯了和云风一样的错。&lt;/p&gt;

&lt;p&gt;其实 libco 的错误要比云风还要多的多。&lt;/p&gt;

&lt;p&gt;废话不多说，接下来点评 libco&lt;/p&gt;

&lt;h1 id=&quot;libco-错在哪里&quot;&gt;libco 错在哪里&lt;/h1&gt;

&lt;p&gt;首先错误的，仓库上一篇，他和云风的错误是一样的。表述相同的部分就不特别说明了。&lt;/p&gt;

&lt;h2 id=&quot;1-协程库不应该干涉程序结构&quot;&gt;1 协程库不应该干涉程序结构&lt;/h2&gt;

&lt;p&gt;但是 libco 的此进入非彼侵入。云风的侵入，是需要永久性的携带 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct schedule *S&lt;/code&gt;,
而 libc 的侵入，则是，它根本就不让你正常的用阻塞的思维模式设计你的代码结构。&lt;/p&gt;

&lt;p&gt;我们那它源码里自带的 example_echosrv.cpp 举例&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/libco_code1.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这是它一个建议的 echo server。前面介绍 iocp4linux 的时候，我就贴过正统代码。
算了，还是直接给个对比吧。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/compare_libco_uasync.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;

&lt;p&gt;图片里两端代码，下面的是正统协程库写的 echo server 里的 accept 循环。
而上面那段则是 libco 里的例子。&lt;/p&gt;

&lt;p&gt;正如我图片里的吐槽那样，腾讯的 libco 在编写代码的时候，很容易陷入不知所谓的境地。&lt;/p&gt;

&lt;p&gt;这就是因为它库设计的缺陷，所以必须要用很绕的方式组织代码逻辑。&lt;/p&gt;

&lt;h2 id=&quot;2-协程库不应该强依赖一个调度器&quot;&gt;2 协程库不应该强依赖一个调度器&lt;/h2&gt;

&lt;p&gt;还是刚刚腾讯自己写的 example_echosrv.cpp 例子。
这次是 main 函数。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/libco_code2.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这不仅仅是依赖调度器了，这是必须，也只能使用 libco 的事件循环。&lt;/p&gt;

&lt;p&gt;如果云风的库只能算对你壁咚，那腾讯的库就是把你裤子都扒光了后入。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;当然，不可否认一些人有网红膜拜症，非要被玩一把才开心。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;当然，如果他的库已经实现的比较完善，那么使用他的事件循环，调用他封装好的 IO api，也未尝不可以接受。神神曾经说过，被强奸的时候，反抗不了，就要学会享受。&lt;/p&gt;

&lt;p&gt;在这点上，云风尚且比腾讯强一丢丢。他的协程库尚且知道，协程库只做协程。IO应该使用另外的 IO库。协程库要做的，就是辅助你使用 IO库的时候，让你写异步IO的代码写的行云流水。&lt;/p&gt;

&lt;p&gt;然而 libco 则是包办一切。试图统括协程调度和异步IO。
想一个库包办一切，自然代价就是，啥也干不好。&lt;/p&gt;

&lt;h2 id=&quot;3-mn-模型是一个已经在上世纪就被证明是个废物的过时技术&quot;&gt;3. M:N 模型是一个已经在上世纪就被证明是个废物的过时技术&lt;/h2&gt;

&lt;p&gt;libco 实际上实现的不是协程，而是一种用户线程，并且使用了 M:N 机制进行调度。&lt;/p&gt;

&lt;p&gt;所谓 M:N 机制，是指用 N 个内核调度实体（也就是线程），去调度 M 个 协程。&lt;/p&gt;

&lt;p&gt;实际上 M:N 模型在上世纪，是 1:1 的 posix thread 模型的有力竞争对手。
并差一点成为 Linux 上的标准线程库。&lt;/p&gt;

&lt;p&gt;要实现 M:N 模型，就需要在每个可能的地方插入“调度点”。
所谓调度点，就是在这个点上，当前运行的协程会被挂起，然后运行其他“就绪”状态的协程。&lt;/p&gt;

&lt;p&gt;为了避免协程长时间占有cpu资源，M:N 模型通常会设计在“所有”的系统api上设计调度点。&lt;/p&gt;

&lt;p&gt;这听起来好像很牛逼的线程模型啊！咋的后来被干掉了呢？&lt;/p&gt;

&lt;p&gt;因为这个模型是假设内核创建线程的开销是很大很大的。于是通过 M:N 的映射关系，将用创建的大量用户级线程，给映射到较少量的内核线程上。&lt;/p&gt;

&lt;p&gt;但是由于调度点的存在，这种线程模型大大增加了线程库的复杂度。并且由于仍然是多线程架构，意味着用户的代码还是得和多线程一样，需要加锁，搞同步。。而且还不能使用内核本身提供的线程间的同步机制。&lt;/p&gt;

&lt;p&gt;大大增加了心智负担，而其所假定的”内核的线程开销比较大“这个前提，禁不起实践的考验。&lt;/p&gt;

&lt;p&gt;最终实现 1:1 模型的 pthreads 将 M:N 模型扫地出门。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;几十年后，还未进棺材的老古董，被 google 重新启用开发了 go ，go 就使用了 M:N 线程模型。但是却谎称是协程。坑了无数刚入门的小白。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;正因为 libco 实际上就是 M:N 模型的线程库，所以他才需要不辞辛劳的对系统 api 进行 hook。&lt;/p&gt;

&lt;h2 id=&quot;4-明明是个-c-库却不支持正统-c-的用法&quot;&gt;4. 明明是个 C++ 库，却不支持正统 c++ 的用法&lt;/h2&gt;

&lt;p&gt;云风的库，其协程函数的入口点是固定签名，带 void* 参数用以支持用户传他自定义的参数。
这是 C 语言的缺陷。只能这么办。&lt;/p&gt;

&lt;p&gt;但是，腾讯你写个 c++ 库，好歹学习一下 std::thread 标准库的接口啊。&lt;/p&gt;

&lt;p&gt;我都用 c++ 了，还用 void* ，那我不是白用 c++ 了？&lt;/p&gt;

&lt;p&gt;这个可不能用设计来搪塞了。这实际上就是无能的表现。&lt;/p&gt;

&lt;p&gt;因为设计为接受 任意多参数作为协程入口。需要使用高级模板技巧。&lt;/p&gt;

&lt;p&gt;学不会的人就会以&lt;strong&gt;太复杂了&lt;/strong&gt;为由搪塞。实际上并不复杂。而且能大幅降低接口的使用难度。&lt;/p&gt;

&lt;p&gt;来，我现场改一个，让 co_create 可以支持入口函数为任意参数&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/libco_code3.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这里面的道道，恐怕他们十年也不一定看得懂。&lt;/p&gt;

&lt;p&gt;虽然其实很简单。根本没有增加任何代码复杂度。以“要维持代码简单”为借口是完全没有根脚的。&lt;/p&gt;

&lt;p&gt;因为本质上， libco 是一群 C with class 程序员写的。C with class 程序员最大的特点就是无能。
把学不会的东西硬说成是不需要的东西。&lt;/p&gt;

&lt;h2 id=&quot;5-上下文切换到底要切些什么都不知道&quot;&gt;5. 上下文切换到底要切些什么都不知道&lt;/h2&gt;

&lt;p&gt;来看下 libco 里执行上下文切换的汇编代码&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/libco_code4.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;可以看出来，它把 “所有” 的 &lt;strong&gt;通用寄存器&lt;/strong&gt; 都切换了。
然而，没有切换状态寄存器，没有切换浮点寄存器。&lt;/p&gt;

&lt;p&gt;好吧，你是说它为了性能，禁止了浮点使用。&lt;/p&gt;

&lt;p&gt;那既然为了性能，又为何对x86 调用约定里直白的写不需要保存的寄存器进行切换了呢？&lt;/p&gt;

&lt;p&gt;为何不需要保留 x86 调用约定规定的易失性寄存器呢？&lt;/p&gt;

&lt;p&gt;因为这个协程切换，是主动进行的切换。是&lt;strong&gt;主动调用&lt;/strong&gt;了 coctx_swap 产生的切换。&lt;/p&gt;

&lt;p&gt;具体的来说，是在 co_coroutine.cpp 里，co_swap（） 调用了：&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;coctx_swap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;curr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pending_co&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;coctx_swap 执行成功后，程序就切换走了。但是，某一天当前协程被恢复了，那么这个 coctx_swap 又会&lt;strong&gt;神奇&lt;/strong&gt;的返回了。&lt;/p&gt;

&lt;p&gt;就好像 setjump() 那样。&lt;/p&gt;

&lt;p&gt;那么对于编译器来说，它本来就会指导，一旦调用了 coctx_swap, 那么编译器就得假定 coctx_swap 返回后，有数个寄存器的内容是已经破坏了。如果编译器需要这些寄存器的内容被保留，编译器在生成 coctx_swap 调用前，就会先行 保存这些寄存器的数值。&lt;/p&gt;

&lt;p&gt;这就是x86调用约定里，关于 “&lt;strong&gt;易失性寄存器&lt;/strong&gt;” 的约定。&lt;/p&gt;

&lt;p&gt;而 libco 的作者一股脑的全恢复了，说明 libco 的作者根本没有深入去思考协程上下文切换。
所谓的高性能，不过是它偷懒没有切换 浮点寄存器和状态寄存器。&lt;/p&gt;

&lt;p&gt;而这种偷懒，一定会有代价。那就是 使用 libco ，就不得不禁止使用 浮点数和 异常。&lt;/p&gt;

&lt;p&gt;是的，连异常都要被禁止。否则协程就会发生未定义行为。&lt;/p&gt;

&lt;p&gt;当然，浮点也不是说完全不能使用。浮点可以在不跨 &lt;strong&gt;调用点&lt;/strong&gt; 的场合下使用。&lt;/p&gt;

&lt;p&gt;也就是说，函数内部没有调用 libco 的 API 的情况下，可以使用浮点。函数内部有调用 libco api 的情况下，浮点的使用要维持在 要么是调用前，要么只能在调用后。不能跨调用点使用。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;其实云风的错误，之所以比 libco 少，完全是因为写的代码少。你看它鸡贼的使用了 ucontext api ，从而避开了
底层汇编进行 cpu 上下文切换的工作。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1 id=&quot;总结&quot;&gt;总结&lt;/h1&gt;

&lt;p&gt;其实 libco 是一个相当的 “玩票” 性质的库。但是因为套了一个“腾讯”的名头，就被很多人顶礼膜拜。&lt;/p&gt;

&lt;p&gt;这篇文章发出去，可以遇见的是徒子徒孙门都会过来骂我。为了维护他们心中的那个 &lt;strong&gt;信仰&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;OVER&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>点评 云风 的 C库 coroutine</title>
   <link href="https://microcai.org/2024/12/16/dig-into-coroutine-lib.html"/>
   <updated>2024-12-16T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/12/16/dig-into-coroutine-lib</id>
   <content type="html">&lt;h1 id=&quot;序&quot;&gt;序&lt;/h1&gt;

&lt;p&gt;在知乎上看到两篇 吹嘘 云风的 coroutine 库的文章。&lt;/p&gt;

&lt;p&gt;人啊就是这样的，出名了以后，就是垃圾也有人吹捧。&lt;/p&gt;

&lt;p&gt;今天来点评下，云风的 coroutine 到底垃圾在哪里。又或者说，一个优秀的协程，应该优秀在哪里。&lt;/p&gt;

&lt;h1 id=&quot;云风错在哪里&quot;&gt;云风错在哪里&lt;/h1&gt;

&lt;p&gt;首先一个优秀的协程库，要做到“自然”。&lt;/p&gt;

&lt;p&gt;什么是“自然”呢？就是要做到尽量不改变原来写同步阻塞IO逻辑的时候的代码。&lt;/p&gt;

&lt;p&gt;因此引出第一错：&lt;/p&gt;

&lt;h2 id=&quot;1-协程库不应该侵入-api-设计&quot;&gt;1 协程库不应该侵入 API 设计&lt;/h2&gt;

&lt;p&gt;要使用云风的协程，被调度的协程必须有如下的函数签名&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;coroutine_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;schedule&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;注意到那个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct schedule *S&lt;/code&gt; 参数了吗？要使用协程，必须带上这个参数。&lt;/p&gt;

&lt;p&gt;哪怕本层级的函数并不直接调用 IO, 也就是不会直接用到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct schedule *S&lt;/code&gt; 参数，他也得带上，并将 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct schedule *S&lt;/code&gt; 传给所有他要调用的函数。&lt;/p&gt;

&lt;p&gt;也就是整个程序所有的函数都得带上 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct schedule *S&lt;/code&gt; 参数。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;这也是 asio 的有栈协程设计失败的地方。一旦使用了有栈协程，所有的函数都得加上 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;asio::yield_context&lt;/code&gt; 参数。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;如果只是某个会调用 IO 的函数需要带上这个 休眠器 还好。但是假设这样的调用链条&lt;/p&gt;

&lt;p&gt;A -&amp;gt; B -&amp;gt; C -&amp;gt; D, 最终在 D函数里调用了  async_read();&lt;/p&gt;

&lt;p&gt;那显然，为了让整条路径能实现逐级传递 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct schedule *S&lt;/code&gt;, 必然会让这条调用链上的所有函数都带上 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct schedule *S&lt;/code&gt; 参数。&lt;/p&gt;

&lt;hr /&gt;

&lt;blockquote&gt;
  &lt;p&gt;但是，如果是 语言自己就支持协程，那么整个层级都得带 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt; 标记，似乎也是传染？
并不是的。因为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt; 是语言的一部分。使用了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async&lt;/code&gt; 并不会导致你整条调用链都&lt;strong&gt;捆绑&lt;/strong&gt;到某个协程库上。因此语言内置的协程，是可以传染，并且不能算是一个坏设计。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;2-协程库不应该强依赖一个调度器&quot;&gt;2 协程库不应该强依赖一个调度器&lt;/h2&gt;

&lt;p&gt;c++20 协程设计，和以往的协程设计有一个显著的不同点：它不依赖某个调度器去调度协程。&lt;/p&gt;

&lt;p&gt;这点，上看，不能说云风的错。应该是因为时代的局限性。云风写此库的年代，协程是必须要依赖一个调度器去调度的。毕竟那时候的理论，就是说 协程是用户调度的线程。&lt;/p&gt;

&lt;p&gt;但是，时代的局限性也是局限性。不能因为是时代造就的缺点就不准批判。正如站在21世纪的我们，不能倒回去去夸奖封建王朝，并且对封建时代的弊病以“时代局限性”为由视而不见。&lt;/p&gt;

&lt;p&gt;今人并不比古人更聪明，但是今人一定比古人有更多的见识。古人的错误必须要认识，吸取，并避免。&lt;/p&gt;

&lt;p&gt;那么，为何 协程库不应该依赖调度器呢？&lt;/p&gt;

&lt;p&gt;先来看下不依赖调度器的协程库该有的 api 和 使用方式&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;ucoro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coro_compute_int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;co_return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ucoro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coro_compute_exec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ucoro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;local_storage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;local storage: &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;any_cast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;comput_promise&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coro_compute_int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;comput_promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;return: &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ucoro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coro_compute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coro_compute_exec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coro_compute_exec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;hello from detached coro&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;hello from main coro&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;coro_compute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;表面上看，用了 co_await，用了协程，但是你会发现，实际上 main() 里并没有调用 协程的调度器。&lt;/p&gt;

&lt;p&gt;整个协程还是自然的运转起来了。&lt;/p&gt;

&lt;p&gt;有人会问，那不依赖调度器的协程，要怎么实现异步IO呢？&lt;/p&gt;

&lt;p&gt;那看下面这个稍加修改的例子&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;ucoro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coro_compute_int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;callback_awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; value&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;co_return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ucoro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coro_compute_exec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;comput_promise&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coro_compute_int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;comput_promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;return: &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;co_return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ucoro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coro_compute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coro_compute_exec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;coro_compute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在 coro_compute_int 里，最终通过&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;callback_awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; value&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个代码，实现将协程转成了一个回调函数。&lt;/p&gt;

&lt;p&gt;如果这里调用了一个  发起IO/完成回调 这种性质的proactor类型的网络库。
那么这个协程就实现了在那一刻，整个调用链被挂起。&lt;/p&gt;

&lt;p&gt;然后等待 网络库的调度。&lt;/p&gt;

&lt;p&gt;没错，协程自身并不提供一个调度器。而是可选的依赖用户代码现有的“完成回调”机制来实现协程调度。&lt;/p&gt;

&lt;p&gt;看起来，如果是云风的库，好像最后写一个 lambda ，然后将 lambda 作为 完成回调。并在完成回调里调用
coroutine_resume 似乎也能实现啊？&lt;/p&gt;

&lt;p&gt;那么为什么我还是说云风这个库设计是错误的呢？&lt;/p&gt;

&lt;p&gt;因为他这个库，脱离了调度器并不能运转。也就是说，如果没有一个调度器去运行 coroutine_resume， 则整个协程就无法运转起来了。具体下个章节会讲为何。&lt;/p&gt;

&lt;p&gt;但是，他的设计，还是有一定的优秀之处的，就是他的协程，可以和其他的调度器搭配使用。
这个和那种内置调度器的 协程库，有着天壤之别。没错，我就是在批评 腾讯的 libco 。libco至少在设计上远不如云风的库。&lt;/p&gt;

&lt;p&gt;如果说云风的库，是错在需要一个调度器，而 libco 错的更离谱， 直接强行内置一个调度器。要把它的协程直接当线程看待。是的，由此可以认定 libgo 也是错误设计，还有 gochannel 更是错误的离谱。&lt;/p&gt;

&lt;h2 id=&quot;3-云风的协程库栈设计是错误的&quot;&gt;3. 云风的协程库栈设计是错误的&lt;/h2&gt;

&lt;p&gt;前面2点，是基于接口设计的理念进行批评的，接下来，就是直面其 C 语言功底的时候了。
前面两个设计缺陷，可以用时代的局限性搪塞。接下来的错误，就不应该犯了，至少不应该是一个为网易编写过游戏引擎的人应该犯的错误。&lt;/p&gt;

&lt;p&gt;接下来的点评，会摘录一部分 云风的代码 来配合讲解&lt;/p&gt;

&lt;p&gt;看一个协程库，一般看3个地方：协程 创建，协程 死亡，协程切换。&lt;/p&gt;

&lt;p&gt;我们首先看协程的切换。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/cloud_wu_code1.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;前面有人可能不服气，以为我说云风的库强行依赖调度器是我黑他。现在看了这个代码的 1 号位置应该是服气了吧。
因为云风的协程，在 创建的时候，仅仅是做了个标记。
真正的创建，是在 切换调度的代码里。在 1 号位这个地方。&lt;/p&gt;

&lt;p&gt;不跑个调度器，根本连运行都运行不起来。&lt;/p&gt;

&lt;p&gt;我们看下2号位的代码，会发现里面赫然一个 &lt;strong&gt;memcpy&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;这是什么鬼？？&lt;/p&gt;

&lt;p&gt;原来云风的协程啊，是通过 memcpy 实现“栈切换”的。&lt;/p&gt;

&lt;p&gt;这种惊为天人的操作，实在是，我看不懂，但是大受震撼。&lt;/p&gt;

&lt;p&gt;原来在 3号位，创建协程的时候，指定栈地址的时候，使用的就是 S-&amp;gt;stack。 也就是，所有的协程，都是使用的同一条栈。&lt;/p&gt;

&lt;p&gt;自然挂起协程的时候，要把栈“复制到别的地方”。而恢复协程的时候，就要把在别的地方保存的栈复制回来。&lt;/p&gt;

&lt;p&gt;这种操作只能说，惊为天人。叹为观止。&lt;/p&gt;

&lt;p&gt;所以，那些叫嚣着，“那我不要调度器，创建后强行 resume 也能脱离调度器使用吧” 这样的行为就一定会崩溃。&lt;/p&gt;

&lt;p&gt;因为这个 resume 操作，注定只能在 主线程栈里进行。也就是说，协程不能通过调用 resume 直接切换到另一个协程。因为他们俩使用的是同一个栈。只能通过yield 回到主线程，在主线程的栈环境上，才能操作 memcpy 复制协程的栈。&lt;/p&gt;

&lt;p&gt;现在知道为何他要在恢复的时候才调用 make_context 创建协程了吧。
因为他使用的是同一个栈，如果在协程里面创建新协程（很自然很正常的需求），则一定会发生数据覆写问题。&lt;/p&gt;

&lt;p&gt;为啥云风会如此设计呢？&lt;/p&gt;

&lt;p&gt;因为一个很简单的道理：云风不知道在 ucontext 里如何正确的实现栈回收操作。
他在自己的blog里说，是因为协程大部分情况下使用的栈都很少，每个协程都分配一个栈会消耗太多内存。
其实他这是在忽悠人呢。因为很多人会被他这句话糊到。
需要对操作系统了解比较深的人才会知道，协程栈需要的内存可以让操作系统“按需分配”。用不到的栈内存，只是“挤占了那段地址范围”，实际并不占用内存。&lt;/p&gt;

&lt;p&gt;而真正的原因，就是他不知道 ucontext规则下，如何实现栈内存回收。&lt;/p&gt;

&lt;p&gt;我们来看以下，在 universal_fiber.h 的代码里，我是如何实现 ucontext  的栈内存回收的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/ucontext_code5.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;在 create_detached_coroutine 里，通过&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FiberContext* new_fiber_ctx = (FiberContext*) malloc(sizeof (FiberContext));&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;分配了一个协程环境。这个 FiberContext 是一个体积 1MB 的巨大结构体。
实际上这里可以调用 mmap 分配 “按需增长” 的内存。但是为了简单起见这里直接使用 malloc.&lt;/p&gt;

&lt;p&gt;接着调用 makecontext 准备好协程上下文环境。&lt;/p&gt;

&lt;p&gt;注意这里，使用的协程入口函数是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__coroutine_entry_point&amp;lt;Args...&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;这也是 C 语言所不能实现的功能。 __coroutine_entry_point 这个模板会根据 协程具体的参数自动实例化。
从而避免依赖  make_context 本身的 “多参数机制”。因为这个多参数机制是无法传递 C++ 对象的。&lt;/p&gt;

&lt;p&gt;在 __coroutine_entry_point 里，将参数解包后， 就可以利用 std::apply 将 tuple 打包的参数作为 func_ptr 的参数进行调用了。&lt;/p&gt;

&lt;p&gt;接下来，就是如注释说的，接下来要想办法释放 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FiberContext* ctx&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;直接调用 free 会必然崩溃。因为 ctx 同时提供了 当前栈。&lt;/p&gt;

&lt;p&gt;所以就通过 makecontext 重新创建一个新协程，并且使用一个 static 的数组变量（因为是 static 变量，因此无需分配，无需释放）作为新协程的栈。&lt;/p&gt;

&lt;p&gt;然后 setcontext 过去，新协程就自然跑起来。而新协程实际上的入口点，就是 C 函数 free()。&lt;/p&gt;

&lt;p&gt;于是新协程运行起来，就把 ctx 释放了。 由于配置了 uc_link = 。因此 free 完成后，由 makecontext 创建的内部代码自动实现 setcontext 到 uc_link 指向的协程。&lt;/p&gt;

&lt;p&gt;于是完成了协程退出的时候自动释放栈并切换到别的协程上。&lt;/p&gt;

&lt;p&gt;这样需要思考得来的代码，云风显然是无法编写出来的。&lt;/p&gt;

&lt;p&gt;因为他的协程，退出自动删除是这么写的&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/cloud_wu_code2.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;在包裹的协程入口代码里，调用那个 C-&amp;gt;func(S, C-&amp;gt;ud) 算是调用了用户的协程函数。
然后用户的协程函数返回的时候，调用 _co_delete(C) 将自己删除。&lt;/p&gt;

&lt;p&gt;注意此时这个代码，运行在 S-&amp;gt;stack 这个栈上！因此可以直接将 C 释放。明白了吧？&lt;/p&gt;

&lt;p&gt;云风其实是因为思考了很久不知道怎么在当前栈还在 C-&amp;gt;stack 上的时候释放 C。
于是整出了大家都用 S-&amp;gt;stack 运行，那 C 就可以随时释放了。&lt;/p&gt;

&lt;h1 id=&quot;所以说技术网红都是水货一个&quot;&gt;所以说，技术网红都是水货一个&lt;/h1&gt;

&lt;p&gt;隔壁老王曾经 &lt;a href=&quot;https://microcai.org/2024/10/07/sainta-quotes.html&quot;&gt;说过&lt;/a&gt;，&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;所以要判断一个人是哪方面的大佬，就看他活跃在哪里，哪里写的东西多，这样判断就好了，如果活跃在github或各种邮件列表的各种项目，自然就是程序大佬，如果主要活跃在社交平台，自然就不是程序大佬而是网红了。&lt;/p&gt;
&lt;/blockquote&gt;

</content>
 </entry>
 
 <entry>
   <title>基于 ucontext + iocp4linux 的超简协程库</title>
   <link href="https://microcai.org/2024/12/15/ucontext-based-uasync.html"/>
   <updated>2024-12-15T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/12/15/ucontext-based-uasync</id>
   <content type="html">&lt;p&gt;虽然说，使用 异步最佳的实践是使用协程。&lt;/p&gt;

&lt;p&gt;然而 c++20 的协程并不总是可用的。总不能说，不能更新编译器的地方就不配写代码吧。&lt;/p&gt;

&lt;p&gt;因此，我在上一篇文章里说道，可以继续忍受回调地狱。或者使用有栈协程。&lt;/p&gt;

&lt;p&gt;当然，如果使用 asio ， 那么一切问题都不存在。asio 支持 有栈协程，无栈协程，daff‘s device 协程，还有最基础的，回调模式。
asio 简直就是个“宇宙级” 的异步库。&lt;/p&gt;

&lt;p&gt;然而，asio 也并不总是可用。&lt;/p&gt;

&lt;p&gt;于是，基于 ucontext 的一个超简协程库就应运而生。&lt;/p&gt;

&lt;p&gt;首先，这个不是一个独立的库，他其实顺手带在 uasync 里。作为  universal_fiber.h 头文件提供。&lt;/p&gt;

&lt;p&gt;就隐藏在 iocp4linux 的 example 代码里。
见 &lt;a href=&quot;https://github.com/microcai/iocp/blob/master/example/echo_server/echo_server_stackfull.c&quot;&gt;echo_server_stackfull.c&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;没错，这次是个 C 代码。尽管我非常讨厌 C。&lt;/p&gt;

&lt;p&gt;这个库，只是为了演示如何将 proactor 和有栈协程给结合起来。虽然我本人建议最好使用 c++20 的协程。但是总有人是升级不了编译器的。
因此我再三思考后，决定还是写一个小 demo 来演示 proactor 和有栈协程 如何极便捷的结合。&lt;/p&gt;

&lt;p&gt;对于这么一个简单的 echo server 例子，作为对比，同文件夹下的 echo_server_callback.cpp 就是基于回调的。可以直观感受下，基于回调和基于协程的代码可读性。&lt;/p&gt;

&lt;h1 id=&quot;先看有栈和无栈例子对比&quot;&gt;先看有栈和无栈例子对比&lt;/h1&gt;

&lt;p&gt;这个是 echo server 左边有栈协程和右边无栈协程的 accept 循环的代码对比
&lt;img src=&quot;/images/ucontext_code4.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;不能说毫无差别，简直可以说是一模一样&lt;/p&gt;

&lt;p&gt;也就是说有栈和无栈，是只存在底层工作机制的差异&lt;/p&gt;

&lt;p&gt;没有使用上的差异。&lt;/p&gt;

&lt;p&gt;这就是 universal_fiber.h 和 universal_async.hpp 的威力。&lt;/p&gt;

&lt;p&gt;关于那个无栈协程 universal_async.hpp 的分析，参考 &lt;a href=&quot;https://microcai.org/2024/12/08/super-lightweight-iocp-coroutine.html&quot;&gt;这篇文章&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;今天我们分析这个有栈协程库。&lt;/p&gt;

&lt;p&gt;正如我前面分析的，观察一个网络库，入口点永远是“事件循环”。一个网络库的事件循环，决定了他的上限。&lt;/p&gt;

&lt;p&gt;由于 run_event_loop() 两个库是一模一样的。因此我们分析有差异的 process_overlapped_event。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/ucontext_code1.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;在获取 OVERLAPPED* 对象然后派发他的完成事件时，&lt;/p&gt;

&lt;p&gt;OVERLAPPED*被强转为 FiberOVERLAPPED* 对象。
然后调用 swapcontext 切换到里面存在的 target
 就把 overlapped 对象绑定的协程给复活了。&lt;/p&gt;

&lt;p&gt;没有其他代码了，异常的简单。&lt;/p&gt;

&lt;p&gt;但是，在 swapcontext 的时候，为何要 使用一个 self 对象，然后又把 __current_yield_ctx 指向 self. 完事后，又为何恢复回去呢？
这个 __current_yield_ctx 是一个 全局变量，目的是方便&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/ucontext_code2.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这个代码里，使用 swapcontext 回到事件循环。&lt;/p&gt;

&lt;p&gt;可以看到， universal_fiber.h 的 接口，是和 universal_async.h 的接口几乎是复刻的。&lt;/p&gt;

&lt;p&gt;比如 无栈协程版的接口是这样的：
&lt;img src=&quot;/images/ucontext_code3.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;除一个是 C++ 语言机制做的无栈协程，另一个是 C语言+底层汇编弄出来的有栈协程。
都说协程，所以用起来是没太大区别的。忽略掉一些语法导致的差异，基本上程序逻辑是毫无差别的。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>深入异步IO 第二篇： 正确使用 IOCP，正确设计 proactor</title>
   <link href="https://microcai.org/2024/12/14/use-iocp-in-the-right-way.html"/>
   <updated>2024-12-14T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/12/14/use-iocp-in-the-right-way</id>
   <content type="html">&lt;h1 id=&quot;前言&quot;&gt;前言&lt;/h1&gt;

&lt;p&gt;干了那么多年码农，遇到 IO 从来都是写异步代码。从最初直面裸 epoll 写，到后来基本上依赖 asio 写。
最近研究 io_uring 后，试着用 io_uring 把 IOCP 那套 API 实现了。&lt;/p&gt;

&lt;p&gt;以前呆在 asio 的舒适区，根本没关注过其他程序员对异步的理解。但是 windows 程序员是最多的，当我研究IOCP的时候，
以前被我忽略掉的那群写 IOCP 的程序员，突然进入了我的视线。&lt;/p&gt;

&lt;p&gt;当我研究了他们对异步的一些吐槽后，才发现了一个真相：大部程序员并不懂异步。&lt;/p&gt;

&lt;p&gt;尤其是，我由于不熟悉 IOCP，故而为了给 iocp4linux 找测试用例，而翻遍网络，寻找使用 IOCP 写成的一些网络代码示例。
能跑通它们，才算 iocp4linux 功能完成了。对吧。&lt;/p&gt;

&lt;p&gt;结果，大部分 IOCP 的例子，虽然能跑。但是我研究了他们的代码后发现，统统都是错误的。完全没有领会到异步IO的精髓。根本就
没有真正的理解异步IO。&lt;/p&gt;

&lt;p&gt;所以，我打算写数篇最深入剖析异步IO的引导文章，足够教会大家 真正的异步思维。&lt;/p&gt;

&lt;h1 id=&quot;reactor-和-proactor&quot;&gt;Reactor 和 Proactor&lt;/h1&gt;

&lt;p&gt;ACE 作者当年提出了数个高性能网络IO的模型，其中将这些模型总结为 Reacto 和 Proactor。如果按字母意思翻译，
则称为 反应器 和前摄器。&lt;/p&gt;

&lt;p&gt;但是，我更愿意使用更精准的汉语表达：多路复用模式 和 重叠IO模式。&lt;/p&gt;

&lt;h2 id=&quot;多路复用模式&quot;&gt;多路复用模式&lt;/h2&gt;

&lt;p&gt;前一篇文章说道，要想实现单线程并发IO, 内核必须要提供的2个机制。在 多路复用模式下，要求的机制就是&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;可读写状态通知&lt;/strong&gt; 和 &lt;strong&gt;无阻塞IO操作&lt;/strong&gt;。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;无阻塞IO操作&lt;/strong&gt; 要求内核在IO不能立即完成的情况下，不能挂起调用线程，而是返回错误。但是此时代码不应该进入无限重试，而是等待内核的通知。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;可读写状态通知&lt;/strong&gt; 如果一个 IO 操作接下来可以无阻塞立即执行，则通知程序，现在可以干活而不会失败了。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;比如我写一个 server，调用 read() 读取客户端发送的数据。如果客户端还未发送数据。则内核会挂起线程直到对方发送数据。然后 read 就执行成功并返回了。&lt;/p&gt;

&lt;p&gt;这种阻塞的 IO 就要求服务端程序要创建大量的线程。每个线程服务一个客户端。而如果使用了多路复用模式。则 内核只会在某个 socket 上收到了数据的时候，才通过多路复用器的接口通知。然后应用程序再执行 read 调用，则可以 100% 立即执行成功，无需挂起。&lt;/p&gt;

&lt;p&gt;过去，内核只能一次性通知 1024 个 socket 的可读可写状态。此接口就是传统的 select。
这意味着如果有超过 1024 个客户端，服务端就要创建线程。每个线程服务只1024个客户端。&lt;/p&gt;

&lt;p&gt;为了剔除这个限制，后来有了理论无限个的 poll() 接口。
但是 poll 接口存在每次调用都要传递个大数组的问题。虽然破除了 select 1024 个的限制，但是性能反而下降了。&lt;em&gt;不然你以为当年为啥 select 使用 1024 固定长度的数组？ 还不是长了性能下降厉害&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;于是，最终内核提供了 epoll 接口。&lt;/p&gt;

&lt;p&gt;但是 select/poll/epoll 本质上是一回事。就是要求内核监控 哪些 socket 上有数据了。此时有数据的 socket 就可以 调用 read 而不阻塞。同时也监控哪些 socket 的发送缓冲区空了。此时调用 write 就可以立即成功而不阻塞。不同的接口，无非是对于“要监控哪些socket”这个信息，使用不同的方式告诉内核。&lt;/p&gt;

&lt;p&gt;所以， select/poll/epoll 乃至 BSD 上的 kqueue, 都是一种东西，就是 IO多路复用器。&lt;/p&gt;

&lt;p&gt;因此，对于 reactor 模型，程序的任务调度结构是这样的：&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;event_loop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(;;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;events&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;poll_events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ev&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;READABLE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;//调用无阻塞读取.&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// 调用事件绑定对象的 on_read 回调&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;on_read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WRITEABLE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// 发送绑定对象的待发送数据&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;write_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write_bufs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;write_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ov&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write_bufs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;c1&quot;&gt;// 无待发送数据，取消 可写监控&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;remove_write_poll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;注意到 reactor 模型的特点了没？&lt;/p&gt;

&lt;p&gt;事件循环，除了 获取事件，派发事件，还要负责 IO！
虽然说无阻塞IO不会导致阻塞。基本上就相当于把内核里的数据拷贝到用户空间。
但是事情多了，这大量的拷贝也是相当的耗时的。&lt;/p&gt;

&lt;p&gt;这还不是主要问题，毕竟这个IO如果真那么费时，可以通过使用独立的 “IO线程” 执行。&lt;/p&gt;

&lt;p&gt;最重要的是，在写入上。&lt;/p&gt;

&lt;p&gt;发现了一个重点没？reactor模型下，网络库自身需要维护一个“发送缓冲区”。&lt;/p&gt;

&lt;p&gt;这就导致了一个现象： 相当一部分reactor模型的网络库，其 ”发送“ 操作是没有返回值的。&lt;/p&gt;

&lt;p&gt;这种现象，可不单单存在于一些初学者写的网络库里。连大名鼎鼎的 libuv, 被 nodejs 所使用的 网络库，
他的 uv_write 也是没有返回值的。 发送数据约等于石沉大海。&lt;/p&gt;

&lt;p&gt;这就是当年很多人批评  TCP 协议其实一点也不可靠的由来。&lt;/p&gt;

&lt;p&gt;但是，在 reactor 模型下， 发送如何有返回值呢？
毕竟发送是异步的，有返回值岂不是得同步发？&lt;/p&gt;

&lt;p&gt;有了，改以下发送的代码。这么写：&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WRITEABLE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 发送绑定对象的待发送数据&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;write_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write_bufs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;number_of_bytes_written&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;write_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 调用发送完成回调.&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;write_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;number_of_bytes_written&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ov&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write_bufs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 无待发送数据，取消 可写监控&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;remove_write_poll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;加个回调嘛。问题解决。&lt;/p&gt;

&lt;p&gt;==， 好像哪里不对劲。。&lt;/p&gt;

&lt;p&gt;虽然 使用的 reactor 模式的操作系统 API, 但是这意味着库的用户面对的接口是这样的&lt;/p&gt;

&lt;p&gt;socket.async_write(buffer, callback);&lt;/p&gt;

&lt;p&gt;这，一个 IO发起，然后一个回调通知，这 它娘的不是 proactor 模型嘛？？？？？&lt;/p&gt;

&lt;p&gt;所以说，为了正确的实现网络库，最终都会走向 proactor。&lt;/p&gt;

&lt;p&gt;reactor 实际上就是一种拍脑袋想出来的残废模型。&lt;/p&gt;

&lt;p&gt;既然写入操作需要一个IO发起，一个完成回调。那么读取操作呢？&lt;/p&gt;

&lt;p&gt;比如我调用一个 RPC 接口，发送一个调用请求，然后等待读取一个请求返回。这是非常自然的程序逻辑。&lt;/p&gt;

&lt;p&gt;然而在 on_read 模式下，事情就变了。因为 on_read 是处于“无时不刻，随时待发”状态的。意味着
on_read 会在任何意想不到的时间被调用。。&lt;/p&gt;

&lt;p&gt;甚至当你调用多个不同的 RPC 接口，则 on_read 要处理的逻辑就会无限增长。&lt;/p&gt;

&lt;p&gt;是的， on_read 模式，仅仅是让 reactor 网络库的 事件循环，看起来简单的点。然而却极大的增加了业务代码的处理复杂度。
业务代码需要处理 on_read “随时被调用” 这么一个情况。编写一个这样的 回调，其难度不亚于编写操作系统的中断处理函数。&lt;/p&gt;

&lt;p&gt;同时也要注意到， on_read 是在 事件循环中被调用的。而那个 on_read 是在一个 大的 for 循环里，对 “每一个收到数据包的 socket 都要调用“。 这导致如果 on_read 回调本身耗时过长，会导致其他待处理的连接处于 ”饥饿“ 状态。&lt;/p&gt;

&lt;p&gt;所以，既然 发送 操作已经变成 proactor 模型，为何继续守着 on_read 模式呢？&lt;/p&gt;

&lt;p&gt;很自然的，读取也可以使用一个 发起+一个回调的方式。&lt;/p&gt;

&lt;p&gt;然后你会豁然开朗。发现异步编程原来如此轻松！&lt;/p&gt;

&lt;h1 id=&quot;重叠io模式&quot;&gt;重叠IO模式&lt;/h1&gt;

&lt;p&gt;相信在上一个小节里，大家已经完全接受了 发送操作使用 发起+回调 的模式。为何对读取，也必须使用发起+回调的模式呢？&lt;/p&gt;

&lt;p&gt;当理论不够明朗的时候，就进行案例分析。&lt;/p&gt;

&lt;p&gt;首先，我们思考一个典型的 proxy 逻辑： 把 socket A 收到的数据，发给 socket B。 把 socket B 收到的数据，发给 socket A。同时干的叫双向代理，只干一个方向的是单向代理。两个方向都干，但是同一时间只能激活一个方向的，叫半双工代理。比如 HTTP 代理就是个典型的半双工代理。而 websocket 代理就是双向代理。&lt;/p&gt;

&lt;p&gt;正如上一篇文章里所言，遇事不决，先用同步IO。&lt;/p&gt;

&lt;p&gt;如果使用同步 IO, 则每个方向各需要一个线程。代码里只要写一份，运行的时候两个参数对调创建2个线程即可。
所以我们看一个方向的线程代码如何处理：&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;splice_socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socketA&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socketB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_write&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUF_SIZE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(;;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;readsize&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socketA&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUF_SIZE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;readsize&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;write_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socketB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;readsize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;total_write&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;write_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果直接使用 proactor 模式的代码，则会陷入回调地狱：&lt;/p&gt;

&lt;p&gt;忽略后面 total_write 和 buffer 参数，那是用来做“状态保存的” 一种简易闭包技巧。&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;splice_socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socketA&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socketB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;callback_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_write&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shared_ptr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUF_SIZE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 读&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;async_read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socketA&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUF_SIZE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;readsize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;readsize&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// 完成&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;total_write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 写&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;async_write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socketB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;write_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;total_write&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;write_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

                &lt;span class=&quot;c1&quot;&gt;// 继续投递自身，实现 循环&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// 注意此时补全的2个参数。&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;splice_socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socketA&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socketB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// 完成&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;total_write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;


    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;代码量明显的就比 同步模式增加了。
但是带来的优势就是无需创建线程了。系统资源的占用就少了。
这对需要代理大量连接的服务器来说，节约系统资源就是节约票子。&lt;/p&gt;

&lt;p&gt;但是，有没有简单的方法呢？ 有啊！上协程！&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;n&quot;&gt;coro_task&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;splice_socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socketA&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socketB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_write&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BUF_SIZE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(;;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;readsize&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;async_read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socketA&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BUF_SIZE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;readsize&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;co_return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;write_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;async_write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socketB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;readsize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;total_write&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;write_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;co_return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;哇塞～！ 和同步的代码一模一样有木有？除了增加的  co_await 关键字，还有使用 async_read/async_write 这种协程版本的 API.&lt;/p&gt;

&lt;p&gt;整个程序的逻辑是如此顺畅。基于回调的代码瞬间不香了！！！&lt;/p&gt;

&lt;p&gt;后面我会讲，为啥协程要求底层库必须为 proactor 。&lt;/p&gt;

&lt;p&gt;现在讲下，为何 reactor 很难写好 proxy。&lt;/p&gt;

&lt;p&gt;首先，因为 传统的 reactor 的 发送操作是没有返回值的。
因此 proxy 对收到的数据，会无脑发送。而不管对端是不是已经网络拥塞收不过来了。
在极端情况下， socketA 以每秒 100M/s 的速度接收数据， 而 socketB 只能以 1MB/s 的速度发送数据，这就会导致 proxy 服务器内存爆炸。&lt;/p&gt;

&lt;p&gt;所以，任何正确的 reactor ，其发送代码也必须，只能，是 proactor 的。&lt;/p&gt;

&lt;p&gt;那么，为何，on_read 模式无法用于编写 proxy 呢？&lt;/p&gt;

&lt;p&gt;还是在内存使用上。&lt;/p&gt;

&lt;p&gt;由于 on_read 模式下，读取是无条件的。哪怕 接收到数据后，使用了 proactor 的模式发送，on_read 仍然是不断的接收数据。
而发送不出去的数据只能堆积在 proxy 的内存里。最终内存爆炸。&lt;/p&gt;

&lt;p&gt;因此，on_read 必须“有条件的调用”。&lt;/p&gt;

&lt;p&gt;所谓 有条件的 on_read , 不就是当代码调用一次 async_read , 才会有一次读取的回调吗？&lt;/p&gt;

&lt;p&gt;这不就是 proactor？？&lt;/p&gt;

&lt;p&gt;这，读取也得 proactor 化啊！！！！&lt;/p&gt;

&lt;p&gt;现在明白了吧？ reactor 模式下，根本无法实现正确的应用逻辑。&lt;/p&gt;

&lt;p&gt;因此，正确实现的网络库，必须，也只能是 proactor 模型的。&lt;/p&gt;

&lt;p&gt;有人问，那我不是写 proxy ， on_read 是不是就能用了呢？&lt;/p&gt;

&lt;h2 id=&quot;处理粘包&quot;&gt;处理粘包&lt;/h2&gt;

&lt;p&gt;要回答这个问题，就请回忆一下，在网络编程领域一个非常热门的话题，如何处理粘包。&lt;/p&gt;

&lt;p&gt;处理粘包在通信协议上的手段
无非2种：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1. 文本协议使用 换行为包结束标志
2. 二进制协议使用  长度+数据 的方式定义一个包
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;剩下的都是这2种的变体，就不赘述。&lt;/p&gt;

&lt;p&gt;先拿简单的二进制来讲，如果简单的都无非处理，那么难的就更麻烦了。&lt;/p&gt;

&lt;p&gt;在 同步IO 和 proactor 模型里，处理粘包都是非常简单的。先读取包头。然后读取剩余部分&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;read_a_packet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 本代码忽略错误处理。只暂时逻辑.&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pkt_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pkt_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;sizeof&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pkt_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;read_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pkt_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pkt_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;而异步，则只需要将  read 换成  co_await async_read ，其他的逻辑是完全一致的。&lt;/p&gt;

&lt;p&gt;那么，在 reactor 模型下，要怎么在 on_read 里处理粘包呢？？？&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;on_read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 这个 buffer 如果 同时包含 了 上一个包的一部分， 和下一个包的一部分&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 要如何高效，正确的，将 粘包给 摘 出来呢？？？？？&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// 只处理了粘包还不够，还得处理业务逻辑，又要怎么把业务逻辑塞进 on_read 里&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 还保持代码的可读性和可维护性呢？&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;是不是突然脑子给干烧了。在同步IO和 proactor 异步的模式下，异常简单的代码，到了 on_read 下居然开始举步维艰。&lt;/p&gt;

&lt;h2 id=&quot;协程的底层必须是-proactor&quot;&gt;协程的底层必须是 proactor&lt;/h2&gt;

&lt;p&gt;好了，回答前面几个小节的时候的一个问题，为何一定要使用 proactor 模型才能使用协程。&lt;/p&gt;

&lt;p&gt;注意到我前面举的几个例子了吗？&lt;/p&gt;

&lt;p&gt;协程版和同步IO版本，是使用相同的代码逻辑。只是在调用 IO 操作的地方，需要使用 co_await 关键字，同时需要调用和同步版本&lt;strong&gt;功能等价&lt;/strong&gt;的协程版本。&lt;/p&gt;

&lt;p&gt;这意味着，协程要求当 api 完成 （指 co_await 返回了一个返回值）的时候，数据是已经“读取完毕”了的。&lt;/p&gt;

&lt;p&gt;也就是说， co_await IO发起（）， 意思就是 发起一个 IO 然后挂起协程，并在 IO完成回调里，将协程恢复。&lt;/p&gt;

&lt;p&gt;看，协程库要求底层库，必须是 发起IO+完成回调 这种形式的。
也就是，必须是 proactor。&lt;/p&gt;

&lt;h2 id=&quot;编译器不支持协程怎么办-reactor-会更好吗&quot;&gt;编译器不支持协程怎么办？ reactor 会更好吗？&lt;/h2&gt;

&lt;p&gt;前面说过， reactor 实际上更本无法编写逻辑正确的代码，或者代价极大。&lt;/p&gt;

&lt;p&gt;所以，从“正确性”的角度而言，proactor 是必需品。如果协程不可用，则可以考虑替代方案&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;老老实实处理回调地狱&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;使用有栈协程&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;实际上，上一篇文章里就讲过，线程就是内核调度的 有栈协程。&lt;/p&gt;

&lt;p&gt;能用线程的语言，都能使用有栈协程。有栈协程无非是在用户代码的层面进行调度，而不是操作系统来调度。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>深入理解异步IO</title>
   <link href="https://microcai.org/2024/12/13/in-deepth-async.html"/>
   <updated>2024-12-13T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/12/13/in-deepth-async</id>
   <content type="html">&lt;h1 id=&quot;前言&quot;&gt;前言&lt;/h1&gt;

&lt;p&gt;干了那么多年码农，遇到 IO 从来都是写异步代码。从最初直面裸 epoll 写，到后来基本上依赖 asio 写。
最近研究 io_uring 后，试着用 io_uring 把 IOCP 那套 API 实现了。&lt;/p&gt;

&lt;p&gt;以前呆在 asio 的舒适区，根本没关注过其他程序员对异步的理解。但是 windows 程序员是最多的，当我研究IOCP的时候，
以前被我忽略掉的那群写 IOCP 的程序员，突然进入了我的视线。&lt;/p&gt;

&lt;p&gt;当我研究了他们对异步的一些吐槽后，才发现了一个真相：大部程序员并不懂异步。&lt;/p&gt;

&lt;p&gt;尤其是，我由于不熟悉 IOCP，故而为了给 iocp4linux 找测试用例，而翻遍网络，寻找使用 IOCP 写成的一些网络代码示例。
能跑通它们，才算 iocp4linux 功能完成了。对吧。&lt;/p&gt;

&lt;p&gt;结果，大部分 IOCP 的例子，虽然能跑。但是我研究了他们的代码后发现，统统都是错误的。完全没有领会到异步IO的精髓。根本就
没有真正的理解异步IO。&lt;/p&gt;

&lt;p&gt;所以，我打算写数篇最深入剖析异步IO的引导文章，足够教会大家 真正的异步思维。&lt;/p&gt;
&lt;h1 id=&quot;线程的由来&quot;&gt;线程的由来&lt;/h1&gt;

&lt;p&gt;现在的程序员大部分都是新世纪后出生的。所以早就忘记了线程的设计初衷了。我看到大部分新世纪程序员都说线程是为了并发，为了压榨cpu的性能。&lt;/p&gt;

&lt;p&gt;其实多线程压榨 cpu 的性能，是最近十几年才有的。而线程的历史，和操作系统一样古老。
计算机诞生的前半个世纪，CPU都是单核的。那时候就已经有了多线程。&lt;/p&gt;

&lt;p&gt;所以，线程的最初真正作用，实际上出乎现在的程序员的意料：是为了实现异步IO。&lt;/p&gt;

&lt;h2 id=&quot;dos-下的-io&quot;&gt;DOS 下的 IO&lt;/h2&gt;

&lt;p&gt;在个人电脑的上古时代，一个打字员的日常工作，是打字，然后 按下键盘上的 Ctrl-S。接着去泡杯茶去喝。&lt;/p&gt;

&lt;p&gt;当 wps 要将文档存入软盘，意味着这段时间，整台电脑都处于“不可操作”的状态。
直到软驱磁头吭呲吭呲的干完活，当前的应用才会复活，能继续执行其他任务了。&lt;/p&gt;

&lt;p&gt;为啥会这样呢？因为 DOS 是一个单任务的操作系统，不支持多线程。这就意味着，DOS 不支持异步 IO。WPS要写磁盘，就只能等待 DOS 完成文件写入。
期间整个电脑都无法干其他工作。&lt;/p&gt;

&lt;p&gt;这个问题，直到“多任务系统”的出现才解决。当然，多任务系统，出现的比 DOS 早多了，只不过，引入 PC 领域要晚的多。一直到 windows 出现。&lt;/p&gt;

&lt;p&gt;在 windows 下，操作系统有了多任务的能力。什么是多任务的能力？简单的来说，就是多线程。虽然那时候，cpu 还是只有一个核心。
多线程，其实并不是真正的并行运行。而是轮流使用 cpu 时间。&lt;/p&gt;

&lt;p&gt;为什么要支持多任务？
你想，当你的 WPS 忙着写磁盘文件，软驱磁头在吭呲吭呲的干活的时候，你是不是可以切换任务，去运行别的软件了？&lt;/p&gt;

&lt;p&gt;这就是多线程带来的 “异步”。当这个任务，在等待 IO 完成的时候，宝贵的CPU时间，可以释放出来运行另一个任务。&lt;/p&gt;

&lt;h2 id=&quot;io-和任务调度---操作系统内核级&quot;&gt;IO 和任务调度 - 操作系统内核级&lt;/h2&gt;

&lt;p&gt;当一个任务要进行 IO ，意味着接下来它已经无法干活了。它一定要等 IO完成才能继续干活。于是，操作系统会很贴心的将任务挂起。
然后调度 CPU 运行别的任务。
当 IO 操作完成了，操作系统就会把那个发起IO的任务给“唤醒”，然后调度它继续运行。&lt;/p&gt;

&lt;p&gt;所以，在操作系统的内部，有一个“事件循环”，还有一票“任务列队”。事件循环不断的获取 事件(可以是硬件发的，也可以是软件发的),
然后 调度 完成事件了的任务去运行。&lt;/p&gt;

&lt;p&gt;如果同一时间有大量事件完成，则这些任务就得按一定的优先级去竞争 CPU 时间。&lt;/p&gt;

&lt;p&gt;而如果同一时间要发出大量IO操作，则需要创建大量的“线程”。&lt;/p&gt;

&lt;p&gt;没错，在古代，发起多个 IO操作就必须要创建多个线程。&lt;/p&gt;

&lt;p&gt;这就是很多下载软件说自己是 “多线程下载” 的由来。&lt;/p&gt;

&lt;p&gt;随着 IO 的性能提高，一台计算机里，同时能支持的 并发IO数量越来越多。这也就意味着，线程的数量会越来越多。。&lt;/p&gt;

&lt;p&gt;然而，CPU 的数量，并没有跟着一起增长出来。在古代， “10k client problem” 是一个研究热门。一万个并发IO，得一万个线程。
想想古代计算机那体系下，创建一万个线程，那操作系统的调度压力会有多大。&lt;/p&gt;

&lt;p&gt;因此，业界迫切需要一种替代线程的异步IO手段。&lt;/p&gt;

&lt;h1 id=&quot;单线程异步io&quot;&gt;单线程异步IO&lt;/h1&gt;

&lt;p&gt;我们现在所说的异步IO，其实指的是单线程异步IO。意思是一个线程就可以发起多个IO操作。
而过去多线程实现的异步IO，在单线程的视角下，就变成了同步IO。&lt;/p&gt;

&lt;p&gt;单线程实现异步IO，其实就是把内核里的 IO和任务调度 的做法，给复制了一份到用户层。
当然因为有内核打底，到用户层这边，这部分的代码就简单多了。并不像内核里那般复杂。&lt;/p&gt;

&lt;p&gt;因此，要支持用户层可以单线程异步IO，内核需要提供几个关键设施&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;事件通知接口&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; 这个事件通知，必须得是多路复用的。一个接口，要负责通知所有的事件。任何遗漏掉的事件，都意味着那部分就得额外使用线程工作。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;IO 发起操作 或 非阻塞 IO操作&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; 所谓 IO发起操作，意思是，调用这个API后，这个 IO 操就可以认为已经进入后台开始执行了。
 执行结果“成功/失败”会通过事件通知接口汇报。
 而 非阻塞IO操作，意味着IO操作要么立即完成，要么无法完成。
 如果立即完成，那么即便是同步IO，也不会阻塞线程，算是解决了最初的问题“IO不能卡电脑啊”。
 如果是无法完成，那么必须可以支持某种“什么时候操作IO不会失败的时候告诉我，我到时候重试”这样的通知机制。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;那么基于内核提供的这些接口，应用软件就可以构造出自己的“任务调度器”。&lt;/p&gt;

&lt;h2 id=&quot;io-和任务调度---应用软件级&quot;&gt;IO 和任务调度 - 应用软件级&lt;/h2&gt;

&lt;p&gt;细心的人已经注意到了，这个小节使用了相同的标题。于是只好加了副标题进行区分。&lt;/p&gt;

&lt;p&gt;既然单线程异步IO实际上就是把内核的工作给复刻一部分到应用层。那么实际上这部分的编码逻辑，就应该参考内核而来。&lt;/p&gt;

&lt;p&gt;如果大家有研究过操作系统的代码，就应该知道，操作系统实际上是有和 cpu 核心数量一样多的调度器。&lt;/p&gt;

&lt;p&gt;每个调度器执行的代码，都是如下所示的伪代码：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;for(;;) // 调度器是个死循环
{
    if (runable_task_list.empty())
    {
        hlt(); // 将 cpu 放入 可中断的低功耗睡眠模式
    }
    else
    {
        cur = runable_task_list.pop();
        switch_to(cur);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;那么，自然，在应用软件里，也应该开启和线程数一样多的“事件循环”。
对系统内核来说，它的任务调度器的数量，是受硬件控制的。自身是不能为所欲为的。
而对于应用软件来说，线程数量倒是可以自己控制。想开几个线程就开几个线程。&lt;/p&gt;

&lt;p&gt;那么，在应用软件里，事件循环应该和内核一样，和具体任务无关，只保留调度代码。用伪代码表示如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;for(;!quit;) // 和内核不一样，这里可停止循环
{
    // 从内核获取 事件.
    event = get_os_event();
    // 运行 事件所“依附”的 任务代码.
    run_task(event.task);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后，就可以基于这个事件循环，逐步构建自己的应用代码。&lt;/p&gt;

&lt;h2 id=&quot;异步io下的-api-设计&quot;&gt;异步IO下的 API 设计&lt;/h2&gt;

&lt;p&gt;之所以前面长篇大论，其实最终还是为了这段内容服务的。&lt;/p&gt;

&lt;p&gt;从前面的历史，线程概念，异步IO概念，就是为了写现在那句话：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;内核调度的多任务是线程。而用户线程调度的多任务，就是协程。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;既然异步IO，实质就是把内核的任务调度，复刻一份到用户层。那么复刻“线程”的概念到用户层，就非常有用，而且也是必须的。&lt;/p&gt;

&lt;p&gt;在最初计算机刚刚发明，开始分 “操作系统”和“应用软件”的时候，系统给应用软件提供的就不是直接基于硬件底层的 API，而是在 API 上面封装一层后的 “同步IO”，并且软件使用多线程进行并发IO。
后来操作系统为了提高工作效率，避免陷入百万线程调度的困境，而选择提供机制让应用层自己调度任务。
那么应用层自己给自己暴露的API，就应该是，也必须是，基于协程的。&lt;/p&gt;

&lt;p&gt;因为事实已经无可辩驳的证明了，程序员在编写逻辑的时候，更倾向于使用“同步IO”的思维模式。因此，程序员使用的IO接口，必然是要么 “阻塞线程” 的 API， 要么是 “阻塞协程” 的 API。&lt;/p&gt;

&lt;p&gt;如果操作系统本身就提供“阻塞协程”的 API，那么皆大欢喜。否则，应用层应该首先就操作系统提供的非阻塞API+多路复用器构建出一套 “阻塞协程” 的 API。&lt;/p&gt;

&lt;p&gt;就拿 “接受 tcp 链接” 这个代码来说，在古代 多线程模式下，代码这么写：&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;client_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client_fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;accept_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;listen_fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(;;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sockaddr&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_client_addr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;addr_len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;listen_fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_client_addr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;addr_len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;create_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;而使用异步IO，必须，也必然要这么写，才能最大化的提高代码可读性，可维护性。&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client_coro&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client_fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;accept_coro&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;listen_fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(;;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sockaddr&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_client_addr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;addr_len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;async_accept&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;listen_fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_client_addr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;addr_len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;create_coro&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client_coro&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但是，co_await 是 c++20 才有的新设施。难道c++20以前，程序员就不配写异步代码吗？&lt;/p&gt;

&lt;p&gt;必然是可以的。因为，co_await 代表的协程，是一种叫 “无栈协程” 的技术路线。
在 c++20 以前，乃至现在和遥远的将来，程序员都可以使用一种叫 “有栈协程” 的技术。
事实上，线程本身就是被内核调度的“有栈协程”。&lt;/p&gt;

&lt;p&gt;因此，在我看来，异步IO必须设计为使用协程并 “阻塞协程” 的同步IO。它只阻塞协程，并不阻塞线程。
线程还是要继续运行，然后到 获取内核的事件通知的时候，才能被阻塞。
正如内核的调度器，要一直运行，直到无事件才调用 hlt 指令让 cpu 进入低功耗待机模式。&lt;/p&gt;

&lt;p&gt;由于 c++20 并不是“随处可得”的普及状态。因此如果设计一个IO库，则必须使用”proactor” 模型。
因为只有 proactor 模型，才能完美适配协程。&lt;/p&gt;

&lt;p&gt;也就是架构如下图所示:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/real_io_lib_struct.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;而用户实际编写代码的时候，要么使用 co_await 协程。要么使用 有栈协程。&lt;/p&gt;

&lt;p&gt;有栈协程并不依赖编译器实现。因此可以视为编译器能力受限情况下的最优解。&lt;/p&gt;

&lt;p&gt;在这个意义下，asio 提供了如何写一个多范式 IO 库的完美例子。&lt;/p&gt;

&lt;p&gt;asio 里的IO对象，其 async_xxx 系列函数，同时提供了 “co_await 协程”，“有栈协程”，“回调” 三种体系。&lt;/p&gt;

&lt;p&gt;如果你自己设计的 IO 库，可以不需要像 asio 那样“考虑周全”。
只需要提供 无栈 或者 有栈 二者之一即可。&lt;/p&gt;

&lt;p&gt;不过，最好 协程是构建在 回调 的基础上。并同时暴露出 基于回调的接口。&lt;/p&gt;

&lt;p&gt;以便用户可以自行扩展。&lt;/p&gt;

&lt;h1 id=&quot;扩展阅读&quot;&gt;扩展阅读&lt;/h1&gt;

&lt;p&gt;为了更好的理解本文，推荐一些扩展阅读。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.jackarain.org/2023/04/12/stupid-on-interface.html&quot;&gt;愚蠢的 on(‘data’, cb) 接口设计&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.jackarain.org/2024/06/07/network-library-design.html&quot;&gt;网络库常见的糟糕设计有哪些&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://microcai.org/2024/11/19/proactor-is-better-than-reactor.html&quot;&gt;重叠IO（proactor）是最理想的IO模型&lt;/a&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>批量提交IO提升性能</title>
   <link href="https://microcai.org/2024/12/10/io_batching-boost-performance.html"/>
   <updated>2024-12-10T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/12/10/io_batching-boost-performance</id>
   <content type="html">&lt;p&gt;在基本完成 iocp4linux 后，我抽空在 &lt;a href=&quot;https://wiki.radxa.com/Rockpi4&quot;&gt;rockpi&lt;/a&gt; 上测了下性能。&lt;/p&gt;

&lt;p&gt;使用仓库里自带的例子  test/web_server/server.cpp，我测得了大约 6000req/s 的性能。&lt;/p&gt;

&lt;p&gt;还不错。但是和 PC 上超过十万的性能相比，有那么点。。。弱鸡。&lt;/p&gt;

&lt;p&gt;但是我通过 htop 注意到了一点。wrk 压测的时候， cpu1 是 cpu 100%。而且都是 红色的。说明cpu 都花在内核时间上了。
这也是 io_uring 很牛逼的原因：活都让内核干了。&lt;/p&gt;

&lt;p&gt;==。&lt;/p&gt;

&lt;p&gt;rockpi 是个6核的soc, 其中 cpu1-cpu4 （我设定 htop 的 cpu 从 1 开始数。）是个 1.4Ghz 的小核。cpu5，cpu6 才是 1.8Ghz 的大核。&lt;/p&gt;

&lt;p&gt;这 不仅仅一核有难，5核围观。这特么干活的还是个小核。&lt;/p&gt;

&lt;p&gt;于是果断找原因，发现是中断亲和性的设定问题。将中断迁移到 cpu5 后，测试发现干活的就是 1.8Ghz 的大核啦。&lt;/p&gt;

&lt;p&gt;然后性能飙到超过 9000req/s。&lt;/p&gt;

&lt;p&gt;之前测试的性能忘记记录了，这次随手截图留存了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/iocp4linux_perf_1.jpg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;嘿嘿，果然给力。这优化。还是免费的。&lt;/p&gt;

&lt;p&gt;就在我沾沾自喜的时候，我又随手测试了以下 老中医写的 proxy_server。它里面带了一个简单的 http server。
wrk 压测以下，我了个去的。轻轻松松 1.6万req/s。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/asio_test1_perf.jpg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;asio 你大爷还是你大爷！&lt;/p&gt;

&lt;p&gt;于是我琢磨着进行优化。&lt;/p&gt;

&lt;p&gt;我想到了 io_uring 提交 IO 的两步曲：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;io_uring_get_sqe()
io_uring_prepXXX

io_uring_submit()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;io_uring_get_sqe 获取到 一个 SEQ 表项后，调用 io_uring_prepXXX 将这个表项配置为相应的 IO 操作。
最后使用 io_uring_submit 提交。&lt;/p&gt;

&lt;p&gt;这里，io_uring_submit 是可以批量提交的。可以多次使用 io_uring_get_sqe,准备好多表项。
然后一次性提交。&lt;/p&gt;

&lt;p&gt;本来这个一次性提交的模式，我是不打算用的。&lt;/p&gt;

&lt;p&gt;主要原因是我并不觉得一次性提交多个 IO 有什么“节约”的。而且会带来“延迟”。因为需要收集多个 IO操作后才批量提交。&lt;/p&gt;

&lt;p&gt;但是，我祭出 strace 后，测得在 rockpi 上，一次 io_uring_enter 调用的开销高达 45us！&lt;/p&gt;

&lt;p&gt;当然，打开 strace 调试后，性能会掉到只有一千多 req/s。这样算来一次 io_uring_enter 调用的开销也是 4us 量级的。&lt;/p&gt;

&lt;p&gt;于是，先修改一个地方&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/iocp4linux_code1.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;将立即 submit 的地方给注释了。&lt;/p&gt;

&lt;p&gt;然后在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GetQueuedCompletionStatus&lt;/code&gt; 的实现里，先调用 submit 后等待IO结果。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/iocp4linux_code2.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;结果发现性能毫无提升。。&lt;/p&gt;

&lt;p&gt;原来是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GetQueuedCompletionStatus&lt;/code&gt; 的API设计限制。这个 API 本来就只能返回一个结果。&lt;/p&gt;

&lt;p&gt;按我示例程序的逻辑，每次 GetQueuedCompletionStatus 返回，就要 resume 一个协程。
每次 resume 一个协程，协程内部也就只会调用一个重叠 IO。然后就调用 co_await 等待IO, 控制权就转回事件循环，然后调用 GetQueuedCompletionStatus 获取下一个事件。&lt;/p&gt;

&lt;p&gt;所以我在 AcceptEx, WSASend, WSARecv 之类的实现里，每次准备好 SEQ ，然后不提交。
结果只是把提交延迟到调用 GetQueuedCompletionStatus 的时候。&lt;/p&gt;

&lt;p&gt;调试结果也验证了我的看法。每次 GetQueuedCompletionStatus 里的 io_uring_submit 只提交了一个 IO操作 。。。。&lt;/p&gt;

&lt;p&gt;看来 IOCP 的缺陷。就无法享受批量提交带来的性能优势了。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;本来这事应该会告一段落。以上多提交的修改，甚至并不是如我所言，是在测试 rockpi 上的性能落后 asio 的时候才奋发图强的去改的。而是在最初的开发阶段就做了。结果发现不会带来性能提升。&lt;/p&gt;

&lt;p&gt;因为 GetQueuedCompletionStatus 就是一次一个的。&lt;/p&gt;

&lt;p&gt;但是受 asio 的性能刺激，我受不了了，于是仔细的研究了自己随手写的 GetQueuedCompletionStatus 事件循环的代码（也就是上文里介绍的 universal_async.hpp 里的 run_event_loop ）。&lt;/p&gt;

&lt;p&gt;突然想到了一点。就是 GetQueuedCompletionStatus 为啥要获取结果就立马执行协程完成事件呢？&lt;/p&gt;

&lt;p&gt;如果先批量GetQueuedCompletionStatus获取结果，直到情况内核完成列队。然后批量执行完成事件。&lt;/p&gt;

&lt;p&gt;那么在批量执行完成事件的时候，就一定会批量投递出新的 IO.&lt;/p&gt;

&lt;p&gt;等批量的IO完成事件执行完了，循环又重新回到 GetQueuedCompletionStatus 获取IO结果，而此时 待提交的 IO 一定非常多！！！&lt;/p&gt;

&lt;p&gt;此时 GetQueuedCompletionStatus 内的 io_uring_submit 就必然是一个大批量提交。&lt;/p&gt;

&lt;p&gt;同时，由于会批量调用 GetQueuedCompletionStatus 获取，因此不能每次 GetQueuedCompletionStatus 都调用一次 io_uring_submit。
于是我把 timeout 参数 = 0 的 GetQueuedCompletionStatus 调用，修改为调用 io_uring_peek_cqe。这个 io_uring_peek_cqe 不会陷入内核。而是直接从 CQ这个无锁列队里拿数据。只有拿不到数据了，再考虑调用 submit 提交。
提交完了，再用个延时很低的 io_uring_wait_cqe_timeout 看 内核能不能马上在很短的时间内给出结果。如果不能，就返回 GetQueuedCompletionStatus 失败。让 run_event_loop 换 timeout = 无穷大 的参数重新进入 GetQueuedCompletionStatus 循环。当然，重新用无穷等待时间进入 GetQueuedCompletionStatus 前，会把已经 获取到的完成事件先执行了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/iocp4linux_code3.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如此修改后，果然性能提高了！！&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/iocp4linux_perf_4.jpg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;然后，通过 strace 检查&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/iocp4linux_perf_5.jpg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;由于每次接受连接，都会调用 getsockname 获取对方 socket 地址。
因此这个 getsockname 的调用数，就表示这段时间内接受了的连接数。&lt;/p&gt;

&lt;p&gt;而图中一共接受了两万九千多个连接。但是，与此同时，却只调用了 io_uring_enter 一千多次。&lt;/p&gt;

&lt;p&gt;说明每次 io_uring_enter （ io_uring_submit 和  io_uring_wait_cqe 都会导致一次 io_uring_enter系统调用）都批量提交了 IO。因此系统调用数七千多，就完成了普通（epoll 模式）模式下一共 epoll_wait + accept + recv + send + close 多个组合预计超过三十万个 系统调用才能处理的 “两万多个请求”。&lt;/p&gt;

&lt;p&gt;将处理两万多个 http 请求所需要的系统调用数量从二十多万缩减到一千多个。&lt;/p&gt;

&lt;p&gt;于是获得了巨大的性能提升。&lt;/p&gt;

&lt;p&gt;当然，最终一万三的处理数，还是败给一万六的 asio 。。。。不过有点欣慰的是超过了 epoll 模式的 asio 了。&lt;/p&gt;

&lt;p&gt;ASIO 你大爷还是你大爷。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>超轻量级 IOCP 协程库</title>
   <link href="https://microcai.org/2024/12/08/super-lightweight-iocp-coroutine.html"/>
   <updated>2024-12-08T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/12/08/super-lightweight-iocp-coroutine</id>
   <content type="html">&lt;p&gt;在 &lt;a href=&quot;/2024/12/04/iocp-on-linux.html&quot;&gt;上一篇&lt;/a&gt; 文章里，我提出了 &lt;a href=&quot;https://github.com/microcai/iocp&quot;&gt;iocp4linux&lt;/a&gt; 。&lt;/p&gt;

&lt;p&gt;在编写 iocp4linux 的过程中，我需要写一些测试代码。&lt;/p&gt;

&lt;p&gt;一开始，我随便的找了一个基于 IOCP 的 echo test 和一个简单的 web server。
首先确保这弄来的例子能在 windows 上编译通过。&lt;/p&gt;

&lt;p&gt;然后修改 ifdef _WIN32守卫，在 linux 平台上改为使用 iocp.h 头文件。&lt;/p&gt;

&lt;p&gt;然后很轻松的就移植到 linux 上了。&lt;/p&gt;

&lt;p&gt;但是，我对于这些 C 风格的 demo 并不满意。因为我完全改不动他们的代码。&lt;/p&gt;

&lt;p&gt;于是，我想着，何不写一个超级轻量级的一个封装库？
这个库，应该做到跨平台 —— 指能同时兼容原版 windows.h和 iocp.h。然后代码尽量短小，同时又足够易用。&lt;/p&gt;

&lt;p&gt;最后。我折腾出了一个超轻量级的协程库。&lt;/p&gt;

&lt;p&gt;废话不说，先看一段 异步 accept 的代码：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/awaitable_overlapped.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;在红框处，使用的是 windows 上的异步 accept 。
在 AcceptEx 调用的时候，传入了一个 ov 对象。
这个 ov 对象，类型是 awaitable_overlapped 。
awaitable_overlapped 可以隐式转换为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WSAOVERLAPPED*&lt;/code&gt; 从而被 AcceptEx 所接受。&lt;/p&gt;

&lt;p&gt;由于是异步 IO ， 这时代码会继续向下运行。一般如果是在非协程的代码里。此时就应该返回函数。&lt;/p&gt;

&lt;p&gt;然后把 accpet 的后续逻辑，放到 调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GetQueuedCompletionStatus&lt;/code&gt; 的事件循环里了。&lt;/p&gt;

&lt;p&gt;然后，进行协程化封装后，此时我们只需要等到投递的那个重叠对象，变成“完成”状态。&lt;/p&gt;

&lt;p&gt;这就是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;co_await get_overlapped_result(ov)&lt;/code&gt; 这行代码的任务了。&lt;/p&gt;

&lt;p&gt;之后，client_socket 就是已经接受了的新连接了。&lt;/p&gt;

&lt;p&gt;然后调用 CreateIoCompletionPort 将他和完成端口绑定。&lt;/p&gt;

&lt;p&gt;最后创建一个新协程。echo_sever_client_session 来处理这个连接。&lt;/p&gt;

&lt;p&gt;这也是这个协程库一个非常好用的地方，任何一个 ucoro::awaitable 协程，都可以要么 co_await 异步等待它，也可以调用 .detach()使它在“后台继续运行”。&lt;/p&gt;

&lt;p&gt;由于这个 accpet 逻辑是不停的接受连接。因此，它要把处理逻辑委托给 一个独立的协程。
因此，这里不能使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;co_await echo_sever_client_session(client_socket)&lt;/code&gt; 而是应该使用
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;echo_sever_client_session(client_socket).detach()&lt;/code&gt; 将协程置入后台继续运行。&lt;/p&gt;

&lt;p&gt;这一个异步 accept 的逻辑，代码异常&lt;strong&gt;清晰&lt;/strong&gt;，&lt;strong&gt;明了&lt;/strong&gt;。仿佛完全不是在使用 IOCP 这种极其复杂的 API。
就好像在使用传统的同步 accept 一样。而且使用的还是原生 windows API。 并不是使用本库封装的 accept。&lt;/p&gt;

&lt;p&gt;这也是本库和其他网络库的一个巨大区别：尽量使用原生API。只是辅助用户管理 重叠IO事务。&lt;/p&gt;

&lt;p&gt;那么是怎么实现让这个库管理重叠事务的呢？我们看下 main 函数：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/awaitable_overlapped3.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;原来 accept_coro().detach() 就创建了多个 accept 协程，并且并发的进行 accept.
然后 直接使用 run_event_loop(iocp_handle) 就把整个协程运转逻辑给跑起来了。&lt;/p&gt;

&lt;p&gt;甚至不需要定义一个什么 io_service 对象！&lt;/p&gt;

&lt;p&gt;另外，这个 awaitable_overlapped 可不是只能使用一次的东西。&lt;/p&gt;

&lt;p&gt;看这个代码：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/awaitable_overlapped2.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这个协程他只有一个 awaitable_overlapped 对象，但是 WSARecv 和 WSASend 都可以复用它。&lt;/p&gt;

&lt;p&gt;对了，如果用在文件 IO 上，则需要不断调整文件的偏移量。这个是用 add_offset 实现的。如下图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/awaitable_overlapped4.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;

&lt;p&gt;需要使用  set_offset 将读写指针移动到文件开头。
然后每次读取后，将返回的读取结果通过 add_offset 更新。&lt;/p&gt;

&lt;p&gt;此时由于这个overlapped 要携带 offset 状态了，于是 WSASend 就不和共用一个 awaitable_overlapped 对象了。
毕竟 WSASend 可不支持传入 offset 偏移量。&lt;/p&gt;

&lt;p&gt;这个库太好用了，其实就一个头文件。他就在 iocp4linux 的仓库里。名为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;universal_async.hpp&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;只要包含这个头文件即可立即享受。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>IOCP 移植到Linux上</title>
   <link href="https://microcai.org/2024/12/04/iocp-on-linux.html"/>
   <updated>2024-12-04T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/12/04/iocp-on-linux</id>
   <content type="html">&lt;h1 id=&quot;序&quot;&gt;序&lt;/h1&gt;

&lt;p&gt;windows 高性能IO使用的是 proactor 模型，而古代 Linux 上则是 reactor 模型。&lt;/p&gt;

&lt;p&gt;因此跨平台的网络库，通常会选择实现为其中一种模型，然后在另一个平台上使用模拟。
比如 asio 使用 proactor 模型。Linux 上使用 epoll 模拟。
又比如 libevent 使用 reactor 模型， windows 上使用 iocp 模拟。&lt;/p&gt;

&lt;p&gt;而将 iocp 模拟为 epoll 的库，就是 &lt;a href=&quot;https://github.com/piscisaureus/wepoll&quot;&gt;wepoll&lt;/a&gt; 了。&lt;/p&gt;

&lt;p&gt;但是，reactor 比较是一种比较落后的模型。因此，进行跨平台封装为 proactor 实际上是更先进的做法。&lt;/p&gt;

&lt;p&gt;虽然已经有 asio 这种 proactor 库了，但是毕竟 asio 是一个比较重量级的库。一种轻量级的 proactor 库
也是非常有市场的。&lt;/p&gt;

&lt;p&gt;更何况，既然有 wepoll 这种把 Linux api 弄到 windows 上的库，那没有 Linux IOCP 就说不过去了。&lt;/p&gt;

&lt;p&gt;于是我决定，开发一个 Linux 版的 IOCP 库。这样方便使用 IOCP 开发的各种软件可以方便的迁移到 Linux 平台。而无需重写。&lt;/p&gt;

&lt;h1 id=&quot;如何实现呢&quot;&gt;如何实现呢？&lt;/h1&gt;

&lt;p&gt;如果是在很久以前，这件事其实还是比较复杂的。然而，嘿嘿。从 linux 5.1 开始，一个崭新的名为 io_uring 的系统接口诞生了。&lt;/p&gt;

&lt;p&gt;io_uring 实现的模型，恰恰就是 proactor。&lt;/p&gt;

&lt;p&gt;于是, 用 io_uring 实现 IOCP 就大幅简化了。&lt;/p&gt;

&lt;p&gt;IOCP 的核心，是一个利用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GetQueuedCompletionStatus&lt;/code&gt; 驱动的事件处理。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GetQueuedCompletionStatus&lt;/code&gt; 从 内核获取完成事件，
并派发完成事件到对应的处理代码。
而使用 io_uring 的程序员，恰恰也是同一个逻辑，使用 io_uring_wait_cqe 等待并获取完成事件，然后派发完成事件到对应的处理代码。&lt;/p&gt;

&lt;p&gt;初始化则有 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CreateIoCompletionPort&lt;/code&gt;， 而 同样 io_uring 这边的初始化是用的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;io_uring_init_queue&lt;/code&gt;&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;IOCP 接口&lt;/th&gt;
      &lt;th&gt;io_uring 对应&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;GetQueuedCompletionStatus&lt;/td&gt;
      &lt;td&gt;io_uring_wait_cqe&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CreateIoCompletionPort&lt;/td&gt;
      &lt;td&gt;io_uring_queue_init&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;WSAOVERLAPPED*&lt;/td&gt;
      &lt;td&gt;io_uring_sqe_set_data&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;WSASend&lt;/td&gt;
      &lt;td&gt;io_uring_prep_sendmsg&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;WSARecv&lt;/td&gt;
      &lt;td&gt;io_uring_prep_recvmsg&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;AcceptEx&lt;/td&gt;
      &lt;td&gt;io_uring_prep_accept&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;PostQueuedCompletionStatus&lt;/td&gt;
      &lt;td&gt;io_uring_prep_msg_ring&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;ReadFile&lt;/td&gt;
      &lt;td&gt;io_uring_prep_readv&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;WriteFile&lt;/td&gt;
      &lt;td&gt;io_uring_prep_writev&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;IOCP 的接口， io_uring 都有完美对应的实现。&lt;/p&gt;

&lt;h1 id=&quot;核心实现逻辑&quot;&gt;核心实现逻辑&lt;/h1&gt;

&lt;p&gt;分2部分。其一为 列队的维护，其二为 IO 的发起操作。&lt;/p&gt;

&lt;p&gt;对应 IO 的发起操作，比如 WSASend 可以映射为 io_uring_prep_sendmsg + io_uring_submit&lt;/p&gt;

&lt;p&gt;实现起来非常简单。主要就是个工作量问题。不用动脑子。&lt;/p&gt;

&lt;p&gt;列队维护，则需要考虑的就是，如何将 WSAOVERLAPPED* 和 io_uring 的完成机制给绑定起来。&lt;/p&gt;

&lt;p&gt;基本的逻辑是，构造一个 io_uring_operation 结构，内部存储一个 WSAOVERLAPPED* 指针。&lt;/p&gt;

&lt;p&gt;io_uring_operation 每次发起 IO 操作的时候分配出来。并使用 io_uring_sqe_set_data 绑定到当前 IO操作上。&lt;/p&gt;

&lt;p&gt;在 io_uring_wait_cqe 获取到完成事件后，使用 io_uring_cqe_get_data 取回 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;io_uring_operation*&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;然后就可以把其中存储的 WSAOVERLAPPED* 返回给 GetQueuedCompletionStatus 的调用者。&lt;/p&gt;

&lt;p&gt;使用 io_uring_operation 结构体而不是直接把 WSAOVERLAPPED* 赋值给 io_uring_sqe_set_data 的原因是，
一些 IO 操作需要“绑定”一些上下文，这些额外的上下文就可以放到 io_uring_operation 里一起绑定起来。比如 io_uring 进行 IO 时常需要的 struct iovec 对象。&lt;/p&gt;

&lt;h1 id=&quot;源码&quot;&gt;源码&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/microcai/iocp&quot;&gt;iocp4linux&lt;/a&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>用iouring扩展asio</title>
   <link href="https://microcai.org/2024/11/21/extending-asio.html"/>
   <updated>2024-11-21T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/11/21/extending-asio</id>
   <content type="html">&lt;h1 id=&quot;序&quot;&gt;序&lt;/h1&gt;

&lt;p&gt;什么叫扩展asio？
Beast给Asio增加了 HTTP 协议，不叫扩展asio。那什么叫扩展asio？
举个例子，给 asio 的 socket 对象，基于io_uring提供的 IORING_OP_SHUTDOWN 增加 async_shutdown() 接口，就算扩展asio。因为原本asio并没有异步shutdown功能。而且这个功能并不能靠组合原有的asio接口实现。这就叫扩展asio。&lt;/p&gt;

&lt;p&gt;扩展asio, 又不能修改asio的代码。看起来有点难办。
好在，asio的设计也是有一定的扩展性的，并非完全依赖修改源码实现扩展。
但是不可避免的，需要使用 asio::detail:: 名字空间下的非公开接口。&lt;/p&gt;

&lt;p&gt;那么，就从 async_shutdown 开始，讲解如何扩展asio吧！&lt;/p&gt;

&lt;h1 id=&quot;接口形式&quot;&gt;接口形式&lt;/h1&gt;

&lt;p&gt;不修改asio代码的大前提下，要给一个对象增加一个 async_shutdown() 的成员函数那是痴人说梦。语言上就禁了此路。&lt;/p&gt;

&lt;p&gt;不过，有两种绕过的做法&lt;/p&gt;

&lt;p&gt;其一。写一个新的socket对象，自 asio::ip::tcp::socket 继承。然后增加一个成员函数。缺点是所有使用 socket 的地方都得修改一下类型。有了IDE的重构大法，实际上为了能异步关闭而进行的迁移并不算特别大的工作量。但是如果有不需要牵一发而动全身的修改，会更好。
其二。写一个全局函数，接受一个 asio::ip::tcp::socket 并将他异步关闭。&lt;/p&gt;

&lt;p&gt;第二个方法，不需要对整个项目进行大手术。只需要在需要异步关闭的地方，将原来的同步关闭 ( &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;socket.shutdown();&lt;/code&gt;)，替换为  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;co_await async_shutdown(socket, defered);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;从设计美学上来说，第二个方法更好。&lt;/p&gt;

&lt;h1 id=&quot;操作系统接口&quot;&gt;操作系统接口&lt;/h1&gt;

&lt;p&gt;扩展asio, 并不是使用asio现有接口去实现一个功能。比如beast利用 asio现有的socket接口就能实现在其基础上增加 HTTP 协议支持。
我们要给 asio 的 socket 增加异步关闭功能，是实打实的让asio多了原本不存在的功能。那么显然，这样的功能，首先我们得问一问，操作系统支持吗？
如果操作系统不支持一个功能，我们想破天也没法给asio扩展出这个功能。&lt;/p&gt;

&lt;p&gt;答案是，异步关闭socket只有io_uring支持了。windows 上虽然也支持异步关闭，但是需要使用 WSAEventSelect + WSAWaitForMultipleEvents + shutdown 组合使用。IOCP不支持异步关闭。没有能传 overlaped* 的 WSAShutdown 版本。&lt;/p&gt;

&lt;h1 id=&quot;asio-的接口组织结构&quot;&gt;asio 的接口组织结构&lt;/h1&gt;

&lt;p&gt;asio 暴露给用户的接口为2种对象，其一为 executor ，其二为 io_object. executor 一般而言就是 asio::io_context。而 io_object 则通常就是 socket 咯。&lt;/p&gt;

&lt;p&gt;在使用上，一般通过绑定一个 io_context 来创建一个 socket. 然后调用 socket 的 aync_* 系列成员函数。或者是能传入 socket 的全局 async_*(socket,…) 函数。全局 async_* 函数最终是通过调用一次或者多次 socket.async_* 实现的。&lt;/p&gt;

&lt;h2 id=&quot;接口背后的驱动框架&quot;&gt;接口背后的驱动框架&lt;/h2&gt;

&lt;p&gt;XX_object 的背后，是 XX_service 。XX_object.async_YY 的背后，实际上是对 XX_servie.async_YY 的调用。&lt;/p&gt;

&lt;p&gt;比如 asio::stream_file 的背后，是 io_uring_file_service.&lt;/p&gt;

&lt;p&gt;asio::basic_socket 的背后，是 io_uring_socke_service.&lt;/p&gt;

&lt;p&gt;但是这些 XX_service 并不需要显式创建。而是在 XX_object 的构造函数里，通过一个叫 asio::use_service&lt;XX_service&gt;(io_context) 的方式，隐式创建并绑定。&lt;/XX_service&gt;&lt;/p&gt;

&lt;p&gt;比如，&lt;/p&gt;
&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;XX_object&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;YY_service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yy_service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;XX_Object_impl&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;XX_obcjet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yy_service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;use_service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;YY_servcie&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;async_XX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;reutrn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yy_service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;async_XX&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后，XX_service 其实还可以进一步依赖更底层的 zz_service.
比如 asio::stream_file 通过 use_service&lt;io_uring_file_service&gt; 绑定 io_uring_file_service。
io_uring_file_service依赖 io_uring_descriptor_service，io_uring_descriptor_service 依赖 io_uring_service。&lt;/io_uring_file_service&gt;&lt;/p&gt;

&lt;p&gt;最终，io_uring_service 才是实际执行 event loop 的对象。由 io_context 根据平台各种宏定义条件编译后创建。
比如在 Linux 平台，io_context 会创建 io_uring_service。
如果是 Win 平台，则  stream_file通过 use&lt;win_iocp_file_service&gt; 绑定 win_iocp_file_service。
win_iocp_file_service 依赖 win_iocp_handle_service。
最终，在Win皮革难题， io_context 是 win_iocp_context 的重定义。而 win_iocp_context 会创建 win_iocp_handle_service。&lt;/win_iocp_file_service&gt;&lt;/p&gt;

&lt;p&gt;如果在 linux 上使用 epoll, 则不支持  stream_file .. 所以改拿 basic_socket举例。&lt;/p&gt;

&lt;p&gt;basic_socket 通过 use_service&lt;reactive_socket_service&gt; 绑定 reactive_socket_service。&lt;/reactive_socket_service&gt;&lt;/p&gt;

&lt;p&gt;reactive_socket_service 依赖 reactive_descriptor_service。
reactive_descriptor_service 又依赖 reactor . 而 reactor 是根据不同api的 typedef, 在 epoll 下， reactor = epoll_reactor.&lt;/p&gt;

&lt;p&gt;根据使不使用 io_uring, io_context 会创建 io_uring_service 或者 reactor. 而 reactor 本身又是条件编译的 typedef ，原类型可以有  epoll_reactor, kqueue_reactor, select_reactor, poll_reactor。&lt;/p&gt;

&lt;p&gt;用一个图来表示关系如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;YY_Object -&amp;gt; use_service&amp;lt;YY_Service&amp;gt; -&amp;gt; .. -&amp;gt; use_service&amp;lt;platform_proactor_service&amp;gt;
                                                            ^
                                                           / \reactor_xx_service
                                                          |   |
io_context -&amp;gt; platform_io_context -&amp;gt; platform_proactor   /     \ platform_reactor
                                  \                              |
                                   -----------------------------/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果要实现 async_shudtown , 显然需要实现一个 io_uring_shutdown_service, 并且这个 io_uring_shutdown_service 同时再依赖 io_uring_service。
在  io_uring_shutdown_service 里实现一个 async_shutdown 的方法。然后向 io_uring_service 投递 相应的SQE (io_uring 术语。表示 请求队列的一个条目)。&lt;/p&gt;

&lt;h2 id=&quot;io_uring_service-的互操作&quot;&gt;io_uring_service 的互操作&lt;/h2&gt;

&lt;p&gt;要向 io_uring_service 投递一个 SQE, 做法并不是非常显而易见的。这涉及到 io_uring 的流程和 io_uring_service 的总体设计。
就目前来说，io_uring_service 有一个 start_op() 的接口。用他投递一个 io_uring_operation* 对象就实现了投递异步操作。&lt;/p&gt;

&lt;p&gt;因此，我们需要实现一个 io_uring_shutdown_op ，并派生自 io_uring_operation。
然后实现 3个静态成员： do_prepare/do_perform/do_complete，并将3个静态成员的指针传给基类io_uring_operation 的构造函数。&lt;/p&gt;

&lt;p&gt;当 io_uring_shutdown_op 被投递的时候，io_uring_service 会分别调用 do_perform/do_prepare/do_perform/do_complete。
do_perform 在我们这里，直接返回io_uring_service传来的 bool after_completion。
在 do_prepare 里，io_uring_service会传来一个待填写的 sqe, 我们调用 io_uring_prep_shutdown 即可。
在 do_complete 里，我们将operation* base （ io_uring_operation 的基类，所以也等于是 io_uring_shutdown_op 的基类）强转
为 io_uring_shutdown_op ， 然后就可以调用里面持有的 handler 回调函数。
这样就完成了一次 异步流程。&lt;/p&gt;

&lt;p&gt;再梳理一次：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
async_shutdown(socket, handler) [代码视角]
    -&amp;gt; io_uring_shutdown_service.async_shutdown
    -&amp;gt; io_uring_service-&amp;gt;start_op( new io_uring_shutdown_op (socket, handler ))
    -&amp;gt; io_uring_shutdown_op::do_complete
    -&amp;gt; handler(error_code)  aka coro.resume()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;以上为协程视角，下面是分拆为2个阶段的异步+回调视角下发生的&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;异步发生的 initiate 阶段：
async_shutdown(socket, handler)
    -&amp;gt; io_uring_shutdown_service.async_shutdown
    -&amp;gt; io_uring_service-&amp;gt;start_op( new io_uring_shutdown_op (socket, handler ))
      -&amp;gt; op_queue-&amp;gt;push( io_uring_shutdown_op )

异步发生的 callback 阶段
    io_contect::run
    -&amp;gt; io_uring_service-&amp;gt;run (批量 submit 阶段)
    -&amp;gt; op_queue-&amp;gt;submit
     -&amp;gt; io_uring_shutdown_op::do_prepare
      -&amp;gt; io_uring_prep_shutdown
    -&amp;gt; io_uring_service-&amp;gt;run (等待完成列队阶段)
     -&amp;gt;io_uring_shutdown_op::do_complete
     -&amp;gt;handler

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;一些需要绕过的注意事项&quot;&gt;一些需要绕过的注意事项&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;io_uring_service::start_op&lt;/code&gt; 需要一个 per_io_object_data 。而这个 per_io_object_data 必须通过 io_uring_service::register_io_object 分配并注册。
使用完毕还必须得通过 io_uring_service::deregister_io_object和 io_uring_service::cleanup_io_object清理。&lt;/p&gt;

&lt;p&gt;但是严格来说，我们实现的 async_shutdown 并不需要一个 per_io_object_data。实际上 win_iocp_service 投递 overlaped 的时候也不需要一个 per_io_object_data，不知道 asio 作者的思路。目前只能假装调用 register_io_object 分配一个。
因此这导致 async_shutdown 不能成为一个全局函数。只能给它搭一个对象。&lt;/p&gt;

&lt;p&gt;考虑到将来封装的不少 io_uring 的 OP 都有类似的情况（不需要真正的一个 io_object 就能使用），因此计划中的 io_uring_shutdown_service 就改名为
io_uring_misc_service。然后创建一个全局的 class misc 对象。&lt;/p&gt;

&lt;p&gt;也就是&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;async_shutdown -&amp;gt; misc.async_shutdowm -&amp;gt; io_uring_misc_service-&amp;gt;async_shutdown
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样多绕一圈。&lt;/p&gt;

&lt;h1 id=&quot;成品&quot;&gt;成品&lt;/h1&gt;

&lt;h2 id=&quot;misc-对象&quot;&gt;misc 对象&lt;/h2&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;misc&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_object_impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_uring_misc_service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;any_io_executor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;impl_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;misc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;impl_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_executor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()){}&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Executor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;misc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Executor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;impl_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;forward&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Executor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CompletionToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;async_shutdown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Socket&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socket_base&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shutdown_type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;how&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CompletionToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;async_initiate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CompletionToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;how&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;mutable&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;impl_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;async_shutdown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;impl_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_implementation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;native_handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;how&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;注意到 io_object_impl，它可以简化 misc 对象和 service 对象的编码。可以看到一个 impl_ 对象就免去了 per_io_object_data 的管理。&lt;/p&gt;

&lt;p&gt;在 misc 对着的 async_shutdown 成员函数里，它通过 impl_.get_serivce() 获得了 io_uring_misc_service 的引用。
从而调用了 io_uring_misc_service::async_shutdown&lt;/p&gt;

&lt;h1 id=&quot;io_uring_misc_service-对象&quot;&gt;io_uring_misc_service 对象&lt;/h1&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;io_uring_misc_service&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;execution_context_service_base&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_uring_misc_service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;public:&lt;/span&gt;
	&lt;span class=&quot;c1&quot;&gt;// The native type of a descriptor.&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;native_handle_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;c1&quot;&gt;// The implementation type of the descriptor.&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;implementation_type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;noncopyable&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;nl&quot;&gt;public:&lt;/span&gt;
		&lt;span class=&quot;c1&quot;&gt;// Default constructor.&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;implementation_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;nl&quot;&gt;private:&lt;/span&gt;
		&lt;span class=&quot;c1&quot;&gt;// Only this service will have access to the internal values.&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;friend&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;io_uring_misc_service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;c1&quot;&gt;// Per I/O object data used by the io_uring_service.&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_uring_service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;per_io_object_data&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io_object_data_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

	&lt;span class=&quot;c1&quot;&gt;// Constructor.&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;io_uring_misc_service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;execution_context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;execution_context_service_base&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_uring_misc_service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io_uring_service_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;use_service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_uring_service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;io_uring_service_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;init_task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;c1&quot;&gt;// Destroy all user-defined handler objects owned by the service.&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shutdown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){}&lt;/span&gt;

	&lt;span class=&quot;c1&quot;&gt;// Construct a new descriptor implementation.&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;construct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;implementation_type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_object_data_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;io_uring_service_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;register_io_object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_object_data_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;c1&quot;&gt;// Destroy a descriptor implementation.&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;destroy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;implementation_type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;io_uring_service_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deregister_io_object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_object_data_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;io_uring_service_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cleanup_io_object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_object_data_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;c1&quot;&gt;// submit op as sqe&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;submit_op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;implementation_type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_uring_operation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    	&lt;span class=&quot;n&quot;&gt;io_uring_service_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;start_op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_object_data_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;private:&lt;/span&gt;
	&lt;span class=&quot;c1&quot;&gt;// The io_uring_service that performs event demultiplexing for the service.&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_uring_service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io_uring_service_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;c1&quot;&gt;// Cached success value to avoid accessing category singleton.&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error_code&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;success_ec_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;protected:&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io_uring_operation&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_uring_operation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;operation&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;operation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;io_uring_shutdown_op&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io_uring_operation&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;BOOST_ASIO_DEFINE_HANDLER_PTR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_uring_shutdown_op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;Handler&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

		&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;how&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

		&lt;span class=&quot;n&quot;&gt;io_uring_shutdown_op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error_code&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;how&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io_uring_operation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;do_prepare&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;do_perform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;do_complete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;forward&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;how&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;how&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

		&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;do_prepare&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_uring_operation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_uring_sqe&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sqe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static_cast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_uring_shutdown_op&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
			&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_uring_prep_shutdown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sqe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;how&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

		&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;do_perform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_uring_operation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;after_completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;after_completion&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

		&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;do_complete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;owner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;operation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error_code&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
								&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/*bytes_transferred*/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;c1&quot;&gt;// Take ownership of the handler object.&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;BOOST_ASIO_ASSUME&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;static_cast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_uring_shutdown_op&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

			&lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;addressof&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

			&lt;span class=&quot;n&quot;&gt;BOOST_ASIO_HANDLER_COMPLETION&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

			&lt;span class=&quot;c1&quot;&gt;// Make a copy of the handler so that the memory can be deallocated before&lt;/span&gt;
			&lt;span class=&quot;c1&quot;&gt;// the upcall is made.&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;binder1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error_code&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ec_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;addressof&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

			&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;public:&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;async_shutdown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;implementation_type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socket_base&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shutdown_type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;how&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;c1&quot;&gt;// Allocate and construct an operation to wrap the handler.&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io_uring_shutdown_op&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;op&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;addressof&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;op&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;allocate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

		&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;success_ec_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;how&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;move&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;submit_op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;impl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;用例：&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;some_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(...)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;misc&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;this_coro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;executor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;async_shutdown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socket_base&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shutdown_both&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;use_awaitable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;所以，扩展 asio 支持更多的 io_uring 操作的方法就是照着 io_uring_shutdown_op 然后扩展出来。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>重叠IO（proactor）是最理想的IO模型</title>
   <link href="https://microcai.org/2024/11/19/proactor-is-better-than-reactor.html"/>
   <updated>2024-11-19T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/11/19/proactor-is-better-than-reactor</id>
   <content type="html">&lt;p&gt;reactor 和 proactor ， 这种毫无联系的英文专用名词，很容易把人弄晕。&lt;/p&gt;

&lt;p&gt;回归正途，使用精确的汉语描述，而非翻译，则正确的说法是：&lt;/p&gt;

&lt;p&gt;多路复用IO 和 重叠 IO 模式。&lt;/p&gt;

&lt;p&gt;所谓多路复用，顾名思义，就是好几个IO复用。。。 复用了谁？复用了线程。
它对比的是最传统的UNIX网络编程： 一个连接一个线程。并发就要开多线程。连接多了，线程就多，系统压力非常大。
而多路复用，则可以让一个线程处理多个连接。这就是复用的含义。为了实现复用，首先IO是非阻塞的。因为如果阻塞了线程，其他连接上的数据不就没法处理了么。既然非阻塞，那就会出现网络数据没准备好，就一直返回 EAGAIN 错误。一直返回难道一直重试吗？显然不现实。所以就有了一个 多路复用器。这个多路复用器最早是 select() 调用。后来是 poll() 最后是 epoll().
而重叠IO,顾名思义，就是 IO 操作是重叠的。和什么重叠？当然是和你当前的线程是重叠的。
也就是，向系统提交一个 IO 操作， 系统就会在“后台”默默的干苦力，当前线程可以立即执行其他任务。不会被阻塞。他和多路复用模式的区别是，多路复用模式，如果会阻塞，内核就返回会阻塞，而不是在后台默默干活。而 重叠IO 则即不会阻塞，也不会返回“请重试”。而是在后台开始默默的进行处理。这个IO的处理，和你程序本身的逻辑，是重叠进行的。所以叫重叠IO。&lt;/p&gt;

&lt;p&gt;话又说回来了，系统是在后台干活了，你得知道他啥时候干好了吧？总不能两眼一摸黑，不知道了吧。
因此，系统需要一个通知机制。Linux 这边，曾经有一种叫 AIO 的东西，它用 “信号” 来通知 IO 完成了。而如今 Linux 使用 io_uring 的 无锁列队 来完成通知。而 windows 则使用 一个叫“完成队列”的东西来通知，当然也可以使用 窗口消息来通知结果。&lt;/p&gt;

&lt;p&gt;不管是 win 的窗口消息，还是linux的 AIO ，在实践中都被人抛弃不用了。因此不讨论这些。&lt;/p&gt;

&lt;p&gt;我们讨论 IO完成端口，和 io_uring, 他们本质上是一样的，就是提供一个异步队列，然后应用层可以使用 一个或多个线程，去读取这个队列获得完成通知。每一个IO操作，都对应一个完成通知。
因此，重叠IO模式，其实和传统的编程思路是一致的：每调用一个函数，就获得一个返回值。
无非是，这个返回不是通过 ret 指令返回，返回结果也不是通过 EAX 寄存器返回。
而是存放到了一个消息队列里。&lt;/p&gt;

&lt;p&gt;但是，操作请求和结果返回，是一一对应的。这是一种非常自然的概念。
自然而然的，在编程上，就可以设计为 IO发起 + 完成回调。
库的作用，就是封装 系统的 IO api ，然后将 IO完成消息转化为 完成回调。
有了 IO发起+完成回调。很自然的，程序就可以组织为一种 协程结构。 发起IO操作，返回一个 promise . IO完成就是让 promise 完成状态。
想同步IO结果，只要 await 这个 promise 就可以了。
非常的容易理解，而且封装起来也非常的轻松。用起来简直就是爽到high起。&lt;/p&gt;

&lt;p&gt;所以，重叠IO 是一种更优秀的编程模型，就不言而喻了。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>真正的重叠IO</title>
   <link href="https://microcai.org/2024/11/18/real-overlaped-io.html"/>
   <updated>2024-11-18T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/11/18/real-overlaped-io</id>
   <content type="html">&lt;p&gt;不知有没有人想过，为何微软将windows 的异步API称为“重叠IO”而不是异步IO。&lt;/p&gt;

&lt;p&gt;在我看来，重叠IO和异步IO的区别，主要在于发起IO后的动作。&lt;/p&gt;

&lt;p&gt;重叠IO, 发起 IO 后，继续执行应用层的逻辑。应用可以选择在任何何时的地点再选择同步（阻塞等待 or 异步等待）IO的结果。
异步IO, 发起 IO 后，立即挂起当前业务逻辑（也就是立即非阻塞等待 IO 结果），回到事件循环，以便执行其他业务逻辑。&lt;/p&gt;

&lt;p&gt;放到协程的设计角度，重叠 IO 相当于这样使用&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;do_read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;read_promise&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(...);&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;干点不依赖&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;IO&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;结果的事情&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;read_result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;read_promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;干需要&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;IO&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;操作结果的事情&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;而异步 IO, 则是这样使用&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;do_read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;async_read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(...);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// do other logic.&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以看出来，重叠 IO 在发出 IO 操作后，可以继续执行其他逻辑。只有当确实需要数据的时候，再进行等待。
而异步IO模式下，发出IO后，必须立即挂起当前协程等待IO完成。&lt;/p&gt;

&lt;p&gt;那么，这两种差异，对现实中的程序有什么影响呢？&lt;/p&gt;

&lt;p&gt;我们先考虑一个最简单的应用： 静态 web 服务器。&lt;/p&gt;

&lt;p&gt;在静态web服务器里，最核心的处理逻辑，就是找到用户要下载的文件，然后将文件发送给客户端。伪代码如下&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handle_send_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;65536&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;flags&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;binary&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;flags&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(;;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;async_read_some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;total&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;async_write_some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;co_return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在这个代码里，主要的逻辑就是 循环读取文件，然后发送。&lt;/p&gt;

&lt;p&gt;看起来似乎好像逻辑很清洗明了。&lt;/p&gt;

&lt;p&gt;但是实际上这个逻辑是错误的。&lt;/p&gt;

&lt;p&gt;因为这会导致 “磁盘读取” 和 “网络发送” 两个操作交替进行。二者无法同时进行，导致各自的 IO 利用率只有一半。&lt;/p&gt;

&lt;p&gt;这其实就是 异步IO带来的问题。要解决这个问题，得使用 重叠IO. 我们看下重叠 IO 是如何操作的：&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;handle_send_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;buf1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;65526&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;buf2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;65526&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;buf1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;buf2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;read_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cur_buf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;next_buf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;total_write&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;read_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;send_promise&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cur_buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;read_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;read_next_buffer_promise&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;next_buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;

        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;send_bytes&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;read_size&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;send_promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;read_next_buffer_promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;total_write&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;send_bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;read_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;swap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cur_buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;next_buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;total_write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在重叠IO模式下，网络发送和磁盘读取会同时进行。充分挖掘 IO 的潜力。绝对不浪费 IO 吞吐量。&lt;/p&gt;

&lt;p&gt;那么，如果已经使用了 c++ 协程，要怎么才能做到 重叠 IO 呢？&lt;/p&gt;

&lt;p&gt;由于 c++ 协程的设计缺陷，c++ 协程只有在被 co_await 的时候，才真正开始执行。而不是创建的时候执行。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;为防止杠精说，c++ 协程通过设置 initual_suspend 也能实现 创建的时候开始执行。问题是，作为一个 协程IO库，你的操作
系统底层是使用的“事件通知”+回调函数实现的。因此必须获得当前协程的睡眠句柄，才能进行在回调处理里唤醒协程这样的操作。
而当前协程的句柄，就必须得通过 co_await 调用才能拿到。也就是说，如果在没拿到协程句柄的情况下，就发起 IO 操作，
就要实现复杂的 “稍后获得协程句柄后提交给已经挂入系统IO回调列队的回调函数” 逻辑。这个逻辑非常难以编写。并且有潜在的
并发和数据竞争问题。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;因此，c++协程，其实真正模拟出来的编程模型，是 “用户调度的线程 + 非阻塞IO”。发起 IO 操作，协程框架就得 挂起当前的用户态线程（&lt;strong&gt;协程&lt;/strong&gt;），然后调度别的用户态线程执行。&lt;/p&gt;

&lt;p&gt;这意味着，并发的IO操作，就必须靠 多协程（多个用户态线程）实现。&lt;/p&gt;

&lt;p&gt;所以，使用 c++ 协程实现发送文件的逻辑，需要创建一个读取和一个写入协程，并且中间使用一个支持协程的消息列队传递数据。
如此编写，才能实现 读写的并发（也就是重叠）。&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;send_coro&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer_queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_write&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(;;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer_queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;async_read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;total_write&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;async_write_some&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;co_return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_coro&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer_queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_read&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;65536&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cur_buf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(;;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;cur_buf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cur_buf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;async_read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cur_buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;total_read&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 列队长度只有1, 多了就会阻塞，而如果 是空列队，就不会阻塞。立即返回&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 从而马上开始下一个 buffer 的读取&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer_queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;async_write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cur_buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_size&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;co_return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;total_read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handle_send_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// queue size = 1&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 意思是空列队的 投递会立即成功。否则等待消费者&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;coro_queue&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer_queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// 打开文件&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;flags&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;binary&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;flags&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// 额外创建一个读取的协程&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;create_task_detached&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read_coro&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer_queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 使用本协程直接执行写协程&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;co_return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;send_coro&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer_queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;对 asio 来说，幸好 asio 提供了这个 支持 协程读写的消息列队，而且可以限制列队长度为1。
对 c++ 协程这种不支持重叠IO的模式来说，要跑满 IO 就必须得开2个协程，一读一写。其实同步IO也是一样的，要跑2个线程，一读一写。
好在协程创建的开销远低于线程。协程列队的开销也低于线程使用的异步队列。
但是，都不如不需要列队的重叠IO来的更好。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>不要 new 一个 char 数组当缓冲区</title>
   <link href="https://microcai.org/2024/11/16/do-not-new-a-buffer.html"/>
   <updated>2024-11-16T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/11/16/do-not-new-a-buffer</id>
   <content type="html">&lt;p&gt;性能调优是一个有魔力的工作。最近研究到了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::pmr::&lt;/code&gt;, 就想着看能否有些老代码能改进改进，提高下性能。&lt;/p&gt;

&lt;p&gt;但是，测试的时候，发现还是有一些路径的效率不理想。&lt;/p&gt;

&lt;p&gt;经过好久的排查，最终定位代码到这么一行&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;make_unique&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buffer_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;好家伙，原来 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make_unique&amp;lt;char[]&amp;gt;&lt;/code&gt; 这么慢的吗？&lt;/p&gt;

&lt;p&gt;原来 make_unique 不单单是 分配了 buffer_size 个字节的内存，同时还 构造了如此数量的 char 对象。
问题是我这只是个 buffer, 不需要初始化啊！&lt;/p&gt;

&lt;p&gt;继续查看生成的汇编代码，发现 make_unique 还是很聪明的，并没有在一个 for 循环里调用5百万次 char 的构造函数。
看来第一种 for 循环五百万次赋值 0 的担忧是没了。
结果看到了汇编代码里的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;call memset@plt&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;所以说，哪怕编译器自动的把初始化代码优化为了一个  memset 调用，它还是调用了 memset ， memset 对一个 5MB 的内存，还是要花点时间的。&lt;/p&gt;

&lt;p&gt;于是我随手把代码改成了&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unique_ptr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;decltype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;free&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;malloc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buffer_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;free&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;测试发现性能飙升了十几倍。&lt;/p&gt;

&lt;h1 id=&quot;总结&quot;&gt;总结&lt;/h1&gt;

&lt;p&gt;在不需要初始化的 buffer 分配领域，使用 malloc 是比 new 更好的选择。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>用本机 clang 进行交叉编译</title>
   <link href="https://microcai.org/2024/11/16/cross-compile-by-clang.html"/>
   <updated>2024-11-16T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/11/16/cross-compile-by-clang</id>
   <content type="html">&lt;p&gt;你在 A 平台上编译一份代码，编译出来的结果，在 A 平台无法运行，只能在 B 平台运行。这个就叫交叉编译。&lt;/p&gt;

&lt;p&gt;通常使用交叉编译，是因为 B 平台太弱鸡，性能无法胜任编译工作。&lt;/p&gt;

&lt;p&gt;因此大部分交叉编译，都是发生在 x86 上为 arm 编译。&lt;/p&gt;

&lt;p&gt;为了进行交叉编译，你需要使用一种专门为交叉编译而开发的工具 —— &lt;strong&gt;交叉编译器&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;在 Gentoo 上，交叉编译器可以使用工具 ”crossdev” 自动构建出来。&lt;/p&gt;

&lt;p&gt;比如&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-base&quot;&gt;crossdev -t aarch64-unknow-linux-gnu
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;最后，你会获得一系列 aarch64-unknow-linux-gnu- 打头的编译工具。其中 aarch64-unknow-linux-gnu-gcc 就是 C语言的交叉编译器。
aarch64-unknow-linux-gnu-g++ 就是 C++ 的交叉编译器。还有 aarch64-unknow-linux-gnu-ld 自然就是交叉连接器。&lt;/p&gt;

&lt;p&gt;这些工具的文件名，你可以传给编译脚本，就实现了交叉编译。&lt;/p&gt;

&lt;p&gt;如果是 CLANG ， 情况发生了一些变化。&lt;/p&gt;

&lt;p&gt;首先，你还是可以通过重新编译 clang ， 设定 clang 的目标平台，配置 clang 的安装前缀，最终获得一系列 aarch64-unknow-linux-gnu-llvm 开头的 llvm 工具链。&lt;/p&gt;

&lt;p&gt;比如 aarch64-unknow-linux-gnu-llvm-link, aarch64-unknow-linux-gnu-llvm-as . 以及 aarch64-unknow-linux-gnu-clang 和 aarch64-unknow-linux-gnu-clang++。&lt;/p&gt;

&lt;p&gt;但是，我要说但是了。这套做法，其实完全没有必要。&lt;/p&gt;

&lt;p&gt;因为 LLVM 的设计结构就决定了，你当前系统里安装的 clang 编译器，体内已经蕴含了全部受llvm支持的平台的机器代码生成能力。
而 gcc 的体内，一次只能含有一个平台的机器码生成能力，其他架构的机器码生成功能会通过条件编译排除。这是 gcc 需要专门构建交叉编译器的原因。&lt;/p&gt;

&lt;p&gt;在任何平台上，你都可以使用 clang 生成 任意平台的代码。方法是传递 -target 参数。&lt;/p&gt;

&lt;p&gt;比如&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;clang++ &lt;span class=&quot;nt&quot;&gt;-target&lt;/span&gt; aarch64-unknow-linux-gnu main.cpp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;不出任何意外的，意外发生了。&lt;/p&gt;

&lt;p&gt;clang 能成功编译，但是在 最后 连接 的阶段报错了。会提示找不到 crtbeginS.o 之类的文件。&lt;/p&gt;

&lt;p&gt;其实很好理解。 clang 虽然拥有生成任意平台的二进制的能力，但是，编译并不只是产生目标文件。更重要的是，还需要连接到运行时库。&lt;/p&gt;

&lt;p&gt;那么，这个运行时库要怎么获取呢？ 答案是： 下载 alarm 的 根目录 包。&lt;/p&gt;

&lt;p&gt;比如下载 ArchLinuxARM-aarch64-latest.tar.gz&lt;/p&gt;

&lt;p&gt;将 ArchLinuxARM-aarch64-latest.tar.gz 解压到 /usr/gnemul/qemu-aarch64/&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo tar&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-xvf&lt;/span&gt; ArchLinuxARM-aarch64-latest.tar.gz &lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt; /usr/gnemul/qemu-aarch64/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后使用这个命令编译&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;clang++ &lt;span class=&quot;nt&quot;&gt;-target&lt;/span&gt; aarch64-unknow-linux-gnu &lt;span class=&quot;nt&quot;&gt;--sysroot&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/usr/gnemul/qemu-aarch64/ main.cpp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;恭喜你，这次成功编译了。（截至今日， archlinuxarm 上带的 STL 是个有 bug 的版本，导致它的头文件有错误。见 &lt;a href=&quot;https://github.com/llvm/llvm-project/issues/92586&quot;&gt;bug&lt;/a&gt;, 如果遇到了，请相信我，不是我教的方法的问题，是真的系统带的头文件有 bug。自己按bug汇报修下吧。）&lt;/p&gt;

&lt;p&gt;对于支持将 “clang++ -target aarch64-unknow-linux-gnu –sysroot=/usr/gnemul/qemu-aarch64/” 作为编译器的 autotools 工具来说，这个教程已经结束了。&lt;/p&gt;

&lt;p&gt;因为只要配置环境变量&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;clang -target aarch64-unknow-linux-gnu --sysroot=/usr/gnemul/qemu-aarch64/&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CXX&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;clang++ -target aarch64-unknow-linux-gnu --sysroot=/usr/gnemul/qemu-aarch64/&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;autotools 系列工具就能正常运行了。&lt;/p&gt;

&lt;p&gt;但是，同样的方法在 cmake 上会失效。
因为 cmake 会把 “clang++ -target aarch64-unknow-linux-gnu –sysroot=/usr/gnemul/qemu-aarch64/” 作为一个整体去调用可执行文件。
当然，系统里并不存在一个名为 “clang++ -target aarch64-unknow-linux-gnu –sysroot=/usr/gnemul/qemu-aarch64/” 的可执行文件。。。。&lt;/p&gt;

&lt;p&gt;对 cmake 来说，还得多做一个工作，就是建立一个 wrapper。&lt;/p&gt;

&lt;p&gt;比如写一个 aarch64-unknow-linux-gnu-clang 的脚本，脚本里面这么写&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;clang &lt;span class=&quot;nt&quot;&gt;-target&lt;/span&gt; aarch64-unknow-linux-gnu &lt;span class=&quot;nt&quot;&gt;--sysroot&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/usr/gnemul/qemu-aarch64/ &lt;span class=&quot;nv&quot;&gt;$*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;还有写一个 aarch64-unknow-linux-gnu-clang++ 的脚本，脚本里面这么写&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;clang++ &lt;span class=&quot;nt&quot;&gt;-target&lt;/span&gt; aarch64-unknow-linux-gnu &lt;span class=&quot;nt&quot;&gt;--sysroot&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/usr/gnemul/qemu-aarch64/ &lt;span class=&quot;nv&quot;&gt;$*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样，就可以如传统的交叉编译器做法一样，让 cmake 使用 aarch64-unknow-linux-gnu-clang 和 aarch64-unknow-linux-gnu-clang++ 作为编译器进行交叉。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>可变模板参数包迭代</title>
   <link href="https://microcai.org/2024/11/13/range-for-on-parameter-pack.html"/>
   <updated>2024-11-13T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/11/13/range-for-on-parameter-pack</id>
   <content type="html">&lt;h1 id=&quot;序&quot;&gt;序&lt;/h1&gt;

&lt;p&gt;如果你写了一个模板函数，模板函数里使用了 可变参数。比如你写了个&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;那么，要如何去访问这些可变的参数呢？&lt;/p&gt;

&lt;h3 id=&quot;方法-1&quot;&gt;方法 1&lt;/h3&gt;

&lt;p&gt;使用递归法。比如定义2个 print, 一个是单参数的，另一个是 2个模板参数的。&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Arg&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arg1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Arg1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在可变参的 print 里， 它首先将第一个参数调用了单参数版的 print. 接着把剩下的参数调用自身。&lt;/p&gt;

&lt;p&gt;如果剩下的参数就一个了，那么编译器也是匹配到单参数版的 print 从而结束递归。&lt;/p&gt;

&lt;h3 id=&quot;方法-2&quot;&gt;方法 2&lt;/h3&gt;

&lt;p&gt;使用 &lt;strong&gt;折叠表达式&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;print_one&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在这个方法里，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(print_one(args), ...);&lt;/code&gt;就是所谓的 &lt;strong&gt;折叠表达式&lt;/strong&gt;。 它的意思就是把 前面的 重复一下。每次重复的时候，各使用参数包里的一个参数。&lt;/p&gt;

&lt;h3 id=&quot;方法-3&quot;&gt;方法 3&lt;/h3&gt;

&lt;p&gt;打包进 std::tuple 后使用。比如&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;print_tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tuple&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tuple_arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;c1&quot;&gt;// ???&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;print_tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;forward_as_tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...));&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;吼吼，那么问题来了， tuple 版本要怎么实现？&lt;/p&gt;

&lt;p&gt;其实又回到了  递归 或者折叠表达式 的老路上了。&lt;/p&gt;

&lt;h1 id=&quot;参数包上的-for-循环&quot;&gt;参数包上的 for 循环&lt;/h1&gt;

&lt;p&gt;折叠表达式的语法并不讨喜。而且只能对每个参数做一模一样的活，因此灵活性欠佳。递归的话，逻辑非常的晦涩难懂。因为它是用递归去实现循环。&lt;/p&gt;

&lt;p&gt;因此，我提出了一种新的语法格式，就是让 for 循环可以用在参数包上。 语法如下&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;for ( auto or typename   identifier  : parameter_pack  )
{

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果 parameter_pack 是类型列表，则使用 typename 比如&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg_type&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;constexpr&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;is_same_v&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;c1&quot;&gt;//&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果 parameter_pack 是形参列表，则使用 auto ，比如&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;constexpr&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is_printable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;decltype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;print_one&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;优势&quot;&gt;优势&lt;/h1&gt;

&lt;p&gt;比 &lt;strong&gt;折叠表达式&lt;/strong&gt; 更容易编写和理解。能表达的意思也更加完善。而且配合 static if 还能实现 折叠表达式 无法实现的选择性操作。
for 内部还能使用 break 提前终止迭代。&lt;/p&gt;

&lt;p&gt;比如对 tuple 的访问，也会非常直观&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;do_with_tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tuple&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tuple_var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;INDEX&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;make_index_sequence&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tuple_size&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tuple_var&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;INDEX&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tuple_var&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

		&lt;span class=&quot;c1&quot;&gt;// do with var&lt;/span&gt;

	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;编译器实现&quot;&gt;编译器实现&lt;/h1&gt;

&lt;p&gt;编译器需要对 for 循环进行完整展开。因为循环的容器是 参数包, 其内容编译期已知。&lt;/p&gt;

&lt;p&gt;对包含 break 和 contine 的 循环体，可以在展开后，使用 goto 进行跳转。&lt;/p&gt;

&lt;p&gt;简单来说，就是这个语法糖是完全可实现的，而且并不麻烦。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>把闭包变成函数指针—— trampoline 原理解析</title>
   <link href="https://microcai.org/2024/11/02/lambda-to-function-pointer.html"/>
   <updated>2024-11-02T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/11/02/lambda-to-function-pointer</id>
   <content type="html">&lt;h1 id=&quot;序&quot;&gt;序&lt;/h1&gt;

&lt;p&gt;十年前，我曾经写过一个让 C 形式的回调函数支持 闭包的小转换工具, &lt;a href=&quot;https://github.com/avplayer/avboost/blob/master/include/boost/cfunction.hpp&quot;&gt;见这&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;那时候，我说过，要想把 boost.function 传给 C 接口，那这个 C 接口，必须得带一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void* user_data&lt;/code&gt; 的参数。&lt;/p&gt;

&lt;p&gt;比如&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;callback_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;register_callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;callback_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以利用这个 cfuntion，把 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;boost::function&amp;lt;int(int, int)&amp;gt;&lt;/code&gt; 给当成 callback_t 使用。
而 boost::function 本身又可以接受 lambda, boost::bind ， 从而组合出非常易用的使用方式。&lt;/p&gt;

&lt;p&gt;但是，这十年来，我也偶尔会遇到一些 C 库的 api ， 他就只能接受裸指针，不能接受 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void* user_data&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;callback_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;register_callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;callback_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这就很难受了。因为没有办法传递 void*, 也就无法给回调函数设置“上下文”。也就彻底失去了上下文关联能力了。不得不将所需的
数据通过“全局变量”传递给回调函数。十分丑陋不说，还导致回调函数变成了无法重入的。&lt;/p&gt;

&lt;h1 id=&quot;转机&quot;&gt;转机&lt;/h1&gt;

&lt;p&gt;其实，如果使用 GCC 编译器，GCC 也提供一个叫 “nested function” 的功能，比如&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;callback_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;register_callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;callback_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hello&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nested_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;hello = %d&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;register_callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nested_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个 nested_function 能访问 main 里定义的对象，算的上一种“闭包”了，但是，它仍然可以转换为不需要 void* 的裸函数指针。&lt;/p&gt;

&lt;p&gt;不过， gcc 并没有进一步发掘 nested function 的魅力。nested_function 在 main 退出后，就会失效。因为它是使用 栈
来存储闭包数据的。也就是说，register_callback其实必须在返回前就调用 nested_function。否则 nested_function 就失效了。&lt;/p&gt;

&lt;p&gt;但是，即便 gcc 的 nested function 有缺陷，但是其背后的实现手法，也值得让我研究。因为它的函数指针，可以隐含一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void*user_data&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;其实 gcc 的实现方式，是把你写的 nested_function，改写为&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__nested_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;captured_data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;trampoline_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;hello = %d&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;trampoline_data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;接着，当运行到这个地方的时候，它在运行时 &lt;strong&gt;动态&lt;/strong&gt; 的编出一个这样的 nested_function&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;nested_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;program_pointer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_program_pointer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;captured_data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;trampoline_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;captured_data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;program_pointer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;__nested_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;trampoline_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;具体的，你的代码是被这样转换的：&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hello&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;captured_data&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

	&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__nested_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;captured_data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;trampoline_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;hello = %d&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;trampoline_data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nested_function_template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;program_pointer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_program_pointer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

		&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;captured_data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;trampoline_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;captured_data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;program_pointer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;trampoline_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;

		&lt;span class=&quot;n&quot;&gt;__nested_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;trampoline_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nested_function_prog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;trampoline_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;captured_data&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hiden_arg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hello&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;memcpy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nested_function_prog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nested_function_template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;sizeof&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nested_function_template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;memcpy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nested_function_prog&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;trampoline_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hiden_arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;sizeof&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hiden_arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;callback_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nested_function&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;callback_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nested_function_prog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;register_callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nested_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个 nested_function 是在当前 “栈” 上创建的。因此这个功能，还要求 “栈内存可执行”。&lt;/p&gt;

&lt;p&gt;虽说是 “动态创建”，其实不过是预先编译了一个 “模板”，这个模板在相对 “指令指针” 的固定位置，存储一个指针。
因此，这个模板可以随时 memcpy 的方式复制到一个新的位置执行。只不过，复制后，要在那个偏移位置，”写入“ 创建的数据对象的地址。
这样这个 动态创建的函数，它就可以在运行时找到那个函数。所以，其实并不是在运行时才 编译 nested_function。而是先编译了一个 位置无关 版本的作为模板。
然后运行时再把模板搭数据，拷贝到 分配的栈内存里。然后把这份实例化的函数作为 nested_function 使用。&lt;/p&gt;

&lt;p&gt;顺着 GCC 的思路。完全可以把 栈 创建给修改为 堆 创建，就完全实现了 lambda 转裸函数指针 的要求。&lt;/p&gt;

&lt;h1 id=&quot;用-c-实现一个&quot;&gt;用 C++ 实现一个&lt;/h1&gt;

&lt;p&gt;C 语言是没有模板的，不过编译器内部实现，也不需要 C 语言有啥模板。&lt;/p&gt;

&lt;p&gt;但是，我们要实现“任意 lambda” 转换为函数指针，那就得使用 C++ 的模板机制。而且，不同的函数，还得“共享” 同一份汇编写的  trampoline 代码模板。
编译器可以为 nest_function 立即生成一份它独有的 trampoline 代码，但是，库作者只能使用语言本身的机制，又不能调用编译器。
所以，为了共享 trampoline 代码，我的设计思路是，首先使用 仿函数来做一次中转。&lt;/p&gt;

&lt;p&gt;中转的思路是 调用方  -&amp;gt; trampoline -&amp;gt; 包装的仿函数 -&amp;gt; 真正的用户 lambda。&lt;/p&gt;

&lt;p&gt;GCC 实现的 nested function 是把 trampoline -&amp;gt; 包装的仿函数 这俩代码给合并成一个。毕竟不同的 lambda 其实是需要不同的 包装器去 调用的。&lt;/p&gt;

&lt;p&gt;所以，我的做法是，把 真正的用户 lambda ，给用  std::function 存起来。然后用模板去实现 包装的仿函数。业绩是说，包装的仿函数长这样&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ReturnType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;wrapped_function&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;ReturnType&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_lambda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;forward&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)...);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ReturnType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_lambda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样，就不需要为不同签名的函数，写无数种排列组合的 trampoline。 wrapped_function 根据用户的 lambda 动态创建出来。然后
trampoline 只需要找到 wrapped_function 的 this 指针。就可以调用了。那么 trampoline 也可以用模板实现出来&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ReturnType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ReturnType&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;trampoline_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;c1&quot;&gt;// get this from IP&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;wrapped_function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ReturnType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_func&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;forward&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)...);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;trampoline_function 的签名，和 C 函数的回调是一模一样的，因此 trampoline_function 可以被 C 直接调用。。。&lt;/p&gt;

&lt;p&gt;吗？&lt;/p&gt;

&lt;p&gt;其实不能的。因为 trampoline_function 编译后，地址是固定的。根据之前 gcc 的实现分析， trampoline 的关键实现，就是有一份 “代码” 被动态的分配到内存里。
这个代码和数据放一起。偏移量是“编译期已知”的。于是，这个动态的代码，他可以利用 CPU 的 IP 寄存器，获取到和代码绑定的数据的地址。&lt;/p&gt;

&lt;p&gt;因此，要再次改进思路，变成这样&lt;/p&gt;

&lt;p&gt;中转的思路是 调用方  -&amp;gt;  汇编写的一段动态代码 –jmp–&amp;gt; trampoline_function -&amp;gt; 包装的仿函数 -&amp;gt; 真正的用户 lambda。&lt;/p&gt;

&lt;p&gt;为啥要用汇编写一个呢？因为 trampoline_function 是个模板，它的大小是不固定的，不可能在运行时去 “复制”到 堆里。C++ 语言也没有一个机制获取一个代码编译后的二进制大小。&lt;/p&gt;

&lt;p&gt;而这份手写的汇编代码，它就是 &lt;strong&gt;确定&lt;/strong&gt; 的。因此这份手写的汇编代码，就可以被到处 复制，并且偏移量已知，可以在运行时 拼 上一份数据，从而将这个代码彻底变成可执行的代码。&lt;/p&gt;

&lt;p&gt;并且，这个汇编代码，它一定是使用  JMP 指令去调用 trampoline_function， 而不是 call 指令。这样，这个 汇编代码，是不需要考虑“参数传递”这个问题的。 调用方传的参数，
交给模板编写的 trampoline_function 去“解码”。&lt;/p&gt;

&lt;p&gt;那么汇编代码是怎么样的呢？ 它长这样:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;
trampoline_function_entry:
	lea rax, [rip]   ; 将 rip 指针赋值给 rax
	mov r10,[rax + 24] ; 固定 24 字节处偏移拿到 this
	mov rax,[rax + 16] ; 固定 16 字节处拿到 trampoline_function 的地址
	jmp rax ; //  跳转到 trampoline_function

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;trampoline_function 里，再使用“内联汇编”。 直接获取 r10 寄存器，就拿到了 包装的仿函数 的 this。&lt;/p&gt;

&lt;p&gt;也就是&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ReturnType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ReturnType&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;trampoline_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;c1&quot;&gt;// get this from IP&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;wrapped_function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ReturnType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;c1&quot;&gt;//内联汇编 从 r10 寄存器获得 _func&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;asm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;mov %%r10, %0&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;=r&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;forward&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)...);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;那么，当用户需要闭包转 裸函数的时候，就是 使用 mmap/VirtualAlloc 之类的内核提供的内存分配接口，分配一段“可执行”的内存。&lt;/p&gt;

&lt;p&gt;然后把 trampoline_function_entry 复制过去，并且在固定的 16 字节和 24 字节处，填
入 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wrapped_function&amp;lt;ReturnType, Args...&amp;gt; *&lt;/code&gt; 的地址和 trampoline_function 的地址。&lt;/p&gt;

&lt;h1 id=&quot;放出完整源码&quot;&gt;放出完整源码&lt;/h1&gt;

&lt;p&gt;这就是核心机制了。实使用的时候，还得考虑 RAII, 还得考虑“方便用户使用”。因此做了些许调整。&lt;/p&gt;

&lt;p&gt;比如 让 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wrapped_function&amp;lt;ReturnType, Args...&amp;gt;&lt;/code&gt; 和 汇编写的 代码，被统一分配到一个内存页上。让这个新分配的代码，反而作为
wrapped_function 的首个成员，于是，this 就变成了 rip , 构建的时候，只要填入 trampoline_function 的地址，不必再
填入 wrapped_function 的地址。&lt;/p&gt;

&lt;p&gt;另外函数名和类名，也有所调整。&lt;/p&gt;

&lt;p&gt;anyway, 放出 github 仓库地址&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/microcai/trampoline&quot;&gt;github/microcai/trampoline&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/trampoline_intro.png&quot; alt=&quot;intro&quot; /&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>重提类型擦除器</title>
   <link href="https://microcai.org/2024/11/01/type-erasuer.html"/>
   <updated>2024-11-01T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/11/01/type-erasuer</id>
   <content type="html">&lt;h1 id=&quot;序&quot;&gt;序&lt;/h1&gt;

&lt;p&gt;十年前，我曾经写过一篇有关类型擦除器的文章, &lt;a href=&quot;/2014/04/21/type-erasure.html&quot;&gt;见这&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;十年后，我打算再探讨下类型擦除器。&lt;/p&gt;

&lt;p&gt;C++ 对 多态 的支持，在核心层面，就两条： 虚继承 和 模板。&lt;/p&gt;

&lt;p&gt;假设你要设计一个 线程池。这个池你可以“投递”各种任务进去。&lt;/p&gt;

&lt;p&gt;什么叫”任务“呢？ 就是一段代码。于是，早期你想到，可以使用 函数指针。&lt;/p&gt;

&lt;p&gt;于是你的 线程池长这样&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;threadpool&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task_function_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)();&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task_function_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;public:&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task_function_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样的代码，有个问题，那就是你“投递” 的任务，他干活得有数据。如果投递的任务，只能是一个函数指针。
那么这个函数，就得使用“全局变量“来传递工作内容了。&lt;/p&gt;

&lt;p&gt;所以说，这个 task ，不能是个函数指针。它得携带对象。&lt;/p&gt;

&lt;p&gt;于是，自然的，你想到了 多态，虚函数。那么很快你就改进了第二个版本&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;task&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;public:&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;threadpool&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;public:&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;显然，如果要给任务携带点状态，就可以写一个 task 对象，比如&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;some_task&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;work_arg1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;work_arg2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;public:&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;some_task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;c1&quot;&gt;// do some work with args.&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;想要带上啥状态，一股脑的给添加到自己写的派生类里就行了。&lt;/p&gt;

&lt;p&gt;显然，相比 函数指针，这个 task 虚基类，让你的任务可以携带状态了。这就抛弃了全局变量通信法。&lt;/p&gt;

&lt;p&gt;但是，有一个小小的缺点，那就是, 为了使用这个线程池的功能，必须得 “派生” 一批对象。而且这些对象海都得从 task 基础。&lt;/p&gt;

&lt;p&gt;这就是所谓的“侵入” 试设计。一个线程池的类型，到处扩散到整个项目里。&lt;/p&gt;

&lt;p&gt;除了类型侵入，这种设计的最大问题就是，多了很多负担。要编写很多 “类”。&lt;/p&gt;

&lt;p&gt;于是，你想到了 lambda 。 如果 能投递一个  lambda, 那么就可以少写很多的类。&lt;/p&gt;

&lt;p&gt;例如接口变成这样&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;threadpool&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;public:&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Lambda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Lambda&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lambda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;好了，这样  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pool.post([data1, data2](){  work on data} );&lt;/code&gt; 写的代码就能使用了。
确实是一个巨大的进步。使用的地方少了很多类型侵入。&lt;/p&gt;

&lt;p&gt;问题是，这 post 是个模板，这个 Lambda 可是随着调用的地方而随时变换类型的。
因此整个任务列队，可没法使用  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::list&amp;lt;Lambda&amp;gt;&lt;/code&gt; 进行存储。&lt;/p&gt;

&lt;p&gt;因此，拿模板实现的多态，他只适合不对传入的数据进行“存储”的算法类使用（比如std::sort。调用的时候，数据就立马处理了。不需要存储为稍后使用的数据。一旦需要存储，模板就不行了。存储需要确定的大小。而一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;typename Lamba&lt;/code&gt; 这个类型，可是随时随地的在变。这怎么放入容器呢？&lt;/p&gt;

&lt;p&gt;从实现的角度而言，放入任务列队的任务，必须具有相同的类型，确定的大小。
而post 函数，是个模板，他拿到的对象，是可以具有任意类型的。&lt;/p&gt;

&lt;p&gt;虽然说它拿到的参数可以是任意类型，但是，也不是真的“任意”类型都可以的。具体的来说，他拿到的东西 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lambda&lt;/code&gt;
，必须具备接下来能对其进行的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lambda()&lt;/code&gt; 操作。也就是，是一种“仿函数”类型，而且不接受参数。&lt;/p&gt;

&lt;p&gt;你看，所谓的任意类型，其实并不任意。他对 Lambda 类型，还是有一个“要求”的，那就是 &lt;strong&gt;可调用&lt;/strong&gt; 和 &lt;strong&gt;无形参&lt;/strong&gt;。至于，这个类型到底是什么类型，却没有具体要求。它可以是一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void (*)()&lt;/code&gt; 函数指针，也可以是一个无形参的 lambda ，也可以是一个带 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void operator () ()&lt;/code&gt; 运算符重载的对象。&lt;/p&gt;

&lt;p&gt;如果我们实现一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;class task&lt;/code&gt; 类型，它不是一个模板类，而是一个确定的类型，那么它就有了固定的大小，就可以放入容器充当任务对象。然后这个对象内部，可以存储满足以上要求的 “任意类型对象”。那么，这个 class task，就叫 “&lt;strong&gt;类型擦除器&lt;/strong&gt;”。&lt;/p&gt;

&lt;p&gt;首先，我们知道，这个 task 对象，需要 “可调用”，对参数形式的要求，还得是 无参数。
因此，首先 task 需要一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void operator()() const&lt;/code&gt; 这样的运算符重载。
而且， 这个 task 要能存储 “任意” 类型的对象，它的构造函数就必须是个模板函数。
于是很快， task 类的 接口声明我们就写出来了。&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;task&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;public:&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;于是搭配我们的线程池，就变成了这样的接口搭配：&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;task&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;public:&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;threadpool&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;public:&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Callable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Callable&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;emplace_back&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;forward&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Callable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如此一来，用户在使用的时候，即不需要使用函数指针时代那样，没地方传参数导致通信全靠全局变量。
也不用像虚函数时代那样，到处都在派生类。&lt;/p&gt;

&lt;p&gt;这样的接口，用起来无疑是舒服，爽快的。&lt;/p&gt;

&lt;h1 id=&quot;实现&quot;&gt;实现&lt;/h1&gt;

&lt;p&gt;那么，如何设计一个 类型擦除器呢？&lt;/p&gt;

&lt;p&gt;task 的构造函数里，它被安排接受“任意”类型，因此，大小是不固定的。
这种大小不固定的东西，打小谭浩强就告诉我们，必须进行“动态分配”。
所以，task 对传入的参数，必须得进行一波 “动态构造”。那么我们这么设计可行？&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;task&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_obj_stor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;public:&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;m_obj_stor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;malloc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;sizeof&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;memcpy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_obj_stor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;sizeof&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;显然，使用了 memcpy 导致 T 对象，必须得是可以用 memcpy 复制的对象。可是一开始可没说它多了个“能被memcpy复制”的这个要求啊？ lambda 对象能被 memcpy 复制吗？函数对象，真的能被 memcpy 复制而没有其他副作用吗？&lt;/p&gt;

&lt;p&gt;显然不对。所以，这地方不能使用memcpy, 而是应该使用 placement new 操作符。&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;m_obj_stor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;malloc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;sizeof&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_obj_stor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;forward&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样，编译器会自动的安排调用 T 类型的 移动构造/拷贝构造 函数。完成将 t 对象移动or复制到m_obj_stor 的工作。好了，构造问题解决。&lt;/p&gt;

&lt;p&gt;接下来的问题是，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void operator()() const;&lt;/code&gt; 该怎么实现呢？
因为 m_obj_stor 是个 void* 指针，它所有的类型信息都被&lt;strong&gt;抹除&lt;/strong&gt;了。&lt;/p&gt;

&lt;p&gt;要知道，只有构造函数能访问 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;typename T&lt;/code&gt; 这个类型，而其他地方都没有了 T 类型信息。&lt;/p&gt;

&lt;p&gt;这意味着，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void operator()() const;&lt;/code&gt; 对着 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void* m_obj_stor&lt;/code&gt; 只能 &lt;em&gt;俩眼一摸黑&lt;/em&gt;。&lt;/p&gt;

&lt;p&gt;那可不行啊！！！！！如果 m_obj_stor 是个虚类就好了，好歹有类型。&lt;/p&gt;

&lt;p&gt;明白！这么干如何？&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;task&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;task_base&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task_base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){}&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;task_base&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_obj_stor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;public:&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;m_obj_stor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;???&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;delete&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_obj_stor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;m_obj_stor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()();&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这么一来，除了 构造，析构和调用，就全都实现了！看起来，我们只要在 构造的地方，自动的构建一个 task_base 的派生类就可以了，因为构造函数，是能访问 T 类型的。&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;task_impl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;task_base&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// ???&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;m_obj_stor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;task_impl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;forward&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)};&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;task_impl 由于使用的是内嵌定义，因此它也是能访问 T 类型的。所以写完整是这样的&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;task&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;task_base&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task_base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){}&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;task_base&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_obj_stor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;public:&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;task_impl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;task_base&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;n&quot;&gt;task_impl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;forward&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;m_obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;m_obj_stor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;task_impl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;forward&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)};&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;delete&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_obj_stor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;m_obj_stor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()();&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;于是，借着“古老”的虚函数，搭配先进的“模板”，一个极为先进和强大的 &lt;strong&gt;类型擦除器&lt;/strong&gt; 就这么诞生了。&lt;/p&gt;

&lt;p&gt;当然，为了能顺利放入容器，这个 task 类型，还得写上 “移动构造”，适应 STL 容器对对象的 “可移动” 需求。&lt;/p&gt;

&lt;h1 id=&quot;轮子&quot;&gt;轮子&lt;/h1&gt;

&lt;p&gt;既然，&lt;strong&gt;可调用对象&lt;/strong&gt; 这种类型进行类型擦除，是个极为普遍迫切的需求，那么 STL 显然不会缺席。
因此， STL 里也提供了一个 std::function 类型，干的就是这种容纳一切可调用对象的需求。&lt;/p&gt;

&lt;p&gt;因为“可调用” 这个需求，本身也会随着“参数多样性”扩展，于是 std::function 本身，又是一个模板。
我们研究了半天的 task, 其实功能上，和  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::function&amp;lt;void()&amp;gt;&lt;/code&gt; 是完全等价的。&lt;/p&gt;

&lt;p&gt;std::function 的模板参数，居然是一种叫 “函数签名” 的东西。&lt;/p&gt;

&lt;p&gt;其实 上古时代，function 的设计是这么使用的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;funtion0&amp;lt;返回类型&amp;gt;&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;function1&amp;lt;返回类型，参数1类型&amp;gt;&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;function2&amp;lt;返回类型，参数1类型，参数2类型&amp;gt;&lt;/code&gt; 。。。 洋洋洒洒，写满几十个  function 类型。&lt;/p&gt;

&lt;p&gt;像 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::function&amp;lt;int (int, double)&amp;gt;&lt;/code&gt; 这样的非常&lt;strong&gt;直观&lt;/strong&gt;的模板参数写法，其实要到 c++11 以后才能实现。所以很多人批评 c++ 进化的越来越复杂，其实那都是不懂的人瞎BB。懂的人都知道，C++ 的一切改进，都是为了让这个语言变得更&lt;strong&gt;易用&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;那么，不自己发明轮子，其实更简化了 线程池的设计，改进后变成这样&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;threadpool&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;thread&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;workers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;mutable&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mutex&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mutex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;public:&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;整个类，没有一个模板，于是，可以正常的 实现 头文件和实现分离。也不会增加编译时间，不会导致二进制膨胀……&lt;/p&gt;

&lt;p&gt;其实看完 task 的写法，就明白了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::function&amp;lt;void()&amp;gt;&lt;/code&gt; 是怎么实现类型擦除的, 但是， std::fuction 是怎么实现模板参数里，是个 “函数签名” 这种东西的呢？？？？？？？有时间我再写啦！&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>类型萃取技术：函数签名当模板参数</title>
   <link href="https://microcai.org/2024/11/01/function-signature-as-template-parameter.html"/>
   <updated>2024-11-01T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/11/01/function-signature-as-template-parameter</id>
   <content type="html">&lt;h1 id=&quot;序&quot;&gt;序&lt;/h1&gt;

&lt;p&gt;在 &lt;a href=&quot;/2024/11/01/type-erasuer.html&quot;&gt;上一篇&lt;/a&gt; 文章里，我提到了 std::function 的模板参数，是一个叫&lt;strong&gt;函数签名&lt;/strong&gt; 的东西。&lt;/p&gt;

&lt;p&gt;什么是函数签名？&lt;/p&gt;

&lt;p&gt;比如 main 函数，他的签名是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int(int, char**)&lt;/code&gt;。所谓函数签名，是指函数声明去掉 函数名和变量名 后得到的一个精简描述。 这个精简描述，指导编译器如何按”调用约定“产生具体的汇编指令以调用一个函数。&lt;/p&gt;

&lt;p&gt;函数名，不过是在最后的 call 指令里，提供了具体的目标地址。&lt;/p&gt;

&lt;p&gt;但是，如果你问 decltype(&amp;amp;main) 的类型是啥，其实他的类型是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int (*)(int, char**)&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;因为 &amp;amp;main 是 main 函数的函数指针。 函数签名是形如 R(args…) 的东西，而函数指针的类型，就是 R (*)(args…)&lt;/p&gt;

&lt;p&gt;函数签名甚至不是一个合法的类型，你不能定义  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int(int,int) p;&lt;/code&gt; 这样的一个变量。
但是你可以用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int (*p)(int, int)&lt;/code&gt; 定一个 函数指针 p, 其指向的函数，具有 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int(int, int)&lt;/code&gt; 这样的签名。&lt;/p&gt;

&lt;p&gt;那么，对于  std::function 来说，他的模板参数要怎么写，才能接受一个签名呢？&lt;/p&gt;

&lt;p&gt;其实很简单，就把签名当成一个类型。也就是&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Signature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;那么有人问了，这 Signature 就一个类型啊，那 function 的 括号操作符 要怎么写？&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Signature operator()(Signature)&lt;/code&gt; ?&lt;/p&gt;

&lt;p&gt;显然不对。&lt;/p&gt;

&lt;p&gt;这括号操作符，必然是要把签名里的 返回值和参数给拆开来。&lt;/p&gt;

&lt;p&gt;把一个类型里的具体组成部分拆出来？&lt;/p&gt;

&lt;p&gt;哈哈！ 这技术，听起来不可思议！&lt;/p&gt;

&lt;p&gt;其实，这就是本期的重点要介绍的屠龙级C++技术： 类型萃取。&lt;/p&gt;

&lt;h1 id=&quot;模板偏特化类型萃取的技术基石&quot;&gt;模板偏特化：类型萃取的技术基石&lt;/h1&gt;

&lt;p&gt;这就不得不提，C++模板的一个偏方用法：模板偏特化。&lt;/p&gt;

&lt;p&gt;比如，你定义了&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Signature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;于此同时，你又定义了一个偏特化&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ReturnType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ReturnType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;那么，当用户使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;function&amp;lt;int(int, int)&amp;gt;&lt;/code&gt; 的时候，
实际上编译器实例化的，正是第二个类。&lt;/p&gt;

&lt;p&gt;也就是，按 ReturnType = int, Args = int, int 实例化&lt;/p&gt;
&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ReturnType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ReturnType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;这个偏特化的模板类。&lt;/p&gt;

&lt;p&gt;正常情况下，第一个模板类声明，将永远不会获得实例化的机会。这也就是为何，这个模板会变成一个没有实现的空声明。&lt;/p&gt;

&lt;p&gt;那么，在第二个 function 的定义里，是不是，ReturnType 就拿到了？&lt;/p&gt;

&lt;p&gt;于是他就可以用&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;ReturnType&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;来定义他的括号操作符了。&lt;/p&gt;

&lt;p&gt;完整来看，这个模板类定义如下&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ReturnType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ReturnType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;ReturnType&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;你看，这样就把 Signature 给拆分了，萃取到了其中的 返回值和参数两部分。
这种技术，就叫类型萃取。是用 模板偏特化 这个超难用的神器 实现的。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>不需要void*user_data的闭包封装</title>
   <link href="https://microcai.org/2024/10/31/c-closure-without-void-userdata.html"/>
   <updated>2024-10-31T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/10/31/c-closure-without-void-userdata</id>
   <content type="html">&lt;p&gt;一般来说，如果 C api 接受一个回调，通常会额外允许设置一个 void* 的回调参数。
用户可以把一些额外的参数用这个 void* 传给回调函数。
比如&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read_function_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;register_read_callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read_function_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;on_read_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样，用户在回调函数里，就可以使用 user_data 这个参数，获取额外的状态信息。&lt;/p&gt;

&lt;p&gt;但是，也有一些古早时代的 C api, 没有办法设置 user_data。 比如：&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read_callback_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;set_read_callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read_callback_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;on_read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;那么回调函数里，就只能拿到库作者设定好的回调信息了。 自己也没有办法传递别的信息给回调处理函数。&lt;/p&gt;

&lt;p&gt;这，必然是库作者的无能，缺陷。是必须要改进的。&lt;/p&gt;

&lt;p&gt;但是，没办法，如果你不得不使用这样的一个无能库，咋办呢？有什么办法传  user_data 进去呢？&lt;/p&gt;

&lt;p&gt;比如，有没有办法实现这样的一个包装器，对上面的 API ，可以做到这样封装&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_cb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lamba_to_c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;捕获其他状态&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;  &lt;span class=&quot;err&quot;&gt;。。。&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;处理代码，可以访问&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;捕获的变量&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;//正常来说， 带捕获的 lambda 是没法 cast 成一个 C 指针的。。。 但是有了这个 魔法，它就可以了。&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;set_read_callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;static_cast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read_callback_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read_cb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;似乎这样的封装对付烂 C API非常给力。只是看起来并不容易实现。&lt;/p&gt;

&lt;p&gt;对 lambda 来说，它本质上是个函数对象。也就是重载了 operator() 的一个匿名类。所以，只要想办法给这个类的 this 找到传递的方式，这个包装器就能实现。
如果 C 库本来就支持 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void* user_data&lt;/code&gt; ，那么这样的一个包装器显然并不难写。问题在于，如果 C 库不支持 user_data 参数呢？&lt;/p&gt;

&lt;h1 id=&quot;思路&quot;&gt;思路&lt;/h1&gt;

&lt;p&gt;其实，还有一个办法，可以传递 user_data, 那就是回调函数本身。
一般来说，回调函数是用户编写的一个函数，这个函数编译后，只会有一份代码。因此设置回调的时候，回调函数的地址是个 constexpr。&lt;/p&gt;

&lt;p&gt;如果把 user_data 和回调函数本身的“代码” 绑定，则可以为每一个 user_data 都 “创建” 出一个单独的回调函数。&lt;/p&gt;

&lt;p&gt;这个“动态创建” 出来的函数，他可以在代码里，直接获取到 user_data , 然后再转而调用真正的回调函数，携带上user_data参数。&lt;/p&gt;

&lt;p&gt;具体做法是：&lt;/p&gt;

&lt;p&gt;编译一个 stub 代码，这个代码和 user_data 的数据一起，作为一个 新的 回调函数创建出来。新的函数长这样&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;new_cb_code:
    get_user_data_relative_to_InstructionPointer # 获取相对当前代码指针偏移了几个字节的 user_data
    call real_cb_with_user_data
    db user_data &amp;lt;-- user_data 存此处
end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这段代码，必须是 “可重定位的”，可以随便复制，任意放置执行。使用的时候，把这个代码作为模板，和 user_data 数据进行拼接。动态的在堆上创建出一个新的函数。&lt;/p&gt;

&lt;p&gt;然后，折断代码被 不支持 user_data的库 调用的时候，使用 “程序计数器寄存器” 进行相对寻址，找到 user_data 数据，然后跳转到真正的，带 user_data 参数要求的回调函数里。&lt;/p&gt;

&lt;p&gt;用汇编来表示，这个代码应该这样&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;dynamic_callback:
    mov rax, [rip+17] ; 此指令 7 个字节，取得 user_data 存入 rax 寄存器
    nop ; 1 个字节
    jmp [rip+2]; // 此指令 6 个字节，直接跳转执行 cb_function_wrapper
    nop ; 1 个字节
    nop ; 1 个字节
    .qword cb_function_wrapper // 8 字节存储带 user_data 的回调
    .qword user_data // 8 字节存储 user_data
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一共32个字节。使用时，每次复制一份新的代码，然后将最后的2个8 字节，填入相应指针。
然后折断代码的起始地址，就可以作为 C 库的 回调函数指针使用了。&lt;/p&gt;

&lt;p&gt;注意的是， cb_function_wrapper 还不是真正的回调函数。因为它需要从 rax 寄存器获取 user_data.
这个 cb_function_wrapper 这么写&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;cb_function_wrapper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;asm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\t&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; mov %%rax,%0&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;=r&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read_buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;delete&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里， user_data 是 std::function,它把真正的用户的 lambda 给包起来了。&lt;/p&gt;

&lt;p&gt;执行完毕，还得删了 user_data;&lt;/p&gt;

&lt;p&gt;ooops,user_data 删了， dynamic_callback 这段动态代码没删。会有内存泄漏的。&lt;/p&gt;

&lt;p&gt;为了简化内存管理，我们把 user_data 所代表的，用户编写的那个 lambda , 和 动态生成的代码，合并到一起存储。&lt;/p&gt;

&lt;p&gt;于是，我们获得了一个这样的类&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;cb_function_wrapper&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_trunk_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;trunk_call_user_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;asm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\t&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; mov %%rax,%0&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;=r&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;reinterpret_cast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cb_function_wrapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read_buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;delete&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;cb_function_wrapper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 把模板代码复制到 _trunk_code&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 并且更新 _trunk_code 的最后2个 指针&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;operator&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_callback_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;reinterpret_cast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read_callback_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_trunk_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;cb_function_wrapper 里面的 _trunk_code 就是上文讲到的32个字节的机器代码。&lt;/p&gt;

&lt;p&gt;这样，cb_function_wrapper 就实现了，把没有 user_data 的 C 库 API, 也能转化为
支持使用 lambda 作为回调了。&lt;/p&gt;

&lt;h1 id=&quot;模板化通用化&quot;&gt;模板化，通用化&lt;/h1&gt;

&lt;p&gt;但是，C 库的回调种类是何其的多！总不能每种回调都写一个 包装器吧。
所以，这时候我们需要祭出模板大法。&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Signature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;c_function_ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Signature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;dynamic_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;dynamic_function&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;R&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;function_ptr&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...);&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;R&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;do_invoke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_rax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;asm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\t&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; mov %%rax,%0&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;=r&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_rax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

		&lt;span class=&quot;n&quot;&gt;dynamic_function&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_this&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;reinterpret_cast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dynamic_function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_rax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;operator&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ExecutableAllocator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;allocate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;operator&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;delete&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ExecutableAllocator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deallocate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LambdaFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;explicit&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dynamic_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LambdaFunction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lambda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ref_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;forward&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LambdaFunction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lambda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;constinit&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;machine_code_template&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;mh&quot;&gt;0x90&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x48&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x8d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x05&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xf8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// nop; lea rax, [rip-8]&lt;/span&gt;
			&lt;span class=&quot;mh&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x25&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x02&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x90&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x90&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// jmp [rip+2]; nop; nop&lt;/span&gt;
			&lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x00&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// c_function_ptr::do_invoke() address&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

		&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wrap_func_ptr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;reinterpret_cast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;do_invoke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

		&lt;span class=&quot;n&quot;&gt;memcpy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_jit_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;machine_code_template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;memcpy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_jit_code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wrap_func_ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;operator&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;function_ptr&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;reinterpret_cast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function_ptr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_jit_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;R&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;make_scoped_exit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]()&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;unref&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unref&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ref_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fetch_sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;delete&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;friend&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;c_function_ptr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;kt&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_jit_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;24&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;atomic_int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ref_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;此时， _jit_code 和 user_function 放到一个类里，一次性的分配出来了。
因此，机器代码也稍作了修改，用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lea rax, [rip-8]&lt;/code&gt; 直接获得 _jit_code 的地址。而 _jit_code
的地址，就是这个类的 this 指针。&lt;/p&gt;

&lt;p&gt;因为 __jit_code 必须放在可执行的内存区域里，因此这个类，必须动态创建。并且使用 ExecutableAllocator 分配内存。
并且整个类都声明为 private 防止用户创建。必须通过 friend class c_function_ptr 使用。&lt;/p&gt;

&lt;p&gt;c_function_ptr 的代码如下&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Signature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;c_function_ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;c_function_ptr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wrapper_class&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dynamic_function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;wrapper_class&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_impl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LambdaFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;explicit&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c_function_ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LambdaFunction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lambda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;_impl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wrapper_class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;forward&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LambdaFunction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lambda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;R&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;function_ptr&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...);&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;operator&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;function_ptr&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static_cast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function_ptr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_impl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;no_auto_destory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;_impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ref_count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;destory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;delete&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_impl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;_impl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;nullptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c_function_ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_impl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;_impl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unref&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;使用的代码如下：（还是使用 set_read_callback 为例）&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_cb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c_function_ptr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read_callback_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;捕获其他状态&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;read_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;  &lt;span class=&quot;err&quot;&gt;。。。&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;处理代码，可以访问&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;捕获的变量&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// NOTE：如果 set_read_callback 是一次设置，永久使用，则调用这个&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 如果是每次设置只回调一次，则不调用&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// read_cb.no_auto_destory();&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;//正常来说， 带捕获的 lambda 是没法 cast 成一个 C 指针的。。。 但是有了这个 魔法，它就可以了。&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;set_read_callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;static_cast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read_callback_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;read_cb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;好了，大功告成。 set_read_callback 这样的 C API ，即便不能设置user_data，也能使用 lambda当回调使用了。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>微软不会起名</title>
   <link href="https://microcai.org/2024/10/30/ms-went-wrong-on-coroutine-naming.html"/>
   <updated>2024-10-30T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/10/30/ms-went-wrong-on-coroutine-naming</id>
   <content type="html">&lt;p&gt;微软不会起名，而且名字还具有极大的误导性。&lt;/p&gt;

&lt;p&gt;先说类名：&lt;/p&gt;

&lt;p&gt;首先，微软的协程原作者，他总是用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Task&amp;lt;&amp;gt;&lt;/code&gt; 来命名一个协程函数。但是这就是他犯的第一个错误。
因为只有 await 构成的一系列调用，那条调用链才是一个 “任务”。 也就是 asio 作者所说的 co_spawn. 只有被 spawn 出来的协程，才叫一个任务。而被 await 的函数，不能叫任务，而是”可异步等待返回的函数“。 因此 asio 作者使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;asio::awaitable&amp;lt;&amp;gt;&lt;/code&gt; 来命名，而不是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;task&amp;lt;&amp;gt;&lt;/code&gt; 是十分妥帖的。&lt;/p&gt;

&lt;p&gt;接着是 promise_type ，这个微软就是纯粹的瞎搞了。不同于 Task&amp;lt;&amp;gt; 只是出现在微软的示例代码里，promise_type 可是直接进了标准。这瞎命名带来的恶劣影响要大的多。为啥说这个不能是 promise_type 呢？ 因为这个对象存储的是 协程状态。因此 asio 使用 coro_frame 来命名，显然要更正确的。它存储的就是当前协程帧的状态数据。&lt;/p&gt;

&lt;p&gt;最后是 final_suspend ，这个类名简直就是严重误导性的集大成者。他干的活和 suspend 毫无关系。实际上他干的恰恰不是 suspend 的活，而是 “恢复调用者” 。协程作为可等待函数，他的调用者调用了一个协程后，调用者才是真正的被 suspend 了。被调用者是执行中。处于 running 状态。当被调用者完成工作，他要 恢复 调用者，让调用者醒来继续干活。&lt;/p&gt;

&lt;p&gt;因此， final_suspend 其实应该叫 resume_caller .&lt;/p&gt;

&lt;p&gt;接着说 三件套函数名：&lt;/p&gt;

&lt;p&gt;await_suspend 其实不是 挂起。协程 A 调用 协程 B, 实际上是 A 被挂起。可是代码上却是 调用 B 的 await_suspend 。这简直就是极大的误导性。当 B 对象的 await_suspend 被调用的时候，实际上恰恰是 B 被 “恢复”， B 进入了 running 状态。这个地方准确的命名，应该叫 await_setup.&lt;/p&gt;

&lt;p&gt;await_resume 实际上不是继续。协程 B 返回给 协程 A 的时候，实际上是 协程B 完工了。恢复的其实是 A. 但是恢复 A ，调用的却是 B 的 await_resume , 这简直就是胡扯一样的名字。 应该叫 await_result ，拿到 await 结果了。&lt;/p&gt;

&lt;p&gt;await_ready 意思其实是反过来的。而且具有极大的误导性。实际上这个 await_ready 的存在价值为 0 。 毫无意义。&lt;/p&gt;

&lt;p&gt;还有存在于 promise_type 里的 三件套函数名：&lt;/p&gt;

&lt;p&gt;initial_suspend/final_suspend 设计简直就是毒瘤。更正确的设计，应该是把这种东西和 awaitable 放一起。promise_type， 或者说 coro_frame , 就单纯的作为一个 “数据存储” 对象。不要携带干活的代码。虽然 c++ 的对象是数据+代码。但是这个 数据上捆绑的代码，更多的是为了实现 RAII ，而不是真的乱来，随便给代码找个对象安上。&lt;/p&gt;

&lt;p&gt;initial_suspend 的活，应该提到 await_setup 里合并工作。 final_suspend 改到 awaitable::await_done 里完成恢复。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>西方饮食的超高热量</title>
   <link href="https://microcai.org/2024/10/27/us-food-has-too-much-cal.html"/>
   <updated>2024-10-27T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/10/27/us-food-has-too-much-cal</id>
   <content type="html">&lt;p&gt;西洋人做食物，最喜欢放的一种调料，叫“黄油”。没有黄油洋人就活不下去。&lt;/p&gt;

&lt;p&gt;洋人对黄油的喜爱到了何种地步？ 制作面包的时候， 是 面粉+白糖+黄油 1：1：1 混合。烤熟后的面包，吃之前要抹黄油。&lt;/p&gt;

&lt;p&gt;偏偏黄油（butter）在英语里，不带油字。做出来的面包，也没有油腻腻的视觉观感。&lt;/p&gt;

&lt;p&gt;于是乎，洋人就会说，中国人吃的炒菜放超级多的油，不健康。他们吃的面包就很健康。&lt;/p&gt;

&lt;p&gt;其实西洋面包，里面的油，比肉包子还多。&lt;/p&gt;

&lt;p&gt;而中国人做馒头，原料居然只是面粉和水，即不加糖，也不加黄油。&lt;/p&gt;

&lt;p&gt;西洋人的食物，有一种魔法，就是把油变成看不见的东西，然后完全吃进肚子里。绝不会遗留在餐盘里让不良商人拿去提炼地沟油。&lt;/p&gt;

&lt;p&gt;又拿吃鸡蛋来说。中国人吃荷包蛋，需要把蛋放油上面煎。煎完的荷包蛋，表面一层油。
洋人吃蛋，会把蛋+油+糖混合，然后打发。变成了一种叫“蛋黄酱”的东西。而且蛋黄酱里超过6层是油。&lt;/p&gt;

&lt;p&gt;不过在英语里，蛋黄酱里即没有蛋黄，也没有酱。所以总是去超市买成品的人，可能还不知道蛋黄酱=鸡蛋+糖+油。它们只知道蛋黄酱很好吃。吃啥都要抹上蛋黄酱。&lt;/p&gt;

&lt;p&gt;西洋人总是说，要多吃菜。于是它们成功的，人均吃菜量相当于中国人的 1/3。
为啥它们菜吃那么少呢？ 原来是为了追求营养价值，它们生吃蔬菜。还污蔑中国人炒菜不健康。&lt;/p&gt;

&lt;p&gt;但是生吃岂是那么好吃的。&lt;/p&gt;

&lt;p&gt;于是它们就要在生吃蔬菜的时候，加配料。也就是沙拉酱。不然完全吃不下。&lt;/p&gt;

&lt;p&gt;那么沙拉酱是啥呢？&lt;/p&gt;

&lt;p&gt;沙拉酱其实就是 糖+油 然后用高速旋转的搅拌机给打发了。&lt;/p&gt;

&lt;p&gt;刚刚还说中国人吃的炒菜表面浮着油，转头自己把油加糖当调料混着生菜给吃了。而且餐盘里还没一滴油剩下。地沟油提炼商都要骂一句抠唆。&lt;/p&gt;

&lt;p&gt;但是有一说一，糖+油打发后，看起来确实不像油。而且名字里也没带油（不管是洋文还是汉文里，都没油）。 于是 糖+油 抹 在菜叶上，这样的食物。。。 看起来就是健康食品啦。&lt;/p&gt;

&lt;p&gt;这就是为啥很多人，只吃西方人推崇的“健康食品”，最后还是吃成了个大胖子。
因为西方人的食品，他就没有健康的。里面全都是油和糖。而且还特意隐藏起来，让你看不到。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>从模板的模板论程序员的美学</title>
   <link href="https://microcai.org/2024/10/19/template-template.html"/>
   <updated>2024-10-19T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/10/19/template-template</id>
   <content type="html">&lt;p&gt;在编写 &lt;a href=&quot;https://github.com/avplayer/ucoro&quot;&gt;µcoro&lt;/a&gt; 的时候，曾经对一个模板类的成员函数犯难了。那就是等待器三件套之一的 await_suspend 函数。&lt;/p&gt;

&lt;p&gt;我为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;template&amp;lt;typename T&amp;gt; class awaitable&lt;/code&gt; 的三件套使用这样一个模板签名&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PromiseType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;await_suspend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coroutine_handle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PromiseType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其中，这个 PromiseType 必须得是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;awaitable&amp;lt;T&amp;gt;::promise_type&lt;/code&gt; (也就是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;awaitable_promise&amp;lt;T&amp;gt;&lt;/code&gt; ) 的其中一个实例。&lt;/p&gt;

&lt;p&gt;按前C++ 时代的做法，这个成员函数应该这么写&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;await_suspend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coroutine_handle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awaitable_promise&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这种写法，可以保证接收到的 h 参数，其 Promise 类型必然是 awaitable_promise 的一个参数化实例。&lt;/p&gt;

&lt;p&gt;但是，与此同时，我还希望要为 std::coroutine_handle&lt;void&gt; 这样的通用参数写一个单独的处理代码。&lt;/void&gt;&lt;/p&gt;

&lt;p&gt;于是，使用按前C++ 时代的做法，我需要写2个 await_suspend， 他们分别是&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;await_suspend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coroutine_handle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awaitable_promise&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;await_suspend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coroutine_handle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;对这两个类型的 promise handle, 各自有一套不同的处理代码。&lt;/p&gt;

&lt;p&gt;但是，老王提出了不同意见。他认为，std::coroutine_handle 在标准库里，就一种表示方式，那就是&lt;/p&gt;
&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PromiseType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coroutine_handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;他批评我说，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::coroutine_handle&amp;lt;awaitable_promise&amp;lt;Any&amp;gt;&amp;gt;&lt;/code&gt; 这样的写法不直观。而且还写了两个 await_suspend
到底哪个在干活，得多费脑细胞。&lt;/p&gt;

&lt;p&gt;后来，函数签名恢复成了&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PromiseType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;await_suspend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coroutine_handle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PromiseType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但是，使用了 enable_if 之类的模板技巧，外加在 awaitable_promise 里添加一些辅助佐料，实现了&lt;/p&gt;
&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;await_suspend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coroutine_handle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awaitable_promise&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Any&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;相同的作用。主要目的，就是为了让 coroutine_handle 的模板参数是 PromiseType。这样一眼就看明白的东西。&lt;/p&gt;

&lt;p&gt;但是，明明 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::coroutine_handle&amp;lt;awaitable_promise&amp;lt;Any&amp;gt;&amp;gt;&lt;/code&gt; 就能实现的东西，为啥还要引入 enable_if 就为了继续
使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::coroutine_handle&amp;lt;PromiseType&amp;gt;&lt;/code&gt; 呢？相比嵌套的 «» 我觉得 enable_if 更复杂。&lt;/p&gt;

&lt;p&gt;我要寻找更通用更一眼看明白的代码！&lt;/p&gt;

&lt;p&gt;于是，我思考，能不能写出这样一类模板元编程&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PromiseType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;await_suspend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coroutine_handle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PromiseType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;constexpr&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is_instance_of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PromiseType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;awaitable_promise&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// handle h as std::coroutine_handle&amp;lt;awaitable_promise&amp;lt;Any&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// handle h as std::coroutine_handle&amp;lt;void&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里，is_instance_of 判断了 PromiseType 这个类型，是不是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;awaitable_promise&amp;lt;T&amp;gt;&lt;/code&gt; 这个模板的一个参数化实例。&lt;/p&gt;

&lt;p&gt;这样，这个代码极其的简化了，而且要做的事情一目了然。await_suspend 也只有一个，没有多个重载。&lt;/p&gt;

&lt;p&gt;那么问题的关键在于，这里，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;is_instance_of&amp;lt;U,T&amp;gt;&lt;/code&gt; 这样的类型判断真的能写出来吗？&lt;/p&gt;

&lt;p&gt;由于 awaitable_promise 具有特定的成员变量，所以只要检查是否有那个成员变量，也能判断吧？&lt;/p&gt;

&lt;p&gt;于是，最初我的 is_instance_of 是更具体的 is_instance_of_awaitable_promise, 具体实现如下&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;typenme&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;concept&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is_instance_of_awaitable_promise&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;requires&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;local_&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;same_as&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shared_ptr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;continuation_&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;same_as&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coroutine_handle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;使用的时候，是&lt;/p&gt;
&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PromiseType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;await_suspend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coroutine_handle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PromiseType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;constexpr&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;is_instance_of_awaitable_promise&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PromiseType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// handle h as std::coroutine_handle&amp;lt;awaitable_promise&amp;lt;Any&amp;gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// handle h as std::coroutine_handle&amp;lt;void&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但是，我对 is_instance_of_awaitable_promise 始终是不满意的。
因为我更希望的是，直接判断 PromiseType 是不是 awaitable_promise 的一种。&lt;/p&gt;

&lt;p&gt;而不是它恰好有两个成员变量对的上号。&lt;/p&gt;

&lt;p&gt;哪怕有两个成员变量对的上号，它就一定是 awaitable_promise 吗？&lt;/p&gt;

&lt;p&gt;如果我修改了 awaitable_promise 的成员变量，是不是还得记得修改 is_instance_of_awaitable_promise的判断？
这还增加了心智负担。&lt;/p&gt;

&lt;p&gt;于是我心心念念要改进这个地方的代码。&lt;/p&gt;

&lt;p&gt;终于有一天，在一个人的 blog 上找到了 &lt;a href=&quot;https://indii.org/blog/is-type-instantiation-of-template/&quot;&gt;解决方法&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;于是抄录之。于是我心心念念的代码总算完成了。&lt;/p&gt;

&lt;p&gt;从我以上的心路历程可以看出来，c++ 程序员都是有强迫症的。他们不仅仅要代码实现特定功能，还得要让代码“看起来”赏心悦目。
没错，虽然 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;is_instance_of&lt;/code&gt; 的具体实现是非常的不赏心悦目的，但是
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if constexpr ( is_instance_of&amp;lt;PromiseType, awaitable_promise&amp;gt; )&lt;/code&gt;
是赏心悦目的。具体原因是因为 is_instance_of 是属于模板元编程实现的基础组件的范畴，只要名字给力，用法给力。实现是可以看不懂的。
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if constexpr ( is_instance_of&amp;lt;PromiseType, awaitable_promise&amp;gt; )&lt;/code&gt; 是属于业务逻辑，必须要赏心悦目。&lt;/p&gt;

&lt;p&gt;虽然代码赏心悦目了，但是 is_instance_of 具体是咋个原理，我看完他的blog也没研究明白。&lt;/p&gt;

&lt;p&gt;诶。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>老王语录</title>
   <link href="https://microcai.org/2024/10/07/sainta-quotes.html"/>
   <updated>2024-10-07T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/10/07/sainta-quotes</id>
   <content type="html">&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;苹果手机目前在国内主要就是果粉和小姐在用。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;国人大部分开源的项目压根就没有什么洋人感兴趣，不过就是一厢情愿的跪舔洋人而已。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;这些觉的自己母语低一等的人啊，往往又经常在简中社区求关注。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;8k没意义？那些保守党早在1080p时就说过同样的话，不需要4k，如果不是科技推着他们走，他们什么都不需要，电脑也是多余的，他们会说，有人脑干嘛要有电脑？&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;学做人不好吗？偏偏学狼。狼这种畜生都快灭的差不多了吧，有几个人见过真狼的，要学畜生也不能学一个快灭绝了的物种啊。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;用不用新标准，看对自己的定位。如果你是一名合格的c++程序员，你就应该使用。
如果你只是一名c with class的伪c++程序员，你会找一万个理由不使用新标准。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;rust是编程界宗教中的最可怕的一支，到处传教，真是恶心死人了，编程语言千千万万，从来没有让我这么讨厌的一门语言，而且并不是因为rust语言本身有多讨厌，而是这些传教的，让人恶心！！！&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;酒桌文化其实就是一种奴才文化，像极了奴才讨好主子，每次在酒桌上看到一个个给领导劲酒那个奴才相，恶心极了&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;所以要判断一个人是哪方面的大佬，就看他活跃在哪里，哪里写的东西多，这样判断就好了，如果活跃在github或各种邮件列表的各种项目，自然就是程序大佬，如果主要活跃在社交平台，自然就不是程序大佬而是网红了。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;明明就是技术差，非还要找个理由说老版本稳定，老版本稳定的理由简直秀逗了而不自知。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;异步 io 库的 api 接口设计，不应该设计为 setInterval，而应是 setTimeout，但凡只设计为 setInterval 这种方式的 io 接口，都是十分糟糕的设计。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;设置回调其实不过是简单的赋值，这个没有什么开销，简而言之，如果要担心函数指针赋值的开销了，我宁愿相信是程序设计出了问题。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;10月12日添加&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;现在的老师啊，是洗脑主力军&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;普通人应该把学外语的精力多用在学习科学技术上面&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;中文里的一个语气助词就能解决的问题, 英文中要把单词顺序颠来倒去…  傻B极了。很多人学了几个英文, 从来不思考, 他们语言这些规则是不是糟糕的, 还沾沾自喜, 这种人更傻B。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;11月27日添加&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;买tsl的基本都是傻b, 你和tsl车主交流,真的很容易感受到他们的愚蠢。所以我们千万不要指望能改变他们, 愚蠢是改不了的。&lt;/li&gt;
  &lt;li&gt;界面显示数字按3位一分组，说明设计人员要么没脑子或者懒, 要么就是崇洋媚外, 甚至还不自知, 我觉得后面这种可能性更大。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;暂时就收集这么多，以后再加。&lt;/p&gt;

&lt;p&gt;12 月 5 日添加&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;国内关于网络编程领域，就连陈硕的muduo的send设计有问题都没人能指出来，疯狂膜拜reactor，就这种平均水平之下，冒出来一些装模作样支持p2300的sender/receiver模型的人，真的是很奇葩。&lt;/li&gt;
  &lt;li&gt;一些人 asio 都用不利索，又TM的会 p2300 的 sender/receiver 了，张口结构化并发，真的是张口就来。&lt;/li&gt;
  &lt;li&gt;学了大硕思想，proactor都给你折腾回reactor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2025 年 3月15日添加&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;挺乌的目的，其实都是为了反华，而不是什么人道主义。&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>µcoro 介绍</title>
   <link href="https://microcai.org/2024/10/04/deep-into-cpp20-coroutine.html"/>
   <updated>2024-10-04T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/10/04/deep-into-cpp20-coroutine</id>
   <content type="html">&lt;h1 id=&quot;什么是-µcoro-ucoro-&quot;&gt;什么是 µcoro (ucoro) &lt;a href=&quot;https://github.com/avplayer/ucoro/actions&quot;&gt;&lt;img src=&quot;https://github.com/avplayer/ucoro/actions/workflows/ci.yml/badge.svg&quot; alt=&quot;actions workflow&quot; /&gt;&lt;/a&gt;&lt;/h1&gt;

&lt;p&gt;µcoro 是一个最小化的c++20协程库。精简到不能再删一行代码。&lt;/p&gt;

&lt;h2 id=&quot;什么是-c20协程&quot;&gt;什么是 c++20协程&lt;/h2&gt;

&lt;p&gt;要理解 c++20 协程，首先要理解 无栈协程。 要理解无栈协程，首先要理解“调用链”。&lt;/p&gt;

&lt;h3 id=&quot;调用链&quot;&gt;调用链&lt;/h3&gt;

&lt;p&gt;函数，是被“调用”的。函数 A 调用 函数 B, 函数 B 再调用 函数 C。意思就是当 C 执行完工作，它返回就会回到 B 函数里调用C的那个地方。然后继续执行。
B 函数返回的时候， 它会返回 A 函数里调用B的那个地方。&lt;/p&gt;

&lt;p&gt;这种链试的返回控制流，就是调用链。&lt;/p&gt;

&lt;p&gt;在线程里，调用链是存储在栈上的。在函数返回的地方，编译器生成 ret 指令。而 ret 指令的执行步骤，就是从栈指针获取返回的目标地址。然后将栈指针退行并跳转到目标地址。
现代的调试器，都能在暂停代码执行的时候，检查栈内存，从而获取调用链。&lt;/p&gt;

&lt;p&gt;而有栈协程，就是指多个 “栈” 共享一个内核调度单元——线程。多个协程之间进行切换，实质上就是直接切换了 栈。而切换栈，是一个主动操作，而不是像线程那样由内核抢占式调度。
所以协程又被成为协作式多任务。&lt;/p&gt;

&lt;p&gt;在无栈协程里， 调用链并不存储于栈上。当 协程函数C 完成任务要返回 B, 控制流程会在另外的地方找到它的调用者B,然后跳转到B继续执行。
如果使用调试器，那么调试器按传统的方式找调用栈，在函数C里下断点，也看不到B和A的调用帧。 函数C，函数B，函数A在调试器里看，永远都是由一种复杂的“协程调度器”代码调用的。
如果非要调试 C 返回B 的过程，会发现 C 的代码会先返回到内部的某种“协程调度器“然后紧接着进入 函数B. 而且 B 函数明明是被框架调用的，但是并不会从头执行，而是在上次挂起的地方继续。所谓上次刮起的地方，实际上这个地方就是安排了对C的调用。&lt;/p&gt;

&lt;p&gt;最简单的实现一个 无栈协程 的方式，就是写闭包。在闭包里存储上次挂起的位置。下次执行函数的时候，就从挂起位置继续。可以使用状态机很容易就实现。&lt;/p&gt;

&lt;p&gt;只不过，用手写状态机实现的闭包模拟的无栈协程，任务代码本身就会被为了实现状态机而添加的代码打乱。因此微软为编译器添加了自动化实现可重入状态机的功能，这个机制，就是 c++20协程啦。&lt;/p&gt;

&lt;p&gt;so, c++20协程，是无栈协程的一种。&lt;/p&gt;

&lt;h2 id=&quot;挂起和恢复&quot;&gt;挂起和恢复&lt;/h2&gt;

&lt;p&gt;在无栈协程里，协程函数之间的调用，是“间接”进行的。 我们思考如下的一个片段&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;n&quot;&gt;ucoro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; value&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;co_return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ucoro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;return: &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;co_return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ucoro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果在 函数C里下个断电进行调试，则在调试器里看到的调用栈，绝对不会是 A -&amp;gt; B -&amp;gt; C，而是 某个内部代码 -&amp;gt; C。
进一步，进行单步调试的时候会发现， C 函数里执行 co_return , 并不会直接返回B， 而是会回到某个内部代码，然后又重新进入B。栈上的调用链条变成某个内部代码 -&amp;gt; B。&lt;/p&gt;

&lt;p&gt;也就是说，在无栈协程里，如果从传统的栈上调用链看， 所有的协程函数都是“平级”的。都是被某种魔法代码平级调用。
真正的调用链，则隐藏在这魔法代码里。&lt;/p&gt;

&lt;p&gt;为了支持这种操作模式，编译器需要对协程函数进行某种转换。也就是将 co_await C 的调用，替换成
某种类似下面的代码&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// 初始化 C 协程函数
C_setup();
// 配置接下来跳转到 C
set_next(&amp;amp;C_body);
// 返回到神秘代码。
return


// 接着在神秘代码里调用

C_body();

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;而在 C协程函数的 co_return 里，要进行这种&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;set_next(B);
return;

// 接着在神秘代码里调用
B_body();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;而 B_body() 初次调用是被 A, 接着 C ”返回“ 的时候，再次调用 B_body. 这个 B_body 就是所谓的”可重入函数“。经过编译器改造后的 B_body, 重入的时候，并不从头执行，而是一波跳转直接从上次 return 的地方继续，也就是 代码如下&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func B_body()
{
    switch(stage)
    {
        case 0:
        // 初始化 C 协程函数
        C_setup();
        // 配置接下来跳转到 C
        set_next(&amp;amp;C_body);
        stage = 1;
        // 返回到神秘代码。
        return ;
        case 1:
        // 从 C 返回了。
	    std::cout &amp;lt;&amp;lt; &quot;return: &quot; &amp;lt;&amp;lt; ret &amp;lt;&amp;lt; std::endl;
    }
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;B_body 的首次 return ， 并不是真的函数返回里，而是“挂起”， 等待 C 的结果。
当 C 任务完成，B_body 的再度执行，会记住上次的状态，从“挂起”的地方继续执行。
这种操作，就是所谓的”恢复“。&lt;/p&gt;

&lt;p&gt;这就是 无栈协程里的 挂起/恢复 两个操作。&lt;/p&gt;

&lt;p&gt;挂起，是为了等待另一个协程的”返回“。
恢复，是被调用的协程干完活了，就通过恢复把控制权叫回来。然后进行后续处理。&lt;/p&gt;

&lt;h2 id=&quot;awitable-对象&quot;&gt;awitable 对象&lt;/h2&gt;

&lt;p&gt;我们注意到，在 协程函数定义返回类型的时候，其类型是 awaitable&lt;传统的返回值类型&gt;。&lt;/传统的返回值类型&gt;&lt;/p&gt;

&lt;p&gt;这是为何呢？&lt;/p&gt;

&lt;h3 id=&quot;携带状态的函数&quot;&gt;携带状态的函数&lt;/h3&gt;

&lt;p&gt;注意到协程在挂起和恢复的时候，需要额外保留状态。包括函数内部定义的本地变量，也要保留状态。重入后，这些变量的数据可是要原样保留的。&lt;/p&gt;

&lt;p&gt;这意味着，一个协程函数，必须得是一个&lt;strong&gt;闭包&lt;/strong&gt;。它需要一些额外的空间存储自己的“状态”。&lt;/p&gt;

&lt;p&gt;同时，协程函数，由必须要能在传统函数里被调用，以便把整个魔法循环开动起来。&lt;/p&gt;

&lt;p&gt;这就要求，协程函数，必须同时仍然是一个传统的函数，而不是特立独行，完全创造新的函数调用体系。&lt;/p&gt;

&lt;p&gt;为达成这个目的，一个 协程函数，它的返回值，从传统函数的视角来看，就得是一个“闭包”。
而协程的魔法，就在这个闭包里完成。&lt;/p&gt;

&lt;p&gt;因此，我们看上述例子的一个全貌&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;n&quot;&gt;ucoro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; value&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;co_return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ucoro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;return: &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;co_return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ucoro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resume&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;main 是一个传统函数， 它调用了 A() 以后，在它的视角，它获得了一个 awaitable&lt;void&gt; 对象。
此时 A 函数其实并没有真正运行, 也就是 A 函数处于“挂起”状态。
接着 main 在 A 返回的 awaitable 对象上调用 resume(), A 函数这才在 “挂起” 状态恢复，进入“恢复” 状态。&lt;/void&gt;&lt;/p&gt;

&lt;p&gt;接下来 A B C 之间的魔法流转，就都在协程内部的代码里消化吸收了。&lt;/p&gt;

&lt;p&gt;在 main 的视角， A 函数彻底执行完毕， 它的 “resume” 才会彻底返回。这就是所谓的非“detached”协程。
也就是“阻塞”协程。而我们一般使用协程，是为了处理“大并发”。是不能阻塞传统函数的。&lt;/p&gt;

&lt;p&gt;而不会阻塞传统函数的协程，被称作 detached 协程。main 调用完 A的 resume, 就会立即返回，此时 ABC 的活，其实并没有立即执行。需要通过一个叫 “executor” 的执行器去“调度”。在执行器的调度下，完成 ABC的工作。&lt;/p&gt;

&lt;p&gt;在调度器里执行的协程，就是过去程序员讲的“纤程”。(win 下的 Fiber 或者 unix 下的 ucontext)。
而未在调度器里执行的协程，就是过去程序员讲的“Generator”。&lt;/p&gt;

&lt;h2 id=&quot;awaitable-里面的魔法&quot;&gt;awaitable 里面的魔法&lt;/h2&gt;

&lt;h3 id=&quot;awaitable-的构造魔法&quot;&gt;awaitable 的构造魔法&lt;/h3&gt;

&lt;p&gt;虽然 例子上的函数 A、B、C 其返回类是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;awaitable&amp;lt;&amp;gt;&lt;/code&gt; 但是，函数内部并没有构造这个对象。
也就是说，编译器看到函数内部使用了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;co_return&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;co_await&lt;/code&gt; 关键字，就自动的构造 awaitable&amp;lt;&amp;gt; 对象。
但是， &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;awaitable&amp;lt;&amp;gt;&lt;/code&gt; 实际上并不是标准库类型，而是用户自定义类。因此，c++必须定义某种协议，帮助编译器将协程和用户自定义类给联系起来。&lt;/p&gt;

&lt;p&gt;这个协议就是, 对  T func_A() 这样的函数来说，如果 func_A 内部出现了 co_await/co_return关键字，就会寻找 T 类型的 T::promise_type::get_return_object 函数。&lt;/p&gt;

&lt;p&gt;这种寻找用户自定义类型里的特定函数以实现编译器功能的桥接的协议，自c++11始就大行其道了。&lt;/p&gt;

&lt;p&gt;首先要明确一点，awaitable 对象，是由程序员安排生命周期的。例如例子里，main 里调用 A().resume()， 就是构造了一个临时对象。
而 awaitalbe::promise_type 对象，则是由编译器安排在堆上分配的。promise_type 是跟着协程的生命期走的。&lt;/p&gt;

&lt;p&gt;当协程调用发生的时候， 编译器调用 awaitable::promise_type::operator new() 操作符分配一个新的 promise_type 对象，并调用他的 get_return_object 构造一个 awaitable 对象然后返回。 因此 awaitable 对象也是要求不可复制，但是可移动。确保 awaitable 对象的唯一性。&lt;/p&gt;

&lt;h3 id=&quot;co_await-和-co_return-的魔法&quot;&gt;co_await 和 co_return 的魔法&lt;/h3&gt;

&lt;p&gt;讲完构造，接下来讲 co_await 和 co_return 分别发生了什么。&lt;/p&gt;

&lt;p&gt;在 A 函数里， co_await B(); 指令发生的时候，编译器实际上生成的代码，是调用了 B() 创建了一个临时对象。然后调用这个临时对象的 await_suspend, 传入 A 的引用，以便 B 建立“返回地址为A” 的链。接着调用 B临时对象的 resume , 将控制权交给 B ，从而执行 B 的函数体。&lt;/p&gt;

&lt;p&gt;在 B 函数的 co_return 指令发生的时候， 编译器实际上生成的代码，是调用 B 对象的 promise_type 里面的 final_suspend . 在 final_suspend 里， B 找到了自己的“返回地址”（其实这里应该叫 调用者，不是程序地址”），然后调用 调用者的 await_resume. 这样控制权就回到了 A 函数。由于前文说过，协程函数，就是一种可重入函数。因此 await_resume 会“自动”的跳入上一次 suspend 的地方。于是这个地方，就恰如其事的 就是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;co_await B();&lt;/code&gt; 这个地方。&lt;/p&gt;

&lt;p&gt;一句话总结：协程的 co_return 就是调用父级的 resume。协程的  co_await 就是调用 父级的 suspend + 子级的 resume。&lt;/p&gt;

&lt;p&gt;那么，思考这么一个代码&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;n&quot;&gt;ucoro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;debugstop2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;co_return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;ucoro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;debugstop1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;debugstop3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resume&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;在 debugstop1 这个地方，调用栈看起来是  main -&amp;gt; foo.resume -&amp;gt; foo.corobody&lt;/li&gt;
  &lt;li&gt;在 debugstop2 这个地方，调用栈看起来是  main -&amp;gt; foo.resume -&amp;gt; foo.corobody -&amp;gt; bar.resume -&amp;gt; bar.corobody&lt;/li&gt;
  &lt;li&gt;在 debugstop3 这个地方，调用栈看起来是  main -&amp;gt; foo.resume -&amp;gt; foo.corobody -&amp;gt; bar.resume -&amp;gt; bar.corobody -&amp;gt; foo.resume -&amp;gt; foo.corobody&lt;/li&gt;
  &lt;li&gt;在 debugstop3 完毕后，会层层 ret 最终回到 main.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这看起来，在协程里，调用栈是单向增长的。直到最终执行完毕，然后突然伴随着海量的 ret 返回到传统函数的调用处。&lt;/p&gt;

&lt;p&gt;微软在提交 coro 提案多年后，才突然意识到这个爆栈问题，因此进行了一次补丁更新。解决之道就是强迫编译器为 协程相关代码打开 &lt;strong&gt;尾调用优化&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;在开启 &lt;strong&gt;尾调用优化&lt;/strong&gt; 后，&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;在 debugstop1 这个地方，调用栈看起来是  main -&amp;gt; foo.corobody&lt;/li&gt;
  &lt;li&gt;在 debugstop2 这个地方，调用栈看起来是  main -&amp;gt; bar.corobody&lt;/li&gt;
  &lt;li&gt;在 debugstop3 这个地方，调用栈看起来是  main -&amp;gt; foo.corobody&lt;/li&gt;
  &lt;li&gt;在 debugstop3 完毕后，直接到 main.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;为了能让编译器 100% 确保 尾调用优化 能实施，微软又双叒叕修改了 协程里 awaiter 对象的 await_suspend 函数定义。确保新定义下，不管你内部代码怎么写，编译器总能使用尾调用优化。&lt;/p&gt;

&lt;h3 id=&quot;awaiter-和-promise-角色关系&quot;&gt;awaiter 和 promise 角色关系&lt;/h3&gt;

&lt;p&gt;能被放到 co_await 关键字后面的对象，叫 awaiter。如本库的 ucoro::awaitable&amp;lt;&amp;gt; 类型。 awaiter 必须要有 await_suspend/await_resume/await_ready 成员。&lt;/p&gt;

&lt;p&gt;一个能运转起来的 coro 库，必须要至少包括3个类： general awaiter / promise / final awaiter。
其中， general awaiter 就是用户可以写在 函数签名上的那个返回类型。它必须要有一个内嵌的 promsie_type 类声明。然后这个 promise 必须要有一个负责收尾的 final awaiter。&lt;/p&gt;

&lt;p&gt;由于一个协程是一个闭包，它需要有一个上下文环境来存储中间状态。这个上下文环境就是 promsie。&lt;/p&gt;

&lt;p&gt;对于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ucoro::awaitable&amp;lt;int&amp;gt; B()&lt;/code&gt; 这样的函数，其上下文环境就存储在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ucoro::awaitalbe&amp;lt;int&amp;gt;::promise_type&lt;/code&gt; 里。&lt;/p&gt;

&lt;p&gt;如果在 A() 函数代码里使用 co_await B(); 这样的表达式，意味着编译器会调用 ucoro::awaitalbe&lt;int&gt; 这个 awaiter。&lt;/int&gt;&lt;/p&gt;

&lt;p&gt;事实上 A() 函数里调用 auto b_ret_value = co_await B(); 转换后的代码如下&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;temp_b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;handle_of_A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coro_state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coro_state_after_B&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;handle_of_A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;await_transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;temp_b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;await_suspend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;corohandle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resume&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;coro_state_after_B&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b_ret_value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;temp_b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;await_resume&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果 A() 的 promise_type 里没有 await_transform , 则使用编译器默认的 await_transform, aka 传啥返回啥。&lt;/p&gt;

&lt;p&gt;也就是这样&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;temp_b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;await_suspend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;corohandle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resume&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;注意代码中的 coro_state_after_B：标签，这个是用来当 B 里面 co_return 的时候， B 会在 final_awaiter 里调用 A的 resume()。 因为协程是实现为“可重入函数”，所以第二次调用 resume 就会 goto 到这个 lable。&lt;/p&gt;

&lt;p&gt;await_transform 一般可以用来实现 co_await 一个非协程对象。比如实现 类似&lt;strong&gt;线程本地存储&lt;/strong&gt;的&lt;strong&gt;协程本地存储&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;只要 co_await ucoro::local_storage; 这样的一个写法，就可以跳入&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; handle_of_this_coro.promise().await_transform(ucoro::local_storage)。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;为何不直接将 ucoro::local_storage 实现为一个 awiter 呢？&lt;/p&gt;

&lt;p&gt;因为如果 将 ucoro::local_storage 实现为一个 awiter，则 local_storage 拿不到调用处的 promise() 对象。
而使用 await_transform , 则可以在 awaitable&amp;lt;&amp;gt;::promise_type::await_transform 里直接拿到 awaitable&amp;lt;&amp;gt; 的 promise 对象，从而获取到和该协程绑定的数据。因为 promise 对象是用户自定义的。用户可以自己往里面赛东西。&lt;/p&gt;

&lt;p&gt;除此之外，还可以在 await_transform 里实现对其他人编写的协程库的兼容。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>猪油有利于减肥</title>
   <link href="https://microcai.org/2024/09/30/lard-is-good-for-health.html"/>
   <updated>2024-09-30T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/09/30/lard-is-good-for-health</id>
   <content type="html">&lt;h1 id=&quot;为啥会肥胖&quot;&gt;为啥会肥胖&lt;/h1&gt;

&lt;p&gt;要减肥，首先要理解，人为什么会变肥。&lt;/p&gt;

&lt;p&gt;人要活着就要消耗能量。而人体的能量来源，主要靠葡萄糖。&lt;/p&gt;

&lt;p&gt;人体有两大储能系统： 血液和脂肪。&lt;/p&gt;

&lt;p&gt;血液里含有葡萄糖，这是直接储蓄，类比为“活期存款”。血糖会随着代谢的进行被源源不断的消耗，同时也会随着消化的进行，源源不断的被补充进来。&lt;/p&gt;

&lt;p&gt;如果血糖的浓度偏离了一个正常值，就会激发人体的代偿机制： 将多余的葡萄糖转换为脂肪或者反过来，将多余的脂肪转换为葡萄糖。&lt;/p&gt;

&lt;p&gt;而人发胖，机制就是因为经常性的触发“葡萄糖转换为脂肪”的储能机制。&lt;/p&gt;

&lt;h1 id=&quot;为啥总是储能而不释放&quot;&gt;为啥总是储能而不释放&lt;/h1&gt;

&lt;p&gt;简单的来说，就是吃多了。&lt;/p&gt;

&lt;p&gt;人吃入的能量，大于自身活动消耗的能量，长年累月下来，可不就“储蓄”起来了。&lt;/p&gt;

&lt;p&gt;由于人类并没有直接排泄储蓄的生理机制。因此要减肥，只有一种办法：长期食物摄入不足。如果不想减少食物摄入，可以通过增加运动消耗来变相实现食物摄入不足。&lt;/p&gt;

&lt;h1 id=&quot;节食为何困难&quot;&gt;节食为何困难&lt;/h1&gt;

&lt;p&gt;增加运动，确实可以抵消吃的多。但是经常运动减肥的人会明白，运动后会吃的更多。于是食物摄入的能量仍然超过了消耗。&lt;/p&gt;

&lt;p&gt;这是为何呢？&lt;/p&gt;

&lt;h1 id=&quot;人体靠什么控制食物摄入&quot;&gt;人体靠什么控制食物摄入&lt;/h1&gt;

&lt;p&gt;人，口渴了就会喝水。饿了就要吃东西。身体会明明白白的通过自身感受来激发大脑的主观能动性，以实现内部的平衡。
所有肥胖的人，都有一个共同特点：易饿。正如所有尿结石的人，也有一个共同特点：不容易口渴。&lt;/p&gt;

&lt;p&gt;饿了，身体就在鞭策大脑，赶紧寻找食物。肥胖的人无法靠运动减肥，正是因为运动后，身体会变得更渴望食物。&lt;/p&gt;

&lt;p&gt;因此，减肥，就是要锻炼自己的意志力，和身体的饥饿信号做斗争。&lt;/p&gt;

&lt;h1 id=&quot;对抗饥饿感&quot;&gt;对抗饥饿感&lt;/h1&gt;

&lt;p&gt;如果人的意志力无比强大，那么吃什么东西，自然只要拿着电子称+热量表。算好每日能量摄入量即可完成减肥。&lt;/p&gt;

&lt;p&gt;但是，这何其的难！&lt;/p&gt;

&lt;p&gt;事实上，有一种更简单的办法，就是创造“饱腹感”。&lt;/p&gt;

&lt;h1 id=&quot;高热量食物并不真的高热&quot;&gt;高热量食物并不真的高热&lt;/h1&gt;

&lt;p&gt;高热食物，是指食物的能量密度高。吃同样体积的食物，高热食物的能量更多。因为人每顿吃的食物是固定体积的。“填”饱肚子就算吃好了。
因此如果吃高热食物，则相比吃低热量食物，会摄入更多的能量。&lt;/p&gt;

&lt;p&gt;然而事实果真如此吗？&lt;/p&gt;

&lt;h1 id=&quot;食物的饱腹感&quot;&gt;食物的饱腹感&lt;/h1&gt;

&lt;p&gt;人的身体发出“吃饱”了的信号，并不完全靠胃容积信号。一些食物，哪怕吃的体积并不大，也会让身体发出“吃饱了”的信号。
这类食物，就是所谓的拥有较高饱腹感的食物。&lt;/p&gt;

&lt;p&gt;因此，哪怕是高热食物，只要它拥有较高的饱腹感，人一样会主动的减少摄入量。使得总的能量摄入，并不比吃低热量食物更多。&lt;/p&gt;

&lt;h1 id=&quot;被误解的猪油&quot;&gt;被误解的猪油&lt;/h1&gt;

&lt;p&gt;我小时候，几乎每家每户都有熬猪油的习惯，家里厨房常备一个罐头瓶子，里面都是凝固的白色猪油，妈妈无论是炒饭，还是煮面条，还有炒青菜都会用猪油，那中温暖滋润的感觉，一辈子都难忘。
可是不知道从什么时候起，白色的猪油瓶子渐渐消失了，很少有人会专门熬猪油，大多数人对猪油更是避之而不及。
大概是从生活条件变好了，植物油的产量也丰盛了，胖子越来越多了，患心脑血管疾病的人越来越多了。直到现在猪油还是被主流的健康媒体认为是不健康的食物，欲除之而后快。&lt;/p&gt;

&lt;p&gt;那些媒体为什么说猪油不健康？主要是说的饱和脂肪含量高、胆固醇和卡路里高，容易让人胖，容易堵塞血管。人们根据以形补形的直观理解，也很容易接受这个观念。&lt;/p&gt;

&lt;p&gt;简单的来说，猪油是一种高热量食物。&lt;/p&gt;

&lt;h1 id=&quot;猪油能提供饱腹感&quot;&gt;猪油能提供饱腹感&lt;/h1&gt;

&lt;p&gt;如果一顿饭里摄入一勺猪油和一勺植物油相比较，前者的满足感明显更强，饱腹感维持的时间也更强。
可见猪油虽然是高热量食物，但是猪油能提供更大的饱腹感。也就是说，使用猪油炒菜，人每顿饭会吃的更少。&lt;/p&gt;

&lt;p&gt;很多人减肥失败，甚至走上暴食的道路，都是由于长期吃饭不满足、不幸福、还饿得快。因为植物油无法提供饱腹感。于是为了追求吃的饱，会下意识的吃的更多。&lt;/p&gt;

&lt;p&gt;低热量的食物，吃多了，总能量也是比 吃少量高热量食物 更多。&lt;/p&gt;

&lt;p&gt;猪油能提供更强的饱腹感，意味着猪油是一种天然的“节食药”。&lt;/p&gt;

&lt;h1 id=&quot;吃猪油能减肥&quot;&gt;吃猪油能减肥&lt;/h1&gt;

&lt;p&gt;因此，我的结论就是，吃猪油的人，会比吃植物油的人，每日摄入更少的食物。虽然食物单位质量所含的能量比使用植物油烹饪出来的多，但是少吃几口饭就把猪油里多含的能量抵消回去了。
而且肚子里有猪油，人会更耐饿。常吃植物油的人，哪怕顿顿都摄入了充足的能量，身体还是会不断的向大脑发出“我饿了，我饿了”的信号。于是忍不住的会吃上一些零食。&lt;/p&gt;

&lt;p&gt;所以，为了健康，还是不要听金龙鱼忽悠吃什么劳什子大豆油吧。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>同居是没有保障的婚姻</title>
   <link href="https://microcai.org/2024/09/12/girls-are-stupid.html"/>
   <updated>2024-09-12T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/09/12/girls-are-stupid</id>
   <content type="html">&lt;p&gt;现代女性恐惧婚姻，却不恐惧同居。反而欣欣向荣。&lt;/p&gt;

&lt;p&gt;然而，同居就是没有保障的婚姻。女人对法律给予的保障十分的恐惧，反而喜欢裸奔。
为啥同居就是没有保障的婚姻呢？&lt;/p&gt;

&lt;p&gt;因为现行的婚姻法，对女性的约束力几乎没有，写满了对男性的约束力。对男人来说，结婚的性价比太低，不如同居划算。因为结婚了，也不能约束伴侣。反而自己被法律给管辖起来了。
倒不如只同居，不结婚。这样双方都是裸奔，都没有法律保护。那就看谁在法律外的力量大了。&lt;/p&gt;

&lt;p&gt;看明白过来的女人，开始热衷相亲。而想透的男人，不结婚了。
于是产生了千人相亲大会没有男人去的奇观。&lt;/p&gt;

&lt;p&gt;只不过，还有很多女人没想明白，热衷于同居被白嫖。男朋友提结婚她还急。
而，男人嘛，一样一堆没想明白的，热衷于结婚。还要花天价彩礼。明明俩人已经同居了，既然能白嫖，为啥还要掏天价彩礼？&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>航天应用下参考系的选取</title>
   <link href="https://microcai.org/2024/07/01/what-reference-frame-to-use.html"/>
   <updated>2024-07-01T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/07/01/what-reference-frame-to-use</id>
   <content type="html">&lt;p&gt;这俩月，航天的话题比较热。吸引了我的全部注意力。&lt;/p&gt;

&lt;p&gt;只是跟着热点跑，那我也就不是我了。&lt;/p&gt;

&lt;p&gt;以前高中学习万有引力的时候，从未考虑过的事情，突然变得烦恼起来。那就是，在航天领域，用的是什么坐标系？&lt;/p&gt;

&lt;p&gt;火箭要送卫星上天，必须要靠导航。导航导航，导引航迹。必须得知道自己在哪。
知道自己在哪，就必须得描述出来。&lt;/p&gt;

&lt;p&gt;在地面上，位置可以用 经纬度+高度 表达。在地面运动的物体，也直接拿地面当惯性参考系。
但是，在天上，总不能拿那个“旋转”的地面当惯性参考系吧？ 应该直接以地心作为坐标原点的三维坐标系，而抛弃经纬度。问题在与，如何确定这个坐标系的轴的方向。&lt;/p&gt;

&lt;p&gt;第一宇宙速度，为 7.9km/s，是以谁为参考的？&lt;/p&gt;

&lt;p&gt;以地心？ 那么问题来了，环绕地球做圆周运动，必须得有第三方参照物。没有第三方参考，2体互绕运动是不成立的。
如何选取这“第三方”参照物呢？&lt;/p&gt;

&lt;p&gt;太阳是一个可选项吗？&lt;/p&gt;

&lt;p&gt;如果太阳是固定的，那么必然可以观测到，卫星在绕地球转。可是，如何确定地球在绕太阳转呢？&lt;/p&gt;

&lt;p&gt;“遥远的恒星” 似乎可以作为一个背景，证明地球在绕太阳转。所以太阳不能作为地心参考系的轴方向参考。&lt;/p&gt;

&lt;p&gt;但是，如果我们要进行导航，就必须建立一个绝对的基准点。“遥远的恒星”这个表述，显然并不能获得认可。在工程实践上也无法使用。&lt;/p&gt;

&lt;p&gt;就在我百思不得其解的时候，突然想到了陀螺仪。&lt;/p&gt;

&lt;p&gt;那么，就可以使用地球的球心作为坐标系原点，以“陀螺仪”的指向为坐标系的轴。如此，卫星和地球，就有了一个绝对的“圆周”运动。&lt;/p&gt;

&lt;p&gt;但是，等等，陀螺仪的指向，就真的是绝对的吗？&lt;/p&gt;

&lt;p&gt;如果陀螺仪的指向是绝对的，那是不是2体互绕运动就有了一个“绝对”的参考系？&lt;/p&gt;

&lt;p&gt;这不符合马赫原理。&lt;/p&gt;

&lt;p&gt;迄今为止，人类真正能说，找到了宇宙绝对不会有错的定理，就是热力学定理和马赫原理。狭义相对论因为无法和马赫原理兼容而抛弃。
而广义相对论，也不完全兼容马赫原理。这正是广义相对论不完善的地方，也是它无法和量子力学统一的根本所在。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>中国古人的天地宇宙观</title>
   <link href="https://microcai.org/2024/05/27/ancient-chinese-knew-earch-is-a-sphere.html"/>
   <updated>2024-05-27T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/05/27/ancient chinese-knew-earch-is-a-sphere</id>
   <content type="html">&lt;p&gt;在学习世界历史的时候，我知道麦哲伦16世纪完成了环球航行。于是证明了地球是个球。地圆说终于获得实捶证据。从此天圆地方再无市场。&lt;/p&gt;

&lt;p&gt;但是，麦哲伦那个时代，一定是对于大地到底是平的还是圆球的，争论不休。也就是说，有相当的一部分人认为，大地是个球。只不过没有实际的证据。&lt;/p&gt;

&lt;p&gt;那，这些早就认为大地是球的人，他们的认知来自何方？ 这种明显偏离日常经验的知识，必然是源自经年累月的思考和观察。&lt;/p&gt;

&lt;p&gt;而且，这样的“争论”应该是已经旷日持久。只有在旷日持久的争论下，可能还是“世纪争论”下，麦哲伦才能向国王申请到“项目经费”，为这场世纪争论盖棺定论。&lt;/p&gt;

&lt;p&gt;既然争论由来已久，那么“地圆说”的支持者，必然是有所传承的。“地圆说”必须得有个开端，不那么准确的猜想开始。然后经历数代人的完善修正，最终提出完整的地圆说。&lt;/p&gt;

&lt;p&gt;不可能是某个天降神人，某日突发奇想，然后就能“说服”一大帮的信徒。&lt;/p&gt;

&lt;p&gt;可惜，我在西方历史上，没找到这样的演化发展痕迹。地圆说在西方好像是突然出现——或者说，突然的挖到了千年前埋藏的某个古希腊著作——然后突然大家争论。然后突然来个冒险家弄了个实锤证据。
西方的很多科学技术，都有突然性。突然就发现了某本古希腊的典籍。这些典籍，千年以来无人问津，突然就天下闻名了。&lt;/p&gt;

&lt;p&gt;像极了中国远古时候。比如 天降神农氏发明药学。天降黄帝发明医学。天降仓颉发明了文字。天降有遂氏发明了火。。。。&lt;/p&gt;

&lt;p&gt;除了上古时代，其他时候倒是各种发明都是有迹可循。慢慢的发展而来。发明不再是某个天降神人，而是很多代人的接力棒。&lt;/p&gt;

&lt;p&gt;不过，中国上古时期的“天降神人”，是因为近古时期的人已经忘记了某种东西具体是如何发明出来的。因此嫁接了一个神人。嫁接的时候，他们用这些东西其实已经很久很久了。久到忘记了“祖师爷”到底是谁。&lt;/p&gt;

&lt;p&gt;西方不一样，天降神人的东西，他们是真的才知道。不是 时代久远导致习以为常使用的东西都不知道是谁发明的。&lt;/p&gt;

&lt;p&gt;好像西方在近代，是突然顿悟了某项科技，而这个科技恰好可以到古希腊里找到出处。而且这个神人必须天赋异禀，在同期的人还在茹毛饮血的时候就已经完成了千年思索。&lt;/p&gt;

&lt;p&gt;后来仔细研究，才发现，地圆说，其实早在汉代张衡就已经很明确的指出了。&lt;/p&gt;

&lt;p&gt;张衡在他的浑天说代表作《浑天仪图注》里说:“浑天如鸡子.天体圆如弹丸,地如鸡中黄,孤居于内,天大而地小.天表里有水,天之包地,犹壳之裹黄.天地各乘气而立,载水而浮.周天三百六十五度又四分度之一;又中分之,则一百八十二度八分度之五覆地上,一百八十二度八分度之五绕地下,故二十八宿半见半隐.其两端谓之南北极”。&lt;/p&gt;

&lt;p&gt;而张衡的学说，也不是他个人的独创。也是整理了先秦的研究而成的。&lt;/p&gt;

&lt;p&gt;但是，很明显，浑天仪“失传”了。&lt;/p&gt;

&lt;p&gt;因为，同时期，中国大地上还有盖天说。盖天说就是天是个锅盖，盖在地上。当多个学说竞争的时候，张衡的学说就未必获得大家的重视。作品失传是很自然的。&lt;/p&gt;

&lt;p&gt;也就是说，天是圆的，但是地是平的还是个球，在古代并没有“定论”。张衡说的那些也不过是 “一家之言”。但是百家争鸣嘛，所以各种学说都有。最关键的是，谁的学说更能解释客观现象。&lt;/p&gt;

&lt;p&gt;但是，浑天说没有失传。严格来说，浑天说，就是日后官方主持修订历法所依赖的主流学说。因为只有浑天说，最符合观测。浑天说虽然处于修天文的人的主流，但是没有成为”社会主流“。
根本原因是因为没有提出”万有引力“定律。导致无法解释球的另一面。 因此，他对于古代中国人来说，算是一个有瑕疵的理论。于是只有学习天文的人才会接受这个理论以便更好的观测天象。
而普罗大众则不接受。&lt;/p&gt;

&lt;p&gt;真正让这个理论，从钦天监官员那，普及到普通人，是因为“大航海”。海上的航行，是真的发现地球曲率的。但是，地的另一边的人怎么不掉下去？&lt;/p&gt;

&lt;p&gt;因此，当时信奉浑天说的人，是用这个东西来安慰自己的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/photo_2024-05-27_04-04-14.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;也就是说，浑天说内部，就分为全球体和半球体两派。&lt;/p&gt;

&lt;p&gt;因为，球的另一半（也就是美洲大陆），没人去过！！&lt;/p&gt;

&lt;p&gt;这就是为何，唐代僧一行主持子午线测量，也不说大地是球。因为他的测量行为，最多证明大地是半球。全球派没有市场。全球派最多是从“哲学”角度思考，半球状的大地，不符合宇宙的美学。but这种想法说服不了众人。既然说服不了众人，就无需说服。他能用这个理论，指导他完成新历法的修订就可以了。&lt;/p&gt;

&lt;p&gt;能真正证明大地是球，除非找到背面的那块大陆，并且通过环球航行证明那个大陆在背面。&lt;/p&gt;

&lt;p&gt;而背面那块大陆，真的找到了！&lt;/p&gt;

&lt;p&gt;当大地确确实实是个球，那么困扰浑天说几千年的问题，就不得不解答了。美洲人为啥不掉下去？&lt;/p&gt;

&lt;p&gt;对这个问题的思考，最终诞生了万有引力定律。&lt;/p&gt;

&lt;p&gt;那么问题来了，麦哲伦为何如此“坚信” 浑天说的大地全球派？ 他的行为，更像是已经从某个“先知”那获得了他自己能完全信服的证据，然后急于向自己的老乡用老乡能懂的语言证明先知是正确的。&lt;/p&gt;

&lt;p&gt;那么只有一种可能：他见过地球仪。&lt;/p&gt;

&lt;p&gt;这种问题，本来是无解的，直到后来，那副 100% 证明地球是个球的《坤舆万国全图》确定绘制于明朝初期。而不是万历年间。&lt;/p&gt;

&lt;p&gt;《坤舆万国全图》被证实是绘制于郑和下西洋时期。于是，利玛窦带来西方的“地圆说”就成了无稽之谈。
顺带的，围绕利玛窦诞生的许多历史，自然全都是伪史了。&lt;/p&gt;

&lt;p&gt;在研究这个历史的时候，我惊讶的发现，儒家的人大部分不支持浑天说。尤其是超级大儒，朱熹。朱熹把浑天说批判为离经叛道。这也就难怪，朱熹的学说在明朝成为儒家唯一正统后，大明的科技日渐败退了。朱熹为何不喜浑天说？&lt;/p&gt;

&lt;p&gt;因为在宋朝，信奉浑天说的人，已经将浑天说演化发展到非常接近现代的宇宙学了，对很多虔诚的儒教徒来说，他们是一群疯子！ 这种宇宙学，对儒家讲的天命观是一种釜底抽薪式的打击！ 这也是为何，朱熹的儒学，是非常的歇斯底里的神经病版的儒学。&lt;/p&gt;

&lt;p&gt;要用疯子对抗疯子。&lt;/p&gt;

&lt;p&gt;大明，随着笃信航海观念的陈友谅的战败，龟缩一地追求安分守己的朱熹派成为天朝正统。并且通过八股取士，海禁，彻底消灭其他异端学说。&lt;/p&gt;

&lt;p&gt;这些异端学说，只能“漂洋过海”，并且在欧洲找到继承人的时候，学习孙悟空的师傅吩咐道，“不许说是我的徒弟”。&lt;/p&gt;

&lt;p&gt;不许说？那就说我师承古希腊人吧。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>物业就是现代版的皇权不下乡</title>
   <link href="https://microcai.org/2024/05/20/no-gov-in-residential.html"/>
   <updated>2024-05-20T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/05/20/no-gov-in-residential</id>
   <content type="html">&lt;h1 id=&quot;皇权不下乡&quot;&gt;皇权不下乡&lt;/h1&gt;

&lt;p&gt;在古代，皇权的主要目的就是征税和征兵。而征税是可以“外包”的，征兵后来被“募兵”取代。&lt;/p&gt;

&lt;p&gt;于是，维持到村的基层治理体系就变得毫无意义。所谓皇权不下县，就是在县以下的行政单位基本上靠“士绅”自治。&lt;/p&gt;

&lt;p&gt;其实古代皇权不下县到明清时代才特别明显。秦汉时期，皇权不仅要下县，还要扎根在村里。
因为秦朝的 “耕战” 制度，全民皆兵。每有战争，皇帝就要派人到村里挨家挨户的征兵。&lt;/p&gt;

&lt;p&gt;在工业社会到来前，人口大多数居于村庄。因此要维持军队数量，必然是要深入广大农村地区征兵。&lt;/p&gt;

&lt;p&gt;明清时期，皇权不下县就特别明显了。主要是皇帝的兵，不来自农村。&lt;/p&gt;

&lt;p&gt;明朝前期，兵员主要来自“卫所”的军户。 军户时代为兵。闲时耕作，战时为兵。这一下子就把全国人划分成了两波人： 负责供养军队的人，和负责上战场的人。而且大部分人口被划入供养军队的范畴。&lt;/p&gt;

&lt;p&gt;不过后来随着吏治的腐败，卫所制度逐渐崩溃。于是募兵制替代了卫所制。募兵制下，兵员由将领用钱招募而来。既然是用钱募集而来，自然犯不上到农村里挨家挨户的征兵。&lt;/p&gt;

&lt;p&gt;清朝其实就是改名了的明朝。 所谓的 “八旗”制度，其实就是明朝的卫所制。明朝的军户世代为兵。八旗的旗丁也是世代为兵。&lt;/p&gt;

&lt;p&gt;只不过，朱元璋的卫所追求数量，全国有几百万军户。而清朝走精兵路线，全国只有十几万旗丁。&lt;/p&gt;

&lt;p&gt;同大明类似，大清也发现“世代为兵”的人数量不够，于是也用钱来凑。于是就有了绿营兵。&lt;/p&gt;

&lt;p&gt;因此在明清两朝，划入“普通农户”的全国绝大多数人口，其实是没有兵役的。当兵，是那些“军户/旗人”的义务。
因此，皇帝并不需要建立村一级的行政机构去挨家挨户的征兵。&lt;/p&gt;

&lt;p&gt;至于征税，则可以“外包”。明清时期，老百姓并不直接向官府纳税。而是将税粮交给“代理人”。这些代理人，在秋收时节，就会下乡挨家挨户的搜刮粮食。&lt;/p&gt;

&lt;p&gt;因此，皇帝大手一挥，“免三年税”。其实只是喂饱了那些代理人。&lt;/p&gt;

&lt;h1 id=&quot;政府不进小区&quot;&gt;政府不进小区&lt;/h1&gt;

&lt;p&gt;到 21 世纪，很多时候城里人会发现，自己事实上是被“资本家”统治的。生活上找不到政府的痕迹。可能对一个普通城市居民来说，他实际上是被一种叫
“物业”的资本家治理的。&lt;/p&gt;

&lt;p&gt;这个其实就是现代城市版的皇权不下乡。在古代，县以下的政府是不存在的。由“乡贤”自治。在现代城市，市以下的政府也是不存在的。城市的基层是由 ”物业“ 统治的。
”物业“也是一种”自治“机构。由”乡贤“承担。哦，不，应该叫”城贤“了。&lt;/p&gt;

&lt;p&gt;乡贤可不是谁都能当的。他得有“功名”在身，还得有“名望”。所谓功名，就是参加过科举，最次也得是各秀才。在卷的地方，还非得是举人才行。所谓名望，就是当地世家大族的“认可”。
城贤也不是谁都能当的。他得有“资本”才行。还得有“业主大会的认可”。&lt;/p&gt;

&lt;p&gt;正如古代社会，皇权不下乡是一步一步的。现代城市的皇权不下乡也是一步一步的。&lt;/p&gt;

&lt;p&gt;在建国初期，城里需要 “挨家挨户” 干活的任务，是交给城市真正的基层政府“居委会”干的。正如秦汉时期，需要到村里挨家挨户干活的任务，是交给当时真正的基层政府做的——比如刘邦当的亭长是也。
但是，到了明清时期，皇权不下乡，导致农村的治理靠乡贤自治。
现在，城市的基层，也进入”皇权不下乡“时代，靠”物业“ 这种城贤自治了。&lt;/p&gt;

&lt;p&gt;其实起源都是一样的，现代城市的征税主体不是“住户”，而是“公司”。因此管理住户这种任务就可以“外包”出去了。
哪怕其实公司，也是住在 “产业园”，但是你会发现，天朝政府并不靠 产业园 的物业管理那些公司，而是直接管理。&lt;/p&gt;

&lt;p&gt;因此，只要政府还靠“物业”进行城市基层管理，就不可能征收的了房产税。
必须重建城市基层政府，才能切实管理“住户”从而从住户身上直接征税。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>阿波罗载人登月了么</title>
   <link href="https://microcai.org/2024/05/17/is-apollo-faked.html"/>
   <updated>2024-05-17T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/05/17/is-apollo-faked</id>
   <content type="html">&lt;p&gt;第一次知道载人登月这个事情，是在教科书上。因为是教科书上讲的事情，我对阿波罗载人登月这个事情是深信不疑的。&lt;/p&gt;

&lt;p&gt;长大后，随着互联网的普及，开始接触到了老美那边流传的”登月造假“阴谋论。&lt;/p&gt;

&lt;p&gt;当然，第一次听到有人说登月是造假的时候，我第一反应是不信（造假）的。
不过，即使不信，我还是会看看他们的理由。&lt;/p&gt;

&lt;p&gt;当质疑者提出他们的理由的时候，我的第一反应是，他们说的好有道理，但是肯定背后有一些我不知道的科学道理。
果然，反驳质疑者马上就一一给圆回来了。&lt;/p&gt;

&lt;p&gt;我的反应是，原来背后还有这么深刻的科学道理啊！学习了！&lt;/p&gt;

&lt;p&gt;那时候，反驳质疑的人，会耐心的讲解质疑者不理解的现象。&lt;/p&gt;

&lt;p&gt;然后过了很多很多年。&lt;/p&gt;

&lt;p&gt;突然质疑登月造假的舆论又开始甚嚣尘上。我还纳闷了，咋这种话题又火了。仔细一搜，发现原来是中国的探测器登月了。借着东风，又炒了一波。&lt;/p&gt;

&lt;p&gt;但是，这次，反驳质疑的人，不耐心了。全力开启了嘲讽模式，对质疑者阴阳怪气。仿佛美国登月是一种宗教，不容质疑。&lt;/p&gt;

&lt;p&gt;而这次，质疑者不再是因为无知而提出了很多问题，而是搬出了证据。&lt;/p&gt;

&lt;p&gt;我也开始觉得，质疑者不是空穴来风了。看来老美也许真的做了假？&lt;/p&gt;

&lt;p&gt;不过，我觉得既然各执一词，不如静待中国载人登月。倒是很多疑惑能获得更好的解答。何必对质疑者阴阳怪气呢？而反驳着那种高高在上的态度，阴阳怪气的口气，让我很不舒服。
我还是倾向于这种事情与其争吵不如静待。现在信的太过，保不齐将来打脸呢？ 我开始隐隐约约的认为老美造假了。
因为反驳者的行事风格就是典型的”水军霸凌“。正义一方何必雇佣水军呢？&lt;/p&gt;

&lt;p&gt;于是，又安静了好多年。直到最近，登陆造假的话题随着嫦娥六号的登月又双叒叕火起来了。&lt;/p&gt;

&lt;p&gt;这次舆论一边倒的质疑了。和前几年那次反驳者霸凌质疑者的情况完全不同，质疑者各种回旋镖，而反驳的人少了很多。
不过，正如去年 Mate 60 发布一样。水军只是迟到，从来不缺席。&lt;/p&gt;

&lt;p&gt;随着时间的推演，反驳着再次占据上风。质疑者再次被打倒为“智障”，“爱国狗”，“义务教育漏网者”。&lt;/p&gt;

&lt;p&gt;这事情没有幕后推手，才是真的有鬼了。&lt;/p&gt;

&lt;p&gt;我不用去仔细看双方的证词。我只要知道，无组织者在网络上斗不过水军。正如 Mate 60 发布一阵后，水军经费到账后，华为再次被黑残。
凭此矩阵行为，我已经可以断定，阿波罗载人登月一定造假。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>现代婚姻制度错在哪？</title>
   <link href="https://microcai.org/2024/05/04/in-the-name-of-gender-equality.html"/>
   <updated>2024-05-04T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/05/04/in-the-name-of-gender-equality</id>
   <content type="html">&lt;p&gt;古代婚姻制度的基础是家族联姻。现代婚姻制度的基础是“人人平等”。&lt;/p&gt;

&lt;p&gt;咋一听，好像进步了。其实不然。准确的说，没有所谓的现代婚姻制度，而是西方的婚姻制度和中国的婚姻制度。大清灭亡后，中国的婚姻制度就没了，被西方婚姻制度取代了。&lt;/p&gt;

&lt;p&gt;西方的婚姻制度的第一要义是“算计”。为啥说是算计呢？ 因为“人人平等”。以人人平等之名行掠夺之实。结婚双方，一直在算计对方。各种西式鸡汤也不断的提醒你，要“计较”婚姻中的得失。免得被算计。&lt;/p&gt;

&lt;p&gt;这种西方的算计式生活数百年来早就融入了西方人的基因。西方人从生下来到死去，没有一秒钟不在“算计”。做生意要算计，工作要算计，买东西要算计，谈恋爱要算计。结婚也要算计。连养孩子都要和孩子算账。&lt;/p&gt;

&lt;p&gt;因此，西方的婚姻制度，是适应西方的人口基因的。&lt;/p&gt;

&lt;p&gt;但是，这样的制度，直接抄到中国来使用，犯了严重的水土不服之病。&lt;/p&gt;

&lt;p&gt;因为西方的女人内裤是很松的。所以西方的婚姻制度，其社会基础，其实是婚前浪如妓，结婚如从良。因此西方的婚姻，更像是妓女从良仪式。彩礼？ 不存在的。能有从良机会就要感动的稀里哗啦的。
西方女人被男人求婚的时候感动到落泪是普遍现象。&lt;/p&gt;

&lt;p&gt;但是在东方，女人要看好裤子的道德观念，并没有随着大清的灭亡而灭亡。女人裤腰带变松，是从改革开放开始的。因此，西方的婚姻制度对中国水土不服的表现其实也是从改革开放以后才显露出来的。而不是从民国引入西方婚姻法开始。&lt;/p&gt;

&lt;p&gt;之所以开始水土不服，是因为女人比男人更快的接纳了西方的算计文化。腰带宽松的妓女，却不按从良的规矩，依旧摆出黄花大闺女的架势要求男性，要三媒六聘，要八抬大轿。
婚后却不守妇道，要以西方“自由平等”的方式对待婚姻。&lt;/p&gt;

&lt;p&gt;于是，不兼容性开始凸显。&lt;/p&gt;

&lt;p&gt;很多男人不知道的一个事实就是，中国一个很普通的家庭主男，在西方社会女人需要十世苦修才能遇上。娶洋妞的家庭，洋妞都是笑开花。而嫁洋屌的女人，除了裤衩满足了，别的说多了都是泪，也不敢说出去。&lt;/p&gt;

&lt;p&gt;洋妞虽然也是西方文化，但是不双标。他用西方文化的标准看待中国老公，自然得出十世好男人的结论。中国女人用双标的眼光看老公，自然是咋看咋不顺眼。&lt;/p&gt;

&lt;p&gt;中国几千年来重男轻女的思想，为何培养出的男人，却是西方人眼中的“十世好男人”？&lt;/p&gt;

&lt;p&gt;因为中国社会，压迫女性的从来就不是婚姻。而是社会。女孩子在社会上多受苦，在家里就要多关爱。而西方社会，女性是在家庭和社会双重压迫下，才生出了平权运动。而且她们的平权运动不敢砍向老公，只能砍向社会。
于是社会上，女性获得了工作的权利。但是所谓的平等，是建立在女性要在生理条件不如男性的情况下强行平等。个中滋味只有女人自己知道。&lt;/p&gt;

&lt;p&gt;而在中国社会，因为女性无法工作。没有独立的经济。因此就必须在婚姻上进行补偿。因此数千年来，中国男人的教育就是要疼老婆。还要把不疼老婆的人写入坏人传。
中国历史上有多少后宫干政的事情？ 没有在宠女人的文化环境下长大的皇子，继位后，只可能把女人当工具。不可能去宠。&lt;/p&gt;

&lt;p&gt;中国男人宠女人，是在女人自身弱小，在社会对女人不公平的情况下产生的。&lt;/p&gt;

&lt;p&gt;结构，改革开放太快了。社会发展太快太快。中国男人的思维还停留在要疼女人的古代观念上。却碰到的尽是精于算计的西方精神的女人。
二者之间的矛盾就无法调节了。&lt;/p&gt;

&lt;p&gt;更要命的是，恪守妇道的传统女人，遇到的尽是精于算计的西方男人。而老实的男人，通常遇不到传统女人。于是整个中国家庭，都是建立在大量的不平等之上。&lt;/p&gt;

&lt;p&gt;这种错配，要一直持续到传统思维下的男人消磨殆尽为止。要知道，西方是不存在捞女的。&lt;/p&gt;

&lt;p&gt;所谓的“婚前财产”设计，只是在目前错误的，不适合国情的婚姻制度下，打了一个小补丁。以此想弥补一下传统的中国男人。
结果利用这个设计的，尽是精于算计的西方男人。被算计的，很多还都是传统思维下的女人。&lt;/p&gt;

&lt;p&gt;到底是要继续动荡到硬着陆，还是承认西方婚姻制度失败，重新探索适合东方家庭的婚姻制度？&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>沙雕动画 vs 腾讯动画</title>
   <link href="https://microcai.org/2024/03/21/shadiao-ani-vs-tencent.html"/>
   <updated>2024-03-21T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/03/21/shadiao-ani-vs-tencent</id>
   <content type="html">&lt;p&gt;很多人说，看国产的动画片，配音不好。然后就跑去看日本片去了。更有奇葩的，要看日语配音的国产动画片。&lt;/p&gt;

&lt;p&gt;其实，cv 不专业并不是主要原因。有人说那叫“母语羞涩”，在我看来也不是主要原因。&lt;/p&gt;

&lt;p&gt;我认为，根本原因是台词出了问题。&lt;/p&gt;

&lt;p&gt;一般人们常把语言分为口语和书面语。
其实，还有一个类型，它既不是口语，也不是书面语，而是专用于台词的语言。&lt;/p&gt;

&lt;p&gt;人物的台词，即不能照搬口语使用，也不能直接使用书面用语。&lt;/p&gt;

&lt;p&gt;最为明显的例子，当属《三国演义》。 三国演义里的台词，既不是原著上的书面语，也不是生活化的口语。它是那种通俗易懂的，一听就懂，但是又不是白话的语言。
让你一听就觉得，古人就应该这么说话。&lt;/p&gt;

&lt;p&gt;其实何止古人。电视剧里的人，都应该这样说话。&lt;/p&gt;

&lt;p&gt;其实，影视剧使用专门的语言，是业内本就早已熟知的事情。&lt;/p&gt;

&lt;p&gt;只不过，这群业内人士，不从事动画片制作。&lt;/p&gt;

&lt;p&gt;因此，腾讯上播放的那些所谓动画片，统统没有对台词进行优化。不仅仅没有进行优化，反而因为从业者对日本动画片的喜爱，不知不觉的搞出了日式汉语。&lt;/p&gt;

&lt;p&gt;正是这个日式汉语，导致动画片的台词听着别扭。&lt;/p&gt;

&lt;p&gt;日式汉语令人听感不佳的特征点有&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;语速较慢。正常人说话的语速为每分钟 300 - 500 字。腾讯动画片里的人物台词语速不足每分钟200字。&lt;/li&gt;
  &lt;li&gt;台词极少出现成语和歇后语。即使是日常生活对话，也是要经常使用成语和歇后语的。腾讯动画片的从业者的日本师傅较少使用台词和歇后语是主要原因。因为日本人汉语水平较低，使用成语会增加观众理解障碍。&lt;/li&gt;
  &lt;li&gt;会不经意的夹杂一些日语梗。除了对日本动画片较为熟悉的观众，无法理解。而导演不自知。&lt;/li&gt;
  &lt;li&gt;语序经常错乱为日语语序。&lt;/li&gt;
  &lt;li&gt;台词词汇上会偏向早年 CCTV 引进日本动画片的时候使用的翻译词汇。而不是本地人所使用的更接底气的词汇。&lt;/li&gt;
  &lt;li&gt;待补充&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这就是为何，我更喜欢熊猫人动画片。因为以上问题，熊猫人动画片统统没有。&lt;/p&gt;

&lt;p&gt;不仅仅没有，熊猫人动画片在台词上，会更有文艺范。因为我看的熊猫人动画片，以穿越剧为主。
因此，他们的台词设计上，就会更加学习三国演义。&lt;/p&gt;

&lt;p&gt;虽然熊猫人动画片用 AI 进行配音，看似缺少了感情。但是，台词功底完爆腾讯动画片。观之反而越来越有味。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>新型单相电机调速器</title>
   <link href="https://microcai.org/2024/03/06/new-ac-motor-speed-controller.html"/>
   <updated>2024-03-06T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/03/06/new-ac-motor-speed-controller</id>
   <content type="html">&lt;h1 id=&quot;传统立扇调速方案&quot;&gt;传统立扇调速方案&lt;/h1&gt;

&lt;p&gt;传统立扇方案主要采用抽头调速方法，通过机械调速开关改变主绕组与辅助绕组接线方式。一般抽头调速的风扇电机是基于满载工况优化设计，在电机高挡位运转时，绕组磁场接近正交的圆形，噪音和效率表现相对较好，而中低挡位时主绕组和副绕组的结构被改变，绕组合成磁场偏向椭圆，电机运行失去对称性，转矩脉动分量增加。并且中低挡位工况下，运行绕组温升更高，电机偏离了原有优化的状态，其输入功率也没有成比例下降，还经常伴随着恼人的电磁噪音。从某种意义来说，抽头调速是靠绕组间“出力抵消“，消耗能量来达到调速目的。此外，因为多抽头绕组结构复杂，实际生产中电机定子绕线需要人工配合，不利于电机标准化生产。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/28469021.png&quot; alt=&quot;抽头调速&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;交流调压控速&quot;&gt;交流调压控速&lt;/h1&gt;

&lt;p&gt;实际上，电扇电机因为功率较小，因此可以对只使用变压器进行调速。事实上中间抽头的做法，就是让电机绕组与抽头形成自耦变压器。然后实际上相当于降压运行。并不需要变频就可以完成简易的调速任务。&lt;/p&gt;

&lt;p&gt;但是，抽头法有2个缺点。其一为档位固定。制作的时候做几个抽头就只有几个档位。其二为低速运行的时候效率急剧下降。&lt;/p&gt;

&lt;p&gt;所以，如果使用交流调压器，其实也可以对电机进行调速。&lt;/p&gt;

&lt;p&gt;但是，交流调压器使用工频变压器就决定了，成本低不了。&lt;/p&gt;

&lt;p&gt;传统上，直流降压使用 Buck 电路。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/20200414104632358.png&quot; alt=&quot;Buck&quot; /&gt;&lt;/p&gt;

&lt;p&gt;所需元件超少，成本非常低。&lt;/p&gt;

&lt;p&gt;但是，只能对直流电起作用。&lt;/p&gt;

&lt;p&gt;如果有一种对交流电直接进行 Buck 变换的电路，岂不是能大大降低交流电的降压成本？然后超低成本实现单相电机的调速功能？&lt;/p&gt;

&lt;h1 id=&quot;交流-buck-降压电路&quot;&gt;交流 Buck 降压电路&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/images/Snipaste_2024-03-06_02-26-04.png&quot; alt=&quot;ACBuck&quot; /&gt;&lt;/p&gt;

&lt;p&gt;在上图电路里， 4个 MOS 管构成了一个 H 桥。但是这个 H 桥的输入端，却是 左边右边一个火线一个零线。&lt;/p&gt;

&lt;p&gt;传统的H桥，左半桥和右半桥，输入都是接的直流母线。
但是，这个 ACBuck 的 H 桥，左右却是接的不同的输入。一个火线一个零线。 H桥的下管源级，倒是接的“地”。&lt;/p&gt;

&lt;p&gt;这个“地”，其实是另一处，火零经过全桥整流后的负极。&lt;/p&gt;

&lt;p&gt;在交流的正半周， Q3、Q4 构成一个 buck 降压电路。Q1 Q2 工作在反向电压，由于其体二极管的存在，不论开关管是打开还是关闭，电流都会畅通无阻的流到零线。&lt;/p&gt;

&lt;p&gt;在交流的负半， Q1 Q2 构成了一个 buck 降压电路。Q3 Q4 工作在反向电压。由于其体二极管的存在，不论开关管是打开还是关闭，电流都会畅通无阻的流到火线。&lt;/p&gt;

&lt;p&gt;因此，只要使用一个带死区控制的互补 PWM 信号，就可以无视交流电的正负周期，而直接对交流电进行 Buck 降压。&lt;/p&gt;

&lt;p&gt;每个桥臂的下管，同时还充当续流管的作用。这还是一个同步 Buck 降压电路。&lt;/p&gt;

&lt;p&gt;只需要调节 PWM 信号的占空比，就能调节输出的电压。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>TO-252 对比 TO-220</title>
   <link href="https://microcai.org/2024/03/03/to252-vs-to220.html"/>
   <updated>2024-03-03T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/03/03/to252-vs-to220</id>
   <content type="html">&lt;p&gt;MOSFET，功率MOS，最常见的外形，就是 TO-252 和 TO-220 两种。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/TO252-TO220.png&quot; alt=&quot;封装对比图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;上图左边的为 TO-252 封装。右侧为 TO-220 封装。&lt;/p&gt;

&lt;p&gt;其实会发现，TO-220 剪掉中间引脚，折弯两边的引脚，也能转变为贴片形式。&lt;/p&gt;

&lt;p&gt;在我设计低压变频器的时候，我一直使用的 TO-252 封装的 MOS。无他，低压的大电流MOS还是TO-252更常见。&lt;/p&gt;

&lt;p&gt;后来，设计220v电压的高压变频器的时候，我转而使用 TO-220 封装的MOS。无他，我观各种使用650v 耐压的mos的电路板，通常使用 TO-220 的直插MOS。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/photo_2024-02-08_13-17-06.jpg&quot; alt=&quot;组装好了&quot; /&gt;&lt;/p&gt;

&lt;p&gt;但是，自从这个版本炸了MOS后，我改回了 TO-252。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/Snipaste_2024-03-03_19-08-24.png&quot; alt=&quot;改回TO-252&quot; /&gt;&lt;/p&gt;

&lt;p&gt;用回 TO-252 后，便没再炸MOS了。&lt;/p&gt;

&lt;p&gt;虽然，板子的前后区别，可不仅仅是MOS改变了封装。&lt;/p&gt;

&lt;p&gt;首先就是修改后的板子，栅极驱动到MOS管的距离更短了。意味着更小的走线寄生电感。&lt;/p&gt;

&lt;p&gt;其次，驱动桥臂的上管源极和下管漏极之间的距离被无限缩小了。之前使用 TO-220 的时候， 上下桥臂距离可没这个版本近。&lt;/p&gt;

&lt;p&gt;然后是增加了若干退耦电容。&lt;/p&gt;

&lt;p&gt;这都为新板子的稳定运行立下功劳。只是不知道具体是哪个措施真正起了作用😂&lt;/p&gt;

&lt;p&gt;于是，对比了 TO-252 和 TO-220 后，我得出了一个结论：TO-220 可以淘汰了。&lt;/p&gt;

&lt;p&gt;首先，TO-220 需要使用散热片散热。背面金属片上的洞就是方便使用螺丝将mos牢牢固定在散热片上的。而且如果不是一个mos配一个散热片，还得配绝缘的导热垫片。因为TO-220背面的金属散热片，和中间的那个引脚都是漏极。&lt;/p&gt;

&lt;p&gt;在pcb制造的过程中，不得不增加非机械臂可完成的操作————将散热片和mos用螺丝固定到一起。这对人工成本越来越贵的当下显然是徒增成本。&lt;/p&gt;

&lt;p&gt;反观 TO-252 封装，减掉了漏极引脚。直接使用散热片当漏极。并且通过焊锡将散热片和 pcb 的铜箔牢牢焊接到一起。热阻远小于使用硅脂+导热垫片。&lt;/p&gt;

&lt;p&gt;在大部分情况下，使用大面积覆铜就可以出色的替代独立的散热片。&lt;/p&gt;

&lt;p&gt;其次，表贴型MOS的引脚更短。更有利于减小封装引入的寄生电感和寄生电容。这就意味着更高的开关速度，更小的开关损耗。当然，还有更不容易炸管。&lt;/p&gt;

&lt;p&gt;为了能利用好 TO-252 的优势。接下来我设计pcb的时候，会注意给MOS管更大的散热用覆铜。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/photo_2024-03-03_20-14-05.jpg&quot; alt=&quot;走线绕开为覆铜&quot; /&gt;&lt;/p&gt;

&lt;p&gt;比如上图这个设计，特意将走线进行绕道，就为了能给 MOS 更大的覆铜面积以供散热。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>三电平 SVPWM</title>
   <link href="https://microcai.org/2024/02/29/3level-svpwm.html"/>
   <updated>2024-02-29T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/02/29/3level-svpwm</id>
   <content type="html">&lt;p&gt;炸机了。&lt;/p&gt;

&lt;p&gt;制作低压变频器的时候，从未出现过炸机。但是，在高压变频器上，出现了。MOS管直接炸开。pcb铜箔炸飞。&lt;/p&gt;

&lt;p&gt;究其原因，还是因为mos关闭时候产生的尖峰电压。&lt;/p&gt;

&lt;p&gt;在研究解决方案的时候，碰到了三电平拓扑。&lt;/p&gt;

&lt;p&gt;三电平拓扑输出三相电需使用12个 MOS 管。&lt;/p&gt;

&lt;p&gt;那么问题来了，12个MOS管，如何控制呢？&lt;/p&gt;

&lt;p&gt;其实，不用修改 svpwm 算法。 svpwm 算法在最终，会输出 A B C 三相的 pwm 值。&lt;/p&gt;

&lt;p&gt;在传统2电平拓扑里，这3个 pwm值，就直接幅值给硬件驱动，产生6路pwm 了。&lt;/p&gt;

&lt;p&gt;在3电平拓扑里，这3个 pwm 值，要转而变成 2个 pwm 定时器的12路 pwm 。&lt;/p&gt;

&lt;p&gt;具体做法为，使用2个定时器。这2个定时器都需要开启6路pwm互补输出模式。对x相来说，有 Qxh1, Qxh2, Qxl1, Qxl2 四个开关。
其中， Qxh1 和 Qxl1 使用1号定时器对应通道。，Qxh2 和 Qxl2 使用2号定时器对应通道。&lt;/p&gt;

&lt;p&gt;对 X 相来说，如果传入的 pwm 值 &amp;lt; 50%, 则，定时器1 的通道输出上管关，下管常开，定时器2的 上下通道输出按 pwm值*2。
如果传入的 pwm 值 &amp;gt;= 50%, 则定时器 2 下管关。 上管常开，1号定时器上下通道输出按 (pwm-0.5)*2。&lt;/p&gt;

&lt;p&gt;比如下面这个代码&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// duty range from [0-1]&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;set_pwm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duty_A&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duty_B&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duty_C&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TWO_LEVEL_SVPWM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;driver_set_pwm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TIM1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;channel1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duty_A&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;perid_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;driver_set_pwm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TIM1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;channel2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duty_B&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;perid_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;driver_set_pwm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TIM1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;channel3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duty_C&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;perid_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;THREE_LEVEL_SVPWM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;duty_A&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;driver_set_pwm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TIM1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;channel1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 下管常开&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;driver_set_pwm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TIM2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;channel1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duty_A&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;perid_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;driver_set_pwm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TIM2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;channel1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;perid_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 上管常开&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;driver_set_pwm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TIM1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;channel1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;duty_A&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;perid_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;


        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;duty_B&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;driver_set_pwm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TIM1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;channel2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 下管常开&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;driver_set_pwm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TIM2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;channel2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duty_B&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;perid_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;driver_set_pwm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TIM2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;channel2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;perid_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 上管常开&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;driver_set_pwm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TIM1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;channel2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;duty_B&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;perid_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;


        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;duty_C&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;driver_set_pwm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TIM1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;channel3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 下管常开&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;driver_set_pwm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TIM2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;channel3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;duty_C&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;perid_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;driver_set_pwm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TIM2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;channel3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;perid_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 上管常开&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;driver_set_pwm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TIM1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;channel3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;duty_C&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;perid_count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个代码就实现了3电平 svpwm 输出。而三相duty的计算方式和之前的并无不同。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>被销售绑架</title>
   <link href="https://microcai.org/2024/02/26/seller-kidnapped-business.html"/>
   <updated>2024-02-26T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/02/26/seller-kidnapped-business</id>
   <content type="html">&lt;p&gt;在芯片销售行业，有一个怪现象。买芯片的人是孙子。&lt;/p&gt;

&lt;p&gt;在线的电子元件商城是行业老鼠，属于行业灰色地带（只是被业内人士定义为灰色。实际上100%合理合法的白色产业）。&lt;/p&gt;

&lt;p&gt;不仅仅买芯片的时候是孙子。在研发 pcb 的时候，更是孙子。需要找芯片厂求来芯片手册。没有手册，就算买到芯片了也用不起来。&lt;/p&gt;

&lt;p&gt;芯片的价格是卖方市场。能打几折买到芯片，就决定了最终产品的竞争力。因此为了赚钱，买方要无下限跪舔卖方的销售。&lt;/p&gt;

&lt;p&gt;如果有这么一个平台，你不用跪销售，就能买到芯片，畅通无阻的查阅各类型号的手册。&lt;/p&gt;

&lt;p&gt;那这个平台一定把芯片销售得罪死。&lt;/p&gt;

&lt;p&gt;销售不仅仅当不了大爷，没有买方舔他了。更重要的是，芯片制造商发现他们不需要这些提成巨大的销售了。这对芯片销售来说是灭顶之灾。&lt;/p&gt;

&lt;p&gt;因此，在销售掌握话语权的公司，你都没法在在线商城里买到他家的芯片。&lt;/p&gt;

&lt;p&gt;不过好在，还有很多公司，销售并不掌控公司。所以元件商场上还是会有各类琳琅满目的芯片可供购买。对他们来说，在线商城，是多了一个销售渠道。
卖东西嘛，渠道是多多益善。&lt;/p&gt;

&lt;p&gt;元件商场常常让人诟病的一个问题是，价格没有优势。&lt;/p&gt;

&lt;p&gt;但是，这不是元件商场的错。因为他们是在芯片原厂的销售抵制的情况下开起来的。为了喂饱这些销售，元件商场的拿货价，都比你直接找销售买的高。&lt;/p&gt;

&lt;p&gt;因此，他们的目标客户，就只能是那些体量较小，舔不起销售的初创公司以及DIY爱好者了。&lt;/p&gt;

&lt;p&gt;芯片公司有一个很诡异的地方。就是芯片公司的官网。只要你用了他的芯片，你就一定要去他官网下文档&lt;/p&gt;

&lt;p&gt;也就是说，你的客户必然会访问你的官网&lt;/p&gt;

&lt;p&gt;那么，此时你在官网直接销售芯片。厂家直销，是水到渠成的。&lt;/p&gt;

&lt;p&gt;正是为了避免发生这种情况，销售才会忽悠公司说，芯片的手册是机密，不能公开。只能通过销售传给买了芯片的人。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>FlyBuck的限制</title>
   <link href="https://microcai.org/2024/02/25/flybuck-limitation.html"/>
   <updated>2024-02-25T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/02/25/flybuck-limitation</id>
   <content type="html">&lt;p&gt;在 &lt;a href=&quot;/2024/02/21/power-on-pcb.html&quot;&gt;这篇&lt;/a&gt; 文章里，我夸奖了 flybuck 拓扑结构。&lt;/p&gt;

&lt;p&gt;不过，在实际使用的时候，发现 flybuck 并没有想象中那么美好。&lt;/p&gt;

&lt;p&gt;第一点，flybuck 并不是任何 buck 芯片都能工作。而是只有能工作在强制 CCM 模式的 同步整流buck 芯片才能工作。
因此只有 Ti 的寥寥数个芯片，被打上了 FlyBuck 标志的才能用在 flybuck 上。而因为 FlyBuck 是 Ti 的商标，
所以其他公司的芯片，是不会打上这个标记的。只能通过仔细阅读芯片手册，确定他可以工作在强制 CCM 模式。
这就要了老命了。因为市面上的 DCDC 降压芯片超过一万款。&lt;/p&gt;

&lt;p&gt;第二点，flybuck 的输出电压也不是 Vout2 = Vout1 * Np/Ns - Vf。而是受漏感影响。
在有漏感的情况下，Vout2 的电压还会同时受 Iout1 和 Iout2 相互影响。&lt;/p&gt;

&lt;p&gt;漏感大了，副边电压就稳不住了。这也要了老命了。因为本来是低成本的方案，结果对变压器的要求很高。漏感低的耦合电感可一点都不便宜。&lt;/p&gt;

&lt;p&gt;实际上，超过2元成本的 flybuck 毫无意义。因为有不到3元的 flyback 方案。
但是 Ti 标记能用于 FlyBuck 的 dcdc  芯片，个个都在两位数价格。毫无成本优势。
Ti  所谓的flybuck的成本优势，是建立在Ti 家的 flyback芯片更贵的基础上的。
当国内厂家生产5毛钱的flyback芯片时，ti 的 flybuck 优势荡然无存。&lt;/p&gt;

&lt;p&gt;因此我只能寄托于国内厂家3毛钱的 buck芯片能用于 flybuck。
但是国内厂家的一个通病就是手册喜欢藏着掖着。生怕用户知道芯片怎么用。
于是关键的信息，芯片是否CCM模式，如何强制CCM模式的信息，是断然不会提供的。&lt;/p&gt;

&lt;p&gt;一个好东西，硬是被玩坏了。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>LLC 真的是天才发明</title>
   <link href="https://microcai.org/2024/02/23/llc-is-a-brillant-invention.html"/>
   <updated>2024-02-23T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/02/23/llc-is-a-brillant-invention</id>
   <content type="html">&lt;h1 id=&quot;开关电源&quot;&gt;开关电源&lt;/h1&gt;

&lt;p&gt;传统上，交流电通过一个巨大的变压器进行变压。因为交流电通过磁耦合的方式传递能量。传递能量的频率为交流电的频率。每次传递的能量取决于变压器铁芯的磁通量。磁通量从最大到最小，最小到最大，
经历一次能量传递。&lt;/p&gt;

&lt;p&gt;变压器要增大功率，要么增加磁通，要么提高频率。显然工作在50hz交流电下的变压器，只能选择增大磁通的方式增加功率。由于材料能承受的磁通量密度是有上限的。过大的磁通密度会饱和。所以变压器要增加功率，只能增加体积。&lt;/p&gt;

&lt;p&gt;而开关电源，则另辟蹊径，使用晶体管的开关功能产生高频的交流电，从而摆脱了 50hz 的频率限制。&lt;/p&gt;

&lt;p&gt;于是，体积很小的变压器就可以传递很大的功率。&lt;/p&gt;

&lt;p&gt;在如何产生高频交流电的问题上， 产生了很多种路径。&lt;/p&gt;

&lt;p&gt;反激，正激，推挽，全桥。&lt;/p&gt;

&lt;p&gt;当中最为广泛使用的，属反激。但是不管是反激，正激，推挽还是全桥，变压器的漏感，都属于有害的东西。要尽量减小。
所谓变压器的漏感，指变压器初级的一部分能量没有传递给次级，而是滞留在初级，导致初级滞留的能量最后在开关管关断的时候要释放出来。
这部分释放能量需要在初级电路里消纳掉，导致电源整体效率下降。&lt;/p&gt;

&lt;h1 id=&quot;漏感利用&quot;&gt;漏感利用&lt;/h1&gt;

&lt;p&gt;人类历史上很多东西的进步都来自变废为宝。比如 LLC 的发明，就将废物的漏感变成了宝。&lt;/p&gt;

&lt;p&gt;要理解 LLC 首先要理解 LC。L 和 C，一个电压滞后，一个电流滞后。搭配起来干活的时候，恰好会反复将对方滞后。。。  也就是俗称的振荡。
也就是一个 L 一个 C, 就构成了一个振荡电路。电流会反复的在 L 和 C 之间徘徊。&lt;/p&gt;

&lt;p&gt;变压器的初级绕组，也是一个线圈绕在铁芯上。他就会是一个电感。在进行电路分析的时候，变压器的电感会被拆分成2个。一个是主电感，一个是漏电感。主电感负责把能量传给次级。
漏电感。。。漏电感负责搞事情。&lt;/p&gt;

&lt;p&gt;这搞事情的漏电感，在 LLC 的眼中，却是好东西。因为他可以和电容构成 LC 振荡。因为主电感把能量输送给次级后，自身就没有能量了，也就是说，变压器的次级接了负载后，主电感的感量就被消耗掉了。
所以，没有漏感的理想变压器，反而不能引起 LC 振荡。必须得再搭配一个L。而反正非理想变压器免费送一个漏感。于是 LLC 就把这个漏感笑纳了。&lt;/p&gt;

&lt;p&gt;在 LLC 里，漏感（固定值），主电感（随负载变动），电容三者构成了一个谐振电路。这个谐振电路恰好能产生交流电，然后这个交流电又恰好发生在变压器的线圈里，于是这个谐振能量就传递给了变压器的次级。&lt;/p&gt;

&lt;p&gt;随着负载的变动，谐振点也会跟着移动。如果驱动电路产生的 pwm 频率和谐振频率不同，就会损失效率。表现为次级电压下降。因此采集次级的电压，可以用于修正 pwm 的频率。让 pwm 的频率始终保持在谐振频率上。因为，如果 pwm 的频率和谐振频率相同，就会产生一种神奇的效果。 就是 MOS 管开关的时间点，正好在谐振电压为0的地方。也就是，产生了 0 压开启的效果。使得mos管的开通损失降为0。&lt;/p&gt;

&lt;p&gt;因此 LLC 的次级电压反馈，主要目的不是为了调整占空比稳压（因为 LLC 始终使用 50% 的占空比，也就是pwm只是单纯的维持谐振电路，而不是调节占空比来调压），而是为了跟踪谐振频率。&lt;/p&gt;

&lt;h1 id=&quot;漏感和谐振频率&quot;&gt;漏感和谐振频率&lt;/h1&gt;

&lt;p&gt;如此说来，其实无漏感的变压器，其实也能 LLC？&lt;/p&gt;

&lt;p&gt;理论上是的，但是实际上如果漏感太小，在重载的时候，由于主电感能量几乎完全传递给了次级，会导致 LC 振荡里， L 太小，导致谐振点无限升高。
为了维持谐振频率在一个可接受的范围，LLC 就要求变压器必须要有漏感，而且还是很大的漏感。这样可以保证即使全负载工作，主电感的感量完全被耦合干净了，LC振荡的条件还在。而且因为漏感较大，不至于把谐振点推高到 pwm 频率跟不上的地方。&lt;/p&gt;

&lt;p&gt;但是漏感过大，也不行。因为这意味着同功率下变压器体积要变大。而且漏感大是因为耦合度低。而耦合度低本身又会带来更大的变压器铁损。反而效率降低。
因此， LLC 的变压器，不能任意提高漏感。&lt;/p&gt;

&lt;p&gt;但是，漏感越高，确实谐振点随负载变动而偏移的就越少。但是，漏感也无需特意做的很大。因为在谐振点较低的时候，是变压器轻载的时候。（轻载的时候，主电感的感量大）
此时 pwm 即使高于谐振点，也因为初级线圈电流较低，而不会有很大的开关损失。因此， LLC 只要考虑在中高负载的情况下， pwm 控制器产生的频率范围，能覆盖 LLC谐振器的谐振点即可。如此一来，轻载的情况下，也无需降低pwm频率来匹配谐振点。而是直接工作在过谐振状态。&lt;/p&gt;

&lt;p&gt;LLC变压器的谐振点范围是    &amp;gt;=1/（2π·（漏感+主电感）*电容 ）， &amp;lt;=1/（2π·漏感*电容 ）。更准确的来说，是 1/（2π·（漏感+（主电感*空载率））*电容 ）&lt;/p&gt;

&lt;p&gt;pwm 的频率，需要 &amp;gt;= 谐振点。不能小于谐振点。因为小于谐振点，和大于谐振点，都会损失效率。小的情况下还会损失多点。&lt;/p&gt;

&lt;p&gt;一般漏感取总电感的 20%，按负载 50% 的情况下开始进入谐振，则谐振频率的最大最小 相差 1.667 倍。也就是说，如果重载的时候， 谐振频率是 500khz , 则 50% 负载的时候， 谐振频率是 300khz.
更低的负载情况下，pwm 频率维持在 300khz 进入过谐振状态。虽然会损失点效率，但是负载降低了，总的损失也是降低的。这样谐振控制器就只需在一个较小的 300khz-500khz 的范围内进行谐振点跟踪即可。&lt;/p&gt;

&lt;p&gt;如果还想缩小谐振频率范围，就不能继续做大漏感了。而是应该考虑一个外置电感构成 LLLC 谐振了。:)&lt;/p&gt;

&lt;h1 id=&quot;谐振为啥可以实现零压开启&quot;&gt;谐振为啥可以实现零压开启&lt;/h1&gt;

&lt;p&gt;变压器绕组和电容进行谐振的时候，绕组的自感电压会和直流母线的电压进行叠加。叠加的结果就是 MOS 管源漏极上的电压，也会呈现正弦变化。在源漏极上的电压跌到最低的时候开启 MOS 管，就可以让MOS的导通损失最小。只不过关断的时候无法实现零压关断。因为 MOS 管关闭的时候 LLC 才会自谐振。MOS 管打开的时候， LLC 的电压被迫和母线电压一致。所以在 MOS 开启期间，无法做到电压因为谐振而自发降低。&lt;/p&gt;

&lt;h1 id=&quot;谐振&quot;&gt;谐振&lt;/h1&gt;

&lt;p&gt;总的来说， LLC 之所以效率高，主要其实是利用了谐振。而不是完全靠 MOS 强推电流。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>变频器制作-第12部分 电源，还是电源</title>
   <link href="https://microcai.org/2024/02/21/power-on-pcb.html"/>
   <updated>2024-02-21T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/02/21/power-on-pcb</id>
   <content type="html">&lt;h1 id=&quot;说到供电&quot;&gt;说到供电&lt;/h1&gt;

&lt;p&gt;一个变频器，需要4组电压供电，分别是 DC 300V, DC 12V, DC 5V, 隔离 5V.&lt;/p&gt;

&lt;p&gt;300v 是高压， 12v 和 5v 乃低压。&lt;/p&gt;

&lt;p&gt;只不过， 12v 和 5v 和 300v 的高压，负极是连到一起的。因此，这个负极，是不安全的负极。这个负极，是整流桥的负极。这个负极会有一个对地的脉动电压。&lt;/p&gt;

&lt;p&gt;其中， 300V DC 由一个整流桥 + 一个 LC 滤波器获得。&lt;/p&gt;

&lt;p&gt;12V 是专门给栅极驱动器供电的。NMOS 一般使用 12V 门级电压确保完全开启。
5v 是给隔离器的高压侧供电的。隔离5v 则是给隔离器的低压侧，和其他需要安全的隔离的电路供电。比如 mcu，显示屏等。&lt;/p&gt;

&lt;h1 id=&quot;使用成品5v小电源获得供电&quot;&gt;使用成品5v小电源获得供电&lt;/h1&gt;

&lt;p&gt;12v 属于低压。在传统上，会使用一个开关电源获取低压供电。
我最初也是这么设计的。由于我并不是电源工程师，所以购买的成品开关电源模块。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/acdc_mod.png&quot; alt=&quot;成品模块&quot; /&gt;&lt;/p&gt;

&lt;p&gt;图中红圈的就是成品开关电源模块。一共使用了2个。其中一个用于提供隔离的5V。另一个则是将输出的负极和高压侧的 GND 连接。以获得非隔离的 5V 供电。然后再使用一个 DCDC 升压芯片获得一个非隔离的12v。当然，如果使用12v 输出的隔离模块，然后使用 dcdc 降压，同样能达成目的。不过这就意味着要购买两种电源模块。会增加 BOM 成本。&lt;/p&gt;

&lt;p&gt;由于使用了成品的开关电源，使得220v变频器的成本里有个无法压缩的2个开关电源模块。而且这种模块必须手焊，无法使用自动化的贴片流水线。凭空增加成本。&lt;/p&gt;

&lt;h1 id=&quot;使用-dcdc-芯片获得12v&quot;&gt;使用 DCDC 芯片获得12v&lt;/h1&gt;

&lt;p&gt;由于板子上有非隔离的12v供电的需求。因此转而使用了DCDC降压芯片获得12v供电。&lt;/p&gt;

&lt;p&gt;大部分DCDC芯片都是为低压的直流转直流设计的。无法耐受高压。但是，架不住人们对成本压缩的渴望。芯片公司转而研发了输入耐压能到 600V 的 DCDC 降压模块。用于将 整流后的 310v 直流电直接降压为低压。相比低压的dcdc芯片，这种非隔离的dcdc芯片仍然被归类到 ac/dc 电源芯片里。但是实际上其架构就是普通的 Buck 降压电路。因此他的成本也就比普通的dcdc芯片多几毛钱。&lt;/p&gt;

&lt;p&gt;再搭配一个 DCDC 降压电路，于是我就获得了比使用成品开关电源模块要便宜很多的 12v 和 5v 供电。&lt;/p&gt;

&lt;p&gt;获得了廉价高压侧的非隔离低压电，解决了栅极驱动芯片和隔离器副边的供电问题。 单片机侧还是需要隔离电源。还是需要使用成品电源模块。&lt;/p&gt;

&lt;h1 id=&quot;使用-flybuck-获得隔离的5v&quot;&gt;使用 FlyBuck 获得隔离的5v&lt;/h1&gt;

&lt;p&gt;趁春假，把唐老师一千多个视频都给过了一遍。在这个宝库里发现了 FlyBuck 架构。
不过也被唐老师给坑了一把。他号称任何 dcdc 芯片都能用于获得隔离的输出。&lt;/p&gt;

&lt;p&gt;其实只有 同步整流的 dcdc 芯片能做到。被他这么一坑，2月的白嫖机会直接浪费。&lt;/p&gt;

&lt;p&gt;不过最终结果还是好的。最终我使用 FlyBuck 拓扑成功获得了隔离的 5v 电源。
扔掉了成品电源模块。供电成本直接砍掉8成。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/flybuck.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;红圈里分别是 1：1 变压器，高压DCDC ic 和普通同步整流 buck 变压器ic。&lt;/p&gt;

&lt;p&gt;从而砍掉了成品的电源模块。
而且同步整流的 DCDC 芯片，能从12v 输入电压同时产生 隔离的 5v 和非隔离的5v.&lt;/p&gt;

&lt;h1 id=&quot;flybuck-永远的神&quot;&gt;FlyBuck 永远的神&lt;/h1&gt;

&lt;p&gt;如果使用多绕组的变压器，FlyBuck 还能一次产生更多的隔离电压。虽然不能用于大电流的场景。但是需要小功率隔离电压的场合还是非常多的。&lt;/p&gt;

&lt;p&gt;比如隔离式 CAN 收发器， 隔离式 RS485 收发器。隔离式 I2C 收发器。&lt;/p&gt;

&lt;p&gt;这些元件都需要一个极低功率的隔离电源供电。而且是每个元件一个。使用 flybuck 就可以用非常低的成本产生巨量的隔离电源。给单片机的每个对外接口都配置一个独立电源供电的隔离器。极大的增强了单片机的电气安全性。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>变频器制作-第11部分 pwm模式用对了吗？</title>
   <link href="https://microcai.org/2024/02/08/pwm-mode-misuse.html"/>
   <updated>2024-02-08T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/02/08/pwm-mode-misuse</id>
   <content type="html">&lt;h1 id=&quot;话说高级定时器&quot;&gt;话说高级定时器&lt;/h1&gt;

&lt;p&gt;不管是 AT32 还是 CH32 还是 EG32 还是 GD32 还是 RP32. 各种 32 单片机，都是使用的 防STM32 的外设。&lt;/p&gt;

&lt;p&gt;因此，他们都会有一种叫“高级定时器”的设备，用来产生多路互补PWM波。&lt;/p&gt;

&lt;p&gt;在高级定时器的设置里，能产生的波形是以下机制的排列组合&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;计数器模式
 向上计数溢出置0，向下计数到0重置，向上然后向下计数。后两者称为中央对称模式。在 svpwm 里，固定使用中央对称模式。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;通道大于占空比值输出真，大于占空比值输出假。&lt;/li&gt;
  &lt;li&gt;真值高电平 真值为低电平&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;有些hal库，上管通道和下管通道可以分别设置 2 号模式。有些没有独立设置。不知道是硬件如此还是hal库认为没必要暴露这种设定。&lt;/p&gt;

&lt;p&gt;2 号 和3 号设定，有互补性。比如 （通道大于占空比值输出真+真值高电平）和 （大于占空比值输出假+真值为低电平）的组合，产生的 pwm 波是一模一样的。&lt;/p&gt;

&lt;p&gt;一开始，我并没有在意，感觉是硬件上可能会做一些没有意义的组合机制。比如 三个上管打开和3个下管同时打开，作用是一样的，在 svpwm 里都是属于0向量。&lt;/p&gt;

&lt;h1 id=&quot;再谈死区时间&quot;&gt;再谈死区时间&lt;/h1&gt;

&lt;p&gt;最初我制作12v的低压变频器的时候，并没有在单片机里设定死区时间。因为栅极驱动已经内置了一个死区时间。&lt;/p&gt;

&lt;p&gt;6pwm 模式相比 3pwm 模式，死区可调的优势当时我是不知道的。我只不过是希望能有6管同时关闭的模式。所以即使不需要死区控制，我也使用了6pwm 模式。&lt;/p&gt;

&lt;p&gt;直到后来，我开始向220v进发。高压 MOS 相比低压 MOS 具有更大的导通和关断延迟。栅极驱动里默认的延迟时间显得不太够用了。&lt;/p&gt;

&lt;p&gt;由此，6pwm 模式软件可调死区的优势就是必须的了。&lt;/p&gt;

&lt;p&gt;在设定一个死区时间后，我就上机调试了。发现电路能正常运行，并且没有炸鸡。我以为大功告成了。&lt;/p&gt;

&lt;p&gt;然而，我的眼花悄悄的给我开起了玩笑。&lt;/p&gt;

&lt;h1 id=&quot;被pwm模式的的排列组合暴击&quot;&gt;被pwm模式的的排列组合暴击&lt;/h1&gt;

&lt;p&gt;之前讲过，（通道大于占空比值输出真+真值高电平）和 （大于占空比值输出假+真值为低电平）的组合，产生的 pwm 波形是一样的。&lt;/p&gt;

&lt;p&gt;事实上，我确实就随意的选了一种模式，并且当时恰好能工作。&lt;/p&gt;

&lt;p&gt;其实，这个结论，只有不使用死区控制的时候是正确的。 使用了死区控制，结论就不正确了。
因为，这关系到，单片机在输出死区这段时间，是上下管的 pwm 波同时高电平，还是同时低电平。&lt;/p&gt;

&lt;p&gt;而且我恰好把这个设置弄反了。结果就是，单片机在死区时间，输出模式乃上下管同时高电平。&lt;/p&gt;

&lt;p&gt;而栅极驱动确实尽职尽责。在单片机输出上下同开的错误命令的时候，检测到了错误，忠实的完成了自己的“防上下管同开”的任务。于是恰好，没有炸管。&lt;/p&gt;

&lt;p&gt;但是突然有那么一个瞬间，栅极驱动不灵了。他未能完成他的任务。上下管同时导通了！&lt;/p&gt;

&lt;p&gt;于是，2个470uF 的滤波电容正极到上管的漏极，这条 PCB 布线被炸断。MOS管自身也在短路电流下炸毁。由于短路是发生在滤波电容上，空开还未来得及动作，PCB布线炸毁导致回路切断。所以空开没有任何反应。只听到滋的一声，MOS亮了以下。然后就没然后了。&lt;/p&gt;

&lt;h1 id=&quot;总结&quot;&gt;总结&lt;/h1&gt;

&lt;p&gt;经过重新编写的 pwm 驱动，终于没有暴击了。单片机高级定时器的这些灵活设计其实也是非常好的。比如光耦版本的高压驱动板上，我就必须设定真值为低电平。因为经过光耦的信号会反相。用单片机的 gpio 术语来说，就是光耦的输出端是开漏的。输入高电平，让内部光敏二极管导通，拉低输出端口电压。因此光耦反相，如果不想多花钱将光耦的输出再次反相，就可以在单片机端设定 pwm 输出信号真值为低电平。这样灵活的设计怎么能怪它坑人呢。 ^_^&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>变频器制作-第十部分 6pwm和死区时间</title>
   <link href="https://microcai.org/2024/02/06/deadtime-important.html"/>
   <updated>2024-02-06T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/02/06/deadtime-important</id>
   <content type="html">&lt;h1 id=&quot;3pwm-vs-6pwm&quot;&gt;3pwm vs 6pwm&lt;/h1&gt;

&lt;p&gt;三相桥需要6个开关管。控制6个开关管需要控制器输出六路pwm信号。&lt;/p&gt;

&lt;p&gt;这将占用单片机6个 IO 口。考虑到 pwm 信号控制的开关管中，上下管是交替导通的。也就是所谓的互补。
因此在一些场景下，也可以使用 3pwm 模式控制6个开关管。
3pwm的信号控制3个上管，对应的下管控制信号由一个反相电路生成。&lt;/p&gt;

&lt;p&gt;咋一看，似乎节约了3个IO引脚。诞生 3pwm模式有一个重要功能无法实现，就是让6个开关管全关。&lt;/p&gt;

&lt;p&gt;因此为了表示这个全关状态，实际上需要额外增加一路 EN 信号。因此 3pwm 模式需要使用4个引脚。&lt;/p&gt;

&lt;p&gt;听上去不错，在实现相同功能的情况下，节约了2个引脚。&lt;/p&gt;

&lt;p&gt;但是，我想说的是，&lt;strong&gt;如果有条件，千万不要使用 3pwm 模式&lt;/strong&gt;。&lt;/p&gt;

&lt;h1 id=&quot;死区时间控制&quot;&gt;死区时间控制&lt;/h1&gt;

&lt;p&gt;理想是丰满的，现实是骨感的。现实中的开关管，并不总是能立即导通/关闭。&lt;/p&gt;

&lt;p&gt;导通和关闭都需要一定的时间发生。如果上管关信号给出的时候，立即给出下管开信号，或者下管关信号给出的时候立即给出上管开信号，也就如使用反相器电路做的那样，则会导致上下管会在某个时间里处于同时打开的状态。&lt;/p&gt;

&lt;p&gt;这会导致严重的短路。&lt;/p&gt;

&lt;p&gt;所以，互补的pwm信号比如插入死区时间。所谓死区时间，就是在上下管切换开启的时候，中间插入两个管子都关闭的时间。以保证实际的管子不会出现同时打开的情况。&lt;/p&gt;

&lt;p&gt;在 3pwm 模式，死区时间需要由硬件设计达成。一般是通过在反相器电路里插入延时实现。&lt;/p&gt;

&lt;p&gt;在现实世界中，一般是使用集成了死区时间控制的栅极驱动器芯片达成目的。&lt;/p&gt;

&lt;p&gt;但是，栅极驱动器的死区控制是死的。一旦选定，就无法更改了。&lt;/p&gt;

&lt;p&gt;因此，为了电路的灵活调整需要，最好是在单片机里控制 pwm 信号的死区时间。&lt;/p&gt;

&lt;p&gt;这就宣判了 3pwm 模式的死刑。&lt;/p&gt;

&lt;h1 id=&quot;死区时间设置多大&quot;&gt;死区时间设置多大&lt;/h1&gt;

&lt;p&gt;在 MOS 管的手册里，会给出 4 个 delay time。分布是开通延时，上升延时。关闭延时，下降延时。&lt;/p&gt;

&lt;p&gt;并且这4个时间还会给出 最小值，典型值，和最大值。&lt;/p&gt;

&lt;p&gt;单片机pwm输出配置的死区时间 = 最大关闭延迟+最大下降延时 - 最小开通延迟  - 栅极驱动器的最小死区时间。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;如果为负，就无需设置死区时间了。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;之所以突然要考虑死区这个问题，是因为最近我开始将变频器给高压化。而高压的 MOS管，其导通延时大大增加。目前选定的一款 MOS， 耐压650V，导通内阻 &amp;lt; 300mΩ，价格不到1元一片。
如此低廉的价格，伴随的是高达450nS的关闭延迟，高达 2000pF 的寄生电容。&lt;/p&gt;

&lt;p&gt;经过计算，发现需要800nS 的死区时间。而栅极驱动器的 死区时间是 200nS。。可见如果不在软件上插入死区时间，必然会炸管。&lt;/p&gt;

&lt;p&gt;谢天谢地，我在把 mos 管焊到板子上的前一天突然刷到一个up他做变频器炸管的视频。由此我联想到自己那块jlc刚刚送来还未来得及焊接的板子会不会也炸管。 要是 220v 下炸管，威力可比12v 下的高不知道多少倍了。&lt;/p&gt;

&lt;p&gt;经过仔细的研究后果然发现，死区时间不够。&lt;/p&gt;

&lt;p&gt;而之前做 12v 的变频器的时候，压根没在软件里配置死区时间，也没问题。是因为用到的低压MOS管，管子的关闭时间都在 100nS上下，经过栅极驱动那 200nS 的死区后，软件里根本无需配置死区，而且我还嫌栅极驱动的死区太大了呢。&lt;/p&gt;

&lt;p&gt;果不其然，今天把 MOS焊接好，软件修改好。终于一次验证通过。把家里220v的电机驱动起来了。&lt;/p&gt;

&lt;p&gt;从此我也可以面对高电压的电路设计啦！&lt;/p&gt;

&lt;p&gt;：）&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>220v 逆变器大功告成</title>
   <link href="https://microcai.org/2024/02/05/220v-vfd-success.html"/>
   <updated>2024-02-05T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/02/05/220v-vfd-success</id>
   <content type="html">&lt;h1 id=&quot;起&quot;&gt;起&lt;/h1&gt;

&lt;p&gt;在 &lt;a href=&quot;/2024/01/17/add-eg6832-to-platformio.html&quot;&gt;这篇&lt;/a&gt; 文章里，我被风扇厂的老板教育了成本控制。&lt;/p&gt;

&lt;p&gt;思来想去，我发现还是有必要制作 220v 的变频器以降低对电机的要求。&lt;/p&gt;

&lt;p&gt;经过数次炸板改版后，最终在今天制作完成了。听到家里 220v 的电机，而不是自己改绕的电机，也发出了熟悉的 vvvf 音乐，心情还是非常激动的。&lt;/p&gt;

&lt;h1 id=&quot;制作&quot;&gt;制作&lt;/h1&gt;

&lt;p&gt;首先， 220v 属于强电，而单片机实验 3.3v 属于弱电。强电和弱电需要隔离。
我首先想到的是使用光耦进行隔离。&lt;/p&gt;

&lt;p&gt;但是，一想到要在pcb上贴6个光耦就感觉成本飙升。而且光耦的 LED 发光二极管，总感觉会随着时间的推移而衰减。而且还感觉 LED 二极管的发光熄灭的速度赶不上 pwm 的速度。&lt;/p&gt;

&lt;p&gt;事实也确实如此。因此需要使用专门为栅极驱动设计的高速光耦。而高速光耦就贵了很多。简直就是成本炸裂。&lt;/p&gt;

&lt;p&gt;就在我苦恼的时候，突然发现了数字隔离器这种东西。一个数字隔离器和一个高速光耦价格相当，但是光耦只能传递一路信号，而数字隔离器能传多路。成本一下子就节约下来了。 最后选了一个6路的数字隔离器，只需一个就完成了6路 pwm 信号的传递。&lt;/p&gt;

&lt;p&gt;另外，为了适应高压，栅极驱动也需要更换为耐压 600v 的版本。&lt;/p&gt;

&lt;p&gt;因为用惯了EGmicro的栅极驱动，所以想都没想，就从 EG2134 升级成了 EG2334。&lt;/p&gt;

&lt;p&gt;于是驱动电路就从&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/EG2134.png&quot; alt=&quot;低压&quot; /&gt;&lt;/p&gt;

&lt;p&gt;变成了&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/EG2334.png&quot; alt=&quot;高压&quot; /&gt;&lt;/p&gt;

&lt;p&gt;当然，为了对比，也做了一个光耦的版本&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/EG2334_Optocoupler.png&quot; alt=&quot;光耦版&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE：经过光耦后，信号反相了。因此需要在单片机里修改输出极性。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;打样回来的光耦版本&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/photo_2024-02-08_13-14-23.jpg&quot; alt=&quot;制作好的光耦&quot; /&gt;&lt;/p&gt;

&lt;p&gt;打样回来的数字隔离器版本&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/photo_2024-02-08_13-14-27.jpg&quot; alt=&quot;打样回来的数字隔离器版本&quot; /&gt;&lt;/p&gt;

&lt;p&gt;除了要使用隔离器进行隔离，栅极驱动和单片机，还得使用两个不同的开关电源供电。
由于我神功还没大成，因此我买了专门的超迷你开关电源模块。
还为这个电源模块在立创EDA里绘制了封装。这样就可以直接引用了。&lt;/p&gt;

&lt;p&gt;比如看下背面&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/photo_2024-02-08_13-22-33.jpg&quot; alt=&quot;背面&quot; /&gt;
有两个电源模块的位置。&lt;/p&gt;

&lt;p&gt;然后搞起来验证&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/photo_2024-02-08_13-17-06.jpg&quot; alt=&quot;组装好了&quot; /&gt;&lt;/p&gt;

&lt;p&gt;因为家里能给我折腾的就只有电风扇。所以三相接了2相出来，也能用来驱动电风扇的电机了。&lt;/p&gt;

&lt;p&gt;还接了个漏保来保护自己。嘿嘿。&lt;/p&gt;

&lt;p&gt;最后，接上电风扇后，听到了熟悉的 VVVF 声音！&lt;/p&gt;

&lt;h1 id=&quot;总结&quot;&gt;总结&lt;/h1&gt;

&lt;p&gt;高压变频器，增加了隔离成本。所以没法和12v/24v的变频器竞争成本。&lt;/p&gt;

&lt;p&gt;隔离成本大头是多了2个隔离电源，多了数字隔离器，还要使用昂贵的隔离采样放大器。&lt;/p&gt;

&lt;p&gt;而且超过 70w 功率，还得配置 PFC 电路。&lt;/p&gt;

&lt;p&gt;还得想办法，把成本扣出来，这样才能卖给电风扇长。毕竟异步电机 60w 也才十几块钱。能制作低成本的变频器，就不需要 20w 的无刷电机做的“变频风扇”忽悠人了。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>单片机也能支持 co_await 协程啦</title>
   <link href="https://microcai.org/2024/02/02/awaitable-for-mcu.html"/>
   <updated>2024-02-02T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/02/02/awaitable-for-mcu</id>
   <content type="html">&lt;h1 id=&quot;序&quot;&gt;序&lt;/h1&gt;

&lt;p&gt;在 &lt;a href=&quot;/2023/12/08/cooperative-multitasking-in-mcu.html&quot;&gt;这篇文章&lt;/a&gt; 里，我为单片机编写了一个简单的 executor。
然后利用这简单的 executor, 再搭配 Duff’s device 就用上了 stackless coroutine 了。&lt;/p&gt;

&lt;p&gt;但是，Duff’s device 也有其缺陷。最明显的就是，在 ASIO_CORO_REENTER(this){ xx } 的函数体里，无法定义变量，也无法使用 switch 指令。&lt;/p&gt;

&lt;p&gt;这挺让人头疼的。&lt;/p&gt;

&lt;p&gt;于是，我迫切的需要一个有栈的协程。 c++23 里带的coroutine就不错。&lt;/p&gt;

&lt;p&gt;最初的计划是移植 asio。
但是 asio 不知为何，在 arm-gcc 上遇到了诡异的语法错误。&lt;/p&gt;

&lt;p&gt;遂放弃。直到最近，突然又想起来这件事。&lt;/p&gt;

&lt;p&gt;然后又开始琢磨怎么写一个 awaitable。
但是实在是毫无头绪。于是准备抄一个库。&lt;/p&gt;

&lt;p&gt;然后找到了阿里巴巴开源的 &lt;a href=&quot;https://github.com/alibaba/async_simple&quot;&gt;async_simple&lt;/a&gt; 库。
经过了一定的裁剪后，跑起来了。&lt;/p&gt;

&lt;h1 id=&quot;阿里的代码不和谐&quot;&gt;阿里的代码不和谐&lt;/h1&gt;

&lt;p&gt;首先，阿里的这个协程库，我是扣了代码的。把不需要的东西都删了。但是也没有完全删干净。还有不少遗留的垃圾。
这就让本就捉襟见肘的单片机存储空间更局促了。&lt;/p&gt;

&lt;p&gt;其次，阿里的这个代码虽然已经比 asio 的简化了很多，但是还是弯弯绕绕非常多，非常不利于分析。&lt;/p&gt;

&lt;p&gt;所以，在&lt;a href=&quot;https://www.jackarain.org/&quot;&gt;哥们&lt;/a&gt; 的帮助下，整了一个更简化的&lt;a href=&quot;https://github.com/microcai/mcu_coro_demo/tree/master/lib/mcu_coro&quot;&gt;版本&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;正如 这个仓库里的 例子那样，使用这个协程的方法很简单，首先，需要在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loop()&lt;/code&gt; 里调用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mcucoro::executor::system_executor().poll();&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;然后，这么定义协程&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;mcucoro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;led_blinker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
 &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(;;)&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;LL_GPIO_TogglePin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GPIOB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GPIO_PIN_14&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coro_delay_ms&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1240&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;协程的返回值得是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mcucoro::awaitable&amp;lt;void&amp;gt;&lt;/code&gt; 类型，&lt;/p&gt;

&lt;p&gt;然后使用&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;mcucoro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([](){&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;led_blinker&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;来启动协程即可。&lt;/p&gt;

&lt;h1 id=&quot;总结&quot;&gt;总结&lt;/h1&gt;

&lt;p&gt;有了 co_await 协程，编写 mcu 代码是如虎添翼。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>准备制作直流UPS</title>
   <link href="https://microcai.org/2024/01/28/dcups-prepare.html"/>
   <updated>2024-01-28T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/01/28/dcups-prepare</id>
   <content type="html">&lt;h1 id=&quot;问题&quot;&gt;问题&lt;/h1&gt;

&lt;p&gt;现在很多需要停电维持工作的东西，其实都是直流供电的。比如监控。比如树莓派，比如路由器，比如光猫。&lt;/p&gt;

&lt;p&gt;在万能的宝上搜索直流UPS，是能找到一些现成的产品。&lt;/p&gt;

&lt;p&gt;但是，统统不是我设想中的那种工作方式。&lt;/p&gt;

&lt;p&gt;我设想中的UPS，既不是后备式，也不是在线式。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;后备式UPS，在市电正常的情况下，电池处于充电状态。当检测到市电故障，则立即将输出切换到电池。
在线式UPS，电池一直处于边冲边放状态。负载一直都是由电池供电。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;后备式UPS，有切换延时问题。在线式UPS，能量过了好几道，效率太差。&lt;/p&gt;

&lt;p&gt;我设想中的UPS，应该是这样子的&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;负载和电池直接并联。电池不仅供电，还能负责稳压。
充电器为负载供电的同时为电池充电。
充电器的功率只要略大于负载的平均功率。负载短时的功率需求由电池提供（所谓稳压）
电池尽量以恒流方式充电。也就让充电器的输出电流恰好等于电池的充电电流+负载的消纳电量
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这看起来不还是在线式UPS吗？但是在线式UPS是输出电池逆变后的交流电。而直接和电池并联的在线式ups，并不需要逆变。省掉了逆变这一过程。&lt;/p&gt;

&lt;p&gt;那万能的宝上卖的路由器UPS不是这样的吗？&lt;/p&gt;

&lt;p&gt;看起来是，但是并不是。他们使用的还是在线式UPS架构，不过把逆变220v交流电的模块换成了从电池升压输出12v的模块。能量还是要经过  12v适配器-&amp;gt;（ups充电器-&amp;gt;电池-&amp;gt;ups升压电路）-&amp;gt;负载 这么一个过程。能量损失巨大。而且电池实际上充电的速度，取决于负载的消耗能力。负载消耗的大了，电池充电的就慢了。也就是没有做到让电池恒流充电。&lt;/p&gt;

&lt;p&gt;另外也有使用后备式架构的直流UPS。他们使用2个12v适配器，一个为电池供电，一个为负载供电。市电故障的时候，立即切换到12v电池直接供电。这个能做到恒流充电了，但是需要制作2个12v开关电源了。成本上升。&lt;/p&gt;

&lt;p&gt;而且，统统有一个问题：没有48v版。&lt;/p&gt;

&lt;p&gt;为啥需要48v呢？因为 POE。家里需要UPS的设备，全部都是从 POE 交换机取电的。路由器，用 poe 供电。光猫，用 poe 供电。 AP，更是直接接到 POE 上。监控摄像头，也是 POE 供电。&lt;/p&gt;

&lt;p&gt;整个供电的重任，就交到了POE交换机的手上。&lt;/p&gt;

&lt;p&gt;而 POE 交换机，就是以 48v 蓄电池供电为基础设计的。
POE 的供电电压，54v，就是48v蓄电池的浮充电压。&lt;/p&gt;

&lt;h1 id=&quot;解决办法&quot;&gt;解决办法&lt;/h1&gt;

&lt;p&gt;我当然不满足于只是制作一个48v的直流ups填补一下空白。&lt;/p&gt;

&lt;p&gt;于是我想到了一个万能直流UPS，这个万能直流UPS是这样的&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1. 有2个端口。一个为充放电口，一个为电池接头
2. 使用的时候，连线如下 ``` 开关电源 ====== 用电器
     ||
    万能UPS == 电池 ```

3. 万能UPS的充放电口，直接并联在开关电源和用电器的直流母线上。万能UPS的唯一功能，是对流入电池的电流进行限流。也就是进行恒流充电。
4. 如果充电器无输出，则电池可以直接从万能UPS直通用电器。
5. 开关电源的输出电压，应该为电池的满电电压
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;也就是万能UPS，正向工作为Buck 降压恒流模式，反向为直通模式。而且只需要恒流，不需要知道电池的电压。只要适配器的输出电压为电池的满电电压即可。&lt;/p&gt;

&lt;p&gt;实际上，一个简单的带恒流功能的 DCDC 降压模块，就能完成这个万能UPS的工作。除了。。。。一个小问题。&lt;/p&gt;

&lt;h1 id=&quot;难倒了&quot;&gt;难倒了&lt;/h1&gt;

&lt;p&gt;为了减少损害，反向工作的时候，不能依赖开关管的寄生二极管实现自动反向，而应该让 mos 管直接持续导通。
而且为了实现电池接近满电后，buck电路的输入电压=输出电压，也需要让 buck 电路的pwm占空比达到 100%。&lt;/p&gt;

&lt;p&gt;而 buck 的mos管，乃是一个“高侧”的 nmos 管。
让高侧的 NMOS 管 100% 占空比导通，并不是一件容易的事情。&lt;/p&gt;

&lt;p&gt;诶，找了许久也没找到合适的 dcdc芯片。&lt;/p&gt;

&lt;h1 id=&quot;更新&quot;&gt;更新&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://www.bilibili.com/video/BV1WrESzVEAx&quot;&gt;直流UPS大功告成&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;一年多后，我总算有了足够的知识，把这个心心念念的 UPS 制作完成了。&lt;/p&gt;

&lt;p&gt;过程看 &lt;a href=&quot;https://microcai.org/2025/04/24/ups-flyback.html&quot;&gt;参加星火计划2025&lt;/a&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>编译期对象构造优化 .bss 为 .rodata</title>
   <link href="https://microcai.org/2024/01/22/constexpr-constructor.html"/>
   <updated>2024-01-22T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/01/22/constexpr-constructor</id>
   <content type="html">&lt;h1 id=&quot;问题&quot;&gt;问题&lt;/h1&gt;

&lt;p&gt;为了提高性能，我编写了一个查表法算 sin 的函数。为了适配不同 ROM 大小的 mcu, 这个表还有大有小多个版本。最大的表，里面有 1800 项，因为是保存的 0-90度的 sin 指，因此分辨率达到了 0.05度。&lt;/p&gt;

&lt;p&gt;而且，为了进一步提高性能，表里存放的，并不是 float, 而是我自己编写的定点数 float_number。&lt;/p&gt;

&lt;p&gt;问题就出在这个float_number 上。&lt;/p&gt;

&lt;p&gt;因为这个定点表我是这么写的&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sin_table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
 &lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.000872664515&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.001745328366&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.002617990887&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
 &lt;span class=&quot;p&quot;&gt;......&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;因为这个表，其内存放的并不是内置类型。而是我自己编写的 float_number 类。这是一个定点数类， 使用 C++ 的运算符重载机制，可以做到无缝替换 float, 实现浮点改定点。&lt;/p&gt;

&lt;p&gt;但是，正因为这个类不是内置类型，于是编译器实际上安排的是，将 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0.0, 0.000872664515&lt;/code&gt; 等数字，存于 .rodata 段，然后将 sin_table 事实上分配于 .bss 段。
并在初始化代码里将 .bss 段的 sin_table 用存于 .rodata 段的各大 double 数据进行初始化。&lt;/p&gt;

&lt;p&gt;也就是说，这个  sin 表，实际上即占用了 .rodata 也占用了 .bss 。而 .bss 段占用的是mcu里更为珍贵的RAM。
之所以最近才思考这个问题，是因为 EG6832 只有区区 8K 的 RAM。相比拥有 384K 的 ESP32 和 32K RAM 的 AT32, 这个 mcu 的内存实在过于珍贵。
以至于我很快遇到内存不足的问题。&lt;/p&gt;

&lt;h1 id=&quot;解决之道&quot;&gt;解决之道&lt;/h1&gt;

&lt;p&gt;解决的办法，便是“编译期” 构造。让 sin_table 直接存放的是已经从 double 类型构造完毕的对象。这样这个 sin_table 就即不会占用内存，也不用在运行时初始化一遍。&lt;/p&gt;

&lt;p&gt;进行编译期构造，我立马想到了2种方式。&lt;/p&gt;

&lt;p&gt;其一，便是将已经完成double到fix point转换的数据，存入 byte 数组，而后运行时使用 reinterpret_cast 强制转换。
这个办法，需要我编写一个脚本，脚本在 项目编译 期间执行，将 sin_table 转换为一个字节数组后写入 .c 文件。&lt;/p&gt;

&lt;p&gt;其二，便是尝试让编译器自己完成“编译期构造”&lt;/p&gt;

&lt;p&gt;第一种做法，实在是过于无趣，虽然这种做法乃是各大单片机佬乐此不疲的通常做法。但那也不过是因为 C 语言过于劣质。他们即便想用编译期构造，那也得编译器支持才行。&lt;/p&gt;

&lt;p&gt;第二种做法，粗看一下似乎可行。细细思考，发现非常困难。深入研究后发现，如此简单。&lt;/p&gt;

&lt;p&gt;是的，深入研究后，发现异常简单。便是将构造函数添加一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;constexpr&lt;/code&gt; 关键字足矣。&lt;/p&gt;

&lt;p&gt;于是，float_number 的构造函数，就从原来的&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;k&quot;&gt;explicit&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;float_number_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scaled_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;static_cast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;number_holder_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SCALE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)){}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;改成了&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;k&quot;&gt;explicit&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;constexpr&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;float_number_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scaled_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;static_cast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;number_holder_t&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SCALE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)){}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;而后重新编译，发现固件的 .data + .bss 段，使用的内存就从 接近 3kb 降低到了 705B. 而 .rodata 段，还因为 float_number 比 double 的体积减少，也减少了占用。&lt;/p&gt;

&lt;p&gt;至此，一个关键字引发的超级优化落幕。不仅仅如此，因为之前 EG6832 的内存不足导致只能使用 45 表项，如今也被我改成了同 ESP32 一样，使用奢侈的 1800 表项。sin 分辨率从 2 度提高到了 0.05 度。使得电机运转更为平滑。&lt;/p&gt;

&lt;h1 id=&quot;c-果然是编写资源受限型代码的神器&quot;&gt;c++ 果然是编写资源受限型代码的神器。&lt;/h1&gt;
</content>
 </entry>
 
 <entry>
   <title>让 PlatformIO 支持 EG6832</title>
   <link href="https://microcai.org/2024/01/17/add-eg6832-to-platformio.html"/>
   <updated>2024-01-17T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/01/17/add-eg6832-to-platformio</id>
   <content type="html">&lt;p&gt;近来做变频器，也研究了不少mcu。让我的代码移植到了 ESP32, ESP32S3, ESP32C3, RP2040, STM32F405, AT32F415, AT32F421。
这些 mcu 都有一个共同点： platformio 支持。其中 ESP32, ESP32S3, ESP32C3, RP2040, STM32F405 受 platform 官方支持，而
AT32F415, AT32F421 则有 雅特力 的官方 github 上开放的 platformio 支持包。&lt;/p&gt;

&lt;p&gt;但是，如果有一款 MCU 不被 platformio 支持，官方也不给支持包，咋办？&lt;/p&gt;

&lt;p&gt;作为一个程序员，如果IDE的底层逻辑不了解透彻，寝食难安。&lt;/p&gt;

&lt;p&gt;好在我不是专业的嵌入式开发者。不了解就不了解吧。&lt;/p&gt;

&lt;p&gt;直到，我准备量产我的变频器。在 “得算力者得天下” 作者的帮助下，我找到了位于宁波的一家电器厂。它为杂牌落地扇贴牌生产。我把我的变频器介绍给了它的老板。&lt;/p&gt;

&lt;p&gt;结果碰了一鼻子灰。主要是我的变频器成本太高了。高达25元（那是按在嘉立创进行1000张小批量生产的报价）（虽然市面上最廉价的三相变频器也得三位数购买）！
低端制造业就是这样，对成本斤斤计较。虽然我25元的变频器，可以支持到 200w 的电机。但是人家电风扇只需要 20w。他现在要改用 直流电机。
直流电机的成本比变频器+交流电机的成本更低。虽然60w 的直流电机很贵，但是电风扇只要20w 就行了。 20w 的直流电机就很便宜了。所以 60w 的交流电机+变频器的方案，在12w 直流电机面前毫无招架之力。
后来我准备走的时候，遇到了给他供应12w直流电机的供应商。我向他咨询了直流电机的成本。意外的发现，他的直流电机上的控制器，成本只要6元！&lt;/p&gt;

&lt;h1 id=&quot;6-元&quot;&gt;6 元！！！&lt;/h1&gt;

&lt;p&gt;这是什么神仙成本。&lt;/p&gt;

&lt;p&gt;其实直流电机的控制器在硬件上 = 变频器+转子位置传感器。所以是不可能比变频器更便宜的。所以，其实我的变频器也可以跑在那种 6 元的硬件上。&lt;/p&gt;

&lt;p&gt;但是，这种 6 元的硬件，其 MCU 几乎肯定必然，都是非主流 MCU.&lt;/p&gt;

&lt;p&gt;经过我的一番搜索，我找到了2种可以实现6元控制器的芯片方案。一种是 8051 内核+ foc 协处理器 的 FU6832, 还有一种是 arm 核的 EG6832。&lt;/p&gt;

&lt;p&gt;名字都叫 6832 ，看来这俩果然是要抢生意。&lt;/p&gt;

&lt;p&gt;这俩有个共同点：都没有详细资料。都需要直接联系原厂的销售代表去索取样品和资料。&lt;/p&gt;

&lt;p&gt;EG6832 对我这种没有专门的采购主管的个人开发者来说要安心点，因为官方直接开了个淘宝店卖样品。于是下单购买，然后淘宝的客服给了我一个压缩包。然后就没有更多的技术支持了。&lt;/p&gt;

&lt;p&gt;压缩包里是一个水泵控制器的 DEMO。虽然是个 DEMO, 但是好在这个 DEMO 是属于 “把依赖的库全带上” 的那种方案。在 third_party/* 目录里塞满各种第三方库的 cpper 会心一笑 :)&lt;/p&gt;

&lt;p&gt;最为关键的是，他这个 demo 带上了 HAL 库。（他可没那么好心的把电机库送人。电机库是个 .lib 静态库，只见 main.c 里对电机库的调用，不见电机库的代码的啦！这世上好心的把电机库送出去的，只有 simplefoc 和 本杰明VESC。 连我都不开源，嘿嘿！）&lt;/p&gt;

&lt;p&gt;在mcu里，所谓 HAL 库，就是把 &lt;strong&gt;操作硬件寄存器&lt;/strong&gt; 实现某种功能给编写为一系列的 “C函数”。比如，在 AT32 的 HAL 库里，把修改 pwm 占空比所需的硬件寄存器操作，给编写成了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tmr_channel_value_set&lt;/code&gt; 这么一个 C API。修改 pwm 频率呢，则使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tmr_period_value_set&lt;/code&gt; 就实现了。我不需要操心具体硬件的寄存器操作，更不需要知道寄存器地址。
而 ESP32 则是把同样的功能封装为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mcpwm_comparator_set_compare_value&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mcpwm_timer_set_period&lt;/code&gt;
雅特力和ESP32的芯片手册，不仅仅会给出寄存器的说明，也同时会介绍 HAL 库的 API 如何使用。&lt;/p&gt;

&lt;p&gt;但是，EG6832 这个就惨了！ 他的芯片手册，居然只有硬件寄存器的描述，而 HAL 库则没有任何文档。没有就没有吧，hal库毕竟给了源码。对着手册的寄存器介绍，再看看源码，应该就知道 api 是干嘛的了。&lt;/p&gt;

&lt;h1 id=&quot;编写-platformio-的支持包&quot;&gt;编写 platformio 的支持包&lt;/h1&gt;

&lt;p&gt;既然有了 hal 库，那理论上来说，就能为 platformio 添加 eg6832 的支持了。&lt;/p&gt;

&lt;p&gt;platformio 里，对一款 MCU 的支持，需要编写2个包。其1 为 platform-xxx 包，其二为 framework-xxx 包。&lt;/p&gt;

&lt;p&gt;platform-xxx 包的任务是编译项目。framework-xxx 包的任务是提供 HAL 库。（以及，更关键的，提供 main 执行之前的代码。）&lt;/p&gt;

&lt;p&gt;好在，雅特力的官方给了我一个很好的 example。于是一顿操作，&lt;a href=&quot;https://github.com/microcai/platform-egmicros&quot;&gt;platform-egmicros&lt;/a&gt; 就诞生了。&lt;/p&gt;

&lt;p&gt;只要在 platform.ini 里写上&lt;/p&gt;

&lt;div class=&quot;language-ini highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;py&quot;&gt;platform&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;https://github.com/microcai/platform-egmicros&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;board&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;genericEG6832&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;framework&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;eg32firmlib&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;就可以开始为 EG6832 写代码啦！&lt;/p&gt;

&lt;p&gt;platform-egmicros 包其实主要的任务就是编译framework-xxx 包。编译的脚本在 &lt;a href=&quot;https://github.com/microcai/platform-egmicros/blob/master/builder/frameworks/eg32firmlib.py&quot;&gt;builder/frameworks/eg32firmlib.py&lt;/a&gt; 里。&lt;/p&gt;

&lt;p&gt;因为按 platformio 的思路，一个 platform 可以使用各种 framework. 比如 裸环境，arduino 环境， freertos 环境。等等。&lt;/p&gt;

&lt;p&gt;不同的环境，就调用 platform-xxx 包里的 builder/{framework}.py 脚本。&lt;/p&gt;

&lt;p&gt;把 EGmicro 官方给的 demo 里的 HAL 代码扣出来，我做了个 framework-eg32firmlib 库。于是一顿编译后， .pio/build/eg6832/firmware.bin 文件顺利诞生！&lt;/p&gt;

&lt;p&gt;HAL 库是如何启动到 main() 的？ 这个问题我就下面再讲解。&lt;/p&gt;

&lt;p&gt;然后，这些都是板子到货之前的工作了。&lt;/p&gt;

&lt;h1 id=&quot;下载遇到了问题&quot;&gt;下载遇到了问题&lt;/h1&gt;

&lt;p&gt;EGmicro 官方给的 Demo 包，里面还包括了一个给 KEIL5 IDE 使用的一个 .pack 文件。我没有用过 keil，不知道这个文件具体的作用。
但是 7z 能打开它，发现是个压缩包。于是解压之。解压后的文件，里面也有一个 HAL 库。显然，将 HAL 库 复制 到 工程里，看来是 keil 的标准做法（而且是极其愚蠢的做法）了。&lt;/p&gt;

&lt;p&gt;其中还有个 EG32M0xx.svd 文件，这个文件看来是调试用的。openocd 需要用到它。
然后还有一个百思不得其解的文件 EG32M0xx_EFLASH_PROG.FLM&lt;/p&gt;

&lt;p&gt;这个文件是干嘛用的呢？&lt;/p&gt;

&lt;h1 id=&quot;openocd-不支持-eg6832&quot;&gt;openocd 不支持 eg6832&lt;/h1&gt;

&lt;p&gt;终于等了数天，板子到了。兴冲冲的接上 swd 调试器，然后 openocd 命令一敲….&lt;/p&gt;

&lt;p&gt;傻眼了。 openocd 不支持。还得写 target 文件。于是依葫芦画瓢写了一个。
一跑，傻眼了， openocd 提示 flash 算法不支持。&lt;/p&gt;

&lt;p&gt;啊？？？ flash 算法？ 那是什么鬼。&lt;/p&gt;

&lt;p&gt;==，EG32M0xx_EFLASH_PROG.FLM 这个文件是不是就是所谓的 flash 算法文件？ 所以 keil 才能对 eg6832 编程？是因为官方的 pack 文件里打包了这个 FLM 文件？&lt;/p&gt;

&lt;p&gt;于是带着疑问研究了 openocd 的文档，一无所获。 只知道 openocd 的 flash 算法，是它自己支持的，不需要也不能靠载入某个 FLM 文件。&lt;/p&gt;

&lt;p&gt;但是意外的发现了一个叫 pyocd 的项目。它虽然说不支持载入 FLM 文件，但是它能把 FLM 文件变成 py 代码，然后整合进去。。。 它就支持了某款 mcu 咯。&lt;/p&gt;

&lt;h1 id=&quot;修改-pyocd搞定了-eg6832-的程序下载&quot;&gt;修改 pyocd，搞定了 EG6832 的程序下载&lt;/h1&gt;

&lt;p&gt;python 真 鸡儿 是个垃圾语言。修改 pyocd 本来应该是一个很简单的事情。结果因为py的垃圾 cache 机制，修改的 .py 文件死活没生效。
真是个垃圾语言。然后修改完毕，跑通！&lt;/p&gt;

&lt;p&gt;顺便给官方发了个 PR 。也不知道响应时间如何。&lt;a href=&quot;https://github.com/microcai/pyOCD&quot;&gt;pyOCD 改版仓库&lt;/a&gt; 有需要的自取。&lt;/p&gt;

&lt;h1 id=&quot;arm-mcu-hal-之-启动&quot;&gt;ARM MCU HAL 之 启动&lt;/h1&gt;

&lt;p&gt;HAL 库主要做两件事： 1. 将寄存器操作抽象为具体的 C 函数名。方便开发和移植。2. 启动 main()&lt;/p&gt;

&lt;p&gt;第一件事其实很好理解，因为 arm mcu 基本上采用的是 MMIO, 因此每个外设包含若干寄存器。每个寄存器就都分配一个内存地址。这样每个外设就会占用一片连续的地址。这片连续的地址，就正好编写为一个 C 结构体。结构体里每个成员变量都对应一个寄存器。然后再使用C语言的强制转换，将这个外设的寄存器首地址，直接强转为这个外设对应的结构体。
比如 定时器，在 HAL 里是一个  struct TimerDef, 然后定义 TIMER1 =  (TimerDef*) ( 0x40020xxx ), TIMER2 =  (TimerDef*) ( 0x40021xxx )。
这样操作寄存器，就简化为了操作 TIMER1-&amp;gt;xxx， TIMER2-&amp;gt;xxx 变量。&lt;/p&gt;

&lt;p&gt;第二个事，就开始难以瞬间秒懂了。启动 main 之前，hal 库都干了啥？&lt;/p&gt;

&lt;p&gt;最关键的是， mcu 代码是没有 OS 的，所以，hal 库，是需要知道内存布局才能工作的。不然 malloc() 调用要如何实现呢？&lt;/p&gt;

&lt;p&gt;原来，这部分功能，是由 链接器 脚本实现的。在 hal 库里，会携带对应 mcu 的 链接器 脚本。由链接期脚本来保证，入口代码一定是放在 Flash 的第一个字节。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-ldscript&quot;&gt;
/* Entry Point */
ENTRY(Reset_Handler)

/* Highest address of the user mode stack */
_estack = 0x20018000;    /* end of RAM */

/* Generate a link error if heap and stack don&apos;t fit into RAM */
_Min_Heap_Size = 0x200;      /* required amount of heap  */
_Min_Stack_Size = 0x400; /* required amount of stack */

/* Specify the memory areas */
MEMORY
{
FLASH (rx)      : ORIGIN = 0x08000000, LENGTH = 1000K
RAM (xrw)       : ORIGIN = 0x20000000, LENGTH = 96K
}

/* Define output sections */
SECTIONS
{
  /* The startup code goes first into FLASH */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector)) /* Startup code */
    . = ALIGN(4);
  } &amp;gt;FLASH

......

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里摘录一个 mcu 的 链接期脚本。ENTRY(Reset_Handler) 定义了入口点。也就是整个代码，从 void Reset_Handler() 开始执行。
注意了，接下来一行是最关键的代码，任何代码都没有这行关键 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_estack = 0x20018000;    /* end of RAM */&lt;/code&gt;
这个代码，其实是定义了内存的顶部地址。在 arm mcu 里，内存会被划分为 [静态分配的变量][堆][栈] 三个区域。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RAM (xrw)       : ORIGIN = 0x20000000, LENGTH = 96K&lt;/code&gt; 这个代码，指示了链接期在放置 .data 区的时候，地址从 0x20000000 开始，最多可以放96k.&lt;/p&gt;

&lt;p&gt;最终，ldscript 里输出的 &lt;strong&gt;bss_end&lt;/strong&gt; 这个变量，就变成了 libc 里的堆的起点。堆向栈的方向增长。
这样， libc 就获得了内存布局。&lt;/p&gt;

&lt;p&gt;启动代码，也得已布局在 0x08000000 开始的地方。由于该 mcu 将 flash 映射为 0x08000000-0x08100000，rodata 和 text 也得以顺利的摆放到正确位置。&lt;/p&gt;

&lt;p&gt;为了让 启动代码确确实实的放置在 0x08000000 的地方，ldscript 特意首先布局 .isr_vector 到 &amp;gt;FLASH. 而 HAL 里，唯一一个定义为 .isr_vector section名字
的代码，就是一个 ISR 数组。数组的第一个是 栈顶， 代码如下&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-C&quot;&gt;/* Vector table */
__attribute__((section(&quot;.isr_vector&quot;)))
const FUNC_IRQ __vector_handlers[] = {

    _estack,
    Reset_Handler,              // Reset Handler
    NMI_Handler,                // NMI Handler
    HardFault_Handler,          // Hard Fault Handler
    0,                          // Reserved
    0,                          // Reserved
    0,                          // Reserved
    0,                          // Reserved
    0,                          // Reserved
    0,                          // Reserved
    0,                          // Reserved

.....


};

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;定义了一个叫 __vector_handlers 的数组，数组的第一个元素，就必然会被放入 0x08000000，而这个地址，按 mcu 的手册，芯片上电后，会自动从 0x08000000 载入栈顶，0x08000004 载入指令。&lt;/p&gt;

&lt;p&gt;0x08000004 恰恰就是 __vector_handlers 的第二个元素，也就是 Reset_Handler 的地址。
0x08000000 恰恰就是 ldscript 里设置的 _estack。&lt;/p&gt;

&lt;p&gt;于是这么安排下，cpu 上电，就自动运行 Reset_Handler 了，而且连 栈顶都设置好了。意味着 Reset_Handler 可以直接由 C 语言编写。无需汇编指令。&lt;/p&gt;

&lt;p&gt;Reset_Handler 的做法，其实就是初始化 libc , 然后执行 main()。&lt;/p&gt;

&lt;h1 id=&quot;总结&quot;&gt;总结&lt;/h1&gt;

&lt;p&gt;mcu 的启动代码，需要 ld script 的搭配。ld script 巧妙的设定了 mcu 规定的指定地址的内容。而 内存大小， FLASH 大小，这些信息都是在 ldscript 里设置的。
ldscript 设置的变量，可以在代码里，按全局变量的方式直接使用。这样就完成了将 mcu 的内存布局信息传递给了 libc. 使得 mcu 里，脱离 OS 也可以继续使用 malloc/free 函数进行内存动态分配。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>PCB和芯片</title>
   <link href="https://microcai.org/2024/01/02/pcb-and-ic-manufactoring.html"/>
   <updated>2024-01-02T00:00:00+00:00</updated>
   <id>https://microcai.org/2024/01/02/pcb-and-ic-manufactoring</id>
   <content type="html">&lt;p&gt;进来做变频器，画了不少 PCB 板子。突然发现画 PCB 和芯片设计，有极大的共同点。&lt;/p&gt;

&lt;h1 id=&quot;先说走线和分层&quot;&gt;先说走线和分层&lt;/h1&gt;

&lt;p&gt;现时 PCB 上的元件，主要以表面贴片为主。元件浮于PCB表面。特别是，我为了节约打样成本，使用的都是单面贴片工艺。更像芯片了。元件只集中于pcb的一个表面。然后线路在 pcb 和 pcb 的另一面。&lt;/p&gt;

&lt;p&gt;如果把电路板倒过来，让贴片元件朝下当“底面”，那就是 元件为底层，上层为走线层。&lt;/p&gt;

&lt;p&gt;这和 IC 的情况是极其相似的。集成电路里，蚀刻法制作的元件，都处于硅的表面。然后是十数层走线层。拿单面贴片的4层板举例，就分了元件层，和4个走线层。&lt;/p&gt;

&lt;h1 id=&quot;接着说封装&quot;&gt;接着说封装&lt;/h1&gt;

&lt;p&gt;在 PCB 上放置一个元件的时候，EDA 并不清楚元件的内部走线。EDA 要做的是，在 PCB 上留下一个“区域” 放置这个元件，以及连接这个元件的若干“焊盘”。&lt;/p&gt;

&lt;p&gt;在 IC 设计领域，也有常说的 “硬IP核”。硬IP核在 EDA 里，也是一个固定大小的，内部情况不透明的“区域”，以及连接这个IP核的若干“输入输出”。&lt;/p&gt;

&lt;p&gt;甚至。连最基础的“MOSFET”，在芯片的 EDA 里，其实也是“封装”。设计者只能选择将这个 MOSFET 放置此处。有3个引脚。但是，这个 MOSFET 的内部结构，是看不到的。这个和在 PCB 上放置最普通的贴片MOS的情况是一模一样的。&lt;/p&gt;

&lt;p&gt;在 PCB 的  EDA 软件里，各种元件都有“封装”，而在芯片的EDA软件里，各种元件，也有封装。
设计pcb既可以选择分立元件，也可以使用各种IC。放在芯片设计领域，就是既可以选择软IP也可以选择硬IP。&lt;/p&gt;

&lt;p&gt;软IP, 在，PCB的 EDA软件里，叫“原理图库”。画原理图的时候可以直接引用。但是转PCB的时候，会把原理图里引入的各种元件引入，你可以重新安排内部元件。而且还可以随时修改原理图。&lt;/p&gt;

&lt;p&gt;在芯片EDA里，软IP, 也不再是一个裸晶上占用固定位置的“封装”，而是会和你自己写的代码一起编译。&lt;/p&gt;

&lt;h1 id=&quot;接着说原理图&quot;&gt;接着说原理图&lt;/h1&gt;

&lt;p&gt;要画 PCB， 先出原理图。原理图上记录里的是 PCB 的原理电路。而芯片，也就集成电路，其实和印刷电路是一回事。只不过一个是毫米级工艺，一个是纳米级工艺。&lt;/p&gt;

&lt;p&gt;既是电路，一样要原理图。&lt;/p&gt;

&lt;p&gt;但是，芯片和 pcb 的最大区别，不是工艺，而是规模。&lt;/p&gt;

&lt;p&gt;一个PCB上，有数百元件，已经算复杂了。&lt;/p&gt;

&lt;p&gt;但是一个芯片，百万元件都是小打小闹。如今最尖端的芯片，上有百亿元件。&lt;/p&gt;

&lt;p&gt;PCB尚可以手绘。设计集成电路，绝无手绘的可能。&lt;/p&gt;

&lt;p&gt;因此，PCB的原理图，可以是一个电路图。
但是，芯片的原理图，就绝无可能是一个电路图。可以说，这个电路图，只能是某种更合适的描述方法的中间产物。&lt;/p&gt;

&lt;p&gt;这个更何时的描述方法，就叫硬件设计语言。而且要经过两次编译转换，才能转换为如pcb那样的原理图。而且这个原理图，无法为人所读。&lt;/p&gt;

&lt;p&gt;硬件设计语言，描述的是硬件的逻辑功能。经过编译器编译后，转变为能实现这个逻辑功能的“门电路”。门电路，是数字电路的基础。正如模拟电路的基础是运放。
但是，不管是门电路还是运放，都不是单一基础元件能实现的。实现一个门电路，需要数个nmos和pmos组成。因为电路里既有nmos又有pmos, 所以又称为互补mos。这也是 CMOS 名称的由来。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;PS：一个运放也是需要数个 NPN型 BJT 和 PNP型 BJT 实现。所以模拟芯片的工艺，又可以叫CBJT？&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;到编译为门电路这一步，芯片的功能设计就完成了。可以试着使用一种叫“可编程门电路阵列”的辅助工具进行调试了。&lt;/p&gt;

&lt;p&gt;只所以在设计阶段只到门电路，是因为接下来仿真的成本直线上升了。门电路仿真，仿真软件最低只仿真到门电路这一级别。而且对里面电流是不模拟的，只模拟逻辑上的 0 和 1。&lt;/p&gt;

&lt;p&gt;但是，从门电路，到 CMOS, 就要面对器件的非数字性了。要考虑信号的上升时间，下降时间，传播时间了。模拟的难度直线上升。特别是现在芯片动辄包含千万门电路，也许动用超算都无法进行完整模拟了。&lt;/p&gt;

&lt;h1 id=&quot;说布线&quot;&gt;说布线&lt;/h1&gt;

&lt;p&gt;从芯片设计语言，再到门电路阵列，再到 MOS 互联电路 （PCB原理图）。接下来要做的，就和 PCB 设计如出一辙了。&lt;/p&gt;

&lt;p&gt;只不过，扩大了的规模，成百上千倍的提高了工作量和难度。&lt;/p&gt;

&lt;p&gt;不过，也有一个好消息。就是数字电路的规模虽大，但是很多netlist其实看起来是简单重复的。
这种简单重复的电路连接，就可以考虑让电脑自动完成布线。&lt;/p&gt;

&lt;p&gt;PCB设计里，虽然有自动布线。但是多数布线还是依赖手工。就是因为 pcb 里很少会出现单一规律的简单重复连线。导致自动布线总是不如意。&lt;/p&gt;

&lt;p&gt;而芯片，且不说这种简单重复的电路，自动布线是得心应手。就算有些不如意，人又如何检查的出来呢？&lt;/p&gt;

&lt;p&gt;正如软件工程师有时候会检查下特定代码经过编译器后会产生如何的机器指令。但是多数时候，他们也只能选择“相信编译器”。然后祈祷编译后的代码能正常运行。&lt;/p&gt;

&lt;p&gt;所以，芯片工程师，也只能选择相信 EDA 给出的结果。然后祈祷流片回来的芯片能正常工作。&lt;/p&gt;

&lt;h1 id=&quot;前后端分工&quot;&gt;前后端分工&lt;/h1&gt;

&lt;p&gt;在PCB行业，pcb的设计分为“原理图绘制” 和 “PCB布线” 两种任务。两个任务可以一个人完成，也可以拆分。&lt;/p&gt;

&lt;p&gt;同样的，芯片制造业，也分芯片前端和芯片后端。&lt;/p&gt;

&lt;p&gt;原理图绘制，面对的主要是“功能逻辑”。而在 PCB布线阶段，就要考虑和 PCB加工厂的合作了。
因为PCB加工厂的能力，决定了PCB布线的时候能否随心所欲。比如 PCB加工厂无法加工低于 0.1mm 的线，那绘制的时候，就必须要保证所有的走线都要比 0.1mm 宽。比如PCB加工厂无法提供“盘中孔”工艺，那pcb布线的时候就只能选择扇出后打孔。而不能直接在焊盘上打孔换层。pcb加工厂只能最多加工8层线路板。那设计的时候就只能多飞线，勤换层了。&lt;/p&gt;

&lt;p&gt;PCB的工艺还会影响线路的“寄生参数”。反过来又要影响 pcb 的设计。&lt;/p&gt;

&lt;p&gt;同样的，在芯片制造业，芯片代工厂的制造能力同样也会限制芯片后端工程师的发挥。工程师只能在工厂的能力范围内进行设计。&lt;/p&gt;

&lt;p&gt;通常来说，芯片工厂只会宣传“特征工艺”的参数。但是影响电流设计的参数千千万万。可不是一个纳米就能描述的清楚的。在高速 PCB设计领域，PCB工艺对设计最直接影响的地方，就是线路的寄生电容和寄生电感。这决定了pcb布线的特征阻抗。而高速信号对阻抗异常敏感。&lt;/p&gt;

&lt;p&gt;而且 pcb 工厂只加工线路，不制造元件。而芯片代工厂不仅仅加工线路，也制造元件。所以代工厂的工艺除了寄生电容寄生电感，还包括其制造的 MOS 的各项参数。&lt;/p&gt;

&lt;h1 id=&quot;结论&quot;&gt;结论&lt;/h1&gt;

&lt;p&gt;所以造芯片和造PCB其实从根上说，是一回事。只不过 芯片倾向于简单电路的超大规模重复叠加。因此更容易实现自动化。而更大的规模也导致不得不依赖自动化。 pcb 的规模一直都维持在较小的范围。因为复杂的逻辑都倾向于使用IC代劳。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>三相的恒功率优势</title>
   <link href="https://microcai.org/2023/12/27/3pharse-talk-again.html"/>
   <updated>2023-12-27T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/12/27/3pharse-talk-again</id>
   <content type="html">&lt;p&gt;在 &lt;a href=&quot;/2022/02/14/ev-charging-and-3phase-balance.html&quot;&gt;这篇&lt;/a&gt; 文章里，我曾经说，三相电是恒功率的。&lt;/p&gt;

&lt;p&gt;现在我用一张更直观的图演示这个结论。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/3pharse_nice.jpg&quot; alt=&quot;三相的恒功率图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;因为电压*电流=功率。所以图片里是使用的纯电阻负载的功率。&lt;/p&gt;

&lt;p&gt;每一相的功率，总是会不断的从 0 到最大，然后从最大到 0.  所以这就是单相电娘胎里带来的劣根性: 每秒钟有100次的功率变化。而且最低是无功率输出。&lt;/p&gt;

&lt;p&gt;但是，如果是三相，情况就发生了变化。虽然每相还是会不断的变化输出功率，但是三者的和却是一个定值。&lt;/p&gt;

&lt;p&gt;如果是电流有一定滞后的负载，比如电机，则这个图片也能说明&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/3pharse_nice2.png&quot; alt=&quot;三相电机的恒功率图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;电流的相位即使滞后，总功率还是恒定的。&lt;/p&gt;

&lt;p&gt;这种总功率恒定的特点，就使得三相电驱动的电机，始终能保持一个恒定的扭矩。
而不会像单相电机那样，扭矩每秒出现100次波动。扭矩每秒出现100次波动，相当与6000转的二冲程发动机的扭矩波动。相当于3000转的4缸发动机的扭矩波动。那是相当的振麻了。&lt;/p&gt;

&lt;p&gt;即使不是电机，也能从恒功率特性中获取优势。
比如，开关电源不再需要一个巨大的储能电容。
当然，一个小容量的滤波电容还是必要的。毕竟需要滤除 pwm 导致的高频谐波。&lt;/p&gt;

&lt;p&gt;– 更新&lt;/p&gt;

&lt;p&gt;弄了个视频来展示三相恒功率和单相脉动功率导致的电机扭矩差异&lt;/p&gt;

&lt;iframe src=&quot;//player.bilibili.com/player.html?bvid=BV1Fc4118794&amp;amp;page=1&quot; scrolling=&quot;no&quot; border=&quot;0&quot; frameborder=&quot;no&quot; framespacing=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt; &lt;/iframe&gt;

</content>
 </entry>
 
 <entry>
   <title>foc速成班</title>
   <link href="https://microcai.org/2023/12/22/clarke-transform-is-not-essential.html"/>
   <updated>2023-12-22T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/12/22/clarke-transform-is-not-essential</id>
   <content type="html">&lt;h1 id=&quot;前言&quot;&gt;前言&lt;/h1&gt;

&lt;p&gt;在我开始研究 foc 的时候，所有的书籍都在介绍 park 变换和 clarke 变换。有一种魔力告诉你，没这俩变换，就写不成 foc.&lt;/p&gt;

&lt;p&gt;然而事实的真相并不是如此。&lt;/p&gt;

&lt;h2 id=&quot;foc-要控制什么&quot;&gt;foc 要控制什么&lt;/h2&gt;

&lt;p&gt;首先得了解，在 foc 发明前，电机是如何控制的？ foc 发明以前，无刷电机采取的是六步换向法。电机每旋转60度，控制器就要进行一次“电子换向”。控制器输出的，是同一时刻
只有2条线有输出的方波。&lt;/p&gt;

&lt;p&gt;由此，在方波时代，电机控制器要控制的无非就是方波的电压。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;真的是这样的吗？&lt;/p&gt;

&lt;p&gt;事实上，经常玩航模的童鞋就知道。航模的电调，有一个参数叫“换向提前角”。电调会在本不是换向的时间提前换向。&lt;em&gt;这个提前的时间点，从时间上来说自然是和转速有关。从角度上来说，就和转速无关了。所以叫提前角，而不是提前时间。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;为何要提前换向呢？转子还没到该换向的位置，为何要提前换向呢？&lt;/p&gt;

&lt;p&gt;如果这个问题没有搞明白，是无法理解 foc 的。&lt;/p&gt;

&lt;p&gt;foc 之所以需要电流采样，本质上就是为了确定提前换向角。而不是像航模的电调那样还得人工配置。&lt;/p&gt;

&lt;p&gt;为啥要提前换向呢？&lt;/p&gt;

&lt;p&gt;因为磁场是电流建立的，不是电压建立的。而绕组是一个电感。电感的电流会滞后电压。准时换向，不过是让电压和转子呈 90度角度。而电流滞后，会导致电流并不和转子呈90度，也就是磁场没有和转子呈90度夹角，也就是扭矩没有最大化发挥。&lt;/p&gt;

&lt;p&gt;要最大化发挥电机的扭矩，要磁场垂直于转子，电压就必须有一定的提前量。这个提前量，就只能通过对电流的采样获取。&lt;/p&gt;

&lt;p&gt;所以， foc 的目的，就是让定子磁场垂直于转子磁场。而电流的采样，就是为了获得电流对电压的滞后角，从而对电压角进行提前。&lt;/p&gt;

&lt;h1 id=&quot;前人为啥要进行-parkclark-变换&quot;&gt;前人为啥要进行 park/clark 变换&lt;/h1&gt;

&lt;p&gt;教材上说，是为了将三相变两相。因为两相少一个变量，好控制。&lt;/p&gt;

&lt;p&gt;其实那都是教材不懂装懂瞎说的。三相电流本来就只有2个属性。幅值和相位。这个幅值，说的是他完整周期内的最大值。&lt;/p&gt;

&lt;p&gt;但是，最大值，一个周期里只出现2次。也就是说，在某个采样的瞬间，你拿到的，大概率都只是中间的某个值。难道要连续采样一个完整周期才才能确定最大值吗？&lt;/p&gt;

&lt;p&gt;不能。&lt;/p&gt;

&lt;p&gt;那如何能在采样的瞬间，就能直接知晓最大值和相位呢？&lt;/p&gt;

&lt;p&gt;前人的答案之一，就是 逆 clark 变换 + 逆 park 变换。&lt;/p&gt;

&lt;h2 id=&quot;三相坐标系里的角度描述很困难&quot;&gt;三相坐标系里的角度描述很困难&lt;/h2&gt;

&lt;p&gt;因为我们的数学工具，对角度的描述，是在平面直角坐标系里的。三相电，其实是在平面三维坐标系里的。如果你有数学工具可以直接在这种平面三维坐标系里工作，那就不需要进行变换。&lt;/p&gt;

&lt;p&gt;但是，如果我们将这个变换，限定在一个模块呢？ 只限定在电流采样模块。&lt;/p&gt;

&lt;p&gt;电流采样模块，只对上层输出两个参数， 电流的交流幅值，和当前的相位角。&lt;/p&gt;

&lt;p&gt;那么，整个电机的控制代码，就不会再有任何一个地方需要 park/clark 变换. 而且电流采样那边，只进行一次反clarke 变换。而这个反 clarke 变换，就只是三相瞬时电流转幅值+相角描述的一个转换。&lt;/p&gt;

&lt;p&gt;那么整个电机控制流程逻辑，就会变得异常清晰明白。不会有反直觉的变换掺杂其中。&lt;/p&gt;

&lt;p&gt;甚至，电流采样是 foc 达到最大效率的一个手段。没有电流采样，不过是效率不能最佳化。所以这个电流采样甚至不是初期必修课。&lt;/p&gt;

&lt;h2 id=&quot;所以前人是为了自己软件工程上的考虑&quot;&gt;所以前人是为了自己软件工程上的考虑&lt;/h2&gt;

&lt;p&gt;前人是为自己软件实现方便上的考虑。既然电流采样这边是必须要进行变换。否则无法获得电流相角。那就索性其他地方也全部改成二维坐标系。
而前人的这个决定，被后人东施效颦，只敢模仿，不敢超越。&lt;/p&gt;

&lt;h1 id=&quot;先抛开电流采样&quot;&gt;先抛开电流采样&lt;/h1&gt;

&lt;p&gt;如果不进行电流采样，foc 的算法如何控制呢？&lt;/p&gt;

&lt;p&gt;算法的输入有2个： 期望的扭矩值，当前转子位置。 输出有3个： 分别是三个相位的 pwm 占空比。&lt;/p&gt;

&lt;p&gt;当前的转子位置，推导出来的是 输出的电压相位值。方法是 转子位置+90度+提前角。 提前角暂取 0.&lt;/p&gt;

&lt;p&gt;根据期望的扭矩值，推算出来的是 电压幅值。这个电压幅值+电压相位值，最终就变成了 三相电的各自电压值，然后得出 pwm 占空比。&lt;/p&gt;

&lt;p&gt;那么三相电压各自的值，由电压幅值*sin(相位值) 获得。另外2相，依此落后120度。然后各自的值，和 直流母线电压的比值，就是 pwm 占空比。&lt;/p&gt;

&lt;p&gt;这个是 spwm 法。其实还有个不同的算法，可以获得三相各自的 pwm 值。这个方法其实脱胎自无刷电机方波控制的六步换向法。&lt;/p&gt;

&lt;p&gt;六步换向法实际上在驱动电机的时候，是在电压角为转子角度60度的时候，换一次向，于是电压提前转子120度。然后转子转60度后，再次换向。&lt;/p&gt;

&lt;p&gt;也就是说，定子磁场的方向是固定的6个方向。 定子磁场和转子磁场夹角缩小到60度的时候进行换向。定子磁场和转子磁场角度就会在 120度和60度之间波动，阶梯前进。
（当然，这是在不考虑电流滞后效应的情况下。）&lt;/p&gt;

&lt;p&gt;而 svpwm ，就是让磁场在 60度和120度之间来回切换，模拟出90度夹角。也就是，用6个固定的矢量，合成出一个360度无死角的矢量。这就是空间矢量合成名字的由来。&lt;/p&gt;

&lt;p&gt;进行矢量合成，是用的临近的2个矢量合成的，所以进行了6个扇区的划分。这样就能知道该用哪两个矢量进行合成。&lt;/p&gt;

&lt;p&gt;在进行合成矢量的时候，会计算出3个时间， T0 T1 和 T2 。 这三个时间，分别是 “无输出” ， 输出 距离转子最近的矢量，和稍微远点的那个矢量。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/svpwm.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如图，黑色是转子的方向，红色是希望生成的定子磁场的方向，两个绿色就是本次要调用的2条矢量。
T1 是深绿色的矢量时间，T2 是浅绿色的矢量时间。&lt;/p&gt;

&lt;p&gt;T1 和 T2 的时间之比，就决定了 红色这条合成的定子磁场方向。而 T0 时间，就决定了红色的长度。&lt;/p&gt;

&lt;p&gt;而 T1 和 T2 的时间，其实很好算，就是 红色和两条绿色线里的两个夹角的 sin 值。&lt;/p&gt;

&lt;p&gt;有了 T1 和 T2 时间，就该利用 红线所在的扇区，把矢量的时间分配，变成 mos 管的导通时间分配。&lt;/p&gt;

&lt;p&gt;于是就有了各大 foc 算法里的那个长 switch case 语句&lt;/p&gt;

&lt;p&gt;你看，在进行 svpwm 输出的时候，是不需要进行什么park变换的。就只需要考虑要输出多大幅值的交流电，要输出的相位角是多少。&lt;/p&gt;

&lt;h1 id=&quot;决定提前角&quot;&gt;决定提前角&lt;/h1&gt;

&lt;p&gt;在上面，红色是磁场方向。我们输出交流电的时候，是假定磁场方向和电压方向相同。
但是实际上，磁场方向会滞后。因为线圈有电感值，电流相位会落后电压。&lt;/p&gt;

&lt;p&gt;但是，具体会落后多少，无法提前知道。只能实时测量。而且每次测量的时候，获得的只能是某个具体一瞬间的3个电流值。&lt;/p&gt;

&lt;p&gt;比如，我测得 0.5A 0.5A -1A 三个电流值。那么如何知道，这个交流电的峰值？ 因为这个峰值，是进行电流闭环控制所必须的数据。 而相位，也是进行计算提前角的必须数据。&lt;/p&gt;

&lt;p&gt;三相交流电，其合成矢量，其实是绕着一个圆进行圆周运动。所以，其实是可以利用3矢量合成法。
就获得了最终的合成矢量。而这个合成矢量的长度，就是三相电电压的幅值。这个合成矢量的方向，就是三相电的相位角。&lt;/p&gt;

&lt;p&gt;所以，其实这里既可以直接按矢量进行计算，也可以进行 Clark 变换后，变成2维坐标，再进行 park 变换后, 获得所谓的 q 轴 和 d 轴电流。 如果 PID 控制算法的作用目标是让 Id = 0， 则最终输出的电压 Vd， 经过 反 clarke 变换后，恰好就会让电压（相比于使用90度垂直）的提前角 = 电流的滞后角。&lt;/p&gt;

&lt;p&gt;所以，既可以让 PID 算法去逼近 Id= 0 的目标，也可以算出电流相位滞后角后，直接对电压的相位角进行提前。&lt;/p&gt;

&lt;h1 id=&quot;为何前人会选择让-pid-算法去逼近-id--0&quot;&gt;为何前人会选择让 PID 算法去逼近 Id = 0&lt;/h1&gt;

&lt;p&gt;因为park提出这个算法的时候，电机控制还属于新兴行业。并没有cpu这种东西可以用来奢侈的进行控制。&lt;/p&gt;

&lt;p&gt;所以，电机输出用的 pwm 波，其实是用 纯电路，三角波和比较器输出的。
硬件电路，特别适合进行“负反馈”操作，对自己的输出进行锁定。&lt;/p&gt;

&lt;p&gt;于是，他们设计了硬件的 park 变换电路，然后用硬件电路进行 负反馈。完成锁相角的任务。
已说到锁相角，搞模拟电路的让应该非常熟悉一个词， PLL。&lt;/p&gt;

&lt;p&gt;后来把控制任务搬到软件上的时候，他们就沿用了这套控制流程。&lt;/p&gt;

&lt;p&gt;而他们搬迁的时候写的 “电机库” 就被业内程序员奉为圭臬了。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>变频器制作-第九部分之ESP32C3 也能输出svpwm</title>
   <link href="https://microcai.org/2023/12/20/esp32c3-can-drive-svpwm.html"/>
   <updated>2023-12-20T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/12/20/esp32c3-can-drive-svpwm</id>
   <content type="html">&lt;h1 id=&quot;前言&quot;&gt;前言&lt;/h1&gt;
&lt;p&gt;先看以下乐馨官方的 ESP32S3 和 ESP32C3 的对比&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/esp32s3-vs-esp32c3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;很明显，ESP32C3 缺乏 MCPWM 设备。&lt;/p&gt;

&lt;p&gt;所谓 MCPWM, 就是专门为产生svpwm设计的电路。他能产生6路互补的PWM信号，并且实现中央对齐。然后还能实现在PWM信号的特定位置产生同步事件。app据此可以在这个同步事件上实现电流采样。&lt;/p&gt;

&lt;p&gt;一度我以为 ESP32C3 是无法用于驱动电机的。&lt;/p&gt;

&lt;p&gt;直到一次偶然，我看到 SimpleFOC 的论坛里有人提到要支持 ESP32C3。我在想，这C3不是没MCPWM么？ SimpleFOC咋支持？&lt;/p&gt;

&lt;p&gt;事实是，SimpleFOC 也确实不支持 C3。但是有人回复他实现过。就用的 LEDC PWM。&lt;/p&gt;

&lt;p&gt;要知道，ESP32C3 可比 S3 便宜了一半还多。要真的能驱动电机，那我的 VVVF 变频器大业，又多了一项可选的MCU！&lt;/p&gt;

&lt;h1 id=&quot;实践&quot;&gt;实践&lt;/h1&gt;

&lt;p&gt;虽然别人有提过他用 LEDC PWM 实现了 foc。但是吧，无代码无真相。但是总归人家说可以。那我研究研究吧。于是购入 ESP32S3。&lt;/p&gt;

&lt;p&gt;等等，不是说C3吗？咋买了S3呢？因为我确信我的VVVF需要蓝牙！需要WIFI！AT32F415已经不能满足我的需求了。&lt;/p&gt;

&lt;p&gt;然后顺便买了个9.9的 ESP32C3开发板。&lt;/p&gt;

&lt;p&gt;等开发板到了，我马上研究起了 ESP32C3。虽然C3是顺便买的，但是不妨碍我先研究它。&lt;/p&gt;

&lt;p&gt;于是在项目里创建了 lib/libvfd/hal/esp32c3pwm.{hpp,cpp} 文件。&lt;/p&gt;

&lt;p&gt;着手研究 ESP32C3 里的 LEDC PWM。&lt;/p&gt;

&lt;p&gt;最后踩了无数的坑后，把他实现出来了。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
#if defined(ESP_PLATFORM)

#ifdef CONFIG_IDF_TARGET_ESP32C3

#include &quot;esp32c3pwm.hpp&quot;
#include &quot;driver/ledc.h&quot;
#include &quot;esp_err.h&quot;
#include &quot;esp_timer.h&quot;
#include &quot;freertos/FreeRTOS.h&quot;
#include &quot;freertos/task.h&quot;
#include &quot;freertos/timers.h&quot;
#include &quot;private/commons.hpp&quot;

#include &quot;os/os.hpp&quot;

#define FOC_MCPWM_TIMER_RESOLUTION_HZ 40000000 // 40MHz, 1 tick = 0.025us

#ifndef INVERT_LOW_SIDE
#define INVERT_LOW_SIDE 0
#endif

const static char* TAG = &quot;vfd&quot;;

namespace motorlib
{
	struct esp32c3pwmdriver_impl
	{
		/*
		 * Prepare and set configuration of timers
		 * that will be used by LED Controller
		 */
		ledc_timer_config_t timer_config = {
			.speed_mode		 = LEDC_LOW_SPEED_MODE, // timer mode
			.duty_resolution = LEDC_TIMER_13_BIT,	// resolution of PWM duty
			.timer_num		 = LEDC_TIMER_0,		// timer index
			.freq_hz		 = 4000,				// frequency of PWM signal
			.clk_cfg		 = LEDC_AUTO_CLK,		// Auto select the source clock
		};

		int gen_gpios[6]; // 3 GPIO pins for generator config

		// hardware variables
		int pwm_frequency;
		int pwm_period;

		esp32c3pwmdriver_impl(
			int pin_AH, int pin_AL, int pin_BH, int pin_BL, int pin_CH, int pin_CL, int PWM_freq)
			: pwm_frequency(PWM_freq)
		{
			gen_gpios[0] = pin_AH;
			gen_gpios[1] = pin_BH;
			gen_gpios[2] = pin_CH;
			gen_gpios[3] = pin_AL;
			gen_gpios[4] = pin_BL;
			gen_gpios[5] = pin_CL;

			// ledc_timer_config.duty_resolution = ledc_find_suitable_duty_resolution(LEDC_USE_XTAL_CLK, pwm_frequency);

			pwm_period = 1 &amp;lt;&amp;lt; timer_config.duty_resolution;
			// pwm_frequency = timer_config.freq_hz;

			esp_err_t ret;
			ledc_timer_rst(LEDC_LOW_SPEED_MODE, LEDC_TIMER_0);

			ret = ledc_timer_config(&amp;amp;timer_config);
			timer_config.timer_num = LEDC_TIMER_1;
			ret = ledc_timer_config(&amp;amp;timer_config);
			os::printf(&quot;ledc_channel_config return %d\n&quot;, ret);

			ledc_channel_config_t ledc_channel[] = {
				{
					.gpio_num	= pin_AH,
					.speed_mode = LEDC_LOW_SPEED_MODE,
					.channel	= LEDC_CHANNEL_0,
					.timer_sel	= LEDC_TIMER_0,
					.duty		= 1,
					.hpoint		= 0,
					.flags		= { .output_invert = 0 },
				},
				{
					.gpio_num	= pin_BH,
					.speed_mode = LEDC_LOW_SPEED_MODE,
					.channel	= LEDC_CHANNEL_1,
					.timer_sel	= LEDC_TIMER_0,
					.duty		= 1,
					.hpoint		= 0,
					.flags		= { .output_invert = 0 },
				},
				{
					.gpio_num	= pin_CH,
					.speed_mode = LEDC_LOW_SPEED_MODE,
					.channel	= LEDC_CHANNEL_2,
					.timer_sel	= LEDC_TIMER_0,
					.duty		= 1,
					.hpoint		= 0,
					.flags		= { .output_invert = 0 },
				},
				{ .gpio_num		= pin_AL,
					.speed_mode = LEDC_LOW_SPEED_MODE,
					.channel	= LEDC_CHANNEL_3,
					.timer_sel	= LEDC_TIMER_1,
					.duty		= 1,
					.hpoint		= 0,
					.flags		= { .output_invert = !INVERT_LOW_SIDE } },
				{ .gpio_num		= pin_BL,
					.speed_mode = LEDC_LOW_SPEED_MODE,
					.channel	= LEDC_CHANNEL_4,
					.timer_sel	= LEDC_TIMER_1,
					.duty		= 1,
					.hpoint		= 0,
					.flags		= { .output_invert = !INVERT_LOW_SIDE } },
				{ .gpio_num		= pin_CL,
					.speed_mode = LEDC_LOW_SPEED_MODE,
					.channel	= LEDC_CHANNEL_5,
					.timer_sel	= LEDC_TIMER_1,
					.duty		= 1,
					.hpoint		= 0,
					.flags		= { .output_invert = !INVERT_LOW_SIDE } },
			};

			for (int ch = 0; ch &amp;lt; 6; ch++)
			{
				ret = ledc_channel_config(&amp;amp;ledc_channel[ch]);
				os::printf(&quot;ledc_channel_config return %d\n&quot;, ret);
			}

			stop();
		}

		void set_duty(float_number U_a, float_number U_b, float_number U_c)
		{
			auto u_lpoint = clamp&amp;lt;int&amp;gt;(U_a * pwm_period, 0, pwm_period-1);
			auto v_lpoint = clamp&amp;lt;int&amp;gt;(U_b * pwm_period, 0, pwm_period-1);
			auto w_lpoint = clamp&amp;lt;int&amp;gt;(U_c * pwm_period, 0, pwm_period-1);

			ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, u_duty);
			ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_1, v_duty);
			ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_2, w_duty);
			ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_3, u_duty);
			ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_4, v_duty);
			ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_5, w_duty);

			ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
			ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_1);
			ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_2);
			ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_3);
			ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_4);
			ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_5);
		}

		void set_frequency(int freq)
		{
			if (freq &amp;lt;= 6000 &amp;amp;&amp;amp; freq &amp;gt;= 3000)
			{
				ledc_set_freq(LEDC_LOW_SPEED_MODE, LEDC_TIMER_0, freq);
				ledc_set_freq(LEDC_LOW_SPEED_MODE, LEDC_TIMER_1, freq);
				pwm_frequency = freq;
                esp_timer_restart(hr_timer, 1000000/freq);
			}
		}

		void stop()
		{
			ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0);
			ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_1, 0);
			ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_2, 0);
			ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_3, 0);
			ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_4, 0);
			ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_5, 0);
		}

		void start() { }

		int get_frequency() { return pwm_frequency; }

		pwmdriver::timer_fn pwm_cb;

		static void timer_cb(void* user_data)
		{
			auto _this = reinterpret_cast&amp;lt;esp32c3pwmdriver_impl*&amp;gt;(user_data);_this-&amp;gt;pwm_cb(_this-&amp;gt;pwm_frequency);
		}

		esp_timer_handle_t hr_timer;

		void link_timer(pwmdriver::timer_fn fn)
		{
			pwm_cb = fn;

			esp_timer_create_args_t timer_arg = {
				.callback			   = timer_cb,
				.arg				   = this,
				.dispatch_method	   = ESP_TIMER_TASK,
				.name				   = &quot;pwm_tmr&quot;,
				.skip_unhandled_events = true,
			};

			esp_timer_create(&amp;amp;timer_arg, &amp;amp;hr_timer);
			// 200us = 5khz pwm
			esp_timer_start_periodic(hr_timer, 200);
		}

        ~esp32c3pwmdriver_impl()
        {
            esp_timer_stop(hr_timer);
			esp_timer_delete(hr_timer);
        }
	};

	//////////////////////////////////////////////////////////////////////////////
	esp32c3pwmdriver::esp32c3pwmdriver(
		int pin_AH, int pin_AL, int pin_BH, int pin_BL, int pin_CH, int pin_CL)
	{
		impl = new esp32c3pwmdriver_impl(pin_AH, pin_AL, pin_BH, pin_BL, pin_CH, pin_CL, 4000);
	}

	esp32c3pwmdriver::~esp32c3pwmdriver()
	{
        delete impl;
    }

	// Function setting the duty cycle to the pwm pin (ex. analogWrite())
	// - BLDC driver - 6PWM setting
	// - hardware specific
	void esp32c3pwmdriver::set_duty(float_number U_a, float_number U_b, float_number U_c)
	{
		impl-&amp;gt;set_duty(U_a, U_b, U_c);
	}

	void esp32c3pwmdriver::start() { impl-&amp;gt;start(); }

	void esp32c3pwmdriver::stop() { impl-&amp;gt;stop(); }

	int esp32c3pwmdriver::get_frequency() { return impl-&amp;gt;get_frequency(); }

	void esp32c3pwmdriver::set_frequency(int f) { impl-&amp;gt;set_frequency(f); }

	void esp32c3pwmdriver::link_timer(timer_fn fn) { impl-&amp;gt;link_timer(fn); }

}

#endif // CONFIG_IDF_TARGET_ESP32C3
#endif // defined(ESP_PLATFORM)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;不同于其他平台使用 PWM 自身的定时器中断，esp32c3 上，pwm 周期更新的回调是由一个高精度定时器回调提供的。之所以不用 pwm 的回调，是因为 ledc pwm 并不会产生这样的中断——除非使用了硬件fade功能。
但电机控制岂是fade能瞎fade的？&lt;/p&gt;

&lt;h1 id=&quot;运行&quot;&gt;运行&lt;/h1&gt;

&lt;p&gt;实际上，由于实现的仓促。这个pwm输出总觉得不是很靠谱。不过当我怀着忐忑的心情，准备报废掉一个耗资300 大洋让JLC打样贴片的驱动版的时候。。。奇迹发生了。电机转起来了。&lt;/p&gt;

&lt;p&gt;不过，因为支持的pwm频率很有限，导致无法用电机播放《世上只有妈妈好了》。&lt;/p&gt;

&lt;p&gt;当然，之前的驱动板播放的音乐可以猛戳&lt;a href=&quot;https://www.bilibili.com/video/BV1ce411C79z/&quot;&gt;这里&lt;/a&gt;看。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>标准库 sin 之错</title>
   <link href="https://microcai.org/2023/12/19/why-sin-is-wrong.html"/>
   <updated>2023-12-19T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/12/19/why-sin-is-wrong</id>
   <content type="html">&lt;h1 id=&quot;标准库-sin&quot;&gt;标准库 sin()&lt;/h1&gt;

&lt;p&gt;在 C 语言里，有一个计算三角函数的函数， sin() , 其定义为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;double sin(double)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;由于 sin 的结果大概率为无理数，所以使用 浮点数为参数也未尝不可。&lt;/p&gt;

&lt;p&gt;但，我要说但是了。&lt;/p&gt;

&lt;p&gt;在工业界，不管是土木工程还是机械制造，我们都倾向于使用“角度”而不是”弧度“。&lt;/p&gt;

&lt;h1 id=&quot;转换一下很困难&quot;&gt;转换一下很困难？&lt;/h1&gt;

&lt;p&gt;如果所有的程序都在干角度转弧度这件事，那只能说明库错了。库逼迫大家用弧度，而实际上大家用角度。&lt;/p&gt;

&lt;p&gt;看下 180/pi 这个常数的定义这各大软件里有多普遍。就知道实际上大家都这用角度存储。只是这需要 sin 值的时候再转弧度调用 sin。
因为 sin 本身是一个开销很大的操作，所以很多程序员只是觉得多了一个乘法，并没有意识到 sin 的接口定义错了。&lt;/p&gt;

&lt;h1 id=&quot;为何应该用角度&quot;&gt;为何应该用角度？&lt;/h1&gt;

&lt;p&gt;因为常用角度是整数。在计算机里，弧度无法精确的表达90度，45度，30度，和60度。这都是最常用的角度。
而无法精确表达就要取近似。取近似就要丢精度。&lt;/p&gt;

&lt;p&gt;而精度误差是可以累积的。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>制作变频器-第八部分</title>
   <link href="https://microcai.org/2023/12/16/sync-modulation-corrected.html"/>
   <updated>2023-12-16T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/12/16/sync-modulation-corrected</id>
   <content type="html">&lt;h1 id=&quot;载波比得多少合适&quot;&gt;载波比得多少合适&lt;/h1&gt;

&lt;p&gt;在 &lt;a href=&quot;/2023/12/05/sync-pwm-async-pwm.html&quot;&gt;上一篇&lt;/a&gt; 文章里，我讲解了同步调制和异步调制。&lt;/p&gt;

&lt;p&gt;那时候，实现的同步调制是有问题的。主要在同步调制在载波比的计算上出了问题。&lt;/p&gt;

&lt;p&gt;原来的想法是，载波比是要能被3整除。同时还得是奇数。&lt;/p&gt;

&lt;p&gt;其实这个公式说的是，正弦波半周上的 pwm 波的数量，得是能被3整除。同时还得是奇数。
也就是说，其实整个周期的载波比，是能同时被6整除. 同时还得是2的奇数倍。&lt;/p&gt;

&lt;p&gt;于是发现了代码中的错误。进行了修正。&lt;/p&gt;

&lt;p&gt;虽然实际上靠听觉并不能发现电机的声音有任何变化。。。。。。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/spwm.png&quot; alt=&quot;正弦波pwm&quot; /&gt;&lt;/p&gt;

&lt;p&gt;在同步调制里，正弦波的频率变高。但是， 每个正弦波所包含的 pwm 方波的个数是维持不变的。而且 pwm 方波的个数，在每个半周里，必须是奇数。
这样 pwm 占空比最大的那个位置，正好对应 正弦值最大的时刻。
这样的谐波是最小的。&lt;/p&gt;

&lt;p&gt;如上图所示， 每个半周里，有5个 pwm 波。整个周期里，一共是10个 pwm。所以载波比为 10。 而 10 正好是 2 的奇数倍。&lt;/p&gt;

&lt;p&gt;而这，只是对单相正弦信号输出的要求。&lt;/p&gt;

&lt;p&gt;如果要输出三相对称正弦波信号，则载波比N，还必须同时满足能被3整除。 这样每个相的正弦波，也都在 pwm 波开始的时间里，保持对称。&lt;/p&gt;

&lt;p&gt;因为 10 不能被3整除&lt;/p&gt;

&lt;p&gt;所以要再向后找  14， 18 . 于是第一个满足这个要求的载波比是 18&lt;/p&gt;

&lt;p&gt;然后接下来，每个满足的载波比数，都依次比上一次多 12&lt;/p&gt;

&lt;p&gt;18 30 42 54&lt;/p&gt;

&lt;p&gt;之前的代码就是这个地方计算错了。&lt;/p&gt;

&lt;p&gt;今天改进了这个。把载波比计算终于正确了。&lt;/p&gt;

&lt;p&gt;错误的载波比，并没用起到同步调制减少谐波的优势。还不如实现起来更简单的异步调制。&lt;/p&gt;

&lt;h1 id=&quot;载波和基波的相位同步&quot;&gt;载波和基波的相位同步&lt;/h1&gt;

&lt;p&gt;除了载波比的计算要正确。载波的相位还得和基波同步。才能保证载波pwm占空比最大的那个地方，恰好也对应于 sin 值最大的地方。&lt;/p&gt;

&lt;p&gt;只要有一次，基波和载波的相位对上，则在下一次动载波比之前，就能一直维持住同步。&lt;/p&gt;

&lt;p&gt;也就是说，进行相位对齐的时机，是在修改载波比的时候。&lt;/p&gt;

&lt;p&gt;为了让输出波形不会突变到和载波对齐。在修改载波比的时候，要进行一段时间的异步调制。然后让载波和基波频率对上后。切换为同步调制。&lt;/p&gt;

&lt;p&gt;在异步调制的时候，随时监控基波和载波的相位差，追平的时候，立刻切换为同步调制。&lt;/p&gt;

&lt;p&gt;一旦进入同步调制。计算占空比的代码其实会非常简单。
因为同步调制下，占空比是周期变化并且固定的。而且是固定的几个数值。&lt;/p&gt;

&lt;p&gt;为何之前说，同步调制的代码更复杂。这里又说，一旦进入同步调制，计算占空比的代码会非常简单呢？&lt;/p&gt;

&lt;p&gt;因为同步调制的代码是逻辑复杂。要进行“换挡” 操作。换挡后还要进行同步。&lt;/p&gt;

&lt;p&gt;但是，计算量却少了。因为占空比的值是完全固化了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/improved_pwm_calc.jpg&quot; alt=&quot;计算用时&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如图所示，经过改进后的代码，计算 pwm 占空比的时间缩短到了5微妙。在次之前，计算时间为 9us。 ps，在使用定点数算法优化前的计算时间为35us。&lt;/p&gt;

&lt;p&gt;图片中的载波比，显示的是半个sin周期里 pwm 波的数量。因此是永远为奇数。且能被3整除。之所以折半显示，是为了能心里默算它是不是正确的。&lt;/p&gt;

&lt;p&gt;不过，这个代码在进入同步状态前，使用的还是异步调制。这个代码计算量就会比同步调制稍大。还是 10us。&lt;/p&gt;

&lt;p&gt;但是因为显示屏更新速度慢，因此是抓不到这个瞬间的。因为相位对齐的速度是非常快的。 在换挡的一瞬间，马上就进入同步状态了。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>单片机的行业怪病</title>
   <link href="https://microcai.org/2023/12/11/mcu-industry-myth.html"/>
   <updated>2023-12-11T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/12/11/mcu-industry-myth</id>
   <content type="html">&lt;h1 id=&quot;从单片机的存储空间说起&quot;&gt;从单片机的存储空间说起&lt;/h1&gt;

&lt;p&gt;别说市面上没有几百家也与几十家的单片机品牌。就是同一个单片机厂家，其单片机型号也是十分繁多的。&lt;/p&gt;

&lt;p&gt;这些名目繁多的单片机型号，归根结底，是 核心性能，存储容量，和封装引脚 数量的排列组合。&lt;/p&gt;

&lt;p&gt;其中，存储容量是极为关键的区分价格的因子。核心性能，都会划分为 低性能，主流性能，高性能。存储容量也会分成小容量，中容量，大容量，超大容量。&lt;/p&gt;

&lt;p&gt;但是，即使是低端低性能单片机，如果搭配的是个大容量乃至超大容量的存储空间，售价一样就会高高在上，超过高性能小容量的单片机。&lt;/p&gt;

&lt;p&gt;而仔细品味品味不同容量之间的售价差距会发现，单片机的存储空间，是按每 KB 多少块钱算的。&lt;/p&gt;

&lt;p&gt;在 PC 行业，一块钱已经可以买到数个GB存储容量的当下，单片机还将 KB 卖到数元的高价。存储容量的价格差距达到百万倍以上。&lt;/p&gt;

&lt;h1 id=&quot;不思进取的单片机业界&quot;&gt;不思进取的单片机业界&lt;/h1&gt;

&lt;p&gt;老中医曾经说过，如果现在还要造 8GB 容量的机械硬盘，其成本并不会比 8TB 的低多少。所以，如果因为某些特殊原因，实在用不上超过 8GB 的存储空间，
使用PC进行项目开发的公司，依旧会选择1TB或者2TB的硬盘容量。并不是因为他需要那么大，而是因为当下这么大的最便宜。&lt;/p&gt;

&lt;p&gt;但是，在单片机这个行业，情况发生了不一样的变化。如果你能够把你的代码裁剪到 8KB 大小，单片机厂就敢造 8KB 容量的单片机。
虽然他造8MB的成本和8KB并没有太大的区别。但是因为业界用不到8MB,他就会继续按8KB的目标市场造。并且为16KB的容量收取额外的溢价。&lt;/p&gt;

&lt;p&gt;而单片机市场的受众，也被这种吃老本的源头所驯化。特别在意代码的裁剪。于是二者是相互成就。一直压着单片机的容量。在30年的时间里，单片机的主流容量，只从 8KB 增长到了 128KB。 增加不过 8 倍。&lt;/p&gt;

&lt;p&gt;而同期的 PC ，存储容量则从 1.44MB 暴涨到 8TB。增长超过五千万倍。&lt;/p&gt;

&lt;p&gt;整个单片机业界陷入一终迟滞。不仅仅厂家喜欢躺着吃老本。顾客也要求厂家吃老本。&lt;/p&gt;

&lt;p&gt;曾经有人说，某单片机厂家非常良心。一款单片机卖了十几年还在供货，而且没涨价。他是想跨人家供货周期长，对自己的项目后续供应有保障。还夸人家十几年不涨价，保障了用户的利益。&lt;/p&gt;

&lt;p&gt;我的天，如果 intel 现在还在按同样的价格卖 80386, 不知道他还会不会夸 intel 业界良心。&lt;/p&gt;

&lt;p&gt;但是即使是同一个人, 他还是会骂 intel 是牙膏厂的.&lt;/p&gt;

&lt;p&gt;那么为何会这样呢?&lt;/p&gt;

&lt;h1 id=&quot;还是代码出了问题&quot;&gt;还是代码出了问题&lt;/h1&gt;

&lt;p&gt;按理说, 顾客是不喜欢吃老本的商家的. 谁都希望花同样的钱买到的东西越来越好. 但是单片机程序员为何是个例外?&lt;/p&gt;

&lt;p&gt;因为, 如果老款停产了,新款性能再好, 对单片机程序员来说都是没有用的.因为他的代码跑不了.&lt;/p&gt;

&lt;p&gt;单片机的程序都是为特定的单片机完全适配开发的. 单片机不仅不能升级增加功能, 连完全不增加新功能只是单纯的提高主频, 都是不可以的. 因为单片机代码里大量的 delay() 函数已经写死了主频. 更换主频就会导致 delay 时间变短. 于是原来设计好的逻辑就无法正常运行了.&lt;/p&gt;

&lt;p&gt;于是为了适配新款单片机,就得对代码进行修改. 修改完毕后要重新走测试验证流程. 这些都会额外的增加项目的开发成本.&lt;/p&gt;

&lt;p&gt;单片机码农为了吃老本, 也就逼迫上游厂家吃老本了. 而上游厂家吃老本, 也逼迫新入行的程序员吃老本. 相互影响相互锁定.&lt;/p&gt;

&lt;p&gt;所以, 破解这个死结, 首先要求程序员自我进步. 让单片机项目彻底和具体的某个单片机型号脱钩. 也就是要编写和硬件平台无关的代码. 把与硬件平台相关的代码最大限度的限制在一个非常小的范围.&lt;/p&gt;

&lt;p&gt;要想达成这个目标, 就得选择一个更容易达成这个目标的好语言. 所谓工欲善其事必先利其器. 一个语言,必须要具备良好的抽象能力.&lt;/p&gt;

&lt;p&gt;所以,选择单片机开发, 语言的第一个要求, 就是要支持更高层次的抽象能力. 所谓更高层次的抽象能力, 说人话, 就是要支持 “面向概念编程”。&lt;/p&gt;

&lt;p&gt;面向概念编程, 具体到某个具体的语言里, 有不同的叫法。C 语言完全不支持。 所以 pass。&lt;/p&gt;

&lt;p&gt;C++ 对面向概念编程的支持所提供的工具就是 c++20 提出的新特性: “concept”。&lt;/p&gt;

&lt;p&gt;另一个具有良好抽象能力的语言，我叫他 javascript， 或则 nodejs。&lt;/p&gt;

&lt;p&gt;除了要支持良好的抽象能力，性能也是不得不考虑的因素。 所谓的语言的性能，指的是编译器将高级语言转换为机器指令的时候，相比人肉写汇编指令多多插入的代码。
语言的性能越高，说明编译器生成的代码就越是精炼。&lt;/p&gt;

&lt;p&gt;很多时候，语言对性能的影响是非常小的。主要还是看编译器的优化能力。&lt;/p&gt;

&lt;p&gt;但是也有例外。编译器的优化能力是首先不能违背语言的定义。&lt;/p&gt;

&lt;p&gt;比如，C 语言规定 除 0 是一个未定义行为。 于是编译器遇到你代码上写着  a / b; 就会直接生成硬件的 div 指令。 至于遇到 除 0 ， cpu 会发生什么，编译器也不关心。因为 cpu 发生什么行为，都符合 C 语言标准的定义。那就是未定义行为。&lt;/p&gt;

&lt;p&gt;但是，如果一个语言规定了，除 0 必须要有某种具体的后果。那么，在 cpu 执行 除法指令的行为和语言规范不一致的情况下，编译器必须要插入除数是否为0的检查。如果为0,则执行语言标准所定义的行为。于是你写了一个 a / b . 编译器却生成了大量的检查 b 是否为 0 ， 并且为 0 后要干什么的指令。&lt;/p&gt;

&lt;p&gt;那么，这种语言，编译器无论如何改进他的优化算法，都不能比C更高效。&lt;/p&gt;

&lt;p&gt;这就是为何，很多程序员诟病 C 语言未定义行为太多。但是最终他们选的任何语言都不可能比C更高效。&lt;/p&gt;

&lt;p&gt;所以，一个能用在单片机开发上的语言，必须比 C 语言更高级，有更高的抽象能力。但是，同样也要比 C 语言更高效。&lt;/p&gt;

&lt;p&gt;恰巧， C++ 语言就是那个 0 开销增加高级抽象能力的， C 的 改进改进版。&lt;/p&gt;

&lt;h1 id=&quot;什么叫-0-开销抽象&quot;&gt;什么叫 0 开销抽象&lt;/h1&gt;

&lt;p&gt;C++ 把他高级的抽象能力叫做 0 开销抽象。为何叫 0 开销抽象呢？&lt;/p&gt;

&lt;p&gt;所谓 0 开销抽象，是说如果你不用 C++ 提供的高级抽象的功能，而是使用 “手撸“ 的方式达成。那么你手写的代码必然不可能比编译器自动生成的代码更高效（开了编译优化的情况下）。 比如你用函数指针的方式实现 虚继承 法的多态。并不会比直接使用虚函数更快。&lt;/p&gt;

&lt;p&gt;c++ 其实对于0开销的说法，是过于谦虚了。 c++ 的许多高级抽象能力，不仅仅不是 0 开销，还是负开销。&lt;/p&gt;

&lt;p&gt;因为会比程序员手撸的方式更快。&lt;/p&gt;

&lt;p&gt;比如模板元编程。让很多计算行为发生在编译期。这在 C 语言里用多少 宏 定义都做不到而不得不放在运行时运行的代码，在C++里，可以用模板元编程在编译期完成计算。&lt;/p&gt;

&lt;p&gt;举个最简单的例子，处理正则表达式。&lt;/p&gt;

&lt;p&gt;在 C 语言里，正则表达式是一个字符串。正则表达式引擎会在运行时的时候解析这个字符串，然后把这个字符串编译为一个字节码。这样后续用这个正则表达式匹配字符串的时候，就会非常高效。&lt;/p&gt;

&lt;p&gt;但是，在 C++ 语言里，正则表达式可以在编译期就被直接转换为机器代码。做到这点靠的就是模板元编程。&lt;/p&gt;

&lt;p&gt;而为了实现这点，需要使用模板元编程的方式写代码。代码就会显得不是特别直观。&lt;/p&gt;

&lt;p&gt;为了解决模板元编程的代码不直观的问题。 C++ 又加入了 constexpr 和 concept 大杀器。&lt;/p&gt;

&lt;p&gt;很多不理解的人，都会认为 C++ 的功能越来越多，越来越膨胀了。&lt;/p&gt;

&lt;p&gt;但是理解的人就知道， C++ 所有的新功能增加，都是为了服务一个目的，就是为了更好的 0 开销抽象。&lt;/p&gt;

&lt;p&gt;C++ 不仅仅没有因为功能越加越多而越来越慢，反而是因为 0 开销抽象能力越来越强而让代码变得越来越快。&lt;/p&gt;

&lt;p&gt;比如右值引用，就减少了大量的毫无意义的拷贝构造。单纯的将原来的老代码用支持 c++11 的编译器重新编译都能获得速度提升。&lt;/p&gt;

&lt;h1 id=&quot;接口设计能力&quot;&gt;接口设计能力&lt;/h1&gt;

&lt;p&gt;完成同样的事情，其实可以设计很多种不同的接口。虽然最终都是殊途同归。但是也分易用和不易用。&lt;/p&gt;

&lt;p&gt;就拿常见的 0.95寸的 OLED 屏幕来说，把这个屏幕封装成一个易用的接口，就有多种方式。&lt;/p&gt;

&lt;p&gt;最常见的方式，是把这个屏幕封装成一个 iostream 流。用 printf 的方式使用。&lt;/p&gt;

&lt;p&gt;其实还有一个封装方法，就是当成字符阵列。比如想在 第一行第三列输出一个字，可以这样写&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;oled[1][3] = &apos;H&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果是在第2行第4列开始输出一行字，可以这样写&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;oled[2][4] = ”一行字“
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果是按像素，则可以依葫芦画瓢。因为 c++ 接受 运算符重载，于是可以通过重载 operator [] 实现按行列定位。
又因为 c++ 支持按函数参数重载。于是可以通过重载 operator = (各种类型) 实现输出不同类型的内容。文字，数字，图片都可以。&lt;/p&gt;

&lt;p&gt;又因为 operator [] 也可以按不同类型的参数重载。 于是可以重载 int 参数，用于按文字大小的行列定位。
如果用 px 类型定位，就按像素。而且 px 类型，又可以使用 c++ 的 user defined literals 实现如下的用法&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;oled[3px][0px] = RED;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;最终，这些操作都被转换成 spi或者 i2c 命令发往 oled 屏幕。&lt;/p&gt;

&lt;p&gt;按日此方式做的接口，就可以说是非常的易用了。而且有利于用户在编写 GUI 代码的时候非常直观的明白输出效果。&lt;/p&gt;

&lt;p&gt;而如果使用 C, 是没有这些能力的。&lt;/p&gt;

&lt;p&gt;如果还是用 C 时代的方法设计接口，就容易只是看起来像是多了个对象，实际上还是 C 。&lt;/p&gt;

&lt;h1 id=&quot;表扬-esp32&quot;&gt;表扬 ESP32&lt;/h1&gt;

&lt;p&gt;在一众以 KB 算容量的单片机里， ESP32 鹤立鸡群的给以 MB 来计算容量的单片机。而且还比这些单片机更便宜。&lt;/p&gt;

&lt;p&gt;而且率先以 c++ 作为开发语言，吊打一众只知道 C 和汇编的老厂。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>制作变频器-第七部分</title>
   <link href="https://microcai.org/2023/12/09/dig-into-burnned-motor.html"/>
   <updated>2023-12-09T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/12/09/dig-into-burnned-motor</id>
   <content type="html">&lt;h1 id=&quot;从第一次烧变频器说起&quot;&gt;从第一次烧变频器说起&lt;/h1&gt;

&lt;p&gt;第一次烧变频器，也是第一次绕电机。&lt;/p&gt;

&lt;p&gt;由于没有供实验用的小功率低压异步电机。所以只能买一个现有的，利用它的外壳，转子，定子。而替换掉它
的线圈。不就可以了？&lt;/p&gt;

&lt;p&gt;于是说干就干。买了一个落地扇电机。主要是他小，而且便宜。糟蹋了不心疼。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/1pharse-motor.jpg&quot; alt=&quot;电风扇电机&quot; /&gt;&lt;/p&gt;

&lt;p&gt;然后&lt;/p&gt;

&lt;p&gt;拆！&lt;/p&gt;

&lt;p&gt;绕好后的效果就是这样&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/my_motor.jpg&quot; alt=&quot;绕好的效果&quot; /&gt;&lt;/p&gt;

&lt;p&gt;然后，绕好的视频演示&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.bilibili.com/video/BV1mg4y1o7rF/&quot;&gt;BV1mg4y1o7rF&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;然后，mos管就烧了。&lt;/p&gt;

&lt;h1 id=&quot;研究问题根源&quot;&gt;研究问题根源&lt;/h1&gt;

&lt;p&gt;一开始我认为是电流过大（废话），降低电压，提高频率，自然就把电流降下来了。&lt;/p&gt;

&lt;p&gt;但是电流降下来了，扭矩也没了。&lt;/p&gt;

&lt;p&gt;要想电机还有扭矩，绕组就必须有一定的电流。这个电流还远大于变频器从电源吸收的电流。&lt;/p&gt;

&lt;p&gt;因为这部分电流，就是在绕组里内部循环。也就是所谓的“无功功率”。&lt;/p&gt;

&lt;p&gt;这个电流必须足够大，才能让转子产生足够的感应磁场。
所以这部分电流，又叫 励磁电流。&lt;/p&gt;

&lt;p&gt;感应电机的特点就是，运行电流基本上由励磁电流构成。&lt;/p&gt;

&lt;p&gt;我自己绕的这个电机，在烧掉 mos 前，我观察到的：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1. 变频器输出电压较低的时候，绕组电流不足 3A, 电机无法运转。
2. 变频器输出足够电压，绕组电流超过 3A. 电机开始运转。此时变频器只从电源吸收0.3A电流。
3. 增加变频器的输出，变频器吸收电流到 2A, 此时绕组电流依旧是 3A 略多。
4. 继续加码，变频器吸收电流开始和绕组电流几乎相同。开始接近 4A.
5. 然后。。mos 冒烟。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其实虽然变频器以30w 以上功率只运行了十几秒。但是其实 mos 以超过 3A 电流运行时间已经超过一分钟有余了。&lt;/p&gt;

&lt;p&gt;我回看了一下电路设计图，发现我居然选了个耐压 100V ，过流能力才 9A 的 mos 。。 脑子瓦特了。应该选 60V/30A 的。当时画图的时候肯定没好好选mos.&lt;/p&gt;

&lt;p&gt;因为 9A 电流是持续电流。需要 mos 工作在持续导通状态。 如果工作在开关状态。有开关损耗。实际 mos 能过的电流只有一半。约 4.5A. 但是那还是在加散热片的情况下。我这没加散热片裸跑的 mos ，超过 3A 的电流跑了数分钟，也该烧。&lt;/p&gt;

&lt;h1 id=&quot;总结&quot;&gt;总结&lt;/h1&gt;

&lt;p&gt;经过此次烧毁，我对异步电机的理解更上一层楼了&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1.  异步电机从变频器吸收的电流，可以划分为做功电流和励磁电流
2.  励磁电流的大小是固定的。要想异步电机工作，必须首先向它注入励磁电流。变频器注入电流小于励磁电流，电机无法启动。所以这就是 V/F 控制的时候，在低频低压区域，要有一个电压补偿的根本原因。就是要维持励磁电流。否则随着电压的降低，励磁电流也会降低。
3.  对小功率异步电机来说，励磁电流的大小超过做功电流的大小
4.  增加线圈匝数可以减小励磁电流的要求
5.  但是增加的匝数同时会增加对供电电压的要求
6.  减小转子和定子的间隙（又叫气隙）可以减少励磁电流的要求，但是对机械加工精度提出更高的要求
7.  励磁电流彻底的表现为无功电流。励磁电流在绕组内部来回跑。中间会路过变频器的MOS管
8.  有功电流又分成机械功电流和损耗电流
9.  励磁电流需要变频器不断的补充损耗电流来维持
10. 线圈匝数多了。励磁电流减少，但是线圈长了，电阻增加。损耗电流并不减少
11. 变频器逐渐提高输出电压，到电机开始能运转，此时的变频器注入的有功电流，就是这台电机的损耗电流。
12. 变频器的容量应该 &amp;gt; 损耗电流 + 励磁电流 + 机械功电流
13. 逆变桥从直流母线吸收的功耗 = 损耗电流+机械功电流
14. mos 管流过电流  = 损耗电流 + 励磁电流 + 机械功电流
15. mos 管选型，要按  电机功率/电压 + 励磁电流 才能有足够余量
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>制作变频器-第六部分</title>
   <link href="https://microcai.org/2023/12/08/cooperative-multitasking-in-mcu.html"/>
   <updated>2023-12-08T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/12/08/cooperative-multitasking-in-mcu</id>
   <content type="html">&lt;h1 id=&quot;从多任务说起&quot;&gt;从多任务说起.&lt;/h1&gt;

&lt;p&gt;前序日子，都在进行核心代码的开发。终于最近进入了 UI 界面的开发了。&lt;/p&gt;

&lt;p&gt;虽然叫 UI, 但是其实并没有窗口。也没有命令行。&lt;/p&gt;

&lt;p&gt;而是几个 LED 灯 + 一个旋钮 + 一个红外遥控器。&lt;/p&gt;

&lt;p&gt;这些也算是能和用户交互了，所以，算UI没什么问题吧？&lt;/p&gt;

&lt;p&gt;一旦进入 UI 的开发领域，马上就遇到单片机的一个掣肘了：
没有操作系统。&lt;/p&gt;

&lt;p&gt;为什么UI需要操作系统呢？&lt;/p&gt;

&lt;p&gt;因为 UI 是由多个子任务构成的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/vvvf_block_diagram.svg&quot; alt=&quot;vvvf图解&quot; /&gt;&lt;/p&gt;

&lt;p&gt;libvfd 里， SVPWM 的生成是受时钟中断驱动的。&lt;/p&gt;

&lt;p&gt;但是，V/F 控制，则是一个独立的循环。V/F 控制，收到外部的控制请求后，
会根据电机的加减速特性，来逐步的提高 svpwm 模块的电压和频率。&lt;/p&gt;

&lt;p&gt;还有，遥控接收，编码器处理。这些代码，都需要自己的一个控制循环。&lt;/p&gt;

&lt;p&gt;也就是意味着，这个变频器的内部，有多个独立的 “控制流”。&lt;/p&gt;

&lt;p&gt;独立的控制流，就是线程。&lt;/p&gt;

&lt;p&gt;但是单片机没有操作系统，如何使用线程呢？&lt;/p&gt;

&lt;h1 id=&quot;用户自己实现的线程&quot;&gt;用户自己实现的线程&lt;/h1&gt;

&lt;p&gt;其实不需要操作系统，用户也可以自己实现“控制流上下文切换”。也就是用户态线程。矣？等等，单片机没有操作系统，更没有用户态和内核态之分。不过也可以借用这个术语嘛。&lt;/p&gt;

&lt;p&gt;那么在单片机里，所谓的用户态线程，指的其实是 c++ 协程。特别是 C++20 的协程。&lt;/p&gt;

&lt;p&gt;其实，在单片机里，所谓协程，主要的上下文切换手段，就是 co_await delay(ms);&lt;/p&gt;

&lt;p&gt;只要当前人物想 sleep 一段时间，就让出 cpu 时间。然后交给协程的列队管理器去调度下一个协程。&lt;/p&gt;

&lt;p&gt;因为协程的运行是不会被抢占的。只能是主动的 delay 放弃。所以，这种多任务的方式，就叫 &lt;strong&gt;协作式多任务&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;实现-yield&quot;&gt;实现 yield&lt;/h2&gt;

&lt;p&gt;目前单片机的编译器版本参差不齐，co_await 对编译器的版本要求太高。
加上 co_await 背后的机制也不容易理解。&lt;/p&gt;

&lt;p&gt;所以，目前我使用的是 asio 作者发明的 stackless coro.&lt;/p&gt;

&lt;p&gt;stackless coro 对系统的要求 = 0 。他自身是表现为一个 函数对象。&lt;/p&gt;

&lt;p&gt;每次调用的时候，他都会从上次 return 的地方继续。&lt;/p&gt;

&lt;p&gt;具体原理可以参考 asio 爸爸的文章 &lt;a href=&quot;老婆出来看月亮&quot;&gt;http://blog.think-async.com/2009/07/wife-says-i-cant-believe-it-works.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;那么，对单片机系统工程本身的需求是什么呢？&lt;/p&gt;

&lt;p&gt;其实就是写了一个 Executor.&lt;/p&gt;

&lt;p&gt;这个 Executor 不需要调用 OS 的 epoll_wait, 也不调用 GetIoCompletionPort, 更不会使用 select. 而是简单的忙等待，死循环。&lt;/p&gt;

&lt;p&gt;正如操作系统的pid 0也是在一个死循环里调用CPU的 WAIT 指令。&lt;/p&gt;

&lt;p&gt;这个简单的 Executor 就可以驱动 asio 的 stackless coroutine 了。&lt;/p&gt;

&lt;p&gt;等日后单片机厂的编译器提上来了，就可以直接使用 asio 的 co_spawn 了。反正 asio 的 cp_spawn 其实也只依赖 Executor, 而不是依赖 asio 的 io_service.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>制作变频器-第五部分</title>
   <link href="https://microcai.org/2023/12/05/sync-pwm-async-pwm.html"/>
   <updated>2023-12-05T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/12/05/sync-pwm-async-pwm</id>
   <content type="html">&lt;h1 id=&quot;pwm-调制-正弦波&quot;&gt;pwm 调制 正弦波&lt;/h1&gt;

&lt;p&gt;计算机的世界是一个离散的世界。离散的世界，是用很小的矩形去逼近连续的世界。&lt;/p&gt;

&lt;p&gt;其实傅里叶告诉我们，一切波形都是正弦波的叠加。&lt;/p&gt;

&lt;p&gt;于是 pwm 看起来是方波，但是其实也是正弦波。&lt;/p&gt;

&lt;p&gt;pwm 之所以可以模拟正弦波的效果，就是因为 pwm 可以看成被模拟的正弦波+很多很多的谐波构成。&lt;/p&gt;

&lt;p&gt;而这些多余的谐波，只要这个谐波的频率比正弦波本体大很多，就可以被低通滤波器完美的过滤掉。&lt;/p&gt;

&lt;h1 id=&quot;pwm-频率和-开关损耗&quot;&gt;pwm 频率和 开关损耗&lt;/h1&gt;

&lt;p&gt;pwm 的频率越高，则调制比就越大。所谓调制比，就是pwm的频率和逆变器要输出的正弦波频率的比值。&lt;/p&gt;

&lt;p&gt;理论上来说， pwm 的频率越高，调制比越大，那么经过电机绕组这个大电感后过滤出来的电流，就会越接近完美的正弦波。
也就是电流的谐波就越少。
因为电感对高频的阻碍作用是越来越大的。电机绕组里的电流越接近正弦波，则谐波损失越少。 
因为只有基频才能输出机械能。高频的谐波只能变成震动和发热。&lt;/p&gt;

&lt;p&gt;所以，从降低电机的铁损来说，pwm 的频率越高越好。&lt;/p&gt;

&lt;p&gt;但是， pwm 频率太高了，也有一个缺点，就是导致开关损耗增加。
因为 MOS 管不是理想的开关管。即使是理想的开关管，也不能突破物理定律让电流突变。&lt;/p&gt;

&lt;p&gt;MOS管关闭和打开的时候，电流从 最大到0，和从0到最大，中间必然是有一个斜坡的。这个斜坡，意味着加在MOS管两端的电压是持续变化的。于是电压和电流的乘积，会先增加后减少。他们的积分，就是 MOS 管的开关损耗。&lt;/p&gt;

&lt;p&gt;MOS管的开关速度越快，积分就会越少。也就是开关损耗越低。&lt;/p&gt;

&lt;p&gt;这就是为啥要使用氮化镓的由来。&lt;/p&gt;

&lt;p&gt;传统上，使用硅参锗做的晶体管，每一层硅原子的耐压不高。导致 MOS 管要想更高的耐压，其参杂厚度就要增加。
而增加的参杂厚度，必然会降低开关速度。&lt;/p&gt;

&lt;p&gt;也就是 MOS 管的 耐压和开关速度，是个矛盾体。&lt;/p&gt;

&lt;p&gt;耐压 1.5v 的，比如用来构成 CPU 里的门电路的管子，开关速度可以做到数个 GHZ。
但是，耐压到 1500v 的管子，开关速度就只能做到 1khz 了。&lt;/p&gt;

&lt;p&gt;如果把制作材料从硅改为氮化镓，则同样 1500v 耐压，开关速度还可以做到 1Mhz。&lt;/p&gt;

&lt;p&gt;而变频器，始终是需要面对大电压大电流的工况的。而新材料MOS管非常的贵。所以变频器，是不能异想天开的使用一个超高的 pwm 频率。
而是始终要面临开关损耗问题。&lt;/p&gt;

&lt;p&gt;在开关损耗和电机的谐波铁损之间取折衷，于是变频器普遍会使用 1khz - 8khz 的开关频率。而且越是大功率的变频器，开关频率都倾向于比小功率的更低。&lt;/p&gt;

&lt;h1 id=&quot;异步调制和同步调制&quot;&gt;异步调制和同步调制&lt;/h1&gt;

&lt;p&gt;异步调制，就是 pwm 的频率和输出的正弦波的频率之间没有关系。二者是异步的。通常在变频器的整个工作期间都会使用某个固定的 pwm 频率。&lt;/p&gt;

&lt;p&gt;异步调制有一个重要的特点就是输出的脉冲波是不对成的，不仅1/4脉冲不对成，正负半周期的脉冲也不对称，这会导致电流输出的谐波较多。&lt;/p&gt;

&lt;p&gt;同步调制的载波比N等于常数，并在变频时使载波和调制波保持同步的方式称为同步调制。且 N 为能被3整除的奇数。
所谓载波比，就是指pwm的频率和输出的正弦波的频率之比。 当载波比为常数时，每个正弦波周期，输出固定个数的 pwm 波。
正弦波的频率提高，pwm的频率也随之提高。
这种调制模式下，谐波含量相比pwm频率相当的异步调制会更少。&lt;/p&gt;

&lt;p&gt;如果载波比是完全固定，则pwm频率会随着输出频率的上升而上升到超过最大 pwm 频率限制。
此时变频器的做法就是换一个小点的载波比。反之频率减小的情况下也会切换更大的载波比。&lt;/p&gt;

&lt;p&gt;这种载波比切换，就会形成一种音阶。&lt;/p&gt;

&lt;h1 id=&quot;同步调制和异步调制在代码实现上的异同&quot;&gt;同步调制和异步调制在代码实现上的异同&lt;/h1&gt;

&lt;p&gt;不管是同步调制还是异步调制。最终都是在 pwm 硬件定时器的中断里更新下一个周期的 pwm 占空比。&lt;/p&gt;

&lt;p&gt;因此下一个 pwm 周期的占空比计算方式就是&lt;/p&gt;

&lt;p&gt;$sin( 当前相位 + （1/pwm周期*正弦波频率）)$&lt;/p&gt;

&lt;p&gt;不管是同步调制还是异步调制，唯一的区别就是 这里的 pwm周期，它是个 constexpr int 变量还是 int 变量.&lt;/p&gt;

&lt;p&gt;同步调制增加了改变pwm频率的代码。在计算下一个周期的占空比时，还要跟新下一个周期的pwm频率。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>制作变频器-第四部分</title>
   <link href="https://microcai.org/2023/12/01/max-power-factor-track.html"/>
   <updated>2023-12-01T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/12/01/max-power-factor-track</id>
   <content type="html">&lt;h1 id=&quot;感应电机的开环控制&quot;&gt;感应电机的开环控制&lt;/h1&gt;

&lt;p&gt;在没有使用变频器的时候，感应电机无法调速。交流电改变频率虽然困难，但是改变电压很容易。
因此也有通过自耦变压器调压后驱动感应电机的方法。&lt;/p&gt;

&lt;p&gt;但是仅仅是降低了电压，扭矩降低导致转差变大。转差变大使得转子发热增加。不利于电机的长期稳定运行。&lt;/p&gt;

&lt;p&gt;所以有了 恒压频比的开环变频调速器。注意，这个词的分词应该是  恒 压频比。&lt;/p&gt;

&lt;p&gt;意思是以固定的 频率和电压的比值进行调速。比如一个额定 220v/50hz 的电机，当试图降一半转速运行的时候，可以设定逆变器输出 110v/25hz 给电机。&lt;/p&gt;

&lt;p&gt;恒压频比是一种开环控制。控制器只是盲目的输出一个设定的电压和频率。但是并不能让电机工作在最佳的特性点上。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/im_torque_characteristics.png&quot; alt=&quot;异步电机的机械特性&quot; /&gt;&lt;/p&gt;

&lt;p&gt;图中，恒坐标是转差率，纵坐标为扭矩。B 点就是电机的最佳工作点。但是电机遇到扰动的时候，很容易进入 B-A 区间。
因此，实际会尽量让电机工作在 B-C 区间，并靠近 B 点。&lt;/p&gt;

&lt;p&gt;Tm 的值表示电机的最大扭矩，电机的最大扭矩正比于电压的平方。&lt;/p&gt;

&lt;p&gt;因此，可以看出，使用恒压频比控制，并不能很好的让电机工作在最佳状态。&lt;/p&gt;

&lt;p&gt;所以就得出了在电动汽车领域更常见的做法：&lt;/p&gt;

&lt;h1 id=&quot;转差率闭环控制&quot;&gt;转差率闭环控制&lt;/h1&gt;

&lt;p&gt;既然电机的最大扭矩取决于电压，那只要知道了电机的转速，控制逆变器输出的频率，让电机始终工作在最大扭矩上。意味着在相同的转速和扭矩输出下（即相同的机械功率输出下）。闭环控制的逆变器始终会比恒压频比的控制器输出更低的电压。于是使得电机吸收的电功率跟小，也就是机械效率更高。&lt;/p&gt;

&lt;p&gt;怎么知道电机的转速呢？&lt;/p&gt;

&lt;p&gt;常规做法，是使用转速传感器。有了转速传感器，就可以进行基于转差率的闭环控制。知道电机转速后，可以立马获得当前输出频率。输出电压设定为 min(恒压频比计算出来的频率, 用户设定的扭矩电压)&lt;/p&gt;

&lt;p&gt;也就是随着转速的提升逐步提高电压，并在达到最大电压后停止电压攀升。如果电机扭矩还有富裕（负载较低）则电机电压不再提升的情况下，频率会继续提升，也就是进入弱磁控制状态。可以榨干电机的机械潜力。&lt;/p&gt;

&lt;p&gt;转差率闭环控制下，异步电机的特性就更贴近于直流电机了。通过调节电压可以轻松的控制扭矩，而频率则通过闭环控制自动跟踪。控制器输出电流频率，正比于电机转速，和同步电机异曲同工之妙。只是不需要控制电流相位。&lt;/p&gt;

&lt;h1 id=&quot;无转速传感器无参数设定的闭环控制&quot;&gt;无转速传感器无参数设定的闭环控制&lt;/h1&gt;

&lt;p&gt;转速传感器不仅仅提高了整个系统的成本，而且降低了电机的可靠性。因为异步电机工作环境通常较为恶劣。也正是因为恶劣环境下必须选择结构较为简单的异步电机。&lt;/p&gt;

&lt;p&gt;所以，如果能使脱离转速传感器，但是依旧使用闭环控制，则能同时兼具开环控制的低成本优势和闭环控制的高效率优势。&lt;/p&gt;

&lt;p&gt;前人提出了不少方式，但是这些方式有一个共同特点：需要异步电机的各项参数。 如绕组电感，绕组电阻，额定转差率。&lt;/p&gt;

&lt;p&gt;事实上，额定转差率是一个更为难获取的数值。其实使用转速传感器进行闭环控制的时候，也需要这个参数。这也是为何闭环控制器，通常和电机绑定。而市面上普遍单独售卖的变频器，都是开环控制。&lt;/p&gt;

&lt;p&gt;因此，如何让变频器可以同直流电机控制器一样，无需设定电机参数，就能自动获得和直流电机一样（或者接近）的控制特性呢？&lt;/p&gt;

&lt;p&gt;于是，经过我长期的思考，我得出了一个解决方案：&lt;/p&gt;

&lt;h2 id=&quot;最大功率因素点跟踪&quot;&gt;最大功率因素点跟踪&lt;/h2&gt;

&lt;p&gt;交流电机在运行过程中，不同的运转状态下，会产生不同的功率因素。&lt;/p&gt;

&lt;p&gt;例如，电机堵转运行的时候，绕组的表现为纯电感。于是功率因素接近 0, ，但是电机要从电网汲取超大的电流。这些超大的电流最后都通过电线的电阻发热损耗掉了。&lt;/p&gt;

&lt;p&gt;电机在空载运行的时候，绕组表现依旧为纯电感。功率因素依旧接近 0 。 但是因为此时转子转速接近同步转速，因此电机产生的反电动势抵消了外部的驱动电压。空载运行时，电机反电动势会极为接近驱动电压。因此电流处于最小值。绕组实际电压为 驱动电压-反电动势电压。然后电流为此时电压下的电感电流。&lt;/p&gt;

&lt;p&gt;而电机正常的处于额定负载的情况下，会产生最大的功率因素。负载低于和高于额定负载，都会使电机的功率因素下降。&lt;/p&gt;

&lt;p&gt;因此，只要寻找到让电机功率因素最大的 频率/电压 组合，就可以在不知晓电机各项参数的情况下，获得接近直流电机般的转矩特性。&lt;/p&gt;

&lt;p&gt;而最大功率因素点跟踪，其思路就来自 太阳能控制器的最大功率点跟踪。&lt;/p&gt;

&lt;p&gt;具体做法是。使用 恒压频比，设定一个初始的频率。而算法的输出，为对初始频率的修正数。&lt;/p&gt;

&lt;p&gt;算法的观测器，为 功率因素。功率因素的获取方式很简单，就是通过电流采样，获取三相各自的电流值。将电流值与此时逆变器的输出电压比较，就可以获得电流相对电压的滞后角。通过滞后角，就得到了功率因素。&lt;/p&gt;

&lt;p&gt;此时算法会尝试 减少或者增加频率，然后观察功率因素的变化方向。
如果功率因素的变化方向，和频率的修正数同向，则电机目前工作在轻载模式。需要继续增加频率。&lt;/p&gt;

&lt;p&gt;如果功率因素的变化方向，和频率的修正数逆向，则电机工作在重载模式。需要降低频率。&lt;/p&gt;

&lt;p&gt;这样经过双向的尝试，控制器很快就能稳定在使功率因素最大的频率上。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>制作变频器-第三部分</title>
   <link href="https://microcai.org/2023/11/30/saddle-wave-form-explain.html"/>
   <updated>2023-11-30T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/11/30/saddle-wave-form-explain</id>
   <content type="html">&lt;h1 id=&quot;轮子上的三条辐条&quot;&gt;轮子上的三条辐条&lt;/h1&gt;

&lt;p&gt;如果有一个轮子，半径为 1 ，在滚动的时候，其圆心会始终离地1高度。整个轮子的最顶端，也始终离地高度2.&lt;/p&gt;

&lt;p&gt;如果在这个轮子上，画出三条辐条。辐条夹角 120度。&lt;/p&gt;

&lt;p&gt;轮子在滚动的时候，辐条的顶部会完美的走过一个正弦波。波峰为 2. 中心为 1 。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/sin.png&quot; alt=&quot;正弦波调制&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这，就是正选波调制的三相电。其相电压的幅值为1。线电压为 $\sqrt{3}$. 由于轮子最高处为2。
因此正弦波调制的直流电压利用率就是 $\sqrt{3}/2$&lt;/p&gt;

&lt;h1 id=&quot;去掉轮毂只留下辐条&quot;&gt;去掉轮毂，只留下辐条&lt;/h1&gt;

&lt;p&gt;因为没了轮毂，只留下辐条，于是，车轮前进的时候，辐条的端点划过的波，却不是正弦波了，而是一个马鞍波。&lt;/p&gt;

&lt;p&gt;因为车轮的中心，在上下跳动！&lt;/p&gt;

&lt;p&gt;于是，辐条的最高点，就不再是2了。因为车轮中心点最高的时候，恰恰两个辐条各自 120 分开。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/saddle.png&quot; alt=&quot;达不到2高度啊&quot; /&gt;&lt;/p&gt;

&lt;p&gt;达不到2高度啊！&lt;/p&gt;

&lt;p&gt;经过计算发现，其实车轮最大的高度，也只堪堪达到了 $\sqrt{3}$&lt;/p&gt;

&lt;p&gt;这意味着，直流母线电压不需要为2 ，只需要 $\sqrt{3}$ 就能达到和之前一样的相电压输出！&lt;/p&gt;

&lt;p&gt;于是，会发现，在这种马鞍调制下，直流母线电压的利用率达到了 100%。&lt;/p&gt;

&lt;p&gt;虽然说，马鞍波的是各个相对 直流 地 的电压。&lt;/p&gt;

&lt;p&gt;但是，相和相之间的电压波形，依旧是完美的正弦波。因为其马鞍波，实际上是正弦波上叠加的一个三次谐波。车轮中心的上下运动，恰好抵消了这个谐波。&lt;/p&gt;

&lt;p&gt;于是相电压为完美的正弦波。而没有叠加的谐波。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>制作变频器-第二部分</title>
   <link href="https://microcai.org/2023/11/29/operator-overload-to-change-float-math-to-int.html"/>
   <updated>2023-11-29T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/11/29/operator-overload-to-change-float-math-to-int</id>
   <content type="html">&lt;h1 id=&quot;引&quot;&gt;引&lt;/h1&gt;

&lt;p&gt;输出对称三相电的逆变器，总是要进行很多浮点运算。因此一般而言，需要使用带FPU的单片机。或者说叫DSP。&lt;/p&gt;

&lt;p&gt;如果只使用整数运算能否实现 svpwm 的计算呢？&lt;/p&gt;

&lt;p&gt;答案是能的，就是使用 “定点数”&lt;/p&gt;

&lt;p&gt;所谓定点数，就是和浮点数相对的，他的整数部分和小数部分是固定的。比如按 16bit 存储整数部分， 16bit 存储小数部分。这样一个 32bit 的定点数字，能表示的数字范围其实和 16位的整数一样。只不过它能额外的携带小数部分。&lt;/p&gt;

&lt;p&gt;而浮点数则是脱胎于科学计数法。比如使用 16bit 存储基数，另外16bit存储系数。则可以表示非常非常大的范围，只不过数字越大，误差就越大。大点的数字都没有小数部分了。所以这种数字表示法，他的小数点是浮动的。所以叫浮点数。&lt;/p&gt;

&lt;p&gt;浮点数其实有各种格式存储，但是为了互操作性， IEEE 定义了一个cpu上普遍使用的格式。IEEE 754。
如果单片机不支持浮点数运算指令，编译器就会用软件模拟。编译器带的软件浮点运算，速度非常非常慢。
这就是“浮点算不快”的由来。&lt;/p&gt;

&lt;p&gt;受限于成本，低端的单片机都不会搭载FPU。而中端的单片机，虽然搭载了FPU,但是 FPU 的速度不能和整数部分相提并论。只有使用较为高端的单片机，其搭载的 FPU 才算能用于 foc/svpwm 控制。&lt;/p&gt;

&lt;p&gt;这就极大的提高了电机控制器的成本。使得廉价控制器总是使用较为简单的只需要六步换向的方波驱动。而不是使用三相对称的正弦波驱动电机。&lt;/p&gt;

&lt;p&gt;那么有没有办法在廉价的单片机上使用 foc 呢？&lt;/p&gt;

&lt;h1 id=&quot;定点数学库&quot;&gt;定点数学库&lt;/h1&gt;

&lt;p&gt;其实是有的。不过将foc控制算法修改为定点数运算，需要修改大量的代码。而且简单的 + - * / 运算符号，统统被替换成了难看的，对定点数学库的调用。
需要修改的地方非常多。将程序改的面目全非。&lt;/p&gt;

&lt;p&gt;这就是 C 语言的弊病。 所以，就到了安利 C++ 的时间啦！&lt;/p&gt;

&lt;h1 id=&quot;定点数学库---header-only-cpp-plug-and-play&quot;&gt;定点数学库 - header only CPP plug and play&lt;/h1&gt;

&lt;p&gt;C++ 的优势，就是具有 “运算符重载” 功能。这个功能使得使用非编译器内置数字类型不需要改变使用方式。你仍然只需要使用正常的 + - * / 运算符。&lt;/p&gt;

&lt;p&gt;比如在所有代码里，凡是需要支持小数运算的地方，我统统使用 float_number 这个类型。&lt;/p&gt;

&lt;p&gt;然后在全局的一个 header 里，我用 using float_number = float;&lt;/p&gt;

&lt;p&gt;这样，在不使用数学库的情况下，我的代码自动的就是使用的编译器提供的 float 类型。&lt;/p&gt;

&lt;p&gt;然后，在开发的某一个阶段，我引入了定点数学库。&lt;/p&gt;

&lt;p&gt;这时候，我只要把 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;using float_number = float;&lt;/code&gt; 修改为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;using float_number = fixpoint_number_provided_by_math_lib;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;然后重新编译。那么我所有用到浮点数的地方，就会全自动的转换为使用了数学库里的定点数。&lt;/p&gt;

&lt;p&gt;这就是 c++ 带来的开发上的巨大优势。&lt;/p&gt;

&lt;h1 id=&quot;这次-mini-vfd-硬件上定点数的速度提升&quot;&gt;这次 mini vfd 硬件上定点数的速度提升&lt;/h1&gt;

&lt;p&gt;使用 Cortex-M4 内核的浮点指令，我做到了 pwm 周期计算代码执行时间为 50us。这差不多相当于极限 20khz 的开关速度。更快的开关速度将会导致 cpu 来不及执行完毕 pwm 中断代码，就又要发生一次 pwm 定时器中断了。&lt;/p&gt;

&lt;p&gt;修改为使用定点数后，同样的代码（除了添加 using float_number = myfloat 声明，未做任何其他修改）执行时间缩短到了 9us。 这差不多相当于能在这颗 cpu 上运行 100khz 开关速度的 svpwm。&lt;/p&gt;

&lt;p&gt;这也意味着，使用主频 12Mhz 的单片机，也能跑不低于 8khz 的 pwm 频率。 8khz 的开关频率，其实已经相当程度上做到开关噪音的抑制了。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>制作变频器-第一部分</title>
   <link href="https://microcai.org/2023/11/25/vfd-part1.html"/>
   <updated>2023-11-25T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/11/25/vfd-part1</id>
   <content type="html">&lt;p&gt;为了验证，我决定亲自设计一款三相电源。这个电源可以随意设定电压和频率。&lt;/p&gt;

&lt;p&gt;三相电源，在任意时刻，可以由2个参数唯一确定： 电压/角度。
这个电压，指的是完整周期的相电压。因为三相的角度各相差120度。所以确定一个相的相位，就能自动推出另外2个相位。&lt;/p&gt;

&lt;p&gt;因此，在三相电源的核心逆变代码，只参考由上层传入的两个控制参数：电压和角度。&lt;/p&gt;

&lt;p&gt;有了角度，就可以知道任意相，他相对直流母线的电压因为 (sin(角度) /2 + 0.5)*电压。另外两相各加120度和240度即可。
如果把 电压设定为 0-1 之间的一个比例，也就是相对直流母线的电压，则算出来的值，就是每相的 上下管的导通之比。&lt;/p&gt;

&lt;p&gt;算出三相的PWM之比后，只要 mcu 设定为 中央对齐的 PWM 输出，就可以完美的输出指定角度的三相电了。&lt;/p&gt;

&lt;p&gt;然后再利用定时器，不断的更新 角度变量，就可以输出周期变化的三相交流电了。&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;U_a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;electron_angle&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;180.0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;2.0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;U_b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;electron_angle&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;120.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;180.0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;2.0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;U_c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;electron_angle&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;240.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;180.0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;2.0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;U_a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;throttle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;U_b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;throttle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;U_c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;throttle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;pwm_set_duty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;U_a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;U_b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;U_c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;pwm 输出必须为中央对齐。可以大大降低开关损失。&lt;/p&gt;

&lt;p&gt;electron_angle 的更新，可以放到 定时器里，也可以放到 pwm 的中断响应里。&lt;/p&gt;

&lt;p&gt;electron_angle 的更新步进为，频率/pwm周期，如果 pwm 周期设定为 频率的整数倍 S，那么 electron_angle 的更新步进就是 360°C/S。&lt;/p&gt;

&lt;p&gt;每个pwm周期要执行三次 sin 运算。但是根据 三相和为 0 ，可以省去一个 sin 计算。简化为&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;U_a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;electron_angle&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;180.0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;2.0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;U_b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;electron_angle&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;120.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;180.0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;2.0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;U_c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.5&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;U_a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;U_b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;即使不使用 clarke 变换，也只要每周期执行2次三角函数。&lt;/p&gt;

&lt;p&gt;11 月 29 日更新：&lt;/p&gt;

&lt;p&gt;直接按 sin 值幅值给 pwm 占空比是不行的。需要进行一定的变化。不过确实不需要 clarke 变换就是了。&lt;/p&gt;

&lt;p&gt;还是根据六步换向法，确定扇区后，再根据 sin 值进行 pwm 占空比赋值。在每个扇区，必有一相是低端管持续开启的。&lt;/p&gt;

&lt;p&gt;次年 1月 20 日更新：&lt;/p&gt;

&lt;p&gt;能用，之前的代码其实没有错误。是pwm驱动的bug所以匆忙下了错误的结论。&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 本函数通过 电角度 和 油门大小，直接算出输出的pwm波.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 无需 Uq 和 Ud 进行反 clarke 变换.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 如需进行 FOC 控制，可以测量功率因素（电流相比电压的滞后相位），直接对电角度进行提前&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// 因此 FOC 控制也只需调制 electron_angle_ 和 throttle&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tuple&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;svpwm&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;caculate_spwm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;half&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;one_half&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// 计算三个 [0,1] 区间的正弦值。相位依次差 120°&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 由于三相的和为 1.5，所以 C 相可以直接减法得出，不用多调用一次 sin&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;U_a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sin_of_degree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;electron_angle_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;		 &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;half&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;U_b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sin_of_degree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;electron_angle_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;120&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;half&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;U_c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;one_half&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;U_a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;U_b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// 将计算结果和油门(0,100%)大小相乘，就得出了三相各自的 pwm 占空比&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;make_tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;U_a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;throttle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;U_b&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;throttle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;U_c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;throttle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个代码已经在我的变频器上验证通过。完全可行。&lt;/p&gt;

&lt;p&gt;次次年更新：&lt;/p&gt;

&lt;p&gt;其实之前的做法算出来的是 SPWM 调制，如果要提高母线电压利用率，可以使用中点偏移法移动中点电位，实现馒头波调制。&lt;/p&gt;

&lt;p&gt;代码如下：&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tuple&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;svpwm&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;caculate_spwm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// find the sector we are in currently&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sector&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static_cast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;electron_angle&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;120&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sector_angle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;120&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;240&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;360&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;angle_in_sector&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;electron_angle&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sector_angle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Uout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sin_of_degree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;120&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;angle_in_sector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Uout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sin_of_degree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;angle_in_sector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tmax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;angle_in_sector&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Tmax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Tmax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// 中点漂移法&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;center&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Tmax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;float_number&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;U_a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;U_b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;U_c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;U_a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;U_b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;U_c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;U_a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;U_b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;U_c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;U_a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;U_b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;U_c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;U_a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;center&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;U_b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;center&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;U_c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;center&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;make_tuple&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;U_a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;U_b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;U_c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>三相220v供电可行性研究</title>
   <link href="https://microcai.org/2023/11/17/3phase-220v.html"/>
   <updated>2023-11-17T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/11/17/3phase-220v</id>
   <content type="html">&lt;p&gt;经过深思熟虑，我提出了一种新的居民用电供电模式： 即三相220伏4线制供电。以适应未来更大的入户功率需求。&lt;/p&gt;

&lt;p&gt;特点为：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;ol&gt;
    &lt;li&gt;入户线路为3根火线，一条地线，没有零线。&lt;/li&gt;
    &lt;li&gt;火线和火线之间的电压为220v。&lt;/li&gt;
    &lt;li&gt;入户电表设计三相不平衡保护开关&lt;/li&gt;
    &lt;li&gt;使用任意2条火线来兼容老的单相220v设备，继续提供 220v 的 2孔和3孔插座。&lt;/li&gt;
    &lt;li&gt;三相设备使用4脚插座。&lt;/li&gt;
  &lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;不同供电制度下的对比如下：&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;220v三相&lt;/td&gt;
      &lt;td&gt;220v 单相&lt;/td&gt;
      &lt;td&gt;380v 三相&lt;/td&gt;
      &lt;td&gt;240v直流&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;电压危险性&lt;/td&gt;
      &lt;td&gt;中&lt;/td&gt;
      &lt;td&gt;中&lt;/td&gt;
      &lt;td&gt;高&lt;/td&gt;
      &lt;td&gt;中&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;同功率下电流&lt;/td&gt;
      &lt;td&gt;中&lt;/td&gt;
      &lt;td&gt;大&lt;/td&gt;
      &lt;td&gt;小&lt;/td&gt;
      &lt;td&gt;中&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;同容量下线材成本&lt;/td&gt;
      &lt;td&gt;低&lt;/td&gt;
      &lt;td&gt;高&lt;/td&gt;
      &lt;td&gt;低&lt;/td&gt;
      &lt;td&gt;中&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;电能质量&lt;/td&gt;
      &lt;td&gt;高&lt;/td&gt;
      &lt;td&gt;低 （100hz功率波动）&lt;/td&gt;
      &lt;td&gt;高&lt;/td&gt;
      &lt;td&gt;高&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;异步电机转矩&lt;/td&gt;
      &lt;td&gt;恒转&lt;/td&gt;
      &lt;td&gt;100hz 转矩波动&lt;/td&gt;
      &lt;td&gt;恒转&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;故障保护难度&lt;/td&gt;
      &lt;td&gt;低&lt;/td&gt;
      &lt;td&gt;低&lt;/td&gt;
      &lt;td&gt;中&lt;/td&gt;
      &lt;td&gt;高&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;整流后电压&lt;/td&gt;
      &lt;td&gt;310v&lt;/td&gt;
      &lt;td&gt;310v&lt;/td&gt;
      &lt;td&gt;540v&lt;/td&gt;
      &lt;td&gt;240v&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;在这个特点下，带来的优势为：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;ol&gt;
    &lt;li&gt;电压等级仍旧为220v。家庭布线只是从3线升级为4线。增加33%的电线成本获得2倍的线路容量。同等供电功率下降低一半的线路成本。&lt;/li&gt;
    &lt;li&gt;对老式单相电器，无需任何修改可以立即兼容。巨大的兼容性保障了过渡期的平稳进行。&lt;/li&gt;
    &lt;li&gt;开关电源可以简单的通过修改前置整流电路直接升级为支持三相输入。整流后的直流电压依旧为310v，其余电路无需任何改动。整流电路的额外成本只是多了2个二极管，从4个升级为6个。&lt;/li&gt;
    &lt;li&gt;对普通电器可以淘汰昂贵易损效率低的单相异步电机。而使用更廉价的三相异步电机，并提高效率。&lt;/li&gt;
    &lt;li&gt;三相供电更稳定，没有100hz的功率脉动现象。提高设备寿命和安全性。&lt;/li&gt;
    &lt;li&gt;如果所有用电器都为三相电设计，则三相负载平衡自动达成。&lt;/li&gt;
  &lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;新设计一个 86 插座&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/插座.png&quot; alt=&quot;插座&quot; /&gt;&lt;/p&gt;

&lt;p&gt;新设计一个插头&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/插头.png&quot; alt=&quot;插头&quot; /&gt;&lt;/p&gt;

&lt;p&gt;其中，三角形边缘的触点为地线。插入的时候地线先于火线接合。&lt;/p&gt;

&lt;p&gt;此插头适用电流为 10A, 因此能适用的功率为 3.8kw。足够满足几乎所有家庭用电器的需求。切为三向插头，可以从任意3个方向插入。&lt;/p&gt;

&lt;p&gt;对于单相的老旧电器，则可以沿用旧有的3脚插头和插座。&lt;/p&gt;

&lt;p&gt;相比使用单相3线制，同样满足 12kw 负荷，家庭布线总线从 3*6 布线改为 4*4布线。用铜量反而略有下降。对于功率非常小（三相负载不均衡在容许值内时）的一些线路，可以仍旧使用双线220v供电。&lt;/p&gt;

&lt;p&gt;视在功率的计算为 220v * 每相电流 * $\sqrt{3}$
相比单相供电，计算的时候要多乘一个 $\sqrt{3}$&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>逆摊丁入亩</title>
   <link href="https://microcai.org/2023/11/16/tdrm.html"/>
   <updated>2023-11-16T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/11/16/tdrm</id>
   <content type="html">&lt;p&gt;税，都逃不过三大体系：人头税，资产税，流转税。
所谓人头税，就是按人头收税。在古时候，人头税是定额税。后来通过摊丁入亩废除了。
如今，人头税通过工资税的形式卷土重来。虽然不再是定额税。但是依旧是按人丁收取。&lt;/p&gt;

&lt;p&gt;资产税，就是按资产收税。一般采取的做法是按固定资产收税。古时候的摊丁入亩改革，正是以
资产税代替人头税。
如今，资产税在西方是非常普遍的。但在中国，房地产税迟迟未落地。
资产税的另一个重要征收方式叫通货膨胀，也叫铸币税。&lt;/p&gt;

&lt;p&gt;流转税，就是在商品的流通过程中收税。增值税，印花税，契税，关税，消费税，都属于流转税。&lt;/p&gt;

&lt;p&gt;在古代，主要依靠的是人头税，然后一部分资产税，几乎没有流转税。但是人民的流转税负担是少不了的，因为他们要通过向土匪交买路财，向城市黑社会交保护费的形式承担流转税。只不过这部分税收归属流氓，而不归政府。&lt;/p&gt;

&lt;p&gt;从明朝开始，统治者就意识到了人头税的弊端。而后有了张居正的改革，废除了人头税，改收以耕地为基础的资产税。这个改革从张居正开始直到雍正才真正落地。地多的多纳税，地少的少纳税。而不再由人口多寡决定税额。正是废除了人头税，中国的人口才起飞。当一个家庭的税负，不再由人口数量的多寡决定，那人多才能真正的力量大。才能放开了生。&lt;/p&gt;

&lt;p&gt;后来，1949年后，我们学习“西方的先进经验”，不分青红皂白的搬了西方的经验。重新捡起了在中国已经废除数百年的人头税。倒行逆施的结果就是摊亩入丁。
占有大量房子和土地的人不纳税。税主要由人数众多的底层老百姓承担。人头税的主要形式就是个人所得税和社保。&lt;/p&gt;

&lt;p&gt;结果就是生育率迅速下降。老龄化严重。&lt;/p&gt;

&lt;p&gt;人口越多，缴纳的税就越多，资产却无需纳税。这导致人口增长放缓，并进入人口萎缩时代。由于资产不纳税，资产开始膨胀。&lt;/p&gt;

&lt;p&gt;西方的老龄化社会，恰恰是因为西方普遍实现的“个人所得税”，也就是人头税。而不是所谓的经济发展导致的生育率自然下降。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>电池扫盲</title>
   <link href="https://microcai.org/2023/10/08/batteries.html"/>
   <updated>2023-10-08T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/10/08/batteries</id>
   <content type="html">&lt;p&gt;作为资深老宅，路由器 nas 充电宝 这三大爱好是必不可少的。最近一友人买电池的时候，一着不慎着了相。买到了错误的电池。不得不花费重金重新采购合适的电池。
故有此文，以供参考。&lt;/p&gt;

&lt;h1 id=&quot;电池电压&quot;&gt;电池电压&lt;/h1&gt;

&lt;p&gt;家庭常见电池的电压如下&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;电池类型&lt;/th&gt;
      &lt;th&gt;标称电压&lt;/th&gt;
      &lt;th&gt;满电电压&lt;/th&gt;
      &lt;th&gt;空电电压&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;镍氢电池&lt;/td&gt;
      &lt;td&gt;1.2v&lt;/td&gt;
      &lt;td&gt;1.4v&lt;/td&gt;
      &lt;td&gt;1v&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;干电池&lt;/td&gt;
      &lt;td&gt;1.5v&lt;/td&gt;
      &lt;td&gt;1.6v&lt;/td&gt;
      &lt;td&gt;0.9v&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;碱性电池&lt;/td&gt;
      &lt;td&gt;1.5v&lt;/td&gt;
      &lt;td&gt;1.7v&lt;/td&gt;
      &lt;td&gt;1.2v&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;锂铁电池&lt;/td&gt;
      &lt;td&gt;1.5v&lt;/td&gt;
      &lt;td&gt;1.8v&lt;/td&gt;
      &lt;td&gt;1.5v&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;主板纽扣电池&lt;/td&gt;
      &lt;td&gt;3v&lt;/td&gt;
      &lt;td&gt;3.3v&lt;/td&gt;
      &lt;td&gt;2v&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;磷酸铁锂电池&lt;/td&gt;
      &lt;td&gt;3.2v&lt;/td&gt;
      &lt;td&gt;3.6v&lt;/td&gt;
      &lt;td&gt;3v&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;三元锂电池&lt;/td&gt;
      &lt;td&gt;3.7v&lt;/td&gt;
      &lt;td&gt;4.2v&lt;/td&gt;
      &lt;td&gt;3.3v&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;1.5v恒压锂电池*&lt;/td&gt;
      &lt;td&gt;1.5v&lt;/td&gt;
      &lt;td&gt;1.5v&lt;/td&gt;
      &lt;td&gt;1.5v&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;其中，1.5v恒压锂电池 其实不是电池，而是一个内置锂电池的供电电源。电源稳压输出 1.5v。
其目的是使为 1.5v 干电池设计的用电器能直接使用锂电池工作。但是因为自身携带的dc/dc电路引入了损耗，因此不适宜用在需要长续航的用电器上（遥控器，钟表，门锁）。用在这些用电器上，往往其内部的锂电池的电量，主要被 dcdc 电路待机消耗掉，而不是对外输出。&lt;/p&gt;

&lt;h1 id=&quot;电池尺寸规格&quot;&gt;电池尺寸规格&lt;/h1&gt;

&lt;p&gt;电池通常制作成圆柱形。圆柱形电池有3种不同的规格表示法。&lt;/p&gt;

&lt;p&gt;其一为最常见的X号叫法。常见为1号电池，5号电池，7号电池。1号电池体积巨大，目前使用的地方为灶台点火器、燃气热水器点火器。因为体积巨大，所以容量很大，那些功率大，又同时不想频繁更换电池的地方就会用1号电池。7号电池苗条迷人，常用于对体积有要求的设备上。如遥控器。5号电池体积适中，如果不是特殊要求，一般就会使用5号电池。&lt;/p&gt;

&lt;p&gt;除了这种常见的号标电池，还有高度超低的圆柱电池，因为高度很低，虽然还是圆柱形，但是太像纽扣而得名“纽扣电池”。纽扣电池体积小，还超薄，所以广泛使用在迷你型遥控器上。如汽车遥控器。还有主板断电后维持时钟，都采用更薄的纽扣电池，而不是7号电池。纽扣电池虽然常见的就一种规格，但是也不是没有其他大小。纽扣电池的大小标记法为直径+高度标记。最常见的型号为 CR2032, 也就是 20mm 直径，3.2mm 厚度。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;CR927&lt;/li&gt;
  &lt;li&gt;CR1025&lt;/li&gt;
  &lt;li&gt;CR1216, CR1220&lt;/li&gt;
  &lt;li&gt;CR1616, CR1620, CR1632&lt;/li&gt;
  &lt;li&gt;CR2012, CR2016, CR2025, CR2032&lt;/li&gt;
  &lt;li&gt;CR2320, CR2330, CR2354&lt;/li&gt;
  &lt;li&gt;CR2450, CR2477&lt;/li&gt;
  &lt;li&gt;CR3032, CR2412&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这么多不太常见的规格，其实都濒临淘汰了。&lt;/p&gt;

&lt;p&gt;还有一种圆柱规格，也是用直径+高度表示，但是高度更高，就不叫纽扣了，而是正经的圆柱形电池了。
这其中大名鼎鼎如雷贯耳的就是18650电池了。18650的意思是 18mm 直径+650mm高度。还有26650, 14500 等等各种次常见规格。特斯拉还凭一己之力把 21700 规格带入主流视野。&lt;/p&gt;

&lt;p&gt;圆柱形电池有统一的规格，但是，非圆柱形电池就完全没有统一规格了。厂家是想做什么尺寸就做什么尺寸，毫无规矩可言了。比如手机内使用的软包方壳电池。尺寸是五花八门。每款手机大小都不同。主打的就是不可替代性。售后换电池又可以坑一笔。&lt;/p&gt;

&lt;h1 id=&quot;排列组合&quot;&gt;排列组合&lt;/h1&gt;

&lt;p&gt;那么电池大小和电池的材料，一个排列组合下来，理论上有万种电池。&lt;/p&gt;

&lt;p&gt;但是，资本主义世界不讲数论。它讲效率。于是，只有几种常用的组合存世。
比如 18650 这个尺寸，就不会有镍氢电池，不会有碱性电池，他只有一种电池，就是锂电池。因此，只有3.7v电压的18650和 3.2v电压的18650。而不会有 1.5v 电压的18650。&lt;/p&gt;

&lt;p&gt;又比如 CR2032 电池，它只会有一种电压，那就是3v。&lt;/p&gt;

&lt;p&gt;在更早的时候，1号电池，5号电池这种号规电池，只有1.5v 一次性和 1.2v 可充电的组合。
不存在3.7v的五号电池这种规格。&lt;/p&gt;

&lt;p&gt;但是人民群众的需求是创造力的第一动力。很多使用5号电池的设备，都是采用3v电压供电。也就是2节五号电池串联。于是人民群众发挥了想象力，使用14500电池+一节导线，成功的将 3.2v 的磷酸铁锂电池代替了2节五号电池。在这个需求的刺激下，资本家开发了5号电池和7号电池尺寸的磷酸铁锂电池。
因此号标电池又有了 3.2v 这个电压。不过，使用3.2v电压的5号电池，必须短路电池仓里另一个电池位置。于是又有了 “占位筒” 电池。占位筒电池，就是单纯的一段导线做成了5号电池的形状。
如果不使用占位筒而直接放入2节锂电池，用电器就要承受6.4v的电压，显然无法正常工作甚至可能烧毁。&lt;/p&gt;

&lt;p&gt;但是使用占位筒大法，一是要求用电器必须使用偶数个5号电池供电。二是家里如果有不明真相的小孩,就容易误放置2节锂电池或放2节占位筒。&lt;/p&gt;

&lt;p&gt;于是，在人民群众的需求驱动下，资本家开发出了1.5v输出电压的5号电池。其实就是一个做成了5号电池外形的电源。内置了一个锂电池。可充电，对外恒压供电1.5v。由于不需要占位筒，不容易用错。而且比占位筒大法的容量更大。因为占位筒大法只有一节真电池。而降压锂电池，虽然被降压电路占据了一定的空间导致单节电池容量较小，但是两节合并的容量要大于一节+占位筒。&lt;/p&gt;

&lt;p&gt;好了。有了这些信息，以后再买电池就应该不会买错了。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>马赫原理</title>
   <link href="https://microcai.org/2023/09/26/mahe-law.html"/>
   <updated>2023-09-26T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/09/26/mahe-law</id>
   <content type="html">&lt;p&gt;离心力是怎么来的？&lt;/p&gt;

&lt;p&gt;牛顿说，没有离心力。是因为旋转的时候，物体要维持直线运动。离心力是向心力的反作用力。
马赫说，离心力是星辰对旋转物质的引力。&lt;/p&gt;

&lt;p&gt;牛顿有着绝对时空观。运动是绝对的。宇宙有一个绝对静止的参考系。
马赫说认为一切都是相对的。旋转也是相对的。&lt;/p&gt;

&lt;p&gt;在牛顿水桶思想实验里，牛顿认为旋转的水桶最终会让水面凹陷。
马赫则认为没有参考系无法确定水桶是旋转的。没有星辰，就没有引力带来的离心力。也就不会引起水面凹陷。&lt;/p&gt;

&lt;p&gt;同时马赫认为，如果星辰少一半，离心力也会小一半。&lt;/p&gt;

&lt;p&gt;爱因斯坦同意马赫的相对性原则，但是并没有同意马赫说的星辰少一半离心力也少一半的说法。&lt;/p&gt;

&lt;h1 id=&quot;宇宙在增重&quot;&gt;宇宙在增重&lt;/h1&gt;

&lt;p&gt;根据大爆炸模型，宇宙是从无到有从虚空中诞生的。而宇宙诞生的时候，物质并不如如今那么丰富。
宇宙间的物质，是随着宇宙膨胀的过程增生出来的。&lt;/p&gt;

&lt;p&gt;因为重力势能也是能量的一种。宇宙膨胀，那么星系之间相互远离，结果就是星系之间的重力势能增加了。
于是宇宙膨胀本身就为宇宙凭空创造了大量的能源。
这增加的能量，就等于宇宙增加了质量&lt;/p&gt;

&lt;p&gt;按马赫原理，这些全宇宙增加的质量，就会增加离心力的大小。&lt;/p&gt;

&lt;h1 id=&quot;自转速度过大的星系&quot;&gt;自转速度过大的星系&lt;/h1&gt;

&lt;p&gt;所以宇宙早期，因为总质量更低，所以离心力也更低。于是早期(遥远)的星系就表现为有更大的自转速度。&lt;/p&gt;

&lt;p&gt;现在的天文观测确实发现了星系自转速度过大的问题。但是把这个现象归结为暗物质。
而如果应用马赫原理，承认离心力取决于全宇宙物质总量，则问题迎刃而解。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>明亡于重士轻工</title>
   <link href="https://microcai.org/2023/09/16/ming-die.html"/>
   <updated>2023-09-16T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/09/16/ming-die</id>
   <content type="html">&lt;h1 id=&quot;明末财政危机&quot;&gt;明末财政危机&lt;/h1&gt;

&lt;p&gt;明朝末年，土地兼并严重。而大明的税收，是靠的 “自耕农”。自耕农破产，导致政府收不到税。
没钱就没兵。没兵就打不过满清。虽然是李自成灭的明朝，但是李自成攻入北京的时候，大明的精锐都在辽东打满清。可以说是满清吸引了大明的主力，让李自成差点摘了桃子。&lt;/p&gt;

&lt;p&gt;所以很多人说，大明亡于土地兼并。历朝历代皆如此。&lt;/p&gt;

&lt;h1 id=&quot;圈地运动&quot;&gt;圈地运动&lt;/h1&gt;

&lt;p&gt;在大明的士绅忙着土地兼并的时候，大英的贵族也在忙着搞圈地运动。
为何大明亡了，而大英却打败了西班牙成为新的海上霸主？&lt;/p&gt;

&lt;p&gt;难道大英不应该亡于土地兼并？&lt;/p&gt;

&lt;h1 id=&quot;儒教立国&quot;&gt;儒教立国&lt;/h1&gt;

&lt;p&gt;很多人认为，中国古代的封建王朝是以农业立国。其实不对。因为中国古代向来并不重视农业。
有人不信，拿出了古代各种重农抑商的政策来说明古代王朝以农业立国。&lt;/p&gt;

&lt;p&gt;那么为何，土豆在明朝传入中国，但要到清朝才大面积推广种植？
以农业立国的大明，却对粮食产量不甚关心？&lt;/p&gt;

&lt;p&gt;根本原因，是大明以儒教立国。&lt;/p&gt;

&lt;p&gt;儒家提倡的是阶级固定的社会。所以儒家定好士农工商四大阶级。并且禁止阶级流动。其中，
由于工商阶级对儒家的统治地位有威胁，所以儒家把工商定为贱籍。然后故意把农和士定为
高级人。本质是因为农对士毫无威胁。只要农不造士的反，农民吃不吃的饱的问题并不在士人
的关心范围内。&lt;/p&gt;

&lt;h1 id=&quot;压制流民&quot;&gt;压制流民&lt;/h1&gt;

&lt;p&gt;历朝历代，朝廷对流民都非常忌惮。因为失去土地的农民，只有两个去处：落草为寇和进城务工。
流民问题之所以难解决，是因为儒家并不希望流民进城务工。因为进城务工的流民，最终都会转换为工商阶级。所以儒家渲染流民只能落草为寇，必须恩剿并施。
所谓恩，就是分土地。所谓剿，无非就是镇压。
王朝初年，尚且可以同时两项操作，但在土地兼并的末期，往往只有剿一个办法。因为朝廷已经没有土地了。&lt;/p&gt;

&lt;p&gt;开疆拓土的时候，儒家会托词徒耗民力。不可轻起战事。
但是遇到流民的时候，儒家就不说好战必亡的话了。&lt;/p&gt;

&lt;h1 id=&quot;匠人世代为奴&quot;&gt;匠人世代为奴&lt;/h1&gt;

&lt;p&gt;朱元璋开创了中国版的种姓制度——匠户制和兵户制。&lt;/p&gt;

&lt;p&gt;兵户世代为兵，匠户世代为匠。&lt;/p&gt;

&lt;p&gt;虽然传统上，手工艺是家族代代传承的。但是这种家族传承的技艺有个弊病，就是无法形成理论体系。
每个工匠只知道自己家的那点道道。在宋代产生的资本主义萌芽，大商人开始雇佣大量的工人的时候，情况发生了变化。
商人对手下工人掌握的技术，肯定是不会让他们藏私，而是将大量工匠的技术结合起来，博采众长。并以商业世家的形
式进行集团化研究。于是就有了科学体系生长的温床。&lt;/p&gt;

&lt;p&gt;但是，明朝的匠户制打破了这一自发的自然过程。将科技重新“散户化”。&lt;/p&gt;

&lt;h1 id=&quot;散户匠人断科研路&quot;&gt;散户匠人断科研路&lt;/h1&gt;

&lt;p&gt;吃不饱的匠户，连传承知识都非常艰难。别提在前人基础上进行迭代了。
传承困难的一大佐证就是大量的匠户逃籍。&lt;/p&gt;

&lt;p&gt;于是匠人就越来越少了。大明的人口越来越多，但是从事科研的工作者却越来越少。难怪科技越来越倒退。&lt;/p&gt;

&lt;p&gt;科技倒退，导致大明严重依赖农业。所以圈地运动就成了亡国运动。而大英开设皇家科学院，圈地运动成了工业革命的催化剂。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>汽油机为何如此设计</title>
   <link href="https://microcai.org/2023/09/14/why-this-way-1.html"/>
   <updated>2023-09-14T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/09/14/why-this-way-1</id>
   <content type="html">&lt;h1 id=&quot;冲程&quot;&gt;冲程&lt;/h1&gt;

&lt;p&gt;汽车发动机有四个冲程，吸气，压缩，做工，排气。
为何是这4个呢？&lt;/p&gt;

&lt;p&gt;热机要工作，必须要燃烧，燃烧就要空气。废气要排出，所以吸气排气是必须的。
但是为何要先压缩呢？&lt;/p&gt;

&lt;p&gt;原来早期阶段，热机是没有压缩这个阶段的。但是奥托发现了压缩能提高热效率。
于是奥托发明了四个阶段循环的发动机。后来这4个循环就被称作奥托循环。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/Stirling_Cycle.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;后来这4个循环，被热力学理论解构。也就是上图的4条线构成的框。热机对外的做工，就体现在这个框里。&lt;/p&gt;

&lt;p&gt;如果有教科书说是因为热力学的4个循环指导了内燃机的设计，那肯定是不对的。是先有的内燃机的设计，后来才有理论去解释。而有了理论后，后续的迭代开发才会使用理论来指导改进。&lt;/p&gt;

&lt;h1 id=&quot;吸的不是空气&quot;&gt;吸的不是空气&lt;/h1&gt;

&lt;p&gt;在奥托循环里，发动机吸气冲程吸入气缸的不是空气。而是汽油和空气的混合气。
为何不是吸的纯空气，然后在压缩的时候再注入汽油呢？&lt;/p&gt;

&lt;p&gt;因为发明汽油机的那个年代，还不存在燃油的高压喷嘴。而非高压喷嘴，就无法雾化燃油。
没有雾化的燃油，就没法迅速的爆燃。所以那个年代就是靠汽油的挥发性，让空气通过一个叫化油器的东西来吸入汽油。&lt;/p&gt;

&lt;p&gt;而汽油发动机的上限，就被这个吸入混合气的过程给锁死了。&lt;/p&gt;

&lt;p&gt;因为汽油发动机吸入的是混合气，所以汽油发动机就不能无限提高压缩比。
因为气体被压缩就会升温。而奥托循环必须要在做工冲程里燃烧汽油。
就要限制压缩循环里混合气的温升。不然混合气提前爆燃，就破坏了热力学循环。&lt;/p&gt;

&lt;p&gt;经过实验发现汽油发动机的极限压缩比在 12：1。超过就很容易让混合气提前爆燃。&lt;/p&gt;

&lt;p&gt;如果吸入的是纯粹的空气，就可以无限提高压缩比了。高压缩比带来的是更高的热力学效率和更大的动力。这个想法指导下，发明出来的发动机就是 “压燃发动机”，但是压燃发动机在很长一段时间内，都只能使用柴油作为燃料。而无法使用汽油。&lt;/p&gt;

&lt;p&gt;所以汽油发动机就一直停留在吸混合气阶段。而只要吸的是混合气，就无法提高压缩比。&lt;/p&gt;

&lt;h1 id=&quot;为何要先压缩&quot;&gt;为何要先压缩&lt;/h1&gt;

&lt;p&gt;我不用热力学的晦涩知识，就用最一般最容易理解的话来讲，为何要压缩。&lt;/p&gt;

&lt;p&gt;燃料在密闭腔室燃烧会产生高温高压的空气。这这个高温高压的空气就有了推动活塞做工的动力。
随着活塞被空气推动，热空气的温度和压力都会下降。理论上来说，这股热空气可以一直膨胀到温度等于室温，压力等于大气压。但是，活塞是有阻力的。温度下降到一定程度后，这股热气的压力就无法推动活塞了。这个无法推动活塞的废气，温度依旧高达600 - 800 度。&lt;/p&gt;

&lt;p&gt;所以，未点燃的混合气温度越接近废气的温度，则燃料的化学能就越是被活塞提取利用了。
如果未压缩，那么燃料燃烧的化学能，有一大部分就留存在废气的温度里。
而压缩提前让混合气的温度提高。那么此时化学能就更多的被利用于推动活塞做工。也就是提高了燃料的能源利用率。&lt;/p&gt;

&lt;p&gt;如果让混合气在点燃前的温度就和排气的温度一样，就意味着燃料的化学能被全部利用了。当然这并不表示引擎的效率达到了 100%，因为压缩本身就要使用掉一部分机械能。&lt;/p&gt;

&lt;h1 id=&quot;为何油门其实是气门&quot;&gt;为何油门其实是气门&lt;/h1&gt;

&lt;p&gt;发动机并不总是要 100% 的干活。很多时候，他都要少干活。
控制发动机干活多寡的东西俗称油门。但是油门实际上并不控制喷油量。
油门直接控制的，是进气量。然后由于汽油发动机需要始终如一的空燃比，所以喷油器会减少喷油。
减少喷油是减少吸气的结果。&lt;/p&gt;

&lt;p&gt;如果油门控制的是喷油，而进气量不变，就很容易导致在小油门的情况下发生富氧燃烧。
也就是氧气的量大大超过燃油的量。&lt;/p&gt;

&lt;p&gt;正如缺氧燃烧会产生一氧化碳一样，富氧燃烧一样会产生污染物氧化氮。
为了减少尾气中的氮氧化物和一氧化碳，汽油和空气必须按比例混合。既不能让空气多，也不能让汽油多。&lt;/p&gt;

&lt;p&gt;所以，减少发动机出力，不能只靠减少喷油。而必须同步减少吸气量。&lt;/p&gt;

&lt;p&gt;恰恰汽油发动机调节功率靠的是调节进气量，导致汽油发动机实际压缩比是动态变化的。
动态变化的压缩比又导致汽油发动机无法采用“压燃”。&lt;/p&gt;

&lt;p&gt;而柴油污染大，恰恰是因为压燃的特点导致柴油在小油门下，是富氧燃烧。而大脚轰油门的时候，又是富油燃烧。导致不管是小油门还是大脚油门，都会产生严重的污染。&lt;/p&gt;

&lt;p&gt;大脚油门下排的黑烟，要靠“颗粒捕捉器” 去除未燃烧的碳。
小脚油门下排的氮氧化物，要靠 “尿素” 去除。&lt;/p&gt;

&lt;p&gt;如果要让柴油机减少污染，就只能让柴油机以固定的空燃比燃烧。。。 也就是固定扭矩使用。然而除非只用来当增程器，否则如此工况是断不可能满足的。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>中国古代名词解释</title>
   <link href="https://microcai.org/2023/05/23/guwen-xinjie.html"/>
   <updated>2023-05-23T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/05/23/guwen-xinjie</id>
   <content type="html">&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;士绅优待
  士绅优待, 指儒教免税. 是儒教自汉代罢黜百家后, 为儒教教徒门争取到的阶级红利.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;士绅一体纳粮
  指取消儒教特权. 是雍正未雨绸缪, 要为将来朝廷收不到自耕农的税后找退路. 从农民手上收钱改为从儒教收钱.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;与民争利
  指朝廷把税扩大到工商阶级. 朝廷要求商人纳税会导致商人地位上升. 威胁儒教地位. 所以儒教要求朝廷不得与民争利.
  只收农业税, 就可以把科举取士范围局限在士人阶级 ( _ 农民只有理论上参与科举的权利 _ ).
  工, 在古时候是指不读四书五经, 而学习科技的那批人. 这些读书人如果参与科举, 会严重影响到四肢不勤五谷不分的
  儒教地位. 正好这些人也没多少钱, 就给予他们免税. 所以朝廷只收农业税, 对儒教是最稳妥的.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;藏富于民
  指朝廷应该满足于农业税. 而让士人和商人掌握社会财富. 不要想着收商税, 不要想着士绅一体纳粮. 要做到藏富于民.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;功高震主
  指皇帝不应该重用非儒教出身的勋贵阶级. 不然就算他们不造反, 儒教的人也不介意推他们一把.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;歪理小说
  指四书五经以外的知识。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>罢黜百家, 自绝天下</title>
   <link href="https://microcai.org/2023/05/22/rujia-wuguo.html"/>
   <updated>2023-05-22T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/05/22/rujia-wuguo</id>
   <content type="html">&lt;p&gt;春秋战国时期, 中华大地上是百家争鸣.
何谓百家?&lt;/p&gt;

&lt;p&gt;研究回字写法是家, 研究如何让弓箭射的更远也是家.
百家, 不仅仅是文学上的百家, 也有理学工学上的百家.&lt;/p&gt;

&lt;p&gt;比如, 兵家, 研究兵法. 研究战阵. 不研究回字的9种写法.
农家, 研究农业, 改良种子, 改进犁具, 不研究回字的9种写法.
墨家, 研究机关, 改进工具, 不研究回字的9种写法.
医家, 研究医术, 治病救人, 也不研究回字的9种写法.&lt;/p&gt;

&lt;p&gt;但是, 这些不研究回字的9种写法的百家, 都不受研究回字的9种写法的儒家待见.
到秦朝初立, 百家就被儒家排挤的差不多了. 朝堂上就只剩下法家在苟延残喘了.&lt;/p&gt;

&lt;p&gt;法家对儒家的最后一击, 就是焚书坑儒.
儒家对法家的报复, 就是天下大乱.&lt;/p&gt;

&lt;p&gt;最后儒家一统天下, 罢黜百家, 独尊儒术.&lt;/p&gt;

&lt;p&gt;如果儒家罢黜的是其他研究回字写法的家, 那倒也没啥问题.
关键问题在于, 儒家罢黜的, 可不止是研究回字写法的家, 更是罢黜了数学家, 化学家, 医学家.
儒家把不研究回字写法的, 统统贬为 “奇技淫巧”.
不仅仅在朝廷上取缔, 更是在民间也取缔.&lt;/p&gt;

&lt;p&gt;从此, 中国社会陷入了2000年的停滞.&lt;/p&gt;

&lt;p&gt;很多人说, 儒家给中国带来了大一统. 其实是错误的. 给中国带来大一统的是法家. 秦以法家治国.
法家有大一统思想. 灭六国后, 法家坚持废除分封, 行郡县制. 儒家非要恢复周制, 行分封.&lt;/p&gt;

&lt;p&gt;刘备可是嬴政小谜弟, 他夺得天下后, 不得不 外儒内法.
外儒内法, 就是让法家变成1对1传承. 只有历代皇帝学法家. 天下人皆学儒家.&lt;/p&gt;

&lt;p&gt;到汉武帝时期, 最终罢黜百家, 独尊儒术. 连皇帝都只能学儒了.
这是因为刘彻并非既定的继承人, 没学过法家.&lt;/p&gt;

&lt;p&gt;这位没学过法家的皇帝, 就被董仲舒忽悠了.
从此, 儒家正式屠尽天下学派. 让中国人从此陷入回字写法的内卷之中.
连不参与朝廷斗争, 隐身于民间的学派都没有放过.&lt;/p&gt;

&lt;p&gt;为了打击异己, 儒家制定 士农工商 贱籍制度. 打压工商业. 明面上抬高农民, 实际上农民根本无力
承担学习重担. 最终就是独尊士族. 所谓士族, 其实就是儒家子弟. 从根本上破坏百家复苏的土壤.&lt;/p&gt;

&lt;p&gt;百家的知识, 最终不得不转战海外. 藏匿千年后传播到欧洲, 最终让欧洲文艺复兴, 完成工业革命.&lt;/p&gt;

&lt;p&gt;完成工业革命后的西方, 对东方降维打击. 让中国陷入了百年黑暗.&lt;/p&gt;

&lt;p&gt;学自西方的工业党, 最后力挽狂澜, 于1949年推翻旧社会.&lt;/p&gt;

&lt;p&gt;但是没过几年, 儒家再次卷土重来. 不得不顶着臭名也要 批林批孔.
儒家对中国的荼毒, 终于被消灭. 中国开始了现代化.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>中国人发明了数学</title>
   <link href="https://microcai.org/2023/05/07/chinese-invented-math.html"/>
   <updated>2023-05-07T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/05/07/chinese-invented-math</id>
   <content type="html">&lt;p&gt;很多人学习数学的时候很好奇, 为何全世界的数学都是十进制的. 然后就有谣言说, 是因为人有十指. 所以全世界的人都发明了十进制的数学.&lt;/p&gt;

&lt;p&gt;其实并不是. 虽然以符号语言为基础的数学确实是十进制体系的, 但是各个国家的语言里的数, 却并不是十进制体系的. 如果说原始人因手有十指就发明了数, 为何各种语言里, 数不都是十进制的?
难道原始人发明语言里的数的时候, 故意和他们的学科上的数学唱反调? 或者说, 原始人是先学会数学后学会说话? 既然是先学会说话后学会数学, 必然后学会的数学, 肯定使用语言里本身的进制.&lt;/p&gt;

&lt;p&gt;如果当今的数学是十进制的, 必然是因为这门学科, 诞生自一个使用十进制口语的民族.&lt;/p&gt;

&lt;p&gt;而口语不是十进制的民族, 其数学必然是舶来品, 而不是一个原生的发明.&lt;/p&gt;

&lt;p&gt;阿拉伯数字, 恰恰是古代阿拉伯人(一说是古印度人)为了学习某个古老民族的数学, 但是又无法承担学习其古老文字的代价而发明的助记符.
随着阿拉伯数字的扩散, 十进制的数学最终统一了世界.&lt;/p&gt;

&lt;p&gt;但是, 阿拉伯数字只统一了写在纸上的数学, 并没有统一口中说出的数学. 这也是另一种形式的 书同文, 但是语不同音.&lt;/p&gt;

&lt;p&gt;其实, 古老的民族里, 各种进制的数学都要. 而且更为广泛使用的其实是12进制. 在古汉语里, 12进制的遗留就是如今大家熟知的子丑寅卯. 12进制的源头, 来自月亮. 一年月圆12次. 于是古代先民发明了12进制. 但是同时古代先民也发明了十进制. 两种系统互相碰撞, 融合为六十进制的天干地支计数. 但是最终10进制胜出.&lt;/p&gt;

&lt;h1 id=&quot;一个数字如何表示又如何念&quot;&gt;一个数字如何表示又如何念&lt;/h1&gt;

&lt;p&gt;一个数字如何表示, 首先是确定进制. 然后将这个数字按进制下, 写下每个位的权重.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;比如 96, 由于阿拉伯数字是十进制的, 因此 96 的含义, 其实是 10进位上是9 , 个位上是6, 其表示的数量, 就是 九个十加六. 念 九十六&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;如果表示为 16 进制 (0123456789ABCDEF), 则是 表示为 60 , 表示 sixteen进位上是 6 , 个位上是 0. 也就是 6*(sixteen) + 0, 在 16进制的语言里, 念 6 sixteen zero.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;如果表示为 20 进制, (0123456789ABCDEFGHIK) 则表示为 4G 念 4 vingt sixteen.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;是不是突然发现, 法语是20进制的?&lt;/p&gt;

&lt;p&gt;实际上, 英语源自法语, 所以英语最初也是20进制的.
但是, 英国人务实, 所以他们从阿拉伯数字学到了十进制, 于是20进制仅仅用在描述 0 - 20 的数字. 21 开始, 就采取的是十进制读法. 九十六, 里的 十, 在英语里念 ty , 也就是 nine ty six .&lt;/p&gt;

&lt;p&gt;而法国人, 有法国人的骄傲, 所以必须在口语里, 既然转换为20进制, 念 quatre vingt seize . 也就是 4 个 vingt 进位 又 seize 个.&lt;/p&gt;

&lt;p&gt;也许小数字, 法国人还可以心理默默执行进制变换. 但是更大的数字怎么办?&lt;/p&gt;

&lt;p&gt;于是法国人的做法是, 把数字 3 位一组, 对每3位执行一次 10 -&amp;gt; 20 进制变换.
其实非常类似计算机里的 BCD 编码, 也就是不完全进制转换.&lt;/p&gt;

&lt;p&gt;法国人早期统治了学术界, 所以就诞生了千进位为基础的国际单位制.&lt;/p&gt;

&lt;h1 id=&quot;进制是一个后天习得的技能&quot;&gt;进制是一个后天习得的技能&lt;/h1&gt;

&lt;p&gt;还记得小学一年级的时候是怎么学习数字的吗?
老师是一步一步的教大家进位的概念.
所以人脑其实并不天然的具备十进制. 而是后天习得.
所以, 全世界使用的数学如果最初是法国人发明的, 必然是20进制的.&lt;/p&gt;

&lt;p&gt;虽然是个后天技能, 但是进制系统, 确实是有优劣之分的. 最终采取十进制的民族, 必然就在数学上有先发优势.&lt;/p&gt;

&lt;p&gt;计算机最适合二进制, 人类也最适合十进制. 但是计算机并不一开始就使用二进制, 第一台计算机是十进制的. 但是二进制化后, 计算机才飞速发展起来. 人类也不是一开始就接受十进制. 但是自从使用了十进制, 人类的数学就飞速发展起来了.&lt;/p&gt;

&lt;p&gt;十三世纪之后，欧洲人才开始接触到十进制，但直到印刷术的流行，十进制才在欧洲真正普及。&lt;/p&gt;

&lt;p&gt;当时，一个叫斐波那契的意大利商人后代，由于痴迷数学，深感阿拉伯数字比罗马数字表达得更便捷，于是特意去国外向阿拉伯数学家学习，大约公元1200年回国。回国之后，斐波那契写下了《计算之书》、《几何实践》两本书，里面就介绍了十进制记数方法的实用价值，这两本书也是欧洲近代数学的开端.&lt;/p&gt;

&lt;p&gt;当时欧洲各国记数方式并不统一，如今各国文字上还保留一些痕迹，比如法国采用的是20进制等，这一现状导致欧洲在数学上很难有突破性的成就，各国在数学交流上也面临制式不统一的难题。&lt;/p&gt;

&lt;p&gt;但是, 随着十进制在欧洲的普及, 欧洲各国的数学都意识到了十进制的优越性. 于是不管母语是几进制的, 欧洲的数学家们纷纷改用十进制, 并以阿拉伯数字作为通用文字.&lt;/p&gt;

&lt;h1 id=&quot;古阿拉伯人还是古印度人还是古中国人发明了十进制&quot;&gt;古阿拉伯人还是古印度人还是古中国人发明了十进制&lt;/h1&gt;

&lt;p&gt;可见, 现代数学, 其实是阿拉伯数学传入欧洲后发展起来的. 阿拉伯数学是十进制的, 自然现代数学就是十进制的.&lt;/p&gt;

&lt;p&gt;但是阿拉伯数字, 其实是印度人发明的. 所以, 十进制的源头, 应该是印度人?&lt;/p&gt;

&lt;p&gt;阿拉伯数字, 是六世纪左右由印度传入阿拉伯的. 但是, 中国使用十进制数字的历史, 要更为久远. 早在8000年前的考古遗址上, 就发现了十进制的踪迹.&lt;/p&gt;

&lt;p&gt;早在南北朝时期, 中国和古印度就有了文化交流, 因此6世纪的印度发明的阿拉伯数字, 必然是因为同中国的文化交流学去的十进制. 因为汉字过于复杂, 所以印度人需要更简便的符号替代一二三四五六七八九十.&lt;/p&gt;

&lt;p&gt;这就是汉语念阿拉伯数字符号表示的数字, 为何念起来非常自然, 而且我们并没有因为阿拉伯数字的传入而改变我们念数字的方式. 我们念数字的方式, 5000年来从未变过.&lt;/p&gt;

&lt;p&gt;所以, 阿拉伯数字, 属于出口转内销.&lt;/p&gt;

&lt;h1 id=&quot;是我们的祖先-发明了十进制&quot;&gt;是我们的祖先, 发明了十进制.&lt;/h1&gt;

&lt;p&gt;可耻的欧洲人, 为了掩盖历史, 故意说是因为人有十指所以数学使用十进制. 但是他们无法解释为何他们的日常语言并不是十进制的.&lt;/p&gt;

&lt;p&gt;值得一提的是，很多以欧洲人姓名命名的数学原理或公式，其实中国人早已研究出来了，比如南宋“杨辉三角”在西方被称为“帕斯卡三角”，前者比后者早了400多年，南北朝“祖暅原理”在西方被称为“卡瓦列利原理”，前者比后者早了1100多年，因此并不是中国古代数学成就不高，而是传播度不够，以及欧洲掌握了近现代的话语权。所以说，落后不仅要挨打，连说话的权力都没有，只能眼睁睁地看着别人“窃取”祖先的成就。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>谁在害怕英语主科地位消失</title>
   <link href="https://microcai.org/2023/03/06/we-must-boycot-english.html"/>
   <updated>2023-03-06T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/03/06/we-must-boycot-english</id>
   <content type="html">&lt;p&gt;每隔几年, 就有代表提议取消英语主科地位. 每次都无功而返.&lt;/p&gt;

&lt;p&gt;每次网上掀起取消英语地位的讨论, 都有很多很多的反对声.&lt;/p&gt;

&lt;p&gt;支持取消英语主科地位的有两类人&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;A类是学习不好, 少学一门是一门. 取消啥学科他们都支持.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;B类是坚信 为中国人不再学英语而努力的学好英语. 如今他们长大了, 觉得时机成熟了&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;而不支持英语取消主科地位的有三类人&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;C类, 自然是搞英语培训产业的. 都上市公司了, 总不能一夜回到解放前吧.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;D类是布林肯. 他反对中国中国在边疆地区普及普通话. 说边疆的孩子学了普通话, 就丢掉了自己的文化. &lt;img src=&quot;/images/blk-anti-han.png&quot; alt=&quot;不能学普通话啊&quot; /&gt; 同时他大力赞扬 乌兹别克斯坦人学英语 &lt;img src=&quot;/images/blk-promot-english.png&quot; alt=&quot;学英语才能当好奴才&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;剩下的F类则是被不林肯忽悠瘸了, 认为国家取消英语地位, 就没人学, 也没人教了. 然后他们想出国或者搞科研要研究国外的文献, 就得自己掏钱去培训班花大钱了.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CD两类人知道自己的观点是站不住脚的. 所以他们采取的策略, 就是不断的在庞大的F类人里寻找代理, 用巨大的水军, 把妄图取消英语地位的人淹死.
而F人的诉求, 看起来是多么的冠冕堂皇, 使得反对者禽兽不如了. 他们污蔑学不好的A类人, 说他们是笨蛋, 还说不取消英语地位, 就是为了鞭策他们好好学习. 他们污蔑学的好的B了类人, 说他们是居心不良, 是想让A类人永远法挑战他们了. 这是在挑动 AB类的内斗.&lt;/p&gt;

&lt;p&gt;但是 B 类的人永远是人数稀少, 数量庞大的A类的人一听 F 类的歪理, 就不自觉的和B类的人划清界限了.&lt;/p&gt;

&lt;p&gt;于是, ACDF 合起来反对 B 类人, 使得他们的努力一直在作废.&lt;/p&gt;

&lt;p&gt;近年来的趋势就是 A F 类的人在觉醒. A 类的人明白过来, 如果非要少一门学科, 那只能是英语. 谁要说取消语文数学, 他们不会再支持. F 类的人也逐渐的明白过来, 即使中国取消英语的地位, 也不是闭关锁国.&lt;/p&gt;

&lt;p&gt;CD两类人彻底的慌了.&lt;/p&gt;

&lt;p&gt;如今他们还在垂死挣扎. 但是我相信, 时间不会等太久.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;下面的话是对 F 类的人说的, 帮助他们尽快的从 CD 类人的谎言中清醒过来.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;迷信: 现在的世界, 英语仍然是主流. 不学英语就不能融入世界
    &lt;blockquote&gt;
      &lt;blockquote&gt;
        &lt;p&gt;破解: 从来就没有融入世界这个说法. 只有同化世界, 和被世界同化. 如果认为学英语是主流, 那就是同意, 我们要被美国同化. 不被美国同化, 就没有未来. 想想千年以前, 盛唐在世的时候, 全世界的人都来长安朝拜. 他们要融入大唐. 不融入大唐, 就是被世界抛弃. 只不过如今, 风水轮流转罢了. 恰恰我们要重新捡起我们的文化, 我们学英语, 不是为了融入美国, 而是为了渗透美国, 师夷长技以制夷. 所以, 社会有分工, 我们不需要所有人都去学英语. 既然文理有分科, 为何不承认, 英语也可以分科, 让擅长的人学擅长的科目呢? 恰恰是如今全民学英语的氛围, 导致了翻译人才的缺失. 译者甚至不再追求信达雅, 反而被批评翻译的不够好的时候丢一句 “你不会去看英语原文吗?”&lt;/p&gt;
      &lt;/blockquote&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;迷信: 取消英语主科地位, 穷人就没有出路了
    &lt;blockquote&gt;
      &lt;blockquote&gt;
        &lt;p&gt;破解: 体育不是主科地位, 所以我们四肢不发达的穷人就没有出路了. 因为学校不再教我们体育. 而那些有钱的家庭就可以参加各种课外培训, 把自己练的身强力壮. 然后我们四肢不发达的穷人就彻底的没出路了.
哦? 我说错了? 这种事情并没有发生? 那是当然的啊! 取消了英语地位, 虽然说穷人就没有国家提供的免费英语教学了. 可是同样的, 穷人也不需要在英语上和富人卷了. 他们所说的那种情况会发生, 那只能是国家取消英语的主科地位, 但是高考还是要考英语. 这才会导致有钱人可以偷偷请老师学英语然后卷死穷人. 其实, 教育什么时候是免费的? 所谓免费的英语老师, 那是占用了多少本应该让他们学好数理化的国家教育经费?
科学技术才是第一生产力. 什么时候会英语成了第一生产力? 我们把有限的教育投入, 将其中的很大一部分拿来教英语, 自然剩下的科学技术就分不到多少教育资源了. 这才是穷人无法翻身的根本所在.
我们造原子弹的时候, 高考还没有英语. 有人说那是靠归国的科学家. 但是这部分留美的科学家, 他们成长于民国时代, 那时候的高考也没有英语. 而且一个好汉三个帮. 没有国内自己培养的数量庞大的科学家, 只靠从美国回来的科学家, 是不可能造出原子弹的. 也恰恰因为那时候没有全民学英语, 所以翻译的人对待工作特别认真. 他们要确保自己的翻译信达雅, 要完整的保留英文原文的意义, 将这个意思传达给国内大多数没学过英语的科学家.&lt;/p&gt;
      &lt;/blockquote&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;迷信: 英语降低地位变成课外”兴趣” 反而会加重学习负担
    &lt;blockquote&gt;
      &lt;blockquote&gt;
        &lt;p&gt;破解: 这是我听过最好笑的论断了. 他们说, 现在的教育不卷主科光卷兴趣班了. 所以英语不是主科了, 家长会加重英语的投资. 可是体育也不是主科, 我从未见到家长加重体育投资的事情发生过. 而且兴趣科目那么多, 为啥家长一定要卷英语? 这不还是英语中心论的殖人思想在作祟么?&lt;/p&gt;
      &lt;/blockquote&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;只有殖民地才会把宗主国的语言列为必学
CDF说的那些道理, 恰恰是公知们用来反对取消英语地位的.
他们非常害怕中国人不再为英语背书. 非常害怕中国人不再当殖民地, 不再为英语卑躬屈膝.
磊哥可能是被某些隔壁人大代表搞怕了, 以为他们人人都是资本家的代表.
中国人崇洋媚外的心态恰恰是英语必修导致的.
恰恰是很多人学不会英语, 所以导致大量的英语课外辅导班. 资本家靠教人说英语, 就能赚到去美国上市&lt;/p&gt;

&lt;p&gt;布林肯：中国新疆孩子学习普通话是文化种族灭绝。
布林肯：乌兹别克斯坦的孩子学习英语对他们的未来有好处。&lt;/p&gt;

&lt;p&gt;说明美国恰恰害怕中国人不再学习英语.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>都是唐人</title>
   <link href="https://microcai.org/2023/03/03/we-are-all-from-datang.html"/>
   <updated>2023-03-03T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/03/03/we-are-all-from-datang</id>
   <content type="html">&lt;p&gt;秦始皇统一了天下, 从此大家都是秦人. 刘邦建立汉朝后, 大家都是汉人.&lt;/p&gt;

&lt;p&gt;李世民建立大唐后, 从此大家都是唐人.
千年后, 哪怕远去他乡, 还自称 “新唐人”&lt;/p&gt;

&lt;p&gt;那么, 唐人都有哪些呢?&lt;/p&gt;

&lt;p&gt;首先看大唐的疆域.&lt;/p&gt;

&lt;p&gt;大唐的疆域, 西到碎叶城, 北至北冰洋 东北到日本海, 东到东海, 南到南海, 西南到喜马拉雅山.&lt;/p&gt;

&lt;p&gt;为历朝历代最大. 而且全部都派遣了官吏治理, 而不是粗旷式的治理. 凡此疆域内诞生的人民, 即是大唐的子民. 都享有读书做官的权力.&lt;/p&gt;

&lt;p&gt;最西部碎叶城出生的李白, 都能到长安做官, 而不是所谓的 “化外之民”.&lt;/p&gt;

&lt;p&gt;因此, 以后, 凡是在这片区域内诞生的人, 都可以”争天下”. 哪怕大唐亡国了, 他们也可以说他们的先祖曾是大唐的子民.&lt;/p&gt;

&lt;p&gt;有人要反驳, 说大唐的北部疆域到不了北冰洋.
那么请问, 大唐的北边还有敌人吗? 既然北边没有敌人, 你怎么能给这些地方生造一个主人呢?
没有敌人, 领土自然是自动延伸到海岸线.&lt;/p&gt;

&lt;p&gt;你不能说, 只有唐人走到过的领土才算唐人的领土. 冰岛人真的走完所有冰雪覆盖了的冰岛了吗?&lt;/p&gt;

&lt;p&gt;既然大唐建立了安北都护府, 并且没有第二个国家反对大唐的主权伸张, 不能因为安北都护府的人没有走到北冰洋, 就说大唐的领土北不达北冰洋.&lt;/p&gt;

&lt;p&gt;西达碎叶城, 是因为更西的地方, 是阿拉伯人的底盘. 向西扩充, 就会遇到阿拉伯人的抵抗.
只能靠武力扩张, 而大唐兵力最西也只能抵达碎叶城.&lt;/p&gt;

&lt;p&gt;当时大唐向东北扩展, 就遇到了另一个国家的阻挠. 那就是高句丽.
很多人把高句丽看成是半岛政权. 那是不对的.&lt;/p&gt;

&lt;p&gt;高句丽盘踞半岛和辽东的肥沃土地. 阻碍了大唐向东北的扩张.
所以, 大唐必灭之.&lt;/p&gt;

&lt;p&gt;高勾丽灭后, 高句丽的领土, 自然就纳入大唐的版图. 同时, 没了高勾丽的阻碍, 大唐可以继续向东北扩张.
然后为了管理东北, 建立了安东都护府. 唐人向东北扩展, 只要没遇到另一个国家, 大唐的领土应该也是自动延伸到海岸线为止.&lt;/p&gt;

&lt;p&gt;事实上是, 确实没有遇到另一个国家了. 强大的高勾丽都被灭了, 继续东北前进, 当地的部落还能抵抗?
只能自动归顺成为安东都护府治下子民了.&lt;/p&gt;

&lt;p&gt;而在西南方向, 大唐和吐蕃打了一战. 吐蕃投降, 成为附属国.&lt;/p&gt;

&lt;p&gt;什么叫附属国? 附属国, 就是大唐的吐蕃自治区. 行政长官称吐蕃国王.
皇帝册封国王, 那是再正常不过了.
周天子册封了多少国王?
也就嬴政头铁, 不愿册封. 但是刘邦建国后, 还不是封了一堆国王. 以至后来有了七王之乱.&lt;/p&gt;

&lt;p&gt;如果附属国还有独立主权, 那为啥要要战败后才肯接受册封. 应该立国就马上屁颠屁颠的跑去求册封.
哦不, 为啥要被册封呢?&lt;/p&gt;

&lt;p&gt;因为松赞干布是篡权夺来的政权, 他要不接受皇帝册封, 他在当地根本没有合法性, 分分钟被贵族推翻.
所以他在战败后, 马上投降成为属国. 因为不投降, 自己就要被推翻了.
所以说, 吐蕃国王的统治合法性, 本身就来自李世民的册封. 被李世民册封后, 那些反对他的贵族们, 纷纷失去了造反的能力. 因为知道打不过大唐的军队. 他们要造反, 造的就不是吐蕃国王的反了, 而是造的大唐的反了.&lt;/p&gt;

&lt;p&gt;那些说册封不是领土的, 可以歇息了.&lt;/p&gt;

&lt;p&gt;可以说, 当今中国的版图, 是继承于唐, 而不是清.
清收复这些地方, 凭借的本身就是唐的法理.
而且, 清本身能入主中原, 也是因为安东都护府提供的法理. 因为女真族的祖先, 本就是唐人.&lt;/p&gt;

&lt;p&gt;高勾丽被灭后, 其实半岛就纳入中原版图了.
朝鲜半岛从此成为中国的一部分, 直到甲午战争, 战败后割让给了日本.
日本战败后, 顺应二战后各殖民地独立的潮流, 没有归还朝鲜半岛, 而是让其独立了.&lt;/p&gt;

&lt;p&gt;虽然半岛从明代开始, 都是以”皇明朝鲜国”的身份存在, 而不是直接推行郡县制. 但是历代朝鲜国王的统治合法性, 都来自中原皇帝的册封. 否则朝鲜半岛何来一个超越王朝周期律的政权?&lt;/p&gt;

&lt;p&gt;清朝对中原版图的扩张贡献, 可能仅仅是彻底征服越南, 使越南成为属国.&lt;/p&gt;

&lt;p&gt;但是, 大清在鸦片战争后, 就把越南割让给了法兰西.&lt;/p&gt;

&lt;p&gt;都说大清割地赔款割地赔款, 结果历史学的不好的人, 只记住了外东北和香港岛.
其实大清割让出去的价值最高的三块土地, 就是 越南朝鲜和台湾.
不过日本投降后, 台湾最后还回来了.&lt;/p&gt;

&lt;p&gt;由于如今朝鲜和越南已然独立建国, 所以历史课本就不再把朝鲜和越南视为割让领土罢了.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>电机和电车的一点想法</title>
   <link href="https://microcai.org/2023/02/28/thoughts-on-motor-and-ev.html"/>
   <updated>2023-02-28T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/02/28/thoughts-on-motor-and-ev</id>
   <content type="html">&lt;p&gt;除了有刷电机外, 无电刷的电机, 驱动起来是一门学问.&lt;/p&gt;

&lt;h1 id=&quot;反电动势&quot;&gt;反电动势&lt;/h1&gt;

&lt;p&gt;在讲解这门学问前, 先回顾一下物理学上的一个公理, 能量守恒定律. 能量守恒定律在电机控制领域非常重要, 电机的很多特性, 都来自宇宙要遵守能量守恒定律导致的.&lt;/p&gt;

&lt;p&gt;反电动势是电机达成能量守恒定律的一个重要手段.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;电机的输入电流 = (电池电压\*占空比 - 电机反电动势) / 绕组阻抗
绕组电流 = 电池电压\*输入电流 / 反电动势
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里面的原因是, 电机的输入电流, 也就是从电池汲取的电流, 功率 = 电池电压*电机输入电流.&lt;/p&gt;

&lt;p&gt;但是, 电机的扭矩来自于绕组的电流(达到磁通饱和前, 电机扭矩正比于绕组电流), 而和电机的转速无关. 因此绕组电流和输入电流不是一回事.
如果绕组电流等同于电池供应给电机的电流, 那么电机就违背了能量守恒定律.&lt;/p&gt;

&lt;p&gt;因为电机的功率 = 扭矩*转速, 而反电动势正比于转速, 扭矩正比于绕组电流. 所以电机的功率 = 绕组电流*反电动势&lt;/p&gt;

&lt;p&gt;这里, 就要得出一个 绕组电流*反电动势 = 电池电压*电机输入电流 的能量守恒来.&lt;/p&gt;

&lt;p&gt;如果电机没有反电动势, 能量守恒就要被破坏.&lt;/p&gt;

&lt;p&gt;正是因为绕组电流和电池供应的电流不同, 所以电机控制器, 不仅仅承担为电机”电子换相” 的工作, 也承担 DC/DC 变压的工作. 准确的来说, 是 Buck Converter.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/Buck_circuit_diagram.svg/600px-Buck_circuit_diagram.svg.png&quot; alt=&quot;降压变换器电路图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;绕组电流需要”续流二极管” 的存在, 这个存在, 是由开关管上的寄生二极管承担的.&lt;/p&gt;

&lt;p&gt;因此, 在电机控制器里, 承受功率的开关管, 其工作电流实际上是和电机扭矩有关, 而和电机输出功率无关. 正是为了保护控制器里的开关管, 同时也避免电机内部磁通饱和, 所以电机的扭矩调教为 初段恒扭矩, 后段恒功率.&lt;/p&gt;

&lt;p&gt;如果一直要输出最大功率, 想想在低速下, 扭矩 = 功率/转速. 转速为 0 或者接近 0 的情况下, 扭矩要无限大. 这在工程上是无法实现的. 所以电机在低速下只能恒扭矩, 随着转速的提升而提升功率. 到了后期, 电机就进入恒功率模式, 扭矩随着转速的提高而下降.&lt;/p&gt;

&lt;p&gt;而电机进入恒功率模式, 恰恰又是因为反电动势的存在, 限制了电机的电流.&lt;/p&gt;

&lt;p&gt;因为, 实际上加在绕组两端的电压, 等于 输入电压/反电动势. 当反电动势接近电池电压后,
绕组两端的电压 / 绕组阻抗 后, 就无法维持更大电流了. 因为随着转速的提高, 电机的换相速度也提高了, 也就是提高了交流电的频率. 那么电机的绕组作为一个感性元件, 其交流感抗也在提高. 电压降低, 阻抗提高, 电流就不得不继续降低了.&lt;/p&gt;

&lt;p&gt;电机从恒扭矩到恒功率转折点就是一个非常关键的指标.&lt;/p&gt;

&lt;p&gt;反电动势是限制电机最高转速的最关键原因. 电池电压必须高过反电动势, 才能让电机继续提速.&lt;/p&gt;

&lt;p&gt;降低反电动势, 就是要减少绕组, 但是会降低电机扭矩.
所以高扭和高转在电机上就是个矛盾体.&lt;/p&gt;

&lt;h1 id=&quot;方波-vs-正弦波&quot;&gt;方波 vs 正弦波&lt;/h1&gt;

&lt;p&gt;无刷电机, 通常会分为直流无刷电机, 或者是永磁同步电机. 他们的结构都是一样的 : 绕组定子+永磁体转子.&lt;/p&gt;

&lt;p&gt;区别就在于, 直流无刷电机, 使用的是6步换向法驱动. 每时每刻只有2条线是通电的. 另一条线悬空. 而永磁无刷电机, 使用的是矢量控制驱动, 让三相电构成的磁场矢量, 始终与定子的磁场矢量垂直以获得最大扭矩.&lt;/p&gt;

&lt;p&gt;一个电机, 使用6步换向法驱动, 还是使用矢量控制驱动, 关键在于其反电动势的波形.
如果反电动势的波形为三相交流电, 则应该使用矢量控制驱动. 如果反电动势的波形为梯形波, 则使用6步换向法驱动.&lt;/p&gt;

&lt;p&gt;6步换向法驱动, 电机每旋转一周, 就要执行6次换向. 每次换向, 不可避免的会带来冲击.
而使用矢量控制驱动, 电机可以输出恒定扭矩, 不会有换向冲击.&lt;/p&gt;

&lt;p&gt;但是为了合成正弦波, 控制器必须使用频率更高的开关速度才能调制出完美的正弦波. 开关频率越高, 波形越完美. 波形越完美, 则输出越稳定, 电机效率也越高.&lt;/p&gt;

&lt;p&gt;同时, 为了定子的合成磁场始终垂直于转子, 需要更高精度的转子位置传感器. 所以矢量控制驱动法的控制器成本会高于6步换向驱动.&lt;/p&gt;

&lt;p&gt;方波相比正弦波, 有个优势就是能 100% 利用电池电压. 而使用正弦波, 电池电压只能用于提供交流电波形里的峰值电压, 而交流电的有效电压只能是电池电压的 1/ √(3) .&lt;/p&gt;

&lt;p&gt;为了克服矢量驱动不能有效利用电池电压的缺点, 有发明一种叫 “马鞍波” 的过调制方法.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/svpwm-maan.jpg&quot; alt=&quot;马鞍波&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这种调制方式可以提高母线电压的利用率.&lt;/p&gt;

&lt;h1 id=&quot;动能回收&quot;&gt;动能回收&lt;/h1&gt;

&lt;p&gt;永磁电机可以当作发电机使用, 将汽车的动能回收回电池.&lt;/p&gt;

&lt;p&gt;但是具体的回收方式, 又分为 负扭矩法, 和整流升压法.&lt;/p&gt;

&lt;p&gt;整流升压法最简单, 就是把电机当成发电机, 发电机输出的三相交流电, 桥式整流后升压, 就可以给电池充电了. 但是这个方法其实在工程上并不使用.&lt;/p&gt;

&lt;p&gt;工程上使用的另一个能量回收的方法, 其实非常的简单, 就是小 pwm 法.
就是使用更小的 pwm , 使得 电池电压*pwm &amp;lt; 当前转速的反电动势.&lt;/p&gt;

&lt;p&gt;只要当前的 pwm 设定会导致控制器输出电压小于电机反电动势电压, 则控制器的逆变桥会反过来, 工作在 Boost 升压状态, 将电动机的电流反馈到输入端.&lt;/p&gt;

&lt;p&gt;因为 6 个开关管构成的逆变桥, 会本能的工作于四象限状态. 只要开关管对应的状态, 和电机转子位置一致即可. 因为电机在工作的时候, 功率因素是不可能为 1 的. 所以电流滞后于电压, 导致电机总是会在每个周期里向输入端回馈部分能量. 所以6个mos构成的逆变桥是天然的工作在4象限的.&lt;/p&gt;

&lt;p&gt;实际上, 三相PFC电路, 就可以看成是对一个恒定转速的电机进行动能回收.&lt;/p&gt;

&lt;p&gt;这种模式的动能回收, 有个缺点, 就是如果转速下降后, 不及时停止, 就会从回收模式进入驱动模式. 而整流升压法就不会有最低回收速度限制.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>GUI 渲染延迟</title>
   <link href="https://microcai.org/2023/02/17/gui-render-latency.html"/>
   <updated>2023-02-17T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/02/17/gui-render-latency</id>
   <content type="html">&lt;p&gt;鼠标点击窗口标题栏, 然后快速拖动. 如果窗口和鼠标紧紧贴到一起, 那么恭喜你, 你的OS并没有GUI渲染延迟问题.
但是, 经过实验后, 各位想必都会发现, 往往是鼠标指针先到达, 然后窗口才会跟过去. 窗口的移动似乎总是延后于鼠标指针.
如果各位是从旧时代过来的人, 应该还记得, 早期的 WinXP, 窗口拖到是非常跟鼠标的.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;没有渲染延迟问题的意思是, UI 的反应总是在下次屏幕刷新的时候出现. 也就是最多延迟为一帧, 最少可以恰好没延迟. 平均延迟时间为 0.5帧.

我们设想屏幕为班车, 如果设定刷新率为 60hz, 就是每 1/60 s 就发车. 而用户的输入, 就像随机到站的乘客.
如果到站后, 总是能乘坐下一趟车, 那么就可以认为 UI 没有延迟. 虽然总体上是平均延迟了 0.5帧, 但是本篇
不考虑总延迟, 只考虑 &quot;错过&quot; 班车的情况. 因此我以 &quot;错过&quot; 班车的数量来定义延迟. 因为鼠标指针的绘制,
是独立的路径, 因此鼠标指针永远不会错过班车. 但是, 窗口的内容绘制, 可能会错过班车. 所以可以以拖动窗口
来观察渲染延迟.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如今不跟了, 主要原因, 其实是现在普遍使用的桌面混成功能导致的.
在非混合时代, 窗口的内容是直接对应显卡上的屏幕输出缓冲区. 而在桌面混成时代, 所有的窗口都有一个独立的缓冲区. 由混成器
将这些图片合成到屏幕上. 也就是说, 要将大量的图片, 合成为一张图片.&lt;/p&gt;

&lt;p&gt;而混成器, 为了避免屏幕撕裂, 必然会打开垂直同步. 可是, 打开垂直同步, 为何会导致错过班车呢?
这就不得不说到目前的 UI 库了. 目前 gui 程序使用的 UI 库本身, 也是打开了垂直同步的.&lt;/p&gt;

&lt;p&gt;于是, GUI 程序更新窗口内容的时候, 会等待下一班车, 虽然他们绘制足够快的话, 看起来也总是能搭上最后一趟.
但是, 关键在于, 他们的班车, 也是 1/60 s 发车, 但是目的地却不是屏幕, 而是把人载到合成器的车站里. 等待合成器的下一趟班车.
由于 GUI 的班车和 屏幕的班车是同时发车的, 所有他们到达屏幕车站后, 必然只能等下一趟.&lt;/p&gt;

&lt;p&gt;于是, 所有的 GUI 内容更新, 一定一定会错过一帧.&lt;/p&gt;

&lt;p&gt;也就是, GUI 内容的更新, 平均延迟时间是 1.5帧. 这还是在 GUI 程序绘制时间为 0 的情况下. 如果绘制时间超过 1/60s , 则延迟时间还要上升到 2.5帧.&lt;/p&gt;

&lt;p&gt;让问题更雪上加霜的是, 现在的显卡驱动, 即便你只要求打开 vsync ( 也就是使用双缓冲 ), 驱动都会在内部实现里, 偷偷给你搞成 三缓冲. 于是这混成器
和app两边各增加一帧延迟.&lt;/p&gt;

&lt;p&gt;最终 GUI 程序的平均延迟时间就达到了恐怖的 3.5 帧了, 也就是 60hz 刷新率的条件下, 平均延迟 60ms. 当 8ms 延迟的鼠标指针喷上 60ms 延迟的窗口内容,
自然就能很明显的分辨出窗口的迟滞了.&lt;/p&gt;

&lt;p&gt;这些延迟, 对所有开启混合功能的 OS 平台都是适用的.&lt;/p&gt;

&lt;p&gt;其中, 早期, 各大平台的策略是对全屏显示的窗口关闭混合功能. 以消除混合功能带来的延迟对游戏的影响.&lt;/p&gt;

&lt;p&gt;到了 wayland 时代, 由于混成功能是必需品, 所以 wayland 有更大的必要去解决混成导致的延迟.
wayland 提出的方法则是 frame callbacks. 除了混成器需要等待 vblank 信号, 其他 app, 使用 frame callback 来确定什么时候上车.
这样保证 app 的车到站后, 恰好遇到 混成器发车. 于是解决错过班车导致的延迟问题.&lt;/p&gt;

&lt;p&gt;但是很可惜, frame callback 机制下, 混成器给app发信号的时机点选择非常重要. 只有恰到好处的时间点, 才能同时兼顾流畅性和低延迟.
KDE 直到 5.21 才解决好.  gnome 则还在 Work In Progress 中.&lt;/p&gt;

&lt;p&gt;不过 KDE 最新版本都到 5.27 啦, 所以其实 KDE 早就解决了. 嘿嘿.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Wayland 已经可用</title>
   <link href="https://microcai.org/2023/02/15/wayland-usable-now.html"/>
   <updated>2023-02-15T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/02/15/wayland-usable-now</id>
   <content type="html">&lt;p&gt;2023 年情人节, KDE 终于发布了 5 系列的最后一个版本: 等离子 5.27&lt;/p&gt;

&lt;p&gt;经过十几年的雕琢, wayland 终于可用了.&lt;/p&gt;

&lt;p&gt;既然 wayland 可用, 我就迫不及待的要删掉 Xorg 了.&lt;/p&gt;

&lt;p&gt;给 xorg-server 去掉了 xorg, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;USE=-xorg emerge xorg-server&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;然后 /usr/bin/Xorg 就拜拜了. 至于为啥还要编译 xorg-server, 是为了他提供的 Xwayland .&lt;/p&gt;

&lt;p&gt;Xorg 没了后, 就可以顺利卸载 xorg-drivers xf86-video-* xf86-input-* 驱动了. Xwayland 并不使用这些驱动.&lt;/p&gt;

&lt;p&gt;Xwayland 是不使用 Xorg 的驱动的. Xwayland 从 kwin 获得输入, 因此无需 xf86-input-* 驱动, Xwayland 本身是作为一个 wayland 客户端, 因此也不需要
xf86-video-* 驱动操作显卡. wayland 客户端用啥 GL 驱动绘制, Xwayland 也用啥 GL 驱动绘制. 驱动的自动选择交给了 libglvnd.&lt;/p&gt;

&lt;p&gt;要同时给 Xorg 和 Mesa 写2个驱动的时代终于过去了. 虽然 Xwayland 仍然保留 X11 兼容性, 但是 Xwayland 不需要专门的驱动了.&lt;/p&gt;

&lt;p&gt;PS: windows 其实是和 X11 一样的落后, 驱动要写2份. 一份给 GDI 调用的 2D 加速, 一份给 dx 调用的 3D 加速.&lt;/p&gt;

&lt;p&gt;等离子体 5.27 还包括了一个非常非常重要的 fix , 就是支持了 text-input-v1 扩展协议, chrome 和一票 electron 程序, 终于不需要 xwayland 也能搞定输入法了.&lt;/p&gt;

&lt;p&gt;也就是说, 等离子 5.27 终于把 wayland 变成了可以完全替代 X11 的东西了.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>面向公知治国</title>
   <link href="https://microcai.org/2023/01/26/gongzhi-oriented-governance.html"/>
   <updated>2023-01-26T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/01/26/gongzhi-oriented-governance</id>
   <content type="html">&lt;p&gt;古时候, 有一个皇帝, 他对于如何治理国家没有主见. 于是他就很喜欢听公知的话.&lt;/p&gt;

&lt;p&gt;当时, 公知说, 太监都是坏人, 尤其是那个魏忠贤, 大大的坏.&lt;/p&gt;

&lt;p&gt;于是他砍了魏忠贤.&lt;/p&gt;

&lt;p&gt;公知说, 锦衣卫只抓好人.&lt;/p&gt;

&lt;p&gt;于是他撤了锦衣卫.&lt;/p&gt;

&lt;p&gt;公知说, 勤俭的皇帝是好皇帝. 于是他每天“鸡鸣而起，夜分不寐”，宫中从无宴乐之事。&lt;/p&gt;

&lt;p&gt;他这么节俭, 国库还是没钱. 他想增加国库收入, 公知说, 要藏富于民, 不能与民争利.
于是他放弃了江南的商税.&lt;/p&gt;

&lt;p&gt;外有鞑子, 内有闯王. 为了抵御外敌, 他听从公知的建议加征三响. 还只收北方的.&lt;/p&gt;

&lt;p&gt;后来公知说, 袁崇焕投敌啦. 于是他马上斩了袁崇焕.&lt;/p&gt;

&lt;p&gt;最后走投无路了, 他发现他什么也没做错, 为啥就亡国了呢?&lt;/p&gt;

&lt;p&gt;今也有一皇帝, 事事听公知的.&lt;/p&gt;

&lt;p&gt;公知说, 新冠很危险. 能打赢新冠的只有民主和自由. 于是他封城了. 他要公知闭嘴.
后来, 公知说, 新冠就是大号流感. 人民要生活, 不能封城. 于是他躺平了.
公知说, 放烟花是自由. 于是烟花又能放了.&lt;/p&gt;

&lt;p&gt;公知说, 武统台湾就会被美国以贸易战报复. 于是他不敢武统.
公知还说了, 辉瑞特效药好, 不让引进就是故意杀人. 于是他引进了辉瑞神药. 但是引进后马上遭遇了老百姓的强烈抵制.&lt;/p&gt;

&lt;p&gt;对了, 公知还说, 中兴被美国制裁是咎由自取, 于是他带着中兴给美国请罪.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>悟空和观音谁的修为高?</title>
   <link href="https://microcai.org/2023/01/21/monky-vs-guanyin.html"/>
   <updated>2023-01-21T00:00:00+00:00</updated>
   <id>https://microcai.org/2023/01/21/monky-vs-guanyin</id>
   <content type="html">&lt;p&gt;悟空在西游结束后, 被封为斗战胜佛. 而一路上帮助他的观音依旧是菩萨.
在灵山教里, 佛大于菩萨大于罗汉. 很多人对一个毛猴突然站观音头上不接受. 于是自欺欺人的说悟空地位低于菩萨.&lt;/p&gt;

&lt;p&gt;首先, 佛和菩萨, 是以什么为划分的? 地位又是以什么为划分的?&lt;/p&gt;

&lt;p&gt;西游世界, 他是一个修仙世界. 修仙世界, 实力为尊. 这就是为何, 年纪更大, 资历更老的人太上老君, 要屈尊于玉帝之下.
因为这个世界, 其实是按实力说话.&lt;/p&gt;

&lt;p&gt;按实力排行, 观音高于悟空.
那么为何悟空成佛, 观音还是菩萨呢?&lt;/p&gt;

&lt;p&gt;这涉及到另灵山教的一个规定. 就是佛和菩萨, 不是以实力划分的. 而是以对佛法的领悟划分的. 对佛法的领悟更高者成佛.
次之则为菩萨. 再次则为罗汉.&lt;/p&gt;

&lt;p&gt;也就是说, 佛和菩萨的区别, 是一个纯粹的学术排名, 而不是实力排名.
其实看西游记就会发现, 到了后期, 悟空对佛法的参悟, 其实已经远远甩开其他人了, 连唐僧都要向悟空讨教.
不过唐僧最后排名高悟空一个名次, 其实主要是因为成佛后, 唐僧取回了金蝉子的记忆. 也就是说, 凡人唐僧对佛法的领悟,
不过区区二十几年, 不如后来大彻大悟的孙悟空. 但是取经成功后, 继承了金蝉子的记忆, 自然对佛的领悟重新超过孙悟空.&lt;/p&gt;

&lt;p&gt;那么悟空成佛, 是不是对佛法的领悟要高于菩萨呢?&lt;/p&gt;

&lt;p&gt;这也不见得. 因为学术排名还有个前提, 人家要参与排名.
观音拿到菩萨学位后, 就放弃了成佛. 因此她对佛的参悟到底几何, 已经无人知晓了.&lt;/p&gt;

&lt;p&gt;也就是说, 佛和菩萨的区别, 是一个学术上的区别. 不是实力上的区别.&lt;/p&gt;

&lt;p&gt;那么, 按实力来说, 观音的实力如何呢?&lt;/p&gt;

&lt;p&gt;人的修为是可以增长的. 比如天庭里很丢人的太白金星, 年纪在天庭都是数一数二的人物, 但是因为修为增长遇到瓶颈. 战斗力是个只有5的渣.
而悟空呢? 刚去菩提座下学了十几年, 实力就已经超过东海龙王了. 这修练速度是非常逆天的. 哪怕是消炎都自叹不如.&lt;/p&gt;

&lt;p&gt;而且悟空离开菩提成为散修后, 并没有停止修练. 到他死亡的时候, 才过去三百多年, 修为更进一步, 地府都奈何不了他了.
不过这里可以看出来, 悟空在生死簿上的寿命只有三百多岁, 大概就是个筑基修士的水平.
也就是说, 悟空在修练地煞72变之前, 在菩提的道场里用了几年时间就修练到筑基了.
但是, 菩提教给他的地煞72变的时候应该是屏蔽了天机, 导致生死簿没有更新悟空的寿命. 所以菩提其实教他地煞72变的时候就已经布局了.
逐出师门菩提早就安排好的, 而不是后来他和师弟炫耀才生气逐出.&lt;/p&gt;

&lt;p&gt;至于悟空的实力, 首先悟空刚刚当弼马温的时候, 实力肯定是已经超过金仙了. 因为天庭封官的一大门槛就是实力要达到金仙.
修仙修仙, 气运之子修练靠天赋, 而凡人修仙靠丹药. 韩立这个没修练资质的人, 到第二季结束终于结丹. 靠的就是吃丹药.
论丹药, 谁吃的有猴子多呢?&lt;/p&gt;

&lt;p&gt;猴子可是同时天赋+嗑药. 因此偷了老君的丹药, 并且在八卦炉里炼化后的悟空, 实力至少应该是在大罗金仙.
而天庭的当值神仙, 通常配置就是大罗为主官, 金仙为副手, 真仙为众手下. 于是大闹天宫的时候, 当值神仙在不拼死相搏的情况下都打不过悟空.&lt;/p&gt;

&lt;p&gt;而天庭其实并不是没有大罗以上的神仙, 而是当时这些神仙并不在天庭轮值. 悟空这属于是偷袭. 打天庭一个措手不及.&lt;/p&gt;

&lt;p&gt;那么观音的修为如何呢? 观音的修为在大罗之上是毋庸置疑的. 观音作为灵山的魏忠贤, 实力肯定是远超一般灵山弟子的.
观音作为多宝的师妹, 实力应该稍逊于如来. 而如来作为灵山扛把子, 实力肯定是准圣巅峰. 毕竟成圣后就会被天道压制, 关入33重天之外不得入世.
那么观音的实力, 不是准圣巅峰就是准圣后期了. 也就是比悟空高一个大境界.&lt;/p&gt;

&lt;p&gt;但是, 那是大闹天宫的时候的实力对比.&lt;/p&gt;

&lt;p&gt;悟空被压 500 年, 肯定会导致境界跌落. 但是跌落多少并没有提及. 作为天庭反骨仔的哪吒, 悟空被压后就没有和悟空交手过.
但是悟空和镇元子交手过. 打得难舍难分, 但是还是略逊一筹. 镇元子作为地仙之祖, 并不是地仙的修为. 镇元子作为和观音同时代的人,
他的实力和观音是可比, 但是还是次于观音. 推测为大罗后期或者准圣初期.
那么悟空在五观庄的时候, 修为大概就是大罗初期, 或者是大罗后期.
而且红孩儿虽然压制了悟空, 但是其实他是靠的三昧真火, 这个火属于可以越境杀敌的存在. 尚且只能压制悟空. 说明悟空至少高红孩儿一个大境界.&lt;/p&gt;

&lt;p&gt;悟空这个人, 其实也没逃脱修仙小说里角色的一贯行为, 就是遇到比自己厉害的多的人马上认怂. 从他调戏牛魔王的老婆这件事就可以看出来,
悟空其实很自信自己实力超过牛魔王的.&lt;/p&gt;

&lt;p&gt;别看最后收牛魔王是找了帮手, 那是因为悟空和牛魔王对战的时候, 牛魔王是被逼迫到燃烧精血了. 牛魔王要使出自己200% 的力量对付悟空.
悟空就帮救兵, 是非常正确的选择.说明悟空和牛魔王是在同一个大境界, 但是悟空为境界中后期修为, 而牛魔王是境界的中前期修为. 相差不大,
但是面对燃烧精血打 buff 的牛魔王, 悟空没有 100% 的把握无伤通关.&lt;/p&gt;

&lt;p&gt;在小雷音寺这个关卡可以看出, 悟空的修为在黄眉老佛之上. 但是黄眉老佛有厉害的法宝导致悟空不敌. 而黄眉老佛的法宝, 理论上来说, 只有准圣才可破, 所以有恃无恐.
而天庭里的神仙, 悟空能请到的, 一个准圣都没有. 所以这难老难了. 也就是说, 在不使用法宝的情况下, 悟空实力略强于黄眉老祖.&lt;/p&gt;

&lt;p&gt;黄眉老祖敢冒充如来, 除了他是弥勒佛的童子外, 他自身的修为也是一大依仗. 推测他的修为至少不低于大罗后期.
因此可以肯定的说, 在悟空西行快结束的时候, 他已经恢复了实力, 并有所长进了.
因此, 悟空成佛后, 实力突破准圣是板上钉钉的事情.&lt;/p&gt;

&lt;p&gt;但是观音迈入准圣境界很久了, 悟空要追上, 是需要一定的时间的.&lt;/p&gt;

&lt;p&gt;所以, 结论是, 悟空成佛后, 实力推测为准圣初期, 或者最次是大罗巅峰.
观音则是在准圣境界待了很久了, 少说是中期修为.&lt;/p&gt;

&lt;p&gt;因此从实力上来说, 悟空确实是不如观音的. 但是悟空成长非常快. 才一千多岁, 其中还有500年是被关押, 修为还有所倒退的情况下, 就修炼到大罗巅峰乃至准圣了.
这个修炼速度, 无人能及.&lt;/p&gt;

&lt;p&gt;如果西游有后传, 悟空不陨落的情况下, 迈入准圣巅峰, 乃至成就圣人, 都是板上钉钉的事情.
反观观音菩萨, 修为原地踏步数千年不得寸进.&lt;/p&gt;

&lt;p&gt;PS: 其实天庭败给如来, 主要原因是可动员实力不如灵山.&lt;/p&gt;

&lt;p&gt;如来要做事, 他手下有3位准圣战力的人可以调遣. (观音普贤和文殊) 这些都是 100% 服从领导的战力.&lt;/p&gt;

&lt;p&gt;而天庭虽然准圣数量明面上有数十人, 但是实际上能供玉帝自由调遣的全部都是大罗和大罗以下的修为. 玉帝无法压服其他准圣是致命缺陷.
而他自身虽然是准圣修为, 但是为了面子从不出手. 而如来则是能成事不计较亲力亲为.&lt;/p&gt;

&lt;p&gt;主要原因是天庭里的准圣, 资历都比玉帝还老. 只是名义上为玉帝所领导. 实际上除非天庭遇到生死存亡的危机, 否则各安天命.
玉帝在能打的过悟空的情况下, 选择请如来, 就是怕 “诺大的天庭, 打个猴子居然要玉帝亲自出手” 这样的结果. 这对天庭的声望会形成巨大打击,
更坐实天庭无准圣战力的谣言.&lt;/p&gt;

&lt;p&gt;天庭只所以会形成这个局面, 就是因为昊天玉帝的天庭, 是抢来的. 初期玉帝因为是道祖的童子, 被推出来当傀儡. 你一个刘协, 还想诸侯臣服?
为了解决昊天手下无人, 道祖选择坑截教帮玉帝扩充手下. 所以, 能实际被玉帝掌握的战力, 只有封神榜上的神仙. 剩下的都只是看道祖的面子
而在名义上归属天庭.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>sfp 形态 gpon 光猫失败</title>
   <link href="https://microcai.org/2022/12/17/gpon-sfp-onu-success.html"/>
   <updated>2022-12-17T00:00:00+00:00</updated>
   <id>https://microcai.org/2022/12/17/gpon-sfp-onu-success</id>
   <content type="html">&lt;p&gt;多年前, 折腾过一次 &lt;a href=&quot;/2019/07/30/gpon-sfp-onu-failed.html&quot;&gt;sfp 形态 gpon 光猫失败&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;时隔三年半, 我又开始折腾 sfp 猫棒了.&lt;/p&gt;

&lt;p&gt;这次非常顺利, 一次成功.&lt;/p&gt;

&lt;p&gt;主要是固件好用起来了. 改好 SN , 填好 password 搞定.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>高房间必然没有高科技</title>
   <link href="https://microcai.org/2022/12/05/high-estate-price-conflict-with-hightech.html"/>
   <updated>2022-12-05T00:00:00+00:00</updated>
   <id>https://microcai.org/2022/12/05/high-estate-price-conflict-with-hightech</id>
   <content type="html">&lt;p&gt;国家要发展, 必然要有高科技. 科技是第一生产力.&lt;/p&gt;

&lt;p&gt;而高科技, 必然和高房价是冲突的.&lt;/p&gt;

&lt;p&gt;第一点: 高科技对土地的需求非常大. 高科技必然意味着大量的研发. 研究就得做实验. 做实验就需要实验室. 有实验室就必须要有大量仪器设备.
这些仪器设备对环境要求苛刻, 必然需要大量的专用房子. 因此科研需要大量的土地.
高房价必然大大推高科研成本. 高科研成本必然导致败给低科研成本的对手.&lt;/p&gt;

&lt;p&gt;第二点: 高科技需要大量高级人才. 高房价必然推高人才的期望待遇. 搞科研, 除了要支付自身的土地需求, 还得间接的支付人员的土地需求.
于是高房价必然导致过高的人员支出. 大大推高科研成本. 高科研成本必然导致败给低科研成本的对手.&lt;/p&gt;

&lt;p&gt;所以, 高房价必然没有高科技. 即便是有, 那也是靠畸形补贴政策催生的, 一旦失去补贴, 立马败给同行.&lt;/p&gt;

&lt;p&gt;所以, 需要高科技的新能源汽车, 必然不可能诞生于北汽和上汽.
需要高科技的芯片行业, 也必然不可能诞生于北京上海.&lt;/p&gt;

&lt;p&gt;正如纽约也造不出芯片, 造不出汽车.&lt;/p&gt;

&lt;p&gt;高科技, 需要高人才, 必然需要大学城.
因此大学城必然需要建设在低房价的地方.&lt;/p&gt;

&lt;p&gt;在高房价的地方建设大学, 大学生面对的将是绝望.
绝望就会催生革命的动力. 必然会被野心家利用.
被利用, 必然就会拿着A4纸上街游行.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>platformIO 单片机开发神器</title>
   <link href="https://microcai.org/2022/11/12/platformio.html"/>
   <updated>2022-11-12T00:00:00+00:00</updated>
   <id>https://microcai.org/2022/11/12/platformio</id>
   <content type="html">&lt;p&gt;近来闲暇无事，想弄个三相变频器玩玩。
三相变频器可以用来驱动异步电机，还可以驱动直线加速器。&lt;/p&gt;

&lt;p&gt;于是搜索万能宝，发现只有380v的变频器。&lt;/p&gt;

&lt;p&gt;可是我想玩安全电压的。最多24V三相交流电。&lt;/p&gt;

&lt;p&gt;找不到，看来只能自己开发一个了。&lt;/p&gt;

&lt;p&gt;硬件不用自己做，只要购买无刷电机驱动板就可以了。
无刷电机驱动板，只要提供3路PWM信号，就可以生成驱动无刷电机用的三相交流电。&lt;/p&gt;

&lt;p&gt;而生成这pwm信号的工作，就留给了单片机。&lt;/p&gt;

&lt;p&gt;我比较中意的单片机是 ESP32. 便宜，性能强。STM32 零头的价格提供了比 STM32 高数倍的性能。&lt;/p&gt;

&lt;p&gt;我写程序啊，不喜欢从头写。哪怕是单片机。
总不能用汇编代码一点一点写吧。再说，汇编我也只知道8051的汇编，不懂 esp32 的汇编。&lt;/p&gt;

&lt;p&gt;所以，用了 SimpleFOC 这个库。&lt;/p&gt;

&lt;p&gt;这个库虽然是用来驱动无刷电机的，但是他带了一个 3PWM Driver . 我只要用他这个。&lt;/p&gt;

&lt;p&gt;起初我用 VSCODE 搭配 ExpressIDF SDK 写过 ESP32 的代码。
但是 SimpleFOC 基于 arduino 库，无法直接使用 ExpressIDF SDK。&lt;/p&gt;

&lt;p&gt;后来我就折腾用 arduino 写。发现 arduino 的编辑器不是很顺手。代码补全，语法提示就是个 0 。&lt;/p&gt;

&lt;p&gt;还是得用vscode写，结果翻阅 simplefoc 的文档发现可以用 platformio。
PlatformIO 是个 vscode 的插件。 装上它，就有了一个单片机的集成开发环境了。
编辑器还是用的 vscode，还能用 clangd 进行自动完成。&lt;/p&gt;

&lt;p&gt;后来还发现 platformio 更高级的功能，就是可以多平台构建。可以同一份源码直接编译出 ESP32 和 STM32 的固件。
虽然soc各有不同的地方， 但是这些不同的地方可以用偶这个C++程序员最擅长的条件编译搞定。&lt;/p&gt;

&lt;p&gt;至于 stm32 的板子，嘿嘿， 之前玩电机的是买过一个 VESC 的电机控制器。控制器上是个 STM32F405G ，而且，板子上 SWD 调试端口。接上 stlink， 在 platformio 里添加一个 stm32 的板子， 点 upload 直接成功！&lt;/p&gt;

&lt;p&gt;然后可以随时在 stm32 和 esp32 里切换，随时 build ， 随时 upload。&lt;/p&gt;

&lt;p&gt;platformio真是开发单片机的神器。&lt;/p&gt;

&lt;p&gt;于是我就捣鼓出了2个三相变频器，一个 esp32 的， 一个 stm32 的。
esp32 的可以用手机连上蓝牙后发命令设定输出电压和频率。
stm32 没蓝牙，只能接了一个电位器，用ADC采样电位器输出后设定电压和频率。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;异步电机开环调速让电压和频率同步提升。也就是固定 V/F 比。这个 V/F 比和电机有关。如果有转速传感器，可以使用固定滑差率设定频率，电压则根据电流和按设定的扭矩进行反馈调节。滑差率也是一个和电机有关的数值。大部分都是 1% 的滑差率。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后淘了一个 三相的异步电机。接上。。。因为板子的耐压是 30v。只能接了30v的直流电给板子。按电机 220v/50hz 的规格（确实是三相220v，不是380v，找了好久才找到的。）， 30v 的直流电只能逆变出 21v 的交流电，所以只能输出 21v/8hz 的交流电驱动。不过这个电机应该是随便绕的，频率提高到 20hz 还能转的更快点。&lt;/p&gt;

&lt;p&gt;但是扭矩感人。毕竟是要用 220v 电压供电的电机，绕组电阻太大。低压下电流太低了。相电流连 50mA 都没有。&lt;/p&gt;

&lt;p&gt;可惜，更低电压的异步电机买不到了。低压的都是永磁同步电机。欸。/(ㄒoㄒ)/~~&lt;/p&gt;

&lt;p&gt;希望能搞到和航母电机一样大小的，12V 电压驱动的三相感应电机。参数最好是 12v/100hz ，7000rpm。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>宗门设定</title>
   <link href="https://microcai.org/2022/10/20/zongmeng.html"/>
   <updated>2022-10-20T00:00:00+00:00</updated>
   <id>https://microcai.org/2022/10/20/zongmeng</id>
   <content type="html">&lt;p&gt;所有的修仙小说里, 都有宗门的设定.&lt;/p&gt;

&lt;p&gt;修仙有两种途径, 或散修, 或投靠宗门.&lt;/p&gt;

&lt;p&gt;投靠宗门修仙, 宗门提供修炼资源.
投靠的方式, 主要就是给宗门当弟子.&lt;/p&gt;

&lt;p&gt;弟子这个设定, 大家会想到学校. 其实弟子不是学生.
学生到学校学习, 是要交学费的.
而弟子拜入宗门, 并不需要交学费. 不仅不交学费, 宗门还要花资源大力培养弟子.
弟子一般都是在宗门管辖区域内, 通过各种办法从凡人里招来. 所以说凡人是修士的根.&lt;/p&gt;

&lt;p&gt;招了弟子, 就得管吃管喝, 还得提供丹药之类的修炼资源, 宗门看似是在做亏本生意.
其实, 弟子是士兵. 而且是廉价炮灰. 因为支付的军饷只是可以廉价印刷的功法秘籍.
而且没有阵亡抚恤金.&lt;/p&gt;

&lt;p&gt;招收弟子, 就是招兵.&lt;/p&gt;

&lt;p&gt;有兵就要打, 因此宗门之间打来打去.&lt;/p&gt;

&lt;p&gt;有兵就要管理. 于是会分为 杂役弟子, 外门弟子, 内门弟子, 真传弟子.
管理层, 则分为 长老, 宗主.
养老的则是 太上长老, 和老祖.
杂役弟子是干杂活的, 不需要有修炼资质. 宗门只要花少量的代价, 其实就是提供食宿就嫖到的廉价劳动力.
因为穷苦百姓很多吃饭都有困难活不下去.&lt;/p&gt;

&lt;p&gt;外门弟子就需要有修炼资质了. 宗门要花的代价, 就是提供稍微高级点的食宿条件, 外加批量印刷的低级功法.
就能嫖到廉价的炮灰了.&lt;/p&gt;

&lt;p&gt;外门弟子是宗门之间战争的主力炮灰. 经常死掉一大片. 哦不对, 在修仙世界不能叫死, 得叫陨落.
外门弟子是没有特定的师父的. 宗门有专门给外门弟子讲课的人, 叫传法长老.&lt;/p&gt;

&lt;p&gt;内门弟子, 是接班人预备队. 内门弟子就有特定的师父了. 其中优秀的会成为下一代的长老们.&lt;/p&gt;

&lt;p&gt;真传弟子就是宗主亲收的弟子了. 这些可不再是炮灰了, 而是重点保护的对象了.&lt;/p&gt;

&lt;p&gt;宗门的下一代领导就在这些人里产生了.&lt;/p&gt;

&lt;p&gt;宗主的大弟子, 一般会接手宗主的位置, 其他的弟子, 就会变成各个长老. 掌握宗门各个要害位置.&lt;/p&gt;

&lt;p&gt;宗门换届, 有时候是宗主陨落, 而多数是宗主觉得弟子已经成长了, 自己就会安心退休当太上长老. 因为退出宗门事务, 才可以有更多的时间修炼.&lt;/p&gt;

&lt;p&gt;一朝天子一朝臣, 在宗门里也是一样的. 长老都是宗主的师弟. 而原来的长老, 就成了宗主的师叔. 这部分人就会退休, 成为太上长老.
太上长老退休了, 就会安心于修炼, 不再处理宗门事务.
而普通的长老, 则可以把位置传给自己的弟子, 不会和核心领导层同步换届.&lt;/p&gt;

&lt;p&gt;老祖就是宗门里, 修炼最厉害, 寿命最长, 熬过了很多代领导层后, 还在继续修炼的人.
老祖其实是宗门的核武器. 因为厉害的人, 都飞升了.
不厉害的, 都陨落了.
而卡在飞升境界, 就是飞不上去的人, 武力值到达人间天花板, 成为宗门老祖. 就成为宗门对外战争中的最后倚仗.&lt;/p&gt;

&lt;p&gt;老祖不插手事务, 一心闭关, 只求飞升. 但是, 宗门遇到身死存亡的地步, 老祖也只能出关救命了. 所以老祖是核武器的存在.
一旦老祖突破成功, 则宗门其实会非常危险.&lt;/p&gt;

&lt;p&gt;因此老祖的数量也非常重要. 不断的有老祖突破, 也不断的有新的太上长老踏入飞升境成为新的老祖. 这样宗门才安全.&lt;/p&gt;

&lt;p&gt;有的大宗门, 宗主之位不是直接传给宗主的大弟子. 而是会在内门弟子里选拔一个重点培养的人. 这个重点培养的人, 就是圣子, 女的就叫圣女了.&lt;/p&gt;

&lt;p&gt;修仙的宗门很少世袭, 设定上是修仙的人大多数得了不孕不育症. 很难产生后代. 所以只能不断的从凡间搜刮体质优秀的人.
而且即便是世袭, 也是把孩子收为弟子. 然后传位给弟子. 绝对不破坏规矩搞父传子(笑).&lt;/p&gt;

&lt;p&gt;长老除了从内门弟子里提拔, 其实还可以从其他宗门挖, 或者是请一些牛逼的散修担任.
这部分并非从宗门内部产生的长老, 一般叫客卿长老.&lt;/p&gt;

&lt;p&gt;了解了身份设定后, 很快就可以把宗门和军阀给对应起来.&lt;/p&gt;

&lt;p&gt;杂役弟子就是负责后勤的. 给军队运粮草, 烧火做饭. 搭帐篷之类的工作.
外门弟子就是大头兵.
内门弟子就是士官了.
长老就是将军.&lt;/p&gt;

&lt;p&gt;宗门的实力, 就是看能养活多少弟子. 有数量庞大的弟子, 就会慢慢的产生大量的长老.
有大量的长老, 才能有机会养出老祖牌原子弹.&lt;/p&gt;

&lt;p&gt;要养活多弟子, 就得多占资源.
修仙界的资源, 一是地盘, 而是矿.
地盘多了, 才有机会在庞大的凡人里筛出合格的人当弟子.
有了矿, 才能给弟子提供修炼资源.&lt;/p&gt;

&lt;p&gt;因此宗门也是需要经营的. 所以宗主通常是由最会经营的人担任, 而不是最牛逼的人担任.
既然是经营, 就得列收入和支出.&lt;/p&gt;

&lt;p&gt;一个军阀的支出, 就是军费. 大头是武器, 其次是买命钱. 而军阀的收入, 则是占领的地盘后收税.&lt;/p&gt;

&lt;p&gt;宗门的支出, 主要就是灵石. 修仙的人一般辟谷, 无需吃饭. 不过杂役弟子和外门弟子通常境界比较低, 还是得吃饭.
吃饭的开销在修仙界算是毛毛雨.
吃一顿饭花不了几个铜钱, 而一颗丹药可以抵一个城池. 所以吃饭的开销忽略不计了.&lt;/p&gt;

&lt;p&gt;丹药要么自己炼, 要么花钱买. 因此主修炼丹的宗门在修仙界都很富裕.&lt;/p&gt;

&lt;p&gt;而宗门的收入, 其中吃饭这点毛毛雨的开支, 一般是靠辖区凡人的保护费. 因为是宗门所以不能叫税.
而大头开支, 最主要是靠挖矿.&lt;/p&gt;

&lt;p&gt;修仙界有两种矿, 一种叫灵石, 一种叫灵药.
灵药需要种植在灵气充裕的地方. 有些还不能人工种植, 只能去危险的地方采摘.
而灵石也是在灵脉里挖到的.&lt;/p&gt;

&lt;p&gt;这都需要宗门有地盘. 这也是宗门战争的由来. 要争夺修炼资源.&lt;/p&gt;

&lt;p&gt;宗门给弟子的修炼资源, 其实就是工资, 不过他们那边叫供奉.
宗门里有很多活修仙的人是不干的, 这部分活, 有些外包给山下的凡人做. 而有些则是让杂役弟子干.
杂役弟子几乎没有供奉, 就是给口饭吃. 杂役弟子的存在, 是因为仙凡有别, 修仙的人想尽量少和凡人打交道.
因此像盖房子, 装修这种可以外包给山下的人做, 等他们做完了会离去.
但是挑水洗衣做饭这样的活, 总不能让山下的人一直住在宗门里干. 于是就整了一个杂役弟子的由头, 招收一些没有修炼体质的凡人进去.
给他们传一些凡人的修炼功法, 让他们这辈子留在山上干活. 也不算破坏了仙凡有别的规矩.
外门弟子每月都可以领钱. 这部分是固定的. 但是一般少的可怜. 一般是不够修炼的. 所以就会被忽悠去干一些危险的时期.
这个制度, 叫贡献点制度. 就是可以去长老那领取任务. 完成后奖励贡献点. 拿贡献点兑换修炼资源.
这个制度就好像现在工厂里, 给工人开最低工资, 然后加班费给多点. 诱惑工人自愿996.&lt;/p&gt;

&lt;p&gt;因此外门弟子是宗门剩余价值产生器.&lt;/p&gt;

&lt;p&gt;内门弟子因为供奉充足, 很少需要做宗门任务. 基本工资高了, 就可以不加班了吗?
师父会拿着 “历练” 的大饼, 让这些弟子下山干脏活. 比如带着外门弟子去攻打小宗门.&lt;/p&gt;

&lt;p&gt;弟子的陨落率其实是非常高的.
所谓一将功成万骨枯.
终于熬到师父退休, 接手了师父的活, 成了长老.
当了长老, 供奉高了, 但是, 开销也大了.
主要是随着修为的提升, 需要的丹药更贵了.
而且工资还和自己收徒的数量有关.&lt;/p&gt;

&lt;p&gt;逼着自己不能一门心思修炼, 还得带徒弟.
毕竟没有徒弟, 怎么完成任务. 比如讨伐魔道.
这都是长老要干的活.&lt;/p&gt;

&lt;p&gt;丹药阁的长老虽然不用出去打打杀杀, 但是每天都要炼丹完成 KPI.
有些特殊药材还得自己亲自去搞.&lt;/p&gt;

&lt;p&gt;即使是混成宗主, 要操心的事情就更多了.&lt;/p&gt;

&lt;p&gt;能安心修炼, 不受打扰, 就只能熬成太上长老.&lt;/p&gt;

&lt;p&gt;但是, 如果自己不把宗门发展壮大就提前退休当太上长老, 则退休的日子没享受, 就被自己徒弟坑了. 因为徒弟搞不定只能请太上长老出马.
万一资源没竞争过, 宗门覆灭, 退休工资都没有了. 命都要搭进去,&lt;/p&gt;

&lt;p&gt;所以修仙修仙, 是非常卷的.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>科学与修仙</title>
   <link href="https://microcai.org/2022/09/19/xiuxian.html"/>
   <updated>2022-09-19T00:00:00+00:00</updated>
   <id>https://microcai.org/2022/09/19/xiuxian</id>
   <content type="html">&lt;p&gt;修仙其实和量子力学密不可分, 是有科学依据的.&lt;/p&gt;

&lt;p&gt;灵气: 灵气其实就是富含氘的空气.
灵水: 灵水就是氘的水.&lt;/p&gt;

&lt;p&gt;灵根: 为何无灵根无法修炼? 因为灵根其实就是一种特殊的线粒体. 普通的线粒体, 是燃烧葡萄糖, 参与碳循环. 而灵根, 是核动力线粒体. 只要吸收灵气就能给细胞供能.&lt;/p&gt;

&lt;p&gt;辟谷: 拥有灵根的人, 修炼到一定境界后, 就可以只依靠灵气存活了. 说明身体的核动力功能完全激活了.&lt;/p&gt;

&lt;p&gt;无垢体: 特殊体质, 其实是指身体没有传统线粒体. 不参与碳循环, 自然不会产生二氧化碳, 不需要进食. 不进食就不会产生排泄需求, 也就是无垢. 完全依靠核动力存活. 是修炼的无上体质.&lt;/p&gt;

&lt;p&gt;法力: 其实就是超能力. 而超能力其实是宏观上的量子叠加态定向坍塌. 也就是俗称的因果律武器. 最次等的因果律武器, 只能干涉单个的量子的坍塌, 在科学界, 只有三体人掌握了这个技术.
而高级的因果律武器, 可以对大量的粒子整齐的干涉到指定塌陷状态, 于是得到宏观上的”超能力”.&lt;/p&gt;

&lt;p&gt;以身合道: 对整个宇宙只能施展一次的法则, 在科学侧的含义就是确定宇宙常数. 在鸿钧以身和道前, 宇宙常数是处于量子叠加态, 是随机的. 所以说先有鸿钧后有天.&lt;/p&gt;

&lt;p&gt;修为等级: 分法力等级和法术等级. 法力等级, 指修仙者能调动的能量的大小. 由身体的等级决定. 法术等级, 是对宏观世界的干涩能力, 由施展的术法决定.
高等级的功法修炼难度也高. 是因为法术其实是一种因果律武器, 需要调动大量的神识去微操, 干涉大量的量子波坍塌才能实现. 因此修仙者需要修炼灵魂, 也就是让自己成为超级观察者, 而具体的干涉又需要能量, 因此需要
锻炼肉身, 提高线粒体的功率.&lt;/p&gt;

&lt;p&gt;到圣人这个等级, 神识已经可以对全宇宙微操, 自身可以调动整个宇宙的能量了. 所以圣人之下皆为蝼蚁.&lt;/p&gt;

&lt;p&gt;为何上古时代妖兽横行? 因为物种演化就是要先兽后人. 后来经历了物种大灭绝, 妖兽绝迹. 少数修练到大罗金仙境界的, 可以有足够的寿命活到人的时代.&lt;/p&gt;

&lt;p&gt;修真小说里, 为何全世界都在说中国话? 因为修真是未来时代, 而不是古代. 修仙的人动不动就活了几百万年. 人类诞生都才几十万年呢.
在未来的某个时间, 地球经历了一场灵气复苏. 又或者未来某个时代, 星际殖民者到了某个富含灵气的星球. 于是开启了人族的修仙时代.
因此女娲可能是未来某个星际移民飞船的头. 道家的那些古籍, 是预言书.&lt;/p&gt;

&lt;p&gt;因此穿越去修真界的人, 随便说点地球的东西都很厉害, 因为对他们来说其实是上古时代的传承.&lt;/p&gt;

&lt;p&gt;传说中的大鹏, 可能就是指古代的飞机. 或者对我们来说是未来的飞机. 是能穿越星际飞行的那种.&lt;/p&gt;

&lt;p&gt;这也侧面说明, 只有中国人实现了星际航行, 并且突破了量子理论, 发展出了修仙理论. 大规模移民后, 在一个叫洪荒的星系建立了新的文明.
女娲造人, 说的是星际移民后, 把飞船里携带的人种在殖民点培育出来. 这也解释了为何女娲造人后才有人, 却又有很多女娲造人前就存在的修仙者.
看似矛盾其实不矛盾. 因为女娲只是把飞船里冷冻的受精卵在当地培育长大. 所以修仙的人早就存在了.&lt;/p&gt;

&lt;p&gt;传送阵: 其实就是虫洞. 每次使用都要消耗大量的灵石. 因为虫洞非常的消耗能源.&lt;/p&gt;

&lt;p&gt;女娲补天: 移民飞船落地后, 先造了玻璃大棚把整个人类城市罩起来. 因此最初的落点是天圆地方的. 后来出了事故导致了破碎, 女娲就去补洞. 拯救了第一批移民. 演化成了补天传说.&lt;/p&gt;

&lt;p&gt;女娲更有可能应该是移民船里的育儿机器人. 为了方便并没有腿, 而是拖着长长的各种线管. 所以有了蛇身的形象.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>准葛尔</title>
   <link href="https://microcai.org/2022/07/16/zge.html"/>
   <updated>2022-07-16T00:00:00+00:00</updated>
   <id>https://microcai.org/2022/07/16/zge</id>
   <content type="html">&lt;p&gt;走新藏线前, 在叶城待了几天. 在叶城待的时候, 就很好奇叶城名字的由来.&lt;/p&gt;

&lt;p&gt;叶城, 得名的原因是叶尔羌汗国的都城. 叶尔羌汗国, 既然是叫汗国, 那肯定是蒙古人的国家了. 可是我在叶城, 发现居民大多数是维吾尔人.
维吾尔人可不搞汗国, 即便要搞也是哈里发.&lt;/p&gt;

&lt;p&gt;叶尔羌汗国的灭亡时间也很早, 在明朝中期就灭亡了. 灭亡原因不详.&lt;/p&gt;

&lt;p&gt;新疆还有蒙古自治州.&lt;/p&gt;

&lt;p&gt;给我一种感觉, 新疆原先是蒙古人的天下, 后来被维吾尔人鸠占鹊巢.&lt;/p&gt;

&lt;p&gt;回到内地后, 再去研究了新疆未回归中国前的历史, 才明白过来一些事情.&lt;/p&gt;

&lt;p&gt;满蒙联盟果然是忽悠人的. 满人的民族政策就是消灭蒙古人.&lt;/p&gt;

&lt;p&gt;准葛尔, 是继匈奴之后, 对中原威胁最大的游牧民族. 终明一朝, 始终无法解决北方边患. 土木堡之变还成了大明由盛转衰的转折点.&lt;/p&gt;

&lt;p&gt;清灭明后, 准葛尔就成了清的心头之患.&lt;/p&gt;

&lt;p&gt;清灭蒙古, 采取的是拉一波打一波的方式.&lt;/p&gt;

&lt;p&gt;首先拉拢的, 是内蒙人. 这个是满蒙联盟的基础.
后来加入的, 是喀尔喀蒙古, 也就是外蒙.&lt;/p&gt;

&lt;p&gt;这个外蒙啊, 是准葛尔送给清的.&lt;/p&gt;

&lt;p&gt;准葛尔征服喀尔喀的过程, 把喀尔喀推向了清.&lt;/p&gt;

&lt;p&gt;由于北元投降了清, 所以清获得了整个蒙古部落的法统. 但是, 这个法统属于地图开疆. 服清的, 只有黄金家族所在的内蒙.
剩下的, 要么后续拉拢, 要么消灭.&lt;/p&gt;

&lt;p&gt;而准葛尔, 就属于要消灭的那部分.&lt;/p&gt;

&lt;p&gt;清和准葛尔, 明和准葛尔, 都打了上百年的战争.&lt;/p&gt;

&lt;p&gt;最终, 游牧民族耗不过农耕民族, 失败了.&lt;/p&gt;

&lt;p&gt;准葛尔也终于从汗国变成了盆地.&lt;/p&gt;

&lt;p&gt;但是清的民族政策, 后患无穷. 清把西域变成了无人区. 直接屠灭了准葛尔.
还不移民实边, 怕汉人占了新疆又搞事.&lt;/p&gt;

&lt;p&gt;结果, 这地方水草丰美, 你不让内地人来, 别人就不来了吗?&lt;/p&gt;

&lt;p&gt;于是中东那边受沙俄迫害的人就润到新疆来了. 形成了新的民族, 维吾尔. 所以维吾尔的历史距今也不过两百年. 他们是客人, 不是主人.
过了百年, 那些润到新疆的人, 就忘记了祖先被沙俄压迫的历史, 后人开始和沙俄勾结到一起, 他们想着独立, 沙俄想着吞并.&lt;/p&gt;

&lt;p&gt;好在左宗棠及时出手. 新疆二次收复.&lt;/p&gt;

&lt;p&gt;安分了百年后, 这群人又开始被老美忽悠.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>什么是汉人</title>
   <link href="https://microcai.org/2022/06/04/what-is-han.html"/>
   <updated>2022-06-04T00:00:00+00:00</updated>
   <id>https://microcai.org/2022/06/04/what-is-han</id>
   <content type="html">&lt;p&gt;汉人这个称呼古已有之. 但是, 内涵发生了巨大的变化, 然后被歪曲. 现在的含义对华夏人伤害非常大.&lt;/p&gt;

&lt;p&gt;汉人的最初意思, 就是汉国人. 这个也是秦人, 唐人, 宋人的含义. 就是这个国家的国民.&lt;/p&gt;

&lt;p&gt;到元代的时候, 因为蒙古人的种姓制度, 元人无法称元人了. 也就是, 元人这个称呼居然不存在了.
为了他自己的统治长治久安, 他就很随意的把人群进行划分, 分了 蒙古人, 色目人, 汉人, 南人.&lt;/p&gt;

&lt;p&gt;注意, 此时的汉人, 其实指先于南宋被统治的金人.&lt;/p&gt;

&lt;p&gt;后来朱元璋北伐灭元. 建立明朝后, 按习惯, 明朝人应该自称明人.&lt;/p&gt;

&lt;p&gt;但是, 朱元璋却搞起了汉人称呼. 于是 明朝人不自称明人, 而是自称汉人.&lt;/p&gt;

&lt;p&gt;于是这个汉人称呼才定了下来, 延续到现在. 所以现在的汉族, 其实就是原来明的国民的意思.&lt;/p&gt;

&lt;p&gt;这并不意味着, 没被明朝统治的东北人, 就不是汉人了. 他们只是不是明人, 于是不自称汉人.&lt;/p&gt;

&lt;p&gt;东北人入关后, 为了维持自己的阶级, 把阶级矛盾转成民族矛盾. 于是没有按以往的惯例, 把自己的国民称清人.
而是划分了5个民族. 满蒙汉回藏. 还把人口最多的人踩脚底下.&lt;/p&gt;

&lt;p&gt;而汉人, 就是指原来的明人.&lt;/p&gt;

&lt;p&gt;此时的汉人, 其实并不是西方意义上的民族的称呼. 而是东方语境下的民族.&lt;/p&gt;

&lt;p&gt;直到民国, 我们学习了西方的技术, 同时也把西方的民族观舶来了. 于是把清朝时划分的汉人, 给整出了一个 汉族人的概念.&lt;/p&gt;

&lt;p&gt;孙中山还没发现自己其实被西方文化坑了, 其实, 如果他要反清复汉, 其实就是只能恢复到明朝的版图. 因为汉人其实就是按明人划分的.
于是他匆匆改口, 搞了五族共和.&lt;/p&gt;

&lt;p&gt;但是民国很动荡, 来不及搞什么真正的五族共和, 始终在内乱, 还被日本侵略了.&lt;/p&gt;

&lt;p&gt;时间很快到了新中国. 新中国学习苏联的经验, 自然也把苏联的糟糠给舶来了. 搞民族细分, 硬生生的搞了56个民族出来. 俄罗斯现在幡然醒悟了, 取消了民族识别.&lt;/p&gt;

&lt;p&gt;于是汉族这个非常伤害中国的概念就固化下来了.&lt;/p&gt;

&lt;p&gt;就是因为这个, 导致了元朝非中国, 清朝非中国的言论甚嚣尘上.&lt;/p&gt;

&lt;p&gt;而且导致国内不同民族的对立.&lt;/p&gt;

&lt;p&gt;但是其实, 所谓的汉人, 其实就是明朝人. 于是就把同为华夏人的东北人给排挤出去了. 至于现在东北人汉人多, 就说是因为汉人闯关东.
于是就把闯关东前的东北人开除华夏籍.&lt;/p&gt;

&lt;p&gt;这是非常非常有害的.&lt;/p&gt;

&lt;p&gt;汉人=明人的解释一旦建立, 汉=中国的语境下, 元当然就不是中国了. 清也不是明人建立的, 于是清也非中国了.&lt;/p&gt;

&lt;p&gt;所以,其实现中国, 应该停止民族识别, 停止说明什么是汉人, 就把汉人和华人, 华夏民族一样, 不提供精确定义.
开始构建新的国家认同.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>不恰饭的车评人危害性更大</title>
   <link href="https://microcai.org/2022/06/03/harmfull-chepingren.html"/>
   <updated>2022-06-03T00:00:00+00:00</updated>
   <id>https://microcai.org/2022/06/03/harmfull-chepingren</id>
   <content type="html">&lt;p&gt;端午节前，汽车界最大的瓜，就是叶问大战38号。&lt;/p&gt;

&lt;p&gt;首先介绍一下事情的来龙去脉&lt;/p&gt;

&lt;p&gt;&lt;em&gt;事情的起因是教主（本名鲁超，自封键盘车神教的教主，以下简称教主）测试领克的时候，被奇瑞的粉丝要求也测一下新发布的凌云S，因为奇瑞宣传这个是赛道级的车。
教主他走的是车评中的细分行业，就是只评家用赛车。在这个细分领域有大量的粉丝。一般的车他还不测，只测那种能下赛道的贵族车。但是他居然测了平民车领克，领克是家用车啊。所以粉丝要求他也测奇瑞的车，并不过分。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;于是他就按他的理解，把奇瑞当成那种贵族的赛车，在赛道上霍霍了一下，果然纯家用的属性就暴露了。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;到这个时候还好，也就是一个小插曲，一个只玩贵族车的人突然一反常态的测了一下屌丝车，然后得出屌丝车下不了赛道的结论。然后成为他粉丝的笑谈。什么垃圾奇瑞也想下赛道之类的云云，在粉丝里充满了快活的空气。节目效果也有了。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;但是，奇瑞的粉丝炸锅了，不服气，他们买的车被教主鄙视，能服气吗？顺带着另一个给奇瑞站台的车评人郑刚也批评教主，形成了一方锤教主的势力。教主气不过啊，被人一挑拨，一怒之下自己买一辆。被人喷，就得怼回去。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;结果不重测还好，一重测问题更大了。这成绩比原来借来的车更差了。教主公然放出量产车只有7.6s的成绩，奇瑞虚假宣传的话。不服来北京测，能跑6.2s车送给你。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;这种小孩子般约架的话，能有个俅用。于是被郑刚继续追着撕咬。眼看自己招架不住，他想到了自己的好朋友，38号。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;这下事情就进入了无可挽回的地步了。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;教主做的是赛车这种细分市场，而38做的，是几乎所有的车的市场。38下场为教主站台，等于乌克兰冲突后美国直接下场。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;都不是一个级别的对线了。这时候，叶问（本名朱殿举，网名叶问讲高考志愿，简称叶问）来锤38号了。叶问这个人，他很早就说，车评人屁股歪，不为国产车叫好，只给合资车站台，他要捶死这个车评行业。当年出道就先捶死了 yyp，yyp 以发一期夸奖 汉DMI 的视频投降。这次 38号 居然想奇瑞死，叶问能不出手？于是叶问下场。战争升级。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;叶问打车评人第一步，首先质疑7.6s成绩是故意抹黑。他自己找公司的一个女员工测了一下，是7.2s。明明能跑的比7.6s更快，却故意测出来 7.6s，是何居心？然后抖出大瓜，说38之前黑凡尔赛的时候敲诈了神龙公司200万。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;教主这边则是以他白天测7.6s，晚上测能到7.3s，但是无论如何测不到 7s 来回应。并指出奇瑞自己的宣传视频里，6.0s 的成绩是P图造假。然后他的粉丝就进入了嘲讽陶腾飞造假的潮吹，充满了快活的气息。（陶鹏飞就是帮奇瑞跑出6.0s成绩的车手）并纷纷心疼教主被陶鹏飞用时间系魔法骗走了18万。其中也不乏帮领克跑 WTCR 的车手，也开启了嘲讽陶鹏飞的盛宴。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;公然造假，奇瑞这下是要吃官司了。教主作为车主起诉是必胜啊，退一赔三，买车钱是肯定有着落了。所以其实粉丝是不用心疼的。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;而 38 的回应则是解释了自己为何只能测到7.6s, 因为成绩受各种因素影响，并且辩解自己并没有说奇瑞只能跑7.6s。然后还辩解自己没有只夸合资车，国产车也夸了。然后就200万敲诈回应，你没证据就是造谣。我悬赏400万求证据。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;此时教主还来嘲讽叶问智商，并表示200w敲诈这种黑料，就是他卧底叶问的群提供的。于是他的粉丝又进入了嘲讽叶问相信敌人的假冒黑料智商垃圾的群体高潮。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;由于 38 的粉丝笃信叶问被教主耍了，于是纷纷跑去在叶问的视频里留言喷他。
一股胜券在握的胜利者姿态。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;于是叶问微微的掉了一些粉。一些讨厌38但是又不好支持叶问的人，都开启了“中立” 模式。因为不能支持一个智商不在线的人，对吧？而且38的粉丝威胁站叶问就是支持奇瑞造假，罪无可赦。车评圈的其他大V们，则是全部下场，几乎全部站队38， 批判奇瑞造假. 像11磅小老虎直接说，谁不站38的都把我拉黑了吧。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;好家伙，好家伙。一边是几乎全体车评人站队38，少数开启中立模式。另一边是叶问的视频下面被嘲讽和劝降的言论刷屏。而中立的人，日子也不好过，因为38的粉丝拿出奇瑞造假的证据，逼他们站38，必须站38. 颇有饭圈的味道。这波啊，38用不能支持企业造假的政治正确，绑架了几乎所有人一起集火叶问。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;连讲叶问打拳影视作品的视频下面，都被要求站队38，你敢信？&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;叶问死定了。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;这瓜越来越大了。在6月1号当天，叶问再次发视频。并给出了38号雇佣水军冲击他的视频评论区的证据。等等，合着之前他视频下一边倒的言论是水军？&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;紧接着马上发第二个视频，向38提出了灵魂质问：你是不是富二代？你恰饭你的钱哪里来的？有没有接受日本外务省的资助？怎么解释以前为日本侵华洗地的言论？&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;并且他发视频，等水军冲完后，将视频删了重发（等于清空评论）。这波啊，如果水军重发贴子，他可以继续删视频然后重发。看你雇佣水军的钱多，还是我重发的钱多。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;果然几次重发后，水军数量大幅减少。于是他的视频下面终于可以看到真正的吃瓜群众的留言了。群众还是吃瓜，并不站38，也不站叶问，更没有人站奇瑞，只是吃瓜看戏。真正的群众怎么会因为奇瑞造假去冲叶问的视频么？吃瓜看戏才是天朝人民的秉性。只不过吃瓜群众还是被人追问，承不承认奇瑞造假？&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;38的粉丝炸锅了！他们炸锅并不是突然发现自己粉的对象有特务嫌疑，而是发现叶问居然不讲事实，不讲证据，居然直接上升到国家安全层面攻击38. 你不讲武德！你无耻，你卑鄙。你下流！你拿奇瑞跑不出6.0s你就是输了，你要退网！讨论奇瑞有没有造假的话题你不能岔开！&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;而原来站38的大人物突然沉默。事情明朗前还是先沉默。只有38的粉丝开始疯狂的输出叶问不讲武德，卑鄙下贱的言论。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;此时一些中立的人，开始表达38其实非常双标，真的很怀疑是不是日本派来的。而且表示38根本不可能不恰饭。但是仍然表示不叶问以防水军冲击。38以前评车时非常双标的言论被扒了出来。而粉丝还在为38战队，连 即便38真的是特务，也不能否定奇瑞造假 这样的失智言论都开始疯狂输出。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;球来到了38这边。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;38 在人设问题上投降，承认自己不是富二代，钱来自懂车帝，没有收车企的钱。表示懂车帝给的钱比车企还多，看不上车企的钱。承认自己去过日本搞“学术交流”，但是是学术交流，不是参加汉奸培训班。然后再次要求叶问拿出证据，不然走着瞧！&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;截止到我写作时间（北京时间6月3好凌晨3点）叶问尚未回应。&lt;/p&gt;

&lt;p&gt;好了。事情的来龙去脉搞清楚了，我来说说我的看法。&lt;/p&gt;

&lt;p&gt;我认为，不恰饭但是立场偏向国外品牌的车评人，对国家危害更大。&lt;/p&gt;

&lt;p&gt;首先，凡是媒体，必有立场。无立场的最好做法是不发表言论。凡是发表了言论还说自己的言论是理性客观中立的，那一定是诈骗。这个立场是说他内心的立场，不是说他发出来的东西的立场。
因为有立场，不表示他一定会说这个立场。因为很多时候人要说违心的话。“他给的太多了”&lt;/p&gt;

&lt;p&gt;于是我把车评人分三种。&lt;/p&gt;

&lt;p&gt;第一类，粉丝不多，完全看不出立场。你能看到的是，他的立场变来变去。有钱就是爷。
第二类，头部大V，粉丝众多。他的立场号称理中客，实际被看到的会有些许偏移，但不明显。偏移情况主要看金主的钱。但是给再多的钱也不能进一步解锁他的立场。不然沦为第一类了。承认恰饭，但是表示恰饭不影响他的立场。
第三类，就是38为代表，已经形成一个教派。完全拒绝充值。&lt;/p&gt;

&lt;p&gt;第一类和第二类车评人，他们的立场是可以用金钱收买的。
无非就是收买车评人的成本，最终要消费者承担。&lt;/p&gt;

&lt;p&gt;但是，不管是自主品牌还是合资品牌，他们其实面对的是相同的敲诈环境。
也就是说，虽然老百姓多花钱供养了车评人，但是自主和合资面对的勒索力度是一样的。
大家公平竞争。谁撒的狗粮多，谁赢。&lt;/p&gt;

&lt;p&gt;输的是买车的人。&lt;/p&gt;

&lt;p&gt;这也是 叶问的爆论 能迅速吸粉的原因，老百姓不想豢養吸血的车评人。这也是第三类车评人的生存之道。现在的趋势就是，不管车评人是第几类，他们都开始声称自己是第三类。&lt;/p&gt;

&lt;p&gt;但是，第三类才是真正的，对国家危害更大。前两类我说过，是让老百姓多花钱豢養吸血的车评人，但是不管是自主品牌还是合资品牌，他们在这场上供竞赛里是公平竞争。&lt;/p&gt;

&lt;p&gt;但是第三类车评人，才是真的让自主品牌永无翻身之力。
因为，他们不受车企的金钱影响。
但是，他们并不中立，也不客观。&lt;/p&gt;

&lt;p&gt;“不管你给我多少钱，我就是要吹比亚迪和其他自主品牌，不会吹你丰田的车” 这是某UP的豪言。他大方的承认自己有立场，但是不收钱，就是要当自主品牌的自干五。&lt;/p&gt;

&lt;p&gt;这个不是我今天要说的，因为这样的人，全国也找不出几个。&lt;/p&gt;

&lt;p&gt;但是另一类人是更多的，他们从小接受的是英美文化的熏陶。成长环境里，国产车一直垃圾（起步阶段能不拉跨么？）于是已经带上了西方的车高级的思想烙印。&lt;/p&gt;

&lt;p&gt;他不接受厂家充值，自喻为理中客，但是他的成长环境对他的影响是无法消除的。
于是他或多或少的，都会偏向于西方人的品牌。&lt;/p&gt;

&lt;p&gt;比如键盘车神教教主，他的内心深处，就认为中国人根本造不出能下赛道的车。谁敢宣传他就敢下场捶死。这是他心中的信仰。除非，除非你能拿出超级无敌牛逼的车，把他彻底征服。在征服他之前，就别想得到公平的待遇。&lt;/p&gt;

&lt;p&gt;好在，赛车从不是车企的主业，可以慢慢发力，不着急。&lt;/p&gt;

&lt;p&gt;又比如 38 号，他内心深处就喜欢美系性能车，回国后，他自己又发现了日本车的闪光点。
他会迷恋上日系车。&lt;/p&gt;

&lt;p&gt;国产车要拿出十倍的产品力才能征服他。&lt;/p&gt;

&lt;p&gt;因为这个车评人，和你家的亲戚是一样的，都会固执的认为某个品牌的车好。很难扭转观念。
区别是，他会把他固执的观念继续传给更多的人。&lt;/p&gt;

&lt;p&gt;你说这怎么同场竞技？&lt;/p&gt;

&lt;p&gt;所以，这些和你家亲戚一样固执的，不接受充值的，喜欢洋货的车评人，才是自主品牌的绊脚石。
而且完全无法用金钱收买。这些人日复一日的宣传“一车传三代” 的固执印象。&lt;/p&gt;

&lt;p&gt;自主品牌如何竞争？&lt;/p&gt;

&lt;p&gt;好在叶问站了出来，虽然叶问认为38是第二类车评人，捶死他。
但是第三类是锤不死的。38本身也是打第二类起家的。
但是，被扒出来他是立场偏向日本的。&lt;/p&gt;

&lt;p&gt;如果立场偏向日本，你就是外务省培养的！
38不论是不是，都必须从此收敛，隐藏自己的倾向。否则他在目前的环境下就社死了。
而且杀鸡儆猴，于是市面上 “人走车还在”的神话慢慢的就无人传唱了。自主品牌才能获得公平的环境。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>民族? 屁!</title>
   <link href="https://microcai.org/2022/05/29/mingzugepi.html"/>
   <updated>2022-05-29T00:00:00+00:00</updated>
   <id>https://microcai.org/2022/05/29/mingzugepi</id>
   <content type="html">&lt;p&gt;由乌克兰和俄罗斯火併想到了一个故事.&lt;/p&gt;

&lt;p&gt;我是微菜, 我生活在南方崇山峻岭里, 从小就喜欢看动画片.
有一年, 我造反了. 我找了一群志同道合的同乡, 打出了消灭电视剧的口号.
因为我们是动画人. 广电他总是不让我们好好的看片.&lt;/p&gt;

&lt;p&gt;我们割据一方, 和中央对峙好多年. 为了加强我们自己人相互之间的认同, 我们发明了动画人概念, 创造了动画字, 以南方某方言为范本发明了动画语.
我们以后都说动画语, 写动画字, 是动画人. 谁要还写汉字, 就是叛徒, 是中央派来的奸细.&lt;/p&gt;

&lt;p&gt;最终, 中央因为腐败倒台了, 我们乘机杀入北京, 建立了动画国.&lt;/p&gt;

&lt;p&gt;为了维持我们的地位, 我们要求所有人都只能看动画片. 谁要看电视剧, 就杀.&lt;/p&gt;

&lt;p&gt;而我们动画人, 从此可以不用干活, 天天窝家里看动画片了. 国家高级职位只能动画人担任.&lt;/p&gt;

&lt;p&gt;但是, 我担心以后会出事情, 于是在各地建动画城, 不让普通动画人出城. 生怕某个动画人搞事情, 又要建国.&lt;/p&gt;

&lt;p&gt;为了更好的治理国家, 我们动画人就推广了北京话. 因为大家都会说. 但是要求动画人必须得学动画语. 不能忘本啊.&lt;/p&gt;

&lt;p&gt;就这么过去了百来年.&lt;/p&gt;

&lt;p&gt;动画国最终还是因为腐败被推翻了. 动画人一夜之间消失了.&lt;/p&gt;

&lt;p&gt;后面的几任领导, 居然连动画语都不会说, 不会写, 太不像话了!&lt;/p&gt;

&lt;p&gt;后来, 新的国家建立了很多年后. 为了民族团结, 不要大家喊打喊杀了. 说少数民族加分加分. 新的国家还包括越南, 朝鲜.
其实这些国家是动画国好大喜功搞进来的. 当然其实也用了一些自古以来. 新国家自然要继承动画国的原有地盘, 不能把这些人分出去.&lt;/p&gt;

&lt;p&gt;结果动画人一夜之间又多起来了. 很多人纷纷表示他以前其实是动画人. 虽然这些人并不会说动画语.&lt;/p&gt;

&lt;p&gt;突然有一天, 越南人说, 几百年前我们越南人和老共国互动频繁, 你们讲的都是动画语. 真正的北京话在我们越南. 我们都是两山轮战的解放军人后代.&lt;/p&gt;

&lt;p&gt;朝鲜人跑出来说, 不对不对, 在我们朝鲜. 当年毛太祖还征发了百万志愿军入朝. 我们都是志愿军的后人. 我们说的才是正宗北京话.&lt;/p&gt;

&lt;p&gt;而老动画国的国民, 也不知道自己说的北京话, 到底是动画人口味的北京话, 还是老北京话了. 虽然动画国还没灭亡的时候, 普通的动画人都不说动画语了, 只有国家领导人政治需要, 非要学一些. 而最后的亡国领导其实自己也是口北京话了.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>普通话就是古汉语活化石</title>
   <link href="https://microcai.org/2022/05/10/yayan.html"/>
   <updated>2022-05-10T00:00:00+00:00</updated>
   <id>https://microcai.org/2022/05/10/yayan</id>
   <content type="html">&lt;p&gt;我是南方人, 从小在学校学习普通话. 平时大家基本上都以普通话交流. 因为温州这个地方, 连跨个村都能换一个方言.
根本就没有能主宰当地的强势方言, 如果有, 那只能是普通话.&lt;/p&gt;

&lt;p&gt;后来因为家庭缘故, 中学时代远去了河南度过.&lt;/p&gt;

&lt;p&gt;河南这个地方, 大家都说河南话, 不说普通话.
只不过, 河南话和普通话, 并没有太大差异. 语音语调略有差异, 类似唱歌跑调.
家中也常来山东大汉做客. 操着山东话和父亲谈生意.&lt;/p&gt;

&lt;p&gt;山东话和河南话也是非常接近. 不熟之人也不太能分别.&lt;/p&gt;

&lt;p&gt;小学时候, 以为的方言就是各种鸟语. 大家鸟语不通, 于是用通用的普通话.
去到北方, 才真切的感受到, 只是南方的方言是鸟语.
北方的方言, 大体还是同一种语言略有差异.&lt;/p&gt;

&lt;p&gt;不过, 也正因为如此, 北方人对普通话就不重视. 这点就让我非常讨厌北方人. 就因为天生母语接近普通话, 就直接不学普通话了事.
难怪经济不发达, 人太懒了.&lt;/p&gt;

&lt;p&gt;到读大学, 又回南方跑去杭州. 又变成了方言大杂烩, 各自听不懂的状态. 于是大家又说普通话了.&lt;/p&gt;

&lt;p&gt;还是普通话好, 消灭方言, 人才能顺利沟通, 消除隔阂.&lt;/p&gt;

&lt;p&gt;但是, 人进了大学, 就容易变成脑残. 自以为高人一等, 学的都是先进知识. 把老人都批判一番. 我进了大学也不能免俗. 我在大学里学到的最重要的知识, 就是64事件.
所谓人一独, 脑就残. 总觉得墙外的信息才是真实信息. 对洋人和殖畜的话是奉为圭臬. 于是对自己的语言都开始了怀疑.&lt;/p&gt;

&lt;p&gt;于是, 那时候我相信, 北方话是胡人的话, 真正的汉语已经消亡了. 要算最接近老祖宗的汉语的, 就属粤语了.&lt;/p&gt;

&lt;p&gt;其实我已经中了陷阱而不自知.&lt;/p&gt;

&lt;p&gt;数年前, 老爹带我去参加了宗祠聚餐. 我讨厌酒文化, 所以以往我从未去过.
那一年我第一次去, 老爸人肉导航指挥我开车数小时到了一处偏僻的山脚下一座小村子.
老爹说, 我们这一系的宗祠就在这里了.&lt;/p&gt;

&lt;p&gt;那年可能是比较重要的聚会吧, 家族里的人都来了. 都是没见过的人居然有几百号人. 还和我同姓, 哦不, 往上数5辈都是同一个祖宗的.
大家聚到一起, 当然是要说普通话了.&lt;/p&gt;

&lt;p&gt;但是, 吃酒的时候, 还是分开了, 亲戚关系比较近的坐一起.&lt;/p&gt;

&lt;p&gt;我们都是同一个祖宗的后代. 但是聚到一起的时候, 我才发现, 大家各自都说着不同的方言.&lt;/p&gt;

&lt;p&gt;这是我第一次真真切切的感受到, 背井离乡就会忘记乡音.&lt;/p&gt;

&lt;p&gt;我突然开始怀疑, 秦始皇派去南征岭南的人, 真的就变成了粤语的祖先吗?&lt;/p&gt;

&lt;p&gt;族谱清清楚楚的显示, 我们家族是在宋朝的时候从河南衣冠南渡去了南方, 北宋的河南人说温州话吗?&lt;/p&gt;

&lt;p&gt;自打那次聚会, 让我真正切切的见识到了和我同宗同源的人, 说着和我完全不同的方言.
原来语言这种东西, 只能入乡随俗.&lt;/p&gt;

&lt;p&gt;北方汉人逃难去了南方, 就只能学会南方的百越语.
胡人就算来了北方, 也只能学会北方的汉语.&lt;/p&gt;

&lt;p&gt;在没有发明磁带的古代, 胡人就算统治了北方, 也根本无法普及胡人的话.&lt;/p&gt;

&lt;p&gt;对了, 德国的犹太人说德语. 乌克兰的犹太人泽连斯基还说乌克兰语呢.
美国的犹太人说美语.&lt;/p&gt;

&lt;p&gt;犹太人都无法免俗, 漂到哪里就学那里的语言.&lt;/p&gt;

&lt;p&gt;所以, 南方方言更像古汉语这种事情根本就是无稽之谈. 其根本目的, 就是为了分化瓦解中华民族.
阻碍 在近代终于有了技术手段可以推行的延迟了两千多年的语同音.&lt;/p&gt;

&lt;p&gt;中国的方言, 为啥要分成北方方言, 南方方言?
北方方言能互通, 南方方言可不互通.&lt;/p&gt;

&lt;p&gt;能互通的北方方言, 所占面积和人口, 都是中国的主要部分.
如果再仔细细纠, 发现, 北方方言区, 不正是周朝的疆域?&lt;/p&gt;

&lt;p&gt;不互通的南方方言区, 不外乎 粤闽浙.&lt;/p&gt;

&lt;p&gt;这三个地方, 虽然在汉代已经纳入版图, 但是处于偏远之地. 而且基本上不被中央直接统治, 而是羁縻统治.
羁縻统治, 不就是当地人不会雅言统治成本高么.&lt;/p&gt;

&lt;p&gt;所以, 今天的北方方言, 就是周朝定的雅言. 语言虽然有变化, 但是绝对不会是翻天覆地的变化.&lt;/p&gt;

&lt;p&gt;周朝还专门有一种从事翻译的官职，名曰“象胥”，每隔几年各诸侯国的象胥都要进京学习，然后回去教乡民官方语言。
所以西周时代, 雅言就开始逐步普及全国了. 只是没有留声机这样的工具, 所以大家有差异. 但是还没到听不懂的地步.&lt;/p&gt;

&lt;p&gt;而且, 为何四川也属于北方方言区? 明明四川是南方. 因为四川是秦国的地盘. 秦国发家的地方, 就是西周的地盘.
西周灭亡后, 东迁, 老地盘被西戎霸占. 于是给老秦人开空头支票, 把老西周的地封给了秦人.
老秦人灭了西戎抢回了地盘, 再南下灭了巴蜀占了四川. 这才有了统一中国的资本.&lt;/p&gt;

&lt;p&gt;而春秋时代, 除了楚国大家都说雅言, 也就是北方方言. 不然孔子门生那么多, 如何交流?
孔子可不只收山东人.&lt;/p&gt;

&lt;p&gt;楚国人自称蛮夷. 其实楚国的封地也是周地, 楚王也讲雅言. 但是楚国向南扩张后, 拿下了如今大半个中国的土地.
奈何新统治的土地讲的都是和北方不通的话, 于是自嘲自己是蛮夷.
这就好像嘲讽二战投降的法国是非洲人一样.&lt;/p&gt;

&lt;p&gt;非要说现行的普通话被谁影响, 最大的影响应该来自吴语, 而不是胡语.&lt;/p&gt;

&lt;p&gt;首先就是楚国, 楚国扩张到江南后, 吴语就开始影响楚国了.&lt;/p&gt;

&lt;p&gt;然后就是南北朝时期, 南方是南朝的中心了, 南朝人不可避免受吴语区方言的影响.
到唐朝重新统一天下, 就要重新确立普通话了. 唐代标准普通话«切韵»就被北方人说不标准了, 说是被吴语带偏了.
但是南北分离了几百年, 这时候不照顾下南方人, 能行吗?&lt;/p&gt;

&lt;p&gt;到宋朝编的 «广韵» 更是被北方人说是把南方话列为正统.&lt;/p&gt;

&lt;p&gt;到明朝的时候, 哪怕朱元璋还是基本按北方话编的 «洪武正韵» 都被指责南方话太多.&lt;/p&gt;

&lt;p&gt;其实 不管是 切韵, 广韵 还是 洪武正韵, 都是以北方话为基础的. 但是确实不可否认会有南方音的影响.&lt;/p&gt;

&lt;p&gt;而这些被批判而吸收了较多的南方话的 «洪武正韵» 构成了明清官话, 其实就就是目前普通话的基础.&lt;/p&gt;

&lt;p&gt;普通话本身就是南北融合的产物, 但主要还是北方话为基础. 也就是说, 还是以周时的雅言为基础.&lt;/p&gt;

&lt;p&gt;非要说的话, 普通话可能是雅言和吴语融合的产物.&lt;/p&gt;

&lt;p&gt;PS, 这个 吴语 不是上海话, 是指南京话和杭州话.&lt;/p&gt;

&lt;p&gt;五胡乱华为啥我认为并没有改变北方的语言, 其实主要是因为 五胡乱华而不是五胡屠华. 殖民者如果不是采取腾龙换鸟的做法, 是不可能消灭原住民的语言的.&lt;/p&gt;

&lt;p&gt;英国统治印度三百年, 都没能让印度人改说英语. 倒是去美洲成功了, 那也是因为他们去美洲的时候杀光了原住民.
法国人统治越南一百年也没能改变越南的语言.
日本统治朝鲜50年, 采取的是非常激进的逼朝鲜人说日语的方法, 也没能消灭朝鲜话.&lt;/p&gt;

&lt;p&gt;只有杀光原住民的做法才能改变语言, 比如澳洲, 比如美洲, 比如海参崴.&lt;/p&gt;

&lt;p&gt;而杀光了原住民, 就没有复国的事情了. 五胡真杀光了北方人, 那北方就绝对不可能再回中国了.&lt;/p&gt;

&lt;p&gt;所以, 我认为, 普通话不是胡化的汉语.
普通话, 是融合了部分吴语的雅言.
雅言是周代的普通话, 雅通夏, 是周人学夏人说话.
所以, 我们现在说的普通话, 就是 5000年前先祖在这片土地上说的话. 差异是会有, 但是差异不大, 善属于能互通的状态.&lt;/p&gt;

&lt;p&gt;就算穿越回古代, 也无需当心语言不通.
那些说会语言不通的, 都是别有用心.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>FEC VPN</title>
   <link href="https://microcai.org/2022/04/29/new-vpn.html"/>
   <updated>2022-04-29T00:00:00+00:00</updated>
   <id>https://microcai.org/2022/04/29/new-vpn</id>
   <content type="html">&lt;p&gt;给大家推荐一个好用的 VPN 软件. 可以在恶劣的环境中为其上承载的网络提供稳定的保障.&lt;/p&gt;

&lt;p&gt;这个 VPN 的诞生缘由, 大概经历了3个时段.&lt;/p&gt;

&lt;p&gt;第一个时段, 我折腾使用了 &lt;a href=&quot;https://github.com/wangyu-/tinyfecVPN&quot;&gt;tinyfecVPN&lt;/a&gt;, 虽然用起来颇为不便, 但好在比 openvpn 要稳的多.
于是推荐给了隔壁老王.&lt;/p&gt;

&lt;p&gt;老王发现这个 tinyfecVPN 代码质量略渣. 但是前先纠错的理念不错. 但是他目前用不到 vpn, 于是冇行动, 冇用起来.&lt;/p&gt;

&lt;p&gt;过了一阵子, 老板又双叒叕脑洞瞎开, 要开发一个比 &lt;a href=&quot;https://www.resilio.com/&quot;&gt;btsync&lt;/a&gt; 还要牛逼的同步软件放他的 NAS 产品上用.
他要把数据拆分成 N 份, 然后存储 M 份到不同的NAS上. 只要在线的NAS数量足够, 数据就可以完全恢复.&lt;/p&gt;

&lt;p&gt;于是老王想到了 tinyfecVPN 的理念, 用上了 FEC 纠错的方法.&lt;/p&gt;

&lt;p&gt;写了一个 FEC 分片工具. 把指定的文件先分片, 然后用 FEC 算法添加冗余纠错. 然后形成大量的冗余分片. 丢失一些分片还可以完整复原整个文件.&lt;/p&gt;

&lt;p&gt;然后进入第三个时段. 老板要他的 NAS 尽可能的保持在线数量. 但是有一些西南边区的用户买过去, 他网络状况就不怎么好, 丢包率有那么一点点高.
并不是说边区的人民网络差, 他们访问互联网大厂的CDN并不慢,也不丢包, 而是这些NAS要 P2P, nas和nas之间p2p的时候, 丢包率有那么一点点高.&lt;/p&gt;

&lt;p&gt;于是老王决定, 写一个基于 FEC 算法的 VPN, 然后老板的 nas 通过这个 VPN 组网, 就解决 p2p 丢包导致的网络问题了.
至于不直接用 tinyfecVPN, 那当然是没法用啊.&lt;/p&gt;

&lt;p&gt;虽然核心的理念来自 tinyfecVPN, 但是具体的实现方法大不相同.&lt;/p&gt;

&lt;p&gt;首先, FEC 只是一个理念, 具体怎么添加冗余, 是有不同的算法的. 隔壁老王用了和 tinyfecVPN 不一样的 FEC 算法.
其次, 老王的vpn, 是 TCP/UDP 并用. 尽可能的利用有限的机会传数据. 而 tinyfecVPN 只能使用 UDP.
最后, 老王的vpn, 用 c++23 写成, tinyfecVPN 是用古代人的渣 C 写成.&lt;/p&gt;

&lt;p&gt;说完老王的vpn比tinyfecVPN先进的地方, 再说说比openvpn先进地方.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;openvpn 一丢包就挂. 老王的vpn有FEC冗余包对抗丢包&lt;/li&gt;
  &lt;li&gt;openvpn 配置繁琐. 老王的vpn配置简单&lt;/li&gt;
  &lt;li&gt;openvpn 要么tcp要么udp, 老王的vpn tcp/udp 双管齐下.&lt;/li&gt;
  &lt;li&gt;openvpn 在windows上需要装驱动. 老王的vpn 免驱.&lt;/li&gt;
  &lt;li&gt;openvpn 加密个寂寞. 老王的vpn用现代的加密&lt;/li&gt;
  &lt;li&gt;openvpn 渣C写成. 老王的vpn用现代的c++23&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;那么, 这么先进的vpn, 要到哪里下载?&lt;/p&gt;

&lt;p&gt;当然是没地方下咯. 老王的 vpn 还在开发中, 还没到 1.0 的发布阶段.
目前想试用的, 可以找老王直接要.&lt;/p&gt;

&lt;p&gt;这里放出老王的联系方式 https://t.me/jackarain&lt;/p&gt;

&lt;p&gt;有需要的直接联系.&lt;/p&gt;

&lt;p&gt;当然, 如果不好意思直接找他, 也可以联系&lt;a href=&quot;https://t.me/microcai&quot;&gt;我&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>异构容器</title>
   <link href="https://microcai.org/2022/04/26/hybrid-container.html"/>
   <updated>2022-04-26T00:00:00+00:00</updated>
   <id>https://microcai.org/2022/04/26/hybrid-container</id>
   <content type="html">&lt;h1 id=&quot;路由器-引起的&quot;&gt;路由器 引起的&lt;/h1&gt;

&lt;p&gt;路由器，基本上都是MIPS处理器。
而 PC，那铁定是 AMD64 的处理器。&lt;/p&gt;

&lt;p&gt;因此，绝无可能把 PC 上的程序直接拷到路由器里跑。&lt;/p&gt;

&lt;p&gt;因为 glibc 特有的 ABI 问题，也不能直接交叉编译。&lt;/p&gt;

&lt;p&gt;gentoo 的 crossdev 工具虽然能方便的生成交叉工具链，但是编译出来的程序，放到路由器上还是会报告 glibc 的符号版本问题。&lt;/p&gt;

&lt;p&gt;因此，我用 qemu 运行了一个 mips 的虚拟机，然后在虚拟机里编译。虚拟机里安装和 路由器相同的 os - Debian 9 MIPS。&lt;/p&gt;

&lt;h1 id=&quot;systemd-nspawn--qemu_user&quot;&gt;systemd-nspawn + qemu_user&lt;/h1&gt;

&lt;p&gt;一个虚拟机要如何运行呢？第一考虑是 qemu 运行一个 mips 虚拟机。&lt;/p&gt;

&lt;p&gt;然而我这次是使用 &lt;a href=&quot;https://microcai.org/2011/04/26/chrootarm.html&quot;&gt;十年前曾提过的 技术&lt;/a&gt;。只不过，当年使用的是 chroot。
如今我重拾这个技术，并对 chroot 做一个升级，使用 systemd-nspawn 进行更高级的 chroot。&lt;/p&gt;

&lt;p&gt;于是，新型虚拟机就诞生了。为啥当年 chroot + qemu_user 我不叫他虚拟机呢？
当然是因为 chroot 太原始了。而 systemd-nspawn 隔离的东西就更多了。网络也可以独立起来。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>各种各样的混动</title>
   <link href="https://microcai.org/2022/04/21/hybrids.html"/>
   <updated>2022-04-21T00:00:00+00:00</updated>
   <id>https://microcai.org/2022/04/21/hybrids</id>
   <content type="html">&lt;h1 id=&quot;混动怎么个混法&quot;&gt;混动怎么个混法?&lt;/h1&gt;

&lt;p&gt;混动混动, 就是发动机和电动机一起干活.&lt;/p&gt;

&lt;p&gt;一起干活, 配合的姿势就有很多种. 具体的搭配方法, 有2大方向: 机械分流, 电子分流.&lt;/p&gt;

&lt;h2 id=&quot;电子分流&quot;&gt;电子分流&lt;/h2&gt;

&lt;p&gt;先说电子分流. 因为这个概念上好理解. 就是一个电机发电, 另一个电机驱动. 发动机的动力全部输出给发电机, 发电机的出力, 分流一部分给电池. 所以叫电子分流.&lt;/p&gt;

&lt;p&gt;电子分流利用的就是电池巨大的缓冲能力, 以及电子控制器迅速的调节能力.&lt;/p&gt;

&lt;p&gt;整车实际上是实现了一个微电网. 负荷是驱动电机. 电池为一次调峰电厂, 发动机为二次调峰电厂.&lt;/p&gt;

&lt;p&gt;驱动电机负荷变化后, 首先由电池承担一次调峰任务, 立即改变充电(or 放电)功率, 以平衡发电. 然后再缓慢调节发动机的功率.&lt;/p&gt;

&lt;p&gt;那么, 电池越大, 调节能力越强, 就越能长期保持发动机运行在高效工况上.&lt;/p&gt;

&lt;p&gt;电子分流要玩的转, 首先需要强大的电力电子技术. 要能玩的转微电网.&lt;/p&gt;

&lt;p&gt;然后, 这个微电网, 其实还分 直流电网/交流电网 两种.&lt;/p&gt;

&lt;p&gt;只不过, 目前市面上并不存在使用交流电网的混动车.&lt;/p&gt;

&lt;p&gt;但实际上是可行的. 尤其是驱动电机使用三相交流异步电机的话, 是可以使用交流电网的.
那么电池作为一次调峰电厂, 就需要并网逆变器. 并且在车速变化的时候, 采取整体改变电网频率的方式变频.&lt;/p&gt;

&lt;p&gt;交流电网适合驱动功率和发电功率相当的情况. 发电机的电力直接输出给驱动电机, 中间不经过整流逆变环节, 能大大提高效率. 但是如果驱动功率和发电功率差异较大, 则需要并网储能逆变器
多多工作, 这里的整流逆变将损失较多能量.&lt;/p&gt;

&lt;p&gt;电子分流的缺点是发电再用电的环节存在效率损失. 但是只要这个损失小于发动机和车轮解耦后带来的效率提升,就是值得的.&lt;/p&gt;

&lt;p&gt;但是, 在高速上, 传统油车的发动机也进入了高效工况, 于是这个中间的转换损失就会大于效率提升.&lt;/p&gt;

&lt;p&gt;因此纯粹的电子分流, 在高速上效率可能会低于传统的油车. 具体低多少, 取决于电力电子技术的优秀程度.
当然这个比较是基于发动机的热效率差不多的情况. 实际上专门为混动开发的发动机最高热效率是会高于普通发动机的. 因此系统整体热效率在高速上可能依然是混动占优.
&lt;em&gt;比如理想one在高速上开120码油耗也低于发动机直驱的纳智捷&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id=&quot;机械分流&quot;&gt;机械分流&lt;/h2&gt;

&lt;p&gt;机械分流的核心是行星齿轮.&lt;/p&gt;

&lt;p&gt;行星齿轮, 其实可以看成一个差速器. 有三个动力输入输出轴. 只不过这3个之间都不是 1:1:1 的关系. 他们之间的比例, 看齿轮的大小比值.
这三个轴的转速关系就是  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;齿比1*轴1 + 齿比2*轴2 + 齿比3*轴3 = 0&lt;/code&gt;, 和差速器是一模一样的工作. 差速器只是比较特殊, 三者其中的2个轴的比值是 1:1 的关系.&lt;/p&gt;

&lt;p&gt;那么, 机械分流的混动, 就是 发动机, M1, M2 三个电机各连接在行星齿轮的一个轴上.&lt;/p&gt;

&lt;p&gt;其中 M2 电机所在的行星齿轮轴承最终和前轴差速器相连, 也就是这个M2 电机可以看成和车轮相连.&lt;/p&gt;

&lt;p&gt;M2 电机在纯电模式下, 直接驱动车轮.&lt;/p&gt;

&lt;p&gt;因此可以看成 M2 电机转速和车速是耦合的.&lt;/p&gt;

&lt;p&gt;混动模式下, 三者共同驱动车轮, 遵循 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;齿比1*轴1 + 齿比2*轴2 + 齿比3*轴3 = 0&lt;/code&gt; 公式, 一般会让发动机运行在固定转速上, 于是通过 M1 电机来平衡发动机的转速, 使得行星齿轮的输出转速和 M2 匹配.
也就是通过调节 M1 电机的转速实现发动机转速和车速解偶.&lt;/p&gt;

&lt;p&gt;由于在机械分流模式下, 系统总功率是三者的叠加, 因此机械分流模式特别适合电力电子技术比较差的年代堆出勉强能用的功率.&lt;/p&gt;

&lt;p&gt;在发动机功率和行驶需求功率相近的情况下, 通过 M1 M2 电机吸收/释放 的功率较小, 因此损耗较低.&lt;/p&gt;

&lt;h1 id=&quot;目前市面上的混动&quot;&gt;目前市面上的混动&lt;/h1&gt;

&lt;p&gt;尽管名称天花乱坠, 其实都逃不开这2个基础分流机制.&lt;/p&gt;

&lt;p&gt;比亚迪, 长城, 本田, 走的是电子分流路线.
但是在高速巡航状态下, 都有发动机直接驱动车轮的另一条捷径.
而理想, 赛力斯和日产则舍弃这个捷径.&lt;/p&gt;

&lt;p&gt;长城更进一步, 把直驱的离合器升成双离合, 于是等于有2个直驱档位. 其实在我看来是脱裤子放屁.
因为低速下直驱效率就是不行, 才要低速增程, 高速直驱. 把低速直驱给加回来了, 那和油车有什么区别?
要说直驱档位多就先进, 那老款的唐DM是不是更先进? 6个直驱档.&lt;/p&gt;

&lt;p&gt;走机械分流模式的, 就是通用,丰田和奇瑞了.&lt;/p&gt;

&lt;p&gt;其实这个机械分流模式最初是通用发明的, 但是丰田在普锐斯上将他发扬光大了.&lt;/p&gt;

&lt;p&gt;机械分流模式其实有个缺陷, 这也是主流厂家抛弃机械分流的原因: 功率虽然能三擎叠加, 扭矩却不能.
这导致了机械分流模式的车, 一旦发动机启动, 油门还是油车的响应, 而不是电车的响应.&lt;/p&gt;

&lt;p&gt;要解决油门响应问题, 就得在发动机端再加一个 BSG 电机进行扭矩补偿. 这样系统就需要3台电机, 大大增加了动力总成的体积重量和成本.&lt;/p&gt;

&lt;p&gt;这对丰田来说, 油门响应慢不是问题, 因为大家都接受丰田车是移动路障.&lt;/p&gt;

&lt;p&gt;但是, 对奇瑞来说, 这就是问题了.&lt;/p&gt;

&lt;p&gt;而且丰田使用的是大排量自吸发动机, 油门响应本身就比奇瑞用的涡轮增压发动机快.&lt;/p&gt;

&lt;p&gt;这让奇瑞的混动车油门响应体验就非常差了.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>棍勇喜欢大公主</title>
   <link href="https://microcai.org/2022/04/11/%E6%A3%8D%E5%8B%87%E5%96%9C%E6%AC%A2%E5%A4%A7%E5%85%AC%E4%B8%BB.html"/>
   <updated>2022-04-11T00:00:00+00:00</updated>
   <id>https://microcai.org/2022/04/11/棍勇喜欢大公主</id>
   <content type="html">&lt;p&gt;这番完结很久了, 但是有些话不吐不快.&lt;/p&gt;

&lt;p&gt;棍勇重生后, 欺负过他的人, 他只留下了大公主活口. 不仅仅留了活口, 还一直带在身边.
不仅仅带在身边, 还不让她身处险境.&lt;/p&gt;

&lt;p&gt;因为棍勇是非常喜欢大公主的.&lt;/p&gt;

&lt;p&gt;有很多人因为一周目的时候大公主虐待棍勇, 就认为棍勇不喜欢大公主, 仇恨大公主. 这个是不对的.&lt;/p&gt;

&lt;p&gt;炮勇和剑勇都虐待过棍勇. 二周目的时候, 棍勇对他们就没心慈手软过.&lt;/p&gt;

&lt;p&gt;反而对待大公主, 棍勇心软了. 棍勇要等大公主虐待他, 才复仇. 因为他从心底也渴望世界线的变动. 希望二周目的大公主是表里如一的人.
如果大公主在二周目的时候突然改了, 棍勇其实就很乐呵呵的继续待大公主边上了.&lt;/p&gt;

&lt;p&gt;即使大公主一如一周目一样开始虐待他, 他也没想杀了大公主复仇. 而是将她洗脑易容, 给她第二生命.&lt;/p&gt;

&lt;p&gt;后来在遇到剑勇的时候, 棍勇宁可自己女装去刺杀剑勇. 他也想过让大公主去接近剑勇, 但是他为什么最终还是决定自己女装上阵呢?
首先, 他怕大公主遇到危险. 其次, 他知道剑勇喜欢大公主, 他吃醋. 他对观众解释自己亲自出马是怕大公主搞不定, 任务失败.&lt;/p&gt;

&lt;p&gt;那不过是他自欺欺人罢了. 大公主也是王国第一勇者, 怎么会打不过老三, 而且还是在偷袭状态下.&lt;/p&gt;

&lt;p&gt;那么大公主喜欢不喜欢棍勇呢?&lt;/p&gt;

&lt;p&gt;大公主既喜欢棍勇, 又不喜欢棍勇. 她不喜欢棍勇主要是因为棍勇无能. 不能帮助她的除魔大业. 然后就是她自视清高, 不喜欢下等人.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;但是, 不喜欢下等人这点是装的. 二公主曾经说她姐姐把亚人当虫子, 但是鹰眼否认说大公主不是这种人&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;但是她又喜欢棍勇, 是因为他们勇者4人行的队伍, 只有棍勇能安慰她. 大公主是勇者, 更是女人啊. 白天打累了, 晚上需要有人安慰.&lt;/p&gt;

&lt;p&gt;他们一共就4个人, 晚上该找谁不言而喻. 剑勇虽然喜欢大公主, 但是大公主是谁? 她需要的是可以被她喜欢的男宠, 而不是喜欢她的女人.
大公主厌恶棍勇, 厌恶他的无能, 厌恶他的出身.
但是大公主又喜欢棍勇, 她喜欢棍勇的美貌, 她喜欢棍勇可以作为宠物一般的把玩.&lt;/p&gt;

&lt;p&gt;但是, 大公主居然也只把棍勇留在身边当男宠, 并没有第二个. 连剑勇都羡慕棍勇可以伺寝. 剑勇自认为男装打扮的她帅气程度不输给棍勇.
但是, 大公主又不喜欢女人. 剑勇是100%没有任何机会的.&lt;/p&gt;

&lt;p&gt;如果大公主能直面自己的内心, 主动找男主表白是迟早的事情. 但是帝王家庭的出身抑制了她的正常感情需求. 所以她对棍勇的爱就表现的极为扭曲.&lt;/p&gt;

&lt;p&gt;那么, 棍勇和大公主知不知道对方喜欢自己呢?&lt;/p&gt;

&lt;p&gt;答案是, 知道, 但是都认为对方不知道.
棍勇知道大公主其实喜欢自己, 但是棍勇认为大公主不知道他知道大公主喜欢自己的事情.
大公主知道棍勇喜欢自己, 但是大公主认为棍勇不知道他知道棍勇喜欢自己的事情. 大公主其实可能都不知道自己喜欢棍勇.&lt;/p&gt;

&lt;p&gt;正是因为大公主知道棍勇喜欢自己,所以她才欺负棍勇. 正是因为他认为棍勇不知道自己喜欢棍勇, 所以她才很扭曲, 要掩盖. 掩盖自己喜欢棍勇的最好办法就是欺负棍勇.&lt;/p&gt;

&lt;p&gt;棍勇知道自己喜欢大公主, 而且爱的深沉. 但是他觉得大公主不喜欢他, 所以很难受, 很憋屈. 还天天被欺负. 所以才产生了扭曲的性格.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>武器很贵</title>
   <link href="https://microcai.org/2022/04/01/weapon-is-costy.html"/>
   <updated>2022-04-01T00:00:00+00:00</updated>
   <id>https://microcai.org/2022/04/01/weapon-is-costy</id>
   <content type="html">&lt;p&gt;俄罗斯和乌克兰打了一个多月了.&lt;/p&gt;

&lt;p&gt;消耗了不计其数的武器和人命.&lt;/p&gt;

&lt;p&gt;战争就是烧钱, 谁先烧完, 谁就得投降.&lt;/p&gt;

&lt;p&gt;因为, 武器是很贵很贵的. 一发导弹能解决的事情, 俄罗斯非要派地面部队填人命的方法去打. 无他, 武器太贵了. 武器比人命要贵的多, 而且是多得多.&lt;/p&gt;

&lt;p&gt;交战双方的战报, 都是 击落敌机 多少架, 击沉战舰多少艘, 击毁坦克多少辆.
至于交战的士兵死了几个, 好像就没有那么在意了.&lt;/p&gt;

&lt;p&gt;武器太贵了!&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;听人常说, 要拿起法律的武器保护自己. 要用法律维权.
法律是武器没假, 但是他们不会告诉你, 法律这个武器, 非常的贵.&lt;/p&gt;

&lt;p&gt;用法律维权和战争一样, 都是烧钱, 看谁先烧完.&lt;/p&gt;

&lt;p&gt;战争让军火商发财, 用法律维权则是让律师发财.&lt;/p&gt;

&lt;p&gt;越来越贵的武器, 让富裕的国家占有了越来越大的优势.&lt;/p&gt;

&lt;p&gt;法律, 也让富人占有越来越大的优势.&lt;/p&gt;

&lt;p&gt;如果你公司账上没有可以随时冻结也不影响运转的三千万资金, 就不要用法律和腾讯斗.
如果你工资卡里没有600万, 就不要和特斯拉斗.&lt;/p&gt;

&lt;p&gt;你烧六百万, 他也烧六百万. 但是, 他有六百个亿可以烧.&lt;/p&gt;

&lt;p&gt;所以, 你选择了 “匹夫一怒 ，血溅三尺”, 富人慌了.&lt;/p&gt;

&lt;p&gt;你有一条命, 资本家…好像也只有一条命.&lt;/p&gt;

&lt;p&gt;那就坐下来好好谈吧.&lt;/p&gt;

&lt;p&gt;可是如果人人效仿, 资本家还赚什么钱?&lt;/p&gt;

&lt;p&gt;所以资本家就得雇佣大量的御用水军对大众进行 PUA. 遇事不要冲动. 冲动是魔鬼. 一定要拿起法律的武器合法的保护自己的权益.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;这才是资本主义法制的真相.&lt;/em&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>键政一把</title>
   <link href="https://microcai.org/2022/03/21/%E9%94%AE%E6%94%BF%E4%B8%80%E6%8A%8A.html"/>
   <updated>2022-03-21T00:00:00+00:00</updated>
   <id>https://microcai.org/2022/03/21/键政一把</id>
   <content type="html">&lt;h1 id=&quot;远交近攻是老祖宗留下的哲学&quot;&gt;远交近攻，是老祖宗留下的哲学&lt;/h1&gt;

&lt;p&gt;谁离我们远？ 欧洲，美洲。
谁离我们近？ 印度。&lt;/p&gt;

&lt;p&gt;现代中国面临的最大的威胁是什么？ 是能源安全，粮食安全。&lt;/p&gt;

&lt;p&gt;打下印度，这2个危险都解决了。&lt;/p&gt;

&lt;p&gt;有了印度洋出海口，美国所谓的岛链不攻自破。&lt;/p&gt;

&lt;p&gt;有了印度，18亿亩耕地红线就自动守住了。&lt;/p&gt;

&lt;p&gt;有了印度大陆的优质耕地，可以再多养活20亿中国人。&lt;/p&gt;

&lt;p&gt;拿下印度， 就不必与巴基斯坦友好， 就不必为了忌惮巴基斯坦而放任国内绿教兴风作浪。&lt;/p&gt;

&lt;h1 id=&quot;死掉的毛熊才是好的毛熊&quot;&gt;死掉的毛熊才是好的毛熊。&lt;/h1&gt;

&lt;p&gt;安全有了保障，就不必和俄罗斯结盟。可以继续肢解俄罗斯。拿回苏武放羊的北海。拿回库页岛。&lt;/p&gt;

&lt;p&gt;压制住俄罗斯，才能做欧洲的话事人。&lt;/p&gt;

&lt;h1 id=&quot;没有美国世界会更美&quot;&gt;没有美国世界会更美&lt;/h1&gt;

&lt;p&gt;没有美国，世界会少很多很多战争。&lt;/p&gt;

&lt;h1 id=&quot;沈万三必须死&quot;&gt;沈万三必须死&lt;/h1&gt;

&lt;p&gt;有钱人最大的罪恶就是有钱。不挣有钱人的钱，挣谁的钱？&lt;/p&gt;

&lt;p&gt;越有钱越是惜命。汤师爷说道，不能拼命啊， 拼命了还怎么挣钱。&lt;/p&gt;

&lt;p&gt;只有把资本家关进笼子，资本才能为人民做事。&lt;/p&gt;

&lt;p&gt;其实前面做的一切，其实都只是为了杀沈万三的时候，资本没有逃路，只能被关进笼子。不然资本外套，去了他国，那就只有变成了被资本家制裁了。&lt;/p&gt;

&lt;p&gt;资本无国界。但是又躲在国界的背后躲避人民的铁拳。&lt;/p&gt;

&lt;p&gt;你打它，他就跑，留下饥饿的人民。&lt;/p&gt;

&lt;p&gt;只有世界大同的时候，资本再无国界可躲，你要他进笼子，他就只能进笼子。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>seccomp 是个好东西</title>
   <link href="https://microcai.org/2022/03/20/seccomp.html"/>
   <updated>2022-03-20T00:00:00+00:00</updated>
   <id>https://microcai.org/2022/03/20/seccomp</id>
   <content type="html">&lt;style&gt;
p {
    text-align: justify;
    font-family: monospace;
}
&lt;/style&gt;

&lt;p&gt;最近在实现 git 商城的时候, 遇到了收款方面的问题.&lt;/p&gt;

&lt;p&gt;不沾钱就不用为钱相关的破事费心. 不想碰钱, 钱总得收了才能发货. 不然岂不是做慈善.&lt;/p&gt;

&lt;p&gt;所以, 我决定, 让卖家在自己的 git 仓库里放一个 js 脚本来收钱.&lt;/p&gt;

&lt;p&gt;具体的来说, 就是每当用户要支付订单的时候, js 脚本就被运行, 要求吐出一行链接. 这个链接可以让用户进行支付. 他可以是一个收款的网页, 也可以是一个能唤醒支付宝或者微信的 scheme 地址.&lt;/p&gt;

&lt;p&gt;这就要求嵌入一个 js 引擎. 但是, 试问现今写 js , 谁不是写 nodejs 呢?
但是, 嵌入一个 node 非我所愿.
我更希望的做法, 是以子进程的方式运行node, 而不是将 libnode.so 嵌入主程序.&lt;/p&gt;

&lt;p&gt;运行独立的 node 进程, 还必须让 node 可控. node 不能做一些危险操作. 这个只要不用 root 账号运行 node, node 自然是无法做危害系统安全的事情.&lt;/p&gt;

&lt;p&gt;但是, 不危害系统安全, 不等于没有危害. 即便没有 root 权限, 脚本还能挖矿, 还能偷数据, 还能当肉鸡 ddos 别人, 还能拿我们的服务器当代理.&lt;/p&gt;

&lt;p&gt;这一个脚本能干的事情太多了. 只是不让用 root, 只是保护了操作系统本身不被破坏有何意义?&lt;/p&gt;

&lt;p&gt;所以我需要能禁锢 node 进程的新法子.
早早换 systemd 做系统 init 的好处就是, systemd 教会了我有一种高级的系统调用保护法, &lt;strong&gt;seccomp&lt;/strong&gt;.
seccomp 可以过滤系统调用. 编写一个 BPF 小代码, 执行在内核里. 这个小代码可以决定, 是允许执行系统调用,还是返回错误; 返回错误的话, 用什么错误代码返回.&lt;/p&gt;

&lt;p&gt;于是我用 seccomp 封锁了绝大多数的系统调用. 只留下了寥寥数十个.
seccomp 的使用方法是, 在 fork 后, 使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;seccomp_init, seccomp_rule_add_exact, seccomp_load&lt;/code&gt; 设定并安装好一个过滤器. 然后再执行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;execve&lt;/code&gt; 运行 node 进程.&lt;/p&gt;

&lt;p&gt;但是, 可恨的是, openat 系统调用无法封锁. 封锁了 openat, 则 ELF loader 无法加载 so.
但是不封锁 openat, node 进程就可以肆意打开文件盗取系统秘密. 不得不防.&lt;/p&gt;

&lt;p&gt;经研究, 发现 seccomp 除了同意/不同意执行, 还有一个办法, 就是把这个决定通过一个 fd 消息发送到父进程去决定允许还是不允许.&lt;/p&gt;

&lt;p&gt;就是在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;seccomp_rule_add_exact()&lt;/code&gt; 调用的时候, action 参数使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SCMP_ACT_NOTIFY&lt;/code&gt; ,  然后 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;seccomp_load&lt;/code&gt;, 安装成功后, 即可通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;seccomp_notify_fd&lt;/code&gt; 获取一个 fd, 这个 fd 通过 file description passing 机制, 到父进程. 就绪后, 父进程就可以用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;seccomp_notify_receive&lt;/code&gt; 获取到子进程要调用的系统调用通知了. 子进程但凡想调用被标记为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SCMP_ACT_NOTIFY&lt;/code&gt; 的系统调用, 进程就会被挂起, 等待父进程的英明决策. 父进程可以拒绝掉, 也可以同意继续执行. 还可以 “代为执行”.&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;seccomp_notif&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;		 &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;nullptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;seccomp_notif_resp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;nullptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;seccomp_notify_alloc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;scoped_exit&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cleanup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;seccomp_notify_free&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;seccomp_notify_receive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;seccomp_notify_fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;errno&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;LOG_DBG&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;[seccomp] seccomp_notify_receive failed with e=&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;co_return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;	&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;EPERM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;	&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SCMP_SYS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;openat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// req-&amp;gt;data.args[1] 是待打开的文件名. 但是, 这个指针是在待打开的进程里的, 所以&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// 要使用跨进程 memcpy&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;iovec&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;this_readbuf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;openat_param1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;sizeof&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;openat_param1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;iovec&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;traced_readbuf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;reinterpret_cast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4096&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;memset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;openat_param1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;sizeof&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;openat_param1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;process_vm_readv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;this_readbuf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;traced_readbuf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node_want_open&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;openat_param1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;c1&quot;&gt;// node_want_open 字符串就是 node 本次 open 要打开的文件名了&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// 可以在这里允许或拒绝&lt;/span&gt;

        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;default:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;seccomp_notify_respond&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;seccomp_notify_fd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;co_return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;父进程里获取到的是子进程空间的参数指针， 因此需要调用 process_vm_readv 直接读取对方的内存。&lt;/p&gt;

&lt;p&gt;在这个父进程里，我只允许 node 打开 /usr/ 下的系统库，/etc 下的个别配置文件.
总之， 只允许node打开必要的文件，其他文件统统不允许打开。&lt;/p&gt;

&lt;p&gt;这样， node 运行起来就在一个安全的沙箱环境， 再也不能肆意妄为了。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>git 定义商城</title>
   <link href="https://microcai.org/2022/03/06/git-based-mall.html"/>
   <updated>2022-03-06T00:00:00+00:00</updated>
   <id>https://microcai.org/2022/03/06/git-based-mall</id>
   <content type="html">&lt;h1 id=&quot;引&quot;&gt;引&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;商人商人实在太伤人
你说所有人都这么看你
怨不得每一天唉声又叹气
感慨着赚钱真的不容易

其实商人就是买东西
把东边的买卖到西边去
辨贵贱 调余缺 度远近
世上不能没有我和你

急人之所急 需人之所需
这才是真正做生意
买的找不到（着）卖的 卖的找不到（着）买的
一潭死水怎会有生机
急人之所急 需人之所需
这才是真正帮了自己
一网不捞鱼 二网不捞鱼
三网就捞个大尾巴尾巴尾巴鱼
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;做买卖, 就得有铺子. 从前的铺子, 在街头.
现在的铺子, 还可以在网上.&lt;/p&gt;

&lt;p&gt;只要是个铺子, 就得有人打理.&lt;/p&gt;

&lt;p&gt;不管是格子铺, 还是架子铺. 东西都要分门别类的摆放好. 逛的人舒心了, 才会多来逛.&lt;/p&gt;

&lt;p&gt;通常网铺会提供一个在线的编辑器, 用于输入描述性的文字. 还可以设定字体,字号,颜色.&lt;/p&gt;

&lt;p&gt;还能插入图片.&lt;/p&gt;

&lt;h1 id=&quot;痛&quot;&gt;痛&lt;/h1&gt;

&lt;p&gt;图文并茂, 才能赏心悦目.&lt;/p&gt;

&lt;p&gt;编辑器可是个大工程, 何况还是在线的.&lt;/p&gt;

&lt;p&gt;于是, 编辑商品的编辑器, 就只能因繁就简了. 只能聊胜于无了.&lt;/p&gt;

&lt;p&gt;每天面对这么简陋的编辑器, 小二也觉得越来越犯2. 于是, 放截图替代了用简陋编辑器含辛茹苦的码字.&lt;/p&gt;

&lt;p&gt;看着好像是个精心排版的东西, 实际上是在其他软件上排版好, 然后截图过来的图片.&lt;/p&gt;

&lt;p&gt;编辑功能彻底成了摆设.&lt;/p&gt;

&lt;p&gt;即使是这样, 小二还嫌他传图功能都鸡肋呢!&lt;/p&gt;

&lt;p&gt;在店小二还在学习 加粗,变大,插图片 富文本三部曲的时候, 程序员也想给自己的 README 加一点点的样式.&lt;/p&gt;

&lt;p&gt;程序员可不喜欢纯靠鼠标精确点击实现的 加粗,变大,插图片 三部曲.&lt;/p&gt;

&lt;p&gt;于是 markdown 应运而生.&lt;/p&gt;

&lt;p&gt;如果仅仅只是把鸡肋的富文本编辑器换成 markdown, 那只完成了改变的第一步.&lt;/p&gt;

&lt;p&gt;第二步, 是把商品元属性的设置, 从鼠标的点击中释放出来.&lt;/p&gt;

&lt;p&gt;程序员不喜欢写文档, 但是喜欢写注释.
因为注释是跟随代码的.&lt;/p&gt;

&lt;p&gt;分离的文档, 如离家的孩子, 流浪在外边. 就像脱离了控制的野指针一样.&lt;/p&gt;

&lt;h1 id=&quot;解&quot;&gt;解&lt;/h1&gt;

&lt;p&gt;于是, 我发明了使用 GIT 管理店铺商品的商城.&lt;/p&gt;

&lt;p&gt;每一件待售的商品, 都是一个 markdown 文档.
商品的 类目, 价格, 等等信息, 和 图文并茂的详情描述, 一起写到一个文档里.&lt;/p&gt;

&lt;p&gt;把所有的商品, 按目录组织好, 并且通过 git 进行版本控制.&lt;/p&gt;

&lt;p&gt;一个店铺, 一个 git 仓库.&lt;/p&gt;

&lt;p&gt;把 本地编辑好的 文档, push 到远程, 上架就完成了.&lt;/p&gt;

&lt;p&gt;下架, 也只需要删除文件, 并 push.&lt;/p&gt;

&lt;p&gt;因为 git 控制了版本, 以后想重新上架, 也只需要 revert 那个删除的提交.&lt;/p&gt;

&lt;p&gt;因为文档都在本地, 可以使用自己最趁手的编辑器编辑. 许多编辑器还有非常高级的批量操作功能, 对管理大量商品简直如虎添翼.&lt;/p&gt;

&lt;p&gt;至于图片, 和仓库保存到一起就行.&lt;/p&gt;

&lt;p&gt;商品的描述, 统统放在 goods 目录下. 图片放在 images/ 目录下.&lt;/p&gt;

&lt;p&gt;假设你编辑的商品是 goods/cat1/iphone4.md&lt;/p&gt;

&lt;p&gt;图片在 images/cat1/iphone.jpg&lt;/p&gt;

&lt;p&gt;编辑的时候, 使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;![img](../../images/cat1/iphone.jpg)&lt;/code&gt; 指令引入图片
由于使用的是相对 iphone4.md 文件的相对路径, 因此 markdown 编辑器在本地是可以正确预览的.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;这部分相对路径的引用, 会在服务端分发给买家的浏览器的时候, 替换成图片的绝对地址.
因此, 本地编辑预览和在线浏览都不会有问题.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1 id=&quot;优&quot;&gt;优&lt;/h1&gt;

&lt;p&gt;除了这个改进, 还有就是整个商城的前端是 SPA. &lt;em&gt;Single Page Application&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;markdown 也是在前端渲染的. 同以往 SPA 使用 RESTfull API 不同, 我的商城使用了
长链接.&lt;/p&gt;

&lt;p&gt;长链接, 就取消了 cookie 的需求. 不用对每个请求都进行鉴权. 只要连接建立的时候鉴权一次.&lt;/p&gt;

&lt;p&gt;长链接, 比 pipeling 更进一步, 实现了 OOR &lt;em&gt;out of order rpc&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;HTTP pipeling, 只是复用链接的时候激进了点, 不等请求返回就发送后续请求.
但是, 应答内容本身还是按请求的次序返回的.
而 OOR, 应答返回是乱序的. 执行完毕就返回, 无需按请求提交的次序返回
故 OOR 需要每个请求都带一个唯一的 id. 服务端返回应答的时候会携带上请求对应的 id&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;因此, 用三个关键字概况我的商城的改进, 就 3 点&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;OOR&lt;/li&gt;
  &lt;li&gt;markdown&lt;/li&gt;
  &lt;li&gt;git&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;店小二只需使用 visual studio code 就可以完成店铺管理.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>c++20 协程</title>
   <link href="https://microcai.org/2022/02/20/co_await.html"/>
   <updated>2022-02-20T00:00:00+00:00</updated>
   <id>https://microcai.org/2022/02/20/co_await</id>
   <content type="html">&lt;p&gt;表面上我是想说 c++20 的协程, 其实我是想说, asio 封装后的 asio::awaitable&amp;lt;&amp;gt; , 这个威力巨大的武器将是未来异步的基石.&lt;/p&gt;

&lt;p&gt;asio::awaitable&amp;lt;&amp;gt; 的概念, 是万物皆可 await. 万物皆可 await 的概念实际上来自 nodejs.&lt;/p&gt;

&lt;p&gt;当初, nodejs 用回调地狱带火了异步. 既然回调地狱为何又火? 因为 nodejs 万物只能异步. nodejs 没有除了异步以外的 IO.
于是 nodejs 逼迫大家在异步的道路上进行探索, 终于诞生了 Promsie/await 对. Promise 是库, await 是语言关键字.
Promise 先出, await 后出. 没有 await 的 Promise 是不完整的 Promsie , 没有 Promise 的 await 是无法使用的废物.
两个天造地设的一对, 居然并不是同时出生.&lt;/p&gt;

&lt;p&gt;Promise 的概念就是, 所有的函数, 都不直接返回数据, 而是返回 Promise , 要取得真正的返回值, 就要 await 这个 Promise. 
那么, 调用返回 Promise 函数的地方, 就变成了 initiator , 而 await promsie 的地方, 就是 completion handler.
也就是, Promsie/await 的概念, 正好契合了 Proactor 的异步并发模型. 自从有了 promise/await , 原先为了避免回调地狱而
设计为 on(‘data’, lisener) 的 reactor 模式的库纷纷被抛弃, 大家都要选择 Promise 版的替代品.&lt;/p&gt;

&lt;p&gt;Proactor 模型中实现的最好的库, 自然就是 boost.asio, 或者说,是 std::net.&lt;/p&gt;

&lt;p&gt;asio 最初对异步的支持, 使用的是  initiator + completion handler 的模式. 同 nodejs 一样, 陷入了 回调地狱.
但是很快, asio 发明了 stackless 协程. 实际就是 Duff’s Device 实现的可重入函数. 基本用法是&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;reenter(this)
{
    yield asio::async_read(socket, *this);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;reenter 和 yield 都是宏, 展开后, 就是变成了一个 for循环+switch case 嵌套.&lt;/p&gt;

&lt;p&gt;但是, 这种使用宏展开变成 Duff’s Device 的可重入函数, 不如语言层面的支持来的利索干净.
因此 asio 作者给标准委员会提案, 干脆把宏变成关键字. 有了编译器的支持, Duff’s Device 带来的一些缺陷就可以规避和修正. 使用上也会更便利.&lt;/p&gt;

&lt;p&gt;然而这样的优雅提案, 并没有别接受. 委员会转而支持了微软提交的 co_await 协程.&lt;/p&gt;

&lt;p&gt;微软缺乏异步大师, 他们有了 co_await 武器, 只能设计出 winrt::IAsyncResult 这样的不能包含万物的协程.&lt;/p&gt;

&lt;p&gt;而 asio 爸爸不一样, 它捣鼓出了 asio::awaitable&amp;lt;&amp;gt;&lt;/p&gt;

&lt;p&gt;万物皆可 await, 使用的方法和 nodejs 如出一辙.&lt;/p&gt;

&lt;p&gt;在 nodejs 里, Promise/await 的使用方法很简单, 首先是将原先的函数声明为 async , 然后就可以在函数体里使用 await .&lt;/p&gt;

&lt;p&gt;声明为 async 的函数, 自身也自动变成了 Promise, 可以被其他函数 await&lt;/p&gt;

&lt;p&gt;万物皆可 await,&lt;/p&gt;

&lt;p&gt;到了 asio 这里, 只要把原来 T 返回值的函数, 换成 asio::awaitable&amp;lt;T&amp;gt; , 就可以在函数体里使用 co_await, 并能被其他函数 co_await.&lt;/p&gt;

&lt;p&gt;万物皆可 co_await 后, 我连程序入口点都改成了 asio::awaitable&lt;int&gt; co_main(int argc, char * argv)&lt;/int&gt;&lt;/p&gt;

&lt;p&gt;然后写一个简单的 stub main&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;co_main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;hello world&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;co_return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;co_main_ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_context&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;co_spawn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;co_main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;co_main_ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exception_ptr&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e_ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;co_main_return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e_ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rethrow_exeption&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e_ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;co_main_ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;co_main_return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;co_main_ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样 co_main 就变成了真正的 main.&lt;/p&gt;

&lt;p&gt;co_main 因为本身处于协程之中, 因此, 他可以使用 co_await asio::this_coro::executor 获取 executor 从而构造需要的 IO 对象.&lt;/p&gt;

&lt;p&gt;比如这样写&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;awaitable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;co_main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;hello &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    
    &lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;steady_clock&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;this_coro&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;executor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;expires_from_now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mx&quot;&gt;1s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;async_await&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;use_awaitable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    
    &lt;span class=&quot;s&quot;&gt;&quot;world&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;co_return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;co_main_ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_context&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;co_spawn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;co_main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;argc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;co_main_ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exception_ptr&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e_ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;co_main_return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e_ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rethrow_exeption&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e_ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;co_main_ret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;co_main_return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;co_main_ret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;调用原先需要传递 回调函数的 asio异步函数, 只要把回调换成 asio::use_awaitable 占位符, 就可以自动把待调用函数变成 awaitable.&lt;/p&gt;

&lt;p&gt;因此, 回调不再是回调了, asio 的文档都已经把 callback handler 改称为 completion token 了. 传递 use_awaitable 当做 completion token,
则 initiator 函数就自动 awaitable.&lt;/p&gt;

&lt;p&gt;asio 需要兼容 回调/co_await 两种模式, 所以使用了 completion token 概念, 而我自己的代码, 不需要这个概念, 我只要全部 co_await 话, 
把所有的函数, 统统变成 awaitable.&lt;/p&gt;

&lt;p&gt;万物皆 awaitable 后, 我发现了新大陆.&lt;/p&gt;

&lt;p&gt;原先的设计思路突然被放开了限制, 豁然开朗.&lt;/p&gt;

&lt;p&gt;原先我设计的程序主体启动代码是这样的&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
my_server srv(io, configs);

srv.start();

asio::signal_set s(io, SIGTERM);
s.async_wait([&amp;amp;](auto ec, auto sig){ srv.stop() ;} )

io.run();


&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;变成 awaitable 后, 我的主体代码是这样的&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
my_server srv(io, configs);
asio::signal_set sighandler(io, SIGTERM);
co_await (  srv.run() || sighandler.async_await(asio::use_awaitable)  ) ;
co_await srv.stop();

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;而且, srv.stop() 变成 awaitable 后, 更容易实现优雅的退出了, 因为可以等待一些费时的逻辑完成退出后, stop 才会 resolve.&lt;/p&gt;

&lt;p&gt;这样, srv 就在干净的状态下析构, co_main 也顺利干净利落的返回.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>新能源汽车充电和三相负荷平衡</title>
   <link href="https://microcai.org/2022/02/14/ev-charging-and-3phase-balance.html"/>
   <updated>2022-02-14T00:00:00+00:00</updated>
   <id>https://microcai.org/2022/02/14/ev-charging-and-3phase-balance</id>
   <content type="html">&lt;p&gt;我喜欢直流电，本质原因是直流电是恒功率的。而交流电的功率是脉动式的。&lt;/p&gt;

&lt;p&gt;后来我才发现， 三相电也是恒功率的。&lt;/p&gt;

&lt;p&gt;因为 sin(t) + sin(t + 120) + sin(t + 240) 的函数是一条直线。&lt;/p&gt;

&lt;p&gt;从功率传输的角度来说，三相电 = 直流电 》单相电。&lt;/p&gt;

&lt;p&gt;单相交流电是最差劲的一种电。为了获得稳定的功率，所有使用单相交流电的设备不得不储备一个庞大的储能装置（通常是巨大的电解电容）&lt;/p&gt;

&lt;p&gt;这个电容储存的能量必须能足够使用 10ms（半个周期）。因此功率越大的设备就需要配置越大的电容。&lt;/p&gt;

&lt;p&gt;越大的电容就会有越大的充电电流。而且还是冲击性的。因此还需要给电容配置预充电电路，大大增加成本和复杂度。&lt;/p&gt;

&lt;p&gt;发电厂并不能发单相电，如果用户使用的都是单相电，电网还要负责平衡三相的负载。&lt;/p&gt;

&lt;p&gt;但是，把全国人分成三波，一波用A相，一波用B相，一波用C相，并不能真的平衡负荷。&lt;/p&gt;

&lt;p&gt;新能源汽车的诞生，让三相负荷平衡问题更雪上加霜了。&lt;/p&gt;

&lt;p&gt;虽然平时家用时候单相电，但是 1来家庭用电功率不打，2来大家的功率也不会相差很大。&lt;/p&gt;

&lt;p&gt;但是， 电动车就不一样了，不用的时候就是 0kw， 一用就是 7kw。&lt;/p&gt;

&lt;p&gt;为什么国家规定交流慢充最大功率 7kw？ 就时候因为考虑要做三相负荷平衡。实际上电网希望慢充不要超过 3kw，但是 3kw 太慢了，被迫接受 7kw。&lt;/p&gt;

&lt;p&gt;这方面还是特斯拉富有远见。&lt;/p&gt;

&lt;p&gt;特斯拉用单相就只能 3.3kw 充电，只有用三相电才能开启真正的交流慢充11kw。&lt;/p&gt;

&lt;p&gt;国内其他车厂，为了成本考虑，不想做11kw的三相充电，就给大家打个折扣，给你6.6kw的充电器。
但是，如果 6.6kw 如果是三相的，每相只有 2.2kw。于是在没有 380v 交流电的地方，只能使用 2.2kw 功率，消费者时候绝无可能接受如此慢的充电功率的。
于是，他们就很贱的，给整成  6.6kw 的单相充电机了。&lt;/p&gt;

&lt;p&gt;这种极度私自的行为，严重的拖垮电网。&lt;/p&gt;

&lt;p&gt;而且，6.6kw 的电绝无可能通过插座引入，实际上只能安装专用的充电桩。
可以使用插座的随车充，还是只能最大 2kw。&lt;/p&gt;

&lt;p&gt;而能安装专用的充电桩的，其实都是有条件使用380v三相电的。&lt;/p&gt;

&lt;p&gt;所以，使用 6.6kw 的单相充电器，真的好处就只有支持用户去买超大功率的山寨随车充。极大的增加风险。&lt;/p&gt;

&lt;p&gt;而且相电流高达32A，极大的增加线路起火风险。&lt;/p&gt;

&lt;p&gt;所以，还是特斯拉这种做法对电网更友好。&lt;/p&gt;

&lt;p&gt;也就是，使用 11kw 的三相充电机，单相使用为 3.3kw。
实际上220v能安全使用的最大功率是空调用的插座，16A 而已，就是 3.3kw。&lt;/p&gt;

&lt;p&gt;并不会减少用户在没有充电桩的地方的充电体验。&lt;/p&gt;

&lt;p&gt;而只要是汽车交流充电桩，就可以，也必须，提供完整的三相电。&lt;/p&gt;

&lt;p&gt;因为车子本身单相就只有3.3kw的能力，因此也杜绝了使用大功率随车充导致的问题。&lt;/p&gt;

&lt;p&gt;而且专用的充电桩都是三相的，电网就不变担忧扩大化的三相负荷不平衡问题，反而能减少不平衡电流的占比。&lt;/p&gt;

&lt;p&gt;还有，三相充电机也不需要大电容就能做到平稳的直流输出。&lt;/p&gt;

&lt;p&gt;结论：
希望车厂停止使用 6.6kw 的车载充电机，改为使用 11kw 的交流充电机。甚至 6.6kw 的三相充电机都远优于目前的单相 6.6kw。&lt;/p&gt;

&lt;p&gt;因为三相6.6kw 我只要2个平方的线！而目前普遍的7kw 交流桩要求6个平方的线。而且没充电桩用的时候，车厂送的不还是弱鸡的 2kw 随车充么？&lt;/p&gt;

&lt;p&gt;至于没有 380v 的问题： 我都能搞定车位，安装了充电桩专用的电表了，还能没380v？ 安装充电桩最难的地方根本不是电，是车位啊车位！&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Future 和 Promise 的区别</title>
   <link href="https://microcai.org/2022/02/05/future-and-promise-comparision.html"/>
   <updated>2022-02-05T00:00:00+00:00</updated>
   <id>https://microcai.org/2022/02/05/future-and-promise-comparision</id>
   <content type="html">&lt;p&gt;promise 和 yield 的区别是什么？&lt;/p&gt;

&lt;p&gt;其实区别大了去了&lt;/p&gt;

&lt;p&gt;yield 虽然写异步看起来像同步，爽了。&lt;/p&gt;

&lt;p&gt;但是强制了必须等待 io 的模型。&lt;/p&gt;

&lt;p&gt;yield async io, 必须 io 完成，你的协程才能进一步执行&lt;/p&gt;

&lt;p&gt;和原来同步模式的线程io是一样的。必须 io完成你的线程才能继续。&lt;/p&gt;

&lt;p&gt;promise 则不然。 promise 可以在需要等待io结果的地方再 yield，结果出来以前可以执行其他操作&lt;/p&gt;

&lt;p&gt;在线程时代，也有一个和 promise 的作用非常类似的东西，就是 future。&lt;/p&gt;

&lt;p&gt;future 从未流行过。&lt;/p&gt;

&lt;p&gt;promise 之于协程，如 std.future 之于线程。然而 future 不流行，因为同步 IO 的写法已经过时了。
现在是异步的天下。&lt;/p&gt;

&lt;p&gt;虽然看起来有点标题党，但是 future 确实就是线程版的Promise. Promise 是为了更好的在异步环境里重叠异步操作。
future则试图给同步的线程模型里赛点异步的东西。&lt;/p&gt;

&lt;p&gt;这种硬塞就好像你给五菱宏光mini ev 塞五连杆悬挂一样，人家根本就用不上。真用上的，还会买五菱宏光吗？&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>asio::promise yyds</title>
   <link href="https://microcai.org/2022/02/04/asio-promise-yyds.html"/>
   <updated>2022-02-04T00:00:00+00:00</updated>
   <id>https://microcai.org/2022/02/04/asio-promise-yyds</id>
   <content type="html">&lt;p&gt;最近在写以太坊的巧克力浏览器（区块链浏览器）。因为区块链浏览器需要访问历史的块，因此需要节点以 archive 模式同步。&lt;/p&gt;

&lt;p&gt;遂运行了一个 geth。 然而半个月过去了，才同步了四百万高度，而且这四百万高度的最后一百万花了80%的时间。
同步速度以肉眼可见的速度下降，ETA 变的越来越长。 从一个月慢慢的增长到6个月。&lt;/p&gt;

&lt;p&gt;于是寻找更快的客户端。找到了 erigon。erigon 号称2天完成 archive 节点同步。
结果牛逼是吹大了啊。最后耗费了10天完成了同步。&lt;/p&gt;

&lt;p&gt;geth 在同步的时候， rpc 是可用的，geth 同步到哪里， eth_getBlockNumber 返回的高度就是哪里。
因此在同步的时候，我也开始同步巧克力浏览器的数据了。&lt;/p&gt;

&lt;p&gt;巧克力浏览器是通过 eth_getblockbynumber 和 eth_gettransactionreceipt 把所有的交易数据都存入自己的数据库实现的。虽然其实可以等用户浏览到那个块再通过 rpc 获取数据，但是这样轻模式就没有检索功能了。所以还是需要解析所有的交易存入自己的数据库。&lt;/p&gt;

&lt;p&gt;最终放弃 geth 二改用 erigon 的时候，巧克力浏览器才同步到一百多万的高度，就被迫停止了。&lt;/p&gt;

&lt;p&gt;因为 erigon 只有完成同步，才会更新 eth_getBlockNumber 返回的高度。他同步的方式是先下载所有的块，然后执行，让后更新state数据，然后更新 rpc 接口的数据，然后获取最新高度，然后把落下的全下载回来，然后执行，让后更新state数据，然后更新 rpc 接口的数据，然后获取最新高度，然后把落下的全下载回来，然后执行，让后更新state数据，然后更新 rpc 接口的数据，然后获取最新高度…… 直到最后每次批处理的块只有1个，就算同步彻底完成。
如果 eth_getBlockNumber 一直返回 0 高度，我的浏览器就没法同步了，于是只能等。&lt;/p&gt;

&lt;p&gt;问题是，他第一次的批量的时候，就试图从 0 一直批量执行到 1401万高度。结果，执行了10天才批量执行完。这一等就是十天, 等到了除夕前一天啊。执行完毕后，就再次落后十万高度了，于是进入第二次批处理。&lt;em&gt;因为我在机械盘上运行 erigon，因此 erigon 最后会永远落后几百到几千个高度，不过这个是后话，第一次批处理完成后，我的浏览器又可以开起来同步了&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;结果，我对巧克力浏览器的同步速度非常的不满了。&lt;/p&gt;

&lt;p&gt;因为，按同步速度估算，可没法10天完成巧克力浏览器的同步。大约需要20天，而且 ETA 在增加！最后可能达到了三年之久。为啥我能提前知道eta在增加呢？因为我观察到，同步空快很快，同步内部有交易的块就慢下来了。早期空块多，但是后期基本都是满块。满块的同步速度低到令人发指的超过数秒！&lt;/p&gt;

&lt;p&gt;经过研究发现，速度是慢在了 get_transactionreceipt 上。这个接口对未缓存的交易返回速度需要数百毫秒。&lt;em&gt;第二次调用其实几个毫秒就返回了.&lt;/em&gt;
而巧克力浏览器访问的都是数年前的交易，都是属于 old data。因此每次调用它都要等待几百毫秒。
如果一个块里有个一百个交易，同步速度就惊人的 slow down 了。&lt;/p&gt;

&lt;p&gt;如果把get_transactionreceipt的调用并发化呢？&lt;/p&gt;

&lt;p&gt;大过年的，大家在看春晚，我却在敲代码。在改进巧克力浏览器的同步速度。完成批量同步，需要对原来的代码进行2个改进，第一个改进，是让原来的 jsonrpc 接口支持 pipelining，其次是实现 &lt;em&gt;等待所有协程完成&lt;/em&gt; 的一个 asio 协程工具。虽然用在线程上此类的工具汗牛充栋，但是用在asio的协程上的工具可是 non-exist的。
有了这2个工具，只要把原来 for each transaction ; do get_transactioreceipt ; done 的代码，改成一次创建所有的协程去 get_transactioreceipt . 然后等待协程返回然后收集结果。 基本上就是 c++ 版的 asyncjs 里的并发 map 操作。&lt;/p&gt;

&lt;p&gt;原来的 jsonrpc client，采取的做法是  async_write 后 async_read (当然是带 yield 的协程版），这样每次只能等待 rpc 返回后才能发起下一个 rpc 调用。
如果不改进 这个客户端类的代码，就只能被迫使用连接池才能并发调用 get_transactioreceipt 了。因此改进了这个 rpc 对象的代码， 可以在多个协程里并发的调用 jsonrpc.async_req(params, yield);&lt;/p&gt;

&lt;p&gt;并发 get_transactioreceipt 的版本上线后，同步速度一下子就改进了！块里交易量的多寡，对同步速度的影响就小下来了。当然是不能指望它毫无影响的 &lt;em&gt;:)&lt;/em&gt;。&lt;/p&gt;

&lt;p&gt;这样就过了一个愉快的除夕和新年。&lt;/p&gt;

&lt;p&gt;那么，代码就从&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;for all tx
    get_transactioreceipt ...
save_block
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;变成了&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;for for all tx
    asio::spawn(  get_transactioreceipt ... ) # 开一堆协程
wait all corotines
save_block
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;同步了几天，高度到了四百万了，按这个速度，10天即可完成同步。考虑到后期满块增加，也不会超过20天吧。看来三月份就可以满血上线了。&lt;/p&gt;

&lt;p&gt;然而，到了立春前天一看日志，情况变不对劲了。速度又慢下来了。&lt;/p&gt;

&lt;p&gt;原来是数据库操作慢下来了！原先空快多，交易量少。同步了三百万高度，交易量都不足千万。
但是到四百多万高度，交易量已经膨胀到超过一个亿了。&lt;/p&gt;

&lt;p&gt;我的小 nas 哪里受得了这么大的表啊。数据库爆表了。马上花了数个小时的时间，做了分表。保存一个block内容的时间才从数百毫秒重新下降到20毫秒上下。
但是显然和初期的5毫秒上下保存有差距。&lt;/p&gt;

&lt;p&gt;显然，如果把数据库的保存操作异步化，就可以提升速度。&lt;/p&gt;

&lt;p&gt;这个虽然很简单，只要把保存这个操作放到单独的协程里进行即可。&lt;/p&gt;

&lt;p&gt;但是，问题出在我的同步逻辑上。因为 jsonrpc 会丢失链接（手动重启 erigon 或者 erigon crash )，数据库也会挂（手贱和其他问题）
如果异步了，就不容易处理在挂掉的点上重新执行一遍这个逻辑。&lt;/p&gt;

&lt;p&gt;这个时候，我很怀念 nodejs 的 promise/await ， 如果有 promise 就好了，我的代码可以改成这样。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// NOTE rpc_get_block_data 还是并发的 get_transactionreceipt 哈
[fetch_ok, data] = await rpc_get_block_data(sync_height)

for (; sync_height &amp;lt; await rpc.get_blockheight();)
{
    next_block_data_promise =  rpc_get_block_data(sync_height + 1); // 马上获取下一个块, 后台获取，不等待结果
    if (fetch_ok)
    {
        save_ok = await save_to_db(data);
        if (save_ok)
        {
            sync_height++;
            [fetch_ok, data] = await next_block_data_promise; // 等待异步完成结果
       }
    }
    else
        [fetch_ok, data] = await rpc_get_block_data(sync_height); // 重获取一次
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样，如果 db 保存失败，sync_height 就不会增加，并且重新获取。
这样避免同步的过程中出现漏同步，出现空洞高度。这种写法还避免了完全异步fetch和save模式下的同步协作问题。大大简化了代码。&lt;/p&gt;

&lt;p&gt;研究了一整天，最后发现了 asio 早已提供了 promise/await！&lt;/p&gt;

&lt;p&gt;下面进入 boost::asio::experimental::use_promise 的世界！&lt;/p&gt;

&lt;p&gt;上面这个代码，用 asio 的方式，写法就是这样&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;// NOTE rpc_get_block_data 还是并发的 get_transactionreceipt 哈
[fetch_ok, data] = rpc_get_block_data(sync_height， yield); // yield 说明上下文是在 asio::spawn 开启的协程里

for (; sync_height &amp;lt; rpc.get_blockheight(yield);)
{
    // 马上获取下一个块, 后台获取，不等待结果, 注意这里的 use_promise 替代了 yield
    next_block_data_promise =  rpc_get_block_data(sync_height + 1, asio::experimental::use_promise); 
    if (fetch_ok)
    {
        save_ok = save_to_db(data, yield);
        if (save_ok)
        {
            sync_height++;
            [fetch_ok, data] = next_block_data_promise.async_wait(yield); // 等待异步完成结果 这里的 wait 用了 yield
       }
    }
    else
        [fetch_ok, data] = rpc_get_block_data(sync_height, yield); // 重获取一次, 不用 promise 了
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当然，自己的 rpc_get_block_data 也要经过修改，实现支持 yield/use_promise 双操作。 括弧 asio 自己的IO对象其实都支持 回调/yield/use_promise 三操作的。&lt;/p&gt;

&lt;p&gt;promise/await 永远的神！&lt;/p&gt;

&lt;p&gt;PS: 我运行在家nas上的巧克力浏览器，虽然还在同步，但是可以使用了 &lt;a href=&quot;http://geth.home.microcai.org:3586/&quot;&gt;geth.home.microcai.org:3586&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;当然，因为是家里的 nas，so 只能用 ipv6 访问。不开放 ipv4 访问，免得麻烦。&lt;/p&gt;

&lt;p&gt;稳定性不保证哈！随时调试。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>江和河有什么区别</title>
   <link href="https://microcai.org/2021/12/28/he-and-jiang.html"/>
   <updated>2021-12-28T00:00:00+00:00</updated>
   <id>https://microcai.org/2021/12/28/he-and-jiang</id>
   <content type="html">&lt;p&gt;黄河水系和长江水系之间的万千群山。这片群山在渭水南岸的百里之遥拔地而起，横空出世，形成第一道高峰绝谷，时人叫做南山，后人称为秦岭。天下水流从这道南山分开，北面的河流绝大部分流入黄河，南面的河流绝大部分流入长江。&lt;/p&gt;

&lt;p&gt;黄河最早叫河水，河就是水名，后来才称黄河。长江最早叫江水，江是水名，后来才叫长江。类似的还有淮河最早叫淮水，淮是水名，后来才叫淮河。&lt;/p&gt;

&lt;p&gt;水最后又演变成了河江。原来的 X水 的称呼就变成了 X江 X河。
那河水自然就改名成黄河。江水改名成长江。&lt;/p&gt;

&lt;p&gt;那么水为啥演变成河江，啥时候称河，啥时候称江呢？&lt;/p&gt;

&lt;p&gt;其实这是个语言习惯问题。&lt;/p&gt;

&lt;p&gt;长江流域的人民，习惯用江。黄河流域的人民，习惯用河。&lt;/p&gt;

&lt;p&gt;其他流域的，则主要取决于汉人向其他地方迁移的时候，是那种语言习惯的人先过去的。先过去的自然就有了命名权。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>压燃发动机更适合做増程器</title>
   <link href="https://microcai.org/2021/12/21/Homogeneous-charge-compression-ignition-better-suited-for-REEV.html"/>
   <updated>2021-12-21T00:00:00+00:00</updated>
   <id>https://microcai.org/2021/12/21/Homogeneous-charge-compression-ignition-better-suited-for-REEV</id>
   <content type="html">&lt;h1 id=&quot;压燃的缺点&quot;&gt;压燃的缺点&lt;/h1&gt;

&lt;p&gt;阻碍汽油机热效率提升的根本原因是汽油机无法实现大压缩比。压缩比过大，混合气体温度过高，汽油会在火花塞点火前被压燃。这会引起爆震。需要更高标号的汽油。L:&lt;/p&gt;

&lt;p&gt;柴油机不会爆震，是因为它吸入的是纯空气。汽油机必须吸入混合气体，是因为汽油必须与空气按14比1混合。&lt;/p&gt;

&lt;p&gt;吸入多少空气就必须注入对应的汽油。要控制油门，就必须减少吸入的空气。这样就会导致压缩比降低。因此汽油机小油门下，效率大大降低。&lt;/p&gt;

&lt;p&gt;这个特性就使得汽油机很难做到压燃。即使用高压缩比实现了压燃，小油门下压缩比又会过低导致无法压燃。&lt;/p&gt;

&lt;p&gt;马自达实现的压燃汽油发动机，为了能在各种工况下工作，其实并不总是处于压燃状态。在低负载下还是要靠火花塞引燃。搞了各种点火机制进去使得发动机复杂度大大提升，导致成本无法降低。&lt;/p&gt;

&lt;h1 id=&quot;压燃用于增程的优势&quot;&gt;压燃用于增程的优势&lt;/h1&gt;

&lt;p&gt;増程器最大的特点就是工况单一。单一工况意味着压燃发动机无需再考虑无法压燃的工况。从而彻底抛弃火花塞，由此大大降低发动机复杂度和制造成本。再配以米勒循环，实现 25:1 的压缩比都不是梦。&lt;/p&gt;

&lt;h1 id=&quot;技术总结&quot;&gt;技术总结&lt;/h1&gt;

&lt;p&gt;实现用于增程器的压燃汽油发动机，其特点应该有：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;大比例废气再循环 废气冷却后再吸回气缸，降低气缸内的氧含量 增加理论空燃比&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;超稀薄燃烧 使用超过 40:1 的空燃比 超高的空燃比下的稀薄燃烧反而能大大降低气缸温度从而降低 NOx 。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;米勒循环  使膨胀比大于压缩比，降低废气温度&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;无节气门，始终充分吸入空气，0 泵气损失&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;利用发电机调节转速，始终让发电机 100% 扭矩输出。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;电辅助废气涡轮增压 在涡轮轴外部套上线圈，通电后产生的旋转磁场和废气合力推动涡轮，或者涡轮转速过高时回收涡轮能量。维持进气压力恒定以优化燃烧室设计。&lt;em&gt;异步电机的物理特性：将旋转磁场设定为需要的涡轮转速。涡轮实际转速低于旋转磁场，就会进入电动机模式，如果涡轮转速过于旋转磁场转速，自动进入发电模式。&lt;/em&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>增程不差，直驱没那么神</title>
   <link href="https://microcai.org/2021/12/20/coupling-clutch-no-that-important.html"/>
   <updated>2021-12-20T00:00:00+00:00</updated>
   <id>https://microcai.org/2021/12/20/coupling-clutch-no-that-important</id>
   <content type="html">&lt;h1 id=&quot;不要迷信发动机直驱&quot;&gt;不要迷信发动机直驱&lt;/h1&gt;

&lt;p&gt;很多人以为 理想one高速油耗高，唐dmi低，是因为dmi有高速直驱模式。&lt;/p&gt;

&lt;p&gt;其实这个是不对的。发电再驱动，固然有一个能量转换效率，然而变速箱的能量传递也并非 100%。通过变速箱驱动和通过发电机电动机的间接驱动，能量传递的效率差距在10%以内。换言之，如果理想one高速是 10L 的油耗，增加直驱也不会让油耗低于9L。显然唐dmi的油耗低于9L 并不全是直驱的功劳。&lt;/p&gt;

&lt;h1 id=&quot;归根结底还是发动机的热效率&quot;&gt;归根结底还是发动机的热效率&lt;/h1&gt;

&lt;p&gt;要说唐dmi的油耗为啥比理想one低，归根结底还是因为骁云发动机的热效率远高于东安动力。使用同样的动力结构的赛力斯油耗就比理想one低，而且赛力斯比理想one还重了50kg。主要也是因为赛力斯的发动机虽然不如骁云发动机，但是也比东安动力的强不少。&lt;/p&gt;

&lt;h1 id=&quot;那直驱还是能提高一点效率的吧&quot;&gt;那直驱还是能提高一点效率的吧？&lt;/h1&gt;

&lt;p&gt;是也不是。&lt;/p&gt;

&lt;p&gt;目前来看是的，但是未来三电技术的发展，发电后驱动电机，整条能量流路径上的效率可以进一步优化。最后使得直驱带来的效率优势抵不过导致的nvh劣势而变得鸡肋。&lt;/p&gt;

&lt;p&gt;而且直驱意味着只能前驱。如果要做四驱，后轮无论如何也不能直驱。没有人会同意为了后轮直驱而给电动车加传动轴。这样四驱下，前轮直驱的效率优势就更低了。&lt;/p&gt;

&lt;h1 id=&quot;直驱咋就有nvh劣势了呢&quot;&gt;直驱咋就有nvh劣势了呢？&lt;/h1&gt;

&lt;p&gt;主要是因为，发动机要驱动车辆，就无法同车身进行柔性连接。发动机的输出轴输出扭矩的时候，机身还会有反扭矩，机身通过比较强的衬套连接在车架上来克服这个反扭矩。于是发动机的振动就不可避免的要通过连接点向车身传递。变速箱也会因为同样的原理必须固定在车身，导致振动传递。&lt;/p&gt;

&lt;p&gt;如果发动机不直接驱动车辆，而是发电，那发动机和发电机这个整体，就可以使用非常柔性的方式固定在车上。比如类似光驱里的转盘机构和光驱外壳的减震结构。&lt;/p&gt;

&lt;p&gt;这样能大大减少发动机的振动传递给车身。&lt;/p&gt;

&lt;p&gt;因此增程结构，有天然的 nvh 优势。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>增程式电车真香</title>
   <link href="https://microcai.org/2021/11/12/bought-new-car.html"/>
   <updated>2021-11-12T00:00:00+00:00</updated>
   <id>https://microcai.org/2021/11/12/bought-new-car</id>
   <content type="html">&lt;h1 id=&quot;唐dm不香了&quot;&gt;唐DM不香了&lt;/h1&gt;

&lt;p&gt;开了三年多的唐DM，总觉得比亚迪的车啊，差点意思。&lt;/p&gt;

&lt;p&gt;第一个差点的意思是，有电一条龙，没电一条虫。
虽然买的时候就知道了，但是那时候，没电的混动车，都是一条虫。&lt;/p&gt;

&lt;p&gt;但是当没电不是一条虫的增程式电动车出来的时候，唐DM就不香了。&lt;/p&gt;

&lt;p&gt;而没电一条虫的根本原因，是比亚迪糟糕的混动逻辑，导致电量无法保持。&lt;/p&gt;

&lt;p&gt;发动机低速下不启动。起步全用电。而2.4吨的家伙，要加速到40码，消耗的电能巨大。起步肉眼可见的掉电。
好不容易起来了，马上红灯就得减速。发动机刚刚起来就立马停掉。低于60的速度，如果不踩油门，发动机就会关闭。&lt;/p&gt;

&lt;p&gt;这样的混动逻辑，电怎么保的住？&lt;/p&gt;

&lt;p&gt;第二个差点的意思啊，是比亚迪背刺老车主。&lt;/p&gt;

&lt;p&gt;秒天秒地秒空气的 DMi 出来了。唐DM瞬间变成了垃圾！&lt;/p&gt;

&lt;p&gt;新车太香，于是老款贬值速度变得太快。&lt;/p&gt;

&lt;p&gt;为了不至于老款变得分文不值，决定乘现在还有点残值，赶紧出手。&lt;/p&gt;

&lt;h1 id=&quot;增程香&quot;&gt;增程香&lt;/h1&gt;

&lt;p&gt;早在2019年理想one出来的时候，我就看上了增程式。无奈那时候唐才开了一年。总不能一年就换车吧。&lt;/p&gt;

&lt;p&gt;于是安慰自己，第一代产品有缺陷的，等第二代吧。&lt;/p&gt;

&lt;p&gt;时间一晃到了2021年。这一年，汽车市场发生了翻天覆地的变化。&lt;/p&gt;

&lt;p&gt;理想出了新款，比亚迪出了DMi。华为开始造车了。&lt;/p&gt;

&lt;p&gt;新车必须式增程式的，因此我把目标锁定在了这几个车上。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;宋Plus DMI
    DMI就是增程式电动车，虽然厂家从不宣传这点。但是从它的运行逻辑来看，它就是增程式电动车。
    但是，宋也有致命缺陷：没四驱，筷子悬挂。于是一直等四驱DMI。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;赛力斯SF5
    其实本来这个车很好，无奈有几个缺点让我一直犹豫不决。最大的缺点就是没有遥控驾驶。然后那柳叶眉的后窗不好看也不实用。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;岚图Free
    和理想one差不多的价格，多提供了很多东西。属于是高性价比的理想one。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;天际ME5
    假药停的遗腹子。虽然价格和宋差不多，不过比宋还是多了很多东西的。起码底盘比宋强。不是筷子悬挂。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;备选车里，没有唐DMI也没有理想one。为啥呢？因为我发现自己其实不需要那么大的车。既然宋dmi和唐dmi唯一的区别就是尺寸，那为啥要多花钱买大壳子？理想one和岚图价格一样，为啥买配置低点的理想one？&lt;/p&gt;

&lt;p&gt;这4款车决定了，于是就跑去试驾去了。&lt;/p&gt;

&lt;p&gt;试驾后发现的体会：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;岚图Free
    提供了上门试驾服务，服务不错。开过来的试驾车电量只有20%了。正好可以感受下增程器开启的模式。然而上车就发现了问题。这个车因为电量低，于是增程器一直保持在开启状态。不然空调会耗尽电池。然而，试驾前销售要讲解，停车十几分钟，电量丝毫没有增加。这增程器增程了个寂寞。完全没有充电！只是单纯的怠速带空调而已。这是油车逻辑啊。一看表显油耗都到15L百公里了。于是被Pass。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;天际ME5
    驾驶起来很舒服。提供了动能回收关闭的选项。关闭后，释放油门进入空单滑行非常舒适，踩刹车依然有动能回收。底盘很结实，不是筷子悬挂。开起来动力也非常猛。增程器四缸，很安静。增程器开启的时候，也是直接进入经济区间发电。不会像理想one那样为了弥补三缸缺陷，除非电量严重过低，否则增程器只运转在非经济区间低转速发电。增程逻辑是正常的。油耗也很低。可惜，只有后驱，没有四驱。销售懂都不懂，说四驱多一个电机油耗就翻倍了。pass。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;赛力斯sf5
    其实这个车是第一个去试驾的，但是因为最终买了这个车，所以最后说。这个车也是上门试驾。车送到门口。这车的机械素质真的是没话说！底盘开起来非常高档，像极了 A6L。考虑到电车的无顿挫加速，那真的是 CVT + V8 版的 A6L 了。
    但是，没比亚迪的遥控功能。没了比亚迪好用的安卓车机，没了比亚迪好看的外观。。。。。。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;于是就一直等宋Plus 四驱DMI。等到了9月30号。&lt;/p&gt;

&lt;p&gt;终于四驱DMI有消息了。报价20万。。。。。&lt;/p&gt;

&lt;p&gt;比亚迪飘了啊，区区紧凑型suv就敢卖20万了，而且还是麦弗逊+三连杆的悬挂。
于是直接下定赛力斯。连试驾都不去了，因为我开了3年的唐，太清楚比亚迪的底盘素质了。&lt;/p&gt;

&lt;h1 id=&quot;半月使用体会&quot;&gt;半月使用体会&lt;/h1&gt;

&lt;p&gt;半个月开了一千多公里，只在提车的时候加了200的油。一直纯电，没怎么烧油。能快充是真的舒服！
以前开唐出门的时候，都得规划下，是马上会回家呢，还是会在外面跑一阵。如果是马上回家，算下总里程会不会超过60km。
只有出门很快会回来，总里程不超过60km的，我才敢纯电出门。否则就得一路混动过去。&lt;/p&gt;

&lt;p&gt;因为唐的发电逻辑很糟糕，混动是几乎不会回充电量。除非一路匀速开高架。而低电量下混动体验非常差。所以必须得保持高电量下的混动行驶。也就是充满电后使用HEV模式行驶。这样起步用电，巡航用油。掉电多少取决于起步/急加速了多少次，和行驶里程无关。&lt;/p&gt;

&lt;p&gt;长途行驶下来，基本上到家还会有30%-50%的电量。&lt;/p&gt;

&lt;p&gt;但是，也有马失前蹄的时候，在路途中电量就到20%以下。那就非常的焦虑了。因为低电量下，起步就不能只用电了。那糟糕的双离合，起步起来顿挫的一塌糊涂。而且还没动力！因为没电了。发动机干吼车就是不走。虽然这个起步仍然比大多数日本车更快，但是，我是开惯了4.5s的人。&lt;/p&gt;

&lt;p&gt;以上是唐给我带来的糟糕馈电体验。&lt;/p&gt;

&lt;p&gt;以下是赛力斯的馈电体验。&lt;/p&gt;

&lt;p&gt;虽然基本纯电行驶。但是也抽空开了一个小长途。来回两百多公里。这个里程纯电续航就无法覆盖了。也不做特殊设置。直接走。
开了几十公里后，电量见底，进入30%。然后一个不小心，再瞄过仪表盘的时候，发现发动机早就不知不觉中开起来了。居然我毫无察觉。&lt;/p&gt;

&lt;p&gt;然后继续行驶的过程中我就时不时的看下发动机状态指示，还有剩余电量。&lt;/p&gt;

&lt;p&gt;发现，发动机并不全程开启。低速行驶时发动机不启动。只有进入30以上的车速发动机才启动。这样风噪和胎噪就盖过了发动机的噪音，难怪无法察觉。即使车速一直高于30，发动机也会停止，因为电量达到了32%。&lt;/p&gt;

&lt;p&gt;也就是说，车速高于30码的时间本身占我总行驶时间也只有一半，这一半里又有一半的时间，因为电量充足发动机就关了。发动机只有一半的一半的时间在工作，就保障了电量供应。而且全程电量没有低于29%过。保电能力不知道比唐DM高到哪里去了。&lt;/p&gt;

&lt;p&gt;发动机工作的时候非常难以察觉。因为是增程式的，发动机不驱动车辆，完全无法察觉出来发动机启动后的介入。因为根本没介入。
不像唐，发动机启动后，能察觉到离合器的闭合导致的一点冲击。而且发动机介入后，就变成了双离合的车了，能察觉到升档和降档的顿挫，还有发动机声音的变化。&lt;/p&gt;

&lt;p&gt;而增程模式下，发动机恒定转速，我使劲踩油门，发动机的声音岿然不动。并不会进入歇斯底里的状态。还是我行我素的以固定的功率发电。不足部分由电池补充。这也是这个增程系统的名字“驼峰增程器” 的由来。驾驶员对功率的需求就像骆驼的驼峰。但是增程器利用大电池，把驼峰给削了。&lt;/p&gt;

&lt;p&gt;其实，以固定功率发电，是赛力斯和天际ME5才执行的逻辑。理想one和岚图Free都不是。理想one和岚图的增程器发电功率和车速是绑定的。这一点非常的愚蠢。但是理想的cto在知乎上给出过理由，那就是理想用的三缸发动机，经济区间的 nvh 太差了！不得不随车速改变功率。尽量低转速，无法进入经济转速。因为这个三缸机的经济转速在2800转。而为了 nvh考虑，理想one只用他1400转到2000转之间的功率。理想one其实只要更换一个更好的发电机，就能大幅降低油耗。至于岚图用的并不是三缸机，为啥还抄袭理想one的逻辑就不知道了。&lt;/p&gt;

&lt;p&gt;油的部分说完了，再说快充。&lt;/p&gt;

&lt;p&gt;唐没有快充，所以出门的时候必须时刻注意保持电量。不然没地方充电。而赛力斯有快充（这点必须感谢李想。因为他在大家喷他插混车搞快充没意义的环境下坚持提供快充接口，最终感化了其他厂家也给混动车提供了快充）即使电量低，也可以随时去公共快充桩上补电。&lt;/p&gt;

&lt;p&gt;而且快充桩地图上搜出来结果发现找不到，找到了发现桩是坏的不能用。能用也被油车占位，等等诸如此类的事情，即使发生了，我也无所谓，烧油就好了。如果是纯电，那就非常焦虑了。不知道去下一个充电桩还够不够续航，下一个充电桩还能不能充上电。&lt;/p&gt;

&lt;p&gt;这个体验都是吊打纯电和原来没快充的插混的。&lt;/p&gt;

&lt;h1 id=&quot;还好没买天际me5&quot;&gt;还好没买天际ME5&lt;/h1&gt;

&lt;p&gt;其实当时是因为没四驱pass掉的天际。&lt;/p&gt;

&lt;p&gt;但是现在庆幸自己没买天际。&lt;/p&gt;

&lt;p&gt;天际现在出现交车困难。感觉好像是传销一样。到处开门店收割订单。然后瞬间关门跑路。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>pipewire 不行</title>
   <link href="https://microcai.org/2021/09/15/pipewire-sucks.html"/>
   <updated>2021-09-15T00:00:00+00:00</updated>
   <id>https://microcai.org/2021/09/15/pipewire-sucks</id>
   <content type="html">&lt;h1 id=&quot;一切都是为了混音&quot;&gt;一切都是为了混音&lt;/h1&gt;

&lt;p&gt;声音服务器， pulseaudio, pipewire, Windows Audio, AudioFlinger, oss4, jack, esd 等等，最主要的目的，就是混音。&lt;/p&gt;

&lt;p&gt;对于消费级声卡，基本上不支持硬件混音。也就是同一时间只能让一个程序发声。&lt;/p&gt;

&lt;p&gt;至于实际上你能听到多个程序发的声音，还能单独调节各个应用的声音大小，那都是因为用了声音服务器。声音服务器就一个嘛，就解决了同一时间只能让一个程序发声的问题。其他程序想发声，把数据交给声音服务器就好了。&lt;/p&gt;

&lt;h1 id=&quot;但是大部分时候用户只想听一个程序发声&quot;&gt;但是大部分时候，用户只想听一个程序发声&lt;/h1&gt;

&lt;p&gt;其实，多个程序发声这个不是真正的需求，因为声音多了，就混乱了。用户大部分时候，只想听一个程序发声音。这个程序可以是电子游戏，可以是播放器，可以看 bilibili。
系统要只是混音，其实主要是为了处理一些 ”后台声音“ 。 比如 QQ 有消息的时候那 ”滴滴滴滴滴” 的提示。迅雷下完东西后那清脆的一声 叮。&lt;/p&gt;

&lt;h1 id=&quot;采样率混战&quot;&gt;采样率混战&lt;/h1&gt;

&lt;p&gt;声音系统，打出生以来，就没有一个全球普世的采样率标准。谁都可以自己决定采样率。
于是系统的来源声音，采样率是多种多样的。&lt;/p&gt;

&lt;p&gt;但是，最终，只能有一个采样率，因为消费级声卡只能接受一个声音流。&lt;/p&gt;

&lt;p&gt;所以声音服务器，要把各种采样率的声音都重新采样为声卡能接受的采样率，然后混音成一条音频流再输出。&lt;/p&gt;

&lt;p&gt;但是，对现有的声音产品来说，最常见的来源是 MP3 和 MP4。前者是音乐，后者是（在线）视频。纯音乐的声音，通常是 44.1khz 采样率。而视频，尤其是 H26{4,5} 为代表的新视频，通常带的是 48khz 采样率的声音。&lt;/p&gt;

&lt;p&gt;所以，消费级声卡为了照顾用户，通常会在硬件上提供 44.1khz 和 48khz 两套 （ 22.05khz 和 96khz 192khz 都是衍生采样率，所以是两套）采样率的支持。&lt;/p&gt;

&lt;p&gt;播放 mp3 的时候，就让声卡的 DAC 进入 44.1khz 模式， 播放 mp4 的时候，就让声卡进入 48khz 的模式。&lt;/p&gt;

&lt;p&gt;这样就可以避免重采样。&lt;/p&gt;

&lt;h1 id=&quot;避免重采样&quot;&gt;避免重采样&lt;/h1&gt;

&lt;p&gt;声卡都搞了2个晶振，就是为了软件不要重采样。结果，某些声音服务器啊，为了自己代码写起来方便，对应用交给他的声音，不管不顾的总是给重采样了。
过去 44.1khz 是主流的时候，他们就牺牲 48khz，后来 48khz 成了主流，他们就牺牲 44.1khz。&lt;/p&gt;

&lt;p&gt;AudioFlinger 这种 java 佬写的垃圾，会这么搞也就算了。反正我不用 android。&lt;/p&gt;

&lt;p&gt;但是你 号称是全新的 声音服务器的 pipewire 居然也学 AudioFliner ?&lt;/p&gt;

&lt;h1 id=&quot;pulseaudio-的正确做法&quot;&gt;pulseaudio 的正确做法&lt;/h1&gt;

&lt;p&gt;既然大部分时候，用户只想听一个程序发声，那就应该尽量保证这个程序的声音不被重采样。除非声卡不支持。&lt;/p&gt;

&lt;p&gt;比如你用 amarok 在播放 44.1khz 的 mp3。此时系统只有一个程序在播放声音。 pulseaudio 会把声卡配置为 44.1khz 采样率模式。然后声音数据基本直通给声卡。完全没有重采样，也没有混音。&lt;/p&gt;

&lt;p&gt;突然，你点开了一个短视频，短视频的声音不太重要，所以你没有关闭 amarok, 只是把浏览器声音关小点。&lt;/p&gt;

&lt;p&gt;这个时候， pulseaudio 会把浏览器传来的声音重采样成 44.1khz 然后和 amarok 混音，再输给声卡。&lt;/p&gt;

&lt;p&gt;pulseaudio 牺牲了浏览器的声音质量。&lt;/p&gt;

&lt;p&gt;一旦你停止播放 mp3. pulseaudio 又会把声卡切换到浏览器提供的采样率上。比如你看 bilibili 视频，那声卡就又切换到 48khz 采样率。&lt;/p&gt;

&lt;p&gt;据我的观察，除了 pulseaudio ， windows （至少在Windows 10 上）也是如此行为。都是为了避免无畏的重采样。&lt;/p&gt;

&lt;h1 id=&quot;audioflinger-和-pipewire-的错误做法&quot;&gt;audioflinger 和 pipewire 的错误做法&lt;/h1&gt;

&lt;p&gt;audioflinger 不管应用程序给的是什么采样率的声音，都会在内部重采样成 44.1khz。然后坐混音，然后交给 HAL。&lt;/p&gt;

&lt;p&gt;HAL 一般由 SoC 厂提供。HAL 拿到 44.1khz 采样率的声音，如果自己的 DAC 支持 44.1khz 那就直接输出。如果不支持，就再次重采样。&lt;/p&gt;

&lt;p&gt;最糟糕的是，当你的播放器播放的是 SoC 本身支持的采样率的声音。但是 不是 audioflinger 内部的 44.1khz 那就要被毫无意义的重采样2次。&lt;/p&gt;

&lt;p&gt;pipewire 不过是把这个写死的 44.1khz 变成了 默认 48khz 并且可在  /etc/pipewire/pipewire.conf 里修改的数值而已。&lt;/p&gt;

&lt;p&gt;都知道 pulseaudio 的那个行为更好，音质更好，更省 cpu 更节能。但是它废程序员的脑子。&lt;/p&gt;

&lt;p&gt;所以 pipewire/audioflinger 是不可能废程序员的脑子的。&lt;/p&gt;

&lt;p&gt;既然开发者不想废脑子，那只能是垃圾了。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>统一</title>
   <link href="https://microcai.org/2021/08/13/what-can-we-do-to-liberty-tw.html"/>
   <updated>2021-08-13T00:00:00+00:00</updated>
   <id>https://microcai.org/2021/08/13/what-can-we-do-to-liberty-tw</id>
   <content type="html">&lt;h2 id=&quot;我们分裂太久了&quot;&gt;我们分裂太久了&lt;/h2&gt;

&lt;p&gt;论天下大势，合久必分，分久必合。&lt;/p&gt;

&lt;p&gt;最近一次，中国维持在统一的状态，是在一百多年前。&lt;/p&gt;

&lt;p&gt;自鸦片战争后，朝鲜分裂了，越南分裂了，吕宋岛分裂了，琉球没了。
藩国都跑了。&lt;/p&gt;

&lt;p&gt;宣统退位后，连中央国内部都开始分裂了。又开始了地方割据势力逐鹿中原的时代。&lt;/p&gt;

&lt;p&gt;逐鹿中原的过程，被日本的入侵中止过8年，日本投降后，又继续了。
最后，西元1949年，天下大定。再经过几年的时间扫荡剩余割据势力，按历史周期律，又该重新回到大一统。重新收拢藩国拱卫中央。&lt;/p&gt;

&lt;p&gt;然而，1952年，朝鲜战争打乱了统一进程。 使得窜逃到台湾的流寇得以休养生息。&lt;/p&gt;

&lt;p&gt;这一拖，就是70余年。
一百多年了，中央国都未完成统一，藩国尽失。藩国内还发生屠我汉民的恶劣事件。&lt;/p&gt;

&lt;p&gt;汉儿学得胡儿语，却向城头骂汉人。&lt;/p&gt;

&lt;p&gt;欸。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>推荐一款神车</title>
   <link href="https://microcai.org/2021/07/15/byd-dmi-rocks.html"/>
   <updated>2021-07-15T00:00:00+00:00</updated>
   <id>https://microcai.org/2021/07/15/byd-dmi-rocks</id>
   <content type="html">&lt;div class=&quot;pre&quot;&gt;
其实我算是后知后觉了，不过还是要推荐一把，毕竟比我后知后觉的人还多着。
&lt;/div&gt;

&lt;h2 id=&quot;油车为什么费油&quot;&gt;油车为什么费油&lt;/h2&gt;

&lt;p&gt;一言以蔽之，发动机高效率（相对高效）区间太窄。发动机是靠燃气的压力推动活塞做功。喷入一定量的燃油，燃烧后产生的气体膨胀速度是有限的，如果活塞下行的速度太快，燃气是来不及推活塞的。而来不及推活塞的废气最后也只能奔着打开的排气阀而去，白白把热能浪费。一定要让燃气使劲的顶着活塞走，非要走到燃气压力低到顶不动了才排气，才能最大化的利用燃气里的热能。
这就要求 1. 活塞下行的速度要和燃气膨胀速度匹配，即不能太快也不能太慢。2. 活塞必须是被燃气强行顶下，而不是自己有个自主的劲在跑. 要达到这2点，就要求：1. 发动机转速一定要维持在一个比较狭小的范围。2. 发动机的负载必须大，要把扭矩充分利用。&lt;/p&gt;

&lt;p&gt;因为扭矩负荷小，转速就得高了才能维持相同的功率，但是燃气即使遇到更低阻力的活塞，也不能膨胀的更快以维持做功不变。于做功会减少，废气会带走更多的能量。&lt;/p&gt;

&lt;p&gt;也就是说，即使转速维持在经济转速上，负荷不够，发动机也无法维持高效率。&lt;/p&gt;

&lt;p&gt;发动机只能维持一个固定的转速，和一个固定的扭矩，才能保持住高效率。离了那个范围效率就会大大降低。&lt;/p&gt;

&lt;p&gt;这个固定的范围，通常是在 2000转上下，扭矩接近发动机最大扭矩。理论上来说，应该就是最大扭矩。但是实际的发动机为了纸面数据的好看，最大扭矩下是通过过量喷油，降低空燃比实现的，所以反而效率低了。理论上最大效率下的扭矩应该是维持最佳空燃比的那个最大扭矩，通常比纸面数据低 10%。&lt;/p&gt;

&lt;p&gt;发动机最大功率通常非常接近最大扭矩 * 最大转速。所以你看一个标称  135kw 的发动机，应该是 6000转的时候以最大扭矩输出。
那么，他最大效率下的功率推测就是  135 * ( 2000/6000) * (1 - 0.1) 大约为 40kw。&lt;/p&gt;

&lt;p&gt;40kw 差不多就是搭载这种发动机的车型在高速上巡航所需的速度。&lt;/p&gt;

&lt;p&gt;这就是油车高速省油的原因。&lt;/p&gt;

&lt;p&gt;但是，在城市里开，就不是这么回事了。首先城市里开速度低，低速下的巡航功率，只有高速需求的 1/4。
所以，低速巡航效率低。&lt;/p&gt;

&lt;p&gt;然后起步，虽然低速巡航需要的功率可能只要 10kw，但是起步的时候，所需功率远大于 100kw。而这时候发动机转速却没有起来，因此是在极不经济的转速上以最大扭矩进行输出。上文说到，最大扭矩通常是以多喷燃油实现的。氧气都不够烧。尾气里含有大量未燃烧充分的燃料（冒黑烟），油耗甚至远高于以更舒适的扭矩下更高的转速进行输出（所以拖档更费油）&lt;/p&gt;

&lt;p&gt;如果变速箱齿比没适配好，还会出现明明是高效功率 40kw 的输出，却因为齿比问题导致发动机不得不以 3500转的速度+更低的扭矩实现低转高扭下相同的功率，也会导致效率低下。&lt;/p&gt;

&lt;p&gt;这也解释了为啥有的车明明发动机效率很高，他实际开起来就是更费油。&lt;/p&gt;

&lt;p&gt;所以，在城市路况下，起步，巡航，都在非经济区间运行。那油耗能低了才见鬼了。&lt;/p&gt;

&lt;h2 id=&quot;电车高速为什么费电&quot;&gt;电车高速为什么费电&lt;/h2&gt;

&lt;p&gt;因为速度越高，风阻越大。移动同样的距离，需要克服阻力做的功越多。而电机基本上什么转速下效率都挺高。于是基本上电车就是越快越费电。越慢越省电。&lt;/p&gt;

&lt;p&gt;至于为啥慢到一定地步了也会费电，那是因为车子即使不动，也会耗电。这部分静态耗电和车速无关，和你车子发动的时间有关。这方面最大的静态损耗就是空调。所以冬天哪怕电池做好温控了续航还是拉跨。&lt;/p&gt;

&lt;h2 id=&quot;混动车为啥省油&quot;&gt;混动车为啥省油&lt;/h2&gt;

&lt;p&gt;混动车，尽量维持发动机在高效范围工作。在需要发动机更大出力的时候，不是靠多喷油解决，而是靠电机辅助扭矩。
在不需要发动机大力气出力的时候，靠发电机强加一定的扭矩负荷，这部分储存的能量，就可以在需要电机辅助的时候释放出来。&lt;/p&gt;

&lt;p&gt;所以混动车省油的关键，就是要有一个巨大的蓄水池。蓄水池越大，能进行错峰调配的能量就越多。&lt;/p&gt;

&lt;p&gt;但是蓄水池大了，他贵啊！要是省的油钱没电池多，就极其不划算了。&lt;/p&gt;

&lt;h2 id=&quot;增程好就是高速更费油&quot;&gt;增程好，就是高速更费油&lt;/h2&gt;

&lt;p&gt;一辆普通轿车，如果是电车，大概率是百公里综合能耗 14度电上下。城市路况（低速但没有频繁刹车）下更可以低到 10度。
但是跑到高速上 120码乃至 130码巡航一百公里，需要的能量就会超过18度电。&lt;/p&gt;

&lt;p&gt;这都是风阻导致的。&lt;/p&gt;

&lt;p&gt;如果是增程车，意味着城市工况下，以 1L油发3度电的效率，能做到百公里电耗3.3L。但是在高速上，就可能超过 6L了。&lt;/p&gt;

&lt;p&gt;可是这种普通轿车，油车在高速上通常仅仅是5L出头的油耗。&lt;/p&gt;

&lt;p&gt;所以增程车在高速上的油耗会超过油车。&lt;/p&gt;

&lt;p&gt;这个原因就是，高速巡航正好进入油车的经济模式。但是增持车有发电再驱动的能量转换，大约会损失 10% 的能量，于是就要多烧 10% 的油。。。 5.5L * 1.1 = 6.05L。&lt;/p&gt;

&lt;p&gt;为了节约这 10% 的能量，可以采取在高速上，由发电机直接驱动车辆。&lt;/p&gt;

&lt;h2 id=&quot;高速直驱的增程车是完美的&quot;&gt;高速直驱的增程车是完美的&lt;/h2&gt;

&lt;p&gt;这里最大的难点在于，首先需要一个增程车。需要电机的功率足够驱动车辆！&lt;/p&gt;

&lt;p&gt;这也是日系败笔。THS 和 IMMD 推出时间都太早，那时候没有体积重量足够小功率足够大的电机和电池。
THS 解决方法是以油为主，IMMD 的解决办法就是直接躺平造肉车。动力垃圾就垃圾。&lt;/p&gt;

&lt;p&gt;IMMD 思路对了，但是等消费者需要性能车的时候，他在有限的成本内堆不出料。最终造的车太贵了。开到报废都省不出油钱。&lt;/p&gt;

&lt;p&gt;THS 思路就错了，发电机为主，驾驶体验被以电为主的吊打。而且电池容量太低，无法有效调峰。&lt;/p&gt;

&lt;p&gt;所以，最佳的思路其实是 F3DM。比亚迪在08年就量产的车。&lt;/p&gt;

&lt;h3 id=&quot;f3dm-为啥失败了&quot;&gt;F3DM 为啥失败了&lt;/h3&gt;

&lt;p&gt;很简单，因为发动机太垃圾。发动机太垃圾，以致于靠电机电池再怎么给发动机续命，油耗都拉跨。
更致命的是，售价也是奇高无比。&lt;/p&gt;

&lt;p&gt;但凡 F3DM 有个40% 热效率的发电机，也不至于输的一败涂地。&lt;/p&gt;

&lt;h2 id=&quot;来了神车来了&quot;&gt;来了，神车来了&lt;/h2&gt;

&lt;p&gt;F3DM 后又过了十来年，这十来年，发生了2件大事，最终 F3DM 成功了。&lt;/p&gt;

&lt;p&gt;第一件事，就是三电成本的下降。使得 F3DM 的成本做到了和油车一致！这是非常关键的因素。&lt;/p&gt;

&lt;p&gt;第二件事，就是比亚迪开发了一个专门为发电而生的发动机。放弃低扭，放弃高转，专注于 2000转nvh比较舒适的转速附近优化燃油效率。增持车一样的舒适性和市区油耗，但是和油车一样的高速油耗。哦不，还略微低了一丢丢。&lt;/p&gt;

&lt;p&gt;脱胎换骨的 F3DM 的动力系统，比亚迪把它命名为 DM-i。&lt;/p&gt;

&lt;p&gt;搭配这套 DM-i 系统的车，售价和同级别合资燃油车一样，但是多了绿牌，多了电车的无敌舒适性（挡换的再好的变速箱，都不如不换挡的电机舒服）。油耗还低了。&lt;/p&gt;

&lt;p&gt;同时结合了油车（便宜续航高）和电车（平顺加速快）的优点，避免了油车（顿挫，提速慢）和电车（贵，续航低）的缺点。&lt;/p&gt;

&lt;p&gt;这不叫神车叫什么？这不颠覆燃油车还有谁能颠覆？&lt;/p&gt;

&lt;h2 id=&quot;还买合资车傻了吧&quot;&gt;还买合资车？傻了吧？&lt;/h2&gt;

&lt;p&gt;目前 DM-i 的产品线是&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;秦Plus-DMi – 轿车&lt;/li&gt;
  &lt;li&gt;汉DMi – 豪华轿车&lt;/li&gt;
  &lt;li&gt;宋Plus-DMi – 普通家用SUV&lt;/li&gt;
  &lt;li&gt;唐-DMi – 豪华装逼SUV&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;有需要的朋友，赶紧去 ssss 交了定金排队。
毕竟这玩意，等你要的时候才买，它就等死你了。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>如何造发动机</title>
   <link href="https://microcai.org/2021/06/17/to-build-engines-part1.html"/>
   <updated>2021-06-17T00:00:00+00:00</updated>
   <id>https://microcai.org/2021/06/17/to-build-engines-part1</id>
   <content type="html">&lt;p&gt;== 热机品种&lt;/p&gt;

&lt;p&gt;最近突然对造发动机起了兴趣，遂研究研究如何造发动机。&lt;/p&gt;

&lt;p&gt;发动机，就是热机，把热能转换成机械能的机器是也。
把热能转换成机械能，谁的热能？气体的热能。&lt;/p&gt;

&lt;p&gt;因为，要在大气层里工作，工质必须得是气体才行。&lt;/p&gt;

&lt;p&gt;提取气体里的能量，有史以来也就只找到了2种方法：活塞和涡轮。&lt;/p&gt;

&lt;p&gt;所以，所有的热机，不外乎往复活塞式，或是涡轮式。&lt;/p&gt;

&lt;p&gt;而工作的气体，如果是直接燃料燃烧后的废气，就是内燃机。如果是通过热交换（俗称烧开水）获得的，就是外燃机了，&lt;/p&gt;

&lt;p&gt;四个象限下的典型热机&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;内燃&lt;/td&gt;
      &lt;td&gt;外燃&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;往复活塞式&lt;/td&gt;
      &lt;td&gt;汽油发动机&lt;/td&gt;
      &lt;td&gt;瓦特蒸汽机&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;涡轮式&lt;/td&gt;
      &lt;td&gt;涡轮发动机&lt;/td&gt;
      &lt;td&gt;蒸汽轮机&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;往复活塞式外燃机，也就是瓦特发明的蒸汽机，已经被淘汰了。
但是烧开的水推动涡轮，一时半会儿还没被淘汰。
外燃机一般使用固体燃料 —— 煤，如果有液态或者气态的燃料，最好的选择还是内燃机。&lt;/p&gt;

&lt;p&gt;内燃机没有体积庞大的换热装置（把废气的能量先转移给水），能减小体积，提高功率密度。&lt;/p&gt;

&lt;p&gt;所以，发动机还是内燃的好。&lt;/p&gt;

&lt;p&gt;那今天先研究研究往复活塞式内燃机。&lt;/p&gt;

&lt;p&gt;== 内燃机循环&lt;/p&gt;

&lt;p&gt;说到内燃机，就必须得有内燃机必备的4个循环：吸气，压缩，膨胀，排气
哪怕是涡轮式的也得有这4个循环。&lt;/p&gt;

&lt;p&gt;为啥非得有这4个循环呢？其实吸气，膨胀，排气都好理解，为啥非得压缩一下呢？&lt;/p&gt;

&lt;p&gt;这就不得不说到热机定律了：&lt;em&gt;理想热机的效率 = 1 - 排气温度/燃气温度。&lt;/em&gt; 理想热机的定义是，理想热机把能用来转换为机械能的内能全部转换为了机械能，剩下的就只有无法转换的废热，绝无可能再从废热里榨取一丁点的能量了。&lt;/p&gt;

&lt;p&gt;为了提高效率，就必须得降低排气温度，提高燃气温度。&lt;/p&gt;

&lt;p&gt;而排气温度受限于热力学第二定律的制约，必须高于环境温度。实际工程实践的时候，都远高于环境温度。连接近都做不到。&lt;/p&gt;

&lt;p&gt;燃料的热值是一定的，意味着燃烧前后的温差是一定的，于是理想热机的效率可以修正为&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;*1 - 排气温度/（燃烧前温度+燃料热值带来的温升）*
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;提高燃气温度，除了使用热值更高的燃料（只有造导弹的人才能自由的换燃料类型，汽油发动机已经定死了，就是汽油，没得换），还有一个办法就是提高燃烧前的温度。&lt;/p&gt;

&lt;p&gt;如果不进行压缩，燃烧前的气体温度，最多也就气温。&lt;/p&gt;

&lt;p&gt;所以压缩就是必须的，这个是提高热机效率的必经之路。&lt;/p&gt;

&lt;p&gt;== 4个冲程&lt;/p&gt;

&lt;p&gt;既然有了4个循环，又设定为是活塞式的内燃机，那内燃机的结构就呼之欲出了。就是一个活塞连在一个偏心轮上。
活塞往复运动完成4个循环，2圈旋转。每2圈完成一个工作流程。&lt;/p&gt;

&lt;p&gt;== 提高效率&lt;/p&gt;

&lt;p&gt;前面的热机效率推理得知，压缩比越高，热机效率越高。&lt;/p&gt;

&lt;p&gt;但是，汽油发动机已经定死了只能烧汽油。只要是汽油，就没法高压缩比。压缩比过高，汽油就会在压缩还未结束的时候就提前燃烧。 也就是说，燃烧前的温度，不能超过汽油的闪点。&lt;/p&gt;

&lt;p&gt;而汽油热值也是固定的，没法改。所以想继续提高效率，就只能从排气温度上想办法。&lt;/p&gt;

&lt;p&gt;降低排气温度，也能提高效率。而降低排气温度，就是要让气体在膨胀阶段多干活。活干的多了，自然温度就低了。&lt;/p&gt;

&lt;p&gt;多膨胀干活，就是提高膨胀比。&lt;/p&gt;

&lt;p&gt;如果活塞还是连在曲轴上往复运动，膨胀比就只能等于压缩比。&lt;/p&gt;

&lt;p&gt;因此，汽油机要提高效率，就是要在压缩比被锁死的情况下，尽量提高膨胀比。&lt;/p&gt;

&lt;p&gt;除非压缩阶段压缩的不是汽油-空气混合气体，而是纯粹的空气，那压缩比倒是不会被锁死。
不过那就成了柴油机烧汽油了。之所以不可行，主要原因就是排放。&lt;/p&gt;

&lt;p&gt;汽油这个东西，空气多了，汽油少了，燃烧就会过于充分导致废气里含有氮氧化物。
空气少了汽油多了，燃烧不充分废气就会含有一氧化碳。两个都是空气污染物。
所以空气和汽油必须按比例精确混合。全油门当然可以用柴油机的技术。问题是怠速的时候呢？非全油门工况呢？
还是吸入那么多空气，油喷的少了，排放可就过不了。所以就只能少吸入空气。
少吸入空气，那压缩比就不够，不仅降低效率，而且压缩后温度不够，最后喷油烧不起来，不是罢工了？&lt;/p&gt;

&lt;p&gt;所以，必须想办法实现一个合适的压缩比+更大的膨胀比。&lt;/p&gt;

&lt;p&gt;=== 进气门晚关&lt;/p&gt;

&lt;p&gt;如果在压缩冲程开始的时候，进气门还开着不关。吸进去的气体就会被活塞还回去。
还的差不多了再关闭进气门开始压缩。这样压缩的行程就只有一部分被用上了。
这就降低了压缩比。就实现了低压缩比和高膨胀比的结合了。&lt;/p&gt;

&lt;p&gt;为何不是进气门早关？&lt;/p&gt;

&lt;p&gt;只考虑改变压缩比这一个策略的话，确实是可以的。但是早关进气门，混合气体就会经历一次真空化的步骤，直到吸气冲程结束，压缩冲程回到关气门的节点。&lt;/p&gt;

&lt;p&gt;这个过程会导致飞轮需要提供巨大的能量给活塞以克服大气压强（除非曲轴箱抽真空，但是这是不可能的）。
这无形中就降低了发动机的效率。想想真空吸尘器为了抽气消耗的能量。&lt;/p&gt;

&lt;p&gt;== 提高燃烧效率&lt;/p&gt;

&lt;p&gt;以上都是介绍从燃气里榨取更多的能量的办法。还有一个提高发动机整体效率的办法，就是别浪费汽油。
尽量让汽油 100% 的燃烧掉，就能再提高效率。&lt;/p&gt;

&lt;p&gt;提高燃烧效率的第一点，就是精确控制混合比例。也就是俗称的空燃比。空气和燃料的比值。
不要让汽油里的碳氢找不到氧对象。为此，就得使用电喷+氧传感器取代化油器。
控制电路根据氧传感器测得的废气氧含量，控制燃油的喷射量，榨干汽油里的每一个碳原子和氢原子。&lt;/p&gt;

&lt;p&gt;混的比例再好，还是会有一些氧气和一些汽油就是不肯结合，就是多出来了。
或者是混合的不够充分，进去的不是混合气，而是空气+汽油雾滴。
或者是燃烧的速度不够快，膨胀完了还有每烧完的。在排气管了继续烧。但是在排气管里烧的，这部分能量并不会传给活塞做功。&lt;/p&gt;

&lt;p&gt;燃烧效率这个东西，和转速息息相关。
因为这个和时间有关。而混合和燃烧，二者都需要时间。
只有恰到好处的时间，才能恰到好处的烧完。&lt;/p&gt;

&lt;p&gt;== 上混动&lt;/p&gt;

&lt;p&gt;如果只有一个特定转速能完成最好的燃烧，那把这个转速下的效率优化到极致，再利用电机和电池实现削峰填谷。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>日本想收快递</title>
   <link href="https://microcai.org/2021/04/26/zone-11-need-df.html"/>
   <updated>2021-04-26T00:00:00+00:00</updated>
   <id>https://microcai.org/2021/04/26/zone-11-need-df</id>
   <content type="html">&lt;p&gt;本子跳的很欢快。日本这样的一个民族，倭人，禽兽也，畏威而不怀德。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/hepin-riben.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;一枚东风携带的核弹头就足够摧毁整个日本了。&lt;/p&gt;

&lt;p&gt;驻日美军是现实的威胁，所以对东风的威胁就可以视而不见。
但是，驻日美军只威胁首相一人。&lt;/p&gt;

&lt;p&gt;宁叫全日本陪葬，毋使我首相下台 乃日本世袭政客的内心写照。&lt;/p&gt;

&lt;p&gt;届时，东风来临，首相可以乘专机逃离。被消灭的，仅仅是被愚弄后，誓死保卫首相的荣华富贵的普通平民而已。&lt;/p&gt;

&lt;p&gt;不过，没了日本人民，政客也失去了利用价值。下场不过是《星球大战》里的分离势力。派一个徒弟过去就尽数消灭了。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>萌王观后感</title>
   <link href="https://microcai.org/2021/04/18/limuru-the-ani.html"/>
   <updated>2021-04-18T00:00:00+00:00</updated>
   <id>https://microcai.org/2021/04/18/limuru-the-ani</id>
   <content type="html">&lt;p&gt;萌王第二季前半部分完结撒花。&lt;/p&gt;

&lt;p&gt;片子，是个好片。萌王一心一意求发展。只为过上好日子。从没有想过要当什么世界霸主。&lt;/p&gt;

&lt;p&gt;萌王对敌人也很仁慈，哪怕战败，也好心接纳他们。把朋友变的多多的，敌人变的少少的。&lt;/p&gt;

&lt;p&gt;奈何你不想当霸主，霸主也视你为威胁。更关键的是，萌王的国家发展，动了西方某大国的奶酪。竞争不过对手，就使出下三滥的手段。&lt;/p&gt;

&lt;p&gt;经历过一番天真导致的惨痛教训后，萌王终于放弃幻想，准备斗争。&lt;/p&gt;

&lt;p&gt;两万马前卒，就是萌王前进路上的祭品。&lt;/p&gt;

&lt;p&gt;这哪里是写的异世界番。。。。。&lt;/p&gt;

&lt;p&gt;所以，伏濑你这是在预言本子的结局嘛？&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>三饷已开征</title>
   <link href="https://microcai.org/2021/04/10/san-xiang.html"/>
   <updated>2021-04-10T00:00:00+00:00</updated>
   <id>https://microcai.org/2021/04/10/san-xiang</id>
   <content type="html">&lt;p&gt;苏联解体后，大明进入了全盛时期。 &lt;img src=&quot;/images/ming-txwd.jpg&quot; class=&quot;inline-img&quot; style=&quot;height: 12em; display:inline-flex; width: auto; &quot; /&gt;
同时，大明也开始进入了衰退期。&lt;/p&gt;

&lt;p&gt;大明的衰退，首先开始于东林党&lt;img src=&quot;/images/donglin-dang.png&quot; class=&quot;inline-img&quot; style=&quot;height: 3em; display:inline-flex; width: auto;&quot; /&gt;和阉党&lt;img src=&quot;/images/yang-dang.png&quot; class=&quot;inline-img&quot; style=&quot;height: 3em; display:inline-flex; width: auto;&quot; /&gt;的斗争。&lt;/p&gt;

&lt;p&gt;当然，最终阉党是斗不过东林党的。东林势力做大后，国内土地兼并愈演愈烈。
但是，瘟疫该来的还是要来。&lt;/p&gt;

&lt;p&gt;对大明来说，消灭瘟疫最有效的手段不是防治，而是天气、屠杀和烈火。坐等百姓的生命被瘟疫蚕食。&lt;/p&gt;

&lt;p&gt;搞的天下人心尽失后，曾经被犁庭扫闾还没消灭干净的后金就开始崛起，不断挑战天朝的权威。对不听话的蛮夷，就得开除朝贡体系&lt;img src=&quot;/images/chaogongtixi.png&quot; class=&quot;inline-img&quot; style=&quot;height: 3em; display:inline-flex; width: auto;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;开除了还活的好好的？那就得给点颜色了。&lt;/p&gt;

&lt;p&gt;可是经过前几任的折腾，国库空虚，发不出饷了。&lt;/p&gt;

&lt;p&gt;那大明就必须得开征辽饷&lt;img src=&quot;/images/liao-xiang.jpeg&quot; class=&quot;inline-img&quot; style=&quot;height: 3em; display:inline-flex; width: auto;&quot; /&gt;
了。&lt;/p&gt;

&lt;p&gt;再耐心点，就可以等到闯王了。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>为cpu废热导风</title>
   <link href="https://microcai.org/2021/04/02/redirect-airflow-in-chassis.html"/>
   <updated>2021-04-02T00:00:00+00:00</updated>
   <id>https://microcai.org/2021/04/02/redirect-airflow-in-chassis</id>
   <content type="html">&lt;p&gt;两年前，我组装第一台 NAS 的时候，对 NAS 的要求就是低功耗。倒不是因为我没钱，花不起电费。而是低功耗 = 低散热需求 = 低噪音。&lt;/p&gt;

&lt;p&gt;毕竟，NAS 是放家里运行的。如果像商品服务器那样，开机约等于飞机起飞，是没法放家里的。
但是功耗大了，就一定有噪音吗？&lt;/p&gt;

&lt;p&gt;最近为公司升级服务器的时候，把3700X 淘汰下来换成了 7302P。EPYC 使用了 4U 高度的散热器后，声音并不大。和普通PC并无区别。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;大部分情况下，使用 PC 平台足够承担服务器的工作。而且成本低非常多。唯一需要注意的是，得把 PC 平台常用的内存换成工作站使用的 Unbuffered ECC 内存。不信各位看看各大云产商的主流虚拟机配置，都是比家用 PC 配置略低的 *&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;实际上，我们也本不需要升级 epyc，只要再配置一台 PC 也足矣。但是因为我们排斥云，坚持使用物理机。排斥机房，坚持将服务器放办公室运行，因此并不希望增加机器的数量增加维护成本。服务器独享一条专线，并不和办公室的网络共享，因此服务器是直连ISP，中间没有使用路由器和交换机。这样可以简化网络的配置。如果使用2台服务器完成工作，就需要增加路由器和交换机了，增加网络设备，就增加了失效点。再三考虑后，决定升级 epyc 而不是用两台 PC *&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这次为公司装服务器的经验，促使我后来将家里的 &lt;a href=&quot;/2021/03/28/epyc-as-3rd-nas.html&quot;&gt;NAS 升级为 EPYC&lt;/a&gt;。原来只要管理好风扇转速，然后设计好风道，epyc 也是可以凉又静的。&lt;/p&gt;

&lt;p&gt;是的，要设计好风道！&lt;/p&gt;

&lt;p&gt;由于服务器上的发热大户就只有 CPU，没有显卡。机箱里是空荡荡的，就一个高耸的散热器塔。装完服务器后我就想，这巨大的塔，把热量都排在机箱里面，太贱了。这就导致机箱必须加装散热器，把废热排出机箱。服务器开机起飞的罪魁祸首就是机箱的暴力风扇。&lt;/p&gt;

&lt;p&gt;为什么 CPU 的废热不能直接排出机箱？非要先排到机箱里面，再利用机箱的风扇和风道排出机箱？&lt;/p&gt;

&lt;p&gt;水冷效果为什么好，不就是因为水冷不会把 cpu 的热量排入机箱么！&lt;/p&gt;

&lt;p&gt;风冷能不能学水冷？&lt;/p&gt;

&lt;p&gt;你肯定会不假思索的说不能。因为风冷的热管是硬的，没法调整散热片的位置，只能待 cpu 上方。热量就必然只能排到机箱里。&lt;/p&gt;

&lt;p&gt;如果给cpu的散热器加个套，让他的废气直通机箱外部呢？那还需要个鬼的机箱风扇！&lt;/p&gt;

&lt;p&gt;bingo！&lt;/p&gt;

&lt;p&gt;想到了这点，说干就干！立马用3D打印机打印了一个散热器罩子。完美契合散热器和机箱的排风孔。机箱的排风口上的风扇就彻底的抛弃不要了。&lt;/p&gt;

&lt;p&gt;少了一个风扇，cpu 的温度非但没有降低，还让主板其他元件再也不受 cpu 的废热之苦。
少了一个风扇，少了一个振动源，噪音大大降低&lt;/p&gt;

&lt;p&gt;但是为什么cpu散热器不自己带个罩子呢？或者机箱不提供这个罩子呢？还非得我自己造。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>epyc deployed</title>
   <link href="https://microcai.org/2021/03/28/epyc-as-3rd-nas.html"/>
   <updated>2021-03-28T00:00:00+00:00</updated>
   <id>https://microcai.org/2021/03/28/epyc-as-3rd-nas</id>
   <content type="html">&lt;p&gt;去年的 &lt;a href=&quot;/2020/12/18/nas-upgraded.html&quot;&gt;这篇文章&lt;/a&gt; 里我受够了嵌入式 ATOM 处理器羸弱的性能而斥巨资购买了 锐龙 5600X 作为新的NAS机器的处理器。然而好景不长，这个新NAS才服役不到4个月，就被我&lt;a href=&quot;/2021-03-07-pcie-shortage-problem.html&quot;&gt;嫌弃了&lt;/a&gt;。在&lt;a href=&quot;/2021/03/07/pcie-shortage-problem.html&quot;&gt;这篇文章&lt;/a&gt; 的结尾，我提到了 &lt;em&gt;EPYC 的精髓在单路&lt;/em&gt; 这句话。
这竟是我抛弃第二台NAS的导火索。&lt;/p&gt;

&lt;p&gt;起初，我购买 NAS 只是为了做一个下载机。开个 Download Station 下下高清的片子。后来我希望它能承载更多的任务，然而 A53 + 512M 的平台，无论如何无法承载更多。于是就有了第一台NAS。原谅我甚至不把那台DS216j看成第一台NAS。&lt;/p&gt;

&lt;p&gt;最初第一台 NAS 没有远见，机器毫无扩展性。甚至初期只用4个盘。但是好歹也有了大容量。也能突破千兆的瓶颈了。
用了一阵子后，受不了 atom 的性能而进行了更新。&lt;/p&gt;

&lt;p&gt;第二台NAS性能非常棒。毕竟 zen3 架构，单核南波万。也正是因为他巨大的性能提升，导致我不只是把它当NAS了。他成了我的私人服务器。不仅仅运行 samba 共享，还运行多媒体服务，还运行 nextcloud 私人备份，当然跑 bt 下载也是少不了的。&lt;/p&gt;

&lt;p&gt;除此之外，他还对我公司的线上数据进行热备。算是一份异地灾备。
然后，还运行了多个区块链的节点 —— 什么比特币，以太坊，波场币，统统都跑一个节点看看。&lt;/p&gt;

&lt;p&gt;这下完犊子了。由于使用的是 ITX 主板，只有2条内存插槽，导致内存最大就被限制到了64G。运行了那么多服务，内存开销自然是小不了。于是时不时的就看到 OOM 崩溃日志。&lt;/p&gt;

&lt;p&gt;加了 SWAP 后算是缓解了OOM。但是 iostat 可以看到 swap 文件长期保持在 300M/s 的读取速度 —— 不用说，就是内存不够了，被大量的 drop 掉，然后又马上访问于是又得 swap in 。&lt;/p&gt;

&lt;p&gt;内存不足，是一件迫在眉睫的事情。我至少需要 128G 的内存！ 换成 matx 的主板固然可以低成本解决问题 —— 但是，128G 要还是不够用了咋办？ 这可是 PC 平台 4条 DIMM 的极限容量了。再增加容量就只能上 64G 单条了。而64G单条又必须是Registered ECC内存，Unbuffered ECC不论如何都只有32G，再大没有了。&lt;/p&gt;

&lt;p&gt;除了内存不足，还有一个窘境是 pcie 通道不足。目前我已经把 5600X 的全部 pcie 通道都利用上了，可是有好几个淘汰下来的 120G 的 NVMe 我也想插上发挥余热！&lt;/p&gt;

&lt;p&gt;一不做二不休，干脆就上 epyc 平台把！7条满血的 PCIe 插槽！8条支持 RECC 的 DIMM！而且这些 pcie 槽还都支持拆分！
意味着买几个廉价的 &lt;img src=&quot;/images/hyper_m2_x4.jpg&quot; class=&quot;inline-img&quot; style=&quot;height: 8em; display:inline-flex; width: auto; &quot; /&gt; 转接卡就能插上4 7 28个 nvme 盘！&lt;/p&gt;

&lt;p&gt;经过2周的采购和等待，最后组装完成了第三台 NAS&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/epyc_nas.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;配置清单为&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt; &lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;主板&lt;/td&gt;
      &lt;td&gt;ASRock EPYCD8&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CPU&lt;/td&gt;
      &lt;td&gt;EPYC 7282 ( 7302P 赶上缺货，改用 7282 )&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;内存&lt;/td&gt;
      &lt;td&gt;三星 RECC DDR4-2933 64G * 4 条&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;散热器&lt;/td&gt;
      &lt;td&gt;普通的 SP3 散热器，暴力风扇拆掉丢垃圾桶，换猫头鹰 NF-A9&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;内存之所以上4条，没有插满8通道，一来是因为 7282 毕竟才区区 16核，用不到8通道的带宽。而来未来可以再买4条 64G 升级成 512G 内存。&lt;/p&gt;

&lt;p&gt;好了，终于可以插上 8个 NVMe 盘了：
&lt;img src=&quot;/images/8nvme.png&quot; /&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>ATX 机箱明明有7个pcie槽位</title>
   <link href="https://microcai.org/2021/03/07/pcie-shortage-problem.html"/>
   <updated>2021-03-07T00:00:00+00:00</updated>
   <id>https://microcai.org/2021/03/07/pcie-shortage-problem</id>
   <content type="html">&lt;p&gt;去年的 &lt;a href=&quot;/2020/12/18/nas-upgraded.html&quot;&gt;这篇文章&lt;/a&gt; 里我受够了嵌入式 ATOM 处理器羸弱的性能而斥巨资购买了 锐龙 5600X 作为新的NAS机器的处理器。
通过 PCIe 拆分卡&lt;img src=&quot;/images/pcie_bifurcation.jpg&quot; class=&quot;inline-img&quot; style=&quot;height: 8em; display:inline-flex; width: auto;&quot; /&gt;实现了 HBA 和 10G 网卡同时接入。把主板的 pcie x16 的显卡槽给利用上了。&lt;/p&gt;

&lt;p&gt;但是，近期发现了一些好东西，就琢磨着给NAS插上。但是想到这些好东西都需要占用主板的 PCIe 槽的时候就犯难了。
锐龙处理器一共就20条可利用的 pcie 通道（还有4条接南桥，不可利用了）。16条给了显卡槽，4条给了第一个 M.2 槽。
&lt;em&gt;PS, intel 的桌面处理器算上南桥的4条也才20条pcie通道。更糟糕&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;给显卡的那条我已经用带拆分的延长器给弄成了2条 x8 的，一条接了 HBA 一条接了网卡。可是，想多接几个 pcie 设备又怎么办？&lt;/p&gt;

&lt;p&gt;&lt;em&gt;PC 平台最大的缺陷是PCIe通道数不足。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;甚至intel就靠卖pcie通道数赚钱。&lt;/p&gt;

&lt;p&gt;明明 ATX 标准设定了7个扩展槽位！而ATX显然是PC的标准，不是什么服务器的标准。&lt;/p&gt;

&lt;p&gt;虽然 ATX 标准是 PCI 时代的产物，但是 PCI 进入 PCIe 时代，主板也应该是把7条PCI槽升级为7条pcie槽。而不是就变成光秃秃的秃驴，就剩下显卡一个槽，剩下的就给 pcie x1 的打发乞丐。而且凑数的 pcie 还是南桥出的，还和 SATA/M.2 有冲突，二选一。&lt;/p&gt;

&lt;p&gt;想到这点，我突然明白了为什么有人说，&lt;em&gt;EPYC 的精髓在单路&lt;/em&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/ROMED8-2T-2(L).jpg&quot; width=&quot;75%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;看这个主板，齐刷刷的7条pcie多漂亮！&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>SFP+ 的下一代，是 SFP112</title>
   <link href="https://microcai.org/2021/01/01/sfp112.html"/>
   <updated>2021-01-01T00:00:00+00:00</updated>
   <id>https://microcai.org/2021/01/01/sfp112</id>
   <content type="html">&lt;p&gt;以太网的标准，通常以10倍来翻倍。10M 100M 1G 10G 100G.
10G 的下一代，应该进化到 100G，可是中间横插入了 40G。这不伦不类的 40G 不仅仅速度不清真，更重要的是，他把以太网带入了歪路。导致了 100G 以太网接口有了3重版本。&lt;/p&gt;

&lt;p&gt;以太网迈入光纤时代，不管是 100M 还是 1G 还是 10G，都使用的一个漂亮的接口，SFP。SFP 接口包含了一对数据链。模块只做电光转换。协议处理由后端负责。&lt;/p&gt;

&lt;p&gt;简单，优雅。&lt;/p&gt;

&lt;p&gt;可是，横插入的 40G 以太网打乱了这个简单优雅。设计了 QSFP 接口。一个 QSFP 其实是4个SFP的简单叠加。重用了供电和其他功能性引脚，但是直接塞入了4对数据链。所以相比4个SFP接口，体积还是稍微小了点。&lt;/p&gt;

&lt;p&gt;但是，这个 QSFP 接口，带来了无穷的混乱。首先就是 4对数据链路导致 QSFP 模块必须插8根光纤，进而让LC SC 这种简单的耦合设计无用武之地。使是有些只有2根光纤的，拿也是在模块内部使用了波分复用，使用了4条激光发送数据。一对光纤一对路的原则被破坏殆尽。我很不喜欢 QSFP。这更导致了 QSFP 没有 bidi 模块（使用一条光纤同时收发的模块）。&lt;/p&gt;

&lt;p&gt;第二个混乱在于，QSFP 被证明用户被强奸后也默默接受了，于是 100G 以太网还要使用4对链路收发。不过谢天谢地没有使用8对链路。但是，标准委员会又引入了不兼容，搞了 QSFP28 接口。&lt;/p&gt;

&lt;p&gt;有 QSFP28 就有 SFP28 了。大小也不一样了，直接物理层不兼容。一下子整出了4个模块接口。SFP SFP28 QSFP QSFP28. 除了 SFP 能塞进 100M 到 16G 之间的各种速度（兼容性大大的好）。。。 其他的都只为以太网的一个特定速度设计。&lt;/p&gt;

&lt;p&gt;这样，10G 并不能平滑的升级，因为不管是 25G 40G 还是 100G，速度，接口都不一样。&lt;/p&gt;

&lt;p&gt;也许是觉得这个局面不够混乱，以太网又整了个50G出来。&lt;/p&gt;

&lt;p&gt;50G 怎么来的呢？ 是200G 的 1/4 版本。&lt;/p&gt;

&lt;p&gt;以太网已经在4对链路的路上开始放飞自我了。 200G 的以太网搞出来，于是他的单链路版本 50G 自然就出来了。&lt;/p&gt;

&lt;p&gt;可是， 50G*2 = 100G 不是吗？&lt;/p&gt;

&lt;p&gt;于是 100G 以太网迎来了2个互不兼容的版本。25*4 版本，和 50*2 版本. 这个 50*2的版本呢，自然就叫 SFP-DD 了。至于为啥不叫 DSFP56 就不知道为啥了。&lt;/p&gt;

&lt;p&gt;但是，200G 相比 100G 只倍数提升，面子挂不住。考虑到 10G 到 40G 已经整了一个 4倍提升，于是 400G 以太网就这么制定出来了。&lt;/p&gt;

&lt;p&gt;也许是觉得，新标准新气象，不能在多链上太过于狂奔，400G 以太网没有使用 50G*8的方式达成，而是使用了 100G*4. 还是熟悉的4对链路。&lt;/p&gt;

&lt;p&gt;这下糟糕了，这 100G*4，当中的每一条，不就是完整的 100G？
于是 100G 以太网又又又迎来第三个不兼容的标准，SFP112.&lt;/p&gt;

&lt;p&gt;不管是原来的 QSFP28 还是后来的 SFP-DD，100G 以太网真正算的上革新的技术，还是 SFP112. 回归原始，使用单链而不是4链组合完成 100G 的速度。这意味着，又可以有 100G 单纤的 bidi 模块可以用了。&lt;/p&gt;

&lt;p&gt;所以， SFP112 才是真正的 100G 以太网。真正的 10G 以太网的继任者。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>升级了NAS机器</title>
   <link href="https://microcai.org/2020/12/18/nas-upgraded.html"/>
   <updated>2020-12-18T00:00:00+00:00</updated>
   <id>https://microcai.org/2020/12/18/nas-upgraded</id>
   <content type="html">&lt;p&gt;上次折腾NAS, 最后搞了8个8T的盘阵列。但是，我愈发的不满意 C3558 的性能。准备替换掉它。但是又觉得C3558花费巨大，没用回本实在不甘心。&lt;/p&gt;

&lt;p&gt;最近几个月，zfs 频繁出现 checkcum error。但是诡异的是，总是 sda sdb sdc sdd 这4个盘出现。&lt;/p&gt;

&lt;p&gt;感觉主板出了问题。或者是机箱的背板出了问题。不论哪个出了问题，都到了让C3558退休的时候了。&lt;/p&gt;

&lt;p&gt;既然要退休，就要想好继任者。继任者在 MIPS （龙芯） ARM64(鲲鹏) 和 x86 里。有缺点都特别明显。&lt;/p&gt;

&lt;p&gt;龙芯&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;优点
    &lt;ul&gt;
      &lt;li&gt;从 bios 到 内核 到 userland 整个都是开源的。方便折腾。&lt;/li&gt;
      &lt;li&gt;bios 可以通过串口访问。取消了 IPMI 的依赖依然可以不接显示器键盘进行配置&lt;/li&gt;
      &lt;li&gt;低功耗
当然，缺点也很大。第一个缺点就是性价比太低。然后性能也跑不满万兆网络。说不定还不如 C3558 呢。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;鲲鹏&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;优点
    &lt;ul&gt;
      &lt;li&gt;arm 架构在 linux 里的支持度不亚于 x86. 比 mips 好太多。&lt;/li&gt;
      &lt;li&gt;性能足够，能跑满万兆&lt;/li&gt;
      &lt;li&gt;进行嵌入式开发的时候可以直接为开发板编译软件，不用在 pc 上配置交叉编译环境。&lt;/li&gt;
      &lt;li&gt;bios 依然可以通过串口访问。无需依赖 IPMI&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;除了性价比太低，么有特别的缺点&lt;/p&gt;

&lt;p&gt;x86 的选择就大了，最终我在3个方案里选择。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;方案1
intel E3 神U + ECC 内存 + SAS HBA + 万兆网卡 + 4U 8盘 380mm深度机箱。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这个是翼王使用的配置。缺点是他是牙膏厂的东西。吃了C3558的亏后不想用牙膏厂的东西，而且14nm的能耗比也差。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;方案2
嵌入式epyc itx 主板 + ECC 内存 + 万兆网卡 + 2U 8盘 450mm深度机箱。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;嵌入式epyc有很多 sata 口，所有只要再加一条万兆网卡就可以了，因此可以使用小板，这样就能使用2U的小机箱了，
机柜能更简洁。缺点是嵌入式 epyc 的主板价格不厚道不说，还不好买。需要等美国进货。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;方案3
amd ryzen cpu + A系列小主板 + ECC 内存 + SAS 卡 + 万兆网卡 + pcie 1分2拆分延长线 + 2U 8盘 450mm深度机箱&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这个方案使用的是 ryzen 系列的 cpu，毕竟 7nm 的 cpu，能耗比是所有方案里最好的。但是这个方案的缺点就是 PC 平台
没有 ipmi。需要调整 bios 或者 内核启动出错的时候，必须依靠显示器。而且 ryzen 并没有核显，需要拔掉 pcie 设备插
上显卡。。。 非常的折腾。但是 pc 平台最大的优势就是性价比突出。正好赶上5600X 上市了，于是没犹豫多少就买了
5600X 和 微星的 A520ITX 主板。&lt;/p&gt;

&lt;p&gt;itx 只有一条pcie x16, 怎么接 sas hba 和 万兆网卡呢？答案是 pcie 拆分。
然后就是烧了好近千大洋，买了多种无 PLX 的 pcie 拆分卡，最后才找到了一个能完美工作的拆分卡。有 PLX 的卡第一缺点钱是贵，第二缺点是卡上的 PLX 本身是发热大户。第三缺点是 PLX 也增加了pcie 的延迟。第四缺点是我 5600X 本身是支持 pcie 拆分的，干嘛不用白不用？ 最终能用的拆分卡如下
&lt;img src=&quot;/images/pcie_bifurcation.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;A520 主板有很多，选微星的一个很重要原因是微星主板上有串口。虽然不是IO挡板提供的，但是主板上有一个 10pin 的 COM header 插座。淘宝上能很容易的买到 ISP转db9 的转接线+pci挡板。而备选的华擎A520则没有 COM 的针脚。这两个主板都是在官网明确说明支持ECC内存的。虽然 ryzen 支持ECC内存，但是也需要主板bios的配合。所有其他牌子没说明支持ECC的就pass了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/isp_to_db9.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;有了串口，虽然pc的 bios 本身不能输出到串口，但是 grub 和 内核能啊！至少可以通过串口操作 grub ，就能编译内核失败的时候在 grub 里换内核。内核能操作串口，就能在需要进入 emergency shell 的时候恢复系统。只是 bios 设置无法调整了。所有插上显卡，配置好参数后，就别再折腾了。&lt;/p&gt;

&lt;p&gt;最后，是机箱。受限于我的机柜，只能使用500mm深度以内的机箱。那种650mm深的正经服务器机箱就不能用了。
一开始 C3558 使用的是 3U 8盘的机箱。这种机箱最大的问题是硬盘散热不足。机箱背面的排气风扇，排出机箱热气的同时，是直接将硬盘下方的进风口的冷空气吸入。硬盘本身的积热没有解决。&lt;/p&gt;

&lt;p&gt;所有我考虑再三，买了2U 的机箱。这种机箱，风扇直接在硬盘后面抽，冷空气要想进入机箱，必须首先经过盘位。从硬盘盒的前方开孔进入盘位，流过硬盘后再被硬盘和主板之间的4个 8cm 风扇抽入主板区域。在这个区域为 cpu 散热后，通过机箱后部的风扇和电源风扇排出。
&lt;img src=&quot;/images/2U_8pan.gif&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这种可以看到，主板只能放 itx 的。但是 pci 挡板位却不止一个（itx只有一个 pcie），显然是要 itx + pcie 拆分卡。（^_^)&lt;/p&gt;

&lt;p&gt;终于，采购齐全，把机器装起来了。&lt;/p&gt;

&lt;p&gt;且慢，为啥我折腾了那么久，花了好几千，只是提升了一下cpu的性能？好像容量也没有增加啊！之前C3558没有使用 sas hba 卡，现在都上了 sas hba 卡了，怎么着也得弄个 sas 背板吧？&lt;/p&gt;

&lt;p&gt;没错，可是带 sas 背板的，都是 650mm 深的大机箱啊！用不了用不了。&lt;/p&gt;

&lt;p&gt;等等，经我耐心的搜索，还真给我找到了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/4U_24pan_500mm.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这个机箱只有 480mm 深，内部依然紧凑，只能使用 itx 主板。但是 pci 挡板位却不止一个（itx只有一个 pcie），显然是要 itx + pcie 拆分卡。（^_^)。&lt;/p&gt;

&lt;p&gt;虽然能上 ATX 电源，但是小空间下放 atx 电源，内部气流容易不畅，so我买了 SFX 电源+ SFX转ATX传接挡板。&lt;/p&gt;

&lt;p&gt;最后，在 jd 突然看到 999 的 2T ssd 。。。。。 还有最后一个 光威Pro NVMe 500G 。。。。&lt;/p&gt;

&lt;p&gt;就这样，花了万把块钱，升级了 NAS。
最终的成品图片如下&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/nas_v2_final.jpg&quot; /&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>读书人最容易出卖灵魂</title>
   <link href="https://microcai.org/2020/07/26/faked-freedom-speech.html"/>
   <updated>2020-07-26T00:00:00+00:00</updated>
   <id>https://microcai.org/2020/07/26/faked-freedom-speech</id>
   <content type="html">&lt;p&gt;咱国家其实对明朝研究的特别少。特少。&lt;/p&gt;

&lt;p&gt;很多人说宋代是文人最好的时代。其实大错特错。 宋代的文人只能写词。明代的文人直接执掌国家。&lt;/p&gt;

&lt;p&gt;很多读书人被明朝皇帝打死，你说明朝读书人有骨气。&lt;/p&gt;

&lt;p&gt;当我真的去研究明朝的那些事的时候，才知道哪里是骨气。分明是利益。&lt;/p&gt;

&lt;p&gt;明朝的时候，税只能向农民收。如果要向商人收税，那是要被读书人批评为“与民争利”的，他们为了阻止皇帝收税，是宁可被打死的。征辽响的时候，他们不会劝皇帝与民休息。征商税的时候，朝廷大员们是要“死谏” 的。&lt;/p&gt;

&lt;p&gt;什么“与民争利”，“藏富于民”， 这里的“民” 一直都是指那些读书人。对读书人不好，还舍不得下杀手的，那一定是暴君。要向读书人收税，那一定是横征暴敛，一定是民不聊生。但是，只要能痛下杀手，读书人的膝盖又是最软的，一定跪下来称你是民心所向，是太平盛世。&lt;/p&gt;

&lt;p&gt;所以平头老百姓啊，千万不要听读书人忽悠。读书人说天下太平的时候，那可能只是说读书人有出路。读书人说民不聊生的时候，那可能只是朝廷不喜欢那些穷酸秀才。&lt;/p&gt;

&lt;p&gt;秦始皇坑几个犬儒，就是暴君了。满清皇帝嘉定三屠，还是圣君。很多时候，百姓是不能发声的。要有个好名声，做好事是没用的，只需要管好读书人的嘴就够了。因为读书人的膝盖是最软的，最容易出卖灵魂。&lt;/p&gt;

&lt;p&gt;&lt;del&gt;读书人里，又以山东的读书人膝盖最软。孔圣人跪过蒙古大汗，也跪过满清皇帝。满清灭亡后，还跟着蒋介石去了台湾。如今跟着绿营认了日本人做父亲。 留在山东的就更没骨气了，要量山东之学子，结黑爹之欢心。&lt;/del&gt;&lt;/p&gt;

&lt;p&gt;要想读书把灵魂卖给你，就需要有天命。有了天命，读书人的膝盖就跪过来了。所谓天命，就是可以让读书人对你进行双标的光环。有了这个天命，入侵他国就不再是入侵，而是吊民伐罪。不论做什么都没事，自有天下的读书人帮你洗。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>国产汽车战略秒棋-tesla上海工厂</title>
   <link href="https://microcai.org/2020/07/15/tesla-and-china-ev.html"/>
   <updated>2020-07-15T00:00:00+00:00</updated>
   <id>https://microcai.org/2020/07/15/tesla-and-china-ev</id>
   <content type="html">&lt;p&gt;汽车这个产业也有上百年的历史了，要想在这个行业崛起，难度非常大。我们国家选择了弯道超车，就是压宝电动车。&lt;/p&gt;

&lt;p&gt;国家发展电动车，最大的困难是什么？&lt;/p&gt;

&lt;p&gt;有人说，是续航短。有人说，是充电麻烦。&lt;/p&gt;

&lt;p&gt;但是，这其实都不是最大的困难。&lt;/p&gt;

&lt;p&gt;最大的困难在人心。&lt;/p&gt;

&lt;p&gt;为什么不买电动车？充电？续航？这都不是真正的考虑。其实，让中国百姓不想买电动车的原因，就只有一个：只能选国产。 国产就是原罪。所有汇集到电动车上的槽点，无非是国产车里挑刺思维的延续。&lt;/p&gt;

&lt;p&gt;任何产品，都有优点和缺点。完美的产品是不存在的。优点能满足你的需要就买，缺点是可以忽略的。&lt;/p&gt;

&lt;p&gt;但是如果你不喜欢一个产品，那优点是看不到的，缺点是可以无限放大的。直到所有缺点都被解决，你还是会说最后一句话气死销售：“我就是不喜欢它啊！“&lt;/p&gt;

&lt;p&gt;放到汽车上，道理是一样的。当车企专注于 fix 电动车的缺点的时候，其实忽略了消费者最简单的心理：他找的缺点都是借口，他只是单纯的不喜欢你而已。&lt;/p&gt;

&lt;p&gt;这个道理，国内一大票造车的都没懂，太平洋彼岸的一个传销头子领悟到了。与其去不断的 fix 电动车的固有缺陷，不如直接无视这些人，把优点发挥出来，让喜欢的人喜欢，让讨厌的人滚蛋。&lt;/p&gt;

&lt;p&gt;这个思维，成就了 tesla。&lt;/p&gt;

&lt;p&gt;那么回到国内，为啥引入 tesla 是国家新能源汽车战略中最妙的一招棋呢？因为中国百姓，他不是不能接受充电慢，不是不能接受续航短。&lt;/p&gt;

&lt;p&gt;他唯一不能接受的是，不能买国产车。&lt;/p&gt;

&lt;p&gt;而电动汽车，是没有洋牌子的。既然没有洋牌子，那就不买。其他的都只是找的借口。&lt;/p&gt;

&lt;p&gt;不买的永远都有不买的借口。真信了你就输了。&lt;/p&gt;

&lt;p&gt;国家终于明白，要推广新能源汽车，就要先治好软骨病。而这个病，最好的解药是以毒攻毒。&lt;/p&gt;

&lt;p&gt;没有洋牌子，就先扶一个洋牌子进来。这个洋牌子，最好是不掌握电动汽车核心技术的。日后打压起来轻松。&lt;/p&gt;

&lt;p&gt;放眼过去，能造好电动车的洋牌子，就在德日美三个国家里找。德国深耕石油车百年，在电车领域已经变成了阿斗。能打的一个都没有。排除了德国，就是日本和美国。&lt;/p&gt;

&lt;p&gt;日本鬼子，虽然电动汽车看起来没啥本事，但是，日本有强大的机电产业！放日本车进来，别看小日本今天没啥电动车，可是人家三电技术哪个没有？ 电池电池造的好，电机电机造的好，电控。。。 那玩意就是功率半导体，也是小日本的强项。&lt;/p&gt;

&lt;p&gt;所以，日本车，因为后劲过于强大，绝对不可引进。不仅仅不能引进，还要极力打压！&lt;/p&gt;

&lt;p&gt;剩下的就是美国了。&lt;/p&gt;

&lt;p&gt;美国的电动车，就和他家的手机一样。只有一个能打的牌子，还特别能打。但是都有致命缺陷：只有营销手段没有核心技术。&lt;/p&gt;

&lt;p&gt;美国的电动车，电机靠台湾，电池靠日本，电控自己虽然有，但是不如小日本，也不如中国的。靠的吹牛皮不死人的营销无敌技术占领的大市场。&lt;/p&gt;

&lt;p&gt;但是，美国的汽车犹如南山必胜客，看起来很强大，但是，是压根上不了美国制裁的实体清单。&lt;/p&gt;

&lt;p&gt;但是，中国的电动车推广，需要的并不是技术，恰恰是美国佬那不要脸到极点的营销技术。用美国的营销手段，把中国的老百姓给洗脑成接受电动车，则国家战略成咦。反正美国佬的车，其实从根上来说，就不讨中国人喜欢。&lt;/p&gt;

&lt;p&gt;等美国佬完成洗脑中国百姓的目的，他就可以被抛弃了。&lt;/p&gt;

&lt;p&gt;为什么上海要求tesla的所有零件实现国产？因为电动车全产业链都在中国。tesla完全可以100%使用中国零件。&lt;/p&gt;

&lt;p&gt;而使用100% 中国零件的 tesla，完成了使命后，只需要把 logo 换成中国姓，改动如此小，百姓接受起来也就容易了。最难的，接受电动车这个，已经由洋大人完成了洗脑。剩下的，无非就是 logo 换成中国字而已。接受起来不要太轻松。&lt;/p&gt;

&lt;p&gt;李斌是第一个吃透了国家战略的人。他把 ES8 卖给了 Tesla 车主当成第二个玩具。轻轻松松就做到了那是duck上市。&lt;/p&gt;

&lt;p&gt;不管 tesla 卖的有多好，最后都是在帮国产电动车培养用户。因为 tesla 的核心技术，其实在中国人的手里。&lt;/p&gt;

&lt;p&gt;千万不能让小日本的电动汽车起来！必须要打压，严厉的打压。美国人不会造电池，他小日本会。美国人不会 cost down ，他小日本会。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>高压钛酸锂电池组完成</title>
   <link href="https://microcai.org/2020/07/11/high-voltage-battery-done.html"/>
   <updated>2020-07-11T00:00:00+00:00</updated>
   <id>https://microcai.org/2020/07/11/high-voltage-battery-done</id>
   <content type="html">&lt;p&gt;大部分交流设备，其实都有一个前置的全桥整流器，把交流输入转换为直流，然后内部再根据需要进行各种变换获得需要的电能。&lt;/p&gt;

&lt;p&gt;这意味着，其实所谓的 AC 输入，倒不如说是支持无极性直流输入。&lt;/p&gt;

&lt;p&gt;既然如此，为啥还要搞逆变器呢？直接把电池输出怼上去就完事了。&lt;/p&gt;

&lt;p&gt;问题是，上哪找220v的电池？&lt;/p&gt;

&lt;p&gt;既然没有，那就自己组一个吧。电池嘛，串起来不就有高压了。&lt;/p&gt;

&lt;p&gt;如果使用的是铅酸电池，倒是这个理。&lt;/p&gt;

&lt;p&gt;可是，铅酸电池我不喜欢啊！要用就要用锂电池，而且得是钛酸锂。一旦用了锂电池，就有问题了，需要 BMS。&lt;/p&gt;

&lt;p&gt;而 BMS 就限制了DIY的时候能串的电池的个数了。首先就是市面上最最最常见的 BMS： 达锂和蚂蚁。最多都只支持到 32节电池。&lt;/p&gt;

&lt;p&gt;32节电池只能实现。。。 2.2V*32 = 70.4v 的电压。&lt;/p&gt;

&lt;p&gt;两个保护板一起串起来呢？咨询了BMS厂商后得知并不能串联。容易烧板子。&lt;/p&gt;

&lt;p&gt;但是新能源汽车倒是串联很多电池。。可见管理几百节串联的 BMS 是有的，只是用的汽车上，DIY 市场上没出。&lt;/p&gt;

&lt;p&gt;这个事情就作罢，一直作罢了几年。直到最近突然给我找到了。&lt;/p&gt;

&lt;p&gt;淘宝上找到了一家做锂电池BMS的，支持最多96节电池串联。支持钛酸锂。他的设计是使用 控制卡 + 采集卡 的分体构造。一个采集卡可以采集 12节电池的信息。 控制卡理论上支持采集卡无限级联，实际上软件只支持到8张采集卡。 12×8 = 96。这就是96节限制的由来。采集卡和控制卡之间使用的 RS485 传递信息。因此采集卡和控制卡不共地，也就是说控制卡不受总电压限制。唯一限制总电压的地方，其实用来 关断电池 的 MOS 管的耐压。然而，他可以使用继电器代替 MOS管。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;小知识： 为什么如果共地会有总电压限制呢？&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;信息通信需要电平。如果采集卡和控制卡使用普通的TTL/RS232协议通信，意味着需要共地。采集卡的 GND 是12串电池的负极，他的 TX 信号对 GND 有 5V。但是只有第一组电池的负是控制卡的 GND，第二组电池的GND是第一组电池的总电压。以 4.2v锂电池为例，第二组电池的逻辑高电平为12*4.2+5=55.4V , 逻辑低电平为12×4.2 = 50.4v ,   到第8组电池，逻辑电平就超过 200V 了。这样，通信的逻辑信号输入端就需要耐受总电池电压。这就是DIY BMS 最多只有32S 的原因。更多的电池需要更高耐压的单片机输入电平转换电路。更糟糕的是，逻辑电平的电压还会随着电池的放电进行迁移。大大增加电平判定难度。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;到了以后发现这个pcb板子不好固定,&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/bms-look.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;所以3D打印了一个卡座&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/bms-shell-3d-print.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;是的，这是第一次使用3D打印，从此爱上了3D打印。。。 后来为了可以不用等好几天，就干脆自己买了3D打印机。。。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;由于我不喜欢继电器，所以还是让他发的 MOS 管版本。耐压 250V。为了安全起见，我决定使用 6×12 = 72S 的模式。总电压是 2.2v * 72 = 158V 。充满电电压为 2.65*72 = 191V.&lt;/p&gt;

&lt;p&gt;这个电压虽然比220v地。但是只要是开关电源设备，一般都支持 90v-240v 宽压输入。换算为 DC 则是 130v-370v 电压。这个电池满电到没电的电压范围正好落在大部分开关电源的输入电压范围。不到 200V 的电压也更安全。&lt;/p&gt;

&lt;p&gt;BMS采购到了，接下来就跑去买了72节拆机钛酸锂电池。买了青棵纸之类的东西。花了好几天的时间组装起来了。一天组装一组12S。呵呵。老费劲了。还得焊接好采集排线。最后把 12片电池和采集模块一起封成一个30V 的电池包。然后6个电池包再串起来。最后接上控制板。&lt;/p&gt;

&lt;p&gt;当然，还有 外壳。用了环氧树脂板，花好图纸，让淘宝的卖家直接 CNC 切割好了发回来。然后组装 起来。&lt;/p&gt;

&lt;p&gt;为了把BMS固定在外壳上，还专门3D打印了一个底座，把 BMS 固定在3D打印好的底座上，再把底座固定到外壳上。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/show-hvdc-lto.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;最后大功告成，上图是只差最后一布组装的样子。比带了外壳更容易 show off 出来这是一个 diy 的电池。&lt;/p&gt;

&lt;p&gt;这个电池当然装好了就吃灰了。因为我不是需要一个这样的电池，我只是单纯的享受设计，采购零部件，然后组装起来的乐趣。&lt;/p&gt;

&lt;p&gt;PS： 下一步，为它折腾一把太阳能充电。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>入手了一个 rock pi 4b</title>
   <link href="https://microcai.org/2020/06/13/bought-rockpi4.html"/>
   <updated>2020-06-13T00:00:00+00:00</updated>
   <id>https://microcai.org/2020/06/13/bought-rockpi4</id>
   <content type="html">&lt;p&gt;闲来无事，又打起了ARM的主意。Rpi3 的性能实在受不太了。毕竟只有 100M 网络。内存也就1GB。想干点啥都不行。寻思着弄个性能好点跟的上时代的 arm 板子玩玩。&lt;/p&gt;

&lt;p&gt;先是找 archlinuxarm 看看有没有啥支持的板子。结果发现都不是啥好板子。后来搜 香蕉派的时候，发现了 rock pi。rock pi 4b 看参数，给力。于是下单了一个 4G 内存的版本。这 rock pi 最吸引我的地方是有一个 M.2 插槽，而且支持从 nvme 启动系统。终于可以丢掉渣 SD 卡了啊。&lt;/p&gt;

&lt;p&gt;于是马上下单买了一个。&lt;/p&gt;

&lt;p&gt;过了几天到了，把官网上提供的 debian 系统给 dd 到 nvme ssd。然后发现无法启动。&lt;/p&gt;

&lt;p&gt;先研究 nvme 启动吧。cpu 并不支持从nvme 直接启动。或者说 cpu 内带的 bootrom 只支持 SPI/sd/emmc 三个启动方式。那么就需要先让 bootrom 载入一个 loader，这个 loader 再从 nvme 载入系统。但是我又不想插 sd卡，于是就需要有 SPI flash。&lt;/p&gt;

&lt;p&gt;然后他这个板子是有焊接上 SPI Flash的，但是却没有烧bootlader进 Flash。&lt;/p&gt;

&lt;p&gt;所以默认还是只能 SD卡启动。于是先把他官方提供的 ubuntu img 给 dd 到 SD 卡然后启动。&lt;/p&gt;

&lt;p&gt;然后按照 wiki 把 spi 版的 uboot 给刷入板子上的 SPI flash。&lt;/p&gt;

&lt;p&gt;于是 nvme 上的 debian 可以在不插 sd卡的情况下启动了。&lt;/p&gt;

&lt;p&gt;但是！我讨厌 debian 啊！&lt;/p&gt;

&lt;p&gt;于是最简单的做法，就是把 rootfs 给删了，把 Archlinux-aarch64-latest.tar.gz 给解压到 rootfs分区。&lt;/p&gt;

&lt;p&gt;然后boom，可以开机进入了 arch。&lt;/p&gt;

&lt;p&gt;but ，内核还是他 4.4 的 debian 内核。4.4 内核太老了！连 io_uring 都么有。这怎么可以，我要用 arch 自己的内核启动！&lt;/p&gt;

&lt;p&gt;于是折腾 boot 好几天，都失败告终。一直黑屏，也不知道问题出在哪里。还自己编译了 uboot 和 内核，都失败告终。就是不能启动。&lt;/p&gt;

&lt;p&gt;最后，最后的最后，我还是低头了，买了一个 USB TTL 线。。。。&lt;/p&gt;

&lt;p&gt;然后果然就看到 内核的错误输出了。。。。 果断修正。。。&lt;/p&gt;

&lt;p&gt;终于在昨天把系统折腾好了。&lt;/p&gt;

&lt;p&gt;还有个小插曲，买的 USB TTL 线居然不支持 1.5M 波特率。。。搞的我只好重新编译 uboot 设定默认波特率为 115200. 然后才能看到 uboot 日志，修正了 extlinux.conf 的写法，然后看 kernel crash 日志，再重新编译内核。&lt;/p&gt;

&lt;p&gt;最后，尝试了一下arch的自带内核，看到了日志，发现原来是他打的 initramfs 没有 nvme.ko …. 手动添加到配置文件里重新打包就解决了。。。&lt;/p&gt;

&lt;p&gt;顺便安利下 Gentoo，交叉编译内核和 uboot 的时候，本以为会很麻烦，结果 crossdev -t aarch64-linux-gnu 一条命令搞定交叉工具链。。。。。&lt;/p&gt;

&lt;p&gt;Gentoo 果然是最适合程序员的操作系统！&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>为家里所有设备一次更新 ipv6 动态域名</title>
   <link href="https://microcai.org/2020/04/27/all-device-ipv6ed-and-ddnsed.html"/>
   <updated>2020-04-27T00:00:00+00:00</updated>
   <id>https://microcai.org/2020/04/27/all-device-ipv6ed-and-ddnsed</id>
   <content type="html">&lt;p&gt;来了！ ipv6 真的来了！ 最近回一趟老家，发现老家的 ipv6 也可以用后， 家，公司，老家1，老家2 四个地方的 ipv6 就全部都能互通了。这样，就没有理由留着 ipv4 了。我甚至觉得搞个公网 ipv4 地址是多么可笑的一件事。&lt;/p&gt;

&lt;p&gt;全ipv6化以后，为了互连方便，我把所有的支持 ipv6 的设备，都配置了动态域名。编译了 dnspodc 还有交叉编译了 dnspodc 到各种设备上。但是，打印机让我头疼了。打印机支持ipv6, 但是不支持运行自己写的程序。&lt;/p&gt;

&lt;p&gt;而且为那么多设备一个一个编译并配置 ddns 更新程序，着实让我有点筋疲力竭。我开始思考，让一个程序自动为所有设备都更新 ddns。&lt;/p&gt;

&lt;p&gt;ipv6 地址，使用的是 prefix + host 的方法构成。不同于
ipv4 时代 prefix 按照 /8 /16 /24 分成了 A B C 三类地址，后来又发明了 CIDR 无分类地址，可以使用任意长度的 prefix， ipv6 时代建议 prefix 永远等于 64 。低于 64 的 prefix 只用于地址分配机构分配地址，还有路由聚合简化路由表的时候用。实际过程中进行地址分配，一个局域网下永远让 prefix=64。&lt;/p&gt;

&lt;p&gt;这么做有个什么好处呢？ 就是 无状态地址自动配置 能发挥作用。而且 64bit 的 prefix 意味着 主机地址可以有 64bit， 也就是说永远不会遇到 主机位不足的情况。 ipv4 时代使用 C 类地址的时候，就容易遇到局域网超过 255 台机器的情况。好不容易划好的网络，增加一台可能导致整个网络重新规划。而 64bit 的 prefix 一劳永逸的解决了这个问题。&lt;/p&gt;

&lt;p&gt;无状态地址自动配置的方法是，由网关每隔一段时间广播一个 RA 包。RA 包里包含了 prefix ， dns，网关 等等信息。主机收到 RA 后， 把 prefix和自己生成的 host 一拼接，就 获得了 全球可达的 ipv6 地址了。&lt;/p&gt;

&lt;p&gt;在 PPPoE 拨号环境下， prefix 是由 dhcp-pd 协议由 ISP 下发给路由器的，所以 prefix 会不断的变化，是动态的，所以需要设置动态域名。但是 host 部分是不会变的。 host 部分是使用 mac 地址经一定规则变换而来。这个规则叫 EUI-64。对于不需要被外部访问的主机来说，可以开启 mac 地址随机化和隐私保护，使用随机的主机地址。当然不想被访问也就不会有动态域名的需求了，所以这里只考虑使用固定的 EUI-64 方法构造的 ipv6 地址。&lt;/p&gt;

&lt;p&gt;这样只要把需要更新动态域名的设备的 mac 地址收集起来，就可以一次性构造出所有设备的 ipv6 地址，不需要在每个设备上单独运行 ddns 程序。&lt;/p&gt;

&lt;p&gt;所以，在运行 ddns 的设备上 （可以是路由器，可以是 nas ，可以是一个 树梅派）获取本地地址，取前64bit，然后依次拼接 eui-64 就可以获得一系列设备的 全局ipv6 地址，然后再调用 dns 供应商的接口更新上去，就完成了。&lt;/p&gt;

&lt;p&gt;于是搞定后，获得了2个优势：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;无法运行自定义程序的设备也能更新dns&lt;/li&gt;
  &lt;li&gt;无需在每个设备上设定dns更新程序&lt;/li&gt;
&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>自由市场思维毁了区块链</title>
   <link href="https://microcai.org/2020/04/20/free-market-also-failed-blockchain.html"/>
   <updated>2020-04-20T00:00:00+00:00</updated>
   <id>https://microcai.org/2020/04/20/free-market-also-failed-blockchain</id>
   <content type="html">&lt;p&gt;西方的自由市场思维很有诱惑力，然而实际上根本行不通。拿 99 碗饭 100 个人来说， 这饭要如何涨价呢？ 无论涨到多少，终会有一个人买不到然后饿死。&lt;/p&gt;

&lt;p&gt;这个时候根本就不能允许自由涨价。很多人说，那是粮食有特殊性，别的物资可以随便涨价吧？
但是，谁来定义所谓的 “特殊商品” 呢？在美国肺炎出现前，也没有人会认为口罩是特殊商品，不能涨价吧。&lt;/p&gt;

&lt;p&gt;一些人可能会说，涨价大家才有动力增产。这又是在说胡话了。这个世界产业大分工下，大部分制造业者早已放弃了制造自己不擅长的东西了。每一个商品的背后，都只是寥寥数个参与的品牌。新进的人已经很难在参一脚了。何况有一些行业还设立了特殊的壁垒。涨价就有新竞争者入局增加供给，只能说理想很美好，现实很骨感。&lt;/p&gt;

&lt;p&gt;而受西方自由市场思维洗脑最深刻的中本聪，也陷入了自由市场的陷阱。他让区块链的交易费用“市场化竞争” 。看起来 “出价最高者，他的交易被打包” 这个游戏规则非常公平而天经地义。但是实际上是落入了陷阱。因为不论你出价多高，比特币的容量是无法增加的。每10分钟只能打包一个区块，每个区块只能有 1MB 字节。1MB字节只能打包大约2000-3000笔交易。这就是打包比特币交易这个商品的供应上限。 在一个有供应力上限的市场里搞自由竞价。结果就只能是所有参与者都被迫无限推高支付的手续费，直到达到“所能承受的手续费上限”，一旦突破承受极限，只能放弃使用比特币网络割肉离场。&lt;/p&gt;

&lt;p&gt;最终的结果就只能是在小圈子里自high.&lt;/p&gt;

&lt;p&gt;大幅降低交易成本，乃是世界趋势。这个最基本的经济原理，中本聪不懂，他就懂了个皮毛的自由市场。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>买不到好笔记本</title>
   <link href="https://microcai.org/2020/04/07/missing-good-notebook.html"/>
   <updated>2020-04-07T00:00:00+00:00</updated>
   <id>https://microcai.org/2020/04/07/missing-good-notebook</id>
   <content type="html">&lt;p&gt;笔记本市场市场竞争非常激烈，从业者都说本子难卖。然而我准备更新我用了4年的老本子的时候，却发现无本可买。&lt;/p&gt;

&lt;p&gt;为啥我要买一个新本子呢？因为不够用了。现有的笔记本内存只有16G, 想想现在的软件个个都是吃内存大户，干活编译代码的时候只能把浏览器关了。 有时候关浏览器还不够， 还得把运维工具也关了才能腾出内存。不然编译器就内存不足崩了。敲着代码还得上爆栈的时候，敲着代码还得 ssh 到服务器上调试的时候， 都非常的抓狂。&lt;/p&gt;

&lt;p&gt;所以我急需要 32G 内存的机器。&lt;/p&gt;

&lt;p&gt;这个本子买过来4年了，几乎就没拿它打过游戏，那闲置的GTX960M显卡一直浪费电源浪费重量浪费散热能力浪费￥。所以我希望下一个笔记本， 一定一定的不要有独显。这样可以更轻更薄更持久。&lt;/p&gt;

&lt;p&gt;微软吃 Surface Book 老本好多年了， 居然吃到后来吃退步了， 本来我打算更新下代的 SB 本，结果大失所望， 新一代的 Surface 居然屏幕分辨率缩水了。所以，为了轻便着想，下一台笔记本必须 4k 起步，最好 5k 8k 分辨率了。但是尺寸不能大， 大了不便携。 14寸是能接受的极限了。不能再大了。&lt;/p&gt;

&lt;p&gt;这3个筛选条件一套，已经是 0 符合要求的了。 何况我还有第四个要求，就是一定要 7nm Zen2 的 cpu 。。。。。&lt;/p&gt;

&lt;p&gt;好了， 直接啥也买不到了。&lt;/p&gt;

&lt;p&gt;多少钱都买不到。&lt;/p&gt;

&lt;p&gt;钱买不到的东西又多了一个，叫 程序员用的笔记本。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>1840 年来百年的屈辱换来的教训</title>
   <link href="https://microcai.org/2020/03/19/learnt-from-1840.html"/>
   <updated>2020-03-19T00:00:00+00:00</updated>
   <id>https://microcai.org/2020/03/19/learnt-from-1840</id>
   <content type="html">&lt;p&gt;1840 年鸦片战争爆发, 到1949年, 中国开始了超过一百年的屈辱. 要说这一百多年的屈辱让这个民族学会了什么的, 那就是一定要工业化. 只有工业化, 才能救中国.&lt;/p&gt;

&lt;p&gt;为什么说只有共产党,才能救中国, 因为纵观1912年到1927年, 中国学习了西方的政治制度, 成立了几百个大大小小的政党, 但是没有救到中国. 归根结底的原因是, 这些政党不是工业党. 不是西方化, 就能救中国, 而是只有工业化, 才能救中国. 而共产党恰恰是唯一的一个工业党, 不仅仅是中国唯一的, 目前也是世界唯一一个工业党.&lt;/p&gt;

&lt;p&gt;苏联大部分国土都在北寒的冻土地带, 只有共产党带去了工业化, 苏联才崛起成为世界一极. 而苏共倒下后的俄罗斯, 沦落为一个资源国, 丧失了工业能力, 再也掀不起浪花.&lt;/p&gt;

&lt;p&gt;为什么说, 共产党是唯一一个工业党. 因为共产主义追求的, 是物质极大丰富, 而物质极大丰富需要追求极高的生产力, 极高的生产力只能靠工业化才能达到. 所以共产党从诞生的那一刻起, 就是一个坚定的工业党.&lt;/p&gt;

&lt;p&gt;而只有坚持发展工业, 才是真正的共产党. 这次抗疫深刻的揭示了一点: 工业部门不计报酬支援前线, 金融业和媒体汉奸辈出.&lt;/p&gt;

&lt;p&gt;而助中国赢得胜利的, 是强大的工业部门.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Mirror Efi Boot Partition</title>
   <link href="https://microcai.org/2019/12/31/mirror-efi-boot-partition.html"/>
   <updated>2019-12-31T00:00:00+00:00</updated>
   <id>https://microcai.org/2019/12/31/mirror-efi-boot-partition</id>
   <content type="html">&lt;p&gt;镜像  EFI boot 分区。&lt;/p&gt;

&lt;p&gt;最近给电脑配了2个 nvme SSD 做 ZFS mirror . 每个盘都分了 2个分区， 一个 EFI system 分区， 一个 ZFS 分区。两个 ZFS 分区放到一个 zfs pool 里做镜像。但是 两个 EFI 分区。。。。怎么 mirror 呢？&lt;/p&gt;

&lt;p&gt;首先尝试了 raid1, 发现 raid1 在盘上建的话， 会破坏 ZFS。如果用分区建 raid1, 则分区类型被修改为 linux raid， 而不是 EFI system partition 了， 主板就不识别了。&lt;/p&gt;

&lt;p&gt;然后尝试的是挂到 boot1  boot2 两个分区， 后台写个脚本 rsync 拷贝同步。但是发现这个解决方案不够优雅。&lt;/p&gt;

&lt;p&gt;如果能建立一个没有 metadata 的 mirror 设备就好了。&lt;/p&gt;

&lt;p&gt;几经周折，发现了 dmsetup 命令， 可以建立不依托 metadata 数据的 mirror 分区。如果没有 metadata，就可以保留原汁原味的分区表，只是在系统层做数据 mirror， 无需主板的任何支持。&lt;/p&gt;

&lt;p&gt;那么就只要用 dmsetup 设置 nvme0n1p1 和 nvme1n1p1 两个分区为 mirror。 然后把  mirror 设备挂到 /boot。&lt;/p&gt;

&lt;p&gt;执行两次  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;efibootmgr -c -d /dev/nvme0n1 -L &quot;Linux Boot Manager&quot; -l \\EFI\\BOOTX64.EFI&lt;/code&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;efibootmgr -c -d /dev/nvme1n1 -L &quot;Linux Boot Manager&quot; -l \\EFI\\BOOTX64.EFI&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;为主板设定两个EFI分区的两个一模一样的引导项。&lt;/p&gt;

&lt;p&gt;为了开机自动挂，又写了 boot.mount&lt;/p&gt;

&lt;div class=&quot;language-ini highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;[Unit]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;Before&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;local-fs.target&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;After&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;mirror-boot.service&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;Requires&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;mirror-boot.service&lt;/span&gt;

&lt;span class=&quot;nn&quot;&gt;[Mount]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;Where&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/boot&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;What&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/dev/mapper/boot-efi-mirror&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;vfat&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;和 mirror-boot.service&lt;/p&gt;

&lt;div class=&quot;language-ini highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;[Unit]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;Description&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Create EFI mirror&lt;/span&gt;

&lt;span class=&quot;nn&quot;&gt;[Service]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;RemainAfterExit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;oneshot&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;ExecStart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/sbin/dmsetup create boot-efi-mirror --table &apos;0 1048576 mirror core 1 1024 2 /dev/nvme0n1p1 0 /dev/nvme1n1p1 0&apos;&lt;/span&gt;

&lt;span class=&quot;nn&quot;&gt;[Install]&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;WantedBy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;basic.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样挂 /boot 的时候会自动启动 mirror-boot.service ，也就是自动调用 dmsetup 把两个 EFI 分区给建好 mirror。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>3950X 再次拯救 Gentoo</title>
   <link href="https://microcai.org/2019/12/27/3950x-save-gentoo-again.html"/>
   <updated>2019-12-27T00:00:00+00:00</updated>
   <id>https://microcai.org/2019/12/27/3950x-save-gentoo-again</id>
   <content type="html">&lt;p&gt;多年前, 我抛弃了 AMD, 从 athlon 64 x2 升级到了 Sandy Bridge 的 Xeon E3-1230. 初代 E3 神U. 写了一篇文章, 说 i7 拯救了 Gentoo .   &lt;em&gt;虽然 E3 从名字上来说不是 i7 就是了, 不过后来我把 1230 升级到了 i7-4790.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;但是, 我从 E3 到 4790 的升级, 其实完全没有感觉到性能进步. 只是心理多了点安慰, 从老平台升级到新平台罢了. 那多 10%不到的性能提升, 实在是感觉不到. 后面的 6700 7700 8700 我都不再升级了.  都没有性能提升. 就守着老死的机器, 编译 Gentoo 越来越力不从心, 随着软件越做越大, 编译器越做越慢, emerge 需要的时间也是越来越大.&lt;/p&gt;

&lt;p&gt;虽然后来有在工作上, 升级使用了 AMD 的 1800X 和 2700X, 不过自个家里的电脑还是老态龙钟的 4790 , 连K都不带.&lt;/p&gt;

&lt;p&gt;因为 4790 用的是 DDR3 内存, 而接下来升级, 不管是 2700X 还是 i 家的 8700k 都要换全套了. 所以就一直耐心的等 3700X 的出现, 等  3700X 一发布我就升级!  结果到了年中的时候, 得知这次新一代U不仅仅是. 3700X 还有 3800X 和 3900X . 于是就等 7月7 的 3900X 了. 结果到六月底的时候, 又得到消息, 9 月发售 3950X !&lt;/p&gt;

&lt;p&gt;那继续等吧! 于是错过了3700X 的车, 就这么等啊等, 到了9月, 得, 不仅仅 3950X 没发售, 其实 9 月的 3900X 还缺货涨价了. 就继续等啊等, 终于… 终于等到12月, 3950X 终于有货了.&lt;/p&gt;

&lt;p&gt;3950X 的运气也好, 正好还清房贷后, 还有多点闲钱, 所以不用再等一个月了,  就立即出手了. 只一天功夫就送到了手上.&lt;/p&gt;

&lt;p&gt;32个框框, 不是8个框框能比的, 64G 内存, 不是区区 16G 内存能比的. 编译 chromium 再也不用担心 OOM 了, 而且编译 chromium 再也… 不用等一个晚上了…..&lt;/p&gt;

&lt;p&gt;3950X 再次拯救了 Gentoo !&lt;/p&gt;

&lt;p&gt;PS, 这个 Gentoo 陪伴我从 速龙 时代到 Sandy Bridge 到 Haswell 由重新回到 Ryzen .  硬件都换过几茬了, 系统内的软件早已迭代更新了不知道多少个版本, 然而, 它还是09年我最初安装它的那个样子, 即使原来的硬件被咸鱼大佬收过去, 再组回来也不是我的那台, 我的电脑一直就陪伴我十年了.  特修斯之船问题解决!&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>升级到4盘 raidz</title>
   <link href="https://microcai.org/2019/08/23/upgrade-to-raidz.html"/>
   <updated>2019-08-23T00:00:00+00:00</updated>
   <id>https://microcai.org/2019/08/23/upgrade-to-raidz</id>
   <content type="html">&lt;p&gt;上次折腾NAS, 使用了2个8T的盘建的 raid1. 速度不太满意. raid0 当然怕挂一个盘数据就没了, 索性就再买2个8T的, 组建4盘位raid5.&lt;/p&gt;

&lt;p&gt;这阵列升级啊, 一般的做法是, 先建 raid5 新阵列, 然后把数据拷贝过去, 然后… 等等, 2个盘建什么 raid5. 那先把 raid1 取消镜像吧. 那也只有3个盘啊! 三个盘是能 raid5  问题是拷贝完了多一个盘, 而且我的目的是4盘 raid5.&lt;/p&gt;

&lt;p&gt;咋办?&lt;/p&gt;

&lt;p&gt;简单, 首先确实是需要取消镜像,  然后把剩下的3个盘, 和一个 假设备 一起建一个 4 盘的 raid5, 然后在数据拷贝进去之前, 把假设备从阵列里下线. 然后把数据从旧盘上拷贝到新阵列.&lt;/p&gt;

&lt;p&gt;最后完成拷贝后, 把旧盘替换到阵列里取代下线的假设备, 然后等待 raid5 重建完成. 即可大功告成.&lt;/p&gt;

&lt;p&gt;假冒设备的制作方法是&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#dd bs=1 count=1 if=/dev/zero of=/fakedisk.img seek=8T&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;接着取消 sda sdb 的镜像&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#zpool detach pool1 sda&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后创建新池并立即下线假盘&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#zpool create pool2 raidz sda /fakedisk.img sdc sdd &lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#zpool offline pool2 /fakedisk.img&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#rm /fakedisk.img&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;接下来就是 zfs send 和 zfs recv 迁移数据&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#zfs send pool1 | zfs recv pool2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;漫长的拷贝完成后&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#zpool destroy pool1&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#zpool replace pool2 /fakedisk.img sdb&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后又是漫长的 raid5 重建的过程.&lt;/p&gt;

&lt;p&gt;当然, pool2 盘已经可用了;) 重建的时候只是性能些许下降.&lt;/p&gt;

&lt;p&gt;/fakedisk.img 文件因为是稀疏文件, 虽然文件大小达到了 8T, 但是实际上并不占用磁盘空间.&lt;/p&gt;

&lt;p&gt;创建完 池后, 大小也是非常小的, 但是如果这个时候开始拷贝数据, 那么这个 fakedisk.img 文件占用的磁盘空间会&lt;/p&gt;

&lt;p&gt;慢慢变大, 就把 NAS 的 根分区(在U盘上)撑爆了. 所以要在考试拷贝数据前, 将它强制下线并删除.&lt;/p&gt;

&lt;p&gt;这个时候 pool2 就是3盘有效的降级状态. 放心, 这个过程中盘挂了 数据还是有2份, 一份在老盘里, 一份在阵列里.&lt;/p&gt;

&lt;p&gt;最危险的时候, 其实是在最后重建raid5 的时候, 这时候挂了一个盘, 数据就没了 (笑)&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>sfp 形态 gpon 光猫失败</title>
   <link href="https://microcai.org/2019/07/30/gpon-sfp-onu-failed.html"/>
   <updated>2019-07-30T00:00:00+00:00</updated>
   <id>https://microcai.org/2019/07/30/gpon-sfp-onu-failed</id>
   <content type="html">&lt;p&gt;路由器上有一个 sfp 口,  有 sfp 模块形式的 gpon 光猫, 如果能用的话, 机柜里就少一个设备, 还少占用一个插座…少接条网线…..&lt;/p&gt;

&lt;p&gt;最近听网友说 TW2362H 这个猫便宜, sfp 模块的. 于是买了一个, 插入路由器. 过了一会启动完毕后就识别出来了.&lt;/p&gt;

&lt;p&gt;配置下路由器的网口 ip 地址, 然后 192.168.1.1 果然能进猫的页面.&lt;/p&gt;

&lt;p&gt;然而, 这简陋的界面犯愁了. 无从配置, 有个能填写 LOID 的页面, 完了.&lt;/p&gt;

&lt;p&gt;然后我才想起来, 我没有记录原来的猫的 LOID …&lt;/p&gt;

&lt;p&gt;于是进入原来的猫的界面, 居然没找到能填 LOID 的地方… 才发现, 这个猫没准是用 SN 认证的.&lt;/p&gt;

&lt;p&gt;然而这个猫的 web 并不能改 SN …  更糟糕的是, 这个猫连查看 光信号功率的地方都没有. 都开始怀疑是不是gpon猫了.&lt;/p&gt;

&lt;p&gt;后来在俄国人的论坛上找了个固件. 刷进去后, 页面回来了. 倒是能看光功率了. 然后又学会了 telnet 进去用命令行强改 SN.&lt;/p&gt;

&lt;p&gt;最后在命令行下查看链路状态, 已经进入 O5 operational 状态了, 但是, 怎么拨号都无法成功.  pppoe 始终是无响应状态.&lt;/p&gt;

&lt;p&gt;折腾了各种方式最后都无法成功. 失败告终.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>准备上 3950x</title>
   <link href="https://microcai.org/2019/07/16/prepare-3950x.html"/>
   <updated>2019-07-16T00:00:00+00:00</updated>
   <id>https://microcai.org/2019/07/16/prepare-3950x</id>
   <content type="html">&lt;p&gt;听闻秒天秒低秒 i9 的 3950x 要在9月份发售了, 陪伴我多年的 i7-4790 终于可以退役了. 本来是打算9月进行退役的, 但是亲戚家有孩子今年开学需要用电脑了, 于是我就把电脑送给她了. 这样, 在9 月份到来前, 我就需要准备好一台备用电脑, 一台能随时升级到 3950x 的电脑.&lt;/p&gt;

&lt;p&gt;主板呢, 肯定是选 X570 芯片组的, 这样日后只需要换 cpu 即可. 选主板花了不少精力, 因为我不想要 SLI , 不要花冤枉钱给 sli 税. 然后没 sli 意味着显卡插槽是实打实的 x16 而不会一不小心变成 x8 或 x4 . 然后主板上的其他 pcie 都乖乖接 pch 上 不会像某个傻逼主板, 一共5 条 pcie 居然 4条是接 cpu 上的, 要 x4+x4+x4+x4 模式跑 sli 四卡交火. 剩下一条还是 x1 的.  神经病啊, x4 的话显卡性能还能发挥吗? 然后要是加别的扩展卡不是显卡就一定不能  x16 了么.&lt;/p&gt;

&lt;p&gt;然后选了好久敲定了几个符合要求的, 最后7月7号晚上9点发售的时候…. 一切的选择都是徒劳的, 直接买了最便宜的首发主板 😂  . 也就是 MSI 的 X570-A PRO&lt;/p&gt;

&lt;p&gt;然后是内存, 因为 3950x 是 16核32线程的 cpu , 为了能发挥 cpu 的威力, 编译的时候 make -j32 是一定要的. 这样内存小了就会非常容易 OOM, 发挥不出 32 个框框的实力.  所以选择了买4条 16G 的内存.&lt;/p&gt;

&lt;p&gt;找了半天, 16G 的内存只有 3200Mhz 的, 3600 乃至 4000 都只有 8G 单条. 无奈只好接受 3200 的条子了.&lt;/p&gt;

&lt;p&gt;SSD 呢, 618 的时候就屯了一条国产ssd. 512G 才四百一.&lt;/p&gt;

&lt;p&gt;最后是 cpu 上栽了跟头.&lt;/p&gt;

&lt;p&gt;因为考虑到9月份才上 3950x 所以就买了个 APU, 没错, 賊便宜的 ryzen 3 2200G. 才四百多.&lt;/p&gt;

&lt;p&gt;然后就翻车了.&lt;/p&gt;

&lt;p&gt;无法开机, 内存,主板, cpu , 哪个都可能出问题, 非常的蛋疼, 无法确定到底谁坏了. 插拔插拔插拔插拔插拔无数次都失败告终.&lt;/p&gt;

&lt;p&gt;最后去 msi 官网看看, 发现.. 支持的 cpu 列表里, 没有 2200G …..&lt;/p&gt;

&lt;p&gt;这翻车翻的是猝不及防….&lt;/p&gt;

&lt;p&gt;最后买了 2500X 过来. 然后因为没有核显, 只能再买个显卡, 买了个最便宜的 RX580 … 五百多, 便宜吧.&lt;/p&gt;

&lt;p&gt;然后这个卡又翻车了, 花屏…. 又退回去换了一个新的过来.&lt;/p&gt;

&lt;p&gt;真是折腾, 终于在上周日晚上的时候, 全部折腾好了. 能开机了.&lt;/p&gt;

&lt;p&gt;此生第一次用 AMD 的显卡啊! 又要重新编译内核, 重新编译驱动 各种编译, 删 nvidia , 删 cuda …&lt;/p&gt;

&lt;p&gt;最后驱动安装完毕, KDE 重新进去的时候, 发现了一个惊人的事实, AMD 的显卡延迟比 nvidia 的低!&lt;/p&gt;

&lt;p&gt;这是怎么一回事呢?&lt;/p&gt;

&lt;p&gt;原来事情是在  glXSwapbuffers 的实现上. nvidia 的实现策略, 导致 kwin 最终呈现在显示器上的图像是4帧前的世界.  app 调用 swapbuffer 刷新界面后, 本来就引入了2帧的延迟了, 而 kwin 获得新的窗口内容后 , 合成到屏幕上, 再次使用 swapbuffer 刷新, 还要再引入2帧延迟, 结果就是总4帧的延迟. 禁用 compositor 后延迟就缩短到2帧.&lt;/p&gt;

&lt;p&gt;而 AMD 的驱动, 开源的, 没有这个迟来迟去的 bug. 其实 nvidia 根本不认为是 bug 他认为这是让显卡帧率提高的 feature. 诶.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>NAS 换 Gentoo</title>
   <link href="https://microcai.org/2019/05/29/goback-to-gentoo.html"/>
   <updated>2019-05-29T00:00:00+00:00</updated>
   <id>https://microcai.org/2019/05/29/goback-to-gentoo</id>
   <content type="html">&lt;p&gt;freenas 使用了一阵子，发现了几个严重的问题&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;软件严重缺乏&lt;/li&gt;
  &lt;li&gt;插件需要使用 jails 而 jails 的网络居然不能使用宿主网络必须桥接&lt;/li&gt;
  &lt;li&gt;桥接导致 mlx 驱动禁用网卡 tcp offload engine&lt;/li&gt;
  &lt;li&gt;不支持 RDMA，atom cpu 性能弱，无法跑满万兆带宽。&lt;/li&gt;
  &lt;li&gt;实际上很少使用 web 管理界面，多数情况下还是 ssh 上去直接敲命令&lt;/li&gt;
  &lt;li&gt;性能差&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;要想性能好，Gentoo 少不了。于是计划 nas 改用 Gentoo。首先要做的事情就是确保 Gentoo 可以使用 ZFS。&lt;/p&gt;

&lt;p&gt;这个呢，我就先在自己机器上折腾了。首先重新编译内核，启用 zfs，安装好 zfs 工具，接着根分区的数据先备份，然后格式化为 zfs。然后拷贝回来。重启。完美启用 zfs。&lt;/p&gt;

&lt;p&gt;具体的迁移步骤为：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;首先进行规划，我的 nvme 盘有2个分区， nvme0n1p1 500M 为 EFI 分区。nvme0n1p2 117G 为 ext4 挂到 / 。首先使用 e2fsresize 命令缩小分区，然后在空闲的地方创建 nvme0n1p3 分区。 50G. 由于 liveusb 系统不支持 zfs，因此做完这些步骤后重启回原来的系统。&lt;/li&gt;
  &lt;li&gt;zpool create -o mountpoint=none -R /newroot Gentoo nvme0n1p3 命令创建一个池。&lt;/li&gt;
  &lt;li&gt;zfs create Gentoo/ROOT&lt;/li&gt;
  &lt;li&gt;zfs set mountpoint=/ Gentoo/ROOT&lt;/li&gt;
  &lt;li&gt;rsync -xav / /newroot&lt;/li&gt;
  &lt;li&gt;完毕后重启，设定 root=ZFS=Gentoo/ROOT&lt;/li&gt;
  &lt;li&gt;接着 zpool attach Gentoo nvme0n1p3 nvme0n1p2 命令，将原先的 ext4 所在分区直接以 mirror 模式加入 Gentoo 池。&lt;/li&gt;
  &lt;li&gt;等待 resilver 完成&lt;/li&gt;
  &lt;li&gt;zpool detach Gentoo nvme0n1p3 把 nvme0n1p3 这个分区分离出 Gentoo 池。&lt;/li&gt;
  &lt;li&gt;fdisk 重新调整分区，把剩余的空间重新划给 nvme0n1p2&lt;/li&gt;
  &lt;li&gt;zpool attache -e Gentoo nvme0n1p2 执行完毕后， Gentoo 池的大小就占满 nvme0n1p2 分区的大小了&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这样 Gentoo 池就是 117G 大小了。ext4 无损切换为了 zfs。&lt;/p&gt;

&lt;p&gt;在确认 Gentoo 可以支持 zfs 格式后，就开始了 nas 的重装计划。&lt;/p&gt;

&lt;p&gt;实际上， freenas 是安装到 U 盘的。因此只要再拿一个 U 盘装个 Gentoo 然后换个 U 盘重启 nas 机器即可。而不是在 nas 上搞编译装系统，导致nas过长时间的停机。&lt;/p&gt;

&lt;p&gt;虽然最后把 U 盘放 nas 上启动的时候遇到了问题，主要是 编译优化的问题，nas 的 cpu 不支持一些指令集。而我编译安装新的 nas Gentoo 的时候编译参数没有设定好。然而 freenas 系统没有 lscpu，因此最后搞清楚 c3558 有啥 cpu feature 是费了不少功夫。&lt;/p&gt;

&lt;p&gt;成功的在 nas 上启动 Gentoo 后， zpool import 导入 freenas 下建的池成功，就进入了比较折腾的 配置 nfs 和 samba 的步骤了。。。 没有了 webui 还确实是麻烦了不少。&lt;/p&gt;

&lt;p&gt;好在实际上这些配置只需要进行一次。并没有频繁的修改共享目录的问题。&lt;/p&gt;

&lt;p&gt;配置完成后， 我的 PC 上就没看到 nfs server no response.. 消息了。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>根分区 ZFS 化</title>
   <link href="https://microcai.org/2019/04/27/root-on-zfs.html"/>
   <updated>2019-04-27T00:00:00+00:00</updated>
   <id>https://microcai.org/2019/04/27/root-on-zfs</id>
   <content type="html">&lt;p&gt;自从用 freenas 接触到了 ZFS, 我就爱上了 ZFS , 变得愈发不可收拾.  50买了2个 320G 的二手盘放公司玩 raidz.&lt;/p&gt;

&lt;p&gt;只是拿 zfs 挂数据的话, 只要编译好 zfs 内核模块和 zfs 命令行工具即可. 但是,如果要 zfs 当 root 文件系统的话, 则免不了一番折腾.&lt;/p&gt;

&lt;p&gt;为啥呢? 因为 zfs 不同于 legacy 文件系统, 他是集卷管理于一身的.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>无盘PC</title>
   <link href="https://microcai.org/2019/04/26/go-diskless.html"/>
   <updated>2019-04-26T00:00:00+00:00</updated>
   <id>https://microcai.org/2019/04/26/go-diskless</id>
   <content type="html">&lt;p&gt;自从上了万兆，把PC上的硬盘拆到nas上，让PC变成无盘的计划就开始执行了。真正的无盘需要 PXE，然而，同时也意味着现有的 NVMe SSD 成了摆设，因为上文提到的，我的 nas 因为主板关系， pcie 通道不足，无法插 M.2 了。所以现有的 ssd 还是继续在主板上服役。M2 只是一个非常小的接口， ssd 插上主板后和主板融为一体了，所以，姑且就算是主板自带的存储好了，如果只有 nvme 那也可以算无盘。&lt;/p&gt;

&lt;p&gt;既然保留了 nvme，那么无盘的工作就简单了，只需要将个人文件  aka &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/home&lt;/code&gt; 目录放到 nas 上通过网络访问即可。由于在完成拷贝之前我就已经迫不及待的把硬盘插上 NAS 了，因此最初的无盘，我使用的 iSCSI。&lt;/p&gt;

&lt;p&gt;将 pc 原本的 4T 硬盘通过 iscsi 共享给 pc 自己。连 fstab 文件都不需要修改，因为 iscsi 连上去的磁盘居然还是 /dev/sda 。就好像只是换了一个 sata 口插一样的方便。&lt;/p&gt;

&lt;p&gt;但是， iscsi 只是 sata 线的替代品。我真正的目的，是要让我的数据全部由 ZFS 保管。只有 ZFS 才能让我感到安心。&lt;/p&gt;

&lt;p&gt;因此， 8T 氦气填充盘到了后，我就开始了乾坤大挪移。rsync 拷贝了数T的数据，把 旧硬盘上的数据全部转移到了 8T盘上的 ZFS。&lt;/p&gt;

&lt;p&gt;然后格式化旧盘，将旧盘作为 mirror 和 8T 的盘组成 raid1。&lt;/p&gt;

&lt;p&gt;4T 和 8T 怎么组 raid1？&lt;/p&gt;

&lt;p&gt;哈，我在 8T 盘上分了一个 4T 的分区，所以是 4T + 4T 的 mirror ZFS。剩下的 4T 则直接是 普通的 ZFS 用于存放不需要保护的数据 aka 下载的电影和小姐姐。&lt;/p&gt;

&lt;p&gt;然后 4T 的分区就作为 /home 以 NFSv4 协议挂载了。&lt;/p&gt;

&lt;p&gt;遇到的第一个问题，就是发现开机非常非常的慢。发现是 NFS 在等待 networkmanager-wait-online 激活花了好久时间，于是把 ip 地址静态化，省去 dhcp。&lt;/p&gt;

&lt;p&gt;还是慢，nfs4 是以 tcp 模式工作的，可能 tcp 不够好吧，于是降级为 nfsv3 换成 udp。&lt;/p&gt;

&lt;p&gt;没软用。&lt;/p&gt;

&lt;p&gt;接着把 MTU 调整到  9000. 发现启动的时候要从 /home 读取大量的数据。MTU 改大后能显著的减小数据包的数量，降低 nfsd 的 cpu 使用。&lt;/p&gt;

&lt;p&gt;速度确实非常的有效，快了不少。&lt;/p&gt;

&lt;p&gt;然而，还是够快。NFS 缺乏prefetch功能，因此肯定不如系统自己挂载文件系统快。但是缺乏 prefetch 只是开机速度慢了点，后面还是不慢的。。。。 错！&lt;/p&gt;

&lt;p&gt;在 bash 里， ～ 目录下，执行简单的 ls 命令都需要卡上数秒。第二次执行 ls 就瞬间完成了。然后继续 ls ls 都是瞬间完成。但是，只要停个几秒， 再执行 ls ，就要再次卡上数秒。&lt;/p&gt;

&lt;p&gt;打开浏览器什么的也是， 每数秒浏览器就要假死一下。&lt;/p&gt;

&lt;p&gt;这个现象的原因就很简单了，原因是 NFS 本地缓存问题。默认 NFS 的本地缓存只有 3秒。 3秒过后，重新到 NFS Server 拉文件列表了，连 ls 都会因此卡上一会会。因为我的 /home 文件夹下有百万计的文件。。。除了我自己的大量 git 仓库带来的大量小文件，还有 KDE 的大量 ～/.cache 文件。&lt;/p&gt;

&lt;p&gt;最终通过将 nfs 的缓存加大到一整天解决， ls 也再没卡过，即使第一次执行也不卡了，因为 ls 执行需要的文件列表早就在系统启动的时候载入了。&lt;/p&gt;

&lt;p&gt;最后本地访问 /home 的读写速度测试后发现在 400MB/s 上下，很明显 nfs 成了瓶颈。因为本地读写实际上是通过 nfs 读写 nas 的内存。虽然本机 drop 了 cache ，但 nas 并没有嘛。我想到了我的网卡只支持 tcp offload ，不支持 udp offload 。。。。 确实我测试读写的时候， nas 上 nfsd 的 cpu 使用飙升到超过 70% 。。。&lt;/p&gt;

&lt;p&gt;于是再次回到 nfsv4 + tcp。这下速度超过了 700MB/s， 而 nfsd 的 cpu 使用也只有到了 50% 了。&lt;/p&gt;

&lt;p&gt;暂时就这样了。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>NAS 的 pcie 短缺问题</title>
   <link href="https://microcai.org/2019/04/23/intel-pcie-shortage.html"/>
   <updated>2019-04-23T00:00:00+00:00</updated>
   <id>https://microcai.org/2019/04/23/intel-pcie-shortage</id>
   <content type="html">&lt;p&gt;万兆网卡到货后，迫不及待的插上主板开机。&lt;/p&gt;

&lt;p&gt;当时就注意到了一个问题，怎么有一个 SATA 控制器变成了 2port controller 了。也就是说只有6个 sata 口了。&lt;/p&gt;

&lt;p&gt;然后和 pc 开始 iperf 测速。发现速度居然只有刚刚超过 2Gbps！！！
什么鬼？&lt;/p&gt;

&lt;p&gt;然后把 pc 上的那个卡换了个插槽，换到 pcie x16 的显卡插槽上了。原来是我这个卡放到了最后一个 pcie 2.0 x2 的插槽上（物理形态是 x16)&lt;/p&gt;

&lt;p&gt;再测，发现速度也是在  4Gbps 上下。然后进 nas 的 bios 发现， 我去！坑死我了。&lt;/p&gt;

&lt;p&gt;原来这个主板的 pcie 和 sata 是2选1通道的。 配置模式是 pcie x4 + sata x4, pcie x2 + sata x6, 或者 sata x8 但是禁用 pcie .&lt;/p&gt;

&lt;p&gt;我去，原来我只能折腾4盘位nas！ 一开始是 sata x8 但是只要 pcie 上插了卡，默认就是 pcie x2 + sata x6 模式。手动调节为 pcie x4 + sata x4 模式 后进系统， 果然达到了 9.4Gbps 的速度，然后改了 MTU 到 9000 就变成了 9.8Gbps 的速度了。&lt;/p&gt;

&lt;p&gt;被这个主板坑死了。居然只能4盘位！！！！ 那么问题来了，群灰 DS1918+ 为啥不提供万兆，就是这个原因！诶！&lt;/p&gt;

&lt;p&gt;等等，这个主板还有个 NVME 的插槽，这个好像是独享的带宽，没有和别的插槽共享。。。。&lt;/p&gt;

&lt;p&gt;于是买了这个玩意&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/m2_to_sata.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;最终还是达成了 8 盘位 + 万兆。&lt;/p&gt;

&lt;p&gt;当然，目前只插了2个盘。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>10Gbps 家庭云存储</title>
   <link href="https://microcai.org/2019/04/22/10GbE-NAS.html"/>
   <updated>2019-04-22T00:00:00+00:00</updated>
   <id>https://microcai.org/2019/04/22/10GbE-NAS</id>
   <content type="html">&lt;p&gt;在万兆内网达成后， 终于到了最重要的环节了，就是搭建一台支持万兆网络访问的 NAS。&lt;/p&gt;

&lt;p&gt;首先问自己一个最重要的问题：我家里有矿吗？&lt;/p&gt;

&lt;p&gt;显然，没有，所以，一切都要以够用为目标，而不追求高大上。&lt;/p&gt;

&lt;p&gt;所以，显然这次万兆够用为主，量力而行。&lt;/p&gt;

&lt;p&gt;第一个选择就是主板。主板我花了超过一周的时间去寻找，对比各种方案。&lt;/p&gt;

&lt;p&gt;当时的方案有3个：&lt;/p&gt;

&lt;p&gt;方案1：集成 Atom C3558 的主板。有ASRock和超微两个牌子都有提供。&lt;/p&gt;

&lt;p&gt;方案2：集成 Xeon D2123 的主板。&lt;/p&gt;

&lt;p&gt;方案3：使用 Ryzen 200GE 和 B450 主板。&lt;/p&gt;

&lt;p&gt;方案1 是因为主板提供了 pcie x4 的插槽，并且有 8 个 SATA 口（后来发现是个坑）
方案2 是以为主板已经集成了万兆网卡。
方案3 是因为毕竟是正常的桌面PC（虽然是低端的）有充足的扩展性。&lt;/p&gt;

&lt;p&gt;最后方案3被 PASS，因为发现需要 ECC 内存。&lt;/p&gt;

&lt;p&gt;方案2 因为主板价格超过五千被 pass。&lt;/p&gt;

&lt;p&gt;然后是选定方案1, Atom C3558。&lt;/p&gt;

&lt;p&gt;选方案1 是因为发现群辉8盘位的 DS1819+ 也是 Atom C3538 的方案。比 3558 还稍微渣了一丁丁呢。 而 DS1819+ 售价超过 8000 呢。&lt;/p&gt;

&lt;p&gt;虽然犹豫了一阵子，不过最后买了超威的 A2SDi-4C-HLN4F 这个款的主板，集成 C3558 cpu。CPU 被动散热，TDP 25w，花了 2333.&lt;/p&gt;

&lt;p&gt;然后就是最糟糕的，选机箱上了。&lt;/p&gt;

&lt;p&gt;因为家里有个小机柜，所以希望买个2U 高度的8个热插拔盘位的机箱。结果发现了一个严重的问题， 2U 高度的机箱，要么4盘位短机箱，要么8盘位长机箱。而长机箱是放不进去我的小机柜的。无奈买了个3U高度的。8个热插拔的机箱，长度40cm，再长点就要顶我机柜后盖了。价格 720 但是运费 60 。。。。&lt;/p&gt;

&lt;p&gt;接着是选电源，因为要根据机箱来决定是 ATX 电源还是买 服务器专用的电源。这个机箱因为支持 ATX 电源，所以自然是。。。 上 ATX 电源了。为了达到静音，专门买了钛金的电源。当然，是二手的。无风扇的。海韵当年的一款 520W 的无风扇电源。新的要一千多呢，二手400搞定。&lt;/p&gt;

&lt;p&gt;最后是随便买了个 8T 的盘，和原来的 4T 的盘一起组成了个 12T 的池子。最后上放到机柜上的效果。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/mynas.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;上面是 群灰和路由器，中间是华为的 S1720 的万兆POE交换机。下面就是 3U 高度的新nas了。&lt;/p&gt;

&lt;p&gt;因为有了 IPMI，所以不需要接显示器， 直接就通过浏览器就能直接安装 OS 了。
哦，忘记了，主板上还插了个 16G 的 U 盘来装系统。
毕竟不能浪费宝贵的 SATA 盘位对吧。&lt;/p&gt;

&lt;p&gt;对了，至于网卡吗， 当然买的二手 Mellanox Connect3X 单口万兆， pcie 3.0 x4 接口。160 块钱。&lt;/p&gt;

&lt;p&gt;然后因为和交换机在一个机柜，就不买模块了，直接上的 DAC 线。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>多WAN平衡</title>
   <link href="https://microcai.org/2019/04/04/multiWAN.html"/>
   <updated>2019-04-04T00:00:00+00:00</updated>
   <id>https://microcai.org/2019/04/04/multiWAN</id>
   <content type="html">&lt;p&gt;曾经我也搞了两条宽带，见 &lt;a href=&quot;/2016/12/07/use-neighbor-CMCC.html&quot;&gt;使用邻居的CMCC网络&lt;/a&gt; 。
然后利用路由器的loadbalance就可以使用多条线路了。
但是，问题恰恰在这里。效果并不好。因为2条宽带并不是一样容量的，对同一目标的访问速度也不尽相同。所以退而求次，选择了只把某些 cmcc 访问速度更快的目标通过 邻居网络出去。&lt;/p&gt;

&lt;p&gt;虽然我换了一个城市，然后选择了CMCC，也没有破解邻居的电信宽带wifi密码。但是多年前的失败经历还是让我感觉需要某种自动的机制自动的探测目标网络使用哪条线路是最快的。&lt;/p&gt;

&lt;p&gt;这就是 &lt;a href=&quot;https://github.com/microcai/smartproxy&quot;&gt;smartproxy&lt;/a&gt; 的了。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>内网万兆达成</title>
   <link href="https://microcai.org/2019/04/04/go-10GbE.html"/>
   <updated>2019-04-04T00:00:00+00:00</updated>
   <id>https://microcai.org/2019/04/04/go-10GbE</id>
   <content type="html">&lt;p&gt;千兆用几年了？ 从我拥有的第一台电脑起，主板自带以太网口就是千兆的。十几年过去了，主板自带网口依旧千兆。&lt;/p&gt;

&lt;p&gt;千兆网和 1366x768 一样，都是业界毒瘤，停滞在一个“够用” 的幻想里止步不前。&lt;/p&gt;

&lt;p&gt;为了打破这个幻想，我最终决定，将家里的内网升级为 10G 光以太网。&lt;/p&gt;

&lt;p&gt;为啥用光纤呢？&lt;/p&gt;

&lt;p&gt;主要是我的 PC 所在的位置，距离 86网络端口面板有数米的距离，中间如果使用网线会非常的碍事。
所以我买了个 86 面板的 AP 把原来的网络端口面板替换了。然后 PC 再上一块 wifi 网卡即可。&lt;/p&gt;

&lt;p&gt;无线代替了有线。虽然链接速度显示为 1733Mbps 但是实测速度只有 600Mbps 而已。&lt;/p&gt;

&lt;p&gt;虽然不够用，但是我更不希望一条网线拖过去。&lt;/p&gt;

&lt;p&gt;就这么用了几个月，突然间，我发现了一种叫“隐性光纤” 的神器。有了这个，就可以把隐性光纤沿着墙角落走线到 PC，不就解决了难看碍事的问题了吗？&lt;/p&gt;

&lt;p&gt;但是，既然上了光纤，就不能继续千兆了，当然要万兆才行。&lt;/p&gt;

&lt;p&gt;兵马未动，粮草先行。买设备升级前，首先解决布线问题。&lt;/p&gt;

&lt;p&gt;于是买了 隐性光纤。然后开始换线。买了条新的网线，然后把新的网线和光纤一起绑在开发商装的网线上，然后从弱点箱抽，非常的困难，最担心的就是把线拉断在中间了。
虽然过程很艰难，但是最终把隐性光纤给换进去了。做好了以后，就是 86 面板AP 的边上还抽出来了一个细细的透明的钓鱼线一样的光纤，然后沿着墙角走线（透明胶大法）一路通到了 PC。另一头也已经从弱电箱出来放到网络机柜的边上了。&lt;/p&gt;

&lt;p&gt;第一步就算完成了，证明开发商虽然黑心，但是好歹线还是有用管子布的。。还是能换的，虽然很难抽。。。。&lt;/p&gt;

&lt;p&gt;但是，这么难抽，有没有把光纤拉断呢？&lt;/p&gt;

&lt;p&gt;于是买了红光笔和冷接套装还有万兆网卡。&lt;/p&gt;

&lt;p&gt;开始接 SC 头。&lt;/p&gt;

&lt;p&gt;冷接真的是个技术活，但是我最终搞定了。隐形光纤有2层皮，最外层是一层软塑料。用米勒钳的第二个口就可以剥掉。然后露出的裸纤我当时就直接上冷接头了。。还很纳闷为啥切割刀无法切割。我改用网线钳去切光纤了。。。。。 最后最好的线红光笔一测，怎么光点这么弱！？&lt;/p&gt;

&lt;p&gt;结果是错误的，要再剥掉一层。用米勒钳的第三个口剥离。当时我直接用第三口剥发现一切就断了。还以为是他第三口洞太小了。原来是要先用口2剥第一层，然后用口3 剥最后一层。&lt;/p&gt;

&lt;p&gt;最后最后才真正的露出了内层玻璃纤维。&lt;/p&gt;

&lt;p&gt;这个玻璃纤维放到切割器上真的一次就切的整整齐齐。&lt;/p&gt;

&lt;p&gt;然后塞入冷接头。红光笔一测，发现和买的成品跳线一样亮了。&lt;/p&gt;

&lt;p&gt;然后把路由器到交换机临时改插网线，把 SFP 模块借用给 PC 了。万兆网卡不出意外的兼容千兆光模块。&lt;/p&gt;

&lt;p&gt;这样就实现了PC千兆光纤到交换机。&lt;/p&gt;

&lt;p&gt;终于可以对 1733Mbps 的 wifi 说拜拜了。&lt;/p&gt;

&lt;p&gt;冷接光纤学会了，自然就不怕了，然后开始选万兆交换机。必须是无风扇的交换机&lt;/p&gt;

&lt;p&gt;这个是最大的一个问题，我最后停留在2个方案上。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;方案1： 使用 MikroTik CRS305, 带4个万兆光口和一个千兆口。千兆电口支持 POE 受电，意味着只要插入原来的交换机上就可以立即工作了。&lt;/li&gt;
  &lt;li&gt;方案2： 使用华为 S1720-28GWR-4X 交换机。4个万兆口，24个千兆口，无风扇被动散热。没有 POE，意味着原先的交换机还得服役。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这两个方案都要原来的交换机继续服役，听起来是保护投资了，其实不然。 第一，交换机到交换机的带宽被限定 1G 可不是好事。第二，两台交换机还是占用了更大的空间，特别是方案2的话我其实不需要2台交换机给我扩充那么多端口。而方案1会有性能瓶颈。&lt;/p&gt;

&lt;p&gt;最后发现， S1720-28GWR-PWR-4X 交换机，和 S1720-28GWR-4X 相比就是多了 POE 功能，因此，只要我POE负载不高，实际上是可以被动散热的。因为 多加的风扇必然只是为了给增加了功率的变压器供电的。&lt;/p&gt;

&lt;p&gt;于是几经挫折后买到了 S1720-28GWR-PWR-4X 。1600 块钱。&lt;/p&gt;

&lt;p&gt;然后配合新买的2个 10G 的 SFP+ 光模块，重新做了 LC 插头（没有找到 SC 插头的万兆光模块），搞定了 PC 到交换机的万兆链接。&lt;/p&gt;

&lt;p&gt;接下来就是寻找合适的能支持 10G 访问的 NAS 了。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>入DN42坑</title>
   <link href="https://microcai.org/2019/03/11/dn42-connected.html"/>
   <updated>2019-03-11T00:00:00+00:00</updated>
   <id>https://microcai.org/2019/03/11/dn42-connected</id>
   <content type="html">&lt;p&gt;近来把 家和公司三处地方的网络（2个家，乡下老家嘛）通过 tunnel 手段连接到了一起， 三个地方的 LAN 分配不同的私网网段，因此配置好路由表后，三处内网的机器就可以互相访问了。&lt;/p&gt;

&lt;p&gt;最初我使用的是添加静态路由，但是因为 vpn 是使用的点对点互联，而不是首先集中连到一个转发服务器上，所以需要配置路由表的路由器数量达到了4台，比较麻烦，关键是期间我还修改过私网使用的网段，结果每台路由器都要登录上去修改路由表， 非常麻烦。&lt;/p&gt;

&lt;p&gt;更为麻烦的是， 其中公司访问老家的 vpn 丢包比较大，如果通过家中的网路跳转访问， 反而更快。但是如果公司到家里的网络断开，还要希望自动的让他们之间的直链线路接手， 于是就折腾上了 OSPF 路由协议。&lt;/p&gt;

&lt;p&gt;当然，最开始我是使用的 bgp 协议， 让3台路由器相互建立邻居，但是bgp的邻居要手动配置，稍微麻烦了点，所以改用 ospf 协议。&lt;/p&gt;

&lt;p&gt;顺便还修改了 EdgeOS （Ubnt 的路由器使用的系统）让它支持配置 zerotier —— 是通过他内置的 configure 命令配置， 而不是 zerotier-cli 命令， 这样就可以在 Web GUI 上为 zerotier 虚接口建立 ospf。&lt;/p&gt;

&lt;p&gt;基本上 EdgeOS 就是 Debian Linux + Perl configure script.
所以，其实 EdgeOS 是可以移植到 x86 软路由上的。不过x86软路由可以直接使用 VyOS。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;^_^&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;有了 动态路由协议，我只要设定老家到公司的vpn链路开销是 200, 而老家到家的开销是 80 ， 家到公司的开销是 80, 通过家中转访问的开销则只有 160，于是 ospf 的选路算法就会使用中转代替直联的那条vpn线路。如果中转的线路不可用， 则回退到直联模式。&lt;/p&gt;

&lt;p&gt;这样我顺便又加了一台在海外的VPS进入这个网络，形成4点的 mesh 网络。&lt;/p&gt;

&lt;p&gt;这个就是我自己折腾出来的，在折腾的过程中，还发现了 dn42 这个网络。&lt;/p&gt;

&lt;p&gt;dn42 就是一个全球性的，像我折腾手里私人网络一样的，通过 vpn 把多个节点连起来，不同的节点是由不同的人管理的，这样一个大型的虚拟网络。&lt;/p&gt;

&lt;p&gt;目前已经拥有 400+ 个自治域网络。&lt;/p&gt;

&lt;p&gt;于是我也去申请加入了。 搞到了 AS4242420571 这个 AS 号。然后分配了 
172.21.87.128/25 172.21.87.64/26 两段共 196 个IP。&lt;/p&gt;

&lt;p&gt;然后和 burble 建立了第一个 peer。顺便学习了 bgp 协议。&lt;/p&gt;

&lt;p&gt;然后 burble 一下子给我 push 了四百多条路由。不过我只给他 push 了2条。&lt;/p&gt;

&lt;p&gt;然后通过他的Transit我就能访问 dn42 里其他人了。&lt;/p&gt;

&lt;p&gt;当然，还得建立更多的 peer 才行。&lt;/p&gt;

&lt;p&gt;然后顺便就把家里的 IP 网段给换到 dn42 分配的 ip 里了。&lt;/p&gt;

&lt;p&gt;下面记录下分配的 IP 列表：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;172.21.87.64/29	-&amp;gt; 分配给所以设备的 Loopback IP , 用 /32 子网, 用于点对点的网络
    &lt;ul&gt;
      &lt;li&gt;点对点连接为如 PPTP， WG&lt;/li&gt;
      &lt;li&gt;172.21.87.66/32 分配给 HOME&lt;/li&gt;
      &lt;li&gt;172.21.87.67/32 分配给 Gaofeng&lt;/li&gt;
      &lt;li&gt;172.21.87.64/32 分配给 Office&lt;/li&gt;
      &lt;li&gt;172.21.87.65/32 给 zhangrui pptp 拨入对接&lt;/li&gt;
      &lt;li&gt;剩下还有 4 个 IP 保留。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;172.21.87.72/30	-&amp;gt; 用于 peer 的 zerotier 网络。&lt;/li&gt;
  &lt;li&gt;172.21.87.76/30	分配给家里第二个LAN, NAS 独享的点对点以太网链接&lt;/li&gt;
  &lt;li&gt;172.21.87.80/30 分配给 a0cbf4b62a4ab103 (OFFICE 2 VPS)&lt;/li&gt;
  &lt;li&gt;172.21.87.84/30 分配给 1c33c1ced03e1a5a	(OFFICE 2 HOME)&lt;/li&gt;
  &lt;li&gt;172.21.87.88/30 分配给 35c192ce9b758b54 (Gaofeng 2 HOME)&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;172.21.87.92/30 分配给 d3ecf5726d7bf5a7 (HOME 2 VPS)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;172.21.87.96/27 -&amp;gt; 办公室 LAN 网络&lt;/li&gt;
  &lt;li&gt;172.21.87.128/26 -&amp;gt; 家里主要网络&lt;/li&gt;
  &lt;li&gt;172.21.87.192/26 -&amp;gt; 丈母娘家网络&lt;/li&gt;
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>拿到了移动宽带的公网IP</title>
   <link href="https://microcai.org/2019/03/01/cmcc-public-ip.html"/>
   <updated>2019-03-01T00:00:00+00:00</updated>
   <id>https://microcai.org/2019/03/01/cmcc-public-ip</id>
   <content type="html">&lt;p&gt;闻觉 CMCC 手握 HKIX 宝剑， 想必连 HK 节点的 VPN 访问世界一定更快。世界的一部分一般在 HK 本地也有 CDN 节点，因此多数时候并不需要再从 HK 去世界绕一圈。于是买新房后，就办理了免费的CMCC宽带。不再向恶势力CT低头了。&lt;/p&gt;

&lt;p&gt;美中不足的是 CMCC 手头 ip 资源紧张，早早部署了 CGNAT，再经过猫一层 NAT，路由器又一层 NAT，访问个局域网都要3层NAT，再访问世界那就加1层VPN的NAT，达到了恐怖的4层NAT。&lt;/p&gt;

&lt;p&gt;于是第一件事情就是猫给改成了桥接模式，让路由器拨号。 猫就应该好好的做简单的光电转换的事情，搞什么非要把路由器的活也干了。这样下去TP-Link 岂不是要倒闭？ （虽然我不用 tp 的东西就是了）&lt;/p&gt;

&lt;p&gt;才少了一层NAT，还是深感不舒适。 特别是当需要从办公室连回家中的时候，只能靠服务器中转，明明在同一个城市，敲击键盘的每一个数字却都要绕地球一圈。&lt;/p&gt;

&lt;p&gt;NAT 破坏了网络的连接性，阻碍了 ipv6 的进程， 实在是十恶不赦。&lt;/p&gt;

&lt;p&gt;传统的NAT只是部署在家中，家里的NAT设备尚在用户掌握之中，大不了爬起来去路由器上敲击几条命令建立端口映射。&lt;/p&gt;

&lt;p&gt;但是 CGNAT 就是完完全全的罪恶涛天了。试问，一个只能打电话不能接听电话的手机，还叫手机吗？&lt;/p&gt;

&lt;p&gt;这辈子都不曾接过电话的人，总是觉得，能打电话就行了，接不到电话有屁关系。&lt;/p&gt;

&lt;p&gt;呵呵，你接不到电话，为了联系你，别人背后不知道做了多少工作，付出了多少成本！这种话，就好像一个生活在首都久了的人，没见过战争，就觉得别人去当兵去边疆守卫国家都是傻逼。&lt;/p&gt;

&lt;p&gt;但是没办法，ip地址不够，NAT 还是必要的，但是尽量让 NAT 只位于家中一层而已吧，运营商不要搞NAT了！&lt;/p&gt;

&lt;p&gt;CMCC 的宽带因为是送的， 直接打电话找它要是肯定没有的，人家宁可拆机也不会给你公网地址。&lt;/p&gt;

&lt;p&gt;所以想着，再办一条CT的宽带得了。只是一直没有行动，一是入户光纤乃开发商预埋，只有一根， 二是懒。 因为只要思想不滑坡， 办法总比困难多。&lt;/p&gt;

&lt;p&gt;忽然有一天，闲的无聊跑了一下 speedtest。其实一直都有跑，只是都没在意网速。那天就特别在意起来了，发现网速不达标。一直不达标。&lt;/p&gt;

&lt;p&gt;于是打了壹卍零捌拾陆。&lt;/p&gt;

&lt;p&gt;第二天修宽带的师傅就上门了。&lt;/p&gt;

&lt;p&gt;给他演示了一下，首先是 speedtest 测速，速度忽快忽慢。&lt;/p&gt;

&lt;p&gt;接着，禁用 ipv6 然后访问 http://mirrors.zju.edu.cn/ 下个iso，速度只有 50M，开 ipv6 访问， 速度 100M。&lt;/p&gt;

&lt;p&gt;十分诡异。&lt;/p&gt;

&lt;p&gt;小哥懵B了，他也不懂ipv6和ipv4的区别。我说 ipv6 是公网地址 ， ipv4 是私网地址啊。&lt;/p&gt;

&lt;p&gt;然后他懂了，他说给你换公网地址试试看。&lt;/p&gt;

&lt;p&gt;也许他在怀疑移动的NAT设备性能问题吧。&lt;/p&gt;

&lt;p&gt;然后过了几天，公网ip就有了，不过糟糕的是， ipv6 没了。。。。。&lt;/p&gt;

&lt;p&gt;好了，虽然用的免费的移动家宽，不过有了公网ip，网络确实用起来正常了。远程回家不需要中转了，需要 p2p 的软件也都工作正常了。 舒坦。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>折腾 NAT64 实现 ipv6 only 内网</title>
   <link href="https://microcai.org/2019/02/13/nat64.html"/>
   <updated>2019-02-13T00:00:00+00:00</updated>
   <id>https://microcai.org/2019/02/13/nat64</id>
   <content type="html">&lt;p&gt;一直以来，虽然启用了 ipv6, 但是都是以 Dual Stack 模式运行。
你不知道，是服务器端不支持 ipv6 呢，还是客户端不支持，亦或二者皆有之。&lt;/p&gt;

&lt;p&gt;而且Dual Stack虽然解决了互联问题，但是看起来非常的丑陋，多个协议一起跑什么的，才不要呢！&lt;/p&gt;

&lt;p&gt;于是就有了搞 IPv6 Only 的想法。为了解决互联问题，还需要一台翻译的设备执行 NAT64。&lt;/p&gt;

&lt;p&gt;所谓 NAT64 就是让一台网关充当 ipv6 和 ipv4 的桥梁，把 ipv4 主机模拟成 ipv6 主机，供 IPv6 Only 的主机访问。
之所以可以这么做到，是因为 ipv6 拥有巨大的地址集。只要其中拿一小部分出来就可以包含所有的 ipv4 地址了。可以做到 ipv6 地址和 ipv4 地址一一对应，然后为这个一一对应执行 NAT 转换。&lt;/p&gt;

&lt;p&gt;只需要一台网关设备提供 Dual Stack 即可，LAN 内的机器都只要 IPv6 Only 了。&lt;/p&gt;

&lt;p&gt;要实现 NAT64 , 首先 需要 DNS64 的帮忙。
何为 DNS64 ? 要让 ipv4 的机器被访问到，首先需要他被转换为 ipv6 地址，做到这一点的就是 DNS64 。&lt;/p&gt;

&lt;p&gt;当向 DNS64 查询 dns 的时候，dns64 会检查域名是否有 AAAA 记录，如果有，就用他自己的 AAAA 记录里的 ipv6 地址，客户端就可以直接访问了，完美。 如果他没有 AAAA 记录，但是有 A 记录，就把这个 A记录里的 ipv4 地址给转换为一个 ipv6 地址，然后返回这个假的 AAAA 记录。&lt;/p&gt;

&lt;p&gt;客户端访问这个假的 ipv6 地址，自然就被路由到了 NAT64 设备上，然后 NAT64 就充当中间人角色完成地址转换。&lt;/p&gt;

&lt;p&gt;对 ipv6 的客户端来说，一切都是透明的。&lt;/p&gt;

&lt;p&gt;说了半天，咋个实现呢？&lt;/p&gt;

&lt;p&gt;首先，你需要一台设备，低功耗24小时运行，性能又不会太差的那种。
嗯，没错，一台基于 Linux 的路由器（openwrt啊，梅林啊，当然，也可以像我一样，用 ubiquiti 的 EdgeMax 系列的路由器）&lt;/p&gt;

&lt;p&gt;但是，因为我最近拔仓库的时候发现了吃灰很久的一个树梅派，所以我决定在派上面安装这些东西。好处就是 EdgeOS 更新的时候不用重新折腾了。&lt;/p&gt;

&lt;p&gt;首先需要 DNS64, 这个DNS64 的存在，使得我之前折腾的ChinaDNS 瞬间毫无意义啦。因为 dns64 我用的 unbound 这个软件，而这个软件支持 DNSSEC 加密。意味着我只要打开 DNSSEC 就对 dns 污染说 good bye 了。于是只要 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pacman -S unbound &amp;amp;&amp;amp; systemctl start unbound&lt;/code&gt; 简单的命令就把 unbound 跑起来了。&lt;/p&gt;

&lt;p&gt;当然，还是要 edit 一下 /etc/unbound/unbound.conf 文件才能启用 DNS64 和 DNSSEC 的。&lt;/p&gt;

&lt;p&gt;DNS64 的伪造AAAA记录用的前缀呢，我就用 8964::/96 了。这样 1.1.1.1 就会被转换成 8964::1.1.1.1 （ 或者 8964::101:101 这种写法）。&lt;/p&gt;

&lt;p&gt;把 DNS 设置到 rpi 上后， nslookup 一下&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$nslookup www.baidu.com 240e:f0:63b:83b1:2e0:4cff:fe68:2846   
Server:         240e:f0:63b:83b1:2e0:4cff:fe68:2846
Address:        240e:f0:63b:83b1:2e0:4cff:fe68:2846#53

Non-authoritative answer:
www.baidu.com   canonical name = www.a.shifen.com.
Name:   www.a.shifen.com
Address: 115.239.211.112
Name:   www.a.shifen.com
Address: 115.239.210.27
Name:   www.a.shifen.com
Address: 8964::73ef:d21b
Name:   www.a.shifen.com
Address: 8964::73ef:d370
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;顺利的看到了 8964:: 开头的 伪 AAAA 记录。然后把路由器的 pppoe 拨号自动获取 dns 给关了，手工指定 dns 为 rpi。
这样 LAN 内的机器还是用的路由器来解析 dns。而路由器再转发给 rpi。因为必须要用路由器的 dnsmasq 解析才能正确的把某些域名的 ip 地址给填入 ipset ，用来进行基于匹配的智能路由。。。你懂的！&lt;/p&gt;

&lt;p&gt;然后开始折腾 NAT64.&lt;/p&gt;

&lt;p&gt;NAT64 使用 tayga 软件。这个软件 pacman 里没有。只好自己编译了。（折腾的时候忘记了用 yay ！）&lt;/p&gt;

&lt;p&gt;然后按照 wiki 把 tayga 跑起来，done！&lt;/p&gt;

&lt;p&gt;当然，要记得在路由器里配置下静态路由，把 8964::/96 的下一跳指向 rpi.&lt;/p&gt;

&lt;p&gt;试试看&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ping www.baidu.com
PING www.baidu.com(8964::73ef:d21b (8964::73ef:d21b)) 56 data bytes
64 bytes from 8964::73ef:d21b (8964::73ef:d21b): icmp_seq=1 ttl=52 time=7.88 ms
64 bytes from 8964::73ef:d21b (8964::73ef:d21b): icmp_seq=2 ttl=52 time=8.28 ms
64 bytes from 8964::73ef:d21b (8964::73ef:d21b): icmp_seq=3 ttl=52 time=8.28 ms
64 bytes from 8964::73ef:d21b (8964::73ef:d21b): icmp_seq=4 ttl=52 time=7.84 ms
^C
--- www.baidu.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 6ms
rtt min/avg/max/mdev = 7.836/8.069/8.282/0.212 ms
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;wow, very nice 啊！&lt;/p&gt;

&lt;p&gt;然后果断 NetworManager 设置打开，禁用 IPv4，重新启用下 eth0.&lt;/p&gt;

&lt;p&gt;然后，就只有ipv6 地址了（还是公网地址，哈哈！）&lt;/p&gt;

&lt;p&gt;然后，打开浏览器，wow，网能正常使用。&lt;/p&gt;

&lt;p&gt;Linux 上除了硬编码 ipv4 地址的客户端外，都能正常使用了。&lt;/p&gt;

&lt;p&gt;至于 Windows 嘛，呵呵，有太多的天朝系的垃圾软件，用 C 写的各种以为自己很牛逼的网络库，各种鄙视 boost.asio 的存在，导致这些垃圾网络库都是 ipv4 only 的。所以，没有 ipv4 地址，这些软件就统统歇菜了。&lt;/p&gt;

&lt;p&gt;but，意外的发现 酷狗音乐啊，网易音乐啊，都还是能用的。。。。。
果然玩音乐的网络代码写的比腾讯阿里好啊！&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>IPv6 这几天的体验</title>
   <link href="https://microcai.org/2019/02/13/ipv6-experience.html"/>
   <updated>2019-02-13T00:00:00+00:00</updated>
   <id>https://microcai.org/2019/02/13/ipv6-experience</id>
   <content type="html">&lt;p&gt;如果只是家里折腾上了 ipv6 那不算 ipv6。网络这种东西一定要互联互通才算。最近把公司的猫也折腾了一下下，上了 ipv6。&lt;/p&gt;

&lt;p&gt;于是发现，在公司可以无缝访问家里的设备了。全部都在公网，无需端口映射之类的东西了。当然，家里也没有公网ipv4地址，映射了也白搭。&lt;/p&gt;

&lt;p&gt;有了 ipv6 后，发现所有的机器都突然没有了 NAT 墙。那一道无形中把整个世界割裂的墙消失了，只要知道 ip 地址，所有的设备都可以点对点互相访问。&lt;/p&gt;

&lt;p&gt;终于，端到端的透明性回来了。&lt;/p&gt;

&lt;p&gt;然而问题来了，因为每个设备 ip 不一样，以往一台机器弄个DDNS就好了，现在需要每台提供外访的机器都要跑一个 ddns。要是路由器能为局域网的机器批量更新 AAAA 记录就好了。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>中国移动原生IPv6配置</title>
   <link href="https://microcai.org/2018/12/18/cmcc-ipv6.html"/>
   <updated>2018-12-18T00:00:00+00:00</updated>
   <id>https://microcai.org/2018/12/18/cmcc-ipv6</id>
   <content type="html">&lt;p&gt;上峰拍了桌子, 要求 2018 年底之前商用 ipv6. 移动响应非常积极,已经可以用了.
ipv6 带来的第一个好处就是削除了NAT, 对万恶的私网ip地址说再见!&lt;/p&gt;

&lt;p&gt;在继续之前，首先讲下，ipv6 同 ipv4 在配置上的不同。&lt;/p&gt;

&lt;p&gt;对于家庭宽带，ipv4 是 pppoe 拨号的时候自动配置的。isp 给且仅给一个 ipv4 地址。
如果有多台设备需要上网，就需要使用一种叫 NAT 的技术进行网址共享。由于NAT隐藏了内网的细节, 在 isp 看来,所有的数据都是来自同一个 ip 地址的. 因此isp和家庭网关之间不需要什么路由协议.
但是 ipv6 得益于超长地址,不再需要节约地址了,于是内网仍然分配全球可路由地址. 这个时候, isp 其实就需要分配两次地址,一次是给家庭网关的WAN口,另一次是给家庭网关的 LAN 口.&lt;/p&gt;

&lt;p&gt;第一次分配,给 WAN 口分配, ipv4 和 ipv6 是一样的. 都是在 PPPoE 拨号完成后动态分配.&lt;/p&gt;

&lt;p&gt;只是, ipv4 时代, LAN 口地址是固定的一个内网地址. 而 ipv6 时代, 即使 ip地址再丰富, 运营商也不会给用户分配固定的 ip 地址, 因此, 需要一种 动态的给 LAN 口分配地址(准确的说是分配前缀)的协议.&lt;/p&gt;

&lt;p&gt;这个协议就是 DHCP-PD. LAN 口分配好前缀后, 整个子网就分配下来了, 至于这个子网里哪个机器用哪个地址, 那就和 isp 没有关系了. 而且实际上 isp 下发子网前缀后, 路由器其实是可以把 下发的子网再细分的, 这个适用于有多个 LAN 的情况.&lt;/p&gt;

&lt;p&gt;ipv6 因为有 RA 协议, 所以只要路由器的 LAN 口分配了前缀, LAN 口下的在同一个交换域里的主机就都可以自动分配ipv6地址.&lt;/p&gt;

&lt;p&gt;那么, EdgeRouter 里如何使用 dhcp-pd 为 LAN 口分配地址呢?&lt;/p&gt;

&lt;p&gt;那么，配置的办法其实很简单， 就是&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ubnt@ubnt:~&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;configure
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;edit]
ubnt@ubnt# &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;interfaces ethernet eth0 pppoe 0 ipv6 address autoconf
ubnt@ubnt# &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 prefix-length /56
ubnt@ubnt# &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 interface switch0 prefix-id 1
ubnt@ubnt# &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 interface switch0 host-address ::7788
ubnt@ubnt# &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 interface switch0 service slaac
ubnt@ubnt# commit&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; save&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;exit
&lt;/span&gt;ubnt@ubnt:~&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;reboot
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;解析:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set interfaces ethernet eth0 pppoe 0 ipv6 address autoconf&lt;/code&gt; 启用了 pppoe 的 ipv6 地址配置, pppoe 认证通过后, 路由器即从 isp 为 pppoe 链接分配 ipv6 地址. 这时候, 路由器本身就可以通过 ipv6 上网了.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ubnt@ubnt# set interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 prefix-length /56&lt;/code&gt; 
这个命令就是启用了 dhcp-pd 的总开关, 向 isp 申请 /56 长度的前缀. 不同的 isp 这个数字是不同的, 大多数运营商都是 /56. 也有少数运营商是分配的 /60 的前缀, 这个自己尝试即可. 因为 edgerouter 是多功能的路由器, 不限定为一个wan口的,所以 dhcp-pd 客户端需要编号. 这里我就用编号1了. 如果有第二个 pppoe 链接(多isp的话) 就可以在别的 pppoe 下再创建 pd 2 了.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ubnt@ubnt# set interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 interface switch0 prefix-id 1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;这条命令创建第一个子前缀. 如果家里只划分了一个 LAN 那就只有这一条 prefix-id 为 1 的子前缀了. 如果有多个 LAN 网络, 就可以用 perfix-id 区分不同的 lan. 这里 switch0 的意思, 就是这个子前缀是为 switch0 分的. 如果你有多个子网, 可能就没有把 eth1234 聚合成 swtich0, 可以在这里使用 eth1 eth2 eth3 分别配置3条子网下去.
当然可可能是通过 VLAN 划分子网的话, 那就是 switch0.vlanid 了. 具体自己灵活即可.&lt;/p&gt;

&lt;p&gt;``` ubnt@ubnt# set interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 interface switch0 host-address ::7788&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
这条命令, 就是在这个 子池里, 选一个 ::7788 作为主机号. 加入 isp 分配下来的是 abcd:1234:1234:1200::/56 这样的前缀, 那么这个子池就是 abcd:1234:1234:1201::/64 注意这里的 1201/64 , 就是在 1200/56 里划分的子池, 子池的 id 就是那个  ```interface switch0 prefix-id 1``` 确定的.

然后, LAN 口的地址将会是 abcd:1234:1234:1201::7788. 


&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;ubnt@ubnt# set interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 interface switch0 service slaac
```
最后的这条配置, 就是启用 RA 广播, 局域网的机器收到后, 就会为自己生成 abcd:1234:1234:1201:????:????:????:???? 这样的地址了. 局域网的机器就能通过 ipv6 访问世界了.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>高并发服务器的静态内存分配策略</title>
   <link href="https://microcai.org/2018/10/25/static-memory-allocation-made-server-fast.html"/>
   <updated>2018-10-25T00:00:00+00:00</updated>
   <id>https://microcai.org/2018/10/25/static-memory-allocation-made-server-fast</id>
   <content type="html">&lt;p&gt;asio 的 async_accept 函数在高并发的时候，会发生遗漏 accept 的情况。根本原因在于，async_accept 回调的时候，已经 监听的socket已经没有执行 accept 操作了。
解决的办法就是投递多个 async_accept. asio 的 async_* 系列 函数通常只能投递一次，多次投递会发生未定义行为。async_accept 是为数不多的例外。&lt;/p&gt;

&lt;p&gt;通常在写一个 server 的时候，client 是采取 accept 一个就 make_shared 一个的做法。对于大量频繁的连接请求，make_shared 很快就会成为新的瓶颈。
在读 beast 的 example/http/fast_server 的时候，发现了 beast 对这个问题的解决方案，非常的聪明。&lt;/p&gt;

&lt;p&gt;beast 的做法就是，固定的 client 对象。每个 client 自己投递 async_accept 请求，把自己的 socket_ 投递给 async_accept。
async_accept 返回后， client 就对自己的 socket_ 执行处理， 该 read read，该 write 就 write。
直到这个 client 的请求全部处理完毕，连接也 shutdown 后， 它就再次投递 async_accept 进入下一个循环。&lt;/p&gt;

&lt;p&gt;在这个模式下，client 对象并没有被不断的 new 出来。 client 对象的数目是固定的。他利用了 async_accept 可以多次投递的 feature。
在超大量的请求下，该模式始终不会产生频繁的内存分配和释放请求，不仅仅加快了速度，而且也极大的避免了内存碎片。&lt;/p&gt;

&lt;p&gt;为了让这个模式调用更少的 new。 beast 定制了内存分配器。
这个定制的内存分配器，每次分配内存只是简单的一次指针移动，而不回收内存。
直到整个连接处理完毕，重置指针完成一次性释放。而这块固定的内存也是随着 client对象最初就已经固定建立了。&lt;/p&gt;

&lt;p&gt;也就是说，这个 fast_server 在运行的过程中，除了 asio 内部可能有内存分配外，其他地方没有任何内存分配。如果定制了 asio 的内存分配器，甚至能做到整个运行过程中 0次调用 malloc。运行过程中需要的内存是完全静态分配的。&lt;/p&gt;

&lt;p&gt;当然，不动态分配内存，缺点就是，该程序能处理的最大并发数是固定的。但是，即使动态分配内存，最大并发数就一定是无限的吗？&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>挖矿与创新</title>
   <link href="https://microcai.org/2018/06/10/mining-and-startup.html"/>
   <updated>2018-06-10T00:00:00+00:00</updated>
   <id>https://microcai.org/2018/06/10/mining-and-startup</id>
   <content type="html">&lt;p&gt;所谓挖矿，就是寻找一个随机数，是的区块头部和随机数结合后的 sha256 数值小于一个特定的目标数字。&lt;/p&gt;

&lt;p&gt;对于哪一个随机数能找到答案，没有人知道，也没有捷径，唯一的做法，就是一个一个的去试。&lt;/p&gt;

&lt;p&gt;比特币诞生也有9年了，9 年来，不断的有人尝试寻找捷径，但最终都没有找到，只能依靠暴力穷举随机数。
只要尝试的次数足够多，参与尝试的矿工足够多，大约每十分钟总会有一个幸运儿找到这样的随机数。
当他找到的时候，他将获得巨大的回报。
而当难度越来越高的时候，就有了矿池，为矿池工作的矿工，即使找到了，也只是获得平庸的奖励，
但是没有找到的矿工，同样获得了奖励。&lt;/p&gt;

&lt;p&gt;其实所谓创业，也是如此。没有人知道下一个独角兽会诞生在哪里。所有人都在无方向的试错。但是每隔一段时间，就有
一个人会找到那个正确的方向，获得巨大的奖励。
而当创业难度越来越高的时候，就有了公司。集合大家的力量去尝试寻找正确的解。&lt;/p&gt;

&lt;p&gt;所以创业成功，就和挖到比特币一样，没有什么秘诀。正如挖矿的算力越来越集中一样，创业也越来越成为少数人的特权。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>换用wireguard</title>
   <link href="https://microcai.org/2018/03/20/switch-to-wireguard.html"/>
   <updated>2018-03-20T00:00:00+00:00</updated>
   <id>https://microcai.org/2018/03/20/switch-to-wireguard</id>
   <content type="html">&lt;p&gt;之前把家里网络 &lt;a href=&quot;/2018/02/22/network-reworked.html&quot;&gt;重新折腾&lt;/a&gt; 了一下, 用的是 走socks5 代理的 tcp 协议 openvpn. socks5 代理由 shadowsocks 提供.&lt;/p&gt;

&lt;p&gt;不过, 最近偶然在 LWN 发现了 wireguard 这个新型 VPN, 于是盘算着用起来看看效果.
结果一不小心, 发现了&lt;a href=&quot;https://github.com/Lochnair/vyatta-wireguard&quot;&gt;vyatta-wireguard&lt;/a&gt;, 这个可以让我的路由器也能用 wireguard!&lt;/p&gt;

&lt;p&gt;于是按照它的文档配置到了路由器上, 然后VPS那边也配置下.&lt;/p&gt;

&lt;p&gt;只是把路由器表里, vtun0 换成了 wg0 就好了, 其他的不需要换.&lt;/p&gt;

&lt;p&gt;完了之后测试了一下速度, 比 openvpn 的时候快了很多. 毕竟 UDP 嘛. vpn 这种东西还是适合 UDP 而不是 tcp 的.
而且在跑流量的时候, ping 的延迟不会增加. 如果是 tcp 则会因为发送窗口的关系, ping 的延迟会因为流量增加而迅速增加.
对网络的体验极其不好.&lt;/p&gt;

&lt;p&gt;当然, udp 模式的 openvpn 其实也可以, 只是 openvpn 已经被墙识别了, 不可能直接用 openvpn 啦.
wg 毕竟新鲜事物, 到墙能识别故意要很长一段时间了. 这段时间内都可以安心的啦.&lt;/p&gt;

&lt;p&gt;PS: 一开始担心机房屏蔽 UDP 包, 结果发现可以愉快的跑, 担心是多余的啦.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>UPS和直流供电的思考</title>
   <link href="https://microcai.org/2018/03/05/hvdc.html"/>
   <updated>2018-03-05T00:00:00+00:00</updated>
   <id>https://microcai.org/2018/03/05/hvdc</id>
   <content type="html">&lt;p&gt;为了在网络断电后还能使用, 我曾经给路由器配置 12v 直流 UPS. 但是, 随着我的网络扩展, 出现了更多的设备, 而且这些设备不再是12v电压供电的了, 而是出现了 24v 和 54v 供电的设备. 同时, 12v 供电的设备也增加了, 原来的 UPS 容量就显得捉襟见肘了. 我继续要对 UPS 进行一次升级.&lt;/p&gt;

&lt;p&gt;一劳永逸的解决方式, 其实是直接购买普通的 220v 逆变输出的 UPS. 但是, 生命在于折腾, 而且, 这种 UPS 效率很低, 因为 DC 经过一次逆变后, 还有再变压直流, 其实这中间的转换就属于脱裤子放屁多此一举, 除了带来毫无意义的能源损失. 除非用电设备是非交流电不能的, 比如 单相异步交流电机 然而我的设备里, 即使是网络存储器的散热风扇, 也是 12v的直流风扇而已. 所以, 直接购买UPS显然不够极客, 也不适合节能减排的今天.&lt;/p&gt;

&lt;p&gt;当然, 我也可以继续扩容 12v DC UPS. 多并联几个就好了. 然后使用 12v -&amp;gt; 24V 和 12v-&amp;gt; 48v 的 DC/DC 变换器来解决使用其他电压的设备供电.&lt;/p&gt;

&lt;p&gt;但是这样做会有2个问题, 第一个问题是, 这种寨牌的成品UPS并不能水平扩容. 第二个问题则是, 又要引入许多电压转换, 带来能源损失. 本身电池储能就有限, 不能肆意铺张浪费.&lt;/p&gt;

&lt;p&gt;思考再三, 我决定储能上使用 48v 电池. 因为如此耗电最大的设备, ES-8 POE 交换机, 就可以直接使用电池供电. 最大限度的减少变压带来的损耗. 电池类型上呢, 决定使用更加经济环保的磷酸铁锂电池. 这种电池在同样体积下, 比铅酸蓄电池提供了更大的容量, 而且并没有贵太多. 虽然单个电池更贵, 但是单位容量的成本反而是下降的.&lt;/p&gt;

&lt;p&gt;接着, 购买了自动切换主备电源的开关, 主电源停电后, 自动闭合备用电源. 用电设备呢, 则是购买了1拖2的那种DC转接线.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/1to2.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;电池的输入和自带电源适配器的输入, 共同并入用电设备. 
由于备用电源在主电源有电的情况下, 是常开状态的, 所以其实这种情况下, 并不会出现适配器的输出给电池充电的情况 (这种情况是要极力避免的, 因为这种适配器并不能用来给电池充电, 否则极易使电池报废).&lt;/p&gt;

&lt;p&gt;大功告成后, 发现了一个问题, 就是这种主备自动切换的开关, 有一个切换延迟. 这个延迟虽然小, 但是还是导致切换的时候, 设备重启了. 这个问题呢, 暂时通过引入一个小容量的超级电容解决了.&lt;/p&gt;

&lt;p&gt;但是, 这个事情呢, 也让我认识到, 后备式UPS 在切换的稳定性上, 不如在线式UPS.
但是, 在线式UPS最大的问题, 就是电压多次变换导致的效率低下. 只要设备还是工作在 220v 交流电下, 在线式就不可避免的要引入逆变器, 不可避免的要效率低下.&lt;/p&gt;

&lt;p&gt;如果不要逆变器呢? 如果我使用的是, 带电池管理功能的 48v 开关电源, 则其实电池是可以直接接入母线. 变成直流的在线式UPS. 这个固然效率很高, 但是只适用于直流供电的设备, 不适用于其他类型的设备.
而且, 48v 电压, 也只能用在路由器交换机这种耗电不大的设备上, 所以, 直流的在线式UPS始终无法应用开来. 否则电流太大, 耗费在电缆上的能源也非常可观, 减少能耗使用非常粗的电缆则不现实.&lt;/p&gt;

&lt;p&gt;真的吗?&lt;/p&gt;

&lt;p&gt;如果提高直流电压, 比如提高到 300v 呢? 220v 交流电压其实只是个等效值, 他的峰值电压其实可以到 311v. 也就是说, 如果使用 300v 的直流电, 其实现有的布线绝缘等级是完全够用的, 而且电压提高了, 实际上能输送更大的功率.&lt;/p&gt;

&lt;p&gt;用电设备又如何呢?&lt;/p&gt;

&lt;p&gt;现如今, 大部分家庭设备, 其实都是直流的, 有的外置,有的内置, 不管外置还是内置, 都有一个开关电源. 这些开关电源的工作原理, 恰恰是对 220v 的交流电进行整流, 变成大约 300v 的直流电, 然后内部再进行 DC 变压进而输出需要的电压.&lt;/p&gt;

&lt;p&gt;而像电饭锅之类的设备, 实际上发热元件只是电阻, 本身并不在意是交流还是直流.&lt;/p&gt;

&lt;p&gt;少数必须交流才能工作的电器(比如电风扇), 则可以单独配置逆变器解决, 或者改成更先进的变频电机(凡是变频电机的设备, 内部其实还是需要直流电的, 通常设备在电源线刚进去, 就通过一个整流器整流为直流电, 然后再使用.)的款式.&lt;/p&gt;

&lt;p&gt;如此一来, 其实现如今, 家庭用电,其实完全可以使用 200v - 300v 的高压直流电.而不需要改造家电. 只需要在电线入户处配置一个高效的大功率整流器即可.&lt;/p&gt;

&lt;p&gt;直流的好处, 首先就是比交流安全. 虽然 300v 的直流电仍然是属于可以电死人的电压, 但是仍然比交流电更安全.
第二个好处, 就是方便使用家庭储能, 少掉逆变器, 大大增加系统的可靠性, 大大降低系统的成本.
第三个好处, 方便接入新能源. 因为新能源通常产生的是直流电. 如果家庭内部使用的是直流电, 就可以直接并网让家庭优先使用本地产生的新能源, 而不需要进行并网控制.
第四个好处, 直流供电的家庭, 无法向电网馈送功率. 大大增强电网的稳定性.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>网络重新配置</title>
   <link href="https://microcai.org/2018/02/22/network-reworked.html"/>
   <updated>2018-02-22T00:00:00+00:00</updated>
   <id>https://microcai.org/2018/02/22/network-reworked</id>
   <content type="html">&lt;p&gt;一直以来, 我用的都是基于 ss-redir 和 ipset + iptables 透明代理方式的科学上网. 但是 iptables 方式的上网, 其实还是不如 ChinaRoute 配合 VPN 来的爽. 因为只有 TCP 连接能被透明代理出去, UDP 和 ICMP 协议统统不能.&lt;/p&gt;

&lt;p&gt;但是 openvpn 这种协议实在是太过招摇, 所以早就被方校长盯上了. 不可不可.&lt;/p&gt;

&lt;p&gt;后来, 我想到了openvpn 可以透过纸飞机的代理再去连接远程服务器, 这样一来, 就解决了 openvpn 过墙问题.&lt;/p&gt;

&lt;p&gt;于是直接在服务器上安装 openvpn 然后设定为使用 tcp 协议, 监听 127.0.0.1 地址.&lt;/p&gt;

&lt;p&gt;然后在路由器上配置 openvpn , 只要配置文件里设定 socks-proxy xxx:1080 , 然后 remote 为 127.0.0.1 1189 就好了.
因为路由器是 ubnt ER-X, 自带 openvpn , 只需要自己编译 ss 就好, 不过因为最近买了个 群晖的 NAS, 于是懒得折腾交叉编译 ss 了, 直接在 NAS 上用 opkg install shadowsocks-libev 安装了纸飞机. 路由器上使用 NAS 上的 ss 代理就好了.&lt;/p&gt;

&lt;p&gt;vtun0 网卡正常上线并获取到ip地址后, 先 ping 下 vtun0 的网关 10.8.0.1 , 发现是 OK 的. 接下来就是配置 route 了.&lt;/p&gt;

&lt;p&gt;其实这个时候有2个办法配置路由表. 一个办法是使用 ChinaRoute 脚本. 让 openvpn 拨上后自动执行.
另一个办法呢, 还是使用 ipset + policy route.&lt;/p&gt;

&lt;p&gt;chinaroute 这个, 任何路由器都能用, 这里讲下 ipset 和 policy route 在 ubnt 的路由器上如何使用.&lt;/p&gt;

&lt;p&gt;虽然底层实现就是 iptables 做 rt-mark 然后用非默认的 route table 做路由, 但是ubnt把这套机制给简化了.&lt;/p&gt;

&lt;p&gt;首先是在 protocol.static.table. 下面设定一个过墙用的路由表. 这个路由表很简单, 就只有一条 默认路由到 vtun0.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;
set protocols static table 10 description &quot;route to VPN&quot;
&lt;br /&gt;set protocols static table 10 interface-route 0.0.0.0/0 next-hop-interface vtun0
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;注意, 因为使用的是 tun 模式, 所以是 interface-route, 如果是 tap 模式,则是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set protocols static table 10 route 0.0.0.0/0 next-hop 10.8.0.1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;因为 tun 是点对点设备, 只要指定下一跳的接口, 而 tap 是虚拟以太网卡, 需要指定网关的 ip 地址.&lt;/p&gt;

&lt;p&gt;这样就设定了一个 id 是 10 的路由表, 表里只有一条到 VPN 的默认路由.&lt;/p&gt;

&lt;p&gt;这个在普通 Linux 里, 设定的方式似乎是在 /etc/iproute2/rt_tables 里配置. 略微麻烦.&lt;/p&gt;

&lt;p&gt;接着, 设定防火墙, 让 匹配某个 ipset 的包都通过 10 号路由表出去. 如果在 Wizard 向导里, 启用了 2WAN 负载均衡的话, 这个时候会已经创建好一个叫 balance 的 防火墙规则.
规则的rule xx 之类的, 都是负载均衡的规则. 执行次序是数字从小到大. 这里我插入了个编号 60  的规则&lt;/p&gt;

&lt;p&gt;&lt;code&gt;
set firewall modify balance rule 60 action modify&lt;br /&gt;
set firewall modify balance rule 60 modify table 10&lt;br /&gt;
set firewall modify balance rule 60 destination group address-group gfwlist&lt;br /&gt;
set firewall modify balance rule 60 description &quot;use vtun0 to route gfwlist&quot;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;这条规则的意思是, 匹配目的地址为 gfwlist 组, 使用 10 号路由表.&lt;/p&gt;

&lt;p&gt;在 EdgeOS 里, address-group network-group 之类的都是使用 ipset 实现的. 所以, 只要在 firewall 里弄个 gfwlist 地址组, 就会有个 gfwlist 的 ipset. 不需要在 EdgeOS 的配置里填入地址, 稍后我们用 dnsmasq 填入 ipset. 使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set firewall group address-group gfwlist&lt;/code&gt; 这个命令建立 gfwlist 这个地址组.&lt;/p&gt;

&lt;p&gt;如果么使用wizard搞 load balance, 则不会有这个  balance 规则. 可以自己建立. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set firewall modify balance&lt;/code&gt; 就建立了.
然后需要在 switch0 里, 导入这个规则.  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set interfaces switch switch0 firewall in modify balance&lt;/code&gt; 这个意思是, 所有从 switch0 收到的包(这里in的意思), 都要经过 balance 这条规则修改.&lt;/p&gt;

&lt;p&gt;接下来是让 dnsmasq 填入 ipset. 使用的方法是 gfwlist2ipset 这个脚本, 生成一个 dnsmasq-ipset.conf 文件, 丢到路由器的 /etc/dnsmasq.d/ 目录下就可以了.&lt;/p&gt;

&lt;p&gt;对了, 别忘记, 如果 gfwlist2ipset 用的 8.8.8.8 这个外网dns, 要使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set protocols static interface-route 8.8.8.8/32 next-hop-interface vtun0&lt;/code&gt; 这条命令, 给 8.8.8.8 地址设定下路由, 使用 vtun0 接口出去. 同样的, 如果是 tap 类型的设备, 要使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set protocols static route 8.8.8.8/32 next-hop 10.8.0.1&lt;/code&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Immutable, RCU and Transnational Container</title>
   <link href="https://microcai.org/2018/01/27/immutable-rcu-and-transactional-container.html"/>
   <updated>2018-01-27T00:00:00+00:00</updated>
   <id>https://microcai.org/2018/01/27/immutable-rcu-and-transactional-container</id>
   <content type="html">&lt;p&gt;在编写并发程序的时候，经常要面对共享数据结构的保护问题。
虽然 C++ 提供的 RAII 实现的各种 guard lock 模式，早就让“忘记解锁” 导致的死锁成为历史。但是，锁带来的性能损耗依然是我们不得不面对的问题。&lt;/p&gt;

&lt;p&gt;为了避免锁带来的开销，使用“无锁” 的数据结构似乎是个好主意。
然而事情的发展总是出乎所有人的意料。现代的多核CPU随着核数的越来越高，核与核之间的通信开销也变得越来越大。于是，所谓的“无锁” 容器，因为内部大量的“原子操作”变得越来越不适应于当前的时代。&lt;/p&gt;

&lt;p&gt;不使用原子操作，又想大并发，又想安全，唯一能想到的，就只有“不修改” 了。 只读的数据，永远不需要锁的保护。这也是为何当下的程序员越来越青睐函数式编程。&lt;/p&gt;

&lt;p&gt;然而，只有 const 没有 variable 的程序是根本不可能的。程序要运行，就得有变化。有变化，就不能使用只读的数据结构，不能只读，还是少不了要上锁。一锁，就离高性能大并发越来越远了。&lt;/p&gt;

&lt;p&gt;有没有既可以修改，又可以无锁，还不需要使用原子操作的办法呢？&lt;/p&gt;

&lt;p&gt;当然有啊，就是 RCU。但是 RCU 的局限性非常大，具体的来说就是，因为其赖以实现的原理导致了 RCU 只能是 list 容器。不能实现为更高效的 vector 和 hashmap 容器。&lt;/p&gt;

&lt;p&gt;RCU 先修改 关联节点的 next/prev 指针，使得 后续的读者，会跳过当前要操作的节点，然后等待一段时间后， 释放节点。这样，读可以全速并发进行。无需任何锁的介入。而只有 list 容器，才能在不使用原子操作和锁的情况下，安全的 Detach 掉一个节点。map 容器，因为要对 tree 重新排序，无法实现成 RCU 操作。&lt;/p&gt;

&lt;p&gt;即使 RCU 的读取速度再怎么好， list 就决定了，它在大元素量下注定的低性能。因此 rcu 即使在内核里，使用的也非常有限。只用在为数不多的 “读远大于写，而且容器元素不多” 的地方。&lt;/p&gt;

&lt;p&gt;这个虽然不是 RCU 的问题，而是 list 的问题，但是 RCU 注定只能使用 list。&lt;/p&gt;

&lt;p&gt;有没有办法让我最喜欢的 map/set/multiset/multimap/boost.multiindex 都能并发化呢？&lt;/p&gt;

&lt;p&gt;一个数据结构，没有办法在修改的时候不上锁，主要原因就是他有“危险的中间态”。锁就是用来阻止其他线程访问危险的中间态的。&lt;/p&gt;

&lt;p&gt;但是其实，不用锁也是有办法隔离掉危险的中间态的，就是 “Transaction”&lt;/p&gt;

&lt;p&gt;如果对数据结构的多个修改，只有最终 commit 的时候， 才生效（其他线程才看的到），就不需要锁了。&lt;/p&gt;

&lt;p&gt;使用 事务内存 就可以达成大并发，无锁，而且还不需要使用原子操作。&lt;/p&gt;

&lt;p&gt;然而，如果没有事务内存又如何呢？其实，事务内存也是可以模拟的！今天要介绍的主角，就是在普通内存的机器上，为容器开启事务功能，达到使用事务内存一样的功效。&lt;/p&gt;

&lt;p&gt;做法也很简单。就是使用2个容器 :smile:&lt;/p&gt;

&lt;p&gt;假设要将一个 map 事务化， 设置一个当前指针 map* cur = new map;
然后，所有的读取操作，都在 *cur 上进行。&lt;/p&gt;

&lt;p&gt;凡是要对 容器 做修改的时候， 先  map * alt = new map(*cur); 复制一份。
然后  alt 上进行修改。
改完了， cur = alt 赋值。搞定。&lt;/p&gt;

&lt;p&gt;接着是对老容器的回收，这种情况下， 不能立即回收，因为可能还有其他线程在访问这个老容器。怎么办呢？ 就是 setTimeout(1000, delete oldmap);&lt;/p&gt;

&lt;p&gt;总不会这么长时间了， 还有线程在访问吧 :wink:&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>C++ 是最好的编程语言</title>
   <link href="https://microcai.org/2017/11/04/c++-is-the-best-language-forever.html"/>
   <updated>2017-11-04T00:00:00+00:00</updated>
   <id>https://microcai.org/2017/11/04/c++-is-the-best-language-forever</id>
   <content type="html">&lt;p&gt;好的语言是怎么来的? 有人说, 是设计出来的, 有人说, 是抄出来的. newbie 说, 是实践来的.&lt;/p&gt;

&lt;p&gt;什么是实践呢? 实践, 就是摸石头过河, 吸收合适的, 抛弃不合适的. 这不就是进化吗?&lt;/p&gt;

&lt;p&gt;正如好的物种是进化来的一样, 好的语言也是进化来的.&lt;/p&gt;

&lt;p&gt;物种的进化, 首先是要适应已有的环境, 然后才是展望未来, 设法适应未来的环境. 即使某些功能器官已经不再需要, 仍然保留着进化的痕迹.
进化是不可逆的. 如果一开始因为环境因素, 进化出的生物特性在后来的环境变化中是非常有害的, 进化却不能推倒重来. 只能继续在这个不利的基础上改进.&lt;/p&gt;

&lt;p&gt;认识到这一点, 再回过头来审视 C++ 语言.  C++ 语言的许多缺陷, 来自对 C 的兼容, 或者确切的说, 是因为从 C 语言开始进化的, 对C的兼容, 就是进化的痕迹.
进化不能推倒重来, 只能在 C 这个不利的基础上改进.&lt;/p&gt;

&lt;p&gt;但是, C 的不利, 是站在今天的角度看的. 在当时, C 是最成功的语言. 兼容 C, 是最有利于发展的. 从 C 开始进化, 在当时就是最好的选择. 而进化, 就是当年最优秀的物种才得以继续发展演化.&lt;/p&gt;

&lt;p&gt;C 当年是最好最成功的语言, 那么进化就会发生在 C 的身上, 而不是其他语言的身上. 正如远古的猿类进化成了人, 同时也进化出了猴子一样, 远古的 C 语言进化出了 C++, 也会有顽固分子选择止步不前, 就有了现在的 C. 猴子当然不如人, 但是猴子也会存在. 所以 C 会继续存在. 但是真正的发展是看C++的.&lt;/p&gt;

&lt;p&gt;C++ 的进化, 是采用”遗传学算法” 进行的. 遗传学算法, 需要一个是否更适用的判定函数, 和一个随机发展的过程函数. 每一个发展, 都是在原有的基础上进行迭代, 然后送入判定函数. 每一次迭代, 都会保留既有的成分.&lt;/p&gt;

&lt;p&gt;C++的发展函数, 靠的是社区大量提交的提案.而判定, 则交给标准委员会的人投票表决. 提案首先要经过一个硬指标, 就是 C++ 之父设定的 零开销原则和兼容 原则. 而后才能进入是否更符合现代的软件工程实践的讨论中去.&lt;/p&gt;

&lt;p&gt;因此, 社会的发展, 对语言的需求, 推动了C++的发展, C++ 一直在适应不断变化的市场, 而唯一不变的, 是 “零开销和兼容上一个版本”. 因此, 零开销和兼容 才是C++的根本, 其他的语法糖, 都是进化的痕迹.&lt;/p&gt;

&lt;p&gt;C++ 是进化来的, 不可避免的会携带历史的包袱, 可正是因为他是进化来的, 他会携带的不仅仅是历史的包袱, 还有历史的财富. 丢弃包袱, 常常意味着放弃了财富, 我们的社会从来都不倾向于白手起家. 没有财富, 就要从头积累.&lt;/p&gt;

&lt;p&gt;问题是: 谁能积累的有 C++ 多?&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>不停机迁移</title>
   <link href="https://microcai.org/2017/06/19/non-stoping-db-mitigrade.html"/>
   <updated>2017-06-19T00:00:00+00:00</updated>
   <id>https://microcai.org/2017/06/19/non-stoping-db-mitigrade</id>
   <content type="html">&lt;p&gt;设计一套系统的时候， 最初的数据库结构总是不能适应未来业务的发展变化。经常需要调整数据库的表结构以适应新的业务需求或调整逻辑结构。&lt;/p&gt;

&lt;p&gt;为此，需要让系统能支持不停机迁移。不停机迁移，用户0感知的，完成数据库升级。要做到这点，首先要做到程序本身的热更新。&lt;/p&gt;

&lt;p&gt;如果是 php 之类的脚本语言编写的服务器端程序，热更新实现起来倒也轻松容易。但是既然是我这个c++佬出来写文章， 一定是关注的c++编写的服务器端程序的热更新。&lt;/p&gt;

&lt;p&gt;systemd 提供了一个热更新的新思路。就是让 systemd 持有 listen socket。然后fork出服务进程。
进程重启， listen socket 并没有销毁，因此用户依然可以在进程关闭期间发起连接。期间创建的连接，在进程启动后会被处理。
用户唯一能感知到的就是重启期间处理有一定的延迟。&lt;/p&gt;

&lt;p&gt;支持 systemd 的 sccket activation 就实现了热更新的第一步。&lt;/p&gt;

&lt;p&gt;接下来才是重点。使用 socket activation 的必然要求就就是重启迅速。新进程要迅速完成启动准备，并进入服务状态。连接总是被 hold 住，响应不及时，用户看来也是停机。&lt;/p&gt;

&lt;p&gt;如果要修改数据库结构， 重启时就是修改的时机。这就要求，对数据库 schema 的修改必须要立即完成。
那么，什么样的操作能立即完成呢？&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;增加一个允许 NULL 字段。
删除一个字段。
重命名一个字段。
删除一个索引
创建一张新表
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;什么样的操作要避免呢？&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;增加一个非空字段
新建一个索引
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;并非所有的操作都要安排在程序重启期间进行。可以在重启前就执行。这就要求，对数据库的修改，必须保证旧程序代码兼容。如果不兼容，就首先热更新一个能同时兼容
旧库和（即将更新的）新库的程序。接着更改数据库。使用这种方式，可以放宽一个要求， 就是只要不（长时间）锁表的操作，都可以进行。例如，pg 的并发新建索引。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>使用异常实现cache</title>
   <link href="https://microcai.org/2017/04/09/simple-cache-container.html"/>
   <updated>2017-04-09T00:00:00+00:00</updated>
   <id>https://microcai.org/2017/04/09/simple-cache-container</id>
   <content type="html">&lt;p&gt;异常用来做错误处理的时候，程序到处都是 try  cache ，代码十分的丑陋，我是不怎么喜欢的，我喜欢 asio 那种用 error_code  汇报错误 —— 不传 ec 的时候就抛异常，传就不抛，改为写入错误到 ec。&lt;/p&gt;

&lt;p&gt;但是，异常用来做流程控制，又特别的好用。流程控制，无非顺序、选择、分支和循环。在 c++里，又比 C 多了一个异常。在嵌套很深的地方，跳出逻辑，除了异常，就没有其他更好的办法了。&lt;/p&gt;

&lt;p&gt;在编写软件的时候，时常需要对一些数据做 cache。在使用的时候，要先检查 cache，存在则使用 cache ，不存在则按照老办法办，然后存入 cache。&lt;/p&gt;

&lt;p&gt;每次使用前都进行判断， 污染了快速路径的代码，对有简洁洁癖的程序员来说，内心是十分的纠结的。&lt;/p&gt;

&lt;p&gt;这个时候， 你就需要 异常。将 cache hit 作为正常的流程进行编写， 假定全部的数据都是在 cache 里的。这万一发生了 cache miss ， 则抛出异常，并在异常处理重新载入数据。然后重启处理。&lt;/p&gt;

&lt;p&gt;说到重启处理， 在 Windows 的 SEH 里，存在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EXECEPT_CONTINUE_EXECUTION&lt;/code&gt; 这个异常处理的结果， windows 看到异常处理函数返回这个，就会回到发生异常的地方重新执行。然而这毕竟是一个 Windows 系统特有的 SEH ， 而且依赖底层CPU提供的机制。 编写C++是断然不能使用这套机制的。&lt;/p&gt;

&lt;p&gt;思考的最终结果，就是下面这样的结构&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
for (int =0; i &amp;lt; retry_times ; i++)
{ 
    try  
    {
            auto  v = cache_map_sometype.get_cache(key);
            // process with v ....
            ........
    }  
    catch(cache_miss&amp;amp;)
    {   
           // load v from other resources, database, filesystem, network, etc.
           ......
            cache_map_sometype.add_cache(key, v);
            contine; // NOTE about this.
    }
    break;  
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在正常处理流程里， 执行到最后会有个 break 退出 for 循环。所以 for 循环在 cache hit 的状态下只执行一次。在 cache miss 的时候， catch block 里最后有一行 continue 。 于是就重启处理过程了。&lt;/p&gt;

&lt;p&gt;下面给出 cache_map 的代码。&lt;/p&gt;

&lt;pre style=&quot;color:#eff0f1;background-color:#232629;&quot;&gt;

&lt;span style=&quot;color:#27ae60;&quot;&gt;#pragma once&lt;/span&gt;

&lt;span style=&quot;color:#27ae60;&quot;&gt;#include &lt;/span&gt;&lt;span style=&quot;color:#27ae60;&quot;&gt;&amp;lt;tuple&amp;gt;&lt;/span&gt;
&lt;span style=&quot;color:#27ae60;&quot;&gt;#include &lt;/span&gt;&lt;span style=&quot;color:#27ae60;&quot;&gt;&amp;lt;map&amp;gt;&lt;/span&gt;

&lt;span style=&quot;color:#27ae60;&quot;&gt;#include &lt;/span&gt;&lt;span style=&quot;color:#27ae60;&quot;&gt;&amp;lt;boost/thread.hpp&amp;gt;&lt;/span&gt;
&lt;span style=&quot;color:#27ae60;&quot;&gt;#include &lt;/span&gt;&lt;span style=&quot;color:#27ae60;&quot;&gt;&amp;lt;boost/thread/shared_mutex.hpp&amp;gt;&lt;/span&gt;
&lt;span style=&quot;color:#27ae60;&quot;&gt;#include &lt;/span&gt;&lt;span style=&quot;color:#27ae60;&quot;&gt;&amp;lt;boost/date_time/posix_time/ptime.hpp&amp;gt;&lt;/span&gt;

&lt;b&gt;struct&lt;/b&gt; cache_miss {};

&lt;b&gt;template&lt;/b&gt;&amp;lt;&lt;b&gt;typename&lt;/b&gt; KeyType, &lt;b&gt;typename&lt;/b&gt; ValueType, &lt;span style=&quot;color:#2980b9;&quot;&gt;int&lt;/span&gt; cache_aging_time = &lt;span style=&quot;color:#f67400;&quot;&gt;30&lt;/span&gt;&amp;gt;
&lt;b&gt;class&lt;/b&gt; cache_map
	: &lt;b&gt;protected&lt;/b&gt; &lt;span style=&quot;color:#59ff04;&quot;&gt;std::map&lt;/span&gt;&amp;lt;KeyType, &lt;span style=&quot;color:#59ff04;&quot;&gt;std::tuple&lt;/span&gt;&amp;lt;ValueType, &lt;span style=&quot;color:#56e092;&quot;&gt;boost::posix_time::ptime&lt;/span&gt;&amp;gt;&amp;gt;
{
	&lt;b&gt;typedef&lt;/b&gt; &lt;span style=&quot;color:#59ff04;&quot;&gt;std::map&lt;/span&gt;&amp;lt;KeyType, &lt;span style=&quot;color:#59ff04;&quot;&gt;std::tuple&lt;/span&gt;&amp;lt;ValueType, &lt;span style=&quot;color:#56e092;&quot;&gt;boost::posix_time::ptime&lt;/span&gt;&amp;gt;&amp;gt; base_type;

&lt;b&gt;public&lt;/b&gt;:
	ValueType get_cache(&lt;span style=&quot;color:#2980b9;&quot;&gt;const&lt;/span&gt; KeyType&amp;amp; key) &lt;b&gt;throw&lt;/b&gt;(cache_miss)
	{
		&lt;span style=&quot;color:#56e092;&quot;&gt;boost::shared_lock&lt;/span&gt;&amp;lt;&lt;span style=&quot;color:#56e092;&quot;&gt;boost::shared_mutex&lt;/span&gt;&amp;gt; l(m_mutex);

		&lt;b&gt;typename&lt;/b&gt; base_type::iterator it = base_type::find(key);

		&lt;b&gt;if&lt;/b&gt; (it == base_type::end())
		{
			&lt;b&gt;throw&lt;/b&gt; cache_miss();
		}

		&lt;span style=&quot;color:#59ff04;&quot;&gt;std::tuple&lt;/span&gt;&amp;lt;ValueType, &lt;span style=&quot;color:#56e092;&quot;&gt;boost::posix_time::ptime&lt;/span&gt;&amp;gt; &amp;amp; value_pack = it-&amp;gt;second;

		&lt;b&gt;auto&lt;/b&gt; should_be_after = &lt;span style=&quot;color:#56e092;&quot;&gt;boost::posix_time::second_clock::universal_time&lt;/span&gt;() - &lt;span style=&quot;color:#56e092;&quot;&gt;boost::posix_time::seconds&lt;/span&gt;(cache_aging_time);

		&lt;b&gt;if&lt;/b&gt; (&lt;span style=&quot;color:#59ff04;&quot;&gt;std::get&lt;/span&gt;&amp;lt;&lt;span style=&quot;color:#f67400;&quot;&gt;1&lt;/span&gt;&amp;gt;(value_pack) &amp;gt; should_be_after)
			&lt;b&gt;return&lt;/b&gt; &lt;span style=&quot;color:#59ff04;&quot;&gt;std::get&lt;/span&gt;&amp;lt;&lt;span style=&quot;color:#f67400;&quot;&gt;0&lt;/span&gt;&amp;gt;(value_pack);
		&lt;b&gt;throw&lt;/b&gt; cache_miss();
	}

	&lt;span style=&quot;color:#2980b9;&quot;&gt;void&lt;/span&gt; remove_cache(&lt;span style=&quot;color:#2980b9;&quot;&gt;const&lt;/span&gt; KeyType&amp;amp; k)
	{
		&lt;span style=&quot;color:#56e092;&quot;&gt;boost::unique_lock&lt;/span&gt;&amp;lt;&lt;span style=&quot;color:#56e092;&quot;&gt;boost::shared_mutex&lt;/span&gt;&amp;gt; l(m_mutex);

		base_type::erase(k);
	}

	ValueType get_cache(&lt;span style=&quot;color:#2980b9;&quot;&gt;const&lt;/span&gt; KeyType&amp;amp; key) &lt;span style=&quot;color:#2980b9;&quot;&gt;const&lt;/span&gt; &lt;b&gt;throw&lt;/b&gt;(cache_miss)
	{
		&lt;span style=&quot;color:#56e092;&quot;&gt;boost::shared_lock&lt;/span&gt;&amp;lt;&lt;span style=&quot;color:#56e092;&quot;&gt;boost::shared_mutex&lt;/span&gt;&amp;gt; l(m_mutex);

		&lt;b&gt;typename&lt;/b&gt; base_type::const_iterator it = base_type::find(key);

		&lt;b&gt;if&lt;/b&gt; (it == base_type::end())
		{
			&lt;b&gt;throw&lt;/b&gt; cache_miss();
		}

		&lt;span style=&quot;color:#2980b9;&quot;&gt;const&lt;/span&gt; &lt;span style=&quot;color:#59ff04;&quot;&gt;std::tuple&lt;/span&gt;&amp;lt;ValueType, &lt;span style=&quot;color:#56e092;&quot;&gt;boost::posix_time::ptime&lt;/span&gt;&amp;gt; &amp;amp; value_pack = it-&amp;gt;second;

		&lt;b&gt;auto&lt;/b&gt; should_be_after = &lt;span style=&quot;color:#56e092;&quot;&gt;boost::posix_time::second_clock::universal_time&lt;/span&gt;() - &lt;span style=&quot;color:#56e092;&quot;&gt;boost::posix_time::seconds&lt;/span&gt;(cache_aging_time);

		&lt;b&gt;if&lt;/b&gt; (&lt;span style=&quot;color:#59ff04;&quot;&gt;std::get&lt;/span&gt;&amp;lt;&lt;span style=&quot;color:#f67400;&quot;&gt;1&lt;/span&gt;&amp;gt;(value_pack) &amp;gt; should_be_after)
			&lt;b&gt;return&lt;/b&gt; &lt;span style=&quot;color:#59ff04;&quot;&gt;std::get&lt;/span&gt;&amp;lt;&lt;span style=&quot;color:#f67400;&quot;&gt;0&lt;/span&gt;&amp;gt;(value_pack);
		&lt;b&gt;throw&lt;/b&gt; cache_miss();
	}

	&lt;span style=&quot;color:#2980b9;&quot;&gt;void&lt;/span&gt; add_to_cache(&lt;span style=&quot;color:#2980b9;&quot;&gt;const&lt;/span&gt; KeyType&amp;amp; k , &lt;span style=&quot;color:#2980b9;&quot;&gt;const&lt;/span&gt; ValueType&amp;amp; v)
	{
		&lt;span style=&quot;color:#56e092;&quot;&gt;boost::unique_lock&lt;/span&gt;&amp;lt;&lt;span style=&quot;color:#56e092;&quot;&gt;boost::shared_mutex&lt;/span&gt;&amp;gt; l(m_mutex);

		base_type::erase(k);

		base_type::insert(&lt;span style=&quot;color:#59ff04;&quot;&gt;std::make_pair&lt;/span&gt;(k, &lt;span style=&quot;color:#59ff04;&quot;&gt;std::make_tuple&lt;/span&gt;(v, &lt;span style=&quot;color:#56e092;&quot;&gt;boost::posix_time::second_clock::universal_time&lt;/span&gt;())));
	}

	&lt;span style=&quot;color:#2980b9;&quot;&gt;void&lt;/span&gt; tick()
	{
		&lt;span style=&quot;color:#56e092;&quot;&gt;boost::upgrade_lock&lt;/span&gt;&amp;lt;&lt;span style=&quot;color:#56e092;&quot;&gt;boost::shared_mutex&lt;/span&gt;&amp;gt; readlock(m_mutex);
		&lt;span style=&quot;color:#59ff04;&quot;&gt;std::shared_ptr&lt;/span&gt;&amp;lt;&lt;span style=&quot;color:#56e092;&quot;&gt;boost::upgrade_to_unique_lock&lt;/span&gt;&amp;lt;&lt;span style=&quot;color:#56e092;&quot;&gt;boost::shared_mutex&lt;/span&gt;&amp;gt;&amp;gt; writelock;
		&lt;b&gt;auto&lt;/b&gt; should_be_after = &lt;span style=&quot;color:#56e092;&quot;&gt;boost::posix_time::second_clock::universal_time&lt;/span&gt;() - &lt;span style=&quot;color:#56e092;&quot;&gt;boost::posix_time::seconds&lt;/span&gt;(&lt;span style=&quot;color:#f67400;&quot;&gt;30&lt;/span&gt;);

		&lt;b&gt;for&lt;/b&gt; (&lt;b&gt;auto&lt;/b&gt; it = base_type::begin(); it != base_type::end(); )
		{
			&lt;span style=&quot;color:#2980b9;&quot;&gt;const&lt;/span&gt; &lt;span style=&quot;color:#59ff04;&quot;&gt;std::tuple&lt;/span&gt;&amp;lt;ValueType, &lt;span style=&quot;color:#56e092;&quot;&gt;boost::posix_time::ptime&lt;/span&gt;&amp;gt; &amp;amp; value_pack = it-&amp;gt;second;

			&lt;b&gt;if&lt;/b&gt; (&lt;span style=&quot;color:#59ff04;&quot;&gt;std::get&lt;/span&gt;&amp;lt;&lt;span style=&quot;color:#f67400;&quot;&gt;1&lt;/span&gt;&amp;gt;(value_pack) &amp;lt; should_be_after)
			{
				&lt;b&gt;if&lt;/b&gt; (!writelock)
					writelock.reset(&lt;b&gt;new&lt;/b&gt; &lt;span style=&quot;color:#56e092;&quot;&gt;boost::upgrade_to_unique_lock&lt;/span&gt;&amp;lt;&lt;span style=&quot;color:#56e092;&quot;&gt;boost::shared_mutex&lt;/span&gt;&amp;gt;(readlock));
				base_type::erase(it++);
			}
			&lt;b&gt;else&lt;/b&gt;
				it++;
		}
	}

&lt;b&gt;private&lt;/b&gt;:
	&lt;span style=&quot;color:#2980b9;&quot;&gt;mutable&lt;/span&gt; &lt;span style=&quot;color:#56e092;&quot;&gt;boost::shared_mutex&lt;/span&gt; m_mutex;
};

&lt;b&gt;template&lt;/b&gt;&amp;lt;&lt;b&gt;typename&lt;/b&gt; KeyType, &lt;b&gt;typename&lt;/b&gt; ValueType&amp;gt;
&lt;b&gt;class&lt;/b&gt; cache_map &amp;lt;KeyType, ValueType, &lt;span style=&quot;color:#f67400;&quot;&gt;0&lt;/span&gt;&amp;gt;
	: &lt;b&gt;protected&lt;/b&gt; &lt;span style=&quot;color:#59ff04;&quot;&gt;std::map&lt;/span&gt;&amp;lt;KeyType, ValueType&amp;gt;
{
	&lt;b&gt;typedef&lt;/b&gt; &lt;span style=&quot;color:#59ff04;&quot;&gt;std::map&lt;/span&gt;&amp;lt;KeyType, ValueType&amp;gt; base_type;
&lt;b&gt;public&lt;/b&gt;:

	ValueType&amp;amp; get_cache(&lt;span style=&quot;color:#2980b9;&quot;&gt;const&lt;/span&gt; KeyType&amp;amp; key) &lt;b&gt;throw&lt;/b&gt;(cache_miss)
	{
		&lt;span style=&quot;color:#56e092;&quot;&gt;boost::shared_lock&lt;/span&gt;&amp;lt;&lt;span style=&quot;color:#56e092;&quot;&gt;boost::shared_mutex&lt;/span&gt;&amp;gt; l(m_mutex);

		&lt;b&gt;auto&lt;/b&gt; it = base_type::find(key);

		&lt;b&gt;if&lt;/b&gt; (it == base_type::end())
		{
			&lt;b&gt;throw&lt;/b&gt; cache_miss();
		}

		&lt;b&gt;return&lt;/b&gt; it-&amp;gt;second;
	}

	&lt;span style=&quot;color:#2980b9;&quot;&gt;void&lt;/span&gt; add_to_cache(&lt;span style=&quot;color:#2980b9;&quot;&gt;const&lt;/span&gt; KeyType&amp;amp; k , &lt;span style=&quot;color:#2980b9;&quot;&gt;const&lt;/span&gt; ValueType&amp;amp; v)
	{
		&lt;span style=&quot;color:#56e092;&quot;&gt;boost::unique_lock&lt;/span&gt;&amp;lt;&lt;span style=&quot;color:#56e092;&quot;&gt;boost::shared_mutex&lt;/span&gt;&amp;gt; l(m_mutex);

		base_type::erase(k);

		base_type::insert(&lt;span style=&quot;color:#59ff04;&quot;&gt;std::make_pair&lt;/span&gt;(k, v));
	}

	&lt;span style=&quot;color:#2980b9;&quot;&gt;void&lt;/span&gt; tick()
	{
	}

&lt;b&gt;private&lt;/b&gt;:
	&lt;span style=&quot;color:#2980b9;&quot;&gt;mutable&lt;/span&gt; &lt;span style=&quot;color:#56e092;&quot;&gt;boost::shared_mutex&lt;/span&gt; m_mutex;
};
&lt;/pre&gt;
</content>
 </entry>
 
 <entry>
   <title>免费版的 PrimoCache</title>
   <link href="https://microcai.org/2017/02/10/fix-slow-hdd-aka-free-version-of-PrimoCache.html"/>
   <updated>2017-02-10T00:00:00+00:00</updated>
   <id>https://microcai.org/2017/02/10/fix-slow-hdd-aka-free-version-of-PrimoCache</id>
   <content type="html">&lt;p&gt;去年DIY一个 NAS. 其实就是台 MINI PC. 自带一个很小的 mSATA SSD 用来装系统, 然后外接了一个大 HDD 做存储.&lt;/p&gt;

&lt;p&gt;问题在于, 这个 大 HDD 是渣机械, 一开迅雷, 硬盘就被 100% 占用! 然后导致整个 OS 响应迟缓. 然后因为 HDD 写入速度不足, 导致迅雷下载速度 0 . 磁盘疯狂写入.&lt;/p&gt;

&lt;p&gt;后来下了一个 PrimoCache ( 以前叫 FancyCache ) 后, 问题解决了.&lt;/p&gt;

&lt;p&gt;然而只有 60 天试用期. 60天过去了, 我又得回到原来的老状态了吗? 非也!
我发现了一个叫 StarWind 的 iscsi software target. 只要装了 StarWind 就可以把 PC 变成一个 iscsi 的目标存储设备.
接着, 因为装的是 Windows Server 2012 R2 系统, 所以是自带了 iscsi 发起程序的. 于是把 127.0.0.1 的目标连起来.
于是写入路径就变成了 本地磁盘 D -&amp;gt; windows iscsi -&amp;gt; 本地网络 -&amp;gt; StarWind -&amp;gt; 原本地磁盘 D&lt;/p&gt;

&lt;p&gt;虽然经过了多道程序的折腾, 但是因为 StarWind 提供了 写入缓存!!! 于是, 迅雷又不卡了.&lt;/p&gt;

&lt;p&gt;StarWind 有免费版哦! 可以一直免费用下去, 没有试用期限制, nice!&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>通知到轮询线程</title>
   <link href="https://microcai.org/2017/01/15/pull-work-with-notify.html"/>
   <updated>2017-01-15T00:00:00+00:00</updated>
   <id>https://microcai.org/2017/01/15/pull-work-with-notify</id>
   <content type="html">&lt;p&gt;在腐都工作也有大半个月了。工作过程中，遇到了一个轮询+通知的消息模式。所以要轮询，是因为通知是不可靠的。
所以要通知，是因为轮询是不及时的。既要保证及时，又要保证可靠，就只能轮询和异步通知一起上。&lt;/p&gt;

&lt;p&gt;因为异步通知的时候，会把轮询需要获得的状态一并携带上了。所以，获得通知后只是取消定时器，让轮询线程立即唤醒干活肯定是。。。 可以的但是有点浪费。如果在异步通知线程里直接调用处理呢，就要把处理的东西从轮询的线程里扣出来，这样轮询的代码就不直观了。&lt;/p&gt;

&lt;p&gt;因为使用的是 asio 的 stackless coroutine。&lt;/p&gt;

&lt;p&gt;不过，如果改造下 timer,  async_wait 的时候是可以2个返回值的回调呢？ 一个 ec 一个 通知的消息呢？&lt;/p&gt;

&lt;p&gt;于是只要在 yield timer.async_wait 下面判断下 ec ，然后 yield async_pull &lt;br /&gt;
如果 ec = aborted 那就不执行 yield async_pull . 直接把第二个参数看成是 async_pull 返回的。&lt;/p&gt;

&lt;p&gt;这样就可以在不改动轮询代码的情况下，插入了异步通知。&lt;/p&gt;

&lt;p&gt;结果伪造代码表示如下下&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error_code&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;some_type_of_pull&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pulled_message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;reenter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;is_timeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 超时后不再轮询&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;expires_from_now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pull_interval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;async_wait&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;
			
			&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;n&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;async_pull_message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;object_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ec&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;opertion_aborted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;c1&quot;&gt;// 强行没错误！&lt;/span&gt;
				&lt;span class=&quot;n&quot;&gt;ec&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
		
			&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;c1&quot;&gt;// 处理轮询消息&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;		
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;	
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wake_up&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;some_type_of_pull&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wake_up&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;因为 timer 的回调的第二个参数是得万能类型，所以 timer 其实是个模板类。&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TimerType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;smart_timer&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;public:&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;explicit&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;smart_timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;expires_from_now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;m_timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;expires_from_now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;async_wait&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BOOST_ASIO_MOVE_ARG&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unique_lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mutex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;no_wait&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;no_wait&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bind_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;make_error_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;operation_aborted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()));&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

		&lt;span class=&quot;n&quot;&gt;m_handler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;handler_set&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;m_timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;async_wait&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;smart_timer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handle_timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;placeholders&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wake_up&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unique_lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mutex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler_set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;handler_set&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bind_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;make_error_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;operation_aborted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error_code&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ignore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;m_timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ignore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;no_wait&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;private:&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handle_timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error_code&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;unique_lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mutex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ec&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;operation_aborted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;c1&quot;&gt;// check for wake_up parameter.&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;handler_set&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bind_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;make_error_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;interrupted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()));&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;private:&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;io_service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;io&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;TimerType&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_timer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler_set&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;no_wait&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error_code&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mutex&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在 coroutine 里， timer 的定义呢，是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;smart_timer&amp;lt;boost::asio::steady_timer, _some_type_of_pull&amp;gt;_&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;这样就可以了。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Yaoi-city-here-I-come</title>
   <link href="https://microcai.org/2016/12/27/Yaoi-city-here-I-come.html"/>
   <updated>2016-12-27T00:00:00+00:00</updated>
   <id>https://microcai.org/2016/12/27/Yaoi-city-here-I-come</id>
   <content type="html">&lt;p&gt;腐都我来啦!&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>TCP拥塞控制</title>
   <link href="https://microcai.org/2016/12/27/TCP-conguestion.html"/>
   <updated>2016-12-27T00:00:00+00:00</updated>
   <id>https://microcai.org/2016/12/27/TCP-conguestion</id>
   <content type="html">&lt;p&gt;BBR 为啥要对包的发送时机进行调度呢? 是为了防止缓存堆积.
比如对方的接收速率是  1MB/s . 你的本地发送速率可以达到  1.1GB/s . 
RTT = 500ms, 窗口大小是  500KB. 
好,  现在有  500KB 的数据要发送, 那么你是一次性发出呢? 还是把  500KB 分到 0.5s 的时间内匀速发出?
500KB 是  500 个包. 在 10Gbps 的本地链路上, 你只要 0.5ms 就全部发出去了.
而对方是 1MB/s 的速度, 所以需要  500ms 的时间接收.
这一速率不匹配, 是靠 中间设备的  “缓存” 来吸收的震荡.
在 BBR 出现前, 都是这么做的,  就是窗口大小的数据, 一股脑的全丢出去了.
靠链路上的各种交换机和路由器的缓存来吸收震荡.
但是 BBR 不这么干 .
BBR 认为, 既然带宽只有  1MB/s 而你的窗口 500KB, 要发 500KB 数据过去, 就是要把 500KB 均匀的铺到 1MB 里.&lt;/p&gt;

&lt;p&gt;控制本地发送的频率.
哪怕你本地是 10Gbps 的网卡. 你也要把这 500KB 的数据个均摊到 500ms 的时间内发.
我认为 这是  BBR 对传统拥塞控制算法的最大改进.&lt;/p&gt;

&lt;p&gt;这个改进在 BBR 里是通过算法同时输出 Pacing Rate 和 cwin 实现的.
BBR 对 Linux 的TCP栈的巨大改进之二是让拥塞控制算法彻底接管 TCP 状态机. Linux 的 TCP 拥塞控制实现里, 拥塞算法只用来探测增窗, 一但丢包减窗逻辑就被内建的 PRR 算法接管. 而 BBR 则改变了之前的做法, 拥塞算法不仅仅控制窗口大小, 也控制发送时机.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>使用邻居的CMCC宽带加速</title>
   <link href="https://microcai.org/2016/12/07/use-neighbor-CMCC.html"/>
   <updated>2016-12-07T00:00:00+00:00</updated>
   <id>https://microcai.org/2016/12/07/use-neighbor-CMCC</id>
   <content type="html">&lt;p&gt;基于某些特殊原因，我知道邻居家的wifi密码。&lt;/p&gt;

&lt;p&gt;但是他家信号在我家虽然能收到，但是比较弱。所以一直没用。最近从newbie获赠了一个叫 Ubnt AirMAX 的山寨产品，有个定向天线。
这使得我可以在家接收邻居家的信号了。于是就开始用啦！&lt;/p&gt;

&lt;p&gt;先上图。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/fakeairmax.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这是在阳台装好的效果。这货还支持 POE 供电，非常不错，网线到就可以干活了。&lt;/p&gt;

&lt;p&gt;邻居家使用的是CMCC的网络，这是我要连上他家网络的主要原因。因为据说 CMCC 的出国带宽好啊! 而我呢，则是重度的墙外网络使用者。
所以想用他家的网络来加速我的国外访问。&lt;/p&gt;

&lt;p&gt;连接的办法是 ERX -&amp;gt; POE交换机 -&amp;gt; 山寨网桥。&lt;/p&gt;

&lt;p&gt;POE 交换机是网管交换机，所以划了单独 VLAN 给网桥。我用了 port 2 接的网桥，划分了 vlan 3389, pvid = 3389 。 由于我用的 port 7 接的 ERX。port 7 做 trunk 端口，允许通过  3389 即可。 ERX 的 eth2是接入的交换机，所以在 eth2 上创建 vlan 3389 即可。在 eth2.3389 接口上配置 ip 地址，静态 192.168.100.2 。因为邻居家是  192.168.100.0/24 的网络嘛。&lt;/p&gt;

&lt;p&gt;然后在 ERX 上 ping 192.168.100.1 成功。意味着能连上邻居的路由器了。&lt;/p&gt;

&lt;p&gt;接着为 eth2.3389 接口开 NAT 伪装。这是必要的。因为我不想在邻居家的路由器上配置静态路由表 —— 因为他家的路由器并不支持动态路由协议 —— 当然我也不希望我家内部的网络被他访问到。&lt;/p&gt;

&lt;p&gt;接下来才是重点。我需要做策略路由, 要求如下：&lt;/p&gt;

&lt;p&gt;对所有 CMCC 的ip地址，通过他家访问。
对部分 CMCC 访问更快的朝外地址，用他家访问。
但是如果他家的网络不通了，以上策略路由要立即 fallback 到使用我自己家的网络。
只对 NAS 直接执行 1:1 负载均衡做宽带叠加，把 CMCC 和我家的宽带进行叠加。因为 NAS 上跑迅雷下载，需要大带宽。
对其他不需要 CMCC 加速的地址，使用 1:1 宽带叠加我双拨的2条PPPOE线路。
但是对于网银地址，不使用叠加（防止 ip 跳动导致的登录问题）。&lt;/p&gt;

&lt;p&gt;于是，折腾了一下 ERX 的 firewall 规则，搞定。&lt;/p&gt;

&lt;p&gt;基本思路是，使用 ERX 自带的 load-balance 功能。 load-balance 建立3个组，叫 A  B C 吧。&lt;/p&gt;

&lt;p&gt;A 组，包含 pppoe0 pppoe1 两条线路，做负载均衡。
B 组，包含 eth2.3389 和 pppoe0 两条，pppoe0 做 fallback-only。
C 组，包含 pppoe0 pppoe1 eth2.3389 三条线路，做负载均衡。&lt;/p&gt;

&lt;p&gt;关键规则如下：&lt;/p&gt;

&lt;p&gt;对 src 为 NAS 的流量，使用 C 组。&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;rule&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;modify&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;use CMCC for NAS&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;modify&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;lb-group&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;address&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;100.64&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.10&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;对 dst 为 cmccip 的线路，使用 B 组。&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;rule&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;70&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;modify&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;use CMCC for cmcc ip&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;destination&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
             &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;network-group&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;cmcc-ip&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;modify&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;lb-group&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;防止银行被负载均衡&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;rule&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;modify&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;do not load blance on some site&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;destination&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
             &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;network-group&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;bank&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;modify&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;默认做负载均衡&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;rule&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;999&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;modify&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;modify&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
         &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;lb-group&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样就搞定了。只要把相应的 IP 加到 network-group 里就可以了。
我是用的 http://bgp.he.net/AS9808 获取的 移动的 IP 段。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>错误配置的路由器</title>
   <link href="https://microcai.org/2016/12/06/misconfigured-router.html"/>
   <updated>2016-12-06T00:00:00+00:00</updated>
   <id>https://microcai.org/2016/12/06/misconfigured-router</id>
   <content type="html">&lt;p&gt;企业专线宽带通常会分配多个固定IP，一般是 4 个。也有的是 8 个。如何使用这多个IP地址也是一门学问。&lt;/p&gt;

&lt;p&gt;我见过的多数企业，对于多个IP的使用基本上就2个模式。&lt;/p&gt;

&lt;p&gt;模式1：只使用一个。（通常负责人还会抱怨给那么多地址干嘛，一个就够了，多的不要了，给降价吧！）&lt;/p&gt;

&lt;p&gt;模式2：作为NAT地址池。提高大并发下的NAT效率。（这个负责人通常遇到上面那种人会觉得对方没文化，企业上网人数多，NAT端口不够用，自然要多个IP来平衡。）&lt;/p&gt;

&lt;p&gt;在这2个模式下，如果企业还要搭建公网服务，则是在路由器上使用 DNAT。多个服务器的话就会把多个IP拿来做 1:1  映射。就是 ip1 的流量全部转给服务器1, ip2 的全部转给服务器2。这个叫 1:1 NAT。&lt;/p&gt;

&lt;p&gt;其实这就是把在家用 TP-LINK 的时候的经验套到了企业网络管理中导致的。&lt;/p&gt;

&lt;p&gt;正确的做法是，把公网地址直接配置到服务器上去。剩下的配置给路由器做NAT池带办公区的电脑上网。&lt;/p&gt;

&lt;p&gt;如果服务器数量比较多，就多购买IP，每个IP的费用其实相当便宜（对拉的起企业专线的公司而言），并不会比服务器消耗的电费更多呢。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>中国电信原生IPv6配置</title>
   <link href="https://microcai.org/2016/11/29/telecom-ipv6.html"/>
   <updated>2016-11-29T00:00:00+00:00</updated>
   <id>https://microcai.org/2016/11/29/telecom-ipv6</id>
   <content type="html">&lt;p&gt;目前电信已经商用 ipv6 网络了，ipv6 的好处你懂得。&lt;/p&gt;

&lt;p&gt;在继续之前，首先讲下，ipv6 同 ipv4 在配置上的不同。&lt;/p&gt;

&lt;p&gt;对于家庭宽带，ipv4 是 pppoe 拨号的时候自动配置的。isp 给且仅给一个 ipv4 地址。
如果有多台设备需要上网，就需要使用一种叫 NAT 的技术进行网址共享。&lt;/p&gt;

&lt;p&gt;但是 ipv6 地址有 128位那么长，世上每一粒沙子都能分配一个 ipv6 地址，意思就是分配不完的！
所以，运营商就很大方的给给你许多许多的 ipv6 地址了，也就不需要 NAT 啦！&lt;/p&gt;

&lt;p&gt;由于不需要 NAT，因此在使用路由器共享上网的时候，配置就变得不同了。
使用 ipv4 的时候，路由器负责拨号，然后使用它自己的 DHCP服务器给局域网里的其他机器分配一个私有地址。
而到了 ipv6 的时候，情况就有所不同了，因为 ipv6 没有 NAT ，自然局域网里的机器并不需要也不能分配私有地址，而是需要获得一个全球唯一的地址。但是路由器也不是随便给你分配一个地址就行的，随机生成的地址并不一定能保证唯一性，而且也无法路由。isp 怎么能帮你路由一个随机的地址呢？
所以到了 ipv6 的时候，配置办法就变了一下。首先，路由器从 isp 获得 ipv6 地址，这个地址还是老办法，dhcp 自动获得。所以是 dhcp 从 pppoe 自动获得了 v4 和 v6 地址。这个没什么不同。
但是接下来就有不同点了。&lt;/p&gt;

&lt;p&gt;接下来，LAN 口的地址配置开始不同了。 ipv4 是固定一个私有的 LAN 口地址，而  ipv6 则使用 dhcp-pd 这个技术从 isp 获得一个 公网地址。因为一般来说一个接口发 dhcp 请求是为了给自己获取地址，而这次，是给别的接口获得地址，所以这个叫 delegate 。又因为，isp 回给你的呢，是一个网段，而不是一般的dhcp给你一个地址。所以这个叫 prefix delegate.&lt;/p&gt;

&lt;p&gt;isp 给你一个网段，对电信来说，是给的一个 /64 的网段。在这个网段里，路由器自己随便 pick 一个主机地址，然后其余的就都可以做 pool 分配给 LAN 里的主机了。&lt;/p&gt;

&lt;p&gt;因此，配置过程就是，首先 pppoe 拨号，接着 dhcp 获得 v6 地址，然后 dhcp-pd 为 LAN 获得地址。&lt;/p&gt;

&lt;p&gt;但是，dhcp-pd 并不能为 LAN 内的主机获得地址。 LAN 内的主机如何获得地址呢？&lt;/p&gt;

&lt;p&gt;答案是 radvd。radvd 的作用是，启动的时候找到 LAN 的地址，然后就知道 LAN 的网段了。然后把这个网段当 pool 给局域网的主机分配。为啥这样能工作呢？
因为 LAN 所在的那个网段， isp 已经分配给你的路由器了。意味着只要是那个网段的地址， isp 都会给路由到你的路由器由你的路由器再做下一跳转发。因此只要你局域网内的机器和路由器的 LAN 口在一个网段即可。&lt;/p&gt;

&lt;p&gt;那么，配置的办法其实很简单， 就是&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ubnt@ubnt:~&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;configure
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;edit]
ubnt@ubnt# &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;interfaces ethernet eth0 pppoe 0 ipv6 address autoconf
ubnt@ubnt# &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;interfaces ethernet eth0 pppoe 0 dhcpv6-pd rapid-commit &lt;span class=&quot;nb&quot;&gt;enable
&lt;/span&gt;ubnt@ubnt# &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 prefix-length /60
ubnt@ubnt# &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 interface switch0 prefix-id 1
ubnt@ubnt# &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 interface switch0 host-address ::7788
ubnt@ubnt# &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 interface switch0 service slaac
ubnt@ubnt# commit&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; save&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;exit
&lt;/span&gt;ubnt@ubnt:~&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;reboot
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>高速转发是如何实现的</title>
   <link href="https://microcai.org/2016/11/26/how-non-blocking-forwarding-achieved.html"/>
   <updated>2016-11-26T00:00:00+00:00</updated>
   <id>https://microcai.org/2016/11/26/how-non-blocking-forwarding-achieved</id>
   <content type="html">&lt;p&gt;以太网发展到如今，已经出现了 400Gbps 的传输速度了。在这个传输速度下，实现无阻塞转发，需要达到的转发性能要达到 579.2Mpps x 端口数，要实现无阻塞转发所需要的背部带宽达到 800Gbps x 端口数。没有任何已知的系统总线能提供如此大的带宽。&lt;/p&gt;

&lt;p&gt;那么，高速转发是如何实现的呢？&lt;/p&gt;

&lt;p&gt;首先，将问题分解，一次转发分解成多个动作，然后看这些动作如何实现。&lt;/p&gt;

&lt;p&gt;第一，网络接口收到数据包。这里，计算机主机的做法是使用中断进行通知。然而路由器是专门的硬件，它没有除了转发数据包以外的任务。因此它不需要使用中断。而是直接CPU不停的轮询。不仅仅如此，它为每一个端口分配一个CPU。为了降低成本，提高速度，这个专门的干活的CPU通常是一块 ASIC 而不是一块通用 CPU。这个专门的芯片就叫转发引擎吧。他就是专门设计的一个只用来干包转发这一个活的CPU。这样就解决了发收包的瓶颈。&lt;/p&gt;

&lt;p&gt;第二，对每个收到的包，要进行查表，决定转发的目的端口。对交换机来说，是以 MAC 地址查表，对三层交换机或路由器来说，是以IP地址进行查表。那么查表速度就成了关键。对内存来说，他的输入是内存地址，输出是该地址的内容，但是，有另一种内存，叫 CAM , Content Addressable Memory，它的输入是内容，输出是内容的key。CAM 能实现硬件查操作，硬件单周期查表。不过 IP 地址和 MAC 地址有些许不同， MAC 地址使用的是精确匹配，而IP地址是用的最长前缀匹配。所以三层转发需要使用 TCAM。 TCAM 就是 三态CAM, 除了 0 和 1 还有个比较状叫 Don’t Care。这样 TCAM 的匹配结果是有多个的，但是只有第一个匹配结果会输出，只要匹配内容预先按照掩码长度排序就可以实现最长前缀匹配了。&lt;/p&gt;

&lt;p&gt;完成查表，知道了目的转发端口后，这个数据就该通过 内部背板 发送到另一个端口了。最初背板带宽是一个总线结构，但是随着端口速率的上升，总线结构的背板早就无法支撑交换机需要的巨大带宽了。每个端口两两互联，倒是能解决了带宽问题，但是这意味着端口连线太多。&lt;/p&gt;

&lt;p&gt;因此现在路由器使用的是 “交换式背板”。结构如下图所示：&lt;/p&gt;

&lt;p&gt;交换式背板是一块纯电路。在每个交叉的位置使用了开关晶体管控制通断。从图片上可以看到 1 2 3 4 发和  1 2 3 4 收可以通过开关实现完全独立的互连。注意这里的 发和收是编号一样的。1 号发和1号收其实就是1号端口。&lt;/p&gt;

&lt;p&gt;这样通过开关控制，所有的端口都可以在需要的时候建立点对点连接。由于是点对点链接，因此互联线路的带宽实际上并不会比以太网更高。设计难度就大大降低了。&lt;/p&gt;

&lt;p&gt;然后到了发送端口，发送端口再根据 QoS 进行排队然后发出或者列队太长就丢弃。&lt;/p&gt;

&lt;p&gt;这样，就可以完全在现有的技术条件下，完成超高速的交换。看起来几百 Tbps 的背板带宽实际上不如 PCIE x16 更有难度。&lt;/p&gt;

&lt;p&gt;转发决策是分布式的，每个端口一个，因此每个转发引擎并不感受巨大的压力。同时压力最大的差路由表又通过 TCAM 硬件实现快速查找。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>死在以太网发展道路上的网们</title>
   <link href="https://microcai.org/2016/11/24/the-networks-that-died-in-the-hands-of-etherenet.html"/>
   <updated>2016-11-24T00:00:00+00:00</updated>
   <id>https://microcai.org/2016/11/24/the-networks-that-died-in-the-hands-of-etherenet</id>
   <content type="html">&lt;p&gt;记得 Robert Metcalf（以太网的发明者）说过如果有技术最终替代了以太网，那么它还是会被叫做“以太网” ，所以以太网永远不会消亡。&lt;/p&gt;

&lt;p&gt;以太网，诚如它所言，真的像火凤凰一样，不断的涅槃重生，从最初的 3Mbps DIX 以太网到最新的  400Gbps 以太网。历经 10Mbps 100Mbps 1000Mbps 10Gbps 40Gbps 100Gbps 的磨练，一次次的幻化重生。然而，都叫以太网。并在这不断的重生中，一路排除掉了全部的竞争对手。&lt;/p&gt;

&lt;p&gt;以太网到底凭什么，有如此顽强的生命力，每一次重生都宣布着对手的永久性死亡。先不忙着下结论，让我们看看， 以太网每次重生，都干掉了那些对手吧。&lt;/p&gt;

&lt;p&gt;以太网初生的时候，有个重量对手， ARCnet。ARCnet 提供了 10Mbps 的总线结构的连接。所有接到同轴电缆上的机器共享 10Mbps 的带宽。这点和以太网是一致的。不一致的地方在于以太网使用 CSMA/CD 在同轴线上竞争使用网络，而ARCnet则使用令牌决定谁来发送数据，减少了冲突，在主机较多的情况下效率更高。因此最初和以太网的较量中，还略占优势。得益于ARCnet的协议简单，其接口控制卡的售价也低于以太网。因此在竞争中占据了优势。&lt;/p&gt;

&lt;p&gt;但是优势很快就消失了，因为基于 非屏蔽双绞线 的以太网诞生了。非屏蔽双绞线大幅降低布线成本。同时网桥的出现隔离了冲突域。而且在2对双绞线上传输能同时实现首发，也就是实现了全双工通信。这些优点使得ARCnet的竞争中败下阵来。&lt;/p&gt;

&lt;p&gt;80 年代末的令牌网已经能提供 16Mbps 的产品同以太网较量。令牌网不仅速度快于以太网，而且由于以太网使用CSMA/CD导致冲突发生时效率地下，不管怎么看以太网都处于竞争劣势。但是拯救以太网的“交换机”神奇的出现了。交换机隔离了冲突域，每台主机同交换机之间都能独享这一条链路上的带宽。由于取消了总线结构，星型结构使得布线和移动网络端口都变得轻而易举。而且容易定位网络错误。使得以太网相对竞争者提供了更好的优势。更重要的是，它保护了现有的投资。只要新增交换机，就可以提升网络质量，而不需要大飞周张的更换设备。&lt;/p&gt;

&lt;p&gt;接着，快速以太网适时发布。100Mbps 的速率即使放到今天都不显得缓慢呢！更重要的是， 100Mbps 的以太网提供了绝佳的向后兼容性。得益于交换机的使用，企业可以混用 100M 和 10M 的设备组网。这样大大的降低了升级的成本。不仅现有的以太网要升级，而且连使用令牌网的用户也跟着转移过来了。&lt;/p&gt;

&lt;p&gt;没过多久，千兆以太网标准化完成。同样提供了向后兼容性。 1000M 100M 和 10M 的设备可以在网络里和谐共存。自此，以太网在局域网领域干掉了所有的竞争对手。&lt;/p&gt;

&lt;p&gt;但是，以太网还有更大的野心，刚掉广域网的竞争对手。他首先干掉了 FDDI。 FDDI 使用光纤介质，提供 100Mbps 的速率。既可以作为局域网，也可以做广域网。而 100Mbps 的光纤以太网的出现把 FDDI 消灭了。如果我在局域网里可以使用以太网，为啥我不可以直接把局域网扩展到更大的范围呢？ 与其使用额外的设备，不如继续使用以太网。如果使用其他协议，就不能和现有的以太网互通了。&lt;/p&gt;

&lt;p&gt;在局域网占据垄断地位的以太网，当他要开发超过100M的通信介质，就把速率低于 以太网的光纤网络一个一个的消灭了。SDH 但是能提供155 Mbit/s 的速度，但是在千兆光纤以太网面前又如何能抵挡。传统的光纤通信格式在以太网面前不断沦陷。&lt;/p&gt;

&lt;p&gt;SONET/SDH 和 ATM 被寄予厚望。毕竟 SONAT 能提供更快的速率。但是在万兆以太网和十万兆以太网的夹击下，市场越来越小。 与此同时，IP 网络的发展也推波助澜。传统上的电信网络，是为了支持“电话” 这一目的，用来跑 IP 包，反而有种杀鸡用牛刀的感觉，成本上无法敌过直接使用以太网作为IP网的承载。当 40G/100G 以太网完成标准化的时候，传统电信网络的木日就到来了。毕竟，对运营商来说，电话越来越少了，IP业务越来越多了。使用更适合承载IP网的技术不是更好的选择吗？ 最后连电话都使用 SIP 网关，运营商改用IP网承载电话业务。&lt;/p&gt;

&lt;p&gt;于是以太网在骨干网全面替代其他对手。以太网完成了局域网和广域网的统一。干掉了一切竞争对手。&lt;/p&gt;

&lt;p&gt;以太网不仅仅在有线通信领域干掉了一切对手，在无线领域依然在逐步蚕食。移动网络的绝大部分流量其实并不是通过 4G 3G 承载的，而是通过的 WiFi。WiFi，是以太网的在无线领域的延伸。WiFi在 MAC层依然使用以太网帧，只是在物理电气层，应用无线替代了有线。正如光纤以太网一样，只是在物理电气层使用了激光代替了电压。甚至 WiFi 使用的共享方式，也是 CSMA/CA 协议。是针对无线信道的特点而对 CSMA/CD 协议的优化。&lt;/p&gt;

&lt;p&gt;WiFi 通常运行于 ISM 频道，但是运行于非ISM频段的 wifi，就摇身一变成了 WiMAX，成为 3G 标准。WiMAX-Advanced 也进入了 4G 标准。虽然在电信行业的联合绞杀下败于 LTE。但是，以太网还会回来的。&lt;/p&gt;

&lt;p&gt;===&lt;/p&gt;

&lt;p&gt;说了那么多历史，以太网为啥能打败竞争对手脱颖而出呢？&lt;/p&gt;

&lt;p&gt;以太网在局域网里打败对手，能学到的理由是，简单协议，足够的性能，低廉的价格, 还有 不断的进步，保持兼容。&lt;/p&gt;

&lt;p&gt;当以太网拿下局域网，扩张到广域网又是如何进行的？ 学到的理由是，建立根据地，农村包围城市。根据地的意思是，有了局域网这个市场，产品有产量保障，而不会因为无人问津产量稀少导致价格高企。
农村包围城市，是因为 骨干网的末端都是一个个的局域网，当所有的局域网都是基于以太网的时候，把广域网纳入以太网就变得顺理成章了。&lt;/p&gt;

&lt;p&gt;同时，抛开硬件不谈，以太网还有一个软件层面的巨大优势： 以太网适合承载IP网。
选对队友真的很重要！&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>脑洞：可编程网卡</title>
   <link href="https://microcai.org/2016/11/21/network-shader.html"/>
   <updated>2016-11-21T00:00:00+00:00</updated>
   <id>https://microcai.org/2016/11/21/network-shader</id>
   <content type="html">&lt;p&gt;很久以前，网卡就只是个物理协议转换装置。各种数据的处理都是靠的 CPU。随着网卡支持的速率的提升，还有人们对效率的追求，网卡开始支持将一些操作给 offload 到网卡上执行。比如checksum的计算。再后来，连 TCP 这样的协议都可以直接交给网卡处理。叫 TCP offload。&lt;/p&gt;

&lt;p&gt;网卡能 offload 的东西越多，高网络负载下的 cpu 使用率就越低。数据处理也就越有效率。
既然连 TCP 都能交给网卡处理了，为何不更进一步实行通用 offload 呢？&lt;/p&gt;

&lt;p&gt;通用 offload 的实现，可以参考显卡的通用可编程渲染管线。搞一个通用网络包处理管线。指定个shader语言，就叫 network shader吧。把 network shader 载入网卡，就可以让网卡执行 network shader 处理收到的数据包。对于太复杂的逻辑，再转给 CPU 上的代码处理，简单的逻辑就可以直接片上处理完成。这对很多 “数据包转发类“ 的引用程序（各种Proxy）来说，可以极大的提升性能。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>路由口和交换口</title>
   <link href="https://microcai.org/2016/11/20/ports-on-router.html"/>
   <updated>2016-11-20T00:00:00+00:00</updated>
   <id>https://microcai.org/2016/11/20/ports-on-router</id>
   <content type="html">&lt;p&gt;很久以前，我买路由器非常关心路由器有几个口。但是我最终发现，其实路由器都只有2个口。
一个 WAN 口一个LAN口。哪怕是一些双WAN的路由器，其实也只有3个口，2个WAN口一个LAN口而已。&lt;/p&gt;

&lt;p&gt;至于你看到的4个LAN口，那不是口，那只是内置了一个交换机。&lt;/p&gt;

&lt;p&gt;说道这，就得科普一下 路由口和交换口 的区别。&lt;/p&gt;

&lt;p&gt;所谓交换口，就是一个2层可以通的RJ45口。而路由口，才是可以配置 IP 地址的口,路由口才能做三层转发.&lt;/p&gt;

&lt;p&gt;一般路由器只有2个口，一个WAN口，DHCP获取或者PPPOE获取，一个 LAN 口，固定ip，并可以为下面的机器提供 DHCP。 LAN 口通过交换机扩展出多个交换口。&lt;/p&gt;

&lt;p&gt;交换口是不值钱的。一个 8LAN 口的和 4 LAN 口的路由器，成本差距微乎其微额。因为本质上这个路由器只是双口路由器，只是内置的交换机4口还是8口的区别。&lt;/p&gt;

&lt;p&gt;而交换机，这种口这么少的，是不值钱的。不信自己上马云家找找看。几十块钱就能买到8个口的交换机。还是8个千兆口的。8LAN口的通常还只是百兆口。&lt;/p&gt;

&lt;p&gt;路由口才值钱。真的路由器，上有5个口，才真的是贵的很呢。&lt;/p&gt;

&lt;p&gt;在路由器市场，杀出了一个叫 Ubnt 的公司，简直就是杀价神，小米在它眼里简直不知道 low 到哪里去了。&lt;/p&gt;

&lt;p&gt;他最新被众人注意到，是因为推出了一个 3 口路由器 ER-Lite。注意我上面说的路由口和交换口的区别。这个是3个路由口的路由器。还支持千兆线速转发。价钱居然不到一百美金。简直是价格屠夫。&lt;/p&gt;

&lt;p&gt;但是这不是重点，重点是没多久它又推出了一个5口路由器 ERX 。价格 49美刀！
注意是5个路由口哦！ ER-PRO 也有5 个口，但是其实后面3个口是在一个交换机上的，本质是3个路由口。&lt;/p&gt;

&lt;p&gt;哦，为啥比3口的还便宜，因为虽然有5个路由口，但是包转发率下来了。虽然下来了，但是如果发的是大包而不是小包，那跑千兆还是绰绰有余的。只是跑小包就达不到千兆了，也就是所谓的无法线速转发。&lt;/p&gt;

&lt;p&gt;但是家庭使用，在需要速度的地方（NAS，迅雷下载），必定是大包转发。所以，就是个49美元能买到的5个路由口的机器啊！简直是价格屠夫。要知道老 思科 那些百兆的5口路由器二手都不止 49美元呢！虽然能线速百兆，问题是也才百兆而已。&lt;/p&gt;

&lt;p&gt;虽然说有5个口，意味着5个网络，家里一般就一个 wan 一个 lan ，哪里用得到5个？
抛开有的人要双拨，然后不同设备组到不同的网段里不谈，大部分人其实只需要双口路由器的。
这种情况下，他也提供了把任意的口划到一个交换组里的功能。 划到交换组里的口就不能设置 ip地址了，不过可以统一给这个交换组设置一个ip。划到交换组里的口，进行二层包转发的时候，也是会使用独立的交换芯片进行，不占用cpu的资源。&lt;/p&gt;

&lt;p&gt;物理上这是个单芯片的路由器，但是逻辑上，芯片上除了cpu，还集成了5个网卡和一个交换机的。 5个 RJ45 的 phy 接口可以随意的配置为接入交换机或者接入网卡。所以接入交换机后，就不受他的标称包转发速率的影响的，其实也是个线速转发的5口交换芯片。&lt;/p&gt;

&lt;p&gt;不过这个价钱你肯定不会把他当个交换机的，当然是5个网段搞起咯。扩展端口什么的，用个额外的交换机就可以啦！&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>soho路由器是单臂路由器</title>
   <link href="https://microcai.org/2016/11/14/soho-router-is-one-armed-router.html"/>
   <updated>2016-11-14T00:00:00+00:00</updated>
   <id>https://microcai.org/2016/11/14/soho-router-is-one-armed-router</id>
   <content type="html">&lt;p&gt;用 VLAN 划分局域网后，2个局域网之间就不能通信了。如果要让这2个网络能互访，就需要路由器。如果VLAN只是用来做端口隔离，那它本身就毫无意义，买多台独立的口少点的交换机更便宜。VLAN所以那么有用，完全是因为 vlan trunk 端口的存在。使得以太网在交换机上变成了一种隧道。一个二层的隧道。于是只有一个接口的路由器在这种隧道的帮助下能访问被 VLAN 隔离的网络。这种路由器，就是单臂路由器。单臂路由器虽然性能不好，但是能以极低的成本解决网间互联问题。如果网间没有大量的数据交互，单臂路由器就非常合适。当然如果网间有大量数据交换那还是上个3层交换机吧。&lt;/p&gt;

&lt;p&gt;单臂路由器的性能受限的原因是，多个VLAN之间的总数据交换大小为 trunk 端口的 1/2。VLAN1到 VLAN2 的单向数据，就占用 trunk 端口的双向流量。因此总交换容量就只有端口速率的 1/2。对大部分网络来说，这是双向50M（百兆以太网） 或者双向500M(千兆以太网)。&lt;/p&gt;

&lt;p&gt;尽管性能不佳，但是成本低廉，因此应用非常广泛。常见的soho路由器就是单臂路由器和vlan交换机的结合体。&lt;/p&gt;

&lt;p&gt;soho路由器的有线部分，实际上就是个单臂路由器。路由器上的 WAN 口和 LAN口只是分属2个（固定的）vlan。交换芯片有一个隐藏端口直连CPU。这个直连端口就是个 trunk 端口。&lt;/p&gt;

&lt;p&gt;这就是为何百兆路由器并不能跑满百兆宽带。因为单臂路由器就是上下行总和100M， 而且百兆宽带是下行 100M。如果还有上行数据，那么无论如何都无法跑满下行带宽了。&lt;/p&gt;

&lt;p&gt;更换千兆路由器其实有时候并不能解决这个问题，因为…&lt;/p&gt;

&lt;p&gt;许多廉价的千兆路由器，居然也仅仅是千兆交换芯片。trunk 口直连 CPU 的线路依然是百兆的。因此不是换千兆路由器就行的。你看到的千兆连接只是假象。这样还不如维持原来的路由器，只通过采购额外的千兆交换机来提升内网速度成本来的更低。而且独立的交换机其背板带宽是充足的。而路由器上自带的交换芯片。。通常背板带宽是严重不足的。你能想象内网两台正在对拷数据，居然会影响第三台电脑上外网速度的吗？ 这就是路由器自带的交换芯片背板带宽不足的表现。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Linux多WAN负载均衡原理</title>
   <link href="https://microcai.org/2016/11/02/load-balancing.html"/>
   <updated>2016-11-02T00:00:00+00:00</updated>
   <id>https://microcai.org/2016/11/02/load-balancing</id>
   <content type="html">&lt;p&gt;玩了一段时间 EdgeRouter , 对他的负载均衡的可玩性非常着迷。仔细研究后发现，他的实现原理是 iptables 标记数据包，然后对标记的数据包使用使用特定的路由表实现的。&lt;/p&gt;

&lt;p&gt;Linux 默认有一个路由表。这张路由表叫 main 路由表。但是可以用 ip route 命令添加其他路由表。非默认路由表可以用一个数字编号。并且 ip rule fwmark 命令可以设定让被标记的数据使用这个路由表。&lt;/p&gt;

&lt;p&gt;接着，接下来的工作就是，对数据包进行标记。&lt;/p&gt;

&lt;p&gt;标记是基于 connection 的，否则对同一个tcp链接的包用不同的接口出去那可就乱套了。
基于 conntrak 链，对被跟踪的连接打标记。 打标记的时候，可以首先根据目的 ip 打标记，这样就可以避免有特定的ip跟踪的网站出现访问异常。
然后接下来的包，就用 probability 规则打标记. 也就是可能性。 这样有 50% 的新链接被打到 mark 1 上， 50% 的打到 mark 2 上。于是就有一半的流量走 table 1 , 一半的走 table 2&lt;/p&gt;

&lt;p&gt;table 1 使用的 interface 1 做默认网关， table 2 用 interface 2 做默认网关。 于是2个接口上的流量就这样被平均过去了。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>家庭wifi覆盖建议</title>
   <link href="https://microcai.org/2016/10/24/home-wifi.html"/>
   <updated>2016-10-24T00:00:00+00:00</updated>
   <id>https://microcai.org/2016/10/24/home-wifi</id>
   <content type="html">&lt;p&gt;私以为，目前民用 wifi 路由器市场已经完全走上了邪路。天线越来越大，发射功率越做越高。都敢冒着法律的风险使用大大超过 100mW 的发射功率了。且不论巨大的发射功率可能对人体造成的影响，就从技术上说wifi的特点，为什么wifi不适合大功率发射吧。&lt;/p&gt;

&lt;h1 id=&quot;第一点空口带宽是个共享带宽&quot;&gt;第一点，空口带宽是个共享带宽。&lt;/h1&gt;

&lt;p&gt;共享范围是无线可达的地方，取决于发射功率和损耗。频率越高损耗越大。发射功率越大，意味着越多的设备进入这个大 HUB 来共享空口带宽。不是一个 AP 下带的客户端共享带宽，而是所有无线设备在这个区域内一同共享带宽。一味增加发射功率，违背了网络隔离较大广播域的原则。&lt;/p&gt;

&lt;h1 id=&quot;第二点同频干扰严重&quot;&gt;第二点，同频干扰严重。&lt;/h1&gt;

&lt;p&gt;ISA 频段限制发射功率并不是没有原因的。就是为了限制同频干扰。把同频干扰限制到一个几十米的范围内。在一个小范围内，出现多个同频发射点的概率就小了。增加发射功率，意味着距离很远的2个家庭都会相互干扰对方的网络。大大劣化网络环境。&lt;/p&gt;

&lt;h1 id=&quot;第三点自干扰&quot;&gt;第三点，自干扰&lt;/h1&gt;

&lt;p&gt;发射功率越大，路由器工作的时候自己给自己增加的干扰就越大。其实无线通信最关键的不是接收信号大小，而是信噪比。
如果接受到的信号很强，但是伴随着同样强的噪音信号，一样是无法通信的。&lt;/p&gt;

&lt;p&gt;===&lt;/p&gt;

&lt;p&gt;#那么，解决的办法有没有？ 其实是有的，就是商业环境已经使用很成熟的 多AP 蜂窝式覆盖。&lt;/p&gt;

&lt;p&gt;有人会说，商业使用的，不适合家用吧？&lt;/p&gt;

&lt;p&gt;呵呵，从前也只有商业使用才会用 100M 宽带，现在不是家家户户都用上了吗？ 并没有什么是不合适家用的，只是价钱问题而已。只要价钱低，家庭用户能承受，并没有一定要傲娇到非不去使用，就觉得那样用法是不家庭的。&lt;/p&gt;

&lt;p&gt;其实很多人已经在家里使用多AP（路由器当AP用）覆盖了，但是效果并不好，需要手动选择最近的 AP。这是为啥？&lt;/p&gt;

&lt;p&gt;因为家用的路由器都缺乏一个功能，就是  AP 漫游。&lt;/p&gt;

&lt;p&gt;没有 AP 漫游，即使2个家用路由器设置了一样的 SSID，即使他们都是接的 LAN 口，关了 DHCP 只当 AP 用。设备也不会认为这2个同名的 SSID 有什么关联。并不会主动的切换到信号更强的那个上。除非距离实在远，信号实在差，丢包很严重后，才会依依不舍的断开连接，寻找下一个信号更强的。而且这个寻找也不会是寻找同 SSID 的下一个信号更强的，而是好像这些 AP 都是属于独立的网络一样，按照信号强度排序。很多时候因为其他 ssid 你没有密码，所以恰好就会连上同 SSID 的另一个路由器而已。&lt;/p&gt;

&lt;p&gt;而商用 AP，由于有了一个集中的 “AP 控制器” 简称 AC ， 所以可以控制 AP 实现无缝漫游。而因为是商业产品，所以价格不菲。因为 AP 的大部分功能被 AC 取代，所以又叫瘦 AP， Fit AP。&lt;/p&gt;

&lt;h1 id=&quot;但是经过多年的发展-ac-和-fit-ap-的价格终于降低到家庭用户可以承担的地步啦&quot;&gt;但是经过多年的发展， AC 和 Fit AP 的价格终于降低到家庭用户可以承担的地步啦！！！！！&lt;/h1&gt;
&lt;p&gt;当然这事得感谢 TP-LINK 。&lt;/p&gt;

&lt;p&gt;其实家用路由器几十块钱就能买到一个，这在 TP-LINK 出现前，也是不可能的事情。所以很久以前家里有多台电脑的用户，电信都是给了 ‘多拨’ 这样的解决办法。就是允许使用交换机，然后每台电脑各自拨号。那时候没有 TP-LINK ，家庭用户最多就买得起交换机而已。大部分可能还是用集线器，交换机都不舍得买。&lt;/p&gt;

&lt;p&gt;好，废话不多说， 了解下， 使用 TP-LINK 的解决方案，需要多少钱。&lt;/p&gt;

&lt;p&gt;首先，你得有个大房子，一个普通路由器信号覆盖不了的那种。要么就买非常贵的 AC1900 的那种高大上的， 8 根天线的，然后保佑买来测试后没什么大的死角。要么就改装天线，改电路，弄个超大功率。再要么，就是使用我推崇的方式，多AP蜂窝覆盖。&lt;/p&gt;

&lt;p&gt;改装这条路 pass 掉。
一个信号非常好，天线很牛逼的路由器，要多少钱呢？&lt;/p&gt;

&lt;p&gt;让我们看看这货：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://gd2.alicdn.com/imgextra/i4/22936598/TB2Usj4epXXXXXZXpXXXXXXXXXX_%21%2122936598.jpg_400x400.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这货是 Google 捣鼓出来的家用路由器。信号测试可以看 Linus 的视频 http://www.bilibili.com/video/av3175439/&lt;/p&gt;

&lt;p&gt;这货的价格是高大上的 1000+ ！&lt;/p&gt;

&lt;p&gt;好，既然你的家庭只能靠一千多的路由器，然后求佛祖保佑能没有死角。那么不如看看另一个解决方案， 是不是能花更少的钱获得更好的覆盖效果。&lt;/p&gt;

&lt;h1 id=&quot;ac--ap-方案&quot;&gt;AC + AP 方案。&lt;/h1&gt;

&lt;p&gt;一台有线路由器, 30块钱一个。
TP-LINK AC100  这个是 AC , 价钱是 200 块钱一个。
一台 POE 交换机 大概 100 - 300 元。看你对端口数量而定。端口数量 &amp;gt;= AP 数量即可。
POE 是可选的。 因为 AP 通常支持 POE 供电 或者 插座供电。
若干 Fit AP. 每台100元左右。&lt;/p&gt;

&lt;p&gt;基本上2个就可以达成比 AC1900 更好的覆盖， 3 个以上就可以达成一台不论多贵的 AC1900 都无法达成的覆盖了。
相信成本上哪个划算已经一目了然了。&lt;/p&gt;

&lt;p&gt;而且可以在卧室重点覆盖，意味着在你经常上网的地方可以保证 100% 信号强度。而不是一台 无线路由器就只能放在正中央，否则无法全覆盖。于是卧室这样的地方信号强度就别想充足了。&lt;/p&gt;

&lt;p&gt;这样带来的上网体验也是不可同日而语。而且因为是 AC + AP 方案。所以多个 AP 是自动管理，可以无缝漫游的。并不会因为 AP 多了带来管理上的麻烦。&lt;/p&gt;

&lt;p&gt;OVER。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>新的路由器</title>
   <link href="https://microcai.org/2016/10/18/new-router.html"/>
   <updated>2016-10-18T00:00:00+00:00</updated>
   <id>https://microcai.org/2016/10/18/new-router</id>
   <content type="html">&lt;p&gt;上文说到，我使用无线网桥衔接了2个家庭，组成了一个局域网，共享了宽带。&lt;/p&gt;

&lt;p&gt;但是，局域网和局域网还是太过开放，网桥又是一段不怎么可靠的网线。因此2个局域网最好能隔离开来，隔离广播域，又能通过IP地址直连。
经过研究发现家里宽带还可以多拨。虽然多拨并不叠加带宽，但是可以多获得一个公网ip。有多一个 ip 对 NAT 来说，也是可以极大的提升网络性能的。&lt;/p&gt;

&lt;p&gt;于是找啊找，找发现了一个利器 “ubnt EdgeRouter X”。 不过 350 块钱的价格，但是却提供了思科路由器一样的功能。BGP OSPF RIP 动态路由协议应有尽有。什么？家用用不到动态路由协议？ 呵呵，马上你就知道了，我确实需要它。 提供5个千兆网口，更重要的是，五个独立的网口。也就是在操作系统里是 eth0 eth1 eth2 wth3 eth4 5块网卡。这就可以进行比较高级的玩法了。可以任意组合 N WAN + M LAN ，而不像普通路由器那样，只有一个 WAN 一个 LAN （ 或者少数几个也不过支持双WAN）。 基本上就是一台软路由的玩法了。而且一般软路由都没有5个网卡哦！&lt;/p&gt;

&lt;p&gt;电信自带光猫有2个LAN口，这就是给多拨准备的啊！&lt;/p&gt;

&lt;p&gt;于是作出了这样的规划&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/all-network-struct.svg&quot; width=&quot;100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;ERX路由器和猫之间连了2条网线。直接双拨。&lt;/p&gt;

&lt;p&gt;路由器一个 LAN 连 原来的 wifi 路由器，另一个 LAN 连网桥。&lt;/p&gt;

&lt;p&gt;把原来的路由器，都使用 WAN 口 （注意，是 WAN口哦！） 连到 ERX。无线网桥相当于是网线，所以是姐姐家的路由器 WAN口连到了 ERX 上。&lt;/p&gt;

&lt;p&gt;同时，在两台wifi路由器上关闭 NAT然后打开动态路由 ！！！ 这个很重要！！！ LAN 口的 DHCP 打开， WAN 口使用 DHCP 自动获取。&lt;/p&gt;

&lt;p&gt;各自的路由器就通过自己的 DHCP 管理自己网段内的设备了。 然后因为开启了 动态路由协议，所以 ERX 能顺利的自动找到2个子网的路由。&lt;/p&gt;

&lt;p&gt;然后2家的局域网就网段隔离了，但是有路由器在，ip 是可以直通的，局域网内主机互访没有问题，没有经过NAT。只有通过外网的时候才被 ERX NAT 一次。而且经过了负载均衡。&lt;/p&gt;

&lt;p&gt;然后意外的发现，上行可以叠加。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>远程共享宽带</title>
   <link href="https://microcai.org/2016/10/11/long-range-wifi.html"/>
   <updated>2016-10-11T00:00:00+00:00</updated>
   <id>https://microcai.org/2016/10/11/long-range-wifi</id>
   <content type="html">&lt;p&gt;最近姐姐搬迁到了新家。在我家屋顶能直接看到她的新家。所谓目视可达。我就开始思考，是不是可以无线直连，然后共享网络呢。&lt;/p&gt;

&lt;p&gt;于是淘宝上搜了一圈，最后弄了对山寨 UBNT 无线网桥。然后买了一箱网线，从家里一直沿着电信的光纤一路跟随布线到了楼顶。&lt;/p&gt;

&lt;p&gt;架设好后，姐姐家同理。也是室外放个网桥，然后线走进屋内。&lt;/p&gt;

&lt;p&gt;在屋内，2个网桥都分别和各自的无线路由器连接。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/ubnt-network-struct.svg&quot; width=&quot;100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;无线网桥就相当于一段网线。 把2个路由器连接起来变成了一个局域网。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>IELTS essay test 1</title>
   <link href="https://microcai.org/2015/11/04/essay.html"/>
   <updated>2015-11-04T00:00:00+00:00</updated>
   <id>https://microcai.org/2015/11/04/essay</id>
   <content type="html">&lt;p&gt;I couldn’t believe that some people just refused to admire the huge advantage that internet has bought us especially when it was utilized as a source of information. They made the assumption that internet brings more demerits, and too difficult to make it beneficial. Well I will never agree with such vision-less opinion with the following reasons.&lt;/p&gt;

&lt;p&gt;First, the internet reduced the cost of information exchanging and brings us equality. In the old days, when the information can only be carried out on bare metal, the cost to exchange the information is the same as delivering physically real things, you pay both the shipping cost and the materials. Thus, the information only belongs to the rich. But the internet brings us to the information age, where all men and all women, all the rich and all the poor are all equal when it comes to getting the information. The poor are no longer blocked by the entrance price of getting information.&lt;/p&gt;

&lt;p&gt;Secondly, the search engine make it really convenient to get what you want from vast majority of information sources out there. One don’t have to filter though books and periodicals. This saves a lot of time.&lt;/p&gt;

&lt;p&gt;But internet does have drawbacks, as it requires more skills which a lot of old people don’t have. Even the young divert a lot in searching skills. When misused, the internet can lead to wrong or even dangerous sources. But I hardly say that books won’t.&lt;/p&gt;

&lt;p&gt;So, the internet brings more advantages over disadvantages, that’s obvious. It does require more skills to be fully utilized, but what is what evolution is all about, that living creatures need more and more skills to survive.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>UDP and congestion control</title>
   <link href="https://microcai.org/2015/10/06/udp-and-congestion-control.html"/>
   <updated>2015-10-06T00:00:00+00:00</updated>
   <id>https://microcai.org/2015/10/06/udp-and-congestion-control</id>
   <content type="html">&lt;p&gt;TCP is good, good for nearly everything. It’s a general purpose abstraction for networking applications. But TCP is bad for one thing: it’s general. If you have some special need, setsockopt(2) can help you. But really, it dosen’t help much.&lt;/p&gt;

&lt;p&gt;What if you have customized need for congestion control?&lt;/p&gt;

&lt;p&gt;The need for different congestion control algorithm is the main reason that we implement
our protocol ontop of UDP.&lt;/p&gt;

&lt;p&gt;I was a little bit shocked that during the 4.2 merge window of Linux, TCP too got a new congestion control algorithm that’s a little bit similar to the one I used in my protocol - that is, based on the change rate of RTT, rather than based on a packet lost.&lt;/p&gt;

&lt;p&gt;The new one in TCP is called Delay-gradient congestion control.&lt;/p&gt;

&lt;p&gt;This new Delay-gradient congestion control and the one employed by my protocol are different in many ways, but they do share a commmon concept : detect the bandwith by the monitoring the changes of RTT. The traditional ways of doing such detecting is by packet lost - you cut your sending speed when you detect a packet lost.&lt;/p&gt;

&lt;p&gt;Since TCP has this new fancy congestion control algorithm, why we still use UDP?&lt;/p&gt;

&lt;p&gt;Well, we are still different in many ways. Our protocol are more tolerance on packet lost than TCP, which makes it more suitable on transnational network.&lt;/p&gt;

&lt;p&gt;We tolerant on packet lost will still employe “Delay-gradient” to prevent a packet lost. Can TCP provide this? No.&lt;/p&gt;

&lt;p&gt;So, use UDP if you need fine control over congestion control algorithm, otherwise TCP.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>ASIO 腾空出世 （那些年我们追过的网络库.PartII）</title>
   <link href="https://microcai.org/2015/09/18/history-of-network-libraries-part-two.html"/>
   <updated>2015-09-18T00:00:00+00:00</updated>
   <id>https://microcai.org/2015/09/18/history-of-network-libraries-part-two</id>
   <content type="html">&lt;blockquote&gt;
  &lt;p&gt;这是上篇 &lt;a href=&quot;http://microcai.org/2015/09/14/history-of-network-libraries-part-one.html&quot;&gt;那些年我们追过的网络库(PartI)&lt;/a&gt; 的后续，&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;asio-腾空出世&quot;&gt;ASIO 腾空出世&lt;/h2&gt;

&lt;p&gt;在地球最大的岛上，另一位少年开始拜读 ACE 的大作。那时候，没有 libuv 没有 libev 更没有 libevent . 有的只是 ACE.
然而这个南方小国的少年没有跟风陷入 ACE 崇拜，他以敏锐的目光察觉到了 ACE 的弊病。
ACE 哪里做的不好？又哪里是值得借鉴的？&lt;/p&gt;

&lt;p&gt;少年在给 c++ 委员会写的一篇上书中说，Proactor 模型乃最优模型。而 Proactor 模型，乃 ACE 提出的 6 个模型之一。根据 IT 界赢者通吃律，一个优秀的网络库，只需要支持 Proactor 模型即可。
支持其他次优模型都是徒劳的。ACE 试图全盘通吃，犯了大忌。&lt;/p&gt;

&lt;p&gt;少年在一次开发者大会的演讲上，再次透露，网络库不宜做成框架，而是要像系统的API那样，作为一个乐高积木。ACE 做成了一个框架，同样不妥。&lt;/p&gt;

&lt;p&gt;你说了那么多 ACE 不好，有本事你弄个好的啊？ 批评者向来都是这么理直气壮。
正如 ACE 的作者实践了 “纸上得来终觉浅 绝知此事要躬行” 一样，这位勇敢的少年也拿出了 ASIO, “实践出真知”，他如是说。&lt;/p&gt;

&lt;p&gt;那还是 SARS 病毒肆虐的年代，几乎没有人注意到，今后颠覆C++的网络世界的 ASIO 悄然出世了。而他的父亲，还只是悉尼的学子。ASIO 并没有显赫的家庭背景，然而英雄不问出处，它注定将有不平凡的一生。&lt;/p&gt;

&lt;p&gt;俗语有云，三岁看老。在 asio 才三岁的时候，它父亲就将 asio 引荐给了 c++ 委员会的老人们。上一次他们这么做的时候，他们接纳了 STL。
ASIO 最终&lt;del&gt;被内定，然后&lt;/del&gt;放入 Boost 锻炼, 经过 Boost 十余的锻炼，ASIO 终于在 2017 年进入了 c++ 标准。&lt;/p&gt;

&lt;h3 id=&quot;proactor--why-proactor&quot;&gt;Proactor ? Why Proactor&lt;/h3&gt;

&lt;p&gt;在给 c++ 老人会的引荐信里，asio 爸爸仔细阐述了asio的设计抉择回答了围绕 asio 的设计提出的很多问题。
为什么 Proactor 会是最佳模型？&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;跨平台           许多操作系统都有异步API，即便是没有异步API的Linux, 通过 epoll 也能模拟 Proactor 模式。&lt;/li&gt;
  &lt;li&gt;支持回调函数组合 将一系列异步操作进行组合，封装成对外的一个异步调用。这个只有Proactor能做到，Reactor 做不到。意味着如果asio使用Reactor模式，就对不起他“库” 之名。&lt;/li&gt;
  &lt;li&gt;相比 Reactor 可以实现 Zero-copy&lt;/li&gt;
  &lt;li&gt;和线程解耦 长时间执行的过程总是有系统异步完成，应用程序无需为此开启线程&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Proactor 也并非全无缺点，缺点就是内存占用比 Reactor 大。Proactor 需要先分配内存而后处理IO, 而 Reactor 是先等待 IO 而后分配内存。相对的Proactor却获得了Zero-copy好处。因为内存已经分配好了，因此操作系统可以将接受到的网络数据直接从网络接口拷贝到应用程序内存，而无需经过内核中转。
Proactor 模式需要一个 loop ,这个 loop asio 将其封装为 io_service.他不仅是 asio的核心，更是一切基于asio设计的程序的核心。&lt;/p&gt;

&lt;h3 id=&quot;宇宙级异步核心&quot;&gt;宇宙级异步核心&lt;/h3&gt;

&lt;p&gt;io_service 脱胎于 IO 但不仅用于 IO. Christopher Kohlhoff 在给委员会的另一份编号 N3747 的信上上说它是 &lt;del&gt;宇宙级异步模型&lt;/del&gt; Universal Asynchronous Model。在宇宙级异步模型里，一个异步操作由三部分构成&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;发起 按照  asio 的编码规范，所有的发起操作都使用 async_ 前缀，使用 async_动词 的形式作为函数名。&lt;/li&gt;
  &lt;li&gt;执行 异步过程在发起的时候被executor执行（系统可以是支持 AIO 的内核，不支持 AIO 的系统则是 aiso 用户层模拟）&lt;/li&gt;
  &lt;li&gt;完成并回调 在发起 async_* 操作的时候，总是携带一个回调的闭包。asio使用闭包作为异步事件完成的处理回调，没而不是C式的回调函数。asio的宇宙异步模型里，回调总是在执行 io_service::run 的线程里执行。asio绝不会在内部线程里调用回调。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在回调里发起新的异步操作，一轮套一轮。整个程序就围绕着 io_service::run 运转起来了。
io_service 不仅仅能用于异步 IO ,还可以用来投递任意闭包。实现作为线程池的功能。这一通用型异步模型彻底击败微软 PPL 提案，致使微软转而研究协程。然而微软在协程上同样面临 asio 的绞杀。&lt;/p&gt;

&lt;h3 id=&quot;闭包和协程&quot;&gt;闭包和协程&lt;/h3&gt;

&lt;p&gt;宇宙级 asio 使用闭包作为回调，而 C 库只能使用函数+void*, ACE 虽然使用的 C++语言，却不知道闭包为何物，使用的是 虚函数作为回调。需要大量的从 ACE 的对象继承。以闭包为回调，asio更是支持了一种叫“无栈协程”的强悍武器。
asio的无栈协程，仅仅通过库的形式，不论是在性能上，还是易用性上，还是简洁性上，甚至是B格上，都超过了微软易于修改语言而得的 await提案。&lt;/p&gt;

&lt;p&gt;微软，乃至 ACE ，并不是不知道闭包，而是在c++里实现闭包的宇宙级executor —— 也就是 io_service，需要对模板技术的精通。
asio “把困难留给自己，把方便带给大家”，以地球人无法理解的方式硬是在 c++98 上实现了宇宙级异步核心。
当然，如果 c++11 早点出现，如果 c++17 早点出现，实现 asio 的宇宙模型会更加的简单 —— 其实这也是 c++ 的理念，增加语言特性，只是为了让语言用起来更简单。&lt;/p&gt;

&lt;h3 id=&quot;buffers&quot;&gt;buffers&lt;/h3&gt;

&lt;p&gt;有了闭包的支持，内存管理也变得轻轻松松起来。
ASIO 本身并不管理内存，所有的IO操作，只提交对用户管理的内存的引用，称 Buffers。asio::buffers 引用了用户提交的内存，保持整个 IO 期间，这块内存的有效性是用户的责任。然而这并不难！
因为回调是一个闭包。通过闭包持有内存，只要 asio 还未回调，闭包就在，闭包在，内存在。asio 在调用完回调后才删除相应的闭包。因此资源管理的责任可以丢给闭包，而闭包可以通过智能指针精确的控制内存。
不是 GC , 胜于 GC 千百倍！益于c++的 RAII机制，再无内存泄漏之忧！&lt;/p&gt;

&lt;h2 id=&quot;进入-asio-的世界&quot;&gt;进入 ASIO 的世界&lt;/h2&gt;

&lt;p&gt;对 C++ 网络库的历史也介绍到差不多了，接下来的章节里，带你深入理解asio , 让你同时获得开发效率和执行效率。
一些在本章里，可能对许多人来说都是初次见到的技术，将在本书剩余章节里详细介绍。&lt;/p&gt;

&lt;p&gt;翻开下一页，进入 ASIO 的世界，领略 C++ 的博大精深，享受网络遨游的快感吧！&lt;/p&gt;

&lt;p&gt;–&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>那些年我们追过的网络库(PartI)</title>
   <link href="https://microcai.org/2015/09/14/history-of-network-libraries-part-one.html"/>
   <updated>2015-09-14T00:00:00+00:00</updated>
   <id>https://microcai.org/2015/09/14/history-of-network-libraries-part-one</id>
   <content type="html">&lt;p&gt;#为什么要用 C++ 编写服务端程序？
如果说答案是性能，那么肯定有人会满不在乎。觉得性能不够的话， 只要加机器就可以了。
然而更少的机器，意味着更低的能耗，更少的硬件投入，更少的人力资源投入去维护机器。总而言之，更低的成本。&lt;/p&gt;

&lt;p&gt;肯定会有人说，C++的开发速度太慢了。然而这并不是绝对的。C++也可以做到非常快速的开发。有句俗语 * “脚本一时爽，重构火葬场” * 说的正是脚本语言开发的项目进入维护阶段后无穷的灾难。而 C++ 经过了几十年的发展, 拥有庞大的工具链. 不管是动态分析还是静态分析都有大量的工具, 能极大的帮助程序员减少错误.
c++得益于精良的设计，严格的检查，越是大型的工程，越是能降低开发成本。&lt;/p&gt;

&lt;p&gt;但这并不意味着C++就不适合小型项目了。小型的项目，也可以快速开发。因为 C++11 开始，已经 &lt;em&gt;感觉像是全新的语言了&lt;/em&gt;，可以完全以脚本的形式去使用，获得接近甚至超越脚本语言的开发速度，同时得益于编译优化，获得不俗的运行时性能。
C++正是鱼和熊掌得兼的语言。&lt;/p&gt;

&lt;p&gt;#为什么要用asio这个库？&lt;/p&gt;

&lt;p&gt;事实上如果使用C++开发服务端程序，你有多得数不清的选择。什么 ACE 啦，libuv 啦，libevent 啦，libev 啦，甚至可以直接使用 epoll/iocp 这样的系统API。
为什么要用 asio 呢？&lt;/p&gt;

&lt;p&gt;##那些年我们用过的网络库&lt;/p&gt;

&lt;p&gt;在计算机史前文明时代, 曾经有个世界难题, 叫 &lt;em&gt;“c10k problem”&lt;/em&gt;. 这个是继 y2k problem 后的又一个重大攻关项目. 全世界的文艺青年都想拿下解决这个问题的荣誉, 正可谓八仙过海, 各显神通.&lt;/p&gt;

&lt;p&gt;那一年, NPTL 还没有研究出来. 还不能创建成千上万个线程 &lt;br /&gt;
那一年, windows 还在蓝屏中挣扎, 无暇顾及网络.&lt;/p&gt;

&lt;p&gt;然而, 曙光还是有的. 异步的出现带给了人以希望. 古老的UNIX早就想到了, 提供了 select() 系统调用供人驱使.
然而问题还是有的, select 只能支持 1024 个文件描述符, windows 上的 select 更是劣质到只能使用64个. 就算通过修改定义强迫接受一万个文件描述符, 也没有解决实际的问题. select  实在是太慢了.&lt;/p&gt;

&lt;p&gt;在这种背景下, IBM 老大哥带领着MS老弟先搞了 IOCP . 然而开源的人有开源的做法, 在 NIH 综合症的影响下, BSD 的人敢为天下所不齿, 发明了 Kqueue. 同样在 NIH 综合症影响下, Linux 的一群 M* 的猴子捣鼓出了 epoll.&lt;/p&gt;

&lt;p&gt;分裂, 让人头疼.&lt;/p&gt;

&lt;p&gt;然而, 他们都声称自己的新接口对 select 有质的提升, 是破解 c10k 问题的不二法宝. 你用也得用, 不用也得用.
为了让自己编写的网络程序能跨平台, 程序员开始了对3大各自为阵的法宝的膜拜学习. &lt;br /&gt;除了需要应对多套互不兼容的 API , 异步本身也需要更高级的抽象, 把程序员从编写异步代码的地狱模式里拯救出来. 于是程序员们急需一个上天入地无所不能的法宝的法宝, 把这3家法宝给统御起来.&lt;/p&gt;

&lt;p&gt;率先站出来悳瑟的是 ACE.&lt;/p&gt;

&lt;h3 id=&quot;悳瑟的-ace&quot;&gt;悳瑟的 ACE&lt;/h3&gt;
&lt;p&gt;恰乱世刚过, 天下待定, C++ 委员会的老人们却韬光养晦, 不问世事. 所谓乱世出英雄, 英雄出少年, 欧文大学出了名秀才. 凭借其洋洋洒洒的一片雄文 《Pattern-Oriented Software Architecture》 中举去了首府学城, 并为ACE奠定了无可撼动的地位.&lt;/p&gt;

&lt;p&gt;ACE 的名字, 也许灵感来自 Adaptive Clubbed Rod, 这也是当年一位英雄少年的宝贝. 既是宝贝, 必需如意. 即是后来的葫芦娃都怕了 “如意宝贝”.&lt;/p&gt;

&lt;p&gt;ACE 如意在什么地方呢？如意其一, 支持 IOCP/kqueu/epoll/select/you_name_it 各种接口, 号曰没有不能跨的平台. 如意其二, 支持多种模型。这些模型都在《Pattern-Oriented Software Architecture》有过详细叙述. ACE 本身就是这篇论文的实践，因为他知道, 纸上得来终觉浅 绝知此事要躬行。 如意其三, 接口和模式排列组合下, 多少种, 竟可不修改代码而适应。&lt;/p&gt;

&lt;p&gt;然而 ACE 毕竟嫩了点, 没过几年就失势了. 现在除了一些老程序员还在用, 新生代的程序员已经不再使用 ACE 了. 为什么呢? 陈硕在他的博客里说, &lt;strong&gt;ACE 过于复杂，甚至比它试图封装的对象更复杂&lt;/strong&gt;,
程序员是指望用你的如意宝贝去驾驭另外那三家宝贝的, 结果你比他们还难。ACE 犯了早期 C++ 库都会犯的一个错误，过度设计， 过度java化。所谓 java 化， 就是以对象代替接口， 以虚函数代替回调，以继承代替组合。以虚类代替模板。对象间关系错综复杂，牵一发而动全身。除了作者，已经无人能参与 ACE 的开发了。&lt;/p&gt;

&lt;p&gt;与此同时，C语言的回归却在背后悄然进行。C语言的复辟，带来了几个更为糟糕的替代品， libevent 和 libev，以及 乘着nodejs的盛行东风而来的 libuv。&lt;/p&gt;

&lt;h3 id=&quot;原始的-libevent&quot;&gt;原始的 libevent&lt;/h3&gt;
&lt;p&gt;C语言有着顽强的生命力，当然，这并不是因为C语言有多好，在后续的章节了我们还会深入的探讨C++相对C的改进。C语言的顽强和人天生的懒惰和偏见是有一定关系的。这种惰性表现为随遇而安，表现为固执己见。 非要拼命的否定C++，固守 C , 对 C 的缺点视而不见，诋毁C++相对C的改进。固守的结果就是简陋原始的 libevent . 然而因为保守党巨大的人数优势， libevent 应其群众基础良好而获得了空前的广泛使用。&lt;/p&gt;

&lt;p&gt;libevent 就如名字所言，是一个异步事件框架。从 OS 那里获得事件， 然后派发。派发机制就是“回调函数”。异步异步，归根结底就是处理从操作系统获得的事件。iocp也好， epoll也罢，都只是用来获取事件的接口。libevent 去掉了ACE华而不实的包装，保留了异步事件，极大的简化了模型。不得不说软件工程是个糟糕的发明，从来都把简单问题复杂化。libevent把简单问题简单化，让异步网络编程反朴归真，应该来说，本是一个好库。&lt;/p&gt;

&lt;p&gt;然而 libevent 因为设计缺陷，例如使用全局变量，定时器无法处理时间跳变，诸如此类的设计缺陷导致了 libev 的出现。
libev 就是为了克服libevent的缺陷而诞生的。然而，libev 就一定好了吗？&lt;/p&gt;

&lt;h3 id=&quot;禁锢的-libev&quot;&gt;禁锢的 libev&lt;/h3&gt;
&lt;p&gt;libev 带着对 libevent 的怨气出世了。 吸收了 libevent 的所有缺点，
虽然承诺过改进。然而 libev 如何改进的了呢？ libev 已经够原始了，向下改进还不如让人直接使用系统的 api, 向上改进，一是会导致和libevent的重叠，二是很快就碰到了 C 语言强加的禁锢。&lt;/p&gt;

&lt;p&gt;C 语言因其语法&lt;del&gt;简陋&lt;/del&gt;简洁而著称。然而，缺乏必要的抽象能力，导致 C 语言编写异步程序，就如同安迪拿着小锤子琢开肖生克监狱的墙壁一样。能，但是要耗费巨大的精力和时间。
编写异步程序， 最需要的2个抽象能力， 其一为协程，其二是函数对象，包括匿名函数对象， 也就是lambda。
C统统没有。函数对象是实现闭包比不可少的，如果没有函数对象， 就只能通过携带 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void*&lt;/code&gt; 指针的形式迂回完整，繁琐不说，还特别容易出错。程序里也到处充满了类型强转。到处是临时定义的类型，就为了传给 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;void*&lt;/code&gt; 使用。&lt;/p&gt;

&lt;p&gt;尽管C 有那么多缺点，然而 libev 还未来得及被C的缺点拖累，因为他不支持 IOCP. 于是 libuv 就出来给 libev 擦屁股了。 支持了 iocp 后的 libuv 就真的只有 C 本身的缺点了吗？&lt;/p&gt;

&lt;h3 id=&quot;混乱的-libuv&quot;&gt;混乱的 libuv&lt;/h3&gt;

&lt;p&gt;libuv 可以说是 C 语言的异步库所能达到的最高高度了。完完全全的触碰到了C语言的自身瓶颈，好在 libuv 只是 nodejs 的底层库，上层软件转移到 javascript 语言而逃避了 C 的禁锢。&lt;/p&gt;

&lt;p&gt;真的是这样的吗？ libuv 自身还有什么缺点呢？&lt;/p&gt;

&lt;p&gt;开源社区avplayer的大拿jackarain曾经说过，一个网络库好不好，就看他有没有正确的处理 TCP 关闭， read write 实现的ui不对。
libuv 很遗憾的是，不合格。libuv 的 uv_write 没有返回值，允许空回调。也就是忽略write错误。
网络出错的情况下， libuv 的用户只能稀里糊涂的知道出错了， 至于错在哪？数据到底有没有发出去了? 一概不知道。
把数据交给 uv_write 后，就是一笔糊涂账了，大概 TCP 不可靠的说法就是从这里传出来的吧。&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;asio-腾空出世&quot;&gt;ASIO 腾空出世&lt;/h2&gt;

&lt;p&gt;请期待下篇！&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>自我意识和物质</title>
   <link href="https://microcai.org/2015/08/16/consciousness-and-matter.html"/>
   <updated>2015-08-16T00:00:00+00:00</updated>
   <id>https://microcai.org/2015/08/16/consciousness-and-matter</id>
   <content type="html">&lt;p&gt;现代的科学认为物质是不生不灭的. 至少在宇宙的寿命内是这样. 物质的诞生意味着宇宙的开端, 物质的消失意味着宇宙的灭亡.
在宇宙中, 意识并没有特殊的地位. 它不过是地球40亿年进化史的一个副产品.&lt;/p&gt;

&lt;p&gt;真的是这样的吗?&lt;/p&gt;

&lt;p&gt;我始终坚信的一个宇宙真理就是, 万物平等. 伽利略给了运动和静止一个平等的地位. 爱因斯坦给了所有惯性参考系平等的地位. 物理学的发展, 就是不断的消除特例, 不断的将原本不同的东西平等起来.&lt;/p&gt;

&lt;p&gt;我相信, 给予意识和物质一个平等地位的科学时代也即将到来. 而这, 已经露出了苗头.&lt;/p&gt;

&lt;h1 id=&quot;薛定谔的猫&quot;&gt;薛定谔的猫&lt;/h1&gt;

&lt;p&gt;薛定谔的猫, 这一经典实验, 揭示着意识对物质的影响. 意识不仅仅只是物质运动的观察者, 意识也是物质运动的一部分, 决定着物质过去的历史, 决定着现在的状态, 乃至未来的发展.&lt;/p&gt;

&lt;h1 id=&quot;除非进行观测否则一切都不是真实的&quot;&gt;除非进行观测，否则一切都不是真实的。&lt;/h1&gt;

&lt;p&gt;物理世界的存在与否, 取决于是否有人观察它. 没有人观察的世界, 它并不存在.
不能不承认，这听起来很有强烈的主观唯心论的味道。但它其实和我们通常理解的那种哲学理论有一定区别.&lt;/p&gt;

&lt;h1 id=&quot;直到人类开始研究宇宙的历史-宇宙才有了确定的历史&quot;&gt;直到人类开始研究宇宙的历史, 宇宙才有了确定的历史&lt;/h1&gt;

&lt;p&gt;而在人类研究宇宙前, 宇宙的历史是混沌的叠加态. 在人类研究宇宙历史的那一刹那, 整个宇宙的历史就被确定下来了.
这就是量子力学揭示的宇宙之谜.&lt;/p&gt;

&lt;p&gt;然而, 这还未曾被接受过.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>GLSL加速 YUV 显示</title>
   <link href="https://microcai.org/2015/03/31/optimize_with_glsl.html"/>
   <updated>2015-03-31T00:00:00+00:00</updated>
   <id>https://microcai.org/2015/03/31/optimize_with_glsl</id>
   <content type="html">&lt;p&gt;通常来说，视频都是使用 YUV 格式编码的。YUV 最符合人眼的结构，因为人眼对亮度要比对颜色敏感的多。 YUV 将颜色分成一个 亮度信号和2个色差信号。于是就可以使用更多的bit数去编码亮度信号。在同样的每像素比特位数下，YUV 能比 RGB 保留更多的信息。&lt;/p&gt;

&lt;p&gt;But YUV 要在 PC 屏幕上显示，不那么友好。需要转成 RGB 格式。网上一搜一大把，这里就不贴转换公式了。&lt;/p&gt;

&lt;p&gt;将YUV转换为 RGB 时，是像素独立的。每个像素都可以独立转换，因此可以被大规模并行。所以 YUV 最佳的转换地方还是在 GPU 上。不适合在 CPU 上转。&lt;/p&gt;

&lt;p&gt;非常可惜的是， OpenGL 是基于 RGBA 的。在opengl里无法直接使用YUV。&lt;/p&gt;

&lt;p&gt;But，先知已经考虑到了这点，先知给 OpenGL 加了一道通用计算的能力。这道通用计算能力就叫 .. 额.. Shader。（为啥中文译名叫着色器呢？）&lt;/p&gt;

&lt;p&gt;Shader 简单的来说就是一小段跑在 GPU 上的代码。这些代码针对每一个提交的顶点和每一个最终屏幕像素并行执行。
处理顶点数据的就是顶点着色器。处理屏幕像素的就是像素着色器了。&lt;/p&gt;

&lt;p&gt;因此, 将 YUV2RGB 的公式写成一个 shader, 就能实现在 GPU 上并发转格式了.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-GLSL&quot;&gt;vec3 yuv2rgb(in vec3 yuv)
{
	// YUV offset
	// const vec3 offset = vec3(-0.0625, -0.5, -0.5);
	const vec3 offset = vec3(-0.0625, -0.5, -0.5);
	// RGB coefficients
	const vec3 Rcoeff = vec3( 1.164, 0.000,  1.596);
	const vec3 Gcoeff = vec3( 1.164, -0.391, -0.813);
	const vec3 Bcoeff = vec3( 1.164, 2.018,  0.000);

	vec3 rgb;

	yuv = clamp(yuv, 0.0, 1.0);

	yuv += offset;

	rgb.r = dot(yuv, Rcoeff);
	rgb.g = dot(yuv, Gcoeff);
	rgb.b = dot(yuv, Bcoeff);
	return rgb;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;输入为一个 vec3 的向量, 返回 vec3 向量. 这个转换对每个像素并发的执行.&lt;/p&gt;

&lt;p&gt;于是, 只要将解码好的视频, Y U V 3个通道分别绑定为3个贴图, 然后在 shader 里访问, 转换成 RGB 就可以了.&lt;/p&gt;

&lt;div class=&quot;language-glsl highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;uniform&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;sampler2D&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;texY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Y&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;uniform&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;sampler2D&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;texU&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// U&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;uniform&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;sampler2D&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;texV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// V&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;varying&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;vec2&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vary_tex_cord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;vec3&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;yuv2rgb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;vec3&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yuv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;vec3&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;get_yuv_from_texture&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;vec2&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tcoord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;vec3&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yuv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;yuv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;texture&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;texY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tcoord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;c1&quot;&gt;// Get the U and V values&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;yuv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;texture&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;texU&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tcoord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;yuv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;texture&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;texV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tcoord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yuv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;vec4&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;mytexture2D&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;vec2&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tcoord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;vec3&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rgb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yuv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;yuv&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;get_yuv_from_texture&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tcoord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;c1&quot;&gt;// Do the color transform&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;rgb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yuv2rgb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;yuv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;vec4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rgb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;vec4&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;out_color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;c1&quot;&gt;// That was easy. :)&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;out_color&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mytexture2D&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vary_tex_cord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;完成&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>ubuntu 太反人类了</title>
   <link href="https://microcai.org/2014/11/11/fuck-ubuntu.html"/>
   <updated>2014-11-11T00:00:00+00:00</updated>
   <id>https://microcai.org/2014/11/11/fuck-ubuntu</id>
   <content type="html">&lt;p&gt;ubuntu 这个烂东西, 我本来不打算吐槽的, 反正烂东西我不触碰就是了.&lt;/p&gt;

&lt;p&gt;但是啊, 烂东西呢, 你不过去招惹它, 它还是会过来招惹你, 不管你愿意还是不愿意.&lt;/p&gt;

&lt;p&gt;ubuntu 为啥烂 ? 怎么招惹我了?&lt;/p&gt;

&lt;p&gt;因为咱用了 travis-ci . travis-ci 是个好东西啊, 每次你 git push , 它就默默的帮你编译一遍代码. 哪个 commit 没编译过, 一目了然.&lt;/p&gt;

&lt;p&gt;问题在于, travis-ci 用的是 ubuntu , 而且更糟糕的是用的 ubuntu 12.04 … 太老了.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;首先遇到的第一个问题, 就是包名好乱. 我要用一个库的时候, 我不知道要安装哪个库! 我不知道 ubuntu 打包的时候到底用了啥名字.
我不能因为用了这个名字的库,而推定 ubuntu 一定打包成这个名字. 必须上  ubuntu 的网站查询! 其实这个问题我老早就说过了. 诶.&lt;/p&gt;

&lt;p&gt;包名混乱, 用户不得不多上网站搜索, 网站流量就上来了, 就看起来 “活跃” 了. 排名就高了… 真是个好生意.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;第二个问题, 是软件太旧了. 真的太久了. 好不容易找好了正确的包名, 开开心心的在 .travis.yaml 文件里添加了 apt-get 命令 …
结果软件实在是太旧了! 这些软件当年刚进入 ubuntu 仓库的时候就已经是旧了, 但还勉强够用. 但是过了这么多年, 等 ubuntu 自己都旧了的时候,
这些软件就是古董到不能再古董了.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;第三个问题, 总是恰好包含有 bug 的那个版本, 即便新版本已经包含了修正, ubuntu 也会因为这些版本太”新” 而拒绝认为他们是稳定的.&lt;/p&gt;

&lt;p&gt;即便新版本已经包含了修正, ubuntu 也不能简单的更新到新版本来包含更正. 而是必须想出自己的办法来解决 bug. 通常还会受限于人手问题迟迟得不到解决,只能等下一个 LTS 简单的包含了更新了的软件…&lt;/p&gt;

&lt;h1 id=&quot;ubuntu-是反人类的-更糟糕的是越来越多人抖m-在用-ubuntu-并认为这是全天下最好的-os&quot;&gt;ubuntu 是反人类的, 更糟糕的是越来越多人抖M 在用 ubuntu 并认为这是全天下最好的 OS&lt;/h1&gt;

</content>
 </entry>
 
 <entry>
   <title>也说 jitter</title>
   <link href="https://microcai.org/2014/10/16/jitters.html"/>
   <updated>2014-10-16T00:00:00+00:00</updated>
   <id>https://microcai.org/2014/10/16/jitters</id>
   <content type="html">&lt;p&gt;搞数字音频的，经常会提到 jitter。 什么是 jitter ？&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/jitter1.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;jitter 就是时钟的随机抖动。这个抖动啊，来自量子世界，是无法消除的，否则海森堡大人要从棺材里跳出来了。
jitter 只能减少，不能削除。&lt;/p&gt;

&lt;p&gt;那么 jitter 对音频有啥影响呢？&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/jitter2.png&quot; width=&quot;70%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;jitter 主要影响就是，经过 DAC 重建模拟信号的时候，时钟的不稳定导致重建信号偏离原来的位置。结果上看就是波形不再符合原来的PCM编码的描述了。
这个偏差引入了失真，但是数据的准确性并没有被破坏。&lt;/p&gt;

&lt;p&gt;那么 jitter 既然无法消除， 总有个量的吧？ 小于某个量后，人耳就无法察觉了，对吧？ 
是的。一般认为，jitter 小于 200皮秒后人耳即无法感知了。&lt;/p&gt;

&lt;p&gt;一般数字电路的 jitter 有多大？ 数字电路通常使用石英晶振产生时钟，石英晶振的 jitter 是很小的。石英晶振 jitter 可以做到小于 1ps （皮秒）。
所以 jitter 影响音质的说法基本上是无稽之谈。&lt;/p&gt;

&lt;p&gt;这个时候，玄学大师会出来说，低 jitter 的那是本地时钟。如果使用的不是本地时钟，比如使用 SPDIF 传输音频，时钟信号是包含在信号里的。
解码器要从被光电转换线路劣化（或者使用同轴传输，信号会被外界电磁干扰劣化）的信号里重建数据和时钟。这个时钟就很难保证低jitter。&lt;/p&gt;

&lt;p&gt;是的确实如此。外界干扰确实让同轴或者光纤传来的信号 jitter 大大增加。有网友以示波器观察，发现波形已经被严重歪曲。数字信号以电压判断， 接收到的波形因为已经扭曲，波形的上升坡度各不相同。所以上升到指定电压以被判定为 1 的时间就各不相同。 哪怕原始波形以精确的时间开始上升 。。。。
如此以来，jitter确实很大。&lt;/p&gt;

&lt;p&gt;但是，哪怕同轴上单个 bit 的 jitter 高达 1000ps (其实已经很低了，但是足以引起听感不适)， 提取出来的信号和时钟的jitter依然能做到非常低。
这是为何呢？ 因为重建信号使用了 PLL （锁相环） 机制。&lt;/p&gt;

&lt;p&gt;spdif 信号并不能直接 DAC 解码，而是需要提取出时钟信号和音频数据。spdif 信号是一帧一帧传播的。而不是一个采样点一个采样点传播的。&lt;/p&gt;

&lt;p&gt;接受方首先要接收完整的一帧的数据，通过帧头获得音频数据的格式。然后才能讲音频数据送入 DAC 。 DAC 则是一次一个采样点进行的。
DAC 需要一个时钟信号，时钟信号每一个滴答，DAC 按照采样点数字输出一个电压。只要这个时钟的 jitter 低就可以了。
spdif 虽然使用 BMC 编码将数据和时钟同时编码，但是这个时钟其实是用来恢复数据使用的——使用 BMC 编码可以使用一条线就将数据正确的传输——并不用它来恢复提供给 DAC 的时钟。
这个时钟是 spdif 接收电路依据 PLL 锁定后输出的。&lt;/p&gt;

&lt;p&gt;这个 PLL 电路的就是保证每帧音频数据的到达时间被恰好平均分割，锁定的是这个时钟。这是什么意思呢？ 
spdif 每帧包含 192 个采样点数据， 在传输 48khz 音频数据的时候， 每 4ms 传输一帧。那么 PLL 电路的任务就是产生一个时钟，每 4ms 恰好是 192 个脉冲。
传输 44.1khz 的音频数据的时候，大约每 4.35ms 传输一帧， 那么 PLL 电路的任务就是产生一个时钟，每 4.35ms 恰好是 192 个脉冲。
那么， spdif 如果在传输过程中，因为线材垃圾了，引入了 1000ps 的 jitter , 那么 最多就是 1000ps 被 192 均分了。也就是平摊后实际线材只为每个采样点引入了 5ps 的 jitter.&lt;/p&gt;

&lt;p&gt;所以，线材引入的 jitter 只要不影响电路执行 BMC 解码以恢复出数据，实际上 spdif 传输的 jitter 主要还是由 PLL 电路自己的 jitter 决定的，和线材关系非常小。
而一般产品上常使用的 TI 生产的一款 spidif 接受芯片，其 PLL 电路的 jitter 为 50ps . 垃圾线材只是再增加 5ps ， 还是远远小于人耳可辨的 200ps。&lt;/p&gt;

&lt;p&gt;再说 USB 传输。
USB Audio 规范里，主机每 1ms 传递一个音频封包。
接收芯片同样也是使用 PLL 电路恢复时钟。
因此 USB 线材引入的 jitter 同样远远小于 PLL 电路自身的 jitter, 当然，也远远小于人耳能分辨的极限。&lt;/p&gt;

&lt;p&gt;说道这里，很多人有疑问，为啥不使用解码器的本地时钟呢？而是使用 PLL 电路去锁定主机的时钟呢？&lt;/p&gt;

&lt;p&gt;因为啊，世上没有一模一样的时钟。
假设我们运气差， PC 使用的是比标准快 的，而 DAC 使用的是慢的，那么 PC 和 DAC 会出现时差。
这个偏差会造成缓存撕裂。PC 比 DAC 快，则 PC 送来的数据 DAC 解码不过来。 PC 比DAC慢，则 DAC 常常要空转. 这2种情况都会导致爆音产生。
加大缓存只能减缓这个时刻的到来，而且增加了播放的延迟。使用更高级的温补晶振也只是缓解而不是避免这个问题。&lt;/p&gt;

&lt;p&gt;解决时钟不同步问题的最佳办法就是只使用一个时钟。由数据源提供时钟，接受端则使用 PLL 电路去锁定这时钟。&lt;/p&gt;

&lt;p&gt;这里注意下，时钟漂移和 jitter 的区别。 时钟漂移是时钟和标准时间相比走的偏快和偏慢的问题。元件规格上的这个漂移是指整个产品线上所有产品的漂移情况统计。
对于一个具体的时钟，漂移则是固定的，基本上只受温度影响下会快一点或者慢一点。
比如对于一个标称 1M 的晶振，可能这个产品实际是 999999hz 的，另一个产品是 1000001hz 的。这样就是正负 60ppm 的偏差。对于一个给定的晶振和一个给定的温度下，并不会出现这一秒 999999hz 下一秒 1000001hz 的情况。
jitter 则是每个时钟脉冲的位置会“随机偏移”, 但是每秒输出的脉冲个数是没变的。&lt;/p&gt;

&lt;p&gt;所以，音频的 jitter 早就是一个被解决掉了的技术问题。以目前的技术，完全无需担心任何 jitter 问题。音频传输相对现在的数据通信领域完全就是小儿科。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>更高采样率，更低成本</title>
   <link href="https://microcai.org/2014/10/11/higher-sample-rate-cheaper-device.html"/>
   <updated>2014-10-11T00:00:00+00:00</updated>
   <id>https://microcai.org/2014/10/11/higher-sample-rate-cheaper-device</id>
   <content type="html">&lt;p&gt;在继续本文之前，容我提出一个摩尔定律的推论： 离摩尔定律越近，发展越快，成本越低；反之，离摩尔定律越远，发展就越缓慢，成本也难以下降。&lt;/p&gt;

&lt;p&gt;[待续]&lt;/p&gt;

&lt;hr /&gt;
&lt;p&gt;=== 2023 年 12月 21 日填坑 ===&lt;/p&gt;

&lt;p&gt;没想到，当年的一篇文章，我居然没完成。挖坑了这么久。&lt;/p&gt;

&lt;p&gt;这次来回填。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;在玄学HIFI领域，是反摩尔定律的。&lt;/p&gt;

&lt;p&gt;数字音频，是个跨领域的行业。一方面，他是数字化的。一只脚在摩尔定律统治的领域。 另一只脚则是在一个传统的音像行业。&lt;/p&gt;

&lt;p&gt;快速发展的摩尔领域，就容易导致劈叉越来越严重。然后就扯到蛋了。&lt;/p&gt;

&lt;p&gt;一方面，摩尔统治下的半导体，性能指数级提升。ADC芯片能实现的采样率，很快就从 44.1khz 到了 25Ghz 以上。&lt;/p&gt;

&lt;p&gt;另一方面，传统的音像行业坚守 44.1khz 的本心不动摇。传统行业的坚守，也让 ADC 芯片的从业者，分化为了两个阵营。一个是摩尔阵营，一个是音响阵营。&lt;/p&gt;

&lt;p&gt;摩尔阵营的 ADC，每 18个月，价格降低一半。或者相同的价格能买到翻倍的采样率。&lt;/p&gt;

&lt;p&gt;音响阵营，实行的是稳定供货策略。一个 44.1khz 的 ADC, 他打着保票说，这个芯片能供货30年以上，而且绝对不乱涨价。&lt;/p&gt;

&lt;p&gt;二者之间还有一个心照不宣的秘密：摩尔阵营的ADC绝对不染指音响市场。&lt;/p&gt;

&lt;p&gt;于是市面上，你能花一两块钱的价格买到的芯片，里面内赠的ADC能达到100khz采样速度，还能采集十几个通道。&lt;/p&gt;

&lt;p&gt;但是如果你要采集的是区区20khz 带宽的信号，就必须买专门的44.1kz 采样率的ADC。这个ADC，价格从几十元到上万元不等。&lt;/p&gt;

&lt;p&gt;在  44.1khz 成为标准的年代。1.44MB/s 的码率异常惊人。耗尽一台个人电脑的全部存储空间，无法记录一秒的声音。&lt;/p&gt;

&lt;p&gt;但是，时间未过几年。个人电脑单是RAM的容量，都能放下数张CD光盘了。 就别提硬盘的容量了。更是指数级提升。&lt;/p&gt;

&lt;p&gt;按理说，音响制作领域，完全可以使用更高的采样率，更大的码率制作音频。&lt;/p&gt;

&lt;p&gt;但是，44.1khz 依旧统治世界。从 VCD 到 DVD ， 再到 BD。
整个媒体文件里，音频信息所占用的数据量比例，不断的下降。到 BD 时代，整个 25GB 容量的光盘里，音频信息只有数百MB。与 VCD 时代相比，只有略微的提升。这略微的提升，还是因为 BD 使用了更多的声道，而不是更高的采样率。&lt;/p&gt;

&lt;p&gt;声音的原始数据量，已经从巨量变成了无足轻重。 无损压缩的音频，突然成了主流。&lt;/p&gt;

&lt;p&gt;摩尔定律界的人，已经嫌弃音频的数据量太小了。填不满巨大的存储空间。不得不劝大家不要压缩声音了。再压缩，硬盘厂都要倒闭了。&lt;/p&gt;

&lt;p&gt;声音，从一开始的大山，变成了一粒尘埃。&lt;/p&gt;

&lt;p&gt;但是。音响界的人不这么看。声音他必须得是大山。我不允许摩尔定律碾压声音。不允许！&lt;/p&gt;

&lt;p&gt;其实，主动远离摩尔定律，何止是 HIFI 行业。还有单片机行业。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/2023/12/11/mcu-industry-myth.html&quot;&gt;这篇&lt;/a&gt;写的，就讲了单片机行业拒绝摩尔定律。&lt;/p&gt;

&lt;p&gt;原来其实9年前，我就已经发现了拒绝摩尔，拒绝发展的行业。今天算是来回填了。&lt;/p&gt;

&lt;p&gt;那么回到最初的标题，更高的采样率，为何会带来更低的成本？&lt;/p&gt;

&lt;p&gt;因为数字滤波器，比模拟滤波器有更高的阻带抑制度。而且成本随着摩尔定律不断下降。&lt;/p&gt;

&lt;p&gt;所以，即使声音无需更高的采样率“存储” 也应该使用更高的采样率进行录制。 然后使用越来越廉价的数字滤波器（其实就是一段代码）将超过20khz 的信号过滤的干干净净。而对20khz 以下的信号原汁原味的保留。&lt;/p&gt;

&lt;p&gt;这不比使用 44.1khz 的采样率，然后绞尽脑汁的设计滤波电路过滤掉20khz 以上信号的方案成本更低？&lt;/p&gt;

&lt;p&gt;何况非 44.1khz 的 ADC，价格只是 44.1kz 的 ADC 价格的万分之一。&lt;/p&gt;

&lt;p&gt;但是，这样的摩尔方案，他不 HIFI。因为他廉价。不配获得hifi玄学大佬的承认。&lt;/p&gt;

&lt;p&gt;正如可移植性代码，不配获得单片机大佬的承认。必须强绑定平台。绑定到具体型号。才算好代码。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>超线程, 优先级和调度策略</title>
   <link href="https://microcai.org/2014/08/17/smthyperthreading-nice-and-scheduling.html"/>
   <updated>2014-08-17T00:00:00+00:00</updated>
   <id>https://microcai.org/2014/08/17/smthyperthreading-nice-and-scheduling</id>
   <content type="html">&lt;p&gt;这是 ck 的一篇文章，原始链接在此 (SMT/Hyperthreading, nice and scheduling policies)[http://ck-hack.blogspot.com/2014/08/smthyperthreading-nice-and-scheduling.html]&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>使用自己硬件的时候写的两个补丁</title>
   <link href="https://microcai.org/2014/08/09/two-kernel-patch-for-my-pc.html"/>
   <updated>2014-08-09T00:00:00+00:00</updated>
   <id>https://microcai.org/2014/08/09/two-kernel-patch-for-my-pc</id>
   <content type="html">&lt;h1 id=&quot;键盘问题&quot;&gt;键盘问题&lt;/h1&gt;

&lt;p&gt;去年买了一个机械键盘。不过一直有一个困扰我的问题，就是键盘插入 USB 口后，会等待 10s 左右的时间，然后才识别。查看 dmesg 输出会看到有什么 Timeout 的。
后来发现是硬件 bug。而这种硬件 bug 是可以被内核 work-arround 的。 具体的来说就是，一个 USB-HID 插入的时候，内核会询问设备的一些特性，比如说是否支持多点触控之类的。
但是我这块键盘并不会不报告，导致内核等了大概 10s 没收到报告，于是就当你不支持咯。才继续下去。当然，除了这个该死的硬件闭口不谈外，没啥别的 bug。 所以 10秒后，键盘还是能正常使用。&lt;/p&gt;

&lt;p&gt;那么解决办法就很简单了，把这个键盘的设备 ID 添加到一个 缺陷列表， 列表文件在 drivers/hid/usbhid/hid-quirks.c 。
当然，遵循内核的设计风格，不能直接把设备 ID 这种 16 进制的数字填进去，所以在 drivers/hid/hid-ids.h 这里添加了宏定义。
补丁如下&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  From 7af026610fe6a41426f53f3a4ea41e3aa1ee780f Mon Sep 17 00:00:00 2001
  From: microcai &amp;lt;microcaicai@gmail.com&amp;gt;
  Date: Sun, 13 Jul 2014 15:41:14 +0800
  Subject: [PATCH] HID: usbhid: add quirks for 0x04d9:0xa096 keyborad device

  I am using a USB keyborad that give me
  &quot;usb_submit_urb(ctrl) failed: -1&quot; error when I plugin it.
  and I need to wait for 10s for this device to be ready.

  by adding this quirks, the usb keyborad is usable right after plugin

  Signed-off-by: Wangzhao Cai &amp;lt;microcaicai@gmail.com&amp;gt;
  ---
  drivers/hid/hid-ids.h           | 1 +
  drivers/hid/usbhid/hid-quirks.c | 1 +
  2 files changed, 2 insertions(+)

  diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
  index 48b66bb..9683f6c 100644
  --- a/drivers/hid/hid-ids.h
  +++ b/drivers/hid/hid-ids.h
  @@ -479,6 +479,7 @@
  #define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A070    0xa070
  #define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A072    0xa072
  #define USB_DEVICE_ID_HOLTEK_ALT_MOUSE_A081    0xa081
  +#define USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD_A096 0xa096
  
  #define USB_VENDOR_ID_IMATION          0x0718
  #define USB_DEVICE_ID_DISC_STAKKA      0xd000
  diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c
  index 31e6727..e2e8b7c 100644
  --- a/drivers/hid/usbhid/hid-quirks.c
  +++ b/drivers/hid/usbhid/hid-quirks.c
  @@ -124,6 +124,7 @@ static const struct hid_blacklist {
	  { USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_HD, HID_QUIRK_NO_INIT_REPORTS },
	  { USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_QUAD_HD, HID_QUIRK_NO_INIT_REPORTS },
	  { USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_TP_V103, HID_QUIRK_NO_INIT_REPORTS },
  +       { USB_VENDOR_ID_HOLTEK_ALT, USB_DEVICE_ID_HOLTEK_ALT_KEYBOARD_A096, HID_QUIRK_NO_INIT_INPUT_REPORTS | HID_QUIRK_HIDINPUT_FORCE },
  
	  { 0, 0 }
  };
  -- 
  2.0.4

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;看门狗设备问题&quot;&gt;看门狗设备问题&lt;/h1&gt;

&lt;p&gt;在 奶茶东 6/18 活动的时候，杀了一块 Z97 主板和 i7 一块回来。当然，顺便低价处理掉了原来的 P8P67 主板和 E3-1230 CPU。&lt;/p&gt;

&lt;p&gt;在使用 P8P67 的时候，我一直有开启 systemd 的 看门狗支持。设备 /dev/watchdog 和看门狗。驱动为 iTCO_wdt。
这个看门狗乃芯片组内置设备。所有的 intel 芯片组（别太老的）都有。
但是换了主板后，看门狗设备不见了。我不相信 intel 阉割了这个功能。肯定是驱动问题。&lt;/p&gt;

&lt;p&gt;看门狗是挂在 LPC 总线上的。CONFIG_LPC_ICH=y 已经确定开启了。但是设备就是没有。&lt;/p&gt;

&lt;p&gt;lspci 看到了一个设备&lt;/p&gt;

&lt;p&gt;00:1f.0 ISA bridge: Intel Corporation 9 Series Chipset Family Z97 LPC Controller&lt;/p&gt;

&lt;p&gt;使用 -vvv 增强输出详细度后，看到 Kernel driver in use: 这里居然是空的。&lt;/p&gt;

&lt;p&gt;等等，为啥是 ISA bridge 啊！ 于是折腾把 ISA 总线编译进去。 结果还是没有。放了一段时间没去搭理。&lt;/p&gt;

&lt;p&gt;今天又看了一下，发现详尽模式输出是这样的&lt;/p&gt;

&lt;p&gt;00:1f.0 ISA bridge [0601]: Intel Corporation 9 Series Chipset Family Z97 LPC Controller [8086:8cc4]&lt;/p&gt;

&lt;p&gt;[8086:8cc4] ?? 这个 ID 内核有么？&lt;/p&gt;

&lt;p&gt;于是搜索了 drivers/mfd/lpc_ich.c 这个文件，发现 id 那么多，唯独没有这个 ID !!! 于是抱着试试看的态度，向这个文件添加了这个 id , 哈哈，重启后，看门狗设备就乖乖出现了。&lt;/p&gt;

&lt;p&gt;下面放出这个补丁，以便让使用这款 华硕 Z97-A 主板的人收益&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  From 2abf7529a1b213ad2cab036cfb99dacba22d3107 Mon Sep 17 00:00:00 2001
  From: Wanzhao Cai &amp;lt;microcaicai@gmail.com&amp;gt;
  Date: Sat, 9 Aug 2014 01:46:29 +0800
  Subject: [PATCH] mfd: lpc_ich: add support for Intel Z97 chipset

  This patch adds the LPC Controller Device IDs found on ASUS Z97-A mother broad.

  lspci output for this mother broad:

  00:1f.0 ISA bridge [0601]: Intel Corporation 9 Series Chipset Family Z97 LPC Controller [8086:8cc4]
	  Subsystem: ASUSTeK Computer Inc. Device [1043:8534]
	  Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx-
	  Status: Cap+ 66MHz- UDF- FastB2B- ParErr-
	  DEVSEL=medium &amp;gt;TAbort- &amp;lt;TAbort- &amp;lt;MAbort- &amp;gt;SERR-
	  &amp;lt;PERR- INTx-
	  Latency: 0
	  Capabilities: [e0] Vendor
	  Specific Information: Len=0c &amp;lt;?&amp;gt;
	  Kernel driver in use: lpc_ich
	  Kernel modules: lpc_ich

  Signed-off-by: Wanzhao Cai &amp;lt;microcaicai@gmail.com&amp;gt;
  ---
  drivers/mfd/lpc_ich.c | 1 +
  1 file changed, 1 insertion(+)

  diff --git a/drivers/mfd/lpc_ich.c b/drivers/mfd/lpc_ich.c
  index 7d8482f..e4b37c0 100644
  --- a/drivers/mfd/lpc_ich.c
  +++ b/drivers/mfd/lpc_ich.c
  @@ -738,6 +738,7 @@ static const struct pci_device_id lpc_ich_ids[] = {
	  { PCI_VDEVICE(INTEL, 0x1f3b), LPC_AVN},
	  { PCI_VDEVICE(INTEL, 0x0f1c), LPC_BAYTRAIL},
	  { PCI_VDEVICE(INTEL, 0x2390), LPC_COLETO},
  +       { PCI_VDEVICE(INTEL, 0x8cc4), LPC_WPT_LP},
	  { PCI_VDEVICE(INTEL, 0x9cc1), LPC_WPT_LP},
	  { PCI_VDEVICE(INTEL, 0x9cc2), LPC_WPT_LP},
	  { PCI_VDEVICE(INTEL, 0x9cc3), LPC_WPT_LP},
  -- 
  2.0.4

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;许多时候为内核添加设备支持就是一个 id 添加进去的事情，呵呵。如果发现自己的设备不被支持了，就试试看这招吧！&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>avbot 10.4 发布</title>
   <link href="https://microcai.org/2014/06/06/avbot-10.4.html"/>
   <updated>2014-06-06T00:00:00+00:00</updated>
   <id>https://microcai.org/2014/06/06/avbot-10.4</id>
   <content type="html">&lt;p&gt;对于不知道　avbot 是神码的同学，猛击&lt;a href=&quot;http://qqbot.avplayer.org&quot;&gt;这里&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;avbot 不知不觉都已经经历一年有余的开发啦。 10.4 版本应该是一个里程碑式稳定版本。呵呵。 更新还是挺多的。&lt;/p&gt;

&lt;p&gt;avbot 的验证码识别功能详情请参考项目 &lt;a href=&quot;http://wiki.avplayer.org/deCAPTCHA&quot;&gt;wiki&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;
&lt;p&gt;下面是 Changelog
—&lt;/p&gt;

&lt;p&gt;10.4
	* 更新了内置的 gloox 库。&lt;/p&gt;

&lt;p&gt;10.3
	* 添加了 systemd 支持&lt;/p&gt;

&lt;p&gt;10.2
	* 添加了 libZMQ 支持. 
	* windows 上改进型的 GUI 处理&lt;/p&gt;

&lt;p&gt;10.1	
	* 使用 stackfull 协程重写 xmpp 逻辑。
	* 移除 boost.log 依赖，我能说 boost.log 是个垃圾库么？&lt;/p&gt;

&lt;p&gt;10.0
	* 新增 python 脚本支持。
	* lua 功能在 win32 平台利用 延迟加载DLL特性，只要存在 lua51.dll 即可开启功能，不存在即关闭。无需作为编译选项。
	* qq密码支持非明文，提高安全性&lt;/p&gt;

&lt;p&gt;9.3
	* 新增了一个 avextension.dll 的扩展支持，通过加载该扩展DLL实现功能扩展&lt;/p&gt;

&lt;p&gt;9.2
	* 添加了 纯真 IP 数据库支持。在聊天中提到的 ip 地址都会响应地理位置.
9.0
	* libwebqq is now submoduled. that means, libwebqq is an seperated project now.
	* .qqbot mail end 并不能终止邮件记录，再次发出该命令将导致目前为止的记录被再次发送. 因而多次执行 .qqbot mail to将导致意外的信息泄漏。修正次错误&lt;/p&gt;

&lt;p&gt;8.0
	* 重启 avbot 不重新登录, 直到 cookie 失效才重登录
	* irc 逻辑重写.
	* 日志支持使用 http::/host:6276/search?channel=群号&amp;amp;q=搜索词&amp;amp;date= 进行搜索
	* 支持 mingw64 编译
	* 日志同时写入 qqlog 目录下的  avlog.db sqlite3数据库文件里.
	* 使用 ircserver 配置 irc 服务器, 不再强制要求 freenode.net
	* re-write RPC server, RPC now works
	* print out backtrace when segfault(only on linux).
	* fix endless loop that will run out your dechaptcha&lt;/p&gt;

&lt;p&gt;7.2
	* fix c++11 mode build that doesn’t even work
	* drop autotools build support
	* a complete re-write of cookie handling make avbot more like a browser&lt;/p&gt;

&lt;p&gt;7.1
	* run as -D , log to syslog
	* 新增慧眼答题打码平台的支持
	* urlpreview 功能可以定义黑名单, 不对指定的url进行响应.&lt;/p&gt;

&lt;p&gt;7.0
	* 添加了两家印度阿三的分布式人肉验证码识别API支持
	* 添加了AVPLAYER.ORG社区提供的无偿验证码识别服务支持
	* 添加了联众打码平台的支持&lt;/p&gt;

&lt;p&gt;6.3
	* httpagent.hpp merged to avhttp, use #include &amp;lt;avhttp/async_read_body.hpp&amp;gt;
	* 日志部分使用 boost.log 重写
	* 此版本开始要求 boost &amp;gt;= 1.54&lt;/p&gt;

&lt;p&gt;6.2
	* 最后一个支持 boost &amp;lt;= 1.53 的版本. 下一个版本开始将要求 boost 1.54
	* 重写了 http 处理代码
	* fix joke segfault
	* fix mail bug
	* add back MSVC compiler support, only used to make jack happy. not intend to be production ready.&lt;/p&gt;

&lt;p&gt;6.1
	* add stock price
	* fix cookie process, make it more stable
	* urlpreview now prevent noticing user the same url if the url is typed too fast
	* urlpreview now parse the html escape character correctly&lt;/p&gt;

&lt;p&gt;6.0
	* fix 100% cpu bug
	* fix random relogin&lt;/p&gt;

&lt;p&gt;5.0
	* 支持白银黄金现货报价
	* new icon set
	* webqq: fix webqq group chat picture
	* htmlog: distribute image files to sub dirs.
	* libwebqq: move cache file to cache/ sub directory&lt;/p&gt;

&lt;p&gt;4.7
	* bulletin: broadcast a bulletin message @ desired time
	* win32: settings dialog make irc optional
	* htmlog: log HTML correttly.
	* lua: add more lua example code.&lt;/p&gt;

&lt;p&gt;4.6
	* allow to -c to specify the location of the config file
	* automantically show url content then some one post an url
	* change .qqbot newbee command to .qqbot welcome
	* add experimental support for luascripting&lt;/p&gt;

&lt;p&gt;4.5
	* sync webqq protocol
	* clean cache file when startup
	* a bug that cause xmpp no connection&lt;/p&gt;

&lt;p&gt;4.4
	* fix a bug that new group is not created automantically
	* 自动欢迎新人使用的是 $qqlogdir/$qqgroup/welcome.txt 的内容
	* 新人加群自动欢迎新人
	* try to re-fetch the joke page if decoding error occurs&lt;/p&gt;

&lt;p&gt;4.3
	* allow change joke interval
	* allow to switchoff joke&lt;/p&gt;

&lt;p&gt;4.2
	* 每十分钟冷场就讲一个笑话.
	* RPC 功能允许 POST&lt;/p&gt;

&lt;p&gt;4.1
	* 修正错误的 asio 用法，由 asio 爸爸的演讲视频指出的
	* avbot will check for image file and redownload corrupted one, aka say, the checksum does not match
	* avbot 启动的时候会对 images 文件夹下的文件执行校验检查。发现文件检验和文件名不符合的时候重新从腾讯服务器下载。&lt;/p&gt;

&lt;p&gt;4.0.2
	* fix strange win32 qq message send error
4.0.1
	* fix XMPP not forward error
4.0
	* avbot 重构，使用 libavbot 提供的 class avbot。 方便其他程序包含 avbot 功能
	* url 记录方式重构。
	* better support for windows platform - via mingw
	* 修复在 windows 控制台下输出乱码的问题&lt;/p&gt;

&lt;p&gt;3.6
	* add autotools build system in case cmake is not avaliable
	* fix a critical bug that will DOS the TX webqq server.
	* to make life easier , drop the support for VC , you can still compile avbot by mingw
	* avhttp now default to use HTTP 1.1 and avbot is using HTTP1.1 too
	* with the control of .qqbot, avbot can join qq groups by itself.
	* allow caching the qqgroup result so that if we have trouble fetching the group list, we won’t suffer
	* allow to fetch the group chat image to local storage&lt;/p&gt;

&lt;p&gt;3.5
	* fix VC2010 compilation, VC sucks, does it?
	* allow IRC/XMPP to use .qqbot command. that’s a long lacking feature
	* allow customize the message preamble
	* allow sending email via “.qqbot mail to  XXX” command
	* SMTP supports STARTSTL, make it more secure&lt;/p&gt;

&lt;p&gt;3.4
	* Internet Mail Format parser re-write.
	* delete mail only if successfully sended to the channel.
	* fix VC2010 compilation
	* windows support for stdin
	* smtp support (libs/developers only, not avliable to avbot)&lt;/p&gt;

&lt;p&gt;3.3
	* pull in upstream avproxy that add http_proxy support.
	* allow to have signel  -d command line option to run as daemon
	* disable stdin process when run in daemon mode
	* limit the IRC msg send rate
	* broadcast the .qqbot command result
	* –xmppnick to set the nick
	* auto rename the nick when nick conflict occurs
	* add new command .qqbot version&lt;/p&gt;

&lt;p&gt;3.2.2
	* fixed many bugs&lt;/p&gt;

&lt;p&gt;3.2.1
	* bug fix
	* XMPP async connect fix
3.2
	* irc support multi line message support&lt;/p&gt;

&lt;p&gt;3.1.1
	* bug fix&lt;/p&gt;

&lt;p&gt;3.1
	* socks5_proxy support
3.0
	* avbot 现在包含了一个POP3客户端，可以到指定(–mailaddr参数指定)的邮箱获取邮件，并将邮件贴到群聊天里。
	* 改进的WebQQ协议处理，更稳定，更少下线时间
	* 支持通过标准输入直接输入验证码，不再需要通过IRC频道输入验证码
	* 大量的代码改进，使用协程优化了WebQQ登录过程的处理，使用协程优化了IRC连接处理&lt;/p&gt;

&lt;p&gt;2.3
	* bug fix
2.0
	* add support for XMPP protocol&lt;/p&gt;

&lt;p&gt;1.0
	* qqbot works for WebQQ and IRC protocol
0.1
	* tech-preview
	* many copy &amp;amp; paste from lwqq
	* only WebQQ supported. can log chat to html&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>avbot 里的回调</title>
   <link href="https://microcai.org/2014/05/25/callbacks-on-avbot.html"/>
   <updated>2014-05-25T00:00:00+00:00</updated>
   <id>https://microcai.org/2014/05/25/callbacks-on-avbot</id>
   <content type="html">&lt;p&gt;asio 这样的库，每次发起一个操作，就带一个回调．回调告诉你，神马时候操作完成了，完成的咋样．&lt;/p&gt;

&lt;p&gt;在所有被主动调用的接口处，　avbot 基本上都遵循了 asio 的这一设计准则：提供回调完成事件通知．&lt;/p&gt;

&lt;p&gt;但是，还有另一种回调：这个回调并不是因为你主动调用了某个接口，而是因为，这是框架的一部分．&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;你不用调用框架，框架会来调用你．&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;在被框架调用的地方，如果阻塞了，势必会导致框架内部的执行流被整个阻塞．比如说，如果设定一个
“验证码回调”，框架登录QQ发现需要验证码的时候就调用．那么，执行解验证码这个操作如果费时（这
肯定费时啊，等待用户从控制台输入嘛）会导致整个avbot的内部循环被阻塞.　&lt;/p&gt;

&lt;p&gt;这明显不是我想要的结果．&lt;/p&gt;

&lt;p&gt;这也是因此我极力避免框架式设计．　但是，框架式设计有时候又是无可奈何的最简单的做法．&lt;/p&gt;

&lt;p&gt;&lt;em&gt;那么，为何不让我们的回调接受一个框架传来的　Handler ?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;是的！ 这样框架就无需要求回调一定是非阻塞的了！&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>读博客园的创业历史</title>
   <link href="https://microcai.org/2014/05/12/about-startup.html"/>
   <updated>2014-05-12T00:00:00+00:00</updated>
   <id>https://microcai.org/2014/05/12/about-startup</id>
   <content type="html">&lt;p&gt;前些日子，看到微博上传说，博客园的真爱是阿里云。说他们在阿里云平台上走了一次又一次的弯路，当了一次又一次的小白。但是，阿里虐他千百遍，他待阿里如初恋。&lt;/p&gt;

&lt;p&gt;顿时觉得有趣，于是去看了博客园解决云计算之路上的一个又一个坑。&lt;/p&gt;

&lt;p&gt;开始是抱着围观群众看热闹的心态去看的，看来他们一年多踩的无数个坑，每次踩坑还都去研究源码去找解决办法，顿时觉得，这个团队其实是一个脚踏实地的人在领导。
没有那种浮躁的氛围，肯耐心去做事情。于是去看站长 dudu 的日志。
发现第一篇博客是 04 年发出的。 原来博客园都已经坚持了十年了！&lt;/p&gt;

&lt;p&gt;十年前，博客园还只是 dudu 的一个小玩具。但是他总是安心的更新技术，每月都有数篇博文。这种坚持实在难得！
4年后， 也就是  08 年，dudu 开始创业，将博客园从个人站转向公司。&lt;/p&gt;

&lt;p&gt;在创业前，以一己之力维护了一个站点4年。4年不间断的更新程序。不间断的写博文记录。这种坚持，实在是佩服！&lt;/p&gt;

&lt;p&gt;开始创业一年多后，他们才注册了公司。&lt;/p&gt;

&lt;p&gt;是的，如果没有必要，不要成立公司。公司一经成立，就开始为政府烧钱，晚成立公司一点，就可以省下大笔开支。&lt;/p&gt;

&lt;p&gt;创业，维持小而精的团队，如无必要，不需要成立公司。在盈利之前都可以无公司组织。公司这种东西，可以等开始盈利的时候去注册。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>纯真数据库下载或自动更新实现</title>
   <link href="https://microcai.org/2014/05/11/qqwry_dat_download.html"/>
   <updated>2014-05-11T00:00:00+00:00</updated>
   <id>https://microcai.org/2014/05/11/qqwry_dat_download</id>
   <content type="html">&lt;p&gt;用过珊瑚虫的童鞋都知道, 有个叫 “纯真数据库” 的东西, 可以查询 ip 地址对应的物理地址.&lt;/p&gt;

&lt;p&gt;纯真数据库是有个名为 QQWry,DAT 的二进制文件, 可以通过纯真数据库自己提供的查询程序进行更新.&lt;/p&gt;

&lt;p&gt;有关该数据库格式和解析的内容, 本帖子暂时不讲, 有机会的话, 偶会另行开个新帖子讲讲.&lt;/p&gt;

&lt;p&gt;这里讲的是, 不通过官方的查询程序, 如何获取到这个数据库. 通过对官方程序进行抓包, 得出下载此数据库, 主要需要下载&lt;/p&gt;

&lt;p&gt;http://update.cz88.net/ip/copywrite.rar&lt;/p&gt;

&lt;p&gt;和&lt;/p&gt;

&lt;p&gt;http://update.cz88.net/ip/qqwry.rar&lt;/p&gt;

&lt;p&gt;两个文件.&lt;/p&gt;

&lt;p&gt;但是很明显,这两个压根不是 rar 文件呀! 别被扩展名迷惑了.&lt;/p&gt;

&lt;p&gt;因为要下载两个文件, 所以得出一个很明显的结论, ** 第二个文件需要用到第一个文件里的信息才能正确解开, 获得 qqwry.dat 文件. **&lt;/p&gt;

&lt;p&gt;那么,  copywrite.rar 里到底有神马东西呢?&lt;/p&gt;

&lt;p&gt;我们来打开它&lt;/p&gt;

&lt;p&gt;ghex copywrite.rar&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/copywrite_rar.png&quot; alt=&quot;copywrite.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;好吧,老实说, 根本看不明白嘛!&lt;/p&gt;

&lt;p&gt;那咋办？&lt;/p&gt;

&lt;p&gt;祭出　IDA !!!!!!&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;华丽的风格线，　其实网上已经有人说了，　这个　copywrite.rar 就是如下一个结构体．&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;copywritetag&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sign&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// &quot;CZIP&quot;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;//一个和日期有关的值&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unknown1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 0x01&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// qqwry.rar大小&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unknown2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// 解密qqwry.rar前0x200字节所需密钥&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;//提供商&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;link&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;//网址&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里，最重要的就是　key 这个整数拉！　接下来要在解码　qqwry.rar 里用到&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;下载，　qqwry.rar 初步断定这个是一个压缩文件．　为啥？　因为比　qqwry.dat 明显小了不少！&lt;/p&gt;

&lt;p&gt;别看他是　rar 扩展名，　肯定不是用的　rar 压缩算法．　为啥？　明显会用　zlib 这样的开源库来压缩嘛！　何况这样的压塑　php 都能做，是吧．　初步估计是　inflate  压塑，　对，　就是　zlib 用的那个．　约莫估计用　php 的　compress() 函数直接压塑来的．&lt;/p&gt;

&lt;p&gt;但是，用　zlib 将　qqwry.dat 压塑后，　文件大小居然一样！　哈，不过，文件头看着他怎么就是不一样呢？&lt;/p&gt;

&lt;p&gt;注意到上面的注释没? key 用来解码　qqwry.rar 的头　0x200 个字节．　也就是说，　先用　key 把开头的　 0x200 个字节给解码了，　新的数据就可以　zlib 解压了．&lt;/p&gt;

&lt;p&gt;啥？　你问我，这　0x200　偏移量怎么来的　？　诶，笨，　自己比较去吧，　却是和　zlib 压塑的，　就只有前面　0x200 不一样罢了．&lt;/p&gt;

&lt;p&gt;那么，这　0x200 个字节的数据，　到底如何解码呢？&lt;/p&gt;

&lt;p&gt;来，再次祭出　IDA !!!!!!&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;好了，网上已经有了祭出　IDA 然后得出解码算法了，　咱看下&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    for (int i = 0; i&amp;lt;0x200; i++)
    {
        key *= 0x805;
        key++;
        key &amp;amp;= 0xFF;
        uint32_t v = reinterpret_cast&amp;lt;const uint8_t*&amp;gt;(qqwry_rar.data())[i] ^ key;

        qqwry_rar[i] = v;
    }


&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;good 这样就完成了．&lt;/p&gt;

&lt;p&gt;接下来把　qqwry_rar 这个数组里的数据喂给　zlib 的　uncompress 函数就完成解压了！　ｂｉｎｇｏ&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>聊聊  type  erasure</title>
   <link href="https://microcai.org/2014/04/21/type-erasure.html"/>
   <updated>2014-04-21T00:00:00+00:00</updated>
   <id>https://microcai.org/2014/04/21/type-erasure</id>
   <content type="html">&lt;h1 id=&quot;什么是类型橡皮擦&quot;&gt;什么是类型橡皮擦?&lt;/h1&gt;

&lt;p&gt;type eraer是什么？ 为什么这么有用？ 到底它是如何帮助你构建灵活强大的软件的？&lt;/p&gt;

&lt;p&gt;我们知道 C＋＋ 是一个强类型的语言。 不同类型之间有天壤之别 ，不能任意转化——函数有签名，只有符合类型检查才能调用。
类型稍微有点不匹配，那带来的是一堆堆编译错误。 
然后很多时候，我们会想，C＋＋的类型能不要那么强就好了！比如说设计一个回调函数 ，这回调函数非得是那个类型的才能使用，多不方便。 
为了方便用户，为了能适应变化，我们将不得不使用 void*  参数，然后不停地进行强制类型转换。&lt;/p&gt;

&lt;p&gt;如果 ……&lt;/p&gt;

&lt;p&gt;如果能把类型变弱点就好了!&lt;/p&gt;

&lt;p&gt;type erasure 技术应运而生。
其实我们常用的 std::function / boost::function 就是一种  type erasure。
std::function 在一定程度上，抹去了函数的类型。不论来的类型是函数对象，还是函数指针，它统统接受，统统能赋值给它。要知道，函数对象可是有无穷多种类型。&lt;/p&gt;

&lt;h1 id=&quot;弱类型-和模板啥区别&quot;&gt;弱类型？ 和模板啥区别？&lt;/h1&gt;

&lt;p&gt;模板靠编译期自动生成一个类型兼容的函数来做到弱类型，而  type erasure则能实现在运行期弱类型。&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;somefunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;somearg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Callback&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个是弱类型了吧，但是是靠编译期实现的。如果有100 个类型的回调，就会生成 100 份代码，去适应 100 个类型的回调函数。
但是如果是这样的代码&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;somefunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;somearg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这个也是弱类型吧，但是，不论有多少回调类型，一份代码足矣。
也就是说， type erasure 能弥补模板运行期弱类型能力不足的问题。&lt;/p&gt;

&lt;p&gt;而有时候，你不得不使用 type erasure。
比如说，你需要保持一个容器，里面全是回调函数。
那么模板就无能为力了。因为创建一个容器的时候，已经要把所有的元素的类型都固定为一个了。 
这个时候你必须借助 type erasure&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;function_container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;有的人会说，用  void*  不也能实现么？&lt;/p&gt;

&lt;h1 id=&quot;type-erasure-和-void-区别在哪&quot;&gt;type erasure 和 void* 区别在哪？&lt;/h1&gt;

&lt;p&gt;和 void* 抹杀一切类型不同， type erasure 只谋杀一部分类型。 
比如说 std::function , 虽然抹杀了各种函数指针和函数对象之间的差异。
但是，他们还是有其共同点： 还是函数，还能调用。
而使用 void* 的话，就什么类型都抹杀了 
所以这是和 void * 的根本差异&lt;/p&gt;

&lt;h1 id=&quot;那么type-erasure-如何实现&quot;&gt;那么，type erasure 如何实现？&lt;/h1&gt;

&lt;p&gt;type erasure 的实现通常包含3 个部分: 接口适配器部分、类型基、模板派生类。 
接口适配器，就是暴露给用户的接口 
这个接口表现的很弱类型。 
内部实现上呢，是通过包含一个基类指针，指向分配出来的 模板派生类实现的 
这样&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;interface&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Base&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;＊&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 

  &lt;span class=&quot;nl&quot;&gt;public:&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;some_common_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;some_common_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ptr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;adapter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Base&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;public:&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;some_common_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; 

&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;adapter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;public:&lt;/span&gt; 
&lt;span class=&quot;n&quot;&gt;some_common_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;some_common_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;adapter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){}&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;通过  new Adapter&lt;被适配的类型&gt;  
赋值给 Base* 来保存这个 被适配的类型。 
通过 C＋＋ 的虚函数机制， 最终调用到被适配的类型里的操作。&lt;/被适配的类型&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>it is hopeless in china</title>
   <link href="https://microcai.org/2014/03/05/hatechina.html"/>
   <updated>2014-03-05T00:00:00+00:00</updated>
   <id>https://microcai.org/2014/03/05/hatechina</id>
   <content type="html">&lt;p&gt;I just Hate CHINA !&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>折腾了一下GPG和智能卡</title>
   <link href="https://microcai.org/2014/02/15/smartcard.html"/>
   <updated>2014-02-15T00:00:00+00:00</updated>
   <id>https://microcai.org/2014/02/15/smartcard</id>
   <content type="html">&lt;p&gt;GPG支持使用智能卡设备存储密钥。
使用文件系统来携带密钥（ 不管是存到 家目录里还是放到U盘里携带）的弊端有很多&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1. 文件泄漏密钥即泄漏
2. 不能保证密钥的物理唯一性
3. 文件可以被拷贝，文件可以被任意读取
4. 无法知道文件是否被拷贝，不能发觉
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;而使用智能卡设备，好处很多&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1. 密钥具有物理唯一性，一旦生成即无法读取，即无法制作拷贝
2. 密钥可以由卡上的CPU生成，更能保证物理唯一性
3. 智能卡可以随身携带，方便使用不同的电脑
4. 卡遗失即密钥遗失
5. 多次尝试破解会导致智能卡自我销毁，保证密钥的安全，所以遗失的卡也不会泄漏密钥
6. 加解密操作由智能卡硬件完成，即便授权应用程序也无法获取KEY。就别说木马了。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;那么， gpg 支持哪些硬件呢？&lt;/p&gt;

&lt;p&gt;gpg 支持 CCID 标准兼容的 USB 读卡器。市面上的智能卡读卡器基本上都基于这个接口。
但是，智能卡有很多种，什么门卡啊，电话卡啊，种类很多， gpg 需要的是一个 “OpenPGP Card” 标准兼容的智能卡。
这种智能卡目前在国内没有购买渠道。&lt;/p&gt;

&lt;p&gt;所幸的是，gpg通过 gnupg-pkcs11-scd 兼容支持了 PKI 设备。
所谓 PKI 设备就是使用 PKCS#11 接口进行访问的物理加解密设备，比如电子狗，U盾。&lt;/p&gt;

&lt;p&gt;那么，我需要的是一个（国内能购买到的）具备 RSA 或者 DSA 加密运算能力的 PKI 设备，同时能在 Linux 下使用。
能在 Linux 下使用，要么是使用标准的 PC/SD 接口，要么是提供了 Linux 下的驱动（PKCS#11 接口允许厂商提供一个 .so 作为驱动访问硬件。应用程序只要使用 PKCS#11 的接口即可。）&lt;/p&gt;

&lt;p&gt;一个 “国内能购买到的” 前提条件，让我的选择范围一下子缩小了。最好能使用标准接口，这样无需额外驱动即可使用。
但是这样的好东西国内似乎很难买到，基本上都需要驱动，而且是 Windows Only。
于是退求其次，找到了 “eToken Pro 72k” 这个设备。他支持 RSA 算法，密钥支持 2048bit 长度，同时也提供了 Linux 驱动。&lt;/p&gt;

&lt;p&gt;于是赶紧购买。驱动在一个 Overlay 里找了， emerge 一下就好了。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;UPDATE:
后来看到 debian wiki 里的智能卡页面发现其实也有国产的也能 Linux 下跑啦。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>折腾了一下 PC HIFI</title>
   <link href="https://microcai.org/2013/12/22/hifi.html"/>
   <updated>2013-12-22T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/12/22/hifi</id>
   <content type="html">&lt;p&gt;最近家里闲置了一对老音箱。很大很重的老音箱。年纪有个十几年了吧。但是以前是很贵的东西。虽然现在音箱很便宜（但是便宜了，音质差了呀），但是我没舍得扔掉。在我眼里古董音箱很值钱！&lt;/p&gt;

&lt;p&gt;于是我想个办法想废物利用。音箱有了，缺一个功放。&lt;/p&gt;

&lt;p&gt;我第一个找到的功放，是基于 TD2024 芯片的数字功放。主要是图个便宜，不到 50 块钱就能有一个。
至于什么是数字功放，我恶补了一下功放方面的知识。&lt;/p&gt;

&lt;p&gt;功放分 A 类 B 类 AB 类和 D 类功放。 D 类也就是数字功放。A类功放，让功放管始终工作在线性放大区。就算输入信号是 0, 功放管也在工作。
功耗大，效率低。效率只有  20% 。 大量的电能被白白消耗。&lt;/p&gt;

&lt;p&gt;B 类功放就节能多了。 B 类功放需要2个对管（一个 PNP 一个 NPN），分别放大信号的正半周和负半周。当输入信号处于正半周的时候，放大正半周的功放管工作；当输入信号处于负半周的时候，放大负半周的功放管工作。没有输入信号的时候，两个功放管都不工作。效率不错，能提高到最高 75%。&lt;/p&gt;

&lt;p&gt;但是 B 类功放有个致命缺点： 交越失真。因为晶体管只有高于大约 0.7v 电压的时候才导通进入放大状态。这样，当输入信号处于  正负0.7v内 的状态时，是不会被放大的。而通常这个时候正是处于信号正半周向负半周交越的时候（或者反过来）。于是出现交越失真。
解决办法就是设定一个偏置电流。让功放管的静态工作点（就是输入信号为 0 的时候的工作点）移到线性区域的起始点。这样就形成 AB 类功放了。&lt;/p&gt;

&lt;p&gt;B类功放的2个对管要严格匹配。需要从大量的管子中筛选出参数最接近的。&lt;/p&gt;

&lt;p&gt;不管是 A 类还是 B 类功放，都是利用功放管（通常是晶体管，要有的是用的电子管） 工作在线性区域的时候对电流的放大作用完成信号放大的。都要求功放管有较高的线性度。&lt;/p&gt;

&lt;p&gt;D类功放和前面介绍的都不一样，D类功放对功放管的线性度没要求。因为 D 类功放让功放管工作在  开关 状态。&lt;/p&gt;

&lt;p&gt;D类功放，首先对输入信号进行采样，生成 PWM  ( pulse width modulation, 脉宽调制 ) 信号。信号幅度高的地方，所调制生成的 PWM 信号占空比也高。PWM 信号的频率在  300khz 以上。接着，调制后的 PWM 信号用来控制开关管，输出大电流的 PWM 信号（通过直接调制电源得到）。高频率高电压的 PWM 信号经过一个滤波器，过滤掉  20khz 以上的信号，即可还原出原始的音频信号。&lt;/p&gt;

&lt;p&gt;因为功放管工作在开关状态，而晶体管在导通的时候电阻非常小，可以小到几个毫欧。使用理论晶体管（导通时无内阻）得出 D 类功放的效率为 100%。实际效率也在 90% 乃至  95% 以上。可以用非常小的散热做到非常大功率的输出。&lt;/p&gt;

&lt;p&gt;等等，D 类功放的核心就是一个 PWM 调制电路，然后用 PWM 信号去控制开关管（只工作在开关状态的晶体管），对吧？ 为啥不从 PCM 数字信号直接生成 PWM 信号？ 这样就免去了数模转换电路带来的损失了！&lt;/p&gt;

&lt;p&gt;基于这个想法，我开始寻找市场上是否有卖以这种思想设计的功放。我认为这种功放，和D类功放也就是数字功放比，是纯数字功放。
在某宝和叉东里直接搜索无果。放狗搜索。&lt;/p&gt;

&lt;p&gt;功夫不负有心人，我找到了一款乾龙QA100 功放。 这个就是依照我上面的原理设计的功放。它虽然有模拟输入，但是模拟输入实际上经过了一次 ADC 转化为数字信号再进行功放。可惜，太贵了！&lt;/p&gt;

&lt;p&gt;另外，刚刚提到，PWM 信号是通过开关管直接调制电源！ 这也是烧HIFI就烧电源的由来啊！如果电源不稳定，输出波形又怎么稳定。
现在使用的一般是开关电源，如果让开关电影的 PWM 和 音频部分的 PWM 信号同步运作，则功放管导通的时候，电源也在发力输出！避免那一瞬间电源空转的时候，要靠电容供电。&lt;/p&gt;

&lt;p&gt;基于这个思想，我觉得有必要让开关电源和功放同步运行啊！ 居然又被我找到了以我这个思想设计的产品， 珀璞朗韵D5P功放。&lt;/p&gt;

&lt;p&gt;还是可惜了，太贵了！&lt;/p&gt;

&lt;p&gt;要知道，一个 D 类功放，便宜的才几十块钱。因为 D 类功放，并不需要昂贵的高度线性工作的功放管，只需要能快速开关的晶体管即可。成本非常低。
而一个纯数字功放，则去掉了 模拟信号-&amp;gt;PWM 的调制电路，改为 PCM-&amp;gt;PWM 的电路。其他都没有变。成本并不会增加多少！&lt;/p&gt;

&lt;p&gt;上千元的售价，那是打劫么？！&lt;/p&gt;

&lt;p&gt;其实我知道，真正的发烧友，是会觉得”便宜没好货“ 的，如果不卖的贵点，怎么入他们的法眼！&lt;/p&gt;

&lt;p&gt;因为设计理念不一样，纯数字功放确实可以低成本实现高保真。&lt;/p&gt;

&lt;p&gt;于是我继续搜索，终于找到了一个只卖 330。价钱一个便宜了很多很多了。 于是马上拍下。&lt;/p&gt;

&lt;p&gt;离开温州，这几天在老婆家住。音箱自然不会带了，于是买了个廉价的凑合一下，就是下面图片中这种效果了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://avlog.avplayer.org/images/91/9197D050E5E67C0BAE1FEB1ADC24A771.jpg&quot; /&gt;&lt;/p&gt;

&lt;p&gt;图中音箱上的那个小 mini 机器就是纯数字功放，注意看到墙上的黑色尾巴，那根是光纤。庆幸我的主板居然带有 S/PDIF  光纤输出，吼吼。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>对未来编译器的意淫</title>
   <link href="https://microcai.org/2013/09/15/future-compiler.html"/>
   <updated>2013-09-15T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/09/15/future-compiler</id>
   <content type="html">&lt;p&gt;典型的 C++ 编译过程分 “预处理” “编译” “链接”&lt;/p&gt;

&lt;p&gt;编译, 是最耗时的环节, 编译需要编译器解析 C++ 语法, 构建 AST (抽象语法树), 解析完成后, C++ 的重任才刚刚开始. 对于 C 来说, C 解析成语法树后可以立即开始生成目标代码. 但是 C++ 不能. 最耗时的步骤才刚刚开始. C++ 要在 AST 上执行 模板展开, 重载判决. 特别是 模板展开, 这可是一次图灵完全的展开啊! 在展开的过程中, 顺便就以图灵完全的模式执行了一次模板代码. 模板展开失败不是错误, 编译器需要找到另外的模板重载重新展开.&lt;/p&gt;

&lt;p&gt;终于全部任务执行完毕后, 生成一个中间的表示. 接着开始进行第一阶段优化, 这一阶段的优化, 死代码消除, 内联展开. 这很重要, 模板代码就是在这一阶段被内联进去, 以至于和手写一样高效. 死代码消除使得为可读性考虑写的代码, 并不会真正生成目标代码, 编译器知道这是无用, 删了. 完成第一阶段优化后, 转化为目标代码. 转换完成后执行第二次优化. 这次优化是在机器指令层面进行的. 优化的地方有, 向量化啦, 指令重排序啦.&lt;/p&gt;

&lt;p&gt;最终生成优化的目标代码, 然后写入目标文件, 等待链接.&lt;/p&gt;

&lt;p&gt;其中, 预处理+解析语法树, 这两个过程, 在很多时候, 都是非常非常非常费时的操作. 虽然从语法树生成目标代码也非常耗时,但是如果不开启编译优化, 这个过程还是非常迅速的.&lt;/p&gt;

&lt;p&gt;不论开关优化, 预处理+解析语法树的时间, 是省不了的.&lt;/p&gt;

&lt;p&gt;对程序员来说, 代码是靠编辑器写出来的. 那么, 编辑器的强大能极大的加速程序员的生产效率.&lt;/p&gt;

&lt;p&gt;什么叫强大的编辑器?
最最最重要的一点: 智能提示.&lt;/p&gt;

&lt;p&gt;智能提示的核心，就是语法解析器。 并将解析到的语法，存储在数据库里。你修改一行代码， 编辑器不会像 C++ 编译器那样重新解析整个文件， 而是只重新解析 修改的那部分。以为其他没有修改的部分的语法，已经在数据库里了呀！&lt;/p&gt;

&lt;p&gt;等等！也就是说，其实 IDE 已经将程序的所有语法都掌握在手里了， 不像编译器一次只能看到一个文件， 编辑器可是看到了全部的文件！&lt;/p&gt;

&lt;p&gt;而且， 有改动，编辑器也知道到底哪里改了！编辑器不会像 C++ 编译器那样重新解析整个文件， 而是只重新解析 修改的那部分。
如果编辑器随时把自己存储的所有语法树的信息，给转化为二进制代码呢？&lt;/p&gt;

&lt;p&gt;这不就成了即时编译了！！！！！！&lt;/p&gt;

&lt;p&gt;而且， 和项目的大小无关，只和改动集的大小有关！&lt;/p&gt;

&lt;p&gt;这真是太好了，所有的编译都在完成编辑的时候完成了！&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>让 C 回调支持 boost::bind</title>
   <link href="https://microcai.org/2013/08/25/function-for-c-callback.html"/>
   <updated>2013-08-25T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/08/25/function-for-c-callback</id>
   <content type="html">&lt;p&gt;C++ 的 bind 非常方便! 但是如果你不得不处理一些 C 接口, 情况就会变得很糟糕, 你不得不处理一堆的 void* , 
不能使用 bind ! 有神码办法可以解决这个问题呢!? 答案就是 接下来介绍的模板技术 c_func_wraper !&lt;/p&gt;

&lt;p&gt;用法很简单, 看下面的例子&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;my_thread_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;c_func_wraper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;  &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;my_thread_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;pthread_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;pthread_create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c_func_ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c_void_ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;pthread_join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;new_thread&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;pthread 是一个典型的 C 接口,  需要传递给线程的参数通过 void* 传递进来.  c_func_wraper 则将 bind 和 C 接口的回调给整合起来了!&lt;/p&gt;

&lt;p&gt;c_func_wraper 接受2个模板参数, 一个是 C 接口需要的 &lt;strong&gt;函数指针&lt;/strong&gt;类型, 另一个是 类似 boost::function 所接受的函数原型声明. 注意, C 类型的声明和 boost::function 风格的声明的区别. 另外就是当前 C  类型必须是 void* 在最后一个参数. 以后可以添加出更多位置支持 :)  如第一个参数是 void* user_data 的 C 回调.&lt;/p&gt;

&lt;p&gt;接着为其 使用 bind 赋值. 赋值完毕, 就可以通过 c_func_ptr 和 c_void_ptr 两个对象获取到兼容的 C 版本了, 然后传递给 pthread_create. 就大公告成了. :)&lt;/p&gt;

&lt;p&gt;下面是 实现&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CFuncType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ClosureSignature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;c_func_wraper&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;noncopyable&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;public:&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;c_func_wraper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;m_wrapped_func&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ClosureSignature&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c_func_wraper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;delete&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_wrapped_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;m_wrapped_func&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
 	&lt;span class=&quot;n&quot;&gt;c_func_wraper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bindedfuntor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
 	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  		&lt;span class=&quot;n&quot;&gt;m_wrapped_func&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ClosureSignature&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
 		&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_wrapped_func&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bindedfuntor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
 	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;c_func_wraper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CFuncType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ClosureSignature&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;operator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bindedfuntor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_wrapped_func&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bindedfuntor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c_void_ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ClosureSignature&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_wrapped_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;CFuncType&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c_func_ptr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CFuncType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wrapperd_callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;private:&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;R&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wrapperd_callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scoped_ptr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ClosureSignature&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wrapped_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
					&lt;span class=&quot;k&quot;&gt;reinterpret_cast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ClosureSignature&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wrapped_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)();&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;R&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;typename&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ARG1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;R&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wrapperd_callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ARG1&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arg1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scoped_ptr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ClosureSignature&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wrapped_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
					&lt;span class=&quot;k&quot;&gt;reinterpret_cast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ClosureSignature&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wrapped_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arg1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;private:&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ClosureSignature&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_wrapped_func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>avbot 7.1 发布</title>
   <link href="https://microcai.org/2013/08/01/avbot-7.1.html"/>
   <updated>2013-08-01T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/08/01/avbot-7.1</id>
   <content type="html">&lt;p&gt;对于不知道　avbot 是神码的同学，猛击&lt;a href=&quot;http://qqbot.avplayer.org&quot;&gt;这里&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;紧急更新，　TX 于 8 月　1　日晚上修改了登录协议，　导致　　avbot　全线瘫痪。所有的用户必须升级！必须升级！&lt;/p&gt;

&lt;p&gt;7.1　新增了慧眼答题平台支持。urlpreview 功能可以定义黑名单, 不对指定的url进行响应. 用法就是在　qqlog 目录下建立　blockurls.txt 文件，写入不希望avbot解析的正则表达式。&lt;/p&gt;

&lt;p&gt;avbot 的验证码识别功能详情请参考项目 &lt;a href=&quot;http://wiki.avplayer.org/deCAPTCHA&quot;&gt;wiki&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;
&lt;p&gt;下面是 Changelog
—&lt;/p&gt;

&lt;p&gt;7.1
	* run as -D , log to syslog
	* 新增慧眼答题打码平台的支持
	* urlpreview 功能可以定义黑名单, 不对指定的url进行响应.&lt;/p&gt;

&lt;p&gt;7.0
	* 添加了两家印度阿三的分布式人肉验证码识别API支持
	* 添加了AVPLAYER.ORG社区提供的无偿验证码识别服务支持
	* 添加了联众打码平台的支持&lt;/p&gt;

&lt;p&gt;6.3
	* httpagent.hpp merged to avhttp, use #include &amp;lt;avhttp/async_read_body.hpp&amp;gt;
	* 日志部分使用 boost.log 重写
	* 此版本开始要求 boost &amp;gt;= 1.54&lt;/p&gt;

&lt;p&gt;6.2
	* 最后一个支持 boost &amp;lt;= 1.53 的版本. 下一个版本开始将要求 boost 1.54
	* 重写了 http 处理代码
	* fix joke segfault
	* fix mail bug
	* add back MSVC compiler support, only used to make jack happy. not intend to be production ready.&lt;/p&gt;

&lt;p&gt;6.1
	* fix cookie process, make it more stable
	* add stock price
	* urlpreview now prevent noticing user the same url if the url is typed too fast
	* urlpreview now parse the html escape character correctly&lt;/p&gt;

&lt;p&gt;6.0
	* fix random relogin
	* forward compatible with about to be released boost 1.54&lt;/p&gt;

&lt;p&gt;5.1
	* fix 100% cpu bug&lt;/p&gt;

&lt;p&gt;5.0
	* 支持白银黄金现货报价
	* new icon set
	* webqq: fix webqq group chat picture
	* htmlog: distribute image files to sub dirs.
	* libwebqq: move cache file to cache/ sub directory&lt;/p&gt;

&lt;p&gt;4.7
	* bulletin: broadcast a bulletin message @ desired time
	* win32: settings dialog make irc optional
	* htmlog: log HTML correttly.
	* lua: add more lua example code.&lt;/p&gt;

&lt;p&gt;4.6
	* allow to -c to specify the location of the config file
	* automantically show url content then some one post an url
	* change .qqbot newbee command to .qqbot welcome
	* add experimental support for luascripting&lt;/p&gt;

&lt;p&gt;4.5
        * sync webqq protocol
        * clean cache file when startup
        * a bug that cause xmpp account accidently not used&lt;/p&gt;

&lt;p&gt;4.4
	* fix a bug that new group is not created automantically
	* 自动欢迎新人使用的是 $qqlogdir/$qqgroup/welcome.txt 的内容
	* 新人加群自动欢迎新人
	* try to re-fetch the joke page if decoding error occurs&lt;/p&gt;

&lt;p&gt;4.3
	* allow change joke interval
	* allow to switchoff joke&lt;/p&gt;

&lt;p&gt;4.2
	* 每十分钟冷场就讲一个笑话.
	* RPC 功能允许 POST&lt;/p&gt;

&lt;p&gt;4.1
	* 修正错误的 asio 用法，由 asio 爸爸的演讲视频指出的
	* avbot will check for image file and redownload corrupted one, aka say, the checksum does not match
	* avbot 启动的时候会对 images 文件夹下的文件执行校验检查。发现文件检验和文件名不符合的时候重新从腾讯服务器下载。
	* fix strange win32 qq message send error
	* fix XMPP not forward error&lt;/p&gt;

&lt;p&gt;4.0
	* avbot 重构，使用 libavbot 提供的 class avbot。 方便其他程序包含 avbot 功能
	* url 记录方式重构。
	* better support for windows platform - via mingw
	* 修复在 windows 控制台下输出乱码的问题&lt;/p&gt;

&lt;p&gt;3.6
	* add autotools build system in case cmake is not avaliable
	* fix a critical bug that will DOS the TX webqq server.
	* to make life easier , drop the support for VC , you can still compile avbot by mingw
	* avhttp now default to use HTTP 1.1 and avbot is using HTTP1.1 too
	* with the control of .qqbot, avbot can join qq groups by itself.
	* allow caching the qqgroup result so that if we have trouble fetching the group list, we won’t suffer
	* allow to fetch the group chat image to local storage&lt;/p&gt;

&lt;p&gt;3.5
	* fix VC2010 compilation, VC sucks, does it?
	* allow IRC/XMPP to use .qqbot command. that’s a long lacking feature
	* allow customize the message preamble
	* allow sending email via “.qqbot mail to  XXX” command
	* SMTP supports STARTSTL, make it more secure&lt;/p&gt;

&lt;p&gt;3.4
	* Internet Mail Format parser re-write.
	* delete mail only if successfully sended to the channel.
	* fix VC2010 compilation
	* windows support for stdin
	* smtp support (libs/developers only, not avliable to avbot)&lt;/p&gt;

&lt;p&gt;3.3
	* pull in upstream avproxy that add http_proxy support.
	* allow to have signel  -d command line option to run as daemon
	* disable stdin process when run in daemon mode
	* limit the IRC msg send rate
	* broadcast the .qqbot command result
	* –xmppnick to set the nick
	* auto rename the nick when nick conflict occurs
	* add new command .qqbot version&lt;/p&gt;

&lt;p&gt;3.2.2
	* fixed many bugs&lt;/p&gt;

&lt;p&gt;3.2.1
	* bug fix
	* XMPP async connect fix
3.2
	* irc support multi line message support&lt;/p&gt;

&lt;p&gt;3.1.1
	* bug fix&lt;/p&gt;

&lt;p&gt;3.1
	* socks5_proxy support
3.0
	* avbot 现在包含了一个POP3客户端，可以到指定(–mailaddr参数指定)的邮箱获取邮件，并将邮件贴到群聊天里。
	* 改进的WebQQ协议处理，更稳定，更少下线时间
	* 支持通过标准输入直接输入验证码，不再需要通过IRC频道输入验证码
	* 大量的代码改进，使用协程优化了WebQQ登录过程的处理，使用协程优化了IRC连接处理&lt;/p&gt;

&lt;p&gt;2.3
	* bug fix
2.0
	* add support for XMPP protocol&lt;/p&gt;

&lt;p&gt;1.0
	* qqbot works for WebQQ and IRC protocol
0.1
	* tech-preview
	* many copy &amp;amp; paste from lwqq
	* only WebQQ supported. can log chat to html&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>GC是错误的内存管理模式</title>
   <link href="https://microcai.org/2013/07/27/gc-is-wrong-way-of-doing-memory-managment.html"/>
   <updated>2013-07-27T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/07/27/gc-is-wrong-way-of-doing-memory-managment</id>
   <content type="html">&lt;p&gt;只要写程序,  就免不了要和各种资源打交道, 其中最频繁的莫过于内存了.&lt;/p&gt;

&lt;p&gt;任何一个程序都需要内存管理. 它不管是简单的还是复杂的, C 语言的还是 java 语言的. 所不一样的是, 内存管理的细节掌握在谁的手里.&lt;/p&gt;

&lt;p&gt;对于 C 语言, 毫无疑问的, 程序员掌握所有细节. 程序员获得了最大的灵活性, 作为代价, 编译器不对任何&lt;strong&gt;&lt;em&gt;内存管理上的疏忽&lt;/em&gt;&lt;/strong&gt;负责. 而人是 &lt;em&gt;最容易犯错&lt;/em&gt; 的生物. 意味着, 程序员总会犯错, 因此很难写出在内存管理上没有瑕疵的程序.&lt;/p&gt;

&lt;p&gt;毫无疑问的, 将内存管理的重担全部丢给程序员, 是编译器水平低下的时代无奈的选择. 随着编译器技术的发展, 将内存管理的任务从程序员手中接管是必然的.&lt;/p&gt;

&lt;p&gt;对于如何接管内存管理, 语言作者们分成了截然不同的两派&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;垃圾收集&lt;/li&gt;
  &lt;li&gt;RAII (Resource Acquisition Is Initialization) + 智能指针&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在带有垃圾收集的语言里, 程序员只管分配内存, 无需操心释放. 垃圾收集器间歇性的运作, 会将不再使用的内存释放掉. 至于如何标记哪些内存是不再使用的, 几十年间发展出了各种算法. 许多语言都带有多种标记算法供选择.  “没有哪一种垃圾收集策略是适合所有程序的, 所以各种语言都发展出多套垃圾收集器, 供运行时选择.”&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;在许多语言里, 垃圾收集并不是编译器实现的, 而是由语言附带的运行时环境实现的, 编译器为运行时提供了附加的信息. 这就导致了语言和运行时的强耦合. 让人无法分清语言的特性和运行时的特性.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;垃圾收集不是完美的, 使用垃圾收集并不意味着就可以高枕无忧了.&lt;/strong&gt; 垃圾收集并不意味着内存泄漏成为过去式, 倒是野指针确实成为了过去式, 因为只要还有指针引用一个对象, 这个对象就绝对不会被释放. (不过, 带有垃圾收集的语言或多或少都废除了指针吧, 用引用替代了指针)&lt;/p&gt;

&lt;p&gt;有很多很多复杂的原因丢会导致垃圾收集器无法回收特定的内存, 导致这部分内存泄漏. 更严重的是, 你很难将内存泄漏和还未被清除的内存完全区别开来. 到底是&lt;strong&gt;延迟收集策略&lt;/strong&gt; 还是真的发生了&lt;strong&gt;内存泄漏&lt;/strong&gt;
 ?   你永远都无法正确分辨.&lt;/p&gt;

&lt;p&gt;结果是, 程序员最终不得不回到 C 语言的老路上, 小心的检查所有的内存分配, 确保没有触发垃圾收集器的bug或者特定的一些策略 . 几乎所有使用带垃圾收集的语言开发的程序, 在其开发的后期都要经历惨痛的 “内存检查” , 回顾所有可能导致内存泄漏的代码.&lt;/p&gt;

&lt;p&gt;垃圾收集器的另一个问题是, 除了内存, 它无法对程序使用的其他资源执行垃圾收集. 垃圾收集是以内存管理为目标产生的, 只能收集不再使用的内存, 而不能收集程序使用的其他资源, 如消息列队, 文件描述符, 共享内存, 锁.等等. 程序员不得不对其他资源执行手工管理, 像 C 程序员那样小心翼翼的操作.&lt;/p&gt;

&lt;h1 id=&quot;最终垃圾收集仍然没有解决-人容易犯错-的问题-还是把其他资源的泄漏问题丢给了程序员&quot;&gt;最终垃圾收集仍然没有解决 “人容易犯错” 的问题, 还是把其他资源的泄漏问题丢给了程序员.&lt;/h1&gt;

&lt;p&gt;C++ 从来不认为垃圾收集是有用的东西, 和 C 派不一样 , C 派不喜欢垃圾收集纯粹是因为喜欢 “自己控制一切” (天生的 M 属性). C++ 派同样认为, 要把程序员从资源管理的重担里解放出来. 同 “投机取巧” 的 GC派不同, C++ 做了很多思考, 并最终经历了 30年的时间终于找到了解决的办法. 写入了 C++11 标准.&lt;/p&gt;

&lt;p&gt;在这30年的时间里, C++ 的资源管理是逐步发展的. C++11 最终提出的智能指针, 源于 C++30年的探索.&lt;/p&gt;

&lt;p&gt;C++ 要想实现 RAII + 智能指针, 两大技术缺一不可  1. 自动确定并调用 构造函数和析构函数 2. 模板&lt;/p&gt;

&lt;p&gt;C++ 的第一步试图解放资源管理重任, 是为 C 加入了构造函数和析构函数. 构造函数和析构函数由编译器调用, 生命期终止的对象会自动调用析构函数. 不管生命期终止的原因是 return 返回, 还是 抛出了异常, 编译器总是保证, 生命期终止的对象一定会被调用析构函数.&lt;/p&gt;

&lt;p&gt;以 “编译器自动保证对象生命期” 的技术依托下, C++ 发明了 RAII 技术, 将资源的管理变成了对象的管理,而&lt;strong&gt;自动变量&lt;/strong&gt; (创建在栈上的对象, 类的成员变量) 的生命期由编译器自动保证,  只要在构造函数里申请资源, 在析构函数里正确的释放资源, RAII 技术解决了一大部分的资源管理问题.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;模板的引入使得 RAII 技术得以 “一次实现到处使用”. 如实现一次 std::vector 就可以到处使用在需要数组的情况下, 而无需为每种类型的分别实现数组RAII类.&lt;/strong&gt;  STL 内置了大量的容器, 几乎满足了所有的需求. STL的容器无法满足需求的情况下, 程序员仍然能借用 STL 的理念实现自己的 RAII 容器.&lt;/p&gt;

&lt;p&gt;但是, 如果对象分配于堆上, 程序员不得不手工调用 delete 删除对象. 忘记将不用的对象 delete 就成了头号资源泄漏原因.&lt;/p&gt;

&lt;p&gt;如果指针也是自动对象就好了.&lt;/p&gt;

&lt;p&gt;C++ 标准的第一次尝试是纳入 std::auto_ptr .  但是效果并不好, 不是所有指针都可以为 auto_ptr 所代替. 最要命的是, STL 容器无法使用 auto_ptr.&lt;/p&gt;

&lt;p&gt;C++ 标准的第二次尝试就是纳入了 std::shared_ptr , shared_ptr 在进入 C++11 标准之前, 已经在 Boost 库里实践了相当长的时间.&lt;/p&gt;

&lt;p&gt;首先得益于 C++ 的模板技术, shared_ptr 只需实现&lt;strong&gt;一次&lt;/strong&gt;, 即变成可用于任何类型的指针. 其次, 得益于 C++ 的&lt;strong&gt;自动生命期管理&lt;/strong&gt;,  智能指针将&lt;strong&gt;需要程序员管理的堆对象&lt;/strong&gt;也变成了能利用编译器&lt;strong&gt;自动管理&lt;/strong&gt;的自动变量.&lt;/p&gt;

&lt;p&gt;也就是, 智能指针彻底的将  delete 关键字 变成了 shared_ptr 才能使用的内部技术. &lt;strong&gt;编译器能自动删除 shared_ptr 对象, 也就是编译器能自动的发出 delete 调用.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;模板是智能指针技术必不可少的一部分, 否则要利用 RAII 实现智能指针就只能利用  “单根继承” 这一老土办法了. 没错, 这也是 MFC 使用的. ( MFC 诞生在 模板还没有加入 C++ 的年代. )&lt;/p&gt;

&lt;h3 id=&quot;直到-1998-年-c-标准纳入了模板-c-才最终具备了实现自动内存管理所必须的特性&quot;&gt;直到 1998 年 C++ 标准纳入了模板, C++ 才最终具备了实现自动内存管理所必须的特性.&lt;/h3&gt;

&lt;h3 id=&quot;但是准备好这些特性-到利用这些特性发明出真正能用的智能指针-则又花了13年的时间--2011年加入了-shared_ptr-&quot;&gt;但是准备好这些特性, 到利用这些特性发明出真正能用的智能指针, 则又花了13年的时间. ( 2011年加入了 shared_ptr. )&lt;/h3&gt;

&lt;h1 id=&quot;发明出编译器实现的自动内存管理需要时间-c-花了-30年的时间-没有耐心的语言走了捷径-gc-就是这条捷径&quot;&gt;发明出编译器实现的自动内存管理需要时间, C++ 花了 30年的时间. 没有耐心的语言走了捷径, GC 就是这条捷径.&lt;/h1&gt;

</content>
 </entry>
 
 <entry>
   <title>C++ 闭包 探秘</title>
   <link href="https://microcai.org/2013/07/20/closure.html"/>
   <updated>2013-07-20T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/07/20/closure</id>
   <content type="html">&lt;p&gt;我经常说协程, 说协程的时候又经常会提到闭包. 还有我常说, boost::bind 是神器  &lt;strong&gt;归根结底, 神的是  “闭包”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;没有闭包, 就无法实现 asio 协程 (注意, 我说的是 ASIO的协程, 并不是通常意义上 setjmp/longjmp 或者 CreateFiber 又或者 boost.context 创建的协程)&lt;/p&gt;

&lt;p&gt;每次使用 bind , 你就创建了一个闭包.&lt;/p&gt;

&lt;h1 id=&quot;简单的来说--闭包就是带状态的函数&quot;&gt;简单的来说,  闭包就是带状态的函数&lt;/h1&gt;
&lt;p&gt;一个函数, 带上了一个状态, 就变成了闭包了.  什么叫 “带上状态” 呢? 意思是这个闭包有属于自己的变量, 这些个变量的值是创建闭包的时候设置的, 并在调用闭包的时候, 可以访问这些变量.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;函数是代码, 状态是一组变量&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;将代码和一组变量捆绑 (bind) , 就形成了闭包&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;内部包含 static 变量的函数, 不是闭包, 因为这个 static 变量不能捆绑. 你不能捆绑不同的 static 变量. 这个在编译的时候已经确定了.  &lt;strong&gt;闭包的状态捆绑, 必须发生在运行时&lt;/strong&gt;.&lt;/p&gt;

&lt;h1 id=&quot;闭包的实现&quot;&gt;闭包的实现&lt;/h1&gt;

&lt;p&gt;C++　里使用闭包有３个办法&lt;/p&gt;

&lt;h2 id=&quot;重载--operator&quot;&gt;重载  operator()&lt;/h2&gt;
&lt;p&gt;因为闭包是一个函数+一个状态, 这个状态通过 隐含的 this 指针传入.  所以 闭包必然是一个函数对象.
因为成员变量就是极好的用于保存状态的工具, 因此实现 operator() 运算符重载, 该类的对象就能作为闭包使用.
默认传入的 this 指针提供了访问成员变量的途径.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;事实上, lambda 和 bind 的原理都是这个.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;lambda&quot;&gt;lambda&lt;/h2&gt;

&lt;p&gt;c++11 里提供的 lambda表达式就是很好的语法糖. 其本质和手写的函数对象没有区别.&lt;/p&gt;

&lt;h2 id=&quot;boostbindstdbind&quot;&gt;boost::bind/std::bind&lt;/h2&gt;

&lt;p&gt;标准库提供的 bind 是更加强大的语法糖, 将手写需要很多很多代码的闭包, 浓缩到一行 bind 就可以搞定了.&lt;/p&gt;

&lt;h1 id=&quot;闭包的用法&quot;&gt;闭包的用法&lt;/h1&gt;

&lt;p&gt;闭包是一个强大的武器, 好好使用能事半功倍&lt;/p&gt;

&lt;h2 id=&quot;用做回调函数&quot;&gt;用做回调函数&lt;/h2&gt;

&lt;p&gt;闭包的第一个用处就是作为回调函数, 消除 C 语言里回调函数常见的 *this 指针.&lt;/p&gt;

&lt;h2 id=&quot;解耦合&quot;&gt;解耦合&lt;/h2&gt;

&lt;p&gt;@hyq 对这个问题这么看的&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;我写了一个用于音频播放的类，我把play(int16_t* data, int size)这个函数直接交给那些需要播放音频的模块&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;但是，如果我某天想要换另外一个音频播放的类，这个类的接口变成了play(int channel, int16_t* data, int size)，那么我可以用boost::bind将第一个参数先行绑定了&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;用boost::bind可以把不同模块之间可能不兼容的接口给拼接起来&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;通过兼容的函数对象, 而不是函数指针, 放宽了函数签名的要求.&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;信息隔离&quot;&gt;信息隔离&lt;/h2&gt;

&lt;p&gt;avbot  的验证码实现里, 有一个功能是 &lt;strong&gt;报告验证码错误&lt;/strong&gt; 的功能. 这个实现就用到了闭包进行信息隔离.&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;vc_code_decoded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error_code&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;provider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vccode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reportbadvc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;avbot&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mybot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;BOOST_LOG_TRIVIAL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;console_out_str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;使用 &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;console_out_str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;provider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;console_out_str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot; 成功解码验证码!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;provider&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;IRC/XMPP 好友辅助验证码解码器&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;mybot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;broadcast_message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;验证码已输入&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;mybot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;feed_login_verify_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vccode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reportbadvc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;need_vc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;on_verify_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;const_buffer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;imgbuf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;avbot&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mybot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;decaptcha&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;deCAPTCHA&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;decaptcha&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buffer_cast&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;imgbuf&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt;	&lt;span class=&quot;n&quot;&gt;imgsize&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buffer_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;imgbuf&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;imgsize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;BOOST_LOG_TRIVIAL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;got vercode from TX, now try to auto resovle it ... ...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;decaptcha&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;async_decaptcha&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vc_code_decoded&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mybot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;async_decaptcha&lt;/code&gt; 的回调函数 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vc_code_decoded&lt;/code&gt; 的第四个参数, 是一个闭包, 如果验证码有错, 直接调用这个闭包就可以完成汇报. 至于回报所需要的一些相关信息 如 &lt;strong&gt;验证码提供商&lt;/strong&gt;  &lt;strong&gt;返回的验证码ID&lt;/strong&gt; 等等信息, 都已经封装在这个闭包里了. 用户无需知道 &lt;em&gt;汇报一个错误识别的验证码到底需要多少信息&lt;/em&gt; , 无需知道. 通过闭包, 验证码识别代码将这些信息全部隐藏起来了, 甚至两一个 HANDLE 都无需提供.&lt;/p&gt;

&lt;h2 id=&quot;用作协程&quot;&gt;用作协程&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;多次调用&lt;/strong&gt;能保留状态的闭包就可以用于实现是协程.&lt;/p&gt;

&lt;p&gt;不仅仅要修改状态, 还要根据状态实现不同的行&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;保留状态的目的就是要后续调用的时候根据前面的状态选择不同的执行路径&lt;/strong&gt; 那么这样做就是协程了.
虽然手工编写状态代码并不难, 但是很麻烦, 繁琐.&lt;/p&gt;

&lt;p&gt;因此 ASIO 爸爸提供了一套强大的宏, 将状态机的实现给自动化了.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>我为什么喜欢用协程</title>
   <link href="https://microcai.org/2013/07/19/why-I-like-stackless-coroutine.html"/>
   <updated>2013-07-19T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/07/19/why-I-like-stackless-coroutine</id>
   <content type="html">&lt;p&gt;查看过 avbot 代码的人都知道, avbot 到处都是协程. 用句 ACG 的话来说,  &lt;em&gt;博士是&lt;strong&gt;协程控&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;那么, 为啥我会那么喜欢使用协程呢? 答案是协程大大简化了编程, 尤其是内存管理.&lt;/p&gt;

&lt;h2 id=&quot;协程简化了内存管理&quot;&gt;协程简化了内存管理&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;写过异步程序的人都知道, 编写异步代码最容易犯的错就是内存泄露了.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;asio的无栈协程通过 &lt;strong&gt;闭包&lt;/strong&gt; 的形式, 将异步过程所要操作的资源绑定到 &lt;strong&gt;闭包&lt;/strong&gt; 上, 并使用 shared_ptr 对这些资源执行引用计数管理. 当最后一个回调执行完毕后, shared_ptr 确保了资源的正确释放. 对于 copyable 的资源, 甚至直接作为 &lt;strong&gt;闭包&lt;/strong&gt; 的一部分, 让资源随着闭包被 ASIO 拷贝, 释放, 拷贝, 释放, 最终最后一个&lt;strong&gt;闭包&lt;/strong&gt;完成使命, 彻底撤销.&lt;/p&gt;

&lt;p&gt;下面这个是 QQ 登陆过程的一个协程代码&lt;/p&gt;

&lt;div class=&quot;language-c++ highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// qq 登录办法-验证码登录&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;login_vc_op&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coroutine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;public:&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;login_vc_op&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shared_ptr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qqimpl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WebQQ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;webqq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_vccode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;webqq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;webqq_handler_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;webqq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vccode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_vccode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;md5&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;webqq_password_encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_passwd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vccode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_verifycode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

		&lt;span class=&quot;c1&quot;&gt;// do login !&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
							  &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
								  &lt;span class=&quot;s&quot;&gt;&quot;%s/login?u=%s&amp;amp;p=%s&amp;amp;verifycode=%s&amp;amp;&quot;&lt;/span&gt;
								  &lt;span class=&quot;s&quot;&gt;&quot;webqq_type=%d&amp;amp;remember_uin=1&amp;amp;aid=%s&amp;amp;login2qq=1&amp;amp;&quot;&lt;/span&gt;
								  &lt;span class=&quot;s&quot;&gt;&quot;u1=http%%3A%%2F%%2Fweb.qq.com%%2Floginproxy.html&quot;&lt;/span&gt;
								  &lt;span class=&quot;s&quot;&gt;&quot;%%3Flogin2qq%%3D1%%26webqq_type%%3D10&amp;amp;h=1&amp;amp;ptredirect=0&amp;amp;&quot;&lt;/span&gt;
								  &lt;span class=&quot;s&quot;&gt;&quot;ptlang=2052&amp;amp;from_ui=1&amp;amp;pttype=1&amp;amp;dumy=&amp;amp;fp=loginerroralert&amp;amp;&quot;&lt;/span&gt;
								  &lt;span class=&quot;s&quot;&gt;&quot;action=2-11-7438&amp;amp;mibao_css=m_webqq&amp;amp;t=1&amp;amp;g=1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
							  &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LWQQ_URL_LOGIN_HOST&lt;/span&gt;
							  &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_qqnum&lt;/span&gt;
							  &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;md5&lt;/span&gt;
							  &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vccode&lt;/span&gt;
							  &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_status&lt;/span&gt;
							  &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;APPID&lt;/span&gt;
						  &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

		&lt;span class=&quot;n&quot;&gt;m_stream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;make_shared&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avhttp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;http_stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_ioservice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()));&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;m_stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request_options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;avhttp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request_opts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avhttp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;http_options&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cookie&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_cookies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lwcookies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;avhttp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;http_options&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;close&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

		&lt;span class=&quot;n&quot;&gt;m_buffer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;make_shared&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;streambuf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

		&lt;span class=&quot;n&quot;&gt;avhttp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;async_read_body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;c1&quot;&gt;// 在这里实现　QQ 的登录.&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error_code&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes_transfered&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;istream&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;check_login&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes_transfered&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LWQQ_STATUS_ONLINE&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_clientid&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;generate_clientid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
			&lt;span class=&quot;c1&quot;&gt;//change status,  this is the last step for login&lt;/span&gt;
			&lt;span class=&quot;c1&quot;&gt;// 设定在线状态.&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;change_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LWQQ_STATUS_ONLINE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_ioservice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bind_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;c1&quot;&gt;// 登录完成 后的后续操作&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error_code&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;detail&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_ioservice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bind_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;BOOST_ASIO_CORO_REENTER&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;c1&quot;&gt;//polling group list&lt;/span&gt;
				&lt;span class=&quot;n&quot;&gt;BOOST_ASIO_CORO_YIELD&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;update_group_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

				&lt;span class=&quot;c1&quot;&gt;// 每 10 分钟修改一下在线状态.&lt;/span&gt;
				&lt;span class=&quot;n&quot;&gt;lwqq_update_status&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_cookies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ptwebqq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

				&lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_group_msg_insending&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_msg_queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

				&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_group_msg_insending&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
				&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
					&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tuple&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WebQQ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;send_group_message_cb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_msg_queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;front&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
					&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;delayedcallms&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_ioservice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WebQQ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;send_group_message_internal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shared_from_this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;v&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
					&lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_msg_queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pop_front&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
				&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

				&lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get_ioservice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bind_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;private:&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;webqq_password_encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;//              ... 代码略 ...&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;private:&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;check_login&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error_code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bytes_transfered&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;//              ... 代码略 ...&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;private:&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shared_ptr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qqimpl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WebQQ&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_webqq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;webqq&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;webqq_handler_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;read_streamptr&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_stream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shared_ptr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;boost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;asio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;streambuf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vccode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在这个协程里, 有的&lt;strong&gt;闭包&lt;/strong&gt;成员被 shared_ptr 管理, 有的没有. 
这些资源, 统统都没有使用显式的内存管理, 而是让这些对象随着闭包的撤销被自动的析构.&lt;/p&gt;

&lt;p&gt;因为 ASIO 直接拷贝了 &lt;strong&gt;闭包&lt;/strong&gt; 所以每次调用 asyn_* 簇的函数的时候, 当前闭包对象都被拷贝,  随后当前闭包被析构.&lt;/p&gt;

&lt;p&gt;新生的闭包一直就存在 ASIO 的列队里, 直到操作完成. 然后被重新调用.  这就是 asio 协程的远离. 对象通过不停的拷贝而自我更新, 一直生存在 ASIO 的列队之中. 最后一次完成回调后, 对象就真正死去了.&lt;/p&gt;

&lt;h1 id=&quot;少编写回调函数&quot;&gt;少编写回调函数&lt;/h1&gt;

&lt;p&gt;协程的另一个作用就是可以少编写回调函数. 只要写一个闭包, 通过协程的 “多个入口多个出口” 的形式就可以 共享一个 &lt;strong&gt;闭包&lt;/strong&gt; 而无须编写大量的 回调函数.&lt;/p&gt;

&lt;p&gt;当然, 这也和 asio 对回调函数的形式进行了高度统一 的功劳是分不开的.&lt;/p&gt;

&lt;h1 id=&quot;看起来像同步逻辑&quot;&gt;看起来像同步逻辑&lt;/h1&gt;

&lt;p&gt;虽然代码执行的时候是异步的, 但是如果抛开 yield 关键字不看, 整个代码俨然就是一个同步的逻辑.
这大大的简化了开发! 大大的简化了逻辑设计!&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>avbot 的一些技术决定</title>
   <link href="https://microcai.org/2013/07/17/some-technical-decision-about-avbot.html"/>
   <updated>2013-07-17T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/07/17/some-technical-decision-about-avbot</id>
   <content type="html">&lt;p&gt;avbot 早已实现最初的目标 : 提供一个机器把 IRC/XMPP和 QQ群联合起来. 让不使用QQ的人和只使用QQ的人都恩嗯畅快的交流.&lt;/p&gt;

&lt;p&gt;现在该歇息片刻, 回顾一下这一段历程, 以及编写avbot的时候所做的一些技术决定了.&lt;/p&gt;

&lt;h1 id=&quot;boostasio&quot;&gt;Boost.Asio&lt;/h1&gt;
&lt;p&gt;为什么 avbot 会使用 Boost 进行开发呢? Boost 有什么 好处? Asio 是干嘛的?&lt;/p&gt;

&lt;p&gt;我觉得对于 Asio,  &lt;a href=&quot;/t/asio-jack-q/151/8&quot;&gt;Jack 的那篇讲座&lt;/a&gt;  足以解释的很清楚了.  Asio 是一个非常强大的网络库. 从一开始就选择了 Asio, 让 avbot 的后续开发不仅仅变得更轻松, 更重要的是, 我从 asio 那里学到了受用不尽的财富.&lt;/p&gt;

&lt;h1 id=&quot;avhttp&quot;&gt;avhttp&lt;/h1&gt;

&lt;p&gt;avhttp 可以说是 avbot 催生的一个项目. 如果不是 avbot , Jack 编写 avhttp 的动力也不是那么浓. 可能会更晚的时候开始编写, 也可能就不会开始编写.&lt;/p&gt;

&lt;p&gt;avhttp 是 Asio 思维的典型产物. 不过因为Jack个人喜好问题,  avhttp 里模板的使用还是太少了, 个人认为绝对是个遗憾.&lt;/p&gt;

&lt;p&gt;在使用 avhttp 之前, avbot 使用的HTTP网络库是 Urdl . 是个已经死掉的项目. avbot 不得不对 Urdl 进行了修改, 才让其支持了 Cookie 和一些其他 webqq 需要用到的 HTTP 头选项.&lt;/p&gt;

&lt;h1 id=&quot;gloox&quot;&gt;gloox&lt;/h1&gt;

&lt;p&gt;avbot 最初的版本只支持了  IRC 和 WebQQ. 紧接着 avbot 就实现了 XMPP 协议支持. avbot 有着严格的 单线程 要求. 因此不得不寻找能够和 Asio 进行集成的 XMPP 库.  我尝试过很多的库, 最终选定了 gloox.&lt;/p&gt;

&lt;p&gt;gloox 虽然并不是 Boost 和Asio 开发的, 但是通过 派生并重载其 TcpConnection 类, 在其重载的方法里我还是有办法调用 Asio 执行网络操作的.  我并不使用 gloox 自身的 IO 代码, 而是利用C++的多态机制重载掉 原来的网络  IO 代码. 这样就能使用主线程跑的 asio 为 gloox 提供网络操作. 如此以来 avbot 就可以继续单线程了.&lt;/p&gt;

&lt;h1 id=&quot;协程&quot;&gt;协程&lt;/h1&gt;

&lt;p&gt;avbot 可以说就是协程化的单线程程序典型.&lt;/p&gt;

&lt;p&gt;我第一次尝试使用协程,  是在为 avbot 添加 POP3 协议处理的时候. 协程实现的 POP3 协议处理代码, 将我深深震撼了.&lt;/p&gt;

&lt;p&gt;虽然是纯异步代码, 但是其代码简洁到比同步处理的还要少.&lt;/p&gt;

&lt;p&gt;在进入 reenter() 之前我就统一了错误处理, 导致写出了比同步过程还要简洁的代码.&lt;/p&gt;

&lt;p&gt;从此之后我就愈发不可收拾. 并将许多 libwebqq 上用回调套回调的代码重写为了协程.&lt;/p&gt;

&lt;p&gt;因为协程实在太好用了, 直接导致了我的沉迷.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;如果不是对异步的追求, 我也不会喜欢上协程吧&lt;/strong&gt;&lt;/p&gt;

&lt;h1 id=&quot;验证码&quot;&gt;验证码&lt;/h1&gt;

&lt;p&gt;一直以来, avbot 都是通过让好友在 IRC 输入验证码发方式对付 腾讯.&lt;/p&gt;

&lt;p&gt;直到我的一个死对头 csslayer (写 fcitx 的那位)  [ &lt;a href=&quot;http://microcai.org/2013/04/06/fcitx-gpl-valation.html&quot;&gt;恩怨参考&lt;/a&gt;   ] 开始使用 avbot. 他将avbot用在了 opensuse 的 irc 聊天室, 然后 opensuse 社区的大姐头 给我提供了一个非常有用的建议, 就是使用 印度阿三的人肉验证码服务.&lt;/p&gt;

&lt;p&gt;这让一直在苦思冥想 机器识别算法的我豁然开朗. 于是就有了 avbot 7.0 的推出.&lt;/p&gt;

&lt;p&gt;现在该歇息片刻, 回顾一下这一段历程, 以及编写avbot的时候所做的一些技术决定了.&lt;/p&gt;

&lt;h1 id=&quot;boostasio-1&quot;&gt;Boost.Asio&lt;/h1&gt;
&lt;p&gt;为什么 avbot 会使用 Boost 进行开发呢? Boost 有什么 好处? Asio 是干嘛的?&lt;/p&gt;

&lt;p&gt;我觉得对于 Asio,  &lt;a href=&quot;/t/asio-jack-q/151/8&quot;&gt;Jack 的那篇讲座&lt;/a&gt;  足以解释的很清楚了.  Asio 是一个非常强大的网络库. 从一开始就选择了 Asio, 让 avbot 的后续开发不仅仅变得更轻松, 更重要的是, 我从 asio 那里学到了受用不尽的财富.&lt;/p&gt;

&lt;h1 id=&quot;avhttp-1&quot;&gt;avhttp&lt;/h1&gt;

&lt;p&gt;avhttp 可以说是 avbot 催生的一个项目. 如果不是 avbot , Jack 编写 avhttp 的动力也不是那么浓. 可能会更晚的时候开始编写, 也可能就不会开始编写.&lt;/p&gt;

&lt;p&gt;avhttp 是 Asio 思维的典型产物. 不过因为Jack个人喜好问题,  avhttp 里模板的使用还是太少了, 个人认为绝对是个遗憾.&lt;/p&gt;

&lt;p&gt;在使用 avhttp 之前, avbot 使用的HTTP网络库是 Urdl . 是个已经死掉的项目. avbot 不得不对 Urdl 进行了修改, 才让其支持了 Cookie 和一些其他 webqq 需要用到的 HTTP 头选项.&lt;/p&gt;

&lt;h1 id=&quot;gloox-1&quot;&gt;gloox&lt;/h1&gt;

&lt;p&gt;avbot 最初的版本只支持了  IRC 和 WebQQ. 紧接着 avbot 就实现了 XMPP 协议支持. avbot 有着严格的 单线程 要求. 因此不得不寻找能够和 Asio 进行集成的 XMPP 库.  我尝试过很多的库, 最终选定了 gloox.&lt;/p&gt;

&lt;p&gt;gloox 虽然并不是 Boost 和Asio 开发的, 但是通过 派生并重载其 TcpConnection 类, 在其重载的方法里我还是有办法调用 Asio 执行网络操作的.  我并不使用 gloox 自身的 IO 代码, 而是利用C++的多态机制重载掉 原来的网络  IO 代码. 这样就能使用主线程跑的 asio 为 gloox 提供网络操作. 如此以来 avbot 就可以继续单线程了.&lt;/p&gt;

&lt;h1 id=&quot;协程-1&quot;&gt;协程&lt;/h1&gt;

&lt;p&gt;avbot 可以说就是协程化的单线程程序典型.&lt;/p&gt;

&lt;p&gt;我第一次尝试使用协程,  是在为 avbot 添加 POP3 协议处理的时候. 协程实现的 POP3 协议处理代码, 将我深深震撼了.&lt;/p&gt;

&lt;p&gt;虽然是纯异步代码, 但是其代码简洁到比同步处理的还要少.&lt;/p&gt;

&lt;p&gt;在进入 reenter() 之前我就统一了错误处理, 导致写出了比同步过程还要简洁的代码.&lt;/p&gt;

&lt;p&gt;从此之后我就愈发不可收拾. 并将许多 libwebqq 上用回调套回调的代码重写为了协程.&lt;/p&gt;

&lt;p&gt;因为协程实在太好用了, 直接导致了我的沉迷.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;如果不是对异步的追求, 我也不会喜欢上协程吧&lt;/strong&gt;&lt;/p&gt;

&lt;h1 id=&quot;验证码-1&quot;&gt;验证码&lt;/h1&gt;

&lt;p&gt;一直以来, avbot 都是通过让好友在 IRC 输入验证码发方式对付 腾讯.&lt;/p&gt;

&lt;p&gt;直到我的一个死对头 csslayer (写 fcitx 的那位)  [ &lt;a href=&quot;http://microcai.org/2013/04/06/fcitx-gpl-valation.html&quot;&gt;恩怨参考&lt;/a&gt;   ] 开始使用 avbot. 他将avbot用在了 opensuse 的 irc 聊天室, 然后 opensuse 社区的大姐头 给我提供了一个非常有用的建议, 就是使用 印度阿三的人肉验证码服务.&lt;/p&gt;

&lt;p&gt;这让一直在苦思冥想 机器识别算法的我豁然开朗. 于是就有了 avbot 7.0 的推出.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>avbot 7.0-rc1 发布</title>
   <link href="https://microcai.org/2013/07/11/avbot-7.0_rc1.html"/>
   <updated>2013-07-11T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/07/11/avbot-7.0_rc1</id>
   <content type="html">&lt;p&gt;对于不知道　avbot 是神码的同学，猛击&lt;a href=&quot;http://qqbot.avplayer.org&quot;&gt;这里&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;就像博士说的, 不开发验证码解决方案不升级版本号, 这次主版本好不到一个月就被提升了, 咋回事呢?&lt;/p&gt;

&lt;p&gt;avbot 7.0 系列发布预览版&lt;a href=&quot;http://sourceforge.net/projects/avbot/files/sources/avbot-6.1.tar.bz2/download&quot;&gt;发布&lt;/a&gt;
了，同时发布的还有　&lt;a href=&quot;https://sourceforge.net/projects/avbot/files/rpm/&quot;&gt;rpm 包&lt;/a&gt; 
和 &lt;a href=&quot;https://sourceforge.net/projects/avbot/files/win32/&quot;&gt;windows 包&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;avbot 7.0 添加了很多功能, 最重要的莫过于支持了验证码识别!&lt;/p&gt;

&lt;p&gt;avbot 的验证码识别功能详情请参考项目 &lt;a href=&quot;http://wiki.avplayer.org/deCAPTCHA&quot;&gt;wiki&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;
&lt;p&gt;下面是 Changelog
—&lt;/p&gt;

&lt;p&gt;7.0-rc1
	* 添加了两家印度阿三的分布式人肉验证码识别API支持
	* 添加了AVPLAYER.ORG社区提供的无偿验证码识别服务支持&lt;/p&gt;

&lt;p&gt;6.3
	* httpagent.hpp merged to avhttp, use #include &amp;lt;avhttp/async_read_body.hpp&amp;gt;
	* 日志部分使用 boost.log 重写
	* 此版本开始要求 boost &amp;gt;= 1.54&lt;/p&gt;

&lt;p&gt;6.2
	* 最后一个支持 boost &amp;lt;= 1.53 的版本. 下一个版本开始将要求 boost 1.54
	* 重写了 http 处理代码
	* fix joke segfault
	* fix mail bug
	* add back MSVC compiler support, only used to make jack happy. not intend to be production ready.&lt;/p&gt;

&lt;p&gt;6.1
	* fix cookie process, make it more stable
	* add stock price
	* urlpreview now prevent noticing user the same url if the url is typed too fast
	* urlpreview now parse the html escape character correctly&lt;/p&gt;

&lt;p&gt;6.0
	* fix random relogin
	* forward compatible with about to be released boost 1.54&lt;/p&gt;

&lt;p&gt;5.1
	* fix 100% cpu bug&lt;/p&gt;

&lt;p&gt;5.0
	* 支持白银黄金现货报价
	* new icon set
	* webqq: fix webqq group chat picture
	* htmlog: distribute image files to sub dirs.
	* libwebqq: move cache file to cache/ sub directory&lt;/p&gt;

&lt;p&gt;4.7
	* bulletin: broadcast a bulletin message @ desired time
	* win32: settings dialog make irc optional
	* htmlog: log HTML correttly.
	* lua: add more lua example code.&lt;/p&gt;

&lt;p&gt;4.6
	* allow to -c to specify the location of the config file
	* automantically show url content then some one post an url
	* change .qqbot newbee command to .qqbot welcome
	* add experimental support for luascripting&lt;/p&gt;

&lt;p&gt;4.5
        * sync webqq protocol
        * clean cache file when startup
        * a bug that cause xmpp account accidently not used&lt;/p&gt;

&lt;p&gt;4.4
	* fix a bug that new group is not created automantically
	* 自动欢迎新人使用的是 $qqlogdir/$qqgroup/welcome.txt 的内容
	* 新人加群自动欢迎新人
	* try to re-fetch the joke page if decoding error occurs&lt;/p&gt;

&lt;p&gt;4.3
	* allow change joke interval
	* allow to switchoff joke&lt;/p&gt;

&lt;p&gt;4.2
	* 每十分钟冷场就讲一个笑话.
	* RPC 功能允许 POST&lt;/p&gt;

&lt;p&gt;4.1
	* 修正错误的 asio 用法，由 asio 爸爸的演讲视频指出的
	* avbot will check for image file and redownload corrupted one, aka say, the checksum does not match
	* avbot 启动的时候会对 images 文件夹下的文件执行校验检查。发现文件检验和文件名不符合的时候重新从腾讯服务器下载。
	* fix strange win32 qq message send error
	* fix XMPP not forward error&lt;/p&gt;

&lt;p&gt;4.0
	* avbot 重构，使用 libavbot 提供的 class avbot。 方便其他程序包含 avbot 功能
	* url 记录方式重构。
	* better support for windows platform - via mingw
	* 修复在 windows 控制台下输出乱码的问题&lt;/p&gt;

&lt;p&gt;3.6
	* add autotools build system in case cmake is not avaliable
	* fix a critical bug that will DOS the TX webqq server.
	* to make life easier , drop the support for VC , you can still compile avbot by mingw
	* avhttp now default to use HTTP 1.1 and avbot is using HTTP1.1 too
	* with the control of .qqbot, avbot can join qq groups by itself.
	* allow caching the qqgroup result so that if we have trouble fetching the group list, we won’t suffer
	* allow to fetch the group chat image to local storage&lt;/p&gt;

&lt;p&gt;3.5
	* fix VC2010 compilation, VC sucks, does it?
	* allow IRC/XMPP to use .qqbot command. that’s a long lacking feature
	* allow customize the message preamble
	* allow sending email via “.qqbot mail to  XXX” command
	* SMTP supports STARTSTL, make it more secure&lt;/p&gt;

&lt;p&gt;3.4
	* Internet Mail Format parser re-write.
	* delete mail only if successfully sended to the channel.
	* fix VC2010 compilation
	* windows support for stdin
	* smtp support (libs/developers only, not avliable to avbot)&lt;/p&gt;

&lt;p&gt;3.3
	* pull in upstream avproxy that add http_proxy support.
	* allow to have signel  -d command line option to run as daemon
	* disable stdin process when run in daemon mode
	* limit the IRC msg send rate
	* broadcast the .qqbot command result
	* –xmppnick to set the nick
	* auto rename the nick when nick conflict occurs
	* add new command .qqbot version&lt;/p&gt;

&lt;p&gt;3.2.2
	* fixed many bugs&lt;/p&gt;

&lt;p&gt;3.2.1
	* bug fix
	* XMPP async connect fix
3.2
	* irc support multi line message support&lt;/p&gt;

&lt;p&gt;3.1.1
	* bug fix&lt;/p&gt;

&lt;p&gt;3.1
	* socks5_proxy support
3.0
	* avbot 现在包含了一个POP3客户端，可以到指定(–mailaddr参数指定)的邮箱获取邮件，并将邮件贴到群聊天里。
	* 改进的WebQQ协议处理，更稳定，更少下线时间
	* 支持通过标准输入直接输入验证码，不再需要通过IRC频道输入验证码
	* 大量的代码改进，使用协程优化了WebQQ登录过程的处理，使用协程优化了IRC连接处理&lt;/p&gt;

&lt;p&gt;2.3
	* bug fix
2.0
	* add support for XMPP protocol&lt;/p&gt;

&lt;p&gt;1.0
	* qqbot works for WebQQ and IRC protocol
0.1
	* tech-preview
	* many copy &amp;amp; paste from lwqq
	* only WebQQ supported. can log chat to html&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>avplayer 维基站点建立</title>
   <link href="https://microcai.org/2013/07/01/wiki-avplayer.html"/>
   <updated>2013-07-01T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/07/01/wiki-avplayer</id>
   <content type="html">&lt;p&gt;avplayer 社区的维基站点建立了, 欢迎大家访问  &lt;a href=&quot;http://wiki.avplayer.org&quot;&gt;http://wiki.avplayer.org&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;目前 avplayer 的维基站点开发免注册编辑权限. 大家可以把 avplayer 社区项目的一些心得分享进行编辑.&lt;/p&gt;

&lt;p&gt;比如在 windows 上如何编译 avbot, 希望辛苦解决的人能给后来人以提示.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>avbot 6.1 发布</title>
   <link href="https://microcai.org/2013/06/27/avbot-6.1.html"/>
   <updated>2013-06-27T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/06/27/avbot-6.1</id>
   <content type="html">&lt;p&gt;对于不知道　avbot 是神码的同学，猛击&lt;a href=&quot;http://qqbot.avplayer.org&quot;&gt;这里&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;avbot 前不久悄悄迎来了半周岁的生日. 因此 avbot 也进入了 6.X 开发周期.&lt;/p&gt;

&lt;p&gt;avbot 6.0 系列第2个版本 6.1  &lt;a href=&quot;http://sourceforge.net/projects/avbot/files/sources/avbot-6.1.tar.bz2/download&quot;&gt;发布&lt;/a&gt;了，同时发布的还有　&lt;a href=&quot;https://sourceforge.net/projects/avbot/files/rpm/&quot;&gt;rpm 包&lt;/a&gt; 和 &lt;a href=&quot;https://sourceforge.net/projects/avbot/files/win32/&quot;&gt;windows 包&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;avbot 6.1 发布, 添加了股票报价功能, 另外也对 WebQQ 协议进行了更新.&lt;/p&gt;

&lt;hr /&gt;
&lt;p&gt;下面是 Changelog
—&lt;/p&gt;

&lt;p&gt;6.1
	* fix cookie process, make it more stable
	* add stock price
	* urlpreview now prevent noticing user the same url if the url is typed too fast
	* urlpreview now parse the html escape character correctly&lt;/p&gt;

&lt;p&gt;6.0
	* fix random relogin
	* forward compatible with about to be released boost 1.54&lt;/p&gt;

&lt;p&gt;5.1
	* fix 100% cpu bug&lt;/p&gt;

&lt;p&gt;5.0
	* 支持白银黄金现货报价
	* new icon set
	* webqq: fix webqq group chat picture
	* htmlog: distribute image files to sub dirs.
	* libwebqq: move cache file to cache/ sub directory&lt;/p&gt;

&lt;p&gt;4.7
	* bulletin: broadcast a bulletin message @ desired time
	* win32: settings dialog make irc optional
	* htmlog: log HTML correttly.
	* lua: add more lua example code.&lt;/p&gt;

&lt;p&gt;4.6
	* allow to -c to specify the location of the config file
	* automantically show url content then some one post an url
	* change .qqbot newbee command to .qqbot welcome
	* add experimental support for luascripting&lt;/p&gt;

&lt;p&gt;4.5
        * sync webqq protocol
        * clean cache file when startup
        * a bug that cause xmpp account accidently not used&lt;/p&gt;

&lt;p&gt;4.4
	* fix a bug that new group is not created automantically
	* 自动欢迎新人使用的是 $qqlogdir/$qqgroup/welcome.txt 的内容
	* 新人加群自动欢迎新人
	* try to re-fetch the joke page if decoding error occurs&lt;/p&gt;

&lt;p&gt;4.3
	* allow change joke interval
	* allow to switchoff joke&lt;/p&gt;

&lt;p&gt;4.2
	* 每十分钟冷场就讲一个笑话.
	* RPC 功能允许 POST&lt;/p&gt;

&lt;p&gt;4.1
	* 修正错误的 asio 用法，由 asio 爸爸的演讲视频指出的
	* avbot will check for image file and redownload corrupted one, aka say, the checksum does not match
	* avbot 启动的时候会对 images 文件夹下的文件执行校验检查。发现文件检验和文件名不符合的时候重新从腾讯服务器下载。
	* fix strange win32 qq message send error
	* fix XMPP not forward error&lt;/p&gt;

&lt;p&gt;4.0
	* avbot 重构，使用 libavbot 提供的 class avbot。 方便其他程序包含 avbot 功能
	* url 记录方式重构。
	* better support for windows platform - via mingw
	* 修复在 windows 控制台下输出乱码的问题&lt;/p&gt;

&lt;p&gt;3.6
	* add autotools build system in case cmake is not avaliable
	* fix a critical bug that will DOS the TX webqq server.
	* to make life easier , drop the support for VC , you can still compile avbot by mingw
	* avhttp now default to use HTTP 1.1 and avbot is using HTTP1.1 too
	* with the control of .qqbot, avbot can join qq groups by itself.
	* allow caching the qqgroup result so that if we have trouble fetching the group list, we won’t suffer
	* allow to fetch the group chat image to local storage&lt;/p&gt;

&lt;p&gt;3.5
	* fix VC2010 compilation, VC sucks, does it?
	* allow IRC/XMPP to use .qqbot command. that’s a long lacking feature
	* allow customize the message preamble
	* allow sending email via “.qqbot mail to  XXX” command
	* SMTP supports STARTSTL, make it more secure&lt;/p&gt;

&lt;p&gt;3.4
	* Internet Mail Format parser re-write.
	* delete mail only if successfully sended to the channel.
	* fix VC2010 compilation
	* windows support for stdin
	* smtp support (libs/developers only, not avliable to avbot)&lt;/p&gt;

&lt;p&gt;3.3
	* pull in upstream avproxy that add http_proxy support.
	* allow to have signel  -d command line option to run as daemon
	* disable stdin process when run in daemon mode
	* limit the IRC msg send rate
	* broadcast the .qqbot command result
	* –xmppnick to set the nick
	* auto rename the nick when nick conflict occurs
	* add new command .qqbot version&lt;/p&gt;

&lt;p&gt;3.2.2
	* fixed many bugs&lt;/p&gt;

&lt;p&gt;3.2.1
	* bug fix
	* XMPP async connect fix
3.2
	* irc support multi line message support&lt;/p&gt;

&lt;p&gt;3.1.1
	* bug fix&lt;/p&gt;

&lt;p&gt;3.1
	* socks5_proxy support
3.0
	* avbot 现在包含了一个POP3客户端，可以到指定(–mailaddr参数指定)的邮箱获取邮件，并将邮件贴到群聊天里。
	* 改进的WebQQ协议处理，更稳定，更少下线时间
	* 支持通过标准输入直接输入验证码，不再需要通过IRC频道输入验证码
	* 大量的代码改进，使用协程优化了WebQQ登录过程的处理，使用协程优化了IRC连接处理&lt;/p&gt;

&lt;p&gt;2.3
	* bug fix
2.0
	* add support for XMPP protocol&lt;/p&gt;

&lt;p&gt;1.0
	* qqbot works for WebQQ and IRC protocol
0.1
	* tech-preview
	* many copy &amp;amp; paste from lwqq
	* only WebQQ supported. can log chat to html&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>avbot 5.1 发布</title>
   <link href="https://microcai.org/2013/06/08/avbot-5.1.html"/>
   <updated>2013-06-08T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/06/08/avbot-5.1</id>
   <content type="html">&lt;p&gt;对于不知道　avbot 是神码的同学，猛击&lt;a href=&quot;http://qqbot.avplayer.org&quot;&gt;这里&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;avbot 5.0 系列开始了.&lt;/p&gt;

&lt;p&gt;avbot 5.0 系列第3个版本 5.1  &lt;a href=&quot;http://sourceforge.net/projects/avbot/files/sources/avbot-5.1.tar.bz2/download&quot;&gt;发布&lt;/a&gt;了，同时发布的还有　&lt;a href=&quot;https://sourceforge.net/projects/avbot/files/rpm/&quot;&gt;rpm 包&lt;/a&gt; 和 &lt;a href=&quot;https://sourceforge.net/projects/avbot/files/win32/&quot;&gt;windows 包&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;avbot 的版本规则是, 每月提升一个大版本号, 因为是开发 avbot 的第 6 个月了, 所以大版本号提升到 5 了.&lt;/p&gt;

&lt;p&gt;avbot 5.1 发布, 修复了一个非常严重,困扰了我们很久的一个 100% CPU 占用问题. 
当然, 5.0 版本也顺带跟随腾讯更新了群图片的图床API. 更新avbot后群图片获取不到的bug就消失了.&lt;/p&gt;

&lt;hr /&gt;
&lt;p&gt;下面是 Changelog
—&lt;/p&gt;

&lt;p&gt;5.1
	* fix 100% cpu bug&lt;/p&gt;

&lt;p&gt;5.0
	* 支持白银黄金现货报价
	* new icon set
	* webqq: fix webqq group chat picture
	* htmlog: distribute image files to sub dirs.
	* libwebqq: move cache file to cache/ sub directory&lt;/p&gt;

&lt;p&gt;4.7
	* bulletin: broadcast a bulletin message @ desired time
	* win32: settings dialog make irc optional
	* htmlog: log HTML correttly.
	* lua: add more lua example code.&lt;/p&gt;

&lt;p&gt;4.6
	* allow to -c to specify the location of the config file
	* automantically show url content then some one post an url
	* change .qqbot newbee command to .qqbot welcome
	* add experimental support for luascripting&lt;/p&gt;

&lt;p&gt;4.5
        * sync webqq protocol
        * clean cache file when startup
        * a bug that cause xmpp account accidently not used&lt;/p&gt;

&lt;p&gt;4.4
	* fix a bug that new group is not created automantically
	* 自动欢迎新人使用的是 $qqlogdir/$qqgroup/welcome.txt 的内容
	* 新人加群自动欢迎新人
	* try to re-fetch the joke page if decoding error occurs&lt;/p&gt;

&lt;p&gt;4.3
	* allow change joke interval
	* allow to switchoff joke&lt;/p&gt;

&lt;p&gt;4.2
	* 每十分钟冷场就讲一个笑话.
	* RPC 功能允许 POST&lt;/p&gt;

&lt;p&gt;4.1
	* 修正错误的 asio 用法，由 asio 爸爸的演讲视频指出的
	* avbot will check for image file and redownload corrupted one, aka say, the checksum does not match
	* avbot 启动的时候会对 images 文件夹下的文件执行校验检查。发现文件检验和文件名不符合的时候重新从腾讯服务器下载。
	* fix strange win32 qq message send error
	* fix XMPP not forward error&lt;/p&gt;

&lt;p&gt;4.0
	* avbot 重构，使用 libavbot 提供的 class avbot。 方便其他程序包含 avbot 功能
	* url 记录方式重构。
	* better support for windows platform - via mingw
	* 修复在 windows 控制台下输出乱码的问题&lt;/p&gt;

&lt;p&gt;3.6
	* add autotools build system in case cmake is not avaliable
	* fix a critical bug that will DOS the TX webqq server.
	* to make life easier , drop the support for VC , you can still compile avbot by mingw
	* avhttp now default to use HTTP 1.1 and avbot is using HTTP1.1 too
	* with the control of .qqbot, avbot can join qq groups by itself.
	* allow caching the qqgroup result so that if we have trouble fetching the group list, we won’t suffer
	* allow to fetch the group chat image to local storage&lt;/p&gt;

&lt;p&gt;3.5
	* fix VC2010 compilation, VC sucks, does it?
	* allow IRC/XMPP to use .qqbot command. that’s a long lacking feature
	* allow customize the message preamble
	* allow sending email via “.qqbot mail to  XXX” command
	* SMTP supports STARTSTL, make it more secure&lt;/p&gt;

&lt;p&gt;3.4
	* Internet Mail Format parser re-write.
	* delete mail only if successfully sended to the channel.
	* fix VC2010 compilation
	* windows support for stdin
	* smtp support (libs/developers only, not avliable to avbot)&lt;/p&gt;

&lt;p&gt;3.3
	* pull in upstream avproxy that add http_proxy support.
	* allow to have signel  -d command line option to run as daemon
	* disable stdin process when run in daemon mode
	* limit the IRC msg send rate
	* broadcast the .qqbot command result
	* –xmppnick to set the nick
	* auto rename the nick when nick conflict occurs
	* add new command .qqbot version&lt;/p&gt;

&lt;p&gt;3.2.2
	* fixed many bugs&lt;/p&gt;

&lt;p&gt;3.2.1
	* bug fix
	* XMPP async connect fix
3.2
	* irc support multi line message support&lt;/p&gt;

&lt;p&gt;3.1.1
	* bug fix&lt;/p&gt;

&lt;p&gt;3.1
	* socks5_proxy support
3.0
	* avbot 现在包含了一个POP3客户端，可以到指定(–mailaddr参数指定)的邮箱获取邮件，并将邮件贴到群聊天里。
	* 改进的WebQQ协议处理，更稳定，更少下线时间
	* 支持通过标准输入直接输入验证码，不再需要通过IRC频道输入验证码
	* 大量的代码改进，使用协程优化了WebQQ登录过程的处理，使用协程优化了IRC连接处理&lt;/p&gt;

&lt;p&gt;2.3
	* bug fix
2.0
	* add support for XMPP protocol&lt;/p&gt;

&lt;p&gt;1.0
	* qqbot works for WebQQ and IRC protocol
0.1
	* tech-preview
	* many copy &amp;amp; paste from lwqq
	* only WebQQ supported. can log chat to html&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>avbot 4.7 更新</title>
   <link href="https://microcai.org/2013/05/08/avbot-4.7.html"/>
   <updated>2013-05-08T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/05/08/avbot-4.7</id>
   <content type="html">&lt;p&gt;对于不知道　avbot 是神码的同学，猛击&lt;a href=&quot;http://qqbot.avplayer.org&quot;&gt;这里&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;avbot 4.0 系列第九个版本 4.7  &lt;a href=&quot;http://sourceforge.net/projects/avbot/files/sources/avbot-4.7.tar.gz/download&quot;&gt;发布&lt;/a&gt;了，同时发布的还有　&lt;a href=&quot;https://sourceforge.net/projects/avbot/files/rpm/&quot;&gt;rpm 包&lt;/a&gt; 和 &lt;a href=&quot;https://sourceforge.net/projects/avbot/files/win32/&quot;&gt;windows 包&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;rpm 包有 x86_32(用于64位系统) 的和 i686 (用于32位系统) 两个版本。其实 rpm 包解压一下也可以用在 Deb 系的系统上。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;PS: sourceforge 上的 avbot　项目我开通了匿名上传权限，欢迎大家提交　deb/rpm 包供大家下载。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;4.7 版本带来的最大的变化莫过于带来了 “自动群公告支持”. 配置一下就能支持每天定时的发送一些设定好的消息.&lt;/p&gt;

&lt;p&gt;接着是完善了实验性的 lua 脚本支持 (通过 –enable-lua 启用)&lt;/p&gt;

&lt;hr /&gt;
&lt;p&gt;下面是 Changelog
—&lt;/p&gt;

&lt;p&gt;4.7
	* bulletin: broadcast a bulletin message @ desired time
	* win32: settings dialog make irc optional
	* htmlog: log HTML correttly.
	* lua: add more lua example code.&lt;/p&gt;

&lt;p&gt;4.6
	* allow to -c to specify the location of the config file
	* automantically show url content then some one post an url
	* change .qqbot newbee command to .qqbot welcome
	* add experimental support for luascripting&lt;/p&gt;

&lt;p&gt;4.5
        * sync webqq protocol
        * clean cache file when startup
        * a bug that cause xmpp account accidently not used&lt;/p&gt;

&lt;p&gt;4.4
	* fix a bug that new group is not created automantically
	* 自动欢迎新人使用的是 $qqlogdir/$qqgroup/welcome.txt 的内容
	* 新人加群自动欢迎新人
	* try to re-fetch the joke page if decoding error occurs&lt;/p&gt;

&lt;p&gt;4.3
	* allow change joke interval
	* allow to switchoff joke&lt;/p&gt;

&lt;p&gt;4.2
	* 每十分钟冷场就讲一个笑话.
	* RPC 功能允许 POST&lt;/p&gt;

&lt;p&gt;4.1
	* 修正错误的 asio 用法，由 asio 爸爸的演讲视频指出的
	* avbot will check for image file and redownload corrupted one, aka say, the checksum does not match
	* avbot 启动的时候会对 images 文件夹下的文件执行校验检查。发现文件检验和文件名不符合的时候重新从腾讯服务器下载。
	* fix strange win32 qq message send error
	* fix XMPP not forward error&lt;/p&gt;

&lt;p&gt;4.0
	* avbot 重构，使用 libavbot 提供的 class avbot。 方便其他程序包含 avbot 功能
	* url 记录方式重构。
	* better support for windows platform - via mingw
	* 修复在 windows 控制台下输出乱码的问题&lt;/p&gt;

&lt;p&gt;3.6
	* add autotools build system in case cmake is not avaliable
	* fix a critical bug that will DOS the TX webqq server.
	* to make life easier , drop the support for VC , you can still compile avbot by mingw
	* avhttp now default to use HTTP 1.1 and avbot is using HTTP1.1 too
	* with the control of .qqbot, avbot can join qq groups by itself.
	* allow caching the qqgroup result so that if we have trouble fetching the group list, we won’t suffer
	* allow to fetch the group chat image to local storage&lt;/p&gt;

&lt;p&gt;3.5
	* fix VC2010 compilation, VC sucks, does it?
	* allow IRC/XMPP to use .qqbot command. that’s a long lacking feature
	* allow customize the message preamble
	* allow sending email via “.qqbot mail to  XXX” command
	* SMTP supports STARTSTL, make it more secure&lt;/p&gt;

&lt;p&gt;3.4
	* Internet Mail Format parser re-write.
	* delete mail only if successfully sended to the channel.
	* fix VC2010 compilation
	* windows support for stdin
	* smtp support (libs/developers only, not avliable to avbot)&lt;/p&gt;

&lt;p&gt;3.3
	* pull in upstream avproxy that add http_proxy support.
	* allow to have signel  -d command line option to run as daemon
	* disable stdin process when run in daemon mode
	* limit the IRC msg send rate
	* broadcast the .qqbot command result
	* –xmppnick to set the nick
	* auto rename the nick when nick conflict occurs
	* add new command .qqbot version&lt;/p&gt;

&lt;p&gt;3.2.2
	* fixed many bugs&lt;/p&gt;

&lt;p&gt;3.2.1
	* bug fix
	* XMPP async connect fix
3.2
	* irc support multi line message support&lt;/p&gt;

&lt;p&gt;3.1.1
	* bug fix&lt;/p&gt;

&lt;p&gt;3.1
	* socks5_proxy support
3.0
	* avbot 现在包含了一个POP3客户端，可以到指定(–mailaddr参数指定)的邮箱获取邮件，并将邮件贴到群聊天里。
	* 改进的WebQQ协议处理，更稳定，更少下线时间
	* 支持通过标准输入直接输入验证码，不再需要通过IRC频道输入验证码
	* 大量的代码改进，使用协程优化了WebQQ登录过程的处理，使用协程优化了IRC连接处理&lt;/p&gt;

&lt;p&gt;2.3
	* bug fix
2.0
	* add support for XMPP protocol&lt;/p&gt;

&lt;p&gt;1.0
	* qqbot works for WebQQ and IRC protocol
0.1
	* tech-preview
	* many copy &amp;amp; paste from lwqq
	* only WebQQ supported. can log chat to html&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>avbot 4.6.1 更新</title>
   <link href="https://microcai.org/2013/05/04/avbot-4.6.html"/>
   <updated>2013-05-04T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/05/04/avbot-4.6</id>
   <content type="html">&lt;p&gt;对于不知道　avbot 是神码的同学，猛击&lt;a href=&quot;http://qqbot.avplayer.org&quot;&gt;这里&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;avbot 4.0 系列第八个版本 4.6.1  &lt;a href=&quot;http://sourceforge.net/projects/avbot/files/sources/avbot-4.6.1.tar.bz2/download&quot;&gt;发布&lt;/a&gt;了，同时发布的还有　&lt;a href=&quot;https://sourceforge.net/projects/avbot/files/rpm/&quot;&gt;rpm 包&lt;/a&gt; 和 &lt;a href=&quot;https://sourceforge.net/projects/avbot/files/win32/&quot;&gt;windows 包&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;rpm 包有 x86_32(用于64位系统) 的和 i686 (用于32位系统) 两个版本。其实 rpm 包解压一下也可以用在 Deb 系的系统上。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;PS: sourceforge 上的 avbot　项目我开通了匿名上传权限，欢迎大家提交　deb/rpm 包供大家下载。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;4.6 版本带来的最大的变化就是支持了 URL 预览功能. 当有群友贴出一个 URL 的时候, avbot 智能的将 url 解析并贴出网页标题.&lt;/p&gt;

&lt;p&gt;接着是加入了实验性的 lua 脚本支持 (通过 –enable-lua 启用)&lt;/p&gt;

&lt;hr /&gt;
&lt;p&gt;下面是 Changelog
—&lt;/p&gt;

&lt;p&gt;4.6
	* allow to -c to specify the location of the config file
	* automantically show url content then some one post an url
	* change .qqbot newbee command to .qqbot welcome
	* add experimental support for luascripting&lt;/p&gt;

&lt;p&gt;4.5
        * sync webqq protocol
        * clean cache file when startup
        * a bug that cause xmpp account accidently not used&lt;/p&gt;

&lt;p&gt;4.4
	* fix a bug that new group is not created automantically
	* 自动欢迎新人使用的是 $qqlogdir/$qqgroup/welcome.txt 的内容
	* 新人加群自动欢迎新人
	* try to re-fetch the joke page if decoding error occurs&lt;/p&gt;

&lt;p&gt;4.3
	* allow change joke interval
	* allow to switchoff joke&lt;/p&gt;

&lt;p&gt;4.2
	* 每十分钟冷场就讲一个笑话.
	* RPC 功能允许 POST&lt;/p&gt;

&lt;p&gt;4.1
	* 修正错误的 asio 用法，由 asio 爸爸的演讲视频指出的
	* avbot will check for image file and redownload corrupted one, aka say, the checksum does not match
	* avbot 启动的时候会对 images 文件夹下的文件执行校验检查。发现文件检验和文件名不符合的时候重新从腾讯服务器下载。
	* fix strange win32 qq message send error
	* fix XMPP not forward error&lt;/p&gt;

&lt;p&gt;4.0
	* avbot 重构，使用 libavbot 提供的 class avbot。 方便其他程序包含 avbot 功能
	* url 记录方式重构。
	* better support for windows platform - via mingw
	* 修复在 windows 控制台下输出乱码的问题&lt;/p&gt;

&lt;p&gt;3.6
	* add autotools build system in case cmake is not avaliable
	* fix a critical bug that will DOS the TX webqq server.
	* to make life easier , drop the support for VC , you can still compile avbot by mingw
	* avhttp now default to use HTTP 1.1 and avbot is using HTTP1.1 too
	* with the control of .qqbot, avbot can join qq groups by itself.
	* allow caching the qqgroup result so that if we have trouble fetching the group list, we won’t suffer
	* allow to fetch the group chat image to local storage&lt;/p&gt;

&lt;p&gt;3.5
	* fix VC2010 compilation, VC sucks, does it?
	* allow IRC/XMPP to use .qqbot command. that’s a long lacking feature
	* allow customize the message preamble
	* allow sending email via “.qqbot mail to  XXX” command
	* SMTP supports STARTSTL, make it more secure&lt;/p&gt;

&lt;p&gt;3.4
	* Internet Mail Format parser re-write.
	* delete mail only if successfully sended to the channel.
	* fix VC2010 compilation
	* windows support for stdin
	* smtp support (libs/developers only, not avliable to avbot)&lt;/p&gt;

&lt;p&gt;3.3
	* pull in upstream avproxy that add http_proxy support.
	* allow to have signel  -d command line option to run as daemon
	* disable stdin process when run in daemon mode
	* limit the IRC msg send rate
	* broadcast the .qqbot command result
	* –xmppnick to set the nick
	* auto rename the nick when nick conflict occurs
	* add new command .qqbot version&lt;/p&gt;

&lt;p&gt;3.2.2
	* fixed many bugs&lt;/p&gt;

&lt;p&gt;3.2.1
	* bug fix
	* XMPP async connect fix
3.2
	* irc support multi line message support&lt;/p&gt;

&lt;p&gt;3.1.1
	* bug fix&lt;/p&gt;

&lt;p&gt;3.1
	* socks5_proxy support
3.0
	* avbot 现在包含了一个POP3客户端，可以到指定(–mailaddr参数指定)的邮箱获取邮件，并将邮件贴到群聊天里。
	* 改进的WebQQ协议处理，更稳定，更少下线时间
	* 支持通过标准输入直接输入验证码，不再需要通过IRC频道输入验证码
	* 大量的代码改进，使用协程优化了WebQQ登录过程的处理，使用协程优化了IRC连接处理&lt;/p&gt;

&lt;p&gt;2.3
	* bug fix
2.0
	* add support for XMPP protocol&lt;/p&gt;

&lt;p&gt;1.0
	* qqbot works for WebQQ and IRC protocol
0.1
	* tech-preview
	* many copy &amp;amp; paste from lwqq
	* only WebQQ supported. can log chat to html&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>avbot 4.5 紧急更新</title>
   <link href="https://microcai.org/2013/05/02/avbot-4.5.html"/>
   <updated>2013-05-02T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/05/02/avbot-4.5</id>
   <content type="html">&lt;ul&gt;
  &lt;li&gt;avbot 4.5 是一个紧急更新版本， 所有用户都应该升级。 因为TX的WebQQ协议有变动， 会导致无法获取群列表。*&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;对于不知道　avbot 是神码的同学，猛击&lt;a href=&quot;http://qqbot.avplayer.org&quot;&gt;这里&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;avbot 4.0 系列第六个版本 4.5  &lt;a href=&quot;http://sourceforge.net/projects/avbot/files/sources/avbot-4.5.tar.bz2/download&quot;&gt;发布&lt;/a&gt;了，同时发布的还有　&lt;a href=&quot;https://sourceforge.net/projects/avbot/files/rpm/&quot;&gt;rpm 包&lt;/a&gt; 和 &lt;a href=&quot;https://sourceforge.net/projects/avbot/files/win32/&quot;&gt;windows 包&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;rpm 包有 x86_32(用于64位系统) 的和 i686 (用于32位系统) 两个版本。其实 rpm 包解压一下也可以用在 Deb 系的系统上。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;PS: sourceforge 上的 avbot　项目我开通了匿名上传权限，欢迎大家提交　deb/rpm 包供大家下载。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What’s NEW in this release ?&lt;/p&gt;

&lt;p&gt;4.5
        * sync webqq protocol
        * clean cache file when startup
        * a bug that cause xmpp account accidently not used&lt;/p&gt;

&lt;p&gt;4.4
	* fix a bug that new group is not created automantically
	* 自动欢迎新人使用的是 $qqlogdir/$qqgroup/welcome.txt 的内容
	* 新人加群自动欢迎新人
	* try to re-fetch the joke page if decoding error occurs&lt;/p&gt;

&lt;p&gt;4.3
	* allow change joke interval
	* allow to switchoff joke&lt;/p&gt;

&lt;p&gt;4.2
	* 每十分钟冷场就讲一个笑话.
	* RPC 功能允许 POST&lt;/p&gt;

&lt;p&gt;4.1
	* 修正错误的 asio 用法，由 asio 爸爸的演讲视频指出的
	* avbot will check for image file and redownload corrupted one, aka say, the checksum does not match
	* avbot 启动的时候会对 images 文件夹下的文件执行校验检查。发现文件检验和文件名不符合的时候重新从腾讯服务器下载。
	* fix strange win32 qq message send error
	* fix XMPP not forward error&lt;/p&gt;

&lt;p&gt;4.0
	* avbot 重构，使用 libavbot 提供的 class avbot。 方便其他程序包含 avbot 功能
	* url 记录方式重构。
	* better support for windows platform - via mingw
	* 修复在 windows 控制台下输出乱码的问题&lt;/p&gt;

&lt;p&gt;3.6
	* add autotools build system in case cmake is not avaliable
	* fix a critical bug that will DOS the TX webqq server.
	* to make life easier , drop the support for VC , you can still compile avbot by mingw
	* avhttp now default to use HTTP 1.1 and avbot is using HTTP1.1 too
	* with the control of .qqbot, avbot can join qq groups by itself.
	* allow caching the qqgroup result so that if we have trouble fetching the group list, we won’t suffer
	* allow to fetch the group chat image to local storage&lt;/p&gt;

&lt;p&gt;3.5
	* fix VC2010 compilation, VC sucks, does it?
	* allow IRC/XMPP to use .qqbot command. that’s a long lacking feature
	* allow customize the message preamble
	* allow sending email via “.qqbot mail to  XXX” command
	* SMTP supports STARTSTL, make it more secure&lt;/p&gt;

&lt;p&gt;3.4
	* Internet Mail Format parser re-write.
	* delete mail only if successfully sended to the channel.
	* fix VC2010 compilation
	* windows support for stdin
	* smtp support (libs/developers only, not avliable to avbot)&lt;/p&gt;

&lt;p&gt;3.3
	* pull in upstream avproxy that add http_proxy support.
	* allow to have signel  -d command line option to run as daemon
	* disable stdin process when run in daemon mode
	* limit the IRC msg send rate
	* broadcast the .qqbot command result
	* –xmppnick to set the nick
	* auto rename the nick when nick conflict occurs
	* add new command .qqbot version&lt;/p&gt;

&lt;p&gt;3.2.2
	* fixed many bugs&lt;/p&gt;

&lt;p&gt;3.2.1
	* bug fix
	* XMPP async connect fix
3.2
	* irc support multi line message support&lt;/p&gt;

&lt;p&gt;3.1.1
	* bug fix&lt;/p&gt;

&lt;p&gt;3.1
	* socks5_proxy support
3.0
	* avbot 现在包含了一个POP3客户端，可以到指定(–mailaddr参数指定)的邮箱获取邮件，并将邮件贴到群聊天里。
	* 改进的WebQQ协议处理，更稳定，更少下线时间
	* 支持通过标准输入直接输入验证码，不再需要通过IRC频道输入验证码
	* 大量的代码改进，使用协程优化了WebQQ登录过程的处理，使用协程优化了IRC连接处理&lt;/p&gt;

&lt;p&gt;2.3
	* bug fix
2.0
	* add support for XMPP protocol&lt;/p&gt;

&lt;p&gt;1.0
	* qqbot works for WebQQ and IRC protocol
0.1
	* tech-preview
	* many copy &amp;amp; paste from lwqq
	* only WebQQ supported. can log chat to html&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>avbot 4.2 新功能解释和实现</title>
   <link href="https://microcai.org/2013/04/29/avbot-new-feature-autojoke.html"/>
   <updated>2013-04-29T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/04/29/avbot-new-feature-autojoke</id>
   <content type="html">&lt;h1 id=&quot;新功能---讲笑话&quot;&gt;新功能 - 讲笑话&lt;/h1&gt;

&lt;p&gt;这是一个早就被呼吁的功能，今天抽空实现了。笑话这个功能实现起来有2个要点：&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;第一，这个笑话虽然是隔十分钟讲一次，可是不能打断大家的讨论，所以是出现十分钟的空闲后才发&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;第二，这个笑话需要从网页上抓取&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;第一个要点, 使用办法就是 Asio 提供的 deadline_timer. 
设定定时器超时 10min , 但是如果有人发言, 就重设时间. 通过链接到 avbot::on_message 就可以知道有没有人发言了.&lt;/p&gt;

&lt;p&gt;实现起来非常简单,就是在 on_message 的时候重新设定定时器.&lt;/p&gt;

&lt;p&gt;第二,笑话要从网上抓取. 这个使用了&lt;a href=&quot;http://github.com/ericsimith/avjoke&quot;&gt;徒弟&lt;/a&gt;写的解析代码了. 不过咱高级,使用的是 avhttp 进行 HTTP 访问. 徒弟他不懂事, 手写 HTTP 解析.&lt;/p&gt;

&lt;hr /&gt;
&lt;h1 id=&quot;实现&quot;&gt;实现&lt;/h1&gt;

&lt;p&gt;将笑话模块实现为一个 class joke,  class joke 重载了 operator(), 也就是说,是个仿函数,本身就可以作为 on_message 的 slot . class joke 重载了多个 operator() , 一个用于 on_message 的 slot , 一个用户 deadline_timer 的 Handler.&lt;/p&gt;

&lt;p&gt;在用作 on_message 的 slot 的 operator() 里, joke 就做了一件事: 重设 timer
在 deadline_timer 的 Handler 里, 首先判断 timer 是到期了还是取消了. 取消的 timer 啥也 不干.&lt;/p&gt;

&lt;p&gt;到期的话 调用 joker fetcher 来下载一个 joke , 然后调用 sender 发送 joke 就可以了. sender 是一个函数对象,由 main.cpp 传入. 其实就是把 avbot::broadcast_message 做了 bind 给 joke 用, 这样就把 joke 和 avbot 解偶了.&lt;/p&gt;

&lt;p&gt;joker fetcher 也是一个函数对象,用于下载 joke , 但是 avbot 提供的另外一个构造函数重载里允许用户传入自己写的笑话下载器.
joke 提供了一个默认的 joke fetcher, 这个 joke fetcher 就是 徒弟写的那个代码的一个 AVBOT 风格化的版本.&lt;/p&gt;

&lt;p&gt;所以整个代码实现都是非常简单清晰易懂的. 嘿嘿.&lt;/p&gt;

&lt;hr /&gt;

</content>
 </entry>
 
 <entry>
   <title>avbot 结构解释</title>
   <link href="https://microcai.org/2013/04/28/avbot-struct.html"/>
   <updated>2013-04-28T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/04/28/avbot-struct</id>
   <content type="html">&lt;p&gt;avbot 由 4 大部分构成 libavbot libavlog botctl avbotrpc&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;   +----------+            +----------+        +----------+
   | libavlog |            | libavbot | ---+---| libwebqq |
   +----------+\         / +----------+    |   +----------+
                 main.cpp                  |   
   +----------+/         \ +----------+    |   +----------+
   |  botctl  |           \| avbotrpc |    +---| libxmpp  |  
   +----------+            +----------+    |   +----------+
                                           |
                                           |   +----------+
                                           +---+  libirc  |
                                           |   +----------+
                                           |
                                           |   +------------------+
                                           +---+  libmailexchange |
                                               +------------------+
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;中间由 main.cpp 作为胶水粘合。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;libavlog 的任务是生成日志文件，botctl 的用处是实现 .qqbot 控制指令。
avbotrcp 用于实现 JSON-RPC&lt;/p&gt;

&lt;h1 id=&quot;libavbot-则是核心功能&quot;&gt;libavbot 则是核心功能。&lt;/h1&gt;

&lt;hr /&gt;

&lt;p&gt;libavbot 由 libwebqq libirc libxmpp libmailexchange 4个子协议组成。&lt;/p&gt;

&lt;p&gt;libmailexchange 同时又分 libsmtp libpop3 和 libInternetMailForamt 3个小模块。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;衔接 libavlog botctl avbotrpc 和 libavbot 的东西就是 class avbot 的一个成员 on_message.&lt;/p&gt;

&lt;p&gt;在 main.cpp 里，包含了 mybot.on_message.connect(XXX) 调用，将 avbot 和 其他3 个模块衔接起来。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  // 记录到日志.
  mybot.on_message.connect(boost::bind(avbot_log, _1, boost::ref(mybot)));
  // 开启 bot 控制.
  mybot.on_message.connect(boost::bind(my_on_bot_command, _1, boost::ref(mybot)));
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;以及&lt;/p&gt;

&lt;pre style=&quot;color:#1f1c1b;background-color:#ffffff;&quot;&gt;
&lt;span style=&quot;color:#0057ae;&quot;&gt;static&lt;/span&gt; &lt;span style=&quot;color:#0057ae;&quot;&gt;void&lt;/span&gt; avbot_rpc_server(&lt;span style=&quot;color:#23a45b;&quot;&gt;boost::shared_ptr&lt;/span&gt;&amp;lt;&lt;span style=&quot;color:#23a45b;&quot;&gt;boost::asio::ip::tcp::socket&lt;/span&gt;&amp;gt; m_socket, avbot &amp;amp; mybot)
{
	&lt;span style=&quot;color:#808080;&quot;&gt;detail::avbot_rpc_server&lt;/span&gt;(m_socket, mybot.on_message);
}
&lt;/pre&gt;

&lt;p&gt;on_message 是一个 boost::signals , 每当 QQ/IRC/XMPP/pop3 收到消息的时候就发起这个信号。libavlog 解析这个信号，然后将消息写入日志。avbotrpc 解析这个信号，然后返回给调用者。botctl 解析这个信号，提取其中的消息，识别其中的命令，然后写入日志文件。&lt;/p&gt;

&lt;p&gt;libavbot 则实现了消息转发和群组功能。libavbot 并不藉由自己全部实现协议，而是交给了 libwebqq libirc libmailexchange 和 libxmpp 4 个协议库去实现协议。&lt;/p&gt;

&lt;p&gt;libwebqq 从 pidgin-lwqq 项目借用了大量的代码，然后将其从 C 语言改写为安全的 Boost 形式。使用了 avhttp  这个 avplayer 社区发起的 HTTP 库进行Web访问。&lt;/p&gt;

&lt;p&gt;libirc 由 “猫” 贡献，非常的简单。&lt;/p&gt;

&lt;p&gt;libxmpp 是 gloox 的一层包装。将 gloox 包装为融入 Boost.Asio 中。而无需另外的线程跑其EventLoop。libxmpp 使用了一些Hack技巧将 gloox 改造为了 可以使用 Boost.Asio.&lt;/p&gt;

&lt;p&gt;libmailexchange 包含了 libInternetMailForamt 库用于解析复杂的 Internet Mail Foramt, 以及两个小库用户执行 POP3 接收和 SMTP 发送。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>用概念编程</title>
   <link href="https://microcai.org/2013/04/27/coding-by-concepts.html"/>
   <updated>2013-04-27T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/04/27/coding-by-concepts</id>
   <content type="html">&lt;p&gt;C++ 爸爸对 c++11 提出了一个 “模板概念” 提案，可是又亲自否决了它。因为他觉得还不够好，还要继续研究一番才行。C++是一个严谨的语言，新特性的加入要小心谨慎。&lt;/p&gt;

&lt;p&gt;虽然提案没有通过，但是，C++爸爸俨然已经将那个 C with Class 的语言进化了，C++不再是一个 C with Class 的语言，而是迈出了“概念”编程的一步。&lt;/p&gt;

&lt;p&gt;概念编程在C++中由来已久，C++爸爸的提案就是为了在语言级更好的支持概念编程。在语言没有支持到家的时候，通过修修补补，也还是能将就使用。&lt;/p&gt;

&lt;p&gt;什么是用概念编程呢？ 简单的来说，就是弱化类型，强调概念。比如 std::sort , 它的参数是什么呢？ 他的参数可以是任何一种迭代器所表示的区间。任何类型的迭代器都可以。不论是指向int的还是string的还是你自己定义的类。这就是 std::sort 所需要的参数的概念。因为在人脑中，对一样东西排序可不需要知道他是数组还是链表。std::sort 对于 迭代器指向的对象也有一个要求：必须能比较大小。不能比较大小的东西还能排序么？&lt;/p&gt;

&lt;p&gt;那么这就是 std::sort 所提出的概念要求。以概念编程，就越来越接近人脑的思维。人不再需要将抽象的概念具像为C语言中的基本类型。&lt;/p&gt;

&lt;p&gt;前段时间我写的 &lt;a href=&quot;/2013/03/28/boost-base64.html&quot;&gt;boost 编码 base64&lt;/a&gt; 也是概念编程的产物。因为你不用再幸苦的思索base64的编码算法了。只要依据 base64 的描述就可以写出编解码的代码了。&lt;/p&gt;

&lt;p&gt;根据base64概念，编码过程就是 6bit 为一个单位，然后转化为 ASCII 字符。那么就简单了，你需要一个将输入流转化为6bit的迭代器。也就是 boost::archive::iterators::transform_width 这个迭代器，模板参数区分别是 6, 8, char。接着你获得了 6bit 的数据，然后通过base64_from_binary这个迭代器输入6bit数据输出ASCII编码。&lt;/p&gt;

&lt;p&gt;将迭代器嵌套起来，就就获得了一个 base64 编码器了。 base64_from_binary&amp;lt;transform_width&amp;lt;6,8,char&amp;gt; &amp;gt; 就是一个 base64 编码的迭代器了。输入二进制数据，输出base64编码的数据。有使用 ，你还需要每78个字符换行，那么简单到再嵌入一个 boost::archive::iterators::insert_linebreaks 就可以自动的插入换行符了！&lt;/p&gt;

&lt;p&gt;同样的，一个 base64 解码器也非常简单，首先是一个 boost::archive::iterators::remove_whitespace 迭代器，移除空白字符，如那个每78字符换行，然后是 boost::archive::iterators::binary_from_base64 从 ASCII 生成 6bit数据，接着是 boost::archive::iterators::transform_width 和编码反过来，模板参数为 &amp;lt;8, 6, char&amp;gt; 将6bit输入拼成8bit输出。&lt;/p&gt;

&lt;p&gt;看，只要使用概念就可以完成编程了！ 千万不要当心效率问题，编译器能将这种多重间接的迭代器访问给优化为单个指针访问。不要低估编译器的优化能力。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>ASIO 与协程</title>
   <link href="https://microcai.org/2013/04/22/asio-statemachine.html"/>
   <updated>2013-04-22T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/04/22/asio-statemachine</id>
   <content type="html">&lt;p&gt;前段时间看了 ASIO 爸爸关于ASIO的一个演讲. ASIO 爸爸说, ASIO 的设计理念就是作为一个 toolkit 而不是一个框架. ASIO并不强迫你使用某种编程模型. 它只是提供一系列的函数和类帮你更容易的编程.&lt;/p&gt;

&lt;p&gt;ASIO 的设计思想其实和 GLIB 的 g_main_loop 非常像. 但是 C++ 因为有模板, 所以能更好的实现. 使用 g_main_loop 的时候, 我不记得我写了多少 void* 到 struct 的转化了. 这就是 C 的弊病. 因为缺少模板,所以回调必须严格符合函数签名. 但是库作者永远想不到用户还需要什么样的参数. 于是void*就是最好的解决办法了.&lt;/p&gt;

&lt;p&gt;但是因为 C++ 有模板,所以放松了对函数签名的要求. 不符合 ASIO 回调函数签名要求的,都可以使用 bind 绑定以符合签名要求. 这就为库的使用者带来了巨大的灵活性的同时仍然保持极佳的易用性.&lt;/p&gt;

&lt;p&gt;C++的另一个强大优势即是函数对象(bind的结果就是一个函数对象). 通过函数对象, 使得一些状态可以保存到成员变量中, 这样就给了我们一个不需要栈就实现的一个协程. 协程本质上是一个拥有多重入口和多重出口的函数. 每次调用都将从上一次退出的地方继续执行. 如果没有成员变量, 所有的变量都将保持于栈上, 这就要求协程的实现必定带来栈切换. 而栈的要求就导致协程必须有额外的创建和撤销动作. 这都给协程的实际使用造成了限制.&lt;/p&gt;

&lt;p&gt;C++的函数对象, 既可以像普通函数那样调用, 又可以像通常的对象那样携带成员变量. 因此通过将变量置于对象而不是栈上, C++就可以实现无栈协程. 无栈协程的开销不过就是一个函数对象, 而无需一次性分配一个巨大的内存用于栈. 通常一个栈实现的协程,需要分配一个 1MB~10MB 的内存用于栈. 而无栈协程所使用的对象,不过就只是一个函数对象,通常大小不过几个字节到几百字节. 因此无栈协程允许创建大量的协程,而无需当心内存开销.&lt;/p&gt;

&lt;p&gt;ASIO 爸爸即创造性的提出了无栈协程的概念, 然后将它用于实践中.&lt;/p&gt;

&lt;p&gt;使用无栈协程, 伴随的ASIO的另一个重要用法: 复合回调函数. 将多个回调函数复合为一个. 多个ASIO操作使用同一个回调函数进行处理. 然后使用 Duff’s Device 的方法自动构建状态机. 每次调用自动的从上一次退出的地方继续执行.&lt;/p&gt;

&lt;p&gt;由于 ASIO 对于回调函数是执行的拷贝操作,因此用于 ASIO 协程的函数对象必须是可拷贝的. 实现这个其实也非常轻松, 即对于不能拷贝的成员变量使用 shared_ptr 封装即可.&lt;/p&gt;

&lt;p&gt;每次使用 yield 发起异步操作的时候, 记住, 当前的对象会在退出函数的时候撤销. 但是于此同时, ASIO 也获得了对象的一份拷贝. 用于下次回调. 这个特性有时候可以被利用, 用于实现非常复杂的逻辑. avbot 的 RPC 实现即利用了这个特性. 将复杂的逻辑使用寥寥数行代码轻松实现.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>NVIDIA 官方驱动支持 Optimus</title>
   <link href="https://microcai.org/2013/04/15/nvidia-optimus.html"/>
   <updated>2013-04-15T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/04/15/nvidia-optimus</id>
   <content type="html">&lt;p&gt;各位，鄙人去年手贱，买了台 Optimus 的笔记本。最开始的时候用的 Bumblebee，但是觉得每
次用 optirun  非常的 egg pain。我想全局启用 NVIDIA 显卡（GT650M显卡买来不用浪费
啊！），而不是手动使用 optirun。&lt;/p&gt;

&lt;p&gt;于是我折腾了  DRM/PRIME , 跑起了开源驱动，找到了PRIME的各种补丁，然后编译+折腾。终于
搞定。&lt;/p&gt;

&lt;p&gt;虽然性能差了点（直到昨天我才知道，差的实现相当的大啊！）好歹比集显好多了。&lt;/p&gt;

&lt;p&gt;前些天，NVIDIA 发布了  319.12 驱动，官方支持了 PRIME 和 xrandr 1.4  !&lt;/p&gt;

&lt;p&gt;我乐死了，赶紧折腾。现在分享一下成功经验： PSGentoo下会easy很多。&lt;/p&gt;

&lt;p&gt;首先，第一重要的是确保自己使用的是  3.9 内核。开启 intel 的 KMS , 去掉 nouveau 驱动。&lt;/p&gt;

&lt;p&gt;然后确保 xorg  是最新版 !  xorg-server &amp;gt;= 1.13 ! &lt;br /&gt;
确保 xrandr 是最新版！ xrandr &amp;gt;= 1.4 !&lt;/p&gt;

&lt;p&gt;然后，要使用 xf86-video-modesetting !! 而不是 xf86-video-intel ，记住！！ 很重要！&lt;/p&gt;

&lt;p&gt;emerge xf86-video-modesetting&lt;/p&gt;

&lt;p&gt;好了，非常关键的，需要 nvidia-drivers &amp;gt;= 319.12 !&lt;/p&gt;

&lt;p&gt;你需要做的就是 在 /etc/portage/package.unmask 解除 nvidia-drivers &amp;gt;= 319.12 版本的屏蔽.
然后再安装&lt;/p&gt;

&lt;p&gt;好了，然后依据 http://us.download.nvidia.com/XFree86/Linux-x86/319.12/README/randr14.html 这个官方说明写好 xorg.conf 就可以了。&lt;/p&gt;

&lt;p&gt;注意一下， BusID 是 使用  “02:00:0” 而 lspci 的输出是 02:00.0 ， 把这个小点换成冒号。&lt;/p&gt;

&lt;p&gt;把&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#！ /bin/bash
xrandr --setprovideroutputsource modesetting NVIDIA-0
xrandr --auto 添加到 /etc/X11/xinitrc.d/00-optimus (添加可执行权限)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;eselect opengl set nvidia !!!! 很重要&lt;/p&gt;

&lt;p&gt;搞定。&lt;/p&gt;

&lt;p&gt;使用 modesetting 驱动而不是 intel 驱动，是因为我们只需要intel显卡执行输出，一切的 2D3D操
作都由 NVIDIA 显卡完成。实际上 就是使用 modesetting 驱动做了一个 mirror 功能，把 NVIDIA 
的内容 拷贝给 intel 显卡做输出。这个 mirror 是 硬件完成的( DMA) 不占用 CPU 时间，也没有多
次拷贝的问题，因此效率比 bumblebee 高很多。&lt;/p&gt;

&lt;p&gt;如果需要 NVIDIA 显卡的HMDI接口外接显示器，Option “UseDisplayDevice” “none” 这个选项就
要去掉。否则要开启这个选项。&lt;/p&gt;

&lt;p&gt;HDMI 功能也OK，多显示器很正常。&lt;/p&gt;

&lt;p&gt;好了，使用官方的 Optimus 后才发现，我了个去，这笔记本的显卡你妹妹的快啊！操！ 比我台
式机快多了 555555555555555555&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>ASIO协程的思维转变</title>
   <link href="https://microcai.org/2013/04/13/asio-stackless-coroutine-meaning.html"/>
   <updated>2013-04-13T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/04/13/asio-stackless-coroutine-meaning</id>
   <content type="html">&lt;p&gt;avbot 是一个纯粹的单线程程序。在设计 avbot 最初的时候，我就给自己下了一个明确的目标：必须单线程。&lt;/p&gt;

&lt;p&gt;但是，它的逻辑可不简单。它需要处理 XMPP 协议，处理 IRC 协议，处理 WebQQ 协议，处理pop3协议，处理 SMTP 协议。
所有的处理都必须异步。绝对不能因为 IO 阻塞。&lt;/p&gt;

&lt;p&gt;可能很多人会不以为然，这有什么， select() 一下就好了。但是你知道这意味着多少代码么？&lt;/p&gt;

&lt;p&gt;你需要编写大量的函数，大量的回调函数。别说复杂的 WebQQ 协议了，就是一个简单的 IRC , 你知道要多少代码完成异步处理么？&lt;/p&gt;

&lt;p&gt;而且除了 WebQQ 以外（WebQQ的代理支持也在添加计划中）， XMPP IRC POP3 SMTP 都是支持代理的。你知道要多少代码完成代理么？&lt;/p&gt;

&lt;p&gt;想必很多吧？&lt;/p&gt;

&lt;p&gt;所幸的是，avbot 没有使用C开发，也没有选择 ACE MFC 这样的不合格的类库开发。而是选择了 Boost。尤其是 ASIO, ASIO让异步过程大大简化。&lt;/p&gt;

&lt;p&gt;而让异步过程更加简化的，就是 ASIO 作者发明的 stackless coroutine!&lt;/p&gt;

&lt;p&gt;avbot 发布了许久了， 最近突然有个用户跑来说，希望能增加个调用 “外部脚本” 的功能，方便扩展。&lt;/p&gt;

&lt;p&gt;我一向对设计一个 plugin 机制极力的避免，不喜欢动态载入的模块扩展程序本身的功能。何况 avbot 是 c++开发的，调用脚本并不是容易的事情。（好吧，真实的原因是我被 mingw （VC 不支持 utf8源码，我已经抛弃了） 折腾怕了，不想再搞个 python 。windows实在是恐怖的平台，写点程序麻烦的要死，编译麻烦的要死。可是 avbot 又必须跨平台，结果是我一天写好的东西要在 windows (虚拟机) 里折腾好几天，累死人 ）&lt;/p&gt;

&lt;p&gt;于是我决定提供一个  JSON 接口，内置一个简单的 HTTP Server, 用脚本(python应该 HTTP JSON 模块有的是，对吧)连接到 avbot ，然后 avbot 将发生的每条消息以 json 的形式返回给 外部脚本。&lt;/p&gt;

&lt;p&gt;另外，默认使用 HTTP 的connection: keep-alive 模式，所以保持一个长连接即可。&lt;/p&gt;

&lt;p&gt;那么，avbot 需要支持不确定数目的消息接收方了。&lt;/p&gt;

&lt;p&gt;对于链接到 avbot 的客户端而言， avbot 并不保留之前的所有消息，而是从连接上的那一刻开始，后续的消息才能通知到。
一个很明显的思路就是，将链接上的客户端做成一个链表/列队， avbot 收到消息后，遍历这个列队执行消息发送。&lt;/p&gt;

&lt;p&gt;这个思路很简单，可是如果要求 ： 必须单线程异步呢？&lt;/p&gt;

&lt;p&gt;avbot 是一个纯粹的单线程程序，绝对不允许多线程化。所有的逻辑必须使用异步处理。&lt;/p&gt;

&lt;p&gt;那么，这个问题就复杂化了， “avbot 收到消息后，遍历这个列队执行消息发送” 这个做法，不可避免的带来了阻塞。好吧，异步遍历吧。&lt;/p&gt;

&lt;p&gt;要是异步遍历还没遍历完，又来一个消息呢？ 考虑这个问题，你会发疯的。因为异步，太多的细节需要考虑了。真的。&lt;/p&gt;

&lt;p&gt;好吧，又有个好主意了，为每个客户端建立一个列队，每次遍历就是把要发送的消息挂入列队即可。这样也不需要异步遍历了，同步就可以。解决了异步遍历的时候又来一个消息导致的痛苦的调度。&lt;/p&gt;

&lt;p&gt;然后细分，考虑每个客户端，就是等待 “发送列队” 不为空！等等，一直这么等待也不行，如果客户断开了链接呢？ 所以要 “同时等待发送列队不为空&amp;amp;&amp;amp;客户正常在线，并且已经发送了 HTTP 请求头部”&lt;/p&gt;

&lt;p&gt;好绕口，不过也只能如此了。&lt;/p&gt;

&lt;p&gt;avbot 因为默认使用了 keep-alive , 所以发送是一个死循环，知道客户端主动断开链接或者网络发生错误。如果 客户端死了，那么，发送列队兴许会出现 爆队 的情况。所以要限制发送列队的大小。不是满了就不发送，而是满了后就把早的消息踢掉，也就是让 客户端发生“暂时性卡死”后，还能继续处理最后的几条信息。&lt;/p&gt;

&lt;p&gt;诶，复杂的逻辑终于理清了，代码呢？！&lt;/p&gt;

&lt;p&gt;啊累？&lt;/p&gt;

&lt;p&gt;靠，这么复杂的 逻辑，得写一长段代码，调试几百年了吧？&lt;/p&gt;

&lt;p&gt;错，我只花了几个小时，不到 100 行的代码就轻松实现了全部要求。&lt;/p&gt;

&lt;p&gt;!!!!!!!!!!!!!!!!!!! WHAT !!!!!!!!!!!!!!!!!!!&lt;/p&gt;

&lt;p&gt;这种功能不可能不用个千把行代码的吧？！&lt;/p&gt;

&lt;p&gt;如果使用以前的老办法，确实如此。&lt;/p&gt;

&lt;p&gt;可是，自从发现了 ASIO 后，我被 ASIO 爸爸发明的协程深深的震惊了！&lt;/p&gt;

&lt;p&gt;利用 ASIO 爸爸提出的协程思想，我只用了不到 100行代码就全部完成了以上复杂的逻辑，而且，全部都是异步的哦～ 。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>fcitx 违反了 GPL2 ? (更新, 已解决)</title>
   <link href="https://microcai.org/2013/04/06/fcitx-gpl-valation.html"/>
   <updated>2013-04-06T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/04/06/fcitx-gpl-valation</id>
   <content type="html">&lt;p&gt;fcitx 是 yuking 的作品。 一直以来都是以 GPL2 协议发布。&lt;/p&gt;

&lt;p&gt;yuking 后来将维护权交给了 csslayer. csslayer 开始了 fcitx 4.X 系列的维护工作。&lt;/p&gt;

&lt;p&gt;最近我查看了fcitx 4 的协议， 发现 fcitx4 将自己的协议降级了。而且降级并没有说明获得过 yuking 和其他贡献者的同意。&lt;/p&gt;

&lt;p&gt;按照 GPL2 的协议， 升级(就是变得比GPL2 还要严格，比如升级到 GPL3)是被允许的。 但是降级（也就是提出了可以不遵守GPL2的例外）显然是不被允许的。&lt;/p&gt;

&lt;p&gt;按照通行法则，软件作者具备更改授权的权利。那么 csslayer 具备这个更改授权的权利么？
那就要看 fcitx 是不是 csslayer 的个人作品了。如果 fcitx 包含了许多人的工作，那么 csslayer 要更改 fcitx 的协议就必须获得所有 fcitx 贡献者的首肯。&lt;/p&gt;

&lt;p&gt;明显 fcitx 4 更改协议的时候并没有获得yuking 和其他贡献者的同意。如果确实获得了他们的同意，我希望 csslayer 能公开声明获得了他们的同意。&lt;/p&gt;

&lt;hr /&gt;

&lt;h1 id=&quot;更新&quot;&gt;更新&lt;/h1&gt;

&lt;p&gt;傲慢的 csslayer 承认了错误，向 yuking 获得了同意。参考 &lt;a href=&quot;http://uploads.csslayer.info/uploads/mail/mail.mbox&quot;&gt;这个邮件&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;现在 fcitx 应该没什么大问题了。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>模板多态</title>
   <link href="https://microcai.org/2013/04/05/template-as-polymiorphism.html"/>
   <updated>2013-04-05T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/04/05/template-as-polymiorphism</id>
   <content type="html">&lt;h1 id=&quot;需求&quot;&gt;需求&lt;/h1&gt;

&lt;p&gt;jack 正在实现一个 avhttp , 并且也已经相当的可用了。 他在实现http协议的时候，就对 HTTPS 和 HTTP 的实现做了一个相当重要的决定 —– HTTPS和HTTP必须是统一的代码。&lt;/p&gt;

&lt;p&gt;那么，要优雅的做到这一点，显然只有一个办法：多态。&lt;/p&gt;

&lt;p&gt;但是很不幸的是，SSL 需要 asio::ssl::ssl_stream , 而 TCP 则用的是 asio::ip::tcp::socket。于是 jack 想到了boost::variant。 这是一个支持 非POD成员作为一个 unin 的库解决方案。&lt;/p&gt;

&lt;p&gt;很不幸，即便如此， SSL 和 TCP 仍然有相当多的代码不能公用。然后对于代理服务器的支持， SSL 和 TCP 就更不能统一处理。&lt;/p&gt;

&lt;p&gt;依然要相当多的条件分支。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;就不能有一个更干净的办法么？
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;分层实现&quot;&gt;分层实现&lt;/h1&gt;

&lt;p&gt;于是我提出了将 avhttp 中 HTTP 请求过程分两层实现，一个是 DNS查询、TCP 链接的发起、代理服务器握手、SSL 握手等一系列的步骤。 也就是说，在真正的发起一次 HTTP 请求之前的一系列操作将其归类到第一层。接着，是第二层，就是真正的 HTTP 协议处理了。&lt;/p&gt;

&lt;p&gt;那么第一层，无论是 HTTPS 还是 HTTP， 几乎肯定都是和 TCP 打交道，只有最后一步 HTTPS 多了一个 SSL 握手， 而 HTTP 则不需要。 这样， 第一层，不论是 HTTPS 还是 HTTP，只有唯一一处条件判断选择 SSL 握手这个分支语句。&lt;/p&gt;

&lt;p&gt;接着到了第二层，不论是 HTTPS 还是 HTTP 都是一模一样的处理流程。&lt;/p&gt;

&lt;p&gt;而对于后续HTTP响应的读取，继续使用目前所用的 boost::variant 机制即可。&lt;/p&gt;

&lt;h2 id=&quot;第二层的实现采用模板多态&quot;&gt;第二层的实现采用模板多态&lt;/h2&gt;

&lt;p&gt;很明显，第二层的实现需要多态。而使用虚继承的形式的多态显然在这里大材小用了。需要写 3 个类（一个基类，两个虚继承的派生类）来封装 asio 的操作，明显也是过于繁琐了。&lt;/p&gt;

&lt;p&gt;那么，显然，虽然 asio::ssl::ssl_stream 和 asio::ip::tcp::socket 不是同一个类型，但是明显都支持一样的操作！ 都支持 asio::async_read/write 操作，都拥有 async_read/write 成员函数！&lt;/p&gt;

&lt;p&gt;对于这样的具有相同“语法”的类，要进行多态，实在是太容易 —— C++ 提供了强大的模板机制，何乐而不为呢！&lt;/p&gt;

&lt;p&gt;于是，明显的，第二层的采用模板实现。只要第一层在最后结束的时候，依据 HTTPS 还算 HTTP 为第二层调用提供不同的模板参数即可！&lt;/p&gt;

&lt;h1 id=&quot;结论&quot;&gt;结论&lt;/h1&gt;

&lt;p&gt;C++ 为我们提供了强大的工具，我们要做的就是为当前的需求寻找C++提供的最恰当的机制。而不需要像 C 那样一味的向语言妥协。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>深入理解C++</title>
   <link href="https://microcai.org/2013/04/02/deepunderstandingcpp.html"/>
   <updated>2013-04-02T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/04/02/deepunderstandingcpp</id>
   <content type="html">&lt;p&gt;说一下我为何回归 C++ 了吧 : 简单: 生命有限。&lt;/p&gt;

&lt;p&gt;用 C , 固然是只有一个范式, 学起来容易, 上手简单， 可是需要操心的问题太多了: 内存泄露, 野指针, 各种断错误。
可能你会说, 内存管理，小心点就可以了。
但我觉得,如果你总是用 200% 的精力去避免内存泄露，你就没有精力开发正常的软件了。你会对软件的逻辑进行折中，因为你实在没有精力开发需要你花很多时间写逻辑的代码了。
于是, 各种因为”实现起来麻烦” 为借口进行功能精简。所以我重新拿起了高级语言。&lt;/p&gt;

&lt;p&gt;那么, 就面临者选择: 1 c++ 2 python 3 java etc&lt;/p&gt;

&lt;h1 id=&quot;那么-为啥选择了-c-呢--因为-c-的抽象能力是所有语言中最高的&quot;&gt;那么, 为啥选择了 c++ 呢? —— 因为 C++ 的抽象能力是所有语言中最高的&lt;/h1&gt;

&lt;p&gt;我为啥怎么说呢？&lt;/p&gt;

&lt;h2 id=&quot;你比如说java为啥java是个烂语言呢&quot;&gt;你比如说java,为啥java是个烂语言呢&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;这个和jvm无关&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;博士说过性能问题是狗屎, 我不喜欢考虑性能问题。 java的烂和jvm无关。&lt;/p&gt;

&lt;h2 id=&quot;java-烂就烂在-gc-和面向对象&quot;&gt;java 烂就烂在 GC 和面向对象&lt;/h2&gt;

&lt;p&gt;面向对象+GC 就组合成了一个臭不可闻的烂语言。&lt;/p&gt;

&lt;h3 id=&quot;为啥-gc-和面向对象被我那么贬低呢&quot;&gt;为啥 GC 和面向对象被我那么贬低呢?&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;因为 对象 的抽象能力实在是太弱了, 并且也不是正确的抽象&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;比如 人&lt;/p&gt;

&lt;p&gt;你要用 class human 抽象?&lt;/p&gt;

&lt;p&gt;那人能干啥?&lt;/p&gt;

&lt;p&gt;这都是你不得不考虑的问题&lt;/p&gt;

&lt;p&gt;还有&lt;/p&gt;

&lt;p&gt;你觉得应该搞继承&lt;/p&gt;

&lt;p&gt;然后人从 哺乳动物继承&lt;/p&gt;

&lt;p&gt;but ~~~~&lt;/p&gt;

&lt;p&gt;多年以后, 你会发现你的分类错了&lt;/p&gt;

&lt;p&gt;咋办?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;原有的代码都高度依赖这个了&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;这就是c++ 和 java 的面向对象都被人诟病的地方&lt;/em&gt;&lt;/p&gt;

&lt;h5 id=&quot;因为-oop-本身就是个骗局&quot;&gt;因为 OOP 本身就是个骗局&lt;/h5&gt;

&lt;p&gt;既然 OOP 本身就是骗局, 自然是要避免以 &lt;em&gt;OOP 为基础&lt;/em&gt; 建立的语言&lt;/p&gt;

&lt;p&gt;那么 java 就不能用了, 因为它是一个强迫你使用 OOP 这种烂技术的语言&lt;/p&gt;

&lt;p&gt;程序是一种状态机， 图灵早就说了。&lt;/p&gt;

&lt;p&gt;状态机的意思是, 程序是由多个状态构成的, 调用语句是为了切换状态。 而面向对象却认为程序是对象构成的， 简直是 bullshit。&lt;/p&gt;

&lt;p&gt;对象压根就不能重用， 因为你根本找不到一个唯一正确的&lt;em&gt;通用&lt;/em&gt;封装&lt;/p&gt;

&lt;h5 id=&quot;结果就是大量的轮子-大家都按照自己的方式封装对象&quot;&gt;结果就是大量的&lt;em&gt;轮子&lt;/em&gt;, 大家都按照自己的方式封装对象。&lt;/h5&gt;

&lt;h3 id=&quot;大家不理解-c--是因为大家把-c-当作了-c-的-java&quot;&gt;大家不理解 C++ , 是因为大家把 C++ 当作了 C 的 java&lt;/h3&gt;

&lt;p&gt;把 C++ 当作了能编译成本地代码的 java， 有指针的 java。实际上大家还是在用 java。
 这是要绝对避免的。&lt;/p&gt;

&lt;h3 id=&quot;我坚定的反对-oop&quot;&gt;我坚定的反对 OOP。&lt;/h3&gt;

&lt;p&gt;OOP 容易指导你写出很烂的代码&lt;/p&gt;

&lt;h1 id=&quot;剩下-python-和-c&quot;&gt;剩下 python 和 C++&lt;/h1&gt;

&lt;h2 id=&quot;那么-为啥是-c而不是-python-呢&quot;&gt;那么, 为啥是 c++而不是 python 呢?&lt;/h2&gt;

&lt;p&gt;因为 “工程性”。&lt;/p&gt;

&lt;p&gt;python 不是一门具有工程性的语言。&lt;/p&gt;

&lt;p&gt;什么叫工程性?&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;工程性的定义是: 认为程序员是人 , 是人就会犯错, 要从语言上提供机制帮助程序员避免犯错. 同时对那些 控制狂, 不能在语言上束缚他们。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;python 束缚太多。 因为 python 想做一个”安全” 的语言。所以束缚多。&lt;/p&gt;

&lt;p&gt;如果这个可以忍受, 那么第一条, “从语言上提供机制帮助程序员避免犯错”， python也没有做到！&lt;/p&gt;

&lt;p&gt;因为 python 是个”安全的语言” , 所以, python 默认行为是 “面条代码”。
    也就是, 绝对避免崩溃退出
除非发生了严重的错误, 而且程序员故意为之, 否则 python 死不崩溃。&lt;/p&gt;

&lt;p&gt;死不出现段错误 (否则python也和C一样在段错误中挣扎怎么行呢)&lt;/p&gt;

&lt;p&gt;死不挂掉&lt;/p&gt;

&lt;p&gt;也就是说, &lt;em&gt;python 鼓励程序员写面条代码&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;就拿简单的一个 python gui 程序来说&lt;/p&gt;

&lt;p&gt;可能他能载入 gtk 模块, 然后界面显示了&lt;/p&gt;

&lt;p&gt;but , 一个功能缺失, 导致的结果不是程序拒绝启动, or 崩溃&lt;/p&gt;

&lt;p&gt;而是界面显示不全&lt;/p&gt;

&lt;p&gt;除非程序员故意做了检查, 抛出异常 , 强制崩溃&lt;/p&gt;

&lt;p&gt;否则 python 的默认行为就是 “面条代码”&lt;/p&gt;

&lt;p&gt;这就是一个不具备工程性的语言&lt;/p&gt;

&lt;h2 id=&quot;还有-python-可能在运行时抛出语法错误&quot;&gt;还有, python 可能在运行时抛出语法错误&lt;/h2&gt;

&lt;p&gt;这就很要命了, 一个有语法问题的 py 代码, 居然能运行。  只有偶然执行到某个有问题的代码才会崩溃抛出错误。&lt;/p&gt;

&lt;h2 id=&quot;也就是说-如果你没有-100-测试覆盖到代码-你就甚至不能肯定你的程序是-100-语法正确的&quot;&gt;也就是说, 如果你没有 100% 测试覆盖到代码, 你就甚至不能肯定你的程序是 100% 语法正确的。&lt;/h2&gt;

&lt;p&gt;这就不是一个具备工程性的语言&lt;/p&gt;

&lt;p&gt;写点玩具还可以&lt;/p&gt;

&lt;p&gt;但是一旦工程变大, 包含数十万甚至是百万千万的代码&lt;/p&gt;

&lt;h2 id=&quot;那么光是确保没有语法错误-这个很基本的东西-python-就要花费大量的人力&quot;&gt;那么光是确保”没有语法错误” 这个很基本的东西, python 就要花费大量的人力&lt;/h2&gt;

&lt;p&gt;这就是一个没有 &lt;em&gt;工程实践&lt;/em&gt; 的程序语言&lt;/p&gt;

&lt;h1 id=&quot;所以-python-是一门很烂很烂的语言&quot;&gt;&lt;a href=&quot;https://avlog.avplayer.org/3597082/python%E6%98%AF%E4%B8%AA%E7%83%82%E8%AF%AD%E8%A8%80.html&quot;&gt;所以 python 是一门很烂很烂的语言&lt;/a&gt;&lt;/h1&gt;

&lt;p&gt;烂就烂在没有工程性。&lt;/p&gt;

&lt;p&gt;可能有人会提到 C#&lt;/p&gt;

&lt;p&gt;为啥我一定会说 C# 是个烂语言呢?&lt;/p&gt;

&lt;p&gt;我一定是有原因的,我不会无缘无故的说某个语言是烂语言&lt;/p&gt;

&lt;h2 id=&quot;因为-c-就是-m-版的-java&quot;&gt;因为 C# 就是 M$ 版的 java&lt;/h2&gt;

&lt;p&gt;java 烂的, C# 一样都学习过来了&lt;/p&gt;

&lt;p&gt;所以你如果不考虑 java , 就压根不需要考虑 c#&lt;/p&gt;

&lt;h2 id=&quot;还别提-c-压根就是不跨平台的&quot;&gt;还别提 C# 压根就是不跨平台的&lt;/h2&gt;

&lt;h1 id=&quot;那么-接下来-我分析一下-c到底好在哪里-为啥-c-是这样设计的&quot;&gt;那么, 接下来, 我分析一下 c++到底好在哪里, 为啥 C++ 是这样设计的&lt;/h1&gt;

&lt;p&gt;首先一个问题, 是容易被 C 程序员忽略的, 什么是 “类型” ?&lt;/p&gt;

&lt;p&gt;C 程序员如果没有意识到这点, 通常在学习 C++ 的时候会变成个垃圾 C++ 程序员。&lt;/p&gt;

&lt;p&gt;因为 C++ 一切的一切 , 都是建立在 “类型” 上的。&lt;/p&gt;

&lt;h2 id=&quot;c的核心在-类型-而非对象不理解-类型--就不能理解-c&quot;&gt;C++的核心在 “类型” 而非对象，不理解 类型 , 就不能理解 c++&lt;/h2&gt;

&lt;p&gt;很多人(即便只用C)也很容易把 指针 和数组混淆。这就是不理解类型的原因。&lt;/p&gt;

&lt;p&gt;那么啥是 “类型” 呢?&lt;/p&gt;

&lt;p&gt;简单的来说, 一个类型 就意味着包含了相同的操作符。&lt;/p&gt;

&lt;p&gt;我是在写 QBASIC 编译器的时候才真正的理解了 类型&lt;/p&gt;

&lt;p&gt;比如 int , 是一个类型。int 是一个什么样的类型呢?  则由 int 这个类型支持的运算符定义。int 支持的运算符有 +-/* 等等 。这就是类型&lt;/p&gt;

&lt;p&gt;[] 也是运算符， 下标运算符。显然 int这个类型并不支持 [] 运算符。可是 int[], int* 类型却支持 [] 运算符。所以他们必定是不同的类型。&lt;/p&gt;

&lt;p&gt;所有支持 [] 运算符的类型, 都有一个共性, 那就是支持 [] 进行下标访问，这个共性被称为 “数组”。&lt;/p&gt;

&lt;p&gt;所有支持 * (不是乘法, 指针的那个解引用运算符) 的运算符 , 都有一个共性, 那就是支持 * 解引用 , 这个共性就被成为 “指针”。&lt;/p&gt;

&lt;p&gt;指针有一个特点, 就是支持 * 访问所引用的对象, 以及可以使用 ++ – 来变更指向的对象。（ 显然 int[] 这个类型不支持 ++ –, 显然数组和指针不是一个类型。）&lt;/p&gt;

&lt;p&gt;这种能力在 STL 里被称为 “迭代器”。 显然, 迭代器指的是支持 “ ++ – * “ 3种运算的类型。&lt;/p&gt;

&lt;p&gt;那么博士介绍到这里大家应该就比较清楚的知道了, 所谓类型, 就是依据支持的运算符定义的。&lt;/p&gt;

&lt;h1 id=&quot;那么-c-为啥要这样设计-c-为啥提供了-运算符重载-&quot;&gt;那么, C++ 为啥要这样设计? C++ 为啥提供了 “运算符重载” ?&lt;/h1&gt;

&lt;h2 id=&quot;因为-c-要-能编译&quot;&gt;因为 C++ 要 “能编译”&lt;/h2&gt;

&lt;p&gt;就这么简单。
比如 动态语言, 天生就支持了 map, list 这样的数据结构。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;但是 c++ 不能&lt;/em&gt;。&lt;/p&gt;

&lt;p&gt;他不能在语言里提供这些, 为啥不能呢? “因为 C++ 必须被编译”。&lt;/p&gt;

&lt;p&gt;这就意味着, 核心语言只能提供某种机制让你达到可以做到将 map. list 当作内置类型看待, 而不是直接内置这些功能。&lt;/p&gt;

&lt;p&gt;c++ 要做到这点, 就需要一个强大的功能 : 运算符重载。&lt;/p&gt;

&lt;p&gt;vector 类, 要重载 []， 以便表现的和 数组一致。提供 [] 运算符重载, 你就能实现出 安全的数组容器。&lt;/p&gt;

&lt;p&gt;重载 * , 你才能写出智能指针, 还和内置的指针用法保持&lt;em&gt;一致&lt;/em&gt;。而不是语言一开始就是提供智能指针。&lt;/p&gt;

&lt;p&gt;重载 – ++ . 你才能实现出迭代器，还和内置的指针用法保持&lt;em&gt;一致&lt;/em&gt;。而不是语言一开始就是提供迭代器。
以便就算你内部实现上对象都不是连续存放的, 都可以屏蔽到这些细节&lt;/p&gt;

&lt;h1 id=&quot;c是一个看重语义的语言&quot;&gt;C++是一个看重语义的语言。&lt;/h1&gt;

&lt;p&gt;所以用库对语言扩展，最好保持语法在形式上的统一。用库扩展了安全的数组容器，要在形式上使用相同的下表运算符。用库实现了智能指针，要在语法形式上提供和内置指针一样的用法, 等等。&lt;/p&gt;

&lt;p&gt;也就是说, c++ 里的一切重载, 都是为了将复杂类型用法和内置类型统一化并且仍然保留 “可编译” 这一个目标奔过去的。&lt;/p&gt;

&lt;p&gt;否则, 编译器插入个解释器, 解释执行不就完了, 就像 lisp 那样。&lt;/p&gt;

&lt;p&gt;所以说, 要理解 “类型” 是 c++ 里最重要的概念, “可编译” 是 C++ 里最重要 设计原则&lt;/p&gt;

&lt;h2 id=&quot;那么为啥-有模板呢&quot;&gt;那么为啥 有模板呢?&lt;/h2&gt;

&lt;p&gt;模板同样是为 “可编译” 服务的。否则, 你拿什么实现通用的容器呢?&lt;/p&gt;

&lt;h3 id=&quot;让大家都从-cobject-继承--那太恶心了&quot;&gt;让大家都从 CObject 继承?  那太恶心了&lt;/h3&gt;

&lt;p&gt;java 会这么干, 不代表 c++ 会这么干。说实话， MFC 就这么干了，所以MFC是烂库，绝对的烂库。&lt;/p&gt;

&lt;p&gt;23:30:28 qq(海盗)：博士的见解确实高明，我一直觉得重载强大，但就是不知道强大在何处。。今天明白了&lt;/p&gt;

&lt;h1 id=&quot;c-对-通用容器的回答是-模板-而不是让大家都从一个-cobject-继承-因为-c-就是一个设计上要避免绑架程序员的语言&quot;&gt;c++ 对 通用容器的回答是 “模板” 而不是让大家都从一个 CObject 继承 因为 c++ 就是一个设计上要避免绑架程序员的语言&lt;/h1&gt;

&lt;p&gt;cpp里, 是注重类型的&lt;/p&gt;

&lt;p&gt;23:38:39 qq(jackarain)：人家10年cpp经验, 也没领悟到你这么些东西&lt;/p&gt;

&lt;p&gt;23:39:13 qq(ywk/?_have_fun)：受教&lt;/p&gt;

&lt;p&gt;23:39:29 qq(jackarain)：模板是多态最重要的表现, 而不是继承&lt;/p&gt;

&lt;p&gt;23:39:39 qq(jackarain)：以不变应万变&lt;/p&gt;

&lt;p&gt;23:40:00 qq(jackarain)：并且老老实实保留着类型信息.&lt;/p&gt;

&lt;p&gt;23:40:07 qq(jackarain)：不会丢失&lt;/p&gt;

&lt;p&gt;23:40:44 qq(jackarain)：继承, 不一样, 通常是使用cobject这种root class来以不变应万变.&lt;/p&gt;

&lt;p&gt;23:40:59 qq(jackarain)：但效果就像void*一样&lt;/p&gt;

&lt;p&gt;23:57:44 qq(jackarain)：几小时不用c++, 就心痒&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>随机编程</title>
   <link href="https://microcai.org/2013/04/01/random-programming.html"/>
   <updated>2013-04-01T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/04/01/random-programming</id>
   <content type="html">&lt;p&gt;这个想法由来已久, 是从和  &lt;a href=&quot;http://codedoom.net&quot;&gt;Jack&lt;/a&gt; 的多次交谈中逐渐悟出来的天朝程序员惯用编程模式.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;所谓随机编程, 就是 _随机的_ 改动一些代码, 以求程序运行通过. 但是并不知道为啥这样修改程序就能工作了.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;随机编程严重的依赖于&lt;em&gt;单元测试&lt;/em&gt;. 给测试人员带来极大的负担.&lt;/p&gt;

&lt;p&gt;随机编程的程序员, 对问题不求甚解. 以通过测试为编程目标. 出现问题就一头扎进 &lt;em&gt;改代码&lt;/em&gt; 的 &lt;em&gt;重复性&lt;/em&gt; &lt;em&gt;随机性&lt;/em&gt; 工作中
为啥一个简单的bug需要那么长时间的修复? 还要程序员加班加点?&lt;/p&gt;

&lt;p&gt;因为他们要随机编程, 随机性的修改, 通过 &lt;em&gt;大量的&lt;/em&gt; 修改, 总有一次修改是正确的.&lt;/p&gt;

&lt;p&gt;有时他们 &lt;em&gt;为了&lt;/em&gt; 减轻这种机械劳动的负担, 使用了自动化的工具做测试, 是为 &lt;em&gt;单元测试&lt;/em&gt; .&lt;/p&gt;

&lt;p&gt;依据单元测试, 如果有一群猴子花无数年的时间, 总有一天他们能写出 &lt;em&gt;正常工作&lt;/em&gt; 的代码.&lt;/p&gt;

&lt;p&gt;如果一个公司异常的强调单元测试, 那么他的程序员一定是 猴子级别的. 只要他们不停的写代码, 总有一天他们能写出通过单元测试的 &lt;em&gt;正常工作&lt;/em&gt; 的代码.&lt;/p&gt;

&lt;p&gt;即便他们的程序员有不是猴子的, 管理员也是把他们当成猴子, 因为管理层不想依赖 &lt;em&gt;程序员&lt;/em&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>使用 boost 执行 base64 解码</title>
   <link href="https://microcai.org/2013/03/28/boost-base64.html"/>
   <updated>2013-03-28T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/03/28/boost-base64</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;http://en.wikipedia.org/wiki/Base64&quot;&gt;base64&lt;/a&gt; 编码最初是为了电子邮件开发的。因为电子邮件是个文本协议，不能传输二进制数据，甚至中文也无法进行传输。只能传输ascii编码的文本。这样一来就诞生了多种将二进制数据编码到ascii里的编码方案，base64是其中之一。&lt;/p&gt;

&lt;p&gt;base64是一种非常简单的编码，只要进行一次迭代即可完成解码。&lt;/p&gt;

&lt;p&gt;什么？一次迭代？？？&lt;/p&gt;

&lt;p&gt;这就让我们有机会借助 Boost 提供的迭代器非常简洁的写出base64解码器。&lt;/p&gt;

&lt;p&gt;Boost 提供了一个叫 boost::archive::iterators::binary_from_base64 的迭代器。但是直接使用它并不能完成 base64解码。&lt;/p&gt;

&lt;p&gt;还应该外面再套一层 archive::iterators::transform_width 以 6bit 为单位转换到 8bit。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;typedef	archive::iterators::transform_width&amp;lt; 
		archive::iterators::binary_from_base64&amp;lt;const char* &amp;gt;, 8, 6, char&amp;gt;
			base64decodeIterator;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;那么这个就应该是用于解码的 base64decodeIterator&lt;/p&gt;

&lt;p&gt;但是，稍等。如果用来解码电子邮件里的东西，会经常出异常，说有不能允许的字符出现在了base64输入里。&lt;/p&gt;

&lt;p&gt;为什么呢？ 因为电子邮件以 78个字符断行了。也就是出现了base64里不允许的 CRLF。&lt;/p&gt;

&lt;p&gt;那么，怎么办？ 解码前先替换删除 CRLF ?&lt;/p&gt;

&lt;p&gt;非也非也，这么做是愚蠢的哦，因为我们要的就是一次迭代的效果。
所以，archive::iterators::binary_from_base64&amp;lt;const char* &amp;gt; 使用的是 const char * 这个迭代器，对吧，我们改一下，使用 boost::filter_iterator 这个迭代器。过滤掉非base64编码字符。&lt;/p&gt;

&lt;p&gt;boost::filter_iterator 需要使用一个模板参数，参数是一个过滤用的仿函数。&lt;/p&gt;

&lt;p&gt;于是我们写一个&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;struct is_base64_char {
bool operator()(char x) { return boost::is_any_of(&quot;0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/=&quot;)(x);}
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后使用     boost::filter_iterator&amp;lt;is_base64_char, const char*&amp;gt; 作为 archive::iterators::binary_from_base64 的迭代器，就形如&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;typedef	archive::iterators::transform_width&amp;lt; 
		    archive::iterators::binary_from_base64&amp;lt;filter_iterator&amp;lt;detail::is_base64_char, const char*&amp;gt; &amp;gt;, 8, 6, char&amp;gt;
			    base64decodeIterator;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后只要使用 base64decodeIterator(base64string) ，然后执行 ++ 不停的迭代，直到遇到 nul 字符即可完成 base64 字符串解码。为了简化这个迭代过程，可以使用&lt;/p&gt;

&lt;p&gt;std::string result(base64Iterator(str.begin()) , base64Iterator(str.end()));&lt;/p&gt;

&lt;p&gt;这样的形式，则 result 的构造函数内部即会执行迭代，将遍历结果存储于 result 字符串内。&lt;/p&gt;

&lt;p&gt;做一个总结，就编写了如下的函数：
&lt;script src=&quot;https://gist.github.com/microcai/5260492.js&quot;&gt;&lt;/script&gt;&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>在高处编程</title>
   <link href="https://microcai.org/2013/03/26/stayhigher.html"/>
   <updated>2013-03-26T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/03/26/stayhigher</id>
   <content type="html">&lt;p&gt;C++之父在一个讲座上说过，C++是一门轻量级的抽象语言。轻量级，意味着C++尽量减小因为多加的抽象带来的负担。抽象语言，意味着C++有着很强的抽象能力，C++的抽象能力，有了BOOST这样的库之后被极大的提升了，配合着BOOST, 我将 C++称呼为 &lt;em&gt;编译型脚本&lt;/em&gt;。&lt;/p&gt;

&lt;p&gt;C++是一门抽象语言，使用C++的时候，千万不要去思考编译器到底是如何安排内存的。认真你就输了。使用C++的时候，千万不要去思考，到底对象如何创建，如何/何时撤销的。认真你就输了。你一定要在心里这么想：这么用就是我要的。不要去想到底发生了什么。如下面的代码&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/microcai/5243264.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;这样的代码，千万不要去思考内存的分配，std::string 的实现过程，== 运算符怎么实现的。你唯一关心的问题就是：这样一段代码是否&lt;em&gt;表达&lt;/em&gt;了你心里所想的。这一段代码是否表达了你心里所要做的事情。&lt;/p&gt;

&lt;p&gt;如果是，请千万不要考虑如何写能&lt;em&gt;更快&lt;/em&gt;。请一定要&lt;em&gt;相信编译器&lt;/em&gt;，编译器知道了你的想法后，一定会生成最优化的代码。&lt;/p&gt;

&lt;p&gt;有的人说，使用了BOOST之后，编译很慢。编译慢，说明编译器在编译期执行了大量的工作。编译期做的事情越多，意味着运行期做的事情越少。不要怀疑编译的能力。&lt;/p&gt;

&lt;p&gt;C++是一门轻量级的抽象语言，请站在高处进行思维，请相信编译器的优化。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>无栈协程</title>
   <link href="https://microcai.org/2013/03/17/stackless-coroutine.html"/>
   <updated>2013-03-17T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/03/17/stackless-coroutine</id>
   <content type="html">&lt;p&gt;在开始之前，先来看一段代码　&lt;/p&gt;

&lt;pre style=&quot;color:#1f1c1b;background-color:#ffffff;&quot;&gt;
&lt;span style=&quot;color:#0057ae;&quot;&gt;void&lt;/span&gt; pop3::&lt;b&gt;operator&lt;/b&gt;() ( &lt;span style=&quot;color:#0057ae;&quot;&gt;const&lt;/span&gt; boost::system::error_code&amp;amp; ec, std::size_t length )
{
	&lt;b&gt;using&lt;/b&gt; &lt;b&gt;namespace&lt;/b&gt; boost::asio;

	ip::tcp::endpoint endpoint;
	std::string		status;
	std::string		maillength;
	std::istream	inbuffer ( m_streambuf.get() );
	std::string		msg;

	reenter ( &lt;b&gt;this&lt;/b&gt; ) {
restart:
		m_socket.reset( &lt;b&gt;new&lt;/b&gt; ip::tcp::socket(io_service) );

		&lt;b&gt;do&lt;/b&gt; {
&lt;span style=&quot;color:#006e28;&quot;&gt;#ifndef DEBUG&lt;/span&gt;
			&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 延时 60s&lt;/span&gt;&lt;/i&gt;
			_yield ::boost::delayedcallsec( io_service, &lt;span style=&quot;color:#b08000;&quot;&gt;60&lt;/span&gt;, boost::bind(*&lt;b&gt;this&lt;/b&gt;, ec, &lt;span style=&quot;color:#b08000;&quot;&gt;0&lt;/span&gt;) );
&lt;span style=&quot;color:#006e28;&quot;&gt;#endif&lt;/span&gt;

			&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// dns 解析并连接.&lt;/span&gt;&lt;/i&gt;
 			_yield boost::async_avconnect(
 				boost::proxychain(io_service).add_proxy()(boost::proxy_tcp(*m_socket, ip::tcp::resolver::query(m_mailserver, &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;110&amp;quot;&lt;/span&gt;))),
 				*&lt;b&gt;this&lt;/b&gt;);

			&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 失败了延时 10s&lt;/span&gt;&lt;/i&gt;
			&lt;b&gt;if&lt;/b&gt; ( ec )
				_yield ::boost::delayedcallsec ( io_service, &lt;span style=&quot;color:#b08000;&quot;&gt;10&lt;/span&gt;, boost::bind(*&lt;b&gt;this&lt;/b&gt;, ec, &lt;span style=&quot;color:#b08000;&quot;&gt;0&lt;/span&gt;) );
		} &lt;b&gt;while&lt;/b&gt; ( ec ); &lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 尝试到连接成功为止!&lt;/span&gt;&lt;/i&gt;

		&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 好了，连接上了.&lt;/span&gt;&lt;/i&gt;
		m_streambuf.reset ( &lt;b&gt;new&lt;/b&gt; streambuf );
		&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// &amp;quot;+OK QQMail POP3 Server v1.0 Service Ready(QQMail v2.0)&amp;quot;&lt;/span&gt;&lt;/i&gt;
		_yield	async_read_until ( *m_socket, *m_streambuf, &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#924c9d;&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;&lt;/span&gt;, *&lt;b&gt;this&lt;/b&gt; );
		inbuffer &amp;gt;&amp;gt; status;

		&lt;b&gt;if&lt;/b&gt; ( status != &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;+OK&amp;quot;&lt;/span&gt; ) {
			&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 失败，重试.&lt;/span&gt;&lt;/i&gt;
			&lt;b&gt;goto&lt;/b&gt; restart;
		}

		&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 发送用户名.&lt;/span&gt;&lt;/i&gt;
		_yield m_socket-&amp;gt;async_write_some ( buffer ( std::string ( &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;user &amp;quot;&lt;/span&gt; ) + m_mailaddr + &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#924c9d;&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;&lt;/span&gt; ), *&lt;b&gt;this&lt;/b&gt; );
		&lt;b&gt;if&lt;/b&gt;(ec) &lt;b&gt;goto&lt;/b&gt; restart;
		&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 接受返回状态.&lt;/span&gt;&lt;/i&gt;
		m_streambuf.reset ( &lt;b&gt;new&lt;/b&gt; streambuf );
		_yield	async_read_until ( *m_socket, *m_streambuf, &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#924c9d;&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;&lt;/span&gt;, *&lt;b&gt;this&lt;/b&gt; );
		inbuffer &amp;gt;&amp;gt; status;

		&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 解析是不是　OK.&lt;/span&gt;&lt;/i&gt;
		&lt;b&gt;if&lt;/b&gt; ( status != &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;+OK&amp;quot;&lt;/span&gt; ) {
			&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 失败，重试.&lt;/span&gt;&lt;/i&gt;
			&lt;b&gt;goto&lt;/b&gt; restart;
		}

		&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 发送密码.&lt;/span&gt;&lt;/i&gt;
		_yield m_socket-&amp;gt;async_write_some ( buffer ( std::string ( &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;pass &amp;quot;&lt;/span&gt; ) + m_passwd + &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#924c9d;&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;&lt;/span&gt; ), *&lt;b&gt;this&lt;/b&gt; );
		&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 接受返回状态.&lt;/span&gt;&lt;/i&gt;
		m_streambuf.reset ( &lt;b&gt;new&lt;/b&gt; streambuf );
		_yield	async_read_until ( *m_socket, *m_streambuf, &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#924c9d;&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;&lt;/span&gt;, *&lt;b&gt;this&lt;/b&gt; );
		inbuffer &amp;gt;&amp;gt; status;

		&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 解析是不是　OK.&lt;/span&gt;&lt;/i&gt;
		&lt;b&gt;if&lt;/b&gt; ( status != &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;+OK&amp;quot;&lt;/span&gt; ) {
			&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 失败，重试.&lt;/span&gt;&lt;/i&gt;
			&lt;b&gt;goto&lt;/b&gt; restart;
		}

		&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 完成登录. 开始接收邮件.&lt;/span&gt;&lt;/i&gt;

		&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 发送　list 命令.&lt;/span&gt;&lt;/i&gt;
		_yield m_socket-&amp;gt;async_write_some ( buffer ( std::string ( &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;list&lt;/span&gt;&lt;span style=&quot;color:#924c9d;&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;&lt;/span&gt; ) ), *&lt;b&gt;this&lt;/b&gt; );
		&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 接受返回的邮件.&lt;/span&gt;&lt;/i&gt;
		m_streambuf.reset ( &lt;b&gt;new&lt;/b&gt; streambuf );
		_yield	async_read_until ( *m_socket, *m_streambuf, &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#924c9d;&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;&lt;/span&gt;, *&lt;b&gt;this&lt;/b&gt; );
		inbuffer &amp;gt;&amp;gt; status;

		&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 解析是不是　OK.&lt;/span&gt;&lt;/i&gt;
		&lt;b&gt;if&lt;/b&gt; ( status != &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;+OK&amp;quot;&lt;/span&gt; ) {
			&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 失败，重试.&lt;/span&gt;&lt;/i&gt;
			&lt;b&gt;goto&lt;/b&gt; restart;
		}

		&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 开始进入循环处理邮件.&lt;/span&gt;&lt;/i&gt;
		maillist.clear();
		_yield	m_socket-&amp;gt;async_read_some ( m_streambuf-&amp;gt;prepare ( &lt;span style=&quot;color:#b08000;&quot;&gt;8192&lt;/span&gt; ), *&lt;b&gt;this&lt;/b&gt; );
		m_streambuf-&amp;gt;commit ( length );

		&lt;b&gt;while&lt;/b&gt; ( status != &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;.&amp;quot;&lt;/span&gt; ) {
			maillength.clear();
			status.clear();
			inbuffer &amp;gt;&amp;gt; status;
			inbuffer &amp;gt;&amp;gt; maillength;

			&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 把邮件的编号push到容器里.&lt;/span&gt;&lt;/i&gt;
			&lt;b&gt;if&lt;/b&gt; ( maillength.length() )
				maillist.push_back ( status );

			&lt;b&gt;if&lt;/b&gt; ( inbuffer.eof() &amp;amp;&amp;amp; status != &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;.&amp;quot;&lt;/span&gt; )
				_yield	m_socket-&amp;gt;async_read_some ( m_streambuf-&amp;gt;prepare ( &lt;span style=&quot;color:#b08000;&quot;&gt;8192&lt;/span&gt; ), *&lt;b&gt;this&lt;/b&gt; );
		}

		&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 获取邮件.&lt;/span&gt;&lt;/i&gt;
		&lt;b&gt;while&lt;/b&gt; ( !maillist.empty() ) {
			&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 发送　retr #number 命令.&lt;/span&gt;&lt;/i&gt;
			msg = boost::str ( boost::format ( &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;retr %s&lt;/span&gt;&lt;span style=&quot;color:#924c9d;&quot;&gt;\r\n&lt;/span&gt;&lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;&lt;/span&gt; ) %  maillist[&lt;span style=&quot;color:#b08000;&quot;&gt;0&lt;/span&gt;] );
			_yield m_socket-&amp;gt;async_write_some ( buffer ( msg ), *&lt;b&gt;this&lt;/b&gt; );
			&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 获得　+OK&lt;/span&gt;&lt;/i&gt;
			m_streambuf.reset ( &lt;b&gt;new&lt;/b&gt; streambuf );
			_yield	async_read_until ( *m_socket, *m_streambuf, &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#924c9d;&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;&lt;/span&gt;, *&lt;b&gt;this&lt;/b&gt; );
			inbuffer &amp;gt;&amp;gt; status;

			&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 解析是不是　OK.&lt;/span&gt;&lt;/i&gt;
			&lt;b&gt;if&lt;/b&gt; ( status != &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;+OK&amp;quot;&lt;/span&gt; ) {
				&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 失败，重试.&lt;/span&gt;&lt;/i&gt;
				&lt;b&gt;goto&lt;/b&gt; restart;
			}

			&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 获取邮件内容，邮件一单行的 . 结束.&lt;/span&gt;&lt;/i&gt;
			_yield	async_read_until ( *m_socket, *m_streambuf, &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#924c9d;&quot;&gt;\r\n&lt;/span&gt;&lt;span style=&quot;color:#bf0303;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#924c9d;&quot;&gt;\r\n&lt;/span&gt;&lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;&lt;/span&gt;, *&lt;b&gt;this&lt;/b&gt; );
			&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 然后将邮件内容给处理.&lt;/span&gt;&lt;/i&gt;
			process_mail ( inbuffer );
			&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 删除邮件啦.&lt;/span&gt;&lt;/i&gt;
			msg = boost::str ( boost::format ( &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;dele %s&lt;/span&gt;&lt;span style=&quot;color:#924c9d;&quot;&gt;\r\n&lt;/span&gt;&lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;&lt;/span&gt; ) %  maillist[&lt;span style=&quot;color:#b08000;&quot;&gt;0&lt;/span&gt;] );
			_yield m_socket-&amp;gt;async_write_some ( buffer ( msg ), *&lt;b&gt;this&lt;/b&gt; );

			maillist.erase ( maillist.begin() );
			&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 获得　+OK&lt;/span&gt;&lt;/i&gt;
			m_streambuf.reset ( &lt;b&gt;new&lt;/b&gt; streambuf );
			_yield	async_read_until ( *m_socket, *m_streambuf, &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color:#924c9d;&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;&lt;/span&gt;, *&lt;b&gt;this&lt;/b&gt; );
			inbuffer &amp;gt;&amp;gt; status;

			&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 解析是不是　OK.&lt;/span&gt;&lt;/i&gt;
			&lt;b&gt;if&lt;/b&gt; ( status != &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;+OK&amp;quot;&lt;/span&gt; ) {
				&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 失败，但是并不是啥大问题.&lt;/span&gt;&lt;/i&gt;
				std::cout &amp;lt;&amp;lt; &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;deleting mail failed&amp;quot;&lt;/span&gt; &amp;lt;&amp;lt; std::endl;
				&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// but 如果是连接出问题那还是要重启的.&lt;/span&gt;&lt;/i&gt;
				&lt;b&gt;if&lt;/b&gt;(ec) &lt;b&gt;goto&lt;/b&gt; restart;
			}
		}

		&lt;i&gt;&lt;span style=&quot;color:#898887;&quot;&gt;// 处理完毕.&lt;/span&gt;&lt;/i&gt;
		_yield async_write ( *m_socket, buffer ( &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;quit&lt;/span&gt;&lt;span style=&quot;color:#924c9d;&quot;&gt;\n&lt;/span&gt;&lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;&lt;/span&gt; ), *&lt;b&gt;this&lt;/b&gt; );
		_yield ::boost::delayedcallsec ( io_service, &lt;span style=&quot;color:#b08000;&quot;&gt;1&lt;/span&gt;, boost::bind ( *&lt;b&gt;this&lt;/b&gt;, ec, &lt;span style=&quot;color:#b08000;&quot;&gt;0&lt;/span&gt; ) );
		&lt;b&gt;if&lt;/b&gt;(m_socket-&amp;gt;is_open())
			m_socket-&amp;gt;shutdown ( ip::tcp::socket::shutdown_both );
		_yield ::boost::delayedcallsec ( io_service, &lt;span style=&quot;color:#b08000;&quot;&gt;1&lt;/span&gt;, boost::bind ( *&lt;b&gt;this&lt;/b&gt;, ec, &lt;span style=&quot;color:#b08000;&quot;&gt;0&lt;/span&gt; ) );
		m_socket.reset();
		std::cout &amp;lt;&amp;lt; &lt;span style=&quot;color:#bf0303;&quot;&gt;&amp;quot;邮件处理完毕&amp;quot;&lt;/span&gt; &amp;lt;&amp;lt; std::endl;
		_yield ::boost::delayedcallsec ( io_service, &lt;span style=&quot;color:#b08000;&quot;&gt;30&lt;/span&gt;, boost::bind ( *&lt;b&gt;this&lt;/b&gt;, ec, &lt;span style=&quot;color:#b08000;&quot;&gt;0&lt;/span&gt; ) );
		&lt;b&gt;goto&lt;/b&gt; restart;
	}
}
&lt;/pre&gt;
&lt;!--  --&gt;

&lt;p&gt;这个代码，乍一看就是同步代码嘛！而事实上它是异步的&lt;/p&gt;

&lt;p&gt;在这个代码里，使用了　_yield 前缀再配合　async_* 异步函数，使用异步实现了同步的pop3登录算法。&lt;/p&gt;

&lt;p&gt;这个神奇的代码，神奇之处就是 reenter(this) 和　_yield。这2个地方就是实现的全部的关键。&lt;/p&gt;

&lt;p&gt;我在群课程里有简单的提到过协程，有兴趣的可以到　&lt;a href=&quot;https://avlog.avplayer.org/3597082/%E5%8D%8F%E7%A8%8B.html&quot;&gt;avplayer社区讲座：协程&lt;/a&gt; 围观。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Why ubuntu sucks and everyone should not use it</title>
   <link href="https://microcai.org/2013/03/08/killubuntu.html"/>
   <updated>2013-03-08T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/03/08/killubuntu</id>
   <content type="html">&lt;p&gt;这篇文章早就应该发了，但是我太自私，只管自己躲在Gentoo的树阴下乘凉，不为那些在ubuntu的刀山祸害里挣扎的人提供援助。
所以，我觉得为他们提供无偿的援助——告诉他们，ubuntu很烂很烂。&lt;/p&gt;

&lt;p&gt;ubuntu的烂要分两个部分，其一是debian的烂，其二是ubuntu的烂。&lt;/p&gt;

&lt;p&gt;#　debian之烂&lt;/p&gt;

&lt;p&gt;debian的烂，体现在包管理器上，特别是debian特色的拆包习俗。&lt;/p&gt;

&lt;p&gt;debian的打包习俗有三大恶：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;拆包细&lt;/li&gt;
  &lt;li&gt;乱改名&lt;/li&gt;
  &lt;li&gt;版本旧&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;拆包过细，大大增加维护负担。虽然对用户来说看似节约了硬盘空间，安装更少的二进制数据，实际上包管理器的数据库变得更加庞大。&lt;/p&gt;

&lt;p&gt;乱改名这就是debian的原罪了。因为deb包本身的限制，无法在包里编制版本信息以允许相同的包的不同版本同时安装。只能采取将软件的版本作为后缀作为包名。比如　libXXX2 libXXX1 libXXX0
这样开发者在写依赖的时候，就需要知道具体后缀是0 还是1 还是2。非常麻烦。&lt;/p&gt;

&lt;p&gt;因为拆包细，写依赖就要写一堆，稍微不注意就少写依赖了。大大增加了打包人员的维护负担。&lt;/p&gt;

&lt;p&gt;乱改名和拆包结合起来，导致一些著名的软件，即便是使用他的本名你也安装不了。
比如 apt-get install git 是无法安装git的。因为　git 被拆包了，而且还改名了。到现在我也不知道如何安装git。&lt;/p&gt;

&lt;p&gt;debian 包以旧为荣耀，片面的认为旧就是稳定。&lt;/p&gt;

&lt;p&gt;debian的包，一个字旧。debian通常包含的是上游停止维护的版本。诶，你说上游停止维护的版本，稳定个鬼啊。&lt;/p&gt;

&lt;p&gt;可是　debian 不这么认为，debian 认为旧就是稳定。&lt;/p&gt;

&lt;h1 id=&quot;ubuntu自身之恶&quot;&gt;ubuntu自身之恶&lt;/h1&gt;

&lt;p&gt;除了&lt;em&gt;原原本本的继承&lt;/em&gt;了全部的debian之恶，ubuntu自身的恶就罄竹难书了。&lt;/p&gt;

&lt;p&gt;ubuntu喜欢控制，喜欢　fork　上游软件。然后自己定制。定制完了不回馈给上游。&lt;/p&gt;

&lt;p&gt;过了一年，ubuntu 又重新　fork　，因为自己　fork 的那个版本落后了，显然不能和上游竞争了。&lt;/p&gt;

&lt;p&gt;ubuntu想控制　gnome , 结果控制不了，就自己　fork 个　unity。先是　gtk 开发，然后失败，
然后　以compiz 插件开发，失败，然后又用　Qt　开发。
哥我还是会断定他继续失败。&lt;/p&gt;

&lt;p&gt;ubuntu一意孤行，一定要&lt;em&gt;故意&lt;/em&gt;创造和其他发行版不兼容的地方。其他发行版用　systemd , ubuntu一意孤行的使用upstart。故意创造不兼容的地方。&lt;/p&gt;

&lt;p&gt;TOBE ADD&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>博客迁移到 github pages</title>
   <link href="https://microcai.org/2013/02/02/blogtrans.html"/>
   <updated>2013-02-02T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/02/02/blogtrans</id>
   <content type="html">&lt;p&gt;越来越觉得花钱买一个 VPS 是个太浪费钱的事情了，而且也没那么多精力去维护服务器，于是将博客迁移到 github 就成了上上选择。&lt;/p&gt;

&lt;p&gt;现在终于完成迁移了，并且也开启了评论功能，真的是非常不错。可以使用 vim 编写博客了，这真的是太爽的事情了。&lt;/p&gt;

&lt;p&gt;目前的 url 是 &lt;a href=&quot;http://microcai.org&quot;&gt;http://microcai.org&lt;/a&gt;, 支持 RSS Feed, url是 &lt;a href=&quot;/feed&quot;&gt;RSS Feed&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;每篇文章都可以使用评论功能哦！&lt;/p&gt;

&lt;p&gt;这真的是太爽了。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>2012 年总结</title>
   <link href="https://microcai.org/2013/01/31/2012sum.html"/>
   <updated>2013-01-31T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/01/31/2012sum</id>
   <content type="html">&lt;p&gt;过去一年里，啥事情没干。&lt;/p&gt;

&lt;p&gt;2012年来的时候，唔说，世界末日到来前不拼命工作。于是就每天宅家里啥也不做了。&lt;/p&gt;

&lt;p&gt;要说啥都没做也不对。&lt;/p&gt;

&lt;h3 id=&quot;乘宅在家里不用工作的机会把很久以前丢下的c重新学习了&quot;&gt;乘宅在家里不用工作的机会，把很久以前丢下的C++重新学习了。&lt;/h3&gt;

&lt;p&gt;重新学习C++是一个机缘巧合的事情，jack 一直是一个C++程序员，重度Boost粉丝。一直向我灌输boost。不过，我一直和云风一样，总是黑C++，说只用C开发。&lt;/p&gt;

&lt;p&gt;不过C++11的发布让我觉得有必要了解一下C++11。就算不用，我总要看别人的c++11代码的吧！本着这个目的，我仔细的看了c++老爸写的C++11 FAQ。&lt;/p&gt;

&lt;p&gt;新的c++11让我惊呼！ C++11乃神器也！&lt;/p&gt;

&lt;p&gt;我开始不黑C++了，我改变了对C++的坏印象。但是真正开始使用C++，与使用Boost可同一时间。
有一天，我读了ByVoid的博客，他推荐了一个叫rimeime的神级输入法。我被rimeime首页上那唯美的诗吸引了，认定rimeime作者一定是个大牛。当我编译rime的时候，发现rime居然是使用Boost开发的。rimeime专注于输入法方面算法，只字不提开发中的啥数据结构啊之类的，让我这个整天活在C世界的乡巴佬汗颜。我过多的思考底层了。是个毛病，该改！我意思到，软件开发就应该专注于这个专业领域的东西，不要被C语言带来的计算机原理模糊掉自己的方向。和 jack 讨论了这个问题，他也认为当下的编程语言，都过分的让程序员考虑计算机的内部原理了。而能让不懂编程的用户快速的开发出程序的语言，都死了。（VB 泪流满面）
于是我再一次拿起C++的时候，自己打算试一试，再加上jack的鼓吹，终于开始使用Boost了。
Boost 真乃神器也~ 有种相见恨晚的感觉。&lt;/p&gt;

&lt;h3 id=&quot;llvm是个好东西于是借着编写一个qbasic编译器的功夫学习了一下llvm&quot;&gt;LLVM是个好东西，于是借着编写一个QBASIC编译器的功夫学习了一下LLVM&lt;/h3&gt;

&lt;p&gt;很早的时候，我就想写一个编译器。一直认为编译器是一个很高难度的东西。于是我选择了一个简单的语言：QBASIC作为我的目标。经过一个星期的编译原理方面的学习，终于靠着llvm这个大山写出了一个编译器，哦也，感觉非常不错。&lt;/p&gt;

&lt;h3 id=&quot;avplayerorg-社区创立&quot;&gt;avplayer.org 社区创立。&lt;/h3&gt;

&lt;p&gt;avplayer是jack编写的一个p2p播放器。我有幸参与了开发，并做了Linux版本的移植工作。
之后加入了jack建立的一个QQ群。因为 Linux 下QQ并不方便使用。于是我打算开发一个能将 QQ和IRC结合起来的机器人。这样我就可以在 IRC 上参与群的讨论。 这个想法的直接结果就是导致 qqbot 的诞生。然后jack和我感觉应该创立一个社区，吸引国内优秀的程序员加入。于是建立了 avplayer.org 社区，并将 qqbot 改名为 avbot ，和 avplayer 一道成为 avplayer.org 社区项目。成立后，hyq 迅速的贡献了自己开发的 avsocks 软件，成为社区的三驾马车。 avsocks 原由go语言开发，我迅速的将其用 boost 重构。avsocks 目前可是最受欢迎的项目，呵呵。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>avplayer 开源软件组织成立，域名  avplayer.org</title>
   <link href="https://microcai.org/2013/01/01/avplayer.html"/>
   <updated>2013-01-01T00:00:00+00:00</updated>
   <id>https://microcai.org/2013/01/01/avplayer</id>
   <content type="html">&lt;h1 id=&quot;avplayerorg-项目组成立了&quot;&gt;avplayer.org 项目组成立了。&lt;/h1&gt;

&lt;p&gt;avplayer 的宗旨是开发易于使用的跨平台 AV播放器框架。avplayer自带一个用 AV播放器框架写的AVPLAYER播放器。 依托于 AVPLAYER本组织还开发了用于成员沟通的 AVBOT (原 QQBOT)。 AVBOT 实现了 QQ/IRC/XMPP的互通。&lt;/p&gt;

&lt;p&gt;为了更科学的上网，本群再次发力，开发了 AVSOCKS 科学操长城软件。&lt;/p&gt;

&lt;p&gt;当然， 所有的代码都在 https://github.com/avplayer&lt;/p&gt;

&lt;p&gt;项目首页 http://avplayer.org&lt;/p&gt;

&lt;h3 id=&quot;如何加入&quot;&gt;如何加入？&lt;/h3&gt;

&lt;p&gt;首先要会 C++。不会也没关系，进来一起学习 AV ,学习的过程中就会 C++了。使用 BOOST 神器加速。多少AV都不怕。来组织的人，都嗑 BOOST 上瘾了，相信你加入后也会被 BOOST 征服的。 
如何加入？&lt;/p&gt;

&lt;p&gt;简单，加入 QQ群 3597082 即可。 没有 QQ ? Linuxer ? 没关系，咱不是用 AVBOT 么！ 进入 #avplayer @ irc.freenode.net 一样可以参与聊天。&lt;/p&gt;

&lt;p&gt;哦？不会 IRC ? 还好有了 AVBOT, 加入 XMPP 聊天室 avplayer@im.linuxapp.org 即可！&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>python是个渣语言</title>
   <link href="https://microcai.org/2012/12/25/whypythonbad.html"/>
   <updated>2012-12-25T00:00:00+00:00</updated>
   <id>https://microcai.org/2012/12/25/whypythonbad</id>
   <content type="html">&lt;p&gt;一直以来我都表达 python 是个糟糕语言的观点，但是没有深入的解释。&lt;/p&gt;

&lt;p&gt;计算机一直都是“工具”，意味着我们是拿它干活的，也就是所谓的提高生产力。&lt;/p&gt;

&lt;p&gt;指挥计算机干活的重要工具就是编程语言。计算机并不是训练来干活的，是编程来干活的。编程语言的效率有2个指标：编写干活指南的效率和机器人执行的效率。&lt;/p&gt;

&lt;p&gt;在人力成本低于硬件的时候，人追捧的是执行效率。&lt;/p&gt;

&lt;p&gt;人力成本越来越高的时候，人开始追求编程的效率。&lt;/p&gt;

&lt;p&gt;人自然是希望一个语言能两头兼顾。可惜的是 python非但没有带来执行效率（这是python不追求的，所以姑且不算缺点），连它拼命牺牲执行效率希望换来的开发效率事实上也一点没有。&lt;/p&gt;

&lt;p&gt;执行效率：首先，执行效率和语言本身高级不高级是没有任何关系的。执行效率的高低只关系到冗余操作的多寡。这也是“优化”的基础，去除冗余操作。&lt;/p&gt;

&lt;p&gt;冗余操作的多寡通常有3个因素影响到：1 编译器的效率  2 程序员的水平 3 语言本身的累赘&lt;/p&gt;

&lt;p&gt;但是衡量一个语言本身效率的，事实上应该是最后一个 “语言本身的累赘”，这个才是编译器永远无法改进的，程序员水平再高也无能为力的。&lt;/p&gt;

&lt;p&gt;不幸的是，python是一个本身的累赘非常多的语言。而累赘最少的语言，应该算是C++语言了。所有C++用到的功能，没有一个是可以在别的语言用更低的代价实现的。当然，有的语言压根就没有C++提供的功能，必须自己模拟。模拟的代价和C++提供的是一样的，水平不够的人来模拟只能获得更烂的结果。&lt;/p&gt;

&lt;p&gt;当然 ，python是个高级语言，语法糖多点，性能烂就接受一下吧！毕竟开发效率高呢（？）&lt;/p&gt;

&lt;p&gt;接下来我们说编程的效率。编程效率主要是受5个因素影响：语法是否自然，语义是否凝炼，文档或者或教程多不多，库是不是丰富，开发环境好不好。不用说python的文档还是很多的，但是显然没有c++多。各种粗制滥造的c++教程铺天盖地，算了，这是c++的坏处。误人子弟的教程太多。库当然是C++最丰富了。python还面临着 python3 和 python2 的分裂。&lt;/p&gt;

&lt;p&gt;开发环境和库一直是C++的优势。不用说 Visual C++这种重量级公司出的IDE ，还有 kdevelop , eclipse CDT 这些免费开源的IDE。自动完成和代码提示让你写代码的时候非常轻松。相比之下 ， python 就没有好的IDE了。&lt;/p&gt;

&lt;p&gt;当然，接下来是程序员最关心的，语法是否自然，语义是否凝炼。python 的语义自然是很多的，一条语句能相当于写几千行 C 代码。这也是人常说的，语法糖多。可惜的是，C++一样有高级语法糖，而且代价很低！比python低太多了。在这一点上，python没有优势，只有劣质。python的语法糖是以牺牲性能换来的，而C++在不损失性能的同时提供了语法糖。&lt;/p&gt;

&lt;p&gt;至于语法自然不，仁者见仁智者见智了。对于大多数C学过来的人来说， 自然是C++的语法简单。当然，前提是不使用模板这种高级货。真的用模板的话，模板是属于用起来简单，写起来难的语法糖。我们自然可以选择把困难留给 boost，快乐留给自己。所以这点上 C++没有输。何况python还不支持模板。&lt;/p&gt;

&lt;p&gt;那么开发效率到底是 C++高还是 python呢？&lt;/p&gt;

&lt;p&gt;差不多！&lt;/p&gt;

&lt;p&gt;那执行效率呢？&lt;/p&gt;

&lt;p&gt;C++和python不是一个档次的，没法比。&lt;/p&gt;

&lt;p&gt;好了，单从这点已经 python 完败。不过我想说的还不是这个。&lt;/p&gt;

&lt;p&gt;如果真的有语言像python那样慢，我觉得对得起它的性能的，就必须拿出像样的功能，这个功能包括&lt;/p&gt;

&lt;p&gt;语言级的并行能力，语言级的多进程能力 （等等，这不就是shell么！），语言级的SIMD能力（语言级的矩阵运算支持）&lt;/p&gt;

&lt;p&gt;语言级的复杂数学公式计算能力  ，内置的标准各种算法（STL笑而不语，不过我要的还不止STL），描述性语义（而非指令性语义）&lt;/p&gt;

&lt;p&gt;我要求的这些，是高级语言需要具备的，而 python 统统没有，连 shell 都能占上一些！&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>内存管理随笔</title>
   <link href="https://microcai.org/2012/10/31/mm.html"/>
   <updated>2012-10-31T00:00:00+00:00</updated>
   <id>https://microcai.org/2012/10/31/mm</id>
   <content type="html">&lt;h3 id=&quot;思考一&quot;&gt;思考一：&lt;/h3&gt;

&lt;p&gt;任何一个程序，只要不是 helloworld 那样的简单程序，必然会用到内存管理。内存管理是写程序不可避免的过程。C程序员最大的恶魔就是野指针和内存泄漏。这是每个C程序员的噩梦。C++继承了C的缺陷，基本上半斤八两。而且C++还可以自由重载new操作符，给内存管理更加重了复杂性。 
我常常在想，写BASH脚本的时候，我们有管理过内存么？即便是java那样的语言，内存管理也是后台进行的，并不是可避免的，俗称GC。可是写脚本的时候，真的完全没用内存管理方面的困扰。&lt;/p&gt;

&lt;p&gt;到底是哪里出了问题呢？到底为何脚本就不用管理内存？&lt;/p&gt;

&lt;h3 id=&quot;思考二&quot;&gt;思考二：&lt;/h3&gt;

&lt;p&gt;能不能完全不依靠malloc/free new/delete 编写出一个 c/c++程序呢？&lt;/p&gt;

&lt;p&gt;结果是显然的，能！&lt;/p&gt;

&lt;p&gt;但是只有对 hello world 那样的程序才有用。最近的一项小随笔项目证实了我的想法 https://github.com/microcai/hm 。在这个程序里，我没用使用任何 malloc/free new/delete 来管理内存。&lt;/p&gt;

&lt;p&gt;为何这个程序可以不用内存管理呢？ 于是在另一个随笔项目里，我依然使用了内存管理，虽然是智能指针自动管理的，但是毕竟使用了 new 操作 https://github.com/microcai/googleproxy 。我在想，凭啥这个程序就必须使用 new 了？&lt;/p&gt;

&lt;h3 id=&quot;思考三&quot;&gt;思考三：&lt;/h3&gt;

&lt;p&gt;程序，说到底就是一个状态机。在 hm 程序里，我采用的是“过程化”编程，外加同步多进程的IO模型。在 googleproxy 程序里，使用的是单线程异步IO的模型。&lt;/p&gt;

&lt;p&gt;hm里，状态机的状态就是进程的状态。一步一步执行下去，状态随之切换。cpu执行到哪一行，哪一行就是当前状态。&lt;/p&gt;

&lt;p&gt;googleproxy里，情况有了变化，因为使用了异步，所有的状态变化都是围绕一个中心进行的： boost::io_service::run() 。 在 run() 里，通过回调来通知状态的变化。也就是说，cpu执行到哪个回调，哪个就是当前状态。 
区别是什么：&lt;/p&gt;

&lt;p&gt;在不同的状态之间，我们有数据要共享！ hm 多使用局部变量，不同的状态需要共享数据，通常也处于同一代码层级！可以直接引用需要的数据！&lt;/p&gt;

&lt;p&gt;googleproxy 里，不同的状态之间，是不同的 run() 操作的回调，需要共享数据，但是上一个状态的局部变量已经消失！上一个状态的局部变量已经消失！ 
要跨栈域引用共享数据，唯一的办法就是将数据创建在堆上！&lt;/p&gt;

&lt;p&gt;简单的来说，在一个程序里，所有代码的执行路径可以归纳为从 main() 开始的一个调用树。一个函数只能引用本层和上层的局部变量，无法引用子层和兄弟层和兄弟的子层的局部变量。因为这些地方的变量都是不存在的呀！&lt;/p&gt;

&lt;p&gt;所以，只能将共享数据创建在堆上。这样才能跨过调用树的生存周期！&lt;/p&gt;

&lt;p&gt;这也是唯一需要堆管理的理由。&lt;/p&gt;

&lt;p&gt;如果一个变量要进入函数的时候创建在堆上，退出的时候释放，通常的做法其实就是使用栈变量，或者是使用可变长度的stl容器。&lt;/p&gt;

&lt;p&gt;如果返回值是对象，也不要使用指针了，直接返回对象吧！别担心临时对象拷贝开销了。&lt;/p&gt;

&lt;p&gt;只有当跨栈域共享对象的时候，才考虑使用堆吧！ 
记住：即便如此，也不要使用裸指针。裸指针只用来进行直接内存访问（底层编程的时候），千万别用来引用对象。使用智能指针吧！放弃对象的拷贝开销的担心吧！ 那几百个周期的拷贝操作比 new/delete 的内存管理代码的开销相比，还是低太多里。何况有了 c++11 的 Move 语义，很多时候已经没用拷贝开销里。&lt;/p&gt;

&lt;p&gt;放心大胆的脚本化C++程序吧！ 实在需要跨栈共享数据的时候，使用 shared_ptr 引用吧！&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>C++ 王者归来</title>
   <link href="https://microcai.org/2012/10/12/cppreturn.html"/>
   <updated>2012-10-12T00:00:00+00:00</updated>
   <id>https://microcai.org/2012/10/12/cppreturn</id>
   <content type="html">&lt;p&gt;曾几何时，学会了C语言。安装了VC6这个神叉IDE。&lt;/p&gt;

&lt;p&gt;然后被迫开始CPP路程。由于C++兼容C，所以一直在以C的方式写C++。然后慢慢的开始学写C++代码。写C++代码是从MFC开始的。慢慢的，我学会了用class，感觉是个比struct好用的多的结构体。再慢慢的，我学会了继承，还有… 多重继承。&lt;/p&gt;

&lt;p&gt;MFC 就是我的导师。开始不停的向MFC学习。既然用了c++的继承，就认识到了继承的陷阱。继承后，构造函数和构析函数的执行次序，等等。还有虚继承后的各种问题和陷阱。&lt;/p&gt;

&lt;p&gt;一一去了解。&lt;/p&gt;

&lt;p&gt;在程序结构方面，开始向 MFC 看齐。喜欢把任何操作都包装到class里。设计class的时候，开始过度设计。总考虑到某天我会需要继承它。说不定还需要多重继承。等等。明明已经设计好class，却喜欢继续添加功能。永远用不到的功能。只因为我“未来可能需要在别的程序里用，可能需要继承它。”&lt;/p&gt;

&lt;p&gt;任何一个程序，我都为它设计对象，全局的程序对象又有子对象，子又有子，子子孙孙无穷尽也～&lt;/p&gt;

&lt;p&gt;成员变量一多，构造函数里成员变量初始化代码就变多。然后设计构造函数重载。写更多的用不到的构造函数。 
我没有意识到，我已经犯了过度设计的毛病了&lt;/p&gt;

&lt;p&gt;而c++另一个强大的功能“模板”我却一无所知。当我发现了c++还有模板功能的时候，我只是简单的尝试了一下，发现模板无法在VC下很好的被自动完成。又听说模板会导致代码膨胀，诸如此类。我只小用一下模板。就丢弃了模板。继续我过度设计的c++之路。&lt;/p&gt;

&lt;font color=&quot;red&quot;&gt; 某一天，我开始Linux之路。  &lt;/font&gt;

&lt;p&gt;当我开始写出第一个GTK程序的时候，发现了C之美。C可以写出和C++一样的对象代码。而且没有了 C++的许多陷阱。&lt;/p&gt;

&lt;p&gt;也正因为C，我开始摒弃了过度设计。所设计的API皆以现阶段够用为限。未来怎么样，未来再添加好了。没有了c++的包袱，再也不设计class了。都是简单的函数。函数完成一个“过程”。而不再是操作一个“对象”。让我把精力放到了“过程”本身，而不会再去设计“漂亮的对象”。&lt;/p&gt;

&lt;p&gt;之后阅读到了Linus对C++的一篇讽刺邮件。对比自身，多多感触。c++确实是个容易写出糟糕代码的语言。&lt;/p&gt;

&lt;p&gt;从此发誓不再用c++，只以C写代码。&lt;/p&gt;

&lt;p&gt;再一次拾起C++，是因为c++11的发布，有了一些新的语法糖。我虽然不再打算写c++代码，但是没打算不再读别人的c++代码。如果别人用c++11写了新的代码，我确看不懂，不是损失是什么。我决定看一看c++11。&lt;/p&gt;

&lt;p&gt;c++11最令我着迷的新特性就是lambda。还有新的for语法。当然，boost::bind 和 boost::function 进入了 std。成为的标准的一部分。我对 std::function 的印象只限于“更好的函数指针替代”。&lt;/p&gt;

&lt;p&gt;lambda 一用，发现非常不错。好多麻烦的 static 函数都可以被匿名的 lambda取代了。我喜欢这种让名称空间更干净的感觉。&lt;/p&gt;

&lt;p&gt;我开始纠结lambda了。动摇了。如果只用c++兼容C的那部分，然后lambda，就是带lambda的C嘛！这样自欺欺人的开始用C++。&lt;/p&gt;

&lt;p&gt;参与了朋友的一个小项目。他是个重度c++粉。但是因为他的关系，我开始真正的拿起了boost。他说，没有boost他就没办法写代码。&lt;/p&gt;

&lt;p&gt;这里有个小插曲，我在Linux下一直用eclipse编程，但是也因为boost把eclipse搞崩溃了，重拾了KDevelop。结果发现KDevelop已经不再是我第一次使用的时候（Kdevelop 3.？ 的样子，好多年前的尝试。）那样使用正则匹配的自动完成了，真正的和eclipse一样是通过语法解析获得的自动完成列表。这导致KDevelop的自动完成功能异常强大，和 eclipse一样智能。所不同的是，eclipse在解析boost的代码的时候会时不时的崩溃，而且eclipse花了很久很久的假死状态解析boost头文件。毕竟是java开发的，速度还是不行啊！但是KDevelop完成boost头文件的解析只需要几秒钟。第一次打开cpp文件的时候，KDevelop在后台花了及秒钟解析了boost头文件，然后打代码的时候boost的所有功能就全部都能提示出来了。和eclipse一样的自动完成，速度快了几百倍，我马上切换到了KDevelop，并卸载了eclipse。&lt;/p&gt;

&lt;p&gt;其实之前也尝试过boost，都被假死状态的eclipse弄怕了。所以关了eclipse的index功能没了boost的语法提升瞎写过。&lt;/p&gt;

&lt;p&gt;但是换到KDevelop后那畅快淋淋的感觉，打出 boost:: 后那完善的提示，我马上开始全身心的试用boost。&lt;/p&gt;

&lt;p&gt;就是这一试用，让我重回了c++的怀抱。重回c++后，我发现了c++正确的用法：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class只能作为基础，构建比int/long/char要高级点的对象。绝不能用来构造程序本身。像MFC那样搞个 CMyApp 绝对是对c++的侮辱。也就是说，程序主体仍是C语言式的。

可以用class构建string，构建bigint，用重载为bigint编写大数乘法... 这才是 class 和运算符重载的意义。绝对不能肆意滥用。

必要的时候编写模板代替宏。

模板更好的用法是使用boost这样现成的模板库。

设计回调函数，使用 std::function/boost::function。这样可以使用 std::bind/boost::bind 绑定任意多的参数给回调函数用，避免C里的void*user_data设计。

虚回调函数绝对是个傻逼设计。迫使用户使用继承并进行虚成员函数重载。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>善待笔记本，启用intel集显的节能模式</title>
   <link href="https://microcai.org/2011/11/16/intelgpurc6.html"/>
   <updated>2011-11-16T00:00:00+00:00</updated>
   <id>https://microcai.org/2011/11/16/intelgpurc6</id>
   <content type="html">&lt;p&gt;在 XP 下用的时候，用计量插座，空闲功耗大概在 13W ~ 14W 之间。&lt;/p&gt;

&lt;p&gt;在 Gentoo 下用的时候，空闲功耗也在 20W 之上。&lt;/p&gt;

&lt;p&gt;很头痛。&lt;/p&gt;

&lt;p&gt;CPU 的节能已经打开，一直在 800M 最低频率运行呢！&lt;/p&gt;

&lt;p&gt;恩，应该是 GPU 费电啊～～～ 有什么办法能让 intel 的 GPU 工作在节能模式呢？&lt;/p&gt;

&lt;p&gt;答案是添加隐含的内核引导参数&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;i915.lvds_downclock=1 i915.i915_enable_fbc=1 i915.i915_enable_rc6=1 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;神奇的参数。&lt;/p&gt;

&lt;p&gt;现在 Gentoo 空闲下来的时候，笔记本也是 13W ~ 14W 的功耗了。哦也 ～～ CPU 也没那么热了。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>i7 拯救了 Gentoo</title>
   <link href="https://microcai.org/2011/11/11/i7savedgentoo.html"/>
   <updated>2011-11-11T00:00:00+00:00</updated>
   <id>https://microcai.org/2011/11/11/i7savedgentoo</id>
   <content type="html">&lt;p&gt;好多年以前，我开始了 Linux 之旅。&lt;/p&gt;

&lt;p&gt;我频繁的切换发行版，从 RedFlag 5 到 RedFlag 6 再到 ubuntu 8.10 马上又切到 Fedora 8 然后是 9 10 , 期间又重回 RedFlag 7 过，间或也使用过 debian 和 puppy。&lt;/p&gt;

&lt;p&gt;如此频繁的切换发行版，只不过是我觉得它们都没有满足我的要求。我不停的切换，企图寻找到一个可以一劳永逸的满足我的需求的系统。 我不停的搜索着，尝试着。&lt;/p&gt;

&lt;p&gt;直到有一天，我发现了 Gentoo 。&lt;/p&gt;

&lt;p&gt;那个时候我已经厌倦了 Fedora . 不停的重装，只是为了升级。尝试过 upgrade , 只不过比不得重装快活。也有很多错误，不如重装干脆。&lt;/p&gt;

&lt;p&gt;我实在是厌倦了每6个月重装一下系统，只是为了升级。我也厌倦了每次重装好系统之后都要开始好几天的清理工作，把不需要的东西删除掉，再把需要的软件又重装一次。&lt;/p&gt;

&lt;p&gt;直到我发现了 Gentoo .&lt;/p&gt;

&lt;p&gt;经过漫长的编译后，我有了一个可以启动的 Gentoo 系统。但是没有 X , 没有gnome，没有 firefox . 啥都没有。&lt;/p&gt;

&lt;p&gt;编译 firefox 前我需要 DE ，我选择 gnome, 编译 gnome 前我需要 X . 为了编译 firefox , 5 百多个软件需要被编译。&lt;/p&gt;

&lt;p&gt;又是一个 overnight 编译。&lt;/p&gt;

&lt;p&gt;第3天早上，打开显示器查看的时候终于编译好了。期间因为编译错误中断过几次，手工排除了一些错误，继续编译。不过总的来说，一个可以上网的环境就编译好了。&lt;/p&gt;

&lt;p&gt;接下来是更多的编译。因为我需要的一切都没有安装。编译 编译 编译 编译 编译 从此成了吃饭后的重要工作。&lt;/p&gt;

&lt;p&gt;过了一个多星期，我终于有了和从前“一样”的环境了。却少了很多垃圾。我不需要的统统用 USE=”-XXX” 的方式清理掉了。&lt;/p&gt;

&lt;p&gt;当 irc 上的朋友发现我失踪了几天再回来的时候，我已经满口的 Gentooooooooooooooooooooo 了。我不停的向大家赞叹 Gentoo 有多么优秀。只有等到数个月后，我才进入了平静期。&lt;/p&gt;

&lt;p&gt;我享受了近2年的 Gentoo 。&lt;/p&gt;

&lt;p&gt;我厌倦了。&lt;/p&gt;

&lt;p&gt;无休止的编译炸干了我最后一点耐性。Firefox 也进入了快速发布时代，而每次firefox 升级，带来的都是数个小时的编译。期间我的电脑由于编译而开始响应缓慢，数个小时不能流畅使用的状态！&lt;/p&gt;

&lt;p&gt;而每次 firefox 升级，都意味着 thunderbird 也要升级。 这些都是需要编译数个小时的大块头。&lt;/p&gt;

&lt;p&gt;每天都要升级，否则积累起来可能就是要一天时间都编译不完的更新。&lt;/p&gt;

&lt;p&gt;我差一点就放弃 Gentoo 了！直到我下了一次狠心，入手了一个 Sandy Bridge 的 Xeon E3-1230 . 其实就是 i7 2600 阉割了GPU. 因为我有显卡，不想再为核芯显卡掏钱。而且我只要 NVIDIA 的显卡。别的统统不要。&lt;/p&gt;

&lt;p&gt;组装好 CPU 后，我被 i7 的性能惊呆了！&lt;/p&gt;

&lt;p&gt;编译 Ooo 居然也不过几十分钟的事情，以前的电脑可是要编译一整个下午的。这次居然只有几十分钟。&lt;/p&gt;

&lt;p&gt;而在编译的过程中，我的电脑一直都处于可用状态，没有因为大量的后台编译而损失性能。&lt;/p&gt;

&lt;p&gt;由于换了 cpu 和主板需要重新编译内核，这次内核的编译时间也让我惊呆了，89s ! 89s! 以前的电脑需要一个多小时啊！89s 只相当于我没有 make clean 而是改动了几个选项重新 make 的时间。&lt;/p&gt;

&lt;p&gt;这次 89s 是全部重新编译的时间。如果我修改了个东西，再编译一下，那只需要几秒到十几秒就好了。&lt;/p&gt;

&lt;p&gt;开了 pgo （需要编译2次） 的 firefox 也只有几分钟就编译好了。&lt;/p&gt;

&lt;p&gt;从此编译再也不是等待，而是一个享受的过程，看到一个巨大的软件，却只需要不到一分钟就编译好了，非常大的软件也只要几分钟就可以了，这不是一种享受是什么？&lt;/p&gt;

&lt;p&gt;i7 , 让 Gentoo 这种全编译的操作都变得非常快速。 i7 你是一个神话。&lt;/p&gt;

&lt;p&gt;感谢 i7 ， 你拯救了 Gentoo 。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>别了 AMD . 来了 Intel.</title>
   <link href="https://microcai.org/2011/10/25/88amd.html"/>
   <updated>2011-10-25T00:00:00+00:00</updated>
   <id>https://microcai.org/2011/10/25/88amd</id>
   <content type="html">&lt;p&gt;刚开始接触到电脑的时候，正是 K8 无限风光， P4 高频低能的时候。那个时候起心里就有了 amd 的 U 便宜又好，低频高能的种子了。&lt;/p&gt;

&lt;p&gt;到自己 DIY 的时候，就买了 AMD 的 U . NVIDIA 的芯片组。 那个时候扣肉还没有出， AMD 的 K8 余光依旧，K10 风头正好。AMD 率先推出双核，intel 还是伪双核, etc .&lt;/p&gt;

&lt;p&gt;当时买的是入门双核 4200+ . 配合 MCP61S 的主板。外加 DDR2-800 1G*2 内存。&lt;/p&gt;

&lt;p&gt;因为配置起来就是为了玩 Linux 的，所以特地买的 NVIDIA 显卡。 事实证明，所有的显卡问题统统都没有让我碰到。先见之明啊&lt;/p&gt;

&lt;p&gt;这个配置就伴随着我3年，直到去年年底，一次刷主板不小心给刷成砖头了。悲剧。&lt;/p&gt;

&lt;p&gt;买个新主板去咯～&lt;/p&gt;

&lt;p&gt;结果，这一买新主板就几乎是大换血。那个时候 AMD 已经升级到了 AM3 接口的 U 了。而内存也已经主流 DDR3 了。&lt;/p&gt;

&lt;p&gt;这次买主板，自然就干脆全换了。 U 升级到了 AMD x2 215 @2.7G , 内存加到 4G . 由于偏爱小机箱，MATX 主板可挑选余地依旧不多。还是 MCP61P 主板。 （MCP61 的生命力啊！）&lt;/p&gt;

&lt;p&gt;时间已经是 2010 年底了！！ 2011 马上就来了！ 如果我知道那个时候 intel 已经凭借扣肉爱妻秒杀了 AMD ，我还会换 AM3 么？ 打死也不会。而且再过几个月，彪悍的 Sandy Bridge 就要出生了。&lt;/p&gt;

&lt;p&gt;可惜，我生活在了 AMD 编制成的幻觉里。不知道外面的世界其实早已经是 4 核8线程， 6核 12 线程的超高端平台也出来了。我还在守着 AMD 的双核，我还想着，4核时代什么时候来呢？ 3年了！ 3年的时间里，我双核原地不动，只不过 CPU 主频从 2.1G 升级到了 2.7G . 内存换到了 ddr3 而已。&lt;/p&gt;

&lt;p&gt;悲剧的人生不解释。&lt;/p&gt;

&lt;p&gt;等到自己工作了，接触到了更多的信息，我才恍然大悟。&lt;/p&gt;

&lt;p&gt;原来现在最强的是 i7 . 6 核12线程。 super PI 10s 以内。彪悍的性能不解释。一般桌面高端使用的是 i7 2600k, 4核8线程，彪悍的性能不解释。&lt;/p&gt;

&lt;p&gt;连 intel 入门级别的 i3 也是双核4线程。&lt;/p&gt;

&lt;p&gt;原来酷睿又叫扣肉，就在我 DIY 了第一台 AMD 电脑不久后发布，性能震惊了全球。在我 的电脑第二次升级的后不久 intel 又发布了扣肉2，性能再次震撼全球。&lt;/p&gt;

&lt;p&gt;AMD 早已不是对手。&lt;/p&gt;

&lt;p&gt;其实我所谓的 2010 年末买的 AMD 双核，也就 intel 08 年发布的一代扣肉 i3 的性能而已。&lt;/p&gt;

&lt;p&gt;我被 AMD 坑爹了。 悲剧的人生不需要解释。&lt;/p&gt;

&lt;p&gt;我愤怒了，难怪我觉得我的电脑性能一直不好。不好到我要通过 Gentoo 来折磨自己获得那可怜的性能提升。就算我后来升级了电脑，性能也一样原地踏步。&lt;/p&gt;

&lt;p&gt;AMD ， 你骗的我好苦啊！ 自从用了AMD , 我从此和摩尔定律拜拜了。&lt;/p&gt;

&lt;p&gt;10月，就在推土机要发布的前一周，我果断抛弃了 AMD ！&lt;/p&gt;

&lt;p&gt;可悲的是，我的那个 215 居然涨价了，我卖二手只亏了 30 , 不能不说是个奇迹。AMD 创造的奇迹。&lt;/p&gt;

&lt;p&gt;i7 2600 显然进入了我的法眼。因为其他配件都很齐全。所以我之需要买 CPU 和主板，这样 2k 的预算显然可以让我买高端而不再迁就于低端。&lt;/p&gt;

&lt;p&gt;但是，频道里高人指点，我买了 Xeon E3-1230 . i5 的价格 i7 的性能。&lt;/p&gt;

&lt;p&gt;高端的 U 自然要高端的主板配合，我买了华硕的 P8P67 主板，虎悍的做工不解释。&lt;/p&gt;

&lt;p&gt;这样，算去卖掉的主板和CPU , 升级到 i7 平台花费不到 2.2k&lt;/p&gt;

&lt;p&gt;从此Gentoo的编译给力起来啦！ 编译不再是折磨！ make -j8 开心的滚动， 89s 结束。要知道以前可是没个半小时编译不停啊。不仅如此，编译的时候桌面那是响应缓慢。&lt;/p&gt;

&lt;p&gt;现在好了，编译的时候桌面依旧流畅。轻松操作。&lt;/p&gt;

&lt;p&gt;原来这才叫 PC 。&lt;/p&gt;

&lt;p&gt;再次 BS 一下 AMD , 那不给力的推土机直接让 intel CPU 涨价，我买 Xeon 的时候已经 1410 了，几个月前还只需要 13xx 就能买到的。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>给老婆的 F41M (注意后面有个M ... M M M M (都是回音)) 升级到 T9300 CPU 和 4G 内存</title>
   <link href="https://microcai.org/2011/10/24/kiki.html"/>
   <updated>2011-10-24T00:00:00+00:00</updated>
   <id>https://microcai.org/2011/10/24/kiki</id>
   <content type="html">&lt;p&gt;上回说道，老婆大人换到 ubuntu 后突然觉得电脑好慢好慢 。。。 。。。 慢到蜗牛都吃到葡萄了这 Unity 界面还没显示出来。&lt;/p&gt;

&lt;p&gt;嗯嗯，言归正传。&lt;/p&gt;

&lt;p&gt;老婆大人的电脑是 08 年买的，好早（老）保护的比较好，外观还很新。不过，鲜丽的外表掩饰不了那落后的奔腾的芯。&lt;/p&gt;

&lt;p&gt;废话不说，晒一下配置&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;CPU: T2330 
内存: 1G 
硬盘: 160G@5400RPM@50-30MB/s 
芯片组：GL960 (没错，就是那个坑爹的960) 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这台老本的升级是在今年3月的时候开始的。 
那个时候，我给自己的EeePC买了一个500G的希捷最快笔记本硬盘。淘汰下来的250G希捷硬盘自然就交给老婆的电脑进行消化咯。&lt;/p&gt;

&lt;p&gt;电脑速度给力提升。windows 评分里硬盘速度已经是当中的最高分了。&lt;/p&gt;

&lt;p&gt;之后我给 EeePC 换到 2G 内存，剩下的 1G 内存就给老婆用，组成了双通道。内存性能又提升了1分。&lt;/p&gt;

&lt;p&gt;然后，然后就是上一篇博文里说的情况了，电脑太不给力了！&lt;/p&gt;

&lt;p&gt;问得频道里老同志后，得知我应该去买 T7500 升级。&lt;/p&gt;

&lt;p&gt;然后打算买T7500的时候，从论坛里搜得有人把 F41 升级到了 T9300 … … 楼下回复得知 GM965主板最高支持 T9500 CPU. （这个时候还不知道 F41M是阉割过的。）&lt;/p&gt;

&lt;p&gt;哈哈！升级！ 淘宝上找了个本地卖家买 T9500（他有 ES 版，特别便宜啊） . 结果没货，但是有 T9300 . 恩，也就差一点，也行。然后他就上门服务来了。&lt;/p&gt;

&lt;p&gt;结果很悲剧。上了 T9300 , 能开机，进入系统很正常。也识别出了 CPU 的型号。可惜，正常了一分钟后，系统就出问题了。USB口失灵，无线没信号。屏幕莫名其妙的闪烁（Aero 消失又回来）。&lt;/p&gt;

&lt;p&gt;什么情况？？？？？？&lt;/p&gt;

&lt;p&gt;过热？ 重新涂抹硅脂试试。&lt;/p&gt;

&lt;p&gt;一样。&lt;/p&gt;

&lt;p&gt;郁闷。&lt;/p&gt;

&lt;p&gt;最后下了个 CPU-Z。&lt;/p&gt;

&lt;p&gt;大跌眼镜啊！居然是 GL960 不是 GM965 ！ 联想你个阉割狂！&lt;/p&gt;

&lt;p&gt;此次升级以失败告终。&lt;/p&gt;

&lt;p&gt;但是我不甘心。继续 google . 终于有人在论坛上发帖子，说自己把 F41M 的主板换成了 F41G，然后就顺利的上了T9300 的 U 。&lt;/p&gt;

&lt;p&gt;好！ 买F41G 主板去～&lt;/p&gt;

&lt;p&gt;经过 N 次 taobao 后，终于选定了一家有 F41G 且愿意和我的 F41M 换的卖家。花了 200 大洋用老婆大人的 F41M 主板换到了 F41G 主板。&lt;/p&gt;

&lt;p&gt;换主板折腾了1个多小时。费力也～ 这东西，组装费都不止 100 啊！&lt;/p&gt;

&lt;p&gt;第一次开机比较期待，看到 BIOS LOGO 后，兴奋的大叫，WOW。芯片组也GM965 的。换主板的时候看到北桥上的字了。&lt;/p&gt;

&lt;p&gt;然后再次联系那个CPU卖家。&lt;/p&gt;

&lt;p&gt;最终换上了 T9300.&lt;/p&gt;

&lt;p&gt;顺便把内存再次升级，换成了 DDR-800 2G*2&lt;/p&gt;

&lt;p&gt;这下性能彪悍了。跑 Super PI 竟然进入了 18s 大关。果然给力！&lt;/p&gt;

&lt;p&gt;这下就打起了那个老早就不工作的光驱的主意了。&lt;/p&gt;

&lt;p&gt;嘻嘻，这次换成吸入式的 ^_^。&lt;/p&gt;

&lt;p&gt;由于这些工作都是上个月的事情了，一直忙，也没记录，导致无法和大家图片分享咯。&lt;/p&gt;

&lt;p&gt;sorry.&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>原来 Linux 已经慢到这种地步了啊</title>
   <link href="https://microcai.org/2011/10/20/linuxslow.html"/>
   <updated>2011-10-20T00:00:00+00:00</updated>
   <id>https://microcai.org/2011/10/20/linuxslow</id>
   <content type="html">&lt;p&gt;因为特别喜欢 Gentoo , 所以自从2年前的一次偶然，我用上了 Gentoo 后就在也没有换过系统。&lt;/p&gt;

&lt;p&gt;Gentoo 的好处非常多，多到数不清。&lt;/p&gt;

&lt;p&gt;连老婆大人的系统我都给折腾成了 Gentoo.&lt;/p&gt;

&lt;p&gt;不过，由于疏于维护，老婆大人的 Gentoo 出了些许毛病，主要是 Gnome 3 太不稳定，太不给力，太容易崩溃。&lt;/p&gt;

&lt;p&gt;老婆坚持下，换了 Ubuntu&lt;/p&gt;

&lt;p&gt;一直以为老婆大人的破硬件不给力，所以进 Gentoo 都得花个30+s 。好慢。&lt;/p&gt;

&lt;p&gt;用了 Ubuntu 我才知道，原来电脑可以这么慢！原来 Linux 已经慢到这种地步了！ 居然要 1min多才能进系统。然后再卡个十几秒才能用。&lt;/p&gt;

&lt;p&gt;！！！！！！！&lt;/p&gt;

&lt;p&gt;不是 Gentoo 太慢，而是我对系统性能要求太高，而是硬件太不给力！&lt;/p&gt;

&lt;p&gt;换硬件+切回Gentoo才是给力的选择。&lt;/p&gt;

&lt;p&gt;于是我开始了艰难的折腾之路，把笔记本从 CPU 到主板到内存到光驱全换了个遍。请留意下回分解。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>从 RedFlag 到 Gentoo, 多少年，一瞬间</title>
   <link href="https://microcai.org/2011/07/20/fromredflagtogentoo.html"/>
   <updated>2011-07-20T00:00:00+00:00</updated>
   <id>https://microcai.org/2011/07/20/fromredflagtogentoo</id>
   <content type="html">&lt;p&gt;很久很久以前，在我还认为米国人生活在水生火热之中的时候，我认识了 Linux , 于是在买了电脑前就去亚马逊买了 Fedora 8 的DVD …&lt;/p&gt;

&lt;p&gt;等我的电脑 DIY 好了，我立刻就安装了 Fedora 8&lt;/p&gt;

&lt;p&gt;但是… 似乎连个 mp3 都播放不了。 就在我寻找如何解决的时候，我发现了 RedFlag 6。&lt;/p&gt;

&lt;p&gt;于是，我试试看的态度安装上了 RedFlag, 呵呵，果然 mp3 啊，rmvb 什么的，都不是问题了。非常喜欢。而且自带的 KDE3 界面也非常 windows like .. 用的多熟悉啊！&lt;/p&gt;

&lt;p&gt;就这样喜欢上了 Linux 桌面。&lt;/p&gt;

&lt;p&gt;RedFlag 是那种大而全的发行版，把什么软件都往里面装。2个 CD 安装好后就和安装了番茄花园一样齐全了。 也不缺什么了。当然，如果什么软件没有带的话，还是要自己 做 ./configure 族的。&lt;/p&gt;

&lt;p&gt;这就和盗版 Windows 很像了。基本系统很大，常用软件都有，但是，要别的 app , 自己装去。&lt;/p&gt;

&lt;p&gt;不过 ，自己安装软件的时候比较少，所以我开始了半年的 RedFlag 征途&lt;/p&gt;

&lt;p&gt;后来，买了个摄像头。&lt;/p&gt;

&lt;p&gt;居然不知道用什么软件能使用摄像头 … 还好没几天 Fedora 10 发布了。恩，里面有个叫 cheese 的软件可以用摄像头。&lt;/p&gt;

&lt;p&gt;RedFlag 里居然没有！&lt;/p&gt;

&lt;p&gt;于是我又开始做 ./configure &amp;amp;&amp;amp; make &amp;amp;&amp;amp; make install 族了。&lt;/p&gt;

&lt;p&gt;等等，这依赖 ….&lt;/p&gt;

&lt;p&gt;RedFlag 所有的库版本都不够，导致我要安装cheese依赖的库先 ….&lt;/p&gt;

&lt;p&gt;最后的结果是，一天时间过去了，我升级了一百多个库，还有一大堆依赖没安装上 …&lt;/p&gt;

&lt;p&gt;我愤怒了！&lt;/p&gt;

&lt;p&gt;这等于我重新编译了一个 Fedora 10 出来啊！&lt;/p&gt;

&lt;p&gt;于是，直接上 Fedora ！&lt;/p&gt;

&lt;p&gt;不就是解码器的问题么？ 我找教程看看，解决一下好了！&lt;/p&gt;

&lt;p&gt;于是我开始了近一年的 Fedora 之旅。&lt;/p&gt;

&lt;p&gt;这一年时间里，我的见识得到的飞速增长。原来米国人不是生活在水深火热之中 …..&lt;/p&gt;

&lt;p&gt;我还通过合作开发 ruijieclient 结识了 gonghang 和 gsalex .. 对，就是提供本 blog 域名的 gsalex.&lt;/p&gt;

&lt;p&gt;然后突然有一天， alex 用上了 Gentoo ….&lt;/p&gt;

&lt;p&gt;我很震惊！我不能输！&lt;/p&gt;

&lt;p&gt;于是我开始安装 Gentoo ，那个传说中最难安装的发行版。&lt;/p&gt;

&lt;p&gt;其实很简单，只是大家把他想难了，呵呵。 凡事试过才知道啊!&lt;/p&gt;

&lt;p&gt;于是使用 Gentoo 至今，已经快2年啦。&lt;/p&gt;

&lt;p&gt;这2年的时间里，我从 Gentoo 小白到 Gentoo 非小白，到能为Gentoo编写ebuild, 成了Gentoo-china 的一分子，为 Gentoo 的发展出了一份微薄之力。&lt;/p&gt;

&lt;p&gt;是什么让 Gentoo 自从赖上我的电脑就再也无法离开？&lt;/p&gt;

&lt;p&gt;是什么让 Gentoo 自从赖上我的电脑就再也无法离开？&lt;/p&gt;

&lt;p&gt;我不知道。&lt;/p&gt;

&lt;p&gt;很多时候我开始寻找答案。&lt;/p&gt;

&lt;p&gt;但是答案似乎每个人都不一样。&lt;/p&gt;

&lt;p&gt;那么我到底是为的什么呢？&lt;/p&gt;

&lt;p&gt;因为 Gentoo 真的太好用了！&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>定期发布 Release 不适合现代 Linux Distro</title>
   <link href="https://microcai.org/2011/07/02/rollingreleaseisbetter.html"/>
   <updated>2011-07-02T00:00:00+00:00</updated>
   <id>https://microcai.org/2011/07/02/rollingreleaseisbetter</id>
   <content type="html">&lt;p&gt;我想说，定期发布 Release 这种模式已经过时了！&lt;/p&gt;

&lt;p&gt;想想看 ubuntu 的 LTS 版本是如何在生命周期结束前就过时的吧！&lt;/p&gt;

&lt;p&gt;上一个 ubuntu TLS 版本，居然不包含 gtk3 , 如果要实用一点，基本上要添加一 大堆的 PPA. 
而使用 PPA 基本上就和 TLS 的理念背道而驰了。&lt;/p&gt;

&lt;p&gt;dabian 这样的发行版，基本上的发布前就过时了。&lt;/p&gt;

&lt;p&gt;! 没错。 glibc, core-utils 这样的软件需要稳定！&lt;/p&gt;

&lt;p&gt;那么用旧一点的版本确实是比较稳定.&lt;/p&gt;

&lt;p&gt;问题是，gnome KDE 这样的软件，反而是新版本比较稳定。&lt;/p&gt;

&lt;p&gt;这样 TLS 这种追求旧 版本的发行版，其实除了基础包，别的包都是在使用的不稳定版本，反而美其名 曰：stable.&lt;/p&gt;

&lt;p&gt;最明显的例子， KDE 4.6 发布的时候，是 KDE 最稳定的时候，这个时候 debian 里刚刚有 KDE4 …. 最不稳定的 KDE 4 …. 恩，还美其名曰 : stable。 
  而 experimental 仓库里的 KDE 4.6, 恩， experimental, 其实才是最稳定的。 
  对于多数软件来说，大版本号最第二大，小版本号最大的软件才是最稳定的。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;比如说， Linux, 2.6.39 显然没有 2.6.39.2 来的稳定。虽然后者是后来出现的。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;dabian 的哲学来说，就是不稳定的。 定期发布 Release , 意味着必须使用稳定版本，否则就等于发布 unstable ， 这是不行的。
但是稳定版本，通常是比较新的版本，这样定期发布 Release , 意味着必须使用过时的版本。&lt;/p&gt;

&lt;p&gt;恩，其实过时的版本包含了巨大的危险性。因为已经不再被 upstream 支持了。&lt;/p&gt;

&lt;h4 id=&quot;你说--upstream-支持的版本稳定还是不支持的版本稳定呢-只有使用-rolling-update-才能解决这个问题&quot;&gt;你说 ， upstream 支持的版本稳定还是不支持的版本稳定呢? 只有使用 rolling update 才能解决这个问题。&lt;/h4&gt;

&lt;p&gt;你看， KDE 4.6 一发布，没过 几天， Gentoo 里就标记为 stable , 就是不用开 ~ARCH 就能安装了。
这样的软件，自然新版本稳定。 再比对 gcc 这样的软件。 4.6.x 的 gcc 一直被 hand mask 4.5.x 的 gcc 被标记为 ~ARCH 4.4 的 stable ，不是 4.4 , 是 4.4 系列里的最后一个版本，谢谢。 而 GNOME , KDE 这样的软件，stable 的版本就是当前的最新 Release. unsable 的版本是当前最新的 -RC 版本。 
不得不说， Gentoo 在软件稳定性上比任何发行版都要做的好。 因为Gentoo有独门秘籍，rolling update. 
当然， ARCH 这样的为何 rolling update 也不稳定呢？
因为 ARCH 这样的发行版，任何软件包只能有一个版本在仓库里。
不像 Gentoo , Gentoo 可以放 N 多版本进去，任用户随意选择。&lt;/p&gt;

&lt;p&gt;rolling update + selectable version, 这是 Gentoo 稳定性压倒一切发行版的 同时软件又够新的秘密。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>为什么你应该让你的项目使用 autotools 而不是别的编译系统</title>
   <link href="https://microcai.org/2011/05/26/whyautotools.html"/>
   <updated>2011-05-26T00:00:00+00:00</updated>
   <id>https://microcai.org/2011/05/26/whyautotools</id>
   <content type="html">&lt;p&gt;编写软件离不开编译。&lt;/p&gt;

&lt;p&gt;简单的编译，无外乎直接调用 gcc 去编译。&lt;/p&gt;

&lt;p&gt;简单又不简单，当源文件不止一个的时候，麻烦的事情就随之而来。&lt;/p&gt;

&lt;p&gt;当然，这个时候你可以选择 Makefile&lt;/p&gt;

&lt;p&gt;但是，如果要求你的 Makefile 支持很多标准化操作，比如&lt;/p&gt;

&lt;p&gt;make install DESTDIR=….&lt;/p&gt;

&lt;p&gt;make dist-bzip2&lt;/p&gt;

&lt;p&gt;你怎么打算呢？&lt;/p&gt;

&lt;p&gt;自然，你需要一个自动创建 Makefile 的工具。&lt;/p&gt;

&lt;p&gt;你有很多选择:&lt;/p&gt;

&lt;p&gt;imake&lt;/p&gt;

&lt;p&gt;qmake&lt;/p&gt;

&lt;p&gt;scons&lt;/p&gt;

&lt;p&gt;cmake&lt;/p&gt;

&lt;p&gt;然而，他们都虽然简单，却不是最终用户友好的。&lt;/p&gt;

&lt;p&gt;因为，最终的用户最习惯的软件编译方式就是三部曲&lt;/p&gt;

&lt;p&gt;./configure –options&lt;/p&gt;

&lt;p&gt;make&lt;/p&gt;

&lt;p&gt;sudo make install&lt;/p&gt;

&lt;p&gt;而开发人员也可以直接 make dist-bzip2 生成发布用的 tarball.&lt;/p&gt;

&lt;p&gt;当然，如果有嵌入式的用户，他们自然会喜欢&lt;/p&gt;

&lt;p&gt;./configure –host=arch-machine-os&lt;/p&gt;

&lt;p&gt;进行交叉编译。&lt;/p&gt;

&lt;p&gt;这是他们最最最熟悉的方式。&lt;/p&gt;

&lt;p&gt;如果你想让你的用户能马上上手知道如何编译你的软件，那么，请使用 autotools&lt;/p&gt;

&lt;p&gt;这是 autotools 带给你的最大的好处。别忘记，你的开发者，一开始也是用户，如果他们第一次就编译失败，他们很有可能因此走掉。&lt;/p&gt;

&lt;p&gt;autotools 就是一把瑞士军刀，一开始就使用autotools，能让你的项目不会到最后被编译系统拖后腿。&lt;/p&gt;

&lt;p&gt;记住， ebuild 文件的复杂度代表了一个软件的编译系统的友好程度。&lt;/p&gt;

&lt;p&gt;而简单的 ebuild 文件只可能出现于使用 autotools 的软件中。&lt;/p&gt;

&lt;p&gt;Autotools can be quite tricky for newcomers, but when you start using them on a daily basis you find it’s a lot easier than having to deal with manual makefiles or other strange build tools such as imake or qmake, or even worse, special autotools-like build scripts that try to recognize the system they are building on. Autotools makes it simple to support new OSes and new hardware platforms, and saves maintainers and porters from having to learn how to custom-build a system to fix compilation. By carefully writing a script, developers can support new platforms without any changes at all.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>BIOS 是如何访问在内存还没有的时候检测并初始化内存的</title>
   <link href="https://microcai.org/2011/05/08/howbiosdetectmemory.html"/>
   <updated>2011-05-08T00:00:00+00:00</updated>
   <id>https://microcai.org/2011/05/08/howbiosdetectmemory</id>
   <content type="html">&lt;p&gt;首先必须明确的一点是， BIOS 运行初期，CPU 其实是不能访问内存的。&lt;/p&gt;

&lt;p&gt;BIOS 所在的 FLASH 是那种可以被 CPU 直接寻址的 FLASH 芯片。被都固定在 
0x4FFFF (记不清具体地址了) 地址上了。类似 ARM 使用的 NOR FLASH。 uboot 
就在 NOR FLASH上。&lt;/p&gt;

&lt;p&gt;然后，BIOS 初始化代码开始通过寄存器和北桥芯片沟通。 
因为 BIOS 就是版卡厂商制作的，这不成问题。使用固化的 IO 地址就可以了。&lt;/p&gt;

&lt;p&gt;成功和北桥联系上后， BIOS 继续前进，利用北桥提供的寄存器操控SMBUS, SMBUS 
是一种 I2C 总线，每个 SMBUS 设备都有一个固定的地址。SDRAM 的 EEPROM 就可 
以通过 SMBUS 访问！ 而 SDRAM 通常在 0x50 ~ 0x53 这4个地址上，对于主板上 
的4个内存插槽。不过，主板厂商也可以改变这些地址。反正 BIOS 是自己出的， 
可以固化这些地址，无所谓。&lt;/p&gt;

&lt;p&gt;读取 SDRAM eeprom 里超过 17 个参数后。 BIOS 就可以使用这些参数设置好北桥 
的寄存器了。而在这之前， BIOS 严重依赖 CPU 的寄存器做这些操作，不能使用 
任何内存！ 北桥用来控制内存的寄存器地址，又是一个每个版卡都不一样的地方 
了。无所谓， BIOS 自己出，爱怎么用就怎么用。&lt;/p&gt;

&lt;p&gt;搞定内存之后， BIOS 终于可以使用内存啦！ BIOS 将内存的前 4k 作为临时 
stack 开始进入了第二步初始化，这个使用有了内存，就可以使用函数了。 
前面，因为无法使用内存，无法进行复杂的运算，要小心编写汇编指令，全部使用 
寄存器进行操作，所以没法进行复杂的运算。现在有 RAM 可以用了，自然就有内 
存了。&lt;/p&gt;

&lt;p&gt;BIOS 通过检查可用的内存值，将自己使用的临时 stack 移步到 &amp;gt; 640K 的地 
方。 开始继续下面的工作。&lt;/p&gt;

&lt;p&gt;下面， BIOS 该开始检测 CPU 了。主要的工作是比较 CPU 的微代码版本是否和 
BIOS 里带的对应CPU的代码一致。否则更新 CPU 的微代码。所以刷新 BIOS 支持 
新 CPU 的道理就在这里！ ;)&lt;/p&gt;

&lt;p&gt;再然后？那要看你是不是打开了 BIOS shadow 就会选择是不是做这一步了。 
注意，这个时候 BIOS 是在一片能被 CPU 直接寻址的 FLASH 里运行的。但是 
FLASH毕竟比 SDRAM 慢. 
所以， BIOS 会通过北桥重新映射 BIOS FLASH 的地址到内存空间的顶端，原有的 
映射没有断开！这个时候由于 A20 gate 没有打开，高 12 位被忽略，导致 CPU 
寻址仍然在 640k ~ 1M，也就是 BIOS 现在在的地址，所以 BIOS 继续运行。 
这个时候 BIOS 切换到 32 位寻址，注意，是 32位寻址，但是还是 16 位 模式。 
想知道详细情况的，可以自己网上 google . 因为先前已经映射好了 BIOS 到高位 
内存，所以 BIOS 继续运行。 然后 BIOS 将原先自己所在的 6040k~1M 地址区间 
通过操作北桥，断开映射！是的，没错，断开了！ 这个时候，这个区间其实就是 
RAM 而不再是 BIOS ROM 了，这个很重要！接着打开 A20。 
接下来就是很平常的 memcpy 了，将自己从高位内存拷贝回 640k ~1M 区间的地 
位内存。 然后关闭 A20. 关闭 A20 后， CPU 寻址重新回到低位内存。 BIOS 完 
成了将自己拷贝到同一个地址的工作. 但是 BIOS 将运行在 SDRAM 中，而不是 
FLASH 里。&lt;/p&gt;

&lt;p&gt;然后干嘛呢？ 自然是继续检测基本硬件啦。基本硬件一般都已经固定使用某个 IO 
端口了。 BIOS 就是发送一个无害的请求看是否有返回就可以知道硬件在不在。&lt;/p&gt;

&lt;p&gt;对于显卡和网卡这类设备， BIOS 并不包括这些设备的初始化代码， BIOS 通过调 
用这些设备的 BIOS 来实现。&lt;/p&gt;

&lt;p&gt;对于任何找到的 PCI 设备，BIOS 都是直接调用对应 BIOS 设备的 BIOS 来初始化 
它的 。 这也是硬盘还原卡截获 init 13 中断的道理。&lt;/p&gt;

&lt;p&gt;而这些设备的 BIOS，自然初始化工作就是配置设备的寄存器地址了。&lt;/p&gt;

&lt;p&gt;BIOS 搞定的 PCI 设备，会形成一个表格，叫 PCI CONFIG ，保存到 PCI 控制器 
里 。可以通过 IO 端口 0xCFC-0xCFF 访问。&lt;/p&gt;

&lt;p&gt;对于集成设备，这些设备的 BIOS 其实都被包含在 system BIOS 里，但是逻辑上 
并不是一个整体。如果用一些 BIOS 工具，还是可以分离各个设备的 BIOS 的。&lt;/p&gt;

&lt;p&gt;没用 BIOS 将设备配置好，并写入 PCI config ， Linux 内核就会当这个设备不 
存在 … 囧。&lt;/p&gt;

&lt;p&gt;还有，各个 PCI 设备除了几个基本寄存器，别的都是通过 MMIO 映射到内存地址 
里进行的，而不是使用 IO 端口地址。BIOS 要负责分配好，使他们不会重叠。&lt;/p&gt;

&lt;p&gt;PCI 设备的地址从 0xFFFFFFFF- sizeof (BIOS) 开始向下分配，SDRAM 的地址从 
0x00000000 开始向上分配。这些都是通过操作北桥的寄存器实现的。&lt;/p&gt;

&lt;p&gt;自然，他们会在中间形成一个洞。不过，那是在你 SDRAM 内存比较少的情况下 …..&lt;/p&gt;

&lt;p&gt;如果你的 SDRAM 内存比较多，恭喜你，你又遇到问题了，你的内存将被吞掉一部 
分 …. 被 PCI 设备覆盖掉了一部分高位内存… 对 CPU 而言，整个内存地址都 
充满了设备咯，没有空洞。&lt;/p&gt;

&lt;p&gt;其实 Linux 认为的 640k~1MB 空洞也是不存在的。如果后续不使用 BIOS ， 可 
以放心的当 SDRAM 来用，因为就是 SDRAM . 自然，如果你没有开启 BIOS shadow 
就另当别论了。&lt;/p&gt;

&lt;p&gt;我之所以接触 Linux ， 也是我本来计划自己写系统，我很讨厌 640K~1M 的内存 
空洞，想知道怎么解决，就去看别的 OS 是怎么实现的，结果就发现了 Linux ， 
然后开始看 ….&lt;/p&gt;

&lt;p&gt;不过后来比较囧的是， Linux 确实没有利用这个区间的 RAM … 真 TMD 有点浪 
费啊！ 现代的 BIOS 都默认 shadow 了，甚至没有地方给你配置了。 如果 Linux 
不使用 BIOS ， 不知道留这个洞在这里干嘛. 诶。 
其实我 google 了 lkml ， 是有人这么提出过这个问题。不过那个 Grek H 什么 
的，就是貌似的Linux二号or三号人物，回的邮件里说，虽然多数系统可以回收这 
个空洞，不过在个别系统上报告问题了。所以就默认不回收了。 
如果你要回收，这里有 patch … use at your own risk ….&lt;/p&gt;

&lt;p&gt;呵呵。&lt;/p&gt;

&lt;p&gt;扯远了。洗洗睡吧。明天估计开机会看到一堆回复了。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>chroot to arm on an x86 Gentoo</title>
   <link href="https://microcai.org/2011/04/26/chrootarm.html"/>
   <updated>2011-04-26T00:00:00+00:00</updated>
   <id>https://microcai.org/2011/04/26/chrootarm</id>
   <content type="html">&lt;p&gt;在早上的邮件中，我指出 Gentoo 是如何方便的构筑交叉环境的。
现在，我要指出，我还要运行里面的arm程序！ 在 chroot 环境中，真正的把它当
作一个发行版！&lt;/p&gt;

&lt;p&gt;我使用的是 crossdev -t arm-unknow-linux-gnueabi 编译的 arm 交叉工具链。
这时候 arm 其实被安装到了  /usr/arm-unknow-linux-gnueabi/&lt;/p&gt;

&lt;p&gt;/usr/arm-unknow-linux-gnueabi/ 下面有完整目录结构，相当于一个 arm 发行版。&lt;/p&gt;

&lt;p&gt;而且之后也会多了一个工具叫 arm-unknow-linux-gnueabi-emerge&lt;/p&gt;

&lt;p&gt;我们的第一个主角就出来了。我们需要 busybox&lt;/p&gt;

&lt;p&gt;USE=”-ipv6 static -pam make-symlinks” &lt;br /&gt;
        arm-unknow-linux-gnueabi-emerge busybox -av&lt;/p&gt;

&lt;p&gt;之后我们的 /usr/arm-unknow-linux-gnueabi/ 其实已经可以作为一个基本完整的
arm 系统的根目录了。&lt;/p&gt;

&lt;p&gt;我们需要第二个主角，一个解释器。qemu-user !&lt;/p&gt;

&lt;p&gt;export QEMU_USER_TARGETS=”arm”
export USE=”-* static”
emerge qemu -av –root=/usr/arm-unknow-linux-gnueabi/ -O&lt;/p&gt;

&lt;p&gt;这样 /usr/arm-unknow-linux-gnueabi/usr/bin/qemu-arm 就成为这个 arm 系统
的解释器了，注意，静态链接是必须的 wink.gif&lt;/p&gt;

&lt;p&gt;接着，我们需要内核的支持
echo
‘:arm:M::\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/arm-unknow-linux-gnueabi/usr/bin/qemu-arm:’&lt;/p&gt;
&lt;blockquote&gt;
  &lt;blockquote&gt;
    &lt;p&gt;/proc/sys/fs/binfmt_misc/register&lt;/p&gt;
  &lt;/blockquote&gt;
&lt;/blockquote&gt;

&lt;p&gt;当然，要需要第三个主角，一个 bash wink.gif
arm-unknow-linux-gnueabi-emerge bash -av&lt;/p&gt;

&lt;p&gt;好了，准备工作就完成了。&lt;/p&gt;

&lt;p&gt;chroot /usr/arm-unknow-linux-gnueabi/ /bin/bash&lt;/p&gt;

&lt;p&gt;呵呵。 arm 结构的 bash 已经被运作起来咯 wink.gif&lt;/p&gt;

&lt;p&gt;怎么样？
试试执行 ls wink.gif 哈哈&lt;/p&gt;

&lt;p&gt;Good luck to every one&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>2.6.38.4 内核对 tq2440 支持补丁</title>
   <link href="https://microcai.org/2011/04/22/patchfortq2440.html"/>
   <updated>2011-04-22T00:00:00+00:00</updated>
   <id>https://microcai.org/2011/04/22/patchfortq2440</id>
   <content type="html">&lt;p&gt;最近折腾了很久，总算搞定了。&lt;/p&gt;

&lt;p&gt;发个补丁，希望大家不要再折腾了，而且也不要看网上的所谓垃圾移植指南了，都是 bullshit.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  From 144759a9929f0858398b834a5cbfdb79ce56f077 Mon Sep 17 00:00:00 2001 
  From: microcai &amp;lt;microcai@fedoraproject.org&amp;gt; 
  Date: Sat, 30 Apr 2011 14:28:11 +0800 
  Subject: [PATCH 2/4] =TQ 2440 补丁 
  MIME-Version: 1.0 
  Content-Type: text/plain; charset=UTF-8 
  Content-Transfer-Encoding: 8bit 

  --- 
  arch/arm/mach-s3c2440/Kconfig | 46 +++ 
  arch/arm/mach-s3c2440/Makefile | 1 + 
  arch/arm/mach-s3c2440/mach-tq2440.c | 539 +++++++++++++++++++++++++++++++++++ 
  3 files changed, 586 insertions(+), 0 deletions(-) 
  create mode 100644 arch/arm/mach-s3c2440/mach-tq2440.c 

  diff --git a/arch/arm/mach-s3c2440/Kconfig b/arch/arm/mach-s3c2440/Kconfig 
  index 50825a3..9afdfe5 100644 
  --- a/arch/arm/mach-s3c2440/Kconfig 
  +++ b/arch/arm/mach-s3c2440/Kconfig 
  @@ -203,4 +203,50 @@ config MACH_RX1950 
  help 
  Say Y here if you&apos;re using HP iPAQ rx1950 

  +config MACH_TQ2440 
  + bool &quot;TQ 2440&quot; 
  + select MACH_SMDK 
  + select CPU_S3C2440 
  + select S3C_DEV_NAND 
  + select MTD 
  + select MTD_NAND 
  + select MTD_NAND_S3C2410 
  + select S3C_DEV_USB_HOST 
  + select RTC_DRV_S3C if RTC_CLASS 
  + select S3C2410_WATCHDOG if WATCHDOG 
  + select SND_SOC_SAMSUNG_S3C24XX_UDA134X if SOUND 
  + select USB_GADGET_S3C2410 
  + select FB_S3C2410 if FB 
  + select SERIAL_SAMSUNG 
  + select SERIAL_S3C2440 
  + select SERIAL_SAMSUNG_CONSOLE 
  + select SERIO 
  + select AEABI 
  + select BINFMT_ELF 
  + select CPU_FREQ_S3C24XX if CPU_FREQ 
  + select DM9000 if NETDEVICES 
  + select DEPRECATED_PARAM_STRUCT 
  + 
  + help 
  + Say Y here if you are using the TQ 2440 Machine 
  + development system 
  + 
  +choice 
  + prompt &quot;LCD size&quot; 
  + default FB_S3C24X0_LCD480272 
  + depends on FB_S3C2410 &amp;amp;&amp;amp; MACH_TQ2440 
  + 
  +config FB_S3C24X0_LCD480272 
  + bool &quot;4.3in LCD&quot; 
  + help 
  + Support 4.3in LCD 
  + 
  + 
  +config FB_S3C24X0_TFT640480 
  + bool &quot;640x480 LCD&quot; 
  + 
  + 
  +endchoice 
  + 
  + 
  endmenu 
  diff --git a/arch/arm/mach-s3c2440/Makefile b/arch/arm/mach-s3c2440/Makefile 
  index d5440fa..c001bdd 100644 
  --- a/arch/arm/mach-s3c2440/Makefile 
  +++ b/arch/arm/mach-s3c2440/Makefile 
  @@ -26,6 +26,7 @@ obj-$(CONFIG_S3C2440_PLL_16934400) += s3c2440-pll-16934400.o 

  # Machine support 

  +obj-$(CONFIG_MACH_TQ2440) += mach-tq2440.o 
  obj-$(CONFIG_MACH_ANUBIS) += mach-anubis.o 
  obj-$(CONFIG_MACH_OSIRIS) += mach-osiris.o 
  obj-$(CONFIG_MACH_RX3715) += mach-rx3715.o 
  diff --git a/arch/arm/mach-s3c2440/mach-tq2440.c b/arch/arm/mach-s3c2440/mach-tq2440.c 
  new file mode 100644 
  index 0000000..3d1bef7 
  --- /dev/null 
  +++ b/arch/arm/mach-s3c2440/mach-tq2440.c 
  @@ -0,0 +1,539 @@ 
  +/* linux/arch/arm/mach-s3c2440/mach-smdk2440.c 
  + * 
  + * Copyright (c) 2004-2005 Simtec Electronics 
  + * Ben Dooks &amp;lt;ben@simtec.co.uk&amp;gt; 
  + * 
  + * http://www.fluff.org/ben/smdk2440/ 
  + * 
  + * Thanks to Dimity Andric and TomTom for the loan of an SMDK2440. 
  + * 
  + * This program is free software; you can redistribute it and/or modify 
  + * it under the terms of the GNU General Public License version 2 as 
  + * published by the Free Software Foundation. 
  + * 
  +*/ 
  + 
  +#include &amp;lt;linux/kernel.h&amp;gt; 
  +#include &amp;lt;linux/types.h&amp;gt; 
  +#include &amp;lt;linux/interrupt.h&amp;gt; 
  +#include &amp;lt;linux/list.h&amp;gt; 
  +#include &amp;lt;linux/timer.h&amp;gt; 
  +#include &amp;lt;linux/init.h&amp;gt; 
  +#include &amp;lt;linux/serial_core.h&amp;gt; 
  +#include &amp;lt;linux/platform_device.h&amp;gt; 
  +#include &amp;lt;linux/mtd/mtd.h&amp;gt; 
  +#include &amp;lt;linux/mtd/nand.h&amp;gt; 
  +#include &amp;lt;linux/mtd/nand_ecc.h&amp;gt; 
  +#include &amp;lt;linux/mtd/partitions.h&amp;gt; 
  +#include &amp;lt;linux/i2c/at24.h&amp;gt; 
  +#include &amp;lt;linux/i2c.h&amp;gt; 
  +#include &amp;lt;linux/io.h&amp;gt; 
  +#include &amp;lt;linux/input.h&amp;gt; 
  +#include &amp;lt;linux/dm9000.h&amp;gt; 
  +#include &amp;lt;linux/gpio_keys.h&amp;gt; 
  +#include &amp;lt;linux/mmc/host.h&amp;gt; 
  +#include &amp;lt;sound/s3c24xx_uda134x.h&amp;gt; 
  + 
  +#include &amp;lt;asm/mach/arch.h&amp;gt; 
  +#include &amp;lt;asm/mach/map.h&amp;gt; 
  +#include &amp;lt;asm/mach/irq.h&amp;gt; 
  + 
  +#include &amp;lt;mach/hardware.h&amp;gt; 
  +#include &amp;lt;asm/irq.h&amp;gt; 
  +#include &amp;lt;asm/mach-types.h&amp;gt; 
  + 
  +#include &amp;lt;plat/regs-serial.h&amp;gt; 
  +#include &amp;lt;plat/regs-adc.h&amp;gt; 
  +#include &amp;lt;mach/regs-mem.h&amp;gt; 
  +#include &amp;lt;plat/iic.h&amp;gt; 
  +#include &amp;lt;plat/nand.h&amp;gt; 
  +#include &amp;lt;plat/s3c2410.h&amp;gt; 
  +#include &amp;lt;plat/s3c244x.h&amp;gt; 
  +#include &amp;lt;plat/clock.h&amp;gt; 
  +#include &amp;lt;plat/devs.h&amp;gt; 
  +#include &amp;lt;plat/cpu.h&amp;gt; 
  +#include &amp;lt;plat/udc.h&amp;gt; 
  +#include &amp;lt;plat/pm.h&amp;gt; 
  +#include &amp;lt;plat/ts.h&amp;gt; 
  +#include &amp;lt;plat/mci.h&amp;gt; 
  + 
  +#include &amp;lt;mach/fb.h&amp;gt; 
  +#include &amp;lt;mach/gpio-fns.h&amp;gt; 
  +#include &amp;lt;mach/regs-gpio.h&amp;gt; 
  +#include &amp;lt;mach/regs-lcd.h&amp;gt; 
  +#include &amp;lt;mach/leds-gpio.h&amp;gt; 
  +#include &amp;lt;mach/idle.h&amp;gt; 
  + 
  + 
  +/*NAND*/ 
  + 
  +static struct mtd_partition smdk_default_nand_part[] = { 
  + 
  + [0] = { 
  + 
  + .name = &quot;uboot&quot;, 
  + 
  + .offset = 0x00000000, 
  + .size = SZ_2M, 
  + 
  + }, 
  + [1] = { 
  + 
  + .name = &quot;kernel&quot;, 
  + .offset = SZ_2M, 
  + .size = SZ_2M + SZ_1M, 
  + }, 
  + 
  + [2] = { 
  + .name = &quot;yaffs2&quot;, 
  + .offset = SZ_4M+SZ_1M, 
  + .size = MTDPART_SIZ_FULL, 
  + } 
  +}; 
  + 
  +static struct s3c2410_nand_set smdk_nand_sets[] = { 
  + [0] = { 
  + .disable_ecc = 1, // 在这里设置才是正确的 
  + .name = &quot;NAND&quot;, 
  + .nr_chips = 1, 
  + .nr_partitions = ARRAY_SIZE(smdk_default_nand_part), 
  + .partitions = smdk_default_nand_part, 
  + }, 
  +}; 
  + 
  + 
  +static struct s3c2410_platform_nand smdk_nand_info = { 
  + .tacls = 20, 
  + .twrph0 = 60, 
  + .twrph1 = 20, 
  + .nr_sets = ARRAY_SIZE(smdk_nand_sets), 
  + .sets = smdk_nand_sets, 
  +}; 
  + 
  + 
  +/*IO*/ 
  +static struct map_desc tq2440_iodesc[] = { 
  + /* ISA IO Space map (memory space selected by A24) */ 
  + 
  + { 
  + .virtual = (u32)S3C24XX_VA_ISA_WORD, 
  + .pfn = __phys_to_pfn(S3C2410_CS2), 
  + .length = 0x10000, 
  + .type = MT_DEVICE, 
  + }, { 
  + .virtual = (u32)S3C24XX_VA_ISA_WORD + 0x10000, 
  + .pfn = __phys_to_pfn(S3C2410_CS2 + (1&amp;lt;&amp;lt;24)), 
  + .length = SZ_4M, 
  + .type = MT_DEVICE, 
  + }, { 
  + .virtual = (u32)S3C24XX_VA_ISA_BYTE, 
  + .pfn = __phys_to_pfn(S3C2410_CS2), 
  + .length = 0x10000, 
  + .type = MT_DEVICE, 
  + }, { 
  + .virtual = (u32)S3C24XX_VA_ISA_BYTE + 0x10000, 
  + .pfn = __phys_to_pfn(S3C2410_CS2 + (1&amp;lt;&amp;lt;24)), 
  + .length = SZ_4M, 
  + .type = MT_DEVICE, 
  + } 
  +}; 
  + 
  +#define UCON S3C2410_UCON_DEFAULT | S3C2410_UCON_UCLK 
  +#define ULCON S3C2410_LCON_CS8 | S3C2410_LCON_PNONE | S3C2410_LCON_STOPB 
  +#define UFCON S3C2410_UFCON_RXTRIG8 | S3C2410_UFCON_FIFOMODE 
  + 
  +static struct s3c2410_uartcfg tq2440_uartcfgs[] = { 
  + [0] = { 
  + .hwport = 0, 
  + .flags = 0, 
  + .ucon = 0x3c5, 
  + .ulcon = 0x03, 
  + .ufcon = 0x51, 
  + }, 
  +}; 
  + 
  +/* LCD driver info */ 
  + 
  +static struct s3c2410fb_display tq2440_lcd_cfg = { 
  + 
  + .lcdcon5 = S3C2410_LCDCON5_FRM565 | 
  + S3C2410_LCDCON5_INVVLINE | 
  + S3C2410_LCDCON5_INVVFRAME | 
  + S3C2410_LCDCON5_PWREN | 
  + S3C2410_LCDCON5_HWSWP, 
  + 
  + .type = S3C2410_LCDCON1_TFT, 
  + 
  +#if defined(CONFIG_FB_S3C24X0_LCD480272) 
  + .width = 480, 
  + .height = 272, 
  + 
  + .pixclock = 40000, /* HCLK 100 MHz, divisor 1 */ 
  + .xres = 480, 
  + .yres = 272, 
  + .bpp = 16, 
  + .left_margin = 15, /* for HFPD*/ 
  + .right_margin = 16, /* for HBPD*/ 
  + .hsync_len = 30, /* for HSPW*/ 
  + .upper_margin = 4, /* for VFPD*/ 
  + .lower_margin = 2, /* for VBPD*/ 
  + .vsync_len = 8, /* for VSPW*/ 
  + 
  +#elif defined(CONFIG_FB_S3C24X0_TFT640480) 
  + .width = 640, 
  + .height = 480, 
  + 
  + .pixclock = 40000, /* HCLK 100 MHz, divisor 1 */ 
  + .xres = 640, 
  + .yres = 480, 
  + .bpp = 16, 
  + .left_margin = 40, /* for HFPD*/ 
  + .right_margin = 67, /* for HBPD*/ 
  + .hsync_len = 31, /* for HSPW*/ 
  + .upper_margin = 5, /* for VFPD*/ 
  + .lower_margin = 25, /* for VBPD*/ 
  + .vsync_len = 1, /* for VSPW*/ 
  + 
  +#else 
  + .width = 240, 
  + .height = 320, 
  + 
  + .pixclock = 166667, /* HCLK 60 MHz, divisor 10 */ 
  + .xres = 240, 
  + .yres = 320, 
  + .bpp = 16, 
  + .left_margin = 20, 
  + .right_margin = 8, 
  + .hsync_len = 4, 
  + .upper_margin = 8, 
  + .lower_margin = 7, 
  + .vsync_len = 4, 
  +#endif 
  + 
  +}; 
  + 
  +static struct s3c2410fb_mach_info tq2440_fb_info = { 
  + .displays = &amp;amp;tq2440_lcd_cfg, 
  + .num_displays = 1, 
  + .default_display = 0, 
  +}; 
  + 
  + 
  +static struct resource s3c_dm9000_resource[] = { 
  + [0] = { 
  + .start = S3C2410_CS4, 
  + .end = S3C2410_CS4+3, 
  + .flags = IORESOURCE_MEM, 
  + }, 
  + 
  + [1] = { 
  + .start = S3C2410_CS4+4, 
  + .end = S3C2410_CS4+4+3, 
  + .flags = IORESOURCE_MEM, 
  + }, 
  + 
  + [2] = { 
  + .start = IRQ_EINT7, 
  + .end =IRQ_EINT7, 
  + .flags =IORESOURCE_IRQ|IORESOURCE_IRQ_HIGHEDGE|IORESOURCE_IRQ_SHAREABLE, 
  + } 
  +}; 
  + 
  +static struct dm9000_plat_data dm9k_plat_data = { 
  + .flags = DM9000_PLATF_16BITONLY|DM9000_PLATF_NO_EEPROM, 
  + .dev_addr = &quot;&quot;, 
  + }; 
  + 
  +static struct platform_device s3c_device_dm9k = { 
  + .name = &quot;dm9000&quot;, 
  + .id = 0, 
  + .num_resources = ARRAY_SIZE(s3c_dm9000_resource), 
  + .resource = s3c_dm9000_resource, 
  + .dev = { 
  + .platform_data = &amp;amp;dm9k_plat_data, 
  + } 
  +}; 
  + 
  + 
  +/*设置udc回调函数udc_pullup()用于使能/禁止UDC上拉电阻*/ 
  +/*udc*/ 
  +static void udc_pullup(enum s3c2410_udc_cmd_e cmd) 
  +{ 
  + switch(cmd) 
  + { 
  + case S3C2410_UDC_P_ENABLE: 
  + s3c2410_gpio_setpin(S3C2410_GPG(12),1); 
  + break; 
  + case S3C2410_UDC_P_DISABLE: 
  + s3c2410_gpio_setpin(S3C2410_GPG(12),0); 
  + break; 
  + case S3C2410_UDC_P_RESET: 
  + break; 
  + default: 
  + break; 
  + } 
  +} 
  +static struct s3c2410_udc_mach_info udc_machine = { 
  + .udc_command = udc_pullup, 
  +}; 
  + 
  + 
  +/*KEY*/ 
  +static struct gpio_keys_button mini2440_buttons[] = { 
  + { 
  + .gpio = S3C2410_GPF(1), /* K1 */ 
  + .code = KEY_UP, 
  + .desc = &quot;UP&quot;, 
  + .active_low = 1, 
  + }, 
  + { 
  + .gpio = S3C2410_GPF(4), /* K2 */ 
  + .code = KEY_DOWN, 
  + .desc = &quot;DOWN&quot;, 
  + .active_low = 1, 
  + }, 
  + { 
  + .gpio = S3C2410_GPF(2), /* K3 */ 
  + .code = KEY_LEFT, 
  + .desc = &quot;LEFT&quot;, 
  + .active_low = 1, 
  + }, 
  + { 
  + .gpio = S3C2410_GPF(0), /* K4 */ 
  + .code = KEY_RIGHT, 
  + .desc = &quot;RIGHT&quot;, 
  + .active_low = 1, 
  + }, 
  + 
  +}; 
  + 
  +static struct gpio_keys_platform_data mini2440_button_data = { 
  + .buttons = mini2440_buttons, 
  + .nbuttons = ARRAY_SIZE(mini2440_buttons), 
  +}; 
  + 
  +static struct platform_device mini2440_button_device = { 
  + .name = &quot;gpio-keys&quot;, 
  + .id = -1, 
  + .dev = { 
  + .platform_data = &amp;amp;mini2440_button_data, 
  + } 
  +}; 
  + 
  +/* LED devices */ 
  + 
  +static struct s3c24xx_led_platdata smdk_pdata_led4 = { 
  + .gpio = S3C2410_GPB(5), 
  + .flags = S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE, 
  + .name = &quot;led4&quot;, 
  + .def_trigger = &quot;timer&quot;, 
  +}; 
  + 
  +static struct s3c24xx_led_platdata smdk_pdata_led5 = { 
  + .gpio = S3C2410_GPB(6), 
  + .flags = S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE, 
  + .name = &quot;led5&quot;, 
  + .def_trigger = &quot;nand-disk&quot;, 
  +}; 
  + 
  +static struct s3c24xx_led_platdata smdk_pdata_led6 = { 
  + .gpio = S3C2410_GPB(7), 
  + .flags = S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE, 
  + .name = &quot;led6&quot;, 
  +}; 
  + 
  +static struct s3c24xx_led_platdata smdk_pdata_led7 = { 
  + .gpio = S3C2410_GPB(8), 
  + .flags = S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE, 
  + .name = &quot;led7&quot;, 
  +}; 
  + 
  +static struct platform_device smdk_led4 = { 
  + .name = &quot;s3c24xx_led&quot;, 
  + .id = 0, 
  + .dev = { 
  + .platform_data = &amp;amp;smdk_pdata_led4, 
  + }, 
  +}; 
  + 
  +static struct platform_device smdk_led5 = { 
  + .name = &quot;s3c24xx_led&quot;, 
  + .id = 1, 
  + .dev = { 
  + .platform_data = &amp;amp;smdk_pdata_led5, 
  + }, 
  +}; 
  + 
  +static struct platform_device smdk_led6 = { 
  + .name = &quot;s3c24xx_led&quot;, 
  + .id = 2, 
  + .dev = { 
  + .platform_data = &amp;amp;smdk_pdata_led6, 
  + }, 
  +}; 
  + 
  +static struct platform_device smdk_led7 = { 
  + .name = &quot;s3c24xx_led&quot;, 
  + .id = 3, 
  + .dev = { 
  + .platform_data = &amp;amp;smdk_pdata_led7, 
  + }, 
  +}; 
  + 
  +static struct s3c24xx_uda134x_platform_data tq2440_audio_pins = { 
  + .l3_clk = S3C2410_GPB(4), 
  + .l3_data = S3C2410_GPB(3), 
  + .l3_mode = S3C2410_GPB(2), 
  + .model = UDA134X_UDA1341, 
  +}; 
  + 
  +static struct platform_device tq2440_audio = { 
  + .name = &quot;s3c24xx_uda134x&quot;, 
  + .id = 0, 
  + .dev = { 
  + .platform_data = &amp;amp;tq2440_audio_pins, 
  + }, 
  +}; 
  + 
  +static struct platform_device uda1340_codec = { 
  + .name = &quot;uda134x-codec&quot;, 
  +}; 
  + 
  +/* Touchscreen */ 
  +static struct s3c2410_ts_mach_info tq2400_ts_info = { 
  + .delay = 10000, 
  + .presc = 49, 
  + .oversampling_shift = 2, 
  +}; 
  + 
  +static struct resource s3c_adc_resource[] = { 
  + [0] = { 
  + .start = S3C2410_PA_ADC, 
  + .end = S3C2410_PA_ADC + 0x20 , 
  + .flags = IORESOURCE_MEM, 
  + }, 
  + [1] = { 
  + .start = IRQ_TC, 
  + .end = IRQ_TC, 
  + .flags = IORESOURCE_IRQ, 
  + }, 
  + [2] = { 
  + .start = IRQ_ADC, 
  + .end = IRQ_ADC, 
  + .flags = IORESOURCE_IRQ, 
  + }, 
  +}; 
  + 
  + 
  +/* 
  + * I2C devices 
  + */ 
  +static struct at24_platform_data at24c08 = { 
  + .byte_len = SZ_8K / 8, 
  + .page_size = 16, 
  +}; 
  + 
  +static struct i2c_board_info mini2440_i2c_devs[] = { 
  + { 
  + I2C_BOARD_INFO(&quot;24c08&quot;, 0x50), 
  + .platform_data = &amp;amp;at24c08, 
  + }, 
  +}; 
  + 
  + 
  +static struct platform_device *tq2440_devices[] = { 
  + &amp;amp;s3c_device_adc, 
  + &amp;amp;s3c_device_ts, 
  + &amp;amp;s3c_device_ohci, // s3c24xx_ohci 
  + &amp;amp;s3c_device_wdt, // 看门狗狗 
  + &amp;amp;s3c_device_lcd, 
  + &amp;amp;s3c_device_i2c0, 
  + &amp;amp;s3c_device_iis, 
  + &amp;amp;s3c_device_nand, // MTD NAND 分区表 
  + &amp;amp;s3c_device_dm9k, // 特别的DM9000网卡芯片 
  + &amp;amp;s3c_device_rtc, 
  + &amp;amp;mini2440_button_device, 
  + &amp;amp;s3c_device_sdi, // SD 卡读卡器 
  + &amp;amp;s3c_device_usbgadget, 
  + &amp;amp;uda1340_codec, 
  + &amp;amp;tq2440_audio, // ALSA 声卡 
  + &amp;amp;samsung_asoc_dma, 
  + &amp;amp;smdk_led4, 
  + &amp;amp;smdk_led5, 
  + &amp;amp;smdk_led6, 
  + &amp;amp;smdk_led7, 
  +}; 
  + 
  +/* MMC/SD */ 
  + 
  +static struct s3c24xx_mci_pdata mini2440_mmc_cfg = { 
  + .gpio_detect = S3C2410_GPG(8), 
  + .gpio_wprotect = S3C2410_GPH(8), 
  + .set_power = NULL, 
  + .ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34, 
  +}; 
  + 
  +static void __init tq2440_map_io(void) 
  +{ 
  + s3c24xx_init_io(tq2440_iodesc, ARRAY_SIZE(tq2440_iodesc)); 
  + s3c24xx_init_clocks(12000000); 
  + s3c24xx_init_uarts(tq2440_uartcfgs, ARRAY_SIZE(tq2440_uartcfgs)); 
  +} 
  + 
  +static void __init tq2440_machine_init(void) 
  +{ 
  + int i; 
  + 
  + s3c24xx_fb_set_platdata(&amp;amp;tq2440_fb_info); 
  + s3c_i2c0_set_platdata(NULL); 
  + 
  + s3c2410_gpio_cfgpin(S3C2410_GPG(8),S3C2410_GPG8_EINT16); 
  + 
  + s3c_nand_set_platdata(&amp;amp;smdk_nand_info); 
  + s3c24xx_mci_set_platdata(&amp;amp;mini2440_mmc_cfg); 
  + 
  +// s3c_device_adc.num_resources = ARRAY_SIZE(s3c_adc_resource); 
  +// s3c_device_adc.resource = s3c_adc_resource; 
  + 
  + s3c_device_ts.dev.platform_data = &amp;amp;tq2400_ts_info; 
  + 
  + 
  +#ifdef CONFIG_DM9000 
  + *((volatile unsigned int *)S3C2410_BANKCON4) = 0x1f7c; //config time seq 
  +#endif 
  + 
  + /* mark the key as input, without pullups (there is one on the board) */ 
  + for ( i = 0; i &amp;lt; ARRAY_SIZE(mini2440_buttons); i++) { 
  + s3c_gpio_setpull(mini2440_buttons[i].gpio, S3C_GPIO_PULL_UP); 
  + s3c_gpio_cfgpin(mini2440_buttons[i].gpio, S3C2410_GPIO_INPUT); 
  + } 
  + 
  + s3c24xx_mci_set_platdata(&amp;amp;mini2440_mmc_cfg); 
  + 
  + i2c_register_board_info(0, mini2440_i2c_devs, 
  + ARRAY_SIZE(mini2440_i2c_devs)); 
  + 
  + platform_add_devices(tq2440_devices, ARRAY_SIZE(tq2440_devices)); 
  + 
  + s3c2410_gpio_setpin(S3C2410_GPG(12),0); 
  + s3c2410_gpio_cfgpin(S3C2410_GPG(12),S3C2410_GPIO_OUTPUT); 
  + s3c24xx_udc_set_platdata(&amp;amp;udc_machine); 
  +} 
  + 
  +#define MACH_TYPE_168 168 
  +/*For some reason , my machine is ID 168*/ 
  +MACHINE_START(168, &quot;TQ2440-168&quot;) 
  + .boot_params = S3C2410_SDRAM_PA + 0x100, 
  + .init_irq = s3c24xx_init_irq, 
  + .map_io = tq2440_map_io, 
  + .init_machine = tq2440_machine_init, 
  + .timer = &amp;amp;s3c24xx_timer, 
  +MACHINE_END 
  + 
  +MACHINE_START(TQ2440, &quot;TQ2440&quot;) 
  + .boot_params = S3C2410_SDRAM_PA + 0x100, 
  + .init_irq = s3c24xx_init_irq, 
  + .map_io = tq2440_map_io, 
  + .init_machine = tq2440_machine_init, 
  + .timer = &amp;amp;s3c24xx_timer, 
  +MACHINE_END 
  -- 
  1.7.5.rc3 

  From 119021354fa6001311db9f1df5d3ea8061a35f9b Mon Sep 17 00:00:00 2001 
  From: microcai &amp;lt;microcai@fedoraproject.org&amp;gt; 
  Date: Thu, 5 May 2011 14:40:44 +0800 
  Subject: [PATCH 3/4] add report for ABS_PRESSURE, this fix tslib problem that 
  not been able to detect pen-down and pen-up events. 

  --- 
  drivers/input/touchscreen/s3c2410_ts.c | 3 +++ 
  1 files changed, 3 insertions(+), 0 deletions(-) 

  diff --git a/drivers/input/touchscreen/s3c2410_ts.c b/drivers/input/touchscreen/s3c2410_ts.c 
  index 8feb7f3..ecab5d4 100644 
  --- a/drivers/input/touchscreen/s3c2410_ts.c 
  +++ b/drivers/input/touchscreen/s3c2410_ts.c 
  @@ -126,6 +126,7 @@ static void touch_timer_fire(unsigned long data) 
  input_report_abs(ts.input, ABS_Y, ts.yp); 

  input_report_key(ts.input, BTN_TOUCH, 1); 
  + input_report_abs(ts.input, ABS_PRESSURE, 1); 
  input_sync(ts.input); 

  ts.xp = 0; 
  @@ -140,6 +141,7 @@ static void touch_timer_fire(unsigned long data) 
  ts.count = 0; 

  input_report_key(ts.input, BTN_TOUCH, 0); 
  + input_report_abs(ts.input, ABS_PRESSURE, 0); 
  input_sync(ts.input); 

  writel(WAIT4INT | INT_DOWN, ts.io + S3C2410_ADCTSC); 
  @@ -318,6 +320,7 @@ static int __devinit s3c2410ts_probe(struct platform_device *pdev) 
  ts.input-&amp;gt;keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); 
  input_set_abs_params(ts.input, ABS_X, 0, 0x3FF, 0, 0); 
  input_set_abs_params(ts.input, ABS_Y, 0, 0x3FF, 0, 0); 
  + input_set_abs_params(ts.input, ABS_PRESSURE, 0, 1, 0, 0); 

  ts.input-&amp;gt;name = &quot;S3C24XX TouchScreen&quot;; 
  ts.input-&amp;gt;id.bustype = BUS_HOST; 
  -- 
  1.7.5.rc3 

  From 4684013358b6edf0e8a790ef32de13a236cf7987 Mon Sep 17 00:00:00 2001 
  From: microcai &amp;lt;microcai@fedoraproject.org&amp;gt; 
  Date: Thu, 5 May 2011 16:19:53 +0800 
  Subject: [PATCH 4/4] fix some time later then driver not response 

  --- 
  drivers/input/touchscreen/s3c2410_ts.c | 1 + 
  1 files changed, 1 insertions(+), 0 deletions(-) 

  diff --git a/drivers/input/touchscreen/s3c2410_ts.c b/drivers/input/touchscreen/s3c2410_ts.c 
  index ecab5d4..db868d2 100644 
  --- a/drivers/input/touchscreen/s3c2410_ts.c 
  +++ b/drivers/input/touchscreen/s3c2410_ts.c 
  @@ -227,6 +227,7 @@ static void s3c24xx_ts_select(struct s3c_adc_client *client, unsigned select) 
  if (select) { 
  writel(S3C2410_ADCTSC_PULL_UP_DISABLE | AUTOPST, 
  ts.io + S3C2410_ADCTSC); 
  + writel(readl(ts.io+S3C2410_ADCCON) | S3C2410_ADCCON_ENABLE_START, ts.io+S3C2410_ADCCON); 
  } else { 
  mod_timer(&amp;amp;touch_timer, jiffies+1); 
  writel(WAIT4INT | INT_UP, ts.io + S3C2410_ADCTSC); 
  -- 
  1.7.5.rc3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>GPG 签名与加密</title>
   <link href="https://microcai.org/2011/04/22/gpg.html"/>
   <updated>2011-04-22T00:00:00+00:00</updated>
   <id>https://microcai.org/2011/04/22/gpg</id>
   <content type="html">&lt;p&gt;首先密码学的模型&lt;/p&gt;

&lt;p&gt;1） 通信双方的机器是没有问题的。没有木马，没有坏人。 
2) 通信线路是不可靠的，有人监听，有人伪造信息&lt;/p&gt;

&lt;p&gt;那么，就要解决2个问题，首先，我得确信这个信息是你发送的，不是别人伪造你 
发送的，俗称数字签名， 第二，我要确保没有人知道我们之间的通信，也就是加密。&lt;/p&gt;

&lt;p&gt;非对称加密的意思就是，同时有2个key. 用其中一个加密，只能用另一个解密。 
这样，随机选一个做私钥，另一个公开做公钥就是下面我要讲的 GPG 签名与加密。&lt;/p&gt;

&lt;p&gt;对于 GPG 来说，GPG 会生成一个密钥对，一个是公钥，一个是私钥。&lt;/p&gt;

&lt;p&gt;对于公钥，你需要将它上传到公钥服务器。 
对于私钥，要绝对反正泄露，并且不能丢失!!!! 我重复，不能丢失，也不能泄露。&lt;/p&gt;

&lt;p&gt;如果你要让别人相信你的邮件不是伪造的，那么你就需要进行签名。 
签名就是用自己的私钥加密邮件。因为公钥是公开的，所以任何人都可以解密你的 
邮件。但是只有用你的私钥才能发出那封邮件，所以别人可以肯定这是你的邮件， 
不是别人伪造的。这就是用私钥进行签名的原因。&lt;/p&gt;

&lt;p&gt;如果是发送加密邮件，那么你必须有对方的 GPG 公钥。 
然后你用 &lt;em&gt;对方&lt;/em&gt; 的 &lt;em&gt;公钥&lt;/em&gt; 加密邮件。&lt;/p&gt;

&lt;p&gt;因为这个时间上只有收件人有加密时使用的公钥的对应私钥，所以只有你的收件人 
能解密这个邮件. 这样你就保证了你的邮件只有他能看到。PS， 你自己也不能解 
密那个邮件。因为你没有他的私钥。&lt;/p&gt;

&lt;p&gt;加密和签名同时使用的时候，就可以保证&lt;/p&gt;

&lt;p&gt;1） 他可以确信邮件是你发送的 
2） 你可以确信只有他能看那封邮件&lt;/p&gt;

&lt;p&gt;===================================== 
但是，这个前提是，你怎么知道/确信指定的公钥就是他的？ 他怎么知道/确信某 
个密钥就是你的！！！！&lt;/p&gt;

&lt;p&gt;这就引入了一个 信任机制。&lt;/p&gt;

&lt;p&gt;比如 A B C&lt;/p&gt;

&lt;p&gt;A 通过见面得知了 B 的公钥。这个时候 A 和 B 可以相互100%确认对方的公钥。&lt;/p&gt;

&lt;p&gt;这个时候来了个 C . C 自称他的公钥是 XXXX 。&lt;/p&gt;

&lt;p&gt;C 和 A 见过面。交换过公钥。&lt;/p&gt;

&lt;p&gt;然后 A 就给 C 的公钥签名，&lt;/p&gt;

&lt;p&gt;B 收到 C 的邮件的时候，发现他的公钥被 A 签名了，B 就可以不需要亲自去核 
实，就可以确定 XXXX 确实是 C 的公钥.&lt;/p&gt;

&lt;p&gt;其实和 SSL 的证书机制差不多的啦。 
我的 GPG 公钥是&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;E84C0359 , 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;指纹是&lt;/p&gt;

&lt;p&gt;85D9 2529 F2D6 EE19 CBEB A685 A4F8 533E E84C 0359&lt;/p&gt;

&lt;p&gt;请记住哦 ;)&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>两种  Mem remap</title>
   <link href="https://microcai.org/2011/04/22/4gmem.html"/>
   <updated>2011-04-22T00:00:00+00:00</updated>
   <id>https://microcai.org/2011/04/22/4gmem</id>
   <content type="html">&lt;p&gt;安装4G内存操作系统却只报告 3.2G 是4G时代困扰我们的一个问题。&lt;/p&gt;

&lt;p&gt;解决办法需要3个条件齐备，缺一不可&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1. 内存控制器支持 &amp;gt; 4G 寻址
2. 操作系统支持 PAE 或者直接64位系统
3. BIOS 支持 mem remap
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;** &amp;gt; 4G 寻址的内存控制器，intel 平台的话要求至少是 945 以上的芯片组。
AMD 平台的话，内存控制器内置于 CPU, 只要是支持 64 位的 AMD CPU 就可以了。&lt;/p&gt;

&lt;p&gt;操作系统吧，不多解释。 32位的windows 使用 PAE 内核，或者64位 windows.
Linux 也一样。
这里要注意的是，开了 PAE 的 32位 windows 7 也不能识别 &amp;gt; 4G 的内存 …… ，可以使用 ReadyFor4G 这个小工具修改内核。&lt;/p&gt;

&lt;p&gt;这里是本文的重点。&lt;/p&gt;

&lt;p&gt;mem remap 就是重新布局 PCI 设备地址和 DRAM 地址。使原本要重叠的地址错开，分布到 &amp;gt; 4G 的地址空间上。
这样才能使安装的 4G DRAM 可以被全部寻址。&lt;/p&gt;

&lt;p&gt;其中，mem remap 分2种方式&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1. 4G DRAM 获得连续的地址空间，PCI 设备地址被放置于 4G 地址之上
2. 4G DRAM 内存被分割。 PCI 设备地址被放置于 4G 以内，其余的 RAM 被放置到 4G 之上 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;对于第一种方式最简单，最容易理解。 但是却有潜在的兼容性问题。
第一 许多 PCI 设备在设计的时候并不能寻址 &amp;gt; 4G 的内存，这样在进行 DMA 的时候非常容易出问题。 如果主板粗暴的将 PCI 地址放到 4G 后面，那么很多设计不良的设备就罢工了。
第二 32bit 的操作系统在此选项下将无法正常工作。因为 PCI 设备的地址都跑到 4G 外面去了。&lt;/p&gt;

&lt;p&gt;第二种方式，给内存地址引入了一个“洞”。 对 32bit OS 和老旧的 PCI 设备提供了良好的兼容。
但是对内存引入的”洞” 确实给内存性能带来了潜在的影响。虽然通过 MTFR 情况还不会太坏。
但是，“洞” 怎么也不让人舒服啊！&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>为何我们不能回到过去</title>
   <link href="https://microcai.org/2011/04/15/timetravel.html"/>
   <updated>2011-04-15T00:00:00+00:00</updated>
   <id>https://microcai.org/2011/04/15/timetravel</id>
   <content type="html">&lt;p&gt;我们能回到过去么？&lt;/p&gt;

&lt;p&gt;答案是：不能。&lt;/p&gt;

&lt;p&gt;单个原子能回到过去么？&lt;/p&gt;

&lt;p&gt;答案是，可能。&lt;/p&gt;

&lt;p&gt;lol&lt;/p&gt;

&lt;p&gt;自从爱因斯坦否决了牛顿的时空观后，很多人就开始在理论上研究时空旅行。 
时间是什么？ 时间由运动产生，没有运动，就没有时间。如果你周围的一切一切都变慢一倍，包括你自己的新陈代谢，那么，你还能知道时间变慢了么？ 决定不能。
时间就是相对的。想到这就很容易理解了。
于是，既然时间是相对的，能回到过去么？&lt;/p&gt;

&lt;p&gt;不能。时间在流逝，流逝的速率可以被运行、质量影响，但是，它在流逝，这是无法避免的。&lt;/p&gt;

&lt;p&gt;时间也符合熵增加原理。&lt;/p&gt;

&lt;p&gt;我重复，&lt;/p&gt;

&lt;p&gt;时间也符合熵增加原理。time is entropy&lt;/p&gt;

&lt;p&gt;时间也是熵的一种。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>Size is not everything</title>
   <link href="https://microcai.org/2011/03/15/sizeofnoteverything.html"/>
   <updated>2011-03-15T00:00:00+00:00</updated>
   <id>https://microcai.org/2011/03/15/sizeofnoteverything</id>
   <content type="html">&lt;p&gt;我们买笔记本的时候，往往笔记本只标示硬盘容量，却往往不标示他所使用的硬盘的牌子和速度。&lt;/p&gt;

&lt;p&gt;我们知道，硬盘的速度是一台电脑整体速度的瓶颈。它慢了，不管容量再大，电脑都快不起来。&lt;/p&gt;

&lt;p&gt;希捷  7200转 500G 带 16MB 缓存的硬盘，接口速度 300MB/s ， 突发数据传输率可达 160MB/s ，持续传输率平均能达到 80MB/s ,  一个要卖 超过 500&lt;/p&gt;

&lt;p&gt;同样容量的日立硬盘，5400转 8MB 缓存，接口速率都只是 150MB/s ，持续传输率不超过 60MB/s ， 一个卖不到 300&lt;/p&gt;

&lt;p&gt;看到这猫腻了吧？！&lt;/p&gt;

&lt;p&gt;别光看容量哦 !&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>gnome 的对与错</title>
   <link href="https://microcai.org/2011/03/15/gnomefault.html"/>
   <updated>2011-03-15T00:00:00+00:00</updated>
   <id>https://microcai.org/2011/03/15/gnomefault</id>
   <content type="html">&lt;p&gt;gnome-shell 也用了许久了。从一开始的新鲜到习惯， 再到离不开它。&lt;/p&gt;

&lt;p&gt;等新鲜感过去的时候，我发现我越来越不能忍受 gnome-shell 那容易崩溃的毛病了。&lt;/p&gt;

&lt;p&gt;在没升级 gnome 其他部件的时候， gnome-shell 崩溃总是会被自动重启。&lt;/p&gt;

&lt;p&gt;后来 ， gnome-shell 崩溃虽然也会被自动重启，可是 … 如果连续崩溃两次间隔很短，则愚蠢的 gnome-session 就会断定 gnome-shell 出了问题，逼我注销 …&lt;/p&gt;

&lt;p&gt;有这样子的么？ 我还有工作没保存 … 还有聊天窗口在和人讨论呢！&lt;/p&gt;

&lt;p&gt;终于忍受不了了，我决定暂时离开 gnome&lt;/p&gt;

&lt;p&gt;决定用 KDE 了。&lt;/p&gt;

&lt;p&gt;KDE，很早以前用 红旗的时候用的。 诶，现在想起来 … 用红旗的日子哦 ~~~&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>OSS4 deprecated ALSA and PulseAudio</title>
   <link href="https://microcai.org/2011/02/15/oss4fuckofalsaandpa.html"/>
   <updated>2011-02-15T00:00:00+00:00</updated>
   <id>https://microcai.org/2011/02/15/oss4fuckofalsaandpa</id>
   <content type="html">&lt;p&gt;Turst me , OSS4 = ALSA + PulseAudio , and all implemented in kernel. very low latency.&lt;/p&gt;

&lt;p&gt;OSS4 have kernel level mix feature which ALSA lack for years and that’s why PuleAudio fucked to burn.&lt;/p&gt;

&lt;p&gt;OSS4 also have per-process volume control which ALSA lack for years and that’s why PuleAudio fucked to burn.&lt;/p&gt;

&lt;p&gt;OSS4 is not a new API but a new implementation. So existing App won’t need any modification. 
But when ALSA burn , every app need to re-write with the poor designed/doc-ed ALSA API.
When nearly lost app support ALSA, PA fuck the world again!!! And every app need to re-write with the PA api.&lt;/p&gt;

&lt;p&gt;But when OSS4 came back. NO app need to re-write. OSS4 works as a drop in replacement!
Great!!!&lt;/p&gt;

&lt;p&gt;And, OSS4 is pure kernel side. OSS4 don’t need assistance from user-land.&lt;/p&gt;

&lt;p&gt;So, pleace deprecated ALSA and PA immediately and include OSS4. 
And ALSA foundation rename to OSS-ng foundation and sopport OSS4.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>force AHCI without BIOS</title>
   <link href="https://microcai.org/2011/02/02/forceahci.html"/>
   <updated>2011-02-02T00:00:00+00:00</updated>
   <id>https://microcai.org/2011/02/02/forceahci</id>
   <content type="html">&lt;p&gt;For long time, I’ve been using IDE mode for SATA.
长久以来，我一直在用SATA的IDE模式。&lt;/p&gt;

&lt;p&gt;but, SATA-II ‘s 300MB/s transfer rate and  NCQ features are missing .
但是 ，SATA-II 的 300MB/s 速度和 NCQ 功能都用不上了。&lt;/p&gt;

&lt;p&gt;So I decide to use AHCI.
于是我打定主意用 AHCI 了。&lt;/p&gt;

&lt;p&gt;But there is no AHCI option in my BIOS setup. What a fuck! I’m sure ICH8 is capable of AHCI.
但是 BIOS 设置里却米有 AHCI ! what a fuck ! 但是我确信ICH8芯片组有 AHCI 功能。&lt;/p&gt;

&lt;p&gt;I googled a  lot, but only one that try to modify NVRAM directly and then AHCI is enabled.
我搜了很多，只有一个人直接修改 NVRAM 启用了 AHCI.&lt;/p&gt;

&lt;p&gt;But , some one in LKML uses setpci and fakephp to force set AHCI mode and rescan PCI bus to use ahci driver.
Linux邮件列表有人用setpci配合fakephp强制设置了AHCI模式重扫描PCI总线后用上了 ahci&lt;/p&gt;

&lt;p&gt;But, I tryed but result as machine panic.
我用一下，死机了。&lt;/p&gt;

&lt;p&gt;Googling , and found that grub2 also has setpci . What a hope!
继续搜发现grub2 也有 setpci 命令？。希望来了么？&lt;/p&gt;

&lt;p&gt;recompile a kernel with AHCI only , and use lspci to remember pci address of my ICH8 SATA controller.
重新编译内核，只启用 AHCI 驱动，使用 lspci 记下 SATA 控制器的 PCI 地址。&lt;/p&gt;

&lt;p&gt;reboot, and enter grub command line. 
重启到 grub 命令模式。&lt;/p&gt;

&lt;p&gt;after excute setpci -d 8086:2828 90.b=40, machine hang
执行 setpci -d 8086:2828 90.b=40 后死机。&lt;/p&gt;

&lt;p&gt;reboot, and enter grub command line. 
重启到 grub 命令模式。&lt;/p&gt;

&lt;p&gt;excute setpci -d 8086:2828 90.b=40 after linux /vmlinux-ahci , machine boot , and desktop showsup!
linux  /vmlinux-ahci 加载内核后执行 setpci -d 8086:2828 90.b=40 ， 机器成功启动，桌面出现。&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;dmesg&lt;/td&gt;
      &lt;td&gt;grep NCQ&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;dmesg&lt;/td&gt;
      &lt;td&gt;grep NCQ&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;YES!, NCQ (31/32) , no longer NCQ (0/32) ， NCQ was enabled! AHCI was enabled!
是的 ，出现 NCQ (31/32) , 而不再是 NCQ (0/32), NCQ 启用了！ AHCI 启用啦！&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>in-kernel UNICODE font support for fbcon</title>
   <link href="https://microcai.org/2011/01/01/cjktty.html"/>
   <updated>2011-01-01T00:00:00+00:00</updated>
   <id>https://microcai.org/2011/01/01/cjktty</id>
   <content type="html">&lt;p&gt;I first got this patch from 内核补丁：字符终端显示UTF-8字符串&lt;/p&gt;

&lt;p&gt;but , there is some problems ….&lt;/p&gt;

&lt;p&gt;1) why not extending kernel VGA font to support UNICODE?&lt;/p&gt;

&lt;p&gt;2) why so many duplicated code ? too many copy&amp;amp;past,  Linus 
      won’t take this patch&lt;/p&gt;

&lt;p&gt;3) Why all char  &amp;gt; 127 should be double-weight ?&lt;/p&gt;

&lt;p&gt;4) I need to use setfont to set UNICODE font, with this patch,
    I can’t. So , I made some changes … 
    to eliminate all problems above.&lt;/p&gt;

&lt;p&gt;so ,here is the git repo&lt;/p&gt;

&lt;p&gt;webgit view 
http://repo.or.cz/w/linux-2.6/cjktty.git&lt;/p&gt;

&lt;p&gt;git clone URL :
   git://repo.or.cz/linux-2.6/cjktty.git
   http://repo.or.cz/r/linux-2.6/cjktty.git&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>内核TTY控制台的问题</title>
   <link href="https://microcai.org/2010/12/28/ttyproblem.html"/>
   <updated>2010-12-28T00:00:00+00:00</updated>
   <id>https://microcai.org/2010/12/28/ttyproblem</id>
   <content type="html">&lt;p&gt;最近因为编写TTY控制台的UNICODE字体支持补丁，拜读了控制台的代码，诶，妈呀，这个是什么代码哟 ……&lt;/p&gt;

&lt;p&gt;TTY 控制台最开始的时候是使用 VGA 显卡的 TEXT模式的。 VGA TEXT 模式有个非常特别的地方就是，显存被映射到了一个固定的位置, 大约接近  1MB 的下方。 两个字节一对。第一个字节指示下一个字节的属性。第二个字节指示一个字符 index. 而这个字符表包含了 255 个字符，在 Windows 中， 这个字符集被称做 OEM codepage, 前127个字符和 ASCII 是一样的. 这样只要向对应的地址写入字符属性和字符的代码，就可以在对应的屏幕位置显示出对于的文字。这TTY 控制台就依据 VGA 字符模式写开了。每个 tty 终端都对应一个结构体。&lt;/p&gt;

&lt;p&gt;这个结构体里有一个 vc_screenbuf 的成员，里面就是按照 VGA text 模式保存的整个屏幕的字符。切换终端的时候，就是一个 memcpy 把这个地方的字符拷贝到显存里，就完成了终端显示的切换。&lt;/p&gt;

&lt;p&gt;问题就出在这里。vc_screenbuf 完全是依据 VGA Text 模式设置的。哪里有地方给你保存 UNICODE 编码啊，只有 255 个字符哦 ……&lt;/p&gt;

&lt;p&gt;直接改成  32bit/32bit pair 不就行了？&lt;/p&gt;

&lt;p&gt;NO~NO~NO~NO~&lt;/p&gt;

&lt;p&gt;不行！ 因为完全和 TTY 无关的别的代码都已经按照现行的结构来写了，这样一改就会大动干戈, 改掉很多很多不是控制台部分的代码。而且，控制台其他部分的代码，也都是直接按照这种格式去操作的，完全没有用宏去封装，改起来难度非常大。&lt;/p&gt;

&lt;p&gt;我曾经花了2天时间去做这种改动。修改了超过一千行代码 … 结果 … 还是改不动。
太多地方已经很顽固的假设这种布局了。&lt;/p&gt;

&lt;p&gt;那么，把 字符和属性分开？ textbuf 和 attrbuf ? 其实就相当于重写。
我也花了2天时间这么搞 .. 结果 tty 部分的代码终于改好了，结果发现内核的很多其他部分也要修改 … …&lt;/p&gt;

&lt;p&gt;什么叫牵一发而动全身？这就是。&lt;/p&gt;

&lt;p&gt;最后，我只得在老代码上缝缝补补，做了点小聪明似的 hack 来保证中文显示。&lt;/p&gt;

&lt;p&gt;但是，我知道，这样的 hack 是绝对进不了官方内核的。我必须想想的办法，改掉这种被动的局面。 尽量低耦合。做到漂亮的 clean up。这样内核开发者才能接受。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>内核要集成glibc么</title>
   <link href="https://microcai.org/2010/12/24/libcinkernel.html"/>
   <updated>2010-12-24T00:00:00+00:00</updated>
   <id>https://microcai.org/2010/12/24/libcinkernel</id>
   <content type="html">&lt;p&gt;往往内核添加了一个功能， glibc 要花很久才会用上。本来linux 那边为这个功能是否进入内核已经吵半天了，glibc
这边又要为是否使用这个内核新特性再次吵架半天 (glibc 不是 Linux 专有的，还得考虑 BSD (虽然人家也不用 glibc)
SysV Windows(诶，这没办法) ， 还有 sun 那消亡的 *** , 还有, 自家的 Hurd  wink.gif
然后，总之，这样新特性让人的接受上。。。 太慢了。&lt;/p&gt;

&lt;p&gt;说近点的，fnotify glibc还没有对应的包装函数呢，futex 和 NPTL 又是花了许久才进入主流的。libc 是 app
和内核的桥梁，libc 理应快速跟上内核的接口变化 .. 但是 … … glibc 和 内核不是一块开发的，所以，这只是理想罢了。
glibc 还要去兼容不同版本的内核呢！
而内核也要去兼容不同版本的 glibc . 双方都背负了太多的历史包袱。glibc 至今保留 LinuxThreads
兼容2.4版本的古老内核。Linux
对已经没用，甚至有bug（接口的问题导致一些bug是必须的）的系统调用也必须保留，随知道用户会用哪个版本的glibc呢？虽然新的glibc
会使用新的调用，但是提供和老的调用一致的 API 来兼容，但是，用户只升级内核而不升级 glibc 是常有的事情. .. 就算升级了
glibc … 你新版本的 glibc 一定就用上内核的新接口？！？！？！？！ 还是再等几年等 glibc 的开发者吵架结束吧&lt;/p&gt;

&lt;p&gt;于是乎，Linux 的大牛们再次使出绝招： 让 libc 变成 VDSO 进驻内核。&lt;/p&gt;

&lt;p&gt;{
这里普及一下 VDSO 这个小知识，知道的人跳过，不知道的人读一下 biggrin.gif
VDSO 就是 Virtual Dynamic Shared Object … 就是内核提供的虚拟的 .so , 这个 .so
文件不在磁盘上，而是在内核里头。
内核把包含某 .so 的内存页在程序启动的时候映射入其内存空间，对应的程序就可以当普通的 .so 来使用里头的函数。比如 syscall()
这个函数就是在 linux-vdso.so.1 里头的，但是磁盘上并没有对应的文件. 可以通过 ldd /bin/bash 看看
}&lt;/p&gt;

&lt;p&gt;这样，随内核发行的 libc (注意，VDSO只是随内核发行，没有在内核空间运行，这个不会导致内核膨胀。)
就唯一的和一个特定版本的内核绑定到一起了。这样内核和libc都不需要为兼容多个不同版本的对方而写太多的代码 … 引入太多的 bug 了&lt;/p&gt;

&lt;p&gt;当然， libc 不当当有到内核的接口，还有很多常用的函数，这些函数不需要
特别的为不同版本的内核小心编写，所以，我估计Linux上会出现两个 libc , 一个 libc 在内核，只是系统调用的包裹，另一个
libc 还是普通的 libc ， 只是这个 libc 再也不需要花精力去配合如此繁多的 kernel 了 …..&lt;/p&gt;

&lt;p&gt;姑且一个叫  klibc, 一个叫 glibc :
… printf() 这些的还在 glibc  。 open() , read() , write(), socket()
这些却不再是 glibc 的了，他们在 klibc 。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>遗忘的语言</title>
   <link href="https://microcai.org/2010/12/21/forgottenLANG.html"/>
   <updated>2010-12-21T00:00:00+00:00</updated>
   <id>https://microcai.org/2010/12/21/forgottenLANG</id>
   <content type="html">&lt;p&gt;WEB , 没错，我指的就是 WEB . 但是不是那个用 浏览器堆积起来的 WEB , 而是大师高爷爷随 TeX 推出的 WEB 编程语言。&lt;/p&gt;

&lt;p&gt;WEB 编程语言，让编程顺着人的思路，而不是机器的思路。&lt;/p&gt;

&lt;p&gt;最近为了写论文，也让我有更多的时候去接触 TeX . 突然很想知道现在用的 TeX 是不是高教授写的，还是后人重新实现的 … 然后去搜寻 TeX 源码 ，终于被我发现， TeX 其实是用 WEB 语言完成的。经处理后变成 PASCAL 代码 + TeX 文档。 PASCAL 代码经 web2c 翻译为 现代 C 程序，然后就可以使用 gcc 编译了。所以，现代TeX发行版里包含的 tex 程序，确实是高教授写的。大师的写程序，居然都是用的不一样的语言啊！&lt;/p&gt;

&lt;p&gt;粗看 WEB 文件，发现除了描述看不到多少代码。几乎都是文档。其实就是思路。 WEB 程序保留了人的思路。完整的保留了人的思维。整个程序的结构也几乎就是安装人的思维去编写的。&lt;/p&gt;

&lt;p&gt;WEB 语言也不限于 PASCAL, 现在有了 CWEB , 就是把 C 结合进 WEB 语言中。GOOD, 有空了试试去。&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>北京之旅</title>
   <link href="https://microcai.org/2010/12/20/tobeijing.html"/>
   <updated>2010-12-20T00:00:00+00:00</updated>
   <id>https://microcai.org/2010/12/20/tobeijing</id>
   <content type="html">&lt;h1 id=&quot;之所以要去北京是因为参加红帽的笔试--废话大家都知道了-讲讲这段五味瓶的日子吧&quot;&gt;之所以要去北京，是因为参加红帽的笔试  (废话，大家都知道了) ，讲讲这段五味瓶的日子吧&lt;/h1&gt;
&lt;p&gt;恩，早上起来打开电脑 … 发现显示器开不起来 … 开开关关多次，亮了 … 以为升级系统导致的显卡驱动问题 …..
算了，现在没空，能用就行 …&lt;/p&gt;

&lt;p&gt;然后挂 IRC , 继续调式内核 … ..&lt;/p&gt;

&lt;p&gt;恩恩，不知道怎么回事，IRC 聊起来红帽的招聘 … kaio 说某人等了7个月 … 我说我都等3个月了 … kaio
问我面试了？ 我说没 .. 我等笔试都等3个月了 …（ 当时不知道 alexcpio 说的 下月4号的有米有我的份呢） kaio
说，人家是面试后等了7个月 …… 我 囧 ….
 然后正要说什么，突然手机dongdongdong …. wow. 说曹操曹操到，激动啊 … …&lt;/p&gt;

&lt;p&gt;接到通知赶紧买票，一查，火车票比飞机票还贵，情何以堪情何以堪啊 … .. 于是就买飞机票去了… …
诶，都是半辈子的积蓄啊，顺便 BS 一下红帽，就不能来杭州招聘啊。&lt;/p&gt;

&lt;p&gt;这行是解决了，住宿呢？旅馆？我过去怎么也得待个四五天的，几百一晚，我不成乞丐了啊….
恩，以前听说某人发光是 twitter 结果就免费周游世界去了（顺便 BS 一下这家伙）… …
恩，说不定好多童鞋在北京的 … 兴许能帮忙找个便宜的旅店，抱着试一试的态度发贴求助。
回应的人好多啊 … 而且给于了我不曾想到的关注和帮助, 让我一个一直被人忽略的人激动得热泪盈眶 .. .. …
还有童鞋提议乘我去北京，再聚会一次… 我内牛满面啊… …
最后duyue说我可以住他宿舍… … 额，免费的？ 恩，应该是 .. 这得节约多少钱啊 .. 偶穷人一个，能省钱就好 …….&lt;/p&gt;

&lt;p&gt;duyue居然就在北航 .. .. … 诶，牛校出牛人 … … 让偶一个小学校来的情何以堪情何以堪… …&lt;/p&gt;

&lt;p&gt;恩，记录下飞机票信息还有duyue的信息，再打印双份以防万一 …..&lt;/p&gt;

&lt;p&gt;然后? .. 然后我的电脑在一次待机中电源管理关闭了显示器 … 结果我的显示器开不起来了 …. 重启电脑又开起来了 … 驱动问题？&lt;/p&gt;

&lt;p&gt;次日，照旧早上起来开机 .. .. ..  ….  开机 … … … 开机 … .. ….
我的显示器就这样牺牲了。为什么一定要这个时候呢 .. 恩恩，跑题了，关于显示器的事情就不说了&lt;/p&gt;

&lt;p&gt;呵呵，这也促成了我使用控制台 (小电脑比较慢，用 X == 自残)。lyxint 好厉害啊，居然三下2下帮我搞定了 weechat 的问题。北京牛人辈出啊～～～&lt;/p&gt;

&lt;p&gt;这样花一天习惯了小电脑和控制台&lt;/p&gt;

&lt;p&gt;接下来太阳又一次西落东起 。。 ==, 还没起来，我就起床敢飞机咯
第一次去机场 … … 机场给我的第一个感觉 。。。 这人也太少了吧？！！！！ 想想看火车站和汽车站 … 那是人山人海啊 … …&lt;/p&gt;

&lt;p&gt;机场有 Wifi … 太好了 … … 恩？ 这个 ESSID 是？！！ CMCC … …
得，免费不了了，刚刚在机楼发现一个 天翼VIP 区.. 上网  30RMB … … 还好在 VPS 上搭建了一个  53 端口的代理
… (不过，后来才发现其实这个 CMCC 就是免费的 …. wink.gif&lt;/p&gt;

&lt;p&gt;在杭州，基本上都是737 … 我没看到过一架大飞机 … …  737 太容易识别了，那特色的引擎外壳 .. 非圆形的 …
…  以前疯狂迷恋 X-plane 的时候学了一堆飞机相关知识 …..&lt;/p&gt;

&lt;p&gt;( PS : 真正坐到飞机上的时候，那种失重和超重的感觉是模拟不出来的，而失重的时候受内耳影响，人会有下意识的动作，这个会害飞机坠机的！..
这是玩 X-plane 永远学不到的。必须实地飞行去克服 …. wink.gif&lt;/p&gt;

&lt;p&gt;终于到北京了 … … 辗转到了北航 … …
和 duyue 碰头的时候 … 恩，老实说，心里好紧张啊 … … 和一个没有见过的人碰面 … …
他会长虾米样子呢？？？？ … … 呵呵，最终见到的时候心舒缓了一下 … …  看上去还可以，不至于太凶 … …&lt;/p&gt;

&lt;p&gt;在机场的时候就意识到了，北京的们似乎都是双层的，而且内外的们是错开的, 不在一条直线上  … …  北京特色吧&lt;/p&gt;

&lt;p&gt;做公交的时候，我很诧异的发现，居然还有有售票的 … … … 而且是刷卡上车，刷卡下车  …
杭州也有分段计费的，但是靠的是自觉 … … 你投一块钱坐5块钱的路也行（其实司机眼很厉害，基本一眼就看出来你会坐到哪里…..）
…. … 北京也太防着市民了吧 … … 呵呵，大城市，什么人都有 … … 兴许是这样原因？&lt;/p&gt;

&lt;p&gt;到北航, 刷卡进寝室. .. 刷卡去超市 … 在中关村也是刷卡吃的饭 … 北京就是个卡卡卡 … 以后就卡京好了 … …
… 我这么说呢， duyue 赶紧掏出一叠的卡 … … 一叠的卡 … …一叠的卡 … …&lt;/p&gt;

&lt;p&gt;==， 你说刷卡进寝室 ???  恩，是的，怪异的事情。呵呵，后来才发现是因为生活区没有大门 … …&lt;/p&gt;

&lt;p&gt;这寝室还真难混进去啊 … … 苦了duyue, 几次陪我在风中瑟瑟发抖到阿姨睡觉了才进去 ……
因此决定最后一个晚上我就住旅馆好了，呵呵，不能让duyue 周日晚上还这么累 …&lt;/p&gt;

&lt;p&gt;北京真是哪里都有暖气 … 寝室居然也有。。。 我为什么说居然呢？ … … 晚上睡觉热死我了 ….
同学们居然整天不开寝室的门，几个爷们居然不生病 …..  气流不畅快，我每天早上起来喉咙都难受 … .. 我向来晚上都是开窗睡觉的
… …&lt;/p&gt;

&lt;p&gt;公交车真是慢到和走路一个速度 … … 恩，首堵见识到啦。其实解决办法也没那么难 … 不需要单双号限行… …
因为很多人住的太远，不得不坐车开车啦。住的远还不是因为住的近住不起（还不说，都是白领在做公车 …
民工都是住的工地，没有公交压力….. ）！ 每天浪费那么多时间在路上，对北京来说，每天浪费的都是多少人年的生产力啊！&lt;/p&gt;

&lt;p&gt;那种在民房办公的创业黄金时代哪里去了？
没有创业，一个地区就会失去活力。不管你这里有多少跨国企业，多少500强。公司迟早会倒闭的，没有不倒闭的公司。只有新兴公司不断产生，不断竞争，这个地方才有活力
…. 让市场淘汰，而不是门槛 …&lt;/p&gt;

&lt;p&gt;马化腾 50w 创业。现在呢？ 50w 都不够办公室的租金 … .. 你创业个毛去啊。民房创业？得，买的起么？租的不允许啊 …&lt;/p&gt;

&lt;p&gt;红帽真牛，居然请到我这么一个大牛 …. 还不待见 …
结果去新浪碰运气 ..  居然被我碰上了。。。 可惜，我还没毕业 … 所以啊… 诶，难做决定哦 …. …&lt;/p&gt;

&lt;p&gt;聚会很和谐 … 见了很多人，有牛人也有菜鸟。呵呵。dongdong 居然是 emacs 党 …&lt;/p&gt;

&lt;p&gt;回来的时候做上了空客 A320 … 呵呵，去一次北京把两家的飞机都坐上了。波音飞机那个叫猛啊，加速很快，才几秒钟就上天了，空客的则在地面滑久多了。&lt;/p&gt;

&lt;p&gt;天上的空气密度很不均匀，所以飞机的升力变化很多，在飞机里面的感觉，好像在做公交车 … 一上一下的 … ….
呵呵。下的时候，就感觉，完了完了，升力没有了。坠机了 …..
上的时候，超重 …. …  呵呵。这是 737
320情况好些，毕竟比 737大多了，所以平稳多了。&lt;/p&gt;

&lt;p&gt;可惜没坐到 787 …  :(&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>修复了三星显示器</title>
   <link href="https://microcai.org/2010/12/16/fixsumsumLCD.html"/>
   <updated>2010-12-16T00:00:00+00:00</updated>
   <id>https://microcai.org/2010/12/16/fixsumsumLCD</id>
   <content type="html">&lt;p&gt;诶，出发去北京的前两天，也就是十一月三十日 … 早上一大早起来开机。我可怜的三星740NW显示器就再也不工作了。 
蓝色的电源指示灯快速的闪动着，可显示器就是不给开起来 ….&lt;/p&gt;

&lt;p&gt;恩，应该是坏掉了，也许问题不那么严重，上网看看有米有什么便捷的方法解决吧！&lt;/p&gt;

&lt;p&gt;呵呵，还好我先见之明 .. 还有一 EPC. ..&lt;/p&gt;

&lt;p&gt;马上开起来 .. 囧。 前些日子为了测试 systemd … 把系统搞坏了. … 马上重装！ 于是花了几个小时重装 了 archlinux …. (额，我 gentoo 控。但是我需要马上用电脑… gentoo 编译 .. 我了个去，将就着 用 arch 先)&lt;/p&gt;

&lt;p&gt;终于到最后安装上 chrome 了，呵呵。 恩，还没安装 ibus … 得，不装先， …&lt;/p&gt;

&lt;p&gt;google 了好些，终于发现很多人也有此症状（对三星的质量先日一下）。 专家说，是 inverter 坏掉了 ….&lt;/p&gt;

&lt;p&gt;我了个去 … 这下我自己修不好了 …..&lt;/p&gt;

&lt;p&gt;诶，得。再买一个去吧 .. 55555555&lt;/p&gt;

&lt;p&gt;既然再买一个，恩恩，这个咱就拆了吧 … .. 说实话，我老早就想拆它了 …. (&lt;em&gt;^__^&lt;/em&gt;) 嘻嘻……&lt;/p&gt;

&lt;p&gt;说干就干 … … 我蛮力的拆解了这款 740nw …&lt;/p&gt;

&lt;p&gt;好家伙，LCD 显示器也忒简单了吧！&lt;/p&gt;

&lt;p&gt;就 3 个东西。 一个板子负责供电，一个板子负责接受 D-SUB 接口的输入，然后用一排线连接到液晶屏幕上。供电的那个也有2个线接过去，看样子就是所谓的灯管了 ….&lt;/p&gt;

&lt;p&gt;恩，就是那个负责供电的电路板烧掉了啊～～&lt;/p&gt;

&lt;p&gt;维修站铁定有。我打电话问问…&lt;/p&gt;

&lt;p&gt;………..&lt;/p&gt;

&lt;p&gt;我了个去，不外卖 … 我说我付维修费 … 你们就当给我修了 .. 你们还不用麻烦就赚了双份的钱 … .. 不行？非得跑你们那 里一趟？外面还下雨呢 … 维修费 120 ？ 材料另算？？ 宰人的呢！ 直接打电话到三星去 .. 一个德行 …. md，我投诉你们 去 … 12315 也和他们一个德行 ….&lt;/p&gt;

&lt;p&gt;诶 … 难道就没有地方买到这种配件？&lt;/p&gt;

&lt;p&gt;==&lt;/p&gt;

&lt;p&gt;网购去 … 试试看么 …&lt;/p&gt;

&lt;p&gt;哈哈，果然有！！！！ 当即让LP买下 … 额，千叮咛万嘱咐，一定今天发货的才买 …..&lt;/p&gt;

&lt;p&gt;恩，安心的用小电脑先 ……&lt;/p&gt;

&lt;p&gt;结果，第二天等到了下午都没有快递过来，我就去查了一下 .. NND ，居然今天还没发货！ 你丫的昨天没货还满口答应！赶紧退款，另外给了个差评。&lt;/p&gt;

&lt;p&gt;诶，马上就去北京了。就不折腾先。&lt;/p&gt;

&lt;p&gt;12-02～～12-06 北京之旅 (我会另外写博文介绍的)&lt;/p&gt;

&lt;p&gt;06 号回来的时候就在车上赶紧让 LP 买一个。 … 恩，这次大意了，没有自己去挑货 …&lt;/p&gt;

&lt;p&gt;07 号下午终于等来了快递 …. 兴奋了一阵 ….&lt;/p&gt;

&lt;p&gt;然后马上开始修咯。&lt;/p&gt;

&lt;p&gt;==, 这大小？ 。。。 居然不一样！！！ 。。。 
==，这. .. 是4灯的版本？！！！ 我要的是2灯的！！！。。。。诶，不是自己去挑，果然有问题 … 失策失策&lt;/p&gt;

&lt;p&gt;只接2个应该也行的吧？！&lt;/p&gt;

&lt;p&gt;于是，动手改造容器，使得可以放下这个大一点的电路板 …..&lt;/p&gt;

&lt;p&gt;改到一半。突然发现，得测试一下先啊 … …&lt;/p&gt;

&lt;p&gt;于是… 外壳不装，先连接好电路 … 信号接小电脑上 …&lt;/p&gt;

&lt;p&gt;我了个去 … 灯不亮灯不亮灯不亮灯不亮灯不亮灯不亮灯不亮 。。。。。&lt;/p&gt;

&lt;p&gt;上网一查，是高压保护，非得4个灯都接上才行 …..&lt;/p&gt;

&lt;p&gt;得，网上给的去掉高压保护的都不适用于这个电路板的 IC …..&lt;/p&gt;

&lt;p&gt;得，重新买一个吧，快递20，电路板才25,退货不划算 … …&lt;/p&gt;

&lt;p&gt;已经晚上了，最快快递买的也得后天到啊～～～&lt;/p&gt;

&lt;p&gt;恩，直接找杭州的卖家 … 明天直接去拿，省了快递，快点….&lt;/p&gt;

&lt;p&gt;要不怎么说杭州就不适%E&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>P2P 终结者 for Linux</title>
   <link href="https://microcai.org/2010/11/02/p2pkiller.html"/>
   <updated>2010-11-02T00:00:00+00:00</updated>
   <id>https://microcai.org/2010/11/02/p2pkiller</id>
   <content type="html">&lt;p&gt;很长一段时间，都在渴望一个 for Linux 的 P2P 终结者。&lt;/p&gt;

&lt;p&gt;但是，谁让我们是小众群体呢？所以，没有。&lt;/p&gt;

&lt;p&gt;但是，今天我突发奇想，找到了！用脚本实现了 P2P 终结者！&lt;/p&gt;

&lt;p&gt;问题是这样的，首先我需要一个 arp 欺骗工具。呵呵， dsniff 就是！&lt;/p&gt;

&lt;p&gt;执行  arpspoof 就可以进行 ARP 欺骗了。 不过，呵呵，我只对需要控制的主机进行 ARP 欺骗，比如 192.168.1.100&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;arpspool -i eth0 -t 192.168.1.100 192.168.1.1 
arpspool -i eth0 -t 192.168.1.1 192.168.1.100 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样成功的让 192.168.1.100 到  192.168.1.1 之间的通讯全部通过自己了&lt;/p&gt;

&lt;p&gt;然后，我打开 内核的 IP 转发。
 echo 1 &amp;gt; /proc/sys/net/ipv4/ip_forward&lt;/p&gt;

&lt;p&gt;呵呵，现在，怎么限速就是我的事情啦！ 可以使用 tc 限速，也可以简单的时候 iptables 限速！
比如，使用  iptables 的 limit 匹配模块来限速. 一秒钟是能发送和接收 3 个数据包，这样速度就限制到 4k/s 了&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;iptables -A FORWARD -s 192.168.1.100 -m limit --limit 3/s -j ACCEPT          
iptables -A FORWARD -d 192.168.1.100 -m limit --limit 3/s -j ACCEPT                 
iptables -A FORWARD -s 192.168.1.100  -j DROP                                                
iptables -A FORWARD -d 192.168.1.100  -j DROP                                             
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>一个Sqrt函数再次引发的血案</title>
   <link href="https://microcai.org/2010/10/16/somethingaboutsqrt.html"/>
   <updated>2010-10-16T00:00:00+00:00</updated>
   <id>https://microcai.org/2010/10/16/somethingaboutsqrt</id>
   <content type="html">&lt;p&gt;这些神人啊，开平方居然有这么快的算法！！！
于是我决定看看 glibc 是怎么实现的！
如果 glibc 比较慢，我一定要改成神人的算法重新编译 glibc ！！！&lt;/p&gt;

&lt;p&gt;等等！先写一个程序测试两种算法的速度&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#include &amp;lt;math.h&amp;gt;

float magic_sqrt(float number)
{
    long i;
    float x, y;
    const float f = 1.5F;

    x = number * 0.5F;
    y  = number;
    i  = * ( long *)    &amp;amp; y  ;
    i  = 0x5f3759df - ( i &amp;gt;&amp;gt;  1 wink.gif     ;
    y  = * ( float * wink.gif    &amp;amp; i  ;
    y  = y * ( f - ( x * y * y wink.gif wink.gif ;
    y  = y * ( f - ( x * y * y wink.gif wink.gif ;
    return number * y;
}
#define TIMES 2000000000
int main(int argc, char argv[0])
{
	unsigned int i;
  if(argv[1]==&apos;s&apos;)
  {
    for(i=0;i &amp;lt; TIMES; i++)
    {
	  sqrt(200.0);
    }
  }else
  {
    for(i=0;i &amp;lt; TIMES; i++)
    {
	  magic_sqrt(200.0);
    }
  }
  return 0;
} 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后用 time ./a.out s 和 time ./a.out m 来测验两个开发算法的速度。
哥震惊了！！！ 一样快！！！莫非 glibc 也使用了神一样的 … ?????&lt;/p&gt;

&lt;p&gt;于是经过漫长时间的下载， 解压 ， grep 之后，我终于找到了我要的 glibc 中实现开方算法的文件&lt;/p&gt;

&lt;p&gt;sysdeps/x86_64/fpu/e_sqrt.c&lt;/p&gt;

&lt;p&gt;哥再次震惊了！哥再次吐血了！！！&lt;/p&gt;

&lt;p&gt;居…居…居居然 ….. 只有一条指令&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;double
__ieee754_sqrt (double x)
{
  double res;

  asm (&quot;sqrtsd %0, %1&quot; : &quot;=x&quot; (res) : &quot;x&quot; (x));

  return res;
} 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;看来以后我可以放心的使用 glibc 的数学函数了 … 事实证明， glibc 总是使用的最快的方法。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>如何避免使用 root 权限调式 libpcap 程序</title>
   <link href="https://microcai.org/2010/10/15/debug-libpcap-program-without-root.html"/>
   <updated>2010-10-15T00:00:00+00:00</updated>
   <id>https://microcai.org/2010/10/15/debug-libpcap-program-without-root</id>
   <content type="html">&lt;p&gt;工作原因，经常用 libpcap 写程序。&lt;/p&gt;

&lt;p&gt;可是调式却一直是个大问题。&lt;/p&gt;

&lt;p&gt;即便是非常麻烦地每次编译后 setuid 
gdb 也是不能调式 setuid 程序，gdb 会使 setuid 失效。
一直以来的解决办法都是原始的sudo + printf ， 而无法单步调式。实在有时候需要单步了，就 sudo eclipse 启动了
结果调式完毕之后还需要
 sudo chown cai:cai ~/workspace -R&lt;/p&gt;

&lt;p&gt;灰常的不爽&lt;/p&gt;

&lt;p&gt;后来发现了 gdb-server&lt;/p&gt;

&lt;p&gt;可以用 sudo gdb-server 启动要调式的程序，再在 eclipse 里选择 gdb/server 作为调式程序。
这样就不必为了调式而整个让 eclipse 启动到 root 环境了&lt;/p&gt;

&lt;p&gt;可是，还是很不爽。每次调式前都要
 sudo gdb-server localhost:5000 我的程序&lt;/p&gt;

&lt;p&gt;后来，终于发现，我可以先用 dumpcap 抓取一定的包，然后保存到一个文件中.
只要把原先打开网卡的代码稍微改一下变成 
pcap_open_offline
 就可以了
而从文件开始读取，可以模拟非常巨大的网络流量，很考验处理程序.&lt;/p&gt;

&lt;p&gt;而通过 有名管道，又可以轻易的支持 在线抓包调式。使用管道的时候经过我的测试，发现必须这样 
dumpcap -w - &amp;gt; /tmp/fifo 
 写才行。然后我的程序就可以把管道作为文件直接打开
发布的时候，代码再改回来，或则干脆就不改，作为一个命令行选项。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>GB系列编码 比 UNICODE 先进，是最先进的编码</title>
   <link href="https://microcai.org/2010/10/11/gbkrocks.html"/>
   <updated>2010-10-11T00:00:00+00:00</updated>
   <id>https://microcai.org/2010/10/11/gbkrocks</id>
   <content type="html">&lt;p&gt;首先 UNICODE 里头只有 utf8 能兼容 ascii,所以，这已经是 UNICODE 的一大失败了。
接着我们将两个大方面的比较&lt;/p&gt;

&lt;p&gt;一，汉字方面
1.1 汉字编码排列方面
UNICODE 编码中，汉字的排列毫无规律可言。 （0分)
GB 系列编码中，汉字都是依据读音笔画排列下来的。非常的有规律。 (1分)&lt;/p&gt;

&lt;p&gt;1.2 编码效率方面
UNICODE 在实现上，只有 utf8是兼容 ascii的，我们只考虑 utf8
utf8汉字大部分是 3byte编码，少数汉字为 4byte (0分）
GB系列编码中，汉字几乎是 2byte编码，少数汉字为 4byte编码（GB18030标准） (1分)&lt;/p&gt;

&lt;p&gt;二，非汉字部分
非汉字部分就是英语咯，还有欧洲语言，还有特殊字符。其中英语是一样的，不用比较
2.1 非ascii字符集支持
GB系列标准中，欧洲字母也是和汉字一样编入方案的。 (1分）
和 UNICODE 一样支持欧洲字母 (1分）
2.2 其他国家语言，包括繁体中文
GBK 中收录大部分繁体。在新的扩增标准 GB18030中，呵呵，保留了足够的码位来支持全世界的字符。 （1分）
UNICODE 一样 (1分）
2.3 非汉字编码效率
UNICODE 反而对很少用到的字符给比较小的码位，在 UTF8中表现为2byte ….  (0分）
GB系列编码中，汉字为2byte,常用的他国语言文字也是2byte，其他少用字符的由 GB18030扩展，为 4byte （1分）&lt;/p&gt;

&lt;p&gt;综合起来，UNICODE 就是一垃圾编码，给这个世界带来了混乱，早该废止！！&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>异步单线程的梦</title>
   <link href="https://microcai.org/2010/10/10/async.html"/>
   <updated>2010-10-10T00:00:00+00:00</updated>
   <id>https://microcai.org/2010/10/10/async</id>
   <content type="html">&lt;p&gt;很享受单线程带来的好处。&lt;/p&gt;

&lt;p&gt;第一：不用考虑锁。
第二：不用考虑线程切换开销。
第三：很锻炼人异步并发的思想。&lt;/p&gt;

&lt;p&gt;特别是第三点，我觉得最喜欢了。
非常喜欢异步执行，异步通知。
用一个线程，完成了所有的逻辑。而且不能阻塞在一个逻辑上。如果需要阻塞，就采用异步通知，等它ready了再处理。&lt;/p&gt;

&lt;p&gt;单线程异步并发用的久了
突然有一天碰上了 ALSA … … …
居然再也不能使用了。。。。。。
ALSA 的异步通知只有一种。那就是信号。
但是信号是不可靠的，而且信号数目有限，怎么能用来做异步通知呢？（所以也很讨厌 aio）
ALSA 居然不能方便的时候 poll/epoll 来进行通知，各种事件非得用 alsa_*(会阻塞的) 获得  …. 逼我用多线程。&lt;/p&gt;

&lt;p&gt;当然，PulseAudio 的话 … 由于是 socket 连接，可以继续你的异步梦了 ….. but .. who likes pa ?&lt;/p&gt;

&lt;p&gt;很多地方都可以异步单线程，而且都是使用的 epoll/poll 通知机制, 所以，任何可能会阻塞的地方，如果能使用 poll
那么，永远都不用当心你的梦会破灭 … (ALSA该死，破灭我一次)&lt;/p&gt;

&lt;p&gt;最后，用 Alan Cox 的名言来打击一下使用多线程的人：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  computer is a state machine. Threads are for people who can&apos;t program state machines. （计算机是一台状态机。线程是给那些不能编写状态机程序的人准备的。）
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;文件IO == 使用 poll/epoll
套接字 == 使用 poll/epoll
Direct IO == 使用 poll/epoll
Timer == 巧妙的编入 polll/eppll 的 timeout 参数
空闲  == 利用 poll/epoll timeout=0
抓包 == pcap_fileno 获得文件描述符，使用 poll/epoll
GUI == 获得 X11 连接的文件描述符，使用 poll/epoll
OpenGL == OpenGL is natively async with GPU , OpenGL 错误被合并入 X11 汇报
文件监视 == 返回的也是文件描述符
Netlink == 返回的也是套接字&lt;/p&gt;

</content>
 </entry>
 
 <entry>
   <title>简历上的神吹</title>
   <link href="https://microcai.org/2010/07/28/fuckingresume.html"/>
   <updated>2010-07-28T00:00:00+00:00</updated>
   <id>https://microcai.org/2010/07/28/fuckingresume</id>
   <content type="html">&lt;p&gt;实习的时候看过几十份求职简历，现在把我看过的简历的特点简单介绍一下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;神吹

     倒不是像唐骏那样撒谎，就是什么东西都写，其实没那么厉害。比如，有个人简历上写自己做过搜索引擎。把人找来一面试，连基本的 C 语言都不会。能吹吧？ 把团队里别人的功劳说成自己的，真 TM 恶，自己不过是一个混班的罢了。这是吹嘘的比较厉害的，还有稍微不厉害点的，比如明明英语基本不会，非说自己过了六级 (这也能写，真逗，参加辅导班过的六级和没有做准备就去考，档次不是一样的。)

 
鸡毛大的事情也当辉煌经历

    比如，考个四六级就当是辉煌了。操，要是有人觉得没什么好写的，就不写，那不是被招聘的人认为是六级都过不了吗？ 操，这些JB。直接导致不写鸡毛事情的人遭殃。

 
学过的就当会的
    这你要是不写吧，人家就觉得你连见识都米有。

都有个名字很好的学位证书。

    嗯，我是主动辍学的，不想继续耗费青春。如果你是个非要看证书的人，请离开。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;简历如海，你们这些投机取巧的人，把我们老实本分的人都欺负了。直接导致用人单位用更高的要求筛选其实要求没这么高的职位。这让老实人只能去做相对其实力要简单很多的职业。 这是对人力资源的浪费。&lt;/p&gt;

&lt;p&gt;好了，讨厌简历的原因写好了，就是说，考场作弊的人太多，坚持不作弊的人还值得去考试么？问题是，考官还不知道谁作弊了！真气人。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>关于机翼的升力</title>
   <link href="https://microcai.org/2010/07/15/aboutlift.html"/>
   <updated>2010-07-15T00:00:00+00:00</updated>
   <id>https://microcai.org/2010/07/15/aboutlift</id>
   <content type="html">&lt;p&gt;科普上，书本上，说升力来自压力差，压力差来自伯努力。 
是这样的吗？&lt;/p&gt;

&lt;p&gt;上边的空气为何要和下边的一起到达机翼后沿？这是没有根据的。 
升力确实来自压力差，但是压力差并不能简单的解释为流速差带来的。因为这样的话，倒飞就无法解释。&lt;/p&gt;

&lt;p&gt;根据伯努力原理，流体的压力分静压和动压。当流体流向于物体表明一致的时候，表明只能感受到静压，当于表明差垂直的时候，能感受到全部的动压和静压。&lt;/p&gt;

&lt;p&gt;飞行时，机翼的玄线和来流方向有个夹角，这个夹角叫迎角，这时，机翼下方受到了气流的动压。这个动压+静压比上表面的大多了，而且动压远远大于流 速差带来的静压差，所以所，迎角是机翼升力的主要原因。根据柯达效应，此时机翼上表面还能感受到负动压，负动压甚至大于下表明的正动压，所以，机翼的升力 主要是由上表面的吸力贡献的。&lt;/p&gt;

&lt;p&gt;这就是升力产生的原理，可不是什么流速差哦。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>For every bug, there is a ugly man</title>
   <link href="https://microcai.org/2009/09/28/foreverybugthereisauglyman.html"/>
   <updated>2009-09-28T00:00:00+00:00</updated>
   <id>https://microcai.org/2009/09/28/foreverybugthereisauglyman</id>
   <content type="html">&lt;p&gt;For every bug found in the softwre, there is a ugly man behind.
For too many days! I’ve been working on that bug! And the software has already ran in a hotel!
The only way that me can use to debug is using ssh plus vim.
No other way. I cannot use gdb, Because that will let the clients unable to surf online.
My program runs on a Linux router. Hang! Hang! There must be dead locks !
Where is it?! Then for many nights, I can’t sleep. Reviewing very code that acquiring locks. There is no mistake.
 ===============================
I have to use gdb. Then re-ran my progrm in gdb.
when it stopped response, I interrupt this stupid program. ….. strike!
The error happens when doing libnet_init() It must not be libnet’s bug. ………
Finally, I knew what’s wrong. memcpy() param 3 too lengh , in the other file. and other thread.&lt;/p&gt;

&lt;p&gt;For every bug found in the softwre, there is a ugly man behind.
 Now it plays on me.&lt;/p&gt;
</content>
 </entry>
 

</feed>
