<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>JOE&#39;S BLOG</title>
    <link>https://www.joe.top</link>
    <description>爱生活，爱coding</description>
    <language>zh-CN</language>
    <lastBuildDate>Tue, 20 Oct 2026 07:38:24 GMT</lastBuildDate>
    <atom:link href="https://www.joe.top/feed.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>浅谈分布式身份</title>
      <link>https://www.joe.top/2023/02/28/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/blockchain/20230228-%E5%88%86%E5%B8%83%E5%BC%8F%E8%BA%AB%E4%BB%BD/</link>
      <guid isPermaLink="true">https://www.joe.top/2023/02/28/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/blockchain/20230228-%E5%88%86%E5%B8%83%E5%BC%8F%E8%BA%AB%E4%BB%BD/</guid>
      <pubDate>Tue, 28 Feb 2023 01:22:18 GMT</pubDate>
      <category>区块链</category>
      <description>分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统是一个很大的话题，这里仅仅简单介绍下基本概念。</description>
      <content:encoded><![CDATA[<h2 id="分布式系统">分布式系统<a class="heading-anchor" href="#分布式系统" aria-hidden="true" tabindex="-1">#</a></h2>
<blockquote>
<p>分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统是一个很大的话题，这里仅仅简单介绍下基本概念。</p>
</blockquote>
<p>单节点已经无法满足日益增长的计算和存储需求，所以才有了现在各式各样的分布式系统。分布式要解决的问题跟单机系统要解决的问题一样，但由于分布式系统是有多个节点组成，分布在网络的各个节点上，从而引入了单机系统不存在的问题，为了解决这些问题又会引入了各种协议、机制等等。区块链就是一个典型的分布式系统，区块链是由分布式架构与分布式存储、块链式数据结构、点对点网络、共识算法、密码学算法、博弈论、智能合约等多种信息技术共同组成的整体解决方案。</p>
<h2 id="身份">身份<a class="heading-anchor" href="#身份" aria-hidden="true" tabindex="-1">#</a></h2>
<blockquote>
<p>国际电子技术委员会将“身份”定义为“一组与实体关联的属性”。这里的实体不仅仅是人，对于机器或者物体都可以是实体，甚至网络中虚拟的东西也可以是实体并拥有身份。</p>
</blockquote>
<p>随着互联网的出现，人们越来越多的交流都是在互联网进行，从事一切的交流活动都需要一个身份作为基础，这个身份在互联网上就是数字身份。人们日常使用的QQ、微信、电话、短信等等都是基于数字身份进行。这里的数字身份不仅仅可以指自然人，各种设备、机构组织、虚拟网络都可以拥有数字身份。数字身份的发展经历了四个阶段,分别是:中心化身份、联盟身份、以用户为中心的身份以及自我主权身份。</p>
<ul>
<li>中心化身份是由单一的权威机构进行管理和控制的，现在互联网上的大多数身份还是中心化身份。</li>
<li>联盟身份的出现解决了中心化身份中身份数据零碎混乱的弊端，此种身份是有多个机构或者联盟进行管理和控制的，用户的身份数据具备了一定程度的可移植性，例如允许用户登录某个网站时， 可以使用其他网站的账户信息，类似于 QQ、微信或者微博的跨平台登录。</li>
<li>以用户为中心的身份则将重点集中在去中心化上，通过授权和许可进行身份数据的共享，例如OpenID。</li>
<li>自我主权身份才是真正意义上的去中心化的、完全由个人所拥有和控制的身份。</li>
</ul>
<h2 id="分布式身份">分布式身份<a class="heading-anchor" href="#分布式身份" aria-hidden="true" tabindex="-1">#</a></h2>
<blockquote>
<p>根据 W3C 的定义，分布式标识符（DID）是一种新型可
验证的 “自主权”数宇身份标识符，通过它能够操作可验证的、分布式的数字身份一一个DID可以标识任何主体(如人、组织、事物、数据模型、抽象实体等）。新的标识符旨在使
DID 的主体能够证明对它的控制，并且它的获取不依赖于任何集中式注册表、身份服务商或证书颁发机构。</p>
</blockquote>
<h3 id="目标">目标<a class="heading-anchor" href="#目标" aria-hidden="true" tabindex="-1">#</a></h3>
<p>分布式数字身份的最终目标是使得网络上进行交互的任意双方可以相互信任。通过在分布式网络中根植凭证发行者的公共可解析DID和公共秘钥，凭证验证人得以验证发行者的数字签名，从而构建了基于非对称的加密信任。</p>
<h3 id="技术原理">技术原理<a class="heading-anchor" href="#技术原理" aria-hidden="true" tabindex="-1">#</a></h3>
<p>一般来说，当前所有 DID 系统的技术栈都是自下而上的结构，最底层是网络协议（一般为 TCP/IP），往上一层是区块链或分布式账本，再往上一层是 DID 协议，最上层是应用软件（DApp、App 等应用程序）。</p>
<h4 id="分布式账本">分布式账本<a class="heading-anchor" href="#分布式账本" aria-hidden="true" tabindex="-1">#</a></h4>
<p>按照 W3C 的定义，分布式账本是一种在网络成员之间共享、复制和同步数据的数据库。按中国人民银行的规范定义，分布式账本指可以在多个站点、不同地理位置或者多个机构组成的网络里实现共同治理及分享的资产数据库。分布式账本技术是密码算法、共识机制、点对点通信协议、分布式存储等多种核心技术体系高度融合形成的一种基础架构与计算范式。  </p>
<p>区块链技术的分布式特性和共识算法使其成为当前分布式账本的主流技术。使用区块链技术能避免身份数据被单一的中心化机构所控制，账本是分发给网络中的所有成员节点，所有交易都记录在由哈希算法链接的区块链中。</p>
<h4 id="did">DID<a class="heading-anchor" href="#did" aria-hidden="true" tabindex="-1">#</a></h4>
<p>DID是一个由3个部分组成的统一资源描述符(URI):</p>
<ol>
<li>固定格式的前缀Scheme: &quot;did:&quot;。</li>
<li>DID方法的标识符DID Method</li>
<li>由DID方法和进行规范生成的、全球唯一的标识符。</li>
</ol>
<p><img src="/remote/3a4650bb003dcd23.png" alt="" loading="lazy" decoding="async"></p>
<h4 id="did-doc">DID DOC<a class="heading-anchor" href="#did-doc" aria-hidden="true" tabindex="-1">#</a></h4>
<p>DID文档由一组属性组成，包含了与DID相关的元数据。它们描述了与 DID 主体有关的验证方法（如公钥）和服务。DID文档根据特定语法进行序列化。DID本身也是语法中&quot;id&quot; 属性的值。DID 文档中的属性可以通过特定的操作进行更新，这些操作由具体的DID方法(DID Method ）说明和实现。
DID文档是一组描述DID 主体的数据，也包含公钥和生物识别等机制，使得 DID 主体可以用来验证自身并证明自身与此DID相关联。DID文档还可以包含描述主体的其他属性或声明。</p>
<p>以下是一个DID文档样例:</p>
<pre class="code-block"><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  <span class="hljs-attr">&quot;@context&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>
    <span class="hljs-string">&quot;https://www.w3.org/ns/did/v1&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-string">&quot;https://w3id.org/security/suites/ed25519-2020/v1&quot;</span>
  <span class="hljs-punctuation">]</span>
  <span class="hljs-attr">&quot;id&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;did:example:123456789abcdefghi&quot;</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">&quot;authentication&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-punctuation">{</span>
    <span class="hljs-comment">// used to authenticate as did:...fghi</span>
    <span class="hljs-attr">&quot;id&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;did:example:123456789abcdefghi#keys-1&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;type&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;Ed25519VerificationKey2020&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;controller&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;did:example:123456789abcdefghi&quot;</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">&quot;publicKeyMultibase&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;zH3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV&quot;</span>
  <span class="hljs-punctuation">}</span><span class="hljs-punctuation">]</span>
<span class="hljs-punctuation">}</span></code></pre>
<h4 id="dkms">DKMS<a class="heading-anchor" href="#dkms" aria-hidden="true" tabindex="-1">#</a></h4>
<p>去中心化密钥管理系统 (Decentralized Key Management System,DKMS）是一种新的分布式的密钥管理方法，使用没有集中管控机构的区块链和分布式账本技术对密钥进行管理。与传统公钥基础设施 PKI不同，DKMS 不依赖于 CA机构对公钥进行认证。在DKMS 中，所有参与者的初始“信任根”是支持分布式标识符DID这一新形式的根身份记录的分布式账本。</p>
<h4 id="可验证凭证">可验证凭证<a class="heading-anchor" href="#可验证凭证" aria-hidden="true" tabindex="-1">#</a></h4>
<p>（Verifiable Claims 或 Verifiable Credentials，本文接下去都简称VC）是一个 DID 给另一个 DID 的某些属性做背书而发出的描述性声明，并附加自己的数字签名，用以证明这些属性的真实性，可以认为是一种数字证书。可以理解为我们日常使用的身份证。</p>
<p>下图是可验证凭证的基本结构:  </p>
<p><img src="/remote/066f3633b90650ef.png" alt="" loading="lazy" decoding="async">  </p>
<ul>
<li>VC元数据，主要就是发行人、发行日期、声明的类型等信息。</li>
<li>声明，一个或者多个关于主体的说明。比如身份证作为公安机关颁发给我的VC，在声明中会包含：姓名、性别、出生日期、民族、住址等信息。</li>
<li>证明，通常就是颁发者的数字签名，保证了本VC能够被验证，防止VC内容被篡改以及验证VC的颁发者。</li>
</ul>
<h4 id="点对点通信协议">点对点通信协议<a class="heading-anchor" href="#点对点通信协议" aria-hidden="true" tabindex="-1">#</a></h4>
<p>DID之间的消息传递遵循DIDComm协议，不依赖于第三方即可进行安全通信。其使用了DPKI技术，类似于现在使用的一些端到端加密系统，比如Signal、Telegram。DIDComm保证了不同DID实体间通信的安全性、隐私保护、互操作性和可扩展性。</p>
<h3 id="技术应用场景">技术应用场景<a class="heading-anchor" href="#技术应用场景" aria-hidden="true" tabindex="-1">#</a></h3>
<ul>
<li>身份认证: 这是DID最基本的应用，解决各种场景下身份验证问题。</li>
<li>无密码登录: 当我们注册或登录各种应用时，只需使用手机或电脑安全芯片中保存的DID信息完成与应用DID的双向认证，整个过程服务器并不知道用户口令，而且也无法获取除DID文档外的任何信息，从而保证了用户隐私。</li>
<li>数字版权保护: 利用区块链不可篡改性以及数字身份的自主可控性，将版权、合同、赛事记录等信息存储在区块链的分布式网络中，永久有效，无法篡改。引入公证处、版权局、知名高校作为版权存证联盟链的存证和监督节点，从而完成版权保护。</li>
<li>供应链金融: 通过DID技术建立企业信用传导，提高企业信用评估审核效率，降低融资服务成本和银行放贷风险。</li>
<li>物联网: 物联网设备间本身就是一个大型的分布式网络，利用DID技术帮助物联网设备产生的数据得到更加规范的管理和保护，在数据流转中可验证，并且无法篡改。</li>
</ul>
<h2 id="后记">后记<a class="heading-anchor" href="#后记" aria-hidden="true" tabindex="-1">#</a></h2>
<p>分布式身份的内容，这里仅仅浅谈下，了解下大概情况。关于分布式身份、区块链的内容不仅于此，有机会我们再细细的浅谈:)</p>
<h3 id="references">References<a class="heading-anchor" href="#references" aria-hidden="true" tabindex="-1">#</a></h3>
<ul>
<li><a href="https://w3c.github.io/did-core/" target="_blank" rel="noopener noreferrer">W3C DID规范</a></li>
<li><a href="https://cloud.tencent.com/developer/article/1648865" target="_blank" rel="noopener noreferrer">去中心化(DID)研究报告</a></li>
<li><a href="https://www.cnblogs.com/xybaby/p/7787034.html" target="_blank" rel="noopener noreferrer">什么是分布式系统</a></li>
<li><a href="https://cloud.tencent.com/developer/article/2025047?from=article.detail.1648865&amp;areaSource=106000.2&amp;traceId=P--ehkf6ozhUECYDzFmXc" target="_blank" rel="noopener noreferrer">去中心化身份DID简介</a></li>
<li><a href="https://www.bfia.org.cn/upload/file/20220107/19786782201071404005.pdf" target="_blank" rel="noopener noreferrer">金融分布式数字身份技术研究报告</a></li>
<li><a href="http://blog.nsfocus.net/dpki-did/" target="_blank" rel="noopener noreferrer">DPKI的崛起之路——分布式数字身份(DID)</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Go汇编/启动例程</title>
      <link>https://www.joe.top/2023/01/15/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/language/GO/go-src/20230115-go%E5%90%AF%E5%8A%A8%E4%BE%8B%E7%A8%8B/</link>
      <guid isPermaLink="true">https://www.joe.top/2023/01/15/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/language/GO/go-src/20230115-go%E5%90%AF%E5%8A%A8%E4%BE%8B%E7%A8%8B/</guid>
      <pubDate>Sun, 15 Jan 2023 05:49:23 GMT</pubDate>
      <category>Go原理系列</category>
      <description>使用go时间久了，就会想搞明白go的底层是如何运行的，也就是go runtime的实现原理。本文主要描述了go的启动过程。不过会先从前置知识汇编学起，go runtime涉及了很多汇编知识，而go使用的汇编是plan9汇编，了解完汇编的大概知识再一步步的跟踪go的启动流程。</description>
      <content:encoded><![CDATA[<h2 id="前言">前言<a class="heading-anchor" href="#前言" aria-hidden="true" tabindex="-1">#</a></h2>
<blockquote>
<p>使用go时间久了，就会想搞明白go的底层是如何运行的，也就是go runtime的实现原理。本文主要描述了go的启动过程。不过会先从前置知识汇编学起，go runtime涉及了很多汇编知识，而go使用的汇编是plan9汇编，了解完汇编的大概知识再一步步的跟踪go的启动流程。</p>
</blockquote>
<h2 id="内存">内存<a class="heading-anchor" href="#内存" aria-hidden="true" tabindex="-1">#</a></h2>
<p>谈谈变量，在编程中我们会定义各种变量，这些变量其实就是一个个的内存地址。</p>
<ul>
<li>变量的本质就是内存</li>
<li>指针保存了内存地址</li>
<li>指针的本质上就是地址</li>
</ul>
<p>下面2个图都是进程经典的虚拟内存结构模型</p>
<p><img src="/remote/0dfc751e37247d04.png" alt="" loading="lazy" decoding="async"></p>
<p><img src="/remote/7cda57e813b28c97.png" alt="" loading="lazy" decoding="async"></p>
<h2 id="汇编基础">汇编基础<a class="heading-anchor" href="#汇编基础" aria-hidden="true" tabindex="-1">#</a></h2>
<p>go使用plan9汇编，plan9汇编的操作数类似AT&amp;T,使用<code>go tool objdump/go tool compile -S</code> 可以输出go的汇编代码, plan9的汇编其实是一个伪汇编，go程序会统一编译成plan9汇编，然后plan9汇编在根据不同的系统架构编译成目标的汇编代码。大概就是下面的流程。 </p>
<p>go-&gt;plan9汇编-&gt;目标机器汇编-&gt;可执行程序  </p>
<h3 id="如何编译汇编">如何编译汇编<a class="heading-anchor" href="#如何编译汇编" aria-hidden="true" tabindex="-1">#</a></h3>
<ul>
<li>go build -gcflags &quot;-N -l&quot; main.go, 然后go tool objdump -s &quot;main.&quot; main </li>
<li>go tool compile -S -N -l main.go </li>
<li>go build -gcflags=&quot;-N -l -S&quot; main.go </li>
<li>GOSSAFUNC=&quot;main&quot; go build main.go //  static single-assignment, 查看 Go 程序编译的整个过程， 最后一步就是汇编代码</li>
</ul>
<p>以上四种方式都可以输出汇编代码，其中几个参数的含义<code>-l 禁止内联 -N 编译时，禁止优化 -S 输出汇编代码</code></p>
<h3 id="一些基本概念">一些基本概念<a class="heading-anchor" href="#一些基本概念" aria-hidden="true" tabindex="-1">#</a></h3>
<ul>
<li>栈：进程、线程、goroutine 都有自己的调用栈，先进后出（FILO）</li>
<li>栈帧：可以理解是函数调用时，在栈上为函数所分配的内存区域</li>
<li>调用者：caller，比如：A 函数调用了 B 函数，那么 A 就是调用者</li>
<li>被调者：callee，比如：A 函数调用了 B 函数，那么 B 就是被调者</li>
</ul>
<h3 id="基本指令">基本指令<a class="heading-anchor" href="#基本指令" aria-hidden="true" tabindex="-1">#</a></h3>
<h4 id="栈调整">栈调整<a class="heading-anchor" href="#栈调整" aria-hidden="true" tabindex="-1">#</a></h4>
<p>plan9中没有push和pop， 栈的调试使用了硬件SP寄存器  </p>
<pre class="code-block"><code class="hljs">SUBQ $0x18, SP // 对 SP 做减法，为函数分配函数栈帧，栈是从高地址向低地址增长
ADDQ $0x18, SP // 对 SP 做加法，清除函数栈帧</code></pre>
<h4 id="数据搬运">数据搬运<a class="heading-anchor" href="#数据搬运" aria-hidden="true" tabindex="-1">#</a></h4>
<p>常数在 plan9 汇编用 $num 表示，可以为负数，默认情况下为十进制。可以用 $0x123 的形式来表示十六进制数。  </p>
<pre class="code-block"><code class="hljs">MOVB $1, DI      // 1 byte  
MOVW $0x10, BX   // 2 bytes
MOVD $1, DX      // 4 bytes
MOVQ $-10, AX     // 8 bytes</code></pre>
<h4 id="计算指令">计算指令<a class="heading-anchor" href="#计算指令" aria-hidden="true" tabindex="-1">#</a></h4>
<pre class="code-block"><code class="hljs">ADDQ  AX, BX   // BX += AX
SUBQ  AX, BX   // BX -= AX
IMULQ AX, BX   // BX *= AX
ANDQ  AX, BX   // BX &amp;= AX
LEAQ  exp, BX  // 按照exp表达式计算地址并赋值给BX
CMPQ  SI, CX   // 表示比较SI和CX的大小。与SUBQ类似，只是不返回相减的结果</code></pre>
<h4 id="比较指令">比较指令<a class="heading-anchor" href="#比较指令" aria-hidden="true" tabindex="-1">#</a></h4>
<pre class="code-block"><code class="hljs">CMPQ AX, BX // BX &gt; AX ZF=0， CF=1；BX &lt; AX ZF=0, CF=0；BX = AX ZF=1, CF=0; ZF:零标志位， CF：符号标志位 </code></pre>
<h4 id="条件跳转无条件跳转">条件跳转/无条件跳转<a class="heading-anchor" href="#条件跳转无条件跳转" aria-hidden="true" tabindex="-1">#</a></h4>
<pre class="code-block"><code class="hljs">// 无条件跳转
JMP addr   // 跳转到地址，地址可为代码中的地址，不过实际上手写不会出现这种东西
JMP label  // 跳转到标签，可以跳转到同一函数内的标签位置
JMP 2(PC)  // 以当前指令为基础，向前/后跳转 x 行
JMP -2(PC) // 同上

// 有条件跳转
JZ target // 如果 zero flag 被 set 过，则跳转, ZF =1
JE target // ZF = 1, 跳转到target
JNZ target // ZF = 0, 跳转到target
JNE target // ZF = 0, 跳转到target
JLS 389上一行的比较结果，左边小于右边则执行跳到0x0185地址处(十进制389转换成十六进制0x0185)</code></pre>
<h4 id="伪指令">伪指令<a class="heading-anchor" href="#伪指令" aria-hidden="true" tabindex="-1">#</a></h4>
<pre class="code-block"><code class="hljs">PCDATA $N, $A 序号为N的全局常量和PC地址A绑定
FUNCDATA $N, $A 序号为N的函数常量和PC地址A绑定</code></pre>
<h4 id="调用类指令">调用类指令<a class="heading-anchor" href="#调用类指令" aria-hidden="true" tabindex="-1">#</a></h4>
<pre class="code-block"><code class="hljs">CALL FUNCA //将当前执行指令的下一条指令地址保存在栈顶，并跳转到函数A的入口
RET        // 将栈顶保存的返回地址弹出，并跳转到返回地址 </code></pre>
<h4 id="指令集">指令集<a class="heading-anchor" href="#指令集" aria-hidden="true" tabindex="-1">#</a></h4>
<p><a href="https://github.com/golang/arch/blob/master/x86/x86.csv" target="_blank" rel="noopener noreferrer">https://github.com/golang/arch/blob/master/x86/x86.csv</a></p>
<h3 id="寄存器">寄存器<a class="heading-anchor" href="#寄存器" aria-hidden="true" tabindex="-1">#</a></h3>
<h4 id="通用寄存器">通用寄存器<a class="heading-anchor" href="#通用寄存器" aria-hidden="true" tabindex="-1">#</a></h4>
<p><a href="https://docs.oracle.com/cd/E19205-01/821-2506/gituv/index.html" target="_blank" rel="noopener noreferrer">amd64通用寄存器信息</a></p>
<h4 id="伪寄存器">伪寄存器<a class="heading-anchor" href="#伪寄存器" aria-hidden="true" tabindex="-1">#</a></h4>
<p>go汇编有以下几种伪寄存器  </p>
<ul>
<li>FP: Frame pointer: arguments and locals 参数和变量位置</li>
<li>PC: Program counter: jumps and branches. 程序计数器，调整和分支使用 </li>
<li>SB: Static base pointer: global symbols. 全局变量等的位置</li>
<li>SP: Stack pointer: top of stack. 栈顶, symbol+offset(SP)形式，则表示伪寄存器 SP。如果是 offset(SP)则表示硬件寄存器 SP, 对于使用go tool 工具生成的汇编，其中的SP一律是硬件寄存器SP</li>
<li>BP: 表示函数调用栈的 起始栈底 (栈的方向从大到小，真 SP 表示栈顶)，记录当前函数栈帧的 结束位置</li>
</ul>
<p>伪寄存器内存模型
<img src="/remote/a83dd7b25817c142.png" alt="" loading="lazy" decoding="async"></p>
<h3 id="变量声明">变量声明<a class="heading-anchor" href="#变量声明" aria-hidden="true" tabindex="-1">#</a></h3>
<p>汇编里的变量一般存储在.rodata或.data段中。一般是程序里的已初始化的全局const、var、static变量/常量。
			</p>
<pre class="code-block"><code class="hljs">DATA    symbol+offset(SB)/width, value </code></pre>
<p>其中 symbol 为变量在汇编语言中对应的标识符，offset 是符号开始地址的偏移量，width 是要初始化内存的宽度大小，value 是要初始化的值。其中当前包中 Go 语言定义的符号 symbol，在汇编代码中对应 ·symbol，其中 “·” 中点符号为一个特殊的 unicode 符号。</p>
<p>使用GLOBAL指令将变量导出可以供其他代码引用   </p>
<pre class="code-block"><code class="hljs">GLOBL symbol(SB), width</code></pre>
<p>其中 symbol 对应汇编中符号的名字，width 为符号对应内存的大小。用以下命令将汇编中的 ·Id 变量导出：</p>
<pre class="code-block"><code class="hljs">GLOBL ·Id, $8</code></pre>
<pre class="code-block"><code class="hljs">var const_id int // readonly

#include &quot;textflag.h&quot;

GLOBL ·const_id(SB),NOPTR|RODATA,$8
DATA  ·const_id+0(SB)/8,$9527</code></pre>
<p>我们使用 #include 语句包含定义标志的 &quot;textflag.h&quot; 头文件（和 C 语言中预处理相同）。然后 GLOBL 汇编命令在定义变量时，给变量增加了 NOPTR 和 RODATA 两个标志（多个标志之间采用竖杠分割），表示变量中没有指针数据同时定义在只读数据段。</p>
<p>变量一般也叫可取地址的值，但是 const_id 虽然可以取地址，但是确实不能修改。不能修改的限制并不是由编译器提供，而是因为对该变量的修改会导致对只读内存段进行写，从而导致异常</p>
<h3 id="函数">函数<a class="heading-anchor" href="#函数" aria-hidden="true" tabindex="-1">#</a></h3>
<pre class="code-block"><code class="hljs">// func add(a, b int) int
//   =&gt; 该声明定义在同一个 package 下的任意 .go 文件中
//   =&gt; 只有函数头，没有实现
TEXT pkgname·add(SB), NOSPLIT, $0-8
    MOVQ a+0(FP), AX
    MOVQ b+8(FP), BX
    ADDQ AX, BX
    MOVQ BX, ret+16(FP)
    RET

                              参数及返回值大小
                                  | 
 TEXT pkgname·add(SB),NOSPLIT,$32-32
       |        |               |
      包名     函数名         栈帧大小(局部变量+可能需要的额外调用函数的参数空间的总大小，但不包括调用其它函数时的 ret address 的大小)

</code></pre>
<p>NOSPLIT: 向编译器表明不应该插入 stack-split 的用来检查栈需要扩张的前导指令。在我们 add 函数的这种情况下，编译器自己帮我们插入了这个标记: 它足够聪明地意识到，由于 add 没有任何局部变量且没有它自己的栈帧，所以一定不会超出当前的栈。不然，每次调用函数时，在这里执行栈检查就是完全浪费 CPU 时间了</p>
<h4 id="函数调用关系">函数调用关系<a class="heading-anchor" href="#函数调用关系" aria-hidden="true" tabindex="-1">#</a></h4>
<pre class="code-block"><code class="hljs">                                       caller
                                 +------------------+
                                 |                  |
       +----------------------&gt;  --------------------
       |                         |                  |
       |                         | caller parent BP |
       |           BP(pseudo SP) --------------------
       |                         |                  |
       |                         |   Local Var0     |
       |                         --------------------
       |                         |                  |
       |                         |   .......        |
       |                         --------------------
 caller stack frame              |                  |
       |                         |   Local VarN     | 
       |                         |------------------| 
       |                         |                  |   
       |                         |   callee arg1(n) |     
       |                         |------------------|   
       |                         |                  | 
       |                         |   callee arg0    |  
       |      SP(Real Register)  ----------------------------------------------+   FP(virtual register)
       |                         |                  |                          |
       |                         |   return addr    |  parent return address   |
       +----------------------&gt;  +------------------+---------------------------    &lt;-------------------------------+
                                                    |  caller BP               |                                    |
                                                    |  (caller frame pointer)  |                                    |
                                     BP(pseudo SP)  ----------------------------                                    |
                                                    |                          |                                    |
                                                    |     Local Var0           |                                    |
                                                    ----------------------------                                    |
                                                    |                          |
                                                    |     Local Var1           |
                                                    ----------------------------                            callee stack frame
                                                    |                          |
                                                    |       .....              |
                                                    ----------------------------                                    |
                                                    |                          |                                    |
                                                    |     Local VarN           |                                    |
                                  SP(Real Register) ----------------------------                                    |
                                                    |                          |                                    |
                                                    |                          |                                    |
                                                    +--------------------------+    &lt;-------------------------------+

                                                              callee</code></pre>
<h3 id="样例">样例<a class="heading-anchor" href="#样例" aria-hidden="true" tabindex="-1">#</a></h3>
<p>样例一</p>
<p>关于BP的操作的含义可以看一下<a href="https://chai2010.cn/advanced-go-programming-book/ch3-asm/ch3-06-func-again.html" target="_blank" rel="noopener noreferrer">再论函数</a>这篇内容介绍</p>
<pre class="code-block"><code class="hljs">package main


const f = 11

func add(a, b int) int{
    sum := 0 // 不设置该局部变量sum，add栈空间大小会是0
    sum = a+b
    return sum
}
func main(){
    println(add(1,2))
    println(f)
}

go tool compile -N -l -S main.go

&quot;&quot;.add STEXT nosplit size=60 args=0x18 locals=0x10 funcid=0x0
	0x0000 00000 (main.go:6)	TEXT	&quot;&quot;.add(SB), NOSPLIT|ABIInternal, $16-24 ;; 16表示该函数栈大小16字节，24是函数入参和返回值的总大小
	0x0000 00000 (main.go:6)	SUBQ	$16, SP ;; 生成add栈空间
	0x0004 00004 (main.go:6)	MOVQ	BP, 8(SP) ;; 首先是将 BP 寄存器保持到多分配的 8 字节栈空间
	0x0009 00009 (main.go:6)	LEAQ	8(SP), BP ;; 然后将 8(SP) 地址重新保持到了 BP 寄存器中
	0x000e 00014 (main.go:6)	FUNCDATA	$0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
	0x000e 00014 (main.go:6)	FUNCDATA	$1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
	0x000e 00014 (main.go:6)	MOVQ	$0, &quot;&quot;.~r2+40(SP) ;;初始化返回值
	0x0017 00023 (main.go:7)	MOVQ	$0, &quot;&quot;.sum(SP) ;; 局部变量sum赋为0
	0x001f 00031 (main.go:8)	MOVQ	&quot;&quot;.a+24(SP), AX ;; 取参数a
	0x0024 00036 (main.go:8)	ADDQ	&quot;&quot;.b+32(SP), AX ;; 等价于AX=a+b
	0x0029 00041 (main.go:8)	MOVQ	AX, &quot;&quot;.sum(SP) ;; 赋值局部变量sum
	0x002d 00045 (main.go:9)	MOVQ	AX, &quot;&quot;.~r2+40(SP) ;; 设置返回值
	0x0032 00050 (main.go:9)	MOVQ	8(SP), BP ;; BP 指令则是从栈中恢复之前备份的前 BP 寄存器的值
	0x0037 00055 (main.go:9)	ADDQ	$16, SP ;; 清除add栈空间
	0x003b 00059 (main.go:9)	RET 
	0x0000 48 83 ec 10 48 89 6c 24 08 48 8d 6c 24 08 48 c7  H...H.l$.H.l$.H.
	0x0010 44 24 28 00 00 00 00 48 c7 04 24 00 00 00 00 48  D$(....H..$....H
	0x0020 8b 44 24 18 48 03 44 24 20 48 89 04 24 48 89 44  .D$.H.D$ H..$H.D
	0x0030 24 28 48 8b 6c 24 08 48 83 c4 10 c3              $(H.l$.H....
&quot;&quot;.main STEXT size=144 args=0x0 locals=0x28 funcid=0x0
	0x0000 00000 (main.go:11)	TEXT	&quot;&quot;.main(SB), ABIInternal, $40-0
	0x0000 00000 (main.go:11)	MOVQ	(TLS), CX
	0x0009 00009 (main.go:11)	CMPQ	SP, 16(CX)
	0x000d 00013 (main.go:11)	PCDATA	$0, $-2
	0x000d 00013 (main.go:11)	JLS	134
	0x000f 00015 (main.go:11)	PCDATA	$0, $-1
	0x000f 00015 (main.go:11)	SUBQ	$40, SP ;; 生成main栈空间
	0x0013 00019 (main.go:11)	MOVQ	BP, 32(SP)
	0x0018 00024 (main.go:11)	LEAQ	32(SP), BP
	0x001d 00029 (main.go:11)	FUNCDATA	$0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
	0x001d 00029 (main.go:11)	FUNCDATA	$1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
	0x001d 00029 (main.go:12)	MOVQ	$1, (SP) ;; add入参：1
	0x0025 00037 (main.go:12)	MOVQ	$2, 8(SP) ;; add入参：2
	0x002e 00046 (main.go:12)	PCDATA	$1, $0
	0x002e 00046 (main.go:12)	CALL	&quot;&quot;.add(SB) ;; 调用add函数
	0x0033 00051 (main.go:12)	MOVQ	16(SP), AX
	0x0038 00056 (main.go:12)	MOVQ	AX, &quot;&quot;..autotmp_0+24(SP)
	0x003d 00061 (main.go:12)	NOP
	0x0040 00064 (main.go:12)	CALL	runtime.printlock(SB)
	0x0045 00069 (main.go:12)	MOVQ	&quot;&quot;..autotmp_0+24(SP), AX
	0x004a 00074 (main.go:12)	MOVQ	AX, (SP)
	0x004e 00078 (main.go:12)	CALL	runtime.printint(SB)
	0x0053 00083 (main.go:12)	CALL	runtime.printnl(SB)
	0x0058 00088 (main.go:12)	CALL	runtime.printunlock(SB)
	0x005d 00093 (main.go:12)	NOP
	0x0060 00096 (main.go:13)	CALL	runtime.printlock(SB)
	0x0065 00101 (main.go:13)	MOVQ	$11, (SP)
	0x006d 00109 (main.go:13)	CALL	runtime.printint(SB)
	0x0072 00114 (main.go:13)	CALL	runtime.printnl(SB)
	0x0077 00119 (main.go:13)	CALL	runtime.printunlock(SB)
	0x007c 00124 (main.go:14)	MOVQ	32(SP), BP
	0x0081 00129 (main.go:14)	ADDQ	$40, SP ;; 清除main栈空间
	0x0085 00133 (main.go:14)	RET
	0x0086 00134 (main.go:14)	NOP
	0x0086 00134 (main.go:11)	PCDATA	$1, $-1
	0x0086 00134 (main.go:11)	PCDATA	$0, $-2
	0x0086 00134 (main.go:11)	CALL	runtime.morestack_noctxt(SB)
	0x008b 00139 (main.go:11)	PCDATA	$0, $-1
	0x008b 00139 (main.go:11)	JMP	0
	0x0000 64 48 8b 0c 25 00 00 00 00 48 3b 61 10 76 77 48  dH..%....H;a.vwH
	0x0010 83 ec 28 48 89 6c 24 20 48 8d 6c 24 20 48 c7 04  ..(H.l$ H.l$ H..
	0x0020 24 01 00 00 00 48 c7 44 24 08 02 00 00 00 e8 00  $....H.D$.......
	0x0030 00 00 00 48 8b 44 24 10 48 89 44 24 18 0f 1f 00  ...H.D$.H.D$....
	0x0040 e8 00 00 00 00 48 8b 44 24 18 48 89 04 24 e8 00  .....H.D$.H..$..
	0x0050 00 00 00 e8 00 00 00 00 e8 00 00 00 00 0f 1f 00  ................
	0x0060 e8 00 00 00 00 48 c7 04 24 0b 00 00 00 e8 00 00  .....H..$.......
	0x0070 00 00 e8 00 00 00 00 e8 00 00 00 00 48 8b 6c 24  ............H.l$
	0x0080 20 48 83 c4 28 c3 e8 00 00 00 00 e9 70 ff ff ff   H..(.......p...
	rel 5+4 t=17 TLS+0
	rel 47+4 t=8 &quot;&quot;.add+0
	rel 65+4 t=8 runtime.printlock+0
	rel 79+4 t=8 runtime.printint+0
	rel 84+4 t=8 runtime.printnl+0
	rel 89+4 t=8 runtime.printunlock+0
	rel 97+4 t=8 runtime.printlock+0
	rel 110+4 t=8 runtime.printint+0
	rel 115+4 t=8 runtime.printnl+0
	rel 120+4 t=8 runtime.printunlock+0
	rel 135+4 t=8 runtime.morestack_noctxt+0
go.cuinfo.packagename. SDWARFCUINFO dupok size=0
	0x0000 6d 61 69 6e                                      main
&quot;&quot;..inittask SNOPTRDATA size=24
	0x0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
	0x0010 00 00 00 00 00 00 00 00                          ........
gclocals·33cdeccccebe80329f1fdbee7f5874cb SRODATA dupok size=8
	0x0000 01 00 00 00 00 00 00 00                          ........</code></pre>
<p>上述样例代码的栈空间用图来表示如下</p>
<p><img src="/remote/d947396183697db6.png" alt="" loading="lazy" decoding="async"></p>
<p>样例二  </p>
<p><code>.s</code>文件可以直接使用<code>.go</code>中定义的全局变量</p>
<pre class="code-block"><code class="hljs language-go">refer.<span class="hljs-keyword">go</span>
<span class="hljs-keyword">package</span> main

<span class="hljs-keyword">var</span> a = <span class="hljs-number">999</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">get</span><span class="hljs-params">()</span></span> <span class="hljs-type">int</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    <span class="hljs-built_in">println</span>(get())
}

refer.s
#include <span class="hljs-string">&quot;textflag.h&quot;</span>

TEXT ·get(SB), NOSPLIT, $<span class="hljs-number">0</span><span class="hljs-number">-8</span>
    MOVQ ·a(SB), AX
    MOVQ AX, ret+<span class="hljs-number">0</span>(FP)
    RET</code></pre>
<p>以上2个文件放到同一目录中，使用<code>go build -gcflags &quot;-N -l&quot; . </code>可以编译    </p>
<p>关于汇编的部分到此未知，这里也仅仅是简单的介绍下，go的汇编不仅于此，更多内容可以查看引用中的一些文章。下面就开始看看go是如何启动的</p>
<h2 id="go生命周期">go生命周期<a class="heading-anchor" href="#go生命周期" aria-hidden="true" tabindex="-1">#</a></h2>
<h3 id="build">build<a class="heading-anchor" href="#build" aria-hidden="true" tabindex="-1">#</a></h3>
<p>先从初学go时的2个命令说起，<code>go run</code>, <code>go build</code>。打开go src，从go的文件目录命名其实就能大概猜出这个目录是做什么事的，所以很明显知道 <code>src/cmd/go</code> 就是跟执行go命令有关的。可以在main.go打个断点，并设置下goland编译参数，设置运行的软件包目录为源码下的cmd/go, 然后再填写程序运行参数，试试go run 和 go build是不是走到这里。其实这里就是用go来编译go src然后用编译后的go src cmd来运行go代码。  </p>
<p><img src="/remote/82f8acde78c7aab0.png" alt="" loading="lazy" decoding="async"></p>
<p>从init函数可以看到，go的命令都是在Command结构体定义, 然后再init里初始化</p>
<p>build src/cmd/go/internal/work.runBuild()</p>
<p>run src/cmd/go/internal/work.runRun()</p>
<pre class="code-block"><code class="hljs language-go">
src/cmd/<span class="hljs-keyword">go</span>/internal/base/base.<span class="hljs-keyword">go</span>

<span class="hljs-comment">// A Command is an implementation of a go command</span>
<span class="hljs-comment">// like go build or go fix.</span>
<span class="hljs-keyword">type</span> Command <span class="hljs-keyword">struct</span> {
	<span class="hljs-comment">// Run runs the command.</span>
	<span class="hljs-comment">// The args are the arguments after the command name.</span>
	Run <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(ctx context.Context, cmd *Command, args []<span class="hljs-type">string</span>)</span></span>

	<span class="hljs-comment">// UsageLine is the one-line usage message.</span>
	<span class="hljs-comment">// The words between &quot;go&quot; and the first flag or argument in the line are taken to be the command name.</span>
	UsageLine <span class="hljs-type">string</span>

	<span class="hljs-comment">// Short is the short description shown in the &#x27;go help&#x27; output.</span>
	Short <span class="hljs-type">string</span>

	<span class="hljs-comment">// Long is the long message shown in the &#x27;go help &lt;this-command&gt;&#x27; output.</span>
	Long <span class="hljs-type">string</span>

	<span class="hljs-comment">// Flag is a set of flags specific to this command.</span>
	Flag flag.FlagSet

	<span class="hljs-comment">// CustomFlags indicates that the command will do its own</span>
	<span class="hljs-comment">// flag parsing.</span>
	CustomFlags <span class="hljs-type">bool</span>

	<span class="hljs-comment">// Commands lists the available commands and help topics.</span>
	<span class="hljs-comment">// The order here is the order in which they are printed by &#x27;go help&#x27;.</span>
	<span class="hljs-comment">// Note that subcommands are in general best avoided.</span>
	Commands []*Command
}

src/cmd/<span class="hljs-keyword">go</span>/main.<span class="hljs-keyword">go</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">init</span><span class="hljs-params">()</span></span> {
	base.Go.Commands = []*base.Command{
		bug.CmdBug,
		work.CmdBuild,
		clean.CmdClean,
		doc.CmdDoc,
		envcmd.CmdEnv,
		fix.CmdFix,
		fmtcmd.CmdFmt,
		generate.CmdGenerate,
		modget.CmdGet,
		.....
	}
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
		_ = go11tag
	flag.Usage = base.Usage
	flag.Parse()
	log.SetFlags(<span class="hljs-number">0</span>)
	<span class="hljs-comment">// 就是解析命令行参数, 解析完 args = [&quot;run/build&quot;,&quot;-o&quot;,&quot;xxx/xxx/main&quot;,&quot;xxx/xxx/main.go&quot;] 这样的数组</span>
	args := flag.Args()
	<span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(args) &lt; <span class="hljs-number">1</span> {
		base.Usage()
	}
	...


}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">invoke</span><span class="hljs-params">(cmd *base.Command, args []<span class="hljs-type">string</span>)</span></span> {
	....

	ctx := maybeStartTrace(context.Background())
	ctx, span := trace.StartSpan(ctx, fmt.Sprint(<span class="hljs-string">&quot;Running &quot;</span>, cmd.Name(), <span class="hljs-string">&quot; command&quot;</span>))
	<span class="hljs-comment">// 执行对应的命令build or run</span>
	cmd.Run(ctx, cmd, args)
	span.Done()
}
</code></pre>
<h3 id="runtime-start">runtime start<a class="heading-anchor" href="#runtime-start" aria-hidden="true" tabindex="-1">#</a></h3>
<p>Go 程序启动后需要对自身运行时进行初始化，其真正的程序入口由 runtime 包控制, 以 AMD64 架构上的 Linux 和 macOS 为例，分别位于：src/runtime/rt0_linux_amd64.s 和 src/runtime/rt0_darwin_amd64.s  </p>
<p>通过使用objdump可以查看程序入口</p>
<pre class="code-block"><code class="hljs language-bash">
➜  demo2 objdump -f demo

demo:     file format elf64-x86-64
architecture: i386:x86-64, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x000000000045ce80

➜  demo2 objdump -d demo | grep <span class="hljs-string">&quot;45ce80&quot;</span>
000000000045ce80 &lt;_rt0_amd64_linux&gt;:
  45ce80:	e9 db ca ff ff       	jmpq   459960 &lt;_rt0_amd64&gt;
</code></pre>
<pre class="code-block"><code class="hljs">
TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
	JMP	_rt0_amd64(SB)


然后跳转到_rt0_amd64, 这里是大部分ADM64系统的启动入口

// _rt0_amd64 is common startup code for most amd64 systems when using
// internal linking. This is the entry point for the program from the
// kernel for an ordinary -buildmode=exe program. The stack holds the
// number of arguments and the C-style argv.
TEXT _rt0_amd64(SB),NOSPLIT,$-8
	// SP的前2个值对应argc和argv
	MOVQ	0(SP), DI	// argc   
	LEAQ	8(SP), SI	// argv
	JMP	runtime·rt0_go(SB)
</code></pre>
<p>其中最核心的就是runtime·rt0_go, 详细的可以看具体的代码,<a href="https://github.com/yinrenxin/go/blob/fad22f60ceffcd5632b44b2bb435216ed4c82a22/src/runtime/asm_amd64.s#L148" target="_blank" rel="noopener noreferrer">src/runtime/asm_amd64.s#L148</a>，这里只总结该过程的流程,其中有涉及的go调度器，这部分单独在讲，这里明白大概意思就行。 </p>
<p><img src="/remote/717e3829ffc629ac.jpg" alt="" loading="lazy" decoding="async"></p>
<h3 id="runtime-main">runtime main<a class="heading-anchor" href="#runtime-main" aria-hidden="true" tabindex="-1">#</a></h3>
<p>下面来看看runtime.main的流程, 代码在src/runtime/proc.go </p>
<pre class="code-block"><code class="hljs language-go">
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
	<span class="hljs-comment">// 获取当前的g结构体</span>
	g := getg()

	...

	<span class="hljs-comment">//  执行栈最大限制：1GB（64位系统）或者 250MB（32位系统）</span>
	<span class="hljs-keyword">if</span> goarch.PtrSize == <span class="hljs-number">8</span> {
		maxstacksize = <span class="hljs-number">1000000000</span>
	} <span class="hljs-keyword">else</span> {
		maxstacksize = <span class="hljs-number">250000000</span>
	}

	...

	<span class="hljs-keyword">if</span> GOARCH != <span class="hljs-string">&quot;wasm&quot;</span> { <span class="hljs-comment">// no threads on wasm yet, so no sysmon</span>
		<span class="hljs-comment">// 启动系统后台监控（定期垃圾回收、抢占调度等等）wasm没有线程所以不需要设置</span>
		systemstack(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
			newm(sysmon, <span class="hljs-literal">nil</span>, <span class="hljs-number">-1</span>)
		})
	}

	... 

		<span class="hljs-comment">// 启动垃圾回收器后台操作</span>
	gcenable()

	...

	<span class="hljs-comment">// 执行用户 main 包中的 main 函数</span>
	fn := main_main <span class="hljs-comment">// make an indirect call, as the linker doesn&#x27;t know the address of the main package when laying down the runtime</span>
	fn()

	...

	exit(<span class="hljs-number">0</span>)
}
</code></pre>
<p>runtine·main的大致执行流程:  </p>
<ul>
<li>获取当前g结构体， 从tls获取</li>
<li>设置执行栈最大限制</li>
<li>启动系统后台监控（定期垃圾回收、抢占调度等等)</li>
<li>init初始化任务</li>
<li>启动gc</li>
<li>执行用户init任务</li>
<li>执行用户 main 包中的 main 函数</li>
<li>执行退出逻辑</li>
</ul>
<p>然后程序就运行到我们熟知的main.main了, 到此go如何启动的整体流程也就讲完了，当然中间还包括很多细节并未讲到。以及runtime.main是如何调度的，这些就在下一篇有关调度器的章节细细探讨。</p>
<h2 id="引用">引用<a class="heading-anchor" href="#引用" aria-hidden="true" tabindex="-1">#</a></h2>
<ul>
<li><a href="https://go.dev/doc/asm" target="_blank" rel="noopener noreferrer">Go asm</a></li>
<li><a href="https://blog.csdn.net/m0_37645820/article/details/108458414" target="_blank" rel="noopener noreferrer">汇编是深入理解 Go 的基础</a></li>
<li><a href="https://chai2010.cn/advanced-go-programming-book/ch3-asm/ch3-01-basic.html" target="_blank" rel="noopener noreferrer">Go语言高级编程</a></li>
<li><a href="https://zhuanlan.zhihu.com/p/95056679" target="_blank" rel="noopener noreferrer">万字长文深入浅出 Golang Runtime</a></li>
<li><a href="https://xuezhaojiang.github.io/LinuxKernel/lab7/lab7.html" target="_blank" rel="noopener noreferrer">Linux内核如何装载和启动一个可执行程序</a></li>
<li><a href="https://golang.design/under-the-hood/" target="_blank" rel="noopener noreferrer">Go语言原本</a></li>
<li><a href="https://fred-book.github.io/golearning/goinside/startup/startup/" target="_blank" rel="noopener noreferrer">Go进程启动</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>道德经-观后感</title>
      <link>https://www.joe.top/2022/10/06/%E8%AF%BB%E4%B9%A6/%E9%81%93%E5%BE%B7%E7%BB%8F/</link>
      <guid isPermaLink="true">https://www.joe.top/2022/10/06/%E8%AF%BB%E4%B9%A6/%E9%81%93%E5%BE%B7%E7%BB%8F/</guid>
      <pubDate>Wed, 05 Oct 2022 16:29:40 GMT</pubDate>
      <category>读书</category>
      <description>道德经 什么是道 道的起源 道可道，非常道；名可名，非常名。 无名天地之始，有名万物之母。 故常无欲以观其妙，常有欲以观其徼。 此两者，同出而异名，同谓之玄。玄之又玄，众妙之门。 物极必反，不停进取 原文： 天下皆知美之为美斯恶矣，皆知善之为善斯不善矣。 故有无相生,难易相成,长短相较(形),高下相倾,音声相贺,前后相随。…</description>
      <content:encoded><![CDATA[<h1 id="道德经">道德经<a class="heading-anchor" href="#道德经" aria-hidden="true" tabindex="-1">#</a></h1>
<h2 id="什么是道">什么是道<a class="heading-anchor" href="#什么是道" aria-hidden="true" tabindex="-1">#</a></h2>
<ul>
<li>道的起源</li>
</ul>
<pre class="code-block"><code class="hljs">道可道，非常道；名可名，非常名。
无名天地之始，有名万物之母。
故常无欲以观其妙，常有欲以观其徼。
此两者，同出而异名，同谓之玄。玄之又玄，众妙之门。</code></pre>
<ul>
<li>物极必反，不停进取</li>
</ul>
<pre class="code-block"><code class="hljs">原文：
天下皆知美之为美斯恶矣，皆知善之为善斯不善矣。
故有无相生,难易相成,长短相较(形),高下相倾,音声相贺,前后相随。
是以圣人处无为之事,行不言之,教万物作焉而不辞。
生而不有为而不恃,功成而弗居,夫唯弗居,是以不去。
注释:
1.天下皆知美之为美斯恶矣，天下所有的人都知道美好的的事物当时确实很好，但是发现到极致的时候就会出现糟糕的状态。
2.皆知善之为善斯不善矣，天下人都知道正确好的事物当时是好的，但如果这件事物好到极点那么他就会开始衰败直到最后彻底消亡。
3.(1)故有无相生,无和有是相伴而生的。眼前拥有的可能在将来某一天就会失去。
(2)难易相成，难做的事和简单的事总是相辅相成的，很难的事情挺一挺突然间就会容易许多了。
(3)长短相较，优点和缺点是相对的，反而优点会害了自己，缺点有时候也会救自己。
(4)高下相倾，高位置的人需要底下人的支持，底下的人也需要高位置人的提拔，高和低的人应该互相团结需要互相依赖的。高的人要谦虚与关爱低的人，低的人要对高的人尊重与配合。
(5)音声相贺，音代表动听的☞赞扬的，声代表刺耳的☞批评难听的，好听的和难听的是同时存在的</code></pre>
<ul>
<li>不尚贤不等于忽略人才</li>
</ul>
<pre class="code-block"><code class="hljs">原文：
不尚贤使民不争，不贵，难得之货，使民不为盗。
不限可欲使民心不乱。
是以圣人之治，虚其心，实其腹，弱其志强其骨。
常使民无知，无欲使夫，智者不敢为也。
为无为，则无不治。
感悟：
1.不尚贤。尚，单独的。一个人想取得成功就不能够只依赖个别人才，需要依赖的是一个大的团队。
2.使民不争，不要让老百姓不正当的竞争。
3.不贵，难得之货，使民不为盗。难得之货，奢侈品。
不贵，不要过分执迷于这些东西。
盗，（1）偷盗（2）小人。
4.不限可欲使民心不乱。一个领导不要把个人的喜好让下级展示，下级就会投其所好，从而使失去了做事的目标和准则。</code></pre>
<ul>
<li>努力进取</li>
</ul>
<pre class="code-block"><code class="hljs">原文：
道冲，而用之或不盈。
渊兮，似万物之宗。
挫其锐，解其纷，和其光，同其尘。
湛兮似或存。
吾不知谁之子，象帝之先。
感悟：
不能太过着急追求成功, 遵循道的规律，欲速者不达
专注做一件事</code></pre>
<pre class="code-block"><code class="hljs">原文：
天地不仁，以万物为刍狗；圣人不仁，以百姓为刍狗。
天地之间，其犹橐籥乎？虚而不屈，动而愈出。
多言数穷，不如守中。
感悟：
人生没有终点，不能只

{{TOC}}

{{TOC}}

搞一件事，不能给自己设限，博学多才
多言数穷</code></pre>
<ul>
<li>养生</li>
</ul>
<pre class="code-block"><code class="hljs">原文：
谷神不死，是谓玄牝。
玄牝之门，是谓天地根。
绵绵若存，用之不勤。
感悟：
不能太累，需要适当的放松，养生
持之以恒</code></pre>
<ul>
<li>自私</li>
</ul>
<pre class="code-block"><code class="hljs">原文：
天长地久。
天地所以能长且久者，
以其不自生，故能长生。
是以圣人后其身而身先，外其身而身存。
非以其无私邪！故能成其私
感悟：
需要考虑自己的利益，需要有一点自私</code></pre>
<ul>
<li>上善若水</li>
</ul>
<pre class="code-block"><code class="hljs">原文：
上善若水。水善利万物而不争，处众人之所恶，故几于道。
居善地，心善渊，与善仁，言善信，政善治，事善能，动善时。
夫唯不争，故无尤。
感悟：
上善若水，主要说明人要像水一样，水是最接近道的状态
水最为接近于道:
居善地（势）
心善渊（完整深入，心思深邃）
与善仁（给与别人有利的，关心赞美鼓励欣赏别人）
言善信（讲话经常讲正确的别人爱听的话守信）
正善治（正直善良，同时也要灵活解决问题，水也是不好欺负的，大多数情况是利于万物，惹急了也有力量产生海啸和洪水。）
事善能（可大可小可方可圆，君子不器，以七十二变应变）
动善时（时机。注意观察，逐步进行，小心投入。第二波成功率更高）</code></pre>
<ul>
<li>中国文化的核心智慧与目标</li>
</ul>
<pre class="code-block"><code class="hljs">原文：
持而盈之，不如其已。
揣而锐之，不可长保。
金玉满堂，莫之能守。
富贵而骄，自遗其咎。
功遂身退，天之道也。
感悟：
持而盈之，不如其已。
（自满）
揣而锐之，不可长保。
（极端，太急）
金玉满堂，莫之能守。
（不把自己财富展示给别人太多，不露富，守的财富才更长）
富贵而骄，自遗其疚。
 (不要骄傲)
功遂身退，天之道也。
 (功成身退)</code></pre>
<ul>
<li>道家抱抱的智慧</li>
</ul>
<pre class="code-block"><code class="hljs">原文：
载营魄抱一，能无离乎？
抟气致柔，能如婴儿乎？
涤除玄览，能无疵乎？
爱民治国，能无知乎？
天门开阖，能为雌乎？
明白四达，能无为乎？
生之畜之。
生而不有，为而不恃，长而不宰。
是谓玄德。
感悟：
不仅要关注身体健康，更要关注心理的健康
专注</code></pre>
<ul>
<li>无胜过有</li>
</ul>
<pre class="code-block"><code class="hljs">原文：
三十辐共一毂，当其无，有车之用；
埏埴以为器，当其无，有器之用；
凿户牖以为室，当其无，有室之用。
故有之以为利，无之以为用。
感悟：
无才有成长的空间，往更高的地方看，不断为宏伟的未来规划</code></pre>
<ul>
<li><ol start="12">
<li>老子的肚子和眼睛</li>
</ol>
</li>
</ul>
<pre class="code-block"><code class="hljs">原文：
五色令人目盲，五音令人耳聋，五味令人口爽，驰骋田猎令人心发狂，
爽，差失也，失口之用，故谓之爽。夫耳、目、口、心皆顺其性也，不以顺性命反以伤自然，故曰盲、聋、爽，狂也。
感受：
针对新出现的事务，不能看以前的经验。尝试新事务的时候要小心翼翼</code></pre>
<ul>
<li>13 宠辱不经大错特错</li>
</ul>
<pre class="code-block"><code class="hljs">原文: </code></pre>
<ul>
<li><ol start="74">
<li>民不畏死</li>
</ol>
</li>
</ul>
<pre class="code-block"><code class="hljs">民不畏死，奈何以死惧之？若使民常畏死，而为奇者，吾得执而杀之，孰敢？常有司杀者杀。而代司杀者杀，是代大匠斫 。夫代大匠斫者，希有不伤其手矣。</code></pre>
<ul>
<li><ol start="75">
<li>越努力事情越做不好吗</li>
</ol>
</li>
</ul>
<pre class="code-block"><code class="hljs">民之饥，以其上食税之多，是以饥。民之难治，以其上之有为，是以难治。民之轻死，以其上求生之厚，是以轻死。夫唯无以生为者，是贤于贵生。</code></pre>
<ul>
<li><ol start="76">
<li>柔弱有时会比强大好</li>
</ol>
</li>
</ul>
<pre class="code-block"><code class="hljs">人之生也柔弱，其死也坚强。万物草木之生也柔弱，其死也枯槁。故坚强者死之徒，柔弱者生之徒。是以兵强则灭，木强则折。强大处下，柔弱处上。
感受：
养生，身体是革命的本钱</code></pre>
<ul>
<li><ol start="77">
<li>为什么圣人功成而不处</li>
</ol>
</li>
</ul>
<pre class="code-block"><code class="hljs">天之道，其犹张弓乎？高者抑之，下者举之；有余者损之，不足者补之。天之道，损有余而补不足。人之道则不然，损不足以奉有余。孰能以有余奉天下？唯有道者。是以圣人，为而不恃，功成而不处，其不欲见贤耶。</code></pre>
<ul>
<li><ol start="78">
<li>能受国之垢是谓天下主</li>
</ol>
</li>
</ul>
<pre class="code-block"><code class="hljs">天下莫柔弱于水，而攻坚强者莫之能胜，其无以易[1]之。
    弱之胜强，柔之胜刚，天下莫不知，莫能行。
    是以圣人云：“能受国之垢，是谓社稷主；能受国之不祥，是谓天下王。”正言若反。</code></pre>
]]></content:encoded>
    </item>
    <item>
      <title>网络体系</title>
      <link>https://www.joe.top/2022/10/06/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/network/%E7%BD%91%E7%BB%9C%E7%A8%8B%E5%BA%8F%E4%BD%93%E7%B3%BB/</link>
      <guid isPermaLink="true">https://www.joe.top/2022/10/06/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/network/%E7%BD%91%E7%BB%9C%E7%A8%8B%E5%BA%8F%E4%BD%93%E7%B3%BB/</guid>
      <pubDate>Wed, 05 Oct 2022 16:29:40 GMT</pubDate>
      <category>网络</category>
      <description>网络整体概览 超时 一个http的生命周期中的各个阶段的超时</description>
      <content:encoded><![CDATA[<h2 id="网络整体概览">网络整体概览<a class="heading-anchor" href="#网络整体概览" aria-hidden="true" tabindex="-1">#</a></h2>
<p><img src="/remote/e49912f2f3eb003c.png" alt="网络程序体系结构" loading="lazy" decoding="async"></p>
<h2 id="超时">超时<a class="heading-anchor" href="#超时" aria-hidden="true" tabindex="-1">#</a></h2>
<p>一个http的生命周期中的各个阶段的超时</p>
<p><img src="/remote/36f07048f8c72150.png" alt="http超时" loading="lazy" decoding="async"></p>
]]></content:encoded>
    </item>
    <item>
      <title>许式伟的架构课-笔记</title>
      <link>https://www.joe.top/2022/10/06/%E8%AF%BB%E4%B9%A6/%E8%AE%B8%E5%BC%8F%E4%BC%9F%E7%9A%84%E6%9E%B6%E6%9E%84%E8%AF%BE/</link>
      <guid isPermaLink="true">https://www.joe.top/2022/10/06/%E8%AF%BB%E4%B9%A6/%E8%AE%B8%E5%BC%8F%E4%BC%9F%E7%9A%84%E6%9E%B6%E6%9E%84%E8%AF%BE/</guid>
      <pubDate>Wed, 05 Oct 2022 16:29:40 GMT</pubDate>
      <category>课程</category>
      <description>首先，基础架构是业务架构的一部分。不了解基础架构，你就不知道自己写的软件背后都发生了什么，你就无法掌控全局，这对你思考架构演进会有很大的局限性，因为你是 “戴着脚镣跳舞”。</description>
      <content:encoded><![CDATA[<h2 id="许式伟的架构课">许式伟的架构课<a class="heading-anchor" href="#许式伟的架构课" aria-hidden="true" tabindex="-1">#</a></h2>
<blockquote>
<p>首先，基础架构是业务架构的一部分。不了解基础架构，你就不知道自己写的软件背后都发生了什么，你就无法掌控全局，这对你思考架构演进会有很大的局限性，因为你是 “戴着脚镣跳舞”。</p>
</blockquote>
<blockquote>
<p>其次，基础架构是最宏大的架构实践案例，需要我们好好感悟。我们不只是要知道基础架构怎么用，还应该理解它为何演变成今天这样。对于优雅的基础架构设计，我们应该要有强烈的共鸣，惊喜赞叹。如果你没有感觉，说明你对架构思维也还没有感觉，也就更不可能构建出极致的架构。  </p>
</blockquote>
<p>一个架构师需要掌握的三个技能:</p>
<ol>
<li>理解代码的能力</li>
<li>读代码的能力</li>
<li>抽象系统的能力</li>
</ol>
<h1 id="架构思维">架构思维<a class="heading-anchor" href="#架构思维" aria-hidden="true" tabindex="-1">#</a></h1>
<h2 id="服务治理">服务治理<a class="heading-anchor" href="#服务治理" aria-hidden="true" tabindex="-1">#</a></h2>
<ol>
<li>sre 变更是故障之源</li>
<li>一个完善的监控系统，并不是 “报警很多很完善” 的系统，而是 “信噪比高，有故障就报警，有报警就直指根因” 的监控系统。</li>
<li>监控的4个指标:延迟、流量、错误和饱和度</li>
<li>通过采样+汇总可以降低成本</li>
<li>少就是指数级的多</li>
<li>灰度发布，减少风险</li>
<li>针对长期会有风险的故障，需要进行严格的白盒代码审核和全面的覆盖率测试</li>
<li>日志有助于故障排查</li>
<li>过载后如何保护系统</li>
<li>过载如何监控系统，可以基于某个资源的阈值比如CPU，而不仅仅是基于QPS设定</li>
</ol>
<p>未来服务端技术的演进会走向何方？服务治理系统的迭代，最终将让我们达到这样的状态。</p>
<ul>
<li>任何业务都可以轻松达到 7x24 小时不间断服务。高并发？高可用？高可靠？小菜一碟。做业务都足够的傻瓜化。</li>
<li>服务端工程师？不存在的，我们要的只是 SQL 工程师。</li>
<li>做一个新的有状态的存储中间件虽然比做业务麻烦一点，但是，一方面也没有多少新的存储中间件需要做的，另一方面做一个存储中间件有足够便捷的辅助设施，也不会比今天做一个内存中的哈希表难多少。</li>
</ul>
<p>最终软件开发不在区分后端和前端</p>
<h2 id="架构设计">架构设计<a class="heading-anchor" href="#架构设计" aria-hidden="true" tabindex="-1">#</a></h2>
<p>架构设计的基本准则</p>
<ul>
<li>KISS：简单比复杂好；</li>
<li>Modularity：着眼于模块而不是框架；</li>
<li>Testable：保证可测试性；</li>
<li>Orthogonal Decomposition：正交分解。</li>
</ul>
<p>其中一个评论感觉也非常重要: </p>
<blockquote>
<p>其实还有一种耦合度经常被人忽视，就是技术耦合度。我们相信，无论是今天多么流行的技术，未来都有失去竞争力的一天，但要开一家长达数十年甚至数百年的技术公司，不可能长期使用已经失去竞争力的技术，必然涉及到技术的多次演进。如果公司的关键性产品与某一种技术耦合度太高，则当该技术失去竞争力或彻底淘汰后，技术迁移成本过高，极易造成迁移彻底失败。所以我从不看好那种一个 Git 库里数十万甚至数百万行 xx 语言或者基于 xx 框架编写的代码，要起一个新项目还必须去引用这个库里的代码不可，这种项目不可能活很久。如果这个项目是公司的关键性产品，那么这个公司也活不了很久。</p>
</blockquote>
<ul>
<li>比框架（架构图）更重要的是数据结构，比数据结构更重要的是接口。</li>
<li>需求分析-&gt;概要设计-&gt;模块的详细设计</li>
<li>任何功能都可已做正交分解</li>
<li>核心系统一定要最小化</li>
<li>坚持不要往核心系统增加新功能，这样业务系统就不可能有臭味</li>
</ul>
<h3 id="开闭原则">开闭原则<a class="heading-anchor" href="#开闭原则" aria-hidden="true" tabindex="-1">#</a></h3>
<p>架构设计的核心工具</p>
<ul>
<li>组合,通过小业务组合大业务</li>
<li>开闭原则</li>
</ul>
<p>开闭原则总结</p>
<ul>
<li>模块的业务要稳定</li>
<li>对于模块变化的部分，可以通过回调函数或者接口开发出去。复制点的可以通过插件把系统分解</li>
</ul>
<p>单一职责原则和开闭原则的区别：</p>
<ul>
<li>单一职责：强调每个模块只负责一个业务</li>
<li>开闭原则：强调把模块业务的变化点抽离出来，包给其他的模块</li>
</ul>
<p>他们说谈论的其实是同一个问题的两面</p>
<h3 id="接口设计准则">接口设计准则<a class="heading-anchor" href="#接口设计准则" aria-hidden="true" tabindex="-1">#</a></h3>
<ul>
<li>如无必要，勿增实体</li>
<li>对于模块的使用界面，推荐KISS原则</li>
<li>对于模块的依赖，遵循最小依赖原则</li>
</ul>
<h1 id="软件工程">软件工程<a class="heading-anchor" href="#软件工程" aria-hidden="true" tabindex="-1">#</a></h1>
<h2 id="软件工程-2">软件工程<a class="heading-anchor" href="#软件工程-2" aria-hidden="true" tabindex="-1">#</a></h2>
<ul>
<li>软件工程是快速变化的，不确定的。软件项目的管理又期望达到确定性。这就是软件工程的核心点</li>
</ul>
<h3 id="软件外包">软件外包<a class="heading-anchor" href="#软件外包" aria-hidden="true" tabindex="-1">#</a></h3>
<ul>
<li>我们尽可能不要做太多事情。非核心竞争力相关的，能够外包的我们尽可能外包。</li>
<li>在外包选择上，我们优先选择云服务，次选开源，最后才考虑传统的外包。</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>职业规划-观后感</title>
      <link>https://www.joe.top/2022/10/06/%E8%AF%BB%E4%B9%A6/%E8%81%8C%E4%B8%9A%E8%A7%84%E5%88%92/</link>
      <guid isPermaLink="true">https://www.joe.top/2022/10/06/%E8%AF%BB%E4%B9%A6/%E8%81%8C%E4%B8%9A%E8%A7%84%E5%88%92/</guid>
      <pubDate>Wed, 05 Oct 2022 16:29:40 GMT</pubDate>
      <category>读书</category>
      <description>职业规划</description>
      <content:encoded><![CDATA[<p>职业规划</p>
<blockquote>
<p>《远见》</p>
</blockquote>
<ul>
<li><p>我如何避免被机器取代？</p>
</li>
<li><p>我能在哪里以什么方式找到工作？</p>
</li>
<li><p>未来我将如何分配时间？</p>
</li>
<li><p>我会把钱花光吗？</p>
</li>
<li><p>工作如何能让我更幸福？</p>
</li>
<li><p>职业生涯将近40年</p>
</li>
<li><p>精通一门技能所需的时间仅1w小时</p>
</li>
<li><p>40岁之后能赚到个人财富百分比85%以上</p>
</li>
<li><p>社交货币200个左右</p>
</li>
<li><p>职场支持者的人数3， 4个</p>
</li>
</ul>
<pre class="code-block"><code class="hljs">职场燃料之可迁移技能：学术学位、专业证书，语言，优点，情商，“人才账户”。
职场燃料之有意义的经验：个人旅行，海外工作经验，企业管理、创业经验，社区、志愿者活动，做出个人贡献的项目，公开演讲、写作、表演的经验，教学、咨询、指导的经验，工作之余的热情所在……
职场燃料之持久的关系：联系人，专家团，关键同事，支持者。
职场望远镜
THE LONG VIEW
年度职业价值评估问题1：我是否正在学习和成长？
年度职业价值评估问题2：我是否正在对某些人、现在的公司，乃至整个社会拥有影响力？
年度职业价值评估问题3：我体验到乐趣了吗？
年度职业价值评估问题4：我是否得到了适当的奖励，并创造了经济价值？
</code></pre>
<ul>
<li>合理利用时间，是否合适可以使用100小时来进行测试</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Redis核心技术和实战-笔记</title>
      <link>https://www.joe.top/2022/10/06/%E8%AF%BB%E4%B9%A6/Redis%E6%A0%B8%E5%BF%83%E6%8A%80%E6%9C%AF%E4%B8%8E%E5%AE%9E%E6%88%98/</link>
      <guid isPermaLink="true">https://www.joe.top/2022/10/06/%E8%AF%BB%E4%B9%A6/Redis%E6%A0%B8%E5%BF%83%E6%8A%80%E6%9C%AF%E4%B8%8E%E5%AE%9E%E6%88%98/</guid>
      <pubDate>Wed, 05 Oct 2022 16:29:40 GMT</pubDate>
      <category>课程</category>
      <description>Redis核心技术与实战</description>
      <content:encoded><![CDATA[<p>Redis核心技术与实战</p>
<h2 id="redis功能">Redis功能<a class="heading-anchor" href="#redis功能" aria-hidden="true" tabindex="-1">#</a></h2>
<h3 id="hyperloglog">hyperloglog<a class="heading-anchor" href="#hyperloglog" aria-hidden="true" tabindex="-1">#</a></h3>
<p>用户统计基数的数据集合类型</p>
<p>pfadd 增加计算
pfcount 统计数据</p>
<p>example:
pfadd uv user1
pfcout uv</p>
<p>pfmerge 合并多个值</p>
<p>code:</p>
<pre class="code-block"><code class="hljs">PFADD page1:uv user1 user2 user3

PFCOUNT page1:uv</code></pre>
<h3 id="布隆过滤器">布隆过滤器<a class="heading-anchor" href="#布隆过滤器" aria-hidden="true" tabindex="-1">#</a></h3>
<p>场景：判断某个值在不在集合中，有误差，比如推荐系统，过滤用户已经看过的新闻</p>
<p>redis命令</p>
<p>添加元素
bf.add news user1</p>
<p>判断是否在集合中
bf.exists news users</p>
<p>添加多个元素
bf.madd news user1 user2 user3</p>
<p>判断多个元素是否在集合中
bf.mexists news user1 user2 user3</p>
<h3 id="redis做消息队列">redis做消息队列<a class="heading-anchor" href="#redis做消息队列" aria-hidden="true" tabindex="-1">#</a></h3>
<pre class="code-block"><code class="hljs">基于List做消息队列
LPUSH 入队，RPOP出队
BRPOP阻塞式读取，可以更省CPU开销
如果读取消息后，redis挂了，可以使用BRPOPLPUSH命名，让消费者程序从一个List中读取消息，同时Redis会把这个图消息再插入到另一个List留存，这样子，redis重启后，就可以从备份的List中读取消息并进行处理

基于Streams的消息队列解决方案
Redis5.0才有
操作命令：
XADD:  插入消息，保证有序，可以自动生成全局唯一ID
XREAD: 用于读取消息，可以按ID读取消息
XREADGROUP: 按消费组形式读取消息
XPENDING和XACK: XPENDING命令可以用来查询每个消费组内所有消费者已读取但尚未确认的消息，XACK命令用于向消息队列确认消息处理已完成

// * 号让系统自动生成id号,也可以自己指定
XADD mqstream * repo 5
&quot;1599203861727-0&quot;



// 从id号开始读取后续的所有数据，block类似BRPOP，阻塞读取，block后面设置的是时间，超过这个时间
// 还没有读取到，则返回空
XREAD BLOCK 100 STREAMS  mqstream 1599203861727-0
1) 1) &quot;mqstream&quot;
   2) 1) 1) &quot;1599274912765-0&quot;
         2) 1) &quot;repo&quot;
            2) &quot;3&quot;
      2) 1) &quot;1599274925823-0&quot;
         2) 1) &quot;repo&quot;
            2) &quot;2&quot;
      3) 1) &quot;1599274927910-0&quot;
         2) 1) &quot;repo&quot;
            2) &quot;1&quot;
            
            
            
