菜菜博士 - 博士在网络的家
2024-03-21T08:33:54+00:00
https://microcai.org
沙雕动画 vs 腾讯动画
2024-03-21T00:00:00+00:00
https://microcai.org/2024/03/21/shadiao-ani-vs-tencent
<p>很多人说,看国产的动画片,配音不好。然后就跑去看日本片去了。更有奇葩的,要看日语配音的国产动画片。</p>
<p>其实,cv 不专业并不是主要原因。有人说那叫“母语羞涩”,在我看来也不是主要原因。</p>
<p>我认为,根本原因是台词出了问题。</p>
<p>一般人们常把语言分为口语和书面语。
其实,还有一个类型,它既不是口语,也不是书面语,而是专用于台词的语言。</p>
<p>人物的台词,即不能照搬口语使用,也不能直接使用书面用语。</p>
<p>最为明显的例子,当属《三国演义》。 三国演义里的台词,既不是原著上的书面语,也不是生活化的口语。它是那种通俗易懂的,一听就懂,但是又不是白话的语言。
让你一听就觉得,古人就应该这么说话。</p>
<p>其实何止古人。电视剧里的人,都应该这样说话。</p>
<p>其实,影视剧使用专门的语言,是业内本就早已熟知的事情。</p>
<p>只不过,这群业内人士,不从事动画片制作。</p>
<p>因此,腾讯上播放的那些所谓动画片,统统没有对台词进行优化。不仅仅没有进行优化,反而因为从业者对日本动画片的喜爱,不知不觉的搞出了日式汉语。</p>
<p>正是这个日式汉语,导致动画片的台词听着别扭。</p>
<p>日式汉语令人听感不佳的特征点有</p>
<ol>
<li>语速较慢。正常人说话的语速为每分钟 300 - 500 字。腾讯动画片里的人物台词语速不足每分钟200字。</li>
<li>台词极少出现成语和歇后语。即使是日常生活对话,也是要经常使用成语和歇后语的。腾讯动画片的从业者的日本师傅较少使用台词和歇后语是主要原因。因为日本人汉语水平较低,使用成语会增加观众理解障碍。</li>
<li>会不经意的夹杂一些日语梗。除了对日本动画片较为熟悉的观众,无法理解。而导演不自知。</li>
<li>语序经常错乱为日语语序。</li>
<li>台词词汇上会偏向早年 CCTV 引进日本动画片的时候使用的翻译词汇。而不是本地人所使用的更接底气的词汇。</li>
<li>待补充</li>
</ol>
<p>这就是为何,我更喜欢熊猫人动画片。因为以上问题,熊猫人动画片统统没有。</p>
<p>不仅仅没有,熊猫人动画片在台词上,会更有文艺范。因为我看的熊猫人动画片,以穿越剧为主。
因此,他们的台词设计上,就会更加学习三国演义。</p>
<p>虽然熊猫人动画片用 AI 进行配音,看似缺少了感情。但是,台词功底完爆腾讯动画片。观之反而越来越有味。</p>
新型单相电机调速器
2024-03-06T00:00:00+00:00
https://microcai.org/2024/03/06/new-ac-motor-speed-controller
<h1 id="传统立扇调速方案">传统立扇调速方案</h1>
<p>传统立扇方案主要采用抽头调速方法,通过机械调速开关改变主绕组与辅助绕组接线方式。一般抽头调速的风扇电机是基于满载工况优化设计,在电机高挡位运转时,绕组磁场接近正交的圆形,噪音和效率表现相对较好,而中低挡位时主绕组和副绕组的结构被改变,绕组合成磁场偏向椭圆,电机运行失去对称性,转矩脉动分量增加。并且中低挡位工况下,运行绕组温升更高,电机偏离了原有优化的状态,其输入功率也没有成比例下降,还经常伴随着恼人的电磁噪音。从某种意义来说,抽头调速是靠绕组间“出力抵消“,消耗能量来达到调速目的。此外,因为多抽头绕组结构复杂,实际生产中电机定子绕线需要人工配合,不利于电机标准化生产。</p>
<p><img src="/images/28469021.png" alt="抽头调速" /></p>
<h1 id="交流调压控速">交流调压控速</h1>
<p>实际上,电扇电机因为功率较小,因此可以对只使用变压器进行调速。事实上中间抽头的做法,就是让电机绕组与抽头形成自耦变压器。然后实际上相当于降压运行。并不需要变频就可以完成简易的调速任务。</p>
<p>但是,抽头法有2个缺点。其一为档位固定。制作的时候做几个抽头就只有几个档位。其二为低速运行的时候效率急剧下降。</p>
<p>所以,如果使用交流调压器,其实也可以对电机进行调速。</p>
<p>但是,交流调压器使用工频变压器就决定了,成本低不了。</p>
<p>传统上,直流降压使用 Buck 电路。</p>
<p><img src="/images/20200414104632358.png" alt="Buck" /></p>
<p>所需元件超少,成本非常低。</p>
<p>但是,只能对直流电起作用。</p>
<p>如果有一种对交流电直接进行 Buck 变换的电路,岂不是能大大降低交流电的降压成本?然后超低成本实现单相电机的调速功能?</p>
<h1 id="交流-buck-降压电路">交流 Buck 降压电路</h1>
<p><img src="/images/Snipaste_2024-03-06_02-26-04.png" alt="ACBuck" /></p>
<p>在上图电路里, 4个 MOS 管构成了一个 H 桥。但是这个 H 桥的输入端,却是 左边右边一个火线一个零线。</p>
<p>传统的H桥,左半桥和右半桥,输入都是接的直流母线。
但是,这个 ACBuck 的 H 桥,左右却是接的不同的输入。一个火线一个零线。 H桥的下管源级,倒是接的“地”。</p>
<p>这个“地”,其实是另一处,火零经过全桥整流后的负极。</p>
<p>在交流的正半周, Q3、Q4 构成一个 buck 降压电路。Q1 Q2 工作在反向电压,由于其体二极管的存在,不论开关管是打开还是关闭,电流都会畅通无阻的流到零线。</p>
<p>在交流的负半, Q1 Q2 构成了一个 buck 降压电路。Q3 Q4 工作在反向电压。由于其体二极管的存在,不论开关管是打开还是关闭,电流都会畅通无阻的流到火线。</p>
<p>因此,只要使用一个带死区控制的互补 PWM 信号,就可以无视交流电的正负周期,而直接对交流电进行 Buck 降压。</p>
<p>每个桥臂的下管,同时还充当续流管的作用。这还是一个同步 Buck 降压电路。</p>
<p>只需要调节 PWM 信号的占空比,就能调节输出的电压。</p>
TO-252 对比 TO-220
2024-03-03T00:00:00+00:00
https://microcai.org/2024/03/03/to252-vs-to220
<p>MOSFET,功率MOS,最常见的外形,就是 TO-252 和 TO-220 两种。</p>
<p><img src="/images/TO252-TO220.png" alt="封装对比图" /></p>
<p>上图左边的为 TO-252 封装。右侧为 TO-220 封装。</p>
<p>其实会发现,TO-220 剪掉中间引脚,折弯两边的引脚,也能转变为贴片形式。</p>
<p>在我设计低压变频器的时候,我一直使用的 TO-252 封装的 MOS。无他,低压的大电流MOS还是TO-252更常见。</p>
<p>后来,设计220v电压的高压变频器的时候,我转而使用 TO-220 封装的MOS。无他,我观各种使用650v 耐压的mos的电路板,通常使用 TO-220 的直插MOS。</p>
<p><img src="/images/photo_2024-02-08_13-17-06.jpg" alt="组装好了" /></p>
<p>但是,自从这个版本炸了MOS后,我改回了 TO-252。</p>
<p><img src="/images/Snipaste_2024-03-03_19-08-24.png" alt="改回TO-252" /></p>
<p>用回 TO-252 后,便没再炸MOS了。</p>
<p>虽然,板子的前后区别,可不仅仅是MOS改变了封装。</p>
<p>首先就是修改后的板子,栅极驱动到MOS管的距离更短了。意味着更小的走线寄生电感。</p>
<p>其次,驱动桥臂的上管源极和下管漏极之间的距离被无限缩小了。之前使用 TO-220 的时候, 上下桥臂距离可没这个版本近。</p>
<p>然后是增加了若干退耦电容。</p>
<p>这都为新板子的稳定运行立下功劳。只是不知道具体是哪个措施真正起了作用😂</p>
<p>于是,对比了 TO-252 和 TO-220 后,我得出了一个结论:TO-220 可以淘汰了。</p>
<p>首先,TO-220 需要使用散热片散热。背面金属片上的洞就是方便使用螺丝将mos牢牢固定在散热片上的。而且如果不是一个mos配一个散热片,还得配绝缘的导热垫片。因为TO-220背面的金属散热片,和中间的那个引脚都是漏极。</p>
<p>在pcb制造的过程中,不得不增加非机械臂可完成的操作————将散热片和mos用螺丝固定到一起。这对人工成本越来越贵的当下显然是徒增成本。</p>
<p>反观 TO-252 封装,减掉了漏极引脚。直接使用散热片当漏极。并且通过焊锡将散热片和 pcb 的铜箔牢牢焊接到一起。热阻远小于使用硅脂+导热垫片。</p>
<p>在大部分情况下,使用大面积覆铜就可以出色的替代独立的散热片。</p>
<p>其次,表贴型MOS的引脚更短。更有利于减小封装引入的寄生电感和寄生电容。这就意味着更高的开关速度,更小的开关损耗。当然,还有更不容易炸管。</p>
<p>为了能利用好 TO-252 的优势。接下来我设计pcb的时候,会注意给MOS管更大的散热用覆铜。</p>
<p><img src="/images/photo_2024-03-03_20-14-05.jpg" alt="走线绕开为覆铜" /></p>
<p>比如上图这个设计,特意将走线进行绕道,就为了能给 MOS 更大的覆铜面积以供散热。</p>
三电平 SVPWM
2024-02-29T00:00:00+00:00
https://microcai.org/2024/02/29/3level-svpwm
<p>炸机了。</p>
<p>制作低压变频器的时候,从未出现过炸机。但是,在高压变频器上,出现了。MOS管直接炸开。pcb铜箔炸飞。</p>
<p>究其原因,还是因为mos关闭时候产生的尖峰电压。</p>
<p>在研究解决方案的时候,碰到了三电平拓扑。</p>
<p>三电平拓扑输出三相电需使用12个 MOS 管。</p>
<p>那么问题来了,12个MOS管,如何控制呢?</p>
<p>其实,不用修改 svpwm 算法。 svpwm 算法在最终,会输出 A B C 三相的 pwm 值。</p>
<p>在传统2电平拓扑里,这3个 pwm值,就直接幅值给硬件驱动,产生6路pwm 了。</p>
<p>在3电平拓扑里,这3个 pwm 值,要转而变成 2个 pwm 定时器的12路 pwm 。</p>
<p>具体做法为,使用2个定时器。这2个定时器都需要开启6路pwm互补输出模式。对x相来说,有 Qxh1, Qxh2, Qxl1, Qxl2 四个开关。
其中, Qxh1 和 Qxl1 使用1号定时器对应通道。,Qxh2 和 Qxl2 使用2号定时器对应通道。</p>
<p>对 X 相来说,如果传入的 pwm 值 < 50%, 则,定时器1 的通道输出上管关,下管常开,定时器2的 上下通道输出按 pwm值*2。
如果传入的 pwm 值 >= 50%, 则定时器 2 下管关。 上管常开,1号定时器上下通道输出按 (pwm-0.5)*2。</p>
<p>比如下面这个代码</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// duty range from [0-1]</span>
<span class="kt">void</span> <span class="nf">set_pwm</span><span class="p">(</span><span class="kt">float</span> <span class="n">duty_A</span><span class="p">,</span> <span class="kt">float</span> <span class="n">duty_B</span><span class="p">,</span> <span class="kt">float</span> <span class="n">duty_C</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">mode</span> <span class="o">==</span> <span class="n">TWO_LEVEL_SVPWM</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">driver_set_pwm</span><span class="p">(</span><span class="n">TIM1</span><span class="p">,</span> <span class="n">channel1</span><span class="p">,</span> <span class="n">duty_A</span> <span class="o">*</span> <span class="n">perid_count</span><span class="p">);</span>
<span class="n">driver_set_pwm</span><span class="p">(</span><span class="n">TIM1</span><span class="p">,</span> <span class="n">channel2</span><span class="p">,</span> <span class="n">duty_B</span> <span class="o">*</span> <span class="n">perid_count</span><span class="p">);</span>
<span class="n">driver_set_pwm</span><span class="p">(</span><span class="n">TIM1</span><span class="p">,</span> <span class="n">channel3</span><span class="p">,</span> <span class="n">duty_C</span> <span class="o">*</span> <span class="n">perid_count</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">mode</span> <span class="o">==</span> <span class="n">THREE_LEVEL_SVPWM</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">duty_A</span> <span class="o"><</span> <span class="mf">0.5</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">driver_set_pwm</span><span class="p">(</span><span class="n">TIM1</span><span class="p">,</span> <span class="n">channel1</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> <span class="c1">// 下管常开</span>
<span class="n">driver_set_pwm</span><span class="p">(</span><span class="n">TIM2</span><span class="p">,</span> <span class="n">channel1</span><span class="p">,</span> <span class="n">duty_A</span><span class="o">*</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">perid_count</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">driver_set_pwm</span><span class="p">(</span><span class="n">TIM2</span><span class="p">,</span> <span class="n">channel1</span><span class="p">,</span> <span class="n">perid_count</span><span class="p">);</span> <span class="c1">// 上管常开</span>
<span class="n">driver_set_pwm</span><span class="p">(</span><span class="n">TIM1</span><span class="p">,</span> <span class="n">channel1</span><span class="p">,</span> <span class="p">(</span><span class="n">duty_A</span> <span class="o">-</span> <span class="mf">0.5</span><span class="p">)</span><span class="o">*</span><span class="mi">2</span> <span class="o">*</span> <span class="n">perid_count</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">duty_B</span> <span class="o"><</span> <span class="mf">0.5</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">driver_set_pwm</span><span class="p">(</span><span class="n">TIM1</span><span class="p">,</span> <span class="n">channel2</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> <span class="c1">// 下管常开</span>
<span class="n">driver_set_pwm</span><span class="p">(</span><span class="n">TIM2</span><span class="p">,</span> <span class="n">channel2</span><span class="p">,</span> <span class="n">duty_B</span><span class="o">*</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">perid_count</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">driver_set_pwm</span><span class="p">(</span><span class="n">TIM2</span><span class="p">,</span> <span class="n">channel2</span><span class="p">,</span> <span class="n">perid_count</span><span class="p">);</span> <span class="c1">// 上管常开</span>
<span class="n">driver_set_pwm</span><span class="p">(</span><span class="n">TIM1</span><span class="p">,</span> <span class="n">channel2</span><span class="p">,</span> <span class="p">(</span><span class="n">duty_B</span> <span class="o">-</span> <span class="mf">0.5</span><span class="p">)</span><span class="o">*</span><span class="mi">2</span> <span class="o">*</span> <span class="n">perid_count</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">duty_C</span> <span class="o"><</span> <span class="mf">0.5</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">driver_set_pwm</span><span class="p">(</span><span class="n">TIM1</span><span class="p">,</span> <span class="n">channel3</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> <span class="c1">// 下管常开</span>
<span class="n">driver_set_pwm</span><span class="p">(</span><span class="n">TIM2</span><span class="p">,</span> <span class="n">channel3</span><span class="p">,</span> <span class="n">duty_C</span><span class="o">*</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">perid_count</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">driver_set_pwm</span><span class="p">(</span><span class="n">TIM2</span><span class="p">,</span> <span class="n">channel3</span><span class="p">,</span> <span class="n">perid_count</span><span class="p">);</span> <span class="c1">// 上管常开</span>
<span class="n">driver_set_pwm</span><span class="p">(</span><span class="n">TIM1</span><span class="p">,</span> <span class="n">channel3</span><span class="p">,</span> <span class="p">(</span><span class="n">duty_C</span> <span class="o">-</span> <span class="mf">0.5</span><span class="p">)</span><span class="o">*</span><span class="mi">2</span> <span class="o">*</span> <span class="n">perid_count</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>这个代码就实现了3电平 svpwm 输出。而三相duty的计算方式和之前的并无不同。</p>
被销售绑架
2024-02-26T00:00:00+00:00
https://microcai.org/2024/02/26/seller-kidnapped-business
<p>在芯片销售行业,有一个怪现象。买芯片的人是孙子。</p>
<p>在线的电子元件商城是行业老鼠,属于行业灰色地带(只是被业内人士定义为灰色。实际上100%合理合法的白色产业)。</p>
<p>不仅仅买芯片的时候是孙子。在研发 pcb 的时候,更是孙子。需要找芯片厂求来芯片手册。没有手册,就算买到芯片了也用不起来。</p>
<p>芯片的价格是卖方市场。能打几折买到芯片,就决定了最终产品的竞争力。因此为了赚钱,买方要无下限跪舔卖方的销售。</p>
<p>如果有这么一个平台,你不用跪销售,就能买到芯片,畅通无阻的查阅各类型号的手册。</p>
<p>那这个平台一定把芯片销售得罪死。</p>
<p>销售不仅仅当不了大爷,没有买方舔他了。更重要的是,芯片制造商发现他们不需要这些提成巨大的销售了。这对芯片销售来说是灭顶之灾。</p>
<p>因此,在销售掌握话语权的公司,你都没法在在线商城里买到他家的芯片。</p>
<p>不过好在,还有很多公司,销售并不掌控公司。所以元件商场上还是会有各类琳琅满目的芯片可供购买。对他们来说,在线商城,是多了一个销售渠道。
卖东西嘛,渠道是多多益善。</p>
<p>元件商场常常让人诟病的一个问题是,价格没有优势。</p>
<p>但是,这不是元件商场的错。因为他们是在芯片原厂的销售抵制的情况下开起来的。为了喂饱这些销售,元件商场的拿货价,都比你直接找销售买的高。</p>
<p>因此,他们的目标客户,就只能是那些体量较小,舔不起销售的初创公司以及DIY爱好者了。</p>
<p>芯片公司有一个很诡异的地方。就是芯片公司的官网。只要你用了他的芯片,你就一定要去他官网下文档</p>
<p>也就是说,你的客户必然会访问你的官网</p>
<p>那么,此时你在官网直接销售芯片。厂家直销,是水到渠成的。</p>
<p>正是为了避免发生这种情况,销售才会忽悠公司说,芯片的手册是机密,不能公开。只能通过销售传给买了芯片的人。</p>
FlyBuck的限制
2024-02-25T00:00:00+00:00
https://microcai.org/2024/02/25/flybuck-limitation
<p>在 <a href="/2024/02/21/power-on-pcb.html">这篇</a> 文章里,我夸奖了 flybuck 拓扑结构。</p>
<p>不过,在实际使用的时候,发现 flybuck 并没有想象中那么美好。</p>
<p>第一点,flybuck 并不是任何 buck 芯片都能工作。而是只有能工作在强制 CCM 模式的 同步整流buck 芯片才能工作。
因此只有 Ti 的寥寥数个芯片,被打上了 FlyBuck 标志的才能用在 flybuck 上。而因为 FlyBuck 是 Ti 的商标,
所以其他公司的芯片,是不会打上这个标记的。只能通过仔细阅读芯片手册,确定他可以工作在强制 CCM 模式。
这就要了老命了。因为市面上的 DCDC 降压芯片超过一万款。</p>
<p>第二点,flybuck 的输出电压也不是 Vout2 = Vout1 * Np/Ns - Vf。而是受漏感影响。
在有漏感的情况下,Vout2 的电压还会同时受 Iout1 和 Iout2 相互影响。</p>
<p>漏感大了,副边电压就稳不住了。这也要了老命了。因为本来是低成本的方案,结果对变压器的要求很高。漏感低的耦合电感可一点都不便宜。</p>
<p>实际上,超过2元成本的 flybuck 毫无意义。因为有不到3元的 flyback 方案。
但是 Ti 标记能用于 FlyBuck 的 dcdc 芯片,个个都在两位数价格。毫无成本优势。
Ti 所谓的flybuck的成本优势,是建立在Ti 家的 flyback芯片更贵的基础上的。
当国内厂家生产5毛钱的flyback芯片时,ti 的 flybuck 优势荡然无存。</p>
<p>因此我只能寄托于国内厂家3毛钱的 buck芯片能用于 flybuck。
但是国内厂家的一个通病就是手册喜欢藏着掖着。生怕用户知道芯片怎么用。
于是关键的信息,芯片是否CCM模式,如何强制CCM模式的信息,是断然不会提供的。</p>
<p>一个好东西,硬是被玩坏了。</p>
LLC 真的是天才发明
2024-02-23T00:00:00+00:00
https://microcai.org/2024/02/23/llc-is-a-brillant-invention
<h1 id="开关电源">开关电源</h1>
<p>传统上,交流电通过一个巨大的变压器进行变压。因为交流电通过磁耦合的方式传递能量。传递能量的频率为交流电的频率。每次传递的能量取决于变压器铁芯的磁通量。磁通量从最大到最小,最小到最大,
经历一次能量传递。</p>
<p>变压器要增大功率,要么增加磁通,要么提高频率。显然工作在50hz交流电下的变压器,只能选择增大磁通的方式增加功率。由于材料能承受的磁通量密度是有上限的。过大的磁通密度会饱和。所以变压器要增加功率,只能增加体积。</p>
<p>而开关电源,则另辟蹊径,使用晶体管的开关功能产生高频的交流电,从而摆脱了 50hz 的频率限制。</p>
<p>于是,体积很小的变压器就可以传递很大的功率。</p>
<p>在如何产生高频交流电的问题上, 产生了很多种路径。</p>
<p>反激,正激,推挽,全桥。</p>
<p>当中最为广泛使用的,属反激。但是不管是反激,正激,推挽还是全桥,变压器的漏感,都属于有害的东西。要尽量减小。
所谓变压器的漏感,指变压器初级的一部分能量没有传递给次级,而是滞留在初级,导致初级滞留的能量最后在开关管关断的时候要释放出来。
这部分释放能量需要在初级电路里消纳掉,导致电源整体效率下降。</p>
<h1 id="漏感利用">漏感利用</h1>
<p>人类历史上很多东西的进步都来自变废为宝。比如 LLC 的发明,就将废物的漏感变成了宝。</p>
<p>要理解 LLC 首先要理解 LC。L 和 C,一个电压滞后,一个电流滞后。搭配起来干活的时候,恰好会反复将对方滞后。。。 也就是俗称的振荡。
也就是一个 L 一个 C, 就构成了一个振荡电路。电流会反复的在 L 和 C 之间徘徊。</p>
<p>变压器的初级绕组,也是一个线圈绕在铁芯上。他就会是一个电感。在进行电路分析的时候,变压器的电感会被拆分成2个。一个是主电感,一个是漏电感。主电感负责把能量传给次级。
漏电感。。。漏电感负责搞事情。</p>
<p>这搞事情的漏电感,在 LLC 的眼中,却是好东西。因为他可以和电容构成 LC 振荡。因为主电感把能量输送给次级后,自身就没有能量了,也就是说,变压器的次级接了负载后,主电感的感量就被消耗掉了。
所以,没有漏感的理想变压器,反而不能引起 LC 振荡。必须得再搭配一个L。而反正非理想变压器免费送一个漏感。于是 LLC 就把这个漏感笑纳了。</p>
<p>在 LLC 里,漏感(固定值),主电感(随负载变动),电容三者构成了一个谐振电路。这个谐振电路恰好能产生交流电,然后这个交流电又恰好发生在变压器的线圈里,于是这个谐振能量就传递给了变压器的次级。</p>
<p>随着负载的变动,谐振点也会跟着移动。如果驱动电路产生的 pwm 频率和谐振频率不同,就会损失效率。表现为次级电压下降。因此采集次级的电压,可以用于修正 pwm 的频率。让 pwm 的频率始终保持在谐振频率上。因为,如果 pwm 的频率和谐振频率相同,就会产生一种神奇的效果。 就是 MOS 管开关的时间点,正好在谐振电压为0的地方。也就是,产生了 0 压开启的效果。使得mos管的开通损失降为0。</p>
<p>因此 LLC 的次级电压反馈,主要目的不是为了调整占空比稳压(因为 LLC 始终使用 50% 的占空比,也就是pwm只是单纯的维持谐振电路,而不是调节占空比来调压),而是为了跟踪谐振频率。</p>
<h1 id="漏感和谐振频率">漏感和谐振频率</h1>
<p>如此说来,其实无漏感的变压器,其实也能 LLC?</p>
<p>理论上是的,但是实际上如果漏感太小,在重载的时候,由于主电感能量几乎完全传递给了次级,会导致 LC 振荡里, L 太小,导致谐振点无限升高。
为了维持谐振频率在一个可接受的范围,LLC 就要求变压器必须要有漏感,而且还是很大的漏感。这样可以保证即使全负载工作,主电感的感量完全被耦合干净了,LC振荡的条件还在。而且因为漏感较大,不至于把谐振点推高到 pwm 频率跟不上的地方。</p>
<p>但是漏感过大,也不行。因为这意味着同功率下变压器体积要变大。而且漏感大是因为耦合度低。而耦合度低本身又会带来更大的变压器铁损。反而效率降低。
因此, LLC 的变压器,不能任意提高漏感。</p>
<p>但是,漏感越高,确实谐振点随负载变动而偏移的就越少。但是,漏感也无需特意做的很大。因为在谐振点较低的时候,是变压器轻载的时候。(轻载的时候,主电感的感量大)
此时 pwm 即使高于谐振点,也因为初级线圈电流较低,而不会有很大的开关损失。因此, LLC 只要考虑在中高负载的情况下, pwm 控制器产生的频率范围,能覆盖 LLC谐振器的谐振点即可。如此一来,轻载的情况下,也无需降低pwm频率来匹配谐振点。而是直接工作在过谐振状态。</p>
<p>LLC变压器的谐振点范围是 >=1/(2π·(漏感+主电感)*电容 ), <=1/(2π·漏感*电容 )。更准确的来说,是 1/(2π·(漏感+(主电感*空载率))*电容 )</p>
<p>pwm 的频率,需要 >= 谐振点。不能小于谐振点。因为小于谐振点,和大于谐振点,都会损失效率。小的情况下还会损失多点。</p>
<p>一般漏感取总电感的 20%,按负载 50% 的情况下开始进入谐振,则谐振频率的最大最小 相差 1.667 倍。也就是说,如果重载的时候, 谐振频率是 500khz , 则 50% 负载的时候, 谐振频率是 300khz.
更低的负载情况下,pwm 频率维持在 300khz 进入过谐振状态。虽然会损失点效率,但是负载降低了,总的损失也是降低的。这样谐振控制器就只需在一个较小的 300khz-500khz 的范围内进行谐振点跟踪即可。</p>
<p>如果还想缩小谐振频率范围,就不能继续做大漏感了。而是应该考虑一个外置电感构成 LLLC 谐振了。:)</p>
<h1 id="谐振为啥可以实现零压开启">谐振为啥可以实现零压开启</h1>
<p>变压器绕组和电容进行谐振的时候,绕组的自感电压会和直流母线的电压进行叠加。叠加的结果就是 MOS 管源漏极上的电压,也会呈现正弦变化。在源漏极上的电压跌到最低的时候开启 MOS 管,就可以让MOS的导通损失最小。只不过关断的时候无法实现零压关断。因为 MOS 管关闭的时候 LLC 才会自谐振。MOS 管打开的时候, LLC 的电压被迫和母线电压一致。所以在 MOS 开启期间,无法做到电压因为谐振而自发降低。</p>
<h1 id="谐振">谐振</h1>
<p>总的来说, LLC 之所以效率高,主要其实是利用了谐振。而不是完全靠 MOS 强推电流。</p>
变频器制作-第12部分 电源,还是电源
2024-02-21T00:00:00+00:00
https://microcai.org/2024/02/21/power-on-pcb
<h1 id="说到供电">说到供电</h1>
<p>一个变频器,需要4组电压供电,分别是 DC 300V, DC 12V, DC 5V, 隔离 5V.</p>
<p>300v 是高压, 12v 和 5v 乃低压。</p>
<p>只不过, 12v 和 5v 和 300v 的高压,负极是连到一起的。因此,这个负极,是不安全的负极。这个负极,是整流桥的负极。这个负极会有一个对地的脉动电压。</p>
<p>其中, 300V DC 由一个整流桥 + 一个 LC 滤波器获得。</p>
<p>12V 是专门给栅极驱动器供电的。NMOS 一般使用 12V 门级电压确保完全开启。
5v 是给隔离器的高压侧供电的。隔离5v 则是给隔离器的低压侧,和其他需要安全的隔离的电路供电。比如 mcu,显示屏等。</p>
<h1 id="使用成品5v小电源获得供电">使用成品5v小电源获得供电</h1>
<p>12v 属于低压。在传统上,会使用一个开关电源获取低压供电。
我最初也是这么设计的。由于我并不是电源工程师,所以购买的成品开关电源模块。</p>
<p><img src="/images/acdc_mod.png" alt="成品模块" /></p>
<p>图中红圈的就是成品开关电源模块。一共使用了2个。其中一个用于提供隔离的5V。另一个则是将输出的负极和高压侧的 GND 连接。以获得非隔离的 5V 供电。然后再使用一个 DCDC 升压芯片获得一个非隔离的12v。当然,如果使用12v 输出的隔离模块,然后使用 dcdc 降压,同样能达成目的。不过这就意味着要购买两种电源模块。会增加 BOM 成本。</p>
<p>由于使用了成品的开关电源,使得220v变频器的成本里有个无法压缩的2个开关电源模块。而且这种模块必须手焊,无法使用自动化的贴片流水线。凭空增加成本。</p>
<h1 id="使用-dcdc-芯片获得12v">使用 DCDC 芯片获得12v</h1>
<p>由于板子上有非隔离的12v供电的需求。因此转而使用了DCDC降压芯片获得12v供电。</p>
<p>大部分DCDC芯片都是为低压的直流转直流设计的。无法耐受高压。但是,架不住人们对成本压缩的渴望。芯片公司转而研发了输入耐压能到 600V 的 DCDC 降压模块。用于将 整流后的 310v 直流电直接降压为低压。相比低压的dcdc芯片,这种非隔离的dcdc芯片仍然被归类到 ac/dc 电源芯片里。但是实际上其架构就是普通的 Buck 降压电路。因此他的成本也就比普通的dcdc芯片多几毛钱。</p>
<p>再搭配一个 DCDC 降压电路,于是我就获得了比使用成品开关电源模块要便宜很多的 12v 和 5v 供电。</p>
<p>获得了廉价高压侧的非隔离低压电,解决了栅极驱动芯片和隔离器副边的供电问题。 单片机侧还是需要隔离电源。还是需要使用成品电源模块。</p>
<h1 id="使用-flybuck-获得隔离的5v">使用 FlyBuck 获得隔离的5v</h1>
<p>趁春假,把唐老师一千多个视频都给过了一遍。在这个宝库里发现了 FlyBuck 架构。
不过也被唐老师给坑了一把。他号称任何 dcdc 芯片都能用于获得隔离的输出。</p>
<p>其实只有 同步整流的 dcdc 芯片能做到。被他这么一坑,2月的白嫖机会直接浪费。</p>
<p>不过最终结果还是好的。最终我使用 FlyBuck 拓扑成功获得了隔离的 5v 电源。
扔掉了成品电源模块。供电成本直接砍掉8成。</p>
<p><img src="/images/flybuck.png" alt="" /></p>
<p>红圈里分别是 1:1 变压器,高压DCDC ic 和普通同步整流 buck 变压器ic。</p>
<p>从而砍掉了成品的电源模块。
而且同步整流的 DCDC 芯片,能从12v 输入电压同时产生 隔离的 5v 和非隔离的5v.</p>
<h1 id="flybuck-永远的神">FlyBuck 永远的神</h1>
<p>如果使用多绕组的变压器,FlyBuck 还能一次产生更多的隔离电压。虽然不能用于大电流的场景。但是需要小功率隔离电压的场合还是非常多的。</p>
<p>比如隔离式 CAN 收发器, 隔离式 RS485 收发器。隔离式 I2C 收发器。</p>
<p>这些元件都需要一个极低功率的隔离电源供电。而且是每个元件一个。使用 flybuck 就可以用非常低的成本产生巨量的隔离电源。给单片机的每个对外接口都配置一个独立电源供电的隔离器。极大的增强了单片机的电气安全性。</p>
变频器制作-第11部分 pwm模式用对了吗?
2024-02-08T00:00:00+00:00
https://microcai.org/2024/02/08/pwm-mode-misuse
<h1 id="话说高级定时器">话说高级定时器</h1>
<p>不管是 AT32 还是 CH32 还是 EG32 还是 GD32 还是 RP32. 各种 32 单片机,都是使用的 防STM32 的外设。</p>
<p>因此,他们都会有一种叫“高级定时器”的设备,用来产生多路互补PWM波。</p>
<p>在高级定时器的设置里,能产生的波形是以下机制的排列组合</p>
<ol>
<li>
<p>计数器模式
向上计数溢出置0,向下计数到0重置,向上然后向下计数。后两者称为中央对称模式。在 svpwm 里,固定使用中央对称模式。</p>
</li>
<li>通道大于占空比值输出真,大于占空比值输出假。</li>
<li>真值高电平 真值为低电平</li>
</ol>
<p>有些hal库,上管通道和下管通道可以分别设置 2 号模式。有些没有独立设置。不知道是硬件如此还是hal库认为没必要暴露这种设定。</p>
<p>2 号 和3 号设定,有互补性。比如 (通道大于占空比值输出真+真值高电平)和 (大于占空比值输出假+真值为低电平)的组合,产生的 pwm 波是一模一样的。</p>
<p>一开始,我并没有在意,感觉是硬件上可能会做一些没有意义的组合机制。比如 三个上管打开和3个下管同时打开,作用是一样的,在 svpwm 里都是属于0向量。</p>
<h1 id="再谈死区时间">再谈死区时间</h1>
<p>最初我制作12v的低压变频器的时候,并没有在单片机里设定死区时间。因为栅极驱动已经内置了一个死区时间。</p>
<p>6pwm 模式相比 3pwm 模式,死区可调的优势当时我是不知道的。我只不过是希望能有6管同时关闭的模式。所以即使不需要死区控制,我也使用了6pwm 模式。</p>
<p>直到后来,我开始向220v进发。高压 MOS 相比低压 MOS 具有更大的导通和关断延迟。栅极驱动里默认的延迟时间显得不太够用了。</p>
<p>由此,6pwm 模式软件可调死区的优势就是必须的了。</p>
<p>在设定一个死区时间后,我就上机调试了。发现电路能正常运行,并且没有炸鸡。我以为大功告成了。</p>
<p>然而,我的眼花悄悄的给我开起了玩笑。</p>
<h1 id="被pwm模式的的排列组合暴击">被pwm模式的的排列组合暴击</h1>
<p>之前讲过,(通道大于占空比值输出真+真值高电平)和 (大于占空比值输出假+真值为低电平)的组合,产生的 pwm 波形是一样的。</p>
<p>事实上,我确实就随意的选了一种模式,并且当时恰好能工作。</p>
<p>其实,这个结论,只有不使用死区控制的时候是正确的。 使用了死区控制,结论就不正确了。
因为,这关系到,单片机在输出死区这段时间,是上下管的 pwm 波同时高电平,还是同时低电平。</p>
<p>而且我恰好把这个设置弄反了。结果就是,单片机在死区时间,输出模式乃上下管同时高电平。</p>
<p>而栅极驱动确实尽职尽责。在单片机输出上下同开的错误命令的时候,检测到了错误,忠实的完成了自己的“防上下管同开”的任务。于是恰好,没有炸管。</p>
<p>但是突然有那么一个瞬间,栅极驱动不灵了。他未能完成他的任务。上下管同时导通了!</p>
<p>于是,2个470uF 的滤波电容正极到上管的漏极,这条 PCB 布线被炸断。MOS管自身也在短路电流下炸毁。由于短路是发生在滤波电容上,空开还未来得及动作,PCB布线炸毁导致回路切断。所以空开没有任何反应。只听到滋的一声,MOS亮了以下。然后就没然后了。</p>
<h1 id="总结">总结</h1>
<p>经过重新编写的 pwm 驱动,终于没有暴击了。单片机高级定时器的这些灵活设计其实也是非常好的。比如光耦版本的高压驱动板上,我就必须设定真值为低电平。因为经过光耦的信号会反相。用单片机的 gpio 术语来说,就是光耦的输出端是开漏的。输入高电平,让内部光敏二极管导通,拉低输出端口电压。因此光耦反相,如果不想多花钱将光耦的输出再次反相,就可以在单片机端设定 pwm 输出信号真值为低电平。这样灵活的设计怎么能怪它坑人呢。 ^_^</p>
变频器制作-第十部分 6pwm和死区时间
2024-02-06T00:00:00+00:00
https://microcai.org/2024/02/06/deadtime-important
<h1 id="3pwm-vs-6pwm">3pwm vs 6pwm</h1>
<p>三相桥需要6个开关管。控制6个开关管需要控制器输出六路pwm信号。</p>
<p>这将占用单片机6个 IO 口。考虑到 pwm 信号控制的开关管中,上下管是交替导通的。也就是所谓的互补。
因此在一些场景下,也可以使用 3pwm 模式控制6个开关管。
3pwm的信号控制3个上管,对应的下管控制信号由一个反相电路生成。</p>
<p>咋一看,似乎节约了3个IO引脚。诞生 3pwm模式有一个重要功能无法实现,就是让6个开关管全关。</p>
<p>因此为了表示这个全关状态,实际上需要额外增加一路 EN 信号。因此 3pwm 模式需要使用4个引脚。</p>
<p>听上去不错,在实现相同功能的情况下,节约了2个引脚。</p>
<p>但是,我想说的是,<strong>如果有条件,千万不要使用 3pwm 模式</strong>。</p>
<h1 id="死区时间控制">死区时间控制</h1>
<p>理想是丰满的,现实是骨感的。现实中的开关管,并不总是能立即导通/关闭。</p>
<p>导通和关闭都需要一定的时间发生。如果上管关信号给出的时候,立即给出下管开信号,或者下管关信号给出的时候立即给出上管开信号,也就如使用反相器电路做的那样,则会导致上下管会在某个时间里处于同时打开的状态。</p>
<p>这会导致严重的短路。</p>
<p>所以,互补的pwm信号比如插入死区时间。所谓死区时间,就是在上下管切换开启的时候,中间插入两个管子都关闭的时间。以保证实际的管子不会出现同时打开的情况。</p>
<p>在 3pwm 模式,死区时间需要由硬件设计达成。一般是通过在反相器电路里插入延时实现。</p>
<p>在现实世界中,一般是使用集成了死区时间控制的栅极驱动器芯片达成目的。</p>
<p>但是,栅极驱动器的死区控制是死的。一旦选定,就无法更改了。</p>
<p>因此,为了电路的灵活调整需要,最好是在单片机里控制 pwm 信号的死区时间。</p>
<p>这就宣判了 3pwm 模式的死刑。</p>
<h1 id="死区时间设置多大">死区时间设置多大</h1>
<p>在 MOS 管的手册里,会给出 4 个 delay time。分布是开通延时,上升延时。关闭延时,下降延时。</p>
<p>并且这4个时间还会给出 最小值,典型值,和最大值。</p>
<p>单片机pwm输出配置的死区时间 = 最大关闭延迟+最大下降延时 - 最小开通延迟 - 栅极驱动器的最小死区时间。</p>
<p><em>如果为负,就无需设置死区时间了。</em></p>
<p>之所以突然要考虑死区这个问题,是因为最近我开始将变频器给高压化。而高压的 MOS管,其导通延时大大增加。目前选定的一款 MOS, 耐压650V,导通内阻 < 300mΩ,价格不到1元一片。
如此低廉的价格,伴随的是高达450nS的关闭延迟,高达 2000pF 的寄生电容。</p>
<p>经过计算,发现需要800nS 的死区时间。而栅极驱动器的 死区时间是 200nS。。可见如果不在软件上插入死区时间,必然会炸管。</p>
<p>谢天谢地,我在把 mos 管焊到板子上的前一天突然刷到一个up他做变频器炸管的视频。由此我联想到自己那块jlc刚刚送来还未来得及焊接的板子会不会也炸管。 要是 220v 下炸管,威力可比12v 下的高不知道多少倍了。</p>
<p>经过仔细的研究后果然发现,死区时间不够。</p>
<p>而之前做 12v 的变频器的时候,压根没在软件里配置死区时间,也没问题。是因为用到的低压MOS管,管子的关闭时间都在 100nS上下,经过栅极驱动那 200nS 的死区后,软件里根本无需配置死区,而且我还嫌栅极驱动的死区太大了呢。</p>
<p>果不其然,今天把 MOS焊接好,软件修改好。终于一次验证通过。把家里220v的电机驱动起来了。</p>
<p>从此我也可以面对高电压的电路设计啦!</p>
<p>:)</p>
220v 逆变器大功告成
2024-02-05T00:00:00+00:00
https://microcai.org/2024/02/05/220v-vfd-success
<h1 id="起">起</h1>
<p>在 <a href="/2024/01/17/add-eg6832-to-platformio.html">这篇</a> 文章里,我被风扇厂的老板教育了成本控制。</p>
<p>思来想去,我发现还是有必要制作 220v 的变频器以降低对电机的要求。</p>
<p>经过数次炸板改版后,最终在今天制作完成了。听到家里 220v 的电机,而不是自己改绕的电机,也发出了熟悉的 vvvf 音乐,心情还是非常激动的。</p>
<h1 id="制作">制作</h1>
<p>首先, 220v 属于强电,而单片机实验 3.3v 属于弱电。强电和弱电需要隔离。
我首先想到的是使用光耦进行隔离。</p>
<p>但是,一想到要在pcb上贴6个光耦就感觉成本飙升。而且光耦的 LED 发光二极管,总感觉会随着时间的推移而衰减。而且还感觉 LED 二极管的发光熄灭的速度赶不上 pwm 的速度。</p>
<p>事实也确实如此。因此需要使用专门为栅极驱动设计的高速光耦。而高速光耦就贵了很多。简直就是成本炸裂。</p>
<p>就在我苦恼的时候,突然发现了数字隔离器这种东西。一个数字隔离器和一个高速光耦价格相当,但是光耦只能传递一路信号,而数字隔离器能传多路。成本一下子就节约下来了。 最后选了一个6路的数字隔离器,只需一个就完成了6路 pwm 信号的传递。</p>
<p>另外,为了适应高压,栅极驱动也需要更换为耐压 600v 的版本。</p>
<p>因为用惯了EGmicro的栅极驱动,所以想都没想,就从 EG2134 升级成了 EG2334。</p>
<p>于是驱动电路就从</p>
<p><img src="/images/EG2134.png" alt="低压" /></p>
<p>变成了</p>
<p><img src="/images/EG2334.png" alt="高压" /></p>
<p>当然,为了对比,也做了一个光耦的版本</p>
<p><img src="/images/EG2334_Optocoupler.png" alt="光耦版" /></p>
<p><strong>NOTE:经过光耦后,信号反相了。因此需要在单片机里修改输出极性。</strong></p>
<p>打样回来的光耦版本</p>
<p><img src="/images/photo_2024-02-08_13-14-23.jpg" alt="制作好的光耦" /></p>
<p>打样回来的数字隔离器版本</p>
<p><img src="/images/photo_2024-02-08_13-14-27.jpg" alt="打样回来的数字隔离器版本" /></p>
<p>除了要使用隔离器进行隔离,栅极驱动和单片机,还得使用两个不同的开关电源供电。
由于我神功还没大成,因此我买了专门的超迷你开关电源模块。
还为这个电源模块在立创EDA里绘制了封装。这样就可以直接引用了。</p>
<p>比如看下背面</p>
<p><img src="/images/photo_2024-02-08_13-22-33.jpg" alt="背面" />
有两个电源模块的位置。</p>
<p>然后搞起来验证</p>
<p><img src="/images/photo_2024-02-08_13-17-06.jpg" alt="组装好了" /></p>
<p>因为家里能给我折腾的就只有电风扇。所以三相接了2相出来,也能用来驱动电风扇的电机了。</p>
<p>还接了个漏保来保护自己。嘿嘿。</p>
<p>最后,接上电风扇后,听到了熟悉的 VVVF 声音!</p>
<h1 id="总结">总结</h1>
<p>高压变频器,增加了隔离成本。所以没法和12v/24v的变频器竞争成本。</p>
<p>隔离成本大头是多了2个隔离电源,多了数字隔离器,还要使用昂贵的隔离采样放大器。</p>
<p>而且超过 70w 功率,还得配置 PFC 电路。</p>
<p>还得想办法,把成本扣出来,这样才能卖给电风扇长。毕竟异步电机 60w 也才十几块钱。能制作低成本的变频器,就不需要 20w 的无刷电机做的“变频风扇”忽悠人了。</p>
单片机也能支持 co_await 协程啦
2024-02-02T00:00:00+00:00
https://microcai.org/2024/02/02/awaitable-for-mcu
<h1 id="序">序</h1>
<p>在 <a href="/2023/12/08/cooperative-multitasking-in-mcu.html">这篇文章</a> 里,我为单片机编写了一个简单的 executor。
然后利用这简单的 executor, 再搭配 Duff’s device 就用上了 stackless coroutine 了。</p>
<p>但是,Duff’s device 也有其缺陷。最明显的就是,在 ASIO_CORO_REENTER(this){ xx } 的函数体里,无法定义变量,也无法使用 switch 指令。</p>
<p>这挺让人头疼的。</p>
<p>于是,我迫切的需要一个有栈的协程。 c++23 里带的coroutine就不错。</p>
<p>最初的计划是移植 asio。
但是 asio 不知为何,在 arm-gcc 上遇到了诡异的语法错误。</p>
<p>遂放弃。直到最近,突然又想起来这件事。</p>
<p>然后又开始琢磨怎么写一个 awaitable。
但是实在是毫无头绪。于是准备抄一个库。</p>
<p>然后找到了阿里巴巴开源的 <a href="https://github.com/alibaba/async_simple">async_simple</a> 库。
经过了一定的裁剪后,跑起来了。</p>
<h1 id="阿里的代码不和谐">阿里的代码不和谐</h1>
<p>首先,阿里的这个协程库,我是扣了代码的。把不需要的东西都删了。但是也没有完全删干净。还有不少遗留的垃圾。
这就让本就捉襟见肘的单片机存储空间更局促了。</p>
<p>其次,阿里的这个代码虽然已经比 asio 的简化了很多,但是还是弯弯绕绕非常多,非常不利于分析。</p>
<p>所以,在<a href="https://www.jackarain.org/">哥们</a> 的帮助下,整了一个更简化的<a href="https://github.com/microcai/mcu_coro_demo/tree/master/lib/mcu_coro">版本</a>.</p>
<p>正如 这个仓库里的 例子那样,使用这个协程的方法很简单,首先,需要在 <code class="language-plaintext highlighter-rouge">loop()</code> 里调用 <code class="language-plaintext highlighter-rouge">mcucoro::executor::system_executor().poll();</code></p>
<p>然后,这么定义协程</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">mcucoro</span><span class="o">::</span><span class="n">awaitable</span><span class="o"><</span><span class="kt">void</span><span class="o">></span> <span class="n">led_blinker</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">for</span><span class="p">(;;)</span>
<span class="p">{</span>
<span class="n">LL_GPIO_TogglePin</span><span class="p">(</span><span class="n">GPIOB</span><span class="p">,</span> <span class="n">GPIO_PIN_14</span><span class="p">);</span>
<span class="k">co_await</span> <span class="n">coro_delay_ms</span><span class="p">(</span><span class="mi">1240</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>协程的返回值得是 <code class="language-plaintext highlighter-rouge">mcucoro::awaitable<void></code> 类型,</p>
<p>然后使用</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">mcucoro</span><span class="o">::</span><span class="n">post</span><span class="p">([](){</span><span class="n">led_blinker</span><span class="p">().</span><span class="n">get</span><span class="p">();});</span>
</code></pre></div></div>
<p>来启动协程即可。</p>
<h1 id="总结">总结</h1>
<p>有了 co_await 协程,编写 mcu 代码是如虎添翼。</p>
准备制作直流UPS
2024-01-28T00:00:00+00:00
https://microcai.org/2024/01/28/dcups-prepare
<h1 id="问题">问题</h1>
<p>现在很多需要停电维持工作的东西,其实都是直流供电的。比如监控。比如树莓派,比如路由器,比如光猫。</p>
<p>在万能的宝上搜索直流UPS,是能找到一些现成的产品。</p>
<p>但是,统统不是我设想中的那种工作方式。</p>
<p>我设想中的UPS,既不是后备式,也不是在线式。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>后备式UPS,在市电正常的情况下,电池处于充电状态。当检测到市电故障,则立即将输出切换到电池。
在线式UPS,电池一直处于边冲边放状态。负载一直都是由电池供电。
</code></pre></div></div>
<p>后备式UPS,有切换延时问题。在线式UPS,能量过了好几道,效率太差。</p>
<p>我设想中的UPS,应该是这样子的</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>负载和电池直接并联。电池不仅供电,还能负责稳压。
充电器为负载供电的同时为电池充电。
充电器的功率只要略大于负载的平均功率。负载短时的功率需求由电池提供(所谓稳压)
电池尽量以恒流方式充电。也就让充电器的输出电流恰好等于电池的充电电流+负载的消纳电量
</code></pre></div></div>
<p>这看起来不还是在线式UPS吗?但是在线式UPS是输出电池逆变后的交流电。而直接和电池并联的在线式ups,并不需要逆变。省掉了逆变这一过程。</p>
<p>那万能的宝上卖的路由器UPS不是这样的吗?</p>
<p>看起来是,但是并不是。他们使用的还是在线式UPS架构,不过把逆变220v交流电的模块换成了从电池升压输出12v的模块。能量还是要经过 12v适配器->(ups充电器->电池->ups升压电路)->负载 这么一个过程。能量损失巨大。而且电池实际上充电的速度,取决于负载的消耗能力。负载消耗的大了,电池充电的就慢了。也就是没有做到让电池恒流充电。</p>
<p>另外也有使用后备式架构的直流UPS。他们使用2个12v适配器,一个为电池供电,一个为负载供电。市电故障的时候,立即切换到12v电池直接供电。这个能做到恒流充电了,但是需要制作2个12v开关电源了。成本上升。</p>
<p>而且,统统有一个问题:没有48v版。</p>
<p>为啥需要48v呢?因为 POE。家里需要UPS的设备,全部都是从 POE 交换机取电的。路由器,用 poe 供电。光猫,用 poe 供电。 AP,更是直接接到 POE 上。监控摄像头,也是 POE 供电。</p>
<p>整个供电的重任,就交到了POE交换机的手上。</p>
<p>而 POE 交换机,就是以 48v 蓄电池供电为基础设计的。
POE 的供电电压,54v,就是48v蓄电池的浮充电压。</p>
<h1 id="解决办法">解决办法</h1>
<p>我当然不满足于只是制作一个48v的直流ups填补一下空白。</p>
<p>于是我想到了一个万能直流UPS,这个万能直流UPS是这样的</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. 有2个端口。一个为充放电口,一个为电池接头
2. 使用的时候,连线如下 ``` 开关电源 ====== 用电器
||
万能UPS == 电池 ```
3. 万能UPS的充放电口,直接并联在开关电源和用电器的直流母线上。万能UPS的唯一功能,是对流入电池的电流进行限流。也就是进行恒流充电。
4. 如果充电器无输出,则电池可以直接从万能UPS直通用电器。
5. 开关电源的输出电压,应该为电池的满电电压
</code></pre></div></div>
<p>也就是万能UPS,正向工作为Buck 降压恒流模式,反向为直通模式。而且只需要恒流,不需要知道电池的电压。只要适配器的输出电压为电池的满电电压即可。</p>
<p>实际上,一个简单的带恒流功能的 DCDC 降压模块,就能完成这个万能UPS的工作。除了。。。。一个小问题。</p>
<h1 id="难倒了">难倒了</h1>
<p>为了减少损害,反向工作的时候,不能依赖开关管的寄生二极管实现自动反向,而应该让 mos 管直接持续导通。
而且为了实现电池接近满电后,buck电路的输入电压=输出电压,也需要让 buck 电路的pwm占空比达到 100%。</p>
<p>而 buck 的mos管,乃是一个“高侧”的 nmos 管。
让高侧的 NMOS 管 100% 占空比导通,并不是一件容易的事情。</p>
<p>诶,找了许久也没找到合适的 dcdc芯片。</p>
编译期对象构造优化 .bss 为 .rodata
2024-01-22T00:00:00+00:00
https://microcai.org/2024/01/22/constexpr-constructor
<h1 id="问题">问题</h1>
<p>为了提高性能,我编写了一个查表法算 sin 的函数。为了适配不同 ROM 大小的 mcu, 这个表还有大有小多个版本。最大的表,里面有 1800 项,因为是保存的 0-90度的 sin 指,因此分辨率达到了 0.05度。</p>
<p>而且,为了进一步提高性能,表里存放的,并不是 float, 而是我自己编写的定点数 float_number。</p>
<p>问题就出在这个float_number 上。</p>
<p>因为这个定点表我是这么写的</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">const</span> <span class="n">float_number</span> <span class="n">sin_table</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
<span class="n">float_number</span><span class="p">(</span><span class="mf">0.0</span><span class="p">),</span>
<span class="n">float_number</span><span class="p">{</span><span class="mf">0.000872664515</span><span class="p">},</span><span class="n">float_number</span><span class="p">{</span><span class="mf">0.001745328366</span><span class="p">},</span><span class="n">float_number</span><span class="p">{</span><span class="mf">0.002617990887</span><span class="p">},</span>
<span class="p">......</span>
<span class="p">};</span>
</code></pre></div></div>
<p>因为这个表,其内存放的并不是内置类型。而是我自己编写的 float_number 类。这是一个定点数类, 使用 C++ 的运算符重载机制,可以做到无缝替换 float, 实现浮点改定点。</p>
<p>但是,正因为这个类不是内置类型,于是编译器实际上安排的是,将 <code class="language-plaintext highlighter-rouge">0.0, 0.000872664515</code> 等数字,存于 .rodata 段,然后将 sin_table 事实上分配于 .bss 段。
并在初始化代码里将 .bss 段的 sin_table 用存于 .rodata 段的各大 double 数据进行初始化。</p>
<p>也就是说,这个 sin 表,实际上即占用了 .rodata 也占用了 .bss 。而 .bss 段占用的是mcu里更为珍贵的RAM。
之所以最近才思考这个问题,是因为 EG6832 只有区区 8K 的 RAM。相比拥有 384K 的 ESP32 和 32K RAM 的 AT32, 这个 mcu 的内存实在过于珍贵。
以至于我很快遇到内存不足的问题。</p>
<h1 id="解决之道">解决之道</h1>
<p>解决的办法,便是“编译期” 构造。让 sin_table 直接存放的是已经从 double 类型构造完毕的对象。这样这个 sin_table 就即不会占用内存,也不用在运行时初始化一遍。</p>
<p>进行编译期构造,我立马想到了2种方式。</p>
<p>其一,便是将已经完成double到fix point转换的数据,存入 byte 数组,而后运行时使用 reinterpret_cast 强制转换。
这个办法,需要我编写一个脚本,脚本在 项目编译 期间执行,将 sin_table 转换为一个字节数组后写入 .c 文件。</p>
<p>其二,便是尝试让编译器自己完成“编译期构造”</p>
<p>第一种做法,实在是过于无趣,虽然这种做法乃是各大单片机佬乐此不疲的通常做法。但那也不过是因为 C 语言过于劣质。他们即便想用编译期构造,那也得编译器支持才行。</p>
<p>第二种做法,粗看一下似乎可行。细细思考,发现非常困难。深入研究后发现,如此简单。</p>
<p>是的,深入研究后,发现异常简单。便是将构造函数添加一个 <code class="language-plaintext highlighter-rouge">constexpr</code> 关键字足矣。</p>
<p>于是,float_number 的构造函数,就从原来的</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">explicit</span> <span class="nf">float_number_t</span><span class="p">(</span><span class="kt">double</span> <span class="n">v</span><span class="p">)</span><span class="o">:</span> <span class="n">scaled_number</span><span class="p">(</span><span class="k">static_cast</span><span class="o"><</span><span class="n">number_holder_t</span><span class="o">></span><span class="p">(</span><span class="n">v</span> <span class="o">*</span> <span class="n">SCALE</span><span class="p">)){}</span>
</code></pre></div></div>
<p>改成了</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">explicit</span> <span class="k">constexpr</span> <span class="nf">float_number_t</span><span class="p">(</span><span class="kt">double</span> <span class="n">v</span><span class="p">)</span><span class="o">:</span> <span class="n">scaled_number</span><span class="p">(</span><span class="k">static_cast</span><span class="o"><</span><span class="n">number_holder_t</span><span class="o">></span><span class="p">(</span><span class="n">v</span> <span class="o">*</span> <span class="n">SCALE</span><span class="p">)){}</span>
</code></pre></div></div>
<p>而后重新编译,发现固件的 .data + .bss 段,使用的内存就从 接近 3kb 降低到了 705B. 而 .rodata 段,还因为 float_number 比 double 的体积减少,也减少了占用。</p>
<p>至此,一个关键字引发的超级优化落幕。不仅仅如此,因为之前 EG6832 的内存不足导致只能使用 45 表项,如今也被我改成了同 ESP32 一样,使用奢侈的 1800 表项。sin 分辨率从 2 度提高到了 0.05 度。使得电机运转更为平滑。</p>
<h1 id="c-果然是编写资源受限型代码的神器">c++ 果然是编写资源受限型代码的神器。</h1>
让 PlatformIO 支持 EG6832
2024-01-17T00:00:00+00:00
https://microcai.org/2024/01/17/add-eg6832-to-platformio
<p>近来做变频器,也研究了不少mcu。让我的代码移植到了 ESP32, ESP32S3, ESP32C3, RP2040, STM32F405, AT32F415, AT32F421。
这些 mcu 都有一个共同点: platformio 支持。其中 ESP32, ESP32S3, ESP32C3, RP2040, STM32F405 受 platform 官方支持,而
AT32F415, AT32F421 则有 雅特力 的官方 github 上开放的 platformio 支持包。</p>
<p>但是,如果有一款 MCU 不被 platformio 支持,官方也不给支持包,咋办?</p>
<p>作为一个程序员,如果IDE的底层逻辑不了解透彻,寝食难安。</p>
<p>好在我不是专业的嵌入式开发者。不了解就不了解吧。</p>
<p>直到,我准备量产我的变频器。在 “得算力者得天下” 作者的帮助下,我找到了位于宁波的一家电器厂。它为杂牌落地扇贴牌生产。我把我的变频器介绍给了它的老板。</p>
<p>结果碰了一鼻子灰。主要是我的变频器成本太高了。高达25元(那是按在嘉立创进行1000张小批量生产的报价)(虽然市面上最廉价的三相变频器也得三位数购买)!
低端制造业就是这样,对成本斤斤计较。虽然我25元的变频器,可以支持到 200w 的电机。但是人家电风扇只需要 20w。他现在要改用 直流电机。
直流电机的成本比变频器+交流电机的成本更低。虽然60w 的直流电机很贵,但是电风扇只要20w 就行了。 20w 的直流电机就很便宜了。所以 60w 的交流电机+变频器的方案,在12w 直流电机面前毫无招架之力。
后来我准备走的时候,遇到了给他供应12w直流电机的供应商。我向他咨询了直流电机的成本。意外的发现,他的直流电机上的控制器,成本只要6元!</p>
<h1 id="6-元">6 元!!!</h1>
<p>这是什么神仙成本。</p>
<p>其实直流电机的控制器在硬件上 = 变频器+转子位置传感器。所以是不可能比变频器更便宜的。所以,其实我的变频器也可以跑在那种 6 元的硬件上。</p>
<p>但是,这种 6 元的硬件,其 MCU 几乎肯定必然,都是非主流 MCU.</p>
<p>经过我的一番搜索,我找到了2种可以实现6元控制器的芯片方案。一种是 8051 内核+ foc 协处理器 的 FU6832, 还有一种是 arm 核的 EG6832。</p>
<p>名字都叫 6832 ,看来这俩果然是要抢生意。</p>
<p>这俩有个共同点:都没有详细资料。都需要直接联系原厂的销售代表去索取样品和资料。</p>
<p>EG6832 对我这种没有专门的采购主管的个人开发者来说要安心点,因为官方直接开了个淘宝店卖样品。于是下单购买,然后淘宝的客服给了我一个压缩包。然后就没有更多的技术支持了。</p>
<p>压缩包里是一个水泵控制器的 DEMO。虽然是个 DEMO, 但是好在这个 DEMO 是属于 “把依赖的库全带上” 的那种方案。在 third_party/* 目录里塞满各种第三方库的 cpper 会心一笑 :)</p>
<p>最为关键的是,他这个 demo 带上了 HAL 库。(他可没那么好心的把电机库送人。电机库是个 .lib 静态库,只见 main.c 里对电机库的调用,不见电机库的代码的啦!这世上好心的把电机库送出去的,只有 simplefoc 和 本杰明VESC。 连我都不开源,嘿嘿!)</p>
<p>在mcu里,所谓 HAL 库,就是把 <strong>操作硬件寄存器</strong> 实现某种功能给编写为一系列的 “C函数”。比如,在 AT32 的 HAL 库里,把修改 pwm 占空比所需的硬件寄存器操作,给编写成了 <code class="language-plaintext highlighter-rouge">tmr_channel_value_set</code> 这么一个 C API。修改 pwm 频率呢,则使用 <code class="language-plaintext highlighter-rouge">tmr_period_value_set</code> 就实现了。我不需要操心具体硬件的寄存器操作,更不需要知道寄存器地址。
而 ESP32 则是把同样的功能封装为 <code class="language-plaintext highlighter-rouge">mcpwm_comparator_set_compare_value</code> 和 <code class="language-plaintext highlighter-rouge">mcpwm_timer_set_period</code>
雅特力和ESP32的芯片手册,不仅仅会给出寄存器的说明,也同时会介绍 HAL 库的 API 如何使用。</p>
<p>但是,EG6832 这个就惨了! 他的芯片手册,居然只有硬件寄存器的描述,而 HAL 库则没有任何文档。没有就没有吧,hal库毕竟给了源码。对着手册的寄存器介绍,再看看源码,应该就知道 api 是干嘛的了。</p>
<h1 id="编写-platformio-的支持包">编写 platformio 的支持包</h1>
<p>既然有了 hal 库,那理论上来说,就能为 platformio 添加 eg6832 的支持了。</p>
<p>platformio 里,对一款 MCU 的支持,需要编写2个包。其1 为 platform-xxx 包,其二为 framework-xxx 包。</p>
<p>platform-xxx 包的任务是编译项目。framework-xxx 包的任务是提供 HAL 库。(以及,更关键的,提供 main 执行之前的代码。)</p>
<p>好在,雅特力的官方给了我一个很好的 example。于是一顿操作,<a href="https://github.com/microcai/platform-egmicros">platform-egmicros</a> 就诞生了。</p>
<p>只要在 platform.ini 里写上</p>
<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">platform</span> <span class="p">=</span> <span class="s">https://github.com/microcai/platform-egmicros</span>
<span class="py">board</span> <span class="p">=</span> <span class="s">genericEG6832</span>
<span class="py">framework</span> <span class="p">=</span> <span class="s">eg32firmlib</span>
</code></pre></div></div>
<p>就可以开始为 EG6832 写代码啦!</p>
<p>platform-egmicros 包其实主要的任务就是编译framework-xxx 包。编译的脚本在 <a href="https://github.com/microcai/platform-egmicros/blob/master/builder/frameworks/eg32firmlib.py">builder/frameworks/eg32firmlib.py</a> 里。</p>
<p>因为按 platformio 的思路,一个 platform 可以使用各种 framework. 比如 裸环境,arduino 环境, freertos 环境。等等。</p>
<p>不同的环境,就调用 platform-xxx 包里的 builder/{framework}.py 脚本。</p>
<p>把 EGmicro 官方给的 demo 里的 HAL 代码扣出来,我做了个 framework-eg32firmlib 库。于是一顿编译后, .pio/build/eg6832/firmware.bin 文件顺利诞生!</p>
<p>HAL 库是如何启动到 main() 的? 这个问题我就下面再讲解。</p>
<p>然后,这些都是板子到货之前的工作了。</p>
<h1 id="下载遇到了问题">下载遇到了问题</h1>
<p>EGmicro 官方给的 Demo 包,里面还包括了一个给 KEIL5 IDE 使用的一个 .pack 文件。我没有用过 keil,不知道这个文件具体的作用。
但是 7z 能打开它,发现是个压缩包。于是解压之。解压后的文件,里面也有一个 HAL 库。显然,将 HAL 库 复制 到 工程里,看来是 keil 的标准做法(而且是极其愚蠢的做法)了。</p>
<p>其中还有个 EG32M0xx.svd 文件,这个文件看来是调试用的。openocd 需要用到它。
然后还有一个百思不得其解的文件 EG32M0xx_EFLASH_PROG.FLM</p>
<p>这个文件是干嘛用的呢?</p>
<h1 id="openocd-不支持-eg6832">openocd 不支持 eg6832</h1>
<p>终于等了数天,板子到了。兴冲冲的接上 swd 调试器,然后 openocd 命令一敲….</p>
<p>傻眼了。 openocd 不支持。还得写 target 文件。于是依葫芦画瓢写了一个。
一跑,傻眼了, openocd 提示 flash 算法不支持。</p>
<p>啊??? flash 算法? 那是什么鬼。</p>
<p>==,EG32M0xx_EFLASH_PROG.FLM 这个文件是不是就是所谓的 flash 算法文件? 所以 keil 才能对 eg6832 编程?是因为官方的 pack 文件里打包了这个 FLM 文件?</p>
<p>于是带着疑问研究了 openocd 的文档,一无所获。 只知道 openocd 的 flash 算法,是它自己支持的,不需要也不能靠载入某个 FLM 文件。</p>
<p>但是意外的发现了一个叫 pyocd 的项目。它虽然说不支持载入 FLM 文件,但是它能把 FLM 文件变成 py 代码,然后整合进去。。。 它就支持了某款 mcu 咯。</p>
<h1 id="修改-pyocd搞定了-eg6832-的程序下载">修改 pyocd,搞定了 EG6832 的程序下载</h1>
<p>python 真 鸡儿 是个垃圾语言。修改 pyocd 本来应该是一个很简单的事情。结果因为py的垃圾 cache 机制,修改的 .py 文件死活没生效。
真是个垃圾语言。然后修改完毕,跑通!</p>
<p>顺便给官方发了个 PR 。也不知道响应时间如何。<a href="https://github.com/microcai/pyOCD">pyOCD 改版仓库</a> 有需要的自取。</p>
<h1 id="arm-mcu-hal-之-启动">ARM MCU HAL 之 启动</h1>
<p>HAL 库主要做两件事: 1. 将寄存器操作抽象为具体的 C 函数名。方便开发和移植。2. 启动 main()</p>
<p>第一件事其实很好理解,因为 arm mcu 基本上采用的是 MMIO, 因此每个外设包含若干寄存器。每个寄存器就都分配一个内存地址。这样每个外设就会占用一片连续的地址。这片连续的地址,就正好编写为一个 C 结构体。结构体里每个成员变量都对应一个寄存器。然后再使用C语言的强制转换,将这个外设的寄存器首地址,直接强转为这个外设对应的结构体。
比如 定时器,在 HAL 里是一个 struct TimerDef, 然后定义 TIMER1 = (TimerDef*) ( 0x40020xxx ), TIMER2 = (TimerDef*) ( 0x40021xxx )。
这样操作寄存器,就简化为了操作 TIMER1->xxx, TIMER2->xxx 变量。</p>
<p>第二个事,就开始难以瞬间秒懂了。启动 main 之前,hal 库都干了啥?</p>
<p>最关键的是, mcu 代码是没有 OS 的,所以,hal 库,是需要知道内存布局才能工作的。不然 malloc() 调用要如何实现呢?</p>
<p>原来,这部分功能,是由 链接器 脚本实现的。在 hal 库里,会携带对应 mcu 的 链接器 脚本。由链接期脚本来保证,入口代码一定是放在 Flash 的第一个字节。</p>
<pre><code class="language-ldscript">
/* 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'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);
} >FLASH
......
</code></pre>
<p>这里摘录一个 mcu 的 链接期脚本。ENTRY(Reset_Handler) 定义了入口点。也就是整个代码,从 void Reset_Handler() 开始执行。
注意了,接下来一行是最关键的代码,任何代码都没有这行关键 <code class="language-plaintext highlighter-rouge">_estack = 0x20018000; /* end of RAM */</code>
这个代码,其实是定义了内存的顶部地址。在 arm mcu 里,内存会被划分为 [静态分配的变量][堆][栈] 三个区域。</p>
<p><code class="language-plaintext highlighter-rouge">RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 96K</code> 这个代码,指示了链接期在放置 .data 区的时候,地址从 0x20000000 开始,最多可以放96k.</p>
<p>最终,ldscript 里输出的 <strong>bss_end</strong> 这个变量,就变成了 libc 里的堆的起点。堆向栈的方向增长。
这样, libc 就获得了内存布局。</p>
<p>启动代码,也得已布局在 0x08000000 开始的地方。由于该 mcu 将 flash 映射为 0x08000000-0x08100000,rodata 和 text 也得以顺利的摆放到正确位置。</p>
<p>为了让 启动代码确确实实的放置在 0x08000000 的地方,ldscript 特意首先布局 .isr_vector 到 >FLASH. 而 HAL 里,唯一一个定义为 .isr_vector section名字
的代码,就是一个 ISR 数组。数组的第一个是 栈顶, 代码如下</p>
<pre><code class="language-C">/* Vector table */
__attribute__((section(".isr_vector")))
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
.....
};
</code></pre>
<p>定义了一个叫 __vector_handlers 的数组,数组的第一个元素,就必然会被放入 0x08000000,而这个地址,按 mcu 的手册,芯片上电后,会自动从 0x08000000 载入栈顶,0x08000004 载入指令。</p>
<p>0x08000004 恰恰就是 __vector_handlers 的第二个元素,也就是 Reset_Handler 的地址。
0x08000000 恰恰就是 ldscript 里设置的 _estack。</p>
<p>于是这么安排下,cpu 上电,就自动运行 Reset_Handler 了,而且连 栈顶都设置好了。意味着 Reset_Handler 可以直接由 C 语言编写。无需汇编指令。</p>
<p>Reset_Handler 的做法,其实就是初始化 libc , 然后执行 main()。</p>
<h1 id="总结">总结</h1>
<p>mcu 的启动代码,需要 ld script 的搭配。ld script 巧妙的设定了 mcu 规定的指定地址的内容。而 内存大小, FLASH 大小,这些信息都是在 ldscript 里设置的。
ldscript 设置的变量,可以在代码里,按全局变量的方式直接使用。这样就完成了将 mcu 的内存布局信息传递给了 libc. 使得 mcu 里,脱离 OS 也可以继续使用 malloc/free 函数进行内存动态分配。</p>
PCB和芯片
2024-01-02T00:00:00+00:00
https://microcai.org/2024/01/02/pcb-and-ic-manufactoring
<p>进来做变频器,画了不少 PCB 板子。突然发现画 PCB 和芯片设计,有极大的共同点。</p>
<h1 id="先说走线和分层">先说走线和分层</h1>
<p>现时 PCB 上的元件,主要以表面贴片为主。元件浮于PCB表面。特别是,我为了节约打样成本,使用的都是单面贴片工艺。更像芯片了。元件只集中于pcb的一个表面。然后线路在 pcb 和 pcb 的另一面。</p>
<p>如果把电路板倒过来,让贴片元件朝下当“底面”,那就是 元件为底层,上层为走线层。</p>
<p>这和 IC 的情况是极其相似的。集成电路里,蚀刻法制作的元件,都处于硅的表面。然后是十数层走线层。拿单面贴片的4层板举例,就分了元件层,和4个走线层。</p>
<h1 id="接着说封装">接着说封装</h1>
<p>在 PCB 上放置一个元件的时候,EDA 并不清楚元件的内部走线。EDA 要做的是,在 PCB 上留下一个“区域” 放置这个元件,以及连接这个元件的若干“焊盘”。</p>
<p>在 IC 设计领域,也有常说的 “硬IP核”。硬IP核在 EDA 里,也是一个固定大小的,内部情况不透明的“区域”,以及连接这个IP核的若干“输入输出”。</p>
<p>甚至。连最基础的“MOSFET”,在芯片的 EDA 里,其实也是“封装”。设计者只能选择将这个 MOSFET 放置此处。有3个引脚。但是,这个 MOSFET 的内部结构,是看不到的。这个和在 PCB 上放置最普通的贴片MOS的情况是一模一样的。</p>
<p>在 PCB 的 EDA 软件里,各种元件都有“封装”,而在芯片的EDA软件里,各种元件,也有封装。
设计pcb既可以选择分立元件,也可以使用各种IC。放在芯片设计领域,就是既可以选择软IP也可以选择硬IP。</p>
<p>软IP, 在,PCB的 EDA软件里,叫“原理图库”。画原理图的时候可以直接引用。但是转PCB的时候,会把原理图里引入的各种元件引入,你可以重新安排内部元件。而且还可以随时修改原理图。</p>
<p>在芯片EDA里,软IP, 也不再是一个裸晶上占用固定位置的“封装”,而是会和你自己写的代码一起编译。</p>
<h1 id="接着说原理图">接着说原理图</h1>
<p>要画 PCB, 先出原理图。原理图上记录里的是 PCB 的原理电路。而芯片,也就集成电路,其实和印刷电路是一回事。只不过一个是毫米级工艺,一个是纳米级工艺。</p>
<p>既是电路,一样要原理图。</p>
<p>但是,芯片和 pcb 的最大区别,不是工艺,而是规模。</p>
<p>一个PCB上,有数百元件,已经算复杂了。</p>
<p>但是一个芯片,百万元件都是小打小闹。如今最尖端的芯片,上有百亿元件。</p>
<p>PCB尚可以手绘。设计集成电路,绝无手绘的可能。</p>
<p>因此,PCB的原理图,可以是一个电路图。
但是,芯片的原理图,就绝无可能是一个电路图。可以说,这个电路图,只能是某种更合适的描述方法的中间产物。</p>
<p>这个更何时的描述方法,就叫硬件设计语言。而且要经过两次编译转换,才能转换为如pcb那样的原理图。而且这个原理图,无法为人所读。</p>
<p>硬件设计语言,描述的是硬件的逻辑功能。经过编译器编译后,转变为能实现这个逻辑功能的“门电路”。门电路,是数字电路的基础。正如模拟电路的基础是运放。
但是,不管是门电路还是运放,都不是单一基础元件能实现的。实现一个门电路,需要数个nmos和pmos组成。因为电路里既有nmos又有pmos, 所以又称为互补mos。这也是 CMOS 名称的由来。</p>
<p><em>PS:一个运放也是需要数个 NPN型 BJT 和 PNP型 BJT 实现。所以模拟芯片的工艺,又可以叫CBJT?</em></p>
<p>到编译为门电路这一步,芯片的功能设计就完成了。可以试着使用一种叫“可编程门电路阵列”的辅助工具进行调试了。</p>
<p>只所以在设计阶段只到门电路,是因为接下来仿真的成本直线上升了。门电路仿真,仿真软件最低只仿真到门电路这一级别。而且对里面电流是不模拟的,只模拟逻辑上的 0 和 1。</p>
<p>但是,从门电路,到 CMOS, 就要面对器件的非数字性了。要考虑信号的上升时间,下降时间,传播时间了。模拟的难度直线上升。特别是现在芯片动辄包含千万门电路,也许动用超算都无法进行完整模拟了。</p>
<h1 id="说布线">说布线</h1>
<p>从芯片设计语言,再到门电路阵列,再到 MOS 互联电路 (PCB原理图)。接下来要做的,就和 PCB 设计如出一辙了。</p>
<p>只不过,扩大了的规模,成百上千倍的提高了工作量和难度。</p>
<p>不过,也有一个好消息。就是数字电路的规模虽大,但是很多netlist其实看起来是简单重复的。
这种简单重复的电路连接,就可以考虑让电脑自动完成布线。</p>
<p>PCB设计里,虽然有自动布线。但是多数布线还是依赖手工。就是因为 pcb 里很少会出现单一规律的简单重复连线。导致自动布线总是不如意。</p>
<p>而芯片,且不说这种简单重复的电路,自动布线是得心应手。就算有些不如意,人又如何检查的出来呢?</p>
<p>正如软件工程师有时候会检查下特定代码经过编译器后会产生如何的机器指令。但是多数时候,他们也只能选择“相信编译器”。然后祈祷编译后的代码能正常运行。</p>
<p>所以,芯片工程师,也只能选择相信 EDA 给出的结果。然后祈祷流片回来的芯片能正常工作。</p>
<h1 id="前后端分工">前后端分工</h1>
<p>在PCB行业,pcb的设计分为“原理图绘制” 和 “PCB布线” 两种任务。两个任务可以一个人完成,也可以拆分。</p>
<p>同样的,芯片制造业,也分芯片前端和芯片后端。</p>
<p>原理图绘制,面对的主要是“功能逻辑”。而在 PCB布线阶段,就要考虑和 PCB加工厂的合作了。
因为PCB加工厂的能力,决定了PCB布线的时候能否随心所欲。比如 PCB加工厂无法加工低于 0.1mm 的线,那绘制的时候,就必须要保证所有的走线都要比 0.1mm 宽。比如PCB加工厂无法提供“盘中孔”工艺,那pcb布线的时候就只能选择扇出后打孔。而不能直接在焊盘上打孔换层。pcb加工厂只能最多加工8层线路板。那设计的时候就只能多飞线,勤换层了。</p>
<p>PCB的工艺还会影响线路的“寄生参数”。反过来又要影响 pcb 的设计。</p>
<p>同样的,在芯片制造业,芯片代工厂的制造能力同样也会限制芯片后端工程师的发挥。工程师只能在工厂的能力范围内进行设计。</p>
<p>通常来说,芯片工厂只会宣传“特征工艺”的参数。但是影响电流设计的参数千千万万。可不是一个纳米就能描述的清楚的。在高速 PCB设计领域,PCB工艺对设计最直接影响的地方,就是线路的寄生电容和寄生电感。这决定了pcb布线的特征阻抗。而高速信号对阻抗异常敏感。</p>
<p>而且 pcb 工厂只加工线路,不制造元件。而芯片代工厂不仅仅加工线路,也制造元件。所以代工厂的工艺除了寄生电容寄生电感,还包括其制造的 MOS 的各项参数。</p>
<h1 id="结论">结论</h1>
<p>所以造芯片和造PCB其实从根上说,是一回事。只不过 芯片倾向于简单电路的超大规模重复叠加。因此更容易实现自动化。而更大的规模也导致不得不依赖自动化。 pcb 的规模一直都维持在较小的范围。因为复杂的逻辑都倾向于使用IC代劳。</p>
三相的恒功率优势
2023-12-27T00:00:00+00:00
https://microcai.org/2023/12/27/3pharse-talk-again
<p>在 <a href="/2022/02/14/ev-charging-and-3phase-balance.html">这篇</a> 文章里,我曾经说,三相电是恒功率的。</p>
<p>现在我用一张更直观的图演示这个结论。</p>
<p><img src="/images/3pharse_nice.jpg" alt="三相的恒功率图" /></p>
<p>因为电压*电流=功率。所以图片里是使用的纯电阻负载的功率。</p>
<p>每一相的功率,总是会不断的从 0 到最大,然后从最大到 0. 所以这就是单相电娘胎里带来的劣根性: 每秒钟有100次的功率变化。而且最低是无功率输出。</p>
<p>但是,如果是三相,情况就发生了变化。虽然每相还是会不断的变化输出功率,但是三者的和却是一个定值。</p>
<p>如果是电流有一定滞后的负载,比如电机,则这个图片也能说明</p>
<p><img src="/images/3pharse_nice2.png" alt="三相电机的恒功率图" /></p>
<p>电流的相位即使滞后,总功率还是恒定的。</p>
<p>这种总功率恒定的特点,就使得三相电驱动的电机,始终能保持一个恒定的扭矩。
而不会像单相电机那样,扭矩每秒出现100次波动。扭矩每秒出现100次波动,相当与6000转的二冲程发动机的扭矩波动。相当于3000转的4缸发动机的扭矩波动。那是相当的振麻了。</p>
<p>即使不是电机,也能从恒功率特性中获取优势。
比如,开关电源不再需要一个巨大的储能电容。
当然,一个小容量的滤波电容还是必要的。毕竟需要滤除 pwm 导致的高频谐波。</p>
<p>– 更新</p>
<p>弄了个视频来展示三相恒功率和单相脉动功率导致的电机扭矩差异</p>
<iframe src="//player.bilibili.com/player.html?bvid=BV1Fc4118794&page=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe>
foc速成班
2023-12-22T00:00:00+00:00
https://microcai.org/2023/12/22/clarke-transform-is-not-essential
<h1 id="前言">前言</h1>
<p>在我开始研究 foc 的时候,所有的书籍都在介绍 park 变换和 clarke 变换。有一种魔力告诉你,没这俩变换,就写不成 foc.</p>
<p>然而事实的真相并不是如此。</p>
<h2 id="foc-要控制什么">foc 要控制什么</h2>
<p>首先得了解,在 foc 发明前,电机是如何控制的? foc 发明以前,无刷电机采取的是六步换向法。电机每旋转60度,控制器就要进行一次“电子换向”。控制器输出的,是同一时刻
只有2条线有输出的方波。</p>
<p>由此,在方波时代,电机控制器要控制的无非就是方波的电压。</p>
<hr />
<p>真的是这样的吗?</p>
<p>事实上,经常玩航模的童鞋就知道。航模的电调,有一个参数叫“换向提前角”。电调会在本不是换向的时间提前换向。<em>这个提前的时间点,从时间上来说自然是和转速有关。从角度上来说,就和转速无关了。所以叫提前角,而不是提前时间。</em></p>
<p>为何要提前换向呢?转子还没到该换向的位置,为何要提前换向呢?</p>
<p>如果这个问题没有搞明白,是无法理解 foc 的。</p>
<p>foc 之所以需要电流采样,本质上就是为了确定提前换向角。而不是像航模的电调那样还得人工配置。</p>
<p>为啥要提前换向呢?</p>
<p>因为磁场是电流建立的,不是电压建立的。而绕组是一个电感。电感的电流会滞后电压。准时换向,不过是让电压和转子呈 90度角度。而电流滞后,会导致电流并不和转子呈90度,也就是磁场没有和转子呈90度夹角,也就是扭矩没有最大化发挥。</p>
<p>要最大化发挥电机的扭矩,要磁场垂直于转子,电压就必须有一定的提前量。这个提前量,就只能通过对电流的采样获取。</p>
<p>所以, foc 的目的,就是让定子磁场垂直于转子磁场。而电流的采样,就是为了获得电流对电压的滞后角,从而对电压角进行提前。</p>
<h1 id="前人为啥要进行-parkclark-变换">前人为啥要进行 park/clark 变换</h1>
<p>教材上说,是为了将三相变两相。因为两相少一个变量,好控制。</p>
<p>其实那都是教材不懂装懂瞎说的。三相电流本来就只有2个属性。幅值和相位。这个幅值,说的是他完整周期内的最大值。</p>
<p>但是,最大值,一个周期里只出现2次。也就是说,在某个采样的瞬间,你拿到的,大概率都只是中间的某个值。难道要连续采样一个完整周期才才能确定最大值吗?</p>
<p>不能。</p>
<p>那如何能在采样的瞬间,就能直接知晓最大值和相位呢?</p>
<p>前人的答案之一,就是 逆 clark 变换 + 逆 park 变换。</p>
<h2 id="三相坐标系里的角度描述很困难">三相坐标系里的角度描述很困难</h2>
<p>因为我们的数学工具,对角度的描述,是在平面直角坐标系里的。三相电,其实是在平面三维坐标系里的。如果你有数学工具可以直接在这种平面三维坐标系里工作,那就不需要进行变换。</p>
<p>但是,如果我们将这个变换,限定在一个模块呢? 只限定在电流采样模块。</p>
<p>电流采样模块,只对上层输出两个参数, 电流的交流幅值,和当前的相位角。</p>
<p>那么,整个电机的控制代码,就不会再有任何一个地方需要 park/clark 变换. 而且电流采样那边,只进行一次反clarke 变换。而这个反 clarke 变换,就只是三相瞬时电流转幅值+相角描述的一个转换。</p>
<p>那么整个电机控制流程逻辑,就会变得异常清晰明白。不会有反直觉的变换掺杂其中。</p>
<p>甚至,电流采样是 foc 达到最大效率的一个手段。没有电流采样,不过是效率不能最佳化。所以这个电流采样甚至不是初期必修课。</p>
<h2 id="所以前人是为了自己软件工程上的考虑">所以前人是为了自己软件工程上的考虑</h2>
<p>前人是为自己软件实现方便上的考虑。既然电流采样这边是必须要进行变换。否则无法获得电流相角。那就索性其他地方也全部改成二维坐标系。
而前人的这个决定,被后人东施效颦,只敢模仿,不敢超越。</p>
<h1 id="先抛开电流采样">先抛开电流采样</h1>
<p>如果不进行电流采样,foc 的算法如何控制呢?</p>
<p>算法的输入有2个: 期望的扭矩值,当前转子位置。 输出有3个: 分别是三个相位的 pwm 占空比。</p>
<p>当前的转子位置,推导出来的是 输出的电压相位值。方法是 转子位置+90度+提前角。 提前角暂取 0.</p>
<p>根据期望的扭矩值,推算出来的是 电压幅值。这个电压幅值+电压相位值,最终就变成了 三相电的各自电压值,然后得出 pwm 占空比。</p>
<p>那么三相电压各自的值,由电压幅值*sin(相位值) 获得。另外2相,依此落后120度。然后各自的值,和 直流母线电压的比值,就是 pwm 占空比。</p>
<p>这个是 spwm 法。其实还有个不同的算法,可以获得三相各自的 pwm 值。这个方法其实脱胎自无刷电机方波控制的六步换向法。</p>
<p>六步换向法实际上在驱动电机的时候,是在电压角为转子角度60度的时候,换一次向,于是电压提前转子120度。然后转子转60度后,再次换向。</p>
<p>也就是说,定子磁场的方向是固定的6个方向。 定子磁场和转子磁场夹角缩小到60度的时候进行换向。定子磁场和转子磁场角度就会在 120度和60度之间波动,阶梯前进。
(当然,这是在不考虑电流滞后效应的情况下。)</p>
<p>而 svpwm ,就是让磁场在 60度和120度之间来回切换,模拟出90度夹角。也就是,用6个固定的矢量,合成出一个360度无死角的矢量。这就是空间矢量合成名字的由来。</p>
<p>进行矢量合成,是用的临近的2个矢量合成的,所以进行了6个扇区的划分。这样就能知道该用哪两个矢量进行合成。</p>
<p>在进行合成矢量的时候,会计算出3个时间, T0 T1 和 T2 。 这三个时间,分别是 “无输出” , 输出 距离转子最近的矢量,和稍微远点的那个矢量。</p>
<p><img src="/images/svpwm.png" alt="" /></p>
<p>如图,黑色是转子的方向,红色是希望生成的定子磁场的方向,两个绿色就是本次要调用的2条矢量。
T1 是深绿色的矢量时间,T2 是浅绿色的矢量时间。</p>
<p>T1 和 T2 的时间之比,就决定了 红色这条合成的定子磁场方向。而 T0 时间,就决定了红色的长度。</p>
<p>而 T1 和 T2 的时间,其实很好算,就是 红色和两条绿色线里的两个夹角的 sin 值。</p>
<p>有了 T1 和 T2 时间,就该利用 红线所在的扇区,把矢量的时间分配,变成 mos 管的导通时间分配。</p>
<p>于是就有了各大 foc 算法里的那个长 switch case 语句</p>
<p>你看,在进行 svpwm 输出的时候,是不需要进行什么park变换的。就只需要考虑要输出多大幅值的交流电,要输出的相位角是多少。</p>
<h1 id="决定提前角">决定提前角</h1>
<p>在上面,红色是磁场方向。我们输出交流电的时候,是假定磁场方向和电压方向相同。
但是实际上,磁场方向会滞后。因为线圈有电感值,电流相位会落后电压。</p>
<p>但是,具体会落后多少,无法提前知道。只能实时测量。而且每次测量的时候,获得的只能是某个具体一瞬间的3个电流值。</p>
<p>比如,我测得 0.5A 0.5A -1A 三个电流值。那么如何知道,这个交流电的峰值? 因为这个峰值,是进行电流闭环控制所必须的数据。 而相位,也是进行计算提前角的必须数据。</p>
<p>三相交流电,其合成矢量,其实是绕着一个圆进行圆周运动。所以,其实是可以利用3矢量合成法。
就获得了最终的合成矢量。而这个合成矢量的长度,就是三相电电压的幅值。这个合成矢量的方向,就是三相电的相位角。</p>
<p>所以,其实这里既可以直接按矢量进行计算,也可以进行 Clark 变换后,变成2维坐标,再进行 park 变换后, 获得所谓的 q 轴 和 d 轴电流。 如果 PID 控制算法的作用目标是让 Id = 0, 则最终输出的电压 Vd, 经过 反 clarke 变换后,恰好就会让电压(相比于使用90度垂直)的提前角 = 电流的滞后角。</p>
<p>所以,既可以让 PID 算法去逼近 Id= 0 的目标,也可以算出电流相位滞后角后,直接对电压的相位角进行提前。</p>
<h1 id="为何前人会选择让-pid-算法去逼近-id--0">为何前人会选择让 PID 算法去逼近 Id = 0</h1>
<p>因为park提出这个算法的时候,电机控制还属于新兴行业。并没有cpu这种东西可以用来奢侈的进行控制。</p>
<p>所以,电机输出用的 pwm 波,其实是用 纯电路,三角波和比较器输出的。
硬件电路,特别适合进行“负反馈”操作,对自己的输出进行锁定。</p>
<p>于是,他们设计了硬件的 park 变换电路,然后用硬件电路进行 负反馈。完成锁相角的任务。
已说到锁相角,搞模拟电路的让应该非常熟悉一个词, PLL。</p>
<p>后来把控制任务搬到软件上的时候,他们就沿用了这套控制流程。</p>
<p>而他们搬迁的时候写的 “电机库” 就被业内程序员奉为圭臬了。</p>
变频器制作-第九部分之ESP32C3 也能输出svpwm
2023-12-20T00:00:00+00:00
https://microcai.org/2023/12/20/esp32c3-can-drive-svpwm
<h1 id="前言">前言</h1>
<p>先看以下乐馨官方的 ESP32S3 和 ESP32C3 的对比</p>
<p><img src="/images/esp32s3-vs-esp32c3.png" alt="" /></p>
<p>很明显,ESP32C3 缺乏 MCPWM 设备。</p>
<p>所谓 MCPWM, 就是专门为产生svpwm设计的电路。他能产生6路互补的PWM信号,并且实现中央对齐。然后还能实现在PWM信号的特定位置产生同步事件。app据此可以在这个同步事件上实现电流采样。</p>
<p>一度我以为 ESP32C3 是无法用于驱动电机的。</p>
<p>直到一次偶然,我看到 SimpleFOC 的论坛里有人提到要支持 ESP32C3。我在想,这C3不是没MCPWM么? SimpleFOC咋支持?</p>
<p>事实是,SimpleFOC 也确实不支持 C3。但是有人回复他实现过。就用的 LEDC PWM。</p>
<p>要知道,ESP32C3 可比 S3 便宜了一半还多。要真的能驱动电机,那我的 VVVF 变频器大业,又多了一项可选的MCU!</p>
<h1 id="实践">实践</h1>
<p>虽然别人有提过他用 LEDC PWM 实现了 foc。但是吧,无代码无真相。但是总归人家说可以。那我研究研究吧。于是购入 ESP32S3。</p>
<p>等等,不是说C3吗?咋买了S3呢?因为我确信我的VVVF需要蓝牙!需要WIFI!AT32F415已经不能满足我的需求了。</p>
<p>然后顺便买了个9.9的 ESP32C3开发板。</p>
<p>等开发板到了,我马上研究起了 ESP32C3。虽然C3是顺便买的,但是不妨碍我先研究它。</p>
<p>于是在项目里创建了 lib/libvfd/hal/esp32c3pwm.{hpp,cpp} 文件。</p>
<p>着手研究 ESP32C3 里的 LEDC PWM。</p>
<p>最后踩了无数的坑后,把他实现出来了。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
#if defined(ESP_PLATFORM)
#ifdef CONFIG_IDF_TARGET_ESP32C3
#include "esp32c3pwm.hpp"
#include "driver/ledc.h"
#include "esp_err.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "private/commons.hpp"
#include "os/os.hpp"
#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 = "vfd";
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 << 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(&timer_config);
timer_config.timer_num = LEDC_TIMER_1;
ret = ledc_timer_config(&timer_config);
os::printf("ledc_channel_config return %d\n", 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 < 6; ch++)
{
ret = ledc_channel_config(&ledc_channel[ch]);
os::printf("ledc_channel_config return %d\n", ret);
}
stop();
}
void set_duty(float_number U_a, float_number U_b, float_number U_c)
{
auto u_lpoint = clamp<int>(U_a * pwm_period, 0, pwm_period-1);
auto v_lpoint = clamp<int>(U_b * pwm_period, 0, pwm_period-1);
auto w_lpoint = clamp<int>(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 <= 6000 && freq >= 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<esp32c3pwmdriver_impl*>(user_data);_this->pwm_cb(_this->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 = "pwm_tmr",
.skip_unhandled_events = true,
};
esp_timer_create(&timer_arg, &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->set_duty(U_a, U_b, U_c);
}
void esp32c3pwmdriver::start() { impl->start(); }
void esp32c3pwmdriver::stop() { impl->stop(); }
int esp32c3pwmdriver::get_frequency() { return impl->get_frequency(); }
void esp32c3pwmdriver::set_frequency(int f) { impl->set_frequency(f); }
void esp32c3pwmdriver::link_timer(timer_fn fn) { impl->link_timer(fn); }
}
#endif // CONFIG_IDF_TARGET_ESP32C3
#endif // defined(ESP_PLATFORM)
</code></pre></div></div>
<p>不同于其他平台使用 PWM 自身的定时器中断,esp32c3 上,pwm 周期更新的回调是由一个高精度定时器回调提供的。之所以不用 pwm 的回调,是因为 ledc pwm 并不会产生这样的中断——除非使用了硬件fade功能。
但电机控制岂是fade能瞎fade的?</p>
<h1 id="运行">运行</h1>
<p>实际上,由于实现的仓促。这个pwm输出总觉得不是很靠谱。不过当我怀着忐忑的心情,准备报废掉一个耗资300 大洋让JLC打样贴片的驱动版的时候。。。奇迹发生了。电机转起来了。</p>
<p>不过,因为支持的pwm频率很有限,导致无法用电机播放《世上只有妈妈好了》。</p>
<p>当然,之前的驱动板播放的音乐可以猛戳<a href="https://www.bilibili.com/video/BV1ce411C79z/">这里</a>看。</p>
标准库 sin 之错
2023-12-19T00:00:00+00:00
https://microcai.org/2023/12/19/why-sin-is-wrong
<h1 id="标准库-sin">标准库 sin()</h1>
<p>在 C 语言里,有一个计算三角函数的函数, sin() , 其定义为 <code class="language-plaintext highlighter-rouge">double sin(double)</code></p>
<p>由于 sin 的结果大概率为无理数,所以使用 浮点数为参数也未尝不可。</p>
<p>但,我要说但是了。</p>
<p>在工业界,不管是土木工程还是机械制造,我们都倾向于使用“角度”而不是”弧度“。</p>
<h1 id="转换一下很困难">转换一下很困难?</h1>
<p>如果所有的程序都在干角度转弧度这件事,那只能说明库错了。库逼迫大家用弧度,而实际上大家用角度。</p>
<p>看下 180/pi 这个常数的定义这各大软件里有多普遍。就知道实际上大家都这用角度存储。只是这需要 sin 值的时候再转弧度调用 sin。
因为 sin 本身是一个开销很大的操作,所以很多程序员只是觉得多了一个乘法,并没有意识到 sin 的接口定义错了。</p>
<h1 id="为何应该用角度">为何应该用角度?</h1>
<p>因为常用角度是整数。在计算机里,弧度无法精确的表达90度,45度,30度,和60度。这都是最常用的角度。
而无法精确表达就要取近似。取近似就要丢精度。</p>
<p>而精度误差是可以累积的。</p>
制作变频器-第八部分
2023-12-16T00:00:00+00:00
https://microcai.org/2023/12/16/sync-modulation-corrected
<h1 id="载波比得多少合适">载波比得多少合适</h1>
<p>在 <a href="/2023/12/05/sync-pwm-async-pwm.html">上一篇</a> 文章里,我讲解了同步调制和异步调制。</p>
<p>那时候,实现的同步调制是有问题的。主要在同步调制在载波比的计算上出了问题。</p>
<p>原来的想法是,载波比是要能被3整除。同时还得是奇数。</p>
<p>其实这个公式说的是,正弦波半周上的 pwm 波的数量,得是能被3整除。同时还得是奇数。
也就是说,其实整个周期的载波比,是能同时被6整除. 同时还得是2的奇数倍。</p>
<p>于是发现了代码中的错误。进行了修正。</p>
<p>虽然实际上靠听觉并不能发现电机的声音有任何变化。。。。。。</p>
<p><img src="/images/spwm.png" alt="正弦波pwm" /></p>
<p>在同步调制里,正弦波的频率变高。但是, 每个正弦波所包含的 pwm 方波的个数是维持不变的。而且 pwm 方波的个数,在每个半周里,必须是奇数。
这样 pwm 占空比最大的那个位置,正好对应 正弦值最大的时刻。
这样的谐波是最小的。</p>
<p>如上图所示, 每个半周里,有5个 pwm 波。整个周期里,一共是10个 pwm。所以载波比为 10。 而 10 正好是 2 的奇数倍。</p>
<p>而这,只是对单相正弦信号输出的要求。</p>
<p>如果要输出三相对称正弦波信号,则载波比N,还必须同时满足能被3整除。 这样每个相的正弦波,也都在 pwm 波开始的时间里,保持对称。</p>
<p>因为 10 不能被3整除</p>
<p>所以要再向后找 14, 18 . 于是第一个满足这个要求的载波比是 18</p>
<p>然后接下来,每个满足的载波比数,都依次比上一次多 12</p>
<p>18 30 42 54</p>
<p>之前的代码就是这个地方计算错了。</p>
<p>今天改进了这个。把载波比计算终于正确了。</p>
<p>错误的载波比,并没用起到同步调制减少谐波的优势。还不如实现起来更简单的异步调制。</p>
<h1 id="载波和基波的相位同步">载波和基波的相位同步</h1>
<p>除了载波比的计算要正确。载波的相位还得和基波同步。才能保证载波pwm占空比最大的那个地方,恰好也对应于 sin 值最大的地方。</p>
<p>只要有一次,基波和载波的相位对上,则在下一次动载波比之前,就能一直维持住同步。</p>
<p>也就是说,进行相位对齐的时机,是在修改载波比的时候。</p>
<p>为了让输出波形不会突变到和载波对齐。在修改载波比的时候,要进行一段时间的异步调制。然后让载波和基波频率对上后。切换为同步调制。</p>
<p>在异步调制的时候,随时监控基波和载波的相位差,追平的时候,立刻切换为同步调制。</p>
<p>一旦进入同步调制。计算占空比的代码其实会非常简单。
因为同步调制下,占空比是周期变化并且固定的。而且是固定的几个数值。</p>
<p>为何之前说,同步调制的代码更复杂。这里又说,一旦进入同步调制,计算占空比的代码会非常简单呢?</p>
<p>因为同步调制的代码是逻辑复杂。要进行“换挡” 操作。换挡后还要进行同步。</p>
<p>但是,计算量却少了。因为占空比的值是完全固化了。</p>
<p><img src="/images/improved_pwm_calc.jpg" alt="计算用时" /></p>
<p>如图所示,经过改进后的代码,计算 pwm 占空比的时间缩短到了5微妙。在次之前,计算时间为 9us。 ps,在使用定点数算法优化前的计算时间为35us。</p>
<p>图片中的载波比,显示的是半个sin周期里 pwm 波的数量。因此是永远为奇数。且能被3整除。之所以折半显示,是为了能心里默算它是不是正确的。</p>
<p>不过,这个代码在进入同步状态前,使用的还是异步调制。这个代码计算量就会比同步调制稍大。还是 10us。</p>
<p>但是因为显示屏更新速度慢,因此是抓不到这个瞬间的。因为相位对齐的速度是非常快的。 在换挡的一瞬间,马上就进入同步状态了。</p>
单片机的行业怪病
2023-12-11T00:00:00+00:00
https://microcai.org/2023/12/11/mcu-industry-myth
<h1 id="从单片机的存储空间说起">从单片机的存储空间说起</h1>
<p>别说市面上没有几百家也与几十家的单片机品牌。就是同一个单片机厂家,其单片机型号也是十分繁多的。</p>
<p>这些名目繁多的单片机型号,归根结底,是 核心性能,存储容量,和封装引脚 数量的排列组合。</p>
<p>其中,存储容量是极为关键的区分价格的因子。核心性能,都会划分为 低性能,主流性能,高性能。存储容量也会分成小容量,中容量,大容量,超大容量。</p>
<p>但是,即使是低端低性能单片机,如果搭配的是个大容量乃至超大容量的存储空间,售价一样就会高高在上,超过高性能小容量的单片机。</p>
<p>而仔细品味品味不同容量之间的售价差距会发现,单片机的存储空间,是按每 KB 多少块钱算的。</p>
<p>在 PC 行业,一块钱已经可以买到数个GB存储容量的当下,单片机还将 KB 卖到数元的高价。存储容量的价格差距达到百万倍以上。</p>
<h1 id="不思进取的单片机业界">不思进取的单片机业界</h1>
<p>老中医曾经说过,如果现在还要造 8GB 容量的机械硬盘,其成本并不会比 8TB 的低多少。所以,如果因为某些特殊原因,实在用不上超过 8GB 的存储空间,
使用PC进行项目开发的公司,依旧会选择1TB或者2TB的硬盘容量。并不是因为他需要那么大,而是因为当下这么大的最便宜。</p>
<p>但是,在单片机这个行业,情况发生了不一样的变化。如果你能够把你的代码裁剪到 8KB 大小,单片机厂就敢造 8KB 容量的单片机。
虽然他造8MB的成本和8KB并没有太大的区别。但是因为业界用不到8MB,他就会继续按8KB的目标市场造。并且为16KB的容量收取额外的溢价。</p>
<p>而单片机市场的受众,也被这种吃老本的源头所驯化。特别在意代码的裁剪。于是二者是相互成就。一直压着单片机的容量。在30年的时间里,单片机的主流容量,只从 8KB 增长到了 128KB。 增加不过 8 倍。</p>
<p>而同期的 PC ,存储容量则从 1.44MB 暴涨到 8TB。增长超过五千万倍。</p>
<p>整个单片机业界陷入一终迟滞。不仅仅厂家喜欢躺着吃老本。顾客也要求厂家吃老本。</p>
<p>曾经有人说,某单片机厂家非常良心。一款单片机卖了十几年还在供货,而且没涨价。他是想跨人家供货周期长,对自己的项目后续供应有保障。还夸人家十几年不涨价,保障了用户的利益。</p>
<p>我的天,如果 intel 现在还在按同样的价格卖 80386, 不知道他还会不会夸 intel 业界良心。</p>
<p>但是即使是同一个人, 他还是会骂 intel 是牙膏厂的.</p>
<p>那么为何会这样呢?</p>
<h1 id="还是代码出了问题">还是代码出了问题</h1>
<p>按理说, 顾客是不喜欢吃老本的商家的. 谁都希望花同样的钱买到的东西越来越好. 但是单片机程序员为何是个例外?</p>
<p>因为, 如果老款停产了,新款性能再好, 对单片机程序员来说都是没有用的.因为他的代码跑不了.</p>
<p>单片机的程序都是为特定的单片机完全适配开发的. 单片机不仅不能升级增加功能, 连完全不增加新功能只是单纯的提高主频, 都是不可以的. 因为单片机代码里大量的 delay() 函数已经写死了主频. 更换主频就会导致 delay 时间变短. 于是原来设计好的逻辑就无法正常运行了.</p>
<p>于是为了适配新款单片机,就得对代码进行修改. 修改完毕后要重新走测试验证流程. 这些都会额外的增加项目的开发成本.</p>
<p>单片机码农为了吃老本, 也就逼迫上游厂家吃老本了. 而上游厂家吃老本, 也逼迫新入行的程序员吃老本. 相互影响相互锁定.</p>
<p>所以, 破解这个死结, 首先要求程序员自我进步. 让单片机项目彻底和具体的某个单片机型号脱钩. 也就是要编写和硬件平台无关的代码. 把与硬件平台相关的代码最大限度的限制在一个非常小的范围.</p>
<p>要想达成这个目标, 就得选择一个更容易达成这个目标的好语言. 所谓工欲善其事必先利其器. 一个语言,必须要具备良好的抽象能力.</p>
<p>所以,选择单片机开发, 语言的第一个要求, 就是要支持更高层次的抽象能力. 所谓更高层次的抽象能力, 说人话, 就是要支持 “面向概念编程”。</p>
<p>面向概念编程, 具体到某个具体的语言里, 有不同的叫法。C 语言完全不支持。 所以 pass。</p>
<p>C++ 对面向概念编程的支持所提供的工具就是 c++20 提出的新特性: “concept”。</p>
<p>另一个具有良好抽象能力的语言,我叫他 javascript, 或则 nodejs。</p>
<p>除了要支持良好的抽象能力,性能也是不得不考虑的因素。 所谓的语言的性能,指的是编译器将高级语言转换为机器指令的时候,相比人肉写汇编指令多多插入的代码。
语言的性能越高,说明编译器生成的代码就越是精炼。</p>
<p>很多时候,语言对性能的影响是非常小的。主要还是看编译器的优化能力。</p>
<p>但是也有例外。编译器的优化能力是首先不能违背语言的定义。</p>
<p>比如,C 语言规定 除 0 是一个未定义行为。 于是编译器遇到你代码上写着 a / b; 就会直接生成硬件的 div 指令。 至于遇到 除 0 , cpu 会发生什么,编译器也不关心。因为 cpu 发生什么行为,都符合 C 语言标准的定义。那就是未定义行为。</p>
<p>但是,如果一个语言规定了,除 0 必须要有某种具体的后果。那么,在 cpu 执行 除法指令的行为和语言规范不一致的情况下,编译器必须要插入除数是否为0的检查。如果为0,则执行语言标准所定义的行为。于是你写了一个 a / b . 编译器却生成了大量的检查 b 是否为 0 , 并且为 0 后要干什么的指令。</p>
<p>那么,这种语言,编译器无论如何改进他的优化算法,都不能比C更高效。</p>
<p>这就是为何,很多程序员诟病 C 语言未定义行为太多。但是最终他们选的任何语言都不可能比C更高效。</p>
<p>所以,一个能用在单片机开发上的语言,必须比 C 语言更高级,有更高的抽象能力。但是,同样也要比 C 语言更高效。</p>
<p>恰巧, C++ 语言就是那个 0 开销增加高级抽象能力的, C 的 改进改进版。</p>
<h1 id="什么叫-0-开销抽象">什么叫 0 开销抽象</h1>
<p>C++ 把他高级的抽象能力叫做 0 开销抽象。为何叫 0 开销抽象呢?</p>
<p>所谓 0 开销抽象,是说如果你不用 C++ 提供的高级抽象的功能,而是使用 “手撸“ 的方式达成。那么你手写的代码必然不可能比编译器自动生成的代码更高效(开了编译优化的情况下)。 比如你用函数指针的方式实现 虚继承 法的多态。并不会比直接使用虚函数更快。</p>
<p>c++ 其实对于0开销的说法,是过于谦虚了。 c++ 的许多高级抽象能力,不仅仅不是 0 开销,还是负开销。</p>
<p>因为会比程序员手撸的方式更快。</p>
<p>比如模板元编程。让很多计算行为发生在编译期。这在 C 语言里用多少 宏 定义都做不到而不得不放在运行时运行的代码,在C++里,可以用模板元编程在编译期完成计算。</p>
<p>举个最简单的例子,处理正则表达式。</p>
<p>在 C 语言里,正则表达式是一个字符串。正则表达式引擎会在运行时的时候解析这个字符串,然后把这个字符串编译为一个字节码。这样后续用这个正则表达式匹配字符串的时候,就会非常高效。</p>
<p>但是,在 C++ 语言里,正则表达式可以在编译期就被直接转换为机器代码。做到这点靠的就是模板元编程。</p>
<p>而为了实现这点,需要使用模板元编程的方式写代码。代码就会显得不是特别直观。</p>
<p>为了解决模板元编程的代码不直观的问题。 C++ 又加入了 constexpr 和 concept 大杀器。</p>
<p>很多不理解的人,都会认为 C++ 的功能越来越多,越来越膨胀了。</p>
<p>但是理解的人就知道, C++ 所有的新功能增加,都是为了服务一个目的,就是为了更好的 0 开销抽象。</p>
<p>C++ 不仅仅没有因为功能越加越多而越来越慢,反而是因为 0 开销抽象能力越来越强而让代码变得越来越快。</p>
<p>比如右值引用,就减少了大量的毫无意义的拷贝构造。单纯的将原来的老代码用支持 c++11 的编译器重新编译都能获得速度提升。</p>
<h1 id="接口设计能力">接口设计能力</h1>
<p>完成同样的事情,其实可以设计很多种不同的接口。虽然最终都是殊途同归。但是也分易用和不易用。</p>
<p>就拿常见的 0.95寸的 OLED 屏幕来说,把这个屏幕封装成一个易用的接口,就有多种方式。</p>
<p>最常见的方式,是把这个屏幕封装成一个 iostream 流。用 printf 的方式使用。</p>
<p>其实还有一个封装方法,就是当成字符阵列。比如想在 第一行第三列输出一个字,可以这样写</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>oled[1][3] = 'H'
</code></pre></div></div>
<p>如果是在第2行第4列开始输出一行字,可以这样写</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>oled[2][4] = ”一行字“
</code></pre></div></div>
<p>如果是按像素,则可以依葫芦画瓢。因为 c++ 接受 运算符重载,于是可以通过重载 operator [] 实现按行列定位。
又因为 c++ 支持按函数参数重载。于是可以通过重载 operator = (各种类型) 实现输出不同类型的内容。文字,数字,图片都可以。</p>
<p>又因为 operator [] 也可以按不同类型的参数重载。 于是可以重载 int 参数,用于按文字大小的行列定位。
如果用 px 类型定位,就按像素。而且 px 类型,又可以使用 c++ 的 user defined literals 实现如下的用法</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>oled[3px][0px] = RED;
</code></pre></div></div>
<p>最终,这些操作都被转换成 spi或者 i2c 命令发往 oled 屏幕。</p>
<p>按日此方式做的接口,就可以说是非常的易用了。而且有利于用户在编写 GUI 代码的时候非常直观的明白输出效果。</p>
<p>而如果使用 C, 是没有这些能力的。</p>
<p>如果还是用 C 时代的方法设计接口,就容易只是看起来像是多了个对象,实际上还是 C 。</p>
<h1 id="表扬-esp32">表扬 ESP32</h1>
<p>在一众以 KB 算容量的单片机里, ESP32 鹤立鸡群的给以 MB 来计算容量的单片机。而且还比这些单片机更便宜。</p>
<p>而且率先以 c++ 作为开发语言,吊打一众只知道 C 和汇编的老厂。</p>
制作变频器-第七部分
2023-12-09T00:00:00+00:00
https://microcai.org/2023/12/09/dig-into-burnned-motor
<h1 id="从第一次烧变频器说起">从第一次烧变频器说起</h1>
<p>第一次烧变频器,也是第一次绕电机。</p>
<p>由于没有供实验用的小功率低压异步电机。所以只能买一个现有的,利用它的外壳,转子,定子。而替换掉它
的线圈。不就可以了?</p>
<p>于是说干就干。买了一个落地扇电机。主要是他小,而且便宜。糟蹋了不心疼。</p>
<p><img src="/images/1pharse-motor.jpg" alt="电风扇电机" /></p>
<p>然后</p>
<p>拆!</p>
<p>绕好后的效果就是这样</p>
<p><img src="/images/my_motor.jpg" alt="绕好的效果" /></p>
<p>然后,绕好的视频演示</p>
<p><a href="https://www.bilibili.com/video/BV1mg4y1o7rF/">BV1mg4y1o7rF</a></p>
<p>然后,mos管就烧了。</p>
<h1 id="研究问题根源">研究问题根源</h1>
<p>一开始我认为是电流过大(废话),降低电压,提高频率,自然就把电流降下来了。</p>
<p>但是电流降下来了,扭矩也没了。</p>
<p>要想电机还有扭矩,绕组就必须有一定的电流。这个电流还远大于变频器从电源吸收的电流。</p>
<p>因为这部分电流,就是在绕组里内部循环。也就是所谓的“无功功率”。</p>
<p>这个电流必须足够大,才能让转子产生足够的感应磁场。
所以这部分电流,又叫 励磁电流。</p>
<p>感应电机的特点就是,运行电流基本上由励磁电流构成。</p>
<p>我自己绕的这个电机,在烧掉 mos 前,我观察到的:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. 变频器输出电压较低的时候,绕组电流不足 3A, 电机无法运转。
2. 变频器输出足够电压,绕组电流超过 3A. 电机开始运转。此时变频器只从电源吸收0.3A电流。
3. 增加变频器的输出,变频器吸收电流到 2A, 此时绕组电流依旧是 3A 略多。
4. 继续加码,变频器吸收电流开始和绕组电流几乎相同。开始接近 4A.
5. 然后。。mos 冒烟。
</code></pre></div></div>
<p>其实虽然变频器以30w 以上功率只运行了十几秒。但是其实 mos 以超过 3A 电流运行时间已经超过一分钟有余了。</p>
<p>我回看了一下电路设计图,发现我居然选了个耐压 100V ,过流能力才 9A 的 mos 。。 脑子瓦特了。应该选 60V/30A 的。当时画图的时候肯定没好好选mos.</p>
<p>因为 9A 电流是持续电流。需要 mos 工作在持续导通状态。 如果工作在开关状态。有开关损耗。实际 mos 能过的电流只有一半。约 4.5A. 但是那还是在加散热片的情况下。我这没加散热片裸跑的 mos ,超过 3A 的电流跑了数分钟,也该烧。</p>
<h1 id="总结">总结</h1>
<p>经过此次烧毁,我对异步电机的理解更上一层楼了</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. 异步电机从变频器吸收的电流,可以划分为做功电流和励磁电流
2. 励磁电流的大小是固定的。要想异步电机工作,必须首先向它注入励磁电流。变频器注入电流小于励磁电流,电机无法启动。所以这就是 V/F 控制的时候,在低频低压区域,要有一个电压补偿的根本原因。就是要维持励磁电流。否则随着电压的降低,励磁电流也会降低。
3. 对小功率异步电机来说,励磁电流的大小超过做功电流的大小
4. 增加线圈匝数可以减小励磁电流的要求
5. 但是增加的匝数同时会增加对供电电压的要求
6. 减小转子和定子的间隙(又叫气隙)可以减少励磁电流的要求,但是对机械加工精度提出更高的要求
7. 励磁电流彻底的表现为无功电流。励磁电流在绕组内部来回跑。中间会路过变频器的MOS管
8. 有功电流又分成机械功电流和损耗电流
9. 励磁电流需要变频器不断的补充损耗电流来维持
10. 线圈匝数多了。励磁电流减少,但是线圈长了,电阻增加。损耗电流并不减少
11. 变频器逐渐提高输出电压,到电机开始能运转,此时的变频器注入的有功电流,就是这台电机的损耗电流。
12. 变频器的容量应该 > 损耗电流 + 励磁电流 + 机械功电流
13. 逆变桥从直流母线吸收的功耗 = 损耗电流+机械功电流
14. mos 管流过电流 = 损耗电流 + 励磁电流 + 机械功电流
15. mos 管选型,要按 电机功率/电压 + 励磁电流 才能有足够余量
</code></pre></div></div>
制作变频器-第六部分
2023-12-08T00:00:00+00:00
https://microcai.org/2023/12/08/cooperative-multitasking-in-mcu
<h1 id="从多任务说起">从多任务说起.</h1>
<p>前序日子,都在进行核心代码的开发。终于最近进入了 UI 界面的开发了。</p>
<p>虽然叫 UI, 但是其实并没有窗口。也没有命令行。</p>
<p>而是几个 LED 灯 + 一个旋钮 + 一个红外遥控器。</p>
<p>这些也算是能和用户交互了,所以,算UI没什么问题吧?</p>
<p>一旦进入 UI 的开发领域,马上就遇到单片机的一个掣肘了:
没有操作系统。</p>
<p>为什么UI需要操作系统呢?</p>
<p>因为 UI 是由多个子任务构成的。</p>
<p><img src="/images/vvvf_block_diagram.svg" alt="vvvf图解" /></p>
<p>libvfd 里, SVPWM 的生成是受时钟中断驱动的。</p>
<p>但是,V/F 控制,则是一个独立的循环。V/F 控制,收到外部的控制请求后,
会根据电机的加减速特性,来逐步的提高 svpwm 模块的电压和频率。</p>
<p>还有,遥控接收,编码器处理。这些代码,都需要自己的一个控制循环。</p>
<p>也就是意味着,这个变频器的内部,有多个独立的 “控制流”。</p>
<p>独立的控制流,就是线程。</p>
<p>但是单片机没有操作系统,如何使用线程呢?</p>
<h1 id="用户自己实现的线程">用户自己实现的线程</h1>
<p>其实不需要操作系统,用户也可以自己实现“控制流上下文切换”。也就是用户态线程。矣?等等,单片机没有操作系统,更没有用户态和内核态之分。不过也可以借用这个术语嘛。</p>
<p>那么在单片机里,所谓的用户态线程,指的其实是 c++ 协程。特别是 C++20 的协程。</p>
<p>其实,在单片机里,所谓协程,主要的上下文切换手段,就是 co_await delay(ms);</p>
<p>只要当前人物想 sleep 一段时间,就让出 cpu 时间。然后交给协程的列队管理器去调度下一个协程。</p>
<p>因为协程的运行是不会被抢占的。只能是主动的 delay 放弃。所以,这种多任务的方式,就叫 <strong>协作式多任务</strong></p>
<h2 id="实现-yield">实现 yield</h2>
<p>目前单片机的编译器版本参差不齐,co_await 对编译器的版本要求太高。
加上 co_await 背后的机制也不容易理解。</p>
<p>所以,目前我使用的是 asio 作者发明的 stackless coro.</p>
<p>stackless coro 对系统的要求 = 0 。他自身是表现为一个 函数对象。</p>
<p>每次调用的时候,他都会从上次 return 的地方继续。</p>
<p>具体原理可以参考 asio 爸爸的文章 <a href="老婆出来看月亮">http://blog.think-async.com/2009/07/wife-says-i-cant-believe-it-works.html</a></p>
<p>那么,对单片机系统工程本身的需求是什么呢?</p>
<p>其实就是写了一个 Executor.</p>
<p>这个 Executor 不需要调用 OS 的 epoll_wait, 也不调用 GetIoCompletionPort, 更不会使用 select. 而是简单的忙等待,死循环。</p>
<p>正如操作系统的pid 0也是在一个死循环里调用CPU的 WAIT 指令。</p>
<p>这个简单的 Executor 就可以驱动 asio 的 stackless coroutine 了。</p>
<p>等日后单片机厂的编译器提上来了,就可以直接使用 asio 的 co_spawn 了。反正 asio 的 cp_spawn 其实也只依赖 Executor, 而不是依赖 asio 的 io_service.</p>
制作变频器-第五部分
2023-12-05T00:00:00+00:00
https://microcai.org/2023/12/05/sync-pwm-async-pwm
<h1 id="pwm-调制-正弦波">pwm 调制 正弦波</h1>
<p>计算机的世界是一个离散的世界。离散的世界,是用很小的矩形去逼近连续的世界。</p>
<p>其实傅里叶告诉我们,一切波形都是正弦波的叠加。</p>
<p>于是 pwm 看起来是方波,但是其实也是正弦波。</p>
<p>pwm 之所以可以模拟正弦波的效果,就是因为 pwm 可以看成被模拟的正弦波+很多很多的谐波构成。</p>
<p>而这些多余的谐波,只要这个谐波的频率比正弦波本体大很多,就可以被低通滤波器完美的过滤掉。</p>
<h1 id="pwm-频率和-开关损耗">pwm 频率和 开关损耗</h1>
<p>pwm 的频率越高,则调制比就越大。所谓调制比,就是pwm的频率和逆变器要输出的正弦波频率的比值。</p>
<p>理论上来说, pwm 的频率越高,调制比越大,那么经过电机绕组这个大电感后过滤出来的电流,就会越接近完美的正弦波。
也就是电流的谐波就越少。
因为电感对高频的阻碍作用是越来越大的。电机绕组里的电流越接近正弦波,则谐波损失越少。
因为只有基频才能输出机械能。高频的谐波只能变成震动和发热。</p>
<p>所以,从降低电机的铁损来说,pwm 的频率越高越好。</p>
<p>但是, pwm 频率太高了,也有一个缺点,就是导致开关损耗增加。
因为 MOS 管不是理想的开关管。即使是理想的开关管,也不能突破物理定律让电流突变。</p>
<p>MOS管关闭和打开的时候,电流从 最大到0,和从0到最大,中间必然是有一个斜坡的。这个斜坡,意味着加在MOS管两端的电压是持续变化的。于是电压和电流的乘积,会先增加后减少。他们的积分,就是 MOS 管的开关损耗。</p>
<p>MOS管的开关速度越快,积分就会越少。也就是开关损耗越低。</p>
<p>这就是为啥要使用氮化镓的由来。</p>
<p>传统上,使用硅参锗做的晶体管,每一层硅原子的耐压不高。导致 MOS 管要想更高的耐压,其参杂厚度就要增加。
而增加的参杂厚度,必然会降低开关速度。</p>
<p>也就是 MOS 管的 耐压和开关速度,是个矛盾体。</p>
<p>耐压 1.5v 的,比如用来构成 CPU 里的门电路的管子,开关速度可以做到数个 GHZ。
但是,耐压到 1500v 的管子,开关速度就只能做到 1khz 了。</p>
<p>如果把制作材料从硅改为氮化镓,则同样 1500v 耐压,开关速度还可以做到 1Mhz。</p>
<p>而变频器,始终是需要面对大电压大电流的工况的。而新材料MOS管非常的贵。所以变频器,是不能异想天开的使用一个超高的 pwm 频率。
而是始终要面临开关损耗问题。</p>
<p>在开关损耗和电机的谐波铁损之间取折衷,于是变频器普遍会使用 1khz - 8khz 的开关频率。而且越是大功率的变频器,开关频率都倾向于比小功率的更低。</p>
<h1 id="异步调制和同步调制">异步调制和同步调制</h1>
<p>异步调制,就是 pwm 的频率和输出的正弦波的频率之间没有关系。二者是异步的。通常在变频器的整个工作期间都会使用某个固定的 pwm 频率。</p>
<p>异步调制有一个重要的特点就是输出的脉冲波是不对成的,不仅1/4脉冲不对成,正负半周期的脉冲也不对称,这会导致电流输出的谐波较多。</p>
<p>同步调制的载波比N等于常数,并在变频时使载波和调制波保持同步的方式称为同步调制。且 N 为能被3整除的奇数。
所谓载波比,就是指pwm的频率和输出的正弦波的频率之比。 当载波比为常数时,每个正弦波周期,输出固定个数的 pwm 波。
正弦波的频率提高,pwm的频率也随之提高。
这种调制模式下,谐波含量相比pwm频率相当的异步调制会更少。</p>
<p>如果载波比是完全固定,则pwm频率会随着输出频率的上升而上升到超过最大 pwm 频率限制。
此时变频器的做法就是换一个小点的载波比。反之频率减小的情况下也会切换更大的载波比。</p>
<p>这种载波比切换,就会形成一种音阶。</p>
<h1 id="同步调制和异步调制在代码实现上的异同">同步调制和异步调制在代码实现上的异同</h1>
<p>不管是同步调制还是异步调制。最终都是在 pwm 硬件定时器的中断里更新下一个周期的 pwm 占空比。</p>
<p>因此下一个 pwm 周期的占空比计算方式就是</p>
<p>$sin( 当前相位 + (1/pwm周期*正弦波频率))$</p>
<p>不管是同步调制还是异步调制,唯一的区别就是 这里的 pwm周期,它是个 constexpr int 变量还是 int 变量.</p>
<p>同步调制增加了改变pwm频率的代码。在计算下一个周期的占空比时,还要跟新下一个周期的pwm频率。</p>
制作变频器-第四部分
2023-12-01T00:00:00+00:00
https://microcai.org/2023/12/01/max-power-factor-track
<h1 id="感应电机的开环控制">感应电机的开环控制</h1>
<p>在没有使用变频器的时候,感应电机无法调速。交流电改变频率虽然困难,但是改变电压很容易。
因此也有通过自耦变压器调压后驱动感应电机的方法。</p>
<p>但是仅仅是降低了电压,扭矩降低导致转差变大。转差变大使得转子发热增加。不利于电机的长期稳定运行。</p>
<p>所以有了 恒压频比的开环变频调速器。注意,这个词的分词应该是 恒 压频比。</p>
<p>意思是以固定的 频率和电压的比值进行调速。比如一个额定 220v/50hz 的电机,当试图降一半转速运行的时候,可以设定逆变器输出 110v/25hz 给电机。</p>
<p>恒压频比是一种开环控制。控制器只是盲目的输出一个设定的电压和频率。但是并不能让电机工作在最佳的特性点上。</p>
<p><img src="/images/im_torque_characteristics.png" alt="异步电机的机械特性" /></p>
<p>图中,恒坐标是转差率,纵坐标为扭矩。B 点就是电机的最佳工作点。但是电机遇到扰动的时候,很容易进入 B-A 区间。
因此,实际会尽量让电机工作在 B-C 区间,并靠近 B 点。</p>
<p>Tm 的值表示电机的最大扭矩,电机的最大扭矩正比于电压的平方。</p>
<p>因此,可以看出,使用恒压频比控制,并不能很好的让电机工作在最佳状态。</p>
<p>所以就得出了在电动汽车领域更常见的做法:</p>
<h1 id="转差率闭环控制">转差率闭环控制</h1>
<p>既然电机的最大扭矩取决于电压,那只要知道了电机的转速,控制逆变器输出的频率,让电机始终工作在最大扭矩上。意味着在相同的转速和扭矩输出下(即相同的机械功率输出下)。闭环控制的逆变器始终会比恒压频比的控制器输出更低的电压。于是使得电机吸收的电功率跟小,也就是机械效率更高。</p>
<p>怎么知道电机的转速呢?</p>
<p>常规做法,是使用转速传感器。有了转速传感器,就可以进行基于转差率的闭环控制。知道电机转速后,可以立马获得当前输出频率。输出电压设定为 min(恒压频比计算出来的频率, 用户设定的扭矩电压)</p>
<p>也就是随着转速的提升逐步提高电压,并在达到最大电压后停止电压攀升。如果电机扭矩还有富裕(负载较低)则电机电压不再提升的情况下,频率会继续提升,也就是进入弱磁控制状态。可以榨干电机的机械潜力。</p>
<p>转差率闭环控制下,异步电机的特性就更贴近于直流电机了。通过调节电压可以轻松的控制扭矩,而频率则通过闭环控制自动跟踪。控制器输出电流频率,正比于电机转速,和同步电机异曲同工之妙。只是不需要控制电流相位。</p>
<h1 id="无转速传感器无参数设定的闭环控制">无转速传感器无参数设定的闭环控制</h1>
<p>转速传感器不仅仅提高了整个系统的成本,而且降低了电机的可靠性。因为异步电机工作环境通常较为恶劣。也正是因为恶劣环境下必须选择结构较为简单的异步电机。</p>
<p>所以,如果能使脱离转速传感器,但是依旧使用闭环控制,则能同时兼具开环控制的低成本优势和闭环控制的高效率优势。</p>
<p>前人提出了不少方式,但是这些方式有一个共同特点:需要异步电机的各项参数。 如绕组电感,绕组电阻,额定转差率。</p>
<p>事实上,额定转差率是一个更为难获取的数值。其实使用转速传感器进行闭环控制的时候,也需要这个参数。这也是为何闭环控制器,通常和电机绑定。而市面上普遍单独售卖的变频器,都是开环控制。</p>
<p>因此,如何让变频器可以同直流电机控制器一样,无需设定电机参数,就能自动获得和直流电机一样(或者接近)的控制特性呢?</p>
<p>于是,经过我长期的思考,我得出了一个解决方案:</p>
<h2 id="最大功率因素点跟踪">最大功率因素点跟踪</h2>
<p>交流电机在运行过程中,不同的运转状态下,会产生不同的功率因素。</p>
<p>例如,电机堵转运行的时候,绕组的表现为纯电感。于是功率因素接近 0, ,但是电机要从电网汲取超大的电流。这些超大的电流最后都通过电线的电阻发热损耗掉了。</p>
<p>电机在空载运行的时候,绕组表现依旧为纯电感。功率因素依旧接近 0 。 但是因为此时转子转速接近同步转速,因此电机产生的反电动势抵消了外部的驱动电压。空载运行时,电机反电动势会极为接近驱动电压。因此电流处于最小值。绕组实际电压为 驱动电压-反电动势电压。然后电流为此时电压下的电感电流。</p>
<p>而电机正常的处于额定负载的情况下,会产生最大的功率因素。负载低于和高于额定负载,都会使电机的功率因素下降。</p>
<p>因此,只要寻找到让电机功率因素最大的 频率/电压 组合,就可以在不知晓电机各项参数的情况下,获得接近直流电机般的转矩特性。</p>
<p>而最大功率因素点跟踪,其思路就来自 太阳能控制器的最大功率点跟踪。</p>
<p>具体做法是。使用 恒压频比,设定一个初始的频率。而算法的输出,为对初始频率的修正数。</p>
<p>算法的观测器,为 功率因素。功率因素的获取方式很简单,就是通过电流采样,获取三相各自的电流值。将电流值与此时逆变器的输出电压比较,就可以获得电流相对电压的滞后角。通过滞后角,就得到了功率因素。</p>
<p>此时算法会尝试 减少或者增加频率,然后观察功率因素的变化方向。
如果功率因素的变化方向,和频率的修正数同向,则电机目前工作在轻载模式。需要继续增加频率。</p>
<p>如果功率因素的变化方向,和频率的修正数逆向,则电机工作在重载模式。需要降低频率。</p>
<p>这样经过双向的尝试,控制器很快就能稳定在使功率因素最大的频率上。</p>
制作变频器-第三部分
2023-11-30T00:00:00+00:00
https://microcai.org/2023/11/30/saddle-wave-form-explain
<h1 id="轮子上的三条辐条">轮子上的三条辐条</h1>
<p>如果有一个轮子,半径为 1 ,在滚动的时候,其圆心会始终离地1高度。整个轮子的最顶端,也始终离地高度2.</p>
<p>如果在这个轮子上,画出三条辐条。辐条夹角 120度。</p>
<p>轮子在滚动的时候,辐条的顶部会完美的走过一个正弦波。波峰为 2. 中心为 1 。</p>
<p><img src="/images/sin.png" alt="正弦波调制" /></p>
<p>这,就是正选波调制的三相电。其相电压的幅值为1。线电压为 $\sqrt{3}$. 由于轮子最高处为2。
因此正弦波调制的直流电压利用率就是 $\sqrt{3}/2$</p>
<h1 id="去掉轮毂只留下辐条">去掉轮毂,只留下辐条</h1>
<p>因为没了轮毂,只留下辐条,于是,车轮前进的时候,辐条的端点划过的波,却不是正弦波了,而是一个马鞍波。</p>
<p>因为车轮的中心,在上下跳动!</p>
<p>于是,辐条的最高点,就不再是2了。因为车轮中心点最高的时候,恰恰两个辐条各自 120 分开。</p>
<p><img src="/images/saddle.png" alt="达不到2高度啊" /></p>
<p>达不到2高度啊!</p>
<p>经过计算发现,其实车轮最大的高度,也只堪堪达到了 $\sqrt{3}$</p>
<p>这意味着,直流母线电压不需要为2 ,只需要 $\sqrt{3}$ 就能达到和之前一样的相电压输出!</p>
<p>于是,会发现,在这种马鞍调制下,直流母线电压的利用率达到了 100%。</p>
<p>虽然说,马鞍波的是各个相对 直流 地 的电压。</p>
<p>但是,相和相之间的电压波形,依旧是完美的正弦波。因为其马鞍波,实际上是正弦波上叠加的一个三次谐波。车轮中心的上下运动,恰好抵消了这个谐波。</p>
<p>于是相电压为完美的正弦波。而没有叠加的谐波。</p>
制作变频器-第二部分
2023-11-29T00:00:00+00:00
https://microcai.org/2023/11/29/operator-overload-to-change-float-math-to-int
<h1 id="引">引</h1>
<p>输出对称三相电的逆变器,总是要进行很多浮点运算。因此一般而言,需要使用带FPU的单片机。或者说叫DSP。</p>
<p>如果只使用整数运算能否实现 svpwm 的计算呢?</p>
<p>答案是能的,就是使用 “定点数”</p>
<p>所谓定点数,就是和浮点数相对的,他的整数部分和小数部分是固定的。比如按 16bit 存储整数部分, 16bit 存储小数部分。这样一个 32bit 的定点数字,能表示的数字范围其实和 16位的整数一样。只不过它能额外的携带小数部分。</p>
<p>而浮点数则是脱胎于科学计数法。比如使用 16bit 存储基数,另外16bit存储系数。则可以表示非常非常大的范围,只不过数字越大,误差就越大。大点的数字都没有小数部分了。所以这种数字表示法,他的小数点是浮动的。所以叫浮点数。</p>
<p>浮点数其实有各种格式存储,但是为了互操作性, IEEE 定义了一个cpu上普遍使用的格式。IEEE 754。
如果单片机不支持浮点数运算指令,编译器就会用软件模拟。编译器带的软件浮点运算,速度非常非常慢。
这就是“浮点算不快”的由来。</p>
<p>受限于成本,低端的单片机都不会搭载FPU。而中端的单片机,虽然搭载了FPU,但是 FPU 的速度不能和整数部分相提并论。只有使用较为高端的单片机,其搭载的 FPU 才算能用于 foc/svpwm 控制。</p>
<p>这就极大的提高了电机控制器的成本。使得廉价控制器总是使用较为简单的只需要六步换向的方波驱动。而不是使用三相对称的正弦波驱动电机。</p>
<p>那么有没有办法在廉价的单片机上使用 foc 呢?</p>
<h1 id="定点数学库">定点数学库</h1>
<p>其实是有的。不过将foc控制算法修改为定点数运算,需要修改大量的代码。而且简单的 + - * / 运算符号,统统被替换成了难看的,对定点数学库的调用。
需要修改的地方非常多。将程序改的面目全非。</p>
<p>这就是 C 语言的弊病。 所以,就到了安利 C++ 的时间啦!</p>
<h1 id="定点数学库---header-only-cpp-plug-and-play">定点数学库 - header only CPP plug and play</h1>
<p>C++ 的优势,就是具有 “运算符重载” 功能。这个功能使得使用非编译器内置数字类型不需要改变使用方式。你仍然只需要使用正常的 + - * / 运算符。</p>
<p>比如在所有代码里,凡是需要支持小数运算的地方,我统统使用 float_number 这个类型。</p>
<p>然后在全局的一个 header 里,我用 using float_number = float;</p>
<p>这样,在不使用数学库的情况下,我的代码自动的就是使用的编译器提供的 float 类型。</p>
<p>然后,在开发的某一个阶段,我引入了定点数学库。</p>
<p>这时候,我只要把 <code class="language-plaintext highlighter-rouge">using float_number = float;</code> 修改为 <code class="language-plaintext highlighter-rouge">using float_number = fixpoint_number_provided_by_math_lib;</code></p>
<p>然后重新编译。那么我所有用到浮点数的地方,就会全自动的转换为使用了数学库里的定点数。</p>
<p>这就是 c++ 带来的开发上的巨大优势。</p>
<h1 id="这次-mini-vfd-硬件上定点数的速度提升">这次 mini vfd 硬件上定点数的速度提升</h1>
<p>使用 Cortex-M4 内核的浮点指令,我做到了 pwm 周期计算代码执行时间为 50us。这差不多相当于极限 20khz 的开关速度。更快的开关速度将会导致 cpu 来不及执行完毕 pwm 中断代码,就又要发生一次 pwm 定时器中断了。</p>
<p>修改为使用定点数后,同样的代码(除了添加 using float_number = myfloat 声明,未做任何其他修改)执行时间缩短到了 9us。 这差不多相当于能在这颗 cpu 上运行 100khz 开关速度的 svpwm。</p>
<p>这也意味着,使用主频 12Mhz 的单片机,也能跑不低于 8khz 的 pwm 频率。 8khz 的开关频率,其实已经相当程度上做到开关噪音的抑制了。</p>
制作变频器-第一部分
2023-11-25T00:00:00+00:00
https://microcai.org/2023/11/25/vfd-part1
<p>为了验证,我决定亲自设计一款三相电源。这个电源可以随意设定电压和频率。</p>
<p>三相电源,在任意时刻,可以由2个参数唯一确定: 电压/角度。
这个电压,指的是完整周期的相电压。因为三相的角度各相差120度。所以确定一个相的相位,就能自动推出另外2个相位。</p>
<p>因此,在三相电源的核心逆变代码,只参考由上层传入的两个控制参数:电压和角度。</p>
<p>有了角度,就可以知道任意相,他相对直流母线的电压因为 (sin(角度) /2 + 0.5)*电压。另外两相各加120度和240度即可。
如果把 电压设定为 0-1 之间的一个比例,也就是相对直流母线的电压,则算出来的值,就是每相的 上下管的导通之比。</p>
<p>算出三相的PWM之比后,只要 mcu 设定为 中央对齐的 PWM 输出,就可以完美的输出指定角度的三相电了。</p>
<p>然后再利用定时器,不断的更新 角度变量,就可以输出周期变化的三相交流电了。</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">auto</span> <span class="n">U_a</span> <span class="o">=</span> <span class="n">sin</span><span class="p">((</span><span class="n">electron_angle</span> <span class="o">+</span> <span class="mf">0.0</span><span class="p">)</span> <span class="o">/</span> <span class="mf">180.0</span> <span class="o">*</span> <span class="n">pi</span><span class="p">)</span> <span class="o">/</span> <span class="mf">2.0</span> <span class="o">+</span> <span class="mf">0.5</span><span class="p">;</span>
<span class="k">auto</span> <span class="n">U_b</span> <span class="o">=</span> <span class="n">sin</span><span class="p">((</span><span class="n">electron_angle</span> <span class="o">+</span> <span class="mf">120.0</span><span class="p">)</span> <span class="o">/</span> <span class="mf">180.0</span> <span class="o">*</span> <span class="n">pi</span><span class="p">)</span> <span class="o">/</span> <span class="mf">2.0</span> <span class="o">+</span> <span class="mf">0.5</span><span class="p">;</span>
<span class="k">auto</span> <span class="n">U_c</span> <span class="o">=</span> <span class="n">sin</span><span class="p">((</span><span class="n">electron_angle</span> <span class="o">+</span> <span class="mf">240.0</span><span class="p">)</span> <span class="o">/</span> <span class="mf">180.0</span> <span class="o">*</span> <span class="n">pi</span><span class="p">)</span> <span class="o">/</span> <span class="mf">2.0</span> <span class="o">+</span> <span class="mf">0.5</span><span class="p">;</span>
<span class="n">U_a</span> <span class="o">*=</span> <span class="n">throttle</span><span class="p">;</span>
<span class="n">U_b</span> <span class="o">*=</span> <span class="n">throttle</span><span class="p">;</span>
<span class="n">U_c</span> <span class="o">*=</span> <span class="n">throttle</span><span class="p">;</span>
<span class="n">pwm_set_duty</span><span class="p">(</span><span class="n">U_a</span><span class="p">,</span> <span class="n">U_b</span><span class="p">,</span> <span class="n">U_c</span><span class="p">);</span>
</code></pre></div></div>
<p>pwm 输出必须为中央对齐。可以大大降低开关损失。</p>
<p>electron_angle 的更新,可以放到 定时器里,也可以放到 pwm 的中断响应里。</p>
<p>electron_angle 的更新步进为,频率/pwm周期,如果 pwm 周期设定为 频率的整数倍 S,那么 electron_angle 的更新步进就是 360°C/S。</p>
<p>每个pwm周期要执行三次 sin 运算。但是根据 三相和为 0 ,可以省去一个 sin 计算。简化为</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">auto</span> <span class="n">U_a</span> <span class="o">=</span> <span class="n">sin</span><span class="p">((</span><span class="n">electron_angle</span> <span class="o">+</span> <span class="mf">0.0</span><span class="p">)</span> <span class="o">/</span> <span class="mf">180.0</span> <span class="o">*</span> <span class="n">pi</span><span class="p">)</span> <span class="o">/</span> <span class="mf">2.0</span> <span class="o">+</span> <span class="mf">0.5</span><span class="p">;</span>
<span class="k">auto</span> <span class="n">U_b</span> <span class="o">=</span> <span class="n">sin</span><span class="p">((</span><span class="n">electron_angle</span> <span class="o">+</span> <span class="mf">120.0</span><span class="p">)</span> <span class="o">/</span> <span class="mf">180.0</span> <span class="o">*</span> <span class="n">pi</span><span class="p">)</span> <span class="o">/</span> <span class="mf">2.0</span> <span class="o">+</span> <span class="mf">0.5</span><span class="p">;</span>
<span class="k">auto</span> <span class="n">U_c</span> <span class="o">=</span> <span class="mf">1.5</span> <span class="o">-</span> <span class="n">U_a</span> <span class="o">-</span> <span class="n">U_b</span><span class="p">;</span>
</code></pre></div></div>
<p>即使不使用 clarke 变换,也只要每周期执行2次三角函数。</p>
<p>11 月 29 日更新:</p>
<p>直接按 sin 值幅值给 pwm 占空比是不行的。需要进行一定的变化。不过确实不需要 clarke 变换就是了。</p>
<p>还是根据六步换向法,确定扇区后,再根据 sin 值进行 pwm 占空比赋值。在每个扇区,必有一相是低端管持续开启的。</p>
<p>次年 1月 20 日更新:</p>
<p>能用,之前的代码其实没有错误。是pwm驱动的bug所以匆忙下了错误的结论。</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 本函数通过 电角度 和 油门大小,直接算出输出的pwm波.</span>
<span class="c1">// 无需 Uq 和 Ud 进行反 clarke 变换.</span>
<span class="c1">// 如需进行 FOC 控制,可以测量功率因素(电流相比电压的滞后相位),直接对电角度进行提前</span>
<span class="c1">// 因此 FOC 控制也只需调制 electron_angle_ 和 throttle</span>
<span class="n">std</span><span class="o">::</span><span class="n">tuple</span><span class="o"><</span><span class="n">float_number</span><span class="p">,</span> <span class="n">float_number</span><span class="p">,</span> <span class="n">float_number</span><span class="o">></span> <span class="n">svpwm</span><span class="o">::</span><span class="n">caculate_spwm</span><span class="p">()</span> <span class="k">const</span>
<span class="p">{</span>
<span class="k">static</span> <span class="k">const</span> <span class="n">float_number</span> <span class="n">half</span><span class="p">{</span><span class="mf">0.5</span><span class="p">};</span>
<span class="k">static</span> <span class="k">const</span> <span class="n">float_number</span> <span class="n">one_half</span><span class="p">{</span><span class="mf">1.5</span><span class="p">};</span>
<span class="c1">// 计算三个 [0,1] 区间的正弦值。相位依次差 120°</span>
<span class="c1">// 由于三相的和为 1.5,所以 C 相可以直接减法得出,不用多调用一次 sin</span>
<span class="n">float_number</span> <span class="n">U_a</span> <span class="o">=</span> <span class="n">sin_of_degree</span><span class="p">(</span><span class="n">electron_angle_</span><span class="p">)</span><span class="o">/</span><span class="mi">2</span> <span class="o">+</span> <span class="n">half</span><span class="p">;</span>
<span class="n">float_number</span> <span class="n">U_b</span> <span class="o">=</span> <span class="n">sin_of_degree</span><span class="p">(</span><span class="n">electron_angle_</span> <span class="o">+</span> <span class="mi">120</span><span class="p">)</span><span class="o">/</span><span class="mi">2</span> <span class="o">+</span> <span class="n">half</span><span class="p">;</span>
<span class="n">float_number</span> <span class="n">U_c</span> <span class="o">=</span> <span class="n">one_half</span> <span class="o">-</span> <span class="n">U_a</span> <span class="o">-</span> <span class="n">U_b</span><span class="p">;</span>
<span class="c1">// 将计算结果和油门(0,100%)大小相乘,就得出了三相各自的 pwm 占空比</span>
<span class="k">return</span> <span class="n">std</span><span class="o">::</span><span class="n">make_tuple</span><span class="p">(</span><span class="n">U_a</span><span class="o">*</span> <span class="n">throttle</span><span class="p">,</span> <span class="n">U_b</span><span class="o">*</span> <span class="n">throttle</span><span class="p">,</span> <span class="n">U_c</span><span class="o">*</span> <span class="n">throttle</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>这个代码已经在我的变频器上验证通过。完全可行。</p>
三相220v供电可行性研究
2023-11-17T00:00:00+00:00
https://microcai.org/2023/11/17/3phase-220v
<p>经过深思熟虑,我提出了一种新的居民用电供电模式: 即三相220伏4线制供电。以适应未来更大的入户功率需求。</p>
<p>特点为:</p>
<blockquote>
<ol>
<li>入户线路为3根火线,一条地线,没有零线。</li>
<li>火线和火线之间的电压为220v。</li>
<li>入户电表设计三相不平衡保护开关</li>
</ol>
</blockquote>
<p>在这个特点下,带来的优势为:</p>
<blockquote>
<ol>
<li>电压等级仍旧为220v。家庭布线只是从3线升级为4线。同等供电功率下每条线可以比单相220v使用更小的截面积。</li>
<li>对老式单相电器,无需任何修改可以立即兼容。巨大的兼容性保障了过渡期的平稳进行。</li>
<li>开关电源可以简单的通过修改前置整流电路直接升级为支持三相输入。整流后的直流电压依旧为310v,其余电路无需任何改动。整流电路的额外成本只是多了2个二极管,从4个升级为6个。</li>
<li>对普通电器可以淘汰昂贵易损效率低的单相异步电机。而使用更廉价的三相异步电机,并提高效率。</li>
<li>三相供电更稳定,没有100hz的功率脉动现象。可以极大的减小设备内的储能电解电容的使用。提高设备寿命和安全性。</li>
<li>如果所有用电器都为三相电设计,则三相负载平衡自动达成,无需在配电网使用昂贵的平衡柜。</li>
</ol>
</blockquote>
<p>新设计一个 86 插座</p>
<p><img src="/images/插座.png" alt="插座" class="lazyload" /></p>
<p>新设计一个插头</p>
<p><img src="/images/插头.png" alt="插头" class="lazyload" /></p>
<p>其中,三角形边缘的触点为地线。插入的时候地线先于火线接合。</p>
<p>此插头适用电流为 10A, 因此能适用的功率为 3.8kw。足够满足几乎所有家庭用电器的需求。切为三向插头,可以从任意3个方向插入。</p>
<p>对于单相的老旧电器,则可以沿用旧有的3脚插头和插座。</p>
<p>相比使用单相3线制,同样满足 12kw 负荷,家庭布线总线从 3*6 布线改为 4*4布线。用铜量反而略有下降。对于功率非常小(三相负载不均衡在容许值内时)的一些线路,可以仍旧使用双线220v供电。</p>
<p>视在功率的计算为 220v * 每相电流 * $\sqrt{3}$
相比单相供电,计算的时候要多乘一个 $\sqrt{3}$</p>
逆摊丁入亩
2023-11-16T00:00:00+00:00
https://microcai.org/2023/11/16/tdrm
<p>税,都逃不过三大体系:人头税,资产税,流转税。
所谓人头税,就是按人头收税。在古时候,人头税是定额税。后来通过摊丁入亩废除了。
如今,人头税通过工资税的形式卷土重来。虽然不再是定额税。但是依旧是按人丁收取。</p>
<p>资产税,就是按资产收税。一般采取的做法是按固定资产收税。古时候的摊丁入亩改革,正是以
资产税代替人头税。
如今,资产税在西方是非常普遍的。但在中国,房地产税迟迟未落地。
资产税的另一个重要征收方式叫通货膨胀,也叫铸币税。</p>
<p>流转税,就是在商品的流通过程中收税。增值税,印花税,契税,关税,消费税,都属于流转税。</p>
<p>在古代,主要依靠的是人头税,然后一部分资产税,几乎没有流转税。但是人民的流转税负担是少不了的,因为他们要通过向土匪交买路财,向城市黑社会交保护费的形式承担流转税。只不过这部分税收归属流氓,而不归政府。</p>
<p>从明朝开始,统治者就意识到了人头税的弊端。而后有了张居正的改革,废除了人头税,改收以耕地为基础的资产税。这个改革从张居正开始直到雍正才真正落地。地多的多纳税,地少的少纳税。而不再由人口多寡决定税额。正是废除了人头税,中国的人口才起飞。当一个家庭的税负,不再由人口数量的多寡决定,那人多才能真正的力量大。才能放开了生。</p>
<p>后来,1949年后,我们学习“西方的先进经验”,不分青红皂白的搬了西方的经验。重新捡起了在中国已经废除数百年的人头税。倒行逆施的结果就是摊亩入丁。
占有大量房子和土地的人不纳税。税主要由人数众多的底层老百姓承担。人头税的主要形式就是个人所得税和社保。</p>
<p>结果就是生育率迅速下降。老龄化严重。</p>
<p>人口越多,缴纳的税就越多,资产却无需纳税。这导致人口增长放缓,并进入人口萎缩时代。由于资产不纳税,资产开始膨胀。</p>
<p>西方的老龄化社会,恰恰是因为西方普遍实现的“个人所得税”,也就是人头税。而不是所谓的经济发展导致的生育率自然下降。</p>
电池扫盲
2023-10-08T00:00:00+00:00
https://microcai.org/2023/10/08/batteries
<p>作为资深老宅,路由器 nas 充电宝 这三大爱好是必不可少的。最近一友人买电池的时候,一着不慎着了相。买到了错误的电池。不得不花费重金重新采购合适的电池。
故有此文,以供参考。</p>
<h1 id="电池电压">电池电压</h1>
<p>家庭常见电池的电压如下</p>
<table>
<thead>
<tr>
<th>电池类型</th>
<th>标称电压</th>
<th>满电电压</th>
<th>空电电压</th>
</tr>
</thead>
<tbody>
<tr>
<td>镍氢电池</td>
<td>1.2v</td>
<td>1.4v</td>
<td>1v</td>
</tr>
<tr>
<td>干电池</td>
<td>1.5v</td>
<td>1.6v</td>
<td>0.9v</td>
</tr>
<tr>
<td>碱性电池</td>
<td>1.5v</td>
<td>1.7v</td>
<td>1.2v</td>
</tr>
<tr>
<td>锂铁电池</td>
<td>1.5v</td>
<td>1.8v</td>
<td>1.5v</td>
</tr>
<tr>
<td>主板纽扣电池</td>
<td>3v</td>
<td>3.3v</td>
<td>2v</td>
</tr>
<tr>
<td>磷酸铁锂电池</td>
<td>3.2v</td>
<td>3.6v</td>
<td>3v</td>
</tr>
<tr>
<td>三元锂电池</td>
<td>3.7v</td>
<td>4.2v</td>
<td>3.3v</td>
</tr>
<tr>
<td>1.5v恒压锂电池*</td>
<td>1.5v</td>
<td>1.5v</td>
<td>1.5v</td>
</tr>
</tbody>
</table>
<p>其中,1.5v恒压锂电池 其实不是电池,而是一个内置锂电池的供电电源。电源稳压输出 1.5v。
其目的是使为 1.5v 干电池设计的用电器能直接使用锂电池工作。但是因为自身携带的dc/dc电路引入了损耗,因此不适宜用在需要长续航的用电器上(遥控器,钟表,门锁)。用在这些用电器上,往往其内部的锂电池的电量,主要被 dcdc 电路待机消耗掉,而不是对外输出。</p>
<h1 id="电池尺寸规格">电池尺寸规格</h1>
<p>电池通常制作成圆柱形。圆柱形电池有3种不同的规格表示法。</p>
<p>其一为最常见的X号叫法。常见为1号电池,5号电池,7号电池。1号电池体积巨大,目前使用的地方为灶台点火器、燃气热水器点火器。因为体积巨大,所以容量很大,那些功率大,又同时不想频繁更换电池的地方就会用1号电池。7号电池苗条迷人,常用于对体积有要求的设备上。如遥控器。5号电池体积适中,如果不是特殊要求,一般就会使用5号电池。</p>
<p>除了这种常见的号标电池,还有高度超低的圆柱电池,因为高度很低,虽然还是圆柱形,但是太像纽扣而得名“纽扣电池”。纽扣电池体积小,还超薄,所以广泛使用在迷你型遥控器上。如汽车遥控器。还有主板断电后维持时钟,都采用更薄的纽扣电池,而不是7号电池。纽扣电池虽然常见的就一种规格,但是也不是没有其他大小。纽扣电池的大小标记法为直径+高度标记。最常见的型号为 CR2032, 也就是 20mm 直径,3.2mm 厚度。</p>
<ul>
<li>CR927</li>
<li>CR1025</li>
<li>CR1216, CR1220</li>
<li>CR1616, CR1620, CR1632</li>
<li>CR2012, CR2016, CR2025, CR2032</li>
<li>CR2320, CR2330, CR2354</li>
<li>CR2450, CR2477</li>
<li>CR3032, CR2412</li>
</ul>
<p>这么多不太常见的规格,其实都濒临淘汰了。</p>
<p>还有一种圆柱规格,也是用直径+高度表示,但是高度更高,就不叫纽扣了,而是正经的圆柱形电池了。
这其中大名鼎鼎如雷贯耳的就是18650电池了。18650的意思是 18mm 直径+650mm高度。还有26650, 14500 等等各种次常见规格。特斯拉还凭一己之力把 21700 规格带入主流视野。</p>
<p>圆柱形电池有统一的规格,但是,非圆柱形电池就完全没有统一规格了。厂家是想做什么尺寸就做什么尺寸,毫无规矩可言了。比如手机内使用的软包方壳电池。尺寸是五花八门。每款手机大小都不同。主打的就是不可替代性。售后换电池又可以坑一笔。</p>
<h1 id="排列组合">排列组合</h1>
<p>那么电池大小和电池的材料,一个排列组合下来,理论上有万种电池。</p>
<p>但是,资本主义世界不讲数论。它讲效率。于是,只有几种常用的组合存世。
比如 18650 这个尺寸,就不会有镍氢电池,不会有碱性电池,他只有一种电池,就是锂电池。因此,只有3.7v电压的18650和 3.2v电压的18650。而不会有 1.5v 电压的18650。</p>
<p>又比如 CR2032 电池,它只会有一种电压,那就是3v。</p>
<p>在更早的时候,1号电池,5号电池这种号规电池,只有1.5v 一次性和 1.2v 可充电的组合。
不存在3.7v的五号电池这种规格。</p>
<p>但是人民群众的需求是创造力的第一动力。很多使用5号电池的设备,都是采用3v电压供电。也就是2节五号电池串联。于是人民群众发挥了想象力,使用14500电池+一节导线,成功的将 3.2v 的磷酸铁锂电池代替了2节五号电池。在这个需求的刺激下,资本家开发了5号电池和7号电池尺寸的磷酸铁锂电池。
因此号标电池又有了 3.2v 这个电压。不过,使用3.2v电压的5号电池,必须短路电池仓里另一个电池位置。于是又有了 “占位筒” 电池。占位筒电池,就是单纯的一段导线做成了5号电池的形状。
如果不使用占位筒而直接放入2节锂电池,用电器就要承受6.4v的电压,显然无法正常工作甚至可能烧毁。</p>
<p>但是使用占位筒大法,一是要求用电器必须使用偶数个5号电池供电。二是家里如果有不明真相的小孩,就容易误放置2节锂电池或放2节占位筒。</p>
<p>于是,在人民群众的需求驱动下,资本家开发出了1.5v输出电压的5号电池。其实就是一个做成了5号电池外形的电源。内置了一个锂电池。可充电,对外恒压供电1.5v。由于不需要占位筒,不容易用错。而且比占位筒大法的容量更大。因为占位筒大法只有一节真电池。而降压锂电池,虽然被降压电路占据了一定的空间导致单节电池容量较小,但是两节合并的容量要大于一节+占位筒。</p>
<p>好了。有了这些信息,以后再买电池就应该不会买错了。</p>
马赫原理
2023-09-26T00:00:00+00:00
https://microcai.org/2023/09/26/mahe-law
<p>离心力是怎么来的?</p>
<p>牛顿说,没有离心力。是因为旋转的时候,物体要维持直线运动。离心力是向心力的反作用力。
马赫说,离心力是星辰对旋转物质的引力。</p>
<p>牛顿有着绝对时空观。运动是绝对的。宇宙有一个绝对静止的参考系。
马赫说认为一切都是相对的。旋转也是相对的。</p>
<p>在牛顿水桶思想实验里,牛顿认为旋转的水桶最终会让水面凹陷。
马赫则认为没有参考系无法确定水桶是旋转的。没有星辰,就没有引力带来的离心力。也就不会引起水面凹陷。</p>
<p>同时马赫认为,如果星辰少一半,离心力也会小一半。</p>
<p>爱因斯坦同意马赫的相对性原则,但是并没有同意马赫说的星辰少一半离心力也少一半的说法。</p>
<h1 id="宇宙在增重">宇宙在增重</h1>
<p>根据大爆炸模型,宇宙是从无到有从虚空中诞生的。而宇宙诞生的时候,物质并不如如今那么丰富。
宇宙间的物质,是随着宇宙膨胀的过程增生出来的。</p>
<p>因为重力势能也是能量的一种。宇宙膨胀,那么星系之间相互远离,结果就是星系之间的重力势能增加了。
于是宇宙膨胀本身就为宇宙凭空创造了大量的能源。
这增加的能量,就等于宇宙增加了质量</p>
<p>按马赫原理,这些全宇宙增加的质量,就会增加离心力的大小。</p>
<h1 id="自转速度过大的星系">自转速度过大的星系</h1>
<p>所以宇宙早期,因为总质量更低,所以离心力也更低。于是早期(遥远)的星系就表现为有更大的自转速度。</p>
<p>现在的天文观测确实发现了星系自转速度过大的问题。但是把这个现象归结为暗物质。
而如果应用马赫原理,承认离心力取决于全宇宙物质总量,则问题迎刃而解。</p>
明亡于重士轻工
2023-09-16T00:00:00+00:00
https://microcai.org/2023/09/16/ming-die
<h1 id="明末财政危机">明末财政危机</h1>
<p>明朝末年,土地兼并严重。而大明的税收,是靠的 “自耕农”。自耕农破产,导致政府收不到税。
没钱就没兵。没兵就打不过满清。虽然是李自成灭的明朝,但是李自成攻入北京的时候,大明的精锐都在辽东打满清。可以说是满清吸引了大明的主力,让李自成差点摘了桃子。</p>
<p>所以很多人说,大明亡于土地兼并。历朝历代皆如此。</p>
<h1 id="圈地运动">圈地运动</h1>
<p>在大明的士绅忙着土地兼并的时候,大英的贵族也在忙着搞圈地运动。
为何大明亡了,而大英却打败了西班牙成为新的海上霸主?</p>
<p>难道大英不应该亡于土地兼并?</p>
<h1 id="儒教立国">儒教立国</h1>
<p>很多人认为,中国古代的封建王朝是以农业立国。其实不对。因为中国古代向来并不重视农业。
有人不信,拿出了古代各种重农抑商的政策来说明古代王朝以农业立国。</p>
<p>那么为何,土豆在明朝传入中国,但要到清朝才大面积推广种植?
以农业立国的大明,却对粮食产量不甚关心?</p>
<p>根本原因,是大明以儒教立国。</p>
<p>儒家提倡的是阶级固定的社会。所以儒家定好士农工商四大阶级。并且禁止阶级流动。其中,
由于工商阶级对儒家的统治地位有威胁,所以儒家把工商定为贱籍。然后故意把农和士定为
高级人。本质是因为农对士毫无威胁。只要农不造士的反,农民吃不吃的饱的问题并不在士人
的关心范围内。</p>
<h1 id="压制流民">压制流民</h1>
<p>历朝历代,朝廷对流民都非常忌惮。因为失去土地的农民,只有两个去处:落草为寇和进城务工。
流民问题之所以难解决,是因为儒家并不希望流民进城务工。因为进城务工的流民,最终都会转换为工商阶级。所以儒家渲染流民只能落草为寇,必须恩剿并施。
所谓恩,就是分土地。所谓剿,无非就是镇压。
王朝初年,尚且可以同时两项操作,但在土地兼并的末期,往往只有剿一个办法。因为朝廷已经没有土地了。</p>
<p>开疆拓土的时候,儒家会托词徒耗民力。不可轻起战事。
但是遇到流民的时候,儒家就不说好战必亡的话了。</p>
<h1 id="匠人世代为奴">匠人世代为奴</h1>
<p>朱元璋开创了中国版的种姓制度——匠户制和兵户制。</p>
<p>兵户世代为兵,匠户世代为匠。</p>
<p>虽然传统上,手工艺是家族代代传承的。但是这种家族传承的技艺有个弊病,就是无法形成理论体系。
每个工匠只知道自己家的那点道道。在宋代产生的资本主义萌芽,大商人开始雇佣大量的工人的时候,情况发生了变化。
商人对手下工人掌握的技术,肯定是不会让他们藏私,而是将大量工匠的技术结合起来,博采众长。并以商业世家的形
式进行集团化研究。于是就有了科学体系生长的温床。</p>
<p>但是,明朝的匠户制打破了这一自发的自然过程。将科技重新“散户化”。</p>
<h1 id="散户匠人断科研路">散户匠人断科研路</h1>
<p>吃不饱的匠户,连传承知识都非常艰难。别提在前人基础上进行迭代了。
传承困难的一大佐证就是大量的匠户逃籍。</p>
<p>于是匠人就越来越少了。大明的人口越来越多,但是从事科研的工作者却越来越少。难怪科技越来越倒退。</p>
<p>科技倒退,导致大明严重依赖农业。所以圈地运动就成了亡国运动。而大英开设皇家科学院,圈地运动成了工业革命的催化剂。</p>
汽油机为何如此设计
2023-09-14T00:00:00+00:00
https://microcai.org/2023/09/14/why-this-way-1
<h1 id="冲程">冲程</h1>
<p>汽车发动机有四个冲程,吸气,压缩,做工,排气。
为何是这4个呢?</p>
<p>热机要工作,必须要燃烧,燃烧就要空气。废气要排出,所以吸气排气是必须的。
但是为何要先压缩呢?</p>
<p>原来早期阶段,热机是没有压缩这个阶段的。但是奥托发现了压缩能提高热效率。
于是奥托发明了四个阶段循环的发动机。后来这4个循环就被称作奥托循环。</p>
<p><img src="/images/Stirling_Cycle.png" alt="" /></p>
<p>后来这4个循环,被热力学理论解构。也就是上图的4条线构成的框。热机对外的做工,就体现在这个框里。</p>
<p>如果有教科书说是因为热力学的4个循环指导了内燃机的设计,那肯定是不对的。是先有的内燃机的设计,后来才有理论去解释。而有了理论后,后续的迭代开发才会使用理论来指导改进。</p>
<h1 id="吸的不是空气">吸的不是空气</h1>
<p>在奥托循环里,发动机吸气冲程吸入气缸的不是空气。而是汽油和空气的混合气。
为何不是吸的纯空气,然后在压缩的时候再注入汽油呢?</p>
<p>因为发明汽油机的那个年代,还不存在燃油的高压喷嘴。而非高压喷嘴,就无法雾化燃油。
没有雾化的燃油,就没法迅速的爆燃。所以那个年代就是靠汽油的挥发性,让空气通过一个叫化油器的东西来吸入汽油。</p>
<p>而汽油发动机的上限,就被这个吸入混合气的过程给锁死了。</p>
<p>因为汽油发动机吸入的是混合气,所以汽油发动机就不能无限提高压缩比。
因为气体被压缩就会升温。而奥托循环必须要在做工冲程里燃烧汽油。
就要限制压缩循环里混合气的温升。不然混合气提前爆燃,就破坏了热力学循环。</p>
<p>经过实验发现汽油发动机的极限压缩比在 12:1。超过就很容易让混合气提前爆燃。</p>
<p>如果吸入的是纯粹的空气,就可以无限提高压缩比了。高压缩比带来的是更高的热力学效率和更大的动力。这个想法指导下,发明出来的发动机就是 “压燃发动机”,但是压燃发动机在很长一段时间内,都只能使用柴油作为燃料。而无法使用汽油。</p>
<p>所以汽油发动机就一直停留在吸混合气阶段。而只要吸的是混合气,就无法提高压缩比。</p>
<h1 id="为何要先压缩">为何要先压缩</h1>
<p>我不用热力学的晦涩知识,就用最一般最容易理解的话来讲,为何要压缩。</p>
<p>燃料在密闭腔室燃烧会产生高温高压的空气。这这个高温高压的空气就有了推动活塞做工的动力。
随着活塞被空气推动,热空气的温度和压力都会下降。理论上来说,这股热空气可以一直膨胀到温度等于室温,压力等于大气压。但是,活塞是有阻力的。温度下降到一定程度后,这股热气的压力就无法推动活塞了。这个无法推动活塞的废气,温度依旧高达600 - 800 度。</p>
<p>所以,未点燃的混合气温度越接近废气的温度,则燃料的化学能就越是被活塞提取利用了。
如果未压缩,那么燃料燃烧的化学能,有一大部分就留存在废气的温度里。
而压缩提前让混合气的温度提高。那么此时化学能就更多的被利用于推动活塞做工。也就是提高了燃料的能源利用率。</p>
<p>如果让混合气在点燃前的温度就和排气的温度一样,就意味着燃料的化学能被全部利用了。当然这并不表示引擎的效率达到了 100%,因为压缩本身就要使用掉一部分机械能。</p>
<h1 id="为何油门其实是气门">为何油门其实是气门</h1>
<p>发动机并不总是要 100% 的干活。很多时候,他都要少干活。
控制发动机干活多寡的东西俗称油门。但是油门实际上并不控制喷油量。
油门直接控制的,是进气量。然后由于汽油发动机需要始终如一的空燃比,所以喷油器会减少喷油。
减少喷油是减少吸气的结果。</p>
<p>如果油门控制的是喷油,而进气量不变,就很容易导致在小油门的情况下发生富氧燃烧。
也就是氧气的量大大超过燃油的量。</p>
<p>正如缺氧燃烧会产生一氧化碳一样,富氧燃烧一样会产生污染物氧化氮。
为了减少尾气中的氮氧化物和一氧化碳,汽油和空气必须按比例混合。既不能让空气多,也不能让汽油多。</p>
<p>所以,减少发动机出力,不能只靠减少喷油。而必须同步减少吸气量。</p>
<p>恰恰汽油发动机调节功率靠的是调节进气量,导致汽油发动机实际压缩比是动态变化的。
动态变化的压缩比又导致汽油发动机无法采用“压燃”。</p>
<p>而柴油污染大,恰恰是因为压燃的特点导致柴油在小油门下,是富氧燃烧。而大脚轰油门的时候,又是富油燃烧。导致不管是小油门还是大脚油门,都会产生严重的污染。</p>
<p>大脚油门下排的黑烟,要靠“颗粒捕捉器” 去除未燃烧的碳。
小脚油门下排的氮氧化物,要靠 “尿素” 去除。</p>
<p>如果要让柴油机减少污染,就只能让柴油机以固定的空燃比燃烧。。。 也就是固定扭矩使用。然而除非只用来当增程器,否则如此工况是断不可能满足的。</p>
中国古代名词解释
2023-05-23T00:00:00+00:00
https://microcai.org/2023/05/23/guwen-xinjie
<ul>
<li>
<p>士绅优待
士绅优待, 指儒教免税. 是儒教自汉代罢黜百家后, 为儒教教徒门争取到的阶级红利.</p>
</li>
<li>
<p>士绅一体纳粮
指取消儒教特权. 是雍正未雨绸缪, 要为将来朝廷收不到自耕农的税后找退路. 从农民手上收钱改为从儒教收钱.</p>
</li>
<li>
<p>与民争利
指朝廷把税扩大到工商阶级. 朝廷要求商人纳税会导致商人地位上升. 威胁儒教地位. 所以儒教要求朝廷不得与民争利.
只收农业税, 就可以把科举取士范围局限在士人阶级 ( _ 农民只有理论上参与科举的权利 _ ).
工, 在古时候是指不读四书五经, 而学习科技的那批人. 这些读书人如果参与科举, 会严重影响到四肢不勤五谷不分的
儒教地位. 正好这些人也没多少钱, 就给予他们免税. 所以朝廷只收农业税, 对儒教是最稳妥的.</p>
</li>
<li>
<p>藏富于民
指朝廷应该满足于农业税. 而让士人和商人掌握社会财富. 不要想着收商税, 不要想着士绅一体纳粮. 要做到藏富于民.</p>
</li>
<li>
<p>功高震主
指皇帝不应该重用非儒教出身的勋贵阶级. 不然就算他们不造反, 儒教的人也不介意推他们一把.</p>
</li>
<li>
<p>歪理小说
指四书五经以外的知识。</p>
</li>
</ul>
罢黜百家, 自绝天下
2023-05-22T00:00:00+00:00
https://microcai.org/2023/05/22/rujia-wuguo
<p>春秋战国时期, 中华大地上是百家争鸣.
何谓百家?</p>
<p>研究回字写法是家, 研究如何让弓箭射的更远也是家.
百家, 不仅仅是文学上的百家, 也有理学工学上的百家.</p>
<p>比如, 兵家, 研究兵法. 研究战阵. 不研究回字的9种写法.
农家, 研究农业, 改良种子, 改进犁具, 不研究回字的9种写法.
墨家, 研究机关, 改进工具, 不研究回字的9种写法.
医家, 研究医术, 治病救人, 也不研究回字的9种写法.</p>
<p>但是, 这些不研究回字的9种写法的百家, 都不受研究回字的9种写法的儒家待见.
到秦朝初立, 百家就被儒家排挤的差不多了. 朝堂上就只剩下法家在苟延残喘了.</p>
<p>法家对儒家的最后一击, 就是焚书坑儒.
儒家对法家的报复, 就是天下大乱.</p>
<p>最后儒家一统天下, 罢黜百家, 独尊儒术.</p>
<p>如果儒家罢黜的是其他研究回字写法的家, 那倒也没啥问题.
关键问题在于, 儒家罢黜的, 可不止是研究回字写法的家, 更是罢黜了数学家, 化学家, 医学家.
儒家把不研究回字写法的, 统统贬为 “奇技淫巧”.
不仅仅在朝廷上取缔, 更是在民间也取缔.</p>
<p>从此, 中国社会陷入了2000年的停滞.</p>
<p>很多人说, 儒家给中国带来了大一统. 其实是错误的. 给中国带来大一统的是法家. 秦以法家治国.
法家有大一统思想. 灭六国后, 法家坚持废除分封, 行郡县制. 儒家非要恢复周制, 行分封.</p>
<p>刘备可是嬴政小谜弟, 他夺得天下后, 不得不 外儒内法.
外儒内法, 就是让法家变成1对1传承. 只有历代皇帝学法家. 天下人皆学儒家.</p>
<p>到汉武帝时期, 最终罢黜百家, 独尊儒术. 连皇帝都只能学儒了.
这是因为刘彻并非既定的继承人, 没学过法家.</p>
<p>这位没学过法家的皇帝, 就被董仲舒忽悠了.
从此, 儒家正式屠尽天下学派. 让中国人从此陷入回字写法的内卷之中.
连不参与朝廷斗争, 隐身于民间的学派都没有放过.</p>
<p>为了打击异己, 儒家制定 士农工商 贱籍制度. 打压工商业. 明面上抬高农民, 实际上农民根本无力
承担学习重担. 最终就是独尊士族. 所谓士族, 其实就是儒家子弟. 从根本上破坏百家复苏的土壤.</p>
<p>百家的知识, 最终不得不转战海外. 藏匿千年后传播到欧洲, 最终让欧洲文艺复兴, 完成工业革命.</p>
<p>完成工业革命后的西方, 对东方降维打击. 让中国陷入了百年黑暗.</p>
<p>学自西方的工业党, 最后力挽狂澜, 于1949年推翻旧社会.</p>
<p>但是没过几年, 儒家再次卷土重来. 不得不顶着臭名也要 批林批孔.
儒家对中国的荼毒, 终于被消灭. 中国开始了现代化.</p>
中国人发明了数学
2023-05-07T00:00:00+00:00
https://microcai.org/2023/05/07/chinese-invented-math
<p>很多人学习数学的时候很好奇, 为何全世界的数学都是十进制的. 然后就有谣言说, 是因为人有十指. 所以全世界的人都发明了十进制的数学.</p>
<p>其实并不是. 虽然以符号语言为基础的数学确实是十进制体系的, 但是各个国家的语言里的数, 却并不是十进制体系的. 如果说原始人因手有十指就发明了数, 为何各种语言里, 数不都是十进制的?
难道原始人发明语言里的数的时候, 故意和他们的学科上的数学唱反调? 或者说, 原始人是先学会数学后学会说话? 既然是先学会说话后学会数学, 必然后学会的数学, 肯定使用语言里本身的进制.</p>
<p>如果当今的数学是十进制的, 必然是因为这门学科, 诞生自一个使用十进制口语的民族.</p>
<p>而口语不是十进制的民族, 其数学必然是舶来品, 而不是一个原生的发明.</p>
<p>阿拉伯数字, 恰恰是古代阿拉伯人(一说是古印度人)为了学习某个古老民族的数学, 但是又无法承担学习其古老文字的代价而发明的助记符.
随着阿拉伯数字的扩散, 十进制的数学最终统一了世界.</p>
<p>但是, 阿拉伯数字只统一了写在纸上的数学, 并没有统一口中说出的数学. 这也是另一种形式的 书同文, 但是语不同音.</p>
<p>其实, 古老的民族里, 各种进制的数学都要. 而且更为广泛使用的其实是12进制. 在古汉语里, 12进制的遗留就是如今大家熟知的子丑寅卯. 12进制的源头, 来自月亮. 一年月圆12次. 于是古代先民发明了12进制. 但是同时古代先民也发明了十进制. 两种系统互相碰撞, 融合为六十进制的天干地支计数. 但是最终10进制胜出.</p>
<h1 id="一个数字如何表示又如何念">一个数字如何表示又如何念</h1>
<p>一个数字如何表示, 首先是确定进制. 然后将这个数字按进制下, 写下每个位的权重.</p>
<ul>
<li>
<p>比如 96, 由于阿拉伯数字是十进制的, 因此 96 的含义, 其实是 10进位上是9 , 个位上是6, 其表示的数量, 就是 九个十加六. 念 九十六</p>
</li>
<li>
<p>如果表示为 16 进制 (0123456789ABCDEF), 则是 表示为 60 , 表示 sixteen进位上是 6 , 个位上是 0. 也就是 6*(sixteen) + 0, 在 16进制的语言里, 念 6 sixteen zero.</p>
</li>
<li>
<p>如果表示为 20 进制, (0123456789ABCDEFGHIK) 则表示为 4G 念 4 vingt sixteen.</p>
</li>
</ul>
<p>是不是突然发现, 法语是20进制的?</p>
<p>实际上, 英语源自法语, 所以英语最初也是20进制的.
但是, 英国人务实, 所以他们从阿拉伯数字学到了十进制, 于是20进制仅仅用在描述 0 - 20 的数字. 21 开始, 就采取的是十进制读法. 九十六, 里的 十, 在英语里念 ty , 也就是 nine ty six .</p>
<p>而法国人, 有法国人的骄傲, 所以必须在口语里, 既然转换为20进制, 念 quatre vingt seize . 也就是 4 个 vingt 进位 又 seize 个.</p>
<p>也许小数字, 法国人还可以心理默默执行进制变换. 但是更大的数字怎么办?</p>
<p>于是法国人的做法是, 把数字 3 位一组, 对每3位执行一次 10 -> 20 进制变换.
其实非常类似计算机里的 BCD 编码, 也就是不完全进制转换.</p>
<p>法国人早期统治了学术界, 所以就诞生了千进位为基础的国际单位制.</p>
<h1 id="进制是一个后天习得的技能">进制是一个后天习得的技能</h1>
<p>还记得小学一年级的时候是怎么学习数字的吗?
老师是一步一步的教大家进位的概念.
所以人脑其实并不天然的具备十进制. 而是后天习得.
所以, 全世界使用的数学如果最初是法国人发明的, 必然是20进制的.</p>
<p>虽然是个后天技能, 但是进制系统, 确实是有优劣之分的. 最终采取十进制的民族, 必然就在数学上有先发优势.</p>
<p>计算机最适合二进制, 人类也最适合十进制. 但是计算机并不一开始就使用二进制, 第一台计算机是十进制的. 但是二进制化后, 计算机才飞速发展起来. 人类也不是一开始就接受十进制. 但是自从使用了十进制, 人类的数学就飞速发展起来了.</p>
<p>十三世纪之后,欧洲人才开始接触到十进制,但直到印刷术的流行,十进制才在欧洲真正普及。</p>
<p>当时,一个叫斐波那契的意大利商人后代,由于痴迷数学,深感阿拉伯数字比罗马数字表达得更便捷,于是特意去国外向阿拉伯数学家学习,大约公元1200年回国。回国之后,斐波那契写下了《计算之书》、《几何实践》两本书,里面就介绍了十进制记数方法的实用价值,这两本书也是欧洲近代数学的开端.</p>
<p>当时欧洲各国记数方式并不统一,如今各国文字上还保留一些痕迹,比如法国采用的是20进制等,这一现状导致欧洲在数学上很难有突破性的成就,各国在数学交流上也面临制式不统一的难题。</p>
<p>但是, 随着十进制在欧洲的普及, 欧洲各国的数学都意识到了十进制的优越性. 于是不管母语是几进制的, 欧洲的数学家们纷纷改用十进制, 并以阿拉伯数字作为通用文字.</p>
<h1 id="古阿拉伯人还是古印度人还是古中国人发明了十进制">古阿拉伯人还是古印度人还是古中国人发明了十进制</h1>
<p>可见, 现代数学, 其实是阿拉伯数学传入欧洲后发展起来的. 阿拉伯数学是十进制的, 自然现代数学就是十进制的.</p>
<p>但是阿拉伯数字, 其实是印度人发明的. 所以, 十进制的源头, 应该是印度人?</p>
<p>阿拉伯数字, 是六世纪左右由印度传入阿拉伯的. 但是, 中国使用十进制数字的历史, 要更为久远. 早在8000年前的考古遗址上, 就发现了十进制的踪迹.</p>
<p>早在南北朝时期, 中国和古印度就有了文化交流, 因此6世纪的印度发明的阿拉伯数字, 必然是因为同中国的文化交流学去的十进制. 因为汉字过于复杂, 所以印度人需要更简便的符号替代一二三四五六七八九十.</p>
<p>这就是汉语念阿拉伯数字符号表示的数字, 为何念起来非常自然, 而且我们并没有因为阿拉伯数字的传入而改变我们念数字的方式. 我们念数字的方式, 5000年来从未变过.</p>
<p>所以, 阿拉伯数字, 属于出口转内销.</p>
<h1 id="是我们的祖先-发明了十进制">是我们的祖先, 发明了十进制.</h1>
<p>可耻的欧洲人, 为了掩盖历史, 故意说是因为人有十指所以数学使用十进制. 但是他们无法解释为何他们的日常语言并不是十进制的.</p>
<p>值得一提的是,很多以欧洲人姓名命名的数学原理或公式,其实中国人早已研究出来了,比如南宋“杨辉三角”在西方被称为“帕斯卡三角”,前者比后者早了400多年,南北朝“祖暅原理”在西方被称为“卡瓦列利原理”,前者比后者早了1100多年,因此并不是中国古代数学成就不高,而是传播度不够,以及欧洲掌握了近现代的话语权。所以说,落后不仅要挨打,连说话的权力都没有,只能眼睁睁地看着别人“窃取”祖先的成就。</p>
谁在害怕英语主科地位消失
2023-03-06T00:00:00+00:00
https://microcai.org/2023/03/06/we-must-boycot-english
<p>每隔几年, 就有代表提议取消英语主科地位. 每次都无功而返.</p>
<p>每次网上掀起取消英语地位的讨论, 都有很多很多的反对声.</p>
<p>支持取消英语主科地位的有两类人</p>
<ul>
<li>
<p>A类是学习不好, 少学一门是一门. 取消啥学科他们都支持.</p>
</li>
<li>
<p>B类是坚信 为中国人不再学英语而努力的学好英语. 如今他们长大了, 觉得时机成熟了</p>
</li>
</ul>
<p>而不支持英语取消主科地位的有三类人</p>
<ul>
<li>
<p>C类, 自然是搞英语培训产业的. 都上市公司了, 总不能一夜回到解放前吧.</p>
</li>
<li>
<p>D类是布林肯. 他反对中国中国在边疆地区普及普通话. 说边疆的孩子学了普通话, 就丢掉了自己的文化. <img src="/images/blk-anti-han.png" alt="不能学普通话啊" /> 同时他大力赞扬 乌兹别克斯坦人学英语 <img src="/images/blk-promot-english.png" alt="学英语才能当好奴才" /></p>
</li>
<li>
<p>剩下的F类则是被不林肯忽悠瘸了, 认为国家取消英语地位, 就没人学, 也没人教了. 然后他们想出国或者搞科研要研究国外的文献, 就得自己掏钱去培训班花大钱了.</p>
</li>
</ul>
<p>CD两类人知道自己的观点是站不住脚的. 所以他们采取的策略, 就是不断的在庞大的F类人里寻找代理, 用巨大的水军, 把妄图取消英语地位的人淹死.
而F人的诉求, 看起来是多么的冠冕堂皇, 使得反对者禽兽不如了. 他们污蔑学不好的A类人, 说他们是笨蛋, 还说不取消英语地位, 就是为了鞭策他们好好学习. 他们污蔑学的好的B了类人, 说他们是居心不良, 是想让A类人永远法挑战他们了. 这是在挑动 AB类的内斗.</p>
<p>但是 B 类的人永远是人数稀少, 数量庞大的A类的人一听 F 类的歪理, 就不自觉的和B类的人划清界限了.</p>
<p>于是, ACDF 合起来反对 B 类人, 使得他们的努力一直在作废.</p>
<p>近年来的趋势就是 A F 类的人在觉醒. A 类的人明白过来, 如果非要少一门学科, 那只能是英语. 谁要说取消语文数学, 他们不会再支持. F 类的人也逐渐的明白过来, 即使中国取消英语的地位, 也不是闭关锁国.</p>
<p>CD两类人彻底的慌了.</p>
<p>如今他们还在垂死挣扎. 但是我相信, 时间不会等太久.</p>
<hr />
<p>下面的话是对 F 类的人说的, 帮助他们尽快的从 CD 类人的谎言中清醒过来.</p>
<ul>
<li>迷信: 现在的世界, 英语仍然是主流. 不学英语就不能融入世界
<blockquote>
<blockquote>
<p>破解: 从来就没有融入世界这个说法. 只有同化世界, 和被世界同化. 如果认为学英语是主流, 那就是同意, 我们要被美国同化. 不被美国同化, 就没有未来. 想想千年以前, 盛唐在世的时候, 全世界的人都来长安朝拜. 他们要融入大唐. 不融入大唐, 就是被世界抛弃. 只不过如今, 风水轮流转罢了. 恰恰我们要重新捡起我们的文化, 我们学英语, 不是为了融入美国, 而是为了渗透美国, 师夷长技以制夷. 所以, 社会有分工, 我们不需要所有人都去学英语. 既然文理有分科, 为何不承认, 英语也可以分科, 让擅长的人学擅长的科目呢? 恰恰是如今全民学英语的氛围, 导致了翻译人才的缺失. 译者甚至不再追求信达雅, 反而被批评翻译的不够好的时候丢一句 “你不会去看英语原文吗?”</p>
</blockquote>
</blockquote>
</li>
<li>迷信: 取消英语主科地位, 穷人就没有出路了
<blockquote>
<blockquote>
<p>破解: 体育不是主科地位, 所以我们四肢不发达的穷人就没有出路了. 因为学校不再教我们体育. 而那些有钱的家庭就可以参加各种课外培训, 把自己练的身强力壮. 然后我们四肢不发达的穷人就彻底的没出路了.
哦? 我说错了? 这种事情并没有发生? 那是当然的啊! 取消了英语地位, 虽然说穷人就没有国家提供的免费英语教学了. 可是同样的, 穷人也不需要在英语上和富人卷了. 他们所说的那种情况会发生, 那只能是国家取消英语的主科地位, 但是高考还是要考英语. 这才会导致有钱人可以偷偷请老师学英语然后卷死穷人. 其实, 教育什么时候是免费的? 所谓免费的英语老师, 那是占用了多少本应该让他们学好数理化的国家教育经费?
科学技术才是第一生产力. 什么时候会英语成了第一生产力? 我们把有限的教育投入, 将其中的很大一部分拿来教英语, 自然剩下的科学技术就分不到多少教育资源了. 这才是穷人无法翻身的根本所在.
我们造原子弹的时候, 高考还没有英语. 有人说那是靠归国的科学家. 但是这部分留美的科学家, 他们成长于民国时代, 那时候的高考也没有英语. 而且一个好汉三个帮. 没有国内自己培养的数量庞大的科学家, 只靠从美国回来的科学家, 是不可能造出原子弹的. 也恰恰因为那时候没有全民学英语, 所以翻译的人对待工作特别认真. 他们要确保自己的翻译信达雅, 要完整的保留英文原文的意义, 将这个意思传达给国内大多数没学过英语的科学家.</p>
</blockquote>
</blockquote>
</li>
<li>迷信: 英语降低地位变成课外”兴趣” 反而会加重学习负担
<blockquote>
<blockquote>
<p>破解: 这是我听过最好笑的论断了. 他们说, 现在的教育不卷主科光卷兴趣班了. 所以英语不是主科了, 家长会加重英语的投资. 可是体育也不是主科, 我从未见到家长加重体育投资的事情发生过. 而且兴趣科目那么多, 为啥家长一定要卷英语? 这不还是英语中心论的殖人思想在作祟么?</p>
</blockquote>
</blockquote>
</li>
</ul>
<p>只有殖民地才会把宗主国的语言列为必学
CDF说的那些道理, 恰恰是公知们用来反对取消英语地位的.
他们非常害怕中国人不再为英语背书. 非常害怕中国人不再当殖民地, 不再为英语卑躬屈膝.
磊哥可能是被某些隔壁人大代表搞怕了, 以为他们人人都是资本家的代表.
中国人崇洋媚外的心态恰恰是英语必修导致的.
恰恰是很多人学不会英语, 所以导致大量的英语课外辅导班. 资本家靠教人说英语, 就能赚到去美国上市</p>
<p>布林肯:中国新疆孩子学习普通话是文化种族灭绝。
布林肯:乌兹别克斯坦的孩子学习英语对他们的未来有好处。</p>
<p>说明美国恰恰害怕中国人不再学习英语.</p>
都是唐人
2023-03-03T00:00:00+00:00
https://microcai.org/2023/03/03/we-are-all-from-datang
<p>秦始皇统一了天下, 从此大家都是秦人. 刘邦建立汉朝后, 大家都是汉人.</p>
<p>李世民建立大唐后, 从此大家都是唐人.
千年后, 哪怕远去他乡, 还自称 “新唐人”</p>
<p>那么, 唐人都有哪些呢?</p>
<p>首先看大唐的疆域.</p>
<p>大唐的疆域, 西到碎叶城, 北至北冰洋 东北到日本海, 东到东海, 南到南海, 西南到喜马拉雅山.</p>
<p>为历朝历代最大. 而且全部都派遣了官吏治理, 而不是粗旷式的治理. 凡此疆域内诞生的人民, 即是大唐的子民. 都享有读书做官的权力.</p>
<p>最西部碎叶城出生的李白, 都能到长安做官, 而不是所谓的 “化外之民”.</p>
<p>因此, 以后, 凡是在这片区域内诞生的人, 都可以”争天下”. 哪怕大唐亡国了, 他们也可以说他们的先祖曾是大唐的子民.</p>
<p>有人要反驳, 说大唐的北部疆域到不了北冰洋.
那么请问, 大唐的北边还有敌人吗? 既然北边没有敌人, 你怎么能给这些地方生造一个主人呢?
没有敌人, 领土自然是自动延伸到海岸线.</p>
<p>你不能说, 只有唐人走到过的领土才算唐人的领土. 冰岛人真的走完所有冰雪覆盖了的冰岛了吗?</p>
<p>既然大唐建立了安北都护府, 并且没有第二个国家反对大唐的主权伸张, 不能因为安北都护府的人没有走到北冰洋, 就说大唐的领土北不达北冰洋.</p>
<p>西达碎叶城, 是因为更西的地方, 是阿拉伯人的底盘. 向西扩充, 就会遇到阿拉伯人的抵抗.
只能靠武力扩张, 而大唐兵力最西也只能抵达碎叶城.</p>
<p>当时大唐向东北扩展, 就遇到了另一个国家的阻挠. 那就是高句丽.
很多人把高句丽看成是半岛政权. 那是不对的.</p>
<p>高句丽盘踞半岛和辽东的肥沃土地. 阻碍了大唐向东北的扩张.
所以, 大唐必灭之.</p>
<p>高勾丽灭后, 高句丽的领土, 自然就纳入大唐的版图. 同时, 没了高勾丽的阻碍, 大唐可以继续向东北扩张.
然后为了管理东北, 建立了安东都护府. 唐人向东北扩展, 只要没遇到另一个国家, 大唐的领土应该也是自动延伸到海岸线为止.</p>
<p>事实上是, 确实没有遇到另一个国家了. 强大的高勾丽都被灭了, 继续东北前进, 当地的部落还能抵抗?
只能自动归顺成为安东都护府治下子民了.</p>
<p>而在西南方向, 大唐和吐蕃打了一战. 吐蕃投降, 成为附属国.</p>
<p>什么叫附属国? 附属国, 就是大唐的吐蕃自治区. 行政长官称吐蕃国王.
皇帝册封国王, 那是再正常不过了.
周天子册封了多少国王?
也就嬴政头铁, 不愿册封. 但是刘邦建国后, 还不是封了一堆国王. 以至后来有了七王之乱.</p>
<p>如果附属国还有独立主权, 那为啥要要战败后才肯接受册封. 应该立国就马上屁颠屁颠的跑去求册封.
哦不, 为啥要被册封呢?</p>
<p>因为松赞干布是篡权夺来的政权, 他要不接受皇帝册封, 他在当地根本没有合法性, 分分钟被贵族推翻.
所以他在战败后, 马上投降成为属国. 因为不投降, 自己就要被推翻了.
所以说, 吐蕃国王的统治合法性, 本身就来自李世民的册封. 被李世民册封后, 那些反对他的贵族们, 纷纷失去了造反的能力. 因为知道打不过大唐的军队. 他们要造反, 造的就不是吐蕃国王的反了, 而是造的大唐的反了.</p>
<p>那些说册封不是领土的, 可以歇息了.</p>
<p>可以说, 当今中国的版图, 是继承于唐, 而不是清.
清收复这些地方, 凭借的本身就是唐的法理.
而且, 清本身能入主中原, 也是因为安东都护府提供的法理. 因为女真族的祖先, 本就是唐人.</p>
<p>高勾丽被灭后, 其实半岛就纳入中原版图了.
朝鲜半岛从此成为中国的一部分, 直到甲午战争, 战败后割让给了日本.
日本战败后, 顺应二战后各殖民地独立的潮流, 没有归还朝鲜半岛, 而是让其独立了.</p>
<p>虽然半岛从明代开始, 都是以”皇明朝鲜国”的身份存在, 而不是直接推行郡县制. 但是历代朝鲜国王的统治合法性, 都来自中原皇帝的册封. 否则朝鲜半岛何来一个超越王朝周期律的政权?</p>
<p>清朝对中原版图的扩张贡献, 可能仅仅是彻底征服越南, 使越南成为属国.</p>
<p>但是, 大清在鸦片战争后, 就把越南割让给了法兰西.</p>
<p>都说大清割地赔款割地赔款, 结果历史学的不好的人, 只记住了外东北和香港岛.
其实大清割让出去的价值最高的三块土地, 就是 越南朝鲜和台湾.
不过日本投降后, 台湾最后还回来了.</p>
<p>由于如今朝鲜和越南已然独立建国, 所以历史课本就不再把朝鲜和越南视为割让领土罢了.</p>
电机和电车的一点想法
2023-02-28T00:00:00+00:00
https://microcai.org/2023/02/28/thoughts-on-motor-and-ev
<p>除了有刷电机外, 无电刷的电机, 驱动起来是一门学问.</p>
<h1 id="反电动势">反电动势</h1>
<p>在讲解这门学问前, 先回顾一下物理学上的一个公理, 能量守恒定律. 能量守恒定律在电机控制领域非常重要, 电机的很多特性, 都来自宇宙要遵守能量守恒定律导致的.</p>
<p>反电动势是电机达成能量守恒定律的一个重要手段.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>电机的输入电流 = (电池电压\*占空比 - 电机反电动势) / 绕组阻抗
绕组电流 = 电池电压\*输入电流 / 反电动势
</code></pre></div></div>
<p>这里面的原因是, 电机的输入电流, 也就是从电池汲取的电流, 功率 = 电池电压*电机输入电流.</p>
<p>但是, 电机的扭矩来自于绕组的电流(达到磁通饱和前, 电机扭矩正比于绕组电流), 而和电机的转速无关. 因此绕组电流和输入电流不是一回事.
如果绕组电流等同于电池供应给电机的电流, 那么电机就违背了能量守恒定律.</p>
<p>因为电机的功率 = 扭矩*转速, 而反电动势正比于转速, 扭矩正比于绕组电流. 所以电机的功率 = 绕组电流*反电动势</p>
<p>这里, 就要得出一个 绕组电流*反电动势 = 电池电压*电机输入电流 的能量守恒来.</p>
<p>如果电机没有反电动势, 能量守恒就要被破坏.</p>
<p>正是因为绕组电流和电池供应的电流不同, 所以电机控制器, 不仅仅承担为电机”电子换相” 的工作, 也承担 DC/DC 变压的工作. 准确的来说, 是 Buck Converter.</p>
<p><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/Buck_circuit_diagram.svg/600px-Buck_circuit_diagram.svg.png" alt="降压变换器电路图" /></p>
<p>绕组电流需要”续流二极管” 的存在, 这个存在, 是由开关管上的寄生二极管承担的.</p>
<p>因此, 在电机控制器里, 承受功率的开关管, 其工作电流实际上是和电机扭矩有关, 而和电机输出功率无关. 正是为了保护控制器里的开关管, 同时也避免电机内部磁通饱和, 所以电机的扭矩调教为 初段恒扭矩, 后段恒功率.</p>
<p>如果一直要输出最大功率, 想想在低速下, 扭矩 = 功率/转速. 转速为 0 或者接近 0 的情况下, 扭矩要无限大. 这在工程上是无法实现的. 所以电机在低速下只能恒扭矩, 随着转速的提升而提升功率. 到了后期, 电机就进入恒功率模式, 扭矩随着转速的提高而下降.</p>
<p>而电机进入恒功率模式, 恰恰又是因为反电动势的存在, 限制了电机的电流.</p>
<p>因为, 实际上加在绕组两端的电压, 等于 输入电压/反电动势. 当反电动势接近电池电压后,
绕组两端的电压 / 绕组阻抗 后, 就无法维持更大电流了. 因为随着转速的提高, 电机的换相速度也提高了, 也就是提高了交流电的频率. 那么电机的绕组作为一个感性元件, 其交流感抗也在提高. 电压降低, 阻抗提高, 电流就不得不继续降低了.</p>
<p>电机从恒扭矩到恒功率转折点就是一个非常关键的指标.</p>
<p>反电动势是限制电机最高转速的最关键原因. 电池电压必须高过反电动势, 才能让电机继续提速.</p>
<p>降低反电动势, 就是要减少绕组, 但是会降低电机扭矩.
所以高扭和高转在电机上就是个矛盾体.</p>
<h1 id="方波-vs-正弦波">方波 vs 正弦波</h1>
<p>无刷电机, 通常会分为直流无刷电机, 或者是永磁同步电机. 他们的结构都是一样的 : 绕组定子+永磁体转子.</p>
<p>区别就在于, 直流无刷电机, 使用的是6步换向法驱动. 每时每刻只有2条线是通电的. 另一条线悬空. 而永磁无刷电机, 使用的是矢量控制驱动, 让三相电构成的磁场矢量, 始终与定子的磁场矢量垂直以获得最大扭矩.</p>
<p>一个电机, 使用6步换向法驱动, 还是使用矢量控制驱动, 关键在于其反电动势的波形.
如果反电动势的波形为三相交流电, 则应该使用矢量控制驱动. 如果反电动势的波形为梯形波, 则使用6步换向法驱动.</p>
<p>6步换向法驱动, 电机每旋转一周, 就要执行6次换向. 每次换向, 不可避免的会带来冲击.
而使用矢量控制驱动, 电机可以输出恒定扭矩, 不会有换向冲击.</p>
<p>但是为了合成正弦波, 控制器必须使用频率更高的开关速度才能调制出完美的正弦波. 开关频率越高, 波形越完美. 波形越完美, 则输出越稳定, 电机效率也越高.</p>
<p>同时, 为了定子的合成磁场始终垂直于转子, 需要更高精度的转子位置传感器. 所以矢量控制驱动法的控制器成本会高于6步换向驱动.</p>
<p>方波相比正弦波, 有个优势就是能 100% 利用电池电压. 而使用正弦波, 电池电压只能用于提供交流电波形里的峰值电压, 而交流电的有效电压只能是电池电压的 1/ √(3) .</p>
<p>为了克服矢量驱动不能有效利用电池电压的缺点, 有发明一种叫 “马鞍波” 的过调制方法.</p>
<p><img src="/images/svpwm-maan.jpg" alt="马鞍波" /></p>
<p>这种调制方式可以提高母线电压的利用率.</p>
<h1 id="动能回收">动能回收</h1>
<p>永磁电机可以当作发电机使用, 将汽车的动能回收回电池.</p>
<p>但是具体的回收方式, 又分为 负扭矩法, 和整流升压法.</p>
<p>整流升压法最简单, 就是把电机当成发电机, 发电机输出的三相交流电, 桥式整流后升压, 就可以给电池充电了. 但是这个方法其实在工程上并不使用.</p>
<p>工程上使用的另一个能量回收的方法, 其实非常的简单, 就是小 pwm 法.
就是使用更小的 pwm , 使得 电池电压*pwm < 当前转速的反电动势.</p>
<p>只要当前的 pwm 设定会导致控制器输出电压小于电机反电动势电压, 则控制器的逆变桥会反过来, 工作在 Boost 升压状态, 将电动机的电流反馈到输入端.</p>
<p>因为 6 个开关管构成的逆变桥, 会本能的工作于四象限状态. 只要开关管对应的状态, 和电机转子位置一致即可. 因为电机在工作的时候, 功率因素是不可能为 1 的. 所以电流滞后于电压, 导致电机总是会在每个周期里向输入端回馈部分能量. 所以6个mos构成的逆变桥是天然的工作在4象限的.</p>
<p>实际上, 三相PFC电路, 就可以看成是对一个恒定转速的电机进行动能回收.</p>
<p>这种模式的动能回收, 有个缺点, 就是如果转速下降后, 不及时停止, 就会从回收模式进入驱动模式. 而整流升压法就不会有最低回收速度限制.</p>
GUI 渲染延迟
2023-02-17T00:00:00+00:00
https://microcai.org/2023/02/17/gui-render-latency
<p>鼠标点击窗口标题栏, 然后快速拖动. 如果窗口和鼠标紧紧贴到一起, 那么恭喜你, 你的OS并没有GUI渲染延迟问题.
但是, 经过实验后, 各位想必都会发现, 往往是鼠标指针先到达, 然后窗口才会跟过去. 窗口的移动似乎总是延后于鼠标指针.
如果各位是从旧时代过来的人, 应该还记得, 早期的 WinXP, 窗口拖到是非常跟鼠标的.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>没有渲染延迟问题的意思是, UI 的反应总是在下次屏幕刷新的时候出现. 也就是最多延迟为一帧, 最少可以恰好没延迟. 平均延迟时间为 0.5帧.
我们设想屏幕为班车, 如果设定刷新率为 60hz, 就是每 1/60 s 就发车. 而用户的输入, 就像随机到站的乘客.
如果到站后, 总是能乘坐下一趟车, 那么就可以认为 UI 没有延迟. 虽然总体上是平均延迟了 0.5帧, 但是本篇
不考虑总延迟, 只考虑 "错过" 班车的情况. 因此我以 "错过" 班车的数量来定义延迟. 因为鼠标指针的绘制,
是独立的路径, 因此鼠标指针永远不会错过班车. 但是, 窗口的内容绘制, 可能会错过班车. 所以可以以拖动窗口
来观察渲染延迟.
</code></pre></div></div>
<p>如今不跟了, 主要原因, 其实是现在普遍使用的桌面混成功能导致的.
在非混合时代, 窗口的内容是直接对应显卡上的屏幕输出缓冲区. 而在桌面混成时代, 所有的窗口都有一个独立的缓冲区. 由混成器
将这些图片合成到屏幕上. 也就是说, 要将大量的图片, 合成为一张图片.</p>
<p>而混成器, 为了避免屏幕撕裂, 必然会打开垂直同步. 可是, 打开垂直同步, 为何会导致错过班车呢?
这就不得不说到目前的 UI 库了. 目前 gui 程序使用的 UI 库本身, 也是打开了垂直同步的.</p>
<p>于是, GUI 程序更新窗口内容的时候, 会等待下一班车, 虽然他们绘制足够快的话, 看起来也总是能搭上最后一趟.
但是, 关键在于, 他们的班车, 也是 1/60 s 发车, 但是目的地却不是屏幕, 而是把人载到合成器的车站里. 等待合成器的下一趟班车.
由于 GUI 的班车和 屏幕的班车是同时发车的, 所有他们到达屏幕车站后, 必然只能等下一趟.</p>
<p>于是, 所有的 GUI 内容更新, 一定一定会错过一帧.</p>
<p>也就是, GUI 内容的更新, 平均延迟时间是 1.5帧. 这还是在 GUI 程序绘制时间为 0 的情况下. 如果绘制时间超过 1/60s , 则延迟时间还要上升到 2.5帧.</p>
<p>让问题更雪上加霜的是, 现在的显卡驱动, 即便你只要求打开 vsync ( 也就是使用双缓冲 ), 驱动都会在内部实现里, 偷偷给你搞成 三缓冲. 于是这混成器
和app两边各增加一帧延迟.</p>
<p>最终 GUI 程序的平均延迟时间就达到了恐怖的 3.5 帧了, 也就是 60hz 刷新率的条件下, 平均延迟 60ms. 当 8ms 延迟的鼠标指针喷上 60ms 延迟的窗口内容,
自然就能很明显的分辨出窗口的迟滞了.</p>
<p>这些延迟, 对所有开启混合功能的 OS 平台都是适用的.</p>
<p>其中, 早期, 各大平台的策略是对全屏显示的窗口关闭混合功能. 以消除混合功能带来的延迟对游戏的影响.</p>
<p>到了 wayland 时代, 由于混成功能是必需品, 所以 wayland 有更大的必要去解决混成导致的延迟.
wayland 提出的方法则是 frame callbacks. 除了混成器需要等待 vblank 信号, 其他 app, 使用 frame callback 来确定什么时候上车.
这样保证 app 的车到站后, 恰好遇到 混成器发车. 于是解决错过班车导致的延迟问题.</p>
<p>但是很可惜, frame callback 机制下, 混成器给app发信号的时机点选择非常重要. 只有恰到好处的时间点, 才能同时兼顾流畅性和低延迟.
KDE 直到 5.21 才解决好. gnome 则还在 Work In Progress 中.</p>
<p>不过 KDE 最新版本都到 5.27 啦, 所以其实 KDE 早就解决了. 嘿嘿.</p>
Wayland 已经可用
2023-02-15T00:00:00+00:00
https://microcai.org/2023/02/15/wayland-usable-now
<p>2023 年情人节, KDE 终于发布了 5 系列的最后一个版本: 等离子 5.27</p>
<p>经过十几年的雕琢, wayland 终于可用了.</p>
<p>既然 wayland 可用, 我就迫不及待的要删掉 Xorg 了.</p>
<p>给 xorg-server 去掉了 xorg, <code class="language-plaintext highlighter-rouge">USE=-xorg emerge xorg-server</code></p>
<p>然后 /usr/bin/Xorg 就拜拜了. 至于为啥还要编译 xorg-server, 是为了他提供的 Xwayland .</p>
<p>Xorg 没了后, 就可以顺利卸载 xorg-drivers xf86-video-* xf86-input-* 驱动了. Xwayland 并不使用这些驱动.</p>
<p>Xwayland 是不使用 Xorg 的驱动的. Xwayland 从 kwin 获得输入, 因此无需 xf86-input-* 驱动, Xwayland 本身是作为一个 wayland 客户端, 因此也不需要
xf86-video-* 驱动操作显卡. wayland 客户端用啥 GL 驱动绘制, Xwayland 也用啥 GL 驱动绘制. 驱动的自动选择交给了 libglvnd.</p>
<p>要同时给 Xorg 和 Mesa 写2个驱动的时代终于过去了. 虽然 Xwayland 仍然保留 X11 兼容性, 但是 Xwayland 不需要专门的驱动了.</p>
<p>PS: windows 其实是和 X11 一样的落后, 驱动要写2份. 一份给 GDI 调用的 2D 加速, 一份给 dx 调用的 3D 加速.</p>
<p>等离子体 5.27 还包括了一个非常非常重要的 fix , 就是支持了 text-input-v1 扩展协议, chrome 和一票 electron 程序, 终于不需要 xwayland 也能搞定输入法了.</p>
<p>也就是说, 等离子 5.27 终于把 wayland 变成了可以完全替代 X11 的东西了.</p>
面向公知治国
2023-01-26T00:00:00+00:00
https://microcai.org/2023/01/26/gongzhi-oriented-governance
<p>古时候, 有一个皇帝, 他对于如何治理国家没有主见. 于是他就很喜欢听公知的话.</p>
<p>当时, 公知说, 太监都是坏人, 尤其是那个魏忠贤, 大大的坏.</p>
<p>于是他砍了魏忠贤.</p>
<p>公知说, 锦衣卫只抓好人.</p>
<p>于是他撤了锦衣卫.</p>
<p>公知说, 勤俭的皇帝是好皇帝. 于是他每天“鸡鸣而起,夜分不寐”,宫中从无宴乐之事。</p>
<p>他这么节俭, 国库还是没钱. 他想增加国库收入, 公知说, 要藏富于民, 不能与民争利.
于是他放弃了江南的商税.</p>
<p>外有鞑子, 内有闯王. 为了抵御外敌, 他听从公知的建议加征三响. 还只收北方的.</p>
<p>后来公知说, 袁崇焕投敌啦. 于是他马上斩了袁崇焕.</p>
<p>最后走投无路了, 他发现他什么也没做错, 为啥就亡国了呢?</p>
<p>今也有一皇帝, 事事听公知的.</p>
<p>公知说, 新冠很危险. 能打赢新冠的只有民主和自由. 于是他封城了. 他要公知闭嘴.
后来, 公知说, 新冠就是大号流感. 人民要生活, 不能封城. 于是他躺平了.
公知说, 放烟花是自由. 于是烟花又能放了.</p>
<p>公知说, 武统台湾就会被美国以贸易战报复. 于是他不敢武统.
公知还说了, 辉瑞特效药好, 不让引进就是故意杀人. 于是他引进了辉瑞神药. 但是引进后马上遭遇了老百姓的强烈抵制.</p>
<p>对了, 公知还说, 中兴被美国制裁是咎由自取, 于是他带着中兴给美国请罪.</p>
悟空和观音谁的修为高?
2023-01-21T00:00:00+00:00
https://microcai.org/2023/01/21/monky-vs-guanyin
<p>悟空在西游结束后, 被封为斗战胜佛. 而一路上帮助他的观音依旧是菩萨.
在灵山教里, 佛大于菩萨大于罗汉. 很多人对一个毛猴突然站观音头上不接受. 于是自欺欺人的说悟空地位低于菩萨.</p>
<p>首先, 佛和菩萨, 是以什么为划分的? 地位又是以什么为划分的?</p>
<p>西游世界, 他是一个修仙世界. 修仙世界, 实力为尊. 这就是为何, 年纪更大, 资历更老的人太上老君, 要屈尊于玉帝之下.
因为这个世界, 其实是按实力说话.</p>
<p>按实力排行, 观音高于悟空.
那么为何悟空成佛, 观音还是菩萨呢?</p>
<p>这涉及到另灵山教的一个规定. 就是佛和菩萨, 不是以实力划分的. 而是以对佛法的领悟划分的. 对佛法的领悟更高者成佛.
次之则为菩萨. 再次则为罗汉.</p>
<p>也就是说, 佛和菩萨的区别, 是一个纯粹的学术排名, 而不是实力排名.
其实看西游记就会发现, 到了后期, 悟空对佛法的参悟, 其实已经远远甩开其他人了, 连唐僧都要向悟空讨教.
不过唐僧最后排名高悟空一个名次, 其实主要是因为成佛后, 唐僧取回了金蝉子的记忆. 也就是说, 凡人唐僧对佛法的领悟,
不过区区二十几年, 不如后来大彻大悟的孙悟空. 但是取经成功后, 继承了金蝉子的记忆, 自然对佛的领悟重新超过孙悟空.</p>
<p>那么悟空成佛, 是不是对佛法的领悟要高于菩萨呢?</p>
<p>这也不见得. 因为学术排名还有个前提, 人家要参与排名.
观音拿到菩萨学位后, 就放弃了成佛. 因此她对佛的参悟到底几何, 已经无人知晓了.</p>
<p>也就是说, 佛和菩萨的区别, 是一个学术上的区别. 不是实力上的区别.</p>
<p>那么, 按实力来说, 观音的实力如何呢?</p>
<p>人的修为是可以增长的. 比如天庭里很丢人的太白金星, 年纪在天庭都是数一数二的人物, 但是因为修为增长遇到瓶颈. 战斗力是个只有5的渣.
而悟空呢? 刚去菩提座下学了十几年, 实力就已经超过东海龙王了. 这修练速度是非常逆天的. 哪怕是消炎都自叹不如.</p>
<p>而且悟空离开菩提成为散修后, 并没有停止修练. 到他死亡的时候, 才过去三百多年, 修为更进一步, 地府都奈何不了他了.
不过这里可以看出来, 悟空在生死簿上的寿命只有三百多岁, 大概就是个筑基修士的水平.
也就是说, 悟空在修练地煞72变之前, 在菩提的道场里用了几年时间就修练到筑基了.
但是, 菩提教给他的地煞72变的时候应该是屏蔽了天机, 导致生死簿没有更新悟空的寿命. 所以菩提其实教他地煞72变的时候就已经布局了.
逐出师门菩提早就安排好的, 而不是后来他和师弟炫耀才生气逐出.</p>
<p>至于悟空的实力, 首先悟空刚刚当弼马温的时候, 实力肯定是已经超过金仙了. 因为天庭封官的一大门槛就是实力要达到金仙.
修仙修仙, 气运之子修练靠天赋, 而凡人修仙靠丹药. 韩立这个没修练资质的人, 到第二季结束终于结丹. 靠的就是吃丹药.
论丹药, 谁吃的有猴子多呢?</p>
<p>猴子可是同时天赋+嗑药. 因此偷了老君的丹药, 并且在八卦炉里炼化后的悟空, 实力至少应该是在大罗金仙.
而天庭的当值神仙, 通常配置就是大罗为主官, 金仙为副手, 真仙为众手下. 于是大闹天宫的时候, 当值神仙在不拼死相搏的情况下都打不过悟空.</p>
<p>而天庭其实并不是没有大罗以上的神仙, 而是当时这些神仙并不在天庭轮值. 悟空这属于是偷袭. 打天庭一个措手不及.</p>
<p>那么观音的修为如何呢? 观音的修为在大罗之上是毋庸置疑的. 观音作为灵山的魏忠贤, 实力肯定是远超一般灵山弟子的.
观音作为多宝的师妹, 实力应该稍逊于如来. 而如来作为灵山扛把子, 实力肯定是准圣巅峰. 毕竟成圣后就会被天道压制, 关入33重天之外不得入世.
那么观音的实力, 不是准圣巅峰就是准圣后期了. 也就是比悟空高一个大境界.</p>
<p>但是, 那是大闹天宫的时候的实力对比.</p>
<p>悟空被压 500 年, 肯定会导致境界跌落. 但是跌落多少并没有提及. 作为天庭反骨仔的哪吒, 悟空被压后就没有和悟空交手过.
但是悟空和镇元子交手过. 打得难舍难分, 但是还是略逊一筹. 镇元子作为地仙之祖, 并不是地仙的修为. 镇元子作为和观音同时代的人,
他的实力和观音是可比, 但是还是次于观音. 推测为大罗后期或者准圣初期.
那么悟空在五观庄的时候, 修为大概就是大罗初期, 或者是大罗后期.
而且红孩儿虽然压制了悟空, 但是其实他是靠的三昧真火, 这个火属于可以越境杀敌的存在. 尚且只能压制悟空. 说明悟空至少高红孩儿一个大境界.</p>
<p>悟空这个人, 其实也没逃脱修仙小说里角色的一贯行为, 就是遇到比自己厉害的多的人马上认怂. 从他调戏牛魔王的老婆这件事就可以看出来,
悟空其实很自信自己实力超过牛魔王的.</p>
<p>别看最后收牛魔王是找了帮手, 那是因为悟空和牛魔王对战的时候, 牛魔王是被逼迫到燃烧精血了. 牛魔王要使出自己200% 的力量对付悟空.
悟空就帮救兵, 是非常正确的选择.说明悟空和牛魔王是在同一个大境界, 但是悟空为境界中后期修为, 而牛魔王是境界的中前期修为. 相差不大,
但是面对燃烧精血打 buff 的牛魔王, 悟空没有 100% 的把握无伤通关.</p>
<p>在小雷音寺这个关卡可以看出, 悟空的修为在黄眉老佛之上. 但是黄眉老佛有厉害的法宝导致悟空不敌. 而黄眉老佛的法宝, 理论上来说, 只有准圣才可破, 所以有恃无恐.
而天庭里的神仙, 悟空能请到的, 一个准圣都没有. 所以这难老难了. 也就是说, 在不使用法宝的情况下, 悟空实力略强于黄眉老祖.</p>
<p>黄眉老祖敢冒充如来, 除了他是弥勒佛的童子外, 他自身的修为也是一大依仗. 推测他的修为至少不低于大罗后期.
因此可以肯定的说, 在悟空西行快结束的时候, 他已经恢复了实力, 并有所长进了.
因此, 悟空成佛后, 实力突破准圣是板上钉钉的事情.</p>
<p>但是观音迈入准圣境界很久了, 悟空要追上, 是需要一定的时间的.</p>
<p>所以, 结论是, 悟空成佛后, 实力推测为准圣初期, 或者最次是大罗巅峰.
观音则是在准圣境界待了很久了, 少说是中期修为.</p>
<p>因此从实力上来说, 悟空确实是不如观音的. 但是悟空成长非常快. 才一千多岁, 其中还有500年是被关押, 修为还有所倒退的情况下, 就修炼到大罗巅峰乃至准圣了.
这个修炼速度, 无人能及.</p>
<p>如果西游有后传, 悟空不陨落的情况下, 迈入准圣巅峰, 乃至成就圣人, 都是板上钉钉的事情.
反观观音菩萨, 修为原地踏步数千年不得寸进.</p>
<p>PS: 其实天庭败给如来, 主要原因是可动员实力不如灵山.</p>
<p>如来要做事, 他手下有3位准圣战力的人可以调遣. (观音普贤和文殊) 这些都是 100% 服从领导的战力.</p>
<p>而天庭虽然准圣数量明面上有数十人, 但是实际上能供玉帝自由调遣的全部都是大罗和大罗以下的修为. 玉帝无法压服其他准圣是致命缺陷.
而他自身虽然是准圣修为, 但是为了面子从不出手. 而如来则是能成事不计较亲力亲为.</p>
<p>主要原因是天庭里的准圣, 资历都比玉帝还老. 只是名义上为玉帝所领导. 实际上除非天庭遇到生死存亡的危机, 否则各安天命.
玉帝在能打的过悟空的情况下, 选择请如来, 就是怕 “诺大的天庭, 打个猴子居然要玉帝亲自出手” 这样的结果. 这对天庭的声望会形成巨大打击,
更坐实天庭无准圣战力的谣言.</p>
<p>天庭只所以会形成这个局面, 就是因为昊天玉帝的天庭, 是抢来的. 初期玉帝因为是道祖的童子, 被推出来当傀儡. 你一个刘协, 还想诸侯臣服?
为了解决昊天手下无人, 道祖选择坑截教帮玉帝扩充手下. 所以, 能实际被玉帝掌握的战力, 只有封神榜上的神仙. 剩下的都只是看道祖的面子
而在名义上归属天庭.</p>
sfp 形态 gpon 光猫失败
2022-12-17T00:00:00+00:00
https://microcai.org/2022/12/17/gpon-sfp-onu-success
<p>多年前, 折腾过一次 <a href="/2019/07/30/gpon-sfp-onu-failed.html">sfp 形态 gpon 光猫失败</a></p>
<p>时隔三年半, 我又开始折腾 sfp 猫棒了.</p>
<p>这次非常顺利, 一次成功.</p>
<p>主要是固件好用起来了. 改好 SN , 填好 password 搞定.</p>
高房间必然没有高科技
2022-12-05T00:00:00+00:00
https://microcai.org/2022/12/05/high-estate-price-conflict-with-hightech
<p>国家要发展, 必然要有高科技. 科技是第一生产力.</p>
<p>而高科技, 必然和高房价是冲突的.</p>
<p>第一点: 高科技对土地的需求非常大. 高科技必然意味着大量的研发. 研究就得做实验. 做实验就需要实验室. 有实验室就必须要有大量仪器设备.
这些仪器设备对环境要求苛刻, 必然需要大量的专用房子. 因此科研需要大量的土地.
高房价必然大大推高科研成本. 高科研成本必然导致败给低科研成本的对手.</p>
<p>第二点: 高科技需要大量高级人才. 高房价必然推高人才的期望待遇. 搞科研, 除了要支付自身的土地需求, 还得间接的支付人员的土地需求.
于是高房价必然导致过高的人员支出. 大大推高科研成本. 高科研成本必然导致败给低科研成本的对手.</p>
<p>所以, 高房价必然没有高科技. 即便是有, 那也是靠畸形补贴政策催生的, 一旦失去补贴, 立马败给同行.</p>
<p>所以, 需要高科技的新能源汽车, 必然不可能诞生于北汽和上汽.
需要高科技的芯片行业, 也必然不可能诞生于北京上海.</p>
<p>正如纽约也造不出芯片, 造不出汽车.</p>
<p>高科技, 需要高人才, 必然需要大学城.
因此大学城必然需要建设在低房价的地方.</p>
<p>在高房价的地方建设大学, 大学生面对的将是绝望.
绝望就会催生革命的动力. 必然会被野心家利用.
被利用, 必然就会拿着A4纸上街游行.</p>
platformIO 单片机开发神器
2022-11-12T00:00:00+00:00
https://microcai.org/2022/11/12/platformio
<p>近来闲暇无事,想弄个三相变频器玩玩。
三相变频器可以用来驱动异步电机,还可以驱动直线加速器。</p>
<p>于是搜索万能宝,发现只有380v的变频器。</p>
<p>可是我想玩安全电压的。最多24V三相交流电。</p>
<p>找不到,看来只能自己开发一个了。</p>
<p>硬件不用自己做,只要购买无刷电机驱动板就可以了。
无刷电机驱动板,只要提供3路PWM信号,就可以生成驱动无刷电机用的三相交流电。</p>
<p>而生成这pwm信号的工作,就留给了单片机。</p>
<p>我比较中意的单片机是 ESP32. 便宜,性能强。STM32 零头的价格提供了比 STM32 高数倍的性能。</p>
<p>我写程序啊,不喜欢从头写。哪怕是单片机。
总不能用汇编代码一点一点写吧。再说,汇编我也只知道8051的汇编,不懂 esp32 的汇编。</p>
<p>所以,用了 SimpleFOC 这个库。</p>
<p>这个库虽然是用来驱动无刷电机的,但是他带了一个 3PWM Driver . 我只要用他这个。</p>
<p>起初我用 VSCODE 搭配 ExpressIDF SDK 写过 ESP32 的代码。
但是 SimpleFOC 基于 arduino 库,无法直接使用 ExpressIDF SDK。</p>
<p>后来我就折腾用 arduino 写。发现 arduino 的编辑器不是很顺手。代码补全,语法提示就是个 0 。</p>
<p>还是得用vscode写,结果翻阅 simplefoc 的文档发现可以用 platformio。
PlatformIO 是个 vscode 的插件。 装上它,就有了一个单片机的集成开发环境了。
编辑器还是用的 vscode,还能用 clangd 进行自动完成。</p>
<p>后来还发现 platformio 更高级的功能,就是可以多平台构建。可以同一份源码直接编译出 ESP32 和 STM32 的固件。
虽然soc各有不同的地方, 但是这些不同的地方可以用偶这个C++程序员最擅长的条件编译搞定。</p>
<p>至于 stm32 的板子,嘿嘿, 之前玩电机的是买过一个 VESC 的电机控制器。控制器上是个 STM32F405G ,而且,板子上 SWD 调试端口。接上 stlink, 在 platformio 里添加一个 stm32 的板子, 点 upload 直接成功!</p>
<p>然后可以随时在 stm32 和 esp32 里切换,随时 build , 随时 upload。</p>
<p>platformio真是开发单片机的神器。</p>
<p>于是我就捣鼓出了2个三相变频器,一个 esp32 的, 一个 stm32 的。
esp32 的可以用手机连上蓝牙后发命令设定输出电压和频率。
stm32 没蓝牙,只能接了一个电位器,用ADC采样电位器输出后设定电压和频率。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>异步电机开环调速让电压和频率同步提升。也就是固定 V/F 比。这个 V/F 比和电机有关。如果有转速传感器,可以使用固定滑差率设定频率,电压则根据电流和按设定的扭矩进行反馈调节。滑差率也是一个和电机有关的数值。大部分都是 1% 的滑差率。
</code></pre></div></div>
<p>然后淘了一个 三相的异步电机。接上。。。因为板子的耐压是 30v。只能接了30v的直流电给板子。按电机 220v/50hz 的规格(确实是三相220v,不是380v,找了好久才找到的。), 30v 的直流电只能逆变出 21v 的交流电,所以只能输出 21v/8hz 的交流电驱动。不过这个电机应该是随便绕的,频率提高到 20hz 还能转的更快点。</p>
<p>但是扭矩感人。毕竟是要用 220v 电压供电的电机,绕组电阻太大。低压下电流太低了。相电流连 50mA 都没有。</p>
<p>可惜,更低电压的异步电机买不到了。低压的都是永磁同步电机。欸。/(ㄒoㄒ)/~~</p>
<p>希望能搞到和航母电机一样大小的,12V 电压驱动的三相感应电机。参数最好是 12v/100hz ,7000rpm。</p>
宗门设定
2022-10-20T00:00:00+00:00
https://microcai.org/2022/10/20/zongmeng
<p>所有的修仙小说里, 都有宗门的设定.</p>
<p>修仙有两种途径, 或散修, 或投靠宗门.</p>
<p>投靠宗门修仙, 宗门提供修炼资源.
投靠的方式, 主要就是给宗门当弟子.</p>
<p>弟子这个设定, 大家会想到学校. 其实弟子不是学生.
学生到学校学习, 是要交学费的.
而弟子拜入宗门, 并不需要交学费. 不仅不交学费, 宗门还要花资源大力培养弟子.
弟子一般都是在宗门管辖区域内, 通过各种办法从凡人里招来. 所以说凡人是修士的根.</p>
<p>招了弟子, 就得管吃管喝, 还得提供丹药之类的修炼资源, 宗门看似是在做亏本生意.
其实, 弟子是士兵. 而且是廉价炮灰. 因为支付的军饷只是可以廉价印刷的功法秘籍.
而且没有阵亡抚恤金.</p>
<p>招收弟子, 就是招兵.</p>
<p>有兵就要打, 因此宗门之间打来打去.</p>
<p>有兵就要管理. 于是会分为 杂役弟子, 外门弟子, 内门弟子, 真传弟子.
管理层, 则分为 长老, 宗主.
养老的则是 太上长老, 和老祖.
杂役弟子是干杂活的, 不需要有修炼资质. 宗门只要花少量的代价, 其实就是提供食宿就嫖到的廉价劳动力.
因为穷苦百姓很多吃饭都有困难活不下去.</p>
<p>外门弟子就需要有修炼资质了. 宗门要花的代价, 就是提供稍微高级点的食宿条件, 外加批量印刷的低级功法.
就能嫖到廉价的炮灰了.</p>
<p>外门弟子是宗门之间战争的主力炮灰. 经常死掉一大片. 哦不对, 在修仙世界不能叫死, 得叫陨落.
外门弟子是没有特定的师父的. 宗门有专门给外门弟子讲课的人, 叫传法长老.</p>
<p>内门弟子, 是接班人预备队. 内门弟子就有特定的师父了. 其中优秀的会成为下一代的长老们.</p>
<p>真传弟子就是宗主亲收的弟子了. 这些可不再是炮灰了, 而是重点保护的对象了.</p>
<p>宗门的下一代领导就在这些人里产生了.</p>
<p>宗主的大弟子, 一般会接手宗主的位置, 其他的弟子, 就会变成各个长老. 掌握宗门各个要害位置.</p>
<p>宗门换届, 有时候是宗主陨落, 而多数是宗主觉得弟子已经成长了, 自己就会安心退休当太上长老. 因为退出宗门事务, 才可以有更多的时间修炼.</p>
<p>一朝天子一朝臣, 在宗门里也是一样的. 长老都是宗主的师弟. 而原来的长老, 就成了宗主的师叔. 这部分人就会退休, 成为太上长老.
太上长老退休了, 就会安心于修炼, 不再处理宗门事务.
而普通的长老, 则可以把位置传给自己的弟子, 不会和核心领导层同步换届.</p>
<p>老祖就是宗门里, 修炼最厉害, 寿命最长, 熬过了很多代领导层后, 还在继续修炼的人.
老祖其实是宗门的核武器. 因为厉害的人, 都飞升了.
不厉害的, 都陨落了.
而卡在飞升境界, 就是飞不上去的人, 武力值到达人间天花板, 成为宗门老祖. 就成为宗门对外战争中的最后倚仗.</p>
<p>老祖不插手事务, 一心闭关, 只求飞升. 但是, 宗门遇到身死存亡的地步, 老祖也只能出关救命了. 所以老祖是核武器的存在.
一旦老祖突破成功, 则宗门其实会非常危险.</p>
<p>因此老祖的数量也非常重要. 不断的有老祖突破, 也不断的有新的太上长老踏入飞升境成为新的老祖. 这样宗门才安全.</p>
<p>有的大宗门, 宗主之位不是直接传给宗主的大弟子. 而是会在内门弟子里选拔一个重点培养的人. 这个重点培养的人, 就是圣子, 女的就叫圣女了.</p>
<p>修仙的宗门很少世袭, 设定上是修仙的人大多数得了不孕不育症. 很难产生后代. 所以只能不断的从凡间搜刮体质优秀的人.
而且即便是世袭, 也是把孩子收为弟子. 然后传位给弟子. 绝对不破坏规矩搞父传子(笑).</p>
<p>长老除了从内门弟子里提拔, 其实还可以从其他宗门挖, 或者是请一些牛逼的散修担任.
这部分并非从宗门内部产生的长老, 一般叫客卿长老.</p>
<p>了解了身份设定后, 很快就可以把宗门和军阀给对应起来.</p>
<p>杂役弟子就是负责后勤的. 给军队运粮草, 烧火做饭. 搭帐篷之类的工作.
外门弟子就是大头兵.
内门弟子就是士官了.
长老就是将军.</p>
<p>宗门的实力, 就是看能养活多少弟子. 有数量庞大的弟子, 就会慢慢的产生大量的长老.
有大量的长老, 才能有机会养出老祖牌原子弹.</p>
<p>要养活多弟子, 就得多占资源.
修仙界的资源, 一是地盘, 而是矿.
地盘多了, 才有机会在庞大的凡人里筛出合格的人当弟子.
有了矿, 才能给弟子提供修炼资源.</p>
<p>因此宗门也是需要经营的. 所以宗主通常是由最会经营的人担任, 而不是最牛逼的人担任.
既然是经营, 就得列收入和支出.</p>
<p>一个军阀的支出, 就是军费. 大头是武器, 其次是买命钱. 而军阀的收入, 则是占领的地盘后收税.</p>
<p>宗门的支出, 主要就是灵石. 修仙的人一般辟谷, 无需吃饭. 不过杂役弟子和外门弟子通常境界比较低, 还是得吃饭.
吃饭的开销在修仙界算是毛毛雨.
吃一顿饭花不了几个铜钱, 而一颗丹药可以抵一个城池. 所以吃饭的开销忽略不计了.</p>
<p>丹药要么自己炼, 要么花钱买. 因此主修炼丹的宗门在修仙界都很富裕.</p>
<p>而宗门的收入, 其中吃饭这点毛毛雨的开支, 一般是靠辖区凡人的保护费. 因为是宗门所以不能叫税.
而大头开支, 最主要是靠挖矿.</p>
<p>修仙界有两种矿, 一种叫灵石, 一种叫灵药.
灵药需要种植在灵气充裕的地方. 有些还不能人工种植, 只能去危险的地方采摘.
而灵石也是在灵脉里挖到的.</p>
<p>这都需要宗门有地盘. 这也是宗门战争的由来. 要争夺修炼资源.</p>
<p>宗门给弟子的修炼资源, 其实就是工资, 不过他们那边叫供奉.
宗门里有很多活修仙的人是不干的, 这部分活, 有些外包给山下的凡人做. 而有些则是让杂役弟子干.
杂役弟子几乎没有供奉, 就是给口饭吃. 杂役弟子的存在, 是因为仙凡有别, 修仙的人想尽量少和凡人打交道.
因此像盖房子, 装修这种可以外包给山下的人做, 等他们做完了会离去.
但是挑水洗衣做饭这样的活, 总不能让山下的人一直住在宗门里干. 于是就整了一个杂役弟子的由头, 招收一些没有修炼体质的凡人进去.
给他们传一些凡人的修炼功法, 让他们这辈子留在山上干活. 也不算破坏了仙凡有别的规矩.
外门弟子每月都可以领钱. 这部分是固定的. 但是一般少的可怜. 一般是不够修炼的. 所以就会被忽悠去干一些危险的时期.
这个制度, 叫贡献点制度. 就是可以去长老那领取任务. 完成后奖励贡献点. 拿贡献点兑换修炼资源.
这个制度就好像现在工厂里, 给工人开最低工资, 然后加班费给多点. 诱惑工人自愿996.</p>
<p>因此外门弟子是宗门剩余价值产生器.</p>
<p>内门弟子因为供奉充足, 很少需要做宗门任务. 基本工资高了, 就可以不加班了吗?
师父会拿着 “历练” 的大饼, 让这些弟子下山干脏活. 比如带着外门弟子去攻打小宗门.</p>
<p>弟子的陨落率其实是非常高的.
所谓一将功成万骨枯.
终于熬到师父退休, 接手了师父的活, 成了长老.
当了长老, 供奉高了, 但是, 开销也大了.
主要是随着修为的提升, 需要的丹药更贵了.
而且工资还和自己收徒的数量有关.</p>
<p>逼着自己不能一门心思修炼, 还得带徒弟.
毕竟没有徒弟, 怎么完成任务. 比如讨伐魔道.
这都是长老要干的活.</p>
<p>丹药阁的长老虽然不用出去打打杀杀, 但是每天都要炼丹完成 KPI.
有些特殊药材还得自己亲自去搞.</p>
<p>即使是混成宗主, 要操心的事情就更多了.</p>
<p>能安心修炼, 不受打扰, 就只能熬成太上长老.</p>
<p>但是, 如果自己不把宗门发展壮大就提前退休当太上长老, 则退休的日子没享受, 就被自己徒弟坑了. 因为徒弟搞不定只能请太上长老出马.
万一资源没竞争过, 宗门覆灭, 退休工资都没有了. 命都要搭进去,</p>
<p>所以修仙修仙, 是非常卷的.</p>
科学与修仙
2022-09-19T00:00:00+00:00
https://microcai.org/2022/09/19/xiuxian
<p>修仙其实和量子力学密不可分, 是有科学依据的.</p>
<p>灵气: 灵气其实就是富含氘的空气.
灵水: 灵水就是氘的水.</p>
<p>灵根: 为何无灵根无法修炼? 因为灵根其实就是一种特殊的线粒体. 普通的线粒体, 是燃烧葡萄糖, 参与碳循环. 而灵根, 是核动力线粒体. 只要吸收灵气就能给细胞供能.</p>
<p>辟谷: 拥有灵根的人, 修炼到一定境界后, 就可以只依靠灵气存活了. 说明身体的核动力功能完全激活了.</p>
<p>无垢体: 特殊体质, 其实是指身体没有传统线粒体. 不参与碳循环, 自然不会产生二氧化碳, 不需要进食. 不进食就不会产生排泄需求, 也就是无垢. 完全依靠核动力存活. 是修炼的无上体质.</p>
<p>法力: 其实就是超能力. 而超能力其实是宏观上的量子叠加态定向坍塌. 也就是俗称的因果律武器. 最次等的因果律武器, 只能干涉单个的量子的坍塌, 在科学界, 只有三体人掌握了这个技术.
而高级的因果律武器, 可以对大量的粒子整齐的干涉到指定塌陷状态, 于是得到宏观上的”超能力”.</p>
<p>以身合道: 对整个宇宙只能施展一次的法则, 在科学侧的含义就是确定宇宙常数. 在鸿钧以身和道前, 宇宙常数是处于量子叠加态, 是随机的. 所以说先有鸿钧后有天.</p>
<p>修为等级: 分法力等级和法术等级. 法力等级, 指修仙者能调动的能量的大小. 由身体的等级决定. 法术等级, 是对宏观世界的干涩能力, 由施展的术法决定.
高等级的功法修炼难度也高. 是因为法术其实是一种因果律武器, 需要调动大量的神识去微操, 干涉大量的量子波坍塌才能实现. 因此修仙者需要修炼灵魂, 也就是让自己成为超级观察者, 而具体的干涉又需要能量, 因此需要
锻炼肉身, 提高线粒体的功率.</p>
<p>到圣人这个等级, 神识已经可以对全宇宙微操, 自身可以调动整个宇宙的能量了. 所以圣人之下皆为蝼蚁.</p>
<p>为何上古时代妖兽横行? 因为物种演化就是要先兽后人. 后来经历了物种大灭绝, 妖兽绝迹. 少数修练到大罗金仙境界的, 可以有足够的寿命活到人的时代.</p>
<p>修真小说里, 为何全世界都在说中国话? 因为修真是未来时代, 而不是古代. 修仙的人动不动就活了几百万年. 人类诞生都才几十万年呢.
在未来的某个时间, 地球经历了一场灵气复苏. 又或者未来某个时代, 星际殖民者到了某个富含灵气的星球. 于是开启了人族的修仙时代.
因此女娲可能是未来某个星际移民飞船的头. 道家的那些古籍, 是预言书.</p>
<p>因此穿越去修真界的人, 随便说点地球的东西都很厉害, 因为对他们来说其实是上古时代的传承.</p>
<p>传说中的大鹏, 可能就是指古代的飞机. 或者对我们来说是未来的飞机. 是能穿越星际飞行的那种.</p>
<p>这也侧面说明, 只有中国人实现了星际航行, 并且突破了量子理论, 发展出了修仙理论. 大规模移民后, 在一个叫洪荒的星系建立了新的文明.
女娲造人, 说的是星际移民后, 把飞船里携带的人种在殖民点培育出来. 这也解释了为何女娲造人后才有人, 却又有很多女娲造人前就存在的修仙者.
看似矛盾其实不矛盾. 因为女娲只是把飞船里冷冻的受精卵在当地培育长大. 所以修仙的人早就存在了.</p>
<p>传送阵: 其实就是虫洞. 每次使用都要消耗大量的灵石. 因为虫洞非常的消耗能源.</p>
<p>女娲补天: 移民飞船落地后, 先造了玻璃大棚把整个人类城市罩起来. 因此最初的落点是天圆地方的. 后来出了事故导致了破碎, 女娲就去补洞. 拯救了第一批移民. 演化成了补天传说.</p>
<p>女娲更有可能应该是移民船里的育儿机器人. 为了方便并没有腿, 而是拖着长长的各种线管. 所以有了蛇身的形象.</p>
准葛尔
2022-07-16T00:00:00+00:00
https://microcai.org/2022/07/16/zge
<p>走新藏线前, 在叶城待了几天. 在叶城待的时候, 就很好奇叶城名字的由来.</p>
<p>叶城, 得名的原因是叶尔羌汗国的都城. 叶尔羌汗国, 既然是叫汗国, 那肯定是蒙古人的国家了. 可是我在叶城, 发现居民大多数是维吾尔人.
维吾尔人可不搞汗国, 即便要搞也是哈里发.</p>
<p>叶尔羌汗国的灭亡时间也很早, 在明朝中期就灭亡了. 灭亡原因不详.</p>
<p>新疆还有蒙古自治州.</p>
<p>给我一种感觉, 新疆原先是蒙古人的天下, 后来被维吾尔人鸠占鹊巢.</p>
<p>回到内地后, 再去研究了新疆未回归中国前的历史, 才明白过来一些事情.</p>
<p>满蒙联盟果然是忽悠人的. 满人的民族政策就是消灭蒙古人.</p>
<p>准葛尔, 是继匈奴之后, 对中原威胁最大的游牧民族. 终明一朝, 始终无法解决北方边患. 土木堡之变还成了大明由盛转衰的转折点.</p>
<p>清灭明后, 准葛尔就成了清的心头之患.</p>
<p>清灭蒙古, 采取的是拉一波打一波的方式.</p>
<p>首先拉拢的, 是内蒙人. 这个是满蒙联盟的基础.
后来加入的, 是喀尔喀蒙古, 也就是外蒙.</p>
<p>这个外蒙啊, 是准葛尔送给清的.</p>
<p>准葛尔征服喀尔喀的过程, 把喀尔喀推向了清.</p>
<p>由于北元投降了清, 所以清获得了整个蒙古部落的法统. 但是, 这个法统属于地图开疆. 服清的, 只有黄金家族所在的内蒙.
剩下的, 要么后续拉拢, 要么消灭.</p>
<p>而准葛尔, 就属于要消灭的那部分.</p>
<p>清和准葛尔, 明和准葛尔, 都打了上百年的战争.</p>
<p>最终, 游牧民族耗不过农耕民族, 失败了.</p>
<p>准葛尔也终于从汗国变成了盆地.</p>
<p>但是清的民族政策, 后患无穷. 清把西域变成了无人区. 直接屠灭了准葛尔.
还不移民实边, 怕汉人占了新疆又搞事.</p>
<p>结果, 这地方水草丰美, 你不让内地人来, 别人就不来了吗?</p>
<p>于是中东那边受沙俄迫害的人就润到新疆来了. 形成了新的民族, 维吾尔. 所以维吾尔的历史距今也不过两百年. 他们是客人, 不是主人.
过了百年, 那些润到新疆的人, 就忘记了祖先被沙俄压迫的历史, 后人开始和沙俄勾结到一起, 他们想着独立, 沙俄想着吞并.</p>
<p>好在左宗棠及时出手. 新疆二次收复.</p>
<p>安分了百年后, 这群人又开始被老美忽悠.</p>
什么是汉人
2022-06-04T00:00:00+00:00
https://microcai.org/2022/06/04/what-is-han
<p>汉人这个称呼古已有之. 但是, 内涵发生了巨大的变化, 然后被歪曲. 现在的含义对华夏人伤害非常大.</p>
<p>汉人的最初意思, 就是汉国人. 这个也是秦人, 唐人, 宋人的含义. 就是这个国家的国民.</p>
<p>到元代的时候, 因为蒙古人的种姓制度, 元人无法称元人了. 也就是, 元人这个称呼居然不存在了.
为了他自己的统治长治久安, 他就很随意的把人群进行划分, 分了 蒙古人, 色目人, 汉人, 南人.</p>
<p>注意, 此时的汉人, 其实指先于南宋被统治的金人.</p>
<p>后来朱元璋北伐灭元. 建立明朝后, 按习惯, 明朝人应该自称明人.</p>
<p>但是, 朱元璋却搞起了汉人称呼. 于是 明朝人不自称明人, 而是自称汉人.</p>
<p>于是这个汉人称呼才定了下来, 延续到现在. 所以现在的汉族, 其实就是原来明的国民的意思.</p>
<p>这并不意味着, 没被明朝统治的东北人, 就不是汉人了. 他们只是不是明人, 于是不自称汉人.</p>
<p>东北人入关后, 为了维持自己的阶级, 把阶级矛盾转成民族矛盾. 于是没有按以往的惯例, 把自己的国民称清人.
而是划分了5个民族. 满蒙汉回藏. 还把人口最多的人踩脚底下.</p>
<p>而汉人, 就是指原来的明人.</p>
<p>此时的汉人, 其实并不是西方意义上的民族的称呼. 而是东方语境下的民族.</p>
<p>直到民国, 我们学习了西方的技术, 同时也把西方的民族观舶来了. 于是把清朝时划分的汉人, 给整出了一个 汉族人的概念.</p>
<p>孙中山还没发现自己其实被西方文化坑了, 其实, 如果他要反清复汉, 其实就是只能恢复到明朝的版图. 因为汉人其实就是按明人划分的.
于是他匆匆改口, 搞了五族共和.</p>
<p>但是民国很动荡, 来不及搞什么真正的五族共和, 始终在内乱, 还被日本侵略了.</p>
<p>时间很快到了新中国. 新中国学习苏联的经验, 自然也把苏联的糟糠给舶来了. 搞民族细分, 硬生生的搞了56个民族出来. 俄罗斯现在幡然醒悟了, 取消了民族识别.</p>
<p>于是汉族这个非常伤害中国的概念就固化下来了.</p>
<p>就是因为这个, 导致了元朝非中国, 清朝非中国的言论甚嚣尘上.</p>
<p>而且导致国内不同民族的对立.</p>
<p>但是其实, 所谓的汉人, 其实就是明朝人. 于是就把同为华夏人的东北人给排挤出去了. 至于现在东北人汉人多, 就说是因为汉人闯关东.
于是就把闯关东前的东北人开除华夏籍.</p>
<p>这是非常非常有害的.</p>
<p>汉人=明人的解释一旦建立, 汉=中国的语境下, 元当然就不是中国了. 清也不是明人建立的, 于是清也非中国了.</p>
<p>所以,其实现中国, 应该停止民族识别, 停止说明什么是汉人, 就把汉人和华人, 华夏民族一样, 不提供精确定义.
开始构建新的国家认同.</p>
不恰饭的车评人危害性更大
2022-06-03T00:00:00+00:00
https://microcai.org/2022/06/03/harmfull-chepingren
<p>端午节前,汽车界最大的瓜,就是叶问大战38号。</p>
<p>首先介绍一下事情的来龙去脉</p>
<p><em>事情的起因是教主(本名鲁超,自封键盘车神教的教主,以下简称教主)测试领克的时候,被奇瑞的粉丝要求也测一下新发布的凌云S,因为奇瑞宣传这个是赛道级的车。
教主他走的是车评中的细分行业,就是只评家用赛车。在这个细分领域有大量的粉丝。一般的车他还不测,只测那种能下赛道的贵族车。但是他居然测了平民车领克,领克是家用车啊。所以粉丝要求他也测奇瑞的车,并不过分。</em></p>
<p><em>于是他就按他的理解,把奇瑞当成那种贵族的赛车,在赛道上霍霍了一下,果然纯家用的属性就暴露了。</em></p>
<p><em>到这个时候还好,也就是一个小插曲,一个只玩贵族车的人突然一反常态的测了一下屌丝车,然后得出屌丝车下不了赛道的结论。然后成为他粉丝的笑谈。什么垃圾奇瑞也想下赛道之类的云云,在粉丝里充满了快活的空气。节目效果也有了。</em></p>
<p><em>但是,奇瑞的粉丝炸锅了,不服气,他们买的车被教主鄙视,能服气吗?顺带着另一个给奇瑞站台的车评人郑刚也批评教主,形成了一方锤教主的势力。教主气不过啊,被人一挑拨,一怒之下自己买一辆。被人喷,就得怼回去。</em></p>
<p><em>结果不重测还好,一重测问题更大了。这成绩比原来借来的车更差了。教主公然放出量产车只有7.6s的成绩,奇瑞虚假宣传的话。不服来北京测,能跑6.2s车送给你。</em></p>
<p><em>这种小孩子般约架的话,能有个俅用。于是被郑刚继续追着撕咬。眼看自己招架不住,他想到了自己的好朋友,38号。</em></p>
<p><em>这下事情就进入了无可挽回的地步了。</em></p>
<p><em>教主做的是赛车这种细分市场,而38做的,是几乎所有的车的市场。38下场为教主站台,等于乌克兰冲突后美国直接下场。</em></p>
<p><em>都不是一个级别的对线了。这时候,叶问(本名朱殿举,网名叶问讲高考志愿,简称叶问)来锤38号了。叶问这个人,他很早就说,车评人屁股歪,不为国产车叫好,只给合资车站台,他要捶死这个车评行业。当年出道就先捶死了 yyp,yyp 以发一期夸奖 汉DMI 的视频投降。这次 38号 居然想奇瑞死,叶问能不出手?于是叶问下场。战争升级。</em></p>
<p><em>叶问打车评人第一步,首先质疑7.6s成绩是故意抹黑。他自己找公司的一个女员工测了一下,是7.2s。明明能跑的比7.6s更快,却故意测出来 7.6s,是何居心?然后抖出大瓜,说38之前黑凡尔赛的时候敲诈了神龙公司200万。</em></p>
<p><em>教主这边则是以他白天测7.6s,晚上测能到7.3s,但是无论如何测不到 7s 来回应。并指出奇瑞自己的宣传视频里,6.0s 的成绩是P图造假。然后他的粉丝就进入了嘲讽陶腾飞造假的潮吹,充满了快活的气息。(陶鹏飞就是帮奇瑞跑出6.0s成绩的车手)并纷纷心疼教主被陶鹏飞用时间系魔法骗走了18万。其中也不乏帮领克跑 WTCR 的车手,也开启了嘲讽陶鹏飞的盛宴。</em></p>
<p><em>公然造假,奇瑞这下是要吃官司了。教主作为车主起诉是必胜啊,退一赔三,买车钱是肯定有着落了。所以其实粉丝是不用心疼的。</em></p>
<p><em>而 38 的回应则是解释了自己为何只能测到7.6s, 因为成绩受各种因素影响,并且辩解自己并没有说奇瑞只能跑7.6s。然后还辩解自己没有只夸合资车,国产车也夸了。然后就200万敲诈回应,你没证据就是造谣。我悬赏400万求证据。</em></p>
<p><em>此时教主还来嘲讽叶问智商,并表示200w敲诈这种黑料,就是他卧底叶问的群提供的。于是他的粉丝又进入了嘲讽叶问相信敌人的假冒黑料智商垃圾的群体高潮。</em></p>
<p><em>由于 38 的粉丝笃信叶问被教主耍了,于是纷纷跑去在叶问的视频里留言喷他。
一股胜券在握的胜利者姿态。</em></p>
<p><em>于是叶问微微的掉了一些粉。一些讨厌38但是又不好支持叶问的人,都开启了“中立” 模式。因为不能支持一个智商不在线的人,对吧?而且38的粉丝威胁站叶问就是支持奇瑞造假,罪无可赦。车评圈的其他大V们,则是全部下场,几乎全部站队38, 批判奇瑞造假. 像11磅小老虎直接说,谁不站38的都把我拉黑了吧。</em></p>
<p><em>好家伙,好家伙。一边是几乎全体车评人站队38,少数开启中立模式。另一边是叶问的视频下面被嘲讽和劝降的言论刷屏。而中立的人,日子也不好过,因为38的粉丝拿出奇瑞造假的证据,逼他们站38,必须站38. 颇有饭圈的味道。这波啊,38用不能支持企业造假的政治正确,绑架了几乎所有人一起集火叶问。</em></p>
<p><em>连讲叶问打拳影视作品的视频下面,都被要求站队38,你敢信?</em></p>
<p><em>叶问死定了。</em></p>
<p><em>这瓜越来越大了。在6月1号当天,叶问再次发视频。并给出了38号雇佣水军冲击他的视频评论区的证据。等等,合着之前他视频下一边倒的言论是水军?</em></p>
<p><em>紧接着马上发第二个视频,向38提出了灵魂质问:你是不是富二代?你恰饭你的钱哪里来的?有没有接受日本外务省的资助?怎么解释以前为日本侵华洗地的言论?</em></p>
<p><em>并且他发视频,等水军冲完后,将视频删了重发(等于清空评论)。这波啊,如果水军重发贴子,他可以继续删视频然后重发。看你雇佣水军的钱多,还是我重发的钱多。</em></p>
<p><em>果然几次重发后,水军数量大幅减少。于是他的视频下面终于可以看到真正的吃瓜群众的留言了。群众还是吃瓜,并不站38,也不站叶问,更没有人站奇瑞,只是吃瓜看戏。真正的群众怎么会因为奇瑞造假去冲叶问的视频么?吃瓜看戏才是天朝人民的秉性。只不过吃瓜群众还是被人追问,承不承认奇瑞造假?</em></p>
<p><em>38的粉丝炸锅了!他们炸锅并不是突然发现自己粉的对象有特务嫌疑,而是发现叶问居然不讲事实,不讲证据,居然直接上升到国家安全层面攻击38. 你不讲武德!你无耻,你卑鄙。你下流!你拿奇瑞跑不出6.0s你就是输了,你要退网!讨论奇瑞有没有造假的话题你不能岔开!</em></p>
<p><em>而原来站38的大人物突然沉默。事情明朗前还是先沉默。只有38的粉丝开始疯狂的输出叶问不讲武德,卑鄙下贱的言论。</em></p>
<p><em>此时一些中立的人,开始表达38其实非常双标,真的很怀疑是不是日本派来的。而且表示38根本不可能不恰饭。但是仍然表示不叶问以防水军冲击。38以前评车时非常双标的言论被扒了出来。而粉丝还在为38战队,连 即便38真的是特务,也不能否定奇瑞造假 这样的失智言论都开始疯狂输出。</em></p>
<p><em>球来到了38这边。</em></p>
<p><em>38 在人设问题上投降,承认自己不是富二代,钱来自懂车帝,没有收车企的钱。表示懂车帝给的钱比车企还多,看不上车企的钱。承认自己去过日本搞“学术交流”,但是是学术交流,不是参加汉奸培训班。然后再次要求叶问拿出证据,不然走着瞧!</em></p>
<p>截止到我写作时间(北京时间6月3好凌晨3点)叶问尚未回应。</p>
<p>好了。事情的来龙去脉搞清楚了,我来说说我的看法。</p>
<p>我认为,不恰饭但是立场偏向国外品牌的车评人,对国家危害更大。</p>
<p>首先,凡是媒体,必有立场。无立场的最好做法是不发表言论。凡是发表了言论还说自己的言论是理性客观中立的,那一定是诈骗。这个立场是说他内心的立场,不是说他发出来的东西的立场。
因为有立场,不表示他一定会说这个立场。因为很多时候人要说违心的话。“他给的太多了”</p>
<p>于是我把车评人分三种。</p>
<p>第一类,粉丝不多,完全看不出立场。你能看到的是,他的立场变来变去。有钱就是爷。
第二类,头部大V,粉丝众多。他的立场号称理中客,实际被看到的会有些许偏移,但不明显。偏移情况主要看金主的钱。但是给再多的钱也不能进一步解锁他的立场。不然沦为第一类了。承认恰饭,但是表示恰饭不影响他的立场。
第三类,就是38为代表,已经形成一个教派。完全拒绝充值。</p>
<p>第一类和第二类车评人,他们的立场是可以用金钱收买的。
无非就是收买车评人的成本,最终要消费者承担。</p>
<p>但是,不管是自主品牌还是合资品牌,他们其实面对的是相同的敲诈环境。
也就是说,虽然老百姓多花钱供养了车评人,但是自主和合资面对的勒索力度是一样的。
大家公平竞争。谁撒的狗粮多,谁赢。</p>
<p>输的是买车的人。</p>
<p>这也是 叶问的爆论 能迅速吸粉的原因,老百姓不想豢養吸血的车评人。这也是第三类车评人的生存之道。现在的趋势就是,不管车评人是第几类,他们都开始声称自己是第三类。</p>
<p>但是,第三类才是真正的,对国家危害更大。前两类我说过,是让老百姓多花钱豢養吸血的车评人,但是不管是自主品牌还是合资品牌,他们在这场上供竞赛里是公平竞争。</p>
<p>但是第三类车评人,才是真的让自主品牌永无翻身之力。
因为,他们不受车企的金钱影响。
但是,他们并不中立,也不客观。</p>
<p>“不管你给我多少钱,我就是要吹比亚迪和其他自主品牌,不会吹你丰田的车” 这是某UP的豪言。他大方的承认自己有立场,但是不收钱,就是要当自主品牌的自干五。</p>
<p>这个不是我今天要说的,因为这样的人,全国也找不出几个。</p>
<p>但是另一类人是更多的,他们从小接受的是英美文化的熏陶。成长环境里,国产车一直垃圾(起步阶段能不拉跨么?)于是已经带上了西方的车高级的思想烙印。</p>
<p>他不接受厂家充值,自喻为理中客,但是他的成长环境对他的影响是无法消除的。
于是他或多或少的,都会偏向于西方人的品牌。</p>
<p>比如键盘车神教教主,他的内心深处,就认为中国人根本造不出能下赛道的车。谁敢宣传他就敢下场捶死。这是他心中的信仰。除非,除非你能拿出超级无敌牛逼的车,把他彻底征服。在征服他之前,就别想得到公平的待遇。</p>
<p>好在,赛车从不是车企的主业,可以慢慢发力,不着急。</p>
<p>又比如 38 号,他内心深处就喜欢美系性能车,回国后,他自己又发现了日本车的闪光点。
他会迷恋上日系车。</p>
<p>国产车要拿出十倍的产品力才能征服他。</p>
<p>因为这个车评人,和你家的亲戚是一样的,都会固执的认为某个品牌的车好。很难扭转观念。
区别是,他会把他固执的观念继续传给更多的人。</p>
<p>你说这怎么同场竞技?</p>
<p>所以,这些和你家亲戚一样固执的,不接受充值的,喜欢洋货的车评人,才是自主品牌的绊脚石。
而且完全无法用金钱收买。这些人日复一日的宣传“一车传三代” 的固执印象。</p>
<p>自主品牌如何竞争?</p>
<p>好在叶问站了出来,虽然叶问认为38是第二类车评人,捶死他。
但是第三类是锤不死的。38本身也是打第二类起家的。
但是,被扒出来他是立场偏向日本的。</p>
<p>如果立场偏向日本,你就是外务省培养的!
38不论是不是,都必须从此收敛,隐藏自己的倾向。否则他在目前的环境下就社死了。
而且杀鸡儆猴,于是市面上 “人走车还在”的神话慢慢的就无人传唱了。自主品牌才能获得公平的环境。</p>
民族? 屁!
2022-05-29T00:00:00+00:00
https://microcai.org/2022/05/29/mingzugepi
<p>由乌克兰和俄罗斯火併想到了一个故事.</p>
<p>我是微菜, 我生活在南方崇山峻岭里, 从小就喜欢看动画片.
有一年, 我造反了. 我找了一群志同道合的同乡, 打出了消灭电视剧的口号.
因为我们是动画人. 广电他总是不让我们好好的看片.</p>
<p>我们割据一方, 和中央对峙好多年. 为了加强我们自己人相互之间的认同, 我们发明了动画人概念, 创造了动画字, 以南方某方言为范本发明了动画语.
我们以后都说动画语, 写动画字, 是动画人. 谁要还写汉字, 就是叛徒, 是中央派来的奸细.</p>
<p>最终, 中央因为腐败倒台了, 我们乘机杀入北京, 建立了动画国.</p>
<p>为了维持我们的地位, 我们要求所有人都只能看动画片. 谁要看电视剧, 就杀.</p>
<p>而我们动画人, 从此可以不用干活, 天天窝家里看动画片了. 国家高级职位只能动画人担任.</p>
<p>但是, 我担心以后会出事情, 于是在各地建动画城, 不让普通动画人出城. 生怕某个动画人搞事情, 又要建国.</p>
<p>为了更好的治理国家, 我们动画人就推广了北京话. 因为大家都会说. 但是要求动画人必须得学动画语. 不能忘本啊.</p>
<p>就这么过去了百来年.</p>
<p>动画国最终还是因为腐败被推翻了. 动画人一夜之间消失了.</p>
<p>后面的几任领导, 居然连动画语都不会说, 不会写, 太不像话了!</p>
<p>后来, 新的国家建立了很多年后. 为了民族团结, 不要大家喊打喊杀了. 说少数民族加分加分. 新的国家还包括越南, 朝鲜.
其实这些国家是动画国好大喜功搞进来的. 当然其实也用了一些自古以来. 新国家自然要继承动画国的原有地盘, 不能把这些人分出去.</p>
<p>结果动画人一夜之间又多起来了. 很多人纷纷表示他以前其实是动画人. 虽然这些人并不会说动画语.</p>
<p>突然有一天, 越南人说, 几百年前我们越南人和老共国互动频繁, 你们讲的都是动画语. 真正的北京话在我们越南. 我们都是两山轮战的解放军人后代.</p>
<p>朝鲜人跑出来说, 不对不对, 在我们朝鲜. 当年毛太祖还征发了百万志愿军入朝. 我们都是志愿军的后人. 我们说的才是正宗北京话.</p>
<p>而老动画国的国民, 也不知道自己说的北京话, 到底是动画人口味的北京话, 还是老北京话了. 虽然动画国还没灭亡的时候, 普通的动画人都不说动画语了, 只有国家领导人政治需要, 非要学一些. 而最后的亡国领导其实自己也是口北京话了.</p>
普通话就是古汉语活化石
2022-05-10T00:00:00+00:00
https://microcai.org/2022/05/10/yayan
<p>我是南方人, 从小在学校学习普通话. 平时大家基本上都以普通话交流. 因为温州这个地方, 连跨个村都能换一个方言.
根本就没有能主宰当地的强势方言, 如果有, 那只能是普通话.</p>
<p>后来因为家庭缘故, 中学时代远去了河南度过.</p>
<p>河南这个地方, 大家都说河南话, 不说普通话.
只不过, 河南话和普通话, 并没有太大差异. 语音语调略有差异, 类似唱歌跑调.
家中也常来山东大汉做客. 操着山东话和父亲谈生意.</p>
<p>山东话和河南话也是非常接近. 不熟之人也不太能分别.</p>
<p>小学时候, 以为的方言就是各种鸟语. 大家鸟语不通, 于是用通用的普通话.
去到北方, 才真切的感受到, 只是南方的方言是鸟语.
北方的方言, 大体还是同一种语言略有差异.</p>
<p>不过, 也正因为如此, 北方人对普通话就不重视. 这点就让我非常讨厌北方人. 就因为天生母语接近普通话, 就直接不学普通话了事.
难怪经济不发达, 人太懒了.</p>
<p>到读大学, 又回南方跑去杭州. 又变成了方言大杂烩, 各自听不懂的状态. 于是大家又说普通话了.</p>
<p>还是普通话好, 消灭方言, 人才能顺利沟通, 消除隔阂.</p>
<p>但是, 人进了大学, 就容易变成脑残. 自以为高人一等, 学的都是先进知识. 把老人都批判一番. 我进了大学也不能免俗. 我在大学里学到的最重要的知识, 就是64事件.
所谓人一独, 脑就残. 总觉得墙外的信息才是真实信息. 对洋人和殖畜的话是奉为圭臬. 于是对自己的语言都开始了怀疑.</p>
<p>于是, 那时候我相信, 北方话是胡人的话, 真正的汉语已经消亡了. 要算最接近老祖宗的汉语的, 就属粤语了.</p>
<p>其实我已经中了陷阱而不自知.</p>
<p>数年前, 老爹带我去参加了宗祠聚餐. 我讨厌酒文化, 所以以往我从未去过.
那一年我第一次去, 老爸人肉导航指挥我开车数小时到了一处偏僻的山脚下一座小村子.
老爹说, 我们这一系的宗祠就在这里了.</p>
<p>那年可能是比较重要的聚会吧, 家族里的人都来了. 都是没见过的人居然有几百号人. 还和我同姓, 哦不, 往上数5辈都是同一个祖宗的.
大家聚到一起, 当然是要说普通话了.</p>
<p>但是, 吃酒的时候, 还是分开了, 亲戚关系比较近的坐一起.</p>
<p>我们都是同一个祖宗的后代. 但是聚到一起的时候, 我才发现, 大家各自都说着不同的方言.</p>
<p>这是我第一次真真切切的感受到, 背井离乡就会忘记乡音.</p>
<p>我突然开始怀疑, 秦始皇派去南征岭南的人, 真的就变成了粤语的祖先吗?</p>
<p>族谱清清楚楚的显示, 我们家族是在宋朝的时候从河南衣冠南渡去了南方, 北宋的河南人说温州话吗?</p>
<p>自打那次聚会, 让我真正切切的见识到了和我同宗同源的人, 说着和我完全不同的方言.
原来语言这种东西, 只能入乡随俗.</p>
<p>北方汉人逃难去了南方, 就只能学会南方的百越语.
胡人就算来了北方, 也只能学会北方的汉语.</p>
<p>在没有发明磁带的古代, 胡人就算统治了北方, 也根本无法普及胡人的话.</p>
<p>对了, 德国的犹太人说德语. 乌克兰的犹太人泽连斯基还说乌克兰语呢.
美国的犹太人说美语.</p>
<p>犹太人都无法免俗, 漂到哪里就学那里的语言.</p>
<p>所以, 南方方言更像古汉语这种事情根本就是无稽之谈. 其根本目的, 就是为了分化瓦解中华民族.
阻碍 在近代终于有了技术手段可以推行的延迟了两千多年的语同音.</p>
<p>中国的方言, 为啥要分成北方方言, 南方方言?
北方方言能互通, 南方方言可不互通.</p>
<p>能互通的北方方言, 所占面积和人口, 都是中国的主要部分.
如果再仔细细纠, 发现, 北方方言区, 不正是周朝的疆域?</p>
<p>不互通的南方方言区, 不外乎 粤闽浙.</p>
<p>这三个地方, 虽然在汉代已经纳入版图, 但是处于偏远之地. 而且基本上不被中央直接统治, 而是羁縻统治.
羁縻统治, 不就是当地人不会雅言统治成本高么.</p>
<p>所以, 今天的北方方言, 就是周朝定的雅言. 语言虽然有变化, 但是绝对不会是翻天覆地的变化.</p>
<p>周朝还专门有一种从事翻译的官职,名曰“象胥”,每隔几年各诸侯国的象胥都要进京学习,然后回去教乡民官方语言。
所以西周时代, 雅言就开始逐步普及全国了. 只是没有留声机这样的工具, 所以大家有差异. 但是还没到听不懂的地步.</p>
<p>而且, 为何四川也属于北方方言区? 明明四川是南方. 因为四川是秦国的地盘. 秦国发家的地方, 就是西周的地盘.
西周灭亡后, 东迁, 老地盘被西戎霸占. 于是给老秦人开空头支票, 把老西周的地封给了秦人.
老秦人灭了西戎抢回了地盘, 再南下灭了巴蜀占了四川. 这才有了统一中国的资本.</p>
<p>而春秋时代, 除了楚国大家都说雅言, 也就是北方方言. 不然孔子门生那么多, 如何交流?
孔子可不只收山东人.</p>
<p>楚国人自称蛮夷. 其实楚国的封地也是周地, 楚王也讲雅言. 但是楚国向南扩张后, 拿下了如今大半个中国的土地.
奈何新统治的土地讲的都是和北方不通的话, 于是自嘲自己是蛮夷.
这就好像嘲讽二战投降的法国是非洲人一样.</p>
<p>非要说现行的普通话被谁影响, 最大的影响应该来自吴语, 而不是胡语.</p>
<p>首先就是楚国, 楚国扩张到江南后, 吴语就开始影响楚国了.</p>
<p>然后就是南北朝时期, 南方是南朝的中心了, 南朝人不可避免受吴语区方言的影响.
到唐朝重新统一天下, 就要重新确立普通话了. 唐代标准普通话«切韵»就被北方人说不标准了, 说是被吴语带偏了.
但是南北分离了几百年, 这时候不照顾下南方人, 能行吗?</p>
<p>到宋朝编的 «广韵» 更是被北方人说是把南方话列为正统.</p>
<p>到明朝的时候, 哪怕朱元璋还是基本按北方话编的 «洪武正韵» 都被指责南方话太多.</p>
<p>其实 不管是 切韵, 广韵 还是 洪武正韵, 都是以北方话为基础的. 但是确实不可否认会有南方音的影响.</p>
<p>而这些被批判而吸收了较多的南方话的 «洪武正韵» 构成了明清官话, 其实就就是目前普通话的基础.</p>
<p>普通话本身就是南北融合的产物, 但主要还是北方话为基础. 也就是说, 还是以周时的雅言为基础.</p>
<p>非要说的话, 普通话可能是雅言和吴语融合的产物.</p>
<p>PS, 这个 吴语 不是上海话, 是指南京话和杭州话.</p>
<p>五胡乱华为啥我认为并没有改变北方的语言, 其实主要是因为 五胡乱华而不是五胡屠华. 殖民者如果不是采取腾龙换鸟的做法, 是不可能消灭原住民的语言的.</p>
<p>英国统治印度三百年, 都没能让印度人改说英语. 倒是去美洲成功了, 那也是因为他们去美洲的时候杀光了原住民.
法国人统治越南一百年也没能改变越南的语言.
日本统治朝鲜50年, 采取的是非常激进的逼朝鲜人说日语的方法, 也没能消灭朝鲜话.</p>
<p>只有杀光原住民的做法才能改变语言, 比如澳洲, 比如美洲, 比如海参崴.</p>
<p>而杀光了原住民, 就没有复国的事情了. 五胡真杀光了北方人, 那北方就绝对不可能再回中国了.</p>
<p>所以, 我认为, 普通话不是胡化的汉语.
普通话, 是融合了部分吴语的雅言.
雅言是周代的普通话, 雅通夏, 是周人学夏人说话.
所以, 我们现在说的普通话, 就是 5000年前先祖在这片土地上说的话. 差异是会有, 但是差异不大, 善属于能互通的状态.</p>
<p>就算穿越回古代, 也无需当心语言不通.
那些说会语言不通的, 都是别有用心.</p>
FEC VPN
2022-04-29T00:00:00+00:00
https://microcai.org/2022/04/29/new-vpn
<p>给大家推荐一个好用的 VPN 软件. 可以在恶劣的环境中为其上承载的网络提供稳定的保障.</p>
<p>这个 VPN 的诞生缘由, 大概经历了3个时段.</p>
<p>第一个时段, 我折腾使用了 <a href="https://github.com/wangyu-/tinyfecVPN">tinyfecVPN</a>, 虽然用起来颇为不便, 但好在比 openvpn 要稳的多.
于是推荐给了隔壁老王.</p>
<p>老王发现这个 tinyfecVPN 代码质量略渣. 但是前先纠错的理念不错. 但是他目前用不到 vpn, 于是冇行动, 冇用起来.</p>
<p>过了一阵子, 老板又双叒叕脑洞瞎开, 要开发一个比 <a href="https://www.resilio.com/">btsync</a> 还要牛逼的同步软件放他的 NAS 产品上用.
他要把数据拆分成 N 份, 然后存储 M 份到不同的NAS上. 只要在线的NAS数量足够, 数据就可以完全恢复.</p>
<p>于是老王想到了 tinyfecVPN 的理念, 用上了 FEC 纠错的方法.</p>
<p>写了一个 FEC 分片工具. 把指定的文件先分片, 然后用 FEC 算法添加冗余纠错. 然后形成大量的冗余分片. 丢失一些分片还可以完整复原整个文件.</p>
<p>然后进入第三个时段. 老板要他的 NAS 尽可能的保持在线数量. 但是有一些西南边区的用户买过去, 他网络状况就不怎么好, 丢包率有那么一点点高.
并不是说边区的人民网络差, 他们访问互联网大厂的CDN并不慢,也不丢包, 而是这些NAS要 P2P, nas和nas之间p2p的时候, 丢包率有那么一点点高.</p>
<p>于是老王决定, 写一个基于 FEC 算法的 VPN, 然后老板的 nas 通过这个 VPN 组网, 就解决 p2p 丢包导致的网络问题了.
至于不直接用 tinyfecVPN, 那当然是没法用啊.</p>
<p>虽然核心的理念来自 tinyfecVPN, 但是具体的实现方法大不相同.</p>
<p>首先, FEC 只是一个理念, 具体怎么添加冗余, 是有不同的算法的. 隔壁老王用了和 tinyfecVPN 不一样的 FEC 算法.
其次, 老王的vpn, 是 TCP/UDP 并用. 尽可能的利用有限的机会传数据. 而 tinyfecVPN 只能使用 UDP.
最后, 老王的vpn, 用 c++23 写成, tinyfecVPN 是用古代人的渣 C 写成.</p>
<p>说完老王的vpn比tinyfecVPN先进的地方, 再说说比openvpn先进地方.</p>
<ul>
<li>openvpn 一丢包就挂. 老王的vpn有FEC冗余包对抗丢包</li>
<li>openvpn 配置繁琐. 老王的vpn配置简单</li>
<li>openvpn 要么tcp要么udp, 老王的vpn tcp/udp 双管齐下.</li>
<li>openvpn 在windows上需要装驱动. 老王的vpn 免驱.</li>
<li>openvpn 加密个寂寞. 老王的vpn用现代的加密</li>
<li>openvpn 渣C写成. 老王的vpn用现代的c++23</li>
</ul>
<p>那么, 这么先进的vpn, 要到哪里下载?</p>
<p>当然是没地方下咯. 老王的 vpn 还在开发中, 还没到 1.0 的发布阶段.
目前想试用的, 可以找老王直接要.</p>
<p>这里放出老王的联系方式 https://t.me/jackarain</p>
<p>有需要的直接联系.</p>
<p>当然, 如果不好意思直接找他, 也可以联系<a href="https://t.me/microcai">我</a></p>
异构容器
2022-04-26T00:00:00+00:00
https://microcai.org/2022/04/26/hybrid-container
<h1 id="路由器-引起的">路由器 引起的</h1>
<p>路由器,基本上都是MIPS处理器。
而 PC,那铁定是 AMD64 的处理器。</p>
<p>因此,绝无可能把 PC 上的程序直接拷到路由器里跑。</p>
<p>因为 glibc 特有的 ABI 问题,也不能直接交叉编译。</p>
<p>gentoo 的 crossdev 工具虽然能方便的生成交叉工具链,但是编译出来的程序,放到路由器上还是会报告 glibc 的符号版本问题。</p>
<p>因此,我用 qemu 运行了一个 mips 的虚拟机,然后在虚拟机里编译。虚拟机里安装和 路由器相同的 os - Debian 9 MIPS。</p>
各种各样的混动
2022-04-21T00:00:00+00:00
https://microcai.org/2022/04/21/hybrids
<h1 id="混动怎么个混法">混动怎么个混法?</h1>
<p>混动混动, 就是发动机和电动机一起干活.</p>
<p>一起干活, 配合的姿势就有很多种. 具体的搭配方法, 有2大方向: 机械分流, 电子分流.</p>
<h2 id="电子分流">电子分流</h2>
<p>先说电子分流. 因为这个概念上好理解. 就是一个电机发电, 另一个电机驱动. 发动机的动力全部输出给发电机, 发电机的出力, 分流一部分给电池. 所以叫电子分流.</p>
<p>电子分流利用的就是电池巨大的缓冲能力, 以及电子控制器迅速的调节能力.</p>
<p>整车实际上是实现了一个微电网. 负荷是驱动电机. 电池为一次调峰电厂, 发动机为二次调峰电厂.</p>
<p>驱动电机负荷变化后, 首先由电池承担一次调峰任务, 立即改变充电(or 放电)功率, 以平衡发电. 然后再缓慢调节发动机的功率.</p>
<p>那么, 电池越大, 调节能力越强, 就越能长期保持发动机运行在高效工况上.</p>
<p>电子分流要玩的转, 首先需要强大的电力电子技术. 要能玩的转微电网.</p>
<p>然后, 这个微电网, 其实还分 直流电网/交流电网 两种.</p>
<p>只不过, 目前市面上并不存在使用交流电网的混动车.</p>
<p>但实际上是可行的. 尤其是驱动电机使用三相交流异步电机的话, 是可以使用交流电网的.
那么电池作为一次调峰电厂, 就需要并网逆变器. 并且在车速变化的时候, 采取整体改变电网频率的方式变频.</p>
<p>交流电网适合驱动功率和发电功率相当的情况. 发电机的电力直接输出给驱动电机, 中间不经过整流逆变环节, 能大大提高效率. 但是如果驱动功率和发电功率差异较大, 则需要并网储能逆变器
多多工作, 这里的整流逆变将损失较多能量.</p>
<p>电子分流的缺点是发电再用电的环节存在效率损失. 但是只要这个损失小于发动机和车轮解耦后带来的效率提升,就是值得的.</p>
<p>但是, 在高速上, 传统油车的发动机也进入了高效工况, 于是这个中间的转换损失就会大于效率提升.</p>
<p>因此纯粹的电子分流, 在高速上效率可能会低于传统的油车. 具体低多少, 取决于电力电子技术的优秀程度.
当然这个比较是基于发动机的热效率差不多的情况. 实际上专门为混动开发的发动机最高热效率是会高于普通发动机的. 因此系统整体热效率在高速上可能依然是混动占优.
<em>比如理想one在高速上开120码油耗也低于发动机直驱的纳智捷</em>.</p>
<h2 id="机械分流">机械分流</h2>
<p>机械分流的核心是行星齿轮.</p>
<p>行星齿轮, 其实可以看成一个差速器. 有三个动力输入输出轴. 只不过这3个之间都不是 1:1:1 的关系. 他们之间的比例, 看齿轮的大小比值.
这三个轴的转速关系就是 <code class="language-plaintext highlighter-rouge">齿比1*轴1 + 齿比2*轴2 + 齿比3*轴3 = 0</code>, 和差速器是一模一样的工作. 差速器只是比较特殊, 三者其中的2个轴的比值是 1:1 的关系.</p>
<p>那么, 机械分流的混动, 就是 发动机, M1, M2 三个电机各连接在行星齿轮的一个轴上.</p>
<p>其中 M2 电机所在的行星齿轮轴承最终和前轴差速器相连, 也就是这个M2 电机可以看成和车轮相连.</p>
<p>M2 电机在纯电模式下, 直接驱动车轮.</p>
<p>因此可以看成 M2 电机转速和车速是耦合的.</p>
<p>混动模式下, 三者共同驱动车轮, 遵循 <code class="language-plaintext highlighter-rouge">齿比1*轴1 + 齿比2*轴2 + 齿比3*轴3 = 0</code> 公式, 一般会让发动机运行在固定转速上, 于是通过 M1 电机来平衡发动机的转速, 使得行星齿轮的输出转速和 M2 匹配.
也就是通过调节 M1 电机的转速实现发动机转速和车速解偶.</p>
<p>由于在机械分流模式下, 系统总功率是三者的叠加, 因此机械分流模式特别适合电力电子技术比较差的年代堆出勉强能用的功率.</p>
<p>在发动机功率和行驶需求功率相近的情况下, 通过 M1 M2 电机吸收/释放 的功率较小, 因此损耗较低.</p>
<h1 id="目前市面上的混动">目前市面上的混动</h1>
<p>尽管名称天花乱坠, 其实都逃不开这2个基础分流机制.</p>
<p>比亚迪, 长城, 本田, 走的是电子分流路线.
但是在高速巡航状态下, 都有发动机直接驱动车轮的另一条捷径.
而理想, 赛力斯和日产则舍弃这个捷径.</p>
<p>长城更进一步, 把直驱的离合器升成双离合, 于是等于有2个直驱档位. 其实在我看来是脱裤子放屁.
因为低速下直驱效率就是不行, 才要低速增程, 高速直驱. 把低速直驱给加回来了, 那和油车有什么区别?
要说直驱档位多就先进, 那老款的唐DM是不是更先进? 6个直驱档.</p>
<p>走机械分流模式的, 就是通用,丰田和奇瑞了.</p>
<p>其实这个机械分流模式最初是通用发明的, 但是丰田在普锐斯上将他发扬光大了.</p>
<p>机械分流模式其实有个缺陷, 这也是主流厂家抛弃机械分流的原因: 功率虽然能三擎叠加, 扭矩却不能.
这导致了机械分流模式的车, 一旦发动机启动, 油门还是油车的响应, 而不是电车的响应.</p>
<p>要解决油门响应问题, 就得在发动机端再加一个 BSG 电机进行扭矩补偿. 这样系统就需要3台电机, 大大增加了动力总成的体积重量和成本.</p>
<p>这对丰田来说, 油门响应慢不是问题, 因为大家都接受丰田车是移动路障.</p>
<p>但是, 对奇瑞来说, 这就是问题了.</p>
<p>而且丰田使用的是大排量自吸发动机, 油门响应本身就比奇瑞用的涡轮增压发动机快.</p>
<p>这让奇瑞的混动车油门响应体验就非常差了.</p>
棍勇喜欢大公主
2022-04-11T00:00:00+00:00
https://microcai.org/2022/04/11/棍勇喜欢大公主
<p>这番完结很久了, 但是有些话不吐不快.</p>
<p>棍勇重生后, 欺负过他的人, 他只留下了大公主活口. 不仅仅留了活口, 还一直带在身边.
不仅仅带在身边, 还不让她身处险境.</p>
<p>因为棍勇是非常喜欢大公主的.</p>
<p>有很多人因为一周目的时候大公主虐待棍勇, 就认为棍勇不喜欢大公主, 仇恨大公主. 这个是不对的.</p>
<p>炮勇和剑勇都虐待过棍勇. 二周目的时候, 棍勇对他们就没心慈手软过.</p>
<p>反而对待大公主, 棍勇心软了. 棍勇要等大公主虐待他, 才复仇. 因为他从心底也渴望世界线的变动. 希望二周目的大公主是表里如一的人.
如果大公主在二周目的时候突然改了, 棍勇其实就很乐呵呵的继续待大公主边上了.</p>
<p>即使大公主一如一周目一样开始虐待他, 他也没想杀了大公主复仇. 而是将她洗脑易容, 给她第二生命.</p>
<p>后来在遇到剑勇的时候, 棍勇宁可自己女装去刺杀剑勇. 他也想过让大公主去接近剑勇, 但是他为什么最终还是决定自己女装上阵呢?
首先, 他怕大公主遇到危险. 其次, 他知道剑勇喜欢大公主, 他吃醋. 他对观众解释自己亲自出马是怕大公主搞不定, 任务失败.</p>
<p>那不过是他自欺欺人罢了. 大公主也是王国第一勇者, 怎么会打不过老三, 而且还是在偷袭状态下.</p>
<p>那么大公主喜欢不喜欢棍勇呢?</p>
<p>大公主既喜欢棍勇, 又不喜欢棍勇. 她不喜欢棍勇主要是因为棍勇无能. 不能帮助她的除魔大业. 然后就是她自视清高, 不喜欢下等人.</p>
<p><em>但是, 不喜欢下等人这点是装的. 二公主曾经说她姐姐把亚人当虫子, 但是鹰眼否认说大公主不是这种人</em></p>
<p>但是她又喜欢棍勇, 是因为他们勇者4人行的队伍, 只有棍勇能安慰她. 大公主是勇者, 更是女人啊. 白天打累了, 晚上需要有人安慰.</p>
<p>他们一共就4个人, 晚上该找谁不言而喻. 剑勇虽然喜欢大公主, 但是大公主是谁? 她需要的是可以被她喜欢的男宠, 而不是喜欢她的女人.
大公主厌恶棍勇, 厌恶他的无能, 厌恶他的出身.
但是大公主又喜欢棍勇, 她喜欢棍勇的美貌, 她喜欢棍勇可以作为宠物一般的把玩.</p>
<p>但是, 大公主居然也只把棍勇留在身边当男宠, 并没有第二个. 连剑勇都羡慕棍勇可以伺寝. 剑勇自认为男装打扮的她帅气程度不输给棍勇.
但是, 大公主又不喜欢女人. 剑勇是100%没有任何机会的.</p>
<p>如果大公主能直面自己的内心, 主动找男主表白是迟早的事情. 但是帝王家庭的出身抑制了她的正常感情需求. 所以她对棍勇的爱就表现的极为扭曲.</p>
<p>那么, 棍勇和大公主知不知道对方喜欢自己呢?</p>
<p>答案是, 知道, 但是都认为对方不知道.
棍勇知道大公主其实喜欢自己, 但是棍勇认为大公主不知道他知道大公主喜欢自己的事情.
大公主知道棍勇喜欢自己, 但是大公主认为棍勇不知道他知道棍勇喜欢自己的事情. 大公主其实可能都不知道自己喜欢棍勇.</p>
<p>正是因为大公主知道棍勇喜欢自己,所以她才欺负棍勇. 正是因为他认为棍勇不知道自己喜欢棍勇, 所以她才很扭曲, 要掩盖. 掩盖自己喜欢棍勇的最好办法就是欺负棍勇.</p>
<p>棍勇知道自己喜欢大公主, 而且爱的深沉. 但是他觉得大公主不喜欢他, 所以很难受, 很憋屈. 还天天被欺负. 所以才产生了扭曲的性格.</p>
武器很贵
2022-04-01T00:00:00+00:00
https://microcai.org/2022/04/01/weapon-is-costy
<p>俄罗斯和乌克兰打了一个多月了.</p>
<p>消耗了不计其数的武器和人命.</p>
<p>战争就是烧钱, 谁先烧完, 谁就得投降.</p>
<p>因为, 武器是很贵很贵的. 一发导弹能解决的事情, 俄罗斯非要派地面部队填人命的方法去打. 无他, 武器太贵了. 武器比人命要贵的多, 而且是多得多.</p>
<p>交战双方的战报, 都是 击落敌机 多少架, 击沉战舰多少艘, 击毁坦克多少辆.
至于交战的士兵死了几个, 好像就没有那么在意了.</p>
<p>武器太贵了!</p>
<hr />
<p>听人常说, 要拿起法律的武器保护自己. 要用法律维权.
法律是武器没假, 但是他们不会告诉你, 法律这个武器, 非常的贵.</p>
<p>用法律维权和战争一样, 都是烧钱, 看谁先烧完.</p>
<p>战争让军火商发财, 用法律维权则是让律师发财.</p>
<p>越来越贵的武器, 让富裕的国家占有了越来越大的优势.</p>
<p>法律, 也让富人占有越来越大的优势.</p>
<p>如果你公司账上没有可以随时冻结也不影响运转的三千万资金, 就不要用法律和腾讯斗.
如果你工资卡里没有600万, 就不要和特斯拉斗.</p>
<p>你烧六百万, 他也烧六百万. 但是, 他有六百个亿可以烧.</p>
<p>所以, 你选择了 “匹夫一怒 ,血溅三尺”, 富人慌了.</p>
<p>你有一条命, 资本家…好像也只有一条命.</p>
<p>那就坐下来好好谈吧.</p>
<p>可是如果人人效仿, 资本家还赚什么钱?</p>
<p>所以资本家就得雇佣大量的御用水军对大众进行 PUA. 遇事不要冲动. 冲动是魔鬼. 一定要拿起法律的武器合法的保护自己的权益.</p>
<p><em>这才是资本主义法制的真相.</em></p>
键政一把
2022-03-21T00:00:00+00:00
https://microcai.org/2022/03/21/键政一把
<h1 id="远交近攻是老祖宗留下的哲学">远交近攻,是老祖宗留下的哲学</h1>
<p>谁离我们远? 欧洲,美洲。
谁离我们近? 印度。</p>
<p>现代中国面临的最大的威胁是什么? 是能源安全,粮食安全。</p>
<p>打下印度,这2个危险都解决了。</p>
<p>有了印度洋出海口,美国所谓的岛链不攻自破。</p>
<p>有了印度,18亿亩耕地红线就自动守住了。</p>
<p>有了印度大陆的优质耕地,可以再多养活20亿中国人。</p>
<p>拿下印度, 就不必与巴基斯坦友好, 就不必为了忌惮巴基斯坦而放任国内绿教兴风作浪。</p>
<h1 id="死掉的毛熊才是好的毛熊">死掉的毛熊才是好的毛熊。</h1>
<p>安全有了保障,就不必和俄罗斯结盟。可以继续肢解俄罗斯。拿回苏武放羊的北海。拿回库页岛。</p>
<p>压制住俄罗斯,才能做欧洲的话事人。</p>
<h1 id="没有美国世界会更美">没有美国世界会更美</h1>
<p>没有美国,世界会少很多很多战争。</p>
<h1 id="沈万三必须死">沈万三必须死</h1>
<p>有钱人最大的罪恶就是有钱。不挣有钱人的钱,挣谁的钱?</p>
<p>越有钱越是惜命。汤师爷说道,不能拼命啊, 拼命了还怎么挣钱。</p>
<p>只有把资本家关进笼子,资本才能为人民做事。</p>
<p>其实前面做的一切,其实都只是为了杀沈万三的时候,资本没有逃路,只能被关进笼子。不然资本外套,去了他国,那就只有变成了被资本家制裁了。</p>
<p>资本无国界。但是又躲在国界的背后躲避人民的铁拳。</p>
<p>你打它,他就跑,留下饥饿的人民。</p>
<p>只有世界大同的时候,资本再无国界可躲,你要他进笼子,他就只能进笼子。</p>
seccomp 是个好东西
2022-03-20T00:00:00+00:00
https://microcai.org/2022/03/20/seccomp
<style>
p {
text-align: justify;
font-family: monospace;
}
</style>
<p>最近在实现 git 商城的时候, 遇到了收款方面的问题.</p>
<p>不沾钱就不用为钱相关的破事费心. 不想碰钱, 钱总得收了才能发货. 不然岂不是做慈善.</p>
<p>所以, 我决定, 让卖家在自己的 git 仓库里放一个 js 脚本来收钱.</p>
<p>具体的来说, 就是每当用户要支付订单的时候, js 脚本就被运行, 要求吐出一行链接. 这个链接可以让用户进行支付. 他可以是一个收款的网页, 也可以是一个能唤醒支付宝或者微信的 scheme 地址.</p>
<p>这就要求嵌入一个 js 引擎. 但是, 试问现今写 js , 谁不是写 nodejs 呢?
但是, 嵌入一个 node 非我所愿.
我更希望的做法, 是以子进程的方式运行node, 而不是将 libnode.so 嵌入主程序.</p>
<p>运行独立的 node 进程, 还必须让 node 可控. node 不能做一些危险操作. 这个只要不用 root 账号运行 node, node 自然是无法做危害系统安全的事情.</p>
<p>但是, 不危害系统安全, 不等于没有危害. 即便没有 root 权限, 脚本还能挖矿, 还能偷数据, 还能当肉鸡 ddos 别人, 还能拿我们的服务器当代理.</p>
<p>这一个脚本能干的事情太多了. 只是不让用 root, 只是保护了操作系统本身不被破坏有何意义?</p>
<p>所以我需要能禁锢 node 进程的新法子.
早早换 systemd 做系统 init 的好处就是, systemd 教会了我有一种高级的系统调用保护法, <strong>seccomp</strong>.
seccomp 可以过滤系统调用. 编写一个 BPF 小代码, 执行在内核里. 这个小代码可以决定, 是允许执行系统调用,还是返回错误; 返回错误的话, 用什么错误代码返回.</p>
<p>于是我用 seccomp 封锁了绝大多数的系统调用. 只留下了寥寥数十个.
seccomp 的使用方法是, 在 fork 后, 使用 <code class="language-plaintext highlighter-rouge">seccomp_init, seccomp_rule_add_exact, seccomp_load</code> 设定并安装好一个过滤器. 然后再执行 <code class="language-plaintext highlighter-rouge">execve</code> 运行 node 进程.</p>
<p>但是, 可恨的是, openat 系统调用无法封锁. 封锁了 openat, 则 ELF loader 无法加载 so.
但是不封锁 openat, node 进程就可以肆意打开文件盗取系统秘密. 不得不防.</p>
<p>经研究, 发现 seccomp 除了同意/不同意执行, 还有一个办法, 就是把这个决定通过一个 fd 消息发送到父进程去决定允许还是不允许.</p>
<p>就是在 <code class="language-plaintext highlighter-rouge">seccomp_rule_add_exact()</code> 调用的时候, action 参数使用 <code class="language-plaintext highlighter-rouge">SCMP_ACT_NOTIFY</code> , 然后 <code class="language-plaintext highlighter-rouge">seccomp_load</code>, 安装成功后, 即可通过 <code class="language-plaintext highlighter-rouge">seccomp_notify_fd</code> 获取一个 fd, 这个 fd 通过 file description passing 机制, 到父进程. 就绪后, 父进程就可以用 <code class="language-plaintext highlighter-rouge">seccomp_notify_receive</code> 获取到子进程要调用的系统调用通知了. 子进程但凡想调用被标记为 <code class="language-plaintext highlighter-rouge">SCMP_ACT_NOTIFY</code> 的系统调用, 进程就会被挂起, 等待父进程的英明决策. 父进程可以拒绝掉, 也可以同意继续执行. 还可以 “代为执行”.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">while</span> <span class="p">(</span><span class="nb">true</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">seccomp_notif</span><span class="o">*</span> <span class="n">req</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
<span class="n">seccomp_notif_resp</span><span class="o">*</span> <span class="n">resp</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
<span class="n">seccomp_notify_alloc</span><span class="p">(</span><span class="o">&</span><span class="n">req</span><span class="p">,</span> <span class="o">&</span><span class="n">resp</span><span class="p">);</span>
<span class="n">scoped_exit</span> <span class="n">cleanup</span><span class="p">([</span><span class="o">=</span><span class="p">]()</span> <span class="p">{</span> <span class="o">::</span><span class="n">seccomp_notify_free</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">);</span> <span class="p">});</span>
<span class="k">auto</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">seccomp_notify_receive</span><span class="p">(</span><span class="n">seccomp_notify_fd</span><span class="p">,</span> <span class="n">req</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">e</span> <span class="o">=</span> <span class="n">errno</span><span class="p">;</span>
<span class="n">LOG_DBG</span> <span class="o"><<</span> <span class="s">"[seccomp] seccomp_notify_receive failed with e="</span> <span class="o"><<</span> <span class="n">e</span><span class="p">;</span>
<span class="k">co_return</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">resp</span><span class="o">-></span><span class="n">id</span> <span class="o">=</span> <span class="n">req</span><span class="o">-></span><span class="n">id</span><span class="p">;</span>
<span class="n">resp</span><span class="o">-></span><span class="n">error</span> <span class="o">=</span> <span class="o">-</span><span class="n">EPERM</span><span class="p">;</span>
<span class="n">resp</span><span class="o">-></span><span class="n">val</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">switch</span> <span class="p">(</span><span class="n">req</span><span class="o">-></span><span class="n">data</span><span class="p">.</span><span class="n">nr</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">case</span> <span class="n">SCMP_SYS</span><span class="p">(</span><span class="n">openat</span><span class="p">):</span>
<span class="p">{</span>
<span class="c1">// req->data.args[1] 是待打开的文件名. 但是, 这个指针是在待打开的进程里的, 所以</span>
<span class="c1">// 要使用跨进程 memcpy</span>
<span class="k">struct</span> <span class="nc">iovec</span> <span class="n">this_readbuf</span> <span class="o">=</span> <span class="p">{</span> <span class="n">openat_param1</span><span class="p">,</span> <span class="k">sizeof</span> <span class="p">(</span><span class="n">openat_param1</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span> <span class="p">};</span>
<span class="k">struct</span> <span class="nc">iovec</span> <span class="n">traced_readbuf</span> <span class="o">=</span> <span class="p">{</span> <span class="k">reinterpret_cast</span><span class="o"><</span><span class="kt">void</span><span class="o">*></span><span class="p">(</span><span class="n">req</span><span class="o">-></span><span class="n">data</span><span class="p">.</span><span class="n">args</span><span class="p">[</span><span class="mi">1</span><span class="p">]),</span> <span class="mi">4096</span> <span class="p">};</span>
<span class="n">memset</span><span class="p">(</span><span class="n">openat_param1</span><span class="p">,</span> <span class="mi">0</span> <span class="p">,</span> <span class="k">sizeof</span> <span class="n">openat_param1</span><span class="p">);</span>
<span class="n">process_vm_readv</span><span class="p">(</span><span class="n">req</span><span class="o">-></span><span class="n">pid</span><span class="p">,</span> <span class="o">&</span><span class="n">this_readbuf</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="o">&</span><span class="n">traced_readbuf</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">node_want_open</span> <span class="o">=</span> <span class="n">openat_param1</span><span class="p">;</span>
<span class="c1">// node_want_open 字符串就是 node 本次 open 要打开的文件名了</span>
<span class="c1">// 可以在这里允许或拒绝</span>
<span class="p">}</span><span class="k">break</span><span class="p">;</span>
<span class="nl">default:</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">seccomp_notify_respond</span><span class="p">(</span><span class="n">seccomp_notify_fd</span><span class="p">,</span> <span class="n">resp</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span>
<span class="k">co_return</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>父进程里获取到的是子进程空间的参数指针, 因此需要调用 process_vm_readv 直接读取对方的内存。</p>
<p>在这个父进程里,我只允许 node 打开 /usr/ 下的系统库,/etc 下的个别配置文件.
总之, 只允许node打开必要的文件,其他文件统统不允许打开。</p>
<p>这样, node 运行起来就在一个安全的沙箱环境, 再也不能肆意妄为了。</p>
git 定义商城
2022-03-06T00:00:00+00:00
https://microcai.org/2022/03/06/git-based-mall
<h1 id="引">引</h1>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>商人商人实在太伤人
你说所有人都这么看你
怨不得每一天唉声又叹气
感慨着赚钱真的不容易
其实商人就是买东西
把东边的买卖到西边去
辨贵贱 调余缺 度远近
世上不能没有我和你
急人之所急 需人之所需
这才是真正做生意
买的找不到(着)卖的 卖的找不到(着)买的
一潭死水怎会有生机
急人之所急 需人之所需
这才是真正帮了自己
一网不捞鱼 二网不捞鱼
三网就捞个大尾巴尾巴尾巴鱼
</code></pre></div></div>
<p>做买卖, 就得有铺子. 从前的铺子, 在街头.
现在的铺子, 还可以在网上.</p>
<p>只要是个铺子, 就得有人打理.</p>
<p>不管是格子铺, 还是架子铺. 东西都要分门别类的摆放好. 逛的人舒心了, 才会多来逛.</p>
<p>通常网铺会提供一个在线的编辑器, 用于输入描述性的文字. 还可以设定字体,字号,颜色.</p>
<p>还能插入图片.</p>
<h1 id="痛">痛</h1>
<p>图文并茂, 才能赏心悦目.</p>
<p>编辑器可是个大工程, 何况还是在线的.</p>
<p>于是, 编辑商品的编辑器, 就只能因繁就简了. 只能聊胜于无了.</p>
<p>每天面对这么简陋的编辑器, 小二也觉得越来越犯2. 于是, 放截图替代了用简陋编辑器含辛茹苦的码字.</p>
<p>看着好像是个精心排版的东西, 实际上是在其他软件上排版好, 然后截图过来的图片.</p>
<p>编辑功能彻底成了摆设.</p>
<p>即使是这样, 小二还嫌他传图功能都鸡肋呢!</p>
<p>在店小二还在学习 加粗,变大,插图片 富文本三部曲的时候, 程序员也想给自己的 README 加一点点的样式.</p>
<p>程序员可不喜欢纯靠鼠标精确点击实现的 加粗,变大,插图片 三部曲.</p>
<p>于是 markdown 应运而生.</p>
<p>如果仅仅只是把鸡肋的富文本编辑器换成 markdown, 那只完成了改变的第一步.</p>
<p>第二步, 是把商品元属性的设置, 从鼠标的点击中释放出来.</p>
<p>程序员不喜欢写文档, 但是喜欢写注释.
因为注释是跟随代码的.</p>
<p>分离的文档, 如离家的孩子, 流浪在外边. 就像脱离了控制的野指针一样.</p>
<h1 id="解">解</h1>
<p>于是, 我发明了使用 GIT 管理店铺商品的商城.</p>
<p>每一件待售的商品, 都是一个 markdown 文档.
商品的 类目, 价格, 等等信息, 和 图文并茂的详情描述, 一起写到一个文档里.</p>
<p>把所有的商品, 按目录组织好, 并且通过 git 进行版本控制.</p>
<p>一个店铺, 一个 git 仓库.</p>
<p>把 本地编辑好的 文档, push 到远程, 上架就完成了.</p>
<p>下架, 也只需要删除文件, 并 push.</p>
<p>因为 git 控制了版本, 以后想重新上架, 也只需要 revert 那个删除的提交.</p>
<p>因为文档都在本地, 可以使用自己最趁手的编辑器编辑. 许多编辑器还有非常高级的批量操作功能, 对管理大量商品简直如虎添翼.</p>
<p>至于图片, 和仓库保存到一起就行.</p>
<p>商品的描述, 统统放在 goods 目录下. 图片放在 images/ 目录下.</p>
<p>假设你编辑的商品是 goods/cat1/iphone4.md</p>
<p>图片在 images/cat1/iphone.jpg</p>
<p>编辑的时候, 使用 <code class="language-plaintext highlighter-rouge">![img](../../images/cat1/iphone.jpg)</code> 指令引入图片
由于使用的是相对 iphone4.md 文件的相对路径, 因此 markdown 编辑器在本地是可以正确预览的.</p>
<blockquote>
<p>这部分相对路径的引用, 会在服务端分发给买家的浏览器的时候, 替换成图片的绝对地址.
因此, 本地编辑预览和在线浏览都不会有问题.</p>
</blockquote>
<h1 id="优">优</h1>
<p>除了这个改进, 还有就是整个商城的前端是 SPA. <em>Single Page Application</em></p>
<p>markdown 也是在前端渲染的. 同以往 SPA 使用 RESTfull API 不同, 我的商城使用了
长链接.</p>
<p>长链接, 就取消了 cookie 的需求. 不用对每个请求都进行鉴权. 只要连接建立的时候鉴权一次.</p>
<p>长链接, 比 pipeling 更进一步, 实现了 OOR <em>out of order rpc</em>.</p>
<blockquote>
<p>HTTP pipeling, 只是复用链接的时候激进了点, 不等请求返回就发送后续请求.
但是, 应答内容本身还是按请求的次序返回的.
而 OOR, 应答返回是乱序的. 执行完毕就返回, 无需按请求提交的次序返回
故 OOR 需要每个请求都带一个唯一的 id. 服务端返回应答的时候会携带上请求对应的 id</p>
</blockquote>
<p>因此, 用三个关键字概况我的商城的改进, 就 3 点</p>
<ul>
<li>OOR</li>
<li>markdown</li>
<li>git</li>
</ul>
<p>店小二只需使用 visual studio code 就可以完成店铺管理.</p>
c++20 协程
2022-02-20T00:00:00+00:00
https://microcai.org/2022/02/20/co_await
<p>表面上我是想说 c++20 的协程, 其实我是想说, asio 封装后的 asio::awaitable<> , 这个威力巨大的武器将是未来异步的基石.</p>
<p>asio::awaitable<> 的概念, 是万物皆可 await. 万物皆可 await 的概念实际上来自 nodejs.</p>
<p>当初, nodejs 用回调地狱带火了异步. 既然回调地狱为何又火? 因为 nodejs 万物只能异步. nodejs 没有除了异步以外的 IO.
于是 nodejs 逼迫大家在异步的道路上进行探索, 终于诞生了 Promsie/await 对. Promise 是库, await 是语言关键字.
Promise 先出, await 后出. 没有 await 的 Promise 是不完整的 Promsie , 没有 Promise 的 await 是无法使用的废物.
两个天造地设的一对, 居然并不是同时出生.</p>
<p>Promise 的概念就是, 所有的函数, 都不直接返回数据, 而是返回 Promise , 要取得真正的返回值, 就要 await 这个 Promise.
那么, 调用返回 Promise 函数的地方, 就变成了 initiator , 而 await promsie 的地方, 就是 completion handler.
也就是, Promsie/await 的概念, 正好契合了 Proactor 的异步并发模型. 自从有了 promise/await , 原先为了避免回调地狱而
设计为 on(‘data’, lisener) 的 reactor 模式的库纷纷被抛弃, 大家都要选择 Promise 版的替代品.</p>
<p>Proactor 模型中实现的最好的库, 自然就是 boost.asio, 或者说,是 std::net.</p>
<p>asio 最初对异步的支持, 使用的是 initiator + completion handler 的模式. 同 nodejs 一样, 陷入了 回调地狱.
但是很快, asio 发明了 stackless 协程. 实际就是 Duff’s Device 实现的可重入函数. 基本用法是</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>reenter(this)
{
yield asio::async_read(socket, *this);
}
</code></pre></div></div>
<p>reenter 和 yield 都是宏, 展开后, 就是变成了一个 for循环+switch case 嵌套.</p>
<p>但是, 这种使用宏展开变成 Duff’s Device 的可重入函数, 不如语言层面的支持来的利索干净.
因此 asio 作者给标准委员会提案, 干脆把宏变成关键字. 有了编译器的支持, Duff’s Device 带来的一些缺陷就可以规避和修正. 使用上也会更便利.</p>
<p>然而这样的优雅提案, 并没有别接受. 委员会转而支持了微软提交的 co_await 协程.</p>
<p>微软缺乏异步大师, 他们有了 co_await 武器, 只能设计出 winrt::IAsyncResult 这样的不能包含万物的协程.</p>
<p>而 asio 爸爸不一样, 它捣鼓出了 asio::awaitable<></p>
<p>万物皆可 await, 使用的方法和 nodejs 如出一辙.</p>
<p>在 nodejs 里, Promise/await 的使用方法很简单, 首先是将原先的函数声明为 async , 然后就可以在函数体里使用 await .</p>
<p>声明为 async 的函数, 自身也自动变成了 Promise, 可以被其他函数 await</p>
<p>万物皆可 await,</p>
<p>到了 asio 这里, 只要把原来 T 返回值的函数, 换成 asio::awaitable<T> , 就可以在函数体里使用 co_await, 并能被其他函数 co_await.</p>
<p>万物皆可 co_await 后, 我连程序入口点都改成了 asio::awaitable<int> co_main(int argc, char * argv)</int></p>
<p>然后写一个简单的 stub main</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">asio</span><span class="o">::</span><span class="n">awaitable</span><span class="o"><</span><span class="kt">int</span><span class="o">></span> <span class="n">co_main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span> <span class="n">argv</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o"><<</span> <span class="s">"hello world</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span>
<span class="k">co_return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="n">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span> <span class="n">argv</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">co_main_ret</span><span class="p">;</span>
<span class="n">asio</span><span class="o">::</span><span class="n">io_context</span> <span class="n">io</span><span class="p">;</span>
<span class="n">asio</span><span class="o">::</span><span class="n">co_spawn</span><span class="p">(</span><span class="n">io</span><span class="p">,</span> <span class="n">co_main</span><span class="p">(</span><span class="n">argc</span><span class="p">,</span> <span class="n">argv</span><span class="p">),</span> <span class="p">[</span><span class="o">&</span><span class="n">co_main_ret</span><span class="p">](</span><span class="n">std</span><span class="o">::</span><span class="n">exception_ptr</span> <span class="n">e_ptr</span><span class="p">,</span> <span class="kt">int</span> <span class="n">co_main_return</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">e_ptr</span><span class="p">)</span>
<span class="n">std</span><span class="o">::</span><span class="n">rethrow_exeption</span><span class="p">(</span><span class="n">e_ptr</span><span class="p">);</span>
<span class="n">co_main_ret</span> <span class="o">=</span> <span class="n">co_main_return</span><span class="p">;</span>
<span class="p">});</span>
<span class="n">io</span><span class="p">.</span><span class="n">run</span><span class="p">();</span>
<span class="k">return</span> <span class="n">co_main_ret</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>这样 co_main 就变成了真正的 main.</p>
<p>co_main 因为本身处于协程之中, 因此, 他可以使用 co_await asio::this_coro::executor 获取 executor 从而构造需要的 IO 对象.</p>
<p>比如这样写</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">asio</span><span class="o">::</span><span class="n">awaitable</span><span class="o"><</span><span class="kt">int</span><span class="o">></span> <span class="n">co_main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span> <span class="n">argv</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o"><<</span> <span class="s">"hello "</span><span class="p">;</span>
<span class="n">asio</span><span class="o">::</span><span class="n">steady_clock</span> <span class="n">timer</span><span class="p">(</span><span class="k">co_await</span> <span class="n">asio</span><span class="o">::</span><span class="n">this_coro</span><span class="o">::</span><span class="n">executor</span><span class="p">);</span>
<span class="n">timer</span><span class="p">.</span><span class="n">expires_from_now</span><span class="p">(</span><span class="mx">1s</span><span class="p">);</span>
<span class="k">co_await</span> <span class="n">timer</span><span class="p">.</span><span class="n">async_await</span><span class="p">(</span><span class="n">asio</span><span class="o">::</span><span class="n">use_awaitable</span><span class="p">);</span>
<span class="s">"world</span><span class="se">\n</span><span class="s">"</span><span class="p">;</span>
<span class="k">co_return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="n">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span> <span class="n">argv</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">co_main_ret</span><span class="p">;</span>
<span class="n">asio</span><span class="o">::</span><span class="n">io_context</span> <span class="n">io</span><span class="p">;</span>
<span class="n">asio</span><span class="o">::</span><span class="n">co_spawn</span><span class="p">(</span><span class="n">io</span><span class="p">,</span> <span class="n">co_main</span><span class="p">(</span><span class="n">argc</span><span class="p">,</span> <span class="n">argv</span><span class="p">),</span> <span class="p">[</span><span class="o">&</span><span class="n">co_main_ret</span><span class="p">](</span><span class="n">std</span><span class="o">::</span><span class="n">exception_ptr</span> <span class="n">e_ptr</span><span class="p">,</span> <span class="kt">int</span> <span class="n">co_main_return</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">e_ptr</span><span class="p">)</span>
<span class="n">std</span><span class="o">::</span><span class="n">rethrow_exeption</span><span class="p">(</span><span class="n">e_ptr</span><span class="p">);</span>
<span class="n">co_main_ret</span> <span class="o">=</span> <span class="n">co_main_return</span><span class="p">;</span>
<span class="p">});</span>
<span class="n">io</span><span class="p">.</span><span class="n">run</span><span class="p">();</span>
<span class="k">return</span> <span class="n">co_main_ret</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>调用原先需要传递 回调函数的 asio异步函数, 只要把回调换成 asio::use_awaitable 占位符, 就可以自动把待调用函数变成 awaitable.</p>
<p>因此, 回调不再是回调了, asio 的文档都已经把 callback handler 改称为 completion token 了. 传递 use_awaitable 当做 completion token,
则 initiator 函数就自动 awaitable.</p>
<p>asio 需要兼容 回调/co_await 两种模式, 所以使用了 completion token 概念, 而我自己的代码, 不需要这个概念, 我只要全部 co_await 话,
把所有的函数, 统统变成 awaitable.</p>
<p>万物皆 awaitable 后, 我发现了新大陆.</p>
<p>原先的设计思路突然被放开了限制, 豁然开朗.</p>
<p>原先我设计的程序主体启动代码是这样的</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
my_server srv(io, configs);
srv.start();
asio::signal_set s(io, SIGTERM);
s.async_wait([&](auto ec, auto sig){ srv.stop() ;} )
io.run();
</code></pre></div></div>
<p>变成 awaitable 后, 我的主体代码是这样的</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
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();
</code></pre></div></div>
<p>而且, srv.stop() 变成 awaitable 后, 更容易实现优雅的退出了, 因为可以等待一些费时的逻辑完成退出后, stop 才会 resolve.</p>
<p>这样, srv 就在干净的状态下析构, co_main 也顺利干净利落的返回.</p>
新能源汽车充电和三相负荷平衡
2022-02-14T00:00:00+00:00
https://microcai.org/2022/02/14/ev-charging-and-3phase-balance
<p>我喜欢直流电,本质原因是直流电是恒功率的。而交流电的功率是脉动式的。</p>
<p>后来我才发现, 三相电也是恒功率的。</p>
<p>因为 sin(t) + sin(t + 120) + sin(t + 240) 的函数是一条直线。</p>
<p>从功率传输的角度来说,三相电 = 直流电 》单相电。</p>
<p>单相交流电是最差劲的一种电。为了获得稳定的功率,所有使用单相交流电的设备不得不储备一个庞大的储能装置(通常是巨大的电解电容)</p>
<p>这个电容储存的能量必须能足够使用 10ms(半个周期)。因此功率越大的设备就需要配置越大的电容。</p>
<p>越大的电容就会有越大的充电电流。而且还是冲击性的。因此还需要给电容配置预充电电路,大大增加成本和复杂度。</p>
<p>发电厂并不能发单相电,如果用户使用的都是单相电,电网还要负责平衡三相的负载。</p>
<p>但是,把全国人分成三波,一波用A相,一波用B相,一波用C相,并不能真的平衡负荷。</p>
<p>新能源汽车的诞生,让三相负荷平衡问题更雪上加霜了。</p>
<p>虽然平时家用时候单相电,但是 1来家庭用电功率不打,2来大家的功率也不会相差很大。</p>
<p>但是, 电动车就不一样了,不用的时候就是 0kw, 一用就是 7kw。</p>
<p>为什么国家规定交流慢充最大功率 7kw? 就时候因为考虑要做三相负荷平衡。实际上电网希望慢充不要超过 3kw,但是 3kw 太慢了,被迫接受 7kw。</p>
<p>这方面还是特斯拉富有远见。</p>
<p>特斯拉用单相就只能 3.3kw 充电,只有用三相电才能开启真正的交流慢充11kw。</p>
<p>国内其他车厂,为了成本考虑,不想做11kw的三相充电,就给大家打个折扣,给你6.6kw的充电器。
但是,如果 6.6kw 如果是三相的,每相只有 2.2kw。于是在没有 380v 交流电的地方,只能使用 2.2kw 功率,消费者时候绝无可能接受如此慢的充电功率的。
于是,他们就很贱的,给整成 6.6kw 的单相充电机了。</p>
<p>这种极度私自的行为,严重的拖垮电网。</p>
<p>而且,6.6kw 的电绝无可能通过插座引入,实际上只能安装专用的充电桩。
可以使用插座的随车充,还是只能最大 2kw。</p>
<p>而能安装专用的充电桩的,其实都是有条件使用380v三相电的。</p>
<p>所以,使用 6.6kw 的单相充电器,真的好处就只有支持用户去买超大功率的山寨随车充。极大的增加风险。</p>
<p>而且相电流高达32A,极大的增加线路起火风险。</p>
<p>所以,还是特斯拉这种做法对电网更友好。</p>
<p>也就是,使用 11kw 的三相充电机,单相使用为 3.3kw。
实际上220v能安全使用的最大功率是空调用的插座,16A 而已,就是 3.3kw。</p>
<p>并不会减少用户在没有充电桩的地方的充电体验。</p>
<p>而只要是汽车交流充电桩,就可以,也必须,提供完整的三相电。</p>
<p>因为车子本身单相就只有3.3kw的能力,因此也杜绝了使用大功率随车充导致的问题。</p>
<p>而且专用的充电桩都是三相的,电网就不变担忧扩大化的三相负荷不平衡问题,反而能减少不平衡电流的占比。</p>
<p>还有,三相充电机也不需要大电容就能做到平稳的直流输出。</p>
<p>结论:
希望车厂停止使用 6.6kw 的车载充电机,改为使用 11kw 的交流充电机。甚至 6.6kw 的三相充电机都远优于目前的单相 6.6kw。</p>
<p>因为三相6.6kw 我只要2个平方的线!而目前普遍的7kw 交流桩要求6个平方的线。而且没充电桩用的时候,车厂送的不还是弱鸡的 2kw 随车充么?</p>
<p>至于没有 380v 的问题: 我都能搞定车位,安装了充电桩专用的电表了,还能没380v? 安装充电桩最难的地方根本不是电,是车位啊车位!</p>
Future 和 Promise 的区别
2022-02-05T00:00:00+00:00
https://microcai.org/2022/02/05/future-and-promise-comparision
<p>promise 和 yield 的区别是什么?</p>
<p>其实区别大了去了</p>
<p>yield 虽然写异步看起来像同步,爽了。</p>
<p>但是强制了必须等待 io 的模型。</p>
<p>yield async io, 必须 io 完成,你的协程才能进一步执行</p>
<p>和原来同步模式的线程io是一样的。必须 io完成你的线程才能继续。</p>
<p>promise 则不然。 promise 可以在需要等待io结果的地方再 yield,结果出来以前可以执行其他操作</p>
<p>在线程时代,也有一个和 promise 的作用非常类似的东西,就是 future。</p>
<p>future 从未流行过。</p>
<p>promise 之于协程,如 std.future 之于线程。然而 future 不流行,因为同步 IO 的写法已经过时了。
现在是异步的天下。</p>
<p>虽然看起来有点标题党,但是 future 确实就是线程版的Promise. Promise 是为了更好的在异步环境里重叠异步操作。
future则试图给同步的线程模型里赛点异步的东西。</p>
<p>这种硬塞就好像你给五菱宏光mini ev 塞五连杆悬挂一样,人家根本就用不上。真用上的,还会买五菱宏光吗?</p>
asio::promise yyds
2022-02-04T00:00:00+00:00
https://microcai.org/2022/02/04/asio-promise-yyds
<p>最近在写以太坊的巧克力浏览器(区块链浏览器)。因为区块链浏览器需要访问历史的块,因此需要节点以 archive 模式同步。</p>
<p>遂运行了一个 geth。 然而半个月过去了,才同步了四百万高度,而且这四百万高度的最后一百万花了80%的时间。
同步速度以肉眼可见的速度下降,ETA 变的越来越长。 从一个月慢慢的增长到6个月。</p>
<p>于是寻找更快的客户端。找到了 erigon。erigon 号称2天完成 archive 节点同步。
结果牛逼是吹大了啊。最后耗费了10天完成了同步。</p>
<p>geth 在同步的时候, rpc 是可用的,geth 同步到哪里, eth_getBlockNumber 返回的高度就是哪里。
因此在同步的时候,我也开始同步巧克力浏览器的数据了。</p>
<p>巧克力浏览器是通过 eth_getblockbynumber 和 eth_gettransactionreceipt 把所有的交易数据都存入自己的数据库实现的。虽然其实可以等用户浏览到那个块再通过 rpc 获取数据,但是这样轻模式就没有检索功能了。所以还是需要解析所有的交易存入自己的数据库。</p>
<p>最终放弃 geth 二改用 erigon 的时候,巧克力浏览器才同步到一百多万的高度,就被迫停止了。</p>
<p>因为 erigon 只有完成同步,才会更新 eth_getBlockNumber 返回的高度。他同步的方式是先下载所有的块,然后执行,让后更新state数据,然后更新 rpc 接口的数据,然后获取最新高度,然后把落下的全下载回来,然后执行,让后更新state数据,然后更新 rpc 接口的数据,然后获取最新高度,然后把落下的全下载回来,然后执行,让后更新state数据,然后更新 rpc 接口的数据,然后获取最新高度…… 直到最后每次批处理的块只有1个,就算同步彻底完成。
如果 eth_getBlockNumber 一直返回 0 高度,我的浏览器就没法同步了,于是只能等。</p>
<p>问题是,他第一次的批量的时候,就试图从 0 一直批量执行到 1401万高度。结果,执行了10天才批量执行完。这一等就是十天, 等到了除夕前一天啊。执行完毕后,就再次落后十万高度了,于是进入第二次批处理。<em>因为我在机械盘上运行 erigon,因此 erigon 最后会永远落后几百到几千个高度,不过这个是后话,第一次批处理完成后,我的浏览器又可以开起来同步了</em></p>
<p>结果,我对巧克力浏览器的同步速度非常的不满了。</p>
<p>因为,按同步速度估算,可没法10天完成巧克力浏览器的同步。大约需要20天,而且 ETA 在增加!最后可能达到了三年之久。为啥我能提前知道eta在增加呢?因为我观察到,同步空快很快,同步内部有交易的块就慢下来了。早期空块多,但是后期基本都是满块。满块的同步速度低到令人发指的超过数秒!</p>
<p>经过研究发现,速度是慢在了 get_transactionreceipt 上。这个接口对未缓存的交易返回速度需要数百毫秒。<em>第二次调用其实几个毫秒就返回了.</em>
而巧克力浏览器访问的都是数年前的交易,都是属于 old data。因此每次调用它都要等待几百毫秒。
如果一个块里有个一百个交易,同步速度就惊人的 slow down 了。</p>
<p>如果把get_transactionreceipt的调用并发化呢?</p>
<p>大过年的,大家在看春晚,我却在敲代码。在改进巧克力浏览器的同步速度。完成批量同步,需要对原来的代码进行2个改进,第一个改进,是让原来的 jsonrpc 接口支持 pipelining,其次是实现 <em>等待所有协程完成</em> 的一个 asio 协程工具。虽然用在线程上此类的工具汗牛充栋,但是用在asio的协程上的工具可是 non-exist的。
有了这2个工具,只要把原来 for each transaction ; do get_transactioreceipt ; done 的代码,改成一次创建所有的协程去 get_transactioreceipt . 然后等待协程返回然后收集结果。 基本上就是 c++ 版的 asyncjs 里的并发 map 操作。</p>
<p>原来的 jsonrpc client,采取的做法是 async_write 后 async_read (当然是带 yield 的协程版),这样每次只能等待 rpc 返回后才能发起下一个 rpc 调用。
如果不改进 这个客户端类的代码,就只能被迫使用连接池才能并发调用 get_transactioreceipt 了。因此改进了这个 rpc 对象的代码, 可以在多个协程里并发的调用 jsonrpc.async_req(params, yield);</p>
<p>并发 get_transactioreceipt 的版本上线后,同步速度一下子就改进了!块里交易量的多寡,对同步速度的影响就小下来了。当然是不能指望它毫无影响的 <em>:)</em>。</p>
<p>这样就过了一个愉快的除夕和新年。</p>
<p>那么,代码就从</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>for all tx
get_transactioreceipt ...
save_block
</code></pre></div></div>
<p>变成了</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>for for all tx
asio::spawn( get_transactioreceipt ... ) # 开一堆协程
wait all corotines
save_block
</code></pre></div></div>
<p>同步了几天,高度到了四百万了,按这个速度,10天即可完成同步。考虑到后期满块增加,也不会超过20天吧。看来三月份就可以满血上线了。</p>
<p>然而,到了立春前天一看日志,情况变不对劲了。速度又慢下来了。</p>
<p>原来是数据库操作慢下来了!原先空快多,交易量少。同步了三百万高度,交易量都不足千万。
但是到四百多万高度,交易量已经膨胀到超过一个亿了。</p>
<p>我的小 nas 哪里受得了这么大的表啊。数据库爆表了。马上花了数个小时的时间,做了分表。保存一个block内容的时间才从数百毫秒重新下降到20毫秒上下。
但是显然和初期的5毫秒上下保存有差距。</p>
<p>显然,如果把数据库的保存操作异步化,就可以提升速度。</p>
<p>这个虽然很简单,只要把保存这个操作放到单独的协程里进行即可。</p>
<p>但是,问题出在我的同步逻辑上。因为 jsonrpc 会丢失链接(手动重启 erigon 或者 erigon crash ),数据库也会挂(手贱和其他问题)
如果异步了,就不容易处理在挂掉的点上重新执行一遍这个逻辑。</p>
<p>这个时候,我很怀念 nodejs 的 promise/await , 如果有 promise 就好了,我的代码可以改成这样。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// NOTE rpc_get_block_data 还是并发的 get_transactionreceipt 哈
[fetch_ok, data] = await rpc_get_block_data(sync_height)
for (; sync_height < 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); // 重获取一次
}
</code></pre></div></div>
<p>这样,如果 db 保存失败,sync_height 就不会增加,并且重新获取。
这样避免同步的过程中出现漏同步,出现空洞高度。这种写法还避免了完全异步fetch和save模式下的同步协作问题。大大简化了代码。</p>
<p>研究了一整天,最后发现了 asio 早已提供了 promise/await!</p>
<p>下面进入 boost::asio::experimental::use_promise 的世界!</p>
<p>上面这个代码,用 asio 的方式,写法就是这样</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// NOTE rpc_get_block_data 还是并发的 get_transactionreceipt 哈
[fetch_ok, data] = rpc_get_block_data(sync_height, yield); // yield 说明上下文是在 asio::spawn 开启的协程里
for (; sync_height < 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 了
}
</code></pre></div></div>
<p>当然,自己的 rpc_get_block_data 也要经过修改,实现支持 yield/use_promise 双操作。 括弧 asio 自己的IO对象其实都支持 回调/yield/use_promise 三操作的。</p>
<p>promise/await 永远的神!</p>
<p>PS: 我运行在家nas上的巧克力浏览器,虽然还在同步,但是可以使用了 <a href="http://geth.home.microcai.org:3586/">geth.home.microcai.org:3586</a></p>
<p>当然,因为是家里的 nas,so 只能用 ipv6 访问。不开放 ipv4 访问,免得麻烦。</p>
<p>稳定性不保证哈!随时调试。</p>
江和河有什么区别
2021-12-28T00:00:00+00:00
https://microcai.org/2021/12/28/he-and-jiang
<p>黄河水系和长江水系之间的万千群山。这片群山在渭水南岸的百里之遥拔地而起,横空出世,形成第一道高峰绝谷,时人叫做南山,后人称为秦岭。天下水流从这道南山分开,北面的河流绝大部分流入黄河,南面的河流绝大部分流入长江。</p>
<p>黄河最早叫河水,河就是水名,后来才称黄河。长江最早叫江水,江是水名,后来才叫长江。类似的还有淮河最早叫淮水,淮是水名,后来才叫淮河。</p>
<p>水最后又演变成了河江。原来的 X水 的称呼就变成了 X江 X河。
那河水自然就改名成黄河。江水改名成长江。</p>
<p>那么水为啥演变成河江,啥时候称河,啥时候称江呢?</p>
<p>其实这是个语言习惯问题。</p>
<p>长江流域的人民,习惯用江。黄河流域的人民,习惯用河。</p>
<p>其他流域的,则主要取决于汉人向其他地方迁移的时候,是那种语言习惯的人先过去的。先过去的自然就有了命名权。</p>
压燃发动机更适合做増程器
2021-12-21T00:00:00+00:00
https://microcai.org/2021/12/21/Homogeneous-charge-compression-ignition-better-suited-for-REEV
<h1 id="压燃的缺点">压燃的缺点</h1>
<p>阻碍汽油机热效率提升的根本原因是汽油机无法实现大压缩比。压缩比过大,混合气体温度过高,汽油会在火花塞点火前被压燃。这会引起爆震。需要更高标号的汽油。L:</p>
<p>柴油机不会爆震,是因为它吸入的是纯空气。汽油机必须吸入混合气体,是因为汽油必须与空气按14比1混合。</p>
<p>吸入多少空气就必须注入对应的汽油。要控制油门,就必须减少吸入的空气。这样就会导致压缩比降低。因此汽油机小油门下,效率大大降低。</p>
<p>这个特性就使得汽油机很难做到压燃。即使用高压缩比实现了压燃,小油门下压缩比又会过低导致无法压燃。</p>
<p>马自达实现的压燃汽油发动机,为了能在各种工况下工作,其实并不总是处于压燃状态。在低负载下还是要靠火花塞引燃。搞了各种点火机制进去使得发动机复杂度大大提升,导致成本无法降低。</p>
<h1 id="压燃用于增程的优势">压燃用于增程的优势</h1>
<p>増程器最大的特点就是工况单一。单一工况意味着压燃发动机无需再考虑无法压燃的工况。从而彻底抛弃火花塞,由此大大降低发动机复杂度和制造成本。再配以米勒循环,实现 25:1 的压缩比都不是梦。</p>
<h1 id="技术总结">技术总结</h1>
<p>实现用于增程器的压燃汽油发动机,其特点应该有:</p>
<ul>
<li>
<p>大比例废气再循环 废气冷却后再吸回气缸,降低气缸内的氧含量 增加理论空燃比</p>
</li>
<li>
<p>超稀薄燃烧 使用超过 40:1 的空燃比 超高的空燃比下的稀薄燃烧反而能大大降低气缸温度从而降低 NOx 。</p>
</li>
<li>
<p>米勒循环 使膨胀比大于压缩比,降低废气温度</p>
</li>
<li>
<p>无节气门,始终充分吸入空气,0 泵气损失</p>
</li>
<li>
<p>利用发电机调节转速,始终让发电机 100% 扭矩输出。</p>
</li>
<li>
<p>电辅助废气涡轮增压 在涡轮轴外部套上线圈,通电后产生的旋转磁场和废气合力推动涡轮,或者涡轮转速过高时回收涡轮能量。维持进气压力恒定以优化燃烧室设计。<em>异步电机的物理特性:将旋转磁场设定为需要的涡轮转速。涡轮实际转速低于旋转磁场,就会进入电动机模式,如果涡轮转速过于旋转磁场转速,自动进入发电模式。</em></p>
</li>
</ul>
增程不差,直驱没那么神
2021-12-20T00:00:00+00:00
https://microcai.org/2021/12/20/coupling-clutch-no-that-important
<h1 id="不要迷信发动机直驱">不要迷信发动机直驱</h1>
<p>很多人以为 理想one高速油耗高,唐dmi低,是因为dmi有高速直驱模式。</p>
<p>其实这个是不对的。发电再驱动,固然有一个能量转换效率,然而变速箱的能量传递也并非 100%。通过变速箱驱动和通过发电机电动机的间接驱动,能量传递的效率差距在10%以内。换言之,如果理想one高速是 10L 的油耗,增加直驱也不会让油耗低于9L。显然唐dmi的油耗低于9L 并不全是直驱的功劳。</p>
<h1 id="归根结底还是发动机的热效率">归根结底还是发动机的热效率</h1>
<p>要说唐dmi的油耗为啥比理想one低,归根结底还是因为骁云发动机的热效率远高于东安动力。使用同样的动力结构的赛力斯油耗就比理想one低,而且赛力斯比理想one还重了50kg。主要也是因为赛力斯的发动机虽然不如骁云发动机,但是也比东安动力的强不少。</p>
<h1 id="那直驱还是能提高一点效率的吧">那直驱还是能提高一点效率的吧?</h1>
<p>是也不是。</p>
<p>目前来看是的,但是未来三电技术的发展,发电后驱动电机,整条能量流路径上的效率可以进一步优化。最后使得直驱带来的效率优势抵不过导致的nvh劣势而变得鸡肋。</p>
<p>而且直驱意味着只能前驱。如果要做四驱,后轮无论如何也不能直驱。没有人会同意为了后轮直驱而给电动车加传动轴。这样四驱下,前轮直驱的效率优势就更低了。</p>
<h1 id="直驱咋就有nvh劣势了呢">直驱咋就有nvh劣势了呢?</h1>
<p>主要是因为,发动机要驱动车辆,就无法同车身进行柔性连接。发动机的输出轴输出扭矩的时候,机身还会有反扭矩,机身通过比较强的衬套连接在车架上来克服这个反扭矩。于是发动机的振动就不可避免的要通过连接点向车身传递。变速箱也会因为同样的原理必须固定在车身,导致振动传递。</p>
<p>如果发动机不直接驱动车辆,而是发电,那发动机和发电机这个整体,就可以使用非常柔性的方式固定在车上。比如类似光驱里的转盘机构和光驱外壳的减震结构。</p>
<p>这样能大大减少发动机的振动传递给车身。</p>
<p>因此增程结构,有天然的 nvh 优势。</p>
增程式电车真香
2021-11-12T00:00:00+00:00
https://microcai.org/2021/11/12/bought-new-car
<h1 id="唐dm不香了">唐DM不香了</h1>
<p>开了三年多的唐DM,总觉得比亚迪的车啊,差点意思。</p>
<p>第一个差点的意思是,有电一条龙,没电一条虫。
虽然买的时候就知道了,但是那时候,没电的混动车,都是一条虫。</p>
<p>但是当没电不是一条虫的增程式电动车出来的时候,唐DM就不香了。</p>
<p>而没电一条虫的根本原因,是比亚迪糟糕的混动逻辑,导致电量无法保持。</p>
<p>发动机低速下不启动。起步全用电。而2.4吨的家伙,要加速到40码,消耗的电能巨大。起步肉眼可见的掉电。
好不容易起来了,马上红灯就得减速。发动机刚刚起来就立马停掉。低于60的速度,如果不踩油门,发动机就会关闭。</p>
<p>这样的混动逻辑,电怎么保的住?</p>
<p>第二个差点的意思啊,是比亚迪背刺老车主。</p>
<p>秒天秒地秒空气的 DMi 出来了。唐DM瞬间变成了垃圾!</p>
<p>新车太香,于是老款贬值速度变得太快。</p>
<p>为了不至于老款变得分文不值,决定乘现在还有点残值,赶紧出手。</p>
<h1 id="增程香">增程香</h1>
<p>早在2019年理想one出来的时候,我就看上了增程式。无奈那时候唐才开了一年。总不能一年就换车吧。</p>
<p>于是安慰自己,第一代产品有缺陷的,等第二代吧。</p>
<p>时间一晃到了2021年。这一年,汽车市场发生了翻天覆地的变化。</p>
<p>理想出了新款,比亚迪出了DMi。华为开始造车了。</p>
<p>新车必须式增程式的,因此我把目标锁定在了这几个车上。</p>
<blockquote>
<p>宋Plus DMI
DMI就是增程式电动车,虽然厂家从不宣传这点。但是从它的运行逻辑来看,它就是增程式电动车。
但是,宋也有致命缺陷:没四驱,筷子悬挂。于是一直等四驱DMI。</p>
</blockquote>
<blockquote>
<p>赛力斯SF5
其实本来这个车很好,无奈有几个缺点让我一直犹豫不决。最大的缺点就是没有遥控驾驶。然后那柳叶眉的后窗不好看也不实用。</p>
</blockquote>
<blockquote>
<p>岚图Free
和理想one差不多的价格,多提供了很多东西。属于是高性价比的理想one。</p>
</blockquote>
<blockquote>
<p>天际ME5
假药停的遗腹子。虽然价格和宋差不多,不过比宋还是多了很多东西的。起码底盘比宋强。不是筷子悬挂。</p>
</blockquote>
<p>备选车里,没有唐DMI也没有理想one。为啥呢?因为我发现自己其实不需要那么大的车。既然宋dmi和唐dmi唯一的区别就是尺寸,那为啥要多花钱买大壳子?理想one和岚图价格一样,为啥买配置低点的理想one?</p>
<p>这4款车决定了,于是就跑去试驾去了。</p>
<p>试驾后发现的体会:</p>
<blockquote>
<p>岚图Free
提供了上门试驾服务,服务不错。开过来的试驾车电量只有20%了。正好可以感受下增程器开启的模式。然而上车就发现了问题。这个车因为电量低,于是增程器一直保持在开启状态。不然空调会耗尽电池。然而,试驾前销售要讲解,停车十几分钟,电量丝毫没有增加。这增程器增程了个寂寞。完全没有充电!只是单纯的怠速带空调而已。这是油车逻辑啊。一看表显油耗都到15L百公里了。于是被Pass。</p>
</blockquote>
<blockquote>
<p>天际ME5
驾驶起来很舒服。提供了动能回收关闭的选项。关闭后,释放油门进入空单滑行非常舒适,踩刹车依然有动能回收。底盘很结实,不是筷子悬挂。开起来动力也非常猛。增程器四缸,很安静。增程器开启的时候,也是直接进入经济区间发电。不会像理想one那样为了弥补三缸缺陷,除非电量严重过低,否则增程器只运转在非经济区间低转速发电。增程逻辑是正常的。油耗也很低。可惜,只有后驱,没有四驱。销售懂都不懂,说四驱多一个电机油耗就翻倍了。pass。</p>
</blockquote>
<blockquote>
<p>赛力斯sf5
其实这个车是第一个去试驾的,但是因为最终买了这个车,所以最后说。这个车也是上门试驾。车送到门口。这车的机械素质真的是没话说!底盘开起来非常高档,像极了 A6L。考虑到电车的无顿挫加速,那真的是 CVT + V8 版的 A6L 了。
但是,没比亚迪的遥控功能。没了比亚迪好用的安卓车机,没了比亚迪好看的外观。。。。。。</p>
</blockquote>
<p>于是就一直等宋Plus 四驱DMI。等到了9月30号。</p>
<p>终于四驱DMI有消息了。报价20万。。。。。</p>
<p>比亚迪飘了啊,区区紧凑型suv就敢卖20万了,而且还是麦弗逊+三连杆的悬挂。
于是直接下定赛力斯。连试驾都不去了,因为我开了3年的唐,太清楚比亚迪的底盘素质了。</p>
<h1 id="半月使用体会">半月使用体会</h1>
<p>半个月开了一千多公里,只在提车的时候加了200的油。一直纯电,没怎么烧油。能快充是真的舒服!
以前开唐出门的时候,都得规划下,是马上会回家呢,还是会在外面跑一阵。如果是马上回家,算下总里程会不会超过60km。
只有出门很快会回来,总里程不超过60km的,我才敢纯电出门。否则就得一路混动过去。</p>
<p>因为唐的发电逻辑很糟糕,混动是几乎不会回充电量。除非一路匀速开高架。而低电量下混动体验非常差。所以必须得保持高电量下的混动行驶。也就是充满电后使用HEV模式行驶。这样起步用电,巡航用油。掉电多少取决于起步/急加速了多少次,和行驶里程无关。</p>
<p>长途行驶下来,基本上到家还会有30%-50%的电量。</p>
<p>但是,也有马失前蹄的时候,在路途中电量就到20%以下。那就非常的焦虑了。因为低电量下,起步就不能只用电了。那糟糕的双离合,起步起来顿挫的一塌糊涂。而且还没动力!因为没电了。发动机干吼车就是不走。虽然这个起步仍然比大多数日本车更快,但是,我是开惯了4.5s的人。</p>
<p>以上是唐给我带来的糟糕馈电体验。</p>
<p>以下是赛力斯的馈电体验。</p>
<p>虽然基本纯电行驶。但是也抽空开了一个小长途。来回两百多公里。这个里程纯电续航就无法覆盖了。也不做特殊设置。直接走。
开了几十公里后,电量见底,进入30%。然后一个不小心,再瞄过仪表盘的时候,发现发动机早就不知不觉中开起来了。居然我毫无察觉。</p>
<p>然后继续行驶的过程中我就时不时的看下发动机状态指示,还有剩余电量。</p>
<p>发现,发动机并不全程开启。低速行驶时发动机不启动。只有进入30以上的车速发动机才启动。这样风噪和胎噪就盖过了发动机的噪音,难怪无法察觉。即使车速一直高于30,发动机也会停止,因为电量达到了32%。</p>
<p>也就是说,车速高于30码的时间本身占我总行驶时间也只有一半,这一半里又有一半的时间,因为电量充足发动机就关了。发动机只有一半的一半的时间在工作,就保障了电量供应。而且全程电量没有低于29%过。保电能力不知道比唐DM高到哪里去了。</p>
<p>发动机工作的时候非常难以察觉。因为是增程式的,发动机不驱动车辆,完全无法察觉出来发动机启动后的介入。因为根本没介入。
不像唐,发动机启动后,能察觉到离合器的闭合导致的一点冲击。而且发动机介入后,就变成了双离合的车了,能察觉到升档和降档的顿挫,还有发动机声音的变化。</p>
<p>而增程模式下,发动机恒定转速,我使劲踩油门,发动机的声音岿然不动。并不会进入歇斯底里的状态。还是我行我素的以固定的功率发电。不足部分由电池补充。这也是这个增程系统的名字“驼峰增程器” 的由来。驾驶员对功率的需求就像骆驼的驼峰。但是增程器利用大电池,把驼峰给削了。</p>
<p>其实,以固定功率发电,是赛力斯和天际ME5才执行的逻辑。理想one和岚图Free都不是。理想one和岚图的增程器发电功率和车速是绑定的。这一点非常的愚蠢。但是理想的cto在知乎上给出过理由,那就是理想用的三缸发动机,经济区间的 nvh 太差了!不得不随车速改变功率。尽量低转速,无法进入经济转速。因为这个三缸机的经济转速在2800转。而为了 nvh考虑,理想one只用他1400转到2000转之间的功率。理想one其实只要更换一个更好的发电机,就能大幅降低油耗。至于岚图用的并不是三缸机,为啥还抄袭理想one的逻辑就不知道了。</p>
<p>油的部分说完了,再说快充。</p>
<p>唐没有快充,所以出门的时候必须时刻注意保持电量。不然没地方充电。而赛力斯有快充(这点必须感谢李想。因为他在大家喷他插混车搞快充没意义的环境下坚持提供快充接口,最终感化了其他厂家也给混动车提供了快充)即使电量低,也可以随时去公共快充桩上补电。</p>
<p>而且快充桩地图上搜出来结果发现找不到,找到了发现桩是坏的不能用。能用也被油车占位,等等诸如此类的事情,即使发生了,我也无所谓,烧油就好了。如果是纯电,那就非常焦虑了。不知道去下一个充电桩还够不够续航,下一个充电桩还能不能充上电。</p>
<p>这个体验都是吊打纯电和原来没快充的插混的。</p>
<h1 id="还好没买天际me5">还好没买天际ME5</h1>
<p>其实当时是因为没四驱pass掉的天际。</p>
<p>但是现在庆幸自己没买天际。</p>
<p>天际现在出现交车困难。感觉好像是传销一样。到处开门店收割订单。然后瞬间关门跑路。</p>
pipewire 不行
2021-09-15T00:00:00+00:00
https://microcai.org/2021/09/15/pipewire-sucks
<h1 id="一切都是为了混音">一切都是为了混音</h1>
<p>声音服务器, pulseaudio, pipewire, Windows Audio, AudioFlinger, oss4, jack, esd 等等,最主要的目的,就是混音。</p>
<p>对于消费级声卡,基本上不支持硬件混音。也就是同一时间只能让一个程序发声。</p>
<p>至于实际上你能听到多个程序发的声音,还能单独调节各个应用的声音大小,那都是因为用了声音服务器。声音服务器就一个嘛,就解决了同一时间只能让一个程序发声的问题。其他程序想发声,把数据交给声音服务器就好了。</p>
<h1 id="但是大部分时候用户只想听一个程序发声">但是大部分时候,用户只想听一个程序发声</h1>
<p>其实,多个程序发声这个不是真正的需求,因为声音多了,就混乱了。用户大部分时候,只想听一个程序发声音。这个程序可以是电子游戏,可以是播放器,可以看 bilibili。
系统要只是混音,其实主要是为了处理一些 ”后台声音“ 。 比如 QQ 有消息的时候那 ”滴滴滴滴滴” 的提示。迅雷下完东西后那清脆的一声 叮。</p>
<h1 id="采样率混战">采样率混战</h1>
<p>声音系统,打出生以来,就没有一个全球普世的采样率标准。谁都可以自己决定采样率。
于是系统的来源声音,采样率是多种多样的。</p>
<p>但是,最终,只能有一个采样率,因为消费级声卡只能接受一个声音流。</p>
<p>所以声音服务器,要把各种采样率的声音都重新采样为声卡能接受的采样率,然后混音成一条音频流再输出。</p>
<p>但是,对现有的声音产品来说,最常见的来源是 MP3 和 MP4。前者是音乐,后者是(在线)视频。纯音乐的声音,通常是 44.1khz 采样率。而视频,尤其是 H26{4,5} 为代表的新视频,通常带的是 48khz 采样率的声音。</p>
<p>所以,消费级声卡为了照顾用户,通常会在硬件上提供 44.1khz 和 48khz 两套 ( 22.05khz 和 96khz 192khz 都是衍生采样率,所以是两套)采样率的支持。</p>
<p>播放 mp3 的时候,就让声卡的 DAC 进入 44.1khz 模式, 播放 mp4 的时候,就让声卡进入 48khz 的模式。</p>
<p>这样就可以避免重采样。</p>
<h1 id="避免重采样">避免重采样</h1>
<p>声卡都搞了2个晶振,就是为了软件不要重采样。结果,某些声音服务器啊,为了自己代码写起来方便,对应用交给他的声音,不管不顾的总是给重采样了。
过去 44.1khz 是主流的时候,他们就牺牲 48khz,后来 48khz 成了主流,他们就牺牲 44.1khz。</p>
<p>AudioFlinger 这种 java 佬写的垃圾,会这么搞也就算了。反正我不用 android。</p>
<p>但是你 号称是全新的 声音服务器的 pipewire 居然也学 AudioFliner ?</p>
<h1 id="pulseaudio-的正确做法">pulseaudio 的正确做法</h1>
<p>既然大部分时候,用户只想听一个程序发声,那就应该尽量保证这个程序的声音不被重采样。除非声卡不支持。</p>
<p>比如你用 amarok 在播放 44.1khz 的 mp3。此时系统只有一个程序在播放声音。 pulseaudio 会把声卡配置为 44.1khz 采样率模式。然后声音数据基本直通给声卡。完全没有重采样,也没有混音。</p>
<p>突然,你点开了一个短视频,短视频的声音不太重要,所以你没有关闭 amarok, 只是把浏览器声音关小点。</p>
<p>这个时候, pulseaudio 会把浏览器传来的声音重采样成 44.1khz 然后和 amarok 混音,再输给声卡。</p>
<p>pulseaudio 牺牲了浏览器的声音质量。</p>
<p>一旦你停止播放 mp3. pulseaudio 又会把声卡切换到浏览器提供的采样率上。比如你看 bilibili 视频,那声卡就又切换到 48khz 采样率。</p>
<p>据我的观察,除了 pulseaudio , windows (至少在Windows 10 上)也是如此行为。都是为了避免无畏的重采样。</p>
<h1 id="audioflinger-和-pipewire-的错误做法">audioflinger 和 pipewire 的错误做法</h1>
<p>audioflinger 不管应用程序给的是什么采样率的声音,都会在内部重采样成 44.1khz。然后坐混音,然后交给 HAL。</p>
<p>HAL 一般由 SoC 厂提供。HAL 拿到 44.1khz 采样率的声音,如果自己的 DAC 支持 44.1khz 那就直接输出。如果不支持,就再次重采样。</p>
<p>最糟糕的是,当你的播放器播放的是 SoC 本身支持的采样率的声音。但是 不是 audioflinger 内部的 44.1khz 那就要被毫无意义的重采样2次。</p>
<p>pipewire 不过是把这个写死的 44.1khz 变成了 默认 48khz 并且可在 /etc/pipewire/pipewire.conf 里修改的数值而已。</p>
<p>都知道 pulseaudio 的那个行为更好,音质更好,更省 cpu 更节能。但是它废程序员的脑子。</p>
<p>所以 pipewire/audioflinger 是不可能废程序员的脑子的。</p>
<p>既然开发者不想废脑子,那只能是垃圾了。</p>
统一
2021-08-13T00:00:00+00:00
https://microcai.org/2021/08/13/what-can-we-do-to-liberty-tw
<h2 id="我们分裂太久了">我们分裂太久了</h2>
<p>论天下大势,合久必分,分久必合。</p>
<p>最近一次,中国维持在统一的状态,是在一百多年前。</p>
<p>自鸦片战争后,朝鲜分裂了,越南分裂了,吕宋岛分裂了,琉球没了。
藩国都跑了。</p>
<p>宣统退位后,连中央国内部都开始分裂了。又开始了地方割据势力逐鹿中原的时代。</p>
<p>逐鹿中原的过程,被日本的入侵中止过8年,日本投降后,又继续了。
最后,西元1949年,天下大定。再经过几年的时间扫荡剩余割据势力,按历史周期律,又该重新回到大一统。重新收拢藩国拱卫中央。</p>
<p>然而,1952年,朝鲜战争打乱了统一进程。 使得窜逃到台湾的流寇得以休养生息。</p>
<p>这一拖,就是70余年。
一百多年了,中央国都未完成统一,藩国尽失。藩国内还发生屠我汉民的恶劣事件。</p>
<p>汉儿学得胡儿语,却向城头骂汉人。</p>
<p>欸。</p>
推荐一款神车
2021-07-15T00:00:00+00:00
https://microcai.org/2021/07/15/byd-dmi-rocks
<div class="pre">
其实我算是后知后觉了,不过还是要推荐一把,毕竟比我后知后觉的人还多着。
</div>
<h2 id="油车为什么费油">油车为什么费油</h2>
<p>一言以蔽之,发动机高效率(相对高效)区间太窄。发动机是靠燃气的压力推动活塞做功。喷入一定量的燃油,燃烧后产生的气体膨胀速度是有限的,如果活塞下行的速度太快,燃气是来不及推活塞的。而来不及推活塞的废气最后也只能奔着打开的排气阀而去,白白把热能浪费。一定要让燃气使劲的顶着活塞走,非要走到燃气压力低到顶不动了才排气,才能最大化的利用燃气里的热能。
这就要求 1. 活塞下行的速度要和燃气膨胀速度匹配,即不能太快也不能太慢。2. 活塞必须是被燃气强行顶下,而不是自己有个自主的劲在跑. 要达到这2点,就要求:1. 发动机转速一定要维持在一个比较狭小的范围。2. 发动机的负载必须大,要把扭矩充分利用。</p>
<p>因为扭矩负荷小,转速就得高了才能维持相同的功率,但是燃气即使遇到更低阻力的活塞,也不能膨胀的更快以维持做功不变。于做功会减少,废气会带走更多的能量。</p>
<p>也就是说,即使转速维持在经济转速上,负荷不够,发动机也无法维持高效率。</p>
<p>发动机只能维持一个固定的转速,和一个固定的扭矩,才能保持住高效率。离了那个范围效率就会大大降低。</p>
<p>这个固定的范围,通常是在 2000转上下,扭矩接近发动机最大扭矩。理论上来说,应该就是最大扭矩。但是实际的发动机为了纸面数据的好看,最大扭矩下是通过过量喷油,降低空燃比实现的,所以反而效率低了。理论上最大效率下的扭矩应该是维持最佳空燃比的那个最大扭矩,通常比纸面数据低 10%。</p>
<p>发动机最大功率通常非常接近最大扭矩 * 最大转速。所以你看一个标称 135kw 的发动机,应该是 6000转的时候以最大扭矩输出。
那么,他最大效率下的功率推测就是 135 * ( 2000/6000) * (1 - 0.1) 大约为 40kw。</p>
<p>40kw 差不多就是搭载这种发动机的车型在高速上巡航所需的速度。</p>
<p>这就是油车高速省油的原因。</p>
<p>但是,在城市里开,就不是这么回事了。首先城市里开速度低,低速下的巡航功率,只有高速需求的 1/4。
所以,低速巡航效率低。</p>
<p>然后起步,虽然低速巡航需要的功率可能只要 10kw,但是起步的时候,所需功率远大于 100kw。而这时候发动机转速却没有起来,因此是在极不经济的转速上以最大扭矩进行输出。上文说到,最大扭矩通常是以多喷燃油实现的。氧气都不够烧。尾气里含有大量未燃烧充分的燃料(冒黑烟),油耗甚至远高于以更舒适的扭矩下更高的转速进行输出(所以拖档更费油)</p>
<p>如果变速箱齿比没适配好,还会出现明明是高效功率 40kw 的输出,却因为齿比问题导致发动机不得不以 3500转的速度+更低的扭矩实现低转高扭下相同的功率,也会导致效率低下。</p>
<p>这也解释了为啥有的车明明发动机效率很高,他实际开起来就是更费油。</p>
<p>所以,在城市路况下,起步,巡航,都在非经济区间运行。那油耗能低了才见鬼了。</p>
<h2 id="电车高速为什么费电">电车高速为什么费电</h2>
<p>因为速度越高,风阻越大。移动同样的距离,需要克服阻力做的功越多。而电机基本上什么转速下效率都挺高。于是基本上电车就是越快越费电。越慢越省电。</p>
<p>至于为啥慢到一定地步了也会费电,那是因为车子即使不动,也会耗电。这部分静态耗电和车速无关,和你车子发动的时间有关。这方面最大的静态损耗就是空调。所以冬天哪怕电池做好温控了续航还是拉跨。</p>
<h2 id="混动车为啥省油">混动车为啥省油</h2>
<p>混动车,尽量维持发动机在高效范围工作。在需要发动机更大出力的时候,不是靠多喷油解决,而是靠电机辅助扭矩。
在不需要发动机大力气出力的时候,靠发电机强加一定的扭矩负荷,这部分储存的能量,就可以在需要电机辅助的时候释放出来。</p>
<p>所以混动车省油的关键,就是要有一个巨大的蓄水池。蓄水池越大,能进行错峰调配的能量就越多。</p>
<p>但是蓄水池大了,他贵啊!要是省的油钱没电池多,就极其不划算了。</p>
<h2 id="增程好就是高速更费油">增程好,就是高速更费油</h2>
<p>一辆普通轿车,如果是电车,大概率是百公里综合能耗 14度电上下。城市路况(低速但没有频繁刹车)下更可以低到 10度。
但是跑到高速上 120码乃至 130码巡航一百公里,需要的能量就会超过18度电。</p>
<p>这都是风阻导致的。</p>
<p>如果是增程车,意味着城市工况下,以 1L油发3度电的效率,能做到百公里电耗3.3L。但是在高速上,就可能超过 6L了。</p>
<p>可是这种普通轿车,油车在高速上通常仅仅是5L出头的油耗。</p>
<p>所以增程车在高速上的油耗会超过油车。</p>
<p>这个原因就是,高速巡航正好进入油车的经济模式。但是增持车有发电再驱动的能量转换,大约会损失 10% 的能量,于是就要多烧 10% 的油。。。 5.5L * 1.1 = 6.05L。</p>
<p>为了节约这 10% 的能量,可以采取在高速上,由发电机直接驱动车辆。</p>
<h2 id="高速直驱的增程车是完美的">高速直驱的增程车是完美的</h2>
<p>这里最大的难点在于,首先需要一个增程车。需要电机的功率足够驱动车辆!</p>
<p>这也是日系败笔。THS 和 IMMD 推出时间都太早,那时候没有体积重量足够小功率足够大的电机和电池。
THS 解决方法是以油为主,IMMD 的解决办法就是直接躺平造肉车。动力垃圾就垃圾。</p>
<p>IMMD 思路对了,但是等消费者需要性能车的时候,他在有限的成本内堆不出料。最终造的车太贵了。开到报废都省不出油钱。</p>
<p>THS 思路就错了,发电机为主,驾驶体验被以电为主的吊打。而且电池容量太低,无法有效调峰。</p>
<p>所以,最佳的思路其实是 F3DM。比亚迪在08年就量产的车。</p>
<h3 id="f3dm-为啥失败了">F3DM 为啥失败了</h3>
<p>很简单,因为发动机太垃圾。发动机太垃圾,以致于靠电机电池再怎么给发动机续命,油耗都拉跨。
更致命的是,售价也是奇高无比。</p>
<p>但凡 F3DM 有个40% 热效率的发电机,也不至于输的一败涂地。</p>
<h2 id="来了神车来了">来了,神车来了</h2>
<p>F3DM 后又过了十来年,这十来年,发生了2件大事,最终 F3DM 成功了。</p>
<p>第一件事,就是三电成本的下降。使得 F3DM 的成本做到了和油车一致!这是非常关键的因素。</p>
<p>第二件事,就是比亚迪开发了一个专门为发电而生的发动机。放弃低扭,放弃高转,专注于 2000转nvh比较舒适的转速附近优化燃油效率。增持车一样的舒适性和市区油耗,但是和油车一样的高速油耗。哦不,还略微低了一丢丢。</p>
<p>脱胎换骨的 F3DM 的动力系统,比亚迪把它命名为 DM-i。</p>
<p>搭配这套 DM-i 系统的车,售价和同级别合资燃油车一样,但是多了绿牌,多了电车的无敌舒适性(挡换的再好的变速箱,都不如不换挡的电机舒服)。油耗还低了。</p>
<p>同时结合了油车(便宜续航高)和电车(平顺加速快)的优点,避免了油车(顿挫,提速慢)和电车(贵,续航低)的缺点。</p>
<p>这不叫神车叫什么?这不颠覆燃油车还有谁能颠覆?</p>
<h2 id="还买合资车傻了吧">还买合资车?傻了吧?</h2>
<p>目前 DM-i 的产品线是</p>
<ul>
<li>秦Plus-DMi – 轿车</li>
<li>汉DMi – 豪华轿车</li>
<li>宋Plus-DMi – 普通家用SUV</li>
<li>唐-DMi – 豪华装逼SUV</li>
</ul>
<p>有需要的朋友,赶紧去 ssss 交了定金排队。
毕竟这玩意,等你要的时候才买,它就等死你了。</p>
如何造发动机
2021-06-17T00:00:00+00:00
https://microcai.org/2021/06/17/to-build-engines-part1
<p>== 热机品种</p>
<p>最近突然对造发动机起了兴趣,遂研究研究如何造发动机。</p>
<p>发动机,就是热机,把热能转换成机械能的机器是也。
把热能转换成机械能,谁的热能?气体的热能。</p>
<p>因为,要在大气层里工作,工质必须得是气体才行。</p>
<p>提取气体里的能量,有史以来也就只找到了2种方法:活塞和涡轮。</p>
<p>所以,所有的热机,不外乎往复活塞式,或是涡轮式。</p>
<p>而工作的气体,如果是直接燃料燃烧后的废气,就是内燃机。如果是通过热交换(俗称烧开水)获得的,就是外燃机了,</p>
<p>四个象限下的典型热机</p>
<table>
<tbody>
<tr>
<td> </td>
<td>内燃</td>
<td>外燃</td>
</tr>
<tr>
<td>往复活塞式</td>
<td>汽油发动机</td>
<td>瓦特蒸汽机</td>
</tr>
<tr>
<td>涡轮式</td>
<td>涡轮发动机</td>
<td>蒸汽轮机</td>
</tr>
</tbody>
</table>
<p>往复活塞式外燃机,也就是瓦特发明的蒸汽机,已经被淘汰了。
但是烧开的水推动涡轮,一时半会儿还没被淘汰。
外燃机一般使用固体燃料 —— 煤,如果有液态或者气态的燃料,最好的选择还是内燃机。</p>
<p>内燃机没有体积庞大的换热装置(把废气的能量先转移给水),能减小体积,提高功率密度。</p>
<p>所以,发动机还是内燃的好。</p>
<p>那今天先研究研究往复活塞式内燃机。</p>
<p>== 内燃机循环</p>
<p>说到内燃机,就必须得有内燃机必备的4个循环:吸气,压缩,膨胀,排气
哪怕是涡轮式的也得有这4个循环。</p>
<p>为啥非得有这4个循环呢?其实吸气,膨胀,排气都好理解,为啥非得压缩一下呢?</p>
<p>这就不得不说到热机定律了:<em>理想热机的效率 = 1 - 排气温度/燃气温度。</em> 理想热机的定义是,理想热机把能用来转换为机械能的内能全部转换为了机械能,剩下的就只有无法转换的废热,绝无可能再从废热里榨取一丁点的能量了。</p>
<p>为了提高效率,就必须得降低排气温度,提高燃气温度。</p>
<p>而排气温度受限于热力学第二定律的制约,必须高于环境温度。实际工程实践的时候,都远高于环境温度。连接近都做不到。</p>
<p>燃料的热值是一定的,意味着燃烧前后的温差是一定的,于是理想热机的效率可以修正为</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>*1 - 排气温度/(燃烧前温度+燃料热值带来的温升)*
</code></pre></div></div>
<p>提高燃气温度,除了使用热值更高的燃料(只有造导弹的人才能自由的换燃料类型,汽油发动机已经定死了,就是汽油,没得换),还有一个办法就是提高燃烧前的温度。</p>
<p>如果不进行压缩,燃烧前的气体温度,最多也就气温。</p>
<p>所以压缩就是必须的,这个是提高热机效率的必经之路。</p>
<p>== 4个冲程</p>
<p>既然有了4个循环,又设定为是活塞式的内燃机,那内燃机的结构就呼之欲出了。就是一个活塞连在一个偏心轮上。
活塞往复运动完成4个循环,2圈旋转。每2圈完成一个工作流程。</p>
<p>== 提高效率</p>
<p>前面的热机效率推理得知,压缩比越高,热机效率越高。</p>
<p>但是,汽油发动机已经定死了只能烧汽油。只要是汽油,就没法高压缩比。压缩比过高,汽油就会在压缩还未结束的时候就提前燃烧。 也就是说,燃烧前的温度,不能超过汽油的闪点。</p>
<p>而汽油热值也是固定的,没法改。所以想继续提高效率,就只能从排气温度上想办法。</p>
<p>降低排气温度,也能提高效率。而降低排气温度,就是要让气体在膨胀阶段多干活。活干的多了,自然温度就低了。</p>
<p>多膨胀干活,就是提高膨胀比。</p>
<p>如果活塞还是连在曲轴上往复运动,膨胀比就只能等于压缩比。</p>
<p>因此,汽油机要提高效率,就是要在压缩比被锁死的情况下,尽量提高膨胀比。</p>
<p>除非压缩阶段压缩的不是汽油-空气混合气体,而是纯粹的空气,那压缩比倒是不会被锁死。
不过那就成了柴油机烧汽油了。之所以不可行,主要原因就是排放。</p>
<p>汽油这个东西,空气多了,汽油少了,燃烧就会过于充分导致废气里含有氮氧化物。
空气少了汽油多了,燃烧不充分废气就会含有一氧化碳。两个都是空气污染物。
所以空气和汽油必须按比例精确混合。全油门当然可以用柴油机的技术。问题是怠速的时候呢?非全油门工况呢?
还是吸入那么多空气,油喷的少了,排放可就过不了。所以就只能少吸入空气。
少吸入空气,那压缩比就不够,不仅降低效率,而且压缩后温度不够,最后喷油烧不起来,不是罢工了?</p>
<p>所以,必须想办法实现一个合适的压缩比+更大的膨胀比。</p>
<p>=== 进气门晚关</p>
<p>如果在压缩冲程开始的时候,进气门还开着不关。吸进去的气体就会被活塞还回去。
还的差不多了再关闭进气门开始压缩。这样压缩的行程就只有一部分被用上了。
这就降低了压缩比。就实现了低压缩比和高膨胀比的结合了。</p>
<p>为何不是进气门早关?</p>
<p>只考虑改变压缩比这一个策略的话,确实是可以的。但是早关进气门,混合气体就会经历一次真空化的步骤,直到吸气冲程结束,压缩冲程回到关气门的节点。</p>
<p>这个过程会导致飞轮需要提供巨大的能量给活塞以克服大气压强(除非曲轴箱抽真空,但是这是不可能的)。
这无形中就降低了发动机的效率。想想真空吸尘器为了抽气消耗的能量。</p>
<p>== 提高燃烧效率</p>
<p>以上都是介绍从燃气里榨取更多的能量的办法。还有一个提高发动机整体效率的办法,就是别浪费汽油。
尽量让汽油 100% 的燃烧掉,就能再提高效率。</p>
<p>提高燃烧效率的第一点,就是精确控制混合比例。也就是俗称的空燃比。空气和燃料的比值。
不要让汽油里的碳氢找不到氧对象。为此,就得使用电喷+氧传感器取代化油器。
控制电路根据氧传感器测得的废气氧含量,控制燃油的喷射量,榨干汽油里的每一个碳原子和氢原子。</p>
<p>混的比例再好,还是会有一些氧气和一些汽油就是不肯结合,就是多出来了。
或者是混合的不够充分,进去的不是混合气,而是空气+汽油雾滴。
或者是燃烧的速度不够快,膨胀完了还有每烧完的。在排气管了继续烧。但是在排气管里烧的,这部分能量并不会传给活塞做功。</p>
<p>燃烧效率这个东西,和转速息息相关。
因为这个和时间有关。而混合和燃烧,二者都需要时间。
只有恰到好处的时间,才能恰到好处的烧完。</p>
<p>== 上混动</p>
<p>如果只有一个特定转速能完成最好的燃烧,那把这个转速下的效率优化到极致,再利用电机和电池实现削峰填谷。</p>
日本想收快递
2021-04-26T00:00:00+00:00
https://microcai.org/2021/04/26/zone-11-need-df
<p>本子跳的很欢快。日本这样的一个民族,倭人,禽兽也,畏威而不怀德。</p>
<p><img src="/images/hepin-riben.png" /></p>
<p>一枚东风携带的核弹头就足够摧毁整个日本了。</p>
<p>驻日美军是现实的威胁,所以对东风的威胁就可以视而不见。
但是,驻日美军只威胁首相一人。</p>
<p>宁叫全日本陪葬,毋使我首相下台 乃日本世袭政客的内心写照。</p>
<p>届时,东风来临,首相可以乘专机逃离。被消灭的,仅仅是被愚弄后,誓死保卫首相的荣华富贵的普通平民而已。</p>
<p>不过,没了日本人民,政客也失去了利用价值。下场不过是《星球大战》里的分离势力。派一个徒弟过去就尽数消灭了。</p>
萌王观后感
2021-04-18T00:00:00+00:00
https://microcai.org/2021/04/18/limuru-the-ani
<p>萌王第二季前半部分完结撒花。</p>
<p>片子,是个好片。萌王一心一意求发展。只为过上好日子。从没有想过要当什么世界霸主。</p>
<p>萌王对敌人也很仁慈,哪怕战败,也好心接纳他们。把朋友变的多多的,敌人变的少少的。</p>
<p>奈何你不想当霸主,霸主也视你为威胁。更关键的是,萌王的国家发展,动了西方某大国的奶酪。竞争不过对手,就使出下三滥的手段。</p>
<p>经历过一番天真导致的惨痛教训后,萌王终于放弃幻想,准备斗争。</p>
<p>两万马前卒,就是萌王前进路上的祭品。</p>
<p>这哪里是写的异世界番。。。。。</p>
<p>所以,伏濑你这是在预言本子的结局嘛?</p>
三饷已开征
2021-04-10T00:00:00+00:00
https://microcai.org/2021/04/10/san-xiang
<p>苏联解体后,大明进入了全盛时期。 <img src="/images/ming-txwd.jpg" class="inline-img" style="height: 12em; display:inline-flex; width: auto; " />
同时,大明也开始进入了衰退期。</p>
<p>大明的衰退,首先开始于东林党<img src="/images/donglin-dang.png" class="inline-img" style="height: 3em; display:inline-flex; width: auto;" />和阉党<img src="/images/yang-dang.png" class="inline-img" style="height: 3em; display:inline-flex; width: auto;" />的斗争。</p>
<p>当然,最终阉党是斗不过东林党的。东林势力做大后,国内土地兼并愈演愈烈。
但是,瘟疫该来的还是要来。</p>
<p>对大明来说,消灭瘟疫最有效的手段不是防治,而是天气、屠杀和烈火。坐等百姓的生命被瘟疫蚕食。</p>
<p>搞的天下人心尽失后,曾经被犁庭扫闾还没消灭干净的后金就开始崛起,不断挑战天朝的权威。对不听话的蛮夷,就得开除朝贡体系<img src="/images/chaogongtixi.png" class="inline-img" style="height: 3em; display:inline-flex; width: auto;" /></p>
<p>开除了还活的好好的?那就得给点颜色了。</p>
<p>可是经过前几任的折腾,国库空虚,发不出饷了。</p>
<p>那大明就必须得开征辽饷<img src="/images/liao-xiang.jpeg" class="inline-img" style="height: 3em; display:inline-flex; width: auto;" />
了。</p>
<p>再耐心点,就可以等到闯王了。</p>
为cpu废热导风
2021-04-02T00:00:00+00:00
https://microcai.org/2021/04/02/redirect-airflow-in-chassis
<p>两年前,我组装第一台 NAS 的时候,对 NAS 的要求就是低功耗。倒不是因为我没钱,花不起电费。而是低功耗 = 低散热需求 = 低噪音。</p>
<p>毕竟,NAS 是放家里运行的。如果像商品服务器那样,开机约等于飞机起飞,是没法放家里的。
但是功耗大了,就一定有噪音吗?</p>
<p>最近为公司升级服务器的时候,把3700X 淘汰下来换成了 7302P。EPYC 使用了 4U 高度的散热器后,声音并不大。和普通PC并无区别。</p>
<ul>
<li>
<p>大部分情况下,使用 PC 平台足够承担服务器的工作。而且成本低非常多。唯一需要注意的是,得把 PC 平台常用的内存换成工作站使用的 Unbuffered ECC 内存。不信各位看看各大云产商的主流虚拟机配置,都是比家用 PC 配置略低的 *</p>
</li>
<li>
<p>实际上,我们也本不需要升级 epyc,只要再配置一台 PC 也足矣。但是因为我们排斥云,坚持使用物理机。排斥机房,坚持将服务器放办公室运行,因此并不希望增加机器的数量增加维护成本。服务器独享一条专线,并不和办公室的网络共享,因此服务器是直连ISP,中间没有使用路由器和交换机。这样可以简化网络的配置。如果使用2台服务器完成工作,就需要增加路由器和交换机了,增加网络设备,就增加了失效点。再三考虑后,决定升级 epyc 而不是用两台 PC *</p>
</li>
</ul>
<p>这次为公司装服务器的经验,促使我后来将家里的 <a href="/2021/03/28/epyc-as-3rd-nas.html">NAS 升级为 EPYC</a>。原来只要管理好风扇转速,然后设计好风道,epyc 也是可以凉又静的。</p>
<p>是的,要设计好风道!</p>
<p>由于服务器上的发热大户就只有 CPU,没有显卡。机箱里是空荡荡的,就一个高耸的散热器塔。装完服务器后我就想,这巨大的塔,把热量都排在机箱里面,太贱了。这就导致机箱必须加装散热器,把废热排出机箱。服务器开机起飞的罪魁祸首就是机箱的暴力风扇。</p>
<p>为什么 CPU 的废热不能直接排出机箱?非要先排到机箱里面,再利用机箱的风扇和风道排出机箱?</p>
<p>水冷效果为什么好,不就是因为水冷不会把 cpu 的热量排入机箱么!</p>
<p>风冷能不能学水冷?</p>
<p>你肯定会不假思索的说不能。因为风冷的热管是硬的,没法调整散热片的位置,只能待 cpu 上方。热量就必然只能排到机箱里。</p>
<p>如果给cpu的散热器加个套,让他的废气直通机箱外部呢?那还需要个鬼的机箱风扇!</p>
<p>bingo!</p>
<p>想到了这点,说干就干!立马用3D打印机打印了一个散热器罩子。完美契合散热器和机箱的排风孔。机箱的排风口上的风扇就彻底的抛弃不要了。</p>
<p>少了一个风扇,cpu 的温度非但没有降低,还让主板其他元件再也不受 cpu 的废热之苦。
少了一个风扇,少了一个振动源,噪音大大降低</p>
<p>但是为什么cpu散热器不自己带个罩子呢?或者机箱不提供这个罩子呢?还非得我自己造。</p>
epyc deployed
2021-03-28T00:00:00+00:00
https://microcai.org/2021/03/28/epyc-as-3rd-nas
<p>去年的 <a href="/2020/12/18/nas-upgraded.html">这篇文章</a> 里我受够了嵌入式 ATOM 处理器羸弱的性能而斥巨资购买了 锐龙 5600X 作为新的NAS机器的处理器。然而好景不长,这个新NAS才服役不到4个月,就被我<a href="/2021-03-07-pcie-shortage-problem.html">嫌弃了</a>。在<a href="/2021/03/07/pcie-shortage-problem.html">这篇文章</a> 的结尾,我提到了 <em>EPYC 的精髓在单路</em> 这句话。
这竟是我抛弃第二台NAS的导火索。</p>
<p>起初,我购买 NAS 只是为了做一个下载机。开个 Download Station 下下高清的片子。后来我希望它能承载更多的任务,然而 A53 + 512M 的平台,无论如何无法承载更多。于是就有了第一台NAS。原谅我甚至不把那台DS216j看成第一台NAS。</p>
<p>最初第一台 NAS 没有远见,机器毫无扩展性。甚至初期只用4个盘。但是好歹也有了大容量。也能突破千兆的瓶颈了。
用了一阵子后,受不了 atom 的性能而进行了更新。</p>
<p>第二台NAS性能非常棒。毕竟 zen3 架构,单核南波万。也正是因为他巨大的性能提升,导致我不只是把它当NAS了。他成了我的私人服务器。不仅仅运行 samba 共享,还运行多媒体服务,还运行 nextcloud 私人备份,当然跑 bt 下载也是少不了的。</p>
<p>除此之外,他还对我公司的线上数据进行热备。算是一份异地灾备。
然后,还运行了多个区块链的节点 —— 什么比特币,以太坊,波场币,统统都跑一个节点看看。</p>
<p>这下完犊子了。由于使用的是 ITX 主板,只有2条内存插槽,导致内存最大就被限制到了64G。运行了那么多服务,内存开销自然是小不了。于是时不时的就看到 OOM 崩溃日志。</p>
<p>加了 SWAP 后算是缓解了OOM。但是 iostat 可以看到 swap 文件长期保持在 300M/s 的读取速度 —— 不用说,就是内存不够了,被大量的 drop 掉,然后又马上访问于是又得 swap in 。</p>
<p>内存不足,是一件迫在眉睫的事情。我至少需要 128G 的内存! 换成 matx 的主板固然可以低成本解决问题 —— 但是,128G 要还是不够用了咋办? 这可是 PC 平台 4条 DIMM 的极限容量了。再增加容量就只能上 64G 单条了。而64G单条又必须是Registered ECC内存,Unbuffered ECC不论如何都只有32G,再大没有了。</p>
<p>除了内存不足,还有一个窘境是 pcie 通道不足。目前我已经把 5600X 的全部 pcie 通道都利用上了,可是有好几个淘汰下来的 120G 的 NVMe 我也想插上发挥余热!</p>
<p>一不做二不休,干脆就上 epyc 平台把!7条满血的 PCIe 插槽!8条支持 RECC 的 DIMM!而且这些 pcie 槽还都支持拆分!
意味着买几个廉价的 <img src="/images/hyper_m2_x4.jpg" class="inline-img" style="height: 8em; display:inline-flex; width: auto; " /> 转接卡就能插上4 7 28个 nvme 盘!</p>
<p>经过2周的采购和等待,最后组装完成了第三台 NAS</p>
<p><img src="/images/epyc_nas.jpg" /></p>
<p>配置清单为</p>
<table>
<thead>
<tr>
<th> </th>
<th> </th>
</tr>
</thead>
<tbody>
<tr>
<td>主板</td>
<td>ASRock EPYCD8</td>
</tr>
<tr>
<td>CPU</td>
<td>EPYC 7282 ( 7302P 赶上缺货,改用 7282 )</td>
</tr>
<tr>
<td>内存</td>
<td>三星 RECC DDR4-2933 64G * 4 条</td>
</tr>
<tr>
<td>散热器</td>
<td>普通的 SP3 散热器,暴力风扇拆掉丢垃圾桶,换猫头鹰 NF-A9</td>
</tr>
</tbody>
</table>
<p>内存之所以上4条,没有插满8通道,一来是因为 7282 毕竟才区区 16核,用不到8通道的带宽。而来未来可以再买4条 64G 升级成 512G 内存。</p>
<p>好了,终于可以插上 8个 NVMe 盘了:
<img src="/images/8nvme.png" /></p>
ATX 机箱明明有7个pcie槽位
2021-03-07T00:00:00+00:00
https://microcai.org/2021/03/07/pcie-shortage-problem
<p>去年的 <a href="/2020/12/18/nas-upgraded.html">这篇文章</a> 里我受够了嵌入式 ATOM 处理器羸弱的性能而斥巨资购买了 锐龙 5600X 作为新的NAS机器的处理器。
通过 PCIe 拆分卡<img src="/images/pcie_bifurcation.jpg" class="inline-img" style="height: 8em; display:inline-flex; width: auto;" />实现了 HBA 和 10G 网卡同时接入。把主板的 pcie x16 的显卡槽给利用上了。</p>
<p>但是,近期发现了一些好东西,就琢磨着给NAS插上。但是想到这些好东西都需要占用主板的 PCIe 槽的时候就犯难了。
锐龙处理器一共就20条可利用的 pcie 通道(还有4条接南桥,不可利用了)。16条给了显卡槽,4条给了第一个 M.2 槽。
<em>PS, intel 的桌面处理器算上南桥的4条也才20条pcie通道。更糟糕</em></p>
<p>给显卡的那条我已经用带拆分的延长器给弄成了2条 x8 的,一条接了 HBA 一条接了网卡。可是,想多接几个 pcie 设备又怎么办?</p>
<p><em>PC 平台最大的缺陷是PCIe通道数不足。</em></p>
<p>甚至intel就靠卖pcie通道数赚钱。</p>
<p>明明 ATX 标准设定了7个扩展槽位!而ATX显然是PC的标准,不是什么服务器的标准。</p>
<p>虽然 ATX 标准是 PCI 时代的产物,但是 PCI 进入 PCIe 时代,主板也应该是把7条PCI槽升级为7条pcie槽。而不是就变成光秃秃的秃驴,就剩下显卡一个槽,剩下的就给 pcie x1 的打发乞丐。而且凑数的 pcie 还是南桥出的,还和 SATA/M.2 有冲突,二选一。</p>
<p>想到这点,我突然明白了为什么有人说,<em>EPYC 的精髓在单路</em>。</p>
<p><img src="/images/ROMED8-2T-2(L).jpg" width="75%" /></p>
<p>看这个主板,齐刷刷的7条pcie多漂亮!</p>
SFP+ 的下一代,是 SFP112
2021-01-01T00:00:00+00:00
https://microcai.org/2021/01/01/sfp112
<p>以太网的标准,通常以10倍来翻倍。10M 100M 1G 10G 100G.
10G 的下一代,应该进化到 100G,可是中间横插入了 40G。这不伦不类的 40G 不仅仅速度不清真,更重要的是,他把以太网带入了歪路。导致了 100G 以太网接口有了3重版本。</p>
<p>以太网迈入光纤时代,不管是 100M 还是 1G 还是 10G,都使用的一个漂亮的接口,SFP。SFP 接口包含了一对数据链。模块只做电光转换。协议处理由后端负责。</p>
<p>简单,优雅。</p>
<p>可是,横插入的 40G 以太网打乱了这个简单优雅。设计了 QSFP 接口。一个 QSFP 其实是4个SFP的简单叠加。重用了供电和其他功能性引脚,但是直接塞入了4对数据链。所以相比4个SFP接口,体积还是稍微小了点。</p>
<p>但是,这个 QSFP 接口,带来了无穷的混乱。首先就是 4对数据链路导致 QSFP 模块必须插8根光纤,进而让LC SC 这种简单的耦合设计无用武之地。使是有些只有2根光纤的,拿也是在模块内部使用了波分复用,使用了4条激光发送数据。一对光纤一对路的原则被破坏殆尽。我很不喜欢 QSFP。这更导致了 QSFP 没有 bidi 模块(使用一条光纤同时收发的模块)。</p>
<p>第二个混乱在于,QSFP 被证明用户被强奸后也默默接受了,于是 100G 以太网还要使用4对链路收发。不过谢天谢地没有使用8对链路。但是,标准委员会又引入了不兼容,搞了 QSFP28 接口。</p>
<p>有 QSFP28 就有 SFP28 了。大小也不一样了,直接物理层不兼容。一下子整出了4个模块接口。SFP SFP28 QSFP QSFP28. 除了 SFP 能塞进 100M 到 16G 之间的各种速度(兼容性大大的好)。。。 其他的都只为以太网的一个特定速度设计。</p>
<p>这样,10G 并不能平滑的升级,因为不管是 25G 40G 还是 100G,速度,接口都不一样。</p>
<p>也许是觉得这个局面不够混乱,以太网又整了个50G出来。</p>
<p>50G 怎么来的呢? 是200G 的 1/4 版本。</p>
<p>以太网已经在4对链路的路上开始放飞自我了。 200G 的以太网搞出来,于是他的单链路版本 50G 自然就出来了。</p>
<p>可是, 50G*2 = 100G 不是吗?</p>
<p>于是 100G 以太网迎来了2个互不兼容的版本。25*4 版本,和 50*2 版本. 这个 50*2的版本呢,自然就叫 SFP-DD 了。至于为啥不叫 DSFP56 就不知道为啥了。</p>
<p>但是,200G 相比 100G 只倍数提升,面子挂不住。考虑到 10G 到 40G 已经整了一个 4倍提升,于是 400G 以太网就这么制定出来了。</p>
<p>也许是觉得,新标准新气象,不能在多链上太过于狂奔,400G 以太网没有使用 50G*8的方式达成,而是使用了 100G*4. 还是熟悉的4对链路。</p>
<p>这下糟糕了,这 100G*4,当中的每一条,不就是完整的 100G?
于是 100G 以太网又又又迎来第三个不兼容的标准,SFP112.</p>
<p>不管是原来的 QSFP28 还是后来的 SFP-DD,100G 以太网真正算的上革新的技术,还是 SFP112. 回归原始,使用单链而不是4链组合完成 100G 的速度。这意味着,又可以有 100G 单纤的 bidi 模块可以用了。</p>
<p>所以, SFP112 才是真正的 100G 以太网。真正的 10G 以太网的继任者。</p>
升级了NAS机器
2020-12-18T00:00:00+00:00
https://microcai.org/2020/12/18/nas-upgraded
<p>上次折腾NAS, 最后搞了8个8T的盘阵列。但是,我愈发的不满意 C3558 的性能。准备替换掉它。但是又觉得C3558花费巨大,没用回本实在不甘心。</p>
<p>最近几个月,zfs 频繁出现 checkcum error。但是诡异的是,总是 sda sdb sdc sdd 这4个盘出现。</p>
<p>感觉主板出了问题。或者是机箱的背板出了问题。不论哪个出了问题,都到了让C3558退休的时候了。</p>
<p>既然要退休,就要想好继任者。继任者在 MIPS (龙芯) ARM64(鲲鹏) 和 x86 里。有缺点都特别明显。</p>
<p>龙芯</p>
<ul>
<li>优点
<ul>
<li>从 bios 到 内核 到 userland 整个都是开源的。方便折腾。</li>
<li>bios 可以通过串口访问。取消了 IPMI 的依赖依然可以不接显示器键盘进行配置</li>
<li>低功耗
当然,缺点也很大。第一个缺点就是性价比太低。然后性能也跑不满万兆网络。说不定还不如 C3558 呢。</li>
</ul>
</li>
</ul>
<p>鲲鹏</p>
<ul>
<li>优点
<ul>
<li>arm 架构在 linux 里的支持度不亚于 x86. 比 mips 好太多。</li>
<li>性能足够,能跑满万兆</li>
<li>进行嵌入式开发的时候可以直接为开发板编译软件,不用在 pc 上配置交叉编译环境。</li>
<li>bios 依然可以通过串口访问。无需依赖 IPMI</li>
</ul>
</li>
</ul>
<p>除了性价比太低,么有特别的缺点</p>
<p>x86 的选择就大了,最终我在3个方案里选择。</p>
<ul>
<li>方案1
intel E3 神U + ECC 内存 + SAS HBA + 万兆网卡 + 4U 8盘 380mm深度机箱。</li>
</ul>
<p>这个是翼王使用的配置。缺点是他是牙膏厂的东西。吃了C3558的亏后不想用牙膏厂的东西,而且14nm的能耗比也差。</p>
<ul>
<li>方案2
嵌入式epyc itx 主板 + ECC 内存 + 万兆网卡 + 2U 8盘 450mm深度机箱。</li>
</ul>
<p>嵌入式epyc有很多 sata 口,所有只要再加一条万兆网卡就可以了,因此可以使用小板,这样就能使用2U的小机箱了,
机柜能更简洁。缺点是嵌入式 epyc 的主板价格不厚道不说,还不好买。需要等美国进货。</p>
<ul>
<li>方案3
amd ryzen cpu + A系列小主板 + ECC 内存 + SAS 卡 + 万兆网卡 + pcie 1分2拆分延长线 + 2U 8盘 450mm深度机箱</li>
</ul>
<p>这个方案使用的是 ryzen 系列的 cpu,毕竟 7nm 的 cpu,能耗比是所有方案里最好的。但是这个方案的缺点就是 PC 平台
没有 ipmi。需要调整 bios 或者 内核启动出错的时候,必须依靠显示器。而且 ryzen 并没有核显,需要拔掉 pcie 设备插
上显卡。。。 非常的折腾。但是 pc 平台最大的优势就是性价比突出。正好赶上5600X 上市了,于是没犹豫多少就买了
5600X 和 微星的 A520ITX 主板。</p>
<p>itx 只有一条pcie x16, 怎么接 sas hba 和 万兆网卡呢?答案是 pcie 拆分。
然后就是烧了好近千大洋,买了多种无 PLX 的 pcie 拆分卡,最后才找到了一个能完美工作的拆分卡。有 PLX 的卡第一缺点钱是贵,第二缺点是卡上的 PLX 本身是发热大户。第三缺点是 PLX 也增加了pcie 的延迟。第四缺点是我 5600X 本身是支持 pcie 拆分的,干嘛不用白不用? 最终能用的拆分卡如下
<img src="/images/pcie_bifurcation.jpg" /></p>
<p>A520 主板有很多,选微星的一个很重要原因是微星主板上有串口。虽然不是IO挡板提供的,但是主板上有一个 10pin 的 COM header 插座。淘宝上能很容易的买到 ISP转db9 的转接线+pci挡板。而备选的华擎A520则没有 COM 的针脚。这两个主板都是在官网明确说明支持ECC内存的。虽然 ryzen 支持ECC内存,但是也需要主板bios的配合。所有其他牌子没说明支持ECC的就pass了。</p>
<p><img src="/images/isp_to_db9.jpg" /></p>
<p>有了串口,虽然pc的 bios 本身不能输出到串口,但是 grub 和 内核能啊!至少可以通过串口操作 grub ,就能编译内核失败的时候在 grub 里换内核。内核能操作串口,就能在需要进入 emergency shell 的时候恢复系统。只是 bios 设置无法调整了。所有插上显卡,配置好参数后,就别再折腾了。</p>
<p>最后,是机箱。受限于我的机柜,只能使用500mm深度以内的机箱。那种650mm深的正经服务器机箱就不能用了。
一开始 C3558 使用的是 3U 8盘的机箱。这种机箱最大的问题是硬盘散热不足。机箱背面的排气风扇,排出机箱热气的同时,是直接将硬盘下方的进风口的冷空气吸入。硬盘本身的积热没有解决。</p>
<p>所有我考虑再三,买了2U 的机箱。这种机箱,风扇直接在硬盘后面抽,冷空气要想进入机箱,必须首先经过盘位。从硬盘盒的前方开孔进入盘位,流过硬盘后再被硬盘和主板之间的4个 8cm 风扇抽入主板区域。在这个区域为 cpu 散热后,通过机箱后部的风扇和电源风扇排出。
<img src="/images/2U_8pan.gif" /></p>
<p>这种可以看到,主板只能放 itx 的。但是 pci 挡板位却不止一个(itx只有一个 pcie),显然是要 itx + pcie 拆分卡。(^_^)</p>
<p>终于,采购齐全,把机器装起来了。</p>
<p>且慢,为啥我折腾了那么久,花了好几千,只是提升了一下cpu的性能?好像容量也没有增加啊!之前C3558没有使用 sas hba 卡,现在都上了 sas hba 卡了,怎么着也得弄个 sas 背板吧?</p>
<p>没错,可是带 sas 背板的,都是 650mm 深的大机箱啊!用不了用不了。</p>
<p>等等,经我耐心的搜索,还真给我找到了。</p>
<p><img src="/images/4U_24pan_500mm.png" /></p>
<p>这个机箱只有 480mm 深,内部依然紧凑,只能使用 itx 主板。但是 pci 挡板位却不止一个(itx只有一个 pcie),显然是要 itx + pcie 拆分卡。(^_^)。</p>
<p>虽然能上 ATX 电源,但是小空间下放 atx 电源,内部气流容易不畅,so我买了 SFX 电源+ SFX转ATX传接挡板。</p>
<p>最后,在 jd 突然看到 999 的 2T ssd 。。。。。 还有最后一个 光威Pro NVMe 500G 。。。。</p>
<p>就这样,花了万把块钱,升级了 NAS。
最终的成品图片如下</p>
<p><img src="/images/nas_v2_final.jpg" /></p>
读书人最容易出卖灵魂
2020-07-26T00:00:00+00:00
https://microcai.org/2020/07/26/faked-freedom-speech
<p>咱国家其实对明朝研究的特别少。特少。</p>
<p>很多人说宋代是文人最好的时代。其实大错特错。 宋代的文人只能写词。明代的文人直接执掌国家。</p>
<p>很多读书人被明朝皇帝打死,你说明朝读书人有骨气。</p>
<p>当我真的去研究明朝的那些事的时候,才知道哪里是骨气。分明是利益。</p>
<p>明朝的时候,税只能向农民收。如果要向商人收税,那是要被读书人批评为“与民争利”的,他们为了阻止皇帝收税,是宁可被打死的。征辽响的时候,他们不会劝皇帝与民休息。征商税的时候,朝廷大员们是要“死谏” 的。</p>
<p>什么“与民争利”,“藏富于民”, 这里的“民” 一直都是指那些读书人。对读书人不好,还舍不得下杀手的,那一定是暴君。要向读书人收税,那一定是横征暴敛,一定是民不聊生。但是,只要能痛下杀手,读书人的膝盖又是最软的,一定跪下来称你是民心所向,是太平盛世。</p>
<p>所以平头老百姓啊,千万不要听读书人忽悠。读书人说天下太平的时候,那可能只是说读书人有出路。读书人说民不聊生的时候,那可能只是朝廷不喜欢那些穷酸秀才。</p>
<p>秦始皇坑几个犬儒,就是暴君了。满清皇帝嘉定三屠,还是圣君。很多时候,百姓是不能发声的。要有个好名声,做好事是没用的,只需要管好读书人的嘴就够了。因为读书人的膝盖是最软的,最容易出卖灵魂。</p>
<p><del>读书人里,又以山东的读书人膝盖最软。孔圣人跪过蒙古大汗,也跪过满清皇帝。满清灭亡后,还跟着蒋介石去了台湾。如今跟着绿营认了日本人做父亲。 留在山东的就更没骨气了,要量山东之学子,结黑爹之欢心。</del></p>
<p>要想读书把灵魂卖给你,就需要有天命。有了天命,读书人的膝盖就跪过来了。所谓天命,就是可以让读书人对你进行双标的光环。有了这个天命,入侵他国就不再是入侵,而是吊民伐罪。不论做什么都没事,自有天下的读书人帮你洗。</p>
国产汽车战略秒棋-tesla上海工厂
2020-07-15T00:00:00+00:00
https://microcai.org/2020/07/15/tesla-and-china-ev
<p>汽车这个产业也有上百年的历史了,要想在这个行业崛起,难度非常大。我们国家选择了弯道超车,就是压宝电动车。</p>
<p>国家发展电动车,最大的困难是什么?</p>
<p>有人说,是续航短。有人说,是充电麻烦。</p>
<p>但是,这其实都不是最大的困难。</p>
<p>最大的困难在人心。</p>
<p>为什么不买电动车?充电?续航?这都不是真正的考虑。其实,让中国百姓不想买电动车的原因,就只有一个:只能选国产。 国产就是原罪。所有汇集到电动车上的槽点,无非是国产车里挑刺思维的延续。</p>
<p>任何产品,都有优点和缺点。完美的产品是不存在的。优点能满足你的需要就买,缺点是可以忽略的。</p>
<p>但是如果你不喜欢一个产品,那优点是看不到的,缺点是可以无限放大的。直到所有缺点都被解决,你还是会说最后一句话气死销售:“我就是不喜欢它啊!“</p>
<p>放到汽车上,道理是一样的。当车企专注于 fix 电动车的缺点的时候,其实忽略了消费者最简单的心理:他找的缺点都是借口,他只是单纯的不喜欢你而已。</p>
<p>这个道理,国内一大票造车的都没懂,太平洋彼岸的一个传销头子领悟到了。与其去不断的 fix 电动车的固有缺陷,不如直接无视这些人,把优点发挥出来,让喜欢的人喜欢,让讨厌的人滚蛋。</p>
<p>这个思维,成就了 tesla。</p>
<p>那么回到国内,为啥引入 tesla 是国家新能源汽车战略中最妙的一招棋呢?因为中国百姓,他不是不能接受充电慢,不是不能接受续航短。</p>
<p>他唯一不能接受的是,不能买国产车。</p>
<p>而电动汽车,是没有洋牌子的。既然没有洋牌子,那就不买。其他的都只是找的借口。</p>
<p>不买的永远都有不买的借口。真信了你就输了。</p>
<p>国家终于明白,要推广新能源汽车,就要先治好软骨病。而这个病,最好的解药是以毒攻毒。</p>
<p>没有洋牌子,就先扶一个洋牌子进来。这个洋牌子,最好是不掌握电动汽车核心技术的。日后打压起来轻松。</p>
<p>放眼过去,能造好电动车的洋牌子,就在德日美三个国家里找。德国深耕石油车百年,在电车领域已经变成了阿斗。能打的一个都没有。排除了德国,就是日本和美国。</p>
<p>日本鬼子,虽然电动汽车看起来没啥本事,但是,日本有强大的机电产业!放日本车进来,别看小日本今天没啥电动车,可是人家三电技术哪个没有? 电池电池造的好,电机电机造的好,电控。。。 那玩意就是功率半导体,也是小日本的强项。</p>
<p>所以,日本车,因为后劲过于强大,绝对不可引进。不仅仅不能引进,还要极力打压!</p>
<p>剩下的就是美国了。</p>
<p>美国的电动车,就和他家的手机一样。只有一个能打的牌子,还特别能打。但是都有致命缺陷:只有营销手段没有核心技术。</p>
<p>美国的电动车,电机靠台湾,电池靠日本,电控自己虽然有,但是不如小日本,也不如中国的。靠的吹牛皮不死人的营销无敌技术占领的大市场。</p>
<p>但是,美国的汽车犹如南山必胜客,看起来很强大,但是,是压根上不了美国制裁的实体清单。</p>
<p>但是,中国的电动车推广,需要的并不是技术,恰恰是美国佬那不要脸到极点的营销技术。用美国的营销手段,把中国的老百姓给洗脑成接受电动车,则国家战略成咦。反正美国佬的车,其实从根上来说,就不讨中国人喜欢。</p>
<p>等美国佬完成洗脑中国百姓的目的,他就可以被抛弃了。</p>
<p>为什么上海要求tesla的所有零件实现国产?因为电动车全产业链都在中国。tesla完全可以100%使用中国零件。</p>
<p>而使用100% 中国零件的 tesla,完成了使命后,只需要把 logo 换成中国姓,改动如此小,百姓接受起来也就容易了。最难的,接受电动车这个,已经由洋大人完成了洗脑。剩下的,无非就是 logo 换成中国字而已。接受起来不要太轻松。</p>
<p>李斌是第一个吃透了国家战略的人。他把 ES8 卖给了 Tesla 车主当成第二个玩具。轻轻松松就做到了那是duck上市。</p>
<p>不管 tesla 卖的有多好,最后都是在帮国产电动车培养用户。因为 tesla 的核心技术,其实在中国人的手里。</p>
<p>千万不能让小日本的电动汽车起来!必须要打压,严厉的打压。美国人不会造电池,他小日本会。美国人不会 cost down ,他小日本会。</p>
高压钛酸锂电池组完成
2020-07-11T00:00:00+00:00
https://microcai.org/2020/07/11/high-voltage-battery-done
<p>大部分交流设备,其实都有一个前置的全桥整流器,把交流输入转换为直流,然后内部再根据需要进行各种变换获得需要的电能。</p>
<p>这意味着,其实所谓的 AC 输入,倒不如说是支持无极性直流输入。</p>
<p>既然如此,为啥还要搞逆变器呢?直接把电池输出怼上去就完事了。</p>
<p>问题是,上哪找220v的电池?</p>
<p>既然没有,那就自己组一个吧。电池嘛,串起来不就有高压了。</p>
<p>如果使用的是铅酸电池,倒是这个理。</p>
<p>可是,铅酸电池我不喜欢啊!要用就要用锂电池,而且得是钛酸锂。一旦用了锂电池,就有问题了,需要 BMS。</p>
<p>而 BMS 就限制了DIY的时候能串的电池的个数了。首先就是市面上最最最常见的 BMS: 达锂和蚂蚁。最多都只支持到 32节电池。</p>
<p>32节电池只能实现。。。 2.2V*32 = 70.4v 的电压。</p>
<p>两个保护板一起串起来呢?咨询了BMS厂商后得知并不能串联。容易烧板子。</p>
<p>但是新能源汽车倒是串联很多电池。。可见管理几百节串联的 BMS 是有的,只是用的汽车上,DIY 市场上没出。</p>
<p>这个事情就作罢,一直作罢了几年。直到最近突然给我找到了。</p>
<p>淘宝上找到了一家做锂电池BMS的,支持最多96节电池串联。支持钛酸锂。他的设计是使用 控制卡 + 采集卡 的分体构造。一个采集卡可以采集 12节电池的信息。 控制卡理论上支持采集卡无限级联,实际上软件只支持到8张采集卡。 12×8 = 96。这就是96节限制的由来。采集卡和控制卡之间使用的 RS485 传递信息。因此采集卡和控制卡不共地,也就是说控制卡不受总电压限制。唯一限制总电压的地方,其实用来 关断电池 的 MOS 管的耐压。然而,他可以使用继电器代替 MOS管。</p>
<p><em>小知识: 为什么如果共地会有总电压限制呢?</em></p>
<p><em>信息通信需要电平。如果采集卡和控制卡使用普通的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 的原因。更多的电池需要更高耐压的单片机输入电平转换电路。更糟糕的是,逻辑电平的电压还会随着电池的放电进行迁移。大大增加电平判定难度。</em></p>
<p>到了以后发现这个pcb板子不好固定,</p>
<p><img src="/images/bms-look.jpg" /></p>
<p>所以3D打印了一个卡座</p>
<p><img src="/images/bms-shell-3d-print.jpg" /></p>
<p><em>是的,这是第一次使用3D打印,从此爱上了3D打印。。。 后来为了可以不用等好几天,就干脆自己买了3D打印机。。。</em></p>
<p>由于我不喜欢继电器,所以还是让他发的 MOS 管版本。耐压 250V。为了安全起见,我决定使用 6×12 = 72S 的模式。总电压是 2.2v * 72 = 158V 。充满电电压为 2.65*72 = 191V.</p>
<p>这个电压虽然比220v地。但是只要是开关电源设备,一般都支持 90v-240v 宽压输入。换算为 DC 则是 130v-370v 电压。这个电池满电到没电的电压范围正好落在大部分开关电源的输入电压范围。不到 200V 的电压也更安全。</p>
<p>BMS采购到了,接下来就跑去买了72节拆机钛酸锂电池。买了青棵纸之类的东西。花了好几天的时间组装起来了。一天组装一组12S。呵呵。老费劲了。还得焊接好采集排线。最后把 12片电池和采集模块一起封成一个30V 的电池包。然后6个电池包再串起来。最后接上控制板。</p>
<p>当然,还有 外壳。用了环氧树脂板,花好图纸,让淘宝的卖家直接 CNC 切割好了发回来。然后组装 起来。</p>
<p>为了把BMS固定在外壳上,还专门3D打印了一个底座,把 BMS 固定在3D打印好的底座上,再把底座固定到外壳上。</p>
<p><img src="/images/show-hvdc-lto.jpg" /></p>
<p>最后大功告成,上图是只差最后一布组装的样子。比带了外壳更容易 show off 出来这是一个 diy 的电池。</p>
<p>这个电池当然装好了就吃灰了。因为我不是需要一个这样的电池,我只是单纯的享受设计,采购零部件,然后组装起来的乐趣。</p>
<p>PS: 下一步,为它折腾一把太阳能充电。</p>
入手了一个 rock pi 4b
2020-06-13T00:00:00+00:00
https://microcai.org/2020/06/13/bought-rockpi4
<p>闲来无事,又打起了ARM的主意。Rpi3 的性能实在受不太了。毕竟只有 100M 网络。内存也就1GB。想干点啥都不行。寻思着弄个性能好点跟的上时代的 arm 板子玩玩。</p>
<p>先是找 archlinuxarm 看看有没有啥支持的板子。结果发现都不是啥好板子。后来搜 香蕉派的时候,发现了 rock pi。rock pi 4b 看参数,给力。于是下单了一个 4G 内存的版本。这 rock pi 最吸引我的地方是有一个 M.2 插槽,而且支持从 nvme 启动系统。终于可以丢掉渣 SD 卡了啊。</p>
<p>于是马上下单买了一个。</p>
<p>过了几天到了,把官网上提供的 debian 系统给 dd 到 nvme ssd。然后发现无法启动。</p>
<p>先研究 nvme 启动吧。cpu 并不支持从nvme 直接启动。或者说 cpu 内带的 bootrom 只支持 SPI/sd/emmc 三个启动方式。那么就需要先让 bootrom 载入一个 loader,这个 loader 再从 nvme 载入系统。但是我又不想插 sd卡,于是就需要有 SPI flash。</p>
<p>然后他这个板子是有焊接上 SPI Flash的,但是却没有烧bootlader进 Flash。</p>
<p>所以默认还是只能 SD卡启动。于是先把他官方提供的 ubuntu img 给 dd 到 SD 卡然后启动。</p>
<p>然后按照 wiki 把 spi 版的 uboot 给刷入板子上的 SPI flash。</p>
<p>于是 nvme 上的 debian 可以在不插 sd卡的情况下启动了。</p>
<p>但是!我讨厌 debian 啊!</p>
<p>于是最简单的做法,就是把 rootfs 给删了,把 Archlinux-aarch64-latest.tar.gz 给解压到 rootfs分区。</p>
<p>然后boom,可以开机进入了 arch。</p>
<p>but ,内核还是他 4.4 的 debian 内核。4.4 内核太老了!连 io_uring 都么有。这怎么可以,我要用 arch 自己的内核启动!</p>
<p>于是折腾 boot 好几天,都失败告终。一直黑屏,也不知道问题出在哪里。还自己编译了 uboot 和 内核,都失败告终。就是不能启动。</p>
<p>最后,最后的最后,我还是低头了,买了一个 USB TTL 线。。。。</p>
<p>然后果然就看到 内核的错误输出了。。。。 果断修正。。。</p>
<p>终于在昨天把系统折腾好了。</p>
<p>还有个小插曲,买的 USB TTL 线居然不支持 1.5M 波特率。。。搞的我只好重新编译 uboot 设定默认波特率为 115200. 然后才能看到 uboot 日志,修正了 extlinux.conf 的写法,然后看 kernel crash 日志,再重新编译内核。</p>
<p>最后,尝试了一下arch的自带内核,看到了日志,发现原来是他打的 initramfs 没有 nvme.ko …. 手动添加到配置文件里重新打包就解决了。。。</p>
<p>顺便安利下 Gentoo,交叉编译内核和 uboot 的时候,本以为会很麻烦,结果 crossdev -t aarch64-linux-gnu 一条命令搞定交叉工具链。。。。。</p>
<p>Gentoo 果然是最适合程序员的操作系统!</p>
为家里所有设备一次更新 ipv6 动态域名
2020-04-27T00:00:00+00:00
https://microcai.org/2020/04/27/all-device-ipv6ed-and-ddnsed
<p>来了! ipv6 真的来了! 最近回一趟老家,发现老家的 ipv6 也可以用后, 家,公司,老家1,老家2 四个地方的 ipv6 就全部都能互通了。这样,就没有理由留着 ipv4 了。我甚至觉得搞个公网 ipv4 地址是多么可笑的一件事。</p>
<p>全ipv6化以后,为了互连方便,我把所有的支持 ipv6 的设备,都配置了动态域名。编译了 dnspodc 还有交叉编译了 dnspodc 到各种设备上。但是,打印机让我头疼了。打印机支持ipv6, 但是不支持运行自己写的程序。</p>
<p>而且为那么多设备一个一个编译并配置 ddns 更新程序,着实让我有点筋疲力竭。我开始思考,让一个程序自动为所有设备都更新 ddns。</p>
<p>ipv6 地址,使用的是 prefix + host 的方法构成。不同于
ipv4 时代 prefix 按照 /8 /16 /24 分成了 A B C 三类地址,后来又发明了 CIDR 无分类地址,可以使用任意长度的 prefix, ipv6 时代建议 prefix 永远等于 64 。低于 64 的 prefix 只用于地址分配机构分配地址,还有路由聚合简化路由表的时候用。实际过程中进行地址分配,一个局域网下永远让 prefix=64。</p>
<p>这么做有个什么好处呢? 就是 无状态地址自动配置 能发挥作用。而且 64bit 的 prefix 意味着 主机地址可以有 64bit, 也就是说永远不会遇到 主机位不足的情况。 ipv4 时代使用 C 类地址的时候,就容易遇到局域网超过 255 台机器的情况。好不容易划好的网络,增加一台可能导致整个网络重新规划。而 64bit 的 prefix 一劳永逸的解决了这个问题。</p>
<p>无状态地址自动配置的方法是,由网关每隔一段时间广播一个 RA 包。RA 包里包含了 prefix , dns,网关 等等信息。主机收到 RA 后, 把 prefix和自己生成的 host 一拼接,就 获得了 全球可达的 ipv6 地址了。</p>
<p>在 PPPoE 拨号环境下, prefix 是由 dhcp-pd 协议由 ISP 下发给路由器的,所以 prefix 会不断的变化,是动态的,所以需要设置动态域名。但是 host 部分是不会变的。 host 部分是使用 mac 地址经一定规则变换而来。这个规则叫 EUI-64。对于不需要被外部访问的主机来说,可以开启 mac 地址随机化和隐私保护,使用随机的主机地址。当然不想被访问也就不会有动态域名的需求了,所以这里只考虑使用固定的 EUI-64 方法构造的 ipv6 地址。</p>
<p>这样只要把需要更新动态域名的设备的 mac 地址收集起来,就可以一次性构造出所有设备的 ipv6 地址,不需要在每个设备上单独运行 ddns 程序。</p>
<p>所以,在运行 ddns 的设备上 (可以是路由器,可以是 nas ,可以是一个 树梅派)获取本地地址,取前64bit,然后依次拼接 eui-64 就可以获得一系列设备的 全局ipv6 地址,然后再调用 dns 供应商的接口更新上去,就完成了。</p>
<p>于是搞定后,获得了2个优势:</p>
<ol>
<li>无法运行自定义程序的设备也能更新dns</li>
<li>无需在每个设备上设定dns更新程序</li>
</ol>
自由市场思维毁了区块链
2020-04-20T00:00:00+00:00
https://microcai.org/2020/04/20/free-market-also-failed-blockchain
<p>西方的自由市场思维很有诱惑力,然而实际上根本行不通。拿 99 碗饭 100 个人来说, 这饭要如何涨价呢? 无论涨到多少,终会有一个人买不到然后饿死。</p>
<p>这个时候根本就不能允许自由涨价。很多人说,那是粮食有特殊性,别的物资可以随便涨价吧?
但是,谁来定义所谓的 “特殊商品” 呢?在美国肺炎出现前,也没有人会认为口罩是特殊商品,不能涨价吧。</p>
<p>一些人可能会说,涨价大家才有动力增产。这又是在说胡话了。这个世界产业大分工下,大部分制造业者早已放弃了制造自己不擅长的东西了。每一个商品的背后,都只是寥寥数个参与的品牌。新进的人已经很难在参一脚了。何况有一些行业还设立了特殊的壁垒。涨价就有新竞争者入局增加供给,只能说理想很美好,现实很骨感。</p>
<p>而受西方自由市场思维洗脑最深刻的中本聪,也陷入了自由市场的陷阱。他让区块链的交易费用“市场化竞争” 。看起来 “出价最高者,他的交易被打包” 这个游戏规则非常公平而天经地义。但是实际上是落入了陷阱。因为不论你出价多高,比特币的容量是无法增加的。每10分钟只能打包一个区块,每个区块只能有 1MB 字节。1MB字节只能打包大约2000-3000笔交易。这就是打包比特币交易这个商品的供应上限。 在一个有供应力上限的市场里搞自由竞价。结果就只能是所有参与者都被迫无限推高支付的手续费,直到达到“所能承受的手续费上限”,一旦突破承受极限,只能放弃使用比特币网络割肉离场。</p>
<p>最终的结果就只能是在小圈子里自high.</p>
<p>大幅降低交易成本,乃是世界趋势。这个最基本的经济原理,中本聪不懂,他就懂了个皮毛的自由市场。</p>
买不到好笔记本
2020-04-07T00:00:00+00:00
https://microcai.org/2020/04/07/missing-good-notebook
<p>笔记本市场市场竞争非常激烈,从业者都说本子难卖。然而我准备更新我用了4年的老本子的时候,却发现无本可买。</p>
<p>为啥我要买一个新本子呢?因为不够用了。现有的笔记本内存只有16G, 想想现在的软件个个都是吃内存大户,干活编译代码的时候只能把浏览器关了。 有时候关浏览器还不够, 还得把运维工具也关了才能腾出内存。不然编译器就内存不足崩了。敲着代码还得上爆栈的时候,敲着代码还得 ssh 到服务器上调试的时候, 都非常的抓狂。</p>
<p>所以我急需要 32G 内存的机器。</p>
<p>这个本子买过来4年了,几乎就没拿它打过游戏,那闲置的GTX960M显卡一直浪费电源浪费重量浪费散热能力浪费¥。所以我希望下一个笔记本, 一定一定的不要有独显。这样可以更轻更薄更持久。</p>
<p>微软吃 Surface Book 老本好多年了, 居然吃到后来吃退步了, 本来我打算更新下代的 SB 本,结果大失所望, 新一代的 Surface 居然屏幕分辨率缩水了。所以,为了轻便着想,下一台笔记本必须 4k 起步,最好 5k 8k 分辨率了。但是尺寸不能大, 大了不便携。 14寸是能接受的极限了。不能再大了。</p>
<p>这3个筛选条件一套,已经是 0 符合要求的了。 何况我还有第四个要求,就是一定要 7nm Zen2 的 cpu 。。。。。</p>
<p>好了, 直接啥也买不到了。</p>
<p>多少钱都买不到。</p>
<p>钱买不到的东西又多了一个,叫 程序员用的笔记本。</p>
1840 年来百年的屈辱换来的教训
2020-03-19T00:00:00+00:00
https://microcai.org/2020/03/19/learnt-from-1840
<p>1840 年鸦片战争爆发, 到1949年, 中国开始了超过一百年的屈辱. 要说这一百多年的屈辱让这个民族学会了什么的, 那就是一定要工业化. 只有工业化, 才能救中国.</p>
<p>为什么说只有共产党,才能救中国, 因为纵观1912年到1927年, 中国学习了西方的政治制度, 成立了几百个大大小小的政党, 但是没有救到中国. 归根结底的原因是, 这些政党不是工业党. 不是西方化, 就能救中国, 而是只有工业化, 才能救中国. 而共产党恰恰是唯一的一个工业党, 不仅仅是中国唯一的, 目前也是世界唯一一个工业党.</p>
<p>苏联大部分国土都在北寒的冻土地带, 只有共产党带去了工业化, 苏联才崛起成为世界一极. 而苏共倒下后的俄罗斯, 沦落为一个资源国, 丧失了工业能力, 再也掀不起浪花.</p>
<p>为什么说, 共产党是唯一一个工业党. 因为共产主义追求的, 是物质极大丰富, 而物质极大丰富需要追求极高的生产力, 极高的生产力只能靠工业化才能达到. 所以共产党从诞生的那一刻起, 就是一个坚定的工业党.</p>
<p>而只有坚持发展工业, 才是真正的共产党. 这次抗疫深刻的揭示了一点: 工业部门不计报酬支援前线, 金融业和媒体汉奸辈出.</p>
<p>而助中国赢得胜利的, 是强大的工业部门.</p>
Mirror Efi Boot Partition
2019-12-31T00:00:00+00:00
https://microcai.org/2019/12/31/mirror-efi-boot-partition
<p>镜像 EFI boot 分区。</p>
<p>最近给电脑配了2个 nvme SSD 做 ZFS mirror . 每个盘都分了 2个分区, 一个 EFI system 分区, 一个 ZFS 分区。两个 ZFS 分区放到一个 zfs pool 里做镜像。但是 两个 EFI 分区。。。。怎么 mirror 呢?</p>
<p>首先尝试了 raid1, 发现 raid1 在盘上建的话, 会破坏 ZFS。如果用分区建 raid1, 则分区类型被修改为 linux raid, 而不是 EFI system partition 了, 主板就不识别了。</p>
<p>然后尝试的是挂到 boot1 boot2 两个分区, 后台写个脚本 rsync 拷贝同步。但是发现这个解决方案不够优雅。</p>
<p>如果能建立一个没有 metadata 的 mirror 设备就好了。</p>
<p>几经周折,发现了 dmsetup 命令, 可以建立不依托 metadata 数据的 mirror 分区。如果没有 metadata,就可以保留原汁原味的分区表,只是在系统层做数据 mirror, 无需主板的任何支持。</p>
<p>那么就只要用 dmsetup 设置 nvme0n1p1 和 nvme1n1p1 两个分区为 mirror。 然后把 mirror 设备挂到 /boot。</p>
<p>执行两次 <code class="language-plaintext highlighter-rouge">efibootmgr -c -d /dev/nvme0n1 -L "Linux Boot Manager" -l \\EFI\\BOOTX64.EFI</code> <code class="language-plaintext highlighter-rouge">efibootmgr -c -d /dev/nvme1n1 -L "Linux Boot Manager" -l \\EFI\\BOOTX64.EFI</code></p>
<p>为主板设定两个EFI分区的两个一模一样的引导项。</p>
<p>为了开机自动挂,又写了 boot.mount</p>
<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[Unit]</span>
<span class="py">Before</span><span class="p">=</span><span class="s">local-fs.target</span>
<span class="py">After</span><span class="p">=</span><span class="s">mirror-boot.service</span>
<span class="py">Requires</span><span class="p">=</span><span class="s">mirror-boot.service</span>
<span class="nn">[Mount]</span>
<span class="py">Where</span><span class="p">=</span><span class="s">/boot</span>
<span class="py">What</span><span class="p">=</span><span class="s">/dev/mapper/boot-efi-mirror</span>
<span class="py">Type</span><span class="p">=</span><span class="s">vfat</span>
</code></pre></div></div>
<p>和 mirror-boot.service</p>
<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[Unit]</span>
<span class="py">Description</span><span class="p">=</span><span class="s">Create EFI mirror</span>
<span class="nn">[Service]</span>
<span class="py">RemainAfterExit</span><span class="p">=</span><span class="s">true</span>
<span class="py">Type</span><span class="p">=</span><span class="s">oneshot</span>
<span class="py">ExecStart</span><span class="p">=</span><span class="s">/sbin/dmsetup create boot-efi-mirror --table '0 1048576 mirror core 1 1024 2 /dev/nvme0n1p1 0 /dev/nvme1n1p1 0'</span>
<span class="nn">[Install]</span>
<span class="py">WantedBy</span><span class="p">=</span><span class="s">basic.target</span>
</code></pre></div></div>
<p>这样挂 /boot 的时候会自动启动 mirror-boot.service ,也就是自动调用 dmsetup 把两个 EFI 分区给建好 mirror。</p>
3950X 再次拯救 Gentoo
2019-12-27T00:00:00+00:00
https://microcai.org/2019/12/27/3950x-save-gentoo-again
<p>多年前, 我抛弃了 AMD, 从 athlon 64 x2 升级到了 Sandy Bridge 的 Xeon E3-1230. 初代 E3 神U. 写了一篇文章, 说 i7 拯救了 Gentoo . <em>虽然 E3 从名字上来说不是 i7 就是了, 不过后来我把 1230 升级到了 i7-4790.</em></p>
<p>但是, 我从 E3 到 4790 的升级, 其实完全没有感觉到性能进步. 只是心理多了点安慰, 从老平台升级到新平台罢了. 那多 10%不到的性能提升, 实在是感觉不到. 后面的 6700 7700 8700 我都不再升级了. 都没有性能提升. 就守着老死的机器, 编译 Gentoo 越来越力不从心, 随着软件越做越大, 编译器越做越慢, emerge 需要的时间也是越来越大.</p>
<p>虽然后来有在工作上, 升级使用了 AMD 的 1800X 和 2700X, 不过自个家里的电脑还是老态龙钟的 4790 , 连K都不带.</p>
<p>因为 4790 用的是 DDR3 内存, 而接下来升级, 不管是 2700X 还是 i 家的 8700k 都要换全套了. 所以就一直耐心的等 3700X 的出现, 等 3700X 一发布我就升级! 结果到了年中的时候, 得知这次新一代U不仅仅是. 3700X 还有 3800X 和 3900X . 于是就等 7月7 的 3900X 了. 结果到六月底的时候, 又得到消息, 9 月发售 3950X !</p>
<p>那继续等吧! 于是错过了3700X 的车, 就这么等啊等, 到了9月, 得, 不仅仅 3950X 没发售, 其实 9 月的 3900X 还缺货涨价了. 就继续等啊等, 终于… 终于等到12月, 3950X 终于有货了.</p>
<p>3950X 的运气也好, 正好还清房贷后, 还有多点闲钱, 所以不用再等一个月了, 就立即出手了. 只一天功夫就送到了手上.</p>
<p>32个框框, 不是8个框框能比的, 64G 内存, 不是区区 16G 内存能比的. 编译 chromium 再也不用担心 OOM 了, 而且编译 chromium 再也… 不用等一个晚上了…..</p>
<p>3950X 再次拯救了 Gentoo !</p>
<p>PS, 这个 Gentoo 陪伴我从 速龙 时代到 Sandy Bridge 到 Haswell 由重新回到 Ryzen . 硬件都换过几茬了, 系统内的软件早已迭代更新了不知道多少个版本, 然而, 它还是09年我最初安装它的那个样子, 即使原来的硬件被咸鱼大佬收过去, 再组回来也不是我的那台, 我的电脑一直就陪伴我十年了. 特修斯之船问题解决!</p>
升级到4盘 raidz
2019-08-23T00:00:00+00:00
https://microcai.org/2019/08/23/upgrade-to-raidz
<p>上次折腾NAS, 使用了2个8T的盘建的 raid1. 速度不太满意. raid0 当然怕挂一个盘数据就没了, 索性就再买2个8T的, 组建4盘位raid5.</p>
<p>这阵列升级啊, 一般的做法是, 先建 raid5 新阵列, 然后把数据拷贝过去, 然后… 等等, 2个盘建什么 raid5. 那先把 raid1 取消镜像吧. 那也只有3个盘啊! 三个盘是能 raid5 问题是拷贝完了多一个盘, 而且我的目的是4盘 raid5.</p>
<p>咋办?</p>
<p>简单, 首先确实是需要取消镜像, 然后把剩下的3个盘, 和一个 假设备 一起建一个 4 盘的 raid5, 然后在数据拷贝进去之前, 把假设备从阵列里下线. 然后把数据从旧盘上拷贝到新阵列.</p>
<p>最后完成拷贝后, 把旧盘替换到阵列里取代下线的假设备, 然后等待 raid5 重建完成. 即可大功告成.</p>
<p>假冒设备的制作方法是</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#dd bs=1 count=1 if=/dev/zero of=/fakedisk.img seek=8T</span>
</code></pre></div></div>
<p>接着取消 sda sdb 的镜像</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#zpool detach pool1 sda</span>
</code></pre></div></div>
<p>然后创建新池并立即下线假盘</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#zpool create pool2 raidz sda /fakedisk.img sdc sdd </span>
<span class="c">#zpool offline pool2 /fakedisk.img</span>
<span class="c">#rm /fakedisk.img</span>
</code></pre></div></div>
<p>接下来就是 zfs send 和 zfs recv 迁移数据</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#zfs send pool1 | zfs recv pool2</span>
</code></pre></div></div>
<p>漫长的拷贝完成后</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#zpool destroy pool1</span>
<span class="c">#zpool replace pool2 /fakedisk.img sdb</span>
</code></pre></div></div>
<p>然后又是漫长的 raid5 重建的过程.</p>
<p>当然, pool2 盘已经可用了;) 重建的时候只是性能些许下降.</p>
<p>/fakedisk.img 文件因为是稀疏文件, 虽然文件大小达到了 8T, 但是实际上并不占用磁盘空间.</p>
<p>创建完 池后, 大小也是非常小的, 但是如果这个时候开始拷贝数据, 那么这个 fakedisk.img 文件占用的磁盘空间会</p>
<p>慢慢变大, 就把 NAS 的 根分区(在U盘上)撑爆了. 所以要在考试拷贝数据前, 将它强制下线并删除.</p>
<p>这个时候 pool2 就是3盘有效的降级状态. 放心, 这个过程中盘挂了 数据还是有2份, 一份在老盘里, 一份在阵列里.</p>
<p>最危险的时候, 其实是在最后重建raid5 的时候, 这时候挂了一个盘, 数据就没了 (笑)</p>
sfp 形态 gpon 光猫失败
2019-07-30T00:00:00+00:00
https://microcai.org/2019/07/30/gpon-sfp-onu-failed
<p>路由器上有一个 sfp 口, 有 sfp 模块形式的 gpon 光猫, 如果能用的话, 机柜里就少一个设备, 还少占用一个插座…少接条网线…..</p>
<p>最近听网友说 TW2362H 这个猫便宜, sfp 模块的. 于是买了一个, 插入路由器. 过了一会启动完毕后就识别出来了.</p>
<p>配置下路由器的网口 ip 地址, 然后 192.168.1.1 果然能进猫的页面.</p>
<p>然而, 这简陋的界面犯愁了. 无从配置, 有个能填写 LOID 的页面, 完了.</p>
<p>然后我才想起来, 我没有记录原来的猫的 LOID …</p>
<p>于是进入原来的猫的界面, 居然没找到能填 LOID 的地方… 才发现, 这个猫没准是用 SN 认证的.</p>
<p>然而这个猫的 web 并不能改 SN … 更糟糕的是, 这个猫连查看 光信号功率的地方都没有. 都开始怀疑是不是gpon猫了.</p>
<p>后来在俄国人的论坛上找了个固件. 刷进去后, 页面回来了. 倒是能看光功率了. 然后又学会了 telnet 进去用命令行强改 SN.</p>
<p>最后在命令行下查看链路状态, 已经进入 O5 operational 状态了, 但是, 怎么拨号都无法成功. pppoe 始终是无响应状态.</p>
<p>折腾了各种方式最后都无法成功. 失败告终.</p>
准备上 3950x
2019-07-16T00:00:00+00:00
https://microcai.org/2019/07/16/prepare-3950x
<p>听闻秒天秒低秒 i9 的 3950x 要在9月份发售了, 陪伴我多年的 i7-4790 终于可以退役了. 本来是打算9月进行退役的, 但是亲戚家有孩子今年开学需要用电脑了, 于是我就把电脑送给她了. 这样, 在9 月份到来前, 我就需要准备好一台备用电脑, 一台能随时升级到 3950x 的电脑.</p>
<p>主板呢, 肯定是选 X570 芯片组的, 这样日后只需要换 cpu 即可. 选主板花了不少精力, 因为我不想要 SLI , 不要花冤枉钱给 sli 税. 然后没 sli 意味着显卡插槽是实打实的 x16 而不会一不小心变成 x8 或 x4 . 然后主板上的其他 pcie 都乖乖接 pch 上 不会像某个傻逼主板, 一共5 条 pcie 居然 4条是接 cpu 上的, 要 x4+x4+x4+x4 模式跑 sli 四卡交火. 剩下一条还是 x1 的. 神经病啊, x4 的话显卡性能还能发挥吗? 然后要是加别的扩展卡不是显卡就一定不能 x16 了么.</p>
<p>然后选了好久敲定了几个符合要求的, 最后7月7号晚上9点发售的时候…. 一切的选择都是徒劳的, 直接买了最便宜的首发主板 😂 . 也就是 MSI 的 X570-A PRO</p>
<p>然后是内存, 因为 3950x 是 16核32线程的 cpu , 为了能发挥 cpu 的威力, 编译的时候 make -j32 是一定要的. 这样内存小了就会非常容易 OOM, 发挥不出 32 个框框的实力. 所以选择了买4条 16G 的内存.</p>
<p>找了半天, 16G 的内存只有 3200Mhz 的, 3600 乃至 4000 都只有 8G 单条. 无奈只好接受 3200 的条子了.</p>
<p>SSD 呢, 618 的时候就屯了一条国产ssd. 512G 才四百一.</p>
<p>最后是 cpu 上栽了跟头.</p>
<p>因为考虑到9月份才上 3950x 所以就买了个 APU, 没错, 賊便宜的 ryzen 3 2200G. 才四百多.</p>
<p>然后就翻车了.</p>
<p>无法开机, 内存,主板, cpu , 哪个都可能出问题, 非常的蛋疼, 无法确定到底谁坏了. 插拔插拔插拔插拔插拔无数次都失败告终.</p>
<p>最后去 msi 官网看看, 发现.. 支持的 cpu 列表里, 没有 2200G …..</p>
<p>这翻车翻的是猝不及防….</p>
<p>最后买了 2500X 过来. 然后因为没有核显, 只能再买个显卡, 买了个最便宜的 RX580 … 五百多, 便宜吧.</p>
<p>然后这个卡又翻车了, 花屏…. 又退回去换了一个新的过来.</p>
<p>真是折腾, 终于在上周日晚上的时候, 全部折腾好了. 能开机了.</p>
<p>此生第一次用 AMD 的显卡啊! 又要重新编译内核, 重新编译驱动 各种编译, 删 nvidia , 删 cuda …</p>
<p>最后驱动安装完毕, KDE 重新进去的时候, 发现了一个惊人的事实, AMD 的显卡延迟比 nvidia 的低!</p>
<p>这是怎么一回事呢?</p>
<p>原来事情是在 glXSwapbuffers 的实现上. nvidia 的实现策略, 导致 kwin 最终呈现在显示器上的图像是4帧前的世界. app 调用 swapbuffer 刷新界面后, 本来就引入了2帧的延迟了, 而 kwin 获得新的窗口内容后 , 合成到屏幕上, 再次使用 swapbuffer 刷新, 还要再引入2帧延迟, 结果就是总4帧的延迟. 禁用 compositor 后延迟就缩短到2帧.</p>
<p>而 AMD 的驱动, 开源的, 没有这个迟来迟去的 bug. 其实 nvidia 根本不认为是 bug 他认为这是让显卡帧率提高的 feature. 诶.</p>
NAS 换 Gentoo
2019-05-29T00:00:00+00:00
https://microcai.org/2019/05/29/goback-to-gentoo
<p>freenas 使用了一阵子,发现了几个严重的问题</p>
<ul>
<li>软件严重缺乏</li>
<li>插件需要使用 jails 而 jails 的网络居然不能使用宿主网络必须桥接</li>
<li>桥接导致 mlx 驱动禁用网卡 tcp offload engine</li>
<li>不支持 RDMA,atom cpu 性能弱,无法跑满万兆带宽。</li>
<li>实际上很少使用 web 管理界面,多数情况下还是 ssh 上去直接敲命令</li>
<li>性能差</li>
</ul>
<p>要想性能好,Gentoo 少不了。于是计划 nas 改用 Gentoo。首先要做的事情就是确保 Gentoo 可以使用 ZFS。</p>
<p>这个呢,我就先在自己机器上折腾了。首先重新编译内核,启用 zfs,安装好 zfs 工具,接着根分区的数据先备份,然后格式化为 zfs。然后拷贝回来。重启。完美启用 zfs。</p>
<p>具体的迁移步骤为:</p>
<ol>
<li>首先进行规划,我的 nvme 盘有2个分区, nvme0n1p1 500M 为 EFI 分区。nvme0n1p2 117G 为 ext4 挂到 / 。首先使用 e2fsresize 命令缩小分区,然后在空闲的地方创建 nvme0n1p3 分区。 50G. 由于 liveusb 系统不支持 zfs,因此做完这些步骤后重启回原来的系统。</li>
<li>zpool create -o mountpoint=none -R /newroot Gentoo nvme0n1p3 命令创建一个池。</li>
<li>zfs create Gentoo/ROOT</li>
<li>zfs set mountpoint=/ Gentoo/ROOT</li>
<li>rsync -xav / /newroot</li>
<li>完毕后重启,设定 root=ZFS=Gentoo/ROOT</li>
<li>接着 zpool attach Gentoo nvme0n1p3 nvme0n1p2 命令,将原先的 ext4 所在分区直接以 mirror 模式加入 Gentoo 池。</li>
<li>等待 resilver 完成</li>
<li>zpool detach Gentoo nvme0n1p3 把 nvme0n1p3 这个分区分离出 Gentoo 池。</li>
<li>fdisk 重新调整分区,把剩余的空间重新划给 nvme0n1p2</li>
<li>zpool attache -e Gentoo nvme0n1p2 执行完毕后, Gentoo 池的大小就占满 nvme0n1p2 分区的大小了</li>
</ol>
<p>这样 Gentoo 池就是 117G 大小了。ext4 无损切换为了 zfs。</p>
<p>在确认 Gentoo 可以支持 zfs 格式后,就开始了 nas 的重装计划。</p>
<p>实际上, freenas 是安装到 U 盘的。因此只要再拿一个 U 盘装个 Gentoo 然后换个 U 盘重启 nas 机器即可。而不是在 nas 上搞编译装系统,导致nas过长时间的停机。</p>
<p>虽然最后把 U 盘放 nas 上启动的时候遇到了问题,主要是 编译优化的问题,nas 的 cpu 不支持一些指令集。而我编译安装新的 nas Gentoo 的时候编译参数没有设定好。然而 freenas 系统没有 lscpu,因此最后搞清楚 c3558 有啥 cpu feature 是费了不少功夫。</p>
<p>成功的在 nas 上启动 Gentoo 后, zpool import 导入 freenas 下建的池成功,就进入了比较折腾的 配置 nfs 和 samba 的步骤了。。。 没有了 webui 还确实是麻烦了不少。</p>
<p>好在实际上这些配置只需要进行一次。并没有频繁的修改共享目录的问题。</p>
<p>配置完成后, 我的 PC 上就没看到 nfs server no response.. 消息了。</p>
根分区 ZFS 化
2019-04-27T00:00:00+00:00
https://microcai.org/2019/04/27/root-on-zfs
<p>自从用 freenas 接触到了 ZFS, 我就爱上了 ZFS , 变得愈发不可收拾. 50买了2个 320G 的二手盘放公司玩 raidz.</p>
<p>只是拿 zfs 挂数据的话, 只要编译好 zfs 内核模块和 zfs 命令行工具即可. 但是,如果要 zfs 当 root 文件系统的话, 则免不了一番折腾.</p>
<p>为啥呢? 因为 zfs 不同于 legacy 文件系统, 他是集卷管理于一身的.</p>
无盘PC
2019-04-26T00:00:00+00:00
https://microcai.org/2019/04/26/go-diskless
<p>自从上了万兆,把PC上的硬盘拆到nas上,让PC变成无盘的计划就开始执行了。真正的无盘需要 PXE,然而,同时也意味着现有的 NVMe SSD 成了摆设,因为上文提到的,我的 nas 因为主板关系, pcie 通道不足,无法插 M.2 了。所以现有的 ssd 还是继续在主板上服役。M2 只是一个非常小的接口, ssd 插上主板后和主板融为一体了,所以,姑且就算是主板自带的存储好了,如果只有 nvme 那也可以算无盘。</p>
<p>既然保留了 nvme,那么无盘的工作就简单了,只需要将个人文件 aka <code class="language-plaintext highlighter-rouge">/home</code> 目录放到 nas 上通过网络访问即可。由于在完成拷贝之前我就已经迫不及待的把硬盘插上 NAS 了,因此最初的无盘,我使用的 iSCSI。</p>
<p>将 pc 原本的 4T 硬盘通过 iscsi 共享给 pc 自己。连 fstab 文件都不需要修改,因为 iscsi 连上去的磁盘居然还是 /dev/sda 。就好像只是换了一个 sata 口插一样的方便。</p>
<p>但是, iscsi 只是 sata 线的替代品。我真正的目的,是要让我的数据全部由 ZFS 保管。只有 ZFS 才能让我感到安心。</p>
<p>因此, 8T 氦气填充盘到了后,我就开始了乾坤大挪移。rsync 拷贝了数T的数据,把 旧硬盘上的数据全部转移到了 8T盘上的 ZFS。</p>
<p>然后格式化旧盘,将旧盘作为 mirror 和 8T 的盘组成 raid1。</p>
<p>4T 和 8T 怎么组 raid1?</p>
<p>哈,我在 8T 盘上分了一个 4T 的分区,所以是 4T + 4T 的 mirror ZFS。剩下的 4T 则直接是 普通的 ZFS 用于存放不需要保护的数据 aka 下载的电影和小姐姐。</p>
<p>然后 4T 的分区就作为 /home 以 NFSv4 协议挂载了。</p>
<p>遇到的第一个问题,就是发现开机非常非常的慢。发现是 NFS 在等待 networkmanager-wait-online 激活花了好久时间,于是把 ip 地址静态化,省去 dhcp。</p>
<p>还是慢,nfs4 是以 tcp 模式工作的,可能 tcp 不够好吧,于是降级为 nfsv3 换成 udp。</p>
<p>没软用。</p>
<p>接着把 MTU 调整到 9000. 发现启动的时候要从 /home 读取大量的数据。MTU 改大后能显著的减小数据包的数量,降低 nfsd 的 cpu 使用。</p>
<p>速度确实非常的有效,快了不少。</p>
<p>然而,还是够快。NFS 缺乏prefetch功能,因此肯定不如系统自己挂载文件系统快。但是缺乏 prefetch 只是开机速度慢了点,后面还是不慢的。。。。 错!</p>
<p>在 bash 里, ~ 目录下,执行简单的 ls 命令都需要卡上数秒。第二次执行 ls 就瞬间完成了。然后继续 ls ls 都是瞬间完成。但是,只要停个几秒, 再执行 ls ,就要再次卡上数秒。</p>
<p>打开浏览器什么的也是, 每数秒浏览器就要假死一下。</p>
<p>这个现象的原因就很简单了,原因是 NFS 本地缓存问题。默认 NFS 的本地缓存只有 3秒。 3秒过后,重新到 NFS Server 拉文件列表了,连 ls 都会因此卡上一会会。因为我的 /home 文件夹下有百万计的文件。。。除了我自己的大量 git 仓库带来的大量小文件,还有 KDE 的大量 ~/.cache 文件。</p>
<p>最终通过将 nfs 的缓存加大到一整天解决, ls 也再没卡过,即使第一次执行也不卡了,因为 ls 执行需要的文件列表早就在系统启动的时候载入了。</p>
<p>最后本地访问 /home 的读写速度测试后发现在 400MB/s 上下,很明显 nfs 成了瓶颈。因为本地读写实际上是通过 nfs 读写 nas 的内存。虽然本机 drop 了 cache ,但 nas 并没有嘛。我想到了我的网卡只支持 tcp offload ,不支持 udp offload 。。。。 确实我测试读写的时候, nas 上 nfsd 的 cpu 使用飙升到超过 70% 。。。</p>
<p>于是再次回到 nfsv4 + tcp。这下速度超过了 700MB/s, 而 nfsd 的 cpu 使用也只有到了 50% 了。</p>
<p>暂时就这样了。</p>
NAS 的 pcie 短缺问题
2019-04-23T00:00:00+00:00
https://microcai.org/2019/04/23/intel-pcie-shortage
<p>万兆网卡到货后,迫不及待的插上主板开机。</p>
<p>当时就注意到了一个问题,怎么有一个 SATA 控制器变成了 2port controller 了。也就是说只有6个 sata 口了。</p>
<p>然后和 pc 开始 iperf 测速。发现速度居然只有刚刚超过 2Gbps!!!
什么鬼?</p>
<p>然后把 pc 上的那个卡换了个插槽,换到 pcie x16 的显卡插槽上了。原来是我这个卡放到了最后一个 pcie 2.0 x2 的插槽上(物理形态是 x16)</p>
<p>再测,发现速度也是在 4Gbps 上下。然后进 nas 的 bios 发现, 我去!坑死我了。</p>
<p>原来这个主板的 pcie 和 sata 是2选1通道的。 配置模式是 pcie x4 + sata x4, pcie x2 + sata x6, 或者 sata x8 但是禁用 pcie .</p>
<p>我去,原来我只能折腾4盘位nas! 一开始是 sata x8 但是只要 pcie 上插了卡,默认就是 pcie x2 + sata x6 模式。手动调节为 pcie x4 + sata x4 模式 后进系统, 果然达到了 9.4Gbps 的速度,然后改了 MTU 到 9000 就变成了 9.8Gbps 的速度了。</p>
<p>被这个主板坑死了。居然只能4盘位!!!! 那么问题来了,群灰 DS1918+ 为啥不提供万兆,就是这个原因!诶!</p>
<p>等等,这个主板还有个 NVME 的插槽,这个好像是独享的带宽,没有和别的插槽共享。。。。</p>
<p>于是买了这个玩意</p>
<p><img src="/images/m2_to_sata.jpg" /></p>
<p>最终还是达成了 8 盘位 + 万兆。</p>
<p>当然,目前只插了2个盘。</p>
10Gbps 家庭云存储
2019-04-22T00:00:00+00:00
https://microcai.org/2019/04/22/10GbE-NAS
<p>在万兆内网达成后, 终于到了最重要的环节了,就是搭建一台支持万兆网络访问的 NAS。</p>
<p>首先问自己一个最重要的问题:我家里有矿吗?</p>
<p>显然,没有,所以,一切都要以够用为目标,而不追求高大上。</p>
<p>所以,显然这次万兆够用为主,量力而行。</p>
<p>第一个选择就是主板。主板我花了超过一周的时间去寻找,对比各种方案。</p>
<p>当时的方案有3个:</p>
<p>方案1:集成 Atom C3558 的主板。有ASRock和超微两个牌子都有提供。</p>
<p>方案2:集成 Xeon D2123 的主板。</p>
<p>方案3:使用 Ryzen 200GE 和 B450 主板。</p>
<p>方案1 是因为主板提供了 pcie x4 的插槽,并且有 8 个 SATA 口(后来发现是个坑)
方案2 是以为主板已经集成了万兆网卡。
方案3 是因为毕竟是正常的桌面PC(虽然是低端的)有充足的扩展性。</p>
<p>最后方案3被 PASS,因为发现需要 ECC 内存。</p>
<p>方案2 因为主板价格超过五千被 pass。</p>
<p>然后是选定方案1, Atom C3558。</p>
<p>选方案1 是因为发现群辉8盘位的 DS1819+ 也是 Atom C3538 的方案。比 3558 还稍微渣了一丁丁呢。 而 DS1819+ 售价超过 8000 呢。</p>
<p>虽然犹豫了一阵子,不过最后买了超威的 A2SDi-4C-HLN4F 这个款的主板,集成 C3558 cpu。CPU 被动散热,TDP 25w,花了 2333.</p>
<p>然后就是最糟糕的,选机箱上了。</p>
<p>因为家里有个小机柜,所以希望买个2U 高度的8个热插拔盘位的机箱。结果发现了一个严重的问题, 2U 高度的机箱,要么4盘位短机箱,要么8盘位长机箱。而长机箱是放不进去我的小机柜的。无奈买了个3U高度的。8个热插拔的机箱,长度40cm,再长点就要顶我机柜后盖了。价格 720 但是运费 60 。。。。</p>
<p>接着是选电源,因为要根据机箱来决定是 ATX 电源还是买 服务器专用的电源。这个机箱因为支持 ATX 电源,所以自然是。。。 上 ATX 电源了。为了达到静音,专门买了钛金的电源。当然,是二手的。无风扇的。海韵当年的一款 520W 的无风扇电源。新的要一千多呢,二手400搞定。</p>
<p>最后是随便买了个 8T 的盘,和原来的 4T 的盘一起组成了个 12T 的池子。最后上放到机柜上的效果。</p>
<p><img src="/images/mynas.jpg" /></p>
<p>上面是 群灰和路由器,中间是华为的 S1720 的万兆POE交换机。下面就是 3U 高度的新nas了。</p>
<p>因为有了 IPMI,所以不需要接显示器, 直接就通过浏览器就能直接安装 OS 了。
哦,忘记了,主板上还插了个 16G 的 U 盘来装系统。
毕竟不能浪费宝贵的 SATA 盘位对吧。</p>
<p>对了,至于网卡吗, 当然买的二手 Mellanox Connect3X 单口万兆, pcie 3.0 x4 接口。160 块钱。</p>
<p>然后因为和交换机在一个机柜,就不买模块了,直接上的 DAC 线。</p>
多WAN平衡
2019-04-04T00:00:00+00:00
https://microcai.org/2019/04/04/multiWAN
<p>曾经我也搞了两条宽带,见 <a href="/2016/12/07/use-neighbor-CMCC.html">使用邻居的CMCC网络</a> 。
然后利用路由器的loadbalance就可以使用多条线路了。
但是,问题恰恰在这里。效果并不好。因为2条宽带并不是一样容量的,对同一目标的访问速度也不尽相同。所以退而求次,选择了只把某些 cmcc 访问速度更快的目标通过 邻居网络出去。</p>
<p>虽然我换了一个城市,然后选择了CMCC,也没有破解邻居的电信宽带wifi密码。但是多年前的失败经历还是让我感觉需要某种自动的机制自动的探测目标网络使用哪条线路是最快的。</p>
<p>这就是 <a href="https://github.com/microcai/smartproxy">smartproxy</a> 的了。</p>
内网万兆达成
2019-04-04T00:00:00+00:00
https://microcai.org/2019/04/04/go-10GbE
<p>千兆用几年了? 从我拥有的第一台电脑起,主板自带以太网口就是千兆的。十几年过去了,主板自带网口依旧千兆。</p>
<p>千兆网和 1366x768 一样,都是业界毒瘤,停滞在一个“够用” 的幻想里止步不前。</p>
<p>为了打破这个幻想,我最终决定,将家里的内网升级为 10G 光以太网。</p>
<p>为啥用光纤呢?</p>
<p>主要是我的 PC 所在的位置,距离 86网络端口面板有数米的距离,中间如果使用网线会非常的碍事。
所以我买了个 86 面板的 AP 把原来的网络端口面板替换了。然后 PC 再上一块 wifi 网卡即可。</p>
<p>无线代替了有线。虽然链接速度显示为 1733Mbps 但是实测速度只有 600Mbps 而已。</p>
<p>虽然不够用,但是我更不希望一条网线拖过去。</p>
<p>就这么用了几个月,突然间,我发现了一种叫“隐性光纤” 的神器。有了这个,就可以把隐性光纤沿着墙角落走线到 PC,不就解决了难看碍事的问题了吗?</p>
<p>但是,既然上了光纤,就不能继续千兆了,当然要万兆才行。</p>
<p>兵马未动,粮草先行。买设备升级前,首先解决布线问题。</p>
<p>于是买了 隐性光纤。然后开始换线。买了条新的网线,然后把新的网线和光纤一起绑在开发商装的网线上,然后从弱点箱抽,非常的困难,最担心的就是把线拉断在中间了。
虽然过程很艰难,但是最终把隐性光纤给换进去了。做好了以后,就是 86 面板AP 的边上还抽出来了一个细细的透明的钓鱼线一样的光纤,然后沿着墙角走线(透明胶大法)一路通到了 PC。另一头也已经从弱电箱出来放到网络机柜的边上了。</p>
<p>第一步就算完成了,证明开发商虽然黑心,但是好歹线还是有用管子布的。。还是能换的,虽然很难抽。。。。</p>
<p>但是,这么难抽,有没有把光纤拉断呢?</p>
<p>于是买了红光笔和冷接套装还有万兆网卡。</p>
<p>开始接 SC 头。</p>
<p>冷接真的是个技术活,但是我最终搞定了。隐形光纤有2层皮,最外层是一层软塑料。用米勒钳的第二个口就可以剥掉。然后露出的裸纤我当时就直接上冷接头了。。还很纳闷为啥切割刀无法切割。我改用网线钳去切光纤了。。。。。 最后最好的线红光笔一测,怎么光点这么弱!?</p>
<p>结果是错误的,要再剥掉一层。用米勒钳的第三个口剥离。当时我直接用第三口剥发现一切就断了。还以为是他第三口洞太小了。原来是要先用口2剥第一层,然后用口3 剥最后一层。</p>
<p>最后最后才真正的露出了内层玻璃纤维。</p>
<p>这个玻璃纤维放到切割器上真的一次就切的整整齐齐。</p>
<p>然后塞入冷接头。红光笔一测,发现和买的成品跳线一样亮了。</p>
<p>然后把路由器到交换机临时改插网线,把 SFP 模块借用给 PC 了。万兆网卡不出意外的兼容千兆光模块。</p>
<p>这样就实现了PC千兆光纤到交换机。</p>
<p>终于可以对 1733Mbps 的 wifi 说拜拜了。</p>
<p>冷接光纤学会了,自然就不怕了,然后开始选万兆交换机。必须是无风扇的交换机</p>
<p>这个是最大的一个问题,我最后停留在2个方案上。</p>
<ul>
<li>方案1: 使用 MikroTik CRS305, 带4个万兆光口和一个千兆口。千兆电口支持 POE 受电,意味着只要插入原来的交换机上就可以立即工作了。</li>
<li>方案2: 使用华为 S1720-28GWR-4X 交换机。4个万兆口,24个千兆口,无风扇被动散热。没有 POE,意味着原先的交换机还得服役。</li>
</ul>
<p>这两个方案都要原来的交换机继续服役,听起来是保护投资了,其实不然。 第一,交换机到交换机的带宽被限定 1G 可不是好事。第二,两台交换机还是占用了更大的空间,特别是方案2的话我其实不需要2台交换机给我扩充那么多端口。而方案1会有性能瓶颈。</p>
<p>最后发现, S1720-28GWR-PWR-4X 交换机,和 S1720-28GWR-4X 相比就是多了 POE 功能,因此,只要我POE负载不高,实际上是可以被动散热的。因为 多加的风扇必然只是为了给增加了功率的变压器供电的。</p>
<p>于是几经挫折后买到了 S1720-28GWR-PWR-4X 。1600 块钱。</p>
<p>然后配合新买的2个 10G 的 SFP+ 光模块,重新做了 LC 插头(没有找到 SC 插头的万兆光模块),搞定了 PC 到交换机的万兆链接。</p>
<p>接下来就是寻找合适的能支持 10G 访问的 NAS 了。</p>
入DN42坑
2019-03-11T00:00:00+00:00
https://microcai.org/2019/03/11/dn42-connected
<p>近来把 家和公司三处地方的网络(2个家,乡下老家嘛)通过 tunnel 手段连接到了一起, 三个地方的 LAN 分配不同的私网网段,因此配置好路由表后,三处内网的机器就可以互相访问了。</p>
<p>最初我使用的是添加静态路由,但是因为 vpn 是使用的点对点互联,而不是首先集中连到一个转发服务器上,所以需要配置路由表的路由器数量达到了4台,比较麻烦,关键是期间我还修改过私网使用的网段,结果每台路由器都要登录上去修改路由表, 非常麻烦。</p>
<p>更为麻烦的是, 其中公司访问老家的 vpn 丢包比较大,如果通过家中的网路跳转访问, 反而更快。但是如果公司到家里的网络断开,还要希望自动的让他们之间的直链线路接手, 于是就折腾上了 OSPF 路由协议。</p>
<p>当然,最开始我是使用的 bgp 协议, 让3台路由器相互建立邻居,但是bgp的邻居要手动配置,稍微麻烦了点,所以改用 ospf 协议。</p>
<p>顺便还修改了 EdgeOS (Ubnt 的路由器使用的系统)让它支持配置 zerotier —— 是通过他内置的 configure 命令配置, 而不是 zerotier-cli 命令, 这样就可以在 Web GUI 上为 zerotier 虚接口建立 ospf。</p>
<p>基本上 EdgeOS 就是 Debian Linux + Perl configure script.
所以,其实 EdgeOS 是可以移植到 x86 软路由上的。不过x86软路由可以直接使用 VyOS。<code class="language-plaintext highlighter-rouge">^_^</code></p>
<p>有了 动态路由协议,我只要设定老家到公司的vpn链路开销是 200, 而老家到家的开销是 80 , 家到公司的开销是 80, 通过家中转访问的开销则只有 160,于是 ospf 的选路算法就会使用中转代替直联的那条vpn线路。如果中转的线路不可用, 则回退到直联模式。</p>
<p>这样我顺便又加了一台在海外的VPS进入这个网络,形成4点的 mesh 网络。</p>
<p>这个就是我自己折腾出来的,在折腾的过程中,还发现了 dn42 这个网络。</p>
<p>dn42 就是一个全球性的,像我折腾手里私人网络一样的,通过 vpn 把多个节点连起来,不同的节点是由不同的人管理的,这样一个大型的虚拟网络。</p>
<p>目前已经拥有 400+ 个自治域网络。</p>
<p>于是我也去申请加入了。 搞到了 AS4242420571 这个 AS 号。然后分配了
172.21.87.128/25 172.21.87.64/26 两段共 196 个IP。</p>
<p>然后和 burble 建立了第一个 peer。顺便学习了 bgp 协议。</p>
<p>然后 burble 一下子给我 push 了四百多条路由。不过我只给他 push 了2条。</p>
<p>然后通过他的Transit我就能访问 dn42 里其他人了。</p>
<p>当然,还得建立更多的 peer 才行。</p>
<p>然后顺便就把家里的 IP 网段给换到 dn42 分配的 ip 里了。</p>
<p>下面记录下分配的 IP 列表:</p>
<ul>
<li>172.21.87.64/29 -> 分配给所以设备的 Loopback IP , 用 /32 子网, 用于点对点的网络
<ul>
<li>点对点连接为如 PPTP, WG</li>
<li>172.21.87.66/32 分配给 HOME</li>
<li>172.21.87.67/32 分配给 Gaofeng</li>
<li>172.21.87.64/32 分配给 Office</li>
<li>172.21.87.65/32 给 zhangrui pptp 拨入对接</li>
<li>剩下还有 4 个 IP 保留。</li>
</ul>
</li>
<li>172.21.87.72/30 -> 用于 peer 的 zerotier 网络。</li>
<li>172.21.87.76/30 分配给家里第二个LAN, NAS 独享的点对点以太网链接</li>
<li>172.21.87.80/30 分配给 a0cbf4b62a4ab103 (OFFICE 2 VPS)</li>
<li>172.21.87.84/30 分配给 1c33c1ced03e1a5a (OFFICE 2 HOME)</li>
<li>172.21.87.88/30 分配给 35c192ce9b758b54 (Gaofeng 2 HOME)</li>
<li>
<p>172.21.87.92/30 分配给 d3ecf5726d7bf5a7 (HOME 2 VPS)</p>
</li>
<li>172.21.87.96/27 -> 办公室 LAN 网络</li>
<li>172.21.87.128/26 -> 家里主要网络</li>
<li>172.21.87.192/26 -> 丈母娘家网络</li>
</ul>
拿到了移动宽带的公网IP
2019-03-01T00:00:00+00:00
https://microcai.org/2019/03/01/cmcc-public-ip
<p>闻觉 CMCC 手握 HKIX 宝剑, 想必连 HK 节点的 VPN 访问世界一定更快。世界的一部分一般在 HK 本地也有 CDN 节点,因此多数时候并不需要再从 HK 去世界绕一圈。于是买新房后,就办理了免费的CMCC宽带。不再向恶势力CT低头了。</p>
<p>美中不足的是 CMCC 手头 ip 资源紧张,早早部署了 CGNAT,再经过猫一层 NAT,路由器又一层 NAT,访问个局域网都要3层NAT,再访问世界那就加1层VPN的NAT,达到了恐怖的4层NAT。</p>
<p>于是第一件事情就是猫给改成了桥接模式,让路由器拨号。 猫就应该好好的做简单的光电转换的事情,搞什么非要把路由器的活也干了。这样下去TP-Link 岂不是要倒闭? (虽然我不用 tp 的东西就是了)</p>
<p>才少了一层NAT,还是深感不舒适。 特别是当需要从办公室连回家中的时候,只能靠服务器中转,明明在同一个城市,敲击键盘的每一个数字却都要绕地球一圈。</p>
<p>NAT 破坏了网络的连接性,阻碍了 ipv6 的进程, 实在是十恶不赦。</p>
<p>传统的NAT只是部署在家中,家里的NAT设备尚在用户掌握之中,大不了爬起来去路由器上敲击几条命令建立端口映射。</p>
<p>但是 CGNAT 就是完完全全的罪恶涛天了。试问,一个只能打电话不能接听电话的手机,还叫手机吗?</p>
<p>这辈子都不曾接过电话的人,总是觉得,能打电话就行了,接不到电话有屁关系。</p>
<p>呵呵,你接不到电话,为了联系你,别人背后不知道做了多少工作,付出了多少成本!这种话,就好像一个生活在首都久了的人,没见过战争,就觉得别人去当兵去边疆守卫国家都是傻逼。</p>
<p>但是没办法,ip地址不够,NAT 还是必要的,但是尽量让 NAT 只位于家中一层而已吧,运营商不要搞NAT了!</p>
<p>CMCC 的宽带因为是送的, 直接打电话找它要是肯定没有的,人家宁可拆机也不会给你公网地址。</p>
<p>所以想着,再办一条CT的宽带得了。只是一直没有行动,一是入户光纤乃开发商预埋,只有一根, 二是懒。 因为只要思想不滑坡, 办法总比困难多。</p>
<p>忽然有一天,闲的无聊跑了一下 speedtest。其实一直都有跑,只是都没在意网速。那天就特别在意起来了,发现网速不达标。一直不达标。</p>
<p>于是打了壹卍零捌拾陆。</p>
<p>第二天修宽带的师傅就上门了。</p>
<p>给他演示了一下,首先是 speedtest 测速,速度忽快忽慢。</p>
<p>接着,禁用 ipv6 然后访问 http://mirrors.zju.edu.cn/ 下个iso,速度只有 50M,开 ipv6 访问, 速度 100M。</p>
<p>十分诡异。</p>
<p>小哥懵B了,他也不懂ipv6和ipv4的区别。我说 ipv6 是公网地址 , ipv4 是私网地址啊。</p>
<p>然后他懂了,他说给你换公网地址试试看。</p>
<p>也许他在怀疑移动的NAT设备性能问题吧。</p>
<p>然后过了几天,公网ip就有了,不过糟糕的是, ipv6 没了。。。。。</p>
<p>好了,虽然用的免费的移动家宽,不过有了公网ip,网络确实用起来正常了。远程回家不需要中转了,需要 p2p 的软件也都工作正常了。 舒坦。</p>
折腾 NAT64 实现 ipv6 only 内网
2019-02-13T00:00:00+00:00
https://microcai.org/2019/02/13/nat64
<p>一直以来,虽然启用了 ipv6, 但是都是以 Dual Stack 模式运行。
你不知道,是服务器端不支持 ipv6 呢,还是客户端不支持,亦或二者皆有之。</p>
<p>而且Dual Stack虽然解决了互联问题,但是看起来非常的丑陋,多个协议一起跑什么的,才不要呢!</p>
<p>于是就有了搞 IPv6 Only 的想法。为了解决互联问题,还需要一台翻译的设备执行 NAT64。</p>
<p>所谓 NAT64 就是让一台网关充当 ipv6 和 ipv4 的桥梁,把 ipv4 主机模拟成 ipv6 主机,供 IPv6 Only 的主机访问。
之所以可以这么做到,是因为 ipv6 拥有巨大的地址集。只要其中拿一小部分出来就可以包含所有的 ipv4 地址了。可以做到 ipv6 地址和 ipv4 地址一一对应,然后为这个一一对应执行 NAT 转换。</p>
<p>只需要一台网关设备提供 Dual Stack 即可,LAN 内的机器都只要 IPv6 Only 了。</p>
<p>要实现 NAT64 , 首先 需要 DNS64 的帮忙。
何为 DNS64 ? 要让 ipv4 的机器被访问到,首先需要他被转换为 ipv6 地址,做到这一点的就是 DNS64 。</p>
<p>当向 DNS64 查询 dns 的时候,dns64 会检查域名是否有 AAAA 记录,如果有,就用他自己的 AAAA 记录里的 ipv6 地址,客户端就可以直接访问了,完美。 如果他没有 AAAA 记录,但是有 A 记录,就把这个 A记录里的 ipv4 地址给转换为一个 ipv6 地址,然后返回这个假的 AAAA 记录。</p>
<p>客户端访问这个假的 ipv6 地址,自然就被路由到了 NAT64 设备上,然后 NAT64 就充当中间人角色完成地址转换。</p>
<p>对 ipv6 的客户端来说,一切都是透明的。</p>
<p>说了半天,咋个实现呢?</p>
<p>首先,你需要一台设备,低功耗24小时运行,性能又不会太差的那种。
嗯,没错,一台基于 Linux 的路由器(openwrt啊,梅林啊,当然,也可以像我一样,用 ubiquiti 的 EdgeMax 系列的路由器)</p>
<p>但是,因为我最近拔仓库的时候发现了吃灰很久的一个树梅派,所以我决定在派上面安装这些东西。好处就是 EdgeOS 更新的时候不用重新折腾了。</p>
<p>首先需要 DNS64, 这个DNS64 的存在,使得我之前折腾的ChinaDNS 瞬间毫无意义啦。因为 dns64 我用的 unbound 这个软件,而这个软件支持 DNSSEC 加密。意味着我只要打开 DNSSEC 就对 dns 污染说 good bye 了。于是只要 <code class="language-plaintext highlighter-rouge">pacman -S unbound && systemctl start unbound</code> 简单的命令就把 unbound 跑起来了。</p>
<p>当然,还是要 edit 一下 /etc/unbound/unbound.conf 文件才能启用 DNS64 和 DNSSEC 的。</p>
<p>DNS64 的伪造AAAA记录用的前缀呢,我就用 8964::/96 了。这样 1.1.1.1 就会被转换成 8964::1.1.1.1 ( 或者 8964::101:101 这种写法)。</p>
<p>把 DNS 设置到 rpi 上后, nslookup 一下</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$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
</code></pre></div></div>
<p>顺利的看到了 8964:: 开头的 伪 AAAA 记录。然后把路由器的 pppoe 拨号自动获取 dns 给关了,手工指定 dns 为 rpi。
这样 LAN 内的机器还是用的路由器来解析 dns。而路由器再转发给 rpi。因为必须要用路由器的 dnsmasq 解析才能正确的把某些域名的 ip 地址给填入 ipset ,用来进行基于匹配的智能路由。。。你懂的!</p>
<p>然后开始折腾 NAT64.</p>
<p>NAT64 使用 tayga 软件。这个软件 pacman 里没有。只好自己编译了。(折腾的时候忘记了用 yay !)</p>
<p>然后按照 wiki 把 tayga 跑起来,done!</p>
<p>当然,要记得在路由器里配置下静态路由,把 8964::/96 的下一跳指向 rpi.</p>
<p>试试看</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ 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
</code></pre></div></div>
<p>wow, very nice 啊!</p>
<p>然后果断 NetworManager 设置打开,禁用 IPv4,重新启用下 eth0.</p>
<p>然后,就只有ipv6 地址了(还是公网地址,哈哈!)</p>
<p>然后,打开浏览器,wow,网能正常使用。</p>
<p>Linux 上除了硬编码 ipv4 地址的客户端外,都能正常使用了。</p>
<p>至于 Windows 嘛,呵呵,有太多的天朝系的垃圾软件,用 C 写的各种以为自己很牛逼的网络库,各种鄙视 boost.asio 的存在,导致这些垃圾网络库都是 ipv4 only 的。所以,没有 ipv4 地址,这些软件就统统歇菜了。</p>
<p>but,意外的发现 酷狗音乐啊,网易音乐啊,都还是能用的。。。。。
果然玩音乐的网络代码写的比腾讯阿里好啊!</p>
IPv6 这几天的体验
2019-02-13T00:00:00+00:00
https://microcai.org/2019/02/13/ipv6-experience
<p>如果只是家里折腾上了 ipv6 那不算 ipv6。网络这种东西一定要互联互通才算。最近把公司的猫也折腾了一下下,上了 ipv6。</p>
<p>于是发现,在公司可以无缝访问家里的设备了。全部都在公网,无需端口映射之类的东西了。当然,家里也没有公网ipv4地址,映射了也白搭。</p>
<p>有了 ipv6 后,发现所有的机器都突然没有了 NAT 墙。那一道无形中把整个世界割裂的墙消失了,只要知道 ip 地址,所有的设备都可以点对点互相访问。</p>
<p>终于,端到端的透明性回来了。</p>
<p>然而问题来了,因为每个设备 ip 不一样,以往一台机器弄个DDNS就好了,现在需要每台提供外访的机器都要跑一个 ddns。要是路由器能为局域网的机器批量更新 AAAA 记录就好了。</p>
中国移动原生IPv6配置
2018-12-18T00:00:00+00:00
https://microcai.org/2018/12/18/cmcc-ipv6
<p>上峰拍了桌子, 要求 2018 年底之前商用 ipv6. 移动响应非常积极,已经可以用了.
ipv6 带来的第一个好处就是削除了NAT, 对万恶的私网ip地址说再见!</p>
<p>在继续之前,首先讲下,ipv6 同 ipv4 在配置上的不同。</p>
<p>对于家庭宽带,ipv4 是 pppoe 拨号的时候自动配置的。isp 给且仅给一个 ipv4 地址。
如果有多台设备需要上网,就需要使用一种叫 NAT 的技术进行网址共享。由于NAT隐藏了内网的细节, 在 isp 看来,所有的数据都是来自同一个 ip 地址的. 因此isp和家庭网关之间不需要什么路由协议.
但是 ipv6 得益于超长地址,不再需要节约地址了,于是内网仍然分配全球可路由地址. 这个时候, isp 其实就需要分配两次地址,一次是给家庭网关的WAN口,另一次是给家庭网关的 LAN 口.</p>
<p>第一次分配,给 WAN 口分配, ipv4 和 ipv6 是一样的. 都是在 PPPoE 拨号完成后动态分配.</p>
<p>只是, ipv4 时代, LAN 口地址是固定的一个内网地址. 而 ipv6 时代, 即使 ip地址再丰富, 运营商也不会给用户分配固定的 ip 地址, 因此, 需要一种 动态的给 LAN 口分配地址(准确的说是分配前缀)的协议.</p>
<p>这个协议就是 DHCP-PD. LAN 口分配好前缀后, 整个子网就分配下来了, 至于这个子网里哪个机器用哪个地址, 那就和 isp 没有关系了. 而且实际上 isp 下发子网前缀后, 路由器其实是可以把 下发的子网再细分的, 这个适用于有多个 LAN 的情况.</p>
<p>ipv6 因为有 RA 协议, 所以只要路由器的 LAN 口分配了前缀, LAN 口下的在同一个交换域里的主机就都可以自动分配ipv6地址.</p>
<p>那么, EdgeRouter 里如何使用 dhcp-pd 为 LAN 口分配地址呢?</p>
<p>那么,配置的办法其实很简单, 就是</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ubnt@ubnt:~<span class="nv">$ </span>configure
<span class="o">[</span>edit]
ubnt@ubnt# <span class="nb">set </span>interfaces ethernet eth0 pppoe 0 ipv6 address autoconf
ubnt@ubnt# <span class="nb">set </span>interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 prefix-length /56
ubnt@ubnt# <span class="nb">set </span>interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 interface switch0 prefix-id 1
ubnt@ubnt# <span class="nb">set </span>interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 interface switch0 host-address ::7788
ubnt@ubnt# <span class="nb">set </span>interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 interface switch0 service slaac
ubnt@ubnt# commit<span class="p">;</span> save<span class="p">;</span> <span class="nb">exit
</span>ubnt@ubnt:~<span class="nv">$ </span>reboot
</code></pre></div></div>
<p>解析:</p>
<p><code class="language-plaintext highlighter-rouge">set interfaces ethernet eth0 pppoe 0 ipv6 address autoconf</code> 启用了 pppoe 的 ipv6 地址配置, pppoe 认证通过后, 路由器即从 isp 为 pppoe 链接分配 ipv6 地址. 这时候, 路由器本身就可以通过 ipv6 上网了.</p>
<p><code class="language-plaintext highlighter-rouge">ubnt@ubnt# set interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 prefix-length /56</code>
这个命令就是启用了 dhcp-pd 的总开关, 向 isp 申请 /56 长度的前缀. 不同的 isp 这个数字是不同的, 大多数运营商都是 /56. 也有少数运营商是分配的 /60 的前缀, 这个自己尝试即可. 因为 edgerouter 是多功能的路由器, 不限定为一个wan口的,所以 dhcp-pd 客户端需要编号. 这里我就用编号1了. 如果有第二个 pppoe 链接(多isp的话) 就可以在别的 pppoe 下再创建 pd 2 了.</p>
<p><code class="language-plaintext highlighter-rouge">ubnt@ubnt# set interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 interface switch0 prefix-id 1</code></p>
<p>这条命令创建第一个子前缀. 如果家里只划分了一个 LAN 那就只有这一条 prefix-id 为 1 的子前缀了. 如果有多个 LAN 网络, 就可以用 perfix-id 区分不同的 lan. 这里 switch0 的意思, 就是这个子前缀是为 switch0 分的. 如果你有多个子网, 可能就没有把 eth1234 聚合成 swtich0, 可以在这里使用 eth1 eth2 eth3 分别配置3条子网下去.
当然可可能是通过 VLAN 划分子网的话, 那就是 switch0.vlanid 了. 具体自己灵活即可.</p>
<p>``` ubnt@ubnt# set interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 interface switch0 host-address ::7788</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
这条命令, 就是在这个 子池里, 选一个 ::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.
</code></pre></div></div>
<p>ubnt@ubnt# set interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 interface switch0 service slaac
```
最后的这条配置, 就是启用 RA 广播, 局域网的机器收到后, 就会为自己生成 abcd:1234:1234:1201:????:????:????:???? 这样的地址了. 局域网的机器就能通过 ipv6 访问世界了.</p>
高并发服务器的静态内存分配策略
2018-10-25T00:00:00+00:00
https://microcai.org/2018/10/25/static-memory-allocation-made-server-fast
<p>asio 的 async_accept 函数在高并发的时候,会发生遗漏 accept 的情况。根本原因在于,async_accept 回调的时候,已经 监听的socket已经没有执行 accept 操作了。
解决的办法就是投递多个 async_accept. asio 的 async_* 系列 函数通常只能投递一次,多次投递会发生未定义行为。async_accept 是为数不多的例外。</p>
<p>通常在写一个 server 的时候,client 是采取 accept 一个就 make_shared 一个的做法。对于大量频繁的连接请求,make_shared 很快就会成为新的瓶颈。
在读 beast 的 example/http/fast_server 的时候,发现了 beast 对这个问题的解决方案,非常的聪明。</p>
<p>beast 的做法就是,固定的 client 对象。每个 client 自己投递 async_accept 请求,把自己的 socket_ 投递给 async_accept。
async_accept 返回后, client 就对自己的 socket_ 执行处理, 该 read read,该 write 就 write。
直到这个 client 的请求全部处理完毕,连接也 shutdown 后, 它就再次投递 async_accept 进入下一个循环。</p>
<p>在这个模式下,client 对象并没有被不断的 new 出来。 client 对象的数目是固定的。他利用了 async_accept 可以多次投递的 feature。
在超大量的请求下,该模式始终不会产生频繁的内存分配和释放请求,不仅仅加快了速度,而且也极大的避免了内存碎片。</p>
<p>为了让这个模式调用更少的 new。 beast 定制了内存分配器。
这个定制的内存分配器,每次分配内存只是简单的一次指针移动,而不回收内存。
直到整个连接处理完毕,重置指针完成一次性释放。而这块固定的内存也是随着 client对象最初就已经固定建立了。</p>
<p>也就是说,这个 fast_server 在运行的过程中,除了 asio 内部可能有内存分配外,其他地方没有任何内存分配。如果定制了 asio 的内存分配器,甚至能做到整个运行过程中 0次调用 malloc。运行过程中需要的内存是完全静态分配的。</p>
<p>当然,不动态分配内存,缺点就是,该程序能处理的最大并发数是固定的。但是,即使动态分配内存,最大并发数就一定是无限的吗?</p>
挖矿与创新
2018-06-10T00:00:00+00:00
https://microcai.org/2018/06/10/mining-and-startup
<p>所谓挖矿,就是寻找一个随机数,是的区块头部和随机数结合后的 sha256 数值小于一个特定的目标数字。</p>
<p>对于哪一个随机数能找到答案,没有人知道,也没有捷径,唯一的做法,就是一个一个的去试。</p>
<p>比特币诞生也有9年了,9 年来,不断的有人尝试寻找捷径,但最终都没有找到,只能依靠暴力穷举随机数。
只要尝试的次数足够多,参与尝试的矿工足够多,大约每十分钟总会有一个幸运儿找到这样的随机数。
当他找到的时候,他将获得巨大的回报。
而当难度越来越高的时候,就有了矿池,为矿池工作的矿工,即使找到了,也只是获得平庸的奖励,
但是没有找到的矿工,同样获得了奖励。</p>
<p>其实所谓创业,也是如此。没有人知道下一个独角兽会诞生在哪里。所有人都在无方向的试错。但是每隔一段时间,就有
一个人会找到那个正确的方向,获得巨大的奖励。
而当创业难度越来越高的时候,就有了公司。集合大家的力量去尝试寻找正确的解。</p>
<p>所以创业成功,就和挖到比特币一样,没有什么秘诀。正如挖矿的算力越来越集中一样,创业也越来越成为少数人的特权。</p>
换用wireguard
2018-03-20T00:00:00+00:00
https://microcai.org/2018/03/20/switch-to-wireguard
<p>之前把家里网络 <a href="/2018/02/22/network-reworked.html">重新折腾</a> 了一下, 用的是 走socks5 代理的 tcp 协议 openvpn. socks5 代理由 shadowsocks 提供.</p>
<p>不过, 最近偶然在 LWN 发现了 wireguard 这个新型 VPN, 于是盘算着用起来看看效果.
结果一不小心, 发现了<a href="https://github.com/Lochnair/vyatta-wireguard">vyatta-wireguard</a>, 这个可以让我的路由器也能用 wireguard!</p>
<p>于是按照它的文档配置到了路由器上, 然后VPS那边也配置下.</p>
<p>只是把路由器表里, vtun0 换成了 wg0 就好了, 其他的不需要换.</p>
<p>完了之后测试了一下速度, 比 openvpn 的时候快了很多. 毕竟 UDP 嘛. vpn 这种东西还是适合 UDP 而不是 tcp 的.
而且在跑流量的时候, ping 的延迟不会增加. 如果是 tcp 则会因为发送窗口的关系, ping 的延迟会因为流量增加而迅速增加.
对网络的体验极其不好.</p>
<p>当然, udp 模式的 openvpn 其实也可以, 只是 openvpn 已经被墙识别了, 不可能直接用 openvpn 啦.
wg 毕竟新鲜事物, 到墙能识别故意要很长一段时间了. 这段时间内都可以安心的啦.</p>
<p>PS: 一开始担心机房屏蔽 UDP 包, 结果发现可以愉快的跑, 担心是多余的啦.</p>
UPS和直流供电的思考
2018-03-05T00:00:00+00:00
https://microcai.org/2018/03/05/hvdc
<p>为了在网络断电后还能使用, 我曾经给路由器配置 12v 直流 UPS. 但是, 随着我的网络扩展, 出现了更多的设备, 而且这些设备不再是12v电压供电的了, 而是出现了 24v 和 54v 供电的设备. 同时, 12v 供电的设备也增加了, 原来的 UPS 容量就显得捉襟见肘了. 我继续要对 UPS 进行一次升级.</p>
<p>一劳永逸的解决方式, 其实是直接购买普通的 220v 逆变输出的 UPS. 但是, 生命在于折腾, 而且, 这种 UPS 效率很低, 因为 DC 经过一次逆变后, 还有再变压直流, 其实这中间的转换就属于脱裤子放屁多此一举, 除了带来毫无意义的能源损失. 除非用电设备是非交流电不能的, 比如 单相异步交流电机 然而我的设备里, 即使是网络存储器的散热风扇, 也是 12v的直流风扇而已. 所以, 直接购买UPS显然不够极客, 也不适合节能减排的今天.</p>
<p>当然, 我也可以继续扩容 12v DC UPS. 多并联几个就好了. 然后使用 12v -> 24V 和 12v-> 48v 的 DC/DC 变换器来解决使用其他电压的设备供电.</p>
<p>但是这样做会有2个问题, 第一个问题是, 这种寨牌的成品UPS并不能水平扩容. 第二个问题则是, 又要引入许多电压转换, 带来能源损失. 本身电池储能就有限, 不能肆意铺张浪费.</p>
<p>思考再三, 我决定储能上使用 48v 电池. 因为如此耗电最大的设备, ES-8 POE 交换机, 就可以直接使用电池供电. 最大限度的减少变压带来的损耗. 电池类型上呢, 决定使用更加经济环保的磷酸铁锂电池. 这种电池在同样体积下, 比铅酸蓄电池提供了更大的容量, 而且并没有贵太多. 虽然单个电池更贵, 但是单位容量的成本反而是下降的.</p>
<p>接着, 购买了自动切换主备电源的开关, 主电源停电后, 自动闭合备用电源. 用电设备呢, 则是购买了1拖2的那种DC转接线.</p>
<p><img src="/images/1to2.png" /></p>
<p>电池的输入和自带电源适配器的输入, 共同并入用电设备.
由于备用电源在主电源有电的情况下, 是常开状态的, 所以其实这种情况下, 并不会出现适配器的输出给电池充电的情况 (这种情况是要极力避免的, 因为这种适配器并不能用来给电池充电, 否则极易使电池报废).</p>
<p>大功告成后, 发现了一个问题, 就是这种主备自动切换的开关, 有一个切换延迟. 这个延迟虽然小, 但是还是导致切换的时候, 设备重启了. 这个问题呢, 暂时通过引入一个小容量的超级电容解决了.</p>
<p>但是, 这个事情呢, 也让我认识到, 后备式UPS 在切换的稳定性上, 不如在线式UPS.
但是, 在线式UPS最大的问题, 就是电压多次变换导致的效率低下. 只要设备还是工作在 220v 交流电下, 在线式就不可避免的要引入逆变器, 不可避免的要效率低下.</p>
<p>如果不要逆变器呢? 如果我使用的是, 带电池管理功能的 48v 开关电源, 则其实电池是可以直接接入母线. 变成直流的在线式UPS. 这个固然效率很高, 但是只适用于直流供电的设备, 不适用于其他类型的设备.
而且, 48v 电压, 也只能用在路由器交换机这种耗电不大的设备上, 所以, 直流的在线式UPS始终无法应用开来. 否则电流太大, 耗费在电缆上的能源也非常可观, 减少能耗使用非常粗的电缆则不现实.</p>
<p>真的吗?</p>
<p>如果提高直流电压, 比如提高到 300v 呢? 220v 交流电压其实只是个等效值, 他的峰值电压其实可以到 311v. 也就是说, 如果使用 300v 的直流电, 其实现有的布线绝缘等级是完全够用的, 而且电压提高了, 实际上能输送更大的功率.</p>
<p>用电设备又如何呢?</p>
<p>现如今, 大部分家庭设备, 其实都是直流的, 有的外置,有的内置, 不管外置还是内置, 都有一个开关电源. 这些开关电源的工作原理, 恰恰是对 220v 的交流电进行整流, 变成大约 300v 的直流电, 然后内部再进行 DC 变压进而输出需要的电压.</p>
<p>而像电饭锅之类的设备, 实际上发热元件只是电阻, 本身并不在意是交流还是直流.</p>
<p>少数必须交流才能工作的电器(比如电风扇), 则可以单独配置逆变器解决, 或者改成更先进的变频电机(凡是变频电机的设备, 内部其实还是需要直流电的, 通常设备在电源线刚进去, 就通过一个整流器整流为直流电, 然后再使用.)的款式.</p>
<p>如此一来, 其实现如今, 家庭用电,其实完全可以使用 200v - 300v 的高压直流电.而不需要改造家电. 只需要在电线入户处配置一个高效的大功率整流器即可.</p>
<p>直流的好处, 首先就是比交流安全. 虽然 300v 的直流电仍然是属于可以电死人的电压, 但是仍然比交流电更安全.
第二个好处, 就是方便使用家庭储能, 少掉逆变器, 大大增加系统的可靠性, 大大降低系统的成本.
第三个好处, 方便接入新能源. 因为新能源通常产生的是直流电. 如果家庭内部使用的是直流电, 就可以直接并网让家庭优先使用本地产生的新能源, 而不需要进行并网控制.
第四个好处, 直流供电的家庭, 无法向电网馈送功率. 大大增强电网的稳定性.</p>
网络重新配置
2018-02-22T00:00:00+00:00
https://microcai.org/2018/02/22/network-reworked
<p>一直以来, 我用的都是基于 ss-redir 和 ipset + iptables 透明代理方式的科学上网. 但是 iptables 方式的上网, 其实还是不如 ChinaRoute 配合 VPN 来的爽. 因为只有 TCP 连接能被透明代理出去, UDP 和 ICMP 协议统统不能.</p>
<p>但是 openvpn 这种协议实在是太过招摇, 所以早就被方校长盯上了. 不可不可.</p>
<p>后来, 我想到了openvpn 可以透过纸飞机的代理再去连接远程服务器, 这样一来, 就解决了 openvpn 过墙问题.</p>
<p>于是直接在服务器上安装 openvpn 然后设定为使用 tcp 协议, 监听 127.0.0.1 地址.</p>
<p>然后在路由器上配置 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 代理就好了.</p>
<p>vtun0 网卡正常上线并获取到ip地址后, 先 ping 下 vtun0 的网关 10.8.0.1 , 发现是 OK 的. 接下来就是配置 route 了.</p>
<p>其实这个时候有2个办法配置路由表. 一个办法是使用 ChinaRoute 脚本. 让 openvpn 拨上后自动执行.
另一个办法呢, 还是使用 ipset + policy route.</p>
<p>chinaroute 这个, 任何路由器都能用, 这里讲下 ipset 和 policy route 在 ubnt 的路由器上如何使用.</p>
<p>虽然底层实现就是 iptables 做 rt-mark 然后用非默认的 route table 做路由, 但是ubnt把这套机制给简化了.</p>
<p>首先是在 protocol.static.table. 下面设定一个过墙用的路由表. 这个路由表很简单, 就只有一条 默认路由到 vtun0.</p>
<p><code>
set protocols static table 10 description "route to VPN"
<br />set protocols static table 10 interface-route 0.0.0.0/0 next-hop-interface vtun0
</code></p>
<p>注意, 因为使用的是 tun 模式, 所以是 interface-route, 如果是 tap 模式,则是 <code class="language-plaintext highlighter-rouge">set protocols static table 10 route 0.0.0.0/0 next-hop 10.8.0.1</code></p>
<p>因为 tun 是点对点设备, 只要指定下一跳的接口, 而 tap 是虚拟以太网卡, 需要指定网关的 ip 地址.</p>
<p>这样就设定了一个 id 是 10 的路由表, 表里只有一条到 VPN 的默认路由.</p>
<p>这个在普通 Linux 里, 设定的方式似乎是在 /etc/iproute2/rt_tables 里配置. 略微麻烦.</p>
<p>接着, 设定防火墙, 让 匹配某个 ipset 的包都通过 10 号路由表出去. 如果在 Wizard 向导里, 启用了 2WAN 负载均衡的话, 这个时候会已经创建好一个叫 balance 的 防火墙规则.
规则的rule xx 之类的, 都是负载均衡的规则. 执行次序是数字从小到大. 这里我插入了个编号 60 的规则</p>
<p><code>
set firewall modify balance rule 60 action modify<br />
set firewall modify balance rule 60 modify table 10<br />
set firewall modify balance rule 60 destination group address-group gfwlist<br />
set firewall modify balance rule 60 description "use vtun0 to route gfwlist"
</code></p>
<p>这条规则的意思是, 匹配目的地址为 gfwlist 组, 使用 10 号路由表.</p>
<p>在 EdgeOS 里, address-group network-group 之类的都是使用 ipset 实现的. 所以, 只要在 firewall 里弄个 gfwlist 地址组, 就会有个 gfwlist 的 ipset. 不需要在 EdgeOS 的配置里填入地址, 稍后我们用 dnsmasq 填入 ipset. 使用 <code class="language-plaintext highlighter-rouge">set firewall group address-group gfwlist</code> 这个命令建立 gfwlist 这个地址组.</p>
<p>如果么使用wizard搞 load balance, 则不会有这个 balance 规则. 可以自己建立. <code class="language-plaintext highlighter-rouge">set firewall modify balance</code> 就建立了.
然后需要在 switch0 里, 导入这个规则. <code class="language-plaintext highlighter-rouge">set interfaces switch switch0 firewall in modify balance</code> 这个意思是, 所有从 switch0 收到的包(这里in的意思), 都要经过 balance 这条规则修改.</p>
<p>接下来是让 dnsmasq 填入 ipset. 使用的方法是 gfwlist2ipset 这个脚本, 生成一个 dnsmasq-ipset.conf 文件, 丢到路由器的 /etc/dnsmasq.d/ 目录下就可以了.</p>
<p>对了, 别忘记, 如果 gfwlist2ipset 用的 8.8.8.8 这个外网dns, 要使用 <code class="language-plaintext highlighter-rouge">set protocols static interface-route 8.8.8.8/32 next-hop-interface vtun0</code> 这条命令, 给 8.8.8.8 地址设定下路由, 使用 vtun0 接口出去. 同样的, 如果是 tap 类型的设备, 要使用 <code class="language-plaintext highlighter-rouge">set protocols static route 8.8.8.8/32 next-hop 10.8.0.1</code></p>
Immutable, RCU and Transnational Container
2018-01-27T00:00:00+00:00
https://microcai.org/2018/01/27/immutable-rcu-and-transactional-container
<p>在编写并发程序的时候,经常要面对共享数据结构的保护问题。
虽然 C++ 提供的 RAII 实现的各种 guard lock 模式,早就让“忘记解锁” 导致的死锁成为历史。但是,锁带来的性能损耗依然是我们不得不面对的问题。</p>
<p>为了避免锁带来的开销,使用“无锁” 的数据结构似乎是个好主意。
然而事情的发展总是出乎所有人的意料。现代的多核CPU随着核数的越来越高,核与核之间的通信开销也变得越来越大。于是,所谓的“无锁” 容器,因为内部大量的“原子操作”变得越来越不适应于当前的时代。</p>
<p>不使用原子操作,又想大并发,又想安全,唯一能想到的,就只有“不修改” 了。 只读的数据,永远不需要锁的保护。这也是为何当下的程序员越来越青睐函数式编程。</p>
<p>然而,只有 const 没有 variable 的程序是根本不可能的。程序要运行,就得有变化。有变化,就不能使用只读的数据结构,不能只读,还是少不了要上锁。一锁,就离高性能大并发越来越远了。</p>
<p>有没有既可以修改,又可以无锁,还不需要使用原子操作的办法呢?</p>
<p>当然有啊,就是 RCU。但是 RCU 的局限性非常大,具体的来说就是,因为其赖以实现的原理导致了 RCU 只能是 list 容器。不能实现为更高效的 vector 和 hashmap 容器。</p>
<p>RCU 先修改 关联节点的 next/prev 指针,使得 后续的读者,会跳过当前要操作的节点,然后等待一段时间后, 释放节点。这样,读可以全速并发进行。无需任何锁的介入。而只有 list 容器,才能在不使用原子操作和锁的情况下,安全的 Detach 掉一个节点。map 容器,因为要对 tree 重新排序,无法实现成 RCU 操作。</p>
<p>即使 RCU 的读取速度再怎么好, list 就决定了,它在大元素量下注定的低性能。因此 rcu 即使在内核里,使用的也非常有限。只用在为数不多的 “读远大于写,而且容器元素不多” 的地方。</p>
<p>这个虽然不是 RCU 的问题,而是 list 的问题,但是 RCU 注定只能使用 list。</p>
<p>有没有办法让我最喜欢的 map/set/multiset/multimap/boost.multiindex 都能并发化呢?</p>
<p>一个数据结构,没有办法在修改的时候不上锁,主要原因就是他有“危险的中间态”。锁就是用来阻止其他线程访问危险的中间态的。</p>
<p>但是其实,不用锁也是有办法隔离掉危险的中间态的,就是 “Transaction”</p>
<p>如果对数据结构的多个修改,只有最终 commit 的时候, 才生效(其他线程才看的到),就不需要锁了。</p>
<p>使用 事务内存 就可以达成大并发,无锁,而且还不需要使用原子操作。</p>
<p>然而,如果没有事务内存又如何呢?其实,事务内存也是可以模拟的!今天要介绍的主角,就是在普通内存的机器上,为容器开启事务功能,达到使用事务内存一样的功效。</p>
<p>做法也很简单。就是使用2个容器 :smile:</p>
<p>假设要将一个 map 事务化, 设置一个当前指针 map* cur = new map;
然后,所有的读取操作,都在 *cur 上进行。</p>
<p>凡是要对 容器 做修改的时候, 先 map * alt = new map(*cur); 复制一份。
然后 alt 上进行修改。
改完了, cur = alt 赋值。搞定。</p>
<p>接着是对老容器的回收,这种情况下, 不能立即回收,因为可能还有其他线程在访问这个老容器。怎么办呢? 就是 setTimeout(1000, delete oldmap);</p>
<p>总不会这么长时间了, 还有线程在访问吧 :wink:</p>
C++ 是最好的编程语言
2017-11-04T00:00:00+00:00
https://microcai.org/2017/11/04/c++-is-the-best-language-forever
<p>好的语言是怎么来的? 有人说, 是设计出来的, 有人说, 是抄出来的. newbie 说, 是实践来的.</p>
<p>什么是实践呢? 实践, 就是摸石头过河, 吸收合适的, 抛弃不合适的. 这不就是进化吗?</p>
<p>正如好的物种是进化来的一样, 好的语言也是进化来的.</p>
<p>物种的进化, 首先是要适应已有的环境, 然后才是展望未来, 设法适应未来的环境. 即使某些功能器官已经不再需要, 仍然保留着进化的痕迹.
进化是不可逆的. 如果一开始因为环境因素, 进化出的生物特性在后来的环境变化中是非常有害的, 进化却不能推倒重来. 只能继续在这个不利的基础上改进.</p>
<p>认识到这一点, 再回过头来审视 C++ 语言. C++ 语言的许多缺陷, 来自对 C 的兼容, 或者确切的说, 是因为从 C 语言开始进化的, 对C的兼容, 就是进化的痕迹.
进化不能推倒重来, 只能在 C 这个不利的基础上改进.</p>
<p>但是, C 的不利, 是站在今天的角度看的. 在当时, C 是最成功的语言. 兼容 C, 是最有利于发展的. 从 C 开始进化, 在当时就是最好的选择. 而进化, 就是当年最优秀的物种才得以继续发展演化.</p>
<p>C 当年是最好最成功的语言, 那么进化就会发生在 C 的身上, 而不是其他语言的身上. 正如远古的猿类进化成了人, 同时也进化出了猴子一样, 远古的 C 语言进化出了 C++, 也会有顽固分子选择止步不前, 就有了现在的 C. 猴子当然不如人, 但是猴子也会存在. 所以 C 会继续存在. 但是真正的发展是看C++的.</p>
<p>C++ 的进化, 是采用”遗传学算法” 进行的. 遗传学算法, 需要一个是否更适用的判定函数, 和一个随机发展的过程函数. 每一个发展, 都是在原有的基础上进行迭代, 然后送入判定函数. 每一次迭代, 都会保留既有的成分.</p>
<p>C++的发展函数, 靠的是社区大量提交的提案.而判定, 则交给标准委员会的人投票表决. 提案首先要经过一个硬指标, 就是 C++ 之父设定的 零开销原则和兼容 原则. 而后才能进入是否更符合现代的软件工程实践的讨论中去.</p>
<p>因此, 社会的发展, 对语言的需求, 推动了C++的发展, C++ 一直在适应不断变化的市场, 而唯一不变的, 是 “零开销和兼容上一个版本”. 因此, 零开销和兼容 才是C++的根本, 其他的语法糖, 都是进化的痕迹.</p>
<p>C++ 是进化来的, 不可避免的会携带历史的包袱, 可正是因为他是进化来的, 他会携带的不仅仅是历史的包袱, 还有历史的财富. 丢弃包袱, 常常意味着放弃了财富, 我们的社会从来都不倾向于白手起家. 没有财富, 就要从头积累.</p>
<p>问题是: 谁能积累的有 C++ 多?</p>
不停机迁移
2017-06-19T00:00:00+00:00
https://microcai.org/2017/06/19/non-stoping-db-mitigrade
<p>设计一套系统的时候, 最初的数据库结构总是不能适应未来业务的发展变化。经常需要调整数据库的表结构以适应新的业务需求或调整逻辑结构。</p>
<p>为此,需要让系统能支持不停机迁移。不停机迁移,用户0感知的,完成数据库升级。要做到这点,首先要做到程序本身的热更新。</p>
<p>如果是 php 之类的脚本语言编写的服务器端程序,热更新实现起来倒也轻松容易。但是既然是我这个c++佬出来写文章, 一定是关注的c++编写的服务器端程序的热更新。</p>
<p>systemd 提供了一个热更新的新思路。就是让 systemd 持有 listen socket。然后fork出服务进程。
进程重启, listen socket 并没有销毁,因此用户依然可以在进程关闭期间发起连接。期间创建的连接,在进程启动后会被处理。
用户唯一能感知到的就是重启期间处理有一定的延迟。</p>
<p>支持 systemd 的 sccket activation 就实现了热更新的第一步。</p>
<p>接下来才是重点。使用 socket activation 的必然要求就就是重启迅速。新进程要迅速完成启动准备,并进入服务状态。连接总是被 hold 住,响应不及时,用户看来也是停机。</p>
<p>如果要修改数据库结构, 重启时就是修改的时机。这就要求,对数据库 schema 的修改必须要立即完成。
那么,什么样的操作能立即完成呢?</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>增加一个允许 NULL 字段。
删除一个字段。
重命名一个字段。
删除一个索引
创建一张新表
</code></pre></div></div>
<p>什么样的操作要避免呢?</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>增加一个非空字段
新建一个索引
</code></pre></div></div>
<p>并非所有的操作都要安排在程序重启期间进行。可以在重启前就执行。这就要求,对数据库的修改,必须保证旧程序代码兼容。如果不兼容,就首先热更新一个能同时兼容
旧库和(即将更新的)新库的程序。接着更改数据库。使用这种方式,可以放宽一个要求, 就是只要不(长时间)锁表的操作,都可以进行。例如,pg 的并发新建索引。</p>
使用异常实现cache
2017-04-09T00:00:00+00:00
https://microcai.org/2017/04/09/simple-cache-container
<p>异常用来做错误处理的时候,程序到处都是 try cache ,代码十分的丑陋,我是不怎么喜欢的,我喜欢 asio 那种用 error_code 汇报错误 —— 不传 ec 的时候就抛异常,传就不抛,改为写入错误到 ec。</p>
<p>但是,异常用来做流程控制,又特别的好用。流程控制,无非顺序、选择、分支和循环。在 c++里,又比 C 多了一个异常。在嵌套很深的地方,跳出逻辑,除了异常,就没有其他更好的办法了。</p>
<p>在编写软件的时候,时常需要对一些数据做 cache。在使用的时候,要先检查 cache,存在则使用 cache ,不存在则按照老办法办,然后存入 cache。</p>
<p>每次使用前都进行判断, 污染了快速路径的代码,对有简洁洁癖的程序员来说,内心是十分的纠结的。</p>
<p>这个时候, 你就需要 异常。将 cache hit 作为正常的流程进行编写, 假定全部的数据都是在 cache 里的。这万一发生了 cache miss , 则抛出异常,并在异常处理重新载入数据。然后重启处理。</p>
<p>说到重启处理, 在 Windows 的 SEH 里,存在 <code class="language-plaintext highlighter-rouge">EXECEPT_CONTINUE_EXECUTION</code> 这个异常处理的结果, windows 看到异常处理函数返回这个,就会回到发生异常的地方重新执行。然而这毕竟是一个 Windows 系统特有的 SEH , 而且依赖底层CPU提供的机制。 编写C++是断然不能使用这套机制的。</p>
<p>思考的最终结果,就是下面这样的结构</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
for (int =0; i < retry_times ; i++)
{
try
{
auto v = cache_map_sometype.get_cache(key);
// process with v ....
........
}
catch(cache_miss&)
{
// load v from other resources, database, filesystem, network, etc.
......
cache_map_sometype.add_cache(key, v);
contine; // NOTE about this.
}
break;
}
</code></pre></div></div>
<p>在正常处理流程里, 执行到最后会有个 break 退出 for 循环。所以 for 循环在 cache hit 的状态下只执行一次。在 cache miss 的时候, catch block 里最后有一行 continue 。 于是就重启处理过程了。</p>
<p>下面给出 cache_map 的代码。</p>
<pre style="color:#eff0f1;background-color:#232629;">
<span style="color:#27ae60;">#pragma once</span>
<span style="color:#27ae60;">#include </span><span style="color:#27ae60;"><tuple></span>
<span style="color:#27ae60;">#include </span><span style="color:#27ae60;"><map></span>
<span style="color:#27ae60;">#include </span><span style="color:#27ae60;"><boost/thread.hpp></span>
<span style="color:#27ae60;">#include </span><span style="color:#27ae60;"><boost/thread/shared_mutex.hpp></span>
<span style="color:#27ae60;">#include </span><span style="color:#27ae60;"><boost/date_time/posix_time/ptime.hpp></span>
<b>struct</b> cache_miss {};
<b>template</b><<b>typename</b> KeyType, <b>typename</b> ValueType, <span style="color:#2980b9;">int</span> cache_aging_time = <span style="color:#f67400;">30</span>>
<b>class</b> cache_map
: <b>protected</b> <span style="color:#59ff04;">std::map</span><KeyType, <span style="color:#59ff04;">std::tuple</span><ValueType, <span style="color:#56e092;">boost::posix_time::ptime</span>>>
{
<b>typedef</b> <span style="color:#59ff04;">std::map</span><KeyType, <span style="color:#59ff04;">std::tuple</span><ValueType, <span style="color:#56e092;">boost::posix_time::ptime</span>>> base_type;
<b>public</b>:
ValueType get_cache(<span style="color:#2980b9;">const</span> KeyType& key) <b>throw</b>(cache_miss)
{
<span style="color:#56e092;">boost::shared_lock</span><<span style="color:#56e092;">boost::shared_mutex</span>> l(m_mutex);
<b>typename</b> base_type::iterator it = base_type::find(key);
<b>if</b> (it == base_type::end())
{
<b>throw</b> cache_miss();
}
<span style="color:#59ff04;">std::tuple</span><ValueType, <span style="color:#56e092;">boost::posix_time::ptime</span>> & value_pack = it->second;
<b>auto</b> should_be_after = <span style="color:#56e092;">boost::posix_time::second_clock::universal_time</span>() - <span style="color:#56e092;">boost::posix_time::seconds</span>(cache_aging_time);
<b>if</b> (<span style="color:#59ff04;">std::get</span><<span style="color:#f67400;">1</span>>(value_pack) > should_be_after)
<b>return</b> <span style="color:#59ff04;">std::get</span><<span style="color:#f67400;">0</span>>(value_pack);
<b>throw</b> cache_miss();
}
<span style="color:#2980b9;">void</span> remove_cache(<span style="color:#2980b9;">const</span> KeyType& k)
{
<span style="color:#56e092;">boost::unique_lock</span><<span style="color:#56e092;">boost::shared_mutex</span>> l(m_mutex);
base_type::erase(k);
}
ValueType get_cache(<span style="color:#2980b9;">const</span> KeyType& key) <span style="color:#2980b9;">const</span> <b>throw</b>(cache_miss)
{
<span style="color:#56e092;">boost::shared_lock</span><<span style="color:#56e092;">boost::shared_mutex</span>> l(m_mutex);
<b>typename</b> base_type::const_iterator it = base_type::find(key);
<b>if</b> (it == base_type::end())
{
<b>throw</b> cache_miss();
}
<span style="color:#2980b9;">const</span> <span style="color:#59ff04;">std::tuple</span><ValueType, <span style="color:#56e092;">boost::posix_time::ptime</span>> & value_pack = it->second;
<b>auto</b> should_be_after = <span style="color:#56e092;">boost::posix_time::second_clock::universal_time</span>() - <span style="color:#56e092;">boost::posix_time::seconds</span>(cache_aging_time);
<b>if</b> (<span style="color:#59ff04;">std::get</span><<span style="color:#f67400;">1</span>>(value_pack) > should_be_after)
<b>return</b> <span style="color:#59ff04;">std::get</span><<span style="color:#f67400;">0</span>>(value_pack);
<b>throw</b> cache_miss();
}
<span style="color:#2980b9;">void</span> add_to_cache(<span style="color:#2980b9;">const</span> KeyType& k , <span style="color:#2980b9;">const</span> ValueType& v)
{
<span style="color:#56e092;">boost::unique_lock</span><<span style="color:#56e092;">boost::shared_mutex</span>> l(m_mutex);
base_type::erase(k);
base_type::insert(<span style="color:#59ff04;">std::make_pair</span>(k, <span style="color:#59ff04;">std::make_tuple</span>(v, <span style="color:#56e092;">boost::posix_time::second_clock::universal_time</span>())));
}
<span style="color:#2980b9;">void</span> tick()
{
<span style="color:#56e092;">boost::upgrade_lock</span><<span style="color:#56e092;">boost::shared_mutex</span>> readlock(m_mutex);
<span style="color:#59ff04;">std::shared_ptr</span><<span style="color:#56e092;">boost::upgrade_to_unique_lock</span><<span style="color:#56e092;">boost::shared_mutex</span>>> writelock;
<b>auto</b> should_be_after = <span style="color:#56e092;">boost::posix_time::second_clock::universal_time</span>() - <span style="color:#56e092;">boost::posix_time::seconds</span>(<span style="color:#f67400;">30</span>);
<b>for</b> (<b>auto</b> it = base_type::begin(); it != base_type::end(); )
{
<span style="color:#2980b9;">const</span> <span style="color:#59ff04;">std::tuple</span><ValueType, <span style="color:#56e092;">boost::posix_time::ptime</span>> & value_pack = it->second;
<b>if</b> (<span style="color:#59ff04;">std::get</span><<span style="color:#f67400;">1</span>>(value_pack) < should_be_after)
{
<b>if</b> (!writelock)
writelock.reset(<b>new</b> <span style="color:#56e092;">boost::upgrade_to_unique_lock</span><<span style="color:#56e092;">boost::shared_mutex</span>>(readlock));
base_type::erase(it++);
}
<b>else</b>
it++;
}
}
<b>private</b>:
<span style="color:#2980b9;">mutable</span> <span style="color:#56e092;">boost::shared_mutex</span> m_mutex;
};
<b>template</b><<b>typename</b> KeyType, <b>typename</b> ValueType>
<b>class</b> cache_map <KeyType, ValueType, <span style="color:#f67400;">0</span>>
: <b>protected</b> <span style="color:#59ff04;">std::map</span><KeyType, ValueType>
{
<b>typedef</b> <span style="color:#59ff04;">std::map</span><KeyType, ValueType> base_type;
<b>public</b>:
ValueType& get_cache(<span style="color:#2980b9;">const</span> KeyType& key) <b>throw</b>(cache_miss)
{
<span style="color:#56e092;">boost::shared_lock</span><<span style="color:#56e092;">boost::shared_mutex</span>> l(m_mutex);
<b>auto</b> it = base_type::find(key);
<b>if</b> (it == base_type::end())
{
<b>throw</b> cache_miss();
}
<b>return</b> it->second;
}
<span style="color:#2980b9;">void</span> add_to_cache(<span style="color:#2980b9;">const</span> KeyType& k , <span style="color:#2980b9;">const</span> ValueType& v)
{
<span style="color:#56e092;">boost::unique_lock</span><<span style="color:#56e092;">boost::shared_mutex</span>> l(m_mutex);
base_type::erase(k);
base_type::insert(<span style="color:#59ff04;">std::make_pair</span>(k, v));
}
<span style="color:#2980b9;">void</span> tick()
{
}
<b>private</b>:
<span style="color:#2980b9;">mutable</span> <span style="color:#56e092;">boost::shared_mutex</span> m_mutex;
};
</pre>
免费版的 PrimoCache
2017-02-10T00:00:00+00:00
https://microcai.org/2017/02/10/fix-slow-hdd-aka-free-version-of-PrimoCache
<p>去年DIY一个 NAS. 其实就是台 MINI PC. 自带一个很小的 mSATA SSD 用来装系统, 然后外接了一个大 HDD 做存储.</p>
<p>问题在于, 这个 大 HDD 是渣机械, 一开迅雷, 硬盘就被 100% 占用! 然后导致整个 OS 响应迟缓. 然后因为 HDD 写入速度不足, 导致迅雷下载速度 0 . 磁盘疯狂写入.</p>
<p>后来下了一个 PrimoCache ( 以前叫 FancyCache ) 后, 问题解决了.</p>
<p>然而只有 60 天试用期. 60天过去了, 我又得回到原来的老状态了吗? 非也!
我发现了一个叫 StarWind 的 iscsi software target. 只要装了 StarWind 就可以把 PC 变成一个 iscsi 的目标存储设备.
接着, 因为装的是 Windows Server 2012 R2 系统, 所以是自带了 iscsi 发起程序的. 于是把 127.0.0.1 的目标连起来.
于是写入路径就变成了 本地磁盘 D -> windows iscsi -> 本地网络 -> StarWind -> 原本地磁盘 D</p>
<p>虽然经过了多道程序的折腾, 但是因为 StarWind 提供了 写入缓存!!! 于是, 迅雷又不卡了.</p>
<p>StarWind 有免费版哦! 可以一直免费用下去, 没有试用期限制, nice!</p>
通知到轮询线程
2017-01-15T00:00:00+00:00
https://microcai.org/2017/01/15/pull-work-with-notify
<p>在腐都工作也有大半个月了。工作过程中,遇到了一个轮询+通知的消息模式。所以要轮询,是因为通知是不可靠的。
所以要通知,是因为轮询是不及时的。既要保证及时,又要保证可靠,就只能轮询和异步通知一起上。</p>
<p>因为异步通知的时候,会把轮询需要获得的状态一并携带上了。所以,获得通知后只是取消定时器,让轮询线程立即唤醒干活肯定是。。。 可以的但是有点浪费。如果在异步通知线程里直接调用处理呢,就要把处理的东西从轮询的线程里扣出来,这样轮询的代码就不直观了。</p>
<p>因为使用的是 asio 的 stackless coroutine。</p>
<p>不过,如果改造下 timer, async_wait 的时候是可以2个返回值的回调呢? 一个 ec 一个 通知的消息呢?</p>
<p>于是只要在 yield timer.async_wait 下面判断下 ec ,然后 yield async_pull <br />
如果 ec = aborted 那就不执行 yield async_pull . 直接把第二个参数看成是 async_pull 返回的。</p>
<p>这样就可以在不改动轮询代码的情况下,插入了异步通知。</p>
<p>结果伪造代码表示如下下</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kt">void</span> <span class="nf">operator</span><span class="p">()(</span><span class="n">boost</span><span class="o">::</span><span class="n">system</span><span class="o">::</span><span class="n">error_code</span> <span class="n">ec</span><span class="p">,</span> <span class="n">some_type_of_pull</span> <span class="n">pulled_message</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">reenter</span><span class="p">(</span><span class="k">this</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">while</span><span class="p">(</span><span class="n">is_timeout</span><span class="p">())</span> <span class="c1">// 超时后不再轮询</span>
<span class="p">{</span>
<span class="n">timer</span><span class="p">.</span><span class="n">expires_from_now</span><span class="p">(</span><span class="n">pull_interval</span><span class="p">);</span>
<span class="n">yield</span> <span class="n">timer</span><span class="p">.</span><span class="n">async_wait</span><span class="p">(</span><span class="o">*</span><span class="k">this</span><span class="p">);</span><span class="n">_</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ec</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">yield</span> <span class="n">async_pull_message</span><span class="p">(</span><span class="n">object_id</span><span class="p">,</span> <span class="o">*</span><span class="k">this</span><span class="p">);</span>
<span class="p">}</span><span class="k">else</span> <span class="k">if</span><span class="p">(</span><span class="n">ec</span> <span class="o">==</span> <span class="n">boost</span><span class="o">::</span><span class="n">asio</span><span class="o">::</span><span class="n">error</span><span class="o">::</span><span class="n">opertion_aborted</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// 强行没错误!</span>
<span class="n">ec</span> <span class="o">=</span> <span class="n">boost</span><span class="o">::</span><span class="n">system</span><span class="o">::</span><span class="n">error_code</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ec</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// 处理轮询消息</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">wake_up</span><span class="p">(</span><span class="n">some_type_of_pull</span> <span class="n">message</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">timer</span><span class="p">.</span><span class="n">wake_up</span><span class="p">(</span><span class="n">message</span><span class="p">);</span><span class="n">_</span>
<span class="p">}</span>
</code></pre></div></div>
<p>因为 timer 的回调的第二个参数是得万能类型,所以 timer 其实是个模板类。</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">template</span><span class="o"><</span><span class="k">typename</span> <span class="nc">TimerType</span><span class="p">,</span> <span class="k">typename</span> <span class="nc">T</span><span class="p">></span>
<span class="k">class</span> <span class="nc">smart_timer</span>
<span class="p">{</span>
<span class="nl">public:</span>
<span class="k">explicit</span> <span class="n">smart_timer</span><span class="p">(</span><span class="n">boost</span><span class="o">::</span><span class="n">asio</span><span class="o">::</span><span class="n">io_service</span><span class="o">&</span> <span class="n">io</span><span class="p">)</span>
<span class="o">:</span> <span class="n">io</span><span class="p">(</span><span class="n">io</span><span class="p">)</span>
<span class="p">,</span> <span class="n">m_timer</span><span class="p">(</span><span class="n">io</span><span class="p">)</span>
<span class="p">{}</span>
<span class="k">template</span><span class="o"><</span><span class="k">typename</span><span class="o">...</span> <span class="nc">TT</span><span class="p">></span>
<span class="kt">void</span> <span class="n">expires_from_now</span><span class="p">(</span><span class="n">TT</span><span class="p">...</span> <span class="n">arg</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">m_timer</span><span class="p">.</span><span class="n">expires_from_now</span><span class="p">(</span><span class="n">arg</span><span class="p">...);</span>
<span class="p">}</span>
<span class="k">template</span><span class="o"><</span><span class="k">typename</span> <span class="nc">Handler</span><span class="p">></span>
<span class="kt">void</span> <span class="n">async_wait</span><span class="p">(</span><span class="n">BOOST_ASIO_MOVE_ARG</span><span class="p">(</span><span class="n">Handler</span><span class="p">)</span> <span class="n">handler</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">unique_lock</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">mutex</span><span class="o">></span> <span class="n">l</span><span class="p">(</span><span class="n">m</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">no_wait</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">no_wait</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
<span class="n">io</span><span class="p">.</span><span class="n">post</span><span class="p">(</span><span class="n">boost</span><span class="o">::</span><span class="n">asio</span><span class="o">::</span><span class="n">detail</span><span class="o">::</span><span class="n">bind_handler</span><span class="p">(</span><span class="n">handler</span><span class="p">,</span> <span class="n">boost</span><span class="o">::</span><span class="n">asio</span><span class="o">::</span><span class="n">error</span><span class="o">::</span><span class="n">make_error_code</span><span class="p">(</span><span class="n">boost</span><span class="o">::</span><span class="n">asio</span><span class="o">::</span><span class="n">error</span><span class="o">::</span><span class="n">operation_aborted</span><span class="p">),</span> <span class="n">T</span><span class="p">()));</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">m_handler</span> <span class="o">=</span> <span class="n">handler</span><span class="p">;</span>
<span class="n">handler_set</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
<span class="n">m_timer</span><span class="p">.</span><span class="n">async_wait</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">bind</span><span class="p">(</span><span class="o">&</span><span class="n">smart_timer</span><span class="o">::</span><span class="n">handle_timer</span><span class="p">,</span> <span class="k">this</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">placeholders</span><span class="o">::</span><span class="n">_1</span><span class="p">));</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="n">wake_up</span><span class="p">(</span><span class="n">T</span> <span class="n">arg</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">unique_lock</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">mutex</span><span class="o">></span> <span class="n">l</span><span class="p">(</span><span class="n">m</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">handler_set</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">handler_set</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
<span class="n">io</span><span class="p">.</span><span class="n">post</span><span class="p">(</span><span class="n">boost</span><span class="o">::</span><span class="n">asio</span><span class="o">::</span><span class="n">detail</span><span class="o">::</span><span class="n">bind_handler</span><span class="p">(</span><span class="n">m_handler</span><span class="p">,</span> <span class="n">boost</span><span class="o">::</span><span class="n">asio</span><span class="o">::</span><span class="n">error</span><span class="o">::</span><span class="n">make_error_code</span><span class="p">(</span><span class="n">boost</span><span class="o">::</span><span class="n">asio</span><span class="o">::</span><span class="n">error</span><span class="o">::</span><span class="n">operation_aborted</span><span class="p">),</span> <span class="n">arg</span><span class="p">));</span>
<span class="n">boost</span><span class="o">::</span><span class="n">system</span><span class="o">::</span><span class="n">error_code</span> <span class="n">ignore</span><span class="p">;</span>
<span class="n">m_timer</span><span class="p">.</span><span class="n">cancel</span><span class="p">(</span><span class="n">ignore</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="n">no_wait</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>
<span class="nl">private:</span>
<span class="kt">void</span> <span class="n">handle_timer</span><span class="p">(</span><span class="n">boost</span><span class="o">::</span><span class="n">system</span><span class="o">::</span><span class="n">error_code</span> <span class="n">ec</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">unique_lock</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">mutex</span><span class="o">></span> <span class="n">l</span><span class="p">(</span><span class="n">m</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ec</span> <span class="o">==</span> <span class="n">boost</span><span class="o">::</span><span class="n">asio</span><span class="o">::</span><span class="n">error</span><span class="o">::</span><span class="n">operation_aborted</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// check for wake_up parameter.</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">handler_set</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
<span class="n">io</span><span class="p">.</span><span class="n">post</span><span class="p">(</span><span class="n">boost</span><span class="o">::</span><span class="n">asio</span><span class="o">::</span><span class="n">detail</span><span class="o">::</span><span class="n">bind_handler</span><span class="p">(</span><span class="n">m_handler</span><span class="p">,</span> <span class="n">boost</span><span class="o">::</span><span class="n">asio</span><span class="o">::</span><span class="n">error</span><span class="o">::</span><span class="n">make_error_code</span><span class="p">(</span><span class="n">boost</span><span class="o">::</span><span class="n">asio</span><span class="o">::</span><span class="n">error</span><span class="o">::</span><span class="n">interrupted</span><span class="p">),</span> <span class="n">T</span><span class="p">()));</span>
<span class="p">}</span>
<span class="nl">private:</span>
<span class="n">boost</span><span class="o">::</span><span class="n">asio</span><span class="o">::</span><span class="n">io_service</span><span class="o">&</span> <span class="n">io</span><span class="p">;</span>
<span class="n">TimerType</span> <span class="n">m_timer</span><span class="p">;</span>
<span class="kt">bool</span> <span class="n">handler_set</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
<span class="kt">bool</span> <span class="n">no_wait</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">function</span><span class="o"><</span><span class="kt">void</span><span class="p">(</span><span class="n">boost</span><span class="o">::</span><span class="n">system</span><span class="o">::</span><span class="n">error_code</span> <span class="n">ec</span><span class="p">,</span> <span class="n">T</span><span class="p">)</span><span class="o">></span> <span class="n">m_handler</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">mutex</span> <span class="n">m</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>
<p>在 coroutine 里, timer 的定义呢,是 <code class="language-plaintext highlighter-rouge">smart_timer<boost::asio::steady_timer, _some_type_of_pull>_</code></p>
<p>这样就可以了。</p>
Yaoi-city-here-I-come
2016-12-27T00:00:00+00:00
https://microcai.org/2016/12/27/Yaoi-city-here-I-come
<p>腐都我来啦!</p>
TCP拥塞控制
2016-12-27T00:00:00+00:00
https://microcai.org/2016/12/27/TCP-conguestion
<p>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 里.</p>
<p>控制本地发送的频率.
哪怕你本地是 10Gbps 的网卡. 你也要把这 500KB 的数据个均摊到 500ms 的时间内发.
我认为 这是 BBR 对传统拥塞控制算法的最大改进.</p>
<p>这个改进在 BBR 里是通过算法同时输出 Pacing Rate 和 cwin 实现的.
BBR 对 Linux 的TCP栈的巨大改进之二是让拥塞控制算法彻底接管 TCP 状态机. Linux 的 TCP 拥塞控制实现里, 拥塞算法只用来探测增窗, 一但丢包减窗逻辑就被内建的 PRR 算法接管. 而 BBR 则改变了之前的做法, 拥塞算法不仅仅控制窗口大小, 也控制发送时机.</p>
使用邻居的CMCC宽带加速
2016-12-07T00:00:00+00:00
https://microcai.org/2016/12/07/use-neighbor-CMCC
<p>基于某些特殊原因,我知道邻居家的wifi密码。</p>
<p>但是他家信号在我家虽然能收到,但是比较弱。所以一直没用。最近从newbie获赠了一个叫 Ubnt AirMAX 的山寨产品,有个定向天线。
这使得我可以在家接收邻居家的信号了。于是就开始用啦!</p>
<p>先上图。</p>
<p><img src="/images/fakeairmax.jpg" /></p>
<p>这是在阳台装好的效果。这货还支持 POE 供电,非常不错,网线到就可以干活了。</p>
<p>邻居家使用的是CMCC的网络,这是我要连上他家网络的主要原因。因为据说 CMCC 的出国带宽好啊! 而我呢,则是重度的墙外网络使用者。
所以想用他家的网络来加速我的国外访问。</p>
<p>连接的办法是 ERX -> POE交换机 -> 山寨网桥。</p>
<p>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 的网络嘛。</p>
<p>然后在 ERX 上 ping 192.168.100.1 成功。意味着能连上邻居的路由器了。</p>
<p>接着为 eth2.3389 接口开 NAT 伪装。这是必要的。因为我不想在邻居家的路由器上配置静态路由表 —— 因为他家的路由器并不支持动态路由协议 —— 当然我也不希望我家内部的网络被他访问到。</p>
<p>接下来才是重点。我需要做策略路由, 要求如下:</p>
<p>对所有 CMCC 的ip地址,通过他家访问。
对部分 CMCC 访问更快的朝外地址,用他家访问。
但是如果他家的网络不通了,以上策略路由要立即 fallback 到使用我自己家的网络。
只对 NAS 直接执行 1:1 负载均衡做宽带叠加,把 CMCC 和我家的宽带进行叠加。因为 NAS 上跑迅雷下载,需要大带宽。
对其他不需要 CMCC 加速的地址,使用 1:1 宽带叠加我双拨的2条PPPOE线路。
但是对于网银地址,不使用叠加(防止 ip 跳动导致的登录问题)。</p>
<p>于是,折腾了一下 ERX 的 firewall 规则,搞定。</p>
<p>基本思路是,使用 ERX 自带的 load-balance 功能。 load-balance 建立3个组,叫 A B C 吧。</p>
<p>A 组,包含 pppoe0 pppoe1 两条线路,做负载均衡。
B 组,包含 eth2.3389 和 pppoe0 两条,pppoe0 做 fallback-only。
C 组,包含 pppoe0 pppoe1 eth2.3389 三条线路,做负载均衡。</p>
<p>关键规则如下:</p>
<p>对 src 为 NAS 的流量,使用 C 组。</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w"> </span><span class="err">rule</span><span class="w"> </span><span class="mi">60</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="err">action</span><span class="w"> </span><span class="err">modify</span><span class="w">
</span><span class="err">description</span><span class="w"> </span><span class="s2">"use CMCC for NAS"</span><span class="w">
</span><span class="err">modify</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="err">lb-group</span><span class="w"> </span><span class="err">C</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="err">source</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="err">address</span><span class="w"> </span><span class="mf">100.64</span><span class="err">.</span><span class="mf">1.10</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>对 dst 为 cmccip 的线路,使用 B 组。</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w"> </span><span class="err">rule</span><span class="w"> </span><span class="mi">70</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="err">action</span><span class="w"> </span><span class="err">modify</span><span class="w">
</span><span class="err">description</span><span class="w"> </span><span class="s2">"use CMCC for cmcc ip"</span><span class="w">
</span><span class="err">destination</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="err">group</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="err">network-group</span><span class="w"> </span><span class="err">cmcc-ip</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="err">modify</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="err">lb-group</span><span class="w"> </span><span class="err">B</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>防止银行被负载均衡</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w"> </span><span class="err">rule</span><span class="w"> </span><span class="mi">50</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="err">action</span><span class="w"> </span><span class="err">modify</span><span class="w">
</span><span class="err">description</span><span class="w"> </span><span class="s2">"do not load blance on some site"</span><span class="w">
</span><span class="err">destination</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="err">group</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="err">network-group</span><span class="w"> </span><span class="err">bank</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="err">modify</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="err">table</span><span class="w"> </span><span class="err">main</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>默认做负载均衡</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w"> </span><span class="err">rule</span><span class="w"> </span><span class="mi">999</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="err">action</span><span class="w"> </span><span class="err">modify</span><span class="w">
</span><span class="err">modify</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="err">lb-group</span><span class="w"> </span><span class="err">A</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>这样就搞定了。只要把相应的 IP 加到 network-group 里就可以了。
我是用的 http://bgp.he.net/AS9808 获取的 移动的 IP 段。</p>
错误配置的路由器
2016-12-06T00:00:00+00:00
https://microcai.org/2016/12/06/misconfigured-router
<p>企业专线宽带通常会分配多个固定IP,一般是 4 个。也有的是 8 个。如何使用这多个IP地址也是一门学问。</p>
<p>我见过的多数企业,对于多个IP的使用基本上就2个模式。</p>
<p>模式1:只使用一个。(通常负责人还会抱怨给那么多地址干嘛,一个就够了,多的不要了,给降价吧!)</p>
<p>模式2:作为NAT地址池。提高大并发下的NAT效率。(这个负责人通常遇到上面那种人会觉得对方没文化,企业上网人数多,NAT端口不够用,自然要多个IP来平衡。)</p>
<p>在这2个模式下,如果企业还要搭建公网服务,则是在路由器上使用 DNAT。多个服务器的话就会把多个IP拿来做 1:1 映射。就是 ip1 的流量全部转给服务器1, ip2 的全部转给服务器2。这个叫 1:1 NAT。</p>
<p>其实这就是把在家用 TP-LINK 的时候的经验套到了企业网络管理中导致的。</p>
<p>正确的做法是,把公网地址直接配置到服务器上去。剩下的配置给路由器做NAT池带办公区的电脑上网。</p>
<p>如果服务器数量比较多,就多购买IP,每个IP的费用其实相当便宜(对拉的起企业专线的公司而言),并不会比服务器消耗的电费更多呢。</p>
中国电信原生IPv6配置
2016-11-29T00:00:00+00:00
https://microcai.org/2016/11/29/telecom-ipv6
<p>目前电信已经商用 ipv6 网络了,ipv6 的好处你懂得。</p>
<p>在继续之前,首先讲下,ipv6 同 ipv4 在配置上的不同。</p>
<p>对于家庭宽带,ipv4 是 pppoe 拨号的时候自动配置的。isp 给且仅给一个 ipv4 地址。
如果有多台设备需要上网,就需要使用一种叫 NAT 的技术进行网址共享。</p>
<p>但是 ipv6 地址有 128位那么长,世上每一粒沙子都能分配一个 ipv6 地址,意思就是分配不完的!
所以,运营商就很大方的给给你许多许多的 ipv6 地址了,也就不需要 NAT 啦!</p>
<p>由于不需要 NAT,因此在使用路由器共享上网的时候,配置就变得不同了。
使用 ipv4 的时候,路由器负责拨号,然后使用它自己的 DHCP服务器给局域网里的其他机器分配一个私有地址。
而到了 ipv6 的时候,情况就有所不同了,因为 ipv6 没有 NAT ,自然局域网里的机器并不需要也不能分配私有地址,而是需要获得一个全球唯一的地址。但是路由器也不是随便给你分配一个地址就行的,随机生成的地址并不一定能保证唯一性,而且也无法路由。isp 怎么能帮你路由一个随机的地址呢?
所以到了 ipv6 的时候,配置办法就变了一下。首先,路由器从 isp 获得 ipv6 地址,这个地址还是老办法,dhcp 自动获得。所以是 dhcp 从 pppoe 自动获得了 v4 和 v6 地址。这个没什么不同。
但是接下来就有不同点了。</p>
<p>接下来,LAN 口的地址配置开始不同了。 ipv4 是固定一个私有的 LAN 口地址,而 ipv6 则使用 dhcp-pd 这个技术从 isp 获得一个 公网地址。因为一般来说一个接口发 dhcp 请求是为了给自己获取地址,而这次,是给别的接口获得地址,所以这个叫 delegate 。又因为,isp 回给你的呢,是一个网段,而不是一般的dhcp给你一个地址。所以这个叫 prefix delegate.</p>
<p>isp 给你一个网段,对电信来说,是给的一个 /64 的网段。在这个网段里,路由器自己随便 pick 一个主机地址,然后其余的就都可以做 pool 分配给 LAN 里的主机了。</p>
<p>因此,配置过程就是,首先 pppoe 拨号,接着 dhcp 获得 v6 地址,然后 dhcp-pd 为 LAN 获得地址。</p>
<p>但是,dhcp-pd 并不能为 LAN 内的主机获得地址。 LAN 内的主机如何获得地址呢?</p>
<p>答案是 radvd。radvd 的作用是,启动的时候找到 LAN 的地址,然后就知道 LAN 的网段了。然后把这个网段当 pool 给局域网的主机分配。为啥这样能工作呢?
因为 LAN 所在的那个网段, isp 已经分配给你的路由器了。意味着只要是那个网段的地址, isp 都会给路由到你的路由器由你的路由器再做下一跳转发。因此只要你局域网内的机器和路由器的 LAN 口在一个网段即可。</p>
<p>那么,配置的办法其实很简单, 就是</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ubnt@ubnt:~<span class="nv">$ </span>configure
<span class="o">[</span>edit]
ubnt@ubnt# <span class="nb">set </span>interfaces ethernet eth0 pppoe 0 ipv6 address autoconf
ubnt@ubnt# <span class="nb">set </span>interfaces ethernet eth0 pppoe 0 dhcpv6-pd rapid-commit <span class="nb">enable
</span>ubnt@ubnt# <span class="nb">set </span>interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 prefix-length /60
ubnt@ubnt# <span class="nb">set </span>interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 interface switch0 prefix-id 1
ubnt@ubnt# <span class="nb">set </span>interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 interface switch0 host-address ::7788
ubnt@ubnt# <span class="nb">set </span>interfaces ethernet eth0 pppoe 0 dhcpv6-pd pd 1 interface switch0 service slaac
ubnt@ubnt# commit<span class="p">;</span> save<span class="p">;</span> <span class="nb">exit
</span>ubnt@ubnt:~<span class="nv">$ </span>reboot
</code></pre></div></div>
高速转发是如何实现的
2016-11-26T00:00:00+00:00
https://microcai.org/2016/11/26/how-non-blocking-forwarding-achieved
<p>以太网发展到如今,已经出现了 400Gbps 的传输速度了。在这个传输速度下,实现无阻塞转发,需要达到的转发性能要达到 579.2Mpps x 端口数,要实现无阻塞转发所需要的背部带宽达到 800Gbps x 端口数。没有任何已知的系统总线能提供如此大的带宽。</p>
<p>那么,高速转发是如何实现的呢?</p>
<p>首先,将问题分解,一次转发分解成多个动作,然后看这些动作如何实现。</p>
<p>第一,网络接口收到数据包。这里,计算机主机的做法是使用中断进行通知。然而路由器是专门的硬件,它没有除了转发数据包以外的任务。因此它不需要使用中断。而是直接CPU不停的轮询。不仅仅如此,它为每一个端口分配一个CPU。为了降低成本,提高速度,这个专门的干活的CPU通常是一块 ASIC 而不是一块通用 CPU。这个专门的芯片就叫转发引擎吧。他就是专门设计的一个只用来干包转发这一个活的CPU。这样就解决了发收包的瓶颈。</p>
<p>第二,对每个收到的包,要进行查表,决定转发的目的端口。对交换机来说,是以 MAC 地址查表,对三层交换机或路由器来说,是以IP地址进行查表。那么查表速度就成了关键。对内存来说,他的输入是内存地址,输出是该地址的内容,但是,有另一种内存,叫 CAM , Content Addressable Memory,它的输入是内容,输出是内容的key。CAM 能实现硬件查操作,硬件单周期查表。不过 IP 地址和 MAC 地址有些许不同, MAC 地址使用的是精确匹配,而IP地址是用的最长前缀匹配。所以三层转发需要使用 TCAM。 TCAM 就是 三态CAM, 除了 0 和 1 还有个比较状叫 Don’t Care。这样 TCAM 的匹配结果是有多个的,但是只有第一个匹配结果会输出,只要匹配内容预先按照掩码长度排序就可以实现最长前缀匹配了。</p>
<p>完成查表,知道了目的转发端口后,这个数据就该通过 内部背板 发送到另一个端口了。最初背板带宽是一个总线结构,但是随着端口速率的上升,总线结构的背板早就无法支撑交换机需要的巨大带宽了。每个端口两两互联,倒是能解决了带宽问题,但是这意味着端口连线太多。</p>
<p>因此现在路由器使用的是 “交换式背板”。结构如下图所示:</p>
<p>交换式背板是一块纯电路。在每个交叉的位置使用了开关晶体管控制通断。从图片上可以看到 1 2 3 4 发和 1 2 3 4 收可以通过开关实现完全独立的互连。注意这里的 发和收是编号一样的。1 号发和1号收其实就是1号端口。</p>
<p>这样通过开关控制,所有的端口都可以在需要的时候建立点对点连接。由于是点对点链接,因此互联线路的带宽实际上并不会比以太网更高。设计难度就大大降低了。</p>
<p>然后到了发送端口,发送端口再根据 QoS 进行排队然后发出或者列队太长就丢弃。</p>
<p>这样,就可以完全在现有的技术条件下,完成超高速的交换。看起来几百 Tbps 的背板带宽实际上不如 PCIE x16 更有难度。</p>
<p>转发决策是分布式的,每个端口一个,因此每个转发引擎并不感受巨大的压力。同时压力最大的差路由表又通过 TCAM 硬件实现快速查找。</p>
死在以太网发展道路上的网们
2016-11-24T00:00:00+00:00
https://microcai.org/2016/11/24/the-networks-that-died-in-the-hands-of-etherenet
<p>记得 Robert Metcalf(以太网的发明者)说过如果有技术最终替代了以太网,那么它还是会被叫做“以太网” ,所以以太网永远不会消亡。</p>
<p>以太网,诚如它所言,真的像火凤凰一样,不断的涅槃重生,从最初的 3Mbps DIX 以太网到最新的 400Gbps 以太网。历经 10Mbps 100Mbps 1000Mbps 10Gbps 40Gbps 100Gbps 的磨练,一次次的幻化重生。然而,都叫以太网。并在这不断的重生中,一路排除掉了全部的竞争对手。</p>
<p>以太网到底凭什么,有如此顽强的生命力,每一次重生都宣布着对手的永久性死亡。先不忙着下结论,让我们看看, 以太网每次重生,都干掉了那些对手吧。</p>
<p>以太网初生的时候,有个重量对手, ARCnet。ARCnet 提供了 10Mbps 的总线结构的连接。所有接到同轴电缆上的机器共享 10Mbps 的带宽。这点和以太网是一致的。不一致的地方在于以太网使用 CSMA/CD 在同轴线上竞争使用网络,而ARCnet则使用令牌决定谁来发送数据,减少了冲突,在主机较多的情况下效率更高。因此最初和以太网的较量中,还略占优势。得益于ARCnet的协议简单,其接口控制卡的售价也低于以太网。因此在竞争中占据了优势。</p>
<p>但是优势很快就消失了,因为基于 非屏蔽双绞线 的以太网诞生了。非屏蔽双绞线大幅降低布线成本。同时网桥的出现隔离了冲突域。而且在2对双绞线上传输能同时实现首发,也就是实现了全双工通信。这些优点使得ARCnet的竞争中败下阵来。</p>
<p>80 年代末的令牌网已经能提供 16Mbps 的产品同以太网较量。令牌网不仅速度快于以太网,而且由于以太网使用CSMA/CD导致冲突发生时效率地下,不管怎么看以太网都处于竞争劣势。但是拯救以太网的“交换机”神奇的出现了。交换机隔离了冲突域,每台主机同交换机之间都能独享这一条链路上的带宽。由于取消了总线结构,星型结构使得布线和移动网络端口都变得轻而易举。而且容易定位网络错误。使得以太网相对竞争者提供了更好的优势。更重要的是,它保护了现有的投资。只要新增交换机,就可以提升网络质量,而不需要大飞周张的更换设备。</p>
<p>接着,快速以太网适时发布。100Mbps 的速率即使放到今天都不显得缓慢呢!更重要的是, 100Mbps 的以太网提供了绝佳的向后兼容性。得益于交换机的使用,企业可以混用 100M 和 10M 的设备组网。这样大大的降低了升级的成本。不仅现有的以太网要升级,而且连使用令牌网的用户也跟着转移过来了。</p>
<p>没过多久,千兆以太网标准化完成。同样提供了向后兼容性。 1000M 100M 和 10M 的设备可以在网络里和谐共存。自此,以太网在局域网领域干掉了所有的竞争对手。</p>
<p>但是,以太网还有更大的野心,刚掉广域网的竞争对手。他首先干掉了 FDDI。 FDDI 使用光纤介质,提供 100Mbps 的速率。既可以作为局域网,也可以做广域网。而 100Mbps 的光纤以太网的出现把 FDDI 消灭了。如果我在局域网里可以使用以太网,为啥我不可以直接把局域网扩展到更大的范围呢? 与其使用额外的设备,不如继续使用以太网。如果使用其他协议,就不能和现有的以太网互通了。</p>
<p>在局域网占据垄断地位的以太网,当他要开发超过100M的通信介质,就把速率低于 以太网的光纤网络一个一个的消灭了。SDH 但是能提供155 Mbit/s 的速度,但是在千兆光纤以太网面前又如何能抵挡。传统的光纤通信格式在以太网面前不断沦陷。</p>
<p>SONET/SDH 和 ATM 被寄予厚望。毕竟 SONAT 能提供更快的速率。但是在万兆以太网和十万兆以太网的夹击下,市场越来越小。 与此同时,IP 网络的发展也推波助澜。传统上的电信网络,是为了支持“电话” 这一目的,用来跑 IP 包,反而有种杀鸡用牛刀的感觉,成本上无法敌过直接使用以太网作为IP网的承载。当 40G/100G 以太网完成标准化的时候,传统电信网络的木日就到来了。毕竟,对运营商来说,电话越来越少了,IP业务越来越多了。使用更适合承载IP网的技术不是更好的选择吗? 最后连电话都使用 SIP 网关,运营商改用IP网承载电话业务。</p>
<p>于是以太网在骨干网全面替代其他对手。以太网完成了局域网和广域网的统一。干掉了一切竞争对手。</p>
<p>以太网不仅仅在有线通信领域干掉了一切对手,在无线领域依然在逐步蚕食。移动网络的绝大部分流量其实并不是通过 4G 3G 承载的,而是通过的 WiFi。WiFi,是以太网的在无线领域的延伸。WiFi在 MAC层依然使用以太网帧,只是在物理电气层,应用无线替代了有线。正如光纤以太网一样,只是在物理电气层使用了激光代替了电压。甚至 WiFi 使用的共享方式,也是 CSMA/CA 协议。是针对无线信道的特点而对 CSMA/CD 协议的优化。</p>
<p>WiFi 通常运行于 ISM 频道,但是运行于非ISM频段的 wifi,就摇身一变成了 WiMAX,成为 3G 标准。WiMAX-Advanced 也进入了 4G 标准。虽然在电信行业的联合绞杀下败于 LTE。但是,以太网还会回来的。</p>
<p>===</p>
<p>说了那么多历史,以太网为啥能打败竞争对手脱颖而出呢?</p>
<p>以太网在局域网里打败对手,能学到的理由是,简单协议,足够的性能,低廉的价格, 还有 不断的进步,保持兼容。</p>
<p>当以太网拿下局域网,扩张到广域网又是如何进行的? 学到的理由是,建立根据地,农村包围城市。根据地的意思是,有了局域网这个市场,产品有产量保障,而不会因为无人问津产量稀少导致价格高企。
农村包围城市,是因为 骨干网的末端都是一个个的局域网,当所有的局域网都是基于以太网的时候,把广域网纳入以太网就变得顺理成章了。</p>
<p>同时,抛开硬件不谈,以太网还有一个软件层面的巨大优势: 以太网适合承载IP网。
选对队友真的很重要!</p>
脑洞:可编程网卡
2016-11-21T00:00:00+00:00
https://microcai.org/2016/11/21/network-shader
<p>很久以前,网卡就只是个物理协议转换装置。各种数据的处理都是靠的 CPU。随着网卡支持的速率的提升,还有人们对效率的追求,网卡开始支持将一些操作给 offload 到网卡上执行。比如checksum的计算。再后来,连 TCP 这样的协议都可以直接交给网卡处理。叫 TCP offload。</p>
<p>网卡能 offload 的东西越多,高网络负载下的 cpu 使用率就越低。数据处理也就越有效率。
既然连 TCP 都能交给网卡处理了,为何不更进一步实行通用 offload 呢?</p>
<p>通用 offload 的实现,可以参考显卡的通用可编程渲染管线。搞一个通用网络包处理管线。指定个shader语言,就叫 network shader吧。把 network shader 载入网卡,就可以让网卡执行 network shader 处理收到的数据包。对于太复杂的逻辑,再转给 CPU 上的代码处理,简单的逻辑就可以直接片上处理完成。这对很多 “数据包转发类“ 的引用程序(各种Proxy)来说,可以极大的提升性能。</p>
路由口和交换口
2016-11-20T00:00:00+00:00
https://microcai.org/2016/11/20/ports-on-router
<p>很久以前,我买路由器非常关心路由器有几个口。但是我最终发现,其实路由器都只有2个口。
一个 WAN 口一个LAN口。哪怕是一些双WAN的路由器,其实也只有3个口,2个WAN口一个LAN口而已。</p>
<p>至于你看到的4个LAN口,那不是口,那只是内置了一个交换机。</p>
<p>说道这,就得科普一下 路由口和交换口 的区别。</p>
<p>所谓交换口,就是一个2层可以通的RJ45口。而路由口,才是可以配置 IP 地址的口,路由口才能做三层转发.</p>
<p>一般路由器只有2个口,一个WAN口,DHCP获取或者PPPOE获取,一个 LAN 口,固定ip,并可以为下面的机器提供 DHCP。 LAN 口通过交换机扩展出多个交换口。</p>
<p>交换口是不值钱的。一个 8LAN 口的和 4 LAN 口的路由器,成本差距微乎其微额。因为本质上这个路由器只是双口路由器,只是内置的交换机4口还是8口的区别。</p>
<p>而交换机,这种口这么少的,是不值钱的。不信自己上马云家找找看。几十块钱就能买到8个口的交换机。还是8个千兆口的。8LAN口的通常还只是百兆口。</p>
<p>路由口才值钱。真的路由器,上有5个口,才真的是贵的很呢。</p>
<p>在路由器市场,杀出了一个叫 Ubnt 的公司,简直就是杀价神,小米在它眼里简直不知道 low 到哪里去了。</p>
<p>他最新被众人注意到,是因为推出了一个 3 口路由器 ER-Lite。注意我上面说的路由口和交换口的区别。这个是3个路由口的路由器。还支持千兆线速转发。价钱居然不到一百美金。简直是价格屠夫。</p>
<p>但是这不是重点,重点是没多久它又推出了一个5口路由器 ERX 。价格 49美刀!
注意是5个路由口哦! ER-PRO 也有5 个口,但是其实后面3个口是在一个交换机上的,本质是3个路由口。</p>
<p>哦,为啥比3口的还便宜,因为虽然有5个路由口,但是包转发率下来了。虽然下来了,但是如果发的是大包而不是小包,那跑千兆还是绰绰有余的。只是跑小包就达不到千兆了,也就是所谓的无法线速转发。</p>
<p>但是家庭使用,在需要速度的地方(NAS,迅雷下载),必定是大包转发。所以,就是个49美元能买到的5个路由口的机器啊!简直是价格屠夫。要知道老 思科 那些百兆的5口路由器二手都不止 49美元呢!虽然能线速百兆,问题是也才百兆而已。</p>
<p>虽然说有5个口,意味着5个网络,家里一般就一个 wan 一个 lan ,哪里用得到5个?
抛开有的人要双拨,然后不同设备组到不同的网段里不谈,大部分人其实只需要双口路由器的。
这种情况下,他也提供了把任意的口划到一个交换组里的功能。 划到交换组里的口就不能设置 ip地址了,不过可以统一给这个交换组设置一个ip。划到交换组里的口,进行二层包转发的时候,也是会使用独立的交换芯片进行,不占用cpu的资源。</p>
<p>物理上这是个单芯片的路由器,但是逻辑上,芯片上除了cpu,还集成了5个网卡和一个交换机的。 5个 RJ45 的 phy 接口可以随意的配置为接入交换机或者接入网卡。所以接入交换机后,就不受他的标称包转发速率的影响的,其实也是个线速转发的5口交换芯片。</p>
<p>不过这个价钱你肯定不会把他当个交换机的,当然是5个网段搞起咯。扩展端口什么的,用个额外的交换机就可以啦!</p>
soho路由器是单臂路由器
2016-11-14T00:00:00+00:00
https://microcai.org/2016/11/14/soho-router-is-one-armed-router
<p>用 VLAN 划分局域网后,2个局域网之间就不能通信了。如果要让这2个网络能互访,就需要路由器。如果VLAN只是用来做端口隔离,那它本身就毫无意义,买多台独立的口少点的交换机更便宜。VLAN所以那么有用,完全是因为 vlan trunk 端口的存在。使得以太网在交换机上变成了一种隧道。一个二层的隧道。于是只有一个接口的路由器在这种隧道的帮助下能访问被 VLAN 隔离的网络。这种路由器,就是单臂路由器。单臂路由器虽然性能不好,但是能以极低的成本解决网间互联问题。如果网间没有大量的数据交互,单臂路由器就非常合适。当然如果网间有大量数据交换那还是上个3层交换机吧。</p>
<p>单臂路由器的性能受限的原因是,多个VLAN之间的总数据交换大小为 trunk 端口的 1/2。VLAN1到 VLAN2 的单向数据,就占用 trunk 端口的双向流量。因此总交换容量就只有端口速率的 1/2。对大部分网络来说,这是双向50M(百兆以太网) 或者双向500M(千兆以太网)。</p>
<p>尽管性能不佳,但是成本低廉,因此应用非常广泛。常见的soho路由器就是单臂路由器和vlan交换机的结合体。</p>
<p>soho路由器的有线部分,实际上就是个单臂路由器。路由器上的 WAN 口和 LAN口只是分属2个(固定的)vlan。交换芯片有一个隐藏端口直连CPU。这个直连端口就是个 trunk 端口。</p>
<p>这就是为何百兆路由器并不能跑满百兆宽带。因为单臂路由器就是上下行总和100M, 而且百兆宽带是下行 100M。如果还有上行数据,那么无论如何都无法跑满下行带宽了。</p>
<p>更换千兆路由器其实有时候并不能解决这个问题,因为…</p>
<p>许多廉价的千兆路由器,居然也仅仅是千兆交换芯片。trunk 口直连 CPU 的线路依然是百兆的。因此不是换千兆路由器就行的。你看到的千兆连接只是假象。这样还不如维持原来的路由器,只通过采购额外的千兆交换机来提升内网速度成本来的更低。而且独立的交换机其背板带宽是充足的。而路由器上自带的交换芯片。。通常背板带宽是严重不足的。你能想象内网两台正在对拷数据,居然会影响第三台电脑上外网速度的吗? 这就是路由器自带的交换芯片背板带宽不足的表现。</p>
Linux多WAN负载均衡原理
2016-11-02T00:00:00+00:00
https://microcai.org/2016/11/02/load-balancing
<p>玩了一段时间 EdgeRouter , 对他的负载均衡的可玩性非常着迷。仔细研究后发现,他的实现原理是 iptables 标记数据包,然后对标记的数据包使用使用特定的路由表实现的。</p>
<p>Linux 默认有一个路由表。这张路由表叫 main 路由表。但是可以用 ip route 命令添加其他路由表。非默认路由表可以用一个数字编号。并且 ip rule fwmark 命令可以设定让被标记的数据使用这个路由表。</p>
<p>接着,接下来的工作就是,对数据包进行标记。</p>
<p>标记是基于 connection 的,否则对同一个tcp链接的包用不同的接口出去那可就乱套了。
基于 conntrak 链,对被跟踪的连接打标记。 打标记的时候,可以首先根据目的 ip 打标记,这样就可以避免有特定的ip跟踪的网站出现访问异常。
然后接下来的包,就用 probability 规则打标记. 也就是可能性。 这样有 50% 的新链接被打到 mark 1 上, 50% 的打到 mark 2 上。于是就有一半的流量走 table 1 , 一半的走 table 2</p>
<p>table 1 使用的 interface 1 做默认网关, table 2 用 interface 2 做默认网关。 于是2个接口上的流量就这样被平均过去了。</p>
家庭wifi覆盖建议
2016-10-24T00:00:00+00:00
https://microcai.org/2016/10/24/home-wifi
<p>私以为,目前民用 wifi 路由器市场已经完全走上了邪路。天线越来越大,发射功率越做越高。都敢冒着法律的风险使用大大超过 100mW 的发射功率了。且不论巨大的发射功率可能对人体造成的影响,就从技术上说wifi的特点,为什么wifi不适合大功率发射吧。</p>
<h1 id="第一点空口带宽是个共享带宽">第一点,空口带宽是个共享带宽。</h1>
<p>共享范围是无线可达的地方,取决于发射功率和损耗。频率越高损耗越大。发射功率越大,意味着越多的设备进入这个大 HUB 来共享空口带宽。不是一个 AP 下带的客户端共享带宽,而是所有无线设备在这个区域内一同共享带宽。一味增加发射功率,违背了网络隔离较大广播域的原则。</p>
<h1 id="第二点同频干扰严重">第二点,同频干扰严重。</h1>
<p>ISA 频段限制发射功率并不是没有原因的。就是为了限制同频干扰。把同频干扰限制到一个几十米的范围内。在一个小范围内,出现多个同频发射点的概率就小了。增加发射功率,意味着距离很远的2个家庭都会相互干扰对方的网络。大大劣化网络环境。</p>
<h1 id="第三点自干扰">第三点,自干扰</h1>
<p>发射功率越大,路由器工作的时候自己给自己增加的干扰就越大。其实无线通信最关键的不是接收信号大小,而是信噪比。
如果接受到的信号很强,但是伴随着同样强的噪音信号,一样是无法通信的。</p>
<p>===</p>
<p>#那么,解决的办法有没有? 其实是有的,就是商业环境已经使用很成熟的 多AP 蜂窝式覆盖。</p>
<p>有人会说,商业使用的,不适合家用吧?</p>
<p>呵呵,从前也只有商业使用才会用 100M 宽带,现在不是家家户户都用上了吗? 并没有什么是不合适家用的,只是价钱问题而已。只要价钱低,家庭用户能承受,并没有一定要傲娇到非不去使用,就觉得那样用法是不家庭的。</p>
<p>其实很多人已经在家里使用多AP(路由器当AP用)覆盖了,但是效果并不好,需要手动选择最近的 AP。这是为啥?</p>
<p>因为家用的路由器都缺乏一个功能,就是 AP 漫游。</p>
<p>没有 AP 漫游,即使2个家用路由器设置了一样的 SSID,即使他们都是接的 LAN 口,关了 DHCP 只当 AP 用。设备也不会认为这2个同名的 SSID 有什么关联。并不会主动的切换到信号更强的那个上。除非距离实在远,信号实在差,丢包很严重后,才会依依不舍的断开连接,寻找下一个信号更强的。而且这个寻找也不会是寻找同 SSID 的下一个信号更强的,而是好像这些 AP 都是属于独立的网络一样,按照信号强度排序。很多时候因为其他 ssid 你没有密码,所以恰好就会连上同 SSID 的另一个路由器而已。</p>
<p>而商用 AP,由于有了一个集中的 “AP 控制器” 简称 AC , 所以可以控制 AP 实现无缝漫游。而因为是商业产品,所以价格不菲。因为 AP 的大部分功能被 AC 取代,所以又叫瘦 AP, Fit AP。</p>
<h1 id="但是经过多年的发展-ac-和-fit-ap-的价格终于降低到家庭用户可以承担的地步啦">但是经过多年的发展, AC 和 Fit AP 的价格终于降低到家庭用户可以承担的地步啦!!!!!</h1>
<p>当然这事得感谢 TP-LINK 。</p>
<p>其实家用路由器几十块钱就能买到一个,这在 TP-LINK 出现前,也是不可能的事情。所以很久以前家里有多台电脑的用户,电信都是给了 ‘多拨’ 这样的解决办法。就是允许使用交换机,然后每台电脑各自拨号。那时候没有 TP-LINK ,家庭用户最多就买得起交换机而已。大部分可能还是用集线器,交换机都不舍得买。</p>
<p>好,废话不多说, 了解下, 使用 TP-LINK 的解决方案,需要多少钱。</p>
<p>首先,你得有个大房子,一个普通路由器信号覆盖不了的那种。要么就买非常贵的 AC1900 的那种高大上的, 8 根天线的,然后保佑买来测试后没什么大的死角。要么就改装天线,改电路,弄个超大功率。再要么,就是使用我推崇的方式,多AP蜂窝覆盖。</p>
<p>改装这条路 pass 掉。
一个信号非常好,天线很牛逼的路由器,要多少钱呢?</p>
<p>让我们看看这货:</p>
<p><img src="https://gd2.alicdn.com/imgextra/i4/22936598/TB2Usj4epXXXXXZXpXXXXXXXXXX_%21%2122936598.jpg_400x400.jpg" /></p>
<p>这货是 Google 捣鼓出来的家用路由器。信号测试可以看 Linus 的视频 http://www.bilibili.com/video/av3175439/</p>
<p>这货的价格是高大上的 1000+ !</p>
<p>好,既然你的家庭只能靠一千多的路由器,然后求佛祖保佑能没有死角。那么不如看看另一个解决方案, 是不是能花更少的钱获得更好的覆盖效果。</p>
<h1 id="ac--ap-方案">AC + AP 方案。</h1>
<p>一台有线路由器, 30块钱一个。
TP-LINK AC100 这个是 AC , 价钱是 200 块钱一个。
一台 POE 交换机 大概 100 - 300 元。看你对端口数量而定。端口数量 >= AP 数量即可。
POE 是可选的。 因为 AP 通常支持 POE 供电 或者 插座供电。
若干 Fit AP. 每台100元左右。</p>
<p>基本上2个就可以达成比 AC1900 更好的覆盖, 3 个以上就可以达成一台不论多贵的 AC1900 都无法达成的覆盖了。
相信成本上哪个划算已经一目了然了。</p>
<p>而且可以在卧室重点覆盖,意味着在你经常上网的地方可以保证 100% 信号强度。而不是一台 无线路由器就只能放在正中央,否则无法全覆盖。于是卧室这样的地方信号强度就别想充足了。</p>
<p>这样带来的上网体验也是不可同日而语。而且因为是 AC + AP 方案。所以多个 AP 是自动管理,可以无缝漫游的。并不会因为 AP 多了带来管理上的麻烦。</p>
<p>OVER。</p>
新的路由器
2016-10-18T00:00:00+00:00
https://microcai.org/2016/10/18/new-router
<p>上文说到,我使用无线网桥衔接了2个家庭,组成了一个局域网,共享了宽带。</p>
<p>但是,局域网和局域网还是太过开放,网桥又是一段不怎么可靠的网线。因此2个局域网最好能隔离开来,隔离广播域,又能通过IP地址直连。
经过研究发现家里宽带还可以多拨。虽然多拨并不叠加带宽,但是可以多获得一个公网ip。有多一个 ip 对 NAT 来说,也是可以极大的提升网络性能的。</p>
<p>于是找啊找,找发现了一个利器 “ubnt EdgeRouter X”。 不过 350 块钱的价格,但是却提供了思科路由器一样的功能。BGP OSPF RIP 动态路由协议应有尽有。什么?家用用不到动态路由协议? 呵呵,马上你就知道了,我确实需要它。 提供5个千兆网口,更重要的是,五个独立的网口。也就是在操作系统里是 eth0 eth1 eth2 wth3 eth4 5块网卡。这就可以进行比较高级的玩法了。可以任意组合 N WAN + M LAN ,而不像普通路由器那样,只有一个 WAN 一个 LAN ( 或者少数几个也不过支持双WAN)。 基本上就是一台软路由的玩法了。而且一般软路由都没有5个网卡哦!</p>
<p>电信自带光猫有2个LAN口,这就是给多拨准备的啊!</p>
<p>于是作出了这样的规划</p>
<p><img src="/images/all-network-struct.svg" width="100%" /></p>
<p>ERX路由器和猫之间连了2条网线。直接双拨。</p>
<p>路由器一个 LAN 连 原来的 wifi 路由器,另一个 LAN 连网桥。</p>
<p>把原来的路由器,都使用 WAN 口 (注意,是 WAN口哦!) 连到 ERX。无线网桥相当于是网线,所以是姐姐家的路由器 WAN口连到了 ERX 上。</p>
<p>同时,在两台wifi路由器上关闭 NAT然后打开动态路由 !!! 这个很重要!!! LAN 口的 DHCP 打开, WAN 口使用 DHCP 自动获取。</p>
<p>各自的路由器就通过自己的 DHCP 管理自己网段内的设备了。 然后因为开启了 动态路由协议,所以 ERX 能顺利的自动找到2个子网的路由。</p>
<p>然后2家的局域网就网段隔离了,但是有路由器在,ip 是可以直通的,局域网内主机互访没有问题,没有经过NAT。只有通过外网的时候才被 ERX NAT 一次。而且经过了负载均衡。</p>
<p>然后意外的发现,上行可以叠加。</p>
远程共享宽带
2016-10-11T00:00:00+00:00
https://microcai.org/2016/10/11/long-range-wifi
<p>最近姐姐搬迁到了新家。在我家屋顶能直接看到她的新家。所谓目视可达。我就开始思考,是不是可以无线直连,然后共享网络呢。</p>
<p>于是淘宝上搜了一圈,最后弄了对山寨 UBNT 无线网桥。然后买了一箱网线,从家里一直沿着电信的光纤一路跟随布线到了楼顶。</p>
<p>架设好后,姐姐家同理。也是室外放个网桥,然后线走进屋内。</p>
<p>在屋内,2个网桥都分别和各自的无线路由器连接。</p>
<p><img src="/images/ubnt-network-struct.svg" width="100%" /></p>
<p>无线网桥就相当于一段网线。 把2个路由器连接起来变成了一个局域网。</p>
IELTS essay test 1
2015-11-04T00:00:00+00:00
https://microcai.org/2015/11/04/essay
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
UDP and congestion control
2015-10-06T00:00:00+00:00
https://microcai.org/2015/10/06/udp-and-congestion-control
<p>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.</p>
<p>What if you have customized need for congestion control?</p>
<p>The need for different congestion control algorithm is the main reason that we implement
our protocol ontop of UDP.</p>
<p>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.</p>
<p>The new one in TCP is called Delay-gradient congestion control.</p>
<p>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.</p>
<p>Since TCP has this new fancy congestion control algorithm, why we still use UDP?</p>
<p>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.</p>
<p>We tolerant on packet lost will still employe “Delay-gradient” to prevent a packet lost. Can TCP provide this? No.</p>
<p>So, use UDP if you need fine control over congestion control algorithm, otherwise TCP.</p>
ASIO 腾空出世 (那些年我们追过的网络库.PartII)
2015-09-18T00:00:00+00:00
https://microcai.org/2015/09/18/history-of-network-libraries-part-two
<blockquote>
<p>这是上篇 <a href="http://microcai.org/2015/09/14/history-of-network-libraries-part-one.html">那些年我们追过的网络库(PartI)</a> 的后续,</p>
</blockquote>
<h2 id="asio-腾空出世">ASIO 腾空出世</h2>
<p>在地球最大的岛上,另一位少年开始拜读 ACE 的大作。那时候,没有 libuv 没有 libev 更没有 libevent . 有的只是 ACE.
然而这个南方小国的少年没有跟风陷入 ACE 崇拜,他以敏锐的目光察觉到了 ACE 的弊病。
ACE 哪里做的不好?又哪里是值得借鉴的?</p>
<p>少年在给 c++ 委员会写的一篇上书中说,Proactor 模型乃最优模型。而 Proactor 模型,乃 ACE 提出的 6 个模型之一。根据 IT 界赢者通吃律,一个优秀的网络库,只需要支持 Proactor 模型即可。
支持其他次优模型都是徒劳的。ACE 试图全盘通吃,犯了大忌。</p>
<p>少年在一次开发者大会的演讲上,再次透露,网络库不宜做成框架,而是要像系统的API那样,作为一个乐高积木。ACE 做成了一个框架,同样不妥。</p>
<p>你说了那么多 ACE 不好,有本事你弄个好的啊? 批评者向来都是这么理直气壮。
正如 ACE 的作者实践了 “纸上得来终觉浅 绝知此事要躬行” 一样,这位勇敢的少年也拿出了 ASIO, “实践出真知”,他如是说。</p>
<p>那还是 SARS 病毒肆虐的年代,几乎没有人注意到,今后颠覆C++的网络世界的 ASIO 悄然出世了。而他的父亲,还只是悉尼的学子。ASIO 并没有显赫的家庭背景,然而英雄不问出处,它注定将有不平凡的一生。</p>
<p>俗语有云,三岁看老。在 asio 才三岁的时候,它父亲就将 asio 引荐给了 c++ 委员会的老人们。上一次他们这么做的时候,他们接纳了 STL。
ASIO 最终<del>被内定,然后</del>放入 Boost 锻炼, 经过 Boost 十余的锻炼,ASIO 终于在 2017 年进入了 c++ 标准。</p>
<h3 id="proactor--why-proactor">Proactor ? Why Proactor</h3>
<p>在给 c++ 老人会的引荐信里,asio 爸爸仔细阐述了asio的设计抉择回答了围绕 asio 的设计提出的很多问题。
为什么 Proactor 会是最佳模型?</p>
<ul>
<li>跨平台 许多操作系统都有异步API,即便是没有异步API的Linux, 通过 epoll 也能模拟 Proactor 模式。</li>
<li>支持回调函数组合 将一系列异步操作进行组合,封装成对外的一个异步调用。这个只有Proactor能做到,Reactor 做不到。意味着如果asio使用Reactor模式,就对不起他“库” 之名。</li>
<li>相比 Reactor 可以实现 Zero-copy</li>
<li>和线程解耦 长时间执行的过程总是有系统异步完成,应用程序无需为此开启线程</li>
</ul>
<p>Proactor 也并非全无缺点,缺点就是内存占用比 Reactor 大。Proactor 需要先分配内存而后处理IO, 而 Reactor 是先等待 IO 而后分配内存。相对的Proactor却获得了Zero-copy好处。因为内存已经分配好了,因此操作系统可以将接受到的网络数据直接从网络接口拷贝到应用程序内存,而无需经过内核中转。
Proactor 模式需要一个 loop ,这个 loop asio 将其封装为 io_service.他不仅是 asio的核心,更是一切基于asio设计的程序的核心。</p>
<h3 id="宇宙级异步核心">宇宙级异步核心</h3>
<p>io_service 脱胎于 IO 但不仅用于 IO. Christopher Kohlhoff 在给委员会的另一份编号 N3747 的信上上说它是 <del>宇宙级异步模型</del> Universal Asynchronous Model。在宇宙级异步模型里,一个异步操作由三部分构成</p>
<ol>
<li>发起 按照 asio 的编码规范,所有的发起操作都使用 async_ 前缀,使用 async_动词 的形式作为函数名。</li>
<li>执行 异步过程在发起的时候被executor执行(系统可以是支持 AIO 的内核,不支持 AIO 的系统则是 aiso 用户层模拟)</li>
<li>完成并回调 在发起 async_* 操作的时候,总是携带一个回调的闭包。asio使用闭包作为异步事件完成的处理回调,没而不是C式的回调函数。asio的宇宙异步模型里,回调总是在执行 io_service::run 的线程里执行。asio绝不会在内部线程里调用回调。</li>
</ol>
<p>在回调里发起新的异步操作,一轮套一轮。整个程序就围绕着 io_service::run 运转起来了。
io_service 不仅仅能用于异步 IO ,还可以用来投递任意闭包。实现作为线程池的功能。这一通用型异步模型彻底击败微软 PPL 提案,致使微软转而研究协程。然而微软在协程上同样面临 asio 的绞杀。</p>
<h3 id="闭包和协程">闭包和协程</h3>
<p>宇宙级 asio 使用闭包作为回调,而 C 库只能使用函数+void*, ACE 虽然使用的 C++语言,却不知道闭包为何物,使用的是 虚函数作为回调。需要大量的从 ACE 的对象继承。以闭包为回调,asio更是支持了一种叫“无栈协程”的强悍武器。
asio的无栈协程,仅仅通过库的形式,不论是在性能上,还是易用性上,还是简洁性上,甚至是B格上,都超过了微软易于修改语言而得的 await提案。</p>
<p>微软,乃至 ACE ,并不是不知道闭包,而是在c++里实现闭包的宇宙级executor —— 也就是 io_service,需要对模板技术的精通。
asio “把困难留给自己,把方便带给大家”,以地球人无法理解的方式硬是在 c++98 上实现了宇宙级异步核心。
当然,如果 c++11 早点出现,如果 c++17 早点出现,实现 asio 的宇宙模型会更加的简单 —— 其实这也是 c++ 的理念,增加语言特性,只是为了让语言用起来更简单。</p>
<h3 id="buffers">buffers</h3>
<p>有了闭包的支持,内存管理也变得轻轻松松起来。
ASIO 本身并不管理内存,所有的IO操作,只提交对用户管理的内存的引用,称 Buffers。asio::buffers 引用了用户提交的内存,保持整个 IO 期间,这块内存的有效性是用户的责任。然而这并不难!
因为回调是一个闭包。通过闭包持有内存,只要 asio 还未回调,闭包就在,闭包在,内存在。asio 在调用完回调后才删除相应的闭包。因此资源管理的责任可以丢给闭包,而闭包可以通过智能指针精确的控制内存。
不是 GC , 胜于 GC 千百倍!益于c++的 RAII机制,再无内存泄漏之忧!</p>
<h2 id="进入-asio-的世界">进入 ASIO 的世界</h2>
<p>对 C++ 网络库的历史也介绍到差不多了,接下来的章节里,带你深入理解asio , 让你同时获得开发效率和执行效率。
一些在本章里,可能对许多人来说都是初次见到的技术,将在本书剩余章节里详细介绍。</p>
<p>翻开下一页,进入 ASIO 的世界,领略 C++ 的博大精深,享受网络遨游的快感吧!</p>
<p>–</p>
那些年我们追过的网络库(PartI)
2015-09-14T00:00:00+00:00
https://microcai.org/2015/09/14/history-of-network-libraries-part-one
<p>#为什么要用 C++ 编写服务端程序?
如果说答案是性能,那么肯定有人会满不在乎。觉得性能不够的话, 只要加机器就可以了。
然而更少的机器,意味着更低的能耗,更少的硬件投入,更少的人力资源投入去维护机器。总而言之,更低的成本。</p>
<p>肯定会有人说,C++的开发速度太慢了。然而这并不是绝对的。C++也可以做到非常快速的开发。有句俗语 * “脚本一时爽,重构火葬场” * 说的正是脚本语言开发的项目进入维护阶段后无穷的灾难。而 C++ 经过了几十年的发展, 拥有庞大的工具链. 不管是动态分析还是静态分析都有大量的工具, 能极大的帮助程序员减少错误.
c++得益于精良的设计,严格的检查,越是大型的工程,越是能降低开发成本。</p>
<p>但这并不意味着C++就不适合小型项目了。小型的项目,也可以快速开发。因为 C++11 开始,已经 <em>感觉像是全新的语言了</em>,可以完全以脚本的形式去使用,获得接近甚至超越脚本语言的开发速度,同时得益于编译优化,获得不俗的运行时性能。
C++正是鱼和熊掌得兼的语言。</p>
<p>#为什么要用asio这个库?</p>
<p>事实上如果使用C++开发服务端程序,你有多得数不清的选择。什么 ACE 啦,libuv 啦,libevent 啦,libev 啦,甚至可以直接使用 epoll/iocp 这样的系统API。
为什么要用 asio 呢?</p>
<p>##那些年我们用过的网络库</p>
<p>在计算机史前文明时代, 曾经有个世界难题, 叫 <em>“c10k problem”</em>. 这个是继 y2k problem 后的又一个重大攻关项目. 全世界的文艺青年都想拿下解决这个问题的荣誉, 正可谓八仙过海, 各显神通.</p>
<p>那一年, NPTL 还没有研究出来. 还不能创建成千上万个线程 <br />
那一年, windows 还在蓝屏中挣扎, 无暇顾及网络.</p>
<p>然而, 曙光还是有的. 异步的出现带给了人以希望. 古老的UNIX早就想到了, 提供了 select() 系统调用供人驱使.
然而问题还是有的, select 只能支持 1024 个文件描述符, windows 上的 select 更是劣质到只能使用64个. 就算通过修改定义强迫接受一万个文件描述符, 也没有解决实际的问题. select 实在是太慢了.</p>
<p>在这种背景下, IBM 老大哥带领着MS老弟先搞了 IOCP . 然而开源的人有开源的做法, 在 NIH 综合症的影响下, BSD 的人敢为天下所不齿, 发明了 Kqueue. 同样在 NIH 综合症影响下, Linux 的一群 M* 的猴子捣鼓出了 epoll.</p>
<p>分裂, 让人头疼.</p>
<p>然而, 他们都声称自己的新接口对 select 有质的提升, 是破解 c10k 问题的不二法宝. 你用也得用, 不用也得用.
为了让自己编写的网络程序能跨平台, 程序员开始了对3大各自为阵的法宝的膜拜学习. <br />除了需要应对多套互不兼容的 API , 异步本身也需要更高级的抽象, 把程序员从编写异步代码的地狱模式里拯救出来. 于是程序员们急需一个上天入地无所不能的法宝的法宝, 把这3家法宝给统御起来.</p>
<p>率先站出来悳瑟的是 ACE.</p>
<h3 id="悳瑟的-ace">悳瑟的 ACE</h3>
<p>恰乱世刚过, 天下待定, C++ 委员会的老人们却韬光养晦, 不问世事. 所谓乱世出英雄, 英雄出少年, 欧文大学出了名秀才. 凭借其洋洋洒洒的一片雄文 《Pattern-Oriented Software Architecture》 中举去了首府学城, 并为ACE奠定了无可撼动的地位.</p>
<p>ACE 的名字, 也许灵感来自 Adaptive Clubbed Rod, 这也是当年一位英雄少年的宝贝. 既是宝贝, 必需如意. 即是后来的葫芦娃都怕了 “如意宝贝”.</p>
<p>ACE 如意在什么地方呢?如意其一, 支持 IOCP/kqueu/epoll/select/you_name_it 各种接口, 号曰没有不能跨的平台. 如意其二, 支持多种模型。这些模型都在《Pattern-Oriented Software Architecture》有过详细叙述. ACE 本身就是这篇论文的实践,因为他知道, 纸上得来终觉浅 绝知此事要躬行。 如意其三, 接口和模式排列组合下, 多少种, 竟可不修改代码而适应。</p>
<p>然而 ACE 毕竟嫩了点, 没过几年就失势了. 现在除了一些老程序员还在用, 新生代的程序员已经不再使用 ACE 了. 为什么呢? 陈硕在他的博客里说, <strong>ACE 过于复杂,甚至比它试图封装的对象更复杂</strong>,
程序员是指望用你的如意宝贝去驾驭另外那三家宝贝的, 结果你比他们还难。ACE 犯了早期 C++ 库都会犯的一个错误,过度设计, 过度java化。所谓 java 化, 就是以对象代替接口, 以虚函数代替回调,以继承代替组合。以虚类代替模板。对象间关系错综复杂,牵一发而动全身。除了作者,已经无人能参与 ACE 的开发了。</p>
<p>与此同时,C语言的回归却在背后悄然进行。C语言的复辟,带来了几个更为糟糕的替代品, libevent 和 libev,以及 乘着nodejs的盛行东风而来的 libuv。</p>
<h3 id="原始的-libevent">原始的 libevent</h3>
<p>C语言有着顽强的生命力,当然,这并不是因为C语言有多好,在后续的章节了我们还会深入的探讨C++相对C的改进。C语言的顽强和人天生的懒惰和偏见是有一定关系的。这种惰性表现为随遇而安,表现为固执己见。 非要拼命的否定C++,固守 C , 对 C 的缺点视而不见,诋毁C++相对C的改进。固守的结果就是简陋原始的 libevent . 然而因为保守党巨大的人数优势, libevent 应其群众基础良好而获得了空前的广泛使用。</p>
<p>libevent 就如名字所言,是一个异步事件框架。从 OS 那里获得事件, 然后派发。派发机制就是“回调函数”。异步异步,归根结底就是处理从操作系统获得的事件。iocp也好, epoll也罢,都只是用来获取事件的接口。libevent 去掉了ACE华而不实的包装,保留了异步事件,极大的简化了模型。不得不说软件工程是个糟糕的发明,从来都把简单问题复杂化。libevent把简单问题简单化,让异步网络编程反朴归真,应该来说,本是一个好库。</p>
<p>然而 libevent 因为设计缺陷,例如使用全局变量,定时器无法处理时间跳变,诸如此类的设计缺陷导致了 libev 的出现。
libev 就是为了克服libevent的缺陷而诞生的。然而,libev 就一定好了吗?</p>
<h3 id="禁锢的-libev">禁锢的 libev</h3>
<p>libev 带着对 libevent 的怨气出世了。 吸收了 libevent 的所有缺点,
虽然承诺过改进。然而 libev 如何改进的了呢? libev 已经够原始了,向下改进还不如让人直接使用系统的 api, 向上改进,一是会导致和libevent的重叠,二是很快就碰到了 C 语言强加的禁锢。</p>
<p>C 语言因其语法<del>简陋</del>简洁而著称。然而,缺乏必要的抽象能力,导致 C 语言编写异步程序,就如同安迪拿着小锤子琢开肖生克监狱的墙壁一样。能,但是要耗费巨大的精力和时间。
编写异步程序, 最需要的2个抽象能力, 其一为协程,其二是函数对象,包括匿名函数对象, 也就是lambda。
C统统没有。函数对象是实现闭包比不可少的,如果没有函数对象, 就只能通过携带 <code class="language-plaintext highlighter-rouge">void*</code> 指针的形式迂回完整,繁琐不说,还特别容易出错。程序里也到处充满了类型强转。到处是临时定义的类型,就为了传给 <code class="language-plaintext highlighter-rouge">void*</code> 使用。</p>
<p>尽管C 有那么多缺点,然而 libev 还未来得及被C的缺点拖累,因为他不支持 IOCP. 于是 libuv 就出来给 libev 擦屁股了。 支持了 iocp 后的 libuv 就真的只有 C 本身的缺点了吗?</p>
<h3 id="混乱的-libuv">混乱的 libuv</h3>
<p>libuv 可以说是 C 语言的异步库所能达到的最高高度了。完完全全的触碰到了C语言的自身瓶颈,好在 libuv 只是 nodejs 的底层库,上层软件转移到 javascript 语言而逃避了 C 的禁锢。</p>
<p>真的是这样的吗? libuv 自身还有什么缺点呢?</p>
<p>开源社区avplayer的大拿jackarain曾经说过,一个网络库好不好,就看他有没有正确的处理 TCP 关闭, read write 实现的ui不对。
libuv 很遗憾的是,不合格。libuv 的 uv_write 没有返回值,允许空回调。也就是忽略write错误。
网络出错的情况下, libuv 的用户只能稀里糊涂的知道出错了, 至于错在哪?数据到底有没有发出去了? 一概不知道。
把数据交给 uv_write 后,就是一笔糊涂账了,大概 TCP 不可靠的说法就是从这里传出来的吧。</p>
<hr />
<h2 id="asio-腾空出世">ASIO 腾空出世</h2>
<p>请期待下篇!</p>
自我意识和物质
2015-08-16T00:00:00+00:00
https://microcai.org/2015/08/16/consciousness-and-matter
<p>现代的科学认为物质是不生不灭的. 至少在宇宙的寿命内是这样. 物质的诞生意味着宇宙的开端, 物质的消失意味着宇宙的灭亡.
在宇宙中, 意识并没有特殊的地位. 它不过是地球40亿年进化史的一个副产品.</p>
<p>真的是这样的吗?</p>
<p>我始终坚信的一个宇宙真理就是, 万物平等. 伽利略给了运动和静止一个平等的地位. 爱因斯坦给了所有惯性参考系平等的地位. 物理学的发展, 就是不断的消除特例, 不断的将原本不同的东西平等起来.</p>
<p>我相信, 给予意识和物质一个平等地位的科学时代也即将到来. 而这, 已经露出了苗头.</p>
<h1 id="薛定谔的猫">薛定谔的猫</h1>
<p>薛定谔的猫, 这一经典实验, 揭示着意识对物质的影响. 意识不仅仅只是物质运动的观察者, 意识也是物质运动的一部分, 决定着物质过去的历史, 决定着现在的状态, 乃至未来的发展.</p>
<h1 id="除非进行观测否则一切都不是真实的">除非进行观测,否则一切都不是真实的。</h1>
<p>物理世界的存在与否, 取决于是否有人观察它. 没有人观察的世界, 它并不存在.
不能不承认,这听起来很有强烈的主观唯心论的味道。但它其实和我们通常理解的那种哲学理论有一定区别.</p>
<h1 id="直到人类开始研究宇宙的历史-宇宙才有了确定的历史">直到人类开始研究宇宙的历史, 宇宙才有了确定的历史</h1>
<p>而在人类研究宇宙前, 宇宙的历史是混沌的叠加态. 在人类研究宇宙历史的那一刹那, 整个宇宙的历史就被确定下来了.
这就是量子力学揭示的宇宙之谜.</p>
<p>然而, 这还未曾被接受过.</p>
GLSL加速 YUV 显示
2015-03-31T00:00:00+00:00
https://microcai.org/2015/03/31/optimize_with_glsl
<p>通常来说,视频都是使用 YUV 格式编码的。YUV 最符合人眼的结构,因为人眼对亮度要比对颜色敏感的多。 YUV 将颜色分成一个 亮度信号和2个色差信号。于是就可以使用更多的bit数去编码亮度信号。在同样的每像素比特位数下,YUV 能比 RGB 保留更多的信息。</p>
<p>But YUV 要在 PC 屏幕上显示,不那么友好。需要转成 RGB 格式。网上一搜一大把,这里就不贴转换公式了。</p>
<p>将YUV转换为 RGB 时,是像素独立的。每个像素都可以独立转换,因此可以被大规模并行。所以 YUV 最佳的转换地方还是在 GPU 上。不适合在 CPU 上转。</p>
<p>非常可惜的是, OpenGL 是基于 RGBA 的。在opengl里无法直接使用YUV。</p>
<p>But,先知已经考虑到了这点,先知给 OpenGL 加了一道通用计算的能力。这道通用计算能力就叫 .. 额.. Shader。(为啥中文译名叫着色器呢?)</p>
<p>Shader 简单的来说就是一小段跑在 GPU 上的代码。这些代码针对每一个提交的顶点和每一个最终屏幕像素并行执行。
处理顶点数据的就是顶点着色器。处理屏幕像素的就是像素着色器了。</p>
<p>因此, 将 YUV2RGB 的公式写成一个 shader, 就能实现在 GPU 上并发转格式了.</p>
<pre><code class="language-GLSL">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;
}
</code></pre>
<p>输入为一个 vec3 的向量, 返回 vec3 向量. 这个转换对每个像素并发的执行.</p>
<p>于是, 只要将解码好的视频, Y U V 3个通道分别绑定为3个贴图, 然后在 shader 里访问, 转换成 RGB 就可以了.</p>
<div class="language-glsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">uniform</span> <span class="kt">sampler2D</span> <span class="n">texY</span><span class="p">;</span> <span class="c1">// Y</span>
<span class="k">uniform</span> <span class="kt">sampler2D</span> <span class="n">texU</span><span class="p">;</span> <span class="c1">// U</span>
<span class="k">uniform</span> <span class="kt">sampler2D</span> <span class="n">texV</span><span class="p">;</span> <span class="c1">// V</span>
<span class="k">varying</span> <span class="kt">vec2</span> <span class="n">vary_tex_cord</span><span class="p">;</span>
<span class="kt">vec3</span> <span class="nf">yuv2rgb</span><span class="p">(</span><span class="k">in</span> <span class="kt">vec3</span> <span class="n">yuv</span><span class="p">);</span>
<span class="kt">vec3</span> <span class="nf">get_yuv_from_texture</span><span class="p">(</span><span class="k">in</span> <span class="kt">vec2</span> <span class="n">tcoord</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">vec3</span> <span class="n">yuv</span><span class="p">;</span>
<span class="n">yuv</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">texture</span><span class="p">(</span><span class="n">texY</span><span class="p">,</span> <span class="n">tcoord</span><span class="p">).</span><span class="n">r</span><span class="p">;</span>
<span class="c1">// Get the U and V values</span>
<span class="n">yuv</span><span class="p">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">texture</span><span class="p">(</span><span class="n">texU</span><span class="p">,</span> <span class="n">tcoord</span><span class="p">).</span><span class="n">r</span><span class="p">;</span>
<span class="n">yuv</span><span class="p">.</span><span class="n">z</span> <span class="o">=</span> <span class="n">texture</span><span class="p">(</span><span class="n">texV</span><span class="p">,</span> <span class="n">tcoord</span><span class="p">).</span><span class="n">r</span><span class="p">;</span>
<span class="k">return</span> <span class="n">yuv</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">vec4</span> <span class="nf">mytexture2D</span><span class="p">(</span><span class="k">in</span> <span class="kt">vec2</span> <span class="n">tcoord</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">vec3</span> <span class="n">rgb</span><span class="p">,</span> <span class="n">yuv</span><span class="p">;</span>
<span class="n">yuv</span> <span class="o">=</span> <span class="n">get_yuv_from_texture</span><span class="p">(</span><span class="n">tcoord</span><span class="p">);</span>
<span class="c1">// Do the color transform</span>
<span class="n">rgb</span> <span class="o">=</span> <span class="n">yuv2rgb</span><span class="p">(</span><span class="n">yuv</span><span class="p">);</span>
<span class="k">return</span> <span class="kt">vec4</span><span class="p">(</span><span class="n">rgb</span><span class="p">,</span> <span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">out</span> <span class="kt">vec4</span> <span class="n">out_color</span><span class="p">;</span>
<span class="kt">void</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// That was easy. :)</span>
<span class="n">out_color</span> <span class="o">=</span> <span class="n">mytexture2D</span><span class="p">(</span><span class="n">vary_tex_cord</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>完成</p>
ubuntu 太反人类了
2014-11-11T00:00:00+00:00
https://microcai.org/2014/11/11/fuck-ubuntu
<p>ubuntu 这个烂东西, 我本来不打算吐槽的, 反正烂东西我不触碰就是了.</p>
<p>但是啊, 烂东西呢, 你不过去招惹它, 它还是会过来招惹你, 不管你愿意还是不愿意.</p>
<p>ubuntu 为啥烂 ? 怎么招惹我了?</p>
<p>因为咱用了 travis-ci . travis-ci 是个好东西啊, 每次你 git push , 它就默默的帮你编译一遍代码. 哪个 commit 没编译过, 一目了然.</p>
<p>问题在于, travis-ci 用的是 ubuntu , 而且更糟糕的是用的 ubuntu 12.04 … 太老了.</p>
<hr />
<p>首先遇到的第一个问题, 就是包名好乱. 我要用一个库的时候, 我不知道要安装哪个库! 我不知道 ubuntu 打包的时候到底用了啥名字.
我不能因为用了这个名字的库,而推定 ubuntu 一定打包成这个名字. 必须上 ubuntu 的网站查询! 其实这个问题我老早就说过了. 诶.</p>
<p>包名混乱, 用户不得不多上网站搜索, 网站流量就上来了, 就看起来 “活跃” 了. 排名就高了… 真是个好生意.</p>
<hr />
<p>第二个问题, 是软件太旧了. 真的太久了. 好不容易找好了正确的包名, 开开心心的在 .travis.yaml 文件里添加了 apt-get 命令 …
结果软件实在是太旧了! 这些软件当年刚进入 ubuntu 仓库的时候就已经是旧了, 但还勉强够用. 但是过了这么多年, 等 ubuntu 自己都旧了的时候,
这些软件就是古董到不能再古董了.</p>
<hr />
<p>第三个问题, 总是恰好包含有 bug 的那个版本, 即便新版本已经包含了修正, ubuntu 也会因为这些版本太”新” 而拒绝认为他们是稳定的.</p>
<p>即便新版本已经包含了修正, ubuntu 也不能简单的更新到新版本来包含更正. 而是必须想出自己的办法来解决 bug. 通常还会受限于人手问题迟迟得不到解决,只能等下一个 LTS 简单的包含了更新了的软件…</p>
<h1 id="ubuntu-是反人类的-更糟糕的是越来越多人抖m-在用-ubuntu-并认为这是全天下最好的-os">ubuntu 是反人类的, 更糟糕的是越来越多人抖M 在用 ubuntu 并认为这是全天下最好的 OS</h1>
也说 jitter
2014-10-16T00:00:00+00:00
https://microcai.org/2014/10/16/jitters
<p>搞数字音频的,经常会提到 jitter。 什么是 jitter ?</p>
<p><img src="/images/jitter1.png" /></p>
<p>jitter 就是时钟的随机抖动。这个抖动啊,来自量子世界,是无法消除的,否则海森堡大人要从棺材里跳出来了。
jitter 只能减少,不能削除。</p>
<p>那么 jitter 对音频有啥影响呢?</p>
<p><img src="/images/jitter2.png" width="70%" /></p>
<p>jitter 主要影响就是,经过 DAC 重建模拟信号的时候,时钟的不稳定导致重建信号偏离原来的位置。结果上看就是波形不再符合原来的PCM编码的描述了。
这个偏差引入了失真,但是数据的准确性并没有被破坏。</p>
<p>那么 jitter 既然无法消除, 总有个量的吧? 小于某个量后,人耳就无法察觉了,对吧?
是的。一般认为,jitter 小于 200皮秒后人耳即无法感知了。</p>
<p>一般数字电路的 jitter 有多大? 数字电路通常使用石英晶振产生时钟,石英晶振的 jitter 是很小的。石英晶振 jitter 可以做到小于 1ps (皮秒)。
所以 jitter 影响音质的说法基本上是无稽之谈。</p>
<p>这个时候,玄学大师会出来说,低 jitter 的那是本地时钟。如果使用的不是本地时钟,比如使用 SPDIF 传输音频,时钟信号是包含在信号里的。
解码器要从被光电转换线路劣化(或者使用同轴传输,信号会被外界电磁干扰劣化)的信号里重建数据和时钟。这个时钟就很难保证低jitter。</p>
<p>是的确实如此。外界干扰确实让同轴或者光纤传来的信号 jitter 大大增加。有网友以示波器观察,发现波形已经被严重歪曲。数字信号以电压判断, 接收到的波形因为已经扭曲,波形的上升坡度各不相同。所以上升到指定电压以被判定为 1 的时间就各不相同。 哪怕原始波形以精确的时间开始上升 。。。。
如此以来,jitter确实很大。</p>
<p>但是,哪怕同轴上单个 bit 的 jitter 高达 1000ps (其实已经很低了,但是足以引起听感不适), 提取出来的信号和时钟的jitter依然能做到非常低。
这是为何呢? 因为重建信号使用了 PLL (锁相环) 机制。</p>
<p>spdif 信号并不能直接 DAC 解码,而是需要提取出时钟信号和音频数据。spdif 信号是一帧一帧传播的。而不是一个采样点一个采样点传播的。</p>
<p>接受方首先要接收完整的一帧的数据,通过帧头获得音频数据的格式。然后才能讲音频数据送入 DAC 。 DAC 则是一次一个采样点进行的。
DAC 需要一个时钟信号,时钟信号每一个滴答,DAC 按照采样点数字输出一个电压。只要这个时钟的 jitter 低就可以了。
spdif 虽然使用 BMC 编码将数据和时钟同时编码,但是这个时钟其实是用来恢复数据使用的——使用 BMC 编码可以使用一条线就将数据正确的传输——并不用它来恢复提供给 DAC 的时钟。
这个时钟是 spdif 接收电路依据 PLL 锁定后输出的。</p>
<p>这个 PLL 电路的就是保证每帧音频数据的到达时间被恰好平均分割,锁定的是这个时钟。这是什么意思呢?
spdif 每帧包含 192 个采样点数据, 在传输 48khz 音频数据的时候, 每 4ms 传输一帧。那么 PLL 电路的任务就是产生一个时钟,每 4ms 恰好是 192 个脉冲。
传输 44.1khz 的音频数据的时候,大约每 4.35ms 传输一帧, 那么 PLL 电路的任务就是产生一个时钟,每 4.35ms 恰好是 192 个脉冲。
那么, spdif 如果在传输过程中,因为线材垃圾了,引入了 1000ps 的 jitter , 那么 最多就是 1000ps 被 192 均分了。也就是平摊后实际线材只为每个采样点引入了 5ps 的 jitter.</p>
<p>所以,线材引入的 jitter 只要不影响电路执行 BMC 解码以恢复出数据,实际上 spdif 传输的 jitter 主要还是由 PLL 电路自己的 jitter 决定的,和线材关系非常小。
而一般产品上常使用的 TI 生产的一款 spidif 接受芯片,其 PLL 电路的 jitter 为 50ps . 垃圾线材只是再增加 5ps , 还是远远小于人耳可辨的 200ps。</p>
<p>再说 USB 传输。
USB Audio 规范里,主机每 1ms 传递一个音频封包。
接收芯片同样也是使用 PLL 电路恢复时钟。
因此 USB 线材引入的 jitter 同样远远小于 PLL 电路自身的 jitter, 当然,也远远小于人耳能分辨的极限。</p>
<p>说道这里,很多人有疑问,为啥不使用解码器的本地时钟呢?而是使用 PLL 电路去锁定主机的时钟呢?</p>
<p>因为啊,世上没有一模一样的时钟。
假设我们运气差, PC 使用的是比标准快 的,而 DAC 使用的是慢的,那么 PC 和 DAC 会出现时差。
这个偏差会造成缓存撕裂。PC 比 DAC 快,则 PC 送来的数据 DAC 解码不过来。 PC 比DAC慢,则 DAC 常常要空转. 这2种情况都会导致爆音产生。
加大缓存只能减缓这个时刻的到来,而且增加了播放的延迟。使用更高级的温补晶振也只是缓解而不是避免这个问题。</p>
<p>解决时钟不同步问题的最佳办法就是只使用一个时钟。由数据源提供时钟,接受端则使用 PLL 电路去锁定这时钟。</p>
<p>这里注意下,时钟漂移和 jitter 的区别。 时钟漂移是时钟和标准时间相比走的偏快和偏慢的问题。元件规格上的这个漂移是指整个产品线上所有产品的漂移情况统计。
对于一个具体的时钟,漂移则是固定的,基本上只受温度影响下会快一点或者慢一点。
比如对于一个标称 1M 的晶振,可能这个产品实际是 999999hz 的,另一个产品是 1000001hz 的。这样就是正负 60ppm 的偏差。对于一个给定的晶振和一个给定的温度下,并不会出现这一秒 999999hz 下一秒 1000001hz 的情况。
jitter 则是每个时钟脉冲的位置会“随机偏移”, 但是每秒输出的脉冲个数是没变的。</p>
<p>所以,音频的 jitter 早就是一个被解决掉了的技术问题。以目前的技术,完全无需担心任何 jitter 问题。音频传输相对现在的数据通信领域完全就是小儿科。</p>
更高采样率,更低成本
2014-10-11T00:00:00+00:00
https://microcai.org/2014/10/11/higher-sample-rate-cheaper-device
<p>在继续本文之前,容我提出一个摩尔定律的推论: 离摩尔定律越近,发展越快,成本越低;反之,离摩尔定律越远,发展就越缓慢,成本也难以下降。</p>
<p>[待续]</p>
<hr />
<p>=== 2023 年 12月 21 日填坑 ===</p>
<p>没想到,当年的一篇文章,我居然没完成。挖坑了这么久。</p>
<p>这次来回填。</p>
<hr />
<p>在玄学HIFI领域,是反摩尔定律的。</p>
<p>数字音频,是个跨领域的行业。一方面,他是数字化的。一只脚在摩尔定律统治的领域。 另一只脚则是在一个传统的音像行业。</p>
<p>快速发展的摩尔领域,就容易导致劈叉越来越严重。然后就扯到蛋了。</p>
<p>一方面,摩尔统治下的半导体,性能指数级提升。ADC芯片能实现的采样率,很快就从 44.1khz 到了 25Ghz 以上。</p>
<p>另一方面,传统的音像行业坚守 44.1khz 的本心不动摇。传统行业的坚守,也让 ADC 芯片的从业者,分化为了两个阵营。一个是摩尔阵营,一个是音响阵营。</p>
<p>摩尔阵营的 ADC,每 18个月,价格降低一半。或者相同的价格能买到翻倍的采样率。</p>
<p>音响阵营,实行的是稳定供货策略。一个 44.1khz 的 ADC, 他打着保票说,这个芯片能供货30年以上,而且绝对不乱涨价。</p>
<p>二者之间还有一个心照不宣的秘密:摩尔阵营的ADC绝对不染指音响市场。</p>
<p>于是市面上,你能花一两块钱的价格买到的芯片,里面内赠的ADC能达到100khz采样速度,还能采集十几个通道。</p>
<p>但是如果你要采集的是区区20khz 带宽的信号,就必须买专门的44.1kz 采样率的ADC。这个ADC,价格从几十元到上万元不等。</p>
<p>在 44.1khz 成为标准的年代。1.44MB/s 的码率异常惊人。耗尽一台个人电脑的全部存储空间,无法记录一秒的声音。</p>
<p>但是,时间未过几年。个人电脑单是RAM的容量,都能放下数张CD光盘了。 就别提硬盘的容量了。更是指数级提升。</p>
<p>按理说,音响制作领域,完全可以使用更高的采样率,更大的码率制作音频。</p>
<p>但是,44.1khz 依旧统治世界。从 VCD 到 DVD , 再到 BD。
整个媒体文件里,音频信息所占用的数据量比例,不断的下降。到 BD 时代,整个 25GB 容量的光盘里,音频信息只有数百MB。与 VCD 时代相比,只有略微的提升。这略微的提升,还是因为 BD 使用了更多的声道,而不是更高的采样率。</p>
<p>声音的原始数据量,已经从巨量变成了无足轻重。 无损压缩的音频,突然成了主流。</p>
<p>摩尔定律界的人,已经嫌弃音频的数据量太小了。填不满巨大的存储空间。不得不劝大家不要压缩声音了。再压缩,硬盘厂都要倒闭了。</p>
<p>声音,从一开始的大山,变成了一粒尘埃。</p>
<p>但是。音响界的人不这么看。声音他必须得是大山。我不允许摩尔定律碾压声音。不允许!</p>
<p>其实,主动远离摩尔定律,何止是 HIFI 行业。还有单片机行业。</p>
<p><a href="/2023/12/11/mcu-industry-myth.html">这篇</a>写的,就讲了单片机行业拒绝摩尔定律。</p>
<p>原来其实9年前,我就已经发现了拒绝摩尔,拒绝发展的行业。今天算是来回填了。</p>
<p>那么回到最初的标题,更高的采样率,为何会带来更低的成本?</p>
<p>因为数字滤波器,比模拟滤波器有更高的阻带抑制度。而且成本随着摩尔定律不断下降。</p>
<p>所以,即使声音无需更高的采样率“存储” 也应该使用更高的采样率进行录制。 然后使用越来越廉价的数字滤波器(其实就是一段代码)将超过20khz 的信号过滤的干干净净。而对20khz 以下的信号原汁原味的保留。</p>
<p>这不比使用 44.1khz 的采样率,然后绞尽脑汁的设计滤波电路过滤掉20khz 以上信号的方案成本更低?</p>
<p>何况非 44.1khz 的 ADC,价格只是 44.1kz 的 ADC 价格的万分之一。</p>
<p>但是,这样的摩尔方案,他不 HIFI。因为他廉价。不配获得hifi玄学大佬的承认。</p>
<p>正如可移植性代码,不配获得单片机大佬的承认。必须强绑定平台。绑定到具体型号。才算好代码。</p>
超线程, 优先级和调度策略
2014-08-17T00:00:00+00:00
https://microcai.org/2014/08/17/smthyperthreading-nice-and-scheduling
<p>这是 ck 的一篇文章,原始链接在此 (SMT/Hyperthreading, nice and scheduling policies)[http://ck-hack.blogspot.com/2014/08/smthyperthreading-nice-and-scheduling.html]</p>
使用自己硬件的时候写的两个补丁
2014-08-09T00:00:00+00:00
https://microcai.org/2014/08/09/two-kernel-patch-for-my-pc
<h1 id="键盘问题">键盘问题</h1>
<p>去年买了一个机械键盘。不过一直有一个困扰我的问题,就是键盘插入 USB 口后,会等待 10s 左右的时间,然后才识别。查看 dmesg 输出会看到有什么 Timeout 的。
后来发现是硬件 bug。而这种硬件 bug 是可以被内核 work-arround 的。 具体的来说就是,一个 USB-HID 插入的时候,内核会询问设备的一些特性,比如说是否支持多点触控之类的。
但是我这块键盘并不会不报告,导致内核等了大概 10s 没收到报告,于是就当你不支持咯。才继续下去。当然,除了这个该死的硬件闭口不谈外,没啥别的 bug。 所以 10秒后,键盘还是能正常使用。</p>
<p>那么解决办法就很简单了,把这个键盘的设备 ID 添加到一个 缺陷列表, 列表文件在 drivers/hid/usbhid/hid-quirks.c 。
当然,遵循内核的设计风格,不能直接把设备 ID 这种 16 进制的数字填进去,所以在 drivers/hid/hid-ids.h 这里添加了宏定义。
补丁如下</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> From 7af026610fe6a41426f53f3a4ea41e3aa1ee780f Mon Sep 17 00:00:00 2001
From: microcai <microcaicai@gmail.com>
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
"usb_submit_urb(ctrl) failed: -1" 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 <microcaicai@gmail.com>
---
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
</code></pre></div></div>
<h1 id="看门狗设备问题">看门狗设备问题</h1>
<p>在 奶茶东 6/18 活动的时候,杀了一块 Z97 主板和 i7 一块回来。当然,顺便低价处理掉了原来的 P8P67 主板和 E3-1230 CPU。</p>
<p>在使用 P8P67 的时候,我一直有开启 systemd 的 看门狗支持。设备 /dev/watchdog 和看门狗。驱动为 iTCO_wdt。
这个看门狗乃芯片组内置设备。所有的 intel 芯片组(别太老的)都有。
但是换了主板后,看门狗设备不见了。我不相信 intel 阉割了这个功能。肯定是驱动问题。</p>
<p>看门狗是挂在 LPC 总线上的。CONFIG_LPC_ICH=y 已经确定开启了。但是设备就是没有。</p>
<p>lspci 看到了一个设备</p>
<p>00:1f.0 ISA bridge: Intel Corporation 9 Series Chipset Family Z97 LPC Controller</p>
<p>使用 -vvv 增强输出详细度后,看到 Kernel driver in use: 这里居然是空的。</p>
<p>等等,为啥是 ISA bridge 啊! 于是折腾把 ISA 总线编译进去。 结果还是没有。放了一段时间没去搭理。</p>
<p>今天又看了一下,发现详尽模式输出是这样的</p>
<p>00:1f.0 ISA bridge [0601]: Intel Corporation 9 Series Chipset Family Z97 LPC Controller [8086:8cc4]</p>
<p>[8086:8cc4] ?? 这个 ID 内核有么?</p>
<p>于是搜索了 drivers/mfd/lpc_ich.c 这个文件,发现 id 那么多,唯独没有这个 ID !!! 于是抱着试试看的态度,向这个文件添加了这个 id , 哈哈,重启后,看门狗设备就乖乖出现了。</p>
<p>下面放出这个补丁,以便让使用这款 华硕 Z97-A 主板的人收益</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> From 2abf7529a1b213ad2cab036cfb99dacba22d3107 Mon Sep 17 00:00:00 2001
From: Wanzhao Cai <microcaicai@gmail.com>
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 >TAbort- <TAbort- <MAbort- >SERR-
<PERR- INTx-
Latency: 0
Capabilities: [e0] Vendor
Specific Information: Len=0c <?>
Kernel driver in use: lpc_ich
Kernel modules: lpc_ich
Signed-off-by: Wanzhao Cai <microcaicai@gmail.com>
---
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
</code></pre></div></div>
<p>许多时候为内核添加设备支持就是一个 id 添加进去的事情,呵呵。如果发现自己的设备不被支持了,就试试看这招吧!</p>
avbot 10.4 发布
2014-06-06T00:00:00+00:00
https://microcai.org/2014/06/06/avbot-10.4
<p>对于不知道 avbot 是神码的同学,猛击<a href="http://qqbot.avplayer.org">这里</a></p>
<p>avbot 不知不觉都已经经历一年有余的开发啦。 10.4 版本应该是一个里程碑式稳定版本。呵呵。 更新还是挺多的。</p>
<p>avbot 的验证码识别功能详情请参考项目 <a href="http://wiki.avplayer.org/deCAPTCHA">wiki</a></p>
<hr />
<p>下面是 Changelog
—</p>
<p>10.4
* 更新了内置的 gloox 库。</p>
<p>10.3
* 添加了 systemd 支持</p>
<p>10.2
* 添加了 libZMQ 支持.
* windows 上改进型的 GUI 处理</p>
<p>10.1
* 使用 stackfull 协程重写 xmpp 逻辑。
* 移除 boost.log 依赖,我能说 boost.log 是个垃圾库么?</p>
<p>10.0
* 新增 python 脚本支持。
* lua 功能在 win32 平台利用 延迟加载DLL特性,只要存在 lua51.dll 即可开启功能,不存在即关闭。无需作为编译选项。
* qq密码支持非明文,提高安全性</p>
<p>9.3
* 新增了一个 avextension.dll 的扩展支持,通过加载该扩展DLL实现功能扩展</p>
<p>9.2
* 添加了 纯真 IP 数据库支持。在聊天中提到的 ip 地址都会响应地理位置.
9.0
* libwebqq is now submoduled. that means, libwebqq is an seperated project now.
* .qqbot mail end 并不能终止邮件记录,再次发出该命令将导致目前为止的记录被再次发送. 因而多次执行 .qqbot mail to将导致意外的信息泄漏。修正次错误</p>
<p>8.0
* 重启 avbot 不重新登录, 直到 cookie 失效才重登录
* irc 逻辑重写.
* 日志支持使用 http::/host:6276/search?channel=群号&q=搜索词&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</p>
<p>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</p>
<p>7.1
* run as -D , log to syslog
* 新增慧眼答题打码平台的支持
* urlpreview 功能可以定义黑名单, 不对指定的url进行响应.</p>
<p>7.0
* 添加了两家印度阿三的分布式人肉验证码识别API支持
* 添加了AVPLAYER.ORG社区提供的无偿验证码识别服务支持
* 添加了联众打码平台的支持</p>
<p>6.3
* httpagent.hpp merged to avhttp, use #include <avhttp/async_read_body.hpp>
* 日志部分使用 boost.log 重写
* 此版本开始要求 boost >= 1.54</p>
<p>6.2
* 最后一个支持 boost <= 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.</p>
<p>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</p>
<p>6.0
* fix 100% cpu bug
* fix random relogin</p>
<p>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</p>
<p>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.</p>
<p>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</p>
<p>4.5
* sync webqq protocol
* clean cache file when startup
* a bug that cause xmpp no connection</p>
<p>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</p>
<p>4.3
* allow change joke interval
* allow to switchoff joke</p>
<p>4.2
* 每十分钟冷场就讲一个笑话.
* RPC 功能允许 POST</p>
<p>4.1
* 修正错误的 asio 用法,由 asio 爸爸的演讲视频指出的
* avbot will check for image file and redownload corrupted one, aka say, the checksum does not match
* avbot 启动的时候会对 images 文件夹下的文件执行校验检查。发现文件检验和文件名不符合的时候重新从腾讯服务器下载。</p>
<p>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 控制台下输出乱码的问题</p>
<p>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</p>
<p>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</p>
<p>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)</p>
<p>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</p>
<p>3.2.2
* fixed many bugs</p>
<p>3.2.1
* bug fix
* XMPP async connect fix
3.2
* irc support multi line message support</p>
<p>3.1.1
* bug fix</p>
<p>3.1
* socks5_proxy support
3.0
* avbot 现在包含了一个POP3客户端,可以到指定(–mailaddr参数指定)的邮箱获取邮件,并将邮件贴到群聊天里。
* 改进的WebQQ协议处理,更稳定,更少下线时间
* 支持通过标准输入直接输入验证码,不再需要通过IRC频道输入验证码
* 大量的代码改进,使用协程优化了WebQQ登录过程的处理,使用协程优化了IRC连接处理</p>
<p>2.3
* bug fix
2.0
* add support for XMPP protocol</p>
<p>1.0
* qqbot works for WebQQ and IRC protocol
0.1
* tech-preview
* many copy & paste from lwqq
* only WebQQ supported. can log chat to html</p>
avbot 里的回调
2014-05-25T00:00:00+00:00
https://microcai.org/2014/05/25/callbacks-on-avbot
<p>asio 这样的库,每次发起一个操作,就带一个回调.回调告诉你,神马时候操作完成了,完成的咋样.</p>
<p>在所有被主动调用的接口处, avbot 基本上都遵循了 asio 的这一设计准则:提供回调完成事件通知.</p>
<p>但是,还有另一种回调:这个回调并不是因为你主动调用了某个接口,而是因为,这是框架的一部分.</p>
<blockquote>
<p>你不用调用框架,框架会来调用你.</p>
</blockquote>
<p>在被框架调用的地方,如果阻塞了,势必会导致框架内部的执行流被整个阻塞.比如说,如果设定一个
“验证码回调”,框架登录QQ发现需要验证码的时候就调用.那么,执行解验证码这个操作如果费时(这
肯定费时啊,等待用户从控制台输入嘛)会导致整个avbot的内部循环被阻塞. </p>
<p>这明显不是我想要的结果.</p>
<p>这也是因此我极力避免框架式设计. 但是,框架式设计有时候又是无可奈何的最简单的做法.</p>
<p><em>那么,为何不让我们的回调接受一个框架传来的 Handler ?</em></p>
<p>是的! 这样框架就无需要求回调一定是非阻塞的了!</p>
读博客园的创业历史
2014-05-12T00:00:00+00:00
https://microcai.org/2014/05/12/about-startup
<p>前些日子,看到微博上传说,博客园的真爱是阿里云。说他们在阿里云平台上走了一次又一次的弯路,当了一次又一次的小白。但是,阿里虐他千百遍,他待阿里如初恋。</p>
<p>顿时觉得有趣,于是去看了博客园解决云计算之路上的一个又一个坑。</p>
<p>开始是抱着围观群众看热闹的心态去看的,看来他们一年多踩的无数个坑,每次踩坑还都去研究源码去找解决办法,顿时觉得,这个团队其实是一个脚踏实地的人在领导。
没有那种浮躁的氛围,肯耐心去做事情。于是去看站长 dudu 的日志。
发现第一篇博客是 04 年发出的。 原来博客园都已经坚持了十年了!</p>
<p>十年前,博客园还只是 dudu 的一个小玩具。但是他总是安心的更新技术,每月都有数篇博文。这种坚持实在难得!
4年后, 也就是 08 年,dudu 开始创业,将博客园从个人站转向公司。</p>
<p>在创业前,以一己之力维护了一个站点4年。4年不间断的更新程序。不间断的写博文记录。这种坚持,实在是佩服!</p>
<p>开始创业一年多后,他们才注册了公司。</p>
<p>是的,如果没有必要,不要成立公司。公司一经成立,就开始为政府烧钱,晚成立公司一点,就可以省下大笔开支。</p>
<p>创业,维持小而精的团队,如无必要,不需要成立公司。在盈利之前都可以无公司组织。公司这种东西,可以等开始盈利的时候去注册。</p>
纯真数据库下载或自动更新实现
2014-05-11T00:00:00+00:00
https://microcai.org/2014/05/11/qqwry_dat_download
<p>用过珊瑚虫的童鞋都知道, 有个叫 “纯真数据库” 的东西, 可以查询 ip 地址对应的物理地址.</p>
<p>纯真数据库是有个名为 QQWry,DAT 的二进制文件, 可以通过纯真数据库自己提供的查询程序进行更新.</p>
<p>有关该数据库格式和解析的内容, 本帖子暂时不讲, 有机会的话, 偶会另行开个新帖子讲讲.</p>
<p>这里讲的是, 不通过官方的查询程序, 如何获取到这个数据库. 通过对官方程序进行抓包, 得出下载此数据库, 主要需要下载</p>
<p>http://update.cz88.net/ip/copywrite.rar</p>
<p>和</p>
<p>http://update.cz88.net/ip/qqwry.rar</p>
<p>两个文件.</p>
<p>但是很明显,这两个压根不是 rar 文件呀! 别被扩展名迷惑了.</p>
<p>因为要下载两个文件, 所以得出一个很明显的结论, ** 第二个文件需要用到第一个文件里的信息才能正确解开, 获得 qqwry.dat 文件. **</p>
<p>那么, copywrite.rar 里到底有神马东西呢?</p>
<p>我们来打开它</p>
<p>ghex copywrite.rar</p>
<p><img src="/images/copywrite_rar.png" alt="copywrite.png" /></p>
<p>好吧,老实说, 根本看不明白嘛!</p>
<p>那咋办?</p>
<p>祭出 IDA !!!!!!</p>
<hr />
<p>华丽的风格线, 其实网上已经有人说了, 这个 copywrite.rar 就是如下一个结构体.</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">struct</span> <span class="nc">copywritetag</span><span class="p">{</span>
<span class="kt">uint32_t</span> <span class="n">sign</span><span class="p">;</span><span class="c1">// "CZIP"</span>
<span class="kt">uint32_t</span> <span class="n">version</span><span class="p">;</span><span class="c1">//一个和日期有关的值</span>
<span class="kt">uint32_t</span> <span class="n">unknown1</span><span class="p">;</span><span class="c1">// 0x01</span>
<span class="kt">uint32_t</span> <span class="n">size</span><span class="p">;</span><span class="c1">// qqwry.rar大小</span>
<span class="kt">uint32_t</span> <span class="n">unknown2</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">key</span><span class="p">;</span><span class="c1">// 解密qqwry.rar前0x200字节所需密钥</span>
<span class="kt">char</span> <span class="n">text</span><span class="p">[</span><span class="mi">128</span><span class="p">];</span><span class="c1">//提供商</span>
<span class="kt">char</span> <span class="n">link</span><span class="p">[</span><span class="mi">128</span><span class="p">];</span><span class="c1">//网址</span>
<span class="p">};</span>
</code></pre></div></div>
<p>这里,最重要的就是 key 这个整数拉! 接下来要在解码 qqwry.rar 里用到</p>
<hr />
<p>下载, qqwry.rar 初步断定这个是一个压缩文件. 为啥? 因为比 qqwry.dat 明显小了不少!</p>
<p>别看他是 rar 扩展名, 肯定不是用的 rar 压缩算法. 为啥? 明显会用 zlib 这样的开源库来压缩嘛! 何况这样的压塑 php 都能做,是吧. 初步估计是 inflate 压塑, 对, 就是 zlib 用的那个. 约莫估计用 php 的 compress() 函数直接压塑来的.</p>
<p>但是,用 zlib 将 qqwry.dat 压塑后, 文件大小居然一样! 哈,不过,文件头看着他怎么就是不一样呢?</p>
<p>注意到上面的注释没? key 用来解码 qqwry.rar 的头 0x200 个字节. 也就是说, 先用 key 把开头的 0x200 个字节给解码了, 新的数据就可以 zlib 解压了.</p>
<p>啥? 你问我,这 0x200 偏移量怎么来的 ? 诶,笨, 自己比较去吧, 却是和 zlib 压塑的, 就只有前面 0x200 不一样罢了.</p>
<p>那么,这 0x200 个字节的数据, 到底如何解码呢?</p>
<p>来,再次祭出 IDA !!!!!!</p>
<hr />
<p>好了,网上已经有了祭出 IDA 然后得出解码算法了, 咱看下</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> for (int i = 0; i<0x200; i++)
{
key *= 0x805;
key++;
key &= 0xFF;
uint32_t v = reinterpret_cast<const uint8_t*>(qqwry_rar.data())[i] ^ key;
qqwry_rar[i] = v;
}
</code></pre></div></div>
<p>good 这样就完成了.</p>
<p>接下来把 qqwry_rar 这个数组里的数据喂给 zlib 的 uncompress 函数就完成解压了! bingo</p>
聊聊 type erasure
2014-04-21T00:00:00+00:00
https://microcai.org/2014/04/21/type-erasure
<h1 id="什么是类型橡皮擦">什么是类型橡皮擦?</h1>
<p>type eraer是什么? 为什么这么有用? 到底它是如何帮助你构建灵活强大的软件的?</p>
<p>我们知道 C++ 是一个强类型的语言。 不同类型之间有天壤之别 ,不能任意转化——函数有签名,只有符合类型检查才能调用。
类型稍微有点不匹配,那带来的是一堆堆编译错误。
然后很多时候,我们会想,C++的类型能不要那么强就好了!比如说设计一个回调函数 ,这回调函数非得是那个类型的才能使用,多不方便。
为了方便用户,为了能适应变化,我们将不得不使用 void* 参数,然后不停地进行强制类型转换。</p>
<p>如果 ……</p>
<p>如果能把类型变弱点就好了!</p>
<p>type erasure 技术应运而生。
其实我们常用的 std::function / boost::function 就是一种 type erasure。
std::function 在一定程度上,抹去了函数的类型。不论来的类型是函数对象,还是函数指针,它统统接受,统统能赋值给它。要知道,函数对象可是有无穷多种类型。</p>
<h1 id="弱类型-和模板啥区别">弱类型? 和模板啥区别?</h1>
<p>模板靠编译期自动生成一个类型兼容的函数来做到弱类型,而 type erasure则能实现在运行期弱类型。</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">template</span><span class="o"><</span><span class="k">class</span> <span class="nc">Callback</span><span class="p">></span>
<span class="kt">void</span> <span class="nf">somefunc</span><span class="p">(</span> <span class="kt">int</span> <span class="n">somearg</span><span class="p">,</span> <span class="n">Callback</span> <span class="n">callback</span><span class="p">);</span>
</code></pre></div></div>
<p>这个是弱类型了吧,但是是靠编译期实现的。如果有100 个类型的回调,就会生成 100 份代码,去适应 100 个类型的回调函数。
但是如果是这样的代码</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">somefunc</span><span class="p">(</span> <span class="kt">int</span> <span class="n">somearg</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">function</span><span class="o"><</span><span class="kt">void</span><span class="o">></span> <span class="n">callback</span><span class="p">);</span>
</code></pre></div></div>
<p>这个也是弱类型吧,但是,不论有多少回调类型,一份代码足矣。
也就是说, type erasure 能弥补模板运行期弱类型能力不足的问题。</p>
<p>而有时候,你不得不使用 type erasure。
比如说,你需要保持一个容器,里面全是回调函数。
那么模板就无能为力了。因为创建一个容器的时候,已经要把所有的元素的类型都固定为一个了。
这个时候你必须借助 type erasure</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">function</span><span class="o"><</span><span class="kt">void</span><span class="p">()</span><span class="o">>></span> <span class="n">function_container</span><span class="p">;</span>
</code></pre></div></div>
<p>有的人会说,用 void* 不也能实现么?</p>
<h1 id="type-erasure-和-void-区别在哪">type erasure 和 void* 区别在哪?</h1>
<p>和 void* 抹杀一切类型不同, type erasure 只谋杀一部分类型。
比如说 std::function , 虽然抹杀了各种函数指针和函数对象之间的差异。
但是,他们还是有其共同点: 还是函数,还能调用。
而使用 void* 的话,就什么类型都抹杀了
所以这是和 void * 的根本差异</p>
<h1 id="那么type-erasure-如何实现">那么,type erasure 如何实现?</h1>
<p>type erasure 的实现通常包含3 个部分: 接口适配器部分、类型基、模板派生类。
接口适配器,就是暴露给用户的接口
这个接口表现的很弱类型。
内部实现上呢,是通过包含一个基类指针,指向分配出来的 模板派生类实现的
这样</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">interface</span>
<span class="p">{</span>
<span class="n">Base</span> <span class="err">*</span> <span class="n">ptr</span><span class="p">;</span>
<span class="nl">public:</span>
<span class="n">some_common_func</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">ptr</span><span class="o">-></span><span class="n">some_common_func</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">template</span><span class="o"><</span><span class="k">class</span> <span class="nc">T</span><span class="p">></span>
<span class="n">interface</span><span class="p">(</span><span class="n">T</span> <span class="n">t</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">ptr</span> <span class="o">=</span> <span class="k">new</span> <span class="n">adapter</span><span class="o"><</span><span class="n">T</span><span class="o">></span><span class="p">(</span><span class="n">t</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="k">class</span> <span class="nc">Base</span> <span class="p">{</span>
<span class="nl">public:</span>
<span class="k">virtual</span> <span class="o">~</span><span class="n">Base</span><span class="p">();</span>
<span class="k">virtual</span> <span class="n">some_common_func</span><span class="p">()</span> <span class="o">=</span> <span class="mi">0</span> <span class="p">;</span>
<span class="p">}</span> <span class="p">;</span>
<span class="k">template</span> <span class="o"><</span> <span class="k">class</span> <span class="nc">T</span> <span class="p">></span>
<span class="k">class</span> <span class="nc">adapter</span> <span class="o">:</span> <span class="k">public</span> <span class="n">Base</span>
<span class="p">{</span>
<span class="n">T</span> <span class="n">t</span><span class="p">;</span>
<span class="nl">public:</span>
<span class="n">some_common_func</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">t</span><span class="p">.</span><span class="n">some_common_func</span><span class="p">();</span>
<span class="p">}</span>
<span class="n">adapter</span><span class="p">(</span><span class="n">T</span> <span class="n">_t</span><span class="p">)</span><span class="o">:</span> <span class="n">t</span><span class="p">(</span><span class="n">_t</span><span class="p">){}</span>
<span class="p">};</span>
</code></pre></div></div>
<p>通过 new Adapter<被适配的类型>
赋值给 Base* 来保存这个 被适配的类型。
通过 C++ 的虚函数机制, 最终调用到被适配的类型里的操作。</被适配的类型></p>
it is hopeless in china
2014-03-05T00:00:00+00:00
https://microcai.org/2014/03/05/hatechina
<p>I just Hate CHINA !</p>
折腾了一下GPG和智能卡
2014-02-15T00:00:00+00:00
https://microcai.org/2014/02/15/smartcard
<p>GPG支持使用智能卡设备存储密钥。
使用文件系统来携带密钥( 不管是存到 家目录里还是放到U盘里携带)的弊端有很多</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. 文件泄漏密钥即泄漏
2. 不能保证密钥的物理唯一性
3. 文件可以被拷贝,文件可以被任意读取
4. 无法知道文件是否被拷贝,不能发觉
</code></pre></div></div>
<p>而使用智能卡设备,好处很多</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. 密钥具有物理唯一性,一旦生成即无法读取,即无法制作拷贝
2. 密钥可以由卡上的CPU生成,更能保证物理唯一性
3. 智能卡可以随身携带,方便使用不同的电脑
4. 卡遗失即密钥遗失
5. 多次尝试破解会导致智能卡自我销毁,保证密钥的安全,所以遗失的卡也不会泄漏密钥
6. 加解密操作由智能卡硬件完成,即便授权应用程序也无法获取KEY。就别说木马了。
</code></pre></div></div>
<p>那么, gpg 支持哪些硬件呢?</p>
<p>gpg 支持 CCID 标准兼容的 USB 读卡器。市面上的智能卡读卡器基本上都基于这个接口。
但是,智能卡有很多种,什么门卡啊,电话卡啊,种类很多, gpg 需要的是一个 “OpenPGP Card” 标准兼容的智能卡。
这种智能卡目前在国内没有购买渠道。</p>
<p>所幸的是,gpg通过 gnupg-pkcs11-scd 兼容支持了 PKI 设备。
所谓 PKI 设备就是使用 PKCS#11 接口进行访问的物理加解密设备,比如电子狗,U盾。</p>
<p>那么,我需要的是一个(国内能购买到的)具备 RSA 或者 DSA 加密运算能力的 PKI 设备,同时能在 Linux 下使用。
能在 Linux 下使用,要么是使用标准的 PC/SD 接口,要么是提供了 Linux 下的驱动(PKCS#11 接口允许厂商提供一个 .so 作为驱动访问硬件。应用程序只要使用 PKCS#11 的接口即可。)</p>
<p>一个 “国内能购买到的” 前提条件,让我的选择范围一下子缩小了。最好能使用标准接口,这样无需额外驱动即可使用。
但是这样的好东西国内似乎很难买到,基本上都需要驱动,而且是 Windows Only。
于是退求其次,找到了 “eToken Pro 72k” 这个设备。他支持 RSA 算法,密钥支持 2048bit 长度,同时也提供了 Linux 驱动。</p>
<p>于是赶紧购买。驱动在一个 Overlay 里找了, emerge 一下就好了。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>UPDATE:
后来看到 debian wiki 里的智能卡页面发现其实也有国产的也能 Linux 下跑啦。
</code></pre></div></div>
折腾了一下 PC HIFI
2013-12-22T00:00:00+00:00
https://microcai.org/2013/12/22/hifi
<p>最近家里闲置了一对老音箱。很大很重的老音箱。年纪有个十几年了吧。但是以前是很贵的东西。虽然现在音箱很便宜(但是便宜了,音质差了呀),但是我没舍得扔掉。在我眼里古董音箱很值钱!</p>
<p>于是我想个办法想废物利用。音箱有了,缺一个功放。</p>
<p>我第一个找到的功放,是基于 TD2024 芯片的数字功放。主要是图个便宜,不到 50 块钱就能有一个。
至于什么是数字功放,我恶补了一下功放方面的知识。</p>
<p>功放分 A 类 B 类 AB 类和 D 类功放。 D 类也就是数字功放。A类功放,让功放管始终工作在线性放大区。就算输入信号是 0, 功放管也在工作。
功耗大,效率低。效率只有 20% 。 大量的电能被白白消耗。</p>
<p>B 类功放就节能多了。 B 类功放需要2个对管(一个 PNP 一个 NPN),分别放大信号的正半周和负半周。当输入信号处于正半周的时候,放大正半周的功放管工作;当输入信号处于负半周的时候,放大负半周的功放管工作。没有输入信号的时候,两个功放管都不工作。效率不错,能提高到最高 75%。</p>
<p>但是 B 类功放有个致命缺点: 交越失真。因为晶体管只有高于大约 0.7v 电压的时候才导通进入放大状态。这样,当输入信号处于 正负0.7v内 的状态时,是不会被放大的。而通常这个时候正是处于信号正半周向负半周交越的时候(或者反过来)。于是出现交越失真。
解决办法就是设定一个偏置电流。让功放管的静态工作点(就是输入信号为 0 的时候的工作点)移到线性区域的起始点。这样就形成 AB 类功放了。</p>
<p>B类功放的2个对管要严格匹配。需要从大量的管子中筛选出参数最接近的。</p>
<p>不管是 A 类还是 B 类功放,都是利用功放管(通常是晶体管,要有的是用的电子管) 工作在线性区域的时候对电流的放大作用完成信号放大的。都要求功放管有较高的线性度。</p>
<p>D类功放和前面介绍的都不一样,D类功放对功放管的线性度没要求。因为 D 类功放让功放管工作在 开关 状态。</p>
<p>D类功放,首先对输入信号进行采样,生成 PWM ( pulse width modulation, 脉宽调制 ) 信号。信号幅度高的地方,所调制生成的 PWM 信号占空比也高。PWM 信号的频率在 300khz 以上。接着,调制后的 PWM 信号用来控制开关管,输出大电流的 PWM 信号(通过直接调制电源得到)。高频率高电压的 PWM 信号经过一个滤波器,过滤掉 20khz 以上的信号,即可还原出原始的音频信号。</p>
<p>因为功放管工作在开关状态,而晶体管在导通的时候电阻非常小,可以小到几个毫欧。使用理论晶体管(导通时无内阻)得出 D 类功放的效率为 100%。实际效率也在 90% 乃至 95% 以上。可以用非常小的散热做到非常大功率的输出。</p>
<p>等等,D 类功放的核心就是一个 PWM 调制电路,然后用 PWM 信号去控制开关管(只工作在开关状态的晶体管),对吧? 为啥不从 PCM 数字信号直接生成 PWM 信号? 这样就免去了数模转换电路带来的损失了!</p>
<p>基于这个想法,我开始寻找市场上是否有卖以这种思想设计的功放。我认为这种功放,和D类功放也就是数字功放比,是纯数字功放。
在某宝和叉东里直接搜索无果。放狗搜索。</p>
<p>功夫不负有心人,我找到了一款乾龙QA100 功放。 这个就是依照我上面的原理设计的功放。它虽然有模拟输入,但是模拟输入实际上经过了一次 ADC 转化为数字信号再进行功放。可惜,太贵了!</p>
<p>另外,刚刚提到,PWM 信号是通过开关管直接调制电源! 这也是烧HIFI就烧电源的由来啊!如果电源不稳定,输出波形又怎么稳定。
现在使用的一般是开关电源,如果让开关电影的 PWM 和 音频部分的 PWM 信号同步运作,则功放管导通的时候,电源也在发力输出!避免那一瞬间电源空转的时候,要靠电容供电。</p>
<p>基于这个思想,我觉得有必要让开关电源和功放同步运行啊! 居然又被我找到了以我这个思想设计的产品, 珀璞朗韵D5P功放。</p>
<p>还是可惜了,太贵了!</p>
<p>要知道,一个 D 类功放,便宜的才几十块钱。因为 D 类功放,并不需要昂贵的高度线性工作的功放管,只需要能快速开关的晶体管即可。成本非常低。
而一个纯数字功放,则去掉了 模拟信号->PWM 的调制电路,改为 PCM->PWM 的电路。其他都没有变。成本并不会增加多少!</p>
<p>上千元的售价,那是打劫么?!</p>
<p>其实我知道,真正的发烧友,是会觉得”便宜没好货“ 的,如果不卖的贵点,怎么入他们的法眼!</p>
<p>因为设计理念不一样,纯数字功放确实可以低成本实现高保真。</p>
<p>于是我继续搜索,终于找到了一个只卖 330。价钱一个便宜了很多很多了。 于是马上拍下。</p>
<p>离开温州,这几天在老婆家住。音箱自然不会带了,于是买了个廉价的凑合一下,就是下面图片中这种效果了。</p>
<p><img src="https://avlog.avplayer.org/images/91/9197D050E5E67C0BAE1FEB1ADC24A771.jpg" /></p>
<p>图中音箱上的那个小 mini 机器就是纯数字功放,注意看到墙上的黑色尾巴,那根是光纤。庆幸我的主板居然带有 S/PDIF 光纤输出,吼吼。</p>
对未来编译器的意淫
2013-09-15T00:00:00+00:00
https://microcai.org/2013/09/15/future-compiler
<p>典型的 C++ 编译过程分 “预处理” “编译” “链接”</p>
<p>编译, 是最耗时的环节, 编译需要编译器解析 C++ 语法, 构建 AST (抽象语法树), 解析完成后, C++ 的重任才刚刚开始. 对于 C 来说, C 解析成语法树后可以立即开始生成目标代码. 但是 C++ 不能. 最耗时的步骤才刚刚开始. C++ 要在 AST 上执行 模板展开, 重载判决. 特别是 模板展开, 这可是一次图灵完全的展开啊! 在展开的过程中, 顺便就以图灵完全的模式执行了一次模板代码. 模板展开失败不是错误, 编译器需要找到另外的模板重载重新展开.</p>
<p>终于全部任务执行完毕后, 生成一个中间的表示. 接着开始进行第一阶段优化, 这一阶段的优化, 死代码消除, 内联展开. 这很重要, 模板代码就是在这一阶段被内联进去, 以至于和手写一样高效. 死代码消除使得为可读性考虑写的代码, 并不会真正生成目标代码, 编译器知道这是无用, 删了. 完成第一阶段优化后, 转化为目标代码. 转换完成后执行第二次优化. 这次优化是在机器指令层面进行的. 优化的地方有, 向量化啦, 指令重排序啦.</p>
<p>最终生成优化的目标代码, 然后写入目标文件, 等待链接.</p>
<p>其中, 预处理+解析语法树, 这两个过程, 在很多时候, 都是非常非常非常费时的操作. 虽然从语法树生成目标代码也非常耗时,但是如果不开启编译优化, 这个过程还是非常迅速的.</p>
<p>不论开关优化, 预处理+解析语法树的时间, 是省不了的.</p>
<p>对程序员来说, 代码是靠编辑器写出来的. 那么, 编辑器的强大能极大的加速程序员的生产效率.</p>
<p>什么叫强大的编辑器?
最最最重要的一点: 智能提示.</p>
<p>智能提示的核心,就是语法解析器。 并将解析到的语法,存储在数据库里。你修改一行代码, 编辑器不会像 C++ 编译器那样重新解析整个文件, 而是只重新解析 修改的那部分。以为其他没有修改的部分的语法,已经在数据库里了呀!</p>
<p>等等!也就是说,其实 IDE 已经将程序的所有语法都掌握在手里了, 不像编译器一次只能看到一个文件, 编辑器可是看到了全部的文件!</p>
<p>而且, 有改动,编辑器也知道到底哪里改了!编辑器不会像 C++ 编译器那样重新解析整个文件, 而是只重新解析 修改的那部分。
如果编辑器随时把自己存储的所有语法树的信息,给转化为二进制代码呢?</p>
<p>这不就成了即时编译了!!!!!!</p>
<p>而且, 和项目的大小无关,只和改动集的大小有关!</p>
<p>这真是太好了,所有的编译都在完成编辑的时候完成了!</p>
让 C 回调支持 boost::bind
2013-08-25T00:00:00+00:00
https://microcai.org/2013/08/25/function-for-c-callback
<p>C++ 的 bind 非常方便! 但是如果你不得不处理一些 C 接口, 情况就会变得很糟糕, 你不得不处理一堆的 void* ,
不能使用 bind ! 有神码办法可以解决这个问题呢!? 答案就是 接下来介绍的模板技术 c_func_wraper !</p>
<p>用法很简单, 看下面的例子</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="o">*</span> <span class="nf">my_thread_func</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">,</span> <span class="kt">int</span> <span class="n">b</span><span class="p">,</span> <span class="kt">int</span> <span class="n">c</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o"><<</span> <span class="n">a</span> <span class="o"><<</span> <span class="n">b</span> <span class="o"><<</span> <span class="n">c</span> <span class="o"><<</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
<span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="n">main</span><span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span><span class="p">[])</span>
<span class="p">{</span>
<span class="n">c_func_wraper</span><span class="o"><</span><span class="kt">void</span> <span class="o">*</span><span class="p">(</span><span class="o">*</span><span class="p">)</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">),</span> <span class="kt">void</span><span class="o">*</span><span class="p">()</span><span class="o">></span> <span class="n">func</span><span class="p">;</span>
<span class="n">func</span> <span class="o">=</span> <span class="n">boost</span><span class="o">::</span><span class="n">bind</span><span class="p">(</span><span class="o">&</span><span class="n">my_thread_func</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span>
<span class="n">pthread_t</span> <span class="n">new_thread</span><span class="p">;</span>
<span class="n">pthread_create</span><span class="p">(</span><span class="o">&</span><span class="n">new_thread</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">func</span><span class="p">.</span><span class="n">c_func_ptr</span><span class="p">(),</span> <span class="n">func</span><span class="p">.</span><span class="n">c_void_ptr</span><span class="p">());</span>
<span class="n">pthread_join</span><span class="p">(</span><span class="n">new_thread</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>pthread 是一个典型的 C 接口, 需要传递给线程的参数通过 void* 传递进来. c_func_wraper 则将 bind 和 C 接口的回调给整合起来了!</p>
<p>c_func_wraper 接受2个模板参数, 一个是 C 接口需要的 <strong>函数指针</strong>类型, 另一个是 类似 boost::function 所接受的函数原型声明. 注意, C 类型的声明和 boost::function 风格的声明的区别. 另外就是当前 C 类型必须是 void* 在最后一个参数. 以后可以添加出更多位置支持 :) 如第一个参数是 void* user_data 的 C 回调.</p>
<p>接着为其 使用 bind 赋值. 赋值完毕, 就可以通过 c_func_ptr 和 c_void_ptr 两个对象获取到兼容的 C 版本了, 然后传递给 pthread_create. 就大公告成了. :)</p>
<p>下面是 实现</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">template</span><span class="o"><</span><span class="k">typename</span> <span class="nc">CFuncType</span><span class="p">,</span> <span class="k">typename</span> <span class="nc">ClosureSignature</span><span class="p">></span>
<span class="k">class</span> <span class="nc">c_func_wraper</span> <span class="o">:</span><span class="n">boost</span><span class="o">::</span><span class="n">noncopyable</span>
<span class="p">{</span>
<span class="nl">public:</span>
<span class="n">c_func_wraper</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">m_wrapped_func</span> <span class="o">=</span> <span class="k">new</span> <span class="n">boost</span><span class="o">::</span><span class="n">function</span><span class="o"><</span><span class="n">ClosureSignature</span><span class="o">></span><span class="p">;</span>
<span class="p">}</span>
<span class="o">~</span><span class="n">c_func_wraper</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">delete</span> <span class="n">m_wrapped_func</span><span class="p">;</span>
<span class="n">m_wrapped_func</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">template</span><span class="o"><</span><span class="k">typename</span> <span class="nc">T</span><span class="p">></span>
<span class="n">c_func_wraper</span><span class="p">(</span><span class="k">const</span> <span class="n">T</span> <span class="o">&</span><span class="n">bindedfuntor</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">m_wrapped_func</span> <span class="o">=</span> <span class="k">new</span> <span class="n">boost</span><span class="o">::</span><span class="n">function</span><span class="o"><</span><span class="n">ClosureSignature</span><span class="o">></span><span class="p">;</span>
<span class="o">*</span><span class="n">m_wrapped_func</span> <span class="o">=</span> <span class="n">bindedfuntor</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">template</span><span class="o"><</span><span class="k">typename</span> <span class="nc">T</span><span class="p">></span>
<span class="n">c_func_wraper</span><span class="o"><</span><span class="n">CFuncType</span><span class="p">,</span> <span class="n">ClosureSignature</span><span class="o">>&</span> <span class="k">operator</span> <span class="o">=</span> <span class="p">(</span><span class="k">const</span> <span class="n">T</span> <span class="o">&</span><span class="n">bindedfuntor</span><span class="p">)</span>
<span class="p">{</span>
<span class="o">*</span><span class="n">m_wrapped_func</span> <span class="o">=</span> <span class="n">bindedfuntor</span><span class="p">;</span>
<span class="k">return</span> <span class="o">*</span><span class="k">this</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="o">*</span> <span class="n">c_void_ptr</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="n">boost</span><span class="o">::</span><span class="n">function</span><span class="o"><</span><span class="n">ClosureSignature</span><span class="o">></span><span class="p">(</span><span class="o">*</span><span class="n">m_wrapped_func</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">CFuncType</span> <span class="n">c_func_ptr</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">return</span> <span class="p">(</span><span class="n">CFuncType</span><span class="p">)</span><span class="n">wrapperd_callback</span><span class="p">;</span>
<span class="p">}</span>
<span class="nl">private:</span>
<span class="k">template</span><span class="o"><</span><span class="k">typename</span> <span class="nc">R</span><span class="p">></span>
<span class="k">static</span> <span class="n">R</span> <span class="n">wrapperd_callback</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">user_data</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">boost</span><span class="o">::</span><span class="n">scoped_ptr</span><span class="o"><</span><span class="n">boost</span><span class="o">::</span><span class="n">function</span><span class="o"><</span><span class="n">ClosureSignature</span><span class="o">></span> <span class="o">></span> <span class="n">wrapped_func</span><span class="p">(</span>
<span class="k">reinterpret_cast</span><span class="o"><</span><span class="n">boost</span><span class="o">::</span><span class="n">function</span><span class="o"><</span><span class="n">ClosureSignature</span><span class="o">></span> <span class="o">*></span><span class="p">(</span><span class="n">user_data</span><span class="p">));</span>
<span class="k">return</span> <span class="p">(</span><span class="n">R</span><span class="p">)(</span><span class="o">*</span><span class="n">wrapped_func</span><span class="p">)();</span>
<span class="p">}</span>
<span class="k">template</span><span class="o"><</span> <span class="k">typename</span> <span class="nc">R</span><span class="p">,</span> <span class="k">typename</span> <span class="nc">ARG1</span><span class="p">></span>
<span class="k">static</span> <span class="n">R</span> <span class="n">wrapperd_callback</span><span class="p">(</span><span class="n">ARG1</span> <span class="n">arg1</span><span class="p">,</span> <span class="kt">void</span><span class="o">*</span> <span class="n">user_data</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">boost</span><span class="o">::</span><span class="n">scoped_ptr</span><span class="o"><</span><span class="n">boost</span><span class="o">::</span><span class="n">function</span><span class="o"><</span><span class="n">ClosureSignature</span><span class="o">></span> <span class="o">></span> <span class="n">wrapped_func</span><span class="p">(</span>
<span class="k">reinterpret_cast</span><span class="o"><</span><span class="n">boost</span><span class="o">::</span><span class="n">function</span><span class="o"><</span><span class="n">ClosureSignature</span><span class="o">></span> <span class="o">*></span><span class="p">(</span><span class="n">user_data</span><span class="p">));</span>
<span class="k">return</span> <span class="p">(</span><span class="o">*</span><span class="n">wrapped_func</span><span class="p">)(</span><span class="n">arg1</span><span class="p">);</span>
<span class="p">}</span>
<span class="nl">private:</span>
<span class="n">boost</span><span class="o">::</span><span class="n">function</span><span class="o"><</span><span class="n">ClosureSignature</span><span class="o">></span> <span class="o">*</span> <span class="n">m_wrapped_func</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>
avbot 7.1 发布
2013-08-01T00:00:00+00:00
https://microcai.org/2013/08/01/avbot-7.1
<p>对于不知道 avbot 是神码的同学,猛击<a href="http://qqbot.avplayer.org">这里</a></p>
<p>紧急更新, TX 于 8 月 1 日晚上修改了登录协议, 导致 avbot 全线瘫痪。所有的用户必须升级!必须升级!</p>
<p>7.1 新增了慧眼答题平台支持。urlpreview 功能可以定义黑名单, 不对指定的url进行响应. 用法就是在 qqlog 目录下建立 blockurls.txt 文件,写入不希望avbot解析的正则表达式。</p>
<p>avbot 的验证码识别功能详情请参考项目 <a href="http://wiki.avplayer.org/deCAPTCHA">wiki</a></p>
<hr />
<p>下面是 Changelog
—</p>
<p>7.1
* run as -D , log to syslog
* 新增慧眼答题打码平台的支持
* urlpreview 功能可以定义黑名单, 不对指定的url进行响应.</p>
<p>7.0
* 添加了两家印度阿三的分布式人肉验证码识别API支持
* 添加了AVPLAYER.ORG社区提供的无偿验证码识别服务支持
* 添加了联众打码平台的支持</p>
<p>6.3
* httpagent.hpp merged to avhttp, use #include <avhttp/async_read_body.hpp>
* 日志部分使用 boost.log 重写
* 此版本开始要求 boost >= 1.54</p>
<p>6.2
* 最后一个支持 boost <= 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.</p>
<p>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</p>
<p>6.0
* fix random relogin
* forward compatible with about to be released boost 1.54</p>
<p>5.1
* fix 100% cpu bug</p>
<p>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</p>
<p>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.</p>
<p>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</p>
<p>4.5
* sync webqq protocol
* clean cache file when startup
* a bug that cause xmpp account accidently not used</p>
<p>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</p>
<p>4.3
* allow change joke interval
* allow to switchoff joke</p>
<p>4.2
* 每十分钟冷场就讲一个笑话.
* RPC 功能允许 POST</p>
<p>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</p>
<p>4.0
* avbot 重构,使用 libavbot 提供的 class avbot。 方便其他程序包含 avbot 功能
* url 记录方式重构。
* better support for windows platform - via mingw
* 修复在 windows 控制台下输出乱码的问题</p>
<p>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</p>
<p>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</p>
<p>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)</p>
<p>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</p>
<p>3.2.2
* fixed many bugs</p>
<p>3.2.1
* bug fix
* XMPP async connect fix
3.2
* irc support multi line message support</p>
<p>3.1.1
* bug fix</p>
<p>3.1
* socks5_proxy support
3.0
* avbot 现在包含了一个POP3客户端,可以到指定(–mailaddr参数指定)的邮箱获取邮件,并将邮件贴到群聊天里。
* 改进的WebQQ协议处理,更稳定,更少下线时间
* 支持通过标准输入直接输入验证码,不再需要通过IRC频道输入验证码
* 大量的代码改进,使用协程优化了WebQQ登录过程的处理,使用协程优化了IRC连接处理</p>
<p>2.3
* bug fix
2.0
* add support for XMPP protocol</p>
<p>1.0
* qqbot works for WebQQ and IRC protocol
0.1
* tech-preview
* many copy & paste from lwqq
* only WebQQ supported. can log chat to html</p>
GC是错误的内存管理模式
2013-07-27T00:00:00+00:00
https://microcai.org/2013/07/27/gc-is-wrong-way-of-doing-memory-managment
<p>只要写程序, 就免不了要和各种资源打交道, 其中最频繁的莫过于内存了.</p>
<p>任何一个程序都需要内存管理. 它不管是简单的还是复杂的, C 语言的还是 java 语言的. 所不一样的是, 内存管理的细节掌握在谁的手里.</p>
<p>对于 C 语言, 毫无疑问的, 程序员掌握所有细节. 程序员获得了最大的灵活性, 作为代价, 编译器不对任何<strong><em>内存管理上的疏忽</em></strong>负责. 而人是 <em>最容易犯错</em> 的生物. 意味着, 程序员总会犯错, 因此很难写出在内存管理上没有瑕疵的程序.</p>
<p>毫无疑问的, 将内存管理的重担全部丢给程序员, 是编译器水平低下的时代无奈的选择. 随着编译器技术的发展, 将内存管理的任务从程序员手中接管是必然的.</p>
<p>对于如何接管内存管理, 语言作者们分成了截然不同的两派</p>
<ul>
<li>垃圾收集</li>
<li>RAII (Resource Acquisition Is Initialization) + 智能指针</li>
</ul>
<p>在带有垃圾收集的语言里, 程序员只管分配内存, 无需操心释放. 垃圾收集器间歇性的运作, 会将不再使用的内存释放掉. 至于如何标记哪些内存是不再使用的, 几十年间发展出了各种算法. 许多语言都带有多种标记算法供选择. “没有哪一种垃圾收集策略是适合所有程序的, 所以各种语言都发展出多套垃圾收集器, 供运行时选择.”</p>
<blockquote>
<p>在许多语言里, 垃圾收集并不是编译器实现的, 而是由语言附带的运行时环境实现的, 编译器为运行时提供了附加的信息. 这就导致了语言和运行时的强耦合. 让人无法分清语言的特性和运行时的特性.</p>
</blockquote>
<p><strong>垃圾收集不是完美的, 使用垃圾收集并不意味着就可以高枕无忧了.</strong> 垃圾收集并不意味着内存泄漏成为过去式, 倒是野指针确实成为了过去式, 因为只要还有指针引用一个对象, 这个对象就绝对不会被释放. (不过, 带有垃圾收集的语言或多或少都废除了指针吧, 用引用替代了指针)</p>
<p>有很多很多复杂的原因丢会导致垃圾收集器无法回收特定的内存, 导致这部分内存泄漏. 更严重的是, 你很难将内存泄漏和还未被清除的内存完全区别开来. 到底是<strong>延迟收集策略</strong> 还是真的发生了<strong>内存泄漏</strong>
? 你永远都无法正确分辨.</p>
<p>结果是, 程序员最终不得不回到 C 语言的老路上, 小心的检查所有的内存分配, 确保没有触发垃圾收集器的bug或者特定的一些策略 . 几乎所有使用带垃圾收集的语言开发的程序, 在其开发的后期都要经历惨痛的 “内存检查” , 回顾所有可能导致内存泄漏的代码.</p>
<p>垃圾收集器的另一个问题是, 除了内存, 它无法对程序使用的其他资源执行垃圾收集. 垃圾收集是以内存管理为目标产生的, 只能收集不再使用的内存, 而不能收集程序使用的其他资源, 如消息列队, 文件描述符, 共享内存, 锁.等等. 程序员不得不对其他资源执行手工管理, 像 C 程序员那样小心翼翼的操作.</p>
<h1 id="最终垃圾收集仍然没有解决-人容易犯错-的问题-还是把其他资源的泄漏问题丢给了程序员">最终垃圾收集仍然没有解决 “人容易犯错” 的问题, 还是把其他资源的泄漏问题丢给了程序员.</h1>
<p>C++ 从来不认为垃圾收集是有用的东西, 和 C 派不一样 , C 派不喜欢垃圾收集纯粹是因为喜欢 “自己控制一切” (天生的 M 属性). C++ 派同样认为, 要把程序员从资源管理的重担里解放出来. 同 “投机取巧” 的 GC派不同, C++ 做了很多思考, 并最终经历了 30年的时间终于找到了解决的办法. 写入了 C++11 标准.</p>
<p>在这30年的时间里, C++ 的资源管理是逐步发展的. C++11 最终提出的智能指针, 源于 C++30年的探索.</p>
<p>C++ 要想实现 RAII + 智能指针, 两大技术缺一不可 1. 自动确定并调用 构造函数和析构函数 2. 模板</p>
<p>C++ 的第一步试图解放资源管理重任, 是为 C 加入了构造函数和析构函数. 构造函数和析构函数由编译器调用, 生命期终止的对象会自动调用析构函数. 不管生命期终止的原因是 return 返回, 还是 抛出了异常, 编译器总是保证, 生命期终止的对象一定会被调用析构函数.</p>
<p>以 “编译器自动保证对象生命期” 的技术依托下, C++ 发明了 RAII 技术, 将资源的管理变成了对象的管理,而<strong>自动变量</strong> (创建在栈上的对象, 类的成员变量) 的生命期由编译器自动保证, 只要在构造函数里申请资源, 在析构函数里正确的释放资源, RAII 技术解决了一大部分的资源管理问题.</p>
<p><strong>模板的引入使得 RAII 技术得以 “一次实现到处使用”. 如实现一次 std::vector 就可以到处使用在需要数组的情况下, 而无需为每种类型的分别实现数组RAII类.</strong> STL 内置了大量的容器, 几乎满足了所有的需求. STL的容器无法满足需求的情况下, 程序员仍然能借用 STL 的理念实现自己的 RAII 容器.</p>
<p>但是, 如果对象分配于堆上, 程序员不得不手工调用 delete 删除对象. 忘记将不用的对象 delete 就成了头号资源泄漏原因.</p>
<p>如果指针也是自动对象就好了.</p>
<p>C++ 标准的第一次尝试是纳入 std::auto_ptr . 但是效果并不好, 不是所有指针都可以为 auto_ptr 所代替. 最要命的是, STL 容器无法使用 auto_ptr.</p>
<p>C++ 标准的第二次尝试就是纳入了 std::shared_ptr , shared_ptr 在进入 C++11 标准之前, 已经在 Boost 库里实践了相当长的时间.</p>
<p>首先得益于 C++ 的模板技术, shared_ptr 只需实现<strong>一次</strong>, 即变成可用于任何类型的指针. 其次, 得益于 C++ 的<strong>自动生命期管理</strong>, 智能指针将<strong>需要程序员管理的堆对象</strong>也变成了能利用编译器<strong>自动管理</strong>的自动变量.</p>
<p>也就是, 智能指针彻底的将 delete 关键字 变成了 shared_ptr 才能使用的内部技术. <strong>编译器能自动删除 shared_ptr 对象, 也就是编译器能自动的发出 delete 调用.</strong></p>
<p>模板是智能指针技术必不可少的一部分, 否则要利用 RAII 实现智能指针就只能利用 “单根继承” 这一老土办法了. 没错, 这也是 MFC 使用的. ( MFC 诞生在 模板还没有加入 C++ 的年代. )</p>
<h3 id="直到-1998-年-c-标准纳入了模板-c-才最终具备了实现自动内存管理所必须的特性">直到 1998 年 C++ 标准纳入了模板, C++ 才最终具备了实现自动内存管理所必须的特性.</h3>
<h3 id="但是准备好这些特性-到利用这些特性发明出真正能用的智能指针-则又花了13年的时间--2011年加入了-shared_ptr-">但是准备好这些特性, 到利用这些特性发明出真正能用的智能指针, 则又花了13年的时间. ( 2011年加入了 shared_ptr. )</h3>
<h1 id="发明出编译器实现的自动内存管理需要时间-c-花了-30年的时间-没有耐心的语言走了捷径-gc-就是这条捷径">发明出编译器实现的自动内存管理需要时间, C++ 花了 30年的时间. 没有耐心的语言走了捷径, GC 就是这条捷径.</h1>
C++ 闭包 探秘
2013-07-20T00:00:00+00:00
https://microcai.org/2013/07/20/closure
<p>我经常说协程, 说协程的时候又经常会提到闭包. 还有我常说, boost::bind 是神器 <strong>归根结底, 神的是 “闭包”</strong></p>
<p>没有闭包, 就无法实现 asio 协程 (注意, 我说的是 ASIO的协程, 并不是通常意义上 setjmp/longjmp 或者 CreateFiber 又或者 boost.context 创建的协程)</p>
<p>每次使用 bind , 你就创建了一个闭包.</p>
<h1 id="简单的来说--闭包就是带状态的函数">简单的来说, 闭包就是带状态的函数</h1>
<p>一个函数, 带上了一个状态, 就变成了闭包了. 什么叫 “带上状态” 呢? 意思是这个闭包有属于自己的变量, 这些个变量的值是创建闭包的时候设置的, 并在调用闭包的时候, 可以访问这些变量.</p>
<blockquote>
<p>函数是代码, 状态是一组变量</p>
</blockquote>
<blockquote>
<p>将代码和一组变量捆绑 (bind) , 就形成了闭包</p>
</blockquote>
<p>内部包含 static 变量的函数, 不是闭包, 因为这个 static 变量不能捆绑. 你不能捆绑不同的 static 变量. 这个在编译的时候已经确定了. <strong>闭包的状态捆绑, 必须发生在运行时</strong>.</p>
<h1 id="闭包的实现">闭包的实现</h1>
<p>C++ 里使用闭包有3个办法</p>
<h2 id="重载--operator">重载 operator()</h2>
<p>因为闭包是一个函数+一个状态, 这个状态通过 隐含的 this 指针传入. 所以 闭包必然是一个函数对象.
因为成员变量就是极好的用于保存状态的工具, 因此实现 operator() 运算符重载, 该类的对象就能作为闭包使用.
默认传入的 this 指针提供了访问成员变量的途径.</p>
<blockquote>
<p>事实上, lambda 和 bind 的原理都是这个.</p>
</blockquote>
<h2 id="lambda">lambda</h2>
<p>c++11 里提供的 lambda表达式就是很好的语法糖. 其本质和手写的函数对象没有区别.</p>
<h2 id="boostbindstdbind">boost::bind/std::bind</h2>
<p>标准库提供的 bind 是更加强大的语法糖, 将手写需要很多很多代码的闭包, 浓缩到一行 bind 就可以搞定了.</p>
<h1 id="闭包的用法">闭包的用法</h1>
<p>闭包是一个强大的武器, 好好使用能事半功倍</p>
<h2 id="用做回调函数">用做回调函数</h2>
<p>闭包的第一个用处就是作为回调函数, 消除 C 语言里回调函数常见的 *this 指针.</p>
<h2 id="解耦合">解耦合</h2>
<p>@hyq 对这个问题这么看的</p>
<blockquote>
<p>我写了一个用于音频播放的类,我把play(int16_t* data, int size)这个函数直接交给那些需要播放音频的模块</p>
</blockquote>
<blockquote>
<p>但是,如果我某天想要换另外一个音频播放的类,这个类的接口变成了play(int channel, int16_t* data, int size),那么我可以用boost::bind将第一个参数先行绑定了</p>
</blockquote>
<blockquote>
<p>用boost::bind可以把不同模块之间可能不兼容的接口给拼接起来</p>
</blockquote>
<p><strong>通过兼容的函数对象, 而不是函数指针, 放宽了函数签名的要求.</strong></p>
<h2 id="信息隔离">信息隔离</h2>
<p>avbot 的验证码实现里, 有一个功能是 <strong>报告验证码错误</strong> 的功能. 这个实现就用到了闭包进行信息隔离.</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">vc_code_decoded</span><span class="p">(</span><span class="n">boost</span><span class="o">::</span><span class="n">system</span><span class="o">::</span><span class="n">error_code</span> <span class="n">ec</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">provider</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">vccode</span><span class="p">,</span> <span class="n">boost</span><span class="o">::</span><span class="n">function</span><span class="o"><</span><span class="kt">void</span><span class="p">()</span><span class="o">></span> <span class="n">reportbadvc</span><span class="p">,</span> <span class="n">avbot</span> <span class="o">&</span> <span class="n">mybot</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">BOOST_LOG_TRIVIAL</span><span class="p">(</span><span class="n">info</span><span class="p">)</span> <span class="o"><<</span> <span class="n">console_out_str</span><span class="p">(</span><span class="s">"使用 "</span><span class="p">)</span> <span class="o"><<</span> <span class="n">console_out_str</span><span class="p">(</span><span class="n">provider</span><span class="p">)</span> <span class="o"><<</span> <span class="n">console_out_str</span><span class="p">(</span><span class="s">" 成功解码验证码!"</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">provider</span> <span class="o">==</span> <span class="s">"IRC/XMPP 好友辅助验证码解码器"</span><span class="p">)</span>
<span class="n">mybot</span><span class="p">.</span><span class="n">broadcast_message</span><span class="p">(</span><span class="s">"验证码已输入"</span><span class="p">);</span>
<span class="n">mybot</span><span class="p">.</span><span class="n">feed_login_verify_code</span><span class="p">(</span><span class="n">vccode</span><span class="p">,</span> <span class="n">reportbadvc</span><span class="p">);</span>
<span class="n">need_vc</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">static</span> <span class="kt">void</span> <span class="n">on_verify_code</span><span class="p">(</span><span class="k">const</span> <span class="n">boost</span><span class="o">::</span><span class="n">asio</span><span class="o">::</span><span class="n">const_buffer</span> <span class="o">&</span> <span class="n">imgbuf</span><span class="p">,</span> <span class="n">avbot</span> <span class="o">&</span> <span class="n">mybot</span><span class="p">,</span> <span class="n">decaptcha</span><span class="o">::</span><span class="n">deCAPTCHA</span> <span class="o">&</span> <span class="n">decaptcha</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">const</span> <span class="kt">char</span> <span class="o">*</span> <span class="n">data</span> <span class="o">=</span> <span class="n">boost</span><span class="o">::</span><span class="n">asio</span><span class="o">::</span><span class="n">buffer_cast</span><span class="o"><</span><span class="k">const</span> <span class="kt">char</span><span class="o">*></span><span class="p">(</span> <span class="n">imgbuf</span> <span class="p">);</span>
<span class="kt">size_t</span> <span class="n">imgsize</span> <span class="o">=</span> <span class="n">boost</span><span class="o">::</span><span class="n">asio</span><span class="o">::</span><span class="n">buffer_size</span><span class="p">(</span> <span class="n">imgbuf</span> <span class="p">);</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">buffer</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">imgsize</span><span class="p">);</span>
<span class="n">BOOST_LOG_TRIVIAL</span><span class="p">(</span><span class="n">info</span><span class="p">)</span> <span class="o"><<</span> <span class="s">"got vercode from TX, now try to auto resovle it ... ..."</span><span class="p">;</span>
<span class="n">decaptcha</span><span class="p">.</span><span class="n">async_decaptcha</span><span class="p">(</span>
<span class="n">buffer</span><span class="p">,</span>
<span class="n">boost</span><span class="o">::</span><span class="n">bind</span><span class="p">(</span><span class="o">&</span><span class="n">vc_code_decoded</span><span class="p">,</span> <span class="n">_1</span><span class="p">,</span> <span class="n">_2</span><span class="p">,</span> <span class="n">_3</span><span class="p">,</span> <span class="n">_4</span><span class="p">,</span> <span class="n">boost</span><span class="o">::</span><span class="n">ref</span><span class="p">(</span><span class="n">mybot</span><span class="p">))</span>
<span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">async_decaptcha</code> 的回调函数 <code class="language-plaintext highlighter-rouge">vc_code_decoded</code> 的第四个参数, 是一个闭包, 如果验证码有错, 直接调用这个闭包就可以完成汇报. 至于回报所需要的一些相关信息 如 <strong>验证码提供商</strong> <strong>返回的验证码ID</strong> 等等信息, 都已经封装在这个闭包里了. 用户无需知道 <em>汇报一个错误识别的验证码到底需要多少信息</em> , 无需知道. 通过闭包, 验证码识别代码将这些信息全部隐藏起来了, 甚至两一个 HANDLE 都无需提供.</p>
<h2 id="用作协程">用作协程</h2>
<p><strong>多次调用</strong>能保留状态的闭包就可以用于实现是协程.</p>
<p>不仅仅要修改状态, 还要根据状态实现不同的行</p>
<p><strong>保留状态的目的就是要后续调用的时候根据前面的状态选择不同的执行路径</strong> 那么这样做就是协程了.
虽然手工编写状态代码并不难, 但是很麻烦, 繁琐.</p>
<p>因此 ASIO 爸爸提供了一套强大的宏, 将状态机的实现给自动化了.</p>
我为什么喜欢用协程
2013-07-19T00:00:00+00:00
https://microcai.org/2013/07/19/why-I-like-stackless-coroutine
<p>查看过 avbot 代码的人都知道, avbot 到处都是协程. 用句 ACG 的话来说, <em>博士是<strong>协程控</strong></em></p>
<p>那么, 为啥我会那么喜欢使用协程呢? 答案是协程大大简化了编程, 尤其是内存管理.</p>
<h2 id="协程简化了内存管理">协程简化了内存管理</h2>
<p><em>写过异步程序的人都知道, 编写异步代码最容易犯的错就是内存泄露了.</em></p>
<p>asio的无栈协程通过 <strong>闭包</strong> 的形式, 将异步过程所要操作的资源绑定到 <strong>闭包</strong> 上, 并使用 shared_ptr 对这些资源执行引用计数管理. 当最后一个回调执行完毕后, shared_ptr 确保了资源的正确释放. 对于 copyable 的资源, 甚至直接作为 <strong>闭包</strong> 的一部分, 让资源随着闭包被 ASIO 拷贝, 释放, 拷贝, 释放, 最终最后一个<strong>闭包</strong>完成使命, 彻底撤销.</p>
<p>下面这个是 QQ 登陆过程的一个协程代码</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// qq 登录办法-验证码登录</span>
<span class="k">class</span> <span class="nc">login_vc_op</span> <span class="o">:</span> <span class="n">boost</span><span class="o">::</span><span class="n">asio</span><span class="o">::</span><span class="n">coroutine</span><span class="p">{</span>
<span class="nl">public:</span>
<span class="k">typedef</span> <span class="kt">void</span> <span class="n">result_type</span><span class="p">;</span>
<span class="n">login_vc_op</span><span class="p">(</span><span class="n">boost</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o"><</span><span class="n">qqimpl</span><span class="o">::</span><span class="n">WebQQ</span><span class="o">></span> <span class="n">webqq</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">_vccode</span><span class="p">,</span> <span class="n">webqq</span><span class="o">::</span><span class="n">webqq_handler_t</span> <span class="n">handler</span><span class="p">)</span>
<span class="o">:</span> <span class="n">m_webqq</span><span class="p">(</span><span class="n">webqq</span><span class="p">),</span> <span class="n">vccode</span><span class="p">(</span><span class="n">_vccode</span><span class="p">),</span> <span class="n">m_handler</span><span class="p">(</span><span class="n">handler</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">md5</span> <span class="o">=</span> <span class="n">webqq_password_encode</span><span class="p">(</span><span class="n">m_webqq</span><span class="o">-></span><span class="n">m_passwd</span><span class="p">,</span> <span class="n">vccode</span><span class="p">,</span> <span class="n">m_webqq</span><span class="o">-></span><span class="n">m_verifycode</span><span class="p">.</span><span class="n">uin</span><span class="p">);</span>
<span class="c1">// do login !</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">url</span> <span class="o">=</span> <span class="n">boost</span><span class="o">::</span><span class="n">str</span><span class="p">(</span>
<span class="n">boost</span><span class="o">::</span><span class="n">format</span><span class="p">(</span>
<span class="s">"%s/login?u=%s&p=%s&verifycode=%s&"</span>
<span class="s">"webqq_type=%d&remember_uin=1&aid=%s&login2qq=1&"</span>
<span class="s">"u1=http%%3A%%2F%%2Fweb.qq.com%%2Floginproxy.html"</span>
<span class="s">"%%3Flogin2qq%%3D1%%26webqq_type%%3D10&h=1&ptredirect=0&"</span>
<span class="s">"ptlang=2052&from_ui=1&pttype=1&dumy=&fp=loginerroralert&"</span>
<span class="s">"action=2-11-7438&mibao_css=m_webqq&t=1&g=1"</span><span class="p">)</span>
<span class="o">%</span> <span class="n">LWQQ_URL_LOGIN_HOST</span>
<span class="o">%</span> <span class="n">m_webqq</span><span class="o">-></span><span class="n">m_qqnum</span>
<span class="o">%</span> <span class="n">md5</span>
<span class="o">%</span> <span class="n">vccode</span>
<span class="o">%</span> <span class="n">m_webqq</span><span class="o">-></span><span class="n">m_status</span>
<span class="o">%</span> <span class="n">APPID</span>
<span class="p">);</span>
<span class="n">m_stream</span> <span class="o">=</span> <span class="n">boost</span><span class="o">::</span><span class="n">make_shared</span><span class="o"><</span><span class="n">avhttp</span><span class="o">::</span><span class="n">http_stream</span><span class="o">></span><span class="p">(</span><span class="n">boost</span><span class="o">::</span><span class="n">ref</span><span class="p">(</span><span class="n">m_webqq</span><span class="o">-></span><span class="n">get_ioservice</span><span class="p">()));</span>
<span class="n">m_stream</span><span class="o">-></span><span class="n">request_options</span><span class="p">(</span>
<span class="n">avhttp</span><span class="o">::</span><span class="n">request_opts</span><span class="p">()</span>
<span class="p">(</span><span class="n">avhttp</span><span class="o">::</span><span class="n">http_options</span><span class="o">::</span><span class="n">cookie</span><span class="p">,</span> <span class="n">m_webqq</span><span class="o">-></span><span class="n">m_cookies</span><span class="p">.</span><span class="n">lwcookies</span><span class="p">)</span>
<span class="p">(</span><span class="n">avhttp</span><span class="o">::</span><span class="n">http_options</span><span class="o">::</span><span class="n">connection</span><span class="p">,</span> <span class="s">"close"</span><span class="p">)</span>
<span class="p">);</span>
<span class="n">m_buffer</span> <span class="o">=</span> <span class="n">boost</span><span class="o">::</span><span class="n">make_shared</span><span class="o"><</span><span class="n">boost</span><span class="o">::</span><span class="n">asio</span><span class="o">::</span><span class="n">streambuf</span><span class="o">></span><span class="p">();</span>
<span class="n">avhttp</span><span class="o">::</span><span class="n">async_read_body</span><span class="p">(</span><span class="o">*</span><span class="n">m_stream</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="o">*</span><span class="n">m_buffer</span><span class="p">,</span> <span class="o">*</span><span class="k">this</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// 在这里实现 QQ 的登录.</span>
<span class="kt">void</span> <span class="k">operator</span><span class="p">()(</span><span class="n">boost</span><span class="o">::</span><span class="n">system</span><span class="o">::</span><span class="n">error_code</span> <span class="n">ec</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">bytes_transfered</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">istream</span> <span class="n">response</span><span class="p">(</span> <span class="n">m_buffer</span><span class="p">.</span><span class="n">get</span><span class="p">());</span>
<span class="k">if</span><span class="p">(</span> <span class="p">(</span> <span class="n">check_login</span><span class="p">(</span> <span class="n">ec</span><span class="p">,</span> <span class="n">bytes_transfered</span> <span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">)</span> <span class="o">&&</span> <span class="p">(</span> <span class="n">m_webqq</span><span class="o">-></span><span class="n">m_status</span> <span class="o">==</span> <span class="n">LWQQ_STATUS_ONLINE</span> <span class="p">)</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">m_webqq</span><span class="o">-></span><span class="n">m_clientid</span> <span class="o">=</span> <span class="n">generate_clientid</span><span class="p">();</span>
<span class="c1">//change status, this is the last step for login</span>
<span class="c1">// 设定在线状态.</span>
<span class="n">m_webqq</span><span class="o">-></span><span class="n">change_status</span><span class="p">(</span><span class="n">LWQQ_STATUS_ONLINE</span><span class="p">,</span> <span class="o">*</span><span class="k">this</span><span class="p">);</span>
<span class="p">}</span><span class="k">else</span>
<span class="p">{</span>
<span class="k">using</span> <span class="k">namespace</span> <span class="n">boost</span><span class="o">::</span><span class="n">asio</span><span class="o">::</span><span class="n">detail</span><span class="p">;</span>
<span class="n">m_webqq</span><span class="o">-></span><span class="n">get_ioservice</span><span class="p">().</span><span class="n">post</span><span class="p">(</span><span class="n">bind_handler</span><span class="p">(</span><span class="n">m_handler</span><span class="p">,</span> <span class="n">ec</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// 登录完成 后的后续操作</span>
<span class="kt">void</span> <span class="k">operator</span><span class="p">()(</span><span class="k">const</span> <span class="n">boost</span><span class="o">::</span><span class="n">system</span><span class="o">::</span><span class="n">error_code</span><span class="o">&</span> <span class="n">ec</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">using</span> <span class="k">namespace</span> <span class="n">boost</span><span class="o">::</span><span class="n">asio</span><span class="o">::</span><span class="n">detail</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="n">ec</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">m_webqq</span><span class="o">-></span><span class="n">get_ioservice</span><span class="p">().</span><span class="n">post</span><span class="p">(</span><span class="n">bind_handler</span><span class="p">(</span><span class="n">m_handler</span><span class="p">,</span> <span class="n">ec</span><span class="p">));</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">BOOST_ASIO_CORO_REENTER</span><span class="p">(</span><span class="k">this</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">//polling group list</span>
<span class="n">BOOST_ASIO_CORO_YIELD</span> <span class="n">m_webqq</span><span class="o">-></span><span class="n">update_group_list</span><span class="p">(</span><span class="o">*</span><span class="k">this</span><span class="p">);</span>
<span class="c1">// 每 10 分钟修改一下在线状态.</span>
<span class="n">lwqq_update_status</span><span class="p">(</span><span class="n">m_webqq</span><span class="p">,</span> <span class="n">m_webqq</span><span class="o">-></span><span class="n">m_cookies</span><span class="p">.</span><span class="n">ptwebqq</span><span class="p">);</span>
<span class="n">m_webqq</span><span class="o">-></span><span class="n">m_group_msg_insending</span> <span class="o">=</span> <span class="o">!</span><span class="n">m_webqq</span><span class="o">-></span><span class="n">m_msg_queue</span><span class="p">.</span><span class="n">empty</span><span class="p">();</span>
<span class="k">if</span><span class="p">(</span> <span class="n">m_webqq</span><span class="o">-></span><span class="n">m_group_msg_insending</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">boost</span><span class="o">::</span><span class="n">tuple</span><span class="o"><</span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="p">,</span> <span class="n">WebQQ</span><span class="o">::</span><span class="n">send_group_message_cb</span><span class="o">></span> <span class="n">v</span> <span class="o">=</span> <span class="n">m_webqq</span><span class="o">-></span><span class="n">m_msg_queue</span><span class="p">.</span><span class="n">front</span><span class="p">();</span>
<span class="n">boost</span><span class="o">::</span><span class="n">delayedcallms</span><span class="p">(</span> <span class="n">m_webqq</span><span class="o">-></span><span class="n">get_ioservice</span><span class="p">(),</span> <span class="mi">500</span><span class="p">,</span> <span class="n">boost</span><span class="o">::</span><span class="n">bind</span><span class="p">(</span> <span class="o">&</span><span class="n">WebQQ</span><span class="o">::</span><span class="n">send_group_message_internal</span><span class="p">,</span> <span class="n">m_webqq</span><span class="o">-></span><span class="n">shared_from_this</span><span class="p">(),</span> <span class="n">boost</span><span class="o">::</span><span class="n">get</span><span class="o"><</span><span class="mi">0</span><span class="o">></span><span class="p">(</span> <span class="n">v</span> <span class="p">),</span> <span class="n">boost</span><span class="o">::</span><span class="n">get</span><span class="o"><</span><span class="mi">1</span><span class="o">></span><span class="p">(</span> <span class="n">v</span> <span class="p">),</span> <span class="n">boost</span><span class="o">::</span><span class="n">get</span><span class="o"><</span><span class="mi">2</span><span class="o">></span><span class="p">(</span> <span class="n">v</span> <span class="p">)</span> <span class="p">)</span> <span class="p">);</span>
<span class="n">m_webqq</span><span class="o">-></span><span class="n">m_msg_queue</span><span class="p">.</span><span class="n">pop_front</span><span class="p">();</span>
<span class="p">}</span>
<span class="n">m_webqq</span><span class="o">-></span><span class="n">get_ioservice</span><span class="p">().</span><span class="n">post</span><span class="p">(</span><span class="n">bind_handler</span><span class="p">(</span><span class="n">m_handler</span><span class="p">,</span> <span class="n">ec</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nl">private:</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">webqq_password_encode</span><span class="p">(</span> <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="o">&</span> <span class="n">pwd</span><span class="p">,</span> <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="o">&</span> <span class="n">vc</span><span class="p">,</span> <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="o">&</span> <span class="n">uin</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// ... 代码略 ...</span>
<span class="p">}</span>
<span class="nl">private:</span>
<span class="kt">int</span> <span class="n">check_login</span><span class="p">(</span><span class="n">boost</span><span class="o">::</span><span class="n">system</span><span class="o">::</span><span class="n">error_code</span> <span class="o">&</span> <span class="n">ec</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="kt">size_t</span> <span class="n">bytes_transfered</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// ... 代码略 ...</span>
<span class="p">}</span>
<span class="nl">private:</span>
<span class="n">boost</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o"><</span><span class="n">qqimpl</span><span class="o">::</span><span class="n">WebQQ</span><span class="o">></span> <span class="n">m_webqq</span><span class="p">;</span>
<span class="n">webqq</span><span class="o">::</span><span class="n">webqq_handler_t</span> <span class="n">m_handler</span><span class="p">;</span>
<span class="n">read_streamptr</span> <span class="n">m_stream</span><span class="p">;</span>
<span class="n">boost</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o"><</span><span class="n">boost</span><span class="o">::</span><span class="n">asio</span><span class="o">::</span><span class="n">streambuf</span><span class="o">></span> <span class="n">m_buffer</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">vccode</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>
<p>在这个协程里, 有的<strong>闭包</strong>成员被 shared_ptr 管理, 有的没有.
这些资源, 统统都没有使用显式的内存管理, 而是让这些对象随着闭包的撤销被自动的析构.</p>
<p>因为 ASIO 直接拷贝了 <strong>闭包</strong> 所以每次调用 asyn_* 簇的函数的时候, 当前闭包对象都被拷贝, 随后当前闭包被析构.</p>
<p>新生的闭包一直就存在 ASIO 的列队里, 直到操作完成. 然后被重新调用. 这就是 asio 协程的远离. 对象通过不停的拷贝而自我更新, 一直生存在 ASIO 的列队之中. 最后一次完成回调后, 对象就真正死去了.</p>
<h1 id="少编写回调函数">少编写回调函数</h1>
<p>协程的另一个作用就是可以少编写回调函数. 只要写一个闭包, 通过协程的 “多个入口多个出口” 的形式就可以 共享一个 <strong>闭包</strong> 而无须编写大量的 回调函数.</p>
<p>当然, 这也和 asio 对回调函数的形式进行了高度统一 的功劳是分不开的.</p>
<h1 id="看起来像同步逻辑">看起来像同步逻辑</h1>
<p>虽然代码执行的时候是异步的, 但是如果抛开 yield 关键字不看, 整个代码俨然就是一个同步的逻辑.
这大大的简化了开发! 大大的简化了逻辑设计!</p>
avbot 的一些技术决定
2013-07-17T00:00:00+00:00
https://microcai.org/2013/07/17/some-technical-decision-about-avbot
<p>avbot 早已实现最初的目标 : 提供一个机器把 IRC/XMPP和 QQ群联合起来. 让不使用QQ的人和只使用QQ的人都恩嗯畅快的交流.</p>
<p>现在该歇息片刻, 回顾一下这一段历程, 以及编写avbot的时候所做的一些技术决定了.</p>
<h1 id="boostasio">Boost.Asio</h1>
<p>为什么 avbot 会使用 Boost 进行开发呢? Boost 有什么 好处? Asio 是干嘛的?</p>
<p>我觉得对于 Asio, <a href="/t/asio-jack-q/151/8">Jack 的那篇讲座</a> 足以解释的很清楚了. Asio 是一个非常强大的网络库. 从一开始就选择了 Asio, 让 avbot 的后续开发不仅仅变得更轻松, 更重要的是, 我从 asio 那里学到了受用不尽的财富.</p>
<h1 id="avhttp">avhttp</h1>
<p>avhttp 可以说是 avbot 催生的一个项目. 如果不是 avbot , Jack 编写 avhttp 的动力也不是那么浓. 可能会更晚的时候开始编写, 也可能就不会开始编写.</p>
<p>avhttp 是 Asio 思维的典型产物. 不过因为Jack个人喜好问题, avhttp 里模板的使用还是太少了, 个人认为绝对是个遗憾.</p>
<p>在使用 avhttp 之前, avbot 使用的HTTP网络库是 Urdl . 是个已经死掉的项目. avbot 不得不对 Urdl 进行了修改, 才让其支持了 Cookie 和一些其他 webqq 需要用到的 HTTP 头选项.</p>
<h1 id="gloox">gloox</h1>
<p>avbot 最初的版本只支持了 IRC 和 WebQQ. 紧接着 avbot 就实现了 XMPP 协议支持. avbot 有着严格的 单线程 要求. 因此不得不寻找能够和 Asio 进行集成的 XMPP 库. 我尝试过很多的库, 最终选定了 gloox.</p>
<p>gloox 虽然并不是 Boost 和Asio 开发的, 但是通过 派生并重载其 TcpConnection 类, 在其重载的方法里我还是有办法调用 Asio 执行网络操作的. 我并不使用 gloox 自身的 IO 代码, 而是利用C++的多态机制重载掉 原来的网络 IO 代码. 这样就能使用主线程跑的 asio 为 gloox 提供网络操作. 如此以来 avbot 就可以继续单线程了.</p>
<h1 id="协程">协程</h1>
<p>avbot 可以说就是协程化的单线程程序典型.</p>
<p>我第一次尝试使用协程, 是在为 avbot 添加 POP3 协议处理的时候. 协程实现的 POP3 协议处理代码, 将我深深震撼了.</p>
<p>虽然是纯异步代码, 但是其代码简洁到比同步处理的还要少.</p>
<p>在进入 reenter() 之前我就统一了错误处理, 导致写出了比同步过程还要简洁的代码.</p>
<p>从此之后我就愈发不可收拾. 并将许多 libwebqq 上用回调套回调的代码重写为了协程.</p>
<p>因为协程实在太好用了, 直接导致了我的沉迷.</p>
<p><strong>如果不是对异步的追求, 我也不会喜欢上协程吧</strong></p>
<h1 id="验证码">验证码</h1>
<p>一直以来, avbot 都是通过让好友在 IRC 输入验证码发方式对付 腾讯.</p>
<p>直到我的一个死对头 csslayer (写 fcitx 的那位) [ <a href="http://microcai.org/2013/04/06/fcitx-gpl-valation.html">恩怨参考</a> ] 开始使用 avbot. 他将avbot用在了 opensuse 的 irc 聊天室, 然后 opensuse 社区的大姐头 给我提供了一个非常有用的建议, 就是使用 印度阿三的人肉验证码服务.</p>
<p>这让一直在苦思冥想 机器识别算法的我豁然开朗. 于是就有了 avbot 7.0 的推出.</p>
<p>现在该歇息片刻, 回顾一下这一段历程, 以及编写avbot的时候所做的一些技术决定了.</p>
<h1 id="boostasio-1">Boost.Asio</h1>
<p>为什么 avbot 会使用 Boost 进行开发呢? Boost 有什么 好处? Asio 是干嘛的?</p>
<p>我觉得对于 Asio, <a href="/t/asio-jack-q/151/8">Jack 的那篇讲座</a> 足以解释的很清楚了. Asio 是一个非常强大的网络库. 从一开始就选择了 Asio, 让 avbot 的后续开发不仅仅变得更轻松, 更重要的是, 我从 asio 那里学到了受用不尽的财富.</p>
<h1 id="avhttp-1">avhttp</h1>
<p>avhttp 可以说是 avbot 催生的一个项目. 如果不是 avbot , Jack 编写 avhttp 的动力也不是那么浓. 可能会更晚的时候开始编写, 也可能就不会开始编写.</p>
<p>avhttp 是 Asio 思维的典型产物. 不过因为Jack个人喜好问题, avhttp 里模板的使用还是太少了, 个人认为绝对是个遗憾.</p>
<p>在使用 avhttp 之前, avbot 使用的HTTP网络库是 Urdl . 是个已经死掉的项目. avbot 不得不对 Urdl 进行了修改, 才让其支持了 Cookie 和一些其他 webqq 需要用到的 HTTP 头选项.</p>
<h1 id="gloox-1">gloox</h1>
<p>avbot 最初的版本只支持了 IRC 和 WebQQ. 紧接着 avbot 就实现了 XMPP 协议支持. avbot 有着严格的 单线程 要求. 因此不得不寻找能够和 Asio 进行集成的 XMPP 库. 我尝试过很多的库, 最终选定了 gloox.</p>
<p>gloox 虽然并不是 Boost 和Asio 开发的, 但是通过 派生并重载其 TcpConnection 类, 在其重载的方法里我还是有办法调用 Asio 执行网络操作的. 我并不使用 gloox 自身的 IO 代码, 而是利用C++的多态机制重载掉 原来的网络 IO 代码. 这样就能使用主线程跑的 asio 为 gloox 提供网络操作. 如此以来 avbot 就可以继续单线程了.</p>
<h1 id="协程-1">协程</h1>
<p>avbot 可以说就是协程化的单线程程序典型.</p>
<p>我第一次尝试使用协程, 是在为 avbot 添加 POP3 协议处理的时候. 协程实现的 POP3 协议处理代码, 将我深深震撼了.</p>
<p>虽然是纯异步代码, 但是其代码简洁到比同步处理的还要少.</p>
<p>在进入 reenter() 之前我就统一了错误处理, 导致写出了比同步过程还要简洁的代码.</p>
<p>从此之后我就愈发不可收拾. 并将许多 libwebqq 上用回调套回调的代码重写为了协程.</p>
<p>因为协程实在太好用了, 直接导致了我的沉迷.</p>
<p><strong>如果不是对异步的追求, 我也不会喜欢上协程吧</strong></p>
<h1 id="验证码-1">验证码</h1>
<p>一直以来, avbot 都是通过让好友在 IRC 输入验证码发方式对付 腾讯.</p>
<p>直到我的一个死对头 csslayer (写 fcitx 的那位) [ <a href="http://microcai.org/2013/04/06/fcitx-gpl-valation.html">恩怨参考</a> ] 开始使用 avbot. 他将avbot用在了 opensuse 的 irc 聊天室, 然后 opensuse 社区的大姐头 给我提供了一个非常有用的建议, 就是使用 印度阿三的人肉验证码服务.</p>
<p>这让一直在苦思冥想 机器识别算法的我豁然开朗. 于是就有了 avbot 7.0 的推出.</p>
avbot 7.0-rc1 发布
2013-07-11T00:00:00+00:00
https://microcai.org/2013/07/11/avbot-7.0_rc1
<p>对于不知道 avbot 是神码的同学,猛击<a href="http://qqbot.avplayer.org">这里</a></p>
<p>就像博士说的, 不开发验证码解决方案不升级版本号, 这次主版本好不到一个月就被提升了, 咋回事呢?</p>
<p>avbot 7.0 系列发布预览版<a href="http://sourceforge.net/projects/avbot/files/sources/avbot-6.1.tar.bz2/download">发布</a>
了,同时发布的还有 <a href="https://sourceforge.net/projects/avbot/files/rpm/">rpm 包</a>
和 <a href="https://sourceforge.net/projects/avbot/files/win32/">windows 包</a></p>
<p>avbot 7.0 添加了很多功能, 最重要的莫过于支持了验证码识别!</p>
<p>avbot 的验证码识别功能详情请参考项目 <a href="http://wiki.avplayer.org/deCAPTCHA">wiki</a></p>
<hr />
<p>下面是 Changelog
—</p>
<p>7.0-rc1
* 添加了两家印度阿三的分布式人肉验证码识别API支持
* 添加了AVPLAYER.ORG社区提供的无偿验证码识别服务支持</p>
<p>6.3
* httpagent.hpp merged to avhttp, use #include <avhttp/async_read_body.hpp>
* 日志部分使用 boost.log 重写
* 此版本开始要求 boost >= 1.54</p>
<p>6.2
* 最后一个支持 boost <= 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.</p>
<p>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</p>
<p>6.0
* fix random relogin
* forward compatible with about to be released boost 1.54</p>
<p>5.1
* fix 100% cpu bug</p>
<p>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</p>
<p>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.</p>
<p>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</p>
<p>4.5
* sync webqq protocol
* clean cache file when startup
* a bug that cause xmpp account accidently not used</p>
<p>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</p>
<p>4.3
* allow change joke interval
* allow to switchoff joke</p>
<p>4.2
* 每十分钟冷场就讲一个笑话.
* RPC 功能允许 POST</p>
<p>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</p>
<p>4.0
* avbot 重构,使用 libavbot 提供的 class avbot。 方便其他程序包含 avbot 功能
* url 记录方式重构。
* better support for windows platform - via mingw
* 修复在 windows 控制台下输出乱码的问题</p>
<p>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</p>
<p>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</p>
<p>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)</p>
<p>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</p>
<p>3.2.2
* fixed many bugs</p>
<p>3.2.1
* bug fix
* XMPP async connect fix
3.2
* irc support multi line message support</p>
<p>3.1.1
* bug fix</p>
<p>3.1
* socks5_proxy support
3.0
* avbot 现在包含了一个POP3客户端,可以到指定(–mailaddr参数指定)的邮箱获取邮件,并将邮件贴到群聊天里。
* 改进的WebQQ协议处理,更稳定,更少下线时间
* 支持通过标准输入直接输入验证码,不再需要通过IRC频道输入验证码
* 大量的代码改进,使用协程优化了WebQQ登录过程的处理,使用协程优化了IRC连接处理</p>
<p>2.3
* bug fix
2.0
* add support for XMPP protocol</p>
<p>1.0
* qqbot works for WebQQ and IRC protocol
0.1
* tech-preview
* many copy & paste from lwqq
* only WebQQ supported. can log chat to html</p>
avplayer 维基站点建立
2013-07-01T00:00:00+00:00
https://microcai.org/2013/07/01/wiki-avplayer
<p>avplayer 社区的维基站点建立了, 欢迎大家访问 <a href="http://wiki.avplayer.org">http://wiki.avplayer.org</a></p>
<p>目前 avplayer 的维基站点开发免注册编辑权限. 大家可以把 avplayer 社区项目的一些心得分享进行编辑.</p>
<p>比如在 windows 上如何编译 avbot, 希望辛苦解决的人能给后来人以提示.</p>
avbot 6.1 发布
2013-06-27T00:00:00+00:00
https://microcai.org/2013/06/27/avbot-6.1
<p>对于不知道 avbot 是神码的同学,猛击<a href="http://qqbot.avplayer.org">这里</a></p>
<p>avbot 前不久悄悄迎来了半周岁的生日. 因此 avbot 也进入了 6.X 开发周期.</p>
<p>avbot 6.0 系列第2个版本 6.1 <a href="http://sourceforge.net/projects/avbot/files/sources/avbot-6.1.tar.bz2/download">发布</a>了,同时发布的还有 <a href="https://sourceforge.net/projects/avbot/files/rpm/">rpm 包</a> 和 <a href="https://sourceforge.net/projects/avbot/files/win32/">windows 包</a></p>
<p>avbot 6.1 发布, 添加了股票报价功能, 另外也对 WebQQ 协议进行了更新.</p>
<hr />
<p>下面是 Changelog
—</p>
<p>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</p>
<p>6.0
* fix random relogin
* forward compatible with about to be released boost 1.54</p>
<p>5.1
* fix 100% cpu bug</p>
<p>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</p>
<p>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.</p>
<p>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</p>
<p>4.5
* sync webqq protocol
* clean cache file when startup
* a bug that cause xmpp account accidently not used</p>
<p>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</p>
<p>4.3
* allow change joke interval
* allow to switchoff joke</p>
<p>4.2
* 每十分钟冷场就讲一个笑话.
* RPC 功能允许 POST</p>
<p>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</p>
<p>4.0
* avbot 重构,使用 libavbot 提供的 class avbot。 方便其他程序包含 avbot 功能
* url 记录方式重构。
* better support for windows platform - via mingw
* 修复在 windows 控制台下输出乱码的问题</p>
<p>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</p>
<p>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</p>
<p>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)</p>
<p>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</p>
<p>3.2.2
* fixed many bugs</p>
<p>3.2.1
* bug fix
* XMPP async connect fix
3.2
* irc support multi line message support</p>
<p>3.1.1
* bug fix</p>
<p>3.1
* socks5_proxy support
3.0
* avbot 现在包含了一个POP3客户端,可以到指定(–mailaddr参数指定)的邮箱获取邮件,并将邮件贴到群聊天里。
* 改进的WebQQ协议处理,更稳定,更少下线时间
* 支持通过标准输入直接输入验证码,不再需要通过IRC频道输入验证码
* 大量的代码改进,使用协程优化了WebQQ登录过程的处理,使用协程优化了IRC连接处理</p>
<p>2.3
* bug fix
2.0
* add support for XMPP protocol</p>
<p>1.0
* qqbot works for WebQQ and IRC protocol
0.1
* tech-preview
* many copy & paste from lwqq
* only WebQQ supported. can log chat to html</p>
avbot 5.1 发布
2013-06-08T00:00:00+00:00
https://microcai.org/2013/06/08/avbot-5.1
<p>对于不知道 avbot 是神码的同学,猛击<a href="http://qqbot.avplayer.org">这里</a></p>
<p>avbot 5.0 系列开始了.</p>
<p>avbot 5.0 系列第3个版本 5.1 <a href="http://sourceforge.net/projects/avbot/files/sources/avbot-5.1.tar.bz2/download">发布</a>了,同时发布的还有 <a href="https://sourceforge.net/projects/avbot/files/rpm/">rpm 包</a> 和 <a href="https://sourceforge.net/projects/avbot/files/win32/">windows 包</a></p>
<p>avbot 的版本规则是, 每月提升一个大版本号, 因为是开发 avbot 的第 6 个月了, 所以大版本号提升到 5 了.</p>
<p>avbot 5.1 发布, 修复了一个非常严重,困扰了我们很久的一个 100% CPU 占用问题.
当然, 5.0 版本也顺带跟随腾讯更新了群图片的图床API. 更新avbot后群图片获取不到的bug就消失了.</p>
<hr />
<p>下面是 Changelog
—</p>
<p>5.1
* fix 100% cpu bug</p>
<p>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</p>
<p>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.</p>
<p>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</p>
<p>4.5
* sync webqq protocol
* clean cache file when startup
* a bug that cause xmpp account accidently not used</p>
<p>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</p>
<p>4.3
* allow change joke interval
* allow to switchoff joke</p>
<p>4.2
* 每十分钟冷场就讲一个笑话.
* RPC 功能允许 POST</p>
<p>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</p>
<p>4.0
* avbot 重构,使用 libavbot 提供的 class avbot。 方便其他程序包含 avbot 功能
* url 记录方式重构。
* better support for windows platform - via mingw
* 修复在 windows 控制台下输出乱码的问题</p>
<p>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</p>
<p>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</p>
<p>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)</p>
<p>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</p>
<p>3.2.2
* fixed many bugs</p>
<p>3.2.1
* bug fix
* XMPP async connect fix
3.2
* irc support multi line message support</p>
<p>3.1.1
* bug fix</p>
<p>3.1
* socks5_proxy support
3.0
* avbot 现在包含了一个POP3客户端,可以到指定(–mailaddr参数指定)的邮箱获取邮件,并将邮件贴到群聊天里。
* 改进的WebQQ协议处理,更稳定,更少下线时间
* 支持通过标准输入直接输入验证码,不再需要通过IRC频道输入验证码
* 大量的代码改进,使用协程优化了WebQQ登录过程的处理,使用协程优化了IRC连接处理</p>
<p>2.3
* bug fix
2.0
* add support for XMPP protocol</p>
<p>1.0
* qqbot works for WebQQ and IRC protocol
0.1
* tech-preview
* many copy & paste from lwqq
* only WebQQ supported. can log chat to html</p>
avbot 4.7 更新
2013-05-08T00:00:00+00:00
https://microcai.org/2013/05/08/avbot-4.7
<p>对于不知道 avbot 是神码的同学,猛击<a href="http://qqbot.avplayer.org">这里</a></p>
<p>avbot 4.0 系列第九个版本 4.7 <a href="http://sourceforge.net/projects/avbot/files/sources/avbot-4.7.tar.gz/download">发布</a>了,同时发布的还有 <a href="https://sourceforge.net/projects/avbot/files/rpm/">rpm 包</a> 和 <a href="https://sourceforge.net/projects/avbot/files/win32/">windows 包</a></p>
<p>rpm 包有 x86_32(用于64位系统) 的和 i686 (用于32位系统) 两个版本。其实 rpm 包解压一下也可以用在 Deb 系的系统上。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PS: sourceforge 上的 avbot 项目我开通了匿名上传权限,欢迎大家提交 deb/rpm 包供大家下载。
</code></pre></div></div>
<p>4.7 版本带来的最大的变化莫过于带来了 “自动群公告支持”. 配置一下就能支持每天定时的发送一些设定好的消息.</p>
<p>接着是完善了实验性的 lua 脚本支持 (通过 –enable-lua 启用)</p>
<hr />
<p>下面是 Changelog
—</p>
<p>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.</p>
<p>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</p>
<p>4.5
* sync webqq protocol
* clean cache file when startup
* a bug that cause xmpp account accidently not used</p>
<p>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</p>
<p>4.3
* allow change joke interval
* allow to switchoff joke</p>
<p>4.2
* 每十分钟冷场就讲一个笑话.
* RPC 功能允许 POST</p>
<p>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</p>
<p>4.0
* avbot 重构,使用 libavbot 提供的 class avbot。 方便其他程序包含 avbot 功能
* url 记录方式重构。
* better support for windows platform - via mingw
* 修复在 windows 控制台下输出乱码的问题</p>
<p>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</p>
<p>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</p>
<p>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)</p>
<p>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</p>
<p>3.2.2
* fixed many bugs</p>
<p>3.2.1
* bug fix
* XMPP async connect fix
3.2
* irc support multi line message support</p>
<p>3.1.1
* bug fix</p>
<p>3.1
* socks5_proxy support
3.0
* avbot 现在包含了一个POP3客户端,可以到指定(–mailaddr参数指定)的邮箱获取邮件,并将邮件贴到群聊天里。
* 改进的WebQQ协议处理,更稳定,更少下线时间
* 支持通过标准输入直接输入验证码,不再需要通过IRC频道输入验证码
* 大量的代码改进,使用协程优化了WebQQ登录过程的处理,使用协程优化了IRC连接处理</p>
<p>2.3
* bug fix
2.0
* add support for XMPP protocol</p>
<p>1.0
* qqbot works for WebQQ and IRC protocol
0.1
* tech-preview
* many copy & paste from lwqq
* only WebQQ supported. can log chat to html</p>
avbot 4.6.1 更新
2013-05-04T00:00:00+00:00
https://microcai.org/2013/05/04/avbot-4.6
<p>对于不知道 avbot 是神码的同学,猛击<a href="http://qqbot.avplayer.org">这里</a></p>
<p>avbot 4.0 系列第八个版本 4.6.1 <a href="http://sourceforge.net/projects/avbot/files/sources/avbot-4.6.1.tar.bz2/download">发布</a>了,同时发布的还有 <a href="https://sourceforge.net/projects/avbot/files/rpm/">rpm 包</a> 和 <a href="https://sourceforge.net/projects/avbot/files/win32/">windows 包</a></p>
<p>rpm 包有 x86_32(用于64位系统) 的和 i686 (用于32位系统) 两个版本。其实 rpm 包解压一下也可以用在 Deb 系的系统上。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PS: sourceforge 上的 avbot 项目我开通了匿名上传权限,欢迎大家提交 deb/rpm 包供大家下载。
</code></pre></div></div>
<p>4.6 版本带来的最大的变化就是支持了 URL 预览功能. 当有群友贴出一个 URL 的时候, avbot 智能的将 url 解析并贴出网页标题.</p>
<p>接着是加入了实验性的 lua 脚本支持 (通过 –enable-lua 启用)</p>
<hr />
<p>下面是 Changelog
—</p>
<p>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</p>
<p>4.5
* sync webqq protocol
* clean cache file when startup
* a bug that cause xmpp account accidently not used</p>
<p>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</p>
<p>4.3
* allow change joke interval
* allow to switchoff joke</p>
<p>4.2
* 每十分钟冷场就讲一个笑话.
* RPC 功能允许 POST</p>
<p>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</p>
<p>4.0
* avbot 重构,使用 libavbot 提供的 class avbot。 方便其他程序包含 avbot 功能
* url 记录方式重构。
* better support for windows platform - via mingw
* 修复在 windows 控制台下输出乱码的问题</p>
<p>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</p>
<p>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</p>
<p>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)</p>
<p>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</p>
<p>3.2.2
* fixed many bugs</p>
<p>3.2.1
* bug fix
* XMPP async connect fix
3.2
* irc support multi line message support</p>
<p>3.1.1
* bug fix</p>
<p>3.1
* socks5_proxy support
3.0
* avbot 现在包含了一个POP3客户端,可以到指定(–mailaddr参数指定)的邮箱获取邮件,并将邮件贴到群聊天里。
* 改进的WebQQ协议处理,更稳定,更少下线时间
* 支持通过标准输入直接输入验证码,不再需要通过IRC频道输入验证码
* 大量的代码改进,使用协程优化了WebQQ登录过程的处理,使用协程优化了IRC连接处理</p>
<p>2.3
* bug fix
2.0
* add support for XMPP protocol</p>
<p>1.0
* qqbot works for WebQQ and IRC protocol
0.1
* tech-preview
* many copy & paste from lwqq
* only WebQQ supported. can log chat to html</p>
avbot 4.5 紧急更新
2013-05-02T00:00:00+00:00
https://microcai.org/2013/05/02/avbot-4.5
<ul>
<li>avbot 4.5 是一个紧急更新版本, 所有用户都应该升级。 因为TX的WebQQ协议有变动, 会导致无法获取群列表。*</li>
</ul>
<p>对于不知道 avbot 是神码的同学,猛击<a href="http://qqbot.avplayer.org">这里</a></p>
<p>avbot 4.0 系列第六个版本 4.5 <a href="http://sourceforge.net/projects/avbot/files/sources/avbot-4.5.tar.bz2/download">发布</a>了,同时发布的还有 <a href="https://sourceforge.net/projects/avbot/files/rpm/">rpm 包</a> 和 <a href="https://sourceforge.net/projects/avbot/files/win32/">windows 包</a></p>
<p>rpm 包有 x86_32(用于64位系统) 的和 i686 (用于32位系统) 两个版本。其实 rpm 包解压一下也可以用在 Deb 系的系统上。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PS: sourceforge 上的 avbot 项目我开通了匿名上传权限,欢迎大家提交 deb/rpm 包供大家下载。
</code></pre></div></div>
<p>What’s NEW in this release ?</p>
<p>4.5
* sync webqq protocol
* clean cache file when startup
* a bug that cause xmpp account accidently not used</p>
<p>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</p>
<p>4.3
* allow change joke interval
* allow to switchoff joke</p>
<p>4.2
* 每十分钟冷场就讲一个笑话.
* RPC 功能允许 POST</p>
<p>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</p>
<p>4.0
* avbot 重构,使用 libavbot 提供的 class avbot。 方便其他程序包含 avbot 功能
* url 记录方式重构。
* better support for windows platform - via mingw
* 修复在 windows 控制台下输出乱码的问题</p>
<p>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</p>
<p>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</p>
<p>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)</p>
<p>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</p>
<p>3.2.2
* fixed many bugs</p>
<p>3.2.1
* bug fix
* XMPP async connect fix
3.2
* irc support multi line message support</p>
<p>3.1.1
* bug fix</p>
<p>3.1
* socks5_proxy support
3.0
* avbot 现在包含了一个POP3客户端,可以到指定(–mailaddr参数指定)的邮箱获取邮件,并将邮件贴到群聊天里。
* 改进的WebQQ协议处理,更稳定,更少下线时间
* 支持通过标准输入直接输入验证码,不再需要通过IRC频道输入验证码
* 大量的代码改进,使用协程优化了WebQQ登录过程的处理,使用协程优化了IRC连接处理</p>
<p>2.3
* bug fix
2.0
* add support for XMPP protocol</p>
<p>1.0
* qqbot works for WebQQ and IRC protocol
0.1
* tech-preview
* many copy & paste from lwqq
* only WebQQ supported. can log chat to html</p>
avbot 4.2 新功能解释和实现
2013-04-29T00:00:00+00:00
https://microcai.org/2013/04/29/avbot-new-feature-autojoke
<h1 id="新功能---讲笑话">新功能 - 讲笑话</h1>
<p>这是一个早就被呼吁的功能,今天抽空实现了。笑话这个功能实现起来有2个要点:</p>
<blockquote>
<p>第一,这个笑话虽然是隔十分钟讲一次,可是不能打断大家的讨论,所以是出现十分钟的空闲后才发</p>
</blockquote>
<blockquote>
<p>第二,这个笑话需要从网页上抓取</p>
</blockquote>
<p>第一个要点, 使用办法就是 Asio 提供的 deadline_timer.
设定定时器超时 10min , 但是如果有人发言, 就重设时间. 通过链接到 avbot::on_message 就可以知道有没有人发言了.</p>
<p>实现起来非常简单,就是在 on_message 的时候重新设定定时器.</p>
<p>第二,笑话要从网上抓取. 这个使用了<a href="http://github.com/ericsimith/avjoke">徒弟</a>写的解析代码了. 不过咱高级,使用的是 avhttp 进行 HTTP 访问. 徒弟他不懂事, 手写 HTTP 解析.</p>
<hr />
<h1 id="实现">实现</h1>
<p>将笑话模块实现为一个 class joke, class joke 重载了 operator(), 也就是说,是个仿函数,本身就可以作为 on_message 的 slot . class joke 重载了多个 operator() , 一个用于 on_message 的 slot , 一个用户 deadline_timer 的 Handler.</p>
<p>在用作 on_message 的 slot 的 operator() 里, joke 就做了一件事: 重设 timer
在 deadline_timer 的 Handler 里, 首先判断 timer 是到期了还是取消了. 取消的 timer 啥也 不干.</p>
<p>到期的话 调用 joker fetcher 来下载一个 joke , 然后调用 sender 发送 joke 就可以了. sender 是一个函数对象,由 main.cpp 传入. 其实就是把 avbot::broadcast_message 做了 bind 给 joke 用, 这样就把 joke 和 avbot 解偶了.</p>
<p>joker fetcher 也是一个函数对象,用于下载 joke , 但是 avbot 提供的另外一个构造函数重载里允许用户传入自己写的笑话下载器.
joke 提供了一个默认的 joke fetcher, 这个 joke fetcher 就是 徒弟写的那个代码的一个 AVBOT 风格化的版本.</p>
<p>所以整个代码实现都是非常简单清晰易懂的. 嘿嘿.</p>
<hr />
avbot 结构解释
2013-04-28T00:00:00+00:00
https://microcai.org/2013/04/28/avbot-struct
<p>avbot 由 4 大部分构成 libavbot libavlog botctl avbotrpc</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> +----------+ +----------+ +----------+
| libavlog | | libavbot | ---+---| libwebqq |
+----------+\ / +----------+ | +----------+
main.cpp |
+----------+/ \ +----------+ | +----------+
| botctl | \| avbotrpc | +---| libxmpp |
+----------+ +----------+ | +----------+
|
| +----------+
+---+ libirc |
| +----------+
|
| +------------------+
+---+ libmailexchange |
+------------------+
</code></pre></div></div>
<p>中间由 main.cpp 作为胶水粘合。</p>
<hr />
<p>libavlog 的任务是生成日志文件,botctl 的用处是实现 .qqbot 控制指令。
avbotrcp 用于实现 JSON-RPC</p>
<h1 id="libavbot-则是核心功能">libavbot 则是核心功能。</h1>
<hr />
<p>libavbot 由 libwebqq libirc libxmpp libmailexchange 4个子协议组成。</p>
<p>libmailexchange 同时又分 libsmtp libpop3 和 libInternetMailForamt 3个小模块。</p>
<hr />
<p>衔接 libavlog botctl avbotrpc 和 libavbot 的东西就是 class avbot 的一个成员 on_message.</p>
<p>在 main.cpp 里,包含了 mybot.on_message.connect(XXX) 调用,将 avbot 和 其他3 个模块衔接起来。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // 记录到日志.
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)));
</code></pre></div></div>
<p>以及</p>
<pre style="color:#1f1c1b;background-color:#ffffff;">
<span style="color:#0057ae;">static</span> <span style="color:#0057ae;">void</span> avbot_rpc_server(<span style="color:#23a45b;">boost::shared_ptr</span><<span style="color:#23a45b;">boost::asio::ip::tcp::socket</span>> m_socket, avbot & mybot)
{
<span style="color:#808080;">detail::avbot_rpc_server</span>(m_socket, mybot.on_message);
}
</pre>
<p>on_message 是一个 boost::signals , 每当 QQ/IRC/XMPP/pop3 收到消息的时候就发起这个信号。libavlog 解析这个信号,然后将消息写入日志。avbotrpc 解析这个信号,然后返回给调用者。botctl 解析这个信号,提取其中的消息,识别其中的命令,然后写入日志文件。</p>
<p>libavbot 则实现了消息转发和群组功能。libavbot 并不藉由自己全部实现协议,而是交给了 libwebqq libirc libmailexchange 和 libxmpp 4 个协议库去实现协议。</p>
<p>libwebqq 从 pidgin-lwqq 项目借用了大量的代码,然后将其从 C 语言改写为安全的 Boost 形式。使用了 avhttp 这个 avplayer 社区发起的 HTTP 库进行Web访问。</p>
<p>libirc 由 “猫” 贡献,非常的简单。</p>
<p>libxmpp 是 gloox 的一层包装。将 gloox 包装为融入 Boost.Asio 中。而无需另外的线程跑其EventLoop。libxmpp 使用了一些Hack技巧将 gloox 改造为了 可以使用 Boost.Asio.</p>
<p>libmailexchange 包含了 libInternetMailForamt 库用于解析复杂的 Internet Mail Foramt, 以及两个小库用户执行 POP3 接收和 SMTP 发送。</p>
用概念编程
2013-04-27T00:00:00+00:00
https://microcai.org/2013/04/27/coding-by-concepts
<p>C++ 爸爸对 c++11 提出了一个 “模板概念” 提案,可是又亲自否决了它。因为他觉得还不够好,还要继续研究一番才行。C++是一个严谨的语言,新特性的加入要小心谨慎。</p>
<p>虽然提案没有通过,但是,C++爸爸俨然已经将那个 C with Class 的语言进化了,C++不再是一个 C with Class 的语言,而是迈出了“概念”编程的一步。</p>
<p>概念编程在C++中由来已久,C++爸爸的提案就是为了在语言级更好的支持概念编程。在语言没有支持到家的时候,通过修修补补,也还是能将就使用。</p>
<p>什么是用概念编程呢? 简单的来说,就是弱化类型,强调概念。比如 std::sort , 它的参数是什么呢? 他的参数可以是任何一种迭代器所表示的区间。任何类型的迭代器都可以。不论是指向int的还是string的还是你自己定义的类。这就是 std::sort 所需要的参数的概念。因为在人脑中,对一样东西排序可不需要知道他是数组还是链表。std::sort 对于 迭代器指向的对象也有一个要求:必须能比较大小。不能比较大小的东西还能排序么?</p>
<p>那么这就是 std::sort 所提出的概念要求。以概念编程,就越来越接近人脑的思维。人不再需要将抽象的概念具像为C语言中的基本类型。</p>
<p>前段时间我写的 <a href="/2013/03/28/boost-base64.html">boost 编码 base64</a> 也是概念编程的产物。因为你不用再幸苦的思索base64的编码算法了。只要依据 base64 的描述就可以写出编解码的代码了。</p>
<p>根据base64概念,编码过程就是 6bit 为一个单位,然后转化为 ASCII 字符。那么就简单了,你需要一个将输入流转化为6bit的迭代器。也就是 boost::archive::iterators::transform_width 这个迭代器,模板参数区分别是 6, 8, char。接着你获得了 6bit 的数据,然后通过base64_from_binary这个迭代器输入6bit数据输出ASCII编码。</p>
<p>将迭代器嵌套起来,就就获得了一个 base64 编码器了。 base64_from_binary<transform_width<6,8,char> > 就是一个 base64 编码的迭代器了。输入二进制数据,输出base64编码的数据。有使用 ,你还需要每78个字符换行,那么简单到再嵌入一个 boost::archive::iterators::insert_linebreaks 就可以自动的插入换行符了!</p>
<p>同样的,一个 base64 解码器也非常简单,首先是一个 boost::archive::iterators::remove_whitespace 迭代器,移除空白字符,如那个每78字符换行,然后是 boost::archive::iterators::binary_from_base64 从 ASCII 生成 6bit数据,接着是 boost::archive::iterators::transform_width 和编码反过来,模板参数为 <8, 6, char> 将6bit输入拼成8bit输出。</p>
<p>看,只要使用概念就可以完成编程了! 千万不要当心效率问题,编译器能将这种多重间接的迭代器访问给优化为单个指针访问。不要低估编译器的优化能力。</p>
ASIO 与协程
2013-04-22T00:00:00+00:00
https://microcai.org/2013/04/22/asio-statemachine
<p>前段时间看了 ASIO 爸爸关于ASIO的一个演讲. ASIO 爸爸说, ASIO 的设计理念就是作为一个 toolkit 而不是一个框架. ASIO并不强迫你使用某种编程模型. 它只是提供一系列的函数和类帮你更容易的编程.</p>
<p>ASIO 的设计思想其实和 GLIB 的 g_main_loop 非常像. 但是 C++ 因为有模板, 所以能更好的实现. 使用 g_main_loop 的时候, 我不记得我写了多少 void* 到 struct 的转化了. 这就是 C 的弊病. 因为缺少模板,所以回调必须严格符合函数签名. 但是库作者永远想不到用户还需要什么样的参数. 于是void*就是最好的解决办法了.</p>
<p>但是因为 C++ 有模板,所以放松了对函数签名的要求. 不符合 ASIO 回调函数签名要求的,都可以使用 bind 绑定以符合签名要求. 这就为库的使用者带来了巨大的灵活性的同时仍然保持极佳的易用性.</p>
<p>C++的另一个强大优势即是函数对象(bind的结果就是一个函数对象). 通过函数对象, 使得一些状态可以保存到成员变量中, 这样就给了我们一个不需要栈就实现的一个协程. 协程本质上是一个拥有多重入口和多重出口的函数. 每次调用都将从上一次退出的地方继续执行. 如果没有成员变量, 所有的变量都将保持于栈上, 这就要求协程的实现必定带来栈切换. 而栈的要求就导致协程必须有额外的创建和撤销动作. 这都给协程的实际使用造成了限制.</p>
<p>C++的函数对象, 既可以像普通函数那样调用, 又可以像通常的对象那样携带成员变量. 因此通过将变量置于对象而不是栈上, C++就可以实现无栈协程. 无栈协程的开销不过就是一个函数对象, 而无需一次性分配一个巨大的内存用于栈. 通常一个栈实现的协程,需要分配一个 1MB~10MB 的内存用于栈. 而无栈协程所使用的对象,不过就只是一个函数对象,通常大小不过几个字节到几百字节. 因此无栈协程允许创建大量的协程,而无需当心内存开销.</p>
<p>ASIO 爸爸即创造性的提出了无栈协程的概念, 然后将它用于实践中.</p>
<p>使用无栈协程, 伴随的ASIO的另一个重要用法: 复合回调函数. 将多个回调函数复合为一个. 多个ASIO操作使用同一个回调函数进行处理. 然后使用 Duff’s Device 的方法自动构建状态机. 每次调用自动的从上一次退出的地方继续执行.</p>
<p>由于 ASIO 对于回调函数是执行的拷贝操作,因此用于 ASIO 协程的函数对象必须是可拷贝的. 实现这个其实也非常轻松, 即对于不能拷贝的成员变量使用 shared_ptr 封装即可.</p>
<p>每次使用 yield 发起异步操作的时候, 记住, 当前的对象会在退出函数的时候撤销. 但是于此同时, ASIO 也获得了对象的一份拷贝. 用于下次回调. 这个特性有时候可以被利用, 用于实现非常复杂的逻辑. avbot 的 RPC 实现即利用了这个特性. 将复杂的逻辑使用寥寥数行代码轻松实现.</p>
NVIDIA 官方驱动支持 Optimus
2013-04-15T00:00:00+00:00
https://microcai.org/2013/04/15/nvidia-optimus
<p>各位,鄙人去年手贱,买了台 Optimus 的笔记本。最开始的时候用的 Bumblebee,但是觉得每
次用 optirun 非常的 egg pain。我想全局启用 NVIDIA 显卡(GT650M显卡买来不用浪费
啊!),而不是手动使用 optirun。</p>
<p>于是我折腾了 DRM/PRIME , 跑起了开源驱动,找到了PRIME的各种补丁,然后编译+折腾。终于
搞定。</p>
<p>虽然性能差了点(直到昨天我才知道,差的实现相当的大啊!)好歹比集显好多了。</p>
<p>前些天,NVIDIA 发布了 319.12 驱动,官方支持了 PRIME 和 xrandr 1.4 !</p>
<p>我乐死了,赶紧折腾。现在分享一下成功经验: PSGentoo下会easy很多。</p>
<p>首先,第一重要的是确保自己使用的是 3.9 内核。开启 intel 的 KMS , 去掉 nouveau 驱动。</p>
<p>然后确保 xorg 是最新版 ! xorg-server >= 1.13 ! <br />
确保 xrandr 是最新版! xrandr >= 1.4 !</p>
<p>然后,要使用 xf86-video-modesetting !! 而不是 xf86-video-intel ,记住!! 很重要!</p>
<p>emerge xf86-video-modesetting</p>
<p>好了,非常关键的,需要 nvidia-drivers >= 319.12 !</p>
<p>你需要做的就是 在 /etc/portage/package.unmask 解除 nvidia-drivers >= 319.12 版本的屏蔽.
然后再安装</p>
<p>好了,然后依据 http://us.download.nvidia.com/XFree86/Linux-x86/319.12/README/randr14.html 这个官方说明写好 xorg.conf 就可以了。</p>
<p>注意一下, BusID 是 使用 “02:00:0” 而 lspci 的输出是 02:00.0 , 把这个小点换成冒号。</p>
<p>把</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#! /bin/bash
xrandr --setprovideroutputsource modesetting NVIDIA-0
xrandr --auto 添加到 /etc/X11/xinitrc.d/00-optimus (添加可执行权限)
</code></pre></div></div>
<p>eselect opengl set nvidia !!!! 很重要</p>
<p>搞定。</p>
<p>使用 modesetting 驱动而不是 intel 驱动,是因为我们只需要intel显卡执行输出,一切的 2D3D操
作都由 NVIDIA 显卡完成。实际上 就是使用 modesetting 驱动做了一个 mirror 功能,把 NVIDIA
的内容 拷贝给 intel 显卡做输出。这个 mirror 是 硬件完成的( DMA) 不占用 CPU 时间,也没有多
次拷贝的问题,因此效率比 bumblebee 高很多。</p>
<p>如果需要 NVIDIA 显卡的HMDI接口外接显示器,Option “UseDisplayDevice” “none” 这个选项就
要去掉。否则要开启这个选项。</p>
<p>HDMI 功能也OK,多显示器很正常。</p>
<p>好了,使用官方的 Optimus 后才发现,我了个去,这笔记本的显卡你妹妹的快啊!操! 比我台
式机快多了 555555555555555555</p>
ASIO协程的思维转变
2013-04-13T00:00:00+00:00
https://microcai.org/2013/04/13/asio-stackless-coroutine-meaning
<p>avbot 是一个纯粹的单线程程序。在设计 avbot 最初的时候,我就给自己下了一个明确的目标:必须单线程。</p>
<p>但是,它的逻辑可不简单。它需要处理 XMPP 协议,处理 IRC 协议,处理 WebQQ 协议,处理pop3协议,处理 SMTP 协议。
所有的处理都必须异步。绝对不能因为 IO 阻塞。</p>
<p>可能很多人会不以为然,这有什么, select() 一下就好了。但是你知道这意味着多少代码么?</p>
<p>你需要编写大量的函数,大量的回调函数。别说复杂的 WebQQ 协议了,就是一个简单的 IRC , 你知道要多少代码完成异步处理么?</p>
<p>而且除了 WebQQ 以外(WebQQ的代理支持也在添加计划中), XMPP IRC POP3 SMTP 都是支持代理的。你知道要多少代码完成代理么?</p>
<p>想必很多吧?</p>
<p>所幸的是,avbot 没有使用C开发,也没有选择 ACE MFC 这样的不合格的类库开发。而是选择了 Boost。尤其是 ASIO, ASIO让异步过程大大简化。</p>
<p>而让异步过程更加简化的,就是 ASIO 作者发明的 stackless coroutine!</p>
<p>avbot 发布了许久了, 最近突然有个用户跑来说,希望能增加个调用 “外部脚本” 的功能,方便扩展。</p>
<p>我一向对设计一个 plugin 机制极力的避免,不喜欢动态载入的模块扩展程序本身的功能。何况 avbot 是 c++开发的,调用脚本并不是容易的事情。(好吧,真实的原因是我被 mingw (VC 不支持 utf8源码,我已经抛弃了) 折腾怕了,不想再搞个 python 。windows实在是恐怖的平台,写点程序麻烦的要死,编译麻烦的要死。可是 avbot 又必须跨平台,结果是我一天写好的东西要在 windows (虚拟机) 里折腾好几天,累死人 )</p>
<p>于是我决定提供一个 JSON 接口,内置一个简单的 HTTP Server, 用脚本(python应该 HTTP JSON 模块有的是,对吧)连接到 avbot ,然后 avbot 将发生的每条消息以 json 的形式返回给 外部脚本。</p>
<p>另外,默认使用 HTTP 的connection: keep-alive 模式,所以保持一个长连接即可。</p>
<p>那么,avbot 需要支持不确定数目的消息接收方了。</p>
<p>对于链接到 avbot 的客户端而言, avbot 并不保留之前的所有消息,而是从连接上的那一刻开始,后续的消息才能通知到。
一个很明显的思路就是,将链接上的客户端做成一个链表/列队, avbot 收到消息后,遍历这个列队执行消息发送。</p>
<p>这个思路很简单,可是如果要求 : 必须单线程异步呢?</p>
<p>avbot 是一个纯粹的单线程程序,绝对不允许多线程化。所有的逻辑必须使用异步处理。</p>
<p>那么,这个问题就复杂化了, “avbot 收到消息后,遍历这个列队执行消息发送” 这个做法,不可避免的带来了阻塞。好吧,异步遍历吧。</p>
<p>要是异步遍历还没遍历完,又来一个消息呢? 考虑这个问题,你会发疯的。因为异步,太多的细节需要考虑了。真的。</p>
<p>好吧,又有个好主意了,为每个客户端建立一个列队,每次遍历就是把要发送的消息挂入列队即可。这样也不需要异步遍历了,同步就可以。解决了异步遍历的时候又来一个消息导致的痛苦的调度。</p>
<p>然后细分,考虑每个客户端,就是等待 “发送列队” 不为空!等等,一直这么等待也不行,如果客户断开了链接呢? 所以要 “同时等待发送列队不为空&&客户正常在线,并且已经发送了 HTTP 请求头部”</p>
<p>好绕口,不过也只能如此了。</p>
<p>avbot 因为默认使用了 keep-alive , 所以发送是一个死循环,知道客户端主动断开链接或者网络发生错误。如果 客户端死了,那么,发送列队兴许会出现 爆队 的情况。所以要限制发送列队的大小。不是满了就不发送,而是满了后就把早的消息踢掉,也就是让 客户端发生“暂时性卡死”后,还能继续处理最后的几条信息。</p>
<p>诶,复杂的逻辑终于理清了,代码呢?!</p>
<p>啊累?</p>
<p>靠,这么复杂的 逻辑,得写一长段代码,调试几百年了吧?</p>
<p>错,我只花了几个小时,不到 100 行的代码就轻松实现了全部要求。</p>
<p>!!!!!!!!!!!!!!!!!!! WHAT !!!!!!!!!!!!!!!!!!!</p>
<p>这种功能不可能不用个千把行代码的吧?!</p>
<p>如果使用以前的老办法,确实如此。</p>
<p>可是,自从发现了 ASIO 后,我被 ASIO 爸爸发明的协程深深的震惊了!</p>
<p>利用 ASIO 爸爸提出的协程思想,我只用了不到 100行代码就全部完成了以上复杂的逻辑,而且,全部都是异步的哦~ 。</p>
fcitx 违反了 GPL2 ? (更新, 已解决)
2013-04-06T00:00:00+00:00
https://microcai.org/2013/04/06/fcitx-gpl-valation
<p>fcitx 是 yuking 的作品。 一直以来都是以 GPL2 协议发布。</p>
<p>yuking 后来将维护权交给了 csslayer. csslayer 开始了 fcitx 4.X 系列的维护工作。</p>
<p>最近我查看了fcitx 4 的协议, 发现 fcitx4 将自己的协议降级了。而且降级并没有说明获得过 yuking 和其他贡献者的同意。</p>
<p>按照 GPL2 的协议, 升级(就是变得比GPL2 还要严格,比如升级到 GPL3)是被允许的。 但是降级(也就是提出了可以不遵守GPL2的例外)显然是不被允许的。</p>
<p>按照通行法则,软件作者具备更改授权的权利。那么 csslayer 具备这个更改授权的权利么?
那就要看 fcitx 是不是 csslayer 的个人作品了。如果 fcitx 包含了许多人的工作,那么 csslayer 要更改 fcitx 的协议就必须获得所有 fcitx 贡献者的首肯。</p>
<p>明显 fcitx 4 更改协议的时候并没有获得yuking 和其他贡献者的同意。如果确实获得了他们的同意,我希望 csslayer 能公开声明获得了他们的同意。</p>
<hr />
<h1 id="更新">更新</h1>
<p>傲慢的 csslayer 承认了错误,向 yuking 获得了同意。参考 <a href="http://uploads.csslayer.info/uploads/mail/mail.mbox">这个邮件</a></p>
<p>现在 fcitx 应该没什么大问题了。</p>
模板多态
2013-04-05T00:00:00+00:00
https://microcai.org/2013/04/05/template-as-polymiorphism
<h1 id="需求">需求</h1>
<p>jack 正在实现一个 avhttp , 并且也已经相当的可用了。 他在实现http协议的时候,就对 HTTPS 和 HTTP 的实现做了一个相当重要的决定 —– HTTPS和HTTP必须是统一的代码。</p>
<p>那么,要优雅的做到这一点,显然只有一个办法:多态。</p>
<p>但是很不幸的是,SSL 需要 asio::ssl::ssl_stream , 而 TCP 则用的是 asio::ip::tcp::socket。于是 jack 想到了boost::variant。 这是一个支持 非POD成员作为一个 unin 的库解决方案。</p>
<p>很不幸,即便如此, SSL 和 TCP 仍然有相当多的代码不能公用。然后对于代理服务器的支持, SSL 和 TCP 就更不能统一处理。</p>
<p>依然要相当多的条件分支。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>就不能有一个更干净的办法么?
</code></pre></div></div>
<h1 id="分层实现">分层实现</h1>
<p>于是我提出了将 avhttp 中 HTTP 请求过程分两层实现,一个是 DNS查询、TCP 链接的发起、代理服务器握手、SSL 握手等一系列的步骤。 也就是说,在真正的发起一次 HTTP 请求之前的一系列操作将其归类到第一层。接着,是第二层,就是真正的 HTTP 协议处理了。</p>
<p>那么第一层,无论是 HTTPS 还是 HTTP, 几乎肯定都是和 TCP 打交道,只有最后一步 HTTPS 多了一个 SSL 握手, 而 HTTP 则不需要。 这样, 第一层,不论是 HTTPS 还是 HTTP,只有唯一一处条件判断选择 SSL 握手这个分支语句。</p>
<p>接着到了第二层,不论是 HTTPS 还是 HTTP 都是一模一样的处理流程。</p>
<p>而对于后续HTTP响应的读取,继续使用目前所用的 boost::variant 机制即可。</p>
<h2 id="第二层的实现采用模板多态">第二层的实现采用模板多态</h2>
<p>很明显,第二层的实现需要多态。而使用虚继承的形式的多态显然在这里大材小用了。需要写 3 个类(一个基类,两个虚继承的派生类)来封装 asio 的操作,明显也是过于繁琐了。</p>
<p>那么,显然,虽然 asio::ssl::ssl_stream 和 asio::ip::tcp::socket 不是同一个类型,但是明显都支持一样的操作! 都支持 asio::async_read/write 操作,都拥有 async_read/write 成员函数!</p>
<p>对于这样的具有相同“语法”的类,要进行多态,实在是太容易 —— C++ 提供了强大的模板机制,何乐而不为呢!</p>
<p>于是,明显的,第二层的采用模板实现。只要第一层在最后结束的时候,依据 HTTPS 还算 HTTP 为第二层调用提供不同的模板参数即可!</p>
<h1 id="结论">结论</h1>
<p>C++ 为我们提供了强大的工具,我们要做的就是为当前的需求寻找C++提供的最恰当的机制。而不需要像 C 那样一味的向语言妥协。</p>
深入理解C++
2013-04-02T00:00:00+00:00
https://microcai.org/2013/04/02/deepunderstandingcpp
<p>说一下我为何回归 C++ 了吧 : 简单: 生命有限。</p>
<p>用 C , 固然是只有一个范式, 学起来容易, 上手简单, 可是需要操心的问题太多了: 内存泄露, 野指针, 各种断错误。
可能你会说, 内存管理,小心点就可以了。
但我觉得,如果你总是用 200% 的精力去避免内存泄露,你就没有精力开发正常的软件了。你会对软件的逻辑进行折中,因为你实在没有精力开发需要你花很多时间写逻辑的代码了。
于是, 各种因为”实现起来麻烦” 为借口进行功能精简。所以我重新拿起了高级语言。</p>
<p>那么, 就面临者选择: 1 c++ 2 python 3 java etc</p>
<h1 id="那么-为啥选择了-c-呢--因为-c-的抽象能力是所有语言中最高的">那么, 为啥选择了 c++ 呢? —— 因为 C++ 的抽象能力是所有语言中最高的</h1>
<p>我为啥怎么说呢?</p>
<h2 id="你比如说java为啥java是个烂语言呢">你比如说java,为啥java是个烂语言呢</h2>
<p><em>这个和jvm无关</em></p>
<p>博士说过性能问题是狗屎, 我不喜欢考虑性能问题。 java的烂和jvm无关。</p>
<h2 id="java-烂就烂在-gc-和面向对象">java 烂就烂在 GC 和面向对象</h2>
<p>面向对象+GC 就组合成了一个臭不可闻的烂语言。</p>
<h3 id="为啥-gc-和面向对象被我那么贬低呢">为啥 GC 和面向对象被我那么贬低呢?</h3>
<p><em>因为 对象 的抽象能力实在是太弱了, 并且也不是正确的抽象</em></p>
<p>比如 人</p>
<p>你要用 class human 抽象?</p>
<p>那人能干啥?</p>
<p>这都是你不得不考虑的问题</p>
<p>还有</p>
<p>你觉得应该搞继承</p>
<p>然后人从 哺乳动物继承</p>
<p>but ~~~~</p>
<p>多年以后, 你会发现你的分类错了</p>
<p>咋办?</p>
<p><em>原有的代码都高度依赖这个了</em></p>
<p><em>这就是c++ 和 java 的面向对象都被人诟病的地方</em></p>
<h5 id="因为-oop-本身就是个骗局">因为 OOP 本身就是个骗局</h5>
<p>既然 OOP 本身就是骗局, 自然是要避免以 <em>OOP 为基础</em> 建立的语言</p>
<p>那么 java 就不能用了, 因为它是一个强迫你使用 OOP 这种烂技术的语言</p>
<p>程序是一种状态机, 图灵早就说了。</p>
<p>状态机的意思是, 程序是由多个状态构成的, 调用语句是为了切换状态。 而面向对象却认为程序是对象构成的, 简直是 bullshit。</p>
<p>对象压根就不能重用, 因为你根本找不到一个唯一正确的<em>通用</em>封装</p>
<h5 id="结果就是大量的轮子-大家都按照自己的方式封装对象">结果就是大量的<em>轮子</em>, 大家都按照自己的方式封装对象。</h5>
<h3 id="大家不理解-c--是因为大家把-c-当作了-c-的-java">大家不理解 C++ , 是因为大家把 C++ 当作了 C 的 java</h3>
<p>把 C++ 当作了能编译成本地代码的 java, 有指针的 java。实际上大家还是在用 java。
这是要绝对避免的。</p>
<h3 id="我坚定的反对-oop">我坚定的反对 OOP。</h3>
<p>OOP 容易指导你写出很烂的代码</p>
<h1 id="剩下-python-和-c">剩下 python 和 C++</h1>
<h2 id="那么-为啥是-c而不是-python-呢">那么, 为啥是 c++而不是 python 呢?</h2>
<p>因为 “工程性”。</p>
<p>python 不是一门具有工程性的语言。</p>
<p>什么叫工程性?</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>工程性的定义是: 认为程序员是人 , 是人就会犯错, 要从语言上提供机制帮助程序员避免犯错. 同时对那些 控制狂, 不能在语言上束缚他们。
</code></pre></div></div>
<p>python 束缚太多。 因为 python 想做一个”安全” 的语言。所以束缚多。</p>
<p>如果这个可以忍受, 那么第一条, “从语言上提供机制帮助程序员避免犯错”, python也没有做到!</p>
<p>因为 python 是个”安全的语言” , 所以, python 默认行为是 “面条代码”。
也就是, 绝对避免崩溃退出
除非发生了严重的错误, 而且程序员故意为之, 否则 python 死不崩溃。</p>
<p>死不出现段错误 (否则python也和C一样在段错误中挣扎怎么行呢)</p>
<p>死不挂掉</p>
<p>也就是说, <em>python 鼓励程序员写面条代码</em></p>
<p>就拿简单的一个 python gui 程序来说</p>
<p>可能他能载入 gtk 模块, 然后界面显示了</p>
<p>but , 一个功能缺失, 导致的结果不是程序拒绝启动, or 崩溃</p>
<p>而是界面显示不全</p>
<p>除非程序员故意做了检查, 抛出异常 , 强制崩溃</p>
<p>否则 python 的默认行为就是 “面条代码”</p>
<p>这就是一个不具备工程性的语言</p>
<h2 id="还有-python-可能在运行时抛出语法错误">还有, python 可能在运行时抛出语法错误</h2>
<p>这就很要命了, 一个有语法问题的 py 代码, 居然能运行。 只有偶然执行到某个有问题的代码才会崩溃抛出错误。</p>
<h2 id="也就是说-如果你没有-100-测试覆盖到代码-你就甚至不能肯定你的程序是-100-语法正确的">也就是说, 如果你没有 100% 测试覆盖到代码, 你就甚至不能肯定你的程序是 100% 语法正确的。</h2>
<p>这就不是一个具备工程性的语言</p>
<p>写点玩具还可以</p>
<p>但是一旦工程变大, 包含数十万甚至是百万千万的代码</p>
<h2 id="那么光是确保没有语法错误-这个很基本的东西-python-就要花费大量的人力">那么光是确保”没有语法错误” 这个很基本的东西, python 就要花费大量的人力</h2>
<p>这就是一个没有 <em>工程实践</em> 的程序语言</p>
<h1 id="所以-python-是一门很烂很烂的语言"><a href="https://avlog.avplayer.org/3597082/python%E6%98%AF%E4%B8%AA%E7%83%82%E8%AF%AD%E8%A8%80.html">所以 python 是一门很烂很烂的语言</a></h1>
<p>烂就烂在没有工程性。</p>
<p>可能有人会提到 C#</p>
<p>为啥我一定会说 C# 是个烂语言呢?</p>
<p>我一定是有原因的,我不会无缘无故的说某个语言是烂语言</p>
<h2 id="因为-c-就是-m-版的-java">因为 C# 就是 M$ 版的 java</h2>
<p>java 烂的, C# 一样都学习过来了</p>
<p>所以你如果不考虑 java , 就压根不需要考虑 c#</p>
<h2 id="还别提-c-压根就是不跨平台的">还别提 C# 压根就是不跨平台的</h2>
<h1 id="那么-接下来-我分析一下-c到底好在哪里-为啥-c-是这样设计的">那么, 接下来, 我分析一下 c++到底好在哪里, 为啥 C++ 是这样设计的</h1>
<p>首先一个问题, 是容易被 C 程序员忽略的, 什么是 “类型” ?</p>
<p>C 程序员如果没有意识到这点, 通常在学习 C++ 的时候会变成个垃圾 C++ 程序员。</p>
<p>因为 C++ 一切的一切 , 都是建立在 “类型” 上的。</p>
<h2 id="c的核心在-类型-而非对象不理解-类型--就不能理解-c">C++的核心在 “类型” 而非对象,不理解 类型 , 就不能理解 c++</h2>
<p>很多人(即便只用C)也很容易把 指针 和数组混淆。这就是不理解类型的原因。</p>
<p>那么啥是 “类型” 呢?</p>
<p>简单的来说, 一个类型 就意味着包含了相同的操作符。</p>
<p>我是在写 QBASIC 编译器的时候才真正的理解了 类型</p>
<p>比如 int , 是一个类型。int 是一个什么样的类型呢? 则由 int 这个类型支持的运算符定义。int 支持的运算符有 +-/* 等等 。这就是类型</p>
<p>[] 也是运算符, 下标运算符。显然 int这个类型并不支持 [] 运算符。可是 int[], int* 类型却支持 [] 运算符。所以他们必定是不同的类型。</p>
<p>所有支持 [] 运算符的类型, 都有一个共性, 那就是支持 [] 进行下标访问,这个共性被称为 “数组”。</p>
<p>所有支持 * (不是乘法, 指针的那个解引用运算符) 的运算符 , 都有一个共性, 那就是支持 * 解引用 , 这个共性就被成为 “指针”。</p>
<p>指针有一个特点, 就是支持 * 访问所引用的对象, 以及可以使用 ++ – 来变更指向的对象。( 显然 int[] 这个类型不支持 ++ –, 显然数组和指针不是一个类型。)</p>
<p>这种能力在 STL 里被称为 “迭代器”。 显然, 迭代器指的是支持 “ ++ – * “ 3种运算的类型。</p>
<p>那么博士介绍到这里大家应该就比较清楚的知道了, 所谓类型, 就是依据支持的运算符定义的。</p>
<h1 id="那么-c-为啥要这样设计-c-为啥提供了-运算符重载-">那么, C++ 为啥要这样设计? C++ 为啥提供了 “运算符重载” ?</h1>
<h2 id="因为-c-要-能编译">因为 C++ 要 “能编译”</h2>
<p>就这么简单。
比如 动态语言, 天生就支持了 map, list 这样的数据结构。</p>
<p><em>但是 c++ 不能</em>。</p>
<p>他不能在语言里提供这些, 为啥不能呢? “因为 C++ 必须被编译”。</p>
<p>这就意味着, 核心语言只能提供某种机制让你达到可以做到将 map. list 当作内置类型看待, 而不是直接内置这些功能。</p>
<p>c++ 要做到这点, 就需要一个强大的功能 : 运算符重载。</p>
<p>vector 类, 要重载 [], 以便表现的和 数组一致。提供 [] 运算符重载, 你就能实现出 安全的数组容器。</p>
<p>重载 * , 你才能写出智能指针, 还和内置的指针用法保持<em>一致</em>。而不是语言一开始就是提供智能指针。</p>
<p>重载 – ++ . 你才能实现出迭代器,还和内置的指针用法保持<em>一致</em>。而不是语言一开始就是提供迭代器。
以便就算你内部实现上对象都不是连续存放的, 都可以屏蔽到这些细节</p>
<h1 id="c是一个看重语义的语言">C++是一个看重语义的语言。</h1>
<p>所以用库对语言扩展,最好保持语法在形式上的统一。用库扩展了安全的数组容器,要在形式上使用相同的下表运算符。用库实现了智能指针,要在语法形式上提供和内置指针一样的用法, 等等。</p>
<p>也就是说, c++ 里的一切重载, 都是为了将复杂类型用法和内置类型统一化并且仍然保留 “可编译” 这一个目标奔过去的。</p>
<p>否则, 编译器插入个解释器, 解释执行不就完了, 就像 lisp 那样。</p>
<p>所以说, 要理解 “类型” 是 c++ 里最重要的概念, “可编译” 是 C++ 里最重要 设计原则</p>
<h2 id="那么为啥-有模板呢">那么为啥 有模板呢?</h2>
<p>模板同样是为 “可编译” 服务的。否则, 你拿什么实现通用的容器呢?</p>
<h3 id="让大家都从-cobject-继承--那太恶心了">让大家都从 CObject 继承? 那太恶心了</h3>
<p>java 会这么干, 不代表 c++ 会这么干。说实话, MFC 就这么干了,所以MFC是烂库,绝对的烂库。</p>
<p>23:30:28 qq(海盗):博士的见解确实高明,我一直觉得重载强大,但就是不知道强大在何处。。今天明白了</p>
<h1 id="c-对-通用容器的回答是-模板-而不是让大家都从一个-cobject-继承-因为-c-就是一个设计上要避免绑架程序员的语言">c++ 对 通用容器的回答是 “模板” 而不是让大家都从一个 CObject 继承 因为 c++ 就是一个设计上要避免绑架程序员的语言</h1>
<p>cpp里, 是注重类型的</p>
<p>23:38:39 qq(jackarain):人家10年cpp经验, 也没领悟到你这么些东西</p>
<p>23:39:13 qq(ywk/?_have_fun):受教</p>
<p>23:39:29 qq(jackarain):模板是多态最重要的表现, 而不是继承</p>
<p>23:39:39 qq(jackarain):以不变应万变</p>
<p>23:40:00 qq(jackarain):并且老老实实保留着类型信息.</p>
<p>23:40:07 qq(jackarain):不会丢失</p>
<p>23:40:44 qq(jackarain):继承, 不一样, 通常是使用cobject这种root class来以不变应万变.</p>
<p>23:40:59 qq(jackarain):但效果就像void*一样</p>
<p>23:57:44 qq(jackarain):几小时不用c++, 就心痒</p>
随机编程
2013-04-01T00:00:00+00:00
https://microcai.org/2013/04/01/random-programming
<p>这个想法由来已久, 是从和 <a href="http://codedoom.net">Jack</a> 的多次交谈中逐渐悟出来的天朝程序员惯用编程模式.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>所谓随机编程, 就是 _随机的_ 改动一些代码, 以求程序运行通过. 但是并不知道为啥这样修改程序就能工作了.
</code></pre></div></div>
<p>随机编程严重的依赖于<em>单元测试</em>. 给测试人员带来极大的负担.</p>
<p>随机编程的程序员, 对问题不求甚解. 以通过测试为编程目标. 出现问题就一头扎进 <em>改代码</em> 的 <em>重复性</em> <em>随机性</em> 工作中
为啥一个简单的bug需要那么长时间的修复? 还要程序员加班加点?</p>
<p>因为他们要随机编程, 随机性的修改, 通过 <em>大量的</em> 修改, 总有一次修改是正确的.</p>
<p>有时他们 <em>为了</em> 减轻这种机械劳动的负担, 使用了自动化的工具做测试, 是为 <em>单元测试</em> .</p>
<p>依据单元测试, 如果有一群猴子花无数年的时间, 总有一天他们能写出 <em>正常工作</em> 的代码.</p>
<p>如果一个公司异常的强调单元测试, 那么他的程序员一定是 猴子级别的. 只要他们不停的写代码, 总有一天他们能写出通过单元测试的 <em>正常工作</em> 的代码.</p>
<p>即便他们的程序员有不是猴子的, 管理员也是把他们当成猴子, 因为管理层不想依赖 <em>程序员</em></p>
使用 boost 执行 base64 解码
2013-03-28T00:00:00+00:00
https://microcai.org/2013/03/28/boost-base64
<p><a href="http://en.wikipedia.org/wiki/Base64">base64</a> 编码最初是为了电子邮件开发的。因为电子邮件是个文本协议,不能传输二进制数据,甚至中文也无法进行传输。只能传输ascii编码的文本。这样一来就诞生了多种将二进制数据编码到ascii里的编码方案,base64是其中之一。</p>
<p>base64是一种非常简单的编码,只要进行一次迭代即可完成解码。</p>
<p>什么?一次迭代???</p>
<p>这就让我们有机会借助 Boost 提供的迭代器非常简洁的写出base64解码器。</p>
<p>Boost 提供了一个叫 boost::archive::iterators::binary_from_base64 的迭代器。但是直接使用它并不能完成 base64解码。</p>
<p>还应该外面再套一层 archive::iterators::transform_width 以 6bit 为单位转换到 8bit。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>typedef archive::iterators::transform_width<
archive::iterators::binary_from_base64<const char* >, 8, 6, char>
base64decodeIterator;
</code></pre></div></div>
<p>那么这个就应该是用于解码的 base64decodeIterator</p>
<p>但是,稍等。如果用来解码电子邮件里的东西,会经常出异常,说有不能允许的字符出现在了base64输入里。</p>
<p>为什么呢? 因为电子邮件以 78个字符断行了。也就是出现了base64里不允许的 CRLF。</p>
<p>那么,怎么办? 解码前先替换删除 CRLF ?</p>
<p>非也非也,这么做是愚蠢的哦,因为我们要的就是一次迭代的效果。
所以,archive::iterators::binary_from_base64<const char* > 使用的是 const char * 这个迭代器,对吧,我们改一下,使用 boost::filter_iterator 这个迭代器。过滤掉非base64编码字符。</p>
<p>boost::filter_iterator 需要使用一个模板参数,参数是一个过滤用的仿函数。</p>
<p>于是我们写一个</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>struct is_base64_char {
bool operator()(char x) { return boost::is_any_of("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/=")(x);}
};
</code></pre></div></div>
<p>然后使用 boost::filter_iterator<is_base64_char, const char*> 作为 archive::iterators::binary_from_base64 的迭代器,就形如</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>typedef archive::iterators::transform_width<
archive::iterators::binary_from_base64<filter_iterator<detail::is_base64_char, const char*> >, 8, 6, char>
base64decodeIterator;
</code></pre></div></div>
<p>然后只要使用 base64decodeIterator(base64string) ,然后执行 ++ 不停的迭代,直到遇到 nul 字符即可完成 base64 字符串解码。为了简化这个迭代过程,可以使用</p>
<p>std::string result(base64Iterator(str.begin()) , base64Iterator(str.end()));</p>
<p>这样的形式,则 result 的构造函数内部即会执行迭代,将遍历结果存储于 result 字符串内。</p>
<p>做一个总结,就编写了如下的函数:
<script src="https://gist.github.com/microcai/5260492.js"></script></p>
在高处编程
2013-03-26T00:00:00+00:00
https://microcai.org/2013/03/26/stayhigher
<p>C++之父在一个讲座上说过,C++是一门轻量级的抽象语言。轻量级,意味着C++尽量减小因为多加的抽象带来的负担。抽象语言,意味着C++有着很强的抽象能力,C++的抽象能力,有了BOOST这样的库之后被极大的提升了,配合着BOOST, 我将 C++称呼为 <em>编译型脚本</em>。</p>
<p>C++是一门抽象语言,使用C++的时候,千万不要去思考编译器到底是如何安排内存的。认真你就输了。使用C++的时候,千万不要去思考,到底对象如何创建,如何/何时撤销的。认真你就输了。你一定要在心里这么想:这么用就是我要的。不要去想到底发生了什么。如下面的代码</p>
<script src="https://gist.github.com/microcai/5243264.js"></script>
<p>这样的代码,千万不要去思考内存的分配,std::string 的实现过程,== 运算符怎么实现的。你唯一关心的问题就是:这样一段代码是否<em>表达</em>了你心里所想的。这一段代码是否表达了你心里所要做的事情。</p>
<p>如果是,请千万不要考虑如何写能<em>更快</em>。请一定要<em>相信编译器</em>,编译器知道了你的想法后,一定会生成最优化的代码。</p>
<p>有的人说,使用了BOOST之后,编译很慢。编译慢,说明编译器在编译期执行了大量的工作。编译期做的事情越多,意味着运行期做的事情越少。不要怀疑编译的能力。</p>
<p>C++是一门轻量级的抽象语言,请站在高处进行思维,请相信编译器的优化。</p>
无栈协程
2013-03-17T00:00:00+00:00
https://microcai.org/2013/03/17/stackless-coroutine
<p>在开始之前,先来看一段代码 </p>
<pre style="color:#1f1c1b;background-color:#ffffff;">
<span style="color:#0057ae;">void</span> pop3::<b>operator</b>() ( <span style="color:#0057ae;">const</span> boost::system::error_code& ec, std::size_t length )
{
<b>using</b> <b>namespace</b> boost::asio;
ip::tcp::endpoint endpoint;
std::string status;
std::string maillength;
std::istream inbuffer ( m_streambuf.get() );
std::string msg;
reenter ( <b>this</b> ) {
restart:
m_socket.reset( <b>new</b> ip::tcp::socket(io_service) );
<b>do</b> {
<span style="color:#006e28;">#ifndef DEBUG</span>
<i><span style="color:#898887;">// 延时 60s</span></i>
_yield ::boost::delayedcallsec( io_service, <span style="color:#b08000;">60</span>, boost::bind(*<b>this</b>, ec, <span style="color:#b08000;">0</span>) );
<span style="color:#006e28;">#endif</span>
<i><span style="color:#898887;">// dns 解析并连接.</span></i>
_yield boost::async_avconnect(
boost::proxychain(io_service).add_proxy()(boost::proxy_tcp(*m_socket, ip::tcp::resolver::query(m_mailserver, <span style="color:#bf0303;">"110"</span>))),
*<b>this</b>);
<i><span style="color:#898887;">// 失败了延时 10s</span></i>
<b>if</b> ( ec )
_yield ::boost::delayedcallsec ( io_service, <span style="color:#b08000;">10</span>, boost::bind(*<b>this</b>, ec, <span style="color:#b08000;">0</span>) );
} <b>while</b> ( ec ); <i><span style="color:#898887;">// 尝试到连接成功为止!</span></i>
<i><span style="color:#898887;">// 好了,连接上了.</span></i>
m_streambuf.reset ( <b>new</b> streambuf );
<i><span style="color:#898887;">// "+OK QQMail POP3 Server v1.0 Service Ready(QQMail v2.0)"</span></i>
_yield async_read_until ( *m_socket, *m_streambuf, <span style="color:#bf0303;">"</span><span style="color:#924c9d;">\n</span><span style="color:#bf0303;">"</span>, *<b>this</b> );
inbuffer >> status;
<b>if</b> ( status != <span style="color:#bf0303;">"+OK"</span> ) {
<i><span style="color:#898887;">// 失败,重试.</span></i>
<b>goto</b> restart;
}
<i><span style="color:#898887;">// 发送用户名.</span></i>
_yield m_socket->async_write_some ( buffer ( std::string ( <span style="color:#bf0303;">"user "</span> ) + m_mailaddr + <span style="color:#bf0303;">"</span><span style="color:#924c9d;">\n</span><span style="color:#bf0303;">"</span> ), *<b>this</b> );
<b>if</b>(ec) <b>goto</b> restart;
<i><span style="color:#898887;">// 接受返回状态.</span></i>
m_streambuf.reset ( <b>new</b> streambuf );
_yield async_read_until ( *m_socket, *m_streambuf, <span style="color:#bf0303;">"</span><span style="color:#924c9d;">\n</span><span style="color:#bf0303;">"</span>, *<b>this</b> );
inbuffer >> status;
<i><span style="color:#898887;">// 解析是不是 OK.</span></i>
<b>if</b> ( status != <span style="color:#bf0303;">"+OK"</span> ) {
<i><span style="color:#898887;">// 失败,重试.</span></i>
<b>goto</b> restart;
}
<i><span style="color:#898887;">// 发送密码.</span></i>
_yield m_socket->async_write_some ( buffer ( std::string ( <span style="color:#bf0303;">"pass "</span> ) + m_passwd + <span style="color:#bf0303;">"</span><span style="color:#924c9d;">\n</span><span style="color:#bf0303;">"</span> ), *<b>this</b> );
<i><span style="color:#898887;">// 接受返回状态.</span></i>
m_streambuf.reset ( <b>new</b> streambuf );
_yield async_read_until ( *m_socket, *m_streambuf, <span style="color:#bf0303;">"</span><span style="color:#924c9d;">\n</span><span style="color:#bf0303;">"</span>, *<b>this</b> );
inbuffer >> status;
<i><span style="color:#898887;">// 解析是不是 OK.</span></i>
<b>if</b> ( status != <span style="color:#bf0303;">"+OK"</span> ) {
<i><span style="color:#898887;">// 失败,重试.</span></i>
<b>goto</b> restart;
}
<i><span style="color:#898887;">// 完成登录. 开始接收邮件.</span></i>
<i><span style="color:#898887;">// 发送 list 命令.</span></i>
_yield m_socket->async_write_some ( buffer ( std::string ( <span style="color:#bf0303;">"list</span><span style="color:#924c9d;">\n</span><span style="color:#bf0303;">"</span> ) ), *<b>this</b> );
<i><span style="color:#898887;">// 接受返回的邮件.</span></i>
m_streambuf.reset ( <b>new</b> streambuf );
_yield async_read_until ( *m_socket, *m_streambuf, <span style="color:#bf0303;">"</span><span style="color:#924c9d;">\n</span><span style="color:#bf0303;">"</span>, *<b>this</b> );
inbuffer >> status;
<i><span style="color:#898887;">// 解析是不是 OK.</span></i>
<b>if</b> ( status != <span style="color:#bf0303;">"+OK"</span> ) {
<i><span style="color:#898887;">// 失败,重试.</span></i>
<b>goto</b> restart;
}
<i><span style="color:#898887;">// 开始进入循环处理邮件.</span></i>
maillist.clear();
_yield m_socket->async_read_some ( m_streambuf->prepare ( <span style="color:#b08000;">8192</span> ), *<b>this</b> );
m_streambuf->commit ( length );
<b>while</b> ( status != <span style="color:#bf0303;">"."</span> ) {
maillength.clear();
status.clear();
inbuffer >> status;
inbuffer >> maillength;
<i><span style="color:#898887;">// 把邮件的编号push到容器里.</span></i>
<b>if</b> ( maillength.length() )
maillist.push_back ( status );
<b>if</b> ( inbuffer.eof() && status != <span style="color:#bf0303;">"."</span> )
_yield m_socket->async_read_some ( m_streambuf->prepare ( <span style="color:#b08000;">8192</span> ), *<b>this</b> );
}
<i><span style="color:#898887;">// 获取邮件.</span></i>
<b>while</b> ( !maillist.empty() ) {
<i><span style="color:#898887;">// 发送 retr #number 命令.</span></i>
msg = boost::str ( boost::format ( <span style="color:#bf0303;">"retr %s</span><span style="color:#924c9d;">\r\n</span><span style="color:#bf0303;">"</span> ) % maillist[<span style="color:#b08000;">0</span>] );
_yield m_socket->async_write_some ( buffer ( msg ), *<b>this</b> );
<i><span style="color:#898887;">// 获得 +OK</span></i>
m_streambuf.reset ( <b>new</b> streambuf );
_yield async_read_until ( *m_socket, *m_streambuf, <span style="color:#bf0303;">"</span><span style="color:#924c9d;">\n</span><span style="color:#bf0303;">"</span>, *<b>this</b> );
inbuffer >> status;
<i><span style="color:#898887;">// 解析是不是 OK.</span></i>
<b>if</b> ( status != <span style="color:#bf0303;">"+OK"</span> ) {
<i><span style="color:#898887;">// 失败,重试.</span></i>
<b>goto</b> restart;
}
<i><span style="color:#898887;">// 获取邮件内容,邮件一单行的 . 结束.</span></i>
_yield async_read_until ( *m_socket, *m_streambuf, <span style="color:#bf0303;">"</span><span style="color:#924c9d;">\r\n</span><span style="color:#bf0303;">.</span><span style="color:#924c9d;">\r\n</span><span style="color:#bf0303;">"</span>, *<b>this</b> );
<i><span style="color:#898887;">// 然后将邮件内容给处理.</span></i>
process_mail ( inbuffer );
<i><span style="color:#898887;">// 删除邮件啦.</span></i>
msg = boost::str ( boost::format ( <span style="color:#bf0303;">"dele %s</span><span style="color:#924c9d;">\r\n</span><span style="color:#bf0303;">"</span> ) % maillist[<span style="color:#b08000;">0</span>] );
_yield m_socket->async_write_some ( buffer ( msg ), *<b>this</b> );
maillist.erase ( maillist.begin() );
<i><span style="color:#898887;">// 获得 +OK</span></i>
m_streambuf.reset ( <b>new</b> streambuf );
_yield async_read_until ( *m_socket, *m_streambuf, <span style="color:#bf0303;">"</span><span style="color:#924c9d;">\n</span><span style="color:#bf0303;">"</span>, *<b>this</b> );
inbuffer >> status;
<i><span style="color:#898887;">// 解析是不是 OK.</span></i>
<b>if</b> ( status != <span style="color:#bf0303;">"+OK"</span> ) {
<i><span style="color:#898887;">// 失败,但是并不是啥大问题.</span></i>
std::cout << <span style="color:#bf0303;">"deleting mail failed"</span> << std::endl;
<i><span style="color:#898887;">// but 如果是连接出问题那还是要重启的.</span></i>
<b>if</b>(ec) <b>goto</b> restart;
}
}
<i><span style="color:#898887;">// 处理完毕.</span></i>
_yield async_write ( *m_socket, buffer ( <span style="color:#bf0303;">"quit</span><span style="color:#924c9d;">\n</span><span style="color:#bf0303;">"</span> ), *<b>this</b> );
_yield ::boost::delayedcallsec ( io_service, <span style="color:#b08000;">1</span>, boost::bind ( *<b>this</b>, ec, <span style="color:#b08000;">0</span> ) );
<b>if</b>(m_socket->is_open())
m_socket->shutdown ( ip::tcp::socket::shutdown_both );
_yield ::boost::delayedcallsec ( io_service, <span style="color:#b08000;">1</span>, boost::bind ( *<b>this</b>, ec, <span style="color:#b08000;">0</span> ) );
m_socket.reset();
std::cout << <span style="color:#bf0303;">"邮件处理完毕"</span> << std::endl;
_yield ::boost::delayedcallsec ( io_service, <span style="color:#b08000;">30</span>, boost::bind ( *<b>this</b>, ec, <span style="color:#b08000;">0</span> ) );
<b>goto</b> restart;
}
}
</pre>
<!-- -->
<p>这个代码,乍一看就是同步代码嘛!而事实上它是异步的</p>
<p>在这个代码里,使用了 _yield 前缀再配合 async_* 异步函数,使用异步实现了同步的pop3登录算法。</p>
<p>这个神奇的代码,神奇之处就是 reenter(this) 和 _yield。这2个地方就是实现的全部的关键。</p>
<p>我在群课程里有简单的提到过协程,有兴趣的可以到 <a href="https://avlog.avplayer.org/3597082/%E5%8D%8F%E7%A8%8B.html">avplayer社区讲座:协程</a> 围观。</p>
Why ubuntu sucks and everyone should not use it
2013-03-08T00:00:00+00:00
https://microcai.org/2013/03/08/killubuntu
<p>这篇文章早就应该发了,但是我太自私,只管自己躲在Gentoo的树阴下乘凉,不为那些在ubuntu的刀山祸害里挣扎的人提供援助。
所以,我觉得为他们提供无偿的援助——告诉他们,ubuntu很烂很烂。</p>
<p>ubuntu的烂要分两个部分,其一是debian的烂,其二是ubuntu的烂。</p>
<p># debian之烂</p>
<p>debian的烂,体现在包管理器上,特别是debian特色的拆包习俗。</p>
<p>debian的打包习俗有三大恶:</p>
<ul>
<li>拆包细</li>
<li>乱改名</li>
<li>版本旧</li>
</ul>
<p>拆包过细,大大增加维护负担。虽然对用户来说看似节约了硬盘空间,安装更少的二进制数据,实际上包管理器的数据库变得更加庞大。</p>
<p>乱改名这就是debian的原罪了。因为deb包本身的限制,无法在包里编制版本信息以允许相同的包的不同版本同时安装。只能采取将软件的版本作为后缀作为包名。比如 libXXX2 libXXX1 libXXX0
这样开发者在写依赖的时候,就需要知道具体后缀是0 还是1 还是2。非常麻烦。</p>
<p>因为拆包细,写依赖就要写一堆,稍微不注意就少写依赖了。大大增加了打包人员的维护负担。</p>
<p>乱改名和拆包结合起来,导致一些著名的软件,即便是使用他的本名你也安装不了。
比如 apt-get install git 是无法安装git的。因为 git 被拆包了,而且还改名了。到现在我也不知道如何安装git。</p>
<p>debian 包以旧为荣耀,片面的认为旧就是稳定。</p>
<p>debian的包,一个字旧。debian通常包含的是上游停止维护的版本。诶,你说上游停止维护的版本,稳定个鬼啊。</p>
<p>可是 debian 不这么认为,debian 认为旧就是稳定。</p>
<h1 id="ubuntu自身之恶">ubuntu自身之恶</h1>
<p>除了<em>原原本本的继承</em>了全部的debian之恶,ubuntu自身的恶就罄竹难书了。</p>
<p>ubuntu喜欢控制,喜欢 fork 上游软件。然后自己定制。定制完了不回馈给上游。</p>
<p>过了一年,ubuntu 又重新 fork ,因为自己 fork 的那个版本落后了,显然不能和上游竞争了。</p>
<p>ubuntu想控制 gnome , 结果控制不了,就自己 fork 个 unity。先是 gtk 开发,然后失败,
然后 以compiz 插件开发,失败,然后又用 Qt 开发。
哥我还是会断定他继续失败。</p>
<p>ubuntu一意孤行,一定要<em>故意</em>创造和其他发行版不兼容的地方。其他发行版用 systemd , ubuntu一意孤行的使用upstart。故意创造不兼容的地方。</p>
<p>TOBE ADD</p>
博客迁移到 github pages
2013-02-02T00:00:00+00:00
https://microcai.org/2013/02/02/blogtrans
<p>越来越觉得花钱买一个 VPS 是个太浪费钱的事情了,而且也没那么多精力去维护服务器,于是将博客迁移到 github 就成了上上选择。</p>
<p>现在终于完成迁移了,并且也开启了评论功能,真的是非常不错。可以使用 vim 编写博客了,这真的是太爽的事情了。</p>
<p>目前的 url 是 <a href="http://microcai.org">http://microcai.org</a>, 支持 RSS Feed, url是 <a href="/feed">RSS Feed</a></p>
<p>每篇文章都可以使用评论功能哦!</p>
<p>这真的是太爽了。</p>
2012 年总结
2013-01-31T00:00:00+00:00
https://microcai.org/2013/01/31/2012sum
<p>过去一年里,啥事情没干。</p>
<p>2012年来的时候,唔说,世界末日到来前不拼命工作。于是就每天宅家里啥也不做了。</p>
<p>要说啥都没做也不对。</p>
<h3 id="乘宅在家里不用工作的机会把很久以前丢下的c重新学习了">乘宅在家里不用工作的机会,把很久以前丢下的C++重新学习了。</h3>
<p>重新学习C++是一个机缘巧合的事情,jack 一直是一个C++程序员,重度Boost粉丝。一直向我灌输boost。不过,我一直和云风一样,总是黑C++,说只用C开发。</p>
<p>不过C++11的发布让我觉得有必要了解一下C++11。就算不用,我总要看别人的c++11代码的吧!本着这个目的,我仔细的看了c++老爸写的C++11 FAQ。</p>
<p>新的c++11让我惊呼! C++11乃神器也!</p>
<p>我开始不黑C++了,我改变了对C++的坏印象。但是真正开始使用C++,与使用Boost可同一时间。
有一天,我读了ByVoid的博客,他推荐了一个叫rimeime的神级输入法。我被rimeime首页上那唯美的诗吸引了,认定rimeime作者一定是个大牛。当我编译rime的时候,发现rime居然是使用Boost开发的。rimeime专注于输入法方面算法,只字不提开发中的啥数据结构啊之类的,让我这个整天活在C世界的乡巴佬汗颜。我过多的思考底层了。是个毛病,该改!我意思到,软件开发就应该专注于这个专业领域的东西,不要被C语言带来的计算机原理模糊掉自己的方向。和 jack 讨论了这个问题,他也认为当下的编程语言,都过分的让程序员考虑计算机的内部原理了。而能让不懂编程的用户快速的开发出程序的语言,都死了。(VB 泪流满面)
于是我再一次拿起C++的时候,自己打算试一试,再加上jack的鼓吹,终于开始使用Boost了。
Boost 真乃神器也~ 有种相见恨晚的感觉。</p>
<h3 id="llvm是个好东西于是借着编写一个qbasic编译器的功夫学习了一下llvm">LLVM是个好东西,于是借着编写一个QBASIC编译器的功夫学习了一下LLVM</h3>
<p>很早的时候,我就想写一个编译器。一直认为编译器是一个很高难度的东西。于是我选择了一个简单的语言:QBASIC作为我的目标。经过一个星期的编译原理方面的学习,终于靠着llvm这个大山写出了一个编译器,哦也,感觉非常不错。</p>
<h3 id="avplayerorg-社区创立">avplayer.org 社区创立。</h3>
<p>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 目前可是最受欢迎的项目,呵呵。</p>
avplayer 开源软件组织成立,域名 avplayer.org
2013-01-01T00:00:00+00:00
https://microcai.org/2013/01/01/avplayer
<h1 id="avplayerorg-项目组成立了">avplayer.org 项目组成立了。</h1>
<p>avplayer 的宗旨是开发易于使用的跨平台 AV播放器框架。avplayer自带一个用 AV播放器框架写的AVPLAYER播放器。 依托于 AVPLAYER本组织还开发了用于成员沟通的 AVBOT (原 QQBOT)。 AVBOT 实现了 QQ/IRC/XMPP的互通。</p>
<p>为了更科学的上网,本群再次发力,开发了 AVSOCKS 科学操长城软件。</p>
<p>当然, 所有的代码都在 https://github.com/avplayer</p>
<p>项目首页 http://avplayer.org</p>
<h3 id="如何加入">如何加入?</h3>
<p>首先要会 C++。不会也没关系,进来一起学习 AV ,学习的过程中就会 C++了。使用 BOOST 神器加速。多少AV都不怕。来组织的人,都嗑 BOOST 上瘾了,相信你加入后也会被 BOOST 征服的。
如何加入?</p>
<p>简单,加入 QQ群 3597082 即可。 没有 QQ ? Linuxer ? 没关系,咱不是用 AVBOT 么! 进入 #avplayer @ irc.freenode.net 一样可以参与聊天。</p>
<p>哦?不会 IRC ? 还好有了 AVBOT, 加入 XMPP 聊天室 avplayer@im.linuxapp.org 即可!</p>
python是个渣语言
2012-12-25T00:00:00+00:00
https://microcai.org/2012/12/25/whypythonbad
<p>一直以来我都表达 python 是个糟糕语言的观点,但是没有深入的解释。</p>
<p>计算机一直都是“工具”,意味着我们是拿它干活的,也就是所谓的提高生产力。</p>
<p>指挥计算机干活的重要工具就是编程语言。计算机并不是训练来干活的,是编程来干活的。编程语言的效率有2个指标:编写干活指南的效率和机器人执行的效率。</p>
<p>在人力成本低于硬件的时候,人追捧的是执行效率。</p>
<p>人力成本越来越高的时候,人开始追求编程的效率。</p>
<p>人自然是希望一个语言能两头兼顾。可惜的是 python非但没有带来执行效率(这是python不追求的,所以姑且不算缺点),连它拼命牺牲执行效率希望换来的开发效率事实上也一点没有。</p>
<p>执行效率:首先,执行效率和语言本身高级不高级是没有任何关系的。执行效率的高低只关系到冗余操作的多寡。这也是“优化”的基础,去除冗余操作。</p>
<p>冗余操作的多寡通常有3个因素影响到:1 编译器的效率 2 程序员的水平 3 语言本身的累赘</p>
<p>但是衡量一个语言本身效率的,事实上应该是最后一个 “语言本身的累赘”,这个才是编译器永远无法改进的,程序员水平再高也无能为力的。</p>
<p>不幸的是,python是一个本身的累赘非常多的语言。而累赘最少的语言,应该算是C++语言了。所有C++用到的功能,没有一个是可以在别的语言用更低的代价实现的。当然,有的语言压根就没有C++提供的功能,必须自己模拟。模拟的代价和C++提供的是一样的,水平不够的人来模拟只能获得更烂的结果。</p>
<p>当然 ,python是个高级语言,语法糖多点,性能烂就接受一下吧!毕竟开发效率高呢(?)</p>
<p>接下来我们说编程的效率。编程效率主要是受5个因素影响:语法是否自然,语义是否凝炼,文档或者或教程多不多,库是不是丰富,开发环境好不好。不用说python的文档还是很多的,但是显然没有c++多。各种粗制滥造的c++教程铺天盖地,算了,这是c++的坏处。误人子弟的教程太多。库当然是C++最丰富了。python还面临着 python3 和 python2 的分裂。</p>
<p>开发环境和库一直是C++的优势。不用说 Visual C++这种重量级公司出的IDE ,还有 kdevelop , eclipse CDT 这些免费开源的IDE。自动完成和代码提示让你写代码的时候非常轻松。相比之下 , python 就没有好的IDE了。</p>
<p>当然,接下来是程序员最关心的,语法是否自然,语义是否凝炼。python 的语义自然是很多的,一条语句能相当于写几千行 C 代码。这也是人常说的,语法糖多。可惜的是,C++一样有高级语法糖,而且代价很低!比python低太多了。在这一点上,python没有优势,只有劣质。python的语法糖是以牺牲性能换来的,而C++在不损失性能的同时提供了语法糖。</p>
<p>至于语法自然不,仁者见仁智者见智了。对于大多数C学过来的人来说, 自然是C++的语法简单。当然,前提是不使用模板这种高级货。真的用模板的话,模板是属于用起来简单,写起来难的语法糖。我们自然可以选择把困难留给 boost,快乐留给自己。所以这点上 C++没有输。何况python还不支持模板。</p>
<p>那么开发效率到底是 C++高还是 python呢?</p>
<p>差不多!</p>
<p>那执行效率呢?</p>
<p>C++和python不是一个档次的,没法比。</p>
<p>好了,单从这点已经 python 完败。不过我想说的还不是这个。</p>
<p>如果真的有语言像python那样慢,我觉得对得起它的性能的,就必须拿出像样的功能,这个功能包括</p>
<p>语言级的并行能力,语言级的多进程能力 (等等,这不就是shell么!),语言级的SIMD能力(语言级的矩阵运算支持)</p>
<p>语言级的复杂数学公式计算能力 ,内置的标准各种算法(STL笑而不语,不过我要的还不止STL),描述性语义(而非指令性语义)</p>
<p>我要求的这些,是高级语言需要具备的,而 python 统统没有,连 shell 都能占上一些!</p>
内存管理随笔
2012-10-31T00:00:00+00:00
https://microcai.org/2012/10/31/mm
<h3 id="思考一">思考一:</h3>
<p>任何一个程序,只要不是 helloworld 那样的简单程序,必然会用到内存管理。内存管理是写程序不可避免的过程。C程序员最大的恶魔就是野指针和内存泄漏。这是每个C程序员的噩梦。C++继承了C的缺陷,基本上半斤八两。而且C++还可以自由重载new操作符,给内存管理更加重了复杂性。
我常常在想,写BASH脚本的时候,我们有管理过内存么?即便是java那样的语言,内存管理也是后台进行的,并不是可避免的,俗称GC。可是写脚本的时候,真的完全没用内存管理方面的困扰。</p>
<p>到底是哪里出了问题呢?到底为何脚本就不用管理内存?</p>
<h3 id="思考二">思考二:</h3>
<p>能不能完全不依靠malloc/free new/delete 编写出一个 c/c++程序呢?</p>
<p>结果是显然的,能!</p>
<p>但是只有对 hello world 那样的程序才有用。最近的一项小随笔项目证实了我的想法 https://github.com/microcai/hm 。在这个程序里,我没用使用任何 malloc/free new/delete 来管理内存。</p>
<p>为何这个程序可以不用内存管理呢? 于是在另一个随笔项目里,我依然使用了内存管理,虽然是智能指针自动管理的,但是毕竟使用了 new 操作 https://github.com/microcai/googleproxy 。我在想,凭啥这个程序就必须使用 new 了?</p>
<h3 id="思考三">思考三:</h3>
<p>程序,说到底就是一个状态机。在 hm 程序里,我采用的是“过程化”编程,外加同步多进程的IO模型。在 googleproxy 程序里,使用的是单线程异步IO的模型。</p>
<p>hm里,状态机的状态就是进程的状态。一步一步执行下去,状态随之切换。cpu执行到哪一行,哪一行就是当前状态。</p>
<p>googleproxy里,情况有了变化,因为使用了异步,所有的状态变化都是围绕一个中心进行的: boost::io_service::run() 。 在 run() 里,通过回调来通知状态的变化。也就是说,cpu执行到哪个回调,哪个就是当前状态。
区别是什么:</p>
<p>在不同的状态之间,我们有数据要共享! hm 多使用局部变量,不同的状态需要共享数据,通常也处于同一代码层级!可以直接引用需要的数据!</p>
<p>googleproxy 里,不同的状态之间,是不同的 run() 操作的回调,需要共享数据,但是上一个状态的局部变量已经消失!上一个状态的局部变量已经消失!
要跨栈域引用共享数据,唯一的办法就是将数据创建在堆上!</p>
<p>简单的来说,在一个程序里,所有代码的执行路径可以归纳为从 main() 开始的一个调用树。一个函数只能引用本层和上层的局部变量,无法引用子层和兄弟层和兄弟的子层的局部变量。因为这些地方的变量都是不存在的呀!</p>
<p>所以,只能将共享数据创建在堆上。这样才能跨过调用树的生存周期!</p>
<p>这也是唯一需要堆管理的理由。</p>
<p>如果一个变量要进入函数的时候创建在堆上,退出的时候释放,通常的做法其实就是使用栈变量,或者是使用可变长度的stl容器。</p>
<p>如果返回值是对象,也不要使用指针了,直接返回对象吧!别担心临时对象拷贝开销了。</p>
<p>只有当跨栈域共享对象的时候,才考虑使用堆吧!
记住:即便如此,也不要使用裸指针。裸指针只用来进行直接内存访问(底层编程的时候),千万别用来引用对象。使用智能指针吧!放弃对象的拷贝开销的担心吧! 那几百个周期的拷贝操作比 new/delete 的内存管理代码的开销相比,还是低太多里。何况有了 c++11 的 Move 语义,很多时候已经没用拷贝开销里。</p>
<p>放心大胆的脚本化C++程序吧! 实在需要跨栈共享数据的时候,使用 shared_ptr 引用吧!</p>
C++ 王者归来
2012-10-12T00:00:00+00:00
https://microcai.org/2012/10/12/cppreturn
<p>曾几何时,学会了C语言。安装了VC6这个神叉IDE。</p>
<p>然后被迫开始CPP路程。由于C++兼容C,所以一直在以C的方式写C++。然后慢慢的开始学写C++代码。写C++代码是从MFC开始的。慢慢的,我学会了用class,感觉是个比struct好用的多的结构体。再慢慢的,我学会了继承,还有… 多重继承。</p>
<p>MFC 就是我的导师。开始不停的向MFC学习。既然用了c++的继承,就认识到了继承的陷阱。继承后,构造函数和构析函数的执行次序,等等。还有虚继承后的各种问题和陷阱。</p>
<p>一一去了解。</p>
<p>在程序结构方面,开始向 MFC 看齐。喜欢把任何操作都包装到class里。设计class的时候,开始过度设计。总考虑到某天我会需要继承它。说不定还需要多重继承。等等。明明已经设计好class,却喜欢继续添加功能。永远用不到的功能。只因为我“未来可能需要在别的程序里用,可能需要继承它。”</p>
<p>任何一个程序,我都为它设计对象,全局的程序对象又有子对象,子又有子,子子孙孙无穷尽也~</p>
<p>成员变量一多,构造函数里成员变量初始化代码就变多。然后设计构造函数重载。写更多的用不到的构造函数。
我没有意识到,我已经犯了过度设计的毛病了</p>
<p>而c++另一个强大的功能“模板”我却一无所知。当我发现了c++还有模板功能的时候,我只是简单的尝试了一下,发现模板无法在VC下很好的被自动完成。又听说模板会导致代码膨胀,诸如此类。我只小用一下模板。就丢弃了模板。继续我过度设计的c++之路。</p>
<font color="red"> 某一天,我开始Linux之路。 </font>
<p>当我开始写出第一个GTK程序的时候,发现了C之美。C可以写出和C++一样的对象代码。而且没有了 C++的许多陷阱。</p>
<p>也正因为C,我开始摒弃了过度设计。所设计的API皆以现阶段够用为限。未来怎么样,未来再添加好了。没有了c++的包袱,再也不设计class了。都是简单的函数。函数完成一个“过程”。而不再是操作一个“对象”。让我把精力放到了“过程”本身,而不会再去设计“漂亮的对象”。</p>
<p>之后阅读到了Linus对C++的一篇讽刺邮件。对比自身,多多感触。c++确实是个容易写出糟糕代码的语言。</p>
<p>从此发誓不再用c++,只以C写代码。</p>
<p>再一次拾起C++,是因为c++11的发布,有了一些新的语法糖。我虽然不再打算写c++代码,但是没打算不再读别人的c++代码。如果别人用c++11写了新的代码,我确看不懂,不是损失是什么。我决定看一看c++11。</p>
<p>c++11最令我着迷的新特性就是lambda。还有新的for语法。当然,boost::bind 和 boost::function 进入了 std。成为的标准的一部分。我对 std::function 的印象只限于“更好的函数指针替代”。</p>
<p>lambda 一用,发现非常不错。好多麻烦的 static 函数都可以被匿名的 lambda取代了。我喜欢这种让名称空间更干净的感觉。</p>
<p>我开始纠结lambda了。动摇了。如果只用c++兼容C的那部分,然后lambda,就是带lambda的C嘛!这样自欺欺人的开始用C++。</p>
<p>参与了朋友的一个小项目。他是个重度c++粉。但是因为他的关系,我开始真正的拿起了boost。他说,没有boost他就没办法写代码。</p>
<p>这里有个小插曲,我在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。</p>
<p>其实之前也尝试过boost,都被假死状态的eclipse弄怕了。所以关了eclipse的index功能没了boost的语法提升瞎写过。</p>
<p>但是换到KDevelop后那畅快淋淋的感觉,打出 boost:: 后那完善的提示,我马上开始全身心的试用boost。</p>
<p>就是这一试用,让我重回了c++的怀抱。重回c++后,我发现了c++正确的用法:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>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设计。
虚回调函数绝对是个傻逼设计。迫使用户使用继承并进行虚成员函数重载。
</code></pre></div></div>
善待笔记本,启用intel集显的节能模式
2011-11-16T00:00:00+00:00
https://microcai.org/2011/11/16/intelgpurc6
<p>在 XP 下用的时候,用计量插座,空闲功耗大概在 13W ~ 14W 之间。</p>
<p>在 Gentoo 下用的时候,空闲功耗也在 20W 之上。</p>
<p>很头痛。</p>
<p>CPU 的节能已经打开,一直在 800M 最低频率运行呢!</p>
<p>恩,应该是 GPU 费电啊~~~ 有什么办法能让 intel 的 GPU 工作在节能模式呢?</p>
<p>答案是添加隐含的内核引导参数</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>i915.lvds_downclock=1 i915.i915_enable_fbc=1 i915.i915_enable_rc6=1
</code></pre></div></div>
<p>神奇的参数。</p>
<p>现在 Gentoo 空闲下来的时候,笔记本也是 13W ~ 14W 的功耗了。哦也 ~~ CPU 也没那么热了。</p>
i7 拯救了 Gentoo
2011-11-11T00:00:00+00:00
https://microcai.org/2011/11/11/i7savedgentoo
<p>好多年以前,我开始了 Linux 之旅。</p>
<p>我频繁的切换发行版,从 RedFlag 5 到 RedFlag 6 再到 ubuntu 8.10 马上又切到 Fedora 8 然后是 9 10 , 期间又重回 RedFlag 7 过,间或也使用过 debian 和 puppy。</p>
<p>如此频繁的切换发行版,只不过是我觉得它们都没有满足我的要求。我不停的切换,企图寻找到一个可以一劳永逸的满足我的需求的系统。 我不停的搜索着,尝试着。</p>
<p>直到有一天,我发现了 Gentoo 。</p>
<p>那个时候我已经厌倦了 Fedora . 不停的重装,只是为了升级。尝试过 upgrade , 只不过比不得重装快活。也有很多错误,不如重装干脆。</p>
<p>我实在是厌倦了每6个月重装一下系统,只是为了升级。我也厌倦了每次重装好系统之后都要开始好几天的清理工作,把不需要的东西删除掉,再把需要的软件又重装一次。</p>
<p>直到我发现了 Gentoo .</p>
<p>经过漫长的编译后,我有了一个可以启动的 Gentoo 系统。但是没有 X , 没有gnome,没有 firefox . 啥都没有。</p>
<p>编译 firefox 前我需要 DE ,我选择 gnome, 编译 gnome 前我需要 X . 为了编译 firefox , 5 百多个软件需要被编译。</p>
<p>又是一个 overnight 编译。</p>
<p>第3天早上,打开显示器查看的时候终于编译好了。期间因为编译错误中断过几次,手工排除了一些错误,继续编译。不过总的来说,一个可以上网的环境就编译好了。</p>
<p>接下来是更多的编译。因为我需要的一切都没有安装。编译 编译 编译 编译 编译 从此成了吃饭后的重要工作。</p>
<p>过了一个多星期,我终于有了和从前“一样”的环境了。却少了很多垃圾。我不需要的统统用 USE=”-XXX” 的方式清理掉了。</p>
<p>当 irc 上的朋友发现我失踪了几天再回来的时候,我已经满口的 Gentooooooooooooooooooooo 了。我不停的向大家赞叹 Gentoo 有多么优秀。只有等到数个月后,我才进入了平静期。</p>
<p>我享受了近2年的 Gentoo 。</p>
<p>我厌倦了。</p>
<p>无休止的编译炸干了我最后一点耐性。Firefox 也进入了快速发布时代,而每次firefox 升级,带来的都是数个小时的编译。期间我的电脑由于编译而开始响应缓慢,数个小时不能流畅使用的状态!</p>
<p>而每次 firefox 升级,都意味着 thunderbird 也要升级。 这些都是需要编译数个小时的大块头。</p>
<p>每天都要升级,否则积累起来可能就是要一天时间都编译不完的更新。</p>
<p>我差一点就放弃 Gentoo 了!直到我下了一次狠心,入手了一个 Sandy Bridge 的 Xeon E3-1230 . 其实就是 i7 2600 阉割了GPU. 因为我有显卡,不想再为核芯显卡掏钱。而且我只要 NVIDIA 的显卡。别的统统不要。</p>
<p>组装好 CPU 后,我被 i7 的性能惊呆了!</p>
<p>编译 Ooo 居然也不过几十分钟的事情,以前的电脑可是要编译一整个下午的。这次居然只有几十分钟。</p>
<p>而在编译的过程中,我的电脑一直都处于可用状态,没有因为大量的后台编译而损失性能。</p>
<p>由于换了 cpu 和主板需要重新编译内核,这次内核的编译时间也让我惊呆了,89s ! 89s! 以前的电脑需要一个多小时啊!89s 只相当于我没有 make clean 而是改动了几个选项重新 make 的时间。</p>
<p>这次 89s 是全部重新编译的时间。如果我修改了个东西,再编译一下,那只需要几秒到十几秒就好了。</p>
<p>开了 pgo (需要编译2次) 的 firefox 也只有几分钟就编译好了。</p>
<p>从此编译再也不是等待,而是一个享受的过程,看到一个巨大的软件,却只需要不到一分钟就编译好了,非常大的软件也只要几分钟就可以了,这不是一种享受是什么?</p>
<p>i7 , 让 Gentoo 这种全编译的操作都变得非常快速。 i7 你是一个神话。</p>
<p>感谢 i7 , 你拯救了 Gentoo 。</p>
别了 AMD . 来了 Intel.
2011-10-25T00:00:00+00:00
https://microcai.org/2011/10/25/88amd
<p>刚开始接触到电脑的时候,正是 K8 无限风光, P4 高频低能的时候。那个时候起心里就有了 amd 的 U 便宜又好,低频高能的种子了。</p>
<p>到自己 DIY 的时候,就买了 AMD 的 U . NVIDIA 的芯片组。 那个时候扣肉还没有出, AMD 的 K8 余光依旧,K10 风头正好。AMD 率先推出双核,intel 还是伪双核, etc .</p>
<p>当时买的是入门双核 4200+ . 配合 MCP61S 的主板。外加 DDR2-800 1G*2 内存。</p>
<p>因为配置起来就是为了玩 Linux 的,所以特地买的 NVIDIA 显卡。 事实证明,所有的显卡问题统统都没有让我碰到。先见之明啊</p>
<p>这个配置就伴随着我3年,直到去年年底,一次刷主板不小心给刷成砖头了。悲剧。</p>
<p>买个新主板去咯~</p>
<p>结果,这一买新主板就几乎是大换血。那个时候 AMD 已经升级到了 AM3 接口的 U 了。而内存也已经主流 DDR3 了。</p>
<p>这次买主板,自然就干脆全换了。 U 升级到了 AMD x2 215 @2.7G , 内存加到 4G . 由于偏爱小机箱,MATX 主板可挑选余地依旧不多。还是 MCP61P 主板。 (MCP61 的生命力啊!)</p>
<p>时间已经是 2010 年底了!! 2011 马上就来了! 如果我知道那个时候 intel 已经凭借扣肉爱妻秒杀了 AMD ,我还会换 AM3 么? 打死也不会。而且再过几个月,彪悍的 Sandy Bridge 就要出生了。</p>
<p>可惜,我生活在了 AMD 编制成的幻觉里。不知道外面的世界其实早已经是 4 核8线程, 6核 12 线程的超高端平台也出来了。我还在守着 AMD 的双核,我还想着,4核时代什么时候来呢? 3年了! 3年的时间里,我双核原地不动,只不过 CPU 主频从 2.1G 升级到了 2.7G . 内存换到了 ddr3 而已。</p>
<p>悲剧的人生不解释。</p>
<p>等到自己工作了,接触到了更多的信息,我才恍然大悟。</p>
<p>原来现在最强的是 i7 . 6 核12线程。 super PI 10s 以内。彪悍的性能不解释。一般桌面高端使用的是 i7 2600k, 4核8线程,彪悍的性能不解释。</p>
<p>连 intel 入门级别的 i3 也是双核4线程。</p>
<p>原来酷睿又叫扣肉,就在我 DIY 了第一台 AMD 电脑不久后发布,性能震惊了全球。在我 的电脑第二次升级的后不久 intel 又发布了扣肉2,性能再次震撼全球。</p>
<p>AMD 早已不是对手。</p>
<p>其实我所谓的 2010 年末买的 AMD 双核,也就 intel 08 年发布的一代扣肉 i3 的性能而已。</p>
<p>我被 AMD 坑爹了。 悲剧的人生不需要解释。</p>
<p>我愤怒了,难怪我觉得我的电脑性能一直不好。不好到我要通过 Gentoo 来折磨自己获得那可怜的性能提升。就算我后来升级了电脑,性能也一样原地踏步。</p>
<p>AMD , 你骗的我好苦啊! 自从用了AMD , 我从此和摩尔定律拜拜了。</p>
<p>10月,就在推土机要发布的前一周,我果断抛弃了 AMD !</p>
<p>可悲的是,我的那个 215 居然涨价了,我卖二手只亏了 30 , 不能不说是个奇迹。AMD 创造的奇迹。</p>
<p>i7 2600 显然进入了我的法眼。因为其他配件都很齐全。所以我之需要买 CPU 和主板,这样 2k 的预算显然可以让我买高端而不再迁就于低端。</p>
<p>但是,频道里高人指点,我买了 Xeon E3-1230 . i5 的价格 i7 的性能。</p>
<p>高端的 U 自然要高端的主板配合,我买了华硕的 P8P67 主板,虎悍的做工不解释。</p>
<p>这样,算去卖掉的主板和CPU , 升级到 i7 平台花费不到 2.2k</p>
<p>从此Gentoo的编译给力起来啦! 编译不再是折磨! make -j8 开心的滚动, 89s 结束。要知道以前可是没个半小时编译不停啊。不仅如此,编译的时候桌面那是响应缓慢。</p>
<p>现在好了,编译的时候桌面依旧流畅。轻松操作。</p>
<p>原来这才叫 PC 。</p>
<p>再次 BS 一下 AMD , 那不给力的推土机直接让 intel CPU 涨价,我买 Xeon 的时候已经 1410 了,几个月前还只需要 13xx 就能买到的。</p>
给老婆的 F41M (注意后面有个M ... M M M M (都是回音)) 升级到 T9300 CPU 和 4G 内存
2011-10-24T00:00:00+00:00
https://microcai.org/2011/10/24/kiki
<p>上回说道,老婆大人换到 ubuntu 后突然觉得电脑好慢好慢 。。。 。。。 慢到蜗牛都吃到葡萄了这 Unity 界面还没显示出来。</p>
<p>嗯嗯,言归正传。</p>
<p>老婆大人的电脑是 08 年买的,好早(老)保护的比较好,外观还很新。不过,鲜丽的外表掩饰不了那落后的奔腾的芯。</p>
<p>废话不说,晒一下配置</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CPU: T2330
内存: 1G
硬盘: 160G@5400RPM@50-30MB/s
芯片组:GL960 (没错,就是那个坑爹的960)
</code></pre></div></div>
<p>这台老本的升级是在今年3月的时候开始的。
那个时候,我给自己的EeePC买了一个500G的希捷最快笔记本硬盘。淘汰下来的250G希捷硬盘自然就交给老婆的电脑进行消化咯。</p>
<p>电脑速度给力提升。windows 评分里硬盘速度已经是当中的最高分了。</p>
<p>之后我给 EeePC 换到 2G 内存,剩下的 1G 内存就给老婆用,组成了双通道。内存性能又提升了1分。</p>
<p>然后,然后就是上一篇博文里说的情况了,电脑太不给力了!</p>
<p>问得频道里老同志后,得知我应该去买 T7500 升级。</p>
<p>然后打算买T7500的时候,从论坛里搜得有人把 F41 升级到了 T9300 … … 楼下回复得知 GM965主板最高支持 T9500 CPU. (这个时候还不知道 F41M是阉割过的。)</p>
<p>哈哈!升级! 淘宝上找了个本地卖家买 T9500(他有 ES 版,特别便宜啊) . 结果没货,但是有 T9300 . 恩,也就差一点,也行。然后他就上门服务来了。</p>
<p>结果很悲剧。上了 T9300 , 能开机,进入系统很正常。也识别出了 CPU 的型号。可惜,正常了一分钟后,系统就出问题了。USB口失灵,无线没信号。屏幕莫名其妙的闪烁(Aero 消失又回来)。</p>
<p>什么情况??????</p>
<p>过热? 重新涂抹硅脂试试。</p>
<p>一样。</p>
<p>郁闷。</p>
<p>最后下了个 CPU-Z。</p>
<p>大跌眼镜啊!居然是 GL960 不是 GM965 ! 联想你个阉割狂!</p>
<p>此次升级以失败告终。</p>
<p>但是我不甘心。继续 google . 终于有人在论坛上发帖子,说自己把 F41M 的主板换成了 F41G,然后就顺利的上了T9300 的 U 。</p>
<p>好! 买F41G 主板去~</p>
<p>经过 N 次 taobao 后,终于选定了一家有 F41G 且愿意和我的 F41M 换的卖家。花了 200 大洋用老婆大人的 F41M 主板换到了 F41G 主板。</p>
<p>换主板折腾了1个多小时。费力也~ 这东西,组装费都不止 100 啊!</p>
<p>第一次开机比较期待,看到 BIOS LOGO 后,兴奋的大叫,WOW。芯片组也GM965 的。换主板的时候看到北桥上的字了。</p>
<p>然后再次联系那个CPU卖家。</p>
<p>最终换上了 T9300.</p>
<p>顺便把内存再次升级,换成了 DDR-800 2G*2</p>
<p>这下性能彪悍了。跑 Super PI 竟然进入了 18s 大关。果然给力!</p>
<p>这下就打起了那个老早就不工作的光驱的主意了。</p>
<p>嘻嘻,这次换成吸入式的 ^_^。</p>
<p>由于这些工作都是上个月的事情了,一直忙,也没记录,导致无法和大家图片分享咯。</p>
<p>sorry.</p>
原来 Linux 已经慢到这种地步了啊
2011-10-20T00:00:00+00:00
https://microcai.org/2011/10/20/linuxslow
<p>因为特别喜欢 Gentoo , 所以自从2年前的一次偶然,我用上了 Gentoo 后就在也没有换过系统。</p>
<p>Gentoo 的好处非常多,多到数不清。</p>
<p>连老婆大人的系统我都给折腾成了 Gentoo.</p>
<p>不过,由于疏于维护,老婆大人的 Gentoo 出了些许毛病,主要是 Gnome 3 太不稳定,太不给力,太容易崩溃。</p>
<p>老婆坚持下,换了 Ubuntu</p>
<p>一直以为老婆大人的破硬件不给力,所以进 Gentoo 都得花个30+s 。好慢。</p>
<p>用了 Ubuntu 我才知道,原来电脑可以这么慢!原来 Linux 已经慢到这种地步了! 居然要 1min多才能进系统。然后再卡个十几秒才能用。</p>
<p>!!!!!!!</p>
<p>不是 Gentoo 太慢,而是我对系统性能要求太高,而是硬件太不给力!</p>
<p>换硬件+切回Gentoo才是给力的选择。</p>
<p>于是我开始了艰难的折腾之路,把笔记本从 CPU 到主板到内存到光驱全换了个遍。请留意下回分解。</p>
从 RedFlag 到 Gentoo, 多少年,一瞬间
2011-07-20T00:00:00+00:00
https://microcai.org/2011/07/20/fromredflagtogentoo
<p>很久很久以前,在我还认为米国人生活在水生火热之中的时候,我认识了 Linux , 于是在买了电脑前就去亚马逊买了 Fedora 8 的DVD …</p>
<p>等我的电脑 DIY 好了,我立刻就安装了 Fedora 8</p>
<p>但是… 似乎连个 mp3 都播放不了。 就在我寻找如何解决的时候,我发现了 RedFlag 6。</p>
<p>于是,我试试看的态度安装上了 RedFlag, 呵呵,果然 mp3 啊,rmvb 什么的,都不是问题了。非常喜欢。而且自带的 KDE3 界面也非常 windows like .. 用的多熟悉啊!</p>
<p>就这样喜欢上了 Linux 桌面。</p>
<p>RedFlag 是那种大而全的发行版,把什么软件都往里面装。2个 CD 安装好后就和安装了番茄花园一样齐全了。 也不缺什么了。当然,如果什么软件没有带的话,还是要自己 做 ./configure 族的。</p>
<p>这就和盗版 Windows 很像了。基本系统很大,常用软件都有,但是,要别的 app , 自己装去。</p>
<p>不过 ,自己安装软件的时候比较少,所以我开始了半年的 RedFlag 征途</p>
<p>后来,买了个摄像头。</p>
<p>居然不知道用什么软件能使用摄像头 … 还好没几天 Fedora 10 发布了。恩,里面有个叫 cheese 的软件可以用摄像头。</p>
<p>RedFlag 里居然没有!</p>
<p>于是我又开始做 ./configure && make && make install 族了。</p>
<p>等等,这依赖 ….</p>
<p>RedFlag 所有的库版本都不够,导致我要安装cheese依赖的库先 ….</p>
<p>最后的结果是,一天时间过去了,我升级了一百多个库,还有一大堆依赖没安装上 …</p>
<p>我愤怒了!</p>
<p>这等于我重新编译了一个 Fedora 10 出来啊!</p>
<p>于是,直接上 Fedora !</p>
<p>不就是解码器的问题么? 我找教程看看,解决一下好了!</p>
<p>于是我开始了近一年的 Fedora 之旅。</p>
<p>这一年时间里,我的见识得到的飞速增长。原来米国人不是生活在水深火热之中 …..</p>
<p>我还通过合作开发 ruijieclient 结识了 gonghang 和 gsalex .. 对,就是提供本 blog 域名的 gsalex.</p>
<p>然后突然有一天, alex 用上了 Gentoo ….</p>
<p>我很震惊!我不能输!</p>
<p>于是我开始安装 Gentoo ,那个传说中最难安装的发行版。</p>
<p>其实很简单,只是大家把他想难了,呵呵。 凡事试过才知道啊!</p>
<p>于是使用 Gentoo 至今,已经快2年啦。</p>
<p>这2年的时间里,我从 Gentoo 小白到 Gentoo 非小白,到能为Gentoo编写ebuild, 成了Gentoo-china 的一分子,为 Gentoo 的发展出了一份微薄之力。</p>
<p>是什么让 Gentoo 自从赖上我的电脑就再也无法离开?</p>
<p>是什么让 Gentoo 自从赖上我的电脑就再也无法离开?</p>
<p>我不知道。</p>
<p>很多时候我开始寻找答案。</p>
<p>但是答案似乎每个人都不一样。</p>
<p>那么我到底是为的什么呢?</p>
<p>因为 Gentoo 真的太好用了!</p>
定期发布 Release 不适合现代 Linux Distro
2011-07-02T00:00:00+00:00
https://microcai.org/2011/07/02/rollingreleaseisbetter
<p>我想说,定期发布 Release 这种模式已经过时了!</p>
<p>想想看 ubuntu 的 LTS 版本是如何在生命周期结束前就过时的吧!</p>
<p>上一个 ubuntu TLS 版本,居然不包含 gtk3 , 如果要实用一点,基本上要添加一 大堆的 PPA.
而使用 PPA 基本上就和 TLS 的理念背道而驰了。</p>
<p>dabian 这样的发行版,基本上的发布前就过时了。</p>
<p>! 没错。 glibc, core-utils 这样的软件需要稳定!</p>
<p>那么用旧一点的版本确实是比较稳定.</p>
<p>问题是,gnome KDE 这样的软件,反而是新版本比较稳定。</p>
<p>这样 TLS 这种追求旧 版本的发行版,其实除了基础包,别的包都是在使用的不稳定版本,反而美其名 曰:stable.</p>
<p>最明显的例子, KDE 4.6 发布的时候,是 KDE 最稳定的时候,这个时候 debian 里刚刚有 KDE4 …. 最不稳定的 KDE 4 …. 恩,还美其名曰 : stable。
而 experimental 仓库里的 KDE 4.6, 恩, experimental, 其实才是最稳定的。
对于多数软件来说,大版本号最第二大,小版本号最大的软件才是最稳定的。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>比如说, Linux, 2.6.39 显然没有 2.6.39.2 来的稳定。虽然后者是后来出现的。
</code></pre></div></div>
<p>dabian 的哲学来说,就是不稳定的。 定期发布 Release , 意味着必须使用稳定版本,否则就等于发布 unstable , 这是不行的。
但是稳定版本,通常是比较新的版本,这样定期发布 Release , 意味着必须使用过时的版本。</p>
<p>恩,其实过时的版本包含了巨大的危险性。因为已经不再被 upstream 支持了。</p>
<h4 id="你说--upstream-支持的版本稳定还是不支持的版本稳定呢-只有使用-rolling-update-才能解决这个问题">你说 , upstream 支持的版本稳定还是不支持的版本稳定呢? 只有使用 rolling update 才能解决这个问题。</h4>
<p>你看, 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 多版本进去,任用户随意选择。</p>
<p>rolling update + selectable version, 这是 Gentoo 稳定性压倒一切发行版的 同时软件又够新的秘密。</p>
为什么你应该让你的项目使用 autotools 而不是别的编译系统
2011-05-26T00:00:00+00:00
https://microcai.org/2011/05/26/whyautotools
<p>编写软件离不开编译。</p>
<p>简单的编译,无外乎直接调用 gcc 去编译。</p>
<p>简单又不简单,当源文件不止一个的时候,麻烦的事情就随之而来。</p>
<p>当然,这个时候你可以选择 Makefile</p>
<p>但是,如果要求你的 Makefile 支持很多标准化操作,比如</p>
<p>make install DESTDIR=….</p>
<p>make dist-bzip2</p>
<p>你怎么打算呢?</p>
<p>自然,你需要一个自动创建 Makefile 的工具。</p>
<p>你有很多选择:</p>
<p>imake</p>
<p>qmake</p>
<p>scons</p>
<p>cmake</p>
<p>然而,他们都虽然简单,却不是最终用户友好的。</p>
<p>因为,最终的用户最习惯的软件编译方式就是三部曲</p>
<p>./configure –options</p>
<p>make</p>
<p>sudo make install</p>
<p>而开发人员也可以直接 make dist-bzip2 生成发布用的 tarball.</p>
<p>当然,如果有嵌入式的用户,他们自然会喜欢</p>
<p>./configure –host=arch-machine-os</p>
<p>进行交叉编译。</p>
<p>这是他们最最最熟悉的方式。</p>
<p>如果你想让你的用户能马上上手知道如何编译你的软件,那么,请使用 autotools</p>
<p>这是 autotools 带给你的最大的好处。别忘记,你的开发者,一开始也是用户,如果他们第一次就编译失败,他们很有可能因此走掉。</p>
<p>autotools 就是一把瑞士军刀,一开始就使用autotools,能让你的项目不会到最后被编译系统拖后腿。</p>
<p>记住, ebuild 文件的复杂度代表了一个软件的编译系统的友好程度。</p>
<p>而简单的 ebuild 文件只可能出现于使用 autotools 的软件中。</p>
<p>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.</p>
BIOS 是如何访问在内存还没有的时候检测并初始化内存的
2011-05-08T00:00:00+00:00
https://microcai.org/2011/05/08/howbiosdetectmemory
<p>首先必须明确的一点是, BIOS 运行初期,CPU 其实是不能访问内存的。</p>
<p>BIOS 所在的 FLASH 是那种可以被 CPU 直接寻址的 FLASH 芯片。被都固定在
0x4FFFF (记不清具体地址了) 地址上了。类似 ARM 使用的 NOR FLASH。 uboot
就在 NOR FLASH上。</p>
<p>然后,BIOS 初始化代码开始通过寄存器和北桥芯片沟通。
因为 BIOS 就是版卡厂商制作的,这不成问题。使用固化的 IO 地址就可以了。</p>
<p>成功和北桥联系上后, BIOS 继续前进,利用北桥提供的寄存器操控SMBUS, SMBUS
是一种 I2C 总线,每个 SMBUS 设备都有一个固定的地址。SDRAM 的 EEPROM 就可
以通过 SMBUS 访问! 而 SDRAM 通常在 0x50 ~ 0x53 这4个地址上,对于主板上
的4个内存插槽。不过,主板厂商也可以改变这些地址。反正 BIOS 是自己出的,
可以固化这些地址,无所谓。</p>
<p>读取 SDRAM eeprom 里超过 17 个参数后。 BIOS 就可以使用这些参数设置好北桥
的寄存器了。而在这之前, BIOS 严重依赖 CPU 的寄存器做这些操作,不能使用
任何内存! 北桥用来控制内存的寄存器地址,又是一个每个版卡都不一样的地方
了。无所谓, BIOS 自己出,爱怎么用就怎么用。</p>
<p>搞定内存之后, BIOS 终于可以使用内存啦! BIOS 将内存的前 4k 作为临时
stack 开始进入了第二步初始化,这个使用有了内存,就可以使用函数了。
前面,因为无法使用内存,无法进行复杂的运算,要小心编写汇编指令,全部使用
寄存器进行操作,所以没法进行复杂的运算。现在有 RAM 可以用了,自然就有内
存了。</p>
<p>BIOS 通过检查可用的内存值,将自己使用的临时 stack 移步到 > 640K 的地
方。 开始继续下面的工作。</p>
<p>下面, BIOS 该开始检测 CPU 了。主要的工作是比较 CPU 的微代码版本是否和
BIOS 里带的对应CPU的代码一致。否则更新 CPU 的微代码。所以刷新 BIOS 支持
新 CPU 的道理就在这里! ;)</p>
<p>再然后?那要看你是不是打开了 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 里。</p>
<p>然后干嘛呢? 自然是继续检测基本硬件啦。基本硬件一般都已经固定使用某个 IO
端口了。 BIOS 就是发送一个无害的请求看是否有返回就可以知道硬件在不在。</p>
<p>对于显卡和网卡这类设备, BIOS 并不包括这些设备的初始化代码, BIOS 通过调
用这些设备的 BIOS 来实现。</p>
<p>对于任何找到的 PCI 设备,BIOS 都是直接调用对应 BIOS 设备的 BIOS 来初始化
它的 。 这也是硬盘还原卡截获 init 13 中断的道理。</p>
<p>而这些设备的 BIOS,自然初始化工作就是配置设备的寄存器地址了。</p>
<p>BIOS 搞定的 PCI 设备,会形成一个表格,叫 PCI CONFIG ,保存到 PCI 控制器
里 。可以通过 IO 端口 0xCFC-0xCFF 访问。</p>
<p>对于集成设备,这些设备的 BIOS 其实都被包含在 system BIOS 里,但是逻辑上
并不是一个整体。如果用一些 BIOS 工具,还是可以分离各个设备的 BIOS 的。</p>
<p>没用 BIOS 将设备配置好,并写入 PCI config , Linux 内核就会当这个设备不
存在 … 囧。</p>
<p>还有,各个 PCI 设备除了几个基本寄存器,别的都是通过 MMIO 映射到内存地址
里进行的,而不是使用 IO 端口地址。BIOS 要负责分配好,使他们不会重叠。</p>
<p>PCI 设备的地址从 0xFFFFFFFF- sizeof (BIOS) 开始向下分配,SDRAM 的地址从
0x00000000 开始向上分配。这些都是通过操作北桥的寄存器实现的。</p>
<p>自然,他们会在中间形成一个洞。不过,那是在你 SDRAM 内存比较少的情况下 …..</p>
<p>如果你的 SDRAM 内存比较多,恭喜你,你又遇到问题了,你的内存将被吞掉一部
分 …. 被 PCI 设备覆盖掉了一部分高位内存… 对 CPU 而言,整个内存地址都
充满了设备咯,没有空洞。</p>
<p>其实 Linux 认为的 640k~1MB 空洞也是不存在的。如果后续不使用 BIOS , 可
以放心的当 SDRAM 来用,因为就是 SDRAM . 自然,如果你没有开启 BIOS shadow
就另当别论了。</p>
<p>我之所以接触 Linux , 也是我本来计划自己写系统,我很讨厌 640K~1M 的内存
空洞,想知道怎么解决,就去看别的 OS 是怎么实现的,结果就发现了 Linux ,
然后开始看 ….</p>
<p>不过后来比较囧的是, Linux 确实没有利用这个区间的 RAM … 真 TMD 有点浪
费啊! 现代的 BIOS 都默认 shadow 了,甚至没有地方给你配置了。 如果 Linux
不使用 BIOS , 不知道留这个洞在这里干嘛. 诶。
其实我 google 了 lkml , 是有人这么提出过这个问题。不过那个 Grek H 什么
的,就是貌似的Linux二号or三号人物,回的邮件里说,虽然多数系统可以回收这
个空洞,不过在个别系统上报告问题了。所以就默认不回收了。
如果你要回收,这里有 patch … use at your own risk ….</p>
<p>呵呵。</p>
<p>扯远了。洗洗睡吧。明天估计开机会看到一堆回复了。</p>
chroot to arm on an x86 Gentoo
2011-04-26T00:00:00+00:00
https://microcai.org/2011/04/26/chrootarm
<p>在早上的邮件中,我指出 Gentoo 是如何方便的构筑交叉环境的。
现在,我要指出,我还要运行里面的arm程序! 在 chroot 环境中,真正的把它当
作一个发行版!</p>
<p>我使用的是 crossdev -t arm-unknow-linux-gnueabi 编译的 arm 交叉工具链。
这时候 arm 其实被安装到了 /usr/arm-unknow-linux-gnueabi/</p>
<p>/usr/arm-unknow-linux-gnueabi/ 下面有完整目录结构,相当于一个 arm 发行版。</p>
<p>而且之后也会多了一个工具叫 arm-unknow-linux-gnueabi-emerge</p>
<p>我们的第一个主角就出来了。我们需要 busybox</p>
<p>USE=”-ipv6 static -pam make-symlinks” <br />
arm-unknow-linux-gnueabi-emerge busybox -av</p>
<p>之后我们的 /usr/arm-unknow-linux-gnueabi/ 其实已经可以作为一个基本完整的
arm 系统的根目录了。</p>
<p>我们需要第二个主角,一个解释器。qemu-user !</p>
<p>export QEMU_USER_TARGETS=”arm”
export USE=”-* static”
emerge qemu -av –root=/usr/arm-unknow-linux-gnueabi/ -O</p>
<p>这样 /usr/arm-unknow-linux-gnueabi/usr/bin/qemu-arm 就成为这个 arm 系统
的解释器了,注意,静态链接是必须的 wink.gif</p>
<p>接着,我们需要内核的支持
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:’</p>
<blockquote>
<blockquote>
<p>/proc/sys/fs/binfmt_misc/register</p>
</blockquote>
</blockquote>
<p>当然,要需要第三个主角,一个 bash wink.gif
arm-unknow-linux-gnueabi-emerge bash -av</p>
<p>好了,准备工作就完成了。</p>
<p>chroot /usr/arm-unknow-linux-gnueabi/ /bin/bash</p>
<p>呵呵。 arm 结构的 bash 已经被运作起来咯 wink.gif</p>
<p>怎么样?
试试执行 ls wink.gif 哈哈</p>
<p>Good luck to every one</p>
2.6.38.4 内核对 tq2440 支持补丁
2011-04-22T00:00:00+00:00
https://microcai.org/2011/04/22/patchfortq2440
<p>最近折腾了很久,总算搞定了。</p>
<p>发个补丁,希望大家不要再折腾了,而且也不要看网上的所谓垃圾移植指南了,都是 bullshit.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> From 144759a9929f0858398b834a5cbfdb79ce56f077 Mon Sep 17 00:00:00 2001
From: microcai <microcai@fedoraproject.org>
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're using HP iPAQ rx1950
+config MACH_TQ2440
+ bool "TQ 2440"
+ 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 "LCD size"
+ default FB_S3C24X0_LCD480272
+ depends on FB_S3C2410 && MACH_TQ2440
+
+config FB_S3C24X0_LCD480272
+ bool "4.3in LCD"
+ help
+ Support 4.3in LCD
+
+
+config FB_S3C24X0_TFT640480
+ bool "640x480 LCD"
+
+
+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 <ben@simtec.co.uk>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/list.h>
+#include <linux/timer.h>
+#include <linux/init.h>
+#include <linux/serial_core.h>
+#include <linux/platform_device.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/nand_ecc.h>
+#include <linux/mtd/partitions.h>
+#include <linux/i2c/at24.h>
+#include <linux/i2c.h>
+#include <linux/io.h>
+#include <linux/input.h>
+#include <linux/dm9000.h>
+#include <linux/gpio_keys.h>
+#include <linux/mmc/host.h>
+#include <sound/s3c24xx_uda134x.h>
+
+#include <asm/mach/arch.h>
+#include <asm/mach/map.h>
+#include <asm/mach/irq.h>
+
+#include <mach/hardware.h>
+#include <asm/irq.h>
+#include <asm/mach-types.h>
+
+#include <plat/regs-serial.h>
+#include <plat/regs-adc.h>
+#include <mach/regs-mem.h>
+#include <plat/iic.h>
+#include <plat/nand.h>
+#include <plat/s3c2410.h>
+#include <plat/s3c244x.h>
+#include <plat/clock.h>
+#include <plat/devs.h>
+#include <plat/cpu.h>
+#include <plat/udc.h>
+#include <plat/pm.h>
+#include <plat/ts.h>
+#include <plat/mci.h>
+
+#include <mach/fb.h>
+#include <mach/gpio-fns.h>
+#include <mach/regs-gpio.h>
+#include <mach/regs-lcd.h>
+#include <mach/leds-gpio.h>
+#include <mach/idle.h>
+
+
+/*NAND*/
+
+static struct mtd_partition smdk_default_nand_part[] = {
+
+ [0] = {
+
+ .name = "uboot",
+
+ .offset = 0x00000000,
+ .size = SZ_2M,
+
+ },
+ [1] = {
+
+ .name = "kernel",
+ .offset = SZ_2M,
+ .size = SZ_2M + SZ_1M,
+ },
+
+ [2] = {
+ .name = "yaffs2",
+ .offset = SZ_4M+SZ_1M,
+ .size = MTDPART_SIZ_FULL,
+ }
+};
+
+static struct s3c2410_nand_set smdk_nand_sets[] = {
+ [0] = {
+ .disable_ecc = 1, // 在这里设置才是正确的
+ .name = "NAND",
+ .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<<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<<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 = &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 = "",
+ };
+
+static struct platform_device s3c_device_dm9k = {
+ .name = "dm9000",
+ .id = 0,
+ .num_resources = ARRAY_SIZE(s3c_dm9000_resource),
+ .resource = s3c_dm9000_resource,
+ .dev = {
+ .platform_data = &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 = "UP",
+ .active_low = 1,
+ },
+ {
+ .gpio = S3C2410_GPF(4), /* K2 */
+ .code = KEY_DOWN,
+ .desc = "DOWN",
+ .active_low = 1,
+ },
+ {
+ .gpio = S3C2410_GPF(2), /* K3 */
+ .code = KEY_LEFT,
+ .desc = "LEFT",
+ .active_low = 1,
+ },
+ {
+ .gpio = S3C2410_GPF(0), /* K4 */
+ .code = KEY_RIGHT,
+ .desc = "RIGHT",
+ .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 = "gpio-keys",
+ .id = -1,
+ .dev = {
+ .platform_data = &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 = "led4",
+ .def_trigger = "timer",
+};
+
+static struct s3c24xx_led_platdata smdk_pdata_led5 = {
+ .gpio = S3C2410_GPB(6),
+ .flags = S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,
+ .name = "led5",
+ .def_trigger = "nand-disk",
+};
+
+static struct s3c24xx_led_platdata smdk_pdata_led6 = {
+ .gpio = S3C2410_GPB(7),
+ .flags = S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,
+ .name = "led6",
+};
+
+static struct s3c24xx_led_platdata smdk_pdata_led7 = {
+ .gpio = S3C2410_GPB(8),
+ .flags = S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,
+ .name = "led7",
+};
+
+static struct platform_device smdk_led4 = {
+ .name = "s3c24xx_led",
+ .id = 0,
+ .dev = {
+ .platform_data = &smdk_pdata_led4,
+ },
+};
+
+static struct platform_device smdk_led5 = {
+ .name = "s3c24xx_led",
+ .id = 1,
+ .dev = {
+ .platform_data = &smdk_pdata_led5,
+ },
+};
+
+static struct platform_device smdk_led6 = {
+ .name = "s3c24xx_led",
+ .id = 2,
+ .dev = {
+ .platform_data = &smdk_pdata_led6,
+ },
+};
+
+static struct platform_device smdk_led7 = {
+ .name = "s3c24xx_led",
+ .id = 3,
+ .dev = {
+ .platform_data = &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 = "s3c24xx_uda134x",
+ .id = 0,
+ .dev = {
+ .platform_data = &tq2440_audio_pins,
+ },
+};
+
+static struct platform_device uda1340_codec = {
+ .name = "uda134x-codec",
+};
+
+/* 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("24c08", 0x50),
+ .platform_data = &at24c08,
+ },
+};
+
+
+static struct platform_device *tq2440_devices[] = {
+ &s3c_device_adc,
+ &s3c_device_ts,
+ &s3c_device_ohci, // s3c24xx_ohci
+ &s3c_device_wdt, // 看门狗狗
+ &s3c_device_lcd,
+ &s3c_device_i2c0,
+ &s3c_device_iis,
+ &s3c_device_nand, // MTD NAND 分区表
+ &s3c_device_dm9k, // 特别的DM9000网卡芯片
+ &s3c_device_rtc,
+ &mini2440_button_device,
+ &s3c_device_sdi, // SD 卡读卡器
+ &s3c_device_usbgadget,
+ &uda1340_codec,
+ &tq2440_audio, // ALSA 声卡
+ &samsung_asoc_dma,
+ &smdk_led4,
+ &smdk_led5,
+ &smdk_led6,
+ &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(&tq2440_fb_info);
+ s3c_i2c0_set_platdata(NULL);
+
+ s3c2410_gpio_cfgpin(S3C2410_GPG(8),S3C2410_GPG8_EINT16);
+
+ s3c_nand_set_platdata(&smdk_nand_info);
+ s3c24xx_mci_set_platdata(&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 = &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 < 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(&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(&udc_machine);
+}
+
+#define MACH_TYPE_168 168
+/*For some reason , my machine is ID 168*/
+MACHINE_START(168, "TQ2440-168")
+ .boot_params = S3C2410_SDRAM_PA + 0x100,
+ .init_irq = s3c24xx_init_irq,
+ .map_io = tq2440_map_io,
+ .init_machine = tq2440_machine_init,
+ .timer = &s3c24xx_timer,
+MACHINE_END
+
+MACHINE_START(TQ2440, "TQ2440")
+ .boot_params = S3C2410_SDRAM_PA + 0x100,
+ .init_irq = s3c24xx_init_irq,
+ .map_io = tq2440_map_io,
+ .init_machine = tq2440_machine_init,
+ .timer = &s3c24xx_timer,
+MACHINE_END
--
1.7.5.rc3
From 119021354fa6001311db9f1df5d3ea8061a35f9b Mon Sep 17 00:00:00 2001
From: microcai <microcai@fedoraproject.org>
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->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->name = "S3C24XX TouchScreen";
ts.input->id.bustype = BUS_HOST;
--
1.7.5.rc3
From 4684013358b6edf0e8a790ef32de13a236cf7987 Mon Sep 17 00:00:00 2001
From: microcai <microcai@fedoraproject.org>
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(&touch_timer, jiffies+1);
writel(WAIT4INT | INT_UP, ts.io + S3C2410_ADCTSC);
--
1.7.5.rc3
</code></pre></div></div>
GPG 签名与加密
2011-04-22T00:00:00+00:00
https://microcai.org/2011/04/22/gpg
<p>首先密码学的模型</p>
<p>1) 通信双方的机器是没有问题的。没有木马,没有坏人。
2) 通信线路是不可靠的,有人监听,有人伪造信息</p>
<p>那么,就要解决2个问题,首先,我得确信这个信息是你发送的,不是别人伪造你
发送的,俗称数字签名, 第二,我要确保没有人知道我们之间的通信,也就是加密。</p>
<p>非对称加密的意思就是,同时有2个key. 用其中一个加密,只能用另一个解密。
这样,随机选一个做私钥,另一个公开做公钥就是下面我要讲的 GPG 签名与加密。</p>
<p>对于 GPG 来说,GPG 会生成一个密钥对,一个是公钥,一个是私钥。</p>
<p>对于公钥,你需要将它上传到公钥服务器。
对于私钥,要绝对反正泄露,并且不能丢失!!!! 我重复,不能丢失,也不能泄露。</p>
<p>如果你要让别人相信你的邮件不是伪造的,那么你就需要进行签名。
签名就是用自己的私钥加密邮件。因为公钥是公开的,所以任何人都可以解密你的
邮件。但是只有用你的私钥才能发出那封邮件,所以别人可以肯定这是你的邮件,
不是别人伪造的。这就是用私钥进行签名的原因。</p>
<p>如果是发送加密邮件,那么你必须有对方的 GPG 公钥。
然后你用 <em>对方</em> 的 <em>公钥</em> 加密邮件。</p>
<p>因为这个时间上只有收件人有加密时使用的公钥的对应私钥,所以只有你的收件人
能解密这个邮件. 这样你就保证了你的邮件只有他能看到。PS, 你自己也不能解
密那个邮件。因为你没有他的私钥。</p>
<p>加密和签名同时使用的时候,就可以保证</p>
<p>1) 他可以确信邮件是你发送的
2) 你可以确信只有他能看那封邮件</p>
<p>=====================================
但是,这个前提是,你怎么知道/确信指定的公钥就是他的? 他怎么知道/确信某
个密钥就是你的!!!!</p>
<p>这就引入了一个 信任机制。</p>
<p>比如 A B C</p>
<p>A 通过见面得知了 B 的公钥。这个时候 A 和 B 可以相互100%确认对方的公钥。</p>
<p>这个时候来了个 C . C 自称他的公钥是 XXXX 。</p>
<p>C 和 A 见过面。交换过公钥。</p>
<p>然后 A 就给 C 的公钥签名,</p>
<p>B 收到 C 的邮件的时候,发现他的公钥被 A 签名了,B 就可以不需要亲自去核
实,就可以确定 XXXX 确实是 C 的公钥.</p>
<p>其实和 SSL 的证书机制差不多的啦。
我的 GPG 公钥是</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>E84C0359 ,
</code></pre></div></div>
<p>指纹是</p>
<p>85D9 2529 F2D6 EE19 CBEB A685 A4F8 533E E84C 0359</p>
<p>请记住哦 ;)</p>
两种 Mem remap
2011-04-22T00:00:00+00:00
https://microcai.org/2011/04/22/4gmem
<p>安装4G内存操作系统却只报告 3.2G 是4G时代困扰我们的一个问题。</p>
<p>解决办法需要3个条件齐备,缺一不可</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. 内存控制器支持 > 4G 寻址
2. 操作系统支持 PAE 或者直接64位系统
3. BIOS 支持 mem remap
</code></pre></div></div>
<p>** > 4G 寻址的内存控制器,intel 平台的话要求至少是 945 以上的芯片组。
AMD 平台的话,内存控制器内置于 CPU, 只要是支持 64 位的 AMD CPU 就可以了。</p>
<p>操作系统吧,不多解释。 32位的windows 使用 PAE 内核,或者64位 windows.
Linux 也一样。
这里要注意的是,开了 PAE 的 32位 windows 7 也不能识别 > 4G 的内存 …… ,可以使用 ReadyFor4G 这个小工具修改内核。</p>
<p>这里是本文的重点。</p>
<p>mem remap 就是重新布局 PCI 设备地址和 DRAM 地址。使原本要重叠的地址错开,分布到 > 4G 的地址空间上。
这样才能使安装的 4G DRAM 可以被全部寻址。</p>
<p>其中,mem remap 分2种方式</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. 4G DRAM 获得连续的地址空间,PCI 设备地址被放置于 4G 地址之上
2. 4G DRAM 内存被分割。 PCI 设备地址被放置于 4G 以内,其余的 RAM 被放置到 4G 之上
</code></pre></div></div>
<p>对于第一种方式最简单,最容易理解。 但是却有潜在的兼容性问题。
第一 许多 PCI 设备在设计的时候并不能寻址 > 4G 的内存,这样在进行 DMA 的时候非常容易出问题。 如果主板粗暴的将 PCI 地址放到 4G 后面,那么很多设计不良的设备就罢工了。
第二 32bit 的操作系统在此选项下将无法正常工作。因为 PCI 设备的地址都跑到 4G 外面去了。</p>
<p>第二种方式,给内存地址引入了一个“洞”。 对 32bit OS 和老旧的 PCI 设备提供了良好的兼容。
但是对内存引入的”洞” 确实给内存性能带来了潜在的影响。虽然通过 MTFR 情况还不会太坏。
但是,“洞” 怎么也不让人舒服啊!</p>
为何我们不能回到过去
2011-04-15T00:00:00+00:00
https://microcai.org/2011/04/15/timetravel
<p>我们能回到过去么?</p>
<p>答案是:不能。</p>
<p>单个原子能回到过去么?</p>
<p>答案是,可能。</p>
<p>lol</p>
<p>自从爱因斯坦否决了牛顿的时空观后,很多人就开始在理论上研究时空旅行。
时间是什么? 时间由运动产生,没有运动,就没有时间。如果你周围的一切一切都变慢一倍,包括你自己的新陈代谢,那么,你还能知道时间变慢了么? 决定不能。
时间就是相对的。想到这就很容易理解了。
于是,既然时间是相对的,能回到过去么?</p>
<p>不能。时间在流逝,流逝的速率可以被运行、质量影响,但是,它在流逝,这是无法避免的。</p>
<p>时间也符合熵增加原理。</p>
<p>我重复,</p>
<p>时间也符合熵增加原理。time is entropy</p>
<p>时间也是熵的一种。</p>
Size is not everything
2011-03-15T00:00:00+00:00
https://microcai.org/2011/03/15/sizeofnoteverything
<p>我们买笔记本的时候,往往笔记本只标示硬盘容量,却往往不标示他所使用的硬盘的牌子和速度。</p>
<p>我们知道,硬盘的速度是一台电脑整体速度的瓶颈。它慢了,不管容量再大,电脑都快不起来。</p>
<p>希捷 7200转 500G 带 16MB 缓存的硬盘,接口速度 300MB/s , 突发数据传输率可达 160MB/s ,持续传输率平均能达到 80MB/s , 一个要卖 超过 500</p>
<p>同样容量的日立硬盘,5400转 8MB 缓存,接口速率都只是 150MB/s ,持续传输率不超过 60MB/s , 一个卖不到 300</p>
<p>看到这猫腻了吧?!</p>
<p>别光看容量哦 !</p>
gnome 的对与错
2011-03-15T00:00:00+00:00
https://microcai.org/2011/03/15/gnomefault
<p>gnome-shell 也用了许久了。从一开始的新鲜到习惯, 再到离不开它。</p>
<p>等新鲜感过去的时候,我发现我越来越不能忍受 gnome-shell 那容易崩溃的毛病了。</p>
<p>在没升级 gnome 其他部件的时候, gnome-shell 崩溃总是会被自动重启。</p>
<p>后来 , gnome-shell 崩溃虽然也会被自动重启,可是 … 如果连续崩溃两次间隔很短,则愚蠢的 gnome-session 就会断定 gnome-shell 出了问题,逼我注销 …</p>
<p>有这样子的么? 我还有工作没保存 … 还有聊天窗口在和人讨论呢!</p>
<p>终于忍受不了了,我决定暂时离开 gnome</p>
<p>决定用 KDE 了。</p>
<p>KDE,很早以前用 红旗的时候用的。 诶,现在想起来 … 用红旗的日子哦 ~~~</p>
OSS4 deprecated ALSA and PulseAudio
2011-02-15T00:00:00+00:00
https://microcai.org/2011/02/15/oss4fuckofalsaandpa
<p>Turst me , OSS4 = ALSA + PulseAudio , and all implemented in kernel. very low latency.</p>
<p>OSS4 have kernel level mix feature which ALSA lack for years and that’s why PuleAudio fucked to burn.</p>
<p>OSS4 also have per-process volume control which ALSA lack for years and that’s why PuleAudio fucked to burn.</p>
<p>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.</p>
<p>But when OSS4 came back. NO app need to re-write. OSS4 works as a drop in replacement!
Great!!!</p>
<p>And, OSS4 is pure kernel side. OSS4 don’t need assistance from user-land.</p>
<p>So, pleace deprecated ALSA and PA immediately and include OSS4.
And ALSA foundation rename to OSS-ng foundation and sopport OSS4.</p>
force AHCI without BIOS
2011-02-02T00:00:00+00:00
https://microcai.org/2011/02/02/forceahci
<p>For long time, I’ve been using IDE mode for SATA.
长久以来,我一直在用SATA的IDE模式。</p>
<p>but, SATA-II ‘s 300MB/s transfer rate and NCQ features are missing .
但是 ,SATA-II 的 300MB/s 速度和 NCQ 功能都用不上了。</p>
<p>So I decide to use AHCI.
于是我打定主意用 AHCI 了。</p>
<p>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 功能。</p>
<p>I googled a lot, but only one that try to modify NVRAM directly and then AHCI is enabled.
我搜了很多,只有一个人直接修改 NVRAM 启用了 AHCI.</p>
<p>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</p>
<p>But, I tryed but result as machine panic.
我用一下,死机了。</p>
<p>Googling , and found that grub2 also has setpci . What a hope!
继续搜发现grub2 也有 setpci 命令?。希望来了么?</p>
<p>recompile a kernel with AHCI only , and use lspci to remember pci address of my ICH8 SATA controller.
重新编译内核,只启用 AHCI 驱动,使用 lspci 记下 SATA 控制器的 PCI 地址。</p>
<p>reboot, and enter grub command line.
重启到 grub 命令模式。</p>
<p>after excute setpci -d 8086:2828 90.b=40, machine hang
执行 setpci -d 8086:2828 90.b=40 后死机。</p>
<p>reboot, and enter grub command line.
重启到 grub 命令模式。</p>
<p>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 , 机器成功启动,桌面出现。</p>
<table>
<tbody>
<tr>
<td>dmesg</td>
<td>grep NCQ</td>
</tr>
<tr>
<td>dmesg</td>
<td>grep NCQ</td>
</tr>
</tbody>
</table>
<p>YES!, NCQ (31/32) , no longer NCQ (0/32) , NCQ was enabled! AHCI was enabled!
是的 ,出现 NCQ (31/32) , 而不再是 NCQ (0/32), NCQ 启用了! AHCI 启用啦!</p>
in-kernel UNICODE font support for fbcon
2011-01-01T00:00:00+00:00
https://microcai.org/2011/01/01/cjktty
<p>I first got this patch from 内核补丁:字符终端显示UTF-8字符串</p>
<p>but , there is some problems ….</p>
<p>1) why not extending kernel VGA font to support UNICODE?</p>
<p>2) why so many duplicated code ? too many copy&past, Linus
won’t take this patch</p>
<p>3) Why all char > 127 should be double-weight ?</p>
<p>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.</p>
<p>so ,here is the git repo</p>
<p>webgit view
http://repo.or.cz/w/linux-2.6/cjktty.git</p>
<p>git clone URL :
git://repo.or.cz/linux-2.6/cjktty.git
http://repo.or.cz/r/linux-2.6/cjktty.git</p>
内核TTY控制台的问题
2010-12-28T00:00:00+00:00
https://microcai.org/2010/12/28/ttyproblem
<p>最近因为编写TTY控制台的UNICODE字体支持补丁,拜读了控制台的代码,诶,妈呀,这个是什么代码哟 ……</p>
<p>TTY 控制台最开始的时候是使用 VGA 显卡的 TEXT模式的。 VGA TEXT 模式有个非常特别的地方就是,显存被映射到了一个固定的位置, 大约接近 1MB 的下方。 两个字节一对。第一个字节指示下一个字节的属性。第二个字节指示一个字符 index. 而这个字符表包含了 255 个字符,在 Windows 中, 这个字符集被称做 OEM codepage, 前127个字符和 ASCII 是一样的. 这样只要向对应的地址写入字符属性和字符的代码,就可以在对应的屏幕位置显示出对于的文字。这TTY 控制台就依据 VGA 字符模式写开了。每个 tty 终端都对应一个结构体。</p>
<p>这个结构体里有一个 vc_screenbuf 的成员,里面就是按照 VGA text 模式保存的整个屏幕的字符。切换终端的时候,就是一个 memcpy 把这个地方的字符拷贝到显存里,就完成了终端显示的切换。</p>
<p>问题就出在这里。vc_screenbuf 完全是依据 VGA Text 模式设置的。哪里有地方给你保存 UNICODE 编码啊,只有 255 个字符哦 ……</p>
<p>直接改成 32bit/32bit pair 不就行了?</p>
<p>NO~NO~NO~NO~</p>
<p>不行! 因为完全和 TTY 无关的别的代码都已经按照现行的结构来写了,这样一改就会大动干戈, 改掉很多很多不是控制台部分的代码。而且,控制台其他部分的代码,也都是直接按照这种格式去操作的,完全没有用宏去封装,改起来难度非常大。</p>
<p>我曾经花了2天时间去做这种改动。修改了超过一千行代码 … 结果 … 还是改不动。
太多地方已经很顽固的假设这种布局了。</p>
<p>那么,把 字符和属性分开? textbuf 和 attrbuf ? 其实就相当于重写。
我也花了2天时间这么搞 .. 结果 tty 部分的代码终于改好了,结果发现内核的很多其他部分也要修改 … …</p>
<p>什么叫牵一发而动全身?这就是。</p>
<p>最后,我只得在老代码上缝缝补补,做了点小聪明似的 hack 来保证中文显示。</p>
<p>但是,我知道,这样的 hack 是绝对进不了官方内核的。我必须想想的办法,改掉这种被动的局面。 尽量低耦合。做到漂亮的 clean up。这样内核开发者才能接受。</p>
内核要集成glibc么
2010-12-24T00:00:00+00:00
https://microcai.org/2010/12/24/libcinkernel
<p>往往内核添加了一个功能, glibc 要花很久才会用上。本来linux 那边为这个功能是否进入内核已经吵半天了,glibc
这边又要为是否使用这个内核新特性再次吵架半天 (glibc 不是 Linux 专有的,还得考虑 BSD (虽然人家也不用 glibc)
SysV Windows(诶,这没办法) , 还有 sun 那消亡的 *** , 还有, 自家的 Hurd wink.gif
然后,总之,这样新特性让人的接受上。。。 太慢了。</p>
<p>说近点的,fnotify glibc还没有对应的包装函数呢,futex 和 NPTL 又是花了许久才进入主流的。libc 是 app
和内核的桥梁,libc 理应快速跟上内核的接口变化 .. 但是 … … glibc 和 内核不是一块开发的,所以,这只是理想罢了。
glibc 还要去兼容不同版本的内核呢!
而内核也要去兼容不同版本的 glibc . 双方都背负了太多的历史包袱。glibc 至今保留 LinuxThreads
兼容2.4版本的古老内核。Linux
对已经没用,甚至有bug(接口的问题导致一些bug是必须的)的系统调用也必须保留,随知道用户会用哪个版本的glibc呢?虽然新的glibc
会使用新的调用,但是提供和老的调用一致的 API 来兼容,但是,用户只升级内核而不升级 glibc 是常有的事情. .. 就算升级了
glibc … 你新版本的 glibc 一定就用上内核的新接口?!?!?!?! 还是再等几年等 glibc 的开发者吵架结束吧</p>
<p>于是乎,Linux 的大牛们再次使出绝招: 让 libc 变成 VDSO 进驻内核。</p>
<p>{
这里普及一下 VDSO 这个小知识,知道的人跳过,不知道的人读一下 biggrin.gif
VDSO 就是 Virtual Dynamic Shared Object … 就是内核提供的虚拟的 .so , 这个 .so
文件不在磁盘上,而是在内核里头。
内核把包含某 .so 的内存页在程序启动的时候映射入其内存空间,对应的程序就可以当普通的 .so 来使用里头的函数。比如 syscall()
这个函数就是在 linux-vdso.so.1 里头的,但是磁盘上并没有对应的文件. 可以通过 ldd /bin/bash 看看
}</p>
<p>这样,随内核发行的 libc (注意,VDSO只是随内核发行,没有在内核空间运行,这个不会导致内核膨胀。)
就唯一的和一个特定版本的内核绑定到一起了。这样内核和libc都不需要为兼容多个不同版本的对方而写太多的代码 … 引入太多的 bug 了</p>
<p>当然, libc 不当当有到内核的接口,还有很多常用的函数,这些函数不需要
特别的为不同版本的内核小心编写,所以,我估计Linux上会出现两个 libc , 一个 libc 在内核,只是系统调用的包裹,另一个
libc 还是普通的 libc , 只是这个 libc 再也不需要花精力去配合如此繁多的 kernel 了 …..</p>
<p>姑且一个叫 klibc, 一个叫 glibc :
… printf() 这些的还在 glibc 。 open() , read() , write(), socket()
这些却不再是 glibc 的了,他们在 klibc 。</p>
遗忘的语言
2010-12-21T00:00:00+00:00
https://microcai.org/2010/12/21/forgottenLANG
<p>WEB , 没错,我指的就是 WEB . 但是不是那个用 浏览器堆积起来的 WEB , 而是大师高爷爷随 TeX 推出的 WEB 编程语言。</p>
<p>WEB 编程语言,让编程顺着人的思路,而不是机器的思路。</p>
<p>最近为了写论文,也让我有更多的时候去接触 TeX . 突然很想知道现在用的 TeX 是不是高教授写的,还是后人重新实现的 … 然后去搜寻 TeX 源码 ,终于被我发现, TeX 其实是用 WEB 语言完成的。经处理后变成 PASCAL 代码 + TeX 文档。 PASCAL 代码经 web2c 翻译为 现代 C 程序,然后就可以使用 gcc 编译了。所以,现代TeX发行版里包含的 tex 程序,确实是高教授写的。大师的写程序,居然都是用的不一样的语言啊!</p>
<p>粗看 WEB 文件,发现除了描述看不到多少代码。几乎都是文档。其实就是思路。 WEB 程序保留了人的思路。完整的保留了人的思维。整个程序的结构也几乎就是安装人的思维去编写的。</p>
<p>WEB 语言也不限于 PASCAL, 现在有了 CWEB , 就是把 C 结合进 WEB 语言中。GOOD, 有空了试试去。</p>
北京之旅
2010-12-20T00:00:00+00:00
https://microcai.org/2010/12/20/tobeijing
<h1 id="之所以要去北京是因为参加红帽的笔试--废话大家都知道了-讲讲这段五味瓶的日子吧">之所以要去北京,是因为参加红帽的笔试 (废话,大家都知道了) ,讲讲这段五味瓶的日子吧</h1>
<p>恩,早上起来打开电脑 … 发现显示器开不起来 … 开开关关多次,亮了 … 以为升级系统导致的显卡驱动问题 …..
算了,现在没空,能用就行 …</p>
<p>然后挂 IRC , 继续调式内核 … ..</p>
<p>恩恩,不知道怎么回事,IRC 聊起来红帽的招聘 … kaio 说某人等了7个月 … 我说我都等3个月了 … kaio
问我面试了? 我说没 .. 我等笔试都等3个月了 …( 当时不知道 alexcpio 说的 下月4号的有米有我的份呢) kaio
说,人家是面试后等了7个月 …… 我 囧 ….
然后正要说什么,突然手机dongdongdong …. wow. 说曹操曹操到,激动啊 … …</p>
<p>接到通知赶紧买票,一查,火车票比飞机票还贵,情何以堪情何以堪啊 … .. 于是就买飞机票去了… …
诶,都是半辈子的积蓄啊,顺便 BS 一下红帽,就不能来杭州招聘啊。</p>
<p>这行是解决了,住宿呢?旅馆?我过去怎么也得待个四五天的,几百一晚,我不成乞丐了啊….
恩,以前听说某人发光是 twitter 结果就免费周游世界去了(顺便 BS 一下这家伙)… …
恩,说不定好多童鞋在北京的 … 兴许能帮忙找个便宜的旅店,抱着试一试的态度发贴求助。
回应的人好多啊 … 而且给于了我不曾想到的关注和帮助, 让我一个一直被人忽略的人激动得热泪盈眶 .. .. …
还有童鞋提议乘我去北京,再聚会一次… 我内牛满面啊… …
最后duyue说我可以住他宿舍… … 额,免费的? 恩,应该是 .. 这得节约多少钱啊 .. 偶穷人一个,能省钱就好 …….</p>
<p>duyue居然就在北航 .. .. … 诶,牛校出牛人 … … 让偶一个小学校来的情何以堪情何以堪… …</p>
<p>恩,记录下飞机票信息还有duyue的信息,再打印双份以防万一 …..</p>
<p>然后? .. 然后我的电脑在一次待机中电源管理关闭了显示器 … 结果我的显示器开不起来了 …. 重启电脑又开起来了 … 驱动问题?</p>
<p>次日,照旧早上起来开机 .. .. .. …. 开机 … … … 开机 … .. ….
我的显示器就这样牺牲了。为什么一定要这个时候呢 .. 恩恩,跑题了,关于显示器的事情就不说了</p>
<p>呵呵,这也促成了我使用控制台 (小电脑比较慢,用 X == 自残)。lyxint 好厉害啊,居然三下2下帮我搞定了 weechat 的问题。北京牛人辈出啊~~~</p>
<p>这样花一天习惯了小电脑和控制台</p>
<p>接下来太阳又一次西落东起 。。 ==, 还没起来,我就起床敢飞机咯
第一次去机场 … … 机场给我的第一个感觉 。。。 这人也太少了吧?!!!! 想想看火车站和汽车站 … 那是人山人海啊 … …</p>
<p>机场有 Wifi … 太好了 … … 恩? 这个 ESSID 是?!! CMCC … …
得,免费不了了,刚刚在机楼发现一个 天翼VIP 区.. 上网 30RMB … … 还好在 VPS 上搭建了一个 53 端口的代理
… (不过,后来才发现其实这个 CMCC 就是免费的 …. wink.gif</p>
<p>在杭州,基本上都是737 … 我没看到过一架大飞机 … … 737 太容易识别了,那特色的引擎外壳 .. 非圆形的 …
… 以前疯狂迷恋 X-plane 的时候学了一堆飞机相关知识 …..</p>
<p>( PS : 真正坐到飞机上的时候,那种失重和超重的感觉是模拟不出来的,而失重的时候受内耳影响,人会有下意识的动作,这个会害飞机坠机的!..
这是玩 X-plane 永远学不到的。必须实地飞行去克服 …. wink.gif</p>
<p>终于到北京了 … … 辗转到了北航 … …
和 duyue 碰头的时候 … 恩,老实说,心里好紧张啊 … … 和一个没有见过的人碰面 … …
他会长虾米样子呢???? … … 呵呵,最终见到的时候心舒缓了一下 … … 看上去还可以,不至于太凶 … …</p>
<p>在机场的时候就意识到了,北京的们似乎都是双层的,而且内外的们是错开的, 不在一条直线上 … … 北京特色吧</p>
<p>做公交的时候,我很诧异的发现,居然还有有售票的 … … … 而且是刷卡上车,刷卡下车 …
杭州也有分段计费的,但是靠的是自觉 … … 你投一块钱坐5块钱的路也行(其实司机眼很厉害,基本一眼就看出来你会坐到哪里…..)
…. … 北京也太防着市民了吧 … … 呵呵,大城市,什么人都有 … … 兴许是这样原因?</p>
<p>到北航, 刷卡进寝室. .. 刷卡去超市 … 在中关村也是刷卡吃的饭 … 北京就是个卡卡卡 … 以后就卡京好了 … …
… 我这么说呢, duyue 赶紧掏出一叠的卡 … … 一叠的卡 … …一叠的卡 … …</p>
<p>==, 你说刷卡进寝室 ??? 恩,是的,怪异的事情。呵呵,后来才发现是因为生活区没有大门 … …</p>
<p>这寝室还真难混进去啊 … … 苦了duyue, 几次陪我在风中瑟瑟发抖到阿姨睡觉了才进去 ……
因此决定最后一个晚上我就住旅馆好了,呵呵,不能让duyue 周日晚上还这么累 …</p>
<p>北京真是哪里都有暖气 … 寝室居然也有。。。 我为什么说居然呢? … … 晚上睡觉热死我了 ….
同学们居然整天不开寝室的门,几个爷们居然不生病 ….. 气流不畅快,我每天早上起来喉咙都难受 … .. 我向来晚上都是开窗睡觉的
… …</p>
<p>公交车真是慢到和走路一个速度 … … 恩,首堵见识到啦。其实解决办法也没那么难 … 不需要单双号限行… …
因为很多人住的太远,不得不坐车开车啦。住的远还不是因为住的近住不起(还不说,都是白领在做公车 …
民工都是住的工地,没有公交压力….. )! 每天浪费那么多时间在路上,对北京来说,每天浪费的都是多少人年的生产力啊!</p>
<p>那种在民房办公的创业黄金时代哪里去了?
没有创业,一个地区就会失去活力。不管你这里有多少跨国企业,多少500强。公司迟早会倒闭的,没有不倒闭的公司。只有新兴公司不断产生,不断竞争,这个地方才有活力
…. 让市场淘汰,而不是门槛 …</p>
<p>马化腾 50w 创业。现在呢? 50w 都不够办公室的租金 … .. 你创业个毛去啊。民房创业?得,买的起么?租的不允许啊 …</p>
<p>红帽真牛,居然请到我这么一个大牛 …. 还不待见 …
结果去新浪碰运气 .. 居然被我碰上了。。。 可惜,我还没毕业 … 所以啊… 诶,难做决定哦 …. …</p>
<p>聚会很和谐 … 见了很多人,有牛人也有菜鸟。呵呵。dongdong 居然是 emacs 党 …</p>
<p>回来的时候做上了空客 A320 … 呵呵,去一次北京把两家的飞机都坐上了。波音飞机那个叫猛啊,加速很快,才几秒钟就上天了,空客的则在地面滑久多了。</p>
<p>天上的空气密度很不均匀,所以飞机的升力变化很多,在飞机里面的感觉,好像在做公交车 … 一上一下的 … ….
呵呵。下的时候,就感觉,完了完了,升力没有了。坠机了 …..
上的时候,超重 …. … 呵呵。这是 737
320情况好些,毕竟比 737大多了,所以平稳多了。</p>
<p>可惜没坐到 787 … :(</p>
修复了三星显示器
2010-12-16T00:00:00+00:00
https://microcai.org/2010/12/16/fixsumsumLCD
<p>诶,出发去北京的前两天,也就是十一月三十日 … 早上一大早起来开机。我可怜的三星740NW显示器就再也不工作了。
蓝色的电源指示灯快速的闪动着,可显示器就是不给开起来 ….</p>
<p>恩,应该是坏掉了,也许问题不那么严重,上网看看有米有什么便捷的方法解决吧!</p>
<p>呵呵,还好我先见之明 .. 还有一 EPC. ..</p>
<p>马上开起来 .. 囧。 前些日子为了测试 systemd … 把系统搞坏了. … 马上重装! 于是花了几个小时重装 了 archlinux …. (额,我 gentoo 控。但是我需要马上用电脑… gentoo 编译 .. 我了个去,将就着 用 arch 先)</p>
<p>终于到最后安装上 chrome 了,呵呵。 恩,还没安装 ibus … 得,不装先, …</p>
<p>google 了好些,终于发现很多人也有此症状(对三星的质量先日一下)。 专家说,是 inverter 坏掉了 ….</p>
<p>我了个去 … 这下我自己修不好了 …..</p>
<p>诶,得。再买一个去吧 .. 55555555</p>
<p>既然再买一个,恩恩,这个咱就拆了吧 … .. 说实话,我老早就想拆它了 …. (<em>^__^</em>) 嘻嘻……</p>
<p>说干就干 … … 我蛮力的拆解了这款 740nw …</p>
<p>好家伙,LCD 显示器也忒简单了吧!</p>
<p>就 3 个东西。 一个板子负责供电,一个板子负责接受 D-SUB 接口的输入,然后用一排线连接到液晶屏幕上。供电的那个也有2个线接过去,看样子就是所谓的灯管了 ….</p>
<p>恩,就是那个负责供电的电路板烧掉了啊~~</p>
<p>维修站铁定有。我打电话问问…</p>
<p>………..</p>
<p>我了个去,不外卖 … 我说我付维修费 … 你们就当给我修了 .. 你们还不用麻烦就赚了双份的钱 … .. 不行?非得跑你们那 里一趟?外面还下雨呢 … 维修费 120 ? 材料另算?? 宰人的呢! 直接打电话到三星去 .. 一个德行 …. md,我投诉你们 去 … 12315 也和他们一个德行 ….</p>
<p>诶 … 难道就没有地方买到这种配件?</p>
<p>==</p>
<p>网购去 … 试试看么 …</p>
<p>哈哈,果然有!!!! 当即让LP买下 … 额,千叮咛万嘱咐,一定今天发货的才买 …..</p>
<p>恩,安心的用小电脑先 ……</p>
<p>结果,第二天等到了下午都没有快递过来,我就去查了一下 .. NND ,居然今天还没发货! 你丫的昨天没货还满口答应!赶紧退款,另外给了个差评。</p>
<p>诶,马上就去北京了。就不折腾先。</p>
<p>12-02~~12-06 北京之旅 (我会另外写博文介绍的)</p>
<p>06 号回来的时候就在车上赶紧让 LP 买一个。 … 恩,这次大意了,没有自己去挑货 …</p>
<p>07 号下午终于等来了快递 …. 兴奋了一阵 ….</p>
<p>然后马上开始修咯。</p>
<p>==, 这大小? 。。。 居然不一样!!! 。。。
==,这. .. 是4灯的版本?!!! 我要的是2灯的!!!。。。。诶,不是自己去挑,果然有问题 … 失策失策</p>
<p>只接2个应该也行的吧?!</p>
<p>于是,动手改造容器,使得可以放下这个大一点的电路板 …..</p>
<p>改到一半。突然发现,得测试一下先啊 … …</p>
<p>于是… 外壳不装,先连接好电路 … 信号接小电脑上 …</p>
<p>我了个去 … 灯不亮灯不亮灯不亮灯不亮灯不亮灯不亮灯不亮 。。。。。</p>
<p>上网一查,是高压保护,非得4个灯都接上才行 …..</p>
<p>得,网上给的去掉高压保护的都不适用于这个电路板的 IC …..</p>
<p>得,重新买一个吧,快递20,电路板才25,退货不划算 … …</p>
<p>已经晚上了,最快快递买的也得后天到啊~~~</p>
<p>恩,直接找杭州的卖家 … 明天直接去拿,省了快递,快点….</p>
<p>要不怎么说杭州就不适%E</p>
P2P 终结者 for Linux
2010-11-02T00:00:00+00:00
https://microcai.org/2010/11/02/p2pkiller
<p>很长一段时间,都在渴望一个 for Linux 的 P2P 终结者。</p>
<p>但是,谁让我们是小众群体呢?所以,没有。</p>
<p>但是,今天我突发奇想,找到了!用脚本实现了 P2P 终结者!</p>
<p>问题是这样的,首先我需要一个 arp 欺骗工具。呵呵, dsniff 就是!</p>
<p>执行 arpspoof 就可以进行 ARP 欺骗了。 不过,呵呵,我只对需要控制的主机进行 ARP 欺骗,比如 192.168.1.100</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>arpspool -i eth0 -t 192.168.1.100 192.168.1.1
arpspool -i eth0 -t 192.168.1.1 192.168.1.100
</code></pre></div></div>
<p>这样成功的让 192.168.1.100 到 192.168.1.1 之间的通讯全部通过自己了</p>
<p>然后,我打开 内核的 IP 转发。
echo 1 > /proc/sys/net/ipv4/ip_forward</p>
<p>呵呵,现在,怎么限速就是我的事情啦! 可以使用 tc 限速,也可以简单的时候 iptables 限速!
比如,使用 iptables 的 limit 匹配模块来限速. 一秒钟是能发送和接收 3 个数据包,这样速度就限制到 4k/s 了</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>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
</code></pre></div></div>
一个Sqrt函数再次引发的血案
2010-10-16T00:00:00+00:00
https://microcai.org/2010/10/16/somethingaboutsqrt
<p>这些神人啊,开平方居然有这么快的算法!!!
于是我决定看看 glibc 是怎么实现的!
如果 glibc 比较慢,我一定要改成神人的算法重新编译 glibc !!!</p>
<p>等等!先写一个程序测试两种算法的速度</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#include <math.h>
float magic_sqrt(float number)
{
long i;
float x, y;
const float f = 1.5F;
x = number * 0.5F;
y = number;
i = * ( long *) & y ;
i = 0x5f3759df - ( i >> 1 wink.gif ;
y = * ( float * wink.gif & 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]=='s')
{
for(i=0;i < TIMES; i++)
{
sqrt(200.0);
}
}else
{
for(i=0;i < TIMES; i++)
{
magic_sqrt(200.0);
}
}
return 0;
}
</code></pre></div></div>
<p>然后用 time ./a.out s 和 time ./a.out m 来测验两个开发算法的速度。
哥震惊了!!! 一样快!!!莫非 glibc 也使用了神一样的 … ?????</p>
<p>于是经过漫长时间的下载, 解压 , grep 之后,我终于找到了我要的 glibc 中实现开方算法的文件</p>
<p>sysdeps/x86_64/fpu/e_sqrt.c</p>
<p>哥再次震惊了!哥再次吐血了!!!</p>
<p>居…居…居居然 ….. 只有一条指令</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>double
__ieee754_sqrt (double x)
{
double res;
asm ("sqrtsd %0, %1" : "=x" (res) : "x" (x));
return res;
}
</code></pre></div></div>
<p>看来以后我可以放心的使用 glibc 的数学函数了 … 事实证明, glibc 总是使用的最快的方法。</p>
如何避免使用 root 权限调式 libpcap 程序
2010-10-15T00:00:00+00:00
https://microcai.org/2010/10/15/debug-libpcap-program-without-root
<p>工作原因,经常用 libpcap 写程序。</p>
<p>可是调式却一直是个大问题。</p>
<p>即便是非常麻烦地每次编译后 setuid
gdb 也是不能调式 setuid 程序,gdb 会使 setuid 失效。
一直以来的解决办法都是原始的sudo + printf , 而无法单步调式。实在有时候需要单步了,就 sudo eclipse 启动了
结果调式完毕之后还需要
sudo chown cai:cai ~/workspace -R</p>
<p>灰常的不爽</p>
<p>后来发现了 gdb-server</p>
<p>可以用 sudo gdb-server 启动要调式的程序,再在 eclipse 里选择 gdb/server 作为调式程序。
这样就不必为了调式而整个让 eclipse 启动到 root 环境了</p>
<p>可是,还是很不爽。每次调式前都要
sudo gdb-server localhost:5000 我的程序</p>
<p>后来,终于发现,我可以先用 dumpcap 抓取一定的包,然后保存到一个文件中.
只要把原先打开网卡的代码稍微改一下变成
pcap_open_offline
就可以了
而从文件开始读取,可以模拟非常巨大的网络流量,很考验处理程序.</p>
<p>而通过 有名管道,又可以轻易的支持 在线抓包调式。使用管道的时候经过我的测试,发现必须这样
dumpcap -w - > /tmp/fifo
写才行。然后我的程序就可以把管道作为文件直接打开
发布的时候,代码再改回来,或则干脆就不改,作为一个命令行选项。</p>
GB系列编码 比 UNICODE 先进,是最先进的编码
2010-10-11T00:00:00+00:00
https://microcai.org/2010/10/11/gbkrocks
<p>首先 UNICODE 里头只有 utf8 能兼容 ascii,所以,这已经是 UNICODE 的一大失败了。
接着我们将两个大方面的比较</p>
<p>一,汉字方面
1.1 汉字编码排列方面
UNICODE 编码中,汉字的排列毫无规律可言。 (0分)
GB 系列编码中,汉字都是依据读音笔画排列下来的。非常的有规律。 (1分)</p>
<p>1.2 编码效率方面
UNICODE 在实现上,只有 utf8是兼容 ascii的,我们只考虑 utf8
utf8汉字大部分是 3byte编码,少数汉字为 4byte (0分)
GB系列编码中,汉字几乎是 2byte编码,少数汉字为 4byte编码(GB18030标准) (1分)</p>
<p>二,非汉字部分
非汉字部分就是英语咯,还有欧洲语言,还有特殊字符。其中英语是一样的,不用比较
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分)</p>
<p>综合起来,UNICODE 就是一垃圾编码,给这个世界带来了混乱,早该废止!!</p>
异步单线程的梦
2010-10-10T00:00:00+00:00
https://microcai.org/2010/10/10/async
<p>很享受单线程带来的好处。</p>
<p>第一:不用考虑锁。
第二:不用考虑线程切换开销。
第三:很锻炼人异步并发的思想。</p>
<p>特别是第三点,我觉得最喜欢了。
非常喜欢异步执行,异步通知。
用一个线程,完成了所有的逻辑。而且不能阻塞在一个逻辑上。如果需要阻塞,就采用异步通知,等它ready了再处理。</p>
<p>单线程异步并发用的久了
突然有一天碰上了 ALSA … … …
居然再也不能使用了。。。。。。
ALSA 的异步通知只有一种。那就是信号。
但是信号是不可靠的,而且信号数目有限,怎么能用来做异步通知呢?(所以也很讨厌 aio)
ALSA 居然不能方便的时候 poll/epoll 来进行通知,各种事件非得用 alsa_*(会阻塞的) 获得 …. 逼我用多线程。</p>
<p>当然,PulseAudio 的话 … 由于是 socket 连接,可以继续你的异步梦了 ….. but .. who likes pa ?</p>
<p>很多地方都可以异步单线程,而且都是使用的 epoll/poll 通知机制, 所以,任何可能会阻塞的地方,如果能使用 poll
那么,永远都不用当心你的梦会破灭 … (ALSA该死,破灭我一次)</p>
<p>最后,用 Alan Cox 的名言来打击一下使用多线程的人:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> computer is a state machine. Threads are for people who can't program state machines. (计算机是一台状态机。线程是给那些不能编写状态机程序的人准备的。)
</code></pre></div></div>
<p>文件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 == 返回的也是套接字</p>
简历上的神吹
2010-07-28T00:00:00+00:00
https://microcai.org/2010/07/28/fuckingresume
<p>实习的时候看过几十份求职简历,现在把我看过的简历的特点简单介绍一下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>神吹
倒不是像唐骏那样撒谎,就是什么东西都写,其实没那么厉害。比如,有个人简历上写自己做过搜索引擎。把人找来一面试,连基本的 C 语言都不会。能吹吧? 把团队里别人的功劳说成自己的,真 TM 恶,自己不过是一个混班的罢了。这是吹嘘的比较厉害的,还有稍微不厉害点的,比如明明英语基本不会,非说自己过了六级 (这也能写,真逗,参加辅导班过的六级和没有做准备就去考,档次不是一样的。)
鸡毛大的事情也当辉煌经历
比如,考个四六级就当是辉煌了。操,要是有人觉得没什么好写的,就不写,那不是被招聘的人认为是六级都过不了吗? 操,这些JB。直接导致不写鸡毛事情的人遭殃。
学过的就当会的
这你要是不写吧,人家就觉得你连见识都米有。
都有个名字很好的学位证书。
嗯,我是主动辍学的,不想继续耗费青春。如果你是个非要看证书的人,请离开。
</code></pre></div></div>
<p>简历如海,你们这些投机取巧的人,把我们老实本分的人都欺负了。直接导致用人单位用更高的要求筛选其实要求没这么高的职位。这让老实人只能去做相对其实力要简单很多的职业。 这是对人力资源的浪费。</p>
<p>好了,讨厌简历的原因写好了,就是说,考场作弊的人太多,坚持不作弊的人还值得去考试么?问题是,考官还不知道谁作弊了!真气人。</p>
关于机翼的升力
2010-07-15T00:00:00+00:00
https://microcai.org/2010/07/15/aboutlift
<p>科普上,书本上,说升力来自压力差,压力差来自伯努力。
是这样的吗?</p>
<p>上边的空气为何要和下边的一起到达机翼后沿?这是没有根据的。
升力确实来自压力差,但是压力差并不能简单的解释为流速差带来的。因为这样的话,倒飞就无法解释。</p>
<p>根据伯努力原理,流体的压力分静压和动压。当流体流向于物体表明一致的时候,表明只能感受到静压,当于表明差垂直的时候,能感受到全部的动压和静压。</p>
<p>飞行时,机翼的玄线和来流方向有个夹角,这个夹角叫迎角,这时,机翼下方受到了气流的动压。这个动压+静压比上表面的大多了,而且动压远远大于流 速差带来的静压差,所以所,迎角是机翼升力的主要原因。根据柯达效应,此时机翼上表面还能感受到负动压,负动压甚至大于下表明的正动压,所以,机翼的升力 主要是由上表面的吸力贡献的。</p>
<p>这就是升力产生的原理,可不是什么流速差哦。</p>
For every bug, there is a ugly man
2009-09-28T00:00:00+00:00
https://microcai.org/2009/09/28/foreverybugthereisauglyman
<p>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.</p>
<p>For every bug found in the softwre, there is a ugly man behind.
Now it plays on me.</p>