// $ 表示读取最新的消息
XREAD block 10000 streams mqstream $
(nil)
(10.00s)



Streams的消费组的概念

todo: 消息队列中的消息一旦被消费组里的一个消费者读取了，就不能再被消费组内的其他消费者读取了
// 创建一个group1的消费组, 该消费组的消息队列为mqstream
XGROUP create mqstream group1 0
OK


// 让group1消费组里的消费者consumer1从mqstream中读取所有消息
// ‘&gt;’ 表示从第一条尚未被消费的消息开始读取
127.0.0.1:6379&gt; xreadgroup group group1 consumer1 streams mqstream &gt;
1) 1) &quot;mqstream&quot;
   2) 1) 1) &quot;1630813716027-0&quot;
         2) 1) &quot;repo&quot;
            2) &quot;5&quot;
      2) 1) &quot;1630813794727-0&quot;
         2) 1) &quot;repo&quot;
            2) &quot;2&quot;
      3) 1) &quot;1630813797519-0&quot;
         2) 1) &quot;repo&quot;
            2) &quot;1&quot;
      4) 1) &quot;1630814298480-0&quot;
         2) 1) &quot;repo1&quot;
            2) &quot;1&quot;
            
            
            
// group2中的consumer1，2，3各自读取一条消息
XREADGROUP group group2 consumer1 count 1 streams mqstream &gt;
1) 1) &quot;mqstream&quot;
   2) 1) 1) &quot;1599203861727-0&quot;
         2) 1) &quot;repo&quot;
            2) &quot;5&quot;

XREADGROUP group group2 consumer2 count 1 streams mqstream &gt;
1) 1) &quot;mqstream&quot;
   2) 1) 1) &quot;1599274912765-0&quot;
         2) 1) &quot;repo&quot;
            2) &quot;3&quot;

XREADGROUP group group2 consumer3 count 1 streams mqstream &gt;
1) 1) &quot;mqstream&quot;
   2) 1) 1) &quot;1599274925823-0&quot;
         2) 1) &quot;repo&quot;
            2) &quot;2&quot;
            
            
//一旦某一条消息被处理了，消费者就可以使用xack通知Streams把这条消息删除

 XACK mqstream group2 1599274912765-0
(integer) 1
XPENDING mqstream group2 - + 10 consumer2
(empty list or set)</code></pre>
<h3 id="清理内存碎片">清理内存碎片<a class="heading-anchor" href="#清理内存碎片" aria-hidden="true" tabindex="-1">#</a></h3>
<p>查看内存碎片</p>
<pre class="code-block"><code class="hljs">INFO memory
# Memory
used_memory:1073741736
used_memory_human:1024.00M
used_memory_rss:1997159792
used_memory_rss_human:1.86G
…
mem_fragmentation_ratio:1.86
</code></pre>
<p>这里有一个 mem_fragmentation_ratio 的指标，它表示的就是 Redis 当前的内存碎片率。那么，这个碎片率是怎么计算的呢？其实，就是上面的命令中的两个指标 used_memory_rss 和 used_memory 相除的结果。</p>
<pre class="code-block"><code class="hljs">
mem_fragmentation_ratio = used_memory_rss/ used_memory</code></pre>
<p>首先，Redis 需要启用自动内存碎片清理，可以把 activedefrag 配置项设置为 yes，命令如下：config set activedefrag yes</p>
<ul>
<li>active-defrag-ignore-bytes 100mb：表示内存碎片的字节数达到 100MB 时，开始清理；active-defrag-threshold-lower 10：表示内存碎片空间占操作系统分配给 Redis 的总空间比例达到 10% 时，开始清理。</li>
<li>active-defrag-cycle-min 25： 表示自动清理过程所用 CPU 时间的比例不低于 25%，保证清理能正常开展；</li>
<li>active-defrag-cycle-max 75：表示自动清理过程所用 CPU 时间的比例不高于 75%，一旦超过，就停止清理，从而避免在清理时，大量的内存拷贝阻塞 Redis，导致响应延迟升高。</li>
</ul>
<h3 id="缓存区">缓存区<a class="heading-anchor" href="#缓存区" aria-hidden="true" tabindex="-1">#</a></h3>
<p>输入缓冲区溢出:</p>
<p>原因:</p>
<ol>
<li>写入了 bigkey，比如一下子写入了多个百万级别的集合类型数据；</li>
<li>服务器端处理请求的速度过慢，例如，Redis 主线程出现了间歇性阻塞，无法及时处理正常发送的请求，导致客户端发送的请求在缓冲区越积越多。
解决办法</li>
<li>调整客户端缓存大小，这个一般是1GB，但是对于一般的生成环境够用了，也没有参数来调整</li>
<li>避免客户端写入bigkey</li>
</ol>
<p>输出缓冲区溢出:  </p>
<p>原因:</p>
<ol>
<li>服务器端返回 bigkey 的大量结果；</li>
<li>执行了 MONITOR 命令；</li>
<li>缓冲区大小设置得不合理。</li>
</ol>
<p>解决办法:  </p>
<ol>
<li>避免 bigkey 操作返回大量数据结果；</li>
<li>避免在线上环境中持续使用 MONITOR 命令。</li>
<li>使用 client-output-buffer-limit 设置合理的缓冲区大小上限，或是缓冲区连续写入时间和写入量上限。</li>
</ol>
<p>主从复制也会导致缓存区溢出</p>
<ol>
<li>控制主节点保存的数据量大小</li>
<li>设置合理的复制缓存区大小</li>
<li>控制从节点数量</li>
</ol>
<h2 id="redis缓存">Redis缓存<a class="heading-anchor" href="#redis缓存" aria-hidden="true" tabindex="-1">#</a></h2>
<p>Redis用户缓存需要解决如下几个问题:</p>
<ol>
<li>Redis 缓存具体是怎么工作的？</li>
<li>Redis 缓存如果满了，该怎么办？</li>
<li>为什么会有缓存一致性、缓存穿透、缓存雪崩、缓存击穿等异常，该如何应对？</li>
<li>Redis 的内存毕竟有限，如果用快速的固态硬盘来保存数据，可以增加缓存的数据量，那么，Redis 缓存可以使用快速固态硬盘吗？</li>
</ol>
<h3 id="解决缓存问题">解决缓存问题<a class="heading-anchor" href="#解决缓存问题" aria-hidden="true" tabindex="-1">#</a></h3>
<h4 id="解决缓存不一致">解决缓存不一致<a class="heading-anchor" href="#解决缓存不一致" aria-hidden="true" tabindex="-1">#</a></h4>
<table>
<thead>
<tr>
<th align="left">操作顺序</th>
<th align="left">是否有并发请求</th>
<th align="left">潜在问题</th>
<th align="left">现象</th>
<th align="left">应对方案</th>
</tr>
</thead>
<tbody><tr>
<td align="left">先删除缓存值，再更新数据库</td>
<td align="left">无</td>
<td align="left">缓存删除成功，但数据库更新失败</td>
<td align="left">应用从数据库读取到旧数据</td>
<td align="left">重试数据库更新</td>
</tr>
<tr>
<td align="left">先删除缓存值，再更新数据库</td>
<td align="left">有</td>
<td align="left">缓存删除后，尚未更新数据库，有并发读请求</td>
<td align="left">并发请求从数据库读到旧值，并且更新到缓存，导致后续请求都读取旧值</td>
<td align="left">延迟双删除</td>
</tr>
<tr>
<td align="left">先更新数据库，再删除缓存</td>
<td align="left">无</td>
<td align="left">数据库更新成功，但缓存删除失败</td>
<td align="left">应用从缓存读到旧数据</td>
<td align="left">重试缓存删除</td>
</tr>
<tr>
<td align="left">先更新数据库，再删除缓存</td>
<td align="left">有</td>
<td align="left">数据库更新成功后，尚未删除缓存，有并发读请求</td>
<td align="left">并发请求从缓存中读到旧值</td>
<td align="left">等待缓存删除完成，期间会有不一致数据短暂存在</td>
</tr>
</tbody></table>
<h4 id="解决缓存雪崩-缓存击穿缓存穿透">解决缓存雪崩， 缓存击穿，缓存穿透<a class="heading-anchor" href="#解决缓存雪崩-缓存击穿缓存穿透" aria-hidden="true" tabindex="-1">#</a></h4>
<p>| 问题 | 原因 | 应对方案 |<br>|:--|:--|:--|:--|
| 缓存雪崩 | * 大量数据同时过期 </br> * 缓存实例宕机  | 1. 给缓存数据的过期时间加上小的随机数 </br> 2. 服务降级 </br> 3. 服务熔断  </br>4. 请求限流 </br> 5. Redis缓存主从集群   |<br>|缓存击穿  | 访问非常频繁的热点数据过期  |不给热点数据设置过期时间，一直保留  |<br>|缓存穿透|缓存和数据库中都没有要访问的数据|1. 缓存空值或缺省值 </br> 2. 请使用布隆过滤器快速判断 3. 请求入口前端对请求合法性进行检查|</p>
<h4 id="解决缓存污染问题">解决缓存污染问题<a class="heading-anchor" href="#解决缓存污染问题" aria-hidden="true" tabindex="-1">#</a></h4>
<blockquote>
<p>那什么是缓存污染呢？在一些场景下，有些数据被访问的次数非常少，甚至只会被访问一次。当这些数据服务完访问请求后，如果还继续留存在缓存中的话，就只会白白占用缓存空间。这种情况，就是缓存污染。</p>
</blockquote>
<p>如何解决:<br>LRU 策略和 LFU 策略</p>
<h4 id="大内存redis">大内存Redis<a class="heading-anchor" href="#大内存redis" aria-hidden="true" tabindex="-1">#</a></h4>
<p>使用ssd解决大内存Redis问题
<a href="https://github.com/OpenAtomFoundation/pika" target="_blank" rel="noopener noreferrer">https://github.com/OpenAtomFoundation/pika</a></p>
<h4 id="redis应对并发">Redis应对并发<a class="heading-anchor" href="#redis应对并发" aria-hidden="true" tabindex="-1">#</a></h4>
<ol>
<li>使用锁</li>
<li>使用原子操作, Lua脚本</li>
</ol>
<h4 id="分布式锁">分布式锁<a class="heading-anchor" href="#分布式锁" aria-hidden="true" tabindex="-1">#</a></h4>
<p>为了避免 Redis 实例故障而导致的锁无法工作的问题，Redis 的开发者 Antirez 提出了分布式锁算法 Redlock。Redlock 算法的基本思路，是让客户端和多个独立的 Redis 实例依次请求加锁，如果客户端能够和半数以上的实例成功地完成加锁操作，那么我们就认为，客户端成功地获得分布式锁了，否则加锁失败。这样一来，即使有单个 Redis 实例发生故障，因为锁变量在其它实例上也有保存，所以，客户端仍然可以正常地进行锁操作，锁变量并不会丢失。</p>
<h4 id="acid">ACID<a class="heading-anchor" href="#acid" aria-hidden="true" tabindex="-1">#</a></h4>
<p>Redis通过MULTI, EXEC, DISCARD,WATCH四个命令来支持事务机制</p>
<table>
<thead>
<tr>
<th align="left">命令</th>
<th align="left">作用</th>
</tr>
</thead>
<tbody><tr>
<td align="left">MULTI</td>
<td align="left">开启一个事务</td>
</tr>
<tr>
<td align="left">EXEC</td>
<td align="left">提交事务，从命令队列中取出提交的操作命令，进行实际执行</td>
</tr>
<tr>
<td align="left">DISCARD</td>
<td align="left">放弃一个事务，清空命令队列</td>
</tr>
<tr>
<td align="left">WATCH</td>
<td align="left">检测一个或多个键的值在事务执行期间是否发生变化，如果发生变化，那么当前事务放弃执行</td>
</tr>
</tbody></table>
<p>Redis 的事务机制可以保证一致性和隔离性，但是无法保证持久性。不过，因为 Redis 本身是内存数据库，持久性并不是一个必须的属性，我们更加关注的还是原子性、一致性和隔离性这三个属性。原子性的情况比较复杂，只有当事务中使用的命令语法有误时，原子性得不到保证，在其它情况下，事务都可以原子性执行。</p>
<h4 id="主从故障">主从故障<a class="heading-anchor" href="#主从故障" aria-hidden="true" tabindex="-1">#</a></h4>
<ul>
<li>主从数据不一致。Redis 采用的是异步复制，所以无法实现强一致性保证（主从数据时时刻刻保持一致），数据不一致是难以避免的。我给你提供了应对方法：保证良好网络环境，以及使用程序监控从库复制进度，一旦从库复制进度超过阈值，不让客户端连接从库。</li>
<li>对于读到过期数据，这是可以提前规避的，一个方法是，使用 Redis 3.2 及以上版本；另外，你也可以使用 EXPIREAT/PEXPIREAT 命令设置过期时间，避免从库上的数据过期时间滞后。不过，这里有个地方需要注意下，因为 EXPIREAT/PEXPIREAT 设置的是时间点，所以，主从节点上的时钟要保持一致，具体的做法是，让主从节点和相同的 NTP 服务器（时间服务器）进行时钟同步。</li>
</ul>
<h4 id="脑裂">脑裂<a class="heading-anchor" href="#脑裂" aria-hidden="true" tabindex="-1">#</a></h4>
<p>所谓的脑裂，就是指在主从集群中，同时有两个主节点，它们都能接收写请求。而脑裂最直接的影响，就是客户端不知道应该往哪个主节点写入数据，结果就是不同的客户端会往不同的主节点上写入数据。而且，严重的话，脑裂会进一步导致数据丢失。</p>
<ul>
<li>min-slaves-to-write：这个配置项设置了主库能进行数据同步的最少从库数量；</li>
<li>min-slaves-max-lag：这个配置项设置了主从库间进行数据复制时，从库给主库发送 ACK 消息的最大延迟（以秒为单位）。</li>
</ul>
<p>假设我们将 min-slaves-to-write 设置为 1，把 min-slaves-max-lag 设置为 12s，把哨兵的 down-after-milliseconds 设置为 10s，主库因为某些原因卡住了 15s，导致哨兵判断主库客观下线，开始进行主从切换。同时，因为原主库卡住了 15s，没有一个从库能和原主库在 12s 内进行数据复制，原主库也无法接收客户端请求了。这样一来，主从切换完成后，也只有新主库能接收请求，不会发生脑裂，也就不会发生数据丢失的问题了。</p>
]]></content:encoded>
    </item>
    <item>
      <title>go web从入门到精通</title>
      <link>https://www.joe.top/2022/08/28/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/language/GO/go-src/20220828-go-web%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E7%B2%BE%E9%80%9A/</link>
      <guid isPermaLink="true">https://www.joe.top/2022/08/28/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/language/GO/go-src/20220828-go-web%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E7%B2%BE%E9%80%9A/</guid>
      <pubDate>Sun, 28 Aug 2022 09:47:55 GMT</pubDate>
      <category>Go</category>
      <description>本文将从context-&gt;http-&gt;gin的路线全面吃透go web开发</description>
      <content:encoded><![CDATA[<h3 id="简介">简介<a class="heading-anchor" href="#简介" aria-hidden="true" tabindex="-1">#</a></h3>
<blockquote>
<p>本文将从context-&gt;http-&gt;gin的路线全面吃透go web开发</p>
</blockquote>
<h3 id="context">context<a class="heading-anchor" href="#context" aria-hidden="true" tabindex="-1">#</a></h3>
<blockquote>
<p>要理解go web开发我认为首先离不开的就是context， 同时context也是最适合用来学习go源码的一个包</p>
</blockquote>
<p>几种ctx的简介</p>
<ol>
<li>emptyCtx: 所有ctx的根，用context.TODO(), context.Background()生成</li>
<li>valueCtx: 在ctx中存储kv数据，同一个ctx只能存储一个kv数据, 更多的kv数据构造了一个树形结构</li>
<li>cancelCtx: 可以用来取消程序的执行树, 父级取消会依次通知下级取消所有的ctx, 也是最为核心实现最复杂的一个ctx</li>
<li>timerCtx: 基于cancelCtx，基于支持时间的cancel</li>
</ol>
<p>context的核心接口，有以下几个方法, 上面的几个ctx也都是围绕该接口实现和扩展的  </p>
<pre class="code-block"><code class="hljs">// Context&#39;s methods may be called by multiple goroutines simultaneously.
type Context interface {
	Deadline() (deadline time.Time, ok bool)
	Done() &lt;-chan struct{}
	Err() error
	Value(key any) any
}
</code></pre>
<h4 id="emptyctx">emptyCtx<a class="heading-anchor" href="#emptyctx" aria-hidden="true" tabindex="-1">#</a></h4>
<p>emptyCtx是所有ctx的根，一般通过context.TODO()和context.Background()来使用, 通过代码也都是可以看到最终都是new(emptyCtx)  </p>
<pre class="code-block"><code class="hljs">// An emptyCtx is never canceled, has no values, and has no deadline. It is not
// struct{}, since vars of this type must have distinct addresses.
type emptyCtx int

var (
	background = new(emptyCtx)
	todo       = new(emptyCtx)
)

// Background returns a non-nil, empty Context. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
// initialization, and tests, and as the top-level Context for incoming
// requests.
func Background() Context {
	return background
}

// TODO returns a non-nil, empty Context. Code should use context.TODO when
// it&#39;s unclear which Context to use or it is not yet available (because the
// surrounding function has not yet been extended to accept a Context
// parameter).
func TODO() Context {
	return todo
}</code></pre>
<p>硬要说区别的话，在调用String方法的时候输出的值会不一样。emptyCtx可以看到其实现的方法都是空的  </p>
<pre class="code-block"><code class="hljs">// An emptyCtx is never canceled, has no values, and has no deadline. It is not
// struct{}, since vars of this type must have distinct addresses.
type emptyCtx int

func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
	return
}

func (*emptyCtx) Done() &lt;-chan struct{} {
	return nil
}

func (*emptyCtx) Err() error {
	return nil
}

func (*emptyCtx) Value(key any) any {
	return nil
}

func (e *emptyCtx) String() string {
	switch e {
	case background:
		return &quot;context.Background&quot;
	case todo:
		return &quot;context.TODO&quot;
	}
	return &quot;unknown empty Context&quot;
}</code></pre>
<h3 id="http">http<a class="heading-anchor" href="#http" aria-hidden="true" tabindex="-1">#</a></h3>
<h3 id="gin">gin<a class="heading-anchor" href="#gin" aria-hidden="true" tabindex="-1">#</a></h3>
<h3 id="参考文献">参考文献<a class="heading-anchor" href="#参考文献" aria-hidden="true" tabindex="-1">#</a></h3>
<ul>
<li><a href="https://github.com/cch123/golang-notes/blob/master/context.md" target="_blank" rel="noopener noreferrer">context</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>cosmos初探</title>
      <link>https://www.joe.top/2022/08/09/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/blockchain/cosmos/2022-08-09-cosmos-%E5%88%9D%E6%8E%A2/</link>
      <guid isPermaLink="true">https://www.joe.top/2022/08/09/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/blockchain/cosmos/2022-08-09-cosmos-%E5%88%9D%E6%8E%A2/</guid>
      <pubDate>Tue, 09 Aug 2022 12:47:55 GMT</pubDate>
      <category>区块链</category>
      <description>区块链前置知识可参考其他资料</description>
      <content:encoded><![CDATA[<blockquote>
<p>区块链前置知识可参考其他资料</p>
</blockquote>
<ul>
<li><a href="https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/articles/index.html" target="_blank" rel="noopener noreferrer">深入浅出BCOS系列文章</a></li>
</ul>
<h3 id="前言">前言<a class="heading-anchor" href="#前言" aria-hidden="true" tabindex="-1">#</a></h3>
<p>cosmos-sdk可从simapp开始看起，从入口函数了解如何启动一个链并发送交易</p>
<p>tendermint 整体流程
<img src="/remote/cfb09939afad9eaa.png" alt="" loading="lazy" decoding="async"></p>
<h3 id="初始化节点">初始化节点<a class="heading-anchor" href="#初始化节点" aria-hidden="true" tabindex="-1">#</a></h3>
<pre class="code-block"><code class="hljs">
./simd init demo

cosmos-sdk/x/genutil/client/cli/init.go
// InitCmd returns a command that initializes all files needed for Tendermint
// and the respective application.

* 获取context  操作sdk 使用的client
* 获取server context 包括 tendermint context viper log
* 链id，如果传入使用传入的，没有则随机生成
* 如果传入 --recover 使用传入的seed来恢复助记词
* 创建节点的公私钥
* 生成配置信息并写入到配置文件
</code></pre>
<h3 id="生成账号">生成账号<a class="heading-anchor" href="#生成账号" aria-hidden="true" tabindex="-1">#</a></h3>
<pre class="code-block"><code class="hljs">./simd keys add joe

cosmos-sdk/client/keys/add.go
/*
input
	- bip39 mnemonic
	- bip39 passphrase
	- bip44 path
	- local encryption password
output
	- armor encrypted private key (saved to file)


--algo 设定签名算法参数
--no-backup 不输出助记词信息

* 获取传入的名称
* 设定签名算法， 默认使用secp256k1
* 从本地钥匙串中查询name是否已经存在， 如果存在提示是否需要覆盖
* 覆盖，从本地钥匙串中把秘钥删除
* 生成一个新的秘钥,如果使用--recover 使用传入的助记词生成


*/
func RunAddCmd(ctx client.Context, cmd *cobra.Command, args []string, inBuf *bufio.Reader) error</code></pre>
<h4 id="查看账号信息">查看账号信息<a class="heading-anchor" href="#查看账号信息" aria-hidden="true" tabindex="-1">#</a></h4>
<pre class="code-block"><code class="hljs">./simd keys show joee

cosmos-sdk/client/keys/show.go

/**
* 获取参数，长度为1表明只查询一条账户信息，大于1表示查询多条
* 根据name，从本地钥匙串中获取秘钥信息
* 根据几个flag，控制展示不同的信息,仅仅展示address public key
* keyring 转成输出的结构 KeyOutput(定义了info的结构信息)
* 输出信息
 */
func runShowCmd(cmd *cobra.Command, args []string) (err error) </code></pre>
<p><img src="/remote/f397483767c0a7da.png" alt="" loading="lazy" decoding="async">
其实就是根据name从本地的钥匙串（mac）中查出来keyinfo然后在转成ko信息进行输出</p>
<h3 id="增加创世账号">增加创世账号<a class="heading-anchor" href="#增加创世账号" aria-hidden="true" tabindex="-1">#</a></h3>
<pre class="code-block"><code class="hljs">./simd add-genesis-account joee 100000000stake

cosmos-sdk/simapp/simd/cmd/genaccount.go



// AddGenesisAccountCmd returns add-genesis-account cobra Command.
* 读取账号name，读取币信息
* 解析name值如果是Bech32则使用（生成的账户地址），否则从本地查询出私钥信息
* 解析币信息多少币以及单位
* 读取创世文件并解析
* 将创世账号设置到创世配置文件里
* 保存新的文件
</code></pre>
<p>账号增加前后，genesis文件对比<br><img src="/remote/df20ca1ad317af80.png" alt="" loading="lazy" decoding="async"></p>
<h3 id="生成创世交易">生成创世交易<a class="heading-anchor" href="#生成创世交易" aria-hidden="true" tabindex="-1">#</a></h3>
<pre class="code-block"><code class="hljs">./simd  gentx joee 70000000stake --chain-id test-chain-hTxGEP 

cosmos-sdk/x/genutil/client/cli/gentx.go


// GenTxCmd builds the application&#39;s gentx command.
/**
* 获取nodeid validator key
* 读取genesis.json配置
* 生成TxCreateValidatorConfig
* 验证创世账号,检查是否有足够的余额， 判断了创世账号和传入的币数量，进行了对比
* 生成 tx client 方便对交易进行签名等
* 生成 &#39;create-validator&#39; message
* 无签名交易, 生成一个 std tx 不带签名的
* 创建 tx builder 依据 std tx
* 签名交易, 利用用户的私钥， 签名的消息在 std tx中， tx builder里也有该结构
* 将std tx写入本地的文件中
 */</code></pre>
<p>std tx 和 tx builder其都是接口, 都实现了tx.wrapper, 最终文件里写入的也是tx的结构信息
<img src="/remote/95f8bf25f0e65b93.png" alt="" loading="lazy" decoding="async"></p>
<p><img src="/remote/66945223d909b461.png" alt="" loading="lazy" decoding="async"></p>
<p><img src="/remote/59a59265be51a64c.png" alt="" loading="lazy" decoding="async"></p>
<h3 id="收集创世交易到创世配置文件中">收集创世交易到创世配置文件中<a class="heading-anchor" href="#收集创世交易到创世配置文件中" aria-hidden="true" tabindex="-1">#</a></h3>
<pre class="code-block"><code class="hljs">./simd collect-gentxs
主要功能就是把gentx/下的创世的tx信息，写入到genesis文件的genutil-&gt;gen_txs下</code></pre>
<h3 id="启动链">启动链<a class="heading-anchor" href="#启动链" aria-hidden="true" tabindex="-1">#</a></h3>
<pre class="code-block"><code class="hljs">./simd start
cosmos-sdk/server/start.go

核心主要是startInProcess方法， 其大概的流程如下
* simapp,  tendermint node 初始化
* db创建
* 初始化链
* 执行各个模块的init
* tendermint模块的启动
* 初始化几个db
* consensus, mempool, query启动
* 创建eventbus，因为可能需要重放tx，因为可能在上一次索引tx前可能程序崩溃了
* grpc server启动</code></pre>
<p>重放 </p>
<pre class="code-block"><code class="hljs">从数据库中读取数据, 包含了同步的高度 最后一个hash等
通过proxyapp的query查询数据
query req:
req:{Version:0.34.20 BlockVersion:11 P2PVersion:8}
rsp:data:&quot;SimApp&quot; last_block_height:876 last_block_app_hash:&quot;\225\002\200\022\210\230m\033\367\270\307\274gf\3015\007\273Ip\004v\345\245\375Q=\036\362\317]\203&quot;


生成默克尔树</code></pre>
<p>执行的大致步骤
<img src="/remote/35fd605c46c3c266.png" alt="" loading="lazy" decoding="async"></p>
<h3 id="查询账户余额">查询账户余额<a class="heading-anchor" href="#查询账户余额" aria-hidden="true" tabindex="-1">#</a></h3>
<p>通过命令行查询账户余额信息，query bank balances 发起一个rpc请求，server接收到请求后调用bank模块的查询方法</p>
<pre class="code-block"><code class="hljs">./simd query bank balances cosmos1trspjhj9hela7x3k0jucs93trjlcm0zv0cemtx 


该cmd代码在
cosmos-sdk/x/bank/client/cli/query.go里GetBalancesCmd方法
这是是client调用的代码


server端代码在
cosmos-sdk/x/bank/keeper/grpc_query.go里的AllBalances方法
</code></pre>
<p>实际查询是调用这里，是一个分页的查询方法，会查询某个prefix的所有value数据。使用了kvstore的迭代器，kv的prefix实际上就是addr
iter的key就是addr， value就是用户的余额信息，每一次的迭代的结果都会调用调用者的回调函数，进行数据的进一步处理，看下一张图</p>
<p><img src="/remote/75d8e0c1f0539807.png" alt="" loading="lazy" decoding="async"></p>
<p>接收到的kvstore里的value，在这里进行了unmarshal转成了coin类型的， 之后就是处理下返回的数据，在把数据通过rpc进行返回，client端就可以看到查询到的数据
<img src="/remote/55c9a852270df91c.png" alt="" loading="lazy" decoding="async"></p>
<h3 id="转账交易">转账交易<a class="heading-anchor" href="#转账交易" aria-hidden="true" tabindex="-1">#</a></h3>
<h4 id="前期准备">前期准备<a class="heading-anchor" href="#前期准备" aria-hidden="true" tabindex="-1">#</a></h4>
<p>首先创建另外一个账户得到需要被转账的账户地址, 创建一个wt的账号，从joee转账到wt  </p>
<pre class="code-block"><code class="hljs">./simd keys add wt 创建一个账号
- name: wt
  type: local
  address: cosmos14a6hsfwvxfsnu4rtshxu2g2qcsj4s985vhflg3
  pubkey: cosmospub1addwnpepqvmcswuhleztl4pt2nfw4n32puxfvjwrflmtzedkw0hf68ytagnr6xjhux5
  mnemonic: &quot;&quot;
  threshold: 0
  pubkeys: []


**Important** write this mnemonic phrase in a safe place.
It is the only way to recover your account if you ever forget your password.

sail lady calm ensure repeat cinnamon tilt sponsor thumb glad also nice bright absurd tone rather scissors wasp salad topic pave choice cabin purpose</code></pre>
<h4 id="签名交易">签名交易<a class="heading-anchor" href="#签名交易" aria-hidden="true" tabindex="-1">#</a></h4>
<pre class="code-block"><code class="hljs">./simd tx bank send cosmos1trspjhj9hela7x3k0jucs93trjlcm0zv0cemtx cosmos14a6hsfwvxfsnu4rtshxu2g2qcsj4s985vhflg3 10stake --chain-id test-chain-hTxGEP --home /Users/joe/.simappdebug

/cosmos.auth.v1beta1.Query/Account
先查询from账号信息
client拿到账号数据信息
准备构造广播交易
client确实是否广播前签名交易

签名前交易
{&quot;body&quot;:{&quot;messages&quot;:[{&quot;@type&quot;:&quot;/cosmos.bank.v1beta1.MsgSend&quot;,&quot;from_address&quot;:&quot;cosmos1trspjhj9hela7x3k0jucs93trjlcm0zv0cemtx&quot;,&quot;to_address&quot;:&quot;cosmos14a6hsfwvxfsnu4rtshxu2g2qcsj4s985vhflg3&quot;,&quot;amount&quot;:[{&quot;denom&quot;:&quot;stake&quot;,&quot;amount&quot;:&quot;10&quot;}]}],&quot;memo&quot;:&quot;&quot;,&quot;timeout_height&quot;:&quot;0&quot;,&quot;extension_options&quot;:[],&quot;non_critical_extension_options&quot;:[]},&quot;auth_info&quot;:{&quot;signer_infos&quot;:[],&quot;fee&quot;:{&quot;amount&quot;:[],&quot;gas_limit&quot;:&quot;200000&quot;,&quot;payer&quot;:&quot;&quot;,&quot;granter&quot;:&quot;&quot;}},&quot;signatures&quot;:[]}

构造http请求, json rpc请求, client会调用链的的接口

以下是一个json rpc请求样例, tx的交易信息大致包括，from和to的address，调用了bank的msgsend，转账的金额，pubkey等信息
{&quot;jsonrpc&quot;:&quot;2.0&quot;,&quot;id&quot;:2,&quot;method&quot;:&quot;broadcast_tx_sync&quot;,&quot;params&quot;:{&quot;tx&quot;:&quot;Co4BCosBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmsKLWNvc21vczF0cnNwamhqOWhlbGE3eDNrMGp1Y3M5M3RyamxjbTB6djBjZW10eBItY29zbW9zMTRhNmhzZnd2eGZzbnU0cnRzaHh1MmcycWNzajRzOTg1dmhmbGczGgsKBXN0YWtlEgIxMBJYClAKRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDT0ZVTObifli0uYI1YWSRv2LI/Ga2n+6WdFnQJDnLYegSBAoCCAEYAxIEEMCaDBpAAP1xjbIPlt5wHwEESFD6rmJr0osa4HUocS5lqPXy/us8CvlXOPrpQm9FlKd9ZRgFCfVXpUInEnHWcHP7RRn44Q==&quot;}}

请求返回样例
{
  &quot;jsonrpc&quot;: &quot;2.0&quot;,
  &quot;id&quot;: 2,
  &quot;result&quot;: {
    &quot;code&quot;: 0,
    &quot;data&quot;: &quot;&quot;,
    &quot;log&quot;: &quot;[]&quot;,
    &quot;codespace&quot;: &quot;&quot;,
    &quot;hash&quot;: &quot;5F10702E8E885B8FFD6D530EC6649781BA12810DB78A3392C0F556A0676B3730&quot;
  }
}

</code></pre>
<h4 id="交易处理流程">交易处理流程<a class="heading-anchor" href="#交易处理流程" aria-hidden="true" tabindex="-1">#</a></h4>
<pre class="code-block"><code class="hljs">接口接收到tx信息，开始处理交易

检查交易，会判断交易大小
	
计算tx的hash值，使用了sha256来计算
将交易放入队列
// mapTxCache maintains a LRU cache of transactions. This only stores the hash
// of the tx, due to memory concerns.
type mapTxCache struct {
	mtx      tmsync.Mutex
	size     int
	cacheMap map[[TxKeySize]byte]*list.Element
	list     *list.List
}
提交共识，节点进行共识操作
执行区块</code></pre>
<h4 id="节点共识流程">节点共识流程<a class="heading-anchor" href="#节点共识流程" aria-hidden="true" tabindex="-1">#</a></h4>
<pre class="code-block"><code class="hljs">TODO: 节点共识流程</code></pre>
<h4 id="执行区块流程">执行区块流程<a class="heading-anchor" href="#执行区块流程" aria-hidden="true" tabindex="-1">#</a></h4>
<pre class="code-block"><code class="hljs">TODO: 执行区块流程</code></pre>
<h3 id="参考文献">参考文献<a class="heading-anchor" href="#参考文献" aria-hidden="true" tabindex="-1">#</a></h3>
<ul>
<li><a href="https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/articles/index.html" target="_blank" rel="noopener noreferrer">深入浅出BCOS系列文章</a></li>
<li><a href="https://tutorials.cosmos.network/academy/3-my-own-chain/node-api-and-cli.html" target="_blank" rel="noopener noreferrer">cosmos run a node</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>GO语言测试&amp;文档&amp;调试</title>
      <link>https://www.joe.top/2020/05/11/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/language/GO/base/go-test-doc-debug/</link>
      <guid isPermaLink="true">https://www.joe.top/2020/05/11/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/language/GO/base/go-test-doc-debug/</guid>
      <pubDate>Mon, 11 May 2020 04:00:00 GMT</pubDate>
      <category>Go</category>
      <description>多做测试 少做调试</description>
      <content:encoded><![CDATA[<h1 id="测试">测试<a class="heading-anchor" href="#测试" aria-hidden="true" tabindex="-1">#</a></h1>
<p>多做测试 少做调试</p>
<p>传统测试 vs 表格驱动测试   </p>
<h3 id="传统测试">传统测试<a class="heading-anchor" href="#传统测试" aria-hidden="true" tabindex="-1">#</a></h3>
<ul>
<li>测试数据和测试逻辑混在一起 </li>
<li>出错信息不明确</li>
<li>一旦一个数据出错测试全部结束</li>
</ul>
<h3 id="表格驱动测试">表格驱动测试<a class="heading-anchor" href="#表格驱动测试" aria-hidden="true" tabindex="-1">#</a></h3>
<ul>
<li>分离测试数据和测试逻辑</li>
<li>明确的出错信息</li>
<li>可以部分失败</li>
</ul>
<p>go语言 测试文件跟与要测试的文件同目录，文件名规则如下  </p>
<pre class="code-block"><code class="hljs">add_test.go</code></pre>
<p>命令行运行测试用例  </p>
<pre class="code-block"><code class="hljs">go test .   </code></pre>
<p>测试用例代码  </p>
<pre class="code-block"><code class="hljs">package main

import &quot;testing&quot;

func TestAdd(t *testing.T) {
	tests := []struct {a, b, c int}{
		{1,2,3},
		{2,3,5},
		{4,6,10},
		{3,4,0},
		{19,1,20},
	}

	for _, tt := range tests {
		if actual := add(tt.a, tt.b); actual != tt.c {
			t.Errorf(&quot;add(%d,%d); &quot;+&quot;got %d; expected %d&quot;,tt.a,tt.b,actual,tt.c)
		}
	}
}</code></pre>
<h4 id="代码覆盖率">代码覆盖率<a class="heading-anchor" href="#代码覆盖率" aria-hidden="true" tabindex="-1">#</a></h4>
<p>命令行运行<br>go test -coverprofile=c.out  </p>
<p>一般生成的文件很难理解  </p>
<p>使用一个工具</p>
<p>go tool cover -html=c.out</p>
<h4 id="性能测试">性能测试<a class="heading-anchor" href="#性能测试" aria-hidden="true" tabindex="-1">#</a></h4>
<p>可以使用命令行<br>go test -bench .</p>
<p>func BenchmarkSubstr(b *testing.B) {
	a := 19999
	c := 123123
	for i := 0; i &lt; b.N; i++ {
		add(a, c)
	}
}</p>
<p>进行性能调优  </p>
<p>生成一个文件<br>go test -bench . -cpuprofile=cpu.out</p>
<p>运行这个
go tool pprof cpu.out<br>有很多功能 使用最简单的 web</p>
<p>如果想用web的话需要安装一个包<br><a href="http://graphviz.org/download/" target="_blank" rel="noopener noreferrer">Download</a><br>然后查看是哪一步占用的时间多</p>
<h3 id="文档">文档<a class="heading-anchor" href="#文档" aria-hidden="true" tabindex="-1">#</a></h3>
<p>godoc -http :6060<br>在本地生成一个文档，有go语言的文档，也有自己的写的代码的文档   </p>
<p>go语言注释可以随意写<br>go doc fmt.Println<br>打印文档注释  </p>
<h3 id="gdb调试">gdb调试<a class="heading-anchor" href="#gdb调试" aria-hidden="true" tabindex="-1">#</a></h3>
<p>go build -gcflags &quot;-N -l&quot; gdbfile.go</p>
]]></content:encoded>
    </item>
    <item>
      <title>GO语言并发编程</title>
      <link>https://www.joe.top/2020/05/10/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/language/GO/base/go-goroutine/</link>
      <guid isPermaLink="true">https://www.joe.top/2020/05/10/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/language/GO/base/go-goroutine/</guid>
      <pubDate>Sun, 10 May 2020 12:00:00 GMT</pubDate>
      <category>Go</category>
      <description>goroutine</description>
      <content:encoded><![CDATA[<h2 id="go语言并发编程">go语言并发编程<a class="heading-anchor" href="#go语言并发编程" aria-hidden="true" tabindex="-1">#</a></h2>
<p>goroutine  </p>
<p>轻量级 线程<br>非抢占式多任务处理，由协程主动交出控制权<br>编译器/解释器/虚拟机层面的多任务</p>
<p>使用go关键字  </p>
<pre class="code-block"><code class="hljs">//检测数据冲突
go run -race main.go</code></pre>
<p>channel<br>goroutine之间通信
不要通过共享内存来通信；通过通信来共享内存  </p>
<p>chan&lt;- int 表示只发送不能接收<br>&lt;-chan int 表示只接收不能发送  </p>
<p>所有的并发问题 都可以用一致的、简单的既定的模式来规避。所有可能的话，将变量限定在goroutine内部，如果是多个goroutine都需要访问的变量，使用互斥条件来访问   </p>
<p>go的调度器，当一个goroutine调用了time.Sleep等 阻塞时，调度器会使其进入休眠并开始执行另一个goroutine直到时机到了再去唤醒第一个goroutine。这种调度不需要进入内核的上下文，所以比线程的代价低  </p>
<h3 id="几个-runtime包中处理goruntine的函数">几个 runtime包中处理goruntine的函数<a class="heading-anchor" href="#几个-runtime包中处理goruntine的函数" aria-hidden="true" tabindex="-1">#</a></h3>
<p>Goexit</p>
<p>退出当前执行的goroutine，但是defer函数还会继续调用</p>
<p>Gosched</p>
<p>让出当前goroutine的执行权限，调度器安排其他等待的任务运行，并在下次某个时候从该位置恢复执行。</p>
<p>NumCPU</p>
<p>返回 CPU 核数量</p>
<p>NumGoroutine</p>
<p>返回正在执行和排队的任务总数</p>
<p>GOMAXPROCS</p>
<p>用来设置可以并行计算的CPU核数的最大值，并返回之前的值。</p>
<h2 id="example">example<a class="heading-anchor" href="#example" aria-hidden="true" tabindex="-1">#</a></h2>
<p>chan 的方向</p>
<pre class="code-block"><code class="hljs">package main

import &quot;fmt&quot;

func main() {
	pings := make(chan string, 1)
	pongs := make(chan string, 1)
	ping(pings, &quot;password message&quot;)
	pong(pings, pongs)
	fmt.Println(&lt;-pongs)
}

//pings 只能用来发送channel
func ping(pings chan&lt;- string, msg string) {
	pings &lt;- msg
}

//pings 只能用来接收channel
func pong(pings &lt;-chan string, pongs chan&lt;- string) {
	msg := &lt;-pings
	pongs &lt;- msg
}
</code></pre>
<p>select</p>
<pre class="code-block"><code class="hljs">package main

import (
	&quot;fmt&quot;
	&quot;time&quot;
)

func main() {

	c1 := make(chan string)
	c2 := make(chan string)

	go func() {
		time.Sleep(time.Second * 1)
		c1 &lt;- &quot;one&quot;
	}()

	go func() {
		time.Sleep(time.Second * 1)
		c2 &lt;- &quot;two&quot;
	}()

	for i := 0; i &lt; 2; i++ {
		select {
		case msg1 := &lt;-c1:
			fmt.Println(&quot;received&quot;, msg1)
		case msg2 := &lt;-c2:
			fmt.Println(&quot;received&quot;, msg2)
		}
	}

}
</code></pre>
]]></content:encoded>
    </item>
    <item>
      <title>GO语言基础</title>
      <link>https://www.joe.top/2020/05/10/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/language/GO/base/go-base/</link>
      <guid isPermaLink="true">https://www.joe.top/2020/05/10/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/language/GO/base/go-base/</guid>
      <pubDate>Sun, 10 May 2020 04:00:00 GMT</pubDate>
      <category>Go</category>
      <description>下载源码包到你想放置的目录,然后设置环境变量</description>
      <content:encoded><![CDATA[<h1 id="安装使用">安装&amp;使用<a class="heading-anchor" href="#安装使用" aria-hidden="true" tabindex="-1">#</a></h1>
<h2 id="安装">安装<a class="heading-anchor" href="#安装" aria-hidden="true" tabindex="-1">#</a></h2>
<p>下载源码包到你想放置的目录,然后设置环境变量  </p>
<pre class="code-block"><code class="hljs">export GOROOT=$HOME/go
export PATH=$PATH:$GOROOT/bin
export GOPATH=/data/work
export PATH=$PATH:$GOPATH/bin</code></pre>
<h2 id="使用">使用<a class="heading-anchor" href="#使用" aria-hidden="true" tabindex="-1">#</a></h2>
<p>有2种方式运行程序  </p>
<p>解释性语言的常用运行方式  </p>
<pre class="code-block"><code class="hljs">go run main.go</code></pre>
<p>先编译成二进制,然后运行  </p>
<pre class="code-block"><code class="hljs">go build -o main main.go &amp; ./main</code></pre>
<p>跨平台编译, 编译完后把二进制包以及必要的资源文件copy到需要使用的机器上，无需安装环境即可使用    </p>
<pre class="code-block"><code class="hljs">GOOS=darwin GOARCH=amd64 go build -o main main.go  
GOOS=windows GOARCH=amd64 go build -o main main.go  
GOOS=linux GOARCH=amd64 go build -o main main.go</code></pre>
<h1 id="语言基础">语言基础<a class="heading-anchor" href="#语言基础" aria-hidden="true" tabindex="-1">#</a></h1>
<h2 id="go语言组成">go语言组成<a class="heading-anchor" href="#go语言组成" aria-hidden="true" tabindex="-1">#</a></h2>
<p>go语言一共有25个关键字,基本上把这25个关键字“掌握”就能写go语言程序了  </p>
<pre class="code-block"><code class="hljs">break    default      func    interface    select
case     defer        go      map          struct
chan     else         goto    package      switch
const    fallthrough  if      range        type
continue for          import  return       var</code></pre>
<p>go语言内置的数据类型标识符(20个)</p>
<pre class="code-block"><code class="hljs">数值(16)
* 整型(12)
  byte int int8 int16 int32 int64  
  uint uint8 uint16 uint32 uint64 uintptr
* 浮点型(2)
  float32 float64
* 复数型(2)
  complex64 complex128

字符和字符串型(2)
  string rune

接口型(1)
  error

布尔型(1)
  bool</code></pre>
<p>go语言内置函数(15个)</p>
<pre class="code-block"><code class="hljs">make new len cap append copy delete panic recover close complex real image print println</code></pre>
<p>常量标识符(4个)  </p>
<pre class="code-block"><code class="hljs">true false
iota
nil</code></pre>
<p>空白标识符(1个)</p>
<pre class="code-block"><code class="hljs">_ 一般用在引入包和变量返回时使用，只是引入包的时候以及忽略变量返回值的时候使用</code></pre>
<p>go语言的操作符(47个)</p>
<pre class="code-block"><code class="hljs">+ &amp; += &amp;= &amp;&amp; == != ( )
- | -= |= || &lt; &lt;=  [ ]
* ^ *= ^= &lt;- &gt; &gt;=  { }
/ &lt;&lt; /= &lt;&lt;= ++ = := , ;
% &gt;&gt; %= &gt;&gt;= -- ! ... . :
&amp;^ &amp;^=</code></pre>
<p>一个go语言程序的组成</p>
<pre class="code-block"><code class="hljs">package main

import &quot;fmt&quot;

func main() {
  fmt.Println(&quot;hello world!!!&quot;)
}</code></pre>
<p>关键字</p>
<pre class="code-block"><code class="hljs">package import func</code></pre>
<p>标识符</p>
<pre class="code-block"><code class="hljs">main fmt Println</code></pre>
<p>字面量</p>
<pre class="code-block"><code class="hljs">&quot;fmt&quot; &quot;hello world!!&quot;</code></pre>
<p>操作符</p>
<pre class="code-block"><code class="hljs">( ) { } .</code></pre>
<p>送给所有学习go的小伙伴的几句话  </p>
<p>Don&#39;t think you have to know everything to be useful<br>Even senior Go Developers don&#39;t know it all<br>Just get stuck in, we&#39;ll help if we can<br>Don&#39;t obsess over performance of code, obsess over making it clear<br>Have fun  </p>
]]></content:encoded>
    </item>
    <item>
      <title>HGOJ使用说明</title>
      <link>https://www.joe.top/2019/06/09/usehgoj/</link>
      <guid isPermaLink="true">https://www.joe.top/2019/06/09/usehgoj/</guid>
      <pubDate>Sun, 09 Jun 2019 12:21:40 GMT</pubDate>
      
      <description>湖南工学院在线判题系统，使用Golang进行了重构，现在正式开放出来给湖工ACM竞赛队的同学使用。</description>
      <content:encoded><![CDATA[<h3 id="hgoj安装和使用说明">HGOJ安装和使用说明<a class="heading-anchor" href="#hgoj安装和使用说明" aria-hidden="true" tabindex="-1">#</a></h3>
<p>湖南工学院在线判题系统，使用Golang进行了重构，现在正式开放出来给湖工ACM竞赛队的同学使用。</p>
<h3 id="环境准备">环境准备<a class="heading-anchor" href="#环境准备" aria-hidden="true" tabindex="-1">#</a></h3>
<p>HGOJ使用Docker进行部署，所以只需要系统安装有Docker和Docker-Compose即可<br>对于mac环境只要下载Docker并安装即可(比较简单)<br>Linux环境的安装如下(以Ubuntu为例):  </p>
<ul>
<li>安装依赖</li>
</ul>
<pre class="code-block"><code class="hljs"># sudo apt-get update &amp;&amp; sudo apt-get install -y vim  curl git</code></pre>
<ul>
<li>安装Docker&amp;&amp;Docker-Compose</li>
</ul>
<pre class="code-block"><code class="hljs"># sudo curl -sSL https://get.daocloud.io/docker | sh</code></pre>
<pre class="code-block"><code class="hljs"># sudo curl -L &quot;https://github.com/docker/compose/releases/download/1.24.0/docker-compose-$(uname -s)-$(uname -m)&quot; -o /usr/local/bin/docker-compose
# sudo chmod +x /usr/local/bin/docker-compose
# sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose</code></pre>
<p>Win环境有兴趣的可以试试</p>
<h3 id="安装系统">安装系统<a class="heading-anchor" href="#安装系统" aria-hidden="true" tabindex="-1">#</a></h3>
<ul>
<li>找到一个磁盘比较充足的盘，运行如下命令</li>
</ul>
<pre class="code-block"><code class="hljs"># git clone -b master https://github.com/yinrenxin/HGOJ-Deploy.git &amp;&amp; cd HGOJ-Deploy</code></pre>
<ul>
<li>启动服务</li>
</ul>
<pre class="code-block"><code class="hljs"># sudo docker-compose up -d</code></pre>
<p>系统运行后会发现运行不起来，是因为还没有数据库，需要手动建立数据库(以后再改),打开8080端口，进入到phpmyadmin，然后建立hgoj命名的数据库，之后再重新启动hgoj容器即可。<br>经过以上步骤，HGOJ就安装好了。默认的首页为index，第一个注册用户为管理员。之后就能愉快的使用了。<br>如果是正式部署使用的话推荐把phpmyadmin容器给停掉，需要操作数据的时候再临时启动，并且更改.env里的mysql密码,然后更改hgoj连接数据库的密码。
如果上面的步骤出了问题可以找我来解决(咕咕咕)</p>
<h3 id="hgoj系统的使用">HGOJ系统的使用<a class="heading-anchor" href="#hgoj系统的使用" aria-hidden="true" tabindex="-1">#</a></h3>
<ol>
<li><p>测试数据
主要说明下测试数据的规范，测试数据需要本地做好后打包成zip压缩包，可以搞多组测试数据，输入文件以.in为结尾，输出文件以.out结尾，把测试数据放到一起进行压缩(切记不要把数据放到一个目录下，然后对这个目录进行压缩)。压缩好后的文件命名必须为data.zip。  </p>
</li>
<li><p>创建题目
创建题目的时候测试数据使用这个压缩包上传。因为使用了markdown，所以题目描述、输入、输出描述要根据markdown语法来进行填写。</p>
</li>
<li><p>创建比赛
比赛创建，根据需要添加比赛开始和结束时间，比赛描述同样需要使用markdown语法。添加题目这里，用逗号分隔。权限和用户这里功能还未开发完毕不用管，默认都是公开的。</p>
</li>
</ol>
<h3 id="联系">联系<a class="heading-anchor" href="#联系" aria-hidden="true" tabindex="-1">#</a></h3>
<ul>
<li>Github:<a href="https://github.com/yinrenxin/hgoj" target="_blank" rel="noopener noreferrer">HGOJ</a></li>
<li>HGOJ开发QQ群:245461736</li>
<li>湖工ACM交流群:662688178</li>
<li>湖工ACM对外小分队:663690027</li>
</ul>
<h3 id="其他">其他<a class="heading-anchor" href="#其他" aria-hidden="true" tabindex="-1">#</a></h3>
<p>因为时间仓促，很多功能还没有实现。因为要兼容以前的OJ系统的数据(以前的OJ系统使用了hustoj)，所以功能上先保证以前有的，HGOJ上也有，判题使用了hustoj的判题，未来的终极目标是使用go重写判题机。如果有小伙伴对go和oj有兴趣，欢迎来一起维护。为啥使用go呢，因为再用PHP写就没意思了(逃) : )</p>
]]></content:encoded>
    </item>
    <item>
      <title>PHP编译选项和常见错误</title>
      <link>https://www.joe.top/2018/12/07/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/language/PHP/php-compile/</link>
      <guid isPermaLink="true">https://www.joe.top/2018/12/07/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/language/PHP/php-compile/</guid>
      <pubDate>Fri, 07 Dec 2018 13:06:48 GMT</pubDate>
      <category>PHP扩展开发</category>
      <description>记录了PHP各个版本的源码编译选项以及编译过程中出现的错误和解决办法</description>
      <content:encoded><![CDATA[<blockquote>
<p>记录了PHP各个版本的源码编译选项以及编译过程中出现的错误和解决办法</p>
</blockquote>
<p>以下为每个版本的PHP的编译错误，以及可能会出现的问题  </p>
<h2 id="php730">PHP7.3.0<a class="heading-anchor" href="#php730" aria-hidden="true" tabindex="-1">#</a></h2>
<pre class="code-block"><code class="hljs">./configure --prefix=/Users/joe/php-7.3.0 \
--with-config-file-path=/Users/joe/php-7.3.0 \
--enable-fpm \
--with-fpm-user=www \
--with-fpm-group=www \
--enable-mysqlnd \
--with-mysqli=mysqlnd \
--with-pdo-mysql=mysqlnd \
--enable-mysqlnd-compression-support \
--with-iconv-dir \
--with-freetype-dir \
--with-jpeg-dir \
--with-png-dir \
--with-zlib \
--with-libxml-dir \
--enable-xml \
--disable-rpath \
--enable-bcmath \
--enable-shmop \
--enable-sysvsem \
--enable-inline-optimization \
--with-curl \
--enable-mbregex \
--enable-mbstring \
--enable-intl \
--with-mcrypt \
--with-libmbfl \
--enable-ftp \
--with-gd \
--enable-gd-native-ttf \
--with-openssl \
--with-mhash \
--enable-pcntl \
--enable-sockets \
--with-xmlrpc \
--enable-zip \
--enable-soap \
--with-gettext \
--disable-fileinfo \
--enable-opcache \
--with-pear \
--enable-maintainer-zts \
--with-ldap=shared \
--without-gdbm \</code></pre>
<pre class="code-block"><code class="hljs">configure: error: Cannot locate header file libintl.h

在mac上运行
brew install gettext
之后编辑configure文件找到(大概在3.8w行左右)$PHP_GETTEXT /usr/local /usr
并在后面添加/usr/local/opt/gettext</code></pre>
<pre class="code-block"><code class="hljs">configure: error: Unable to detect ICU prefix or no failed. Please verify ICU install prefix and make sure icu-config works.
在mac上运行
brew install icu4c


export LDFLAGS=&quot;-L/usr/local/opt/icu4c/lib&quot;
export CPPFLAGS=&quot;-I/usr/local/opt/icu4c/include&quot;  </code></pre>
]]></content:encoded>
    </item>
    <item>
      <title>C语言中几个基本概念</title>
      <link>https://www.joe.top/2018/10/24/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/language/C/C%2B%2B/c-advanced/</link>
      <guid isPermaLink="true">https://www.joe.top/2018/10/24/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/language/C/C%2B%2B/c-advanced/</guid>
      <pubDate>Wed, 24 Oct 2018 04:08:43 GMT</pubDate>
      <category>C语言</category>
      <description>内容主要来自《C专家编程》《C陷阱和缺陷》《C和指针》</description>
      <content:encoded><![CDATA[<h2 id="分清以下几个概念">分清以下几个概念<a class="heading-anchor" href="#分清以下几个概念" aria-hidden="true" tabindex="-1">#</a></h2>
<ol>
<li>指针数组 数组指针</li>
<li>指针函数 函数指针</li>
<li>指针常量 常量指针</li>
<li>typedef</li>
<li>#define</li>
<li>可变参数</li>
<li>字符串常量</li>
</ol>
<blockquote>
<p>内容主要来自《C专家编程》《C陷阱和缺陷》《C和指针》 </p>
</blockquote>
<h2 id="常量和指针">常量和指针<a class="heading-anchor" href="#常量和指针" aria-hidden="true" tabindex="-1">#</a></h2>
<p>首先看比较容易理解的常量指针和指针常量  </p>
<pre class="code-block"><code class="hljs">int const a;
const int a;</code></pre>
<p>首先看如上声明,const 在前还是在后表示的意思都是一样的，表示a是一个常量 类型为int，a的值初始化后就不能修改了</p>
<pre class="code-block"><code class="hljs">int const *pi;
const int *pi;</code></pre>
<p>常量指针有2种表示方法意思都是一样的, 以上2个都表示常量指针,
可以修改指针的值但不能修改指针指向的值,这个在函数库的原型里可以见到比如printf和scanf,
后面的...表示可变参数后面会讲到</p>
<pre class="code-block"><code class="hljs">int	 printf(const char * __restrict, ...) 
int	 scanf(const char * __restrict, ...)</code></pre>
<p>再来看看指针常量的声明,一般只有一种表示方法
如下</p>
<pre class="code-block"><code class="hljs">int * const pi;</code></pre>
<p>表示pi 这个指针是常量，pi的值不能修改,但是pi指向的值是可以修改的  </p>
<p>如果既不想改变指针的值也不想改变指针指向的值，可以声明如下</p>
<pre class="code-block"><code class="hljs">int const * const pi;</code></pre>
<h2 id="指针和数组的爱恨情仇">指针和数组的爱恨情仇<a class="heading-anchor" href="#指针和数组的爱恨情仇" aria-hidden="true" tabindex="-1">#</a></h2>
<p>指针和数组他们之间没有任何关系  </p>
<ul>
<li>指针就是指针，指针变量在32位系统下，永远占4个byte，其值为某一个内存的地址。指针可以指向任何地方，但是不是任何地方你都能通过这个指针变量访问到。</li>
<li>数组就是数组，其大小与元素的类型和个数有关。定义数组时必须指定其元素的类型和个数。数组可以存放任何类型的数据，但不能存函数。</li>
</ul>
<h3 id="如何分辨数组指针和指针数组">如何分辨数组指针和指针数组<a class="heading-anchor" href="#如何分辨数组指针和指针数组" aria-hidden="true" tabindex="-1">#</a></h3>
<ul>
<li>指针数组：首先它是一个数组，数组的元素都是指针，数组占多少个字节由数组本身决定。它是“存储指针的数组”的简称</li>
<li>数组指针：首先它是一个指针，他指向一个数组。在32位系统下永远是占4个字节，至于它指向的数组占多少个字节，不知道。它是“指向数组的指针”的简称</li>
</ul>
<p>考虑下，下面2个哪个是指针数组，哪个是数组指针</p>
<pre class="code-block"><code class="hljs">A). int *p1[10];
B). int (*p2)[10];</code></pre>
<p>根据右左法则，先找到未定义标识符，p1右边是[]因为[]比<code>*</code>的优先级高，p1先与[]结合，所以它是一个数组。<code>int *</code>修饰的是数组的内容。<br>再看p2，因为&quot;()&quot;比&quot;[]&quot;优先级高，所以<code>(*p2)</code>表示这是一个指针，指针变量名为p2,int修饰的数组内容，即数组的每个元素。数组在这里没有名字，是匿名数组。所以p2是一个数组指针，指向一个包含10个int类型数据的数组。</p>
<h3 id="多维数组和多级指针">多维数组和多级指针<a class="heading-anchor" href="#多维数组和多级指针" aria-hidden="true" tabindex="-1">#</a></h3>
<p>考虑下面一个例子<br>&amp;p[4][2]-&amp;a[4][2]的值为多少</p>
<pre class="code-block"><code class="hljs">#include &lt;stdio.h&gt;
int main()
{
    int a[5][5];
    int (*p)[4];
    p = a;
    printf(&quot;a_ptr=%#p,p_ptr=%#p\n&quot;,&amp;a[4][2],&amp;p[4][2]);
    printf(&quot;%p,%d\n&quot;,&amp;p[4][2]-&amp;a[4][2],&amp;p[4][2]-&amp;a[4][2]);
    return 0;
}</code></pre>
<p>运行结果如下：<br><img src="/remote/image-unavailable.svg" alt="" loading="lazy" decoding="async"><br>文字解析，不如看图来的好<br><img src="/remote/image-unavailable.svg" alt="" loading="lazy" decoding="async">  </p>
<h3 id="数组参数与指针参数">数组参数与指针参数<a class="heading-anchor" href="#数组参数与指针参数" aria-hidden="true" tabindex="-1">#</a></h3>
<h4 id="一维数组参数">一维数组参数<a class="heading-anchor" href="#一维数组参数" aria-hidden="true" tabindex="-1">#</a></h4>
<h5 id="能否向函数传递一个数组">能否向函数传递一个数组？<a class="heading-anchor" href="#能否向函数传递一个数组" aria-hidden="true" tabindex="-1">#</a></h5>
<p>考虑以下程序</p>
<pre class="code-block"><code class="hljs">#include &lt;stdio.h&gt;

void fun(char a[10])
{
    char c = a[3];
}

int main(void)
{
    char b[10] = &quot;abcdefg&quot;;
    fun(b[10]);
    return 0;
}</code></pre>
<p>上面的调用，fun(b[10]);将b[10]这个数组传递到fun函数。这样显然不对。<br>b[0]代表是数组的一个元素，那么b[10]也是，只是这里越界了。但在编译阶段，编译器并不会真正计算b[10]的地址并取值，所在在编译阶段不会报错，但是编译器会给出警告：</p>
<pre class="code-block"><code class="hljs">//在我的Mac上编译出现这个
warning: incompatible integer to pointer conversion passing &#39;char&#39; to parameter of type &#39;char *&#39;; take the address with &amp; [-Wint-conversion]  
warning: array index 10 is past the end of the array (which contains 10 elements) [-Warray-bounds]
//在作者的电脑上给出的警告是这样的  
warning C4047: &#39;function&#39; : &#39;char *&#39; differs in levels of indirection from &#39;char &#39;
warning C4024: &#39;fun&#39; : different types for formal and actual parameter 1
</code></pre>
<p>虽然编译没问题，但运行肯定有问题。<br>具体分析如下:  </p>
<ol>
<li>b[10]并不存在，在编译的时候由于没有去实际地址取值，所以没有出错，但是在运行时，将计算b[10]的实际地址，并且取值。这时发生越界错误</li>
<li>编译器的警告已经告诉我们编译器需要的是一个char*类型的参数，而传递过去的是一个char类型的参数，这时候fun函数会将传入的char类型的数据当地址处理，同样会发生错误</li>
<li>对第二个错误的理解，fun函数明明传递的是一个数组啊，编译器怎么会说是char*类型呢？</li>
</ol>
<p>将fun(b[10])改为fun(b)，编译后不会出现警告了。</p>
<h5 id="无法向函数传递一个数组">无法向函数传递一个数组<a class="heading-anchor" href="#无法向函数传递一个数组" aria-hidden="true" tabindex="-1">#</a></h5>
<p>可以简单验证下，代码如下</p>
<pre class="code-block"><code class="hljs">#include &lt;stdio.h&gt;

void fun(char a[10])
{
    int i=sizeof(a);
    printf(&quot;i:%d\n&quot;,i);
    char c=a[3];
}

int main(void)
{
    char b[10] = &quot;abcdefg&quot;;
    int j = sizeof(b);
    printf(&quot;j:%d\n&quot;,j);
    fun(b);
    return 0;
}</code></pre>
<p><img src="/remote/image-unavailable.svg" alt="" loading="lazy" decoding="async"><br>如果数组b真正传递到函数内部，那么i的值应该为10，但我测试后发现i的值为8(作者测的值为4)。造成这样的原因是这样一条规则:   </p>
<ul>
<li>C语言中，当一维数组作为函数参数的时候，编译器总是把它解析成一个指向其首元素首地址的指针</li>
</ul>
<blockquote>
<p>这么做是有原因的。在 C 语言中，所有非数组形式的数据实参均以传值形式（对实参
做一份拷贝并传递给被调用的函数，函数不能修改作为实参的实际变量的值，而只能修改
传递给它的那份拷贝）调用。然而，如果要拷贝整个数组，无论在空间上还是在时间上，
其开销都是非常大的。更重要的是，在绝大部分情况下，你其实并不需要整个数组的拷贝，
你只想告诉函数在那一刻对哪个特定的数组感兴趣。这样的话，为了节省时间和空间，提
高程序运行的效率，于是就有了上述的规则。同样的，函数的返回值也不能是一个数组，
而只能是指针。这里要明确的一个概念就是：函数本身是没有类型的，只有函数的返回值
才有类型。很多书都把这点弄错了，甚至出现“XXX 类型的函数”这种说法。简直是荒唐
至极！</p>
</blockquote>
<p>所以我们写代码的时候，可以写成这样</p>
<pre class="code-block"><code class="hljs">#include &lt;stdio.h&gt;
void fun(char a[])
{
    char c=a[3];
}

int main(void)
{
    char b[10] = &quot;abcdefg&quot;;
    fun(b);
    return 0;
}</code></pre>
<p>或者也可以</p>
<pre class="code-block"><code class="hljs">void fun(char *p)
{
    char c=p[3];
}</code></pre>
<h2 id="函数和指针">函数和指针<a class="heading-anchor" href="#函数和指针" aria-hidden="true" tabindex="-1">#</a></h2>
<p>指针函数首先是一个函数，返回的类型是一个指针形式的<br>函数指针是一个指针，指向的是一个函数  </p>
<p>首先看下函数声明
任何C变量的声明都由两部分组成:</p>
<ul>
<li>类型以及一组类似表达式的声明符(declarator)。声明符从表面上看与表达式有些类似，对它求值应该返回一个声明中给定类型的结果</li>
</ul>
<p>最简单的声明符就是单个变量:</p>
<pre class="code-block"><code class="hljs">float f, g;</code></pre>
<p>这个声明的含义:</p>
<ul>
<li>当对其求值时，表达式f和g的类型为浮点数类型(float)。</li>
</ul>
<p>因为声明符与表达式的相似，所以我们也可以在声明符中任意使用括号:</p>
<pre class="code-block"><code class="hljs">float ((f));</code></pre>
<p>这个声明的含义是:</p>
<ul>
<li>当对其求值时，((f))的类型为浮点型，由此可以推知，f也是浮点类型。</li>
</ul>
<p>同样的逻辑也适合用于函数和指针类型的声明，例如:</p>
<pre class="code-block"><code class="hljs">float ff();</code></pre>
<p>这个声明的含义是:</p>
<ul>
<li>表达式ff()求值结果是一个浮点数，也就是说，ff是一个返回值为浮点数类型的函数。</li>
</ul>
<p>类似的:</p>
<pre class="code-block"><code class="hljs">float *pf;</code></pre>
<ul>
<li>这个声明的含义是*pf是一个浮点数，也就是说，pf是一个指向浮点数的指针</li>
</ul>
<p>以上形式组合起来:</p>
<pre class="code-block"><code class="hljs">float *g(), (*h)();</code></pre>
<ul>
<li>表示<code>*g()</code>与<code>(*h)()</code>是浮点表达式。因为()结合优先级高于*,<code>*g()</code>也就是*(g()): g是一个函数，该函数的返回值类型为指向浮点数的指针。同理，h是一个函数指针，h所指向函数的返回值为浮点类型。</li>
</ul>
<p>有了以上的说明 函数指针 指针函数也就能明白了  </p>
<pre class="code-block"><code class="hljs">int *func(int a, int b);</code></pre>
<p>表示 func是一个函数 其返回值是int *，所以是指针函数  </p>
<pre class="code-block"><code class="hljs">int (*func)(int a, intb);</code></pre>
<p>首先 func是一个指针 该指针指向一个函数 返回值是int 类型的，在使用的时候需要把一个函数的地址传给他  </p>
<p>函数指针的使用经常用于回调函数<br>下面就是一个简单的回调函数使用</p>
<pre class="code-block"><code class="hljs">#include &lt;stdio.h&gt;
typedef int (*callback)(char *p);

int fun1(char *str){
    printf(&quot;回调 func1: %s\n&quot;,str);
    return 0;
}

int fun2(char *str){
    printf(&quot;回调 func2: %s\n&quot;,str);
    return 0;
}

int t1(callback p_callback, char *str){
    p_callback(str);
    return 0;
}


int main() {
    char *str = &quot;hi!&quot;;
    t1(fun1, str);
    return 0;
}</code></pre>
<h2 id="typedef的使用">typedef的使用<a class="heading-anchor" href="#typedef的使用" aria-hidden="true" tabindex="-1">#</a></h2>
<p>typedef 常常用于为类型创建一个新的别名<br>比如：</p>
<pre class="code-block"><code class="hljs">typedef char Line[12];
Line text; // 这行的意思就是 char text[12];</code></pre>
<p>使用typedef 主要是简化代码比如上面的回调函数   </p>
<pre class="code-block"><code class="hljs">typedef int (*callback)(char *p)   </code></pre>
<p>使用的时候就可是使用如下方法  </p>
<pre class="code-block"><code class="hljs">callback call;  </code></pre>
<p>typedef的另外一个用处就是定义机器无关的类型，促进跨平台</p>
<h2 id="宏定义">宏定义<a class="heading-anchor" href="#宏定义" aria-hidden="true" tabindex="-1">#</a></h2>
<p>宏只是简单的字符串替换<br>通过使用 -E 参数查看宏展开后的结果  </p>
<pre class="code-block"><code class="hljs">gcc -E main.c -o main.i</code></pre>
<p>比如如下代码  </p>
<pre class="code-block"><code class="hljs">#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#define MALLOC(size,type) ((type*)malloc(sizeof(type) * (size)))
#define PRINT(FORMAT,VALUE)\
printf(&quot;The value of&quot;#VALUE&quot;is &quot; FORMAT&quot;\n&quot;,VALUE)
#define M 10
#define SQ(x) ((x)*(x))
#define DEBUG printf(&quot;FIle %s line %d :&quot;\
                    &quot;x = %d, y = %d \n&quot;,\
                    __FILE__, __LINE__,10,20)
#define MAX(a,b) ((a)&gt;(b)?(a):(b))
#define ERROR_LOG(module)   fprintf(stderr,&quot;error: &quot;module&quot;\n&quot;)
int main() {
    int n = M;
    printf(&quot;%d\n&quot;,n);
    DEBUG;
    int b = 121/SQ(2);
    printf(&quot;%d\n&quot;,b);
    int *p = MALLOC(10,int);
    int a = 0;
    int c = 1;
    int d = MAX(a++,c++);
    int x = 1;
    PRINT(&quot;%d&quot;,x+20);
    ERROR_LOG(&quot;add&quot;);
    return 0;
}

那么展开后的结果如下
省略一大段
int main() {
    int n = 10;
    printf(&quot;%d\n&quot;,n);
    printf(&quot;FIle %s line %d :&quot; &quot;x = %d, y = %d \n&quot;, &quot;main.c&quot;, 17,10,20);
    int b = 121/((2)*(2));
    printf(&quot;%d\n&quot;,b);
    int *p = ((int*)malloc(sizeof(int) * (10)));
    int a = 0;
    int c = 1;
    int d = ((a++)&gt;(c++)?(a++):(c++));
    int x = 1;
    printf(&quot;The value of&quot;&quot;x+20&quot;&quot;is &quot; &quot;%d&quot;&quot;\n&quot;,x+20);
    fprintf(__stderrp,&quot;error: &quot;&quot;add&quot;&quot;\n&quot;);
    return 0;
}</code></pre>
<h2 id="可变参数">可变参数<a class="heading-anchor" href="#可变参数" aria-hidden="true" tabindex="-1">#</a></h2>
<pre class="code-block"><code class="hljs">#include &lt;stdio.h&gt;
#include &lt;stdarg.h&gt;

int fun(int n, ...)
{
    va_list var_arg;
    int sum = 0;
    int i;

    va_start(var_arg, n); 
    for(i = 0; i &lt; n; i+=1)
    {   
        sum += va_arg(var_arg, int);
    }   

    va_end(var_arg);
    return sum;
}

int main()
{
    int n = 5;
    int sum = fun(n,1,2,3,4,5);
    printf(&quot;%d\n&quot;,sum);
    return 0;
}</code></pre>
<h2 id="字符串常量">字符串常量<a class="heading-anchor" href="#字符串常量" aria-hidden="true" tabindex="-1">#</a></h2>
<p>字符串常量用于表达式中，为指针常量<br>对于如下的程序  </p>
<pre class="code-block"><code class="hljs">#include &lt;stdio.h&gt;
int main() {
    char *str = &quot;hello&quot;+1;
    printf(&quot;%c\n&quot;,*str);
}</code></pre>
<p>运行结果是 e<br>字符串常量实际上是个指针,加1的话结果是个指针，指向第二个字符,所以结果是e</p>
]]></content:encoded>
    </item>
    <item>
      <title>PHP的基本变量</title>
      <link>https://www.joe.top/2018/09/11/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/language/PHP/php-extension-2.1/</link>
      <guid isPermaLink="true">https://www.joe.top/2018/09/11/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/language/PHP/php-extension-2.1/</guid>
      <pubDate>Tue, 11 Sep 2018 00:08:43 GMT</pubDate>
      <category>PHP扩展开发</category>
      <description>PHP在内核中是通过zval这个结构体来存储变量的，PHP的变量是弱类型的，首先来看看PHP5的zval设计</description>
      <content:encoded><![CDATA[<h2 id="zval">zval<a class="heading-anchor" href="#zval" aria-hidden="true" tabindex="-1">#</a></h2>
<p>PHP在内核中是通过zval这个结构体来存储变量的，PHP的变量是弱类型的，首先来看看PHP5的zval设计   </p>
<h3 id="php5的zval">PHP5的zval<a class="heading-anchor" href="#php5的zval" aria-hidden="true" tabindex="-1">#</a></h3>
<pre class="code-block"><code class="hljs">/*
 考虑到结构体对齐 大小为24字节
*/
struct _zval_struct{
	/*变量信息*/
	zvalue_value value;     // 16字节
	zend_uint refcount__gc;  // 4字节
	zend_uchar type;         /* 类型   1字节*/
	zend_uchar is_ref__gc;   // 1字节
};</code></pre>
<pre class="code-block"><code class="hljs">/*
 以下说的大小是在 x86-64下
 在内存对齐的情况下_zvalue_value的大小为16字节

*/
typedef union _zvalue_value {
	long lval;    // 8字节
	double dval;  // 8字节
	struct{
		char *val;
		int len;
	} str;        // 12字节
	HashTable *ht;       /*HashTable 数组  8字节*/
	zend_object_value obj;   // 12字节
	zend_ast *ast;     // 8字节
}zvalue_value;       </code></pre>
<p>PHP5的zval核心由一个zvalue_value类型的联合体和zend_uchar类型的type组成，
<code>refcount__gc</code>是用来进行垃圾回收的，<code>is_ref__gc</code> 表示是否是引用类型<br>PHP5.3 为了解决循环引用实际申请的结构体为<code>_zval_gc_info</code>  </p>
<pre class="code-block"><code class="hljs">/*
该大小为 32字节
*/
typedef struct _zval_gc_info {
	zval z;
	union {
		gc_root_buffer     *buffered;
		struct _zval_gc_info *next;
	} u;    // 4字节
}zval_gc_info;</code></pre>
<p>zval_gc_info 在内存池中分配，内存池会为每个zval_gc_info额外申请一个大小为16字节的zend_mm_block结构体，用来存放内存相关信息</p>
<pre class="code-block"><code class="hljs">typedef struct _zend_mm_block_info {
	size_t _size;
	size_t _prev;
} zend_mm_block_info;

typedef struct _zend_mm_block{
	zend_mm_block_info info;
} zend_mm_block;</code></pre>
<p>最终一个变量在PHP5中占48字节  </p>
<h3 id="php7的zval">PHP7的zval<a class="heading-anchor" href="#php7的zval" aria-hidden="true" tabindex="-1">#</a></h3>
<pre class="code-block"><code class="hljs">
typedef union _zend_value {
	zend_long         lval;             /* 整形 */
	double            dval;             /* 浮点型 */
	zend_refcounted  *counted;          /* 引用计数 */
	zend_string      *str;              /* 字符串类型 */
	zend_array       *arr;              /* 数组类型 */
	zend_object      *obj;              /* 对象类型 */
	zend_resource    *res;              /* 资源类型 *
	zend_reference   *ref;              /* 引用类型 */
	zend_ast_ref     *ast;              /* 抽象语法树 */
	zval             *zv;               /* zval类型 */
	void             *ptr;              /* 指针类型 */
	zend_class_entry *ce;               /* class类型 */
	zend_function    *func;             /* function类型 */
	struct {
		uint32_t w1;
		uint32_t w2;
	} ww;
} zend_value;


struct _zval_struct {
	zend_value        value;			/* value */
	union {
		struct {
			ZEND_ENDIAN_LOHI_4(
				zend_uchar    type,			/* zval类型 */
				zend_uchar    type_flags,
				zend_uchar    const_flags,
				zend_uchar    reserved)	    /* call info for EX(This) */
		} v;
		uint32_t type_info;
	} u1;
	union {
		uint32_t     next;                 /* hash collision chain */
		uint32_t     cache_slot;           /* literal cache slot */
		uint32_t     lineno;               /* line number (for ast nodes) */
		uint32_t     num_args;             /* arguments number for EX(This) */
		uint32_t     fe_pos;               /* foreach position */
		uint32_t     fe_iter_idx;          /* foreach iterator index */
		uint32_t     access_flags;         /* class constant access flags */
		uint32_t     property_guard;       /* single property guard */
		uint32_t     extra;                /* not further specified */
	} u2;
};</code></pre>
<p>可以看到PHP7中zval定义的好像比较复杂但都是联合体，所以实际占用大小只有16字节<br>接下类看看各个字段的含义。  </p>
<p>u1字段的含义  </p>
<ol>
<li>type: 记录变量类型  </li>
<li>type_flag: 对应变量类型的特有的标记</li>
</ol>
<pre class="code-block"><code class="hljs">IS_TYPE_CONSTANT        /* 是常量类型 */
IS_TYPE_IMMUTABLE       /* 不可变的类型 */
IS_TYPE_REFCOUNTED      /* 需要引用计数的类型 */
IS_TYPE_COLLECTABLE     /* 可能包含循环引用的类型(IS_ARRAY, IS_OBJECT) */
IS_TYPE_COPYABLE        /* 可被复制的类型 */</code></pre>
<ol start="3">
<li>const_flag: 常量类型的标记</li>
</ol>
<pre class="code-block"><code class="hljs">#define IS_CONSTANT_UNQUALIFIED    0x010
#define IS_CONSTANT_VISITED_MARK   0x020
#define IS_CONSTANT_CLASS          0x080
#define IS_CONSTANT_IN_NAMESPACE   0x100</code></pre>
<ol start="4">
<li>reserved: 保留字段</li>
</ol>
<p>u2字段的含义，主要是辅助字段  </p>
<ol>
<li>next: 用于哈希冲突中记录下一个元素的位置</li>
<li>cache_slot: 运行时缓存</li>
<li>lineno:  文件执行的行号，应用在AST节点上</li>
<li>num_args: 函数调用时传入参数的个数</li>
<li>fe_pos:  遍历数组时的当前位置</li>
<li>fe_iter_idx: 与fe_pos类似，针对对象使用</li>
<li>access_flags: 对象类的访问标志, public protected private</li>
<li>property_guard: 防止类中魔术方法的循环调用</li>
</ol>
<h2 id="php7-变量类型">PHP7 变量类型<a class="heading-anchor" href="#php7-变量类型" aria-hidden="true" tabindex="-1">#</a></h2>
<p>PHP7 通过定义20种宏，来u1.v.type字段</p>
<pre class="code-block"><code class="hljs">/* regular data types */
#define IS_UNDEF                    0
#define IS_NULL                     1
#define IS_FALSE                    2
#define IS_TRUE                     3
#define IS_LONG                     4
#define IS_DOUBLE                   5
#define IS_STRING                   6
#define IS_ARRAY                    7
#define IS_OBJECT                   8
#define IS_RESOURCE                 9
#define IS_REFERENCE                10

/* constant expressions */
#define IS_CONSTANT                 11
#define IS_CONSTANT_AST             12

/* fake types */
#define _IS_BOOL                    13
#define IS_CALLABLE                 14
#define IS_ITERABLE                 19
#define IS_VOID                     18

/* internal types */
#define IS_INDIRECT                 15  /* 间接类型 */
#define IS_PTR                      17  /* 指针类型 */
#define _IS_ERROR                   20  /* 错误类型 *
</code></pre>
<h3 id="整形和浮点型">整形和浮点型<a class="heading-anchor" href="#整形和浮点型" aria-hidden="true" tabindex="-1">#</a></h3>
<p>整形和浮点型在zval是直接存储的，实现的比较简单</p>
<h3 id="字符串类型">字符串类型<a class="heading-anchor" href="#字符串类型" aria-hidden="true" tabindex="-1">#</a></h3>
<p>字符串类型的结构如下共占32字节  </p>
<pre class="code-block"><code class="hljs">struct _zend_string{
	zend_refcounted_h gc;     // 用于引用计数 8字节
	zend_ulong        h;      // 哈希值 8字节
	size_t            len;    // 字符串长度 8字节
	char              val[1]; // 柔性数组，1字节 字符串的值存储位置
};

typedef struct _zend_refcounted_h {
    uint32_t        refcounted; 
    union {
        struct{
            ZEND_ENDIAN_LOHI_3 (
                zend_uchar  type,
                zend_uchar  flags,
                uint16_t    gc_info
            )
    } v;
    uint32_t type_info;
    } u;
}zend_refcounted_h;</code></pre>
<h4 id="字段说明">字段说明<a class="heading-anchor" href="#字段说明" aria-hidden="true" tabindex="-1">#</a></h4>
<p>gc<br>gc字段主要存放引用计数等信息。flags存放类别信息，type存放变量类别  </p>
<p>h<br>h字段的作用是缓存字符串的哈希值  </p>
<p>val<br>val字段存储字符串值  </p>
<p>len<br>字符串长度  </p>
<p>PHP中的字符串是二进制安全的，在遇到\0的时候并不认为结束了，读取的时候以len的值为准  </p>
<h4 id="几个常用字符串的操作">几个常用字符串的操作<a class="heading-anchor" href="#几个常用字符串的操作" aria-hidden="true" tabindex="-1">#</a></h4>
<p>echo 输出一个或多个字符串<br>在Zend/zend_vm_execute.h下 </p>
<pre class="code-block"><code class="hljs">static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ECHO_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    USE_OPLINE

    zval *z;

    SAVE_OPLINE();
    z = EX_CONSTANT(opline-&gt;op1); //获取变量

    if (Z_TYPE_P(z) == IS_STRING) { // 判断是否是字符串 是的或就直接输出
        zend_string *str = Z_STR_P(z); 

        if (ZSTR_LEN(str) != 0) {
            zend_write(ZSTR_VAL(str), ZSTR_LEN(str));  //将字符串写入到标准输出
        }
    } else {
        zend_string *str = _zval_get_string_func(z);  //强制转换

        if (ZSTR_LEN(str) != 0) {
            zend_write(ZSTR_VAL(str), ZSTR_LEN(str));   //写入到标准输出
        } else if (IS_CONST == IS_CV &amp;&amp; UNEXPECTED(Z_TYPE_P(z) == IS_UNDEF)) {
            GET_OP1_UNDEF_CV(z, BP_VAR_R);
        }
        zend_string_release(str);
    }

    ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
</code></pre>
]]></content:encoded>
    </item>
    <item>
      <title>认识PHP的一些基本原理</title>
      <link>https://www.joe.top/2018/09/01/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/language/PHP/php-extension-1-1/</link>
      <guid isPermaLink="true">https://www.joe.top/2018/09/01/%E8%AE%A1%E7%AE%97%E6%9C%BA%E4%BD%93%E7%B3%BB/language/PHP/php-extension-1-1/</guid>
      <pubDate>Sat, 01 Sep 2018 10:08:43 GMT</pubDate>
      <category>PHP扩展开发</category>
      <description>PHP是一门解释型语言,执行过程如下： 第一步：源码通过词法分析得到Token。Token是PHP代码被切割成的有意义的标识。在zend_language_parse.h文件中做了定义，比如echo对应的Token是T_ECHO，值为328 第二步：语法分析将Token和符合文法规则的代码生成抽象语法树。在PHP7中，AST是新特征…</description>
      <content:encoded><![CDATA[<h2 id="从php的执行原理开始">从PHP的执行原理开始<a class="heading-anchor" href="#从php的执行原理开始" aria-hidden="true" tabindex="-1">#</a></h2>
<p>PHP是一门解释型语言,执行过程如下：<br>第一步：源码通过词法分析得到Token。Token是PHP代码被切割成的有意义的标识。在zend_language_parse.h文件中做了定义，比如echo对应的Token是T_ECHO，值为328<br>第二步：语法分析将Token和符合文法规则的代码生成抽象语法树。在PHP7中，AST是新特征<br>第三步：将AST生成对应的opcode，并被虚拟机执行。opcode是PHP定义的一组指令标识，对应相应的handler。opcodes是opcode的集合形式，是PHP执行过程的中间代码，类似Java的字节码。PHP优化中有一个常见的“开启opcache”，指的就是opcodes的缓存  </p>
<h2 id="php7内核架构">PHP7内核架构<a class="heading-anchor" href="#php7内核架构" aria-hidden="true" tabindex="-1">#</a></h2>
<p>主要来自于《PHP7底层设计与源码实现》</p>
<p><img src="/remote/image-unavailable.svg" alt="" loading="lazy" decoding="async"></p>
<ul>
<li><p>Zend引擎：词法/语法分析、AST编译和opcodes的执行均在Zend引擎中实现。引擎为PHP提供了基础服务，PHP的可靠性和高性能都依赖引擎的基础支撑。  </p>
</li>
<li><p>PHP层：来自外部的交互通过PHP层来处理。  </p>
</li>
<li><p>SAPI：SAPI(Server API),包含了常见的cli SAPI，cgi SAPI和fmpSAPI。SAPI提供了一个和外部通信的接口。  </p>
</li>
<li><p>扩展部分：Zend引擎提供了核心能力和接口规范。在此基础上进行扩展开发。</p>
</li>
</ul>
<p>以下为几种常见的SAPI  </p>
<ul>
<li>cli： PHP的命令行交互接口</li>
<li>fpm-fcgi: fmp全称为FastCGI Process Manager，PHP官方提供的FastCGI进程管理器。举例来说，当有http请求发送到nginx服务器，nginx按照FastCGI协议把请求交给php-fpm进程处理  </li>
<li>apache2handler: Apache扩展，编译后生产动态链接库，配置到Apache下，当有http请求到Apache时，根据配置会调用此动态链接库，执行PHP代码，完成与PHP的交互</li>
</ul>
<h2 id="php的生命周期">PHP的生命周期<a class="heading-anchor" href="#php的生命周期" aria-hidden="true" tabindex="-1">#</a></h2>
<p>在cli模式下，PHP的执行过程主要分为5大阶段：  </p>
<ol>
<li>模块初始化阶段  php_module_startup</li>
<li>请求初始化阶段  php_request_startup</li>
<li>执行阶段  php_execute_script</li>
<li>请求关闭阶段  php_request_shutdown</li>
<li>模块关闭阶段  php_module_shutdown</li>
</ol>
<p>web模式没有1或5</p>
]]></content:encoded>
    </item>
    <item>
      <title>记一次性能优化点</title>
      <link>https://www.joe.top/2018/08/27/mysql-optimize-1/</link>
      <guid isPermaLink="true">https://www.joe.top/2018/08/27/mysql-optimize-1/</guid>
      <pubDate>Mon, 27 Aug 2018 11:39:08 GMT</pubDate>
      <category>MYSQL优化</category>
      <description>最近学习了个小项目, 打算把它作为我们队的论坛系统，但部署到服务器上时发现 打开一个页面要等好久。在此记录下如何解决的，最后的处理结果也是非常简单。</description>
      <content:encoded><![CDATA[<blockquote>
<p>最近学习了个小项目, 打算把它作为我们队的论坛系统，但部署到服务器上时发现 打开一个页面要等好久。在此记录下如何解决的，最后的处理结果也是非常简单。  </p>
</blockquote>
<p><a href="https://bbs.joe.top" target="_blank" rel="noopener noreferrer">线上地址</a>  </p>
<p><a href="https://github.com/yinrenxin/laravel-larabbs" target="_blank" rel="noopener noreferrer">项目地址</a>  </p>
<h2 id="部署">部署<a class="heading-anchor" href="#部署" aria-hidden="true" tabindex="-1">#</a></h2>
<p>这个项目是用Laravel框架，本地开发使用Homestead，开发起来非常方便。线上部署使用了<a href="http://laradock.io/introduction/" target="_blank" rel="noopener noreferrer">Laradock</a>比较好用的一个Docker化的一个PHP运行环境  </p>
<p>因为某个还未解决的问题，没有使用Laradock里的MySQL容器，我就想着使用另外一台服务器运行MySQL，当整个部署完成后，MySQL远程连接开启。启动项目：）   </p>
<h2 id="问题展现">问题展现<a class="heading-anchor" href="#问题展现" aria-hidden="true" tabindex="-1">#</a></h2>
<p>。。。。怎么有点慢，打开 Laravel Debugbar，发现页面加载耗时10S(......)，再次查看，发现每次请求,第一条sql耗时将近10S,后面的sql语句还算正常。  </p>
<h2 id="问题解决">问题解决<a class="heading-anchor" href="#问题解决" aria-hidden="true" tabindex="-1">#</a></h2>
<p>通过查询相关资料，原来是MySQL还要进行DNS解析，这就好办了禁用DNS解析就行通过在mysql的配置文件里添加  </p>
<pre class="code-block"><code class="hljs">[mysqld]
skip-name-resolve </code></pre>
<p>如果禁用DNS查询MySQL怎么知道远程连接的客户端在哪个位置呢，这个也好解决，只要在配置MySQL远程连接时指定一个固定的IP就行(必须使用IP地址)。  </p>
<p>之后再打开应用就快很多了。</p>
<blockquote>
<p>When a new thread connects to mysqld, mysqld will spawn a new thread to handle the request. This thread will first check if the hostname is in the hostname cache. If not the thread will call gethostbyaddr_r() and gethostbyname_r() to resolve the hostname.</p>
</blockquote>
<blockquote>
<p>If the operating system doesn&#39;t support the above thread-safe calls, the thread will lock a mutex and call gethostbyaddr() and gethostbyname() instead. Note that in this case no other thread can resolve other hostnames that is not in the hostname cache until the first thread is ready.</p>
</blockquote>
<blockquote>
<p>You can disable DNS host lookup by starting mysqld with --skip-name-resolve. In this case you can however only use IP names in the MySQL privilege tables.</p>
</blockquote>
<blockquote>
<p>If you have a very slow DNS and many hosts, you can get more performance by either disabling DNS lookop with --skip-name-resolve or by increasing the HOST_CACHE_SIZE define (default: 128) and recompile mysqld.</p>
</blockquote>
<blockquote>
<p>You can disable the hostname cache with --skip-host-cache. You can clear the hostname cache with FLUSH HOSTS or mysqladmin flush-hosts.</p>
</blockquote>
<blockquote>
<p>If you don&#39;t want to allow connections over TCP/IP, you can do this by starting mysqld with --skip-networking.</p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>网络IPC: 套接字(十六)</title>
      <link>https://www.joe.top/2018/08/03/%E8%AF%BB%E4%B9%A6/UNIX%E7%8E%AF%E5%A2%83%E9%AB%98%E7%BA%A7%E7%BC%96%E7%A8%8B/network-IPC/</link>
      <guid isPermaLink="true">https://www.joe.top/2018/08/03/%E8%AF%BB%E4%B9%A6/UNIX%E7%8E%AF%E5%A2%83%E9%AB%98%E7%BA%A7%E7%BC%96%E7%A8%8B/network-IPC/</guid>
      <pubDate>Fri, 03 Aug 2018 06:03:47 GMT</pubDate>
      <category>UNIX环境高级编程</category>
      <description>UNIX系统提供的经典进程间通信机制(IPC): 管道、FIFO、消息队列、信号量以及共享存储。这些机制允许在同一台计算机上运行的进程可以相互通信。本章内容主要是研究在不同计算机上的进程相互通信的机制: 网络进程间通信(network IPC)。</description>
      <content:encoded><![CDATA[<h2 id="引言">引言<a class="heading-anchor" href="#引言" aria-hidden="true" tabindex="-1">#</a></h2>
<p>UNIX系统提供的经典进程间通信机制(IPC): 管道、FIFO、消息队列、信号量以及共享存储。这些机制允许在同一台计算机上运行的进程可以相互通信。本章内容主要是研究在不同计算机上的进程相互通信的机制: 网络进程间通信(network IPC)。  </p>
<h2 id="套接字描述符">套接字描述符<a class="heading-anchor" href="#套接字描述符" aria-hidden="true" tabindex="-1">#</a></h2>
<p>套接字描述符在UNIX系统上被当做一种文件描述符，UNIX系统一切皆文件。<br>调用socket函数，创建一个套接字  </p>
<pre class="code-block"><code class="hljs">#include &lt;sys/socket.h&gt;
int socket(int damain, int type, int protocol);
//成功：返回描述符，出错：返回-1</code></pre>
<p>参数domain确定通信特征，包括地址格式  </p>
<table>
<thead>
<tr>
<th align="left">域</th>
<th align="left">描述</th>
</tr>
</thead>
<tbody><tr>
<td align="left">AF_INET</td>
<td align="left">IPv4</td>
</tr>
<tr>
<td align="left">AF_INET6</td>
<td align="left">IPv6</td>
</tr>
<tr>
<td align="left">AF_UNIX</td>
<td align="left">UNIX域</td>
</tr>
<tr>
<td align="left">AF_UPSPEC</td>
<td align="left">未指定</td>
</tr>
</tbody></table>
<p>参数type确定套接字类型，进一步确定通信特征   </p>
<table>
<thead>
<tr>
<th align="left">类型</th>
<th align="left">描述</th>
</tr>
</thead>
<tbody><tr>
<td align="left">SOCK_DGRAM</td>
<td align="left">固定长度、无连接、不可靠的报文传递</td>
</tr>
<tr>
<td align="left">SOCK_RAW</td>
<td align="left">IP协议的数据报接口</td>
</tr>
<tr>
<td align="left">SOCK_SEQPACKET</td>
<td align="left">固定长度、有序的、可靠的、面向连接的报文传递</td>
</tr>
<tr>
<td align="left">SOCK_STREAM</td>
<td align="left">有序的、可靠的、双向的、面向连接的字节流</td>
</tr>
</tbody></table>
<p>参数protocal通常是0、表示为给定的域和套接字类型选择默认协议  </p>
<p>数据报(SOCK_DGRAM)接口，类似于UDP通信，两个对等进程之间通信时不需要逻辑连接。只需要向对等进程所使用的套接字送出一个报文。<br>字节流(SOCK_STREAM),类似于TCP，在交换数据前，需要建立一个逻辑连接。  </p>
<p>套接字通信时双向的。可以采用shutdown函数来禁止一个套接字的I/O。  </p>
<pre class="code-block"><code class="hljs">#include &lt;sys/socket.h&gt;
int shutdown(int sockfd, int how);
//成功：返回0 失败：返回-1</code></pre>
<p>how参数  </p>
<ul>
<li>SHUT_RE(关闭读端)，无法从套接字读取数据</li>
<li>SHUT_WR(关闭写端)，无法使用套接字发送数据</li>
<li>SHUT_RDWR，无法读，也无法读</li>
</ul>
<p>有两个函数用于点分十进制和二进制地址格式之前的相互转换  </p>
<pre class="code-block"><code class="hljs">#include &lt;arpa/inet.h&gt;
const char *inet_ntop(int domain, const void *restrict addr,
                    char *restrict str, socklen_t size);
                    //成功：返回地址字符串指针，出错：返回NULL

int inet_pton(int domain, const char *restrict str,
                void *restrict addr);
                    //成功：返回1，格式无效返回0，失败：返回-1</code></pre>
<h2 id="将套接字与地址关联">将套接字与地址关联<a class="heading-anchor" href="#将套接字与地址关联" aria-hidden="true" tabindex="-1">#</a></h2>
<p>使用bind函数来关联地址和套接字  </p>
<pre class="code-block"><code class="hljs">#include &lt;sys/socket.h&gt;
int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
//成功：返回0，失败：返回-1</code></pre>
<h2 id="建立连接">建立连接<a class="heading-anchor" href="#建立连接" aria-hidden="true" tabindex="-1">#</a></h2>
<p>使用connect函数来建立连接  </p>
<pre class="code-block"><code class="hljs">#include &lt;sys/socket.h&gt;
int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
//成功：返回0，失败：返回-1</code></pre>
<p>在connect中指定的地址是我们想与之通信的服务器地址。如果sockfd没有绑定到一个地址，connect会给调用者绑定一个默认地址。  </p>
<p>服务器调用listen函数宣告它愿意接受连接请求  </p>
<pre class="code-block"><code class="hljs">#include &lt;sys/socket.h&gt;
int listen(int sockfd, int backlog);
//成功：返回0，失败：返回-1</code></pre>
<p>参数backlog提供了一个提示，提示系统该进程所要入队的未完成连接请求数量。实际值由系统决定<br>一旦队列满，系统就会拒绝多余的连接请求，所以backlog的值应该基于服务器期望负载和处理量来选择，其中处理量是指接受连接请求与启动服务的数量。<br>一旦服务器调用了listen，所用的套接字就能接收连接请求。使用accept函数来获得连接请求并建立连接。  </p>
<pre class="code-block"><code class="hljs">#include &lt;sys/socket.h&gt;
int accept(int sockfd, struct sockaddr *restrict addr,
            socklen_t *restrict len);
            //成功：返回文件描述符，出错：返回-1</code></pre>
<p>函数accept返回的是套接字描述符，该描述符连接到调用connect的客户端。这个新的套接字描述符和原始套接字(sockfd)具有相同的套接字类型和地址族。传给accept的原始套接字没有关联到这个连接，而是继续保持可用状态并接收其他连接请求。<br>如果不关心客户端标识，可以将参赛addr和len设为NULL。否则，在调用accept之前，将addr参数设为足够大的缓冲区来存放地址，并且将len指向的整数设为这个缓冲区的字节大小。返回时，accept会在缓冲区填充客户端的地址，并且更新指向len的整数来反映该地址的大小。<br>如果没有连接请求等待，accept会阻塞直到一个请求到来。如果sockfd处于非阻塞模式，accept会返回-1，并将errno设置为EAGAIN或EWOULDBLOCK。<br>如果服务器调用accept，并且当前没有连接请求，服务器会阻塞直到一个请求到来。另外，服务器可以使用poll或select来等待一个请求的到来。在这种情况下，一个带有等待连接请求的套接字会以可读的方式出现。  </p>
<h2 id="数据传输">数据传输<a class="heading-anchor" href="#数据传输" aria-hidden="true" tabindex="-1">#</a></h2>
<pre class="code-block"><code class="hljs">#include &lt;sys/socket.h&gt;
ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
//成功: 返回发送的字节数，失败：返回-1</code></pre>
<p>对于支持报文边界的协议，如果尝试发送单个报文的长度超过协议锁支持的最大长度，那么send会失败，并将errno设为EMSGSIZE。对于字节流协议，send会阻塞直到整个数据传输完成。函数sendto和send类似。区别在于sendto可以在无连接的套接字上指定一个目标地址。  </p>
<pre class="code-block"><code class="hljs">#include &lt;sys/socket.h&gt;
ssize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags, const struct sockaddr * destaddr, socklen_t destlen);
//成功：返回发送的字节数，失败：返回-1</code></pre>
<p>使用recv函数接收数据   </p>
<pre class="code-block"><code class="hljs">#include &lt;sys/socket.h&gt;
ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
//返回数据的字节长度，无数据或等放已经按序结束，返回0，出错：返回-1</code></pre>
<h2 id="非阻塞和异步io">非阻塞和异步I/O<a class="heading-anchor" href="#非阻塞和异步io" aria-hidden="true" tabindex="-1">#</a></h2>
<p>通常，recv函数没有数据可用时会阻塞等待。同样的，当套接字输出队列没有足够空间来发送消息时，send函数会阻塞。在套接字非阻塞模式下，行为会改变。在这种情况下，这些函数不会阻塞而是会失败，将errno设置为EWOULDBLOCK或者EAGAIN。当这种情况发生时，可以用poll或select来判断能否接收或者传输数据。  </p>
<p>Single UNIX Specification 包含通用异步I/O机制的支持。套接字机制有自己的处理异步I/O的方式。<br>在基于套接字的异步I/O中，当从套接字中读取数据时，或者当套接字写队列中空间变得可用时，可以安排要发送的信号SIGIO。<br>启用异步I/O的步骤  </p>
<ol>
<li>建立套接字所有权，这样信号可以被传递到合适的进程。  <ul>
<li>在fcntl中使用F_SETOWN命令</li>
<li>在ioctl中使用FIOSETOWN命令</li>
<li>在ioctl中使用SIOCSGRP命令</li>
</ul>
</li>
<li>通知套接字当I/O操作不会阻塞时发信号。  <ul>
<li>在fcntl中使用F_SETFL命令并且启用文件标志<code>O_ASYNC</code>。</li>
<li>在ioctl中使用FIOASYNC命令。</li>
</ul>
</li>
</ol>
<p>但没有得到普遍支持</p>
]]></content:encoded>
    </item>
    <item>
      <title>守护进程(十三)</title>
      <link>https://www.joe.top/2018/07/10/%E8%AF%BB%E4%B9%A6/UNIX%E7%8E%AF%E5%A2%83%E9%AB%98%E7%BA%A7%E7%BC%96%E7%A8%8B/daemons/</link>
      <guid isPermaLink="true">https://www.joe.top/2018/07/10/%E8%AF%BB%E4%B9%A6/UNIX%E7%8E%AF%E5%A2%83%E9%AB%98%E7%BA%A7%E7%BC%96%E7%A8%8B/daemons/</guid>
      <pubDate>Mon, 09 Jul 2018 16:17:42 GMT</pubDate>
      <category>UNIX环境高级编程</category>
      <description>守护进程(daemon)是在系统引导装入时启动，仅在系统关闭时才终止。守护进程是在后台运行的。</description>
      <content:encoded><![CDATA[<h2 id="前言">前言<a class="heading-anchor" href="#前言" aria-hidden="true" tabindex="-1">#</a></h2>
<p>守护进程(daemon)是在系统引导装入时启动，仅在系统关闭时才终止。守护进程是在后台运行的。  </p>
<h2 id="守护进程特征">守护进程特征<a class="heading-anchor" href="#守护进程特征" aria-hidden="true" tabindex="-1">#</a></h2>
<p>在命令行下输入 ps -axj(BSD系统)  查看系统中各个进程的状态。  </p>
<p><img src="/remote/image-unavailable.svg" alt="" loading="lazy" decoding="async">  </p>
<p>父ID为0的个进程通常是内核进程，内核进程存在于系统的整个生命周期，他们以超级用户特权运行，无控制终端，无命令行。  </p>
<p>在ps的输出中，内核守护进程的名字出现在方括号中。  </p>
<p><img src="/remote/image-unavailable.svg" alt="" loading="lazy" decoding="async">  </p>
<p>可以看到Linux使用了kthreadd的特殊内核进程来创建其他的内核进程，所以其他内核进程的父进程为2 也就是kthreadd进程。对于需要在进程上下文执行工作但却不被用户层进程上下文调用的每一个内核组件，通常有它自己的内核守护进程。如在Linux中。  </p>
<ul>
<li>kswapd 内存换页守护进程。它支持虚拟内存子系统在经过一段时间后将脏页面慢慢写会磁盘。</li>
<li>flush  守护进程在可用内存达到设置的最小阀值时将脏页面冲洗至磁盘。同时它还定期将脏页面冲洗会磁盘防止数据丢失。</li>
<li>sync_supers 定期将文件系统元数据冲洗至磁盘。</li>
<li>jbd 帮助实现了ext4文件系统中的日志功能。</li>
</ul>
<hr>
<ul>
<li>进程1通常是init进程，它是系统守护进程，主要负责启动各运行层次特定的系统服务。  </li>
<li>rpcbind守护进程提供将RPC程序号映射为网络端口号的服务。  </li>
<li>rsyslogd守护进程可以被由管理员启用的将系统日志记录的任何程序使用  </li>
<li>inetd守护进程。侦听系统网络接口，以便取得来自网络的对各种网络服务进程的请求  </li>
<li>nfsd、nfsiod、lockd、rpciod、rpc.idmapd、rpc.statd、rpc.mountd提供了网络文件系统的支持。</li>
</ul>
<h2 id="编写规则">编写规则<a class="heading-anchor" href="#编写规则" aria-hidden="true" tabindex="-1">#</a></h2>
<p>编写一个守护进程需要遵循一些规则，防止产生不必要的交互。  </p>
<ol>
<li>调用umask将文件模式创建屏蔽字设置为一个已知值通常为0</li>
<li>调用fork，然后父进程exit</li>
<li>调用setsid创建一个新会话</li>
<li>当前工作目录更改为根目录</li>
<li>关闭不需要的文件描述符</li>
<li>某些守护进程打开/dev/null 使其具有文件描述符0、1、2</li>
</ol>
<h2 id="出错记录">出错记录<a class="heading-anchor" href="#出错记录" aria-hidden="true" tabindex="-1">#</a></h2>
<p>守护进程出错处理，大多数守护进程都使用 BSD的syslog设施。  </p>
<p><img src="/remote/image-unavailable.svg" alt="" loading="lazy" decoding="async">  </p>
<p> 有3种产生日志消息的方式  </p>
<ol>
<li>内核例程可以调用log函数。</li>
<li>大多数用户进程(守护进程)调用syslog(3)函数来产生日志消息，</li>
<li>无论一个用户进程是在此主机上，还是在通过TPC/IP网络连接到此主机的其他主机上，都可以将日志消息发送到UDP的514端口。</li>
</ol>
<p> 通常，syslogd守护进程读取所有3种格式的日志消息。启动时读取一个配置文件，通常是/etc/syslogd.conf该文件决定了不同种类的消息应送到何处。  </p>
<p> syslog设施的接口是syslog函数  </p>
<pre class="code-block"><code class="hljs">#include &lt;syslog.h&gt;
void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void);
int setlogmask(int maskpri);</code></pre>
<h2 id="单实例守护进程">单实例守护进程<a class="heading-anchor" href="#单实例守护进程" aria-hidden="true" tabindex="-1">#</a></h2>
<p> 某些守护进程会实现为，在任意时刻只运行该守护进程的一个副本。对于cron守护进程而言，如果有多个实例运行，那么每个副本都可能试图开始某个预定的操作，会造成重复执行，这可能会出错。  </p>
<p> 如果守护进程需要访问一个设备，而该设备驱动程序有时会阻止想要多次打开/dev 目录下相应设备节点的尝试。这就限制了在一个时刻只能运行守护进程的一个副本。但如果没有这种设备可供使用，那么就需要自行处理。   </p>
<p> 文件和记录锁机制为一种方法提供了基础，该方法保证一个守护进程只有一个副本在运行。如果每一个守护进程创建一个固定名字的文件，并在该文件的整体上加一把写锁，那么只允许创建一把这样的锁。在此之后创建写锁的尝试都会失败，这向后续进程副本指明已有一个副本正在运行。  </p>
<h2 id="守护进程惯例">守护进程惯例<a class="heading-anchor" href="#守护进程惯例" aria-hidden="true" tabindex="-1">#</a></h2>
<p> 在UNIX系统中，守护进程遵循以下通用惯例  </p>
<ul>
<li>若守护进程使用锁文件，那么该文件通常存储在/var/run目录中。</li>
<li>若守护进程支持配置选项，那么配置文件一般位于/etc下，如/etc/syslog.conf  </li>
<li>守护进程可以由命令行启动，但通常它们是由系统启动脚本之一启动的<code>/etc/rc*或/etc/init.d/*</code>。</li>
<li>配置文件一般只在启动时读取，如果更改配置文件，就要重启进程。</li>
</ul>
]]></content:encoded>
    </item>
  </channel>
</rss>